Clustering support
This commit is contained in:
parent
6075ef221f
commit
99c73a9cc7
67 changed files with 3250 additions and 176 deletions
33
connections/infinispan/pom.xml
Executable file
33
connections/infinispan/pom.xml
Executable file
|
@ -0,0 +1,33 @@
|
|||
<?xml version="1.0"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.1.0-Alpha1-SNAPSHOT</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>keycloak-connections-infinispan</artifactId>
|
||||
<name>Keycloak Connections Infinispan</name>
|
||||
<description/>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-model-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.infinispan</groupId>
|
||||
<artifactId>infinispan-core</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
|
@ -0,0 +1,26 @@
|
|||
package org.keycloak.connections.infinispan;
|
||||
|
||||
import org.infinispan.Cache;
|
||||
import org.infinispan.manager.EmbeddedCacheManager;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class DefaultInfinispanConnectionProvider implements InfinispanConnectionProvider {
|
||||
|
||||
private EmbeddedCacheManager cacheManager;
|
||||
|
||||
public DefaultInfinispanConnectionProvider(EmbeddedCacheManager cacheManager) {
|
||||
this.cacheManager = cacheManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <K, V> Cache<K, V> getCache(String name) {
|
||||
return cacheManager.getCache(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
package org.keycloak.connections.infinispan;
|
||||
|
||||
import org.infinispan.configuration.cache.CacheMode;
|
||||
import org.infinispan.configuration.cache.Configuration;
|
||||
import org.infinispan.configuration.cache.ConfigurationBuilder;
|
||||
import org.infinispan.configuration.global.GlobalConfigurationBuilder;
|
||||
import org.infinispan.manager.DefaultCacheManager;
|
||||
import org.infinispan.manager.EmbeddedCacheManager;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
||||
import javax.naming.InitialContext;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class DefaultInfinispanConnectionProviderFactory implements InfinispanConnectionProviderFactory {
|
||||
|
||||
protected static final Logger logger = Logger.getLogger(DefaultInfinispanConnectionProviderFactory.class);
|
||||
|
||||
private Config.Scope config;
|
||||
|
||||
private EmbeddedCacheManager cacheManager;
|
||||
|
||||
private boolean containerManaged;
|
||||
|
||||
@Override
|
||||
public InfinispanConnectionProvider create(KeycloakSession session) {
|
||||
lazyInit();
|
||||
|
||||
return new DefaultInfinispanConnectionProvider(cacheManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (cacheManager != null && !containerManaged) {
|
||||
cacheManager.stop();
|
||||
}
|
||||
cacheManager = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "default";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
private void lazyInit() {
|
||||
if (cacheManager == null) {
|
||||
synchronized (this) {
|
||||
if (cacheManager == null) {
|
||||
String cacheContainer = config.get("cacheContainer");
|
||||
if (cacheContainer != null) {
|
||||
initContainerManaged(cacheContainer);
|
||||
} else {
|
||||
initEmbedded();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void initContainerManaged(String cacheContainerLookup) {
|
||||
try {
|
||||
cacheManager = (EmbeddedCacheManager) new InitialContext().lookup(cacheContainerLookup);
|
||||
containerManaged = true;
|
||||
|
||||
logger.debugv("Using container managed Infinispan cache container, lookup={1}", cacheContainerLookup);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to retrieve cache container", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void initEmbedded() {
|
||||
GlobalConfigurationBuilder gcb = new GlobalConfigurationBuilder();
|
||||
if (config.getBoolean("transport", false)) {
|
||||
gcb.transport().defaultTransport();
|
||||
}
|
||||
cacheManager = new DefaultCacheManager(gcb.build());
|
||||
containerManaged = false;
|
||||
|
||||
logger.debug("Started embedded Infinispan cache container");
|
||||
|
||||
cacheManager.defineConfiguration("sessions", createConfiguration("sessions"));
|
||||
cacheManager.defineConfiguration("realms", createConfiguration("realms"));
|
||||
}
|
||||
|
||||
private Configuration createConfiguration(String cacheName) {
|
||||
Config.Scope cacheConfig = config.scope("caches", cacheName);
|
||||
ConfigurationBuilder cb = new ConfigurationBuilder();
|
||||
|
||||
String cacheMode = cacheConfig.get("cacheMode", "local");
|
||||
boolean async = cacheConfig.getBoolean("async", false);
|
||||
|
||||
if (cacheMode.equalsIgnoreCase("replicated")) {
|
||||
cb.clustering().cacheMode(async ? CacheMode.REPL_ASYNC : CacheMode.REPL_SYNC);
|
||||
} else if (cacheMode.equalsIgnoreCase("distributed")) {
|
||||
cb.clustering().cacheMode(async ? CacheMode.DIST_ASYNC : CacheMode.DIST_SYNC);
|
||||
|
||||
int owners = cacheConfig.getInt("owners", 2);
|
||||
int segments = cacheConfig.getInt("segments", 60);
|
||||
|
||||
cb.clustering().hash().numOwners(owners).numSegments(segments);
|
||||
} else if (cacheMode.equalsIgnoreCase("invalidation")) {
|
||||
cb.clustering().cacheMode(async ? CacheMode.INVALIDATION_ASYNC : CacheMode.INVALIDATION_SYNC);
|
||||
} else if (!cacheMode.equalsIgnoreCase("local")) {
|
||||
throw new RuntimeException("Invalid cache mode " + cacheMode);
|
||||
}
|
||||
|
||||
logger.debugv("Configured cache {0} with mode={1}, async={2}", cacheName, cacheMode, async);
|
||||
|
||||
return cb.build();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package org.keycloak.connections.infinispan;
|
||||
|
||||
import org.infinispan.Cache;
|
||||
import org.keycloak.provider.Provider;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public interface InfinispanConnectionProvider extends Provider {
|
||||
|
||||
<K, V> Cache<K, V> getCache(String name);
|
||||
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package org.keycloak.connections.infinispan;
|
||||
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public interface InfinispanConnectionProviderFactory extends ProviderFactory<InfinispanConnectionProvider> {
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package org.keycloak.connections.infinispan;
|
||||
|
||||
import org.keycloak.provider.Provider;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
import org.keycloak.provider.Spi;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class InfinispanConnectionSpi implements Spi {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "connectionsInfinispan";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Provider> getProviderClass() {
|
||||
return InfinispanConnectionProvider.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
||||
return InfinispanConnectionProviderFactory.class;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
org.keycloak.connections.infinispan.DefaultInfinispanConnectionProviderFactory
|
|
@ -0,0 +1 @@
|
|||
org.keycloak.connections.infinispan.InfinispanConnectionSpi
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
<modules>
|
||||
<module>jpa</module>
|
||||
<module>infinispan</module>
|
||||
<module>mongo</module>
|
||||
</modules>
|
||||
|
||||
|
|
|
@ -121,6 +121,17 @@ public class Config {
|
|||
return v != null ? Boolean.parseBoolean(v) : defaultValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Scope scope(String... scope) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(prefix + ".");
|
||||
for (String s : scope) {
|
||||
sb.append(s);
|
||||
sb.append(".");
|
||||
}
|
||||
return new SystemPropertiesScope(sb.toString());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -146,5 +157,7 @@ public class Config {
|
|||
|
||||
Boolean getBoolean(String key, Boolean defaultValue);
|
||||
|
||||
Scope scope(String... scope);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,12 +7,18 @@ import java.util.Date;
|
|||
*/
|
||||
public class Time {
|
||||
|
||||
private static int offset;
|
||||
|
||||
public static int currentTime() {
|
||||
return (int) (System.currentTimeMillis() / 1000);
|
||||
return ((int) (System.currentTimeMillis() / 1000)) + offset;
|
||||
}
|
||||
|
||||
public static Date toDate(int time) {
|
||||
return new Date(((long) time ) * 1000);
|
||||
}
|
||||
|
||||
public static void setOffset(int offset) {
|
||||
Time.offset = offset;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
15
dependencies/server-all/pom.xml
vendored
15
dependencies/server-all/pom.xml
vendored
|
@ -26,6 +26,11 @@
|
|||
<artifactId>keycloak-connections-jpa</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-connections-infinispan</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-model-jpa</artifactId>
|
||||
|
@ -46,6 +51,16 @@
|
|||
<artifactId>keycloak-model-sessions-mongo</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-model-sessions-infinispan</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-invalidation-cache-infinispan</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-events-jpa</artifactId>
|
||||
|
|
|
@ -9,32 +9,18 @@ public interface UserSessionModel {
|
|||
|
||||
String getId();
|
||||
|
||||
void setId(String id);
|
||||
|
||||
UserModel getUser();
|
||||
|
||||
void setUser(UserModel user);
|
||||
|
||||
String getLoginUsername();
|
||||
|
||||
void setLoginUsername(String loginUsername);
|
||||
|
||||
String getIpAddress();
|
||||
|
||||
void setIpAddress(String ipAddress);
|
||||
|
||||
String getAuthMethod();
|
||||
|
||||
void setAuthMethod(String authMethod);
|
||||
|
||||
boolean isRememberMe();
|
||||
|
||||
void setRememberMe(boolean rememberMe);
|
||||
|
||||
int getStarted();
|
||||
|
||||
void setStarted(int started);
|
||||
|
||||
int getLastSessionRefresh();
|
||||
|
||||
void setLastSessionRefresh(int seconds);
|
||||
|
|
|
@ -28,7 +28,6 @@ public interface UserSessionProvider extends Provider {
|
|||
|
||||
UsernameLoginFailureModel getUserLoginFailure(RealmModel realm, String username);
|
||||
UsernameLoginFailureModel addUserLoginFailure(RealmModel realm, String username);
|
||||
List<UsernameLoginFailureModel> getAllUserLoginFailures(RealmModel realm);
|
||||
|
||||
void onRealmRemoved(RealmModel realm);
|
||||
void onClientRemoved(RealmModel realm, ClientModel client);
|
||||
|
|
38
model/invalidation-cache/infinispan/pom.xml
Executable file
38
model/invalidation-cache/infinispan/pom.xml
Executable file
|
@ -0,0 +1,38 @@
|
|||
<?xml version="1.0"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.1.0-Alpha1-SNAPSHOT</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>keycloak-invalidation-cache-infinispan</artifactId>
|
||||
<name>Keycloak Invalidation Cache Infinispan</name>
|
||||
<description/>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-model-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-invalidation-cache-model</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-connections-infinispan</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.infinispan</groupId>
|
||||
<artifactId>infinispan-core</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
|
@ -0,0 +1,42 @@
|
|||
package org.keycloak.models.cache.infinispan;
|
||||
|
||||
import org.infinispan.Cache;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.cache.CacheRealmProvider;
|
||||
import org.keycloak.models.cache.CacheRealmProviderFactory;
|
||||
import org.keycloak.models.cache.DefaultCacheRealmProvider;
|
||||
import org.keycloak.models.cache.RealmCache;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class InfinispanCacheRealmProviderFactory implements CacheRealmProviderFactory {
|
||||
|
||||
protected final ConcurrentHashMap<String, String> realmLookup = new ConcurrentHashMap<String, String>();
|
||||
|
||||
@Override
|
||||
public CacheRealmProvider create(KeycloakSession session) {
|
||||
Cache<String, Object> cache = session.getProvider(InfinispanConnectionProvider.class).getCache("realms");
|
||||
RealmCache realmCache = new InfinispanRealmCache(cache, realmLookup);
|
||||
return new DefaultCacheRealmProvider(realmCache, session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "infinispan";
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
package org.keycloak.models.cache.infinispan;
|
||||
|
||||
import org.infinispan.Cache;
|
||||
import org.infinispan.notifications.Listener;
|
||||
import org.infinispan.notifications.cachelistener.annotation.CacheEntryCreated;
|
||||
import org.infinispan.notifications.cachelistener.annotation.CacheEntryRemoved;
|
||||
import org.infinispan.notifications.cachelistener.event.CacheEntryCreatedEvent;
|
||||
import org.infinispan.notifications.cachelistener.event.CacheEntryRemovedEvent;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.cache.CacheUserProvider;
|
||||
import org.keycloak.models.cache.CacheUserProviderFactory;
|
||||
import org.keycloak.models.cache.DefaultCacheUserProvider;
|
||||
import org.keycloak.models.cache.entities.CachedUser;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class InfinispanCacheUserProviderFactory implements CacheUserProviderFactory {
|
||||
|
||||
protected InfinispanUserCache userCache;
|
||||
|
||||
protected final RealmLookup usernameLookup = new RealmLookup();
|
||||
|
||||
protected final RealmLookup emailLookup = new RealmLookup();
|
||||
|
||||
@Override
|
||||
public CacheUserProvider create(KeycloakSession session) {
|
||||
lazyInit(session);
|
||||
return new DefaultCacheUserProvider(userCache, session);
|
||||
}
|
||||
|
||||
private void lazyInit(KeycloakSession session) {
|
||||
if (userCache == null) {
|
||||
synchronized (this) {
|
||||
if (userCache == null) {
|
||||
Cache<String, CachedUser> cache = session.getProvider(InfinispanConnectionProvider.class).getCache("users");
|
||||
cache.addListener(new CacheListener());
|
||||
userCache = new InfinispanUserCache(cache, usernameLookup, emailLookup);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "infinispan";
|
||||
}
|
||||
|
||||
@Listener
|
||||
private class CacheListener {
|
||||
|
||||
@CacheEntryCreated
|
||||
public void userCreated(CacheEntryCreatedEvent<String, CachedUser> event) {
|
||||
if (!event.isPre() && event.getValue() != null) {
|
||||
CachedUser cachedUser = event.getValue();
|
||||
String realm = cachedUser.getRealm();
|
||||
usernameLookup.put(realm, cachedUser.getUsername(), cachedUser.getId());
|
||||
if (cachedUser.getEmail() != null) {
|
||||
emailLookup.put(realm, cachedUser.getEmail(), cachedUser.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@CacheEntryRemoved
|
||||
public void userRemoved(CacheEntryRemovedEvent<String, CachedUser> event) {
|
||||
if (event.isPre() && event.getValue() != null) {
|
||||
CachedUser cachedUser = event.getValue();
|
||||
String realm = cachedUser.getRealm();
|
||||
usernameLookup.remove(realm, cachedUser.getUsername());
|
||||
if (cachedUser.getEmail() != null) {
|
||||
emailLookup.remove(realm, cachedUser.getEmail());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class RealmLookup {
|
||||
|
||||
protected final ConcurrentHashMap<String, ConcurrentHashMap<String, String>> lookup = new ConcurrentHashMap<String, ConcurrentHashMap<String, String>>();
|
||||
|
||||
public void put(String realm, String key, String value) {
|
||||
ConcurrentHashMap<String, String> map = lookup.get(realm);
|
||||
if(map == null) {
|
||||
map = new ConcurrentHashMap<String, String>();
|
||||
ConcurrentHashMap<String, String> p = lookup.putIfAbsent(realm, map);
|
||||
if (p != null) {
|
||||
map = p;
|
||||
}
|
||||
}
|
||||
map.put(key, value);
|
||||
}
|
||||
|
||||
public String get(String realm, String key) {
|
||||
ConcurrentHashMap<String, String> map = lookup.get(realm);
|
||||
return map != null ? map.get(key) : null;
|
||||
}
|
||||
|
||||
public void remove(String realm, String key) {
|
||||
ConcurrentHashMap<String, String> map = lookup.get(realm);
|
||||
if (map != null) {
|
||||
map.remove(key);
|
||||
if (map.isEmpty()) {
|
||||
lookup.remove(realm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
166
model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanRealmCache.java
vendored
Executable file
166
model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanRealmCache.java
vendored
Executable file
|
@ -0,0 +1,166 @@
|
|||
package org.keycloak.models.cache.infinispan;
|
||||
|
||||
import org.infinispan.Cache;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.models.cache.RealmCache;
|
||||
import org.keycloak.models.cache.entities.CachedApplication;
|
||||
import org.keycloak.models.cache.entities.CachedOAuthClient;
|
||||
import org.keycloak.models.cache.entities.CachedRealm;
|
||||
import org.keycloak.models.cache.entities.CachedRole;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class InfinispanRealmCache implements RealmCache {
|
||||
|
||||
protected static final Logger logger = Logger.getLogger(InfinispanRealmCache.class);
|
||||
|
||||
protected final Cache<String, Object> cache;
|
||||
protected final ConcurrentHashMap<String, String> realmLookup;
|
||||
protected volatile boolean enabled = true;
|
||||
|
||||
public InfinispanRealmCache(Cache<String, Object> cache, ConcurrentHashMap<String, String> realmLookup) {
|
||||
this.cache = cache;
|
||||
this.realmLookup = realmLookup;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
cache.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnabled(boolean enabled) {
|
||||
clear();
|
||||
this.enabled = enabled;
|
||||
clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CachedRealm getCachedRealm(String id) {
|
||||
if (!enabled) return null;
|
||||
return get(id, CachedRealm.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateCachedRealm(CachedRealm realm) {
|
||||
logger.tracev("Invalidating realm {0}", realm.getId());
|
||||
cache.remove(realm.getId());
|
||||
realmLookup.remove(realm.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateCachedRealmById(String id) {
|
||||
CachedRealm cached = (CachedRealm) cache.remove(id);
|
||||
if (cached != null) realmLookup.remove(cached.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCachedRealm(CachedRealm realm) {
|
||||
if (!enabled) return;
|
||||
logger.tracev("Adding realm {0}", realm.getId());
|
||||
cache.put(realm.getId(), realm);
|
||||
realmLookup.put(realm.getName(), realm.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CachedRealm getCachedRealmByName(String name) {
|
||||
if (!enabled) return null;
|
||||
String id = realmLookup.get(name);
|
||||
return id != null ? getCachedRealm(id) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CachedApplication getApplication(String id) {
|
||||
if (!enabled) return null;
|
||||
return get(id, CachedApplication.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateApplication(CachedApplication app) {
|
||||
logger.tracev("Removing application {0}", app.getId());
|
||||
cache.remove(app.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCachedApplication(CachedApplication app) {
|
||||
if (!enabled) return;
|
||||
logger.tracev("Adding application {0}", app.getId());
|
||||
cache.put(app.getId(), app);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateCachedApplicationById(String id) {
|
||||
logger.tracev("Removing application {0}", id);
|
||||
cache.remove(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CachedOAuthClient getOAuthClient(String id) {
|
||||
if (!enabled) return null;
|
||||
return get(id, CachedOAuthClient.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateOAuthClient(CachedOAuthClient client) {
|
||||
logger.tracev("Removing oauth client {0}", client.getId());
|
||||
cache.remove(client.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCachedOAuthClient(CachedOAuthClient client) {
|
||||
if (!enabled) return;
|
||||
logger.tracev("Adding oauth client {0}", client.getId());
|
||||
cache.put(client.getId(), client);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateCachedOAuthClientById(String id) {
|
||||
logger.tracev("Removing oauth client {0}", id);
|
||||
cache.remove(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CachedRole getRole(String id) {
|
||||
if (!enabled) return null;
|
||||
return get(id, CachedRole.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateRole(CachedRole role) {
|
||||
logger.tracev("Removing role {0}", role.getId());
|
||||
cache.remove(role);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateRoleById(String id) {
|
||||
logger.tracev("Removing role {0}", id);
|
||||
cache.remove(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCachedRole(CachedRole role) {
|
||||
if (!enabled) return;
|
||||
logger.tracev("Adding role {0}", role.getId());
|
||||
cache.put(role.getId(), role);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateCachedRoleById(String id) {
|
||||
logger.tracev("Removing role {0}", id);
|
||||
cache.remove(id);
|
||||
}
|
||||
|
||||
private <T> T get(String id, Class<T> type) {
|
||||
Object o = cache.get(id);
|
||||
return o != null && type.isInstance(o) ? type.cast(o) : null;
|
||||
}
|
||||
|
||||
}
|
95
model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanUserCache.java
vendored
Executable file
95
model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanUserCache.java
vendored
Executable file
|
@ -0,0 +1,95 @@
|
|||
package org.keycloak.models.cache.infinispan;
|
||||
|
||||
import org.infinispan.Cache;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.models.cache.UserCache;
|
||||
import org.keycloak.models.cache.entities.CachedUser;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class InfinispanUserCache implements UserCache {
|
||||
|
||||
protected static final Logger logger = Logger.getLogger(InfinispanRealmCache.class);
|
||||
|
||||
protected volatile boolean enabled = true;
|
||||
|
||||
protected final Cache<String, CachedUser> cache;
|
||||
|
||||
protected final InfinispanCacheUserProviderFactory.RealmLookup usernameLookup;
|
||||
|
||||
protected final InfinispanCacheUserProviderFactory.RealmLookup emailLookup;
|
||||
|
||||
public InfinispanUserCache(Cache<String, CachedUser> cache, InfinispanCacheUserProviderFactory.RealmLookup usernameLookup, InfinispanCacheUserProviderFactory.RealmLookup emailLookup) {
|
||||
this.cache = cache;
|
||||
this.usernameLookup = usernameLookup;
|
||||
this.emailLookup = emailLookup;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnabled(boolean enabled) {
|
||||
clear();
|
||||
this.enabled = enabled;
|
||||
clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CachedUser getCachedUser(String realmId, String id) {
|
||||
if (realmId == null || id == null) return null;
|
||||
CachedUser user = cache.get(id);
|
||||
return user != null && realmId.equals(user.getRealm()) ? user : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateCachedUser(String realmId, CachedUser user) {
|
||||
logger.tracev("Invalidating user {0}", user.getId());
|
||||
cache.remove(user.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateCachedUserById(String realmId, String id) {
|
||||
logger.tracev("Invalidating user {0}", id);
|
||||
cache.remove(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCachedUser(String realmId, CachedUser user) {
|
||||
logger.tracev("Adding user {0}", user.getId());
|
||||
cache.put(user.getId(), user);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CachedUser getCachedUserByUsername(String realmId, String name) {
|
||||
String id = usernameLookup.get(realmId, name);
|
||||
return id != null ? getCachedUser(realmId, id) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CachedUser getCachedUserByEmail(String realmId, String email) {
|
||||
String id = emailLookup.get(realmId, email);
|
||||
return id != null ? getCachedUser(realmId, id) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateRealmUsers(String realmId) {
|
||||
logger.tracev("Invalidating users for realm {0}", realmId);
|
||||
for (Map.Entry<String, CachedUser> u : cache.entrySet()) {
|
||||
if (u.getValue().getRealm().equals(realmId)) {
|
||||
cache.remove(u.getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
cache.clear();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
org.keycloak.models.cache.infinispan.InfinispanCacheRealmProviderFactory
|
|
@ -0,0 +1 @@
|
|||
org.keycloak.models.cache.infinispan.InfinispanCacheUserProviderFactory
|
|
@ -18,6 +18,7 @@ import java.util.Set;
|
|||
*/
|
||||
public class CachedUser {
|
||||
private String id;
|
||||
private String realm;
|
||||
private String username;
|
||||
private String firstName;
|
||||
private String lastName;
|
||||
|
@ -34,6 +35,7 @@ public class CachedUser {
|
|||
|
||||
public CachedUser(RealmModel realm, UserModel user) {
|
||||
this.id = user.getId();
|
||||
this.realm = realm.getId();
|
||||
this.username = user.getUsername();
|
||||
this.firstName = user.getFirstName();
|
||||
this.lastName = user.getLastName();
|
||||
|
@ -54,6 +56,10 @@ public class CachedUser {
|
|||
return id;
|
||||
}
|
||||
|
||||
public String getRealm() {
|
||||
return realm;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
|
|
@ -26,5 +26,6 @@
|
|||
</build>
|
||||
<modules>
|
||||
<module>model-adapters</module>
|
||||
<module>infinispan</module>
|
||||
</modules>
|
||||
</project>
|
||||
|
|
|
@ -32,5 +32,6 @@
|
|||
<module>sessions-jpa</module>
|
||||
<module>sessions-mem</module>
|
||||
<module>sessions-mongo</module>
|
||||
<module>sessions-infinispan</module>
|
||||
</modules>
|
||||
</project>
|
||||
|
|
41
model/sessions-infinispan/pom.xml
Executable file
41
model/sessions-infinispan/pom.xml
Executable file
|
@ -0,0 +1,41 @@
|
|||
<?xml version="1.0"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.1.0-Alpha1-SNAPSHOT</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>keycloak-model-sessions-infinispan</artifactId>
|
||||
<name>Keycloak Model Sessions Infinispan</name>
|
||||
<description/>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-model-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-connections-infinispan</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.infinispan</groupId>
|
||||
<artifactId>infinispan-core</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
|
@ -0,0 +1,151 @@
|
|||
package org.keycloak.models.sessions.infinispan;
|
||||
|
||||
import org.infinispan.Cache;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.sessions.infinispan.entities.ClientSessionEntity;
|
||||
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class ClientSessionAdapter implements ClientSessionModel {
|
||||
|
||||
private KeycloakSession session;
|
||||
private InfinispanUserSessionProvider provider;
|
||||
private Cache<String, SessionEntity> cache;
|
||||
private RealmModel realm;
|
||||
private ClientSessionEntity entity;
|
||||
|
||||
public ClientSessionAdapter(KeycloakSession session, InfinispanUserSessionProvider provider, Cache<String, SessionEntity> cache, RealmModel realm, ClientSessionEntity entity) {
|
||||
this.session = session;
|
||||
this.provider = provider;
|
||||
this.cache = cache;
|
||||
this.realm = realm;
|
||||
this.entity = entity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return entity.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RealmModel getRealm() {
|
||||
return realm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientModel getClient() {
|
||||
return realm.findClientById(entity.getClient());
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserSessionModel getUserSession() {
|
||||
return entity.getUserSession() != null ? provider.getUserSession(realm, entity.getUserSession()) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUserSession(UserSessionModel userSession) {
|
||||
if (entity.getUserSession() != null) {
|
||||
if (entity.getUserSession().equals(userSession.getId())) {
|
||||
return;
|
||||
} else {
|
||||
provider.dettachSession(userSession, this);
|
||||
}
|
||||
} else {
|
||||
provider.attachSession(userSession, this);
|
||||
}
|
||||
|
||||
entity.setUserSession(userSession.getId());
|
||||
update();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRedirectUri() {
|
||||
return entity.getRedirectUri();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRedirectUri(String uri) {
|
||||
entity.setRedirectUri(uri);
|
||||
update();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTimestamp() {
|
||||
return entity.getTimestamp();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTimestamp(int timestamp) {
|
||||
entity.setTimestamp(timestamp);
|
||||
update();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action getAction() {
|
||||
return entity.getAction();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAction(Action action) {
|
||||
entity.setAction(action);
|
||||
update();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getRoles() {
|
||||
return entity.getRoles();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRoles(Set<String> roles) {
|
||||
entity.setRoles(roles);
|
||||
update();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAuthMethod() {
|
||||
return entity.getAuthMethod();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAuthMethod(String authMethod) {
|
||||
entity.setAuthMethod(authMethod);
|
||||
update();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNote(String name) {
|
||||
return entity.getNotes() != null ? entity.getNotes().get(name) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNote(String name, String value) {
|
||||
if (entity.getNotes() == null) {
|
||||
entity.setNotes(new HashMap<String, String>());
|
||||
}
|
||||
entity.getNotes().put(name, value);
|
||||
update();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeNote(String name) {
|
||||
if (entity.getNotes() != null) {
|
||||
entity.getNotes().remove(name);
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
void update() {
|
||||
provider.getTx().replace(cache, entity.getId(), entity);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,439 @@
|
|||
package org.keycloak.models.sessions.infinispan;
|
||||
|
||||
import org.infinispan.Cache;
|
||||
import org.infinispan.distexec.mapreduce.MapReduceTask;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakTransaction;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.UserSessionProvider;
|
||||
import org.keycloak.models.UsernameLoginFailureModel;
|
||||
import org.keycloak.models.sessions.infinispan.entities.ClientSessionEntity;
|
||||
import org.keycloak.models.sessions.infinispan.entities.LoginFailureEntity;
|
||||
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
|
||||
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
|
||||
import org.keycloak.models.sessions.infinispan.mapreduce.ClientSessionMapper;
|
||||
import org.keycloak.models.sessions.infinispan.mapreduce.FirstResultReducer;
|
||||
import org.keycloak.models.sessions.infinispan.mapreduce.LargestResultReducer;
|
||||
import org.keycloak.models.sessions.infinispan.mapreduce.SessionMapper;
|
||||
import org.keycloak.models.sessions.infinispan.mapreduce.UserSessionMapper;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.util.Time;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class InfinispanUserSessionProvider implements UserSessionProvider {
|
||||
|
||||
private final KeycloakSession session;
|
||||
private final Cache<String, SessionEntity> sessionCache;
|
||||
private final Cache<String, LoginFailureEntity> loginFailureCache;
|
||||
private final InfinispanKeycloakTransaction tx;
|
||||
|
||||
public InfinispanUserSessionProvider(KeycloakSession session, Cache<String, SessionEntity> sessionCache, Cache<String, LoginFailureEntity> loginFailureCache) {
|
||||
this.session = session;
|
||||
this.sessionCache = sessionCache;
|
||||
this.loginFailureCache = loginFailureCache;
|
||||
this.tx = new InfinispanKeycloakTransaction();
|
||||
|
||||
session.getTransaction().enlistAfterCompletion(tx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientSessionModel createClientSession(RealmModel realm, ClientModel client) {
|
||||
String id = KeycloakModelUtils.generateId();
|
||||
|
||||
ClientSessionEntity entity = new ClientSessionEntity();
|
||||
entity.setId(id);
|
||||
entity.setRealm(realm.getId());
|
||||
entity.setTimestamp(Time.currentTime());
|
||||
entity.setClient(client.getId());
|
||||
|
||||
tx.put(sessionCache, id, entity);
|
||||
|
||||
return wrap(realm, entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserSessionModel createUserSession(RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe) {
|
||||
String id = KeycloakModelUtils.generateId();
|
||||
|
||||
UserSessionEntity entity = new UserSessionEntity();
|
||||
entity.setId(id);
|
||||
entity.setRealm(realm.getId());
|
||||
entity.setUser(user.getId());
|
||||
entity.setLoginUsername(loginUsername);
|
||||
entity.setIpAddress(ipAddress);
|
||||
entity.setAuthMethod(authMethod);
|
||||
entity.setRememberMe(rememberMe);
|
||||
|
||||
int currentTime = Time.currentTime();
|
||||
|
||||
entity.setStarted(currentTime);
|
||||
entity.setLastSessionRefresh(currentTime);
|
||||
|
||||
tx.put(sessionCache, id, entity);
|
||||
|
||||
return wrap(realm, entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientSessionModel getClientSession(RealmModel realm, String id) {
|
||||
ClientSessionEntity entity = (ClientSessionEntity) sessionCache.get(id);
|
||||
return wrap(realm, entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientSessionModel getClientSession(String id) {
|
||||
ClientSessionEntity entity = (ClientSessionEntity) sessionCache.get(id);
|
||||
if (entity != null) {
|
||||
RealmModel realm = session.realms().getRealm(entity.getRealm());
|
||||
return wrap(realm, entity);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserSessionModel getUserSession(RealmModel realm, String id) {
|
||||
UserSessionEntity entity = (UserSessionEntity) sessionCache.get(id);
|
||||
return wrap(realm, entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserSessionModel> getUserSessions(RealmModel realm, UserModel user) {
|
||||
Map<String, UserSessionEntity> sessions = new MapReduceTask(sessionCache)
|
||||
.mappedWith(UserSessionMapper.create(realm.getId()).user(user.getId()))
|
||||
.reducedWith(new FirstResultReducer())
|
||||
.execute();
|
||||
|
||||
return wrapUserSessions(realm, sessions.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserSessionModel> getUserSessions(RealmModel realm, ClientModel client) {
|
||||
return getUserSessions(realm, client, -1, -1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserSessionModel> getUserSessions(RealmModel realm, ClientModel client, int firstResult, int maxResults) {
|
||||
Map<String, Integer> map = new MapReduceTask(sessionCache)
|
||||
.mappedWith(ClientSessionMapper.create(realm.getId()).client(client.getId()).emitUserSessionAndTimestamp())
|
||||
.reducedWith(new LargestResultReducer())
|
||||
.execute();
|
||||
|
||||
List<Map.Entry<String, Integer>> sessionTimestamps = new LinkedList<Map.Entry<String, Integer>>(map.entrySet());
|
||||
|
||||
Collections.sort(sessionTimestamps, new Comparator<Map.Entry<String, Integer>>() {
|
||||
@Override
|
||||
public int compare(Map.Entry<String, Integer> e1, Map.Entry<String, Integer> e2) {
|
||||
return e1.getValue().compareTo(e2.getValue());
|
||||
}
|
||||
});
|
||||
|
||||
if (firstResult != -1 || maxResults == -1) {
|
||||
if (firstResult == -1) {
|
||||
firstResult = 0;
|
||||
}
|
||||
|
||||
if (maxResults == -1) {
|
||||
maxResults = Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
if (firstResult > sessionTimestamps.size()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
int toIndex = (firstResult + maxResults) < sessionTimestamps.size() ? firstResult + maxResults : sessionTimestamps.size();
|
||||
sessionTimestamps = sessionTimestamps.subList(firstResult, toIndex);
|
||||
}
|
||||
|
||||
List<UserSessionModel> userSessions = new LinkedList<UserSessionModel>();
|
||||
for (Map.Entry<String, Integer> e : sessionTimestamps) {
|
||||
UserSessionEntity userSessionEntity = (UserSessionEntity) sessionCache.get(e.getKey());
|
||||
if (userSessionEntity != null) {
|
||||
userSessions.add(wrap(realm, userSessionEntity));
|
||||
}
|
||||
}
|
||||
|
||||
return userSessions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getActiveUserSessions(RealmModel realm, ClientModel client) {
|
||||
Map map = new MapReduceTask(sessionCache)
|
||||
.mappedWith(ClientSessionMapper.create(realm.getId()).client(client.getId()).emitUserSessionAndTimestamp())
|
||||
.reducedWith(new LargestResultReducer()).execute();
|
||||
|
||||
return map.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUserSession(RealmModel realm, UserSessionModel session) {
|
||||
removeUserSession(realm, session.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUserSessions(RealmModel realm, UserModel user) {
|
||||
Map<String, String> sessions = new MapReduceTask(sessionCache)
|
||||
.mappedWith(UserSessionMapper.create(realm.getId()).user(user.getId()).emitKey())
|
||||
.reducedWith(new FirstResultReducer())
|
||||
.execute();
|
||||
|
||||
for (String id : sessions.keySet()) {
|
||||
removeUserSession(realm, id);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeExpiredUserSessions(RealmModel realm) {
|
||||
int expired = Time.currentTime() - realm.getSsoSessionMaxLifespan();
|
||||
int expiredRefresh = Time.currentTime() - realm.getSsoSessionIdleTimeout();
|
||||
|
||||
Map<String, String> map = new MapReduceTask(sessionCache)
|
||||
.mappedWith(UserSessionMapper.create(realm.getId()).expired(expired, expiredRefresh).emitKey())
|
||||
.reducedWith(new FirstResultReducer())
|
||||
.execute();
|
||||
|
||||
for (String id : map.keySet()) {
|
||||
removeUserSession(realm, id);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUserSessions(RealmModel realm) {
|
||||
Map<String, String> ids = new MapReduceTask(sessionCache)
|
||||
.mappedWith(SessionMapper.create(realm.getId()).emitKey())
|
||||
.reducedWith(new FirstResultReducer())
|
||||
.execute();
|
||||
|
||||
for (String id : ids.keySet()) {
|
||||
sessionCache.remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public UsernameLoginFailureModel getUserLoginFailure(RealmModel realm, String username) {
|
||||
return wrap(loginFailureCache.get(realm.getId() + ":" + username));
|
||||
}
|
||||
|
||||
@Override
|
||||
public UsernameLoginFailureModel addUserLoginFailure(RealmModel realm, String username) {
|
||||
LoginFailureEntity entity = new LoginFailureEntity();
|
||||
entity.setRealm(realm.getId());
|
||||
entity.setUsername(username);
|
||||
tx.put(loginFailureCache, entity.getId(), entity);
|
||||
return wrap(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRealmRemoved(RealmModel realm) {
|
||||
removeUserSessions(realm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClientRemoved(RealmModel realm, ClientModel client) {
|
||||
Map<String, String> map = new MapReduceTask(sessionCache)
|
||||
.mappedWith(ClientSessionMapper.create(realm.getId()).client(client.getId()).emitKey())
|
||||
.reducedWith(new FirstResultReducer())
|
||||
.execute();
|
||||
|
||||
for (String id : map.keySet()) {
|
||||
tx.remove(sessionCache, id);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUserRemoved(RealmModel realm, UserModel user) {
|
||||
removeUserSessions(realm, user);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
void attachSession(UserSessionModel userSession, ClientSessionModel clientSession) {
|
||||
UserSessionEntity entity = ((UserSessionAdapter) userSession).getEntity();
|
||||
String clientSessionId = clientSession.getId();
|
||||
if (entity.getClientSessions() == null) {
|
||||
entity.setClientSessions(new HashSet<String>());
|
||||
}
|
||||
if (!entity.getClientSessions().contains(clientSessionId)) {
|
||||
entity.getClientSessions().add(clientSessionId);
|
||||
tx.replace(sessionCache, entity.getId(), entity);
|
||||
}
|
||||
}
|
||||
|
||||
void dettachSession(UserSessionModel userSession, ClientSessionModel clientSession) {
|
||||
UserSessionEntity entity = ((UserSessionAdapter) userSession).getEntity();
|
||||
String clientSessionId = clientSession.getId();
|
||||
if (entity.getClientSessions() != null && entity.getClientSessions().contains(clientSessionId)) {
|
||||
entity.getClientSessions().remove(clientSessionId);
|
||||
if (entity.getClientSessions().isEmpty()) {
|
||||
entity.setClientSessions(null);
|
||||
}
|
||||
tx.replace(sessionCache, entity.getId(), entity);
|
||||
}
|
||||
}
|
||||
|
||||
void removeUserSession(RealmModel realm, String userSessionId) {
|
||||
tx.remove(sessionCache, userSessionId);
|
||||
|
||||
Map<String, String> map = new MapReduceTask(sessionCache)
|
||||
.mappedWith(ClientSessionMapper.create(realm.getId()).userSession(userSessionId).emitKey())
|
||||
.reducedWith(new FirstResultReducer())
|
||||
.execute();
|
||||
|
||||
for (String id : map.keySet()) {
|
||||
tx.remove(sessionCache, id);
|
||||
}
|
||||
}
|
||||
|
||||
InfinispanKeycloakTransaction getTx() {
|
||||
return tx;
|
||||
}
|
||||
|
||||
UserSessionModel wrap(RealmModel realm, UserSessionEntity entity) {
|
||||
return entity != null ? new UserSessionAdapter(session, this, sessionCache, realm, entity) : null;
|
||||
}
|
||||
|
||||
List<UserSessionModel> wrapUserSessions(RealmModel realm, Collection<UserSessionEntity> entities) {
|
||||
List<UserSessionModel> models = new LinkedList<UserSessionModel>();
|
||||
for (UserSessionEntity e : entities) {
|
||||
models.add(wrap(realm, e));
|
||||
}
|
||||
return models;
|
||||
}
|
||||
|
||||
ClientSessionModel wrap(RealmModel realm, ClientSessionEntity entity) {
|
||||
return entity != null ? new ClientSessionAdapter(session, this, sessionCache, realm, entity) : null;
|
||||
}
|
||||
|
||||
|
||||
UsernameLoginFailureModel wrap(LoginFailureEntity entity) {
|
||||
return entity != null ? new UsernameLoginFailureAdapter(this, loginFailureCache, entity) : null;
|
||||
}
|
||||
|
||||
List<ClientSessionModel> wrapClientSessions(RealmModel realm, Collection<ClientSessionEntity> entities) {
|
||||
List<ClientSessionModel> models = new LinkedList<ClientSessionModel>();
|
||||
for (ClientSessionEntity e : entities) {
|
||||
models.add(wrap(realm, e));
|
||||
}
|
||||
return models;
|
||||
}
|
||||
|
||||
class InfinispanKeycloakTransaction implements KeycloakTransaction {
|
||||
|
||||
private boolean active;
|
||||
private boolean rollback;
|
||||
private Map<String, CacheTask> tasks = new HashMap<String, CacheTask>();
|
||||
|
||||
@Override
|
||||
public void begin() {
|
||||
active = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commit() {
|
||||
if (rollback) {
|
||||
throw new RuntimeException("Rollback only!");
|
||||
}
|
||||
|
||||
for (CacheTask task : tasks.values()) {
|
||||
task.execute();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rollback() {
|
||||
tasks.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRollbackOnly() {
|
||||
rollback = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getRollbackOnly() {
|
||||
return rollback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
return active;
|
||||
}
|
||||
|
||||
public void put(Cache cache, String key, Object value) {
|
||||
if (tasks.containsKey(key)) {
|
||||
throw new IllegalStateException("Can't add session: task in progress for session");
|
||||
} else {
|
||||
tasks.put(key, new CacheTask(cache, CacheOperation.ADD, key, value));
|
||||
}
|
||||
}
|
||||
|
||||
public void replace(Cache cache, String key, Object value) {
|
||||
CacheTask current = tasks.get(key);
|
||||
if (current != null) {
|
||||
switch (current.operation) {
|
||||
case ADD:
|
||||
case REPLACE:
|
||||
current.value = value;
|
||||
return;
|
||||
case REMOVE:
|
||||
throw new IllegalStateException("Can't remove session: task in progress for session");
|
||||
}
|
||||
} else {
|
||||
tasks.put(key, new CacheTask(cache, CacheOperation.ADD, key, value));
|
||||
}
|
||||
}
|
||||
|
||||
public void remove(Cache cache, String key) {
|
||||
tasks.put(key, new CacheTask(cache, CacheOperation.REMOVE, key, null));
|
||||
}
|
||||
|
||||
public class CacheTask {
|
||||
private Cache cache;
|
||||
private CacheOperation operation;
|
||||
private String key;
|
||||
private Object value;
|
||||
|
||||
public CacheTask(Cache cache, CacheOperation operation, String key, Object value) {
|
||||
this.cache = cache;
|
||||
this.operation = operation;
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public void execute() {
|
||||
switch (operation) {
|
||||
case ADD:
|
||||
cache.put(key, value);
|
||||
break;
|
||||
case REMOVE:
|
||||
cache.remove(key);
|
||||
break;
|
||||
case REPLACE:
|
||||
cache.replace(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public enum CacheOperation {
|
||||
ADD, REMOVE, REPLACE
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
package org.keycloak.models.sessions.infinispan;
|
||||
|
||||
import org.infinispan.Cache;
|
||||
import org.infinispan.configuration.cache.CacheMode;
|
||||
import org.infinispan.configuration.cache.ConfigurationBuilder;
|
||||
import org.infinispan.configuration.global.GlobalConfigurationBuilder;
|
||||
import org.infinispan.manager.DefaultCacheManager;
|
||||
import org.infinispan.manager.EmbeddedCacheManager;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.UserSessionProvider;
|
||||
import org.keycloak.models.UserSessionProviderFactory;
|
||||
import org.keycloak.models.sessions.infinispan.entities.LoginFailureEntity;
|
||||
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
|
||||
|
||||
import javax.naming.InitialContext;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class InfinispanUserSessionProviderFactory implements UserSessionProviderFactory {
|
||||
|
||||
private static final String SESSION_CACHE_NAME = "sessions";
|
||||
private static final String LOGIN_FAILURE_CACHE_NAME = "loginFailures";
|
||||
|
||||
@Override
|
||||
public UserSessionProvider create(KeycloakSession session) {
|
||||
InfinispanConnectionProvider connections = session.getProvider(InfinispanConnectionProvider.class);
|
||||
Cache<String, SessionEntity> cache = connections.getCache(SESSION_CACHE_NAME);
|
||||
Cache<String, LoginFailureEntity> loginFailures = connections.getCache(LOGIN_FAILURE_CACHE_NAME);
|
||||
return new InfinispanUserSessionProvider(session, cache, loginFailures);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "infinispan";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
package org.keycloak.models.sessions.infinispan;
|
||||
|
||||
import org.infinispan.Cache;
|
||||
import org.infinispan.distexec.mapreduce.MapReduceTask;
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
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.entities.ClientSessionEntity;
|
||||
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
|
||||
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
|
||||
import org.keycloak.models.sessions.infinispan.mapreduce.ClientSessionMapper;
|
||||
import org.keycloak.models.sessions.infinispan.mapreduce.FirstResultReducer;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class UserSessionAdapter implements UserSessionModel {
|
||||
|
||||
private final KeycloakSession session;
|
||||
|
||||
private final InfinispanUserSessionProvider provider;
|
||||
|
||||
private final Cache<String, SessionEntity> cache;
|
||||
|
||||
private final RealmModel realm;
|
||||
|
||||
private final UserSessionEntity entity;
|
||||
|
||||
public UserSessionAdapter(KeycloakSession session, InfinispanUserSessionProvider provider, Cache<String, SessionEntity> cache, RealmModel realm, UserSessionEntity entity) {
|
||||
this.session = session;
|
||||
this.provider = provider;
|
||||
this.cache = cache;
|
||||
this.realm = realm;
|
||||
this.entity = entity;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return entity.getId();
|
||||
}
|
||||
|
||||
public UserModel getUser() {
|
||||
return session.users().getUserById(entity.getUser(), realm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLoginUsername() {
|
||||
return entity.getLoginUsername();
|
||||
}
|
||||
|
||||
public String getIpAddress() {
|
||||
return entity.getIpAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAuthMethod() {
|
||||
return entity.getAuthMethod();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRememberMe() {
|
||||
return entity.isRememberMe();
|
||||
}
|
||||
|
||||
public int getStarted() {
|
||||
return entity.getStarted();
|
||||
}
|
||||
|
||||
public int getLastSessionRefresh() {
|
||||
return entity.getLastSessionRefresh();
|
||||
}
|
||||
|
||||
public void setLastSessionRefresh(int lastSessionRefresh) {
|
||||
entity.setLastSessionRefresh(lastSessionRefresh);
|
||||
update();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ClientSessionModel> getClientSessions() {
|
||||
if (entity.getClientSessions() != null) {
|
||||
List<ClientSessionEntity> clientSessions = new LinkedList<ClientSessionEntity>();
|
||||
for (String c : entity.getClientSessions()) {
|
||||
ClientSessionEntity clientSession = (ClientSessionEntity) cache.get(c);
|
||||
if (clientSession != null) {
|
||||
clientSessions.add(clientSession);
|
||||
}
|
||||
}
|
||||
return provider.wrapClientSessions(realm, clientSessions);
|
||||
} else {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || !(o instanceof UserSessionModel)) return false;
|
||||
|
||||
UserSessionModel that = (UserSessionModel) o;
|
||||
return that.getId().equals(getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getId().hashCode();
|
||||
}
|
||||
|
||||
UserSessionEntity getEntity() {
|
||||
return entity;
|
||||
}
|
||||
|
||||
void update() {
|
||||
provider.getTx().replace(cache, entity.getId(), entity);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
package org.keycloak.models.sessions.infinispan;
|
||||
|
||||
import org.infinispan.Cache;
|
||||
import org.keycloak.models.UsernameLoginFailureModel;
|
||||
import org.keycloak.models.sessions.infinispan.entities.LoginFailureEntity;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class UsernameLoginFailureAdapter implements UsernameLoginFailureModel {
|
||||
|
||||
private InfinispanUserSessionProvider provider;
|
||||
private Cache<String, LoginFailureEntity> cache;
|
||||
private LoginFailureEntity entity;
|
||||
|
||||
public UsernameLoginFailureAdapter(InfinispanUserSessionProvider provider, Cache<String, LoginFailureEntity> cache, LoginFailureEntity entity) {
|
||||
this.provider = provider;
|
||||
this.cache = cache;
|
||||
this.entity = entity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsername() {
|
||||
return entity.getUsername();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFailedLoginNotBefore() {
|
||||
return entity.getFailedLoginNotBefore();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFailedLoginNotBefore(int notBefore) {
|
||||
entity.setFailedLoginNotBefore(notBefore);
|
||||
update();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNumFailures() {
|
||||
return entity.getNumFailures();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void incrementFailures() {
|
||||
entity.setNumFailures(getNumFailures() + 1);
|
||||
update();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearFailures() {
|
||||
entity.setNumFailures(0);
|
||||
update();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLastFailure() {
|
||||
return entity.getLastFailure();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLastFailure(long lastFailure) {
|
||||
entity.setLastFailure(lastFailure);
|
||||
update();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLastIPFailure() {
|
||||
return entity.getLastIPFailure();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLastIPFailure(String ip) {
|
||||
entity.setLastIPFailure(ip);
|
||||
update();
|
||||
}
|
||||
|
||||
void update() {
|
||||
provider.getTx().replace(cache, entity.getId(), entity);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
package org.keycloak.models.sessions.infinispan.entities;
|
||||
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class ClientSessionEntity extends SessionEntity {
|
||||
|
||||
private String client;
|
||||
|
||||
private String userSession;
|
||||
|
||||
private String authMethod;
|
||||
|
||||
private String redirectUri;
|
||||
|
||||
private String state;
|
||||
|
||||
private int timestamp;
|
||||
|
||||
private ClientSessionModel.Action action;
|
||||
|
||||
private Set<String> roles;
|
||||
private Map<String, String> notes;
|
||||
|
||||
public String getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
public void setClient(String client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
public String getUserSession() {
|
||||
return userSession;
|
||||
}
|
||||
|
||||
public void setUserSession(String userSession) {
|
||||
this.userSession = userSession;
|
||||
}
|
||||
|
||||
public String getAuthMethod() {
|
||||
return authMethod;
|
||||
}
|
||||
|
||||
public void setAuthMethod(String authMethod) {
|
||||
this.authMethod = authMethod;
|
||||
}
|
||||
|
||||
public String getRedirectUri() {
|
||||
return redirectUri;
|
||||
}
|
||||
|
||||
public void setRedirectUri(String redirectUri) {
|
||||
this.redirectUri = redirectUri;
|
||||
}
|
||||
|
||||
public String getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public void setState(String state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public int getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public void setTimestamp(int timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public ClientSessionModel.Action getAction() {
|
||||
return action;
|
||||
}
|
||||
|
||||
public void setAction(ClientSessionModel.Action action) {
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
public Set<String> getRoles() {
|
||||
return roles;
|
||||
}
|
||||
|
||||
public void setRoles(Set<String> roles) {
|
||||
this.roles = roles;
|
||||
}
|
||||
|
||||
public Map<String, String> getNotes() {
|
||||
return notes;
|
||||
}
|
||||
|
||||
public void setNotes(Map<String, String> notes) {
|
||||
this.notes = notes;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
package org.keycloak.models.sessions.infinispan.entities;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class LoginFailureEntity {
|
||||
|
||||
private String username;
|
||||
private String realm;
|
||||
private int failedLoginNotBefore;
|
||||
private int numFailures;
|
||||
private long lastFailure;
|
||||
private String lastIPFailure;
|
||||
|
||||
public String getId() {
|
||||
return realm + ":" + username;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getRealm() {
|
||||
return realm;
|
||||
}
|
||||
|
||||
public void setRealm(String realm) {
|
||||
this.realm = realm;
|
||||
}
|
||||
|
||||
public int getFailedLoginNotBefore() {
|
||||
return failedLoginNotBefore;
|
||||
}
|
||||
|
||||
public void setFailedLoginNotBefore(int failedLoginNotBefore) {
|
||||
this.failedLoginNotBefore = failedLoginNotBefore;
|
||||
}
|
||||
|
||||
public int getNumFailures() {
|
||||
return numFailures;
|
||||
}
|
||||
|
||||
public void setNumFailures(int numFailures) {
|
||||
this.numFailures = numFailures;
|
||||
}
|
||||
|
||||
public long getLastFailure() {
|
||||
return lastFailure;
|
||||
}
|
||||
|
||||
public void setLastFailure(long lastFailure) {
|
||||
this.lastFailure = lastFailure;
|
||||
}
|
||||
|
||||
public String getLastIPFailure() {
|
||||
return lastIPFailure;
|
||||
}
|
||||
|
||||
public void setLastIPFailure(String lastIPFailure) {
|
||||
this.lastIPFailure = lastIPFailure;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package org.keycloak.models.sessions.infinispan.entities;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class SessionEntity implements Serializable {
|
||||
|
||||
private String id;
|
||||
|
||||
private String realm;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getRealm() {
|
||||
return realm;
|
||||
}
|
||||
|
||||
public void setRealm(String realm) {
|
||||
this.realm = realm;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
package org.keycloak.models.sessions.infinispan.entities;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class UserSessionEntity extends SessionEntity {
|
||||
|
||||
private String user;
|
||||
|
||||
private String loginUsername;
|
||||
|
||||
private String ipAddress;
|
||||
|
||||
private String authMethod;
|
||||
|
||||
private boolean rememberMe;
|
||||
|
||||
private int started;
|
||||
|
||||
private int lastSessionRefresh;
|
||||
|
||||
private Set<String> clientSessions;
|
||||
|
||||
public String getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
public void setUser(String user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
public String getLoginUsername() {
|
||||
return loginUsername;
|
||||
}
|
||||
|
||||
public void setLoginUsername(String loginUsername) {
|
||||
this.loginUsername = loginUsername;
|
||||
}
|
||||
|
||||
public String getIpAddress() {
|
||||
return ipAddress;
|
||||
}
|
||||
|
||||
public void setIpAddress(String ipAddress) {
|
||||
this.ipAddress = ipAddress;
|
||||
}
|
||||
|
||||
public String getAuthMethod() {
|
||||
return authMethod;
|
||||
}
|
||||
|
||||
public void setAuthMethod(String authMethod) {
|
||||
this.authMethod = authMethod;
|
||||
}
|
||||
|
||||
public boolean isRememberMe() {
|
||||
return rememberMe;
|
||||
}
|
||||
|
||||
public void setRememberMe(boolean rememberMe) {
|
||||
this.rememberMe = rememberMe;
|
||||
}
|
||||
|
||||
public int getStarted() {
|
||||
return started;
|
||||
}
|
||||
|
||||
public void setStarted(int started) {
|
||||
this.started = started;
|
||||
}
|
||||
|
||||
public int getLastSessionRefresh() {
|
||||
return lastSessionRefresh;
|
||||
}
|
||||
|
||||
public void setLastSessionRefresh(int lastSessionRefresh) {
|
||||
this.lastSessionRefresh = lastSessionRefresh;
|
||||
}
|
||||
|
||||
public Set<String> getClientSessions() {
|
||||
return clientSessions;
|
||||
}
|
||||
|
||||
public void setClientSessions(Set<String> clientSessions) {
|
||||
this.clientSessions = clientSessions;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
package org.keycloak.models.sessions.infinispan.mapreduce;
|
||||
|
||||
import org.infinispan.distexec.mapreduce.Collector;
|
||||
import org.infinispan.distexec.mapreduce.Mapper;
|
||||
import org.keycloak.models.sessions.infinispan.entities.ClientSessionEntity;
|
||||
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class ClientSessionMapper implements Mapper<String, SessionEntity, String, Object>, Serializable {
|
||||
|
||||
public ClientSessionMapper(String realm) {
|
||||
this.realm = realm;
|
||||
}
|
||||
|
||||
private enum EmitValue {
|
||||
KEY, ENTITY, USER_SESSION_AND_TIMESTAMP
|
||||
}
|
||||
|
||||
private String realm;
|
||||
|
||||
private EmitValue emit = EmitValue.ENTITY;
|
||||
|
||||
private String client;
|
||||
|
||||
private String userSession;
|
||||
|
||||
public static ClientSessionMapper create(String realm) {
|
||||
return new ClientSessionMapper(realm);
|
||||
}
|
||||
|
||||
public ClientSessionMapper emitKey() {
|
||||
emit = EmitValue.KEY;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClientSessionMapper emitUserSessionAndTimestamp() {
|
||||
emit = EmitValue.USER_SESSION_AND_TIMESTAMP;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClientSessionMapper client(String client) {
|
||||
this.client = client;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClientSessionMapper userSession(String userSession) {
|
||||
this.userSession = userSession;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void map(String key, SessionEntity e, Collector collector) {
|
||||
if (!realm.equals(e.getRealm())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(e instanceof ClientSessionEntity)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ClientSessionEntity entity = (ClientSessionEntity) e;
|
||||
|
||||
if (client != null && !entity.getClient().equals(client)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (userSession != null && !userSession.equals(entity.getUserSession())) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (emit) {
|
||||
case KEY:
|
||||
collector.emit(key, key);
|
||||
break;
|
||||
case ENTITY:
|
||||
collector.emit(key, entity);
|
||||
break;
|
||||
case USER_SESSION_AND_TIMESTAMP:
|
||||
collector.emit(entity.getUserSession(), entity.getTimestamp());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package org.keycloak.models.sessions.infinispan.mapreduce;
|
||||
|
||||
import org.infinispan.distexec.mapreduce.Reducer;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class FirstResultReducer implements Reducer<Object, Object>, Serializable {
|
||||
|
||||
@Override
|
||||
public Object reduce(Object reducedKey, Iterator<Object> itr) {
|
||||
return itr.next();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package org.keycloak.models.sessions.infinispan.mapreduce;
|
||||
|
||||
import org.infinispan.distexec.mapreduce.Reducer;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class LargestResultReducer implements Reducer<String, Integer> {
|
||||
|
||||
@Override
|
||||
public Integer reduce(String reducedKey, Iterator<Integer> itr) {
|
||||
Integer largest = itr.next();
|
||||
while (itr.hasNext()) {
|
||||
Integer next = itr.next();
|
||||
if (next > largest) {
|
||||
largest = next;
|
||||
}
|
||||
}
|
||||
return largest;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package org.keycloak.models.sessions.infinispan.mapreduce;
|
||||
|
||||
import org.infinispan.distexec.mapreduce.Collector;
|
||||
import org.infinispan.distexec.mapreduce.Mapper;
|
||||
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
|
||||
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class SessionMapper implements Mapper<String, SessionEntity, String, Object>, Serializable {
|
||||
|
||||
public SessionMapper(String realm) {
|
||||
this.realm = realm;
|
||||
}
|
||||
|
||||
private enum EmitValue {
|
||||
KEY, ENTITY
|
||||
}
|
||||
|
||||
private String realm;
|
||||
|
||||
private EmitValue emit = EmitValue.ENTITY;
|
||||
|
||||
public static SessionMapper create(String realm) {
|
||||
return new SessionMapper(realm);
|
||||
}
|
||||
|
||||
public SessionMapper emitKey() {
|
||||
emit = EmitValue.KEY;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void map(String key, SessionEntity e, Collector collector) {
|
||||
if (!realm.equals(e.getRealm())) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (emit) {
|
||||
case KEY:
|
||||
collector.emit(key, key);
|
||||
break;
|
||||
case ENTITY:
|
||||
collector.emit(key, e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
package org.keycloak.models.sessions.infinispan.mapreduce;
|
||||
|
||||
import org.infinispan.distexec.mapreduce.Collector;
|
||||
import org.infinispan.distexec.mapreduce.Mapper;
|
||||
import org.keycloak.models.sessions.infinispan.entities.ClientSessionEntity;
|
||||
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
|
||||
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class UserSessionMapper implements Mapper<String, SessionEntity, String, Object>, Serializable {
|
||||
|
||||
public UserSessionMapper(String realm) {
|
||||
this.realm = realm;
|
||||
}
|
||||
|
||||
private enum EmitValue {
|
||||
KEY, ENTITY
|
||||
}
|
||||
|
||||
private String realm;
|
||||
|
||||
private EmitValue emit = EmitValue.ENTITY;
|
||||
|
||||
private String user;
|
||||
|
||||
private Long expired;
|
||||
|
||||
private Long expiredRefresh;
|
||||
|
||||
public static UserSessionMapper create(String realm) {
|
||||
return new UserSessionMapper(realm);
|
||||
}
|
||||
|
||||
public UserSessionMapper emitKey() {
|
||||
emit = EmitValue.KEY;
|
||||
return this;
|
||||
}
|
||||
|
||||
public UserSessionMapper user(String user) {
|
||||
this.user = user;
|
||||
return this;
|
||||
}
|
||||
|
||||
public UserSessionMapper expired(long expired, long expiredRefresh) {
|
||||
this.expired = expired;
|
||||
this.expiredRefresh = expiredRefresh;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void map(String key, SessionEntity e, Collector collector) {
|
||||
if (!(e instanceof UserSessionEntity)) {
|
||||
return;
|
||||
}
|
||||
|
||||
UserSessionEntity entity = (UserSessionEntity) e;
|
||||
|
||||
if (!realm.equals(entity.getRealm())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (user != null && !entity.getUser().equals(user)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (expired != null && expiredRefresh != null && entity.getStarted() > expired && entity.getLastSessionRefresh() > expiredRefresh) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (emit) {
|
||||
case KEY:
|
||||
collector.emit(key, key);
|
||||
break;
|
||||
case ENTITY:
|
||||
collector.emit(key, entity);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
org.keycloak.models.sessions.infinispan.InfinispanUserSessionProviderFactory
|
|
@ -86,17 +86,6 @@ public class JpaUserSessionProvider implements UserSessionProvider {
|
|||
return new UsernameLoginFailureAdapter(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UsernameLoginFailureModel> getAllUserLoginFailures(RealmModel realm) {
|
||||
TypedQuery<UsernameLoginFailureEntity> query = em.createNamedQuery("getAllFailures", UsernameLoginFailureEntity.class);
|
||||
List<UsernameLoginFailureEntity> entities = query.getResultList();
|
||||
List<UsernameLoginFailureModel> models = new ArrayList<UsernameLoginFailureModel>();
|
||||
for (UsernameLoginFailureEntity entity : entities) {
|
||||
models.add(new UsernameLoginFailureAdapter(entity));
|
||||
}
|
||||
return models;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserSessionModel createUserSession(RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe) {
|
||||
UserSessionEntity entity = new UserSessionEntity();
|
||||
|
|
|
@ -38,71 +38,36 @@ public class UserSessionAdapter implements UserSessionModel {
|
|||
return entity.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setId(String id) {
|
||||
entity.setId(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserModel getUser() {
|
||||
return session.users().getUserById(entity.getUserId(), realm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUser(UserModel user) {
|
||||
entity.setUserId(user.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLoginUsername() {
|
||||
return entity.getLoginUsername();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLoginUsername(String loginUsername) {
|
||||
entity.setLoginUsername(loginUsername);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIpAddress() {
|
||||
return entity.getIpAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIpAddress(String ipAddress) {
|
||||
entity.setIpAddress(ipAddress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAuthMethod() {
|
||||
return entity.getAuthMethod();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAuthMethod(String authMethod) {
|
||||
entity.setAuthMethod(authMethod);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRememberMe() {
|
||||
return entity.isRememberMe();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRememberMe(boolean rememberMe) {
|
||||
entity.setRememberMe(rememberMe);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStarted() {
|
||||
return entity.getStarted();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStarted(int started) {
|
||||
entity.setStarted(started);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLastSessionRefresh() {
|
||||
return entity.getLastSessionRefresh();
|
||||
|
|
|
@ -239,17 +239,6 @@ public class MemUserSessionProvider implements UserSessionProvider {
|
|||
return new UsernameLoginFailureAdapter(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UsernameLoginFailureModel> getAllUserLoginFailures(RealmModel realm) {
|
||||
List<UsernameLoginFailureModel> failures = new LinkedList<UsernameLoginFailureModel>();
|
||||
for (UsernameLoginFailureEntity entity : loginFailures.values()) {
|
||||
if (entity.getRealm().equals(realm.getId())) {
|
||||
failures.add(new UsernameLoginFailureAdapter(entity));
|
||||
}
|
||||
}
|
||||
return failures;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRealmRemoved(RealmModel realm) {
|
||||
removeUserSessions(realm);
|
||||
|
|
|
@ -55,47 +55,24 @@ public class UserSessionAdapter implements UserSessionModel {
|
|||
return entity.getLoginUsername();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLoginUsername(String loginUsername) {
|
||||
entity.setLoginUsername(loginUsername);
|
||||
}
|
||||
|
||||
public String getIpAddress() {
|
||||
return entity.getIpAddress();
|
||||
}
|
||||
|
||||
public void setIpAddress(String ipAddress) {
|
||||
entity.setIpAddress(ipAddress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAuthMethod() {
|
||||
return entity.getAuthMethod();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAuthMethod(String authMethod) {
|
||||
entity.setAuthMethod(authMethod);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRememberMe() {
|
||||
return entity.isRememberMe();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRememberMe(boolean rememberMe) {
|
||||
entity.setRememberMe(rememberMe);
|
||||
}
|
||||
|
||||
public int getStarted() {
|
||||
return entity.getStarted();
|
||||
}
|
||||
|
||||
public void setStarted(int started) {
|
||||
entity.setStarted(started);
|
||||
}
|
||||
|
||||
public int getLastSessionRefresh() {
|
||||
return entity.getLastSessionRefresh();
|
||||
}
|
||||
|
|
|
@ -221,22 +221,6 @@ public class MongoUserSessionProvider implements UserSessionProvider {
|
|||
return new UsernameLoginFailureAdapter(invocationContext, userEntity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UsernameLoginFailureModel> getAllUserLoginFailures(RealmModel realm) {
|
||||
DBObject query = new QueryBuilder()
|
||||
.and("realmId").is(realm.getId())
|
||||
.get();
|
||||
List<MongoUsernameLoginFailureEntity> failures = mongoStore.loadEntities(MongoUsernameLoginFailureEntity.class, query, invocationContext);
|
||||
|
||||
List<UsernameLoginFailureModel> result = new LinkedList<UsernameLoginFailureModel>();
|
||||
if (failures == null) return result;
|
||||
for (MongoUsernameLoginFailureEntity failure : failures) {
|
||||
result.add(new UsernameLoginFailureAdapter(invocationContext, failure));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRealmRemoved(RealmModel realm) {
|
||||
removeUserSessions(realm);
|
||||
|
|
|
@ -18,8 +18,6 @@ import java.util.List;
|
|||
*/
|
||||
public class UserSessionAdapter extends AbstractMongoAdapter<MongoUserSessionEntity> implements UserSessionModel {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(UserSessionAdapter.class);
|
||||
|
||||
private final MongoUserSessionProvider provider;
|
||||
private MongoUserSessionEntity entity;
|
||||
private RealmModel realm;
|
||||
|
@ -45,78 +43,36 @@ public class UserSessionAdapter extends AbstractMongoAdapter<MongoUserSessionEnt
|
|||
return entity.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setId(String id) {
|
||||
entity.setId(id);
|
||||
updateMongoEntity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserModel getUser() {
|
||||
return keycloakSession.users().getUserById(entity.getUser(), realm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUser(UserModel user) {
|
||||
entity.setUser(user.getId());
|
||||
updateMongoEntity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLoginUsername() {
|
||||
return entity.getLoginUsername();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLoginUsername(String loginUsername) {
|
||||
entity.setLoginUsername(loginUsername);
|
||||
updateMongoEntity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIpAddress() {
|
||||
return entity.getIpAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIpAddress(String ipAddress) {
|
||||
entity.setIpAddress(ipAddress);
|
||||
updateMongoEntity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAuthMethod() {
|
||||
return entity.getAuthMethod();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAuthMethod(String authMethod) {
|
||||
entity.setAuthMethod(authMethod);
|
||||
updateMongoEntity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRememberMe() {
|
||||
return entity.isRememberMe();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRememberMe(boolean rememberMe) {
|
||||
entity.setRememberMe(rememberMe);
|
||||
updateMongoEntity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStarted() {
|
||||
return entity.getStarted();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStarted(int started) {
|
||||
entity.setStarted(started);
|
||||
updateMongoEntity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLastSessionRefresh() {
|
||||
return entity.getLastSessionRefresh();
|
||||
|
|
6
pom.xml
6
pom.xml
|
@ -43,6 +43,7 @@
|
|||
<twitter4j.version>3.0.5</twitter4j.version>
|
||||
<selenium.version>2.35.0</selenium.version>
|
||||
<javax.mail.version>1.4.5</javax.mail.version>
|
||||
<infinispan.version>6.0.2.Final</infinispan.version>
|
||||
|
||||
<!-- maven-compiler-plugin -->
|
||||
<maven.compiler.target>1.6</maven.compiler.target>
|
||||
|
@ -437,6 +438,11 @@
|
|||
<artifactId>jboss-logging-processor</artifactId>
|
||||
<version>${jboss-logging-tools.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.infinispan</groupId>
|
||||
<artifactId>infinispan-core</artifactId>
|
||||
<version>${infinispan.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
<module name="org.jboss.resteasy.resteasy-jackson-provider" services="import"/>
|
||||
<module name="org.codehaus.jackson.jackson-core-asl"/>
|
||||
<module name="org.codehaus.jackson.jackson-mapper-asl"/>
|
||||
<module name="org.infinispan"/>
|
||||
</dependencies>
|
||||
<exclusions>
|
||||
<module name="org.jboss.resteasy.resteasy-jackson2-provider"/>
|
||||
|
|
|
@ -82,13 +82,12 @@ public class AuthenticationManager {
|
|||
UserModel user = userSession.getUser();
|
||||
|
||||
logger.debugv("Logging out: {0} ({1})", user.getUsername(), userSession.getId());
|
||||
|
||||
session.sessions().removeUserSession(realm, userSession);
|
||||
expireIdentityCookie(realm, uriInfo, connection);
|
||||
expireRememberMeCookie(realm, uriInfo, connection);
|
||||
|
||||
new ResourceAdminManager().logoutUser(uriInfo.getRequestUri(), realm, user.getId(), userSession);
|
||||
|
||||
session.sessions().removeUserSession(realm, userSession);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -142,7 +142,7 @@ public class UsersResource {
|
|||
if (session.users().getUserByUsername(rep.getUsername(), realm) != null) {
|
||||
return Flows.errors().exists("User exists with same username");
|
||||
}
|
||||
if (session.users().getUserByEmail(rep.getEmail(), realm) != null) {
|
||||
if (rep.getEmail() != null && session.users().getUserByEmail(rep.getEmail(), realm) != null) {
|
||||
return Flows.errors().exists("User exists with same email");
|
||||
}
|
||||
|
||||
|
|
|
@ -17,17 +17,20 @@ public class JsonConfigProvider implements Config.ConfigProvider {
|
|||
|
||||
@Override
|
||||
public String getProvider(String spi) {
|
||||
JsonNode n = getNode(spi, "provider");
|
||||
JsonNode n = getNode(config, spi, "provider");
|
||||
return n != null ? StringPropertyReplacer.replaceProperties(n.getTextValue()) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Config.Scope scope(String... path) {
|
||||
return new JsonScope(getNode(path));
|
||||
return new JsonScope(getNode(config, path));
|
||||
}
|
||||
|
||||
private JsonNode getNode(String... path) {
|
||||
JsonNode n = config;
|
||||
private static JsonNode getNode(JsonNode root, String... path) {
|
||||
if (root == null) {
|
||||
return null;
|
||||
}
|
||||
JsonNode n = root;
|
||||
for (String p : path) {
|
||||
n = n.get(p);
|
||||
if (n == null) {
|
||||
|
@ -144,6 +147,11 @@ public class JsonConfigProvider implements Config.ConfigProvider {
|
|||
return n.getBooleanValue();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Config.Scope scope(String... path) {
|
||||
return new JsonScope(getNode(config, path));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
135
testsuite/docker-cluster/README.md
Normal file
135
testsuite/docker-cluster/README.md
Normal file
|
@ -0,0 +1,135 @@
|
|||
How to test Keycloak cluster with Docker
|
||||
========================================
|
||||
Docker+Fig allows to easily setup and test the whole environment with:
|
||||
* Apache HTTPD 2.4 + modcluster 1.3 as Load Balancer
|
||||
* MySQL 5.6.1 as database
|
||||
* Various number of Keycloak cluster nodes running on WildFly (with "demo" examples deployed)
|
||||
|
||||
You don't need to setup Apache with modcluster + MySQL on your laptop as Docker will do it for you and all will run in Docker containers.
|
||||
|
||||
Steps to setup
|
||||
--------------
|
||||
1) Download and install [Docker](https://docs.docker.com/installation) and [Fig](http://www.fig.sh/install.html)
|
||||
|
||||
2) Build Keycloak including distribution. This will be used by Docker+Fig. The point is that you can test clustering stuff from latest Keycloak master:
|
||||
```shell
|
||||
$ cd $KEYCLOAK_HOME
|
||||
$ mvn clean install
|
||||
$ cd distribution
|
||||
$ mvn clean install
|
||||
````
|
||||
|
||||
3) Build Docker with maven to ensure that needed data will be accessible to Docker+Fig volumes:
|
||||
```shell
|
||||
$ cd $KEYCLOAK_HOME/testsuite/docker-cluster
|
||||
$ mvn clean install
|
||||
````
|
||||
|
||||
4) Build fig and run the whole env. By default it will run Apache + MySQL + 1 Keycloak node:
|
||||
```shell
|
||||
$ fig build
|
||||
$ fig up
|
||||
````
|
||||
|
||||
First build will take long time as it need to download bunch of stuff and install into Docker container. Next builds will be much faster due to Docker cache.
|
||||
After some time, WildFly server is started
|
||||
|
||||
Testing
|
||||
-------
|
||||
|
||||
Apache is running in separate container and have 2 ports exposed locally: 10001 and 8000. Port 10001 is for modCluster - you should
|
||||
be able to access Apache modCluster status page: [http://localhost:10001/mod_cluster_manager](http://localhost:10001/mod_cluster_manager) and see one node
|
||||
with deployed "auth-server.war" and few other WARs (keycloak demo).
|
||||
|
||||
Also you can access Keycloak admin console via loadBalancer on [http://localhost:8000/auth/admin](http://localhost:8000/auth/admin) and similarly Account mgmt.
|
||||
TODO: Examples currently doesn't work and I am looking at it..
|
||||
|
||||
MySQL can be directly accessed from your machine (if you have MySQL client installed):
|
||||
```shell
|
||||
$ mysql -h127.0.0.1 -P33306 -uroot -pmysecretpassword
|
||||
````
|
||||
Used database is "keycloak_db"
|
||||
|
||||
Remote debugging
|
||||
----------------
|
||||
|
||||
With command:
|
||||
```shell
|
||||
$ docker ps
|
||||
````
|
||||
|
||||
You can see running ports. For the Keycloak node you may see output similar to this:
|
||||
```shell
|
||||
0.0.0.0:49153->8080/tcp, 0.0.0.0:49154->8787/tcp, 0.0.0.0:49155->9990/tcp
|
||||
````
|
||||
|
||||
This means that you can directly access Keycloak (bypass loadbalancer) by going to [http://localhost:49153/auth/admin](http://localhost:49153/auth/admin) .
|
||||
Also it means that debugger is mapped From Docker port 8787 to local port 49154 . So in your IDE you can connect with settings similar to:
|
||||
```shell
|
||||
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=49154
|
||||
````
|
||||
|
||||
Scale / more cluster nodes
|
||||
--------------------------
|
||||
|
||||
Run this in separate terminal to add more (in this case 2) cluster nodes:
|
||||
```shell
|
||||
$ fig scale node=2
|
||||
````
|
||||
|
||||
Now it should be visible on mod_cluster_manager page that they are 2 nodes.
|
||||
|
||||
Seeing logs
|
||||
-----------
|
||||
It's easiest to do:
|
||||
```shell
|
||||
$ fig logs
|
||||
````
|
||||
to see output of MySql and Keycloak server consoles.
|
||||
|
||||
To see Apache and debug logs of keycloak server:
|
||||
```shell
|
||||
$ fig run node /bin/bash
|
||||
````
|
||||
|
||||
Then you're in shell inside docker container, which has some mounted volumes with apache logs and keycloak nodes. Apache logs are at:
|
||||
```shell
|
||||
$ cd /apachelogs/
|
||||
````
|
||||
|
||||
Keycloak nodes are at (debug logging enabled by default for "org.keycloak"):
|
||||
```shell
|
||||
$ cd /keycloak-docker/shared
|
||||
````
|
||||
|
||||
Restart whole environment
|
||||
-------------------------
|
||||
|
||||
Just run:
|
||||
```shell
|
||||
$ fig stop
|
||||
$ fig start
|
||||
````
|
||||
|
||||
This will restart apache + MySQL + all nodes, but won't clear data.
|
||||
|
||||
Changing configuration and clear data
|
||||
-------------------------------------
|
||||
Changing configuration (for example UserSession provider from 'mem' to 'jpa') is possible in
|
||||
```shell
|
||||
$KEYCLOAK_HOME/testsuite/docker-cluster/target/keycloak-docker-cluster/deployments/auth-server.war/WEB-INF/classes/META-INF/keycloak-server.json
|
||||
````
|
||||
|
||||
then whole environment needs to be stopped, containers removed (in order to update configuration in nodes) and started again:
|
||||
```shell
|
||||
$ fig stop
|
||||
$ fig rm
|
||||
$ fig up
|
||||
````
|
||||
|
||||
Rebuilding after changed sources
|
||||
-------------------------------
|
||||
In this case you might need to stop and remove existing containers. Then start from step 2 (Rebuild Keycloak or at least
|
||||
changed jars, then rebuild distribution and testsuite/docker-cluster
|
||||
(or just copy changed JAR into $KEYCLOAK_HOME/testsuite/docker-cluster/target/keycloak-docker-cluster/deployments/auth-server.war/WEB-INF/lib if it's not adapter stuff.
|
||||
But 'fig rm' is safer to call anyway)
|
30
testsuite/docker-cluster/assembly.xml
Normal file
30
testsuite/docker-cluster/assembly.xml
Normal file
|
@ -0,0 +1,30 @@
|
|||
<assembly>
|
||||
<id>docker-cluster</id>
|
||||
|
||||
<formats>
|
||||
<format>dir</format>
|
||||
</formats>
|
||||
<includeBaseDirectory>false</includeBaseDirectory>
|
||||
|
||||
<fileSets>
|
||||
<fileSet>
|
||||
<directory>../../examples/demo-template</directory>
|
||||
<outputDirectory>examples</outputDirectory>
|
||||
<includes>
|
||||
<include>**/target/*.war</include>
|
||||
<include>**/testrealm.json</include>
|
||||
</includes>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>target/deployments</directory>
|
||||
<outputDirectory>deployments</outputDirectory>
|
||||
<excludes>
|
||||
<exclude>**/keycloak-ds.xml</exclude>
|
||||
</excludes>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>target/modules</directory>
|
||||
<outputDirectory>modules</outputDirectory>
|
||||
</fileSet>
|
||||
</fileSets>
|
||||
</assembly>
|
31
testsuite/docker-cluster/fig.yml
Normal file
31
testsuite/docker-cluster/fig.yml
Normal file
|
@ -0,0 +1,31 @@
|
|||
httpd:
|
||||
build: httpd
|
||||
ports:
|
||||
- "8000:80"
|
||||
- "10001:10001"
|
||||
volumes_from:
|
||||
- mysql
|
||||
mysql:
|
||||
image: mysql:5.6.20
|
||||
environment:
|
||||
- MYSQL_ROOT_PASSWORD=mysecretpassword
|
||||
- MYSQL_DATABASE=keycloak_db
|
||||
volumes:
|
||||
- /keycloak-docker-shared
|
||||
- /apachelogs
|
||||
ports:
|
||||
- "33306:3306"
|
||||
node:
|
||||
build: wildfly
|
||||
command: /keycloak-run-node.sh
|
||||
volumes:
|
||||
- target/keycloak-docker-cluster:/keycloak-docker-cluster
|
||||
volumes_from:
|
||||
- mysql
|
||||
links:
|
||||
- httpd:httpd
|
||||
- mysql:mysql
|
||||
ports:
|
||||
- "8787"
|
||||
- "8080"
|
||||
- "9990"
|
16
testsuite/docker-cluster/httpd/Dockerfile
Normal file
16
testsuite/docker-cluster/httpd/Dockerfile
Normal file
|
@ -0,0 +1,16 @@
|
|||
FROM fedora:20
|
||||
|
||||
RUN cd /
|
||||
RUN yum -y install wget && yum -y install unzip
|
||||
RUN yum clean all
|
||||
|
||||
RUN wget https://dl.dropboxusercontent.com/u/5525920/apache24-modcluster131.zip
|
||||
RUN unzip -q apache24-modcluster131.zip
|
||||
|
||||
ADD httpd-run /bin/httpd-run
|
||||
RUN chmod u+x /bin/httpd-run
|
||||
|
||||
ADD httpd.conf /opt/jboss/httpd/httpd/conf/httpd.conf
|
||||
|
||||
EXPOSE 80 10001
|
||||
CMD ["/bin/httpd-run"]
|
8
testsuite/docker-cluster/httpd/httpd-run
Normal file
8
testsuite/docker-cluster/httpd/httpd-run
Normal file
|
@ -0,0 +1,8 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Make sure we're not confused by old, incompletely-shutdown httpd
|
||||
# context after restarting the container. httpd won't start correctly
|
||||
# if it thinks it is already running.
|
||||
rm -rf /opt/jboss/httpd/httpd/logs/httpd.pid
|
||||
|
||||
exec /opt/jboss/httpd/sbin/apachectl -D FOREGROUND
|
550
testsuite/docker-cluster/httpd/httpd.conf
Normal file
550
testsuite/docker-cluster/httpd/httpd.conf
Normal file
|
@ -0,0 +1,550 @@
|
|||
#
|
||||
# This is the main Apache HTTP server configuration file. It contains the
|
||||
# configuration directives that give the server its instructions.
|
||||
# See <URL:http://httpd.apache.org/docs/2.4/> for detailed information.
|
||||
# In particular, see
|
||||
# <URL:http://httpd.apache.org/docs/2.4/mod/directives.html>
|
||||
# for a discussion of each configuration directive.
|
||||
#
|
||||
# Do NOT simply read the instructions in here without understanding
|
||||
# what they do. They're here only as hints or reminders. If you are unsure
|
||||
# consult the online docs. You have been warned.
|
||||
#
|
||||
# Configuration and logfile names: If the filenames you specify for many
|
||||
# of the server's control files begin with "/" (or "drive:/" for Win32), the
|
||||
# server will use that explicit path. If the filenames do *not* begin
|
||||
# with "/", the value of ServerRoot is prepended -- so "logs/access_log"
|
||||
# with ServerRoot set to "/usr/local/apache2" will be interpreted by the
|
||||
# server as "/usr/local/apache2/logs/access_log", whereas "/logs/access_log"
|
||||
# will be interpreted as '/logs/access_log'.
|
||||
|
||||
#
|
||||
# ServerRoot: The top of the directory tree under which the server's
|
||||
# configuration, error, and log files are kept.
|
||||
#
|
||||
# Do not add a slash at the end of the directory path. If you point
|
||||
# ServerRoot at a non-local disk, be sure to specify a local disk on the
|
||||
# Mutex directive, if file-based mutexes are used. If you wish to share the
|
||||
# same ServerRoot for multiple httpd daemons, you will need to change at
|
||||
# least PidFile.
|
||||
#
|
||||
ServerRoot "/opt/jboss/httpd/httpd"
|
||||
|
||||
#
|
||||
# Mutex: Allows you to set the mutex mechanism and mutex file directory
|
||||
# for individual mutexes, or change the global defaults
|
||||
#
|
||||
# Uncomment and change the directory if mutexes are file-based and the default
|
||||
# mutex file directory is not on a local disk or is not appropriate for some
|
||||
# other reason.
|
||||
#
|
||||
# Mutex default:logs
|
||||
|
||||
#
|
||||
# Listen: Allows you to bind Apache to specific IP addresses and/or
|
||||
# ports, instead of the default. See also the <VirtualHost>
|
||||
# directive.
|
||||
#
|
||||
# Change this to Listen on specific IP addresses as shown below to
|
||||
# prevent Apache from glomming onto all bound IP addresses.
|
||||
#
|
||||
#Listen 12.34.56.78:80
|
||||
Listen 80
|
||||
|
||||
#
|
||||
# Dynamic Shared Object (DSO) Support
|
||||
#
|
||||
# To be able to use the functionality of a module which was built as a DSO you
|
||||
# have to place corresponding `LoadModule' lines at this location so the
|
||||
# directives contained in it are actually available _before_ they are used.
|
||||
# Statically compiled modules (those listed by `httpd -l') do not need
|
||||
# to be loaded here.
|
||||
#
|
||||
# Example:
|
||||
# LoadModule foo_module modules/mod_foo.so
|
||||
#
|
||||
LoadModule authn_file_module /opt/jboss/httpd/lib/httpd/modules/mod_authn_file.so
|
||||
#LoadModule authn_dbm_module /opt/jboss/httpd/lib/httpd/modules/mod_authn_dbm.so
|
||||
#LoadModule authn_anon_module /opt/jboss/httpd/lib/httpd/modules/mod_authn_anon.so
|
||||
#LoadModule authn_dbd_module /opt/jboss/httpd/lib/httpd/modules/mod_authn_dbd.so
|
||||
#LoadModule authn_socache_module /opt/jboss/httpd/lib/httpd/modules/mod_authn_socache.so
|
||||
LoadModule authn_core_module /opt/jboss/httpd/lib/httpd/modules/mod_authn_core.so
|
||||
LoadModule authz_host_module /opt/jboss/httpd/lib/httpd/modules/mod_authz_host.so
|
||||
LoadModule authz_groupfile_module /opt/jboss/httpd/lib/httpd/modules/mod_authz_groupfile.so
|
||||
LoadModule authz_user_module /opt/jboss/httpd/lib/httpd/modules/mod_authz_user.so
|
||||
#LoadModule authz_dbm_module /opt/jboss/httpd/lib/httpd/modules/mod_authz_dbm.so
|
||||
#LoadModule authz_owner_module /opt/jboss/httpd/lib/httpd/modules/mod_authz_owner.so
|
||||
#LoadModule authz_dbd_module /opt/jboss/httpd/lib/httpd/modules/mod_authz_dbd.so
|
||||
LoadModule authz_core_module /opt/jboss/httpd/lib/httpd/modules/mod_authz_core.so
|
||||
LoadModule access_compat_module /opt/jboss/httpd/lib/httpd/modules/mod_access_compat.so
|
||||
LoadModule auth_basic_module /opt/jboss/httpd/lib/httpd/modules/mod_auth_basic.so
|
||||
#LoadModule auth_form_module /opt/jboss/httpd/lib/httpd/modules/mod_auth_form.so
|
||||
#LoadModule auth_digest_module /opt/jboss/httpd/lib/httpd/modules/mod_auth_digest.so
|
||||
#LoadModule allowmethods_module /opt/jboss/httpd/lib/httpd/modules/mod_allowmethods.so
|
||||
LoadModule advertise_module /opt/jboss/httpd/lib/httpd/modules/mod_advertise.so
|
||||
#LoadModule file_cache_module /opt/jboss/httpd/lib/httpd/modules/mod_file_cache.so
|
||||
#LoadModule cache_module /opt/jboss/httpd/lib/httpd/modules/mod_cache.so
|
||||
#LoadModule cache_disk_module /opt/jboss/httpd/lib/httpd/modules/mod_cache_disk.so
|
||||
#LoadModule cache_socache_module /opt/jboss/httpd/lib/httpd/modules/mod_cache_socache.so
|
||||
#LoadModule socache_shmcb_module /opt/jboss/httpd/lib/httpd/modules/mod_socache_shmcb.so
|
||||
#LoadModule socache_dbm_module /opt/jboss/httpd/lib/httpd/modules/mod_socache_dbm.so
|
||||
#LoadModule socache_memcache_module /opt/jboss/httpd/lib/httpd/modules/mod_socache_memcache.so
|
||||
#LoadModule watchdog_module /opt/jboss/httpd/lib/httpd/modules/mod_watchdog.so
|
||||
#LoadModule macro_module /opt/jboss/httpd/lib/httpd/modules/mod_macro.so
|
||||
#LoadModule dbd_module /opt/jboss/httpd/lib/httpd/modules/mod_dbd.so
|
||||
#LoadModule dumpio_module /opt/jboss/httpd/lib/httpd/modules/mod_dumpio.so
|
||||
#LoadModule echo_module /opt/jboss/httpd/lib/httpd/modules/mod_echo.so
|
||||
#LoadModule buffer_module /opt/jboss/httpd/lib/httpd/modules/mod_buffer.so
|
||||
#LoadModule data_module /opt/jboss/httpd/lib/httpd/modules/mod_data.so
|
||||
#LoadModule ratelimit_module /opt/jboss/httpd/lib/httpd/modules/mod_ratelimit.so
|
||||
LoadModule reqtimeout_module /opt/jboss/httpd/lib/httpd/modules/mod_reqtimeout.so
|
||||
#LoadModule ext_filter_module /opt/jboss/httpd/lib/httpd/modules/mod_ext_filter.so
|
||||
#LoadModule request_module /opt/jboss/httpd/lib/httpd/modules/mod_request.so
|
||||
#LoadModule include_module /opt/jboss/httpd/lib/httpd/modules/mod_include.so
|
||||
LoadModule filter_module /opt/jboss/httpd/lib/httpd/modules/mod_filter.so
|
||||
#LoadModule reflector_module /opt/jboss/httpd/lib/httpd/modules/mod_reflector.so
|
||||
#LoadModule substitute_module /opt/jboss/httpd/lib/httpd/modules/mod_substitute.so
|
||||
#LoadModule sed_module /opt/jboss/httpd/lib/httpd/modules/mod_sed.so
|
||||
#LoadModule charset_lite_module /opt/jboss/httpd/lib/httpd/modules/mod_charset_lite.so
|
||||
#LoadModule deflate_module /opt/jboss/httpd/lib/httpd/modules/mod_deflate.so
|
||||
#LoadModule xml2enc_module /opt/jboss/httpd/lib/httpd/modules/mod_xml2enc.so
|
||||
#LoadModule proxy_html_module /opt/jboss/httpd/lib/httpd/modules/mod_proxy_html.so
|
||||
LoadModule mime_module /opt/jboss/httpd/lib/httpd/modules/mod_mime.so
|
||||
LoadModule log_config_module /opt/jboss/httpd/lib/httpd/modules/mod_log_config.so
|
||||
#LoadModule log_debug_module /opt/jboss/httpd/lib/httpd/modules/mod_log_debug.so
|
||||
#LoadModule log_forensic_module /opt/jboss/httpd/lib/httpd/modules/mod_log_forensic.so
|
||||
#LoadModule logio_module /opt/jboss/httpd/lib/httpd/modules/mod_logio.so
|
||||
LoadModule env_module /opt/jboss/httpd/lib/httpd/modules/mod_env.so
|
||||
#LoadModule mime_magic_module /opt/jboss/httpd/lib/httpd/modules/mod_mime_magic.so
|
||||
#LoadModule expires_module /opt/jboss/httpd/lib/httpd/modules/mod_expires.so
|
||||
LoadModule headers_module /opt/jboss/httpd/lib/httpd/modules/mod_headers.so
|
||||
#LoadModule usertrack_module /opt/jboss/httpd/lib/httpd/modules/mod_usertrack.so
|
||||
#LoadModule unique_id_module /opt/jboss/httpd/lib/httpd/modules/mod_unique_id.so
|
||||
LoadModule setenvif_module /opt/jboss/httpd/lib/httpd/modules/mod_setenvif.so
|
||||
LoadModule version_module /opt/jboss/httpd/lib/httpd/modules/mod_version.so
|
||||
#LoadModule remoteip_module /opt/jboss/httpd/lib/httpd/modules/mod_remoteip.so
|
||||
LoadModule proxy_module /opt/jboss/httpd/lib/httpd/modules/mod_proxy.so
|
||||
LoadModule proxy_connect_module /opt/jboss/httpd/lib/httpd/modules/mod_proxy_connect.so
|
||||
LoadModule proxy_ftp_module /opt/jboss/httpd/lib/httpd/modules/mod_proxy_ftp.so
|
||||
LoadModule proxy_http_module /opt/jboss/httpd/lib/httpd/modules/mod_proxy_http.so
|
||||
LoadModule proxy_fcgi_module /opt/jboss/httpd/lib/httpd/modules/mod_proxy_fcgi.so
|
||||
LoadModule proxy_scgi_module /opt/jboss/httpd/lib/httpd/modules/mod_proxy_scgi.so
|
||||
#LoadModule proxy_fdpass_module /opt/jboss/httpd/lib/httpd/modules/mod_proxy_fdpass.so
|
||||
LoadModule proxy_wstunnel_module /opt/jboss/httpd/lib/httpd/modules/mod_proxy_wstunnel.so
|
||||
LoadModule proxy_ajp_module /opt/jboss/httpd/lib/httpd/modules/mod_proxy_ajp.so
|
||||
LoadModule proxy_cluster_module /opt/jboss/httpd/lib/httpd/modules/mod_proxy_cluster.so
|
||||
LoadModule proxy_express_module /opt/jboss/httpd/lib/httpd/modules/mod_proxy_express.so
|
||||
#LoadModule session_module /opt/jboss/httpd/lib/httpd/modules/mod_session.so
|
||||
#LoadModule session_cookie_module /opt/jboss/httpd/lib/httpd/modules/mod_session_cookie.so
|
||||
#LoadModule session_dbd_module /opt/jboss/httpd/lib/httpd/modules/mod_session_dbd.so
|
||||
#LoadModule slotmem_shm_module /opt/jboss/httpd/lib/httpd/modules/mod_slotmem_shm.so
|
||||
#LoadModule slotmem_plain_module /opt/jboss/httpd/lib/httpd/modules/mod_slotmem_plain.so
|
||||
#LoadModule ssl_module /opt/jboss/httpd/lib/httpd/modules/mod_ssl.so
|
||||
#LoadModule dialup_module /opt/jboss/httpd/lib/httpd/modules/mod_dialup.so
|
||||
LoadModule lbmethod_byrequests_module /opt/jboss/httpd/lib/httpd/modules/mod_lbmethod_byrequests.so
|
||||
LoadModule lbmethod_bytraffic_module /opt/jboss/httpd/lib/httpd/modules/mod_lbmethod_bytraffic.so
|
||||
LoadModule lbmethod_bybusyness_module /opt/jboss/httpd/lib/httpd/modules/mod_lbmethod_bybusyness.so
|
||||
LoadModule lbmethod_heartbeat_module /opt/jboss/httpd/lib/httpd/modules/mod_lbmethod_heartbeat.so
|
||||
LoadModule unixd_module /opt/jboss/httpd/lib/httpd/modules/mod_unixd.so
|
||||
#LoadModule heartbeat_module /opt/jboss/httpd/lib/httpd/modules/mod_heartbeat.so
|
||||
#LoadModule heartmonitor_module /opt/jboss/httpd/lib/httpd/modules/mod_heartmonitor.so
|
||||
#LoadModule dav_module /opt/jboss/httpd/lib/httpd/modules/mod_dav.so
|
||||
LoadModule status_module /opt/jboss/httpd/lib/httpd/modules/mod_status.so
|
||||
LoadModule autoindex_module /opt/jboss/httpd/lib/httpd/modules/mod_autoindex.so
|
||||
#LoadModule asis_module /opt/jboss/httpd/lib/httpd/modules/mod_asis.so
|
||||
#LoadModule info_module /opt/jboss/httpd/lib/httpd/modules/mod_info.so
|
||||
#LoadModule suexec_module /opt/jboss/httpd/lib/httpd/modules/mod_suexec.so
|
||||
#LoadModule cgi_module /opt/jboss/httpd/lib/httpd/modules/mod_cgi.so
|
||||
#LoadModule cgid_module /opt/jboss/httpd/lib/httpd/modules/mod_cgid.so
|
||||
LoadModule cluster_slotmem_module /opt/jboss/httpd/lib/httpd/modules/mod_cluster_slotmem.so
|
||||
LoadModule manager_module /opt/jboss/httpd/lib/httpd/modules/mod_manager.so
|
||||
#LoadModule dav_fs_module /opt/jboss/httpd/lib/httpd/modules/mod_dav_fs.so
|
||||
#LoadModule dav_lock_module /opt/jboss/httpd/lib/httpd/modules/mod_dav_lock.so
|
||||
#LoadModule vhost_alias_module /opt/jboss/httpd/lib/httpd/modules/mod_vhost_alias.so
|
||||
#LoadModule negotiation_module /opt/jboss/httpd/lib/httpd/modules/mod_negotiation.so
|
||||
LoadModule dir_module /opt/jboss/httpd/lib/httpd/modules/mod_dir.so
|
||||
#LoadModule actions_module /opt/jboss/httpd/lib/httpd/modules/mod_actions.so
|
||||
#LoadModule speling_module /opt/jboss/httpd/lib/httpd/modules/mod_speling.so
|
||||
#LoadModule userdir_module /opt/jboss/httpd/lib/httpd/modules/mod_userdir.so
|
||||
LoadModule alias_module /opt/jboss/httpd/lib/httpd/modules/mod_alias.so
|
||||
#LoadModule rewrite_module /opt/jboss/httpd/lib/httpd/modules/mod_rewrite.so
|
||||
|
||||
<IfModule unixd_module>
|
||||
#
|
||||
# If you wish httpd to run as a different user or group, you must run
|
||||
# httpd as root initially and it will switch.
|
||||
#
|
||||
# User/Group: The name (or #number) of the user/group to run httpd as.
|
||||
# It is usually good practice to create a dedicated user and group for
|
||||
# running httpd, as with most system services.
|
||||
#
|
||||
User daemon
|
||||
Group daemon
|
||||
|
||||
</IfModule>
|
||||
|
||||
# 'Main' server configuration
|
||||
#
|
||||
# The directives in this section set up the values used by the 'main'
|
||||
# server, which responds to any requests that aren't handled by a
|
||||
# <VirtualHost> definition. These values also provide defaults for
|
||||
# any <VirtualHost> containers you may define later in the file.
|
||||
#
|
||||
# All of these directives may appear inside <VirtualHost> containers,
|
||||
# in which case these default settings will be overridden for the
|
||||
# virtual host being defined.
|
||||
#
|
||||
|
||||
#
|
||||
# ServerAdmin: Your address, where problems with the server should be
|
||||
# e-mailed. This address appears on some server-generated pages, such
|
||||
# as error documents. e.g. admin@your-domain.com
|
||||
#
|
||||
ServerAdmin you@example.com
|
||||
|
||||
#
|
||||
# ServerName gives the name and port that the server uses to identify itself.
|
||||
# This can often be determined automatically, but we recommend you specify
|
||||
# it explicitly to prevent problems during startup.
|
||||
#
|
||||
# If your host doesn't have a registered DNS name, enter its IP address here.
|
||||
#
|
||||
#ServerName www.example.com:80
|
||||
|
||||
#
|
||||
# Deny access to the entirety of your server's filesystem. You must
|
||||
# explicitly permit access to web content directories in other
|
||||
# <Directory> blocks below.
|
||||
#
|
||||
<Directory />
|
||||
AllowOverride none
|
||||
Require all denied
|
||||
</Directory>
|
||||
|
||||
#
|
||||
# Note that from this point forward you must specifically allow
|
||||
# particular features to be enabled - so if something's not working as
|
||||
# you might expect, make sure that you have specifically enabled it
|
||||
# below.
|
||||
#
|
||||
|
||||
#
|
||||
# DocumentRoot: The directory out of which you will serve your
|
||||
# documents. By default, all requests are taken from this directory, but
|
||||
# symbolic links and aliases may be used to point to other locations.
|
||||
#
|
||||
DocumentRoot "/opt/jboss/httpd/htdocs/htdocs"
|
||||
<Directory "/opt/jboss/httpd/htdocs/htdocs">
|
||||
#
|
||||
# Possible values for the Options directive are "None", "All",
|
||||
# or any combination of:
|
||||
# Indexes Includes FollowSymLinks SymLinksifOwnerMatch ExecCGI MultiViews
|
||||
#
|
||||
# Note that "MultiViews" must be named *explicitly* --- "Options All"
|
||||
# doesn't give it to you.
|
||||
#
|
||||
# The Options directive is both complicated and important. Please see
|
||||
# http://httpd.apache.org/docs/2.4/mod/core.html#options
|
||||
# for more information.
|
||||
#
|
||||
Options Indexes FollowSymLinks
|
||||
|
||||
#
|
||||
# AllowOverride controls what directives may be placed in .htaccess files.
|
||||
# It can be "All", "None", or any combination of the keywords:
|
||||
# AllowOverride FileInfo AuthConfig Limit
|
||||
#
|
||||
AllowOverride None
|
||||
|
||||
#
|
||||
# Controls who can get stuff from this server.
|
||||
#
|
||||
Require all granted
|
||||
</Directory>
|
||||
|
||||
#
|
||||
# DirectoryIndex: sets the file that Apache will serve if a directory
|
||||
# is requested.
|
||||
#
|
||||
<IfModule dir_module>
|
||||
DirectoryIndex index.html
|
||||
</IfModule>
|
||||
|
||||
#
|
||||
# The following lines prevent .htaccess and .htpasswd files from being
|
||||
# viewed by Web clients.
|
||||
#
|
||||
<Files ".ht*">
|
||||
Require all denied
|
||||
</Files>
|
||||
|
||||
#
|
||||
# ErrorLog: The location of the error log file.
|
||||
# If you do not specify an ErrorLog directive within a <VirtualHost>
|
||||
# container, error messages relating to that virtual host will be
|
||||
# logged here. If you *do* define an error logfile for a <VirtualHost>
|
||||
# container, that host's errors will be logged there and not here.
|
||||
#
|
||||
# Note: Actually it's custom location mounted to docker volume
|
||||
ErrorLog "/apachelogs/error_log"
|
||||
|
||||
#
|
||||
# LogLevel: Control the number of messages logged to the error_log.
|
||||
# Possible values include: debug, info, notice, warn, error, crit,
|
||||
# alert, emerg.
|
||||
#
|
||||
LogLevel info
|
||||
|
||||
<IfModule log_config_module>
|
||||
#
|
||||
# The following directives define some format nicknames for use with
|
||||
# a CustomLog directive (see below).
|
||||
#
|
||||
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
|
||||
LogFormat "%h %l %u %t \"%r\" %>s %b" common
|
||||
|
||||
<IfModule logio_module>
|
||||
# You need to enable mod_logio.c to use %I and %O
|
||||
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" combinedio
|
||||
</IfModule>
|
||||
|
||||
#
|
||||
# The location and format of the access logfile (Common Logfile Format).
|
||||
# If you do not define any access logfiles within a <VirtualHost>
|
||||
# container, they will be logged here. Contrariwise, if you *do*
|
||||
# define per-<VirtualHost> access logfiles, transactions will be
|
||||
# logged therein and *not* in this file.
|
||||
#
|
||||
CustomLog "/apachelogs/access_log" common
|
||||
|
||||
#
|
||||
# If you prefer a logfile with access, agent, and referer information
|
||||
# (Combined Logfile Format) you can use the following directive.
|
||||
#
|
||||
#CustomLog "logs/access_log" combined
|
||||
</IfModule>
|
||||
|
||||
<IfModule alias_module>
|
||||
#
|
||||
# Redirect: Allows you to tell clients about documents that used to
|
||||
# exist in your server's namespace, but do not anymore. The client
|
||||
# will make a new request for the document at its new location.
|
||||
# Example:
|
||||
# Redirect permanent /foo http://www.example.com/bar
|
||||
|
||||
#
|
||||
# Alias: Maps web paths into filesystem paths and is used to
|
||||
# access content that does not live under the DocumentRoot.
|
||||
# Example:
|
||||
# Alias /webpath /full/filesystem/path
|
||||
#
|
||||
# If you include a trailing / on /webpath then the server will
|
||||
# require it to be present in the URL. You will also likely
|
||||
# need to provide a <Directory> section to allow access to
|
||||
# the filesystem path.
|
||||
|
||||
#
|
||||
# ScriptAlias: This controls which directories contain server scripts.
|
||||
# ScriptAliases are essentially the same as Aliases, except that
|
||||
# documents in the target directory are treated as applications and
|
||||
# run by the server when requested rather than as documents sent to the
|
||||
# client. The same rules about trailing "/" apply to ScriptAlias
|
||||
# directives as to Alias.
|
||||
#
|
||||
ScriptAlias /cgi-bin/ "/opt/jboss/httpd/htdocs/cgi-bin/"
|
||||
|
||||
</IfModule>
|
||||
|
||||
<IfModule cgid_module>
|
||||
#
|
||||
# ScriptSock: On threaded servers, designate the path to the UNIX
|
||||
# socket used to communicate with the CGI daemon of mod_cgid.
|
||||
#
|
||||
#Scriptsock cgisock
|
||||
</IfModule>
|
||||
|
||||
#
|
||||
# "/opt/jboss/httpd/htdocs/cgi-bin" should be changed to whatever your ScriptAliased
|
||||
# CGI directory exists, if you have that configured.
|
||||
#
|
||||
<Directory "/opt/jboss/httpd/htdocs/cgi-bin">
|
||||
AllowOverride None
|
||||
Options None
|
||||
Require all granted
|
||||
</Directory>
|
||||
|
||||
<IfModule mime_module>
|
||||
#
|
||||
# TypesConfig points to the file containing the list of mappings from
|
||||
# filename extension to MIME-type.
|
||||
#
|
||||
TypesConfig conf/mime.types
|
||||
|
||||
#
|
||||
# AddType allows you to add to or override the MIME configuration
|
||||
# file specified in TypesConfig for specific file types.
|
||||
#
|
||||
#AddType application/x-gzip .tgz
|
||||
#
|
||||
# AddEncoding allows you to have certain browsers uncompress
|
||||
# information on the fly. Note: Not all browsers support this.
|
||||
#
|
||||
#AddEncoding x-compress .Z
|
||||
#AddEncoding x-gzip .gz .tgz
|
||||
#
|
||||
# If the AddEncoding directives above are commented-out, then you
|
||||
# probably should define those extensions to indicate media types:
|
||||
#
|
||||
AddType application/x-compress .Z
|
||||
AddType application/x-gzip .gz .tgz
|
||||
|
||||
#
|
||||
# AddHandler allows you to map certain file extensions to "handlers":
|
||||
# actions unrelated to filetype. These can be either built into the server
|
||||
# or added with the Action directive (see below)
|
||||
#
|
||||
# To use CGI scripts outside of ScriptAliased directories:
|
||||
# (You will also need to add "ExecCGI" to the "Options" directive.)
|
||||
#
|
||||
#AddHandler cgi-script .cgi
|
||||
|
||||
# For type maps (negotiated resources):
|
||||
#AddHandler type-map var
|
||||
|
||||
#
|
||||
# Filters allow you to process content before it is sent to the client.
|
||||
#
|
||||
# To parse .shtml files for server-side includes (SSI):
|
||||
# (You will also need to add "Includes" to the "Options" directive.)
|
||||
#
|
||||
#AddType text/html .shtml
|
||||
#AddOutputFilter INCLUDES .shtml
|
||||
</IfModule>
|
||||
|
||||
#
|
||||
# The mod_mime_magic module allows the server to use various hints from the
|
||||
# contents of the file itself to determine its type. The MIMEMagicFile
|
||||
# directive tells the module where the hint definitions are located.
|
||||
#
|
||||
#MIMEMagicFile conf/magic
|
||||
|
||||
#
|
||||
# Customizable error responses come in three flavors:
|
||||
# 1) plain text 2) local redirects 3) external redirects
|
||||
#
|
||||
# Some examples:
|
||||
#ErrorDocument 500 "The server made a boo boo."
|
||||
#ErrorDocument 404 /missing.html
|
||||
#ErrorDocument 404 "/cgi-bin/missing_handler.pl"
|
||||
#ErrorDocument 402 http://www.example.com/subscription_info.html
|
||||
#
|
||||
|
||||
#
|
||||
# MaxRanges: Maximum number of Ranges in a request before
|
||||
# returning the entire resource, or one of the special
|
||||
# values 'default', 'none' or 'unlimited'.
|
||||
# Default setting is to accept 200 Ranges.
|
||||
#MaxRanges unlimited
|
||||
|
||||
#
|
||||
# EnableMMAP and EnableSendfile: On systems that support it,
|
||||
# memory-mapping or the sendfile syscall may be used to deliver
|
||||
# files. This usually improves server performance, but must
|
||||
# be turned off when serving from networked-mounted
|
||||
# filesystems or if support for these functions is otherwise
|
||||
# broken on your system.
|
||||
# Defaults: EnableMMAP On, EnableSendfile Off
|
||||
#
|
||||
#EnableMMAP off
|
||||
#EnableSendfile on
|
||||
|
||||
# Supplemental configuration
|
||||
#
|
||||
# The configuration files in the conf/extra/ directory can be
|
||||
# included to add extra features or to modify the default configuration of
|
||||
# the server, or you may simply copy their contents here and change as
|
||||
# necessary.
|
||||
|
||||
# Server-pool management (MPM specific)
|
||||
#Include conf/extra/httpd-mpm.conf
|
||||
|
||||
# Multi-language error messages
|
||||
#Include conf/extra/httpd-multilang-errordoc.conf
|
||||
|
||||
# Fancy directory listings
|
||||
#Include conf/extra/httpd-autoindex.conf
|
||||
|
||||
# Language settings
|
||||
#Include conf/extra/httpd-languages.conf
|
||||
|
||||
# User home directories
|
||||
#Include conf/extra/httpd-userdir.conf
|
||||
|
||||
# Real-time info on requests and configuration
|
||||
#Include conf/extra/httpd-info.conf
|
||||
|
||||
# Virtual hosts
|
||||
#Include conf/extra/httpd-vhosts.conf
|
||||
|
||||
# Local access to the Apache HTTP Server Manual
|
||||
#Include conf/extra/httpd-manual.conf
|
||||
|
||||
# Distributed authoring and versioning (WebDAV)
|
||||
#Include conf/extra/httpd-dav.conf
|
||||
|
||||
# Various default settings
|
||||
#Include conf/extra/httpd-default.conf
|
||||
|
||||
# Configure mod_proxy_html to understand HTML4/XHTML1
|
||||
<IfModule proxy_html_module>
|
||||
Include conf/extra/proxy-html.conf
|
||||
</IfModule>
|
||||
|
||||
# Secure (SSL/TLS) connections
|
||||
#Include conf/extra/httpd-ssl.conf
|
||||
#
|
||||
# Note: The following must must be present to support
|
||||
# starting without SSL on platforms with no /dev/random equivalent
|
||||
# but a statically compiled-in mod_ssl.
|
||||
#
|
||||
<IfModule ssl_module>
|
||||
SSLRandomSeed startup builtin
|
||||
SSLRandomSeed connect builtin
|
||||
</IfModule>
|
||||
#
|
||||
# uncomment out the below to deal with user agents that deliberately
|
||||
# violate open standards by misusing DNT (DNT *must* be a specific
|
||||
# end-user choice)
|
||||
#
|
||||
#<IfModule setenvif_module>
|
||||
#BrowserMatch "MSIE 10.0;" bad_DNT
|
||||
#</IfModule>
|
||||
#<IfModule headers_module>
|
||||
#RequestHeader unset DNT env=bad_DNT
|
||||
#</IfModule>
|
||||
|
||||
# MOD_CLUSTER_ADDS
|
||||
# Adjust to you hostname and subnet.
|
||||
<IfModule manager_module>
|
||||
Listen *:10001
|
||||
ManagerBalancerName mycluster
|
||||
<VirtualHost *:10001>
|
||||
<Location />
|
||||
Require all granted
|
||||
</Location>
|
||||
|
||||
KeepAliveTimeout 300
|
||||
MaxKeepAliveRequests 0
|
||||
#ServerAdvertise on http://@IP@:6666
|
||||
AdvertiseFrequency 5
|
||||
#AdvertiseSecurityKey secret
|
||||
#AdvertiseGroup @ADVIP@:23364
|
||||
EnableMCPMReceive
|
||||
|
||||
<Location /mod_cluster_manager>
|
||||
SetHandler mod_cluster-manager
|
||||
Require all granted
|
||||
</Location>
|
||||
|
||||
</VirtualHost>
|
||||
</IfModule>
|
89
testsuite/docker-cluster/pom.xml
Normal file
89
testsuite/docker-cluster/pom.xml
Normal file
|
@ -0,0 +1,89 @@
|
|||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<artifactId>keycloak-testsuite-pom</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.1.0-Alpha1-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<artifactId>keycloak-testsuite-docker-cluster</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<name>Keycloak Docker Cluster</name>
|
||||
<description/>
|
||||
|
||||
<dependencies>
|
||||
</dependencies>
|
||||
<build>
|
||||
<finalName>keycloak-docker-cluster</finalName>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-deploy-plugin</artifactId>
|
||||
<configuration>
|
||||
<skip>true</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<!--<version>2.7</version> -->
|
||||
<executions>
|
||||
<execution>
|
||||
<id>unpack</id>
|
||||
<phase>prepare-package</phase>
|
||||
<goals>
|
||||
<goal>unpack</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<artifactItems>
|
||||
<artifactItem>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-war-deployment</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<type>zip</type>
|
||||
<outputDirectory>${project.build.directory}</outputDirectory>
|
||||
</artifactItem>
|
||||
<artifactItem>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-wildfly-adapter-dist</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<type>zip</type>
|
||||
<outputDirectory>${project.build.directory}</outputDirectory>
|
||||
</artifactItem>
|
||||
</artifactItems>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<version>2.4</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>assemble</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>single</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<descriptors>
|
||||
<descriptor>assembly.xml</descriptor>
|
||||
</descriptors>
|
||||
<outputDirectory>
|
||||
target
|
||||
</outputDirectory>
|
||||
<workDirectory>
|
||||
target/assembly/work
|
||||
</workDirectory>
|
||||
<appendAssemblyId>false</appendAssemblyId>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
35
testsuite/docker-cluster/wildfly/Dockerfile
Normal file
35
testsuite/docker-cluster/wildfly/Dockerfile
Normal file
|
@ -0,0 +1,35 @@
|
|||
FROM jboss/wildfly
|
||||
|
||||
USER root
|
||||
RUN yum install -y unzip && yum install -y wget && yum install -y mc && yum -y install nc
|
||||
RUN yum clean all
|
||||
|
||||
RUN cd /tmp
|
||||
RUN wget http://search.maven.org/remotecontent?filepath=mysql/mysql-connector-java/5.1.32/mysql-connector-java-5.1.32.jar
|
||||
RUN mv *.jar mysql-connector-java-5.1.32.jar
|
||||
|
||||
RUN mkdir -p mysql/main && mv mysql-connector-java-5.1.32.jar mysql/main/
|
||||
ADD mysql-module.xml mysql/main/module.xml
|
||||
RUN mv mysql /opt/wildfly/modules/system/layers/base/com/
|
||||
|
||||
RUN sed -i -e 's/<extensions>/&\n <extension module="org.keycloak.keycloak-wildfly-subsystem"\/>/' /opt/wildfly/standalone/configuration/standalone-ha.xml && \
|
||||
sed -i -e 's/<profile>/&\n <subsystem xmlns="urn:jboss:domain:keycloak:1.0"\/>/' /opt/wildfly/standalone/configuration/standalone-ha.xml && \
|
||||
sed -i -e 's/<security-domains>/&\n <security-domain name="keycloak">\n <authentication>\n <login-module code="org.keycloak.adapters.jboss.KeycloakLoginModule" flag="required"\/>\n <\/authentication>\n <\/security-domain>/' /opt/wildfly/standalone/configuration/standalone-ha.xml && \
|
||||
sed -i -e 's/<drivers>/&\n <driver name="mysql" module="com.mysql">\n <xa-datasource-class>com.mysql.jdbc.Driver<\/xa-datasource-class>\n <\/driver>/' /opt/wildfly/standalone/configuration/standalone-ha.xml && \
|
||||
sed -i -e 's/<\/periodic-rotating-file-handler>/&\n <logger category=\"org.keycloak\">\n <level name=\"DEBUG\" \/> \n <\/logger>/' /opt/wildfly/standalone/configuration/standalone-ha.xml
|
||||
|
||||
RUN sed -i -e 's/<subsystem xmlns=\"urn:jboss:domain:infinispan:2\.0\">/&\n <cache-container name=\"keycloak\" jndi-name=\"infinispan\/Keycloak\" start=\"EAGER\"> \
|
||||
\n <transport lock-timeout=\"60000\"\/>\n <distributed-cache name=\"sessions\" mode=\"SYNC\" owners=\"2\" segments=\"60\"\/> \
|
||||
\n <invalidation-cache name=\"realms\" mode=\"SYNC\"\/>\n <\/cache-container>/' /opt/wildfly/standalone/configuration/standalone-ha.xml
|
||||
|
||||
RUN sed -i "s|<mod-cluster-config .*>|<mod-cluster-config advertise-socket=\"modcluster\" proxy-list=\"\$\{httpd.proxyList\}\" proxy-url=\"\/\" balancer=\"mycluster\" advertise=\"false\" connector=\"ajp\" sticky-session=\"true\">|" /opt/wildfly/standalone/configuration/standalone-ha.xml
|
||||
|
||||
RUN sed -i "s|#JAVA_OPTS=\"\$JAVA_OPTS -agentlib:jdwp=transport=dt_socket|JAVA_OPTS=\"\$JAVA_OPTS -agentlib:jdwp=transport=dt_socket|" /opt/wildfly/bin/standalone.conf
|
||||
|
||||
ADD mysql-keycloak-ds.xml /opt/wildfly/standalone/deployments/
|
||||
ADD keycloak-run-node.sh /keycloak-run-node.sh
|
||||
RUN chmod u+x /keycloak-run-node.sh
|
||||
|
||||
EXPOSE 8787
|
||||
|
||||
CMD [ "/keycloak-run-node.sh" ]
|
75
testsuite/docker-cluster/wildfly/keycloak-run-node.sh
Normal file
75
testsuite/docker-cluster/wildfly/keycloak-run-node.sh
Normal file
|
@ -0,0 +1,75 @@
|
|||
#!/bin/bash
|
||||
|
||||
export MYHOST=node$(echo $MYSQL_NAME | awk -F"/dockercluster[^0-9]*|\/mysql" '{print $2 }');
|
||||
echo "MYHOST is $MYHOST. MYSQL_NAME is $MYSQL_NAME";
|
||||
|
||||
function waitForPreviousNodeStart
|
||||
{
|
||||
myHostNumber=$(echo $MYHOST | awk -F"node" '{ print $2 }');
|
||||
if [ $myHostNumber -eq 1 ]; then
|
||||
echo "Our host is node1. No need to wait for previous server";
|
||||
else
|
||||
previous=node$(($myHostNumber-1));
|
||||
echo "Waiting for host $previous to start";
|
||||
|
||||
for I in $(seq 1 10); do
|
||||
cat /keycloak-docker-shared/keycloak-wildfly-$previous/standalone/log/server.log | grep "\(INFO\|ERROR\).*WildFly.*started";
|
||||
if [ 0 -eq $? ]; then
|
||||
echo "Host $previous started. Going to start $MYHOST";
|
||||
return;
|
||||
fi;
|
||||
|
||||
echo "Host $previous not started yet. Still waiting...";
|
||||
sleep 5;
|
||||
done;
|
||||
|
||||
echo "Host $previous not started yet within timeout.";
|
||||
fi;
|
||||
}
|
||||
|
||||
function waitForMySQLStart
|
||||
{
|
||||
for I in $(seq 1 10); do
|
||||
nc $MYSQL_PORT_3306_TCP_ADDR 3306 < /dev/null;
|
||||
mysqlRunning=$(echo $?);
|
||||
if [ $mysqlRunning -eq 0 ]; then
|
||||
echo "MySQL is running. Starting our server";
|
||||
return;
|
||||
else
|
||||
echo "MySQL not yet available. Still waiting...";
|
||||
sleep 5;
|
||||
fi;
|
||||
done;
|
||||
}
|
||||
|
||||
echo "Creating keycloak-wildfly-$MYHOST";
|
||||
|
||||
cd /opt/wildfly
|
||||
cp -r /keycloak-docker-cluster/modules ./
|
||||
|
||||
# Deploy keycloak
|
||||
cp -r /keycloak-docker-cluster/deployments/* /opt/wildfly/standalone/deployments/
|
||||
|
||||
# Deploy examples
|
||||
cd /keycloak-docker-cluster/examples
|
||||
for I in $(find . | grep .war$); do cp $I /opt/wildfly/standalone/deployments/; done;
|
||||
|
||||
# Deploy to volume
|
||||
rm -rf /keycloak-docker-shared/keycloak-wildfly-$MYHOST
|
||||
cp -r /opt/wildfly-8.1.0.Final /keycloak-docker-shared/keycloak-wildfly-$MYHOST
|
||||
chmod -R 777 /keycloak-docker-shared/keycloak-wildfly-$MYHOST
|
||||
echo "keycloak-wildfly-$MYHOST prepared and copyied to volume";
|
||||
|
||||
|
||||
waitForPreviousNodeStart;
|
||||
waitForMySQLStart;
|
||||
|
||||
echo "Running keycloak node $MYHOST. Additional arguments: $@";
|
||||
cd /keycloak-docker-shared
|
||||
export JBOSS_HOME=/keycloak-docker-shared/keycloak-wildfly-$MYHOST;
|
||||
|
||||
cd $JBOSS_HOME/bin/
|
||||
|
||||
./standalone.sh -c standalone-ha.xml -Djboss.node.name=$MYHOST -b `hostname -i` -Djboss.mod_cluster.jvmRoute=$MYHOST \
|
||||
-Dmysql.host=$MYSQL_PORT_3306_TCP_ADDR -Dhttpd.proxyList=$HTTPD_1_PORT_10001_TCP_ADDR:$HTTPD_PORT_10001_TCP_PORT \
|
||||
-Dkeycloak.import=/keycloak-docker-cluster/examples/testrealm.json "$@"
|
11
testsuite/docker-cluster/wildfly/mysql-keycloak-ds.xml
Normal file
11
testsuite/docker-cluster/wildfly/mysql-keycloak-ds.xml
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<datasources xmlns="http://www.jboss.org/ironjacamar/schema">
|
||||
<datasource jndi-name="java:jboss/datasources/KeycloakDS" pool-name="KeycloakDS" enabled="true" use-java-context="true">
|
||||
<connection-url>jdbc:mysql://${mysql.host}/keycloak_db</connection-url>
|
||||
<driver>mysql</driver>
|
||||
<security>
|
||||
<user-name>root</user-name>
|
||||
<password>mysecretpassword</password>
|
||||
</security>
|
||||
</datasource>
|
||||
</datasources>
|
34
testsuite/docker-cluster/wildfly/mysql-module.xml
Normal file
34
testsuite/docker-cluster/wildfly/mysql-module.xml
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!--
|
||||
~ JBoss, Home of Professional Open Source.
|
||||
~ Copyright 2011, Red Hat, Inc., and individual contributors
|
||||
~ as indicated by the @author tags. See the copyright.txt file in the
|
||||
~ distribution for a full listing of individual contributors.
|
||||
~
|
||||
~ This is free software; you can redistribute it and/or modify it
|
||||
~ under the terms of the GNU Lesser General Public License as
|
||||
~ published by the Free Software Foundation; either version 2.1 of
|
||||
~ the License, or (at your option) any later version.
|
||||
~
|
||||
~ This software is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
~ Lesser General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU Lesser General Public
|
||||
~ License along with this software; if not, write to the Free
|
||||
~ Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
|
||||
~ 02110-1301 USA, or see the FSF site: http://www.fsf.org.
|
||||
-->
|
||||
|
||||
<module xmlns="urn:jboss:module:1.0" name="com.mysql">
|
||||
<resources>
|
||||
<resource-root path="mysql-connector-java-5.1.32.jar"/>
|
||||
</resources>
|
||||
|
||||
<dependencies>
|
||||
<module name="javax.api" />
|
||||
<module name="javax.transaction.api" />
|
||||
</dependencies>
|
||||
</module>
|
|
@ -183,6 +183,10 @@
|
|||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.infinispan</groupId>
|
||||
<artifactId>infinispan-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.seleniumhq.selenium</groupId>
|
||||
<artifactId>selenium-java</artifactId>
|
||||
|
@ -377,6 +381,26 @@
|
|||
|
||||
</profile>
|
||||
|
||||
<profile>
|
||||
<id>infinispan</id>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<systemPropertyVariables>
|
||||
<keycloak.realm.cache.provider>infinispan</keycloak.realm.cache.provider>
|
||||
<keycloak.user.cache.provider>infinispan</keycloak.user.cache.provider>
|
||||
<keycloak.userSessions.provider>infinispan</keycloak.userSessions.provider>
|
||||
</systemPropertyVariables>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
|
||||
<!-- MySQL -->
|
||||
<profile>
|
||||
<activation>
|
||||
|
|
|
@ -156,7 +156,7 @@ public class AssertEvents implements TestRule, EventListenerProviderFactory {
|
|||
}
|
||||
|
||||
public ExpectedEvent expectRegister(String username, String email) {
|
||||
UserRepresentation user = keycloak.getUser("test", username);
|
||||
UserRepresentation user = username != null ? keycloak.getUser("test", username) : null;
|
||||
return expect(EventType.REGISTER)
|
||||
.user(user != null ? user.getId() : null)
|
||||
.detail(Details.USERNAME, username)
|
||||
|
|
|
@ -160,12 +160,12 @@ public class AccountTest {
|
|||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void runit() throws Exception {
|
||||
Thread.sleep(10000000);
|
||||
|
||||
}
|
||||
// @Test
|
||||
// @Ignore
|
||||
// public void runit() throws Exception {
|
||||
// Thread.sleep(10000000);
|
||||
//
|
||||
// }
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -104,12 +104,12 @@ public class FederationProvidersIntegrationTest {
|
|||
@WebResource
|
||||
protected AccountPasswordPage changePasswordPage;
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void runit() throws Exception {
|
||||
Thread.sleep(10000000);
|
||||
|
||||
}
|
||||
// @Test
|
||||
// @Ignore
|
||||
// public void runit() throws Exception {
|
||||
// Thread.sleep(10000000);
|
||||
//
|
||||
// }
|
||||
|
||||
static UserModel addUser(KeycloakSession session, RealmModel realm, String username, String email, String password) {
|
||||
UserModel user = session.users().addUser(realm, username);
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
<module>performance</module>
|
||||
<module>tools</module>
|
||||
<module>performance-web</module>
|
||||
<!--<module>docker-cluster</module>-->
|
||||
</modules>
|
||||
|
||||
</project>
|
||||
|
|
Loading…
Reference in a new issue