KEYCLOAK-5354 (#4440)

This commit is contained in:
Stian Thorgersen authored and GitHub committed 2017-08-30 07:59:47 +02:00
1 parent 801205a5bc
commit 433a8dd2e3
20 files changed
+307 -57

No files matched your search

@@ -0,0 +1,424 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite;
import io.undertow.Undertow;
import io.undertow.Undertow.Builder;
import io.undertow.servlet.Servlets;
import io.undertow.servlet.api.DefaultServletConfig;
import io.undertow.servlet.api.DeploymentInfo;
import io.undertow.servlet.api.FilterInfo;
import io.undertow.servlet.api.ServletInfo;
import org.jboss.logging.Logger;
import org.jboss.resteasy.plugins.server.servlet.HttpServlet30Dispatcher;
import org.jboss.resteasy.plugins.server.undertow.UndertowJaxrsServer;
import org.jboss.resteasy.spi.ResteasyDeployment;
import org.keycloak.common.Version;
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RealmModel;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.services.filters.KeycloakSessionServletFilter;
import org.keycloak.services.managers.ApplianceBootstrap;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.resources.KeycloakApplication;
import org.keycloak.testsuite.util.cli.TestsuiteCLI;
import org.keycloak.util.JsonSerialization;
import javax.servlet.DispatcherType;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import javax.net.ssl.SSLContext;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class KeycloakServer {
private static final Logger log = Logger.getLogger(KeycloakServer.class);
private boolean sysout = false;
public static class KeycloakServerConfig {
private String host = "localhost";
private int port = 8081;
private int portHttps = -1;
private int workerThreads = Math.max(Runtime.getRuntime().availableProcessors(), 2) * 8;
private String resourcesHome;
public String getHost() {
return host;
}
public int getPort() {
return port;
}
public int getPortHttps() {
return portHttps;
}
public String getResourcesHome() {
return resourcesHome;
}
public void setHost(String host) {
this.host = host;
}
public void setPort(int port) {
this.port = port;
}
public void setPortHttps(int portHttps) {
this.portHttps = portHttps;
}
public void setResourcesHome(String resourcesHome) {
this.resourcesHome = resourcesHome;
}
public int getWorkerThreads() {
return workerThreads;
}
public void setWorkerThreads(int workerThreads) {
this.workerThreads = workerThreads;
}
}
public static <T> T loadJson(InputStream is, Class<T> type) {
try {
return JsonSerialization.readValue(is, type);
} catch (IOException e) {
throw new RuntimeException("Failed to parse json", e);
}
}
public static void main(String[] args) throws Throwable {
if (!System.getenv().containsKey("MAVEN_CMD_LINE_ARGS")) {
Version.BUILD_TIME = new SimpleDateFormat("yyyy-MM-dd HH:mm").format(new Date());
}
bootstrapKeycloakServer(args);
}
public static KeycloakServer bootstrapKeycloakServer(String[] args) throws Throwable {
File f = new File(System.getProperty("user.home"), ".keycloak-server.properties");
if (f.isFile()) {
Properties p = new Properties();
p.load(new FileInputStream(f));
System.getProperties().putAll(p);
}
KeycloakServerConfig config = new KeycloakServerConfig();
for (int i = 0; i < args.length; i++) {
if (args[i].equals("-b")) {
config.setHost(args[++i]);
}
if (args[i].equals("-p")) {
config.setPort(Integer.valueOf(args[++i]));
}
}
if (System.getProperty("keycloak.port") != null) {
config.setPort(Integer.valueOf(System.getProperty("keycloak.port")));
}
if (System.getProperty("keycloak.port.https") != null) {
config.setPortHttps(Integer.valueOf(System.getProperty("keycloak.port.https")));
}
if (System.getProperty("keycloak.bind.address") != null) {
config.setHost(System.getProperty("keycloak.bind.address"));
}
if (System.getenv("KEYCLOAK_DEV_PORT") != null) {
config.setPort(Integer.valueOf(System.getenv("KEYCLOAK_DEV_PORT")));
}
if (System.getProperties().containsKey("resources")) {
String resources = System.getProperty("resources");
if (resources == null || resources.equals("") || resources.equals("true")) {
if (System.getProperties().containsKey("maven.home")) {
resources = System.getProperty("user.dir").replaceFirst("testsuite.utils.*", "");
} else {
for (String c : System.getProperty("java.class.path").split(File.pathSeparator)) {
if (c.contains(File.separator + "testsuite" + File.separator + "utils")) {
resources = c.replaceFirst("testsuite.utils.*", "");
}
}
}
}
File dir = new File(resources).getAbsoluteFile();
if (!dir.isDirectory()) {
throw new RuntimeException("Invalid base resources directory");
}
if (!new File(dir, "themes").isDirectory()) {
throw new RuntimeException("Invalid resources forms directory");
}
if (!System.getProperties().containsKey("keycloak.theme.dir")) {
System.setProperty("keycloak.theme.dir", file(dir.getAbsolutePath(), "themes", "src", "main", "resources", "theme").getAbsolutePath());
} else {
String foo = System.getProperty("keycloak.theme.dir");
System.out.println(foo);
}
if (!System.getProperties().containsKey("keycloak.theme.cacheTemplates")) {
System.setProperty("keycloak.theme.cacheTemplates", "false");
}
if (!System.getProperties().containsKey("keycloak.theme.cacheThemes")) {
System.setProperty("keycloak.theme.cacheThemes", "false");
}
if (!System.getProperties().containsKey("keycloak.theme.staticMaxAge")) {
System.setProperty("keycloak.theme.staticMaxAge", "-1");
}
config.setResourcesHome(dir.getAbsolutePath());
}
if (System.getProperties().containsKey("undertowWorkerThreads")) {
int undertowWorkerThreads = Integer.parseInt(System.getProperty("undertowWorkerThreads"));
config.setWorkerThreads(undertowWorkerThreads);
}
detectNodeName(config);
final KeycloakServer keycloak = new KeycloakServer(config);
keycloak.sysout = true;
keycloak.start();
for (int i = 0; i < args.length; i++) {
if (args[i].equals("-import")) {
keycloak.importRealm(new FileInputStream(args[++i]));
}
}
if (System.getProperties().containsKey("import")) {
keycloak.importRealm(new FileInputStream(System.getProperty("import")));
}
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
keycloak.stop();
}
});
if (System.getProperties().containsKey("startTestsuiteCLI")) {
new TestsuiteCLI(keycloak).start();
}
return keycloak;
}
private KeycloakServerConfig config;
private KeycloakSessionFactory sessionFactory;
private UndertowJaxrsServer server;
public KeycloakServer() {
this(new KeycloakServerConfig());
}
public KeycloakServer(KeycloakServerConfig config) {
this.config = config;
}
public KeycloakSessionFactory getSessionFactory() {
return sessionFactory;
}
public UndertowJaxrsServer getServer() {
return server;
}
public KeycloakServerConfig getConfig() {
return config;
}
public void importRealm(InputStream realm) {
RealmRepresentation rep = loadJson(realm, RealmRepresentation.class);
importRealm(rep);
}
public void importRealm(RealmRepresentation rep) {
KeycloakSession session = sessionFactory.create();;
session.getTransactionManager().begin();
try {
RealmManager manager = new RealmManager(session);
if (rep.getId() != null && manager.getRealm(rep.getId()) != null) {
info("Not importing realm " + rep.getRealm() + " realm already exists");
return;
}
if (manager.getRealmByName(rep.getRealm()) != null) {
info("Not importing realm " + rep.getRealm() + " realm already exists");
return;
}
manager.setContextPath("/auth");
RealmModel realm = manager.importRealm(rep);
info("Imported realm " + realm.getName());
session.getTransactionManager().commit();
} finally {
session.close();
}
}
protected void setupDevConfig() {
if (System.getProperty("keycloak.createAdminUser", "true").equals("true")) {
KeycloakSession session = sessionFactory.create();
try {
session.getTransactionManager().begin();
if (new ApplianceBootstrap(session).isNoMasterUser()) {
new ApplianceBootstrap(session).createMasterRealmUser("admin", "admin");
}
session.getTransactionManager().commit();
} finally {
session.close();
}
}
}
public void start() throws Throwable {
long start = System.currentTimeMillis();
ResteasyDeployment deployment = new ResteasyDeployment();
deployment.setApplicationClass(KeycloakApplication.class.getName());
Builder builder = Undertow.builder()
.addHttpListener(config.getPort(), config.getHost())
.setWorkerThreads(config.getWorkerThreads())
.setIoThreads(config.getWorkerThreads() / 8);
if (config.getPortHttps() != -1) {
builder = builder.addHttpsListener(config.getPortHttps(), config.getHost(), SSLContext.getDefault());
}
server = new UndertowJaxrsServer();
try {
server.start(builder);
DeploymentInfo di = server.undertowDeployment(deployment, "");
di.setClassLoader(getClass().getClassLoader());
di.setContextPath("/auth");
di.setDeploymentName("Keycloak");
di.setDefaultEncoding("UTF-8");
di.addInitParameter(KeycloakApplication.KEYCLOAK_EMBEDDED, "true");
di.setDefaultServletConfig(new DefaultServletConfig(true));
ServletInfo restEasyDispatcher = Servlets.servlet("Keycloak REST Interface", HttpServlet30Dispatcher.class);
restEasyDispatcher.addInitParam("resteasy.servlet.mapping.prefix", "/");
restEasyDispatcher.setAsyncSupported(true);
di.addServlet(restEasyDispatcher);
FilterInfo filter = Servlets.filter("SessionFilter", KeycloakSessionServletFilter.class);
filter.setAsyncSupported(true);
di.addFilter(filter);
di.addFilterUrlMapping("SessionFilter", "/*", DispatcherType.REQUEST);
server.deploy(di);
sessionFactory = ((KeycloakApplication) deployment.getApplication()).getSessionFactory();
setupDevConfig();
if (config.getResourcesHome() != null) {
info("Loading resources from " + config.getResourcesHome());
}
info("Started Keycloak (http://" + config.getHost() + ":" + config.getPort() + "/auth"
+ (config.getPortHttps() > 0 ? ", https://" + config.getHost() + ":" + config.getPortHttps()+ "/auth" : "")
+ ") in "
+ (System.currentTimeMillis() - start) + " ms\n");
} catch (RuntimeException e) {
server.stop();
throw e;
}
}
private void info(String message) {
if (sysout) {
System.out.println(message);
} else {
log.info(message);
}
}
public void stop() {
sessionFactory.close();
server.stop();
info("Stopped Keycloak");
}
private static File file(String... path) {
StringBuilder s = new StringBuilder();
for (String p : path) {
s.append(File.separator);
s.append(p);
}
return new File(s.toString());
}
private static void detectNodeName(KeycloakServerConfig config) {
String nodeName = System.getProperty(InfinispanConnectionProvider.JBOSS_NODE_NAME);
if (nodeName == null) {
// Try to autodetect "jboss.node.name" from the port
Map<Integer, String> nodesCfg = new HashMap<>();
nodesCfg.put(8181, "node1");
nodesCfg.put(8182, "node2");
nodeName = nodesCfg.get(config.getPort());
if (nodeName != null) {
System.setProperty(InfinispanConnectionProvider.JBOSS_NODE_NAME, nodeName);
}
}
if (nodeName != null) {
log.infof("Node name: %s", nodeName);
}
}
}
@@ -0,0 +1,62 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite;
import com.icegreen.greenmail.util.GreenMail;
import com.icegreen.greenmail.util.ServerSetup;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMessage.RecipientType;
import javax.mail.internet.MimeMultipart;
public class MailServer {
public static void main(String[] args) throws Exception {
ServerSetup setup = new ServerSetup(3025, "localhost", "smtp");
GreenMail greenMail = new GreenMail(setup);
greenMail.start();
System.out.println("Started mail server (localhost:3025)");
System.out.println();
while (true) {
int c = greenMail.getReceivedMessages().length;
if (greenMail.waitForIncomingEmail(Long.MAX_VALUE, c + 1)) {
MimeMessage message = greenMail.getReceivedMessages()[c++];
System.out.println("-------------------------------------------------------");
System.out.println("Received mail to " + message.getRecipients(RecipientType.TO)[0]);
if (message.getContent() instanceof MimeMultipart) {
MimeMultipart mimeMultipart = (MimeMultipart) message.getContent();
for (int i = 0; i < mimeMultipart.getCount(); i++) {
System.out.println("----");
System.out.println(mimeMultipart.getBodyPart(i).getContentType() + ":");
System.out.println();
System.out.println(mimeMultipart.getBodyPart(i).getContent());
}
} else {
System.out.println();
System.out.println(message.getContent());
}
System.out.println("-------------------------------------------------------");
}
}
}
}
@@ -0,0 +1,67 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite;
import org.keycloak.models.utils.Base32;
import org.keycloak.models.utils.TimeBasedOTP;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
public class TotpGenerator {
public static void main(String[] args) throws IOException {
Timer timer = new Timer();
TotpTask task = null;
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
System.out.print("Insert secret: ");
for (String l = br.readLine(); true; l = br.readLine()) {
if (task != null) {
task.cancel();
}
System.out.println("Secret: " + l);
task = new TotpTask(l);
timer.schedule(task, 0, TimeUnit.SECONDS.toMillis(TimeBasedOTP.DEFAULT_INTERVAL_SECONDS));
}
}
private static class TotpTask extends TimerTask {
private String secret;
private TotpTask(String secret) {
this.secret = secret;
}
@Override
public void run() {
String google = new String(Base32.decode(secret));
TimeBasedOTP otp = new TimeBasedOTP();
System.out.println(otp.generateTOTP(google));
}
}
}
@@ -0,0 +1,89 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.util.cli;
import org.jboss.logging.Logger;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.KeycloakSessionTask;
import org.keycloak.models.utils.KeycloakModelUtils;
import java.util.List;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public abstract class AbstractCommand {
protected final Logger log = Logger.getLogger(this.getClass().getName());
protected List<String> args;
protected KeycloakSessionFactory sessionFactory;
public void injectProperties(List<String> args, TestsuiteCLI cli, KeycloakSessionFactory sessionFactory) {
this.args = args;
this.sessionFactory = sessionFactory;
}
public void runCommand() {
try {
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
@Override
public void run(KeycloakSession session) {
doRunCommand(session);
}
});
} catch (HandledException handled) {
// Fine to ignore. Was handled already
} catch (RuntimeException e) {
log.error("Error occured during command. ", e);
}
}
public abstract String getName();
protected abstract void doRunCommand(KeycloakSession session);
protected String getArg(int index) {
try {
return args.get(index);
} catch (IndexOutOfBoundsException ex) {
log.errorf("Usage: %s", printUsage());
throw new HandledException();
}
}
protected Integer getIntArg(int index) {
String str = getArg(index);
try {
return Integer.parseInt(str);
} catch (NumberFormatException nex) {
log.errorf("Usage: %s", printUsage());
throw new HandledException();
}
}
public String printUsage() {
return getName();
}
public static class HandledException extends RuntimeException {
}
}
@@ -0,0 +1,350 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.util.cli;
import org.infinispan.AdvancedCache;
import org.infinispan.Cache;
import org.infinispan.context.Flag;
import org.keycloak.common.util.Time;
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.sessions.infinispan.changes.SessionEntityWrapper;
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
import org.keycloak.models.utils.KeycloakModelUtils;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public abstract class AbstractSessionCacheCommand extends AbstractCommand {
@Override
protected void doRunCommand(KeycloakSession session) {
InfinispanConnectionProvider provider = session.getProvider(InfinispanConnectionProvider.class);
String cacheName = getArg(0);
if (!cacheName.equals(InfinispanConnectionProvider.SESSION_CACHE_NAME) && !cacheName.equals(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME)) {
log.errorf("Invalid cache name: '%s', Only cache names '%s' or '%s' are supported", cacheName, InfinispanConnectionProvider.SESSION_CACHE_NAME,
InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME);
throw new HandledException();
}
Cache<String, SessionEntityWrapper> ispnCache = provider.getCache(cacheName);
doRunCacheCommand(session, ispnCache);
ispnCache.entrySet().stream().skip(0).limit(10).collect(java.util.stream.Collectors.toMap(new java.util.function.Function() {
public Object apply(Object entry) {
return ((java.util.Map.Entry) entry).getKey();
}
}, new java.util.function.Function() {
public Object apply(Object entry) {
return ((java.util.Map.Entry) entry).getValue();
}
}));
}
protected void printSession(String id, UserSessionEntity userSession) {
if (userSession == null) {
log.info("Not found session with Id: " + id);
} else {
log.info("Found session. ID: " + toString(userSession));
}
}
protected String toString(UserSessionEntity userSession) {
int clientSessionsSize = userSession.getAuthenticatedClientSessions()==null ? 0 : userSession.getAuthenticatedClientSessions().size();
return "ID: " + userSession.getId() + ", realm: " + userSession.getRealm() + ", lastAccessTime: " + Time.toDate(userSession.getLastSessionRefresh()) +
", authenticatedClientSessions: " + clientSessionsSize;
}
@Override
public String printUsage() {
return getName() + " <cache-name>";
}
protected abstract void doRunCacheCommand(KeycloakSession session, Cache<String, SessionEntityWrapper> cache);
// IMPLS
public static class PutCommand extends AbstractSessionCacheCommand {
@Override
public String getName() {
return "put";
}
@Override
protected void doRunCacheCommand(KeycloakSession session, Cache<String, SessionEntityWrapper> cache) {
UserSessionEntity userSession = new UserSessionEntity();
String id = getArg(1);
userSession.setId(id);
userSession.setRealm(getArg(2));
userSession.setLastSessionRefresh(Time.currentTime());
cache.put(id, new SessionEntityWrapper(userSession));
}
@Override
public String printUsage() {
return getName() + " <cache-name> <user-session-id> <realm-name>";
}
}
public static class GetCommand extends AbstractSessionCacheCommand {
@Override
public String getName() {
return "get";
}
@Override
protected void doRunCacheCommand(KeycloakSession session, Cache<String, SessionEntityWrapper> cache) {
String id = getArg(1);
UserSessionEntity userSession = (UserSessionEntity) cache.get(id).getEntity();
printSession(id, userSession);
}
@Override
public String printUsage() {
return getName() + " <cache-name> <user-session-id>";
}
}
// Just to check performance of multiple get calls. And comparing what's the change between the case when item is available locally or not.
public static class GetMultipleCommand extends AbstractSessionCacheCommand {
@Override
public String getName() {
return "getMulti";
}
@Override
protected void doRunCacheCommand(KeycloakSession session, Cache<String, SessionEntityWrapper> cache) {
String id = getArg(1);
int count = getIntArg(2);
long start = System.currentTimeMillis();
for (int i=0 ; i<count ; i++) {
UserSessionEntity userSession = (UserSessionEntity) cache.get(id).getEntity();
//printSession(id, userSession);
}
long took = System.currentTimeMillis() - start;
log.infof("Took %d milliseconds", took);
}
@Override
public String printUsage() {
return getName() + " <cache-name> <user-session-id> <count-of-gets>";
}
}
public static class RemoveCommand extends AbstractSessionCacheCommand {
@Override
public String getName() {
return "remove";
}
@Override
protected void doRunCacheCommand(KeycloakSession session, Cache<String, SessionEntityWrapper> cache) {
String id = getArg(1);
cache.remove(id);
}
@Override
public String printUsage() {
return getName() + " <cache-name> <user-session-id>";
}
}
public static class ClearCommand extends AbstractSessionCacheCommand {
@Override
public String getName() {
return "clear";
}
@Override
protected void doRunCacheCommand(KeycloakSession session, Cache<String, SessionEntityWrapper> cache) {
cache.clear();
}
}
public static class SizeCommand extends AbstractSessionCacheCommand {
@Override
public String getName() {
return "size";
}
@Override
protected void doRunCacheCommand(KeycloakSession session, Cache<String, SessionEntityWrapper> cache) {
log.info("Size: " + cache.size());
}
}
public static class ListCommand extends AbstractSessionCacheCommand {
@Override
public String getName() {
return "list";
}
@Override
protected void doRunCacheCommand(KeycloakSession session, Cache<String, SessionEntityWrapper> cache) {
for (String id : cache.keySet()) {
SessionEntity entity = cache.get(id).getEntity();
if (!(entity instanceof UserSessionEntity)) {
continue;
}
UserSessionEntity userSession = (UserSessionEntity) cache.get(id).getEntity();
log.info("list: key=" + id + ", value=" + toString(userSession));
}
}
}
public static class GetLocalCommand extends AbstractSessionCacheCommand {
@Override
public String getName() {
return "getLocal";
}
@Override
protected void doRunCacheCommand(KeycloakSession session, Cache<String, SessionEntityWrapper> cache) {
String id = getArg(1);
cache = ((AdvancedCache) cache).withFlags(Flag.CACHE_MODE_LOCAL);
UserSessionEntity userSession = (UserSessionEntity) cache.get(id).getEntity();
printSession(id, userSession);
}
@Override
public String printUsage() {
return getName() + " <cache-name> <user-session-id>";
}
}
public static class SizeLocalCommand extends AbstractSessionCacheCommand {
@Override
public String getName() {
return "sizeLocal";
}
@Override
protected void doRunCacheCommand(KeycloakSession session, Cache<String, SessionEntityWrapper> cache) {
log.info("Size local: " + cache.getAdvancedCache().withFlags(Flag.CACHE_MODE_LOCAL).size());
}
}
public static class CreateManySessionsCommand extends AbstractSessionCacheCommand {
@Override
public String getName() {
return "createManySessions";
}
@Override
protected void doRunCacheCommand(KeycloakSession session, Cache<String, SessionEntityWrapper> cache) {
String realmName = getArg(1);
int count = getIntArg(2);
int batchCount = getIntArg(3);
BatchTaskRunner.runInBatches(0, count, batchCount, session.getKeycloakSessionFactory(), (KeycloakSession batchSession, int firstInIteration, int countInIteration) -> {
for (int i=0 ; i<countInIteration ; i++) {
UserSessionEntity userSession = new UserSessionEntity();
String id = KeycloakModelUtils.generateId();
userSession.setId(id);
userSession.setRealm(realmName);
userSession.setLastSessionRefresh(Time.currentTime());
cache.put(id, new SessionEntityWrapper(userSession));
}
log.infof("Created '%d' sessions started from offset '%d'", countInIteration, firstInIteration);
});
log.infof("Created all '%d' sessions", count);
}
@Override
public String printUsage() {
return getName() + " <cache-name> <realm-name> <count> <count-in-batch>";
}
}
// This will propagate creating sessions to remoteCache too
public static class CreateManySessionsProviderCommand extends AbstractSessionCacheCommand {
@Override
public String getName() {
return "createManySessionsProvider";
}
@Override
protected void doRunCacheCommand(KeycloakSession session, Cache<String, SessionEntityWrapper> cache) {
String realmName = getArg(1);
String clientId = getArg(2);
String username = getArg(3);
int count = getIntArg(4);
int batchCount = getIntArg(5);
BatchTaskRunner.runInBatches(0, count, batchCount, session.getKeycloakSessionFactory(), (KeycloakSession batchSession, int firstInIteration, int countInIteration) -> {
RealmModel realm = batchSession.realms().getRealmByName(realmName);
ClientModel client = realm.getClientByClientId(clientId);
UserModel user = batchSession.users().getUserByUsername(username, realm);
for (int i=0 ; i<countInIteration ; i++) {
UserSessionModel userSession = session.sessions().createUserSession(KeycloakModelUtils.generateId(), realm, user, username, "127.0.0.1", "form", false, null, null);
session.sessions().createClientSession(userSession.getRealm(), client, userSession);
}
log.infof("Created '%d' sessions started from offset '%d'", countInIteration, firstInIteration);
});
log.infof("Created all '%d' sessions", count);
}
@Override
public String printUsage() {
return getName() + " <cache-name> <realm-name> <client-id> <user-name> <count> <count-in-batch>";
}
}
}
@@ -0,0 +1,67 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.util.cli;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.KeycloakSessionTask;
import org.keycloak.models.utils.KeycloakModelUtils;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
class BatchTaskRunner {
static void runInBatches(int first, int count, int batchCount, KeycloakSessionFactory sessionFactory, BatchTask batchTask) {
final StateHolder state = new StateHolder();
state.firstInThisBatch = first;
state.remaining = count;
state.countInThisBatch = Math.min(batchCount, state.remaining);
while (state.remaining > 0) {
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
@Override
public void run(KeycloakSession session) {
batchTask.run(session, state.firstInThisBatch, state.countInThisBatch);
}
});
// update state
state.firstInThisBatch = state.firstInThisBatch + state.countInThisBatch;
state.remaining = state.remaining - state.countInThisBatch;
state.countInThisBatch = Math.min(batchCount, state.remaining);
}
}
private static class StateHolder {
int firstInThisBatch;
int countInThisBatch;
int remaining;
};
@FunctionalInterface
public interface BatchTask {
void run(KeycloakSession session, int firstInThisIteration, int countInThisIteration);
}
}
@@ -0,0 +1,115 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.util.cli;
import java.util.Map;
import java.util.Set;
import org.infinispan.Cache;
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class CacheCommands {
public static class ListCachesCommand extends AbstractCommand {
@Override
public String getName() {
return "listCaches";
}
@Override
protected void doRunCommand(KeycloakSession session) {
InfinispanConnectionProvider ispnProvider = session.getProvider(InfinispanConnectionProvider.class);
Set<String> cacheNames = ispnProvider.getCache("realms").getCacheManager().getCacheNames();
log.infof("Available caches: %s", cacheNames);
}
}
public static class GetCacheCommand extends AbstractCommand {
@Override
public String getName() {
return "getCache";
}
@Override
protected void doRunCommand(KeycloakSession session) {
String cacheName = getArg(0);
InfinispanConnectionProvider ispnProvider = session.getProvider(InfinispanConnectionProvider.class);
Cache<Object, Object> cache = ispnProvider.getCache(cacheName);
if (cache == null) {
log.errorf("Cache '%s' doesn't exist", cacheName);
throw new HandledException();
}
printCache(cache);
}
private void printCache(Cache<Object, Object> cache) {
int size = cache.size();
log.infof("Cache %s, size: %d", cache.getName(), size);
if (size > 50) {
log.info("Skip printing cache records due to big size");
} else {
for (Map.Entry<Object, Object> entry : cache.entrySet()) {
log.infof("%s=%s", entry.getKey(), entry.getValue());
}
}
}
@Override
public String printUsage() {
return super.printUsage() + " <cache-name> . cache-name is name of the infinispan cache provided by InfinispanConnectionProvider";
}
}
public static class CacheRealmObjectsCommand extends AbstractCommand {
@Override
public String getName() {
return "cacheRealmObjects";
}
@Override
protected void doRunCommand(KeycloakSession session) {
String realmName = getArg(0);
RealmModel realm = session.realms().getRealmByName(realmName);
if (realm == null) {
log.errorf("Realm not found: %s", realmName);
throw new HandledException();
}
TestCacheUtils.cacheRealmWithEverything(session, realmName);
}
@Override
public String printUsage() {
return super.printUsage() + " <realm-name>";
}
}
}
@@ -0,0 +1,75 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.util.cli;
import org.keycloak.cluster.ClusterProvider;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.models.KeycloakSession;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class ClusterProviderTaskCommand extends AbstractCommand {
private static final ExecutorService executors = Executors.newCachedThreadPool();
@Override
protected void doRunCommand(KeycloakSession session) {
String taskName = getArg(0);
int taskTimeout = getIntArg(1);
int sleepTime = getIntArg(2);
ClusterProvider cluster = session.getProvider(ClusterProvider.class);
Future future = cluster.executeIfNotExecutedAsync(taskName, taskTimeout, () -> {
log.infof("Started sleeping for " + sleepTime + " seconds");
Thread.sleep(sleepTime * 1000);
log.infof("Stopped sleeping");
return null;
});
log.info("I've retrieved future successfully");
executors.execute(() -> {
try {
future.get();
log.info("Successfully finished future!");
} catch (Exception e) {
e.printStackTrace();
}
});
}
private void updateConfig(MultivaluedHashMap<String, String> cfg, int waitTime) {
cfg.putSingle("wait-time", String.valueOf(waitTime));
}
@Override
public String getName() {
return "clusterProviderTask";
}
@Override
public String printUsage() {
return super.printUsage() + " <task-name> <task-wait-time-in-seconds> <sleep-time-in-seconds>";
}
}
@@ -0,0 +1,47 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.util.cli;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.UserSessionProvider;
import org.keycloak.models.UserSessionProviderFactory;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class LoadPersistentSessionsCommand extends AbstractCommand {
@Override
public String getName() {
return "loadPersistentSessions";
}
@Override
protected void doRunCommand(KeycloakSession session) {
int sessionsPerSegment = getIntArg(0);
UserSessionProviderFactory sessionProviderFactory = (UserSessionProviderFactory) sessionFactory.getProviderFactory(UserSessionProvider.class);
sessionProviderFactory.loadPersistentSessions(sessionFactory, 10, sessionsPerSegment);
log.info("All persistent sessions loaded successfully");
}
@Override
public String printUsage() {
return super.printUsage() + " <sessions-count-per-segment>";
}
}
@@ -0,0 +1,125 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.util.cli;
import java.util.LinkedList;
import java.util.List;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionTask;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.session.UserSessionPersisterProvider;
import org.keycloak.models.utils.KeycloakModelUtils;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class PersistSessionsCommand extends AbstractCommand {
@Override
public String getName() {
return "persistSessions";
}
@Override
public void doRunCommand(KeycloakSession sess) {
final int count = getIntArg(0);
final int batchCount = getIntArg(1);
int remaining = count;
while (remaining > 0) {
int createInThisBatch = Math.min(batchCount, remaining);
createSessionsBatch(createInThisBatch);
remaining = remaining - createInThisBatch;
}
// Write some summary
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
@Override
public void run(KeycloakSession session) {
UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
log.info("Command finished. Total number of sessions in persister: " + persister.getUserSessionsCount(true));
}
});
}
private void createSessionsBatch(final int countInThisBatch) {
final List<String> userSessionIds = new LinkedList<>();
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
@Override
public void run(KeycloakSession session) {
RealmModel realm = session.realms().getRealmByName("master");
UserModel john = session.users().getUserByUsername("admin", realm);
ClientModel testApp = realm.getClientByClientId("security-admin-console");
UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
for (int i = 0; i < countInThisBatch; i++) {
UserSessionModel userSession = session.sessions().createUserSession(KeycloakModelUtils.generateId(), realm, john, "john-doh@localhost", "127.0.0.2", "form", true, null, null);
AuthenticatedClientSessionModel clientSession = session.sessions().createClientSession(realm, testApp, userSession);
clientSession.setRedirectUri("http://redirect");
clientSession.setNote("foo", "bar-" + i);
userSessionIds.add(userSession.getId());
}
}
});
log.infof("%d sessions created in infinispan storage", countInThisBatch);
// Persist them now
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
@Override
public void run(KeycloakSession session) {
RealmModel realm = session.realms().getRealmByName("master");
ClientModel testApp = realm.getClientByClientId("security-admin-console");
UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
int counter = 0;
for (String userSessionId : userSessionIds) {
counter++;
UserSessionModel userSession = session.sessions().getUserSession(realm, userSessionId);
persister.createUserSession(userSession, true);
AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessions().get(testApp.getId());
persister.createClientSession(clientSession, true);
}
log.infof("%d user sessions persisted. Continue", counter);
}
});
}
@Override
public String printUsage() {
return super.printUsage() + " <sessions-count> <sessions-count-per-each-transaction>";
}
}
@@ -0,0 +1,106 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.util.cli;
import java.util.HashSet;
import java.util.Set;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionTask;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleContainerModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.utils.KeycloakModelUtils;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class RoleCommands {
public static class CreateRoles extends AbstractCommand {
private String rolePrefix;
private String roleContainer;
@Override
public String getName() {
return "createRoles";
}
@Override
protected void doRunCommand(KeycloakSession session) {
rolePrefix = getArg(0);
roleContainer = getArg(1);
int first = getIntArg(2);
int count = getIntArg(3);
int batchCount = getIntArg(4);
BatchTaskRunner.runInBatches(first, count, batchCount, session.getKeycloakSessionFactory(), (KeycloakSession bathcSession, int firstInThisIteration, int countInThisIteration) -> {
createRolesInBatch(session, roleContainer, rolePrefix, firstInThisIteration, countInThisIteration);
});
log.infof("Command finished. All roles from %s to %s created", rolePrefix + first, rolePrefix + (first + count - 1));
}
private void createRolesInBatch(KeycloakSession session, String roleContainer, String rolePrefix, int first, int count) {
RoleContainerModel container = getRoleContainer(session, roleContainer);
int last = first + count;
for (int counter = first; counter < last; counter++) {
String roleName = rolePrefix + counter;
RoleModel role = container.addRole(roleName);
}
log.infof("Roles from %s to %s created", rolePrefix + first, rolePrefix + (last - 1));
}
private RoleContainerModel getRoleContainer(KeycloakSession session, String roleContainer) {
String[] parts = roleContainer.split("/");
String realmName = parts[0];
RealmModel realm = session.realms().getRealmByName(realmName);
if (realm == null) {
log.errorf("Unknown realm: %s", realmName);
throw new HandledException();
}
if (parts.length == 1) {
return realm;
} else {
String clientId = parts[1];
ClientModel client = session.realms().getClientByClientId(clientId, realm);
if (client == null) {
log.errorf("Unknown client: %s", clientId);
throw new HandledException();
}
return client;
}
}
@Override
public String printUsage() {
return super.printUsage() + " <role-prefix> <role-container> <starting-role-offset> <total-count> <batch-size> . " +
"\n'total-count' refers to total count of newly created roles. 'batch-size' refers to number of created roles in each transaction. 'starting-role-offset' refers to starting role offset." +
"\nFor example if 'starting-role-offset' is 15 and total-count is 10 and role-prefix is 'test', it will create roles test15, test16, test17, ... , test24" +
"\n'role-container' is either realm (then use just realmName like 'demo' or client (then use realm/clientId like 'demo/my-client' .\n" +
"Example usage: " + super.printUsage() + " test demo 0 500 100";
}
}
}
@@ -0,0 +1,77 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.util.cli;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.services.managers.UserStorageSyncManager;
import org.keycloak.storage.UserStorageProviderModel;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class SyncDummyFederationProviderCommand extends AbstractCommand {
@Override
protected void doRunCommand(KeycloakSession session) {
int waitTime = getIntArg(0);
int changedSyncPeriod = getIntArg(1);
RealmModel realm = session.realms().getRealmByName("master");
UserStorageProviderModel fedProviderModel = KeycloakModelUtils.findUserStorageProviderByName("cluster-dummy", realm);
if (fedProviderModel == null) {
MultivaluedHashMap<String, String> cfg = fedProviderModel.getConfig();
updateConfig(cfg, waitTime);
UserStorageProviderModel model = new UserStorageProviderModel();
model.setProviderId("sync-dummy");
model.setPriority(1);
model.setName("cluster-dummy");
model.setFullSyncPeriod(-1);
model.setChangedSyncPeriod(changedSyncPeriod);
model.setLastSync(-1);
fedProviderModel = new UserStorageProviderModel(realm.addComponentModel(model));
} else {
MultivaluedHashMap<String, String> cfg = fedProviderModel.getConfig();
updateConfig(cfg, waitTime);
fedProviderModel.setChangedSyncPeriod(changedSyncPeriod);
realm.updateComponent(fedProviderModel);
}
new UserStorageSyncManager().notifyToRefreshPeriodicSync(session, realm, fedProviderModel, false);
log.infof("User federation provider created and sync was started", waitTime);
}
private void updateConfig(MultivaluedHashMap<String, String> cfg, int waitTime) {
cfg.putSingle("wait-time", String.valueOf(waitTime));
}
@Override
public String getName() {
return "startSyncDummy";
}
@Override
public String printUsage() {
return super.printUsage() + " <wait-time-before-sync-commit-in-seconds> <changed-sync-period-in-seconds>";
}
}
@@ -0,0 +1,88 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.util.cli;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientTemplateModel;
import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleContainerModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class TestCacheUtils {
public static void cacheRealmWithEverything(KeycloakSession session, String realmName) {
RealmModel realm = session.realms().getRealmByName(realmName);
for (ClientModel client : realm.getClients()) {
realm.getClientById(client.getId());
realm.getClientByClientId(client.getClientId());
cacheRoles(session, realm, client);
}
cacheRoles(session, realm, realm);
for (GroupModel group : realm.getTopLevelGroups()) {
cacheGroupRecursive(realm, group);
}
for (ClientTemplateModel clientTemplate : realm.getClientTemplates()) {
realm.getClientTemplateById(clientTemplate.getId());
}
for (UserModel user : session.users().getUsers(realm)) {
session.users().getUserById(user.getId(), realm);
if (user.getEmail() != null) {
session.users().getUserByEmail(user.getEmail(), realm);
}
session.users().getUserByUsername(user.getUsername(), realm);
session.users().getConsents(realm, user.getId());
for (FederatedIdentityModel fedIdentity : session.users().getFederatedIdentities(user, realm)) {
session.users().getUserByFederatedIdentity(fedIdentity, realm);
}
}
}
private static void cacheRoles(KeycloakSession session, RealmModel realm, RoleContainerModel roleContainer) {
for (RoleModel role : roleContainer.getRoles()) {
realm.getRoleById(role.getId());
roleContainer.getRole(role.getName());
if (roleContainer instanceof RealmModel) {
session.realms().getRealmRole(realm, role.getName());
} else {
session.realms().getClientRole(realm, (ClientModel) roleContainer, role.getName());
}
}
}
private static void cacheGroupRecursive(RealmModel realm, GroupModel group) {
realm.getGroupById(group.getId());
for (GroupModel sub : group.getSubGroups()) {
cacheGroupRecursive(realm, sub);
}
}
}
@@ -0,0 +1,184 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.util.cli;
import org.jboss.logging.Logger;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.testsuite.KeycloakServer;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* See Testsuite.md (section how to create many users and offline sessions)
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class TestsuiteCLI {
private static final Logger log = Logger.getLogger(TestsuiteCLI.class);
private static final Class<?>[] BUILTIN_COMMANDS = {
ExitCommand.class,
HelpCommand.class,
AbstractSessionCacheCommand.PutCommand.class,
AbstractSessionCacheCommand.GetCommand.class,
AbstractSessionCacheCommand.GetMultipleCommand.class,
AbstractSessionCacheCommand.GetLocalCommand.class,
AbstractSessionCacheCommand.SizeLocalCommand.class,
AbstractSessionCacheCommand.RemoveCommand.class,
AbstractSessionCacheCommand.SizeCommand.class,
AbstractSessionCacheCommand.ListCommand.class,
AbstractSessionCacheCommand.ClearCommand.class,
AbstractSessionCacheCommand.CreateManySessionsCommand.class,
AbstractSessionCacheCommand.CreateManySessionsProviderCommand.class,
PersistSessionsCommand.class,
LoadPersistentSessionsCommand.class,
UserCommands.Create.class,
UserCommands.Remove.class,
UserCommands.Count.class,
UserCommands.GetUser.class,
SyncDummyFederationProviderCommand.class,
RoleCommands.CreateRoles.class,
CacheCommands.ListCachesCommand.class,
CacheCommands.GetCacheCommand.class,
CacheCommands.CacheRealmObjectsCommand.class,
ClusterProviderTaskCommand.class
};
private final KeycloakSessionFactory sessionFactory;
private final Map<String, Class<? extends AbstractCommand>> commands = new LinkedHashMap<>();
public TestsuiteCLI(KeycloakServer server) {
this.sessionFactory = server.getSessionFactory();
// register builtin commands
for (Class<?> clazz : BUILTIN_COMMANDS) {
Class<? extends AbstractCommand> commandClazz = (Class<? extends AbstractCommand>) clazz;
try {
AbstractCommand command = commandClazz.newInstance();
commands.put(command.getName(), commandClazz);
} catch (Exception ex) {
log.error("Error registering command of class: " + commandClazz.getName(), ex);
}
}
}
public void registerCommand(String name, Class<? extends AbstractCommand> command) {
commands.put(name, command);
}
// WARNING: Stdin blocking operation
public void start() throws IOException {
log.info("Starting testsuite CLI. Exit with 'exit' . Available commands with 'help' ");
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
String line;
System.out.print("$ ");
try {
while ((line = reader.readLine()) != null) {
String[] splits = line.split(" ");
String commandName = splits[0];
Class<? extends AbstractCommand> commandClass = commands.get(commandName);
if (commandClass == null) {
log.errorf("Unknown command: %s", commandName);
} else {
try {
AbstractCommand command = commandClass.newInstance();
List<String> args = new ArrayList<>(Arrays.asList(splits));
args.remove(0);
command.injectProperties(args, this, sessionFactory);
command.runCommand();
// Just special handling of ExitCommand
if (command instanceof ExitCommand) {
return;
}
} catch (InstantiationException ex) {
log.error(ex);
} catch (IllegalAccessException ex) {
log.error(ex);
}
}
System.out.print("$ ");
}
} finally {
log.info("Exit testsuite CLI");
reader.close();
}
}
public static class ExitCommand extends AbstractCommand {
@Override
public String getName() {
return "exit";
}
@Override
public void runCommand() {
// no need to implement. Exit handled in parent
}
@Override
protected void doRunCommand(KeycloakSession session) {
// no need to implement
}
@Override
public String printUsage() {
return getName();
}
}
public static class HelpCommand extends AbstractCommand {
private List<String> commandNames = new ArrayList<>();
@Override
public void injectProperties(List<String> args, TestsuiteCLI cli, KeycloakSessionFactory sessionFactory) {
for (String commandName : cli.commands.keySet()) {
commandNames.add(commandName);
}
}
@Override
public String getName() {
return "help";
}
@Override
public void runCommand() {
log.info("Available commands: " + commandNames.toString());
}
@Override
protected void doRunCommand(KeycloakSession session) {
// no need to implement
}
}
}
@@ -0,0 +1,246 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.util.cli;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionTask;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class UserCommands {
public static class Create extends AbstractCommand {
private String usernamePrefix;
private String password;
private String realmName;
private String roleNames;
@Override
public String getName() {
return "createUsers";
}
@Override
protected void doRunCommand(KeycloakSession session) {
usernamePrefix = getArg(0);
password = getArg(1);
realmName = getArg(2);
int first = getIntArg(3);
int count = getIntArg(4);
int batchCount = getIntArg(5);
roleNames = getArg(6);
BatchTaskRunner.runInBatches(first, count, batchCount, session.getKeycloakSessionFactory(), this::createUsersInBatch);
log.infof("Command finished. All users from %s to %s created", usernamePrefix + first, usernamePrefix + (first + count - 1));
}
private void createUsersInBatch(KeycloakSession session, int first, int count) {
RealmModel realm = session.realms().getRealmByName(realmName);
if (realm == null) {
log.errorf("Unknown realm: %s", realmName);
throw new HandledException();
}
Set<RoleModel> roles = findRoles(realm, roleNames);
int last = first + count;
for (int counter = first; counter < last; counter++) {
String username = usernamePrefix + counter;
UserModel user = session.users().addUser(realm, username);
user.setEnabled(true);
user.setEmail(username + "@keycloak.org");
UserCredentialModel passwordCred = UserCredentialModel.password(password);
session.userCredentialManager().updateCredential(realm, user, passwordCred);
for (RoleModel role : roles) {
user.grantRole(role);
}
}
log.infof("Users from %s to %s created", usernamePrefix + first, usernamePrefix + (last - 1));
}
@Override
public String printUsage() {
return super.printUsage() + " <username-prefix> <password> <realm-name> <starting-user-offset> <total-count> <batch-size> <realm-roles-list>. " +
"\n'total-count' refers to total count of newly created users. 'batch-size' refers to number of created users in each transaction. 'starting-user-offset' refers to starting username offset." +
"\nFor example if 'starting-user-offset' is 15 and total-count is 10 and username-prefix is 'test', it will create users test15, test16, test17, ... , test24" +
"\nRoles list is divided by comma (client roles are referenced with format <client-id>/<role-name> )>\n" +
"Example usage: " + super.printUsage() + " test test demo 0 500 100 user,admin";
}
private Set<RoleModel> findRoles(RealmModel realm, String rolesList) {
Set<RoleModel> result = new HashSet<>();
String[] roles = rolesList.split(",");
for (String roleName : roles) {
roleName = roleName.trim();
RoleModel role;
if (roleName.contains("/")) {
String[] spl = roleName.split("/");
ClientModel client = realm.getClientByClientId(spl[0]);
if (client == null) {
log.errorf("Client not found: %s", spl[0]);
throw new HandledException();
}
role = client.getRole(spl[1]);
} else {
role = realm.getRole(roleName);
}
if (role == null) {
log.errorf("Role not found: %s", roleName);
throw new HandledException();
}
result.add(role);
}
return result;
}
}
public static class Remove extends AbstractCommand {
@Override
public String getName() {
return "removeUsers";
}
@Override
protected void doRunCommand(KeycloakSession session) {
String usernamePrefix = getArg(0);
String realmName = getArg(1);
int first = getIntArg(2);
int count = getIntArg(3);
RealmModel realm = session.realms().getRealmByName(realmName);
if (realm == null) {
log.errorf("Unknown realm: %s", realmName);
return;
}
int last = first + count;
for (int counter = first; counter < last; counter++) {
String username = usernamePrefix + counter;
UserModel user = session.users().getUserByUsername(username, realm);
if (user == null) {
log.errorf("User '%s' not found", username);
} else {
session.users().removeUser(realm, user);
}
}
log.infof("Users from %s to %s removed", usernamePrefix + first, usernamePrefix + (last - 1));
}
@Override
public String printUsage() {
return super.printUsage() + " <username-prefix> <realm-name> <starting-user-offset> <count> \n" +
"Example usage: " + super.printUsage() + " test demo 0 20";
}
}
public static class Count extends AbstractCommand {
@Override
public String getName() {
return "getUsersCount";
}
@Override
protected void doRunCommand(KeycloakSession session) {
String realmName = getArg(0);
RealmModel realm = session.realms().getRealmByName(realmName);
if (realm == null) {
log.errorf("Unknown realm: %s", realmName);
return;
}
int usersCount = session.users().getUsersCount(realm);
log.infof("Users count in realm %s: %d", realmName, usersCount);
}
@Override
public String printUsage() {
return super.printUsage() + " <realm-name>";
}
}
public static class GetUser extends AbstractCommand {
@Override
public String getName() {
return "getUser";
}
@Override
protected void doRunCommand(KeycloakSession session) {
String realmName = getArg(0);
String username = getArg(1);
RealmModel realm = session.realms().getRealmByName(realmName);
if (realm == null) {
log.errorf("Unknown realm: %s", realmName);
return;
}
UserModel user = session.users().getUserByUsername(username, realm);
if (user == null) {
log.infof("User '%s' doesn't exist in realm '%s'", username, realmName);
} else {
List<String> roleMappings = getRoleMappings(session, realm, user);
log.infof("User: ID: '%s', username: '%s', mail: '%s', roles: '%s'", user.getId(), user.getUsername(), user.getEmail(), roleMappings.toString());
}
}
private List<String> getRoleMappings(KeycloakSession session, RealmModel realm, UserModel user) {
Set<RoleModel> roles = user.getRoleMappings();
List<String> result = new LinkedList<>();
for (RoleModel role : roles) {
if (role.getContainer() instanceof RealmModel) {
result.add(role.getName());
} else {
ClientModel client = (ClientModel) role.getContainer();
result.add(client.getClientId() + "/" + role.getName());
}
}
return result;
}
@Override
public String printUsage() {
return super.printUsage() + " <realm-name> <username>";
}
}
}
@@ -0,0 +1,111 @@
{
"admin": {
"realm": "master"
},
"eventsStore": {
"provider": "${keycloak.eventsStore.provider:jpa}"
},
"eventsListener": {
"jboss-logging" : {
"success-level": "debug",
"error-level": "warn"
}
},
"realm": {
"provider": "${keycloak.realm.provider:jpa}"
},
"user": {
"provider": "${keycloak.user.provider:jpa}"
},
"userFederatedStorage": {
"provider": "${keycloak.userFederatedStorage.provider:jpa}"
},
"userSessionPersister": {
"provider": "${keycloak.userSessionPersister.provider:jpa}"
},
"authorizationPersister": {
"provider": "${keycloak.authorization.provider:jpa}"
},
"userCache": {
"default" : {
"enabled": true
}
},
"timer": {
"provider": "basic"
},
"theme": {
"staticMaxAge": "${keycloak.theme.staticMaxAge:2592000}",
"cacheTemplates": "${keycloak.theme.cacheTemplates:true}",
"cacheThemes": "${keycloak.theme.cacheThemes:true}",
"folder": {
"dir": "${keycloak.theme.dir}"
}
},
"scheduled": {
"interval": 900
},
"connectionsHttpClient": {
"default": {}
},
"connectionsJpa": {
"default": {
"url": "${keycloak.connectionsJpa.url:jdbc:h2:mem:test;DB_CLOSE_DELAY=-1}",
"driver": "${keycloak.connectionsJpa.driver:org.h2.Driver}",
"driverDialect": "${keycloak.connectionsJpa.driverDialect:}",
"user": "${keycloak.connectionsJpa.user:sa}",
"password": "${keycloak.connectionsJpa.password:}",
"initializeEmpty": true,
"migrationStrategy": "update",
"showSql": "${keycloak.connectionsJpa.showSql:false}",
"formatSql": "${keycloak.connectionsJpa.formatSql:true}",
"globalStatsInterval": "${keycloak.connectionsJpa.globalStatsInterval:-1}"
}
},
"realmCache": {
"default" : {
"enabled": true
}
},
"connectionsInfinispan": {
"default": {
"jgroupsUdpMcastAddr": "${keycloak.connectionsInfinispan.jgroupsUdpMcastAddr:234.56.78.90}",
"nodeName": "${keycloak.connectionsInfinispan.nodeName,jboss.node.name:}",
"siteName": "${keycloak.connectionsInfinispan.siteName,jboss.site.name:}",
"clustered": "${keycloak.connectionsInfinispan.clustered:false}",
"async": "${keycloak.connectionsInfinispan.async:false}",
"sessionsOwners": "${keycloak.connectionsInfinispan.sessionsOwners:1}",
"l1Lifespan": "${keycloak.connectionsInfinispan.l1Lifespan:600000}",
"remoteStoreEnabled": "${keycloak.connectionsInfinispan.remoteStoreEnabled:false}",
"remoteStoreHost": "${keycloak.connectionsjen neInfinispan.remoteStoreHost:localhost}",
"remoteStorePort": "${keycloak.connectionsInfinispan.remoteStorePort:11222}"
}
},
"scripting": {
},
"jta-lookup": {
"provider": "${keycloak.jta.lookup.provider:jboss}",
"jboss" : {
"enabled": true
}
}
}
+104
View File
@@ -0,0 +1,104 @@
#
# Copyright 2016 Red Hat, Inc. and/or its affiliates
# and other contributors as indicated by the @author tags.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
log4j.rootLogger=info, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{HH:mm:ss,SSS} %-5p %t [%c] %m%n
# For debug, run KeycloakServer with -Dkeycloak.logging.level=debug
keycloak.logging.level=info
log4j.logger.org.keycloak=${keycloak.logging.level}
# Enable to view events
# log4j.logger.org.keycloak.events=debug
# Enable to view loaded SPI and Providers
# log4j.logger.org.keycloak.services.DefaultKeycloakSessionFactory=debug
# log4j.logger.org.keycloak.provider.ProviderManager=debug
# log4j.logger.org.keycloak.provider.FileSystemProviderLoaderFactory=debug
#log4j.logger.org.infinispan.transaction.impl.TransactionCoordinator=OFF
#log4j.logger.org.infinispan.transaction.tm.DummyTransaction=OFF
#log4j.logger.org.infinispan.container.entries.RepeatableReadEntry=OFF
# Broker logging
keycloak.testsuite.logging.level=info
log4j.logger.org.keycloak.testsuite=${keycloak.testsuite.logging.level}
# Liquibase updates logged with "info" by default. Logging level can be changed by system property "keycloak.liquibase.logging.level"
keycloak.liquibase.logging.level=info
log4j.logger.org.keycloak.connections.jpa.updater.liquibase=${keycloak.liquibase.logging.level}
# Enable to view infinispan initialization
# log4j.logger.org.keycloak.models.sessions.infinispan.initializer=trace
# Enable to view cache activity
#log4j.logger.org.keycloak.cluster.infinispan=trace
#log4j.logger.org.keycloak.models.cache.infinispan=debug
# Enable to view database updates
log4j.logger.org.keycloak.connections.jpa.DefaultJpaConnectionProviderFactory=${keycloak.liquibase.logging.level}
# log4j.logger.org.keycloak.migration.MigrationModelManager=debug
# Enable to view hibernate statistics
log4j.logger.org.keycloak.connections.jpa.HibernateStatsReporter=debug
keycloak.infinispan.logging.level=info
log4j.logger.org.keycloak.cluster.infinispan=${keycloak.infinispan.logging.level}
log4j.logger.org.keycloak.connections.infinispan=${keycloak.infinispan.logging.level}
log4j.logger.org.keycloak.keys.infinispan=${keycloak.infinispan.logging.level}
log4j.logger.org.keycloak.models.cache.infinispan=${keycloak.infinispan.logging.level}
log4j.logger.org.keycloak.models.sessions.infinispan=${keycloak.infinispan.logging.level}
# Enable to view ldap logging
# log4j.logger.org.keycloak.storage.ldap=trace
# Enable to view queries to LDAP
# log4j.logger.org.keycloak.storage.ldap.idm.store.ldap.LDAPIdentityStore=trace
# Enable to view kerberos/spnego logging
# log4j.logger.org.keycloak.federation.kerberos=trace
# Enable to view detailed AS REQ and TGS REQ requests to embedded Kerberos server
# log4j.logger.org.apache.directory.server.kerberos=debug
#log4j.logger.org.keycloak.saml=debug
log4j.logger.org.xnio=off
log4j.logger.org.hibernate=off
log4j.logger.org.jboss.resteasy=warn
log4j.logger.org.apache.directory.api=warn
log4j.logger.org.apache.directory.server.core=warn
log4j.logger.org.apache.directory.server.ldap.LdapProtocolHandler=error
# Enable to view HttpClient connection pool activity
#log4j.logger.org.apache.http.impl.conn=debug
# Enable to view details from identity provider authenticator
#log4j.logger.org.keycloak.authentication.authenticators.browser.IdentityProviderAuthenticator=trace
#log4j.logger.org.keycloak.services.resources.IdentityBrokerService=trace
#log4j.logger.org.keycloak.broker=trace
#log4j.logger.org.keycloak.cluster.infinispan.InfinispanNotificationsManager=trace
#log4j.logger.io.undertow=trace
#log4j.logger.org.keycloak.protocol=debug
#log4j.logger.org.keycloak.services.resources.LoginActionsService=debug
#log4j.logger.org.keycloak.services.managers=debug
#log4j.logger.org.keycloak.services.resources.SessionCodeChecks=debug
#log4j.logger.org.keycloak.authentication=debug