Merge branch 'feature/group-search-and-pagination' of https://github.com/guitaro/keycloak into feature/group-search-and-pagination

This commit is contained in:
Levente NAGY committed 2017-09-13 16:48:24 +02:00
commit d18aa44fb4
1615 files changed
+143297 -4802

No files matched your search

+1 -1
View File
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>3.3.0.CR1-SNAPSHOT</version>
<version>3.4.0.CR1-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -40,12 +40,9 @@ import org.jboss.logging.Logger;
import org.jgroups.JChannel;
import org.keycloak.Config;
import org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory;
import org.keycloak.common.util.HostUtils;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.sessions.infinispan.remotestore.KcRemoteStoreConfigurationBuilder;
import org.keycloak.models.sessions.infinispan.util.InfinispanUtil;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.sessions.infinispan.remotestore.KeycloakRemoteStoreConfigurationBuilder;
import javax.naming.InitialContext;
@@ -249,7 +246,7 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
if (jdgEnabled) {
sessionConfigBuilder = new ConfigurationBuilder();
sessionConfigBuilder.read(sessionCacheConfigurationBase);
configureRemoteCacheStore(sessionConfigBuilder, async, InfinispanConnectionProvider.SESSION_CACHE_NAME, KcRemoteStoreConfigurationBuilder.class);
configureRemoteCacheStore(sessionConfigBuilder, async, InfinispanConnectionProvider.SESSION_CACHE_NAME, KeycloakRemoteStoreConfigurationBuilder.class);
}
Configuration sessionCacheConfiguration = sessionConfigBuilder.build();
cacheManager.defineConfiguration(InfinispanConnectionProvider.SESSION_CACHE_NAME, sessionCacheConfiguration);
@@ -257,12 +254,19 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
if (jdgEnabled) {
sessionConfigBuilder = new ConfigurationBuilder();
sessionConfigBuilder.read(sessionCacheConfigurationBase);
configureRemoteCacheStore(sessionConfigBuilder, async, InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME, KcRemoteStoreConfigurationBuilder.class);
configureRemoteCacheStore(sessionConfigBuilder, async, InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME, KeycloakRemoteStoreConfigurationBuilder.class);
}
sessionCacheConfiguration = sessionConfigBuilder.build();
cacheManager.defineConfiguration(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME, sessionCacheConfiguration);
cacheManager.defineConfiguration(InfinispanConnectionProvider.LOGIN_FAILURE_CACHE_NAME, sessionCacheConfigurationBase);
if (jdgEnabled) {
sessionConfigBuilder = new ConfigurationBuilder();
sessionConfigBuilder.read(sessionCacheConfigurationBase);
configureRemoteCacheStore(sessionConfigBuilder, async, InfinispanConnectionProvider.LOGIN_FAILURE_CACHE_NAME, KeycloakRemoteStoreConfigurationBuilder.class);
}
sessionCacheConfiguration = sessionConfigBuilder.build();
cacheManager.defineConfiguration(InfinispanConnectionProvider.LOGIN_FAILURE_CACHE_NAME, sessionCacheConfiguration);
cacheManager.defineConfiguration(InfinispanConnectionProvider.AUTHENTICATION_SESSIONS_CACHE_NAME, sessionCacheConfigurationBase);
// Retrieve caches to enforce rebalance
@@ -334,6 +334,8 @@ public class UserCacheSession implements UserCache {
}
protected UserModel cacheUser(RealmModel realm, UserModel delegate, Long revision) {
int notBefore = getDelegate().getNotBeforeOfUser(realm, delegate);
StorageId storageId = new StorageId(delegate.getId());
CachedUser cached = null;
if (!storageId.isLocal()) {
@@ -343,7 +345,7 @@ public class UserCacheSession implements UserCache {
if (policy != null && policy == UserStorageProviderModel.CachePolicy.NO_CACHE) {
return delegate;
}
cached = new CachedUser(revision, realm, delegate);
cached = new CachedUser(revision, realm, delegate, notBefore);
if (policy == null || policy == UserStorageProviderModel.CachePolicy.DEFAULT) {
cache.addRevisioned(cached, startupRevision);
} else {
@@ -366,7 +368,7 @@ public class UserCacheSession implements UserCache {
}
}
} else {
cached = new CachedUser(revision, realm, delegate);
cached = new CachedUser(revision, realm, delegate, notBefore);
cache.addRevisioned(cached, startupRevision);
}
UserAdapter adapter = new UserAdapter(cached, this, session, realm);
@@ -765,6 +767,32 @@ public class UserCacheSession implements UserCache {
return consentModel;
}
@Override
public void setNotBeforeForUser(RealmModel realm, UserModel user, int notBefore) {
if (!isRegisteredForInvalidation(realm, user.getId())) {
UserModel foundUser = getUserById(user.getId(), realm);
if (foundUser instanceof UserAdapter) {
((UserAdapter) foundUser).invalidate();
}
}
getDelegate().setNotBeforeForUser(realm, user, notBefore);
}
@Override
public int getNotBeforeOfUser(RealmModel realm, UserModel user) {
if (isRegisteredForInvalidation(realm, user.getId())) {
return getDelegate().getNotBeforeOfUser(realm, user);
}
UserModel foundUser = getUserById(user.getId(), realm);
if (foundUser instanceof UserAdapter) {
return ((UserAdapter) foundUser).cached.getNotBefore();
} else {
return getDelegate().getNotBeforeOfUser(realm, user);
}
}
@Override
public UserModel addUser(RealmModel realm, String id, String username, boolean addDefaultRoles, boolean addDefaultRequiredActions) {
@@ -46,7 +46,7 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
@Override
public Resource getDelegateForUpdate() {
if (updated == null) {
cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), cached.getUri(), cached.getScopesIds(), cached.getResourceServerId());
cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), cached.getUri(), cached.getScopesIds(), cached.getResourceServerId(), cached.getOwner());
updated = cacheSession.getResourceStoreDelegate().findById(cached.getId(), cached.getResourceServerId());
if (updated == null) throw new IllegalStateException("Not found in database");
}
@@ -95,7 +95,7 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
@Override
public void setName(String name) {
getDelegateForUpdate();
cacheSession.registerResourceInvalidation(cached.getId(), name, cached.getType(), cached.getUri(), cached.getScopesIds(), cached.getResourceServerId());
cacheSession.registerResourceInvalidation(cached.getId(), name, cached.getType(), cached.getUri(), cached.getScopesIds(), cached.getResourceServerId(), cached.getOwner());
updated.setName(name);
}
@@ -127,7 +127,7 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
@Override
public void setUri(String uri) {
getDelegateForUpdate();
cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), uri, cached.getScopesIds(), cached.getResourceServerId());
cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), uri, cached.getScopesIds(), cached.getResourceServerId(), cached.getOwner());
updated.setUri(uri);
}
@@ -140,7 +140,7 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
@Override
public void setType(String type) {
getDelegateForUpdate();
cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), type, cached.getUri(), cached.getScopesIds(), cached.getResourceServerId());
cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), type, cached.getUri(), cached.getScopesIds(), cached.getResourceServerId(), cached.getOwner());
updated.setType(type);
}
@@ -168,7 +168,7 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
@Override
public void updateScopes(Set<Scope> scopes) {
getDelegateForUpdate();
cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), cached.getUri(), scopes.stream().map(scope1 -> scope1.getId()).collect(Collectors.toSet()), cached.getResourceServerId());
cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), cached.getUri(), scopes.stream().map(scope1 -> scope1.getId()).collect(Collectors.toSet()), cached.getResourceServerId(), cached.getOwner());
updated.updateScopes(scopes);
}
@@ -38,7 +38,7 @@ public class ResourceServerAdapter implements ResourceServer, CachedModel<Resour
@Override
public ResourceServer getDelegateForUpdate() {
if (updated == null) {
cacheSession.registerResourceServerInvalidation(cached.getId(), cached.getClientId());
cacheSession.registerResourceServerInvalidation(cached.getId());
updated = cacheSession.getResourceServerStoreDelegate().findById(cached.getId());
if (updated == null) throw new IllegalStateException("Not found in database");
}
@@ -78,12 +78,6 @@ public class ResourceServerAdapter implements ResourceServer, CachedModel<Resour
return cached.getId();
}
@Override
public String getClientId() {
if (isUpdated()) return updated.getClientId();
return cached.getClientId();
}
@Override
public boolean isAllowRemoteResourceManagement() {
if (isUpdated()) return updated.isAllowRemoteResourceManagement();
@@ -53,13 +53,13 @@ public class StoreFactoryCacheManager extends CacheManager {
}
}
public void resourceServerUpdated(String id, String clientId, Set<String> invalidations) {
public void resourceServerUpdated(String id, Set<String> invalidations) {
invalidations.add(id);
invalidations.add(StoreFactoryCacheSession.getResourceServerByClientCacheKey(clientId));
invalidations.add(StoreFactoryCacheSession.getResourceServerByClientCacheKey(id));
}
public void resourceServerRemoval(String id, String name, Set<String> invalidations) {
resourceServerUpdated(id, name, invalidations);
public void resourceServerRemoval(String id, Set<String> invalidations) {
resourceServerUpdated(id, invalidations);
addInvalidations(InResourceServerPredicate.create().resourceServer(id), invalidations);
}
@@ -75,9 +75,10 @@ public class StoreFactoryCacheManager extends CacheManager {
addInvalidations(InScopePredicate.create().scope(id), invalidations);
}
public void resourceUpdated(String id, String name, String type, String uri, Set<String> scopes, String serverId, Set<String> invalidations) {
public void resourceUpdated(String id, String name, String type, String uri, Set<String> scopes, String serverId, String owner, Set<String> invalidations) {
invalidations.add(id);
invalidations.add(StoreFactoryCacheSession.getResourceByNameCacheKey(name, serverId));
invalidations.add(StoreFactoryCacheSession.getResourceByOwnerCacheKey(owner, serverId));
if (type != null) {
invalidations.add(StoreFactoryCacheSession.getResourceByTypeCacheKey(type, serverId));
@@ -97,8 +98,7 @@ public class StoreFactoryCacheManager extends CacheManager {
}
public void resourceRemoval(String id, String name, String type, String uri, String owner, Set<String> scopes, String serverId, Set<String> invalidations) {
resourceUpdated(id, name, type, uri, scopes, serverId, invalidations);
invalidations.add(StoreFactoryCacheSession.getResourceByOwnerCacheKey(owner, serverId));
resourceUpdated(id, name, type, uri, scopes, serverId, owner, invalidations);
addInvalidations(InResourcePredicate.create().resource(id), invalidations);
}
@@ -229,12 +229,12 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
return invalidations.contains(id);
}
public void registerResourceServerInvalidation(String id, String clientId) {
cache.resourceServerUpdated(id, clientId, invalidations);
public void registerResourceServerInvalidation(String id) {
cache.resourceServerUpdated(id, invalidations);
ResourceServerAdapter adapter = managedResourceServers.get(id);
if (adapter != null) adapter.invalidateFlag();
invalidationEvents.add(ResourceServerUpdatedEvent.create(id, clientId));
invalidationEvents.add(ResourceServerUpdatedEvent.create(id));
}
public void registerScopeInvalidation(String id, String name, String serverId) {
@@ -245,12 +245,12 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
invalidationEvents.add(ScopeUpdatedEvent.create(id, name, serverId));
}
public void registerResourceInvalidation(String id, String name, String type, String uri, Set<String> scopes, String serverId) {
cache.resourceUpdated(id, name, type, uri, scopes, serverId, invalidations);
public void registerResourceInvalidation(String id, String name, String type, String uri, Set<String> scopes, String serverId, String owner) {
cache.resourceUpdated(id, name, type, uri, scopes, serverId, owner, invalidations);
ResourceAdapter adapter = managedResources.get(id);
if (adapter != null) adapter.invalidateFlag();
invalidationEvents.add(ResourceUpdatedEvent.create(id, name, type, uri, scopes, serverId));
invalidationEvents.add(ResourceUpdatedEvent.create(id, name, type, uri, scopes, serverId, owner));
}
public void registerPolicyInvalidation(String id, String name, Set<String> resources, Set<String> scopes, String serverId) {
@@ -350,7 +350,7 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
@Override
public ResourceServer create(String clientId) {
ResourceServer server = getResourceServerStoreDelegate().create(clientId);
registerResourceServerInvalidation(server.getId(), server.getClientId());
registerResourceServerInvalidation(server.getId());
return server;
}
@@ -361,8 +361,8 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
if (server == null) return;
cache.invalidateObject(id);
invalidationEvents.add(ResourceServerRemovedEvent.create(id, server.getClientId()));
cache.resourceServerRemoval(id, server.getClientId(), invalidations);
invalidationEvents.add(ResourceServerRemovedEvent.create(id, server.getId()));
cache.resourceServerRemoval(id, invalidations);
getResourceServerStoreDelegate().delete(id);
}
@@ -392,33 +392,6 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
managedResourceServers.put(id, adapter);
return adapter;
}
@Override
public ResourceServer findByClient(String clientId) {
String cacheKey = getResourceServerByClientCacheKey(clientId);
ResourceServerListQuery query = cache.get(cacheKey, ResourceServerListQuery.class);
if (query != null) {
logger.tracev("ResourceServer by clientId cache hit: {0}", clientId);
}
if (query == null) {
Long loaded = cache.getCurrentRevision(cacheKey);
ResourceServer model = getResourceServerStoreDelegate().findByClient(clientId);
if (model == null) return null;
if (invalidations.contains(model.getId())) return model;
query = new ResourceServerListQuery(loaded, cacheKey, model.getId());
cache.addRevisioned(query, startupRevision);
return model;
} else if (invalidations.contains(cacheKey)) {
return getResourceServerStoreDelegate().findByClient(clientId);
} else {
String serverId = query.getResourceServers().iterator().next();
if (invalidations.contains(serverId)) {
return getResourceServerStoreDelegate().findByClient(clientId);
}
return findById(serverId);
}
}
}
protected class ScopeCache implements ScopeStore {
@@ -509,8 +482,9 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
@Override
public Resource create(String name, ResourceServer resourceServer, String owner) {
Resource resource = getResourceStoreDelegate().create(name, resourceServer, owner);
registerResourceInvalidation(resource.getId(), resource.getName(), resource.getType(), resource.getUri(), resource.getScopes().stream().map(scope -> scope.getId()).collect(Collectors.toSet()), resourceServer.getId());
return resource;
Resource cached = findById(resource.getId(), resourceServer.getId());
registerResourceInvalidation(resource.getId(), resource.getName(), resource.getType(), resource.getUri(), resource.getScopes().stream().map(scope -> scope.getId()).collect(Collectors.toSet()), resourceServer.getId(), resource.getOwner());
return cached;
}
@Override
@@ -22,29 +22,20 @@ import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.models.cache.infinispan.entities.AbstractRevisioned;
import org.keycloak.representations.idm.authorization.PolicyEnforcementMode;
import java.io.Serializable;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class CachedResourceServer extends AbstractRevisioned {
private String clientId;
private boolean allowRemoteResourceManagement;
private PolicyEnforcementMode policyEnforcementMode;
public CachedResourceServer(Long revision, ResourceServer resourceServer) {
super(revision, resourceServer.getId());
this.clientId = resourceServer.getClientId();
this.allowRemoteResourceManagement = resourceServer.isAllowRemoteResourceManagement();
this.policyEnforcementMode = resourceServer.getPolicyEnforcementMode();
}
public String getClientId() {
return this.clientId;
}
public boolean isAllowRemoteResourceManagement() {
return this.allowRemoteResourceManagement;
}
@@ -49,6 +49,6 @@ public class ResourceServerRemovedEvent extends InvalidationEvent implements Aut
@Override
public void addInvalidations(StoreFactoryCacheManager cache, Set<String> invalidations) {
cache.resourceServerRemoval(id, clientId, invalidations);
cache.resourceServerRemoval(id, invalidations);
}
}
@@ -28,12 +28,10 @@ import java.util.Set;
public class ResourceServerUpdatedEvent extends InvalidationEvent implements AuthorizationCacheInvalidationEvent {
private String id;
private String clientId;
public static ResourceServerUpdatedEvent create(String id, String clientId) {
public static ResourceServerUpdatedEvent create(String id) {
ResourceServerUpdatedEvent event = new ResourceServerUpdatedEvent();
event.id = id;
event.clientId = clientId;
return event;
}
@@ -44,11 +42,11 @@ public class ResourceServerUpdatedEvent extends InvalidationEvent implements Aut
@Override
public String toString() {
return String.format("ResourceServerRemovedEvent [ id=%s, clientId=%s ]", id, clientId);
return String.format("ResourceServerRemovedEvent [ id=%s, clientId=%s ]", id, id);
}
@Override
public void addInvalidations(StoreFactoryCacheManager cache, Set<String> invalidations) {
cache.resourceServerUpdated(id, clientId, invalidations);
cache.resourceServerUpdated(id, invalidations);
}
}
@@ -33,8 +33,9 @@ public class ResourceUpdatedEvent extends InvalidationEvent implements Authoriza
private String type;
private String uri;
private Set<String> scopes;
private String owner;
public static ResourceUpdatedEvent create(String id, String name, String type, String uri, Set<String> scopes, String serverId) {
public static ResourceUpdatedEvent create(String id, String name, String type, String uri, Set<String> scopes, String serverId, String owner) {
ResourceUpdatedEvent event = new ResourceUpdatedEvent();
event.id = id;
event.name = name;
@@ -42,6 +43,7 @@ public class ResourceUpdatedEvent extends InvalidationEvent implements Authoriza
event.uri = uri;
event.scopes = scopes;
event.serverId = serverId;
event.owner = owner;
return event;
}
@@ -57,6 +59,6 @@ public class ResourceUpdatedEvent extends InvalidationEvent implements Authoriza
@Override
public void addInvalidations(StoreFactoryCacheManager cache, Set<String> invalidations) {
cache.resourceUpdated(id, name, type, uri, scopes, serverId, invalidations);
cache.resourceUpdated(id, name, type, uri, scopes, serverId, owner, invalidations);
}
}
@@ -45,10 +45,11 @@ public class CachedUser extends AbstractExtendableRevisioned implements InRealm
private Set<String> requiredActions = new HashSet<>();
private Set<String> roleMappings = new HashSet<>();
private Set<String> groups = new HashSet<>();
private int notBefore;
public CachedUser(Long revision, RealmModel realm, UserModel user) {
public CachedUser(Long revision, RealmModel realm, UserModel user, int notBefore) {
super(revision, user.getId());
this.realm = realm.getId();
this.username = user.getUsername();
@@ -71,6 +72,7 @@ public class CachedUser extends AbstractExtendableRevisioned implements InRealm
groups.add(group.getId());
}
}
this.notBefore = notBefore;
}
public String getRealm() {
@@ -129,4 +131,7 @@ public class CachedUser extends AbstractExtendableRevisioned implements InRealm
return groups;
}
public int getNotBefore() {
return notBefore;
}
}
@@ -42,7 +42,6 @@ import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessi
import org.keycloak.models.sessions.infinispan.entities.LoginFailureEntity;
import org.keycloak.models.sessions.infinispan.entities.LoginFailureKey;
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
import org.keycloak.models.sessions.infinispan.events.ClientRemovedSessionEvent;
import org.keycloak.models.sessions.infinispan.events.RealmRemovedSessionEvent;
import org.keycloak.models.sessions.infinispan.events.RemoveAllUserLoginFailuresEvent;
import org.keycloak.models.sessions.infinispan.events.RemoveUserSessionsEvent;
@@ -52,6 +51,7 @@ import org.keycloak.models.sessions.infinispan.stream.Mappers;
import org.keycloak.models.sessions.infinispan.stream.SessionPredicate;
import org.keycloak.models.sessions.infinispan.stream.UserLoginFailurePredicate;
import org.keycloak.models.sessions.infinispan.stream.UserSessionPredicate;
import org.keycloak.models.sessions.infinispan.util.FuturesHelper;
import org.keycloak.models.sessions.infinispan.util.InfinispanUtil;
import java.util.Iterator;
@@ -59,6 +59,7 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Future;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Stream;
@@ -74,11 +75,11 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
protected final Cache<String, SessionEntityWrapper<UserSessionEntity>> sessionCache;
protected final Cache<String, SessionEntityWrapper<UserSessionEntity>> offlineSessionCache;
protected final Cache<LoginFailureKey, LoginFailureEntity> loginFailureCache;
protected final Cache<LoginFailureKey, SessionEntityWrapper<LoginFailureEntity>> loginFailureCache;
protected final InfinispanChangelogBasedTransaction<UserSessionEntity> sessionTx;
protected final InfinispanChangelogBasedTransaction<UserSessionEntity> offlineSessionTx;
protected final InfinispanKeycloakTransaction tx;
protected final InfinispanChangelogBasedTransaction<String, UserSessionEntity> sessionTx;
protected final InfinispanChangelogBasedTransaction<String, UserSessionEntity> offlineSessionTx;
protected final InfinispanChangelogBasedTransaction<LoginFailureKey, LoginFailureEntity> loginFailuresTx;
protected final SessionEventsSenderTransaction clusterEventsSenderTx;
@@ -91,7 +92,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
LastSessionRefreshStore offlineLastSessionRefreshStore,
Cache<String, SessionEntityWrapper<UserSessionEntity>> sessionCache,
Cache<String, SessionEntityWrapper<UserSessionEntity>> offlineSessionCache,
Cache<LoginFailureKey, LoginFailureEntity> loginFailureCache) {
Cache<LoginFailureKey, SessionEntityWrapper<LoginFailureEntity>> loginFailureCache) {
this.session = session;
this.sessionCache = sessionCache;
@@ -101,24 +102,24 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
this.sessionTx = new InfinispanChangelogBasedTransaction<>(session, InfinispanConnectionProvider.SESSION_CACHE_NAME, sessionCache, remoteCacheInvoker);
this.offlineSessionTx = new InfinispanChangelogBasedTransaction<>(session, InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME, offlineSessionCache, remoteCacheInvoker);
this.tx = new InfinispanKeycloakTransaction();
this.loginFailuresTx = new InfinispanChangelogBasedTransaction<>(session, InfinispanConnectionProvider.LOGIN_FAILURE_CACHE_NAME, loginFailureCache, remoteCacheInvoker);
this.clusterEventsSenderTx = new SessionEventsSenderTransaction(session);
this.lastSessionRefreshStore = lastSessionRefreshStore;
this.offlineLastSessionRefreshStore = offlineLastSessionRefreshStore;
session.getTransactionManager().enlistAfterCompletion(tx);
session.getTransactionManager().enlistAfterCompletion(clusterEventsSenderTx);
session.getTransactionManager().enlistAfterCompletion(sessionTx);
session.getTransactionManager().enlistAfterCompletion(offlineSessionTx);
session.getTransactionManager().enlistAfterCompletion(loginFailuresTx);
}
protected Cache<String, SessionEntityWrapper<UserSessionEntity>> getCache(boolean offline) {
return offline ? offlineSessionCache : sessionCache;
}
protected InfinispanChangelogBasedTransaction<UserSessionEntity> getTransaction(boolean offline) {
protected InfinispanChangelogBasedTransaction<String, UserSessionEntity> getTransaction(boolean offline) {
return offline ? offlineSessionTx : sessionTx;
}
@@ -134,7 +135,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
public AuthenticatedClientSessionModel createClientSession(RealmModel realm, ClientModel client, UserSessionModel userSession) {
AuthenticatedClientSessionEntity entity = new AuthenticatedClientSessionEntity();
InfinispanChangelogBasedTransaction<UserSessionEntity> updateTx = getTransaction(false);
InfinispanChangelogBasedTransaction<String, UserSessionEntity> updateTx = getTransaction(false);
AuthenticatedClientSessionAdapter adapter = new AuthenticatedClientSessionAdapter(entity, client, (UserSessionAdapter) userSession, this, updateTx);
adapter.setUserSession(userSession);
return adapter;
@@ -200,7 +201,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
}
private UserSessionEntity getUserSessionEntity(String id, boolean offline) {
InfinispanChangelogBasedTransaction<UserSessionEntity> tx = getTransaction(offline);
InfinispanChangelogBasedTransaction<String, UserSessionEntity> tx = getTransaction(offline);
SessionEntityWrapper<UserSessionEntity> entityWrapper = tx.get(id);
return entityWrapper==null ? null : entityWrapper.getEntity();
}
@@ -309,7 +310,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
UserSessionModel remoteSessionAdapter = wrap(realm, remoteSessionEntity, offline);
if (predicate.test(remoteSessionAdapter)) {
InfinispanChangelogBasedTransaction<UserSessionEntity> tx = getTransaction(offline);
InfinispanChangelogBasedTransaction<String, UserSessionEntity> tx = getTransaction(offline);
// Remote entity contains our predicate. Update local cache with the remote entity
SessionEntityWrapper<UserSessionEntity> sessionWrapper = remoteSessionEntity.mergeRemoteEntityWithLocalEntity(tx.get(id));
@@ -396,11 +397,11 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
int expired = Time.currentTime() - realm.getSsoSessionMaxLifespan();
int expiredRefresh = Time.currentTime() - realm.getSsoSessionIdleTimeout();
FuturesHelper futures = new FuturesHelper();
// Each cluster node cleanups just local sessions, which are those owned by himself (+ few more taking l1 cache into account)
Cache<String, SessionEntityWrapper<UserSessionEntity>> localCache = CacheDecorators.localCache(sessionCache);
int[] counter = { 0 };
Cache<String, SessionEntityWrapper<UserSessionEntity>> localCacheStoreIgnore = CacheDecorators.skipCacheLoaders(localCache);
// Ignore remoteStore for stream iteration. But we will invoke remoteStore for userSession removal propagate
@@ -413,14 +414,15 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
@Override
public void accept(String sessionId) {
counter[0]++;
tx.remove(localCache, sessionId);
Future future = localCache.removeAsync(sessionId);
futures.addTask(future);
}
});
futures.waitForAllToFinish();
log.debugf("Removed %d expired user sessions for realm '%s'", counter[0], realm.getName());
log.debugf("Removed %d expired user sessions for realm '%s'", futures.size(), realm.getName());
}
private void removeExpiredOfflineUserSessions(RealmModel realm) {
@@ -432,7 +434,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
UserSessionPredicate predicate = UserSessionPredicate.create(realm.getId()).expired(null, expiredOffline);
final int[] counter = { 0 };
FuturesHelper futures = new FuturesHelper();
Cache<String, SessionEntityWrapper<UserSessionEntity>> localCacheStoreIgnore = CacheDecorators.skipCacheLoaders(localCache);
@@ -446,8 +448,8 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
@Override
public void accept(UserSessionEntity userSessionEntity) {
counter[0]++;
tx.remove(localCache, userSessionEntity.getId());
Future future = localCache.removeAsync(userSessionEntity.getId());
futures.addTask(future);
// TODO:mposolda can be likely optimized to delete all expired at one step
persister.removeUserSession( userSessionEntity.getId(), true);
@@ -459,7 +461,9 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
}
});
log.debugf("Removed %d expired offline user sessions for realm '%s'", counter, realm.getName());
futures.waitForAllToFinish();
log.debugf("Removed %d expired offline user sessions for realm '%s'", futures.size(), realm.getName());
}
@Override
@@ -475,6 +479,8 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
}
private void removeLocalUserSessions(String realmId, boolean offline) {
FuturesHelper futures = new FuturesHelper();
Cache<String, SessionEntityWrapper<UserSessionEntity>> cache = getCache(offline);
Cache<String, SessionEntityWrapper<UserSessionEntity>> localCache = CacheDecorators.localCache(cache);
@@ -489,17 +495,29 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
@Override
public void accept(String sessionId) {
// Remove session from remoteCache too
localCache.remove(sessionId);
// Remove session from remoteCache too. Use removeAsync for better perf
Future future = localCache.removeAsync(sessionId);
futures.addTask(future);
}
});
futures.waitForAllToFinish();
log.debugf("Removed %d sessions in realm %s. Offline: %b", (Object) futures.size(), realmId, offline);
}
@Override
public UserLoginFailureModel getUserLoginFailure(RealmModel realm, String userId) {
LoginFailureKey key = new LoginFailureKey(realm.getId(), userId);
return wrap(key, loginFailureCache.get(key));
LoginFailureEntity entity = getLoginFailureEntity(key);
return wrap(key, entity);
}
private LoginFailureEntity getLoginFailureEntity(LoginFailureKey key) {
InfinispanChangelogBasedTransaction<LoginFailureKey, LoginFailureEntity> tx = getLoginFailuresTx();
SessionEntityWrapper<LoginFailureEntity> entityWrapper = tx.get(key);
return entityWrapper==null ? null : entityWrapper.getEntity();
}
@Override
@@ -508,13 +526,53 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
LoginFailureEntity entity = new LoginFailureEntity();
entity.setRealm(realm.getId());
entity.setUserId(userId);
tx.put(loginFailureCache, key, entity);
SessionUpdateTask<LoginFailureEntity> createLoginFailureTask = new SessionUpdateTask<LoginFailureEntity>() {
@Override
public void runUpdate(LoginFailureEntity session) {
}
@Override
public CacheOperation getOperation(LoginFailureEntity session) {
return CacheOperation.ADD_IF_ABSENT;
}
@Override
public CrossDCMessageStatus getCrossDCMessageStatus(SessionEntityWrapper<LoginFailureEntity> sessionWrapper) {
return CrossDCMessageStatus.SYNC;
}
};
loginFailuresTx.addTask(key, createLoginFailureTask, entity);
return wrap(key, entity);
}
@Override
public void removeUserLoginFailure(RealmModel realm, String userId) {
tx.remove(loginFailureCache, new LoginFailureKey(realm.getId(), userId));
SessionUpdateTask<LoginFailureEntity> removeTask = new SessionUpdateTask<LoginFailureEntity>() {
@Override
public void runUpdate(LoginFailureEntity entity) {
}
@Override
public CacheOperation getOperation(LoginFailureEntity entity) {
return CacheOperation.REMOVE;
}
@Override
public CrossDCMessageStatus getCrossDCMessageStatus(SessionEntityWrapper<LoginFailureEntity> sessionWrapper) {
return CrossDCMessageStatus.SYNC;
}
};
loginFailuresTx.addTask(new LoginFailureKey(realm.getId(), userId), removeTask);
}
@Override
@@ -529,9 +587,11 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
}
private void removeAllLocalUserLoginFailuresEvent(String realmId) {
Cache<LoginFailureKey, LoginFailureEntity> localCache = CacheDecorators.localCache(loginFailureCache);
FuturesHelper futures = new FuturesHelper();
Cache<LoginFailureKey, LoginFailureEntity> localCacheStoreIgnore = CacheDecorators.skipCacheLoaders(localCache);
Cache<LoginFailureKey, SessionEntityWrapper<LoginFailureEntity>> localCache = CacheDecorators.localCache(loginFailureCache);
Cache<LoginFailureKey, SessionEntityWrapper<LoginFailureEntity>> localCacheStoreIgnore = CacheDecorators.skipCacheLoaders(localCache);
localCacheStoreIgnore
.entrySet()
@@ -539,9 +599,14 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
.filter(UserLoginFailurePredicate.create(realmId))
.map(Mappers.loginFailureId())
.forEach(loginFailureKey -> {
// Remove loginFailure from remoteCache too
localCache.remove(loginFailureKey);
// Remove loginFailure from remoteCache too. Use removeAsync for better perf
Future future = localCache.removeAsync(loginFailureKey);
futures.addTask(future);
});
futures.waitForAllToFinish();
log.debugf("Removed %d login failures in realm %s", futures.size(), realmId);
}
@Override
@@ -574,8 +639,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
removeUserSessions(realm, user, true);
removeUserSessions(realm, user, false);
loginFailureCache.remove(new LoginFailureKey(realm.getId(), user.getUsername()));
loginFailureCache.remove(new LoginFailureKey(realm.getId(), user.getEmail()));
removeUserLoginFailure(realm, user.getId());
}
@Override
@@ -583,7 +647,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
}
protected void removeUserSession(UserSessionEntity sessionEntity, boolean offline) {
InfinispanChangelogBasedTransaction<UserSessionEntity> tx = getTransaction(offline);
InfinispanChangelogBasedTransaction<String, UserSessionEntity> tx = getTransaction(offline);
SessionUpdateTask<UserSessionEntity> removeTask = new SessionUpdateTask<UserSessionEntity>() {
@@ -607,17 +671,17 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
tx.addTask(sessionEntity.getId(), removeTask);
}
InfinispanKeycloakTransaction getTx() {
return tx;
InfinispanChangelogBasedTransaction<LoginFailureKey, LoginFailureEntity> getLoginFailuresTx() {
return loginFailuresTx;
}
UserSessionAdapter wrap(RealmModel realm, UserSessionEntity entity, boolean offline) {
InfinispanChangelogBasedTransaction<UserSessionEntity> tx = getTransaction(offline);
InfinispanChangelogBasedTransaction<String, UserSessionEntity> tx = getTransaction(offline);
return entity != null ? new UserSessionAdapter(session, this, tx, realm, entity, offline) : null;
}
UserLoginFailureModel wrap(LoginFailureKey key, LoginFailureEntity entity) {
return entity != null ? new UserLoginFailureAdapter(this, loginFailureCache, key, entity) : null;
return entity != null ? new UserLoginFailureAdapter(this, key, entity) : null;
}
UserSessionEntity getUserSessionEntity(UserSessionModel userSession, boolean offline) {
@@ -720,7 +784,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
entity.setLastSessionRefresh(userSession.getLastSessionRefresh());
InfinispanChangelogBasedTransaction<UserSessionEntity> tx = getTransaction(offline);
InfinispanChangelogBasedTransaction<String, UserSessionEntity> tx = getTransaction(offline);
SessionUpdateTask importTask = new SessionUpdateTask<UserSessionEntity>() {
@@ -756,7 +820,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
private AuthenticatedClientSessionAdapter importClientSession(UserSessionAdapter importedUserSession, AuthenticatedClientSessionModel clientSession,
InfinispanChangelogBasedTransaction<UserSessionEntity> updateTx) {
InfinispanChangelogBasedTransaction<String, UserSessionEntity> updateTx) {
AuthenticatedClientSessionEntity entity = new AuthenticatedClientSessionEntity();
entity.setAction(clientSession.getAction());
@@ -85,7 +85,7 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
InfinispanConnectionProvider connections = session.getProvider(InfinispanConnectionProvider.class);
Cache<String, SessionEntityWrapper<UserSessionEntity>> cache = connections.getCache(InfinispanConnectionProvider.SESSION_CACHE_NAME);
Cache<String, SessionEntityWrapper<UserSessionEntity>> offlineSessionsCache = connections.getCache(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME);
Cache<LoginFailureKey, LoginFailureEntity> loginFailures = connections.getCache(InfinispanConnectionProvider.LOGIN_FAILURE_CACHE_NAME);
Cache<LoginFailureKey, SessionEntityWrapper<LoginFailureEntity>> loginFailures = connections.getCache(InfinispanConnectionProvider.LOGIN_FAILURE_CACHE_NAME);
return new InfinispanUserSessionProvider(session, remoteCacheInvoker, lastSessionRefreshStore, offlineLastSessionRefreshStore, cache, offlineSessionsCache, loginFailures);
}
@@ -224,6 +224,11 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
if (offlineSessionsRemoteCache) {
offlineLastSessionRefreshStore = new LastSessionRefreshStoreFactory().createAndInit(session, offlineSessionsCache, true);
}
Cache loginFailuresCache = ispn.getCache(InfinispanConnectionProvider.LOGIN_FAILURE_CACHE_NAME);
boolean loginFailuresRemoteCache = checkRemoteCache(session, loginFailuresCache, (RealmModel realm) -> {
return realm.getMaxDeltaTimeSeconds() * 1000;
});
}
private boolean checkRemoteCache(KeycloakSession session, Cache ispnCache, RemoteCacheInvoker.MaxIdleTimeLoader maxIdleLoader) {
@@ -248,12 +253,12 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
private void loadSessionsFromRemoteCaches(KeycloakSession session) {
for (String cacheName : remoteCacheInvoker.getRemoteCacheNames()) {
loadSessionsFromRemoteCache(session.getKeycloakSessionFactory(), cacheName, getMaxErrors());
loadSessionsFromRemoteCache(session.getKeycloakSessionFactory(), cacheName, getSessionsPerSegment(), getMaxErrors());
}
}
private void loadSessionsFromRemoteCache(final KeycloakSessionFactory sessionFactory, String cacheName, final int maxErrors) {
private void loadSessionsFromRemoteCache(final KeycloakSessionFactory sessionFactory, String cacheName, final int sessionsPerSegment, final int maxErrors) {
log.debugf("Check pre-loading userSessions from remote cache '%s'", cacheName);
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
@@ -263,8 +268,7 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
InfinispanConnectionProvider connections = session.getProvider(InfinispanConnectionProvider.class);
Cache<String, Serializable> workCache = connections.getCache(InfinispanConnectionProvider.WORK_CACHE_NAME);
// Use limit for sessionsPerSegment as RemoteCache bulk load doesn't have support for pagination :/
BaseCacheInitializer initializer = new SingleWorkerCacheInitializer(session, workCache, new RemoteCacheSessionsLoader(cacheName), "remoteCacheLoad::" + cacheName);
InfinispanCacheInitializer initializer = new InfinispanCacheInitializer(sessionFactory, workCache, new RemoteCacheSessionsLoader(cacheName), "remoteCacheLoad::" + cacheName, sessionsPerSegment, maxErrors);
initializer.initCache();
initializer.loadSessions();
@@ -17,8 +17,8 @@
package org.keycloak.models.sessions.infinispan;
import org.infinispan.Cache;
import org.keycloak.models.UserLoginFailureModel;
import org.keycloak.models.sessions.infinispan.changes.LoginFailuresUpdateTask;
import org.keycloak.models.sessions.infinispan.entities.LoginFailureEntity;
import org.keycloak.models.sessions.infinispan.entities.LoginFailureKey;
@@ -28,13 +28,11 @@ import org.keycloak.models.sessions.infinispan.entities.LoginFailureKey;
public class UserLoginFailureAdapter implements UserLoginFailureModel {
private InfinispanUserSessionProvider provider;
private Cache<LoginFailureKey, LoginFailureEntity> cache;
private LoginFailureKey key;
private LoginFailureEntity entity;
public UserLoginFailureAdapter(InfinispanUserSessionProvider provider, Cache<LoginFailureKey, LoginFailureEntity> cache, LoginFailureKey key, LoginFailureEntity entity) {
public UserLoginFailureAdapter(InfinispanUserSessionProvider provider, LoginFailureKey key, LoginFailureEntity entity) {
this.provider = provider;
this.cache = cache;
this.key = key;
this.entity = entity;
}
@@ -51,8 +49,16 @@ public class UserLoginFailureAdapter implements UserLoginFailureModel {
@Override
public void setFailedLoginNotBefore(int notBefore) {
entity.setFailedLoginNotBefore(notBefore);
update();
LoginFailuresUpdateTask task = new LoginFailuresUpdateTask() {
@Override
public void runUpdate(LoginFailureEntity entity) {
entity.setFailedLoginNotBefore(notBefore);
}
};
update(task);
}
@Override
@@ -62,14 +68,30 @@ public class UserLoginFailureAdapter implements UserLoginFailureModel {
@Override
public void incrementFailures() {
entity.setNumFailures(getNumFailures() + 1);
update();
LoginFailuresUpdateTask task = new LoginFailuresUpdateTask() {
@Override
public void runUpdate(LoginFailureEntity entity) {
entity.setNumFailures(entity.getNumFailures() + 1);
}
};
update(task);
}
@Override
public void clearFailures() {
entity.clearFailures();
update();
LoginFailuresUpdateTask task = new LoginFailuresUpdateTask() {
@Override
public void runUpdate(LoginFailureEntity entity) {
entity.clearFailures();
}
};
update(task);
}
@Override
@@ -79,8 +101,16 @@ public class UserLoginFailureAdapter implements UserLoginFailureModel {
@Override
public void setLastFailure(long lastFailure) {
entity.setLastFailure(lastFailure);
update();
LoginFailuresUpdateTask task = new LoginFailuresUpdateTask() {
@Override
public void runUpdate(LoginFailureEntity entity) {
entity.setLastFailure(lastFailure);
}
};
update(task);
}
@Override
@@ -90,12 +120,20 @@ public class UserLoginFailureAdapter implements UserLoginFailureModel {
@Override
public void setLastIPFailure(String ip) {
entity.setLastIPFailure(ip);
update();
LoginFailuresUpdateTask task = new LoginFailuresUpdateTask() {
@Override
public void runUpdate(LoginFailureEntity entity) {
entity.setLastIPFailure(ip);
}
};
update(task);
}
void update() {
provider.getTx().replace(cache, key, entity);
void update(LoginFailuresUpdateTask task) {
provider.getLoginFailuresTx().addTask(key, task);
}
}
@@ -82,25 +82,28 @@ public class UserSessionAdapter implements UserSessionModel {
});
}
// Update user session
if (!removedClientUUIDS.isEmpty()) {
UserSessionUpdateTask task = new UserSessionUpdateTask() {
@Override
public void runUpdate(UserSessionEntity entity) {
for (String clientUUID : removedClientUUIDS) {
entity.getAuthenticatedClientSessions().remove(clientUUID);
}
}
};
update(task);
}
removeAuthenticatedClientSessions(removedClientUUIDS);
return Collections.unmodifiableMap(result);
}
@Override
public void removeAuthenticatedClientSessions(Iterable<String> removedClientUUIDS) {
if (removedClientUUIDS == null || ! removedClientUUIDS.iterator().hasNext()) {
return;
}
// Update user session
UserSessionUpdateTask task = new UserSessionUpdateTask() {
@Override
public void runUpdate(UserSessionEntity entity) {
removedClientUUIDS.forEach(entity.getAuthenticatedClientSessions()::remove);
}
};
update(task);
}
public String getId() {
return entity.getId();
}
@@ -33,18 +33,18 @@ import org.keycloak.models.sessions.infinispan.remotestore.RemoteCacheInvoker;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class InfinispanChangelogBasedTransaction<S extends SessionEntity> extends AbstractKeycloakTransaction {
public class InfinispanChangelogBasedTransaction<K, V extends SessionEntity> extends AbstractKeycloakTransaction {
public static final Logger logger = Logger.getLogger(InfinispanChangelogBasedTransaction.class);
private final KeycloakSession kcSession;
private final String cacheName;
private final Cache<String, SessionEntityWrapper<S>> cache;
private final Cache<K, SessionEntityWrapper<V>> cache;
private final RemoteCacheInvoker remoteCacheInvoker;
private final Map<String, SessionUpdatesList<S>> updates = new HashMap<>();
private final Map<K, SessionUpdatesList<V>> updates = new HashMap<>();
public InfinispanChangelogBasedTransaction(KeycloakSession kcSession, String cacheName, Cache<String, SessionEntityWrapper<S>> cache, RemoteCacheInvoker remoteCacheInvoker) {
public InfinispanChangelogBasedTransaction(KeycloakSession kcSession, String cacheName, Cache<K, SessionEntityWrapper<V>> cache, RemoteCacheInvoker remoteCacheInvoker) {
this.kcSession = kcSession;
this.cacheName = cacheName;
this.cache = cache;
@@ -52,11 +52,11 @@ public class InfinispanChangelogBasedTransaction<S extends SessionEntity> extend
}
public void addTask(String key, SessionUpdateTask<S> task) {
SessionUpdatesList<S> myUpdates = updates.get(key);
public void addTask(K key, SessionUpdateTask<V> task) {
SessionUpdatesList<V> myUpdates = updates.get(key);
if (myUpdates == null) {
// Lookup entity from cache
SessionEntityWrapper<S> wrappedEntity = cache.get(key);
SessionEntityWrapper<V> wrappedEntity = cache.get(key);
if (wrappedEntity == null) {
logger.warnf("Not present cache item for key %s", key);
return;
@@ -75,14 +75,14 @@ public class InfinispanChangelogBasedTransaction<S extends SessionEntity> extend
// Create entity and new version for it
public void addTask(String key, SessionUpdateTask<S> task, S entity) {
public void addTask(K key, SessionUpdateTask<V> task, V entity) {
if (entity == null) {
throw new IllegalArgumentException("Null entity not allowed");
}
RealmModel realm = kcSession.realms().getRealm(entity.getRealm());
SessionEntityWrapper<S> wrappedEntity = new SessionEntityWrapper<>(entity);
SessionUpdatesList<S> myUpdates = new SessionUpdatesList<>(realm, wrappedEntity);
SessionEntityWrapper<V> wrappedEntity = new SessionEntityWrapper<>(entity);
SessionUpdatesList<V> myUpdates = new SessionUpdatesList<>(realm, wrappedEntity);
updates.put(key, myUpdates);
// Run the update now, so reader in same transaction can see it
@@ -91,19 +91,19 @@ public class InfinispanChangelogBasedTransaction<S extends SessionEntity> extend
}
public void reloadEntityInCurrentTransaction(RealmModel realm, String key, SessionEntityWrapper<S> entity) {
public void reloadEntityInCurrentTransaction(RealmModel realm, K key, SessionEntityWrapper<V> entity) {
if (entity == null) {
throw new IllegalArgumentException("Null entity not allowed");
}
SessionEntityWrapper<S> latestEntity = cache.get(key);
SessionEntityWrapper<V> latestEntity = cache.get(key);
if (latestEntity == null) {
return;
}
SessionUpdatesList<S> newUpdates = new SessionUpdatesList<>(realm, latestEntity);
SessionUpdatesList<V> newUpdates = new SessionUpdatesList<>(realm, latestEntity);
SessionUpdatesList<S> existingUpdates = updates.get(key);
SessionUpdatesList<V> existingUpdates = updates.get(key);
if (existingUpdates != null) {
newUpdates.setUpdateTasks(existingUpdates.getUpdateTasks());
}
@@ -112,10 +112,10 @@ public class InfinispanChangelogBasedTransaction<S extends SessionEntity> extend
}
public SessionEntityWrapper<S> get(String key) {
SessionUpdatesList<S> myUpdates = updates.get(key);
public SessionEntityWrapper<V> get(K key) {
SessionUpdatesList<V> myUpdates = updates.get(key);
if (myUpdates == null) {
SessionEntityWrapper<S> wrappedEntity = cache.get(key);
SessionEntityWrapper<V> wrappedEntity = cache.get(key);
if (wrappedEntity == null) {
return null;
}
@@ -127,7 +127,7 @@ public class InfinispanChangelogBasedTransaction<S extends SessionEntity> extend
return wrappedEntity;
} else {
S entity = myUpdates.getEntityWrapper().getEntity();
V entity = myUpdates.getEntityWrapper().getEntity();
// If entity is scheduled for remove, we don't return it.
boolean scheduledForRemove = myUpdates.getUpdateTasks().stream().filter((SessionUpdateTask task) -> {
@@ -143,13 +143,13 @@ public class InfinispanChangelogBasedTransaction<S extends SessionEntity> extend
@Override
protected void commitImpl() {
for (Map.Entry<String, SessionUpdatesList<S>> entry : updates.entrySet()) {
SessionUpdatesList<S> sessionUpdates = entry.getValue();
SessionEntityWrapper<S> sessionWrapper = sessionUpdates.getEntityWrapper();
for (Map.Entry<K, SessionUpdatesList<V>> entry : updates.entrySet()) {
SessionUpdatesList<V> sessionUpdates = entry.getValue();
SessionEntityWrapper<V> sessionWrapper = sessionUpdates.getEntityWrapper();
RealmModel realm = sessionUpdates.getRealm();
MergedUpdate<S> merged = MergedUpdate.computeUpdate(sessionUpdates.getUpdateTasks(), sessionWrapper);
MergedUpdate<V> merged = MergedUpdate.computeUpdate(sessionUpdates.getUpdateTasks(), sessionWrapper);
if (merged != null) {
// Now run the operation in our cluster
@@ -162,8 +162,8 @@ public class InfinispanChangelogBasedTransaction<S extends SessionEntity> extend
}
private void runOperationInCluster(String key, MergedUpdate<S> task, SessionEntityWrapper<S> sessionWrapper) {
S session = sessionWrapper.getEntity();
private void runOperationInCluster(K key, MergedUpdate<V> task, SessionEntityWrapper<V> sessionWrapper) {
V session = sessionWrapper.getEntity();
SessionUpdateTask.CacheOperation operation = task.getOperation(session);
// Don't need to run update of underlying entity. Local updates were already run
@@ -182,9 +182,14 @@ public class InfinispanChangelogBasedTransaction<S extends SessionEntity> extend
.put(key, sessionWrapper, task.getLifespanMs(), TimeUnit.MILLISECONDS);
break;
case ADD_IF_ABSENT:
SessionEntityWrapper existing = cache.putIfAbsent(key, sessionWrapper);
SessionEntityWrapper<V> existing = cache.putIfAbsent(key, sessionWrapper);
if (existing != null) {
throw new IllegalStateException("There is already existing value in cache for key " + key);
logger.debugf("Existing entity in cache for key: %s . Will update it", key);
// Apply updates on the existing entity and replace it
task.runUpdate(existing.getEntity());
replace(key, task, existing);
}
break;
case REPLACE:
@@ -197,12 +202,12 @@ public class InfinispanChangelogBasedTransaction<S extends SessionEntity> extend
}
private void replace(String key, MergedUpdate<S> task, SessionEntityWrapper<S> oldVersionEntity) {
private void replace(K key, MergedUpdate<V> task, SessionEntityWrapper<V> oldVersionEntity) {
boolean replaced = false;
S session = oldVersionEntity.getEntity();
V session = oldVersionEntity.getEntity();
while (!replaced) {
SessionEntityWrapper<S> newVersionEntity = generateNewVersionAndWrapEntity(session, oldVersionEntity.getLocalMetadata());
SessionEntityWrapper<V> newVersionEntity = generateNewVersionAndWrapEntity(session, oldVersionEntity.getLocalMetadata());
// Atomic cluster-aware replace
replaced = cache.replace(key, oldVersionEntity, newVersionEntity);
@@ -235,7 +240,7 @@ public class InfinispanChangelogBasedTransaction<S extends SessionEntity> extend
protected void rollbackImpl() {
}
private SessionEntityWrapper<S> generateNewVersionAndWrapEntity(S entity, Map<String, String> localMetadata) {
private SessionEntityWrapper<V> generateNewVersionAndWrapEntity(V entity, Map<String, String> localMetadata) {
return new SessionEntityWrapper<>(localMetadata, entity);
}
@@ -0,0 +1,36 @@
/*
* Copyright 2017 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models.sessions.infinispan.changes;
import org.keycloak.models.sessions.infinispan.entities.LoginFailureEntity;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public abstract class LoginFailuresUpdateTask implements SessionUpdateTask<LoginFailureEntity> {
@Override
public CacheOperation getOperation(LoginFailureEntity session) {
return CacheOperation.REPLACE;
}
@Override
public CrossDCMessageStatus getCrossDCMessageStatus(SessionEntityWrapper<LoginFailureEntity> sessionWrapper) {
return CrossDCMessageStatus.SYNC;
}
}
@@ -32,7 +32,7 @@ class MergedUpdate<S extends SessionEntity> implements SessionUpdateTask<S> {
private CrossDCMessageStatus crossDCMessageStatus;
public MergedUpdate(CacheOperation operation, CrossDCMessageStatus crossDCMessageStatus) {
private MergedUpdate(CacheOperation operation, CrossDCMessageStatus crossDCMessageStatus) {
this.operation = operation;
this.crossDCMessageStatus = crossDCMessageStatus;
}
@@ -17,8 +17,6 @@
package org.keycloak.models.sessions.infinispan.changes;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
/**
@@ -51,7 +49,7 @@ public interface SessionUpdateTask<S extends SessionEntity> {
if (this == ADD | this == ADD_IF_ABSENT) {
if (other == ADD | other == ADD_IF_ABSENT) {
throw new IllegalStateException("Illegal state. Task already in progress for session " + entity.getId());
throw new IllegalStateException("Illegal state. Task already in progress for session " + entity.toString());
}
return this;
@@ -17,8 +17,6 @@
package org.keycloak.models.sessions.infinispan.changes;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
/**
@@ -60,7 +60,7 @@ public class LastSessionRefreshChecker {
Integer lsrr = sessionWrapper.getLocalMetadataNoteInt(UserSessionEntity.LAST_SESSION_REFRESH_REMOTE);
if (lsrr == null) {
logger.warnf("Not available lsrr note on user session %s.", sessionWrapper.getEntity().getId());
logger.debugf("Not available lsrr note on user session %s.", sessionWrapper.getEntity().getId());
return SessionUpdateTask.CrossDCMessageStatus.SYNC;
}
@@ -21,6 +21,7 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import org.keycloak.sessions.AuthenticationSessionModel;
@@ -29,6 +30,8 @@ import org.keycloak.sessions.AuthenticationSessionModel;
*/
public class AuthenticationSessionEntity extends SessionEntity {
private String id;
private String clientUuid;
private String authUserId;
@@ -46,6 +49,14 @@ public class AuthenticationSessionEntity extends SessionEntity {
private Set<String> requiredActions = new HashSet<>();
private Map<String, String> userSessionNotes;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getClientUuid() {
return clientUuid;
}
@@ -149,4 +160,26 @@ public class AuthenticationSessionEntity extends SessionEntity {
public void setAuthNotes(Map<String, String> authNotes) {
this.authNotes = authNotes;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof AuthenticationSessionEntity)) return false;
AuthenticationSessionEntity that = (AuthenticationSessionEntity) o;
if (id != null ? !id.equals(that.id) : that.id != null) return false;
return true;
}
@Override
public int hashCode() {
return id != null ? id.hashCode() : 0;
}
@Override
public String toString() {
return String.format("AuthenticationSessionEntity [id=%s, realm=%s, clientUuid=%s ]", getId(), getRealm(), getClientUuid());
}
}
@@ -17,15 +17,12 @@
package org.keycloak.models.sessions.infinispan.entities;
import java.io.Serializable;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class LoginFailureEntity implements Serializable {
public class LoginFailureEntity extends SessionEntity {
private String userId;
private String realm;
private int failedLoginNotBefore;
private int numFailures;
private long lastFailure;
@@ -39,14 +36,6 @@ public class LoginFailureEntity implements Serializable {
this.userId = userId;
}
public String getRealm() {
return realm;
}
public void setRealm(String realm) {
this.realm = realm;
}
public int getFailedLoginNotBefore() {
return failedLoginNotBefore;
}
@@ -85,4 +74,30 @@ public class LoginFailureEntity implements Serializable {
this.lastFailure = 0;
this.lastIPFailure = null;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof LoginFailureEntity)) return false;
LoginFailureEntity that = (LoginFailureEntity) o;
if (userId != null ? !userId.equals(that.userId) : that.userId != null) return false;
if (getRealm() != null ? !getRealm().equals(that.getRealm()) : that.getRealm() != null) return false;
return true;
}
@Override
public int hashCode() {
int hashCode = getRealm() != null ? getRealm().hashCode() : 0;
hashCode = hashCode * 13 + (userId != null ? userId.hashCode() : 0);
return hashCode;
}
@Override
public String toString() {
return String.format("LoginFailureEntity [ userId=%s, realm=%s, numFailures=%d ]", userId, getRealm(), numFailures);
}
}
@@ -52,4 +52,9 @@ public class LoginFailureKey implements Serializable {
return result;
}
@Override
public String toString() {
return String.format("LoginFailureKey [ realm=%s. userId=%s ]", realm, userId);
}
}
@@ -26,17 +26,8 @@ import org.keycloak.models.sessions.infinispan.changes.SessionEntityWrapper;
*/
public abstract 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;
@@ -46,26 +37,13 @@ public abstract class SessionEntity implements Serializable {
this.realm = realm;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof SessionEntity)) return false;
SessionEntity that = (SessionEntity) o;
if (id != null ? !id.equals(that.id) : that.id != null) return false;
return true;
}
@Override
public int hashCode() {
return id != null ? id.hashCode() : 0;
}
public SessionEntityWrapper mergeRemoteEntityWithLocalEntity(SessionEntityWrapper localEntityWrapper) {
throw new IllegalStateException("Not yet implemented");
if (localEntityWrapper == null) {
return new SessionEntityWrapper<>(this);
} else {
return new SessionEntityWrapper<>(localEntityWrapper.getLocalMetadata(), this);
}
};
}
@@ -43,6 +43,8 @@ public class UserSessionEntity extends SessionEntity {
// Metadata attribute, which contains the lastSessionRefresh available on remoteCache. Used in decide whether we need to write to remoteCache (DC) or not
public static final String LAST_SESSION_REFRESH_REMOTE = "lsrr";
private String id;
private String user;
private String brokerSessionId;
@@ -62,6 +64,14 @@ public class UserSessionEntity extends SessionEntity {
private UserSessionModel.State state;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
private Map<String, String> notes = new ConcurrentHashMap<>();
private Map<String, AuthenticatedClientSessionEntity> authenticatedClientSessions = new ConcurrentHashMap<>();
@@ -162,6 +172,23 @@ public class UserSessionEntity extends SessionEntity {
this.brokerUserId = brokerUserId;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof UserSessionEntity)) return false;
UserSessionEntity that = (UserSessionEntity) o;
if (id != null ? !id.equals(that.id) : that.id != null) return false;
return true;
}
@Override
public int hashCode() {
return id != null ? id.hashCode() : 0;
}
@Override
public String toString() {
return String.format("UserSessionEntity [id=%s, realm=%s, lastSessionRefresh=%d, clients=%s]", getId(), getRealm(), getLastSessionRefresh(),
@@ -106,7 +106,7 @@ public abstract class BaseCacheInitializer extends CacheInitializer {
private InitializerState getStateFromCache() {
// We ignore cacheStore for now, so that in Cross-DC scenario (with RemoteStore enabled) is the remoteStore ignored. This means that every DC needs to load offline sessions separately.
// We ignore cacheStore for now, so that in Cross-DC scenario (with RemoteStore enabled) is the remoteStore ignored.
return (InitializerState) workCache.getAdvancedCache()
.withFlags(Flag.SKIP_CACHE_STORE, Flag.SKIP_CACHE_LOAD)
.get(stateKey);
@@ -122,7 +122,7 @@ public abstract class BaseCacheInitializer extends CacheInitializer {
public void run() {
// Save this synchronously to ensure all nodes read correct state
// We ignore cacheStore for now, so that in Cross-DC scenario (with RemoteStore enabled) is the remoteStore ignored. This means that every DC needs to load offline sessions separately.
// We ignore cacheStore for now, so that in Cross-DC scenario (with RemoteStore enabled) is the remoteStore ignored.
BaseCacheInitializer.this.workCache.getAdvancedCache().
withFlags(Flag.IGNORE_RETURN_VALUES, Flag.FORCE_SYNCHRONOUS, Flag.SKIP_CACHE_STORE, Flag.SKIP_CACHE_LOAD)
.put(stateKey, state);
@@ -35,7 +35,7 @@ import java.util.concurrent.Future;
* Startup initialization for reading persistent userSessions to be filled into infinispan/memory . In cluster,
* the initialization is distributed among all cluster nodes, so the startup time is even faster
*
* TODO: Move to clusterService. Implementation is already pretty generic and doesn't contain any "userSession" specific stuff. All sessions-specific logic is in the SessionLoader implementation
* Implementation is pretty generic and doesn't contain any "userSession" specific stuff. All logic related to how are sessions loaded is in the SessionLoader implementation
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@@ -1,39 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models.sessions.infinispan.remotestore;
import org.infinispan.configuration.cache.PersistenceConfigurationBuilder;
import org.infinispan.persistence.remote.configuration.RemoteStoreConfiguration;
import org.infinispan.persistence.remote.configuration.RemoteStoreConfigurationBuilder;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class KcRemoteStoreConfigurationBuilder extends RemoteStoreConfigurationBuilder {
public KcRemoteStoreConfigurationBuilder(PersistenceConfigurationBuilder builder) {
super(builder);
}
@Override
public KcRemoteStoreConfiguration create() {
RemoteStoreConfiguration cfg = super.create();
KcRemoteStoreConfiguration cfg2 = new KcRemoteStoreConfiguration(cfg.attributes(), cfg.async(), cfg.singletonStore(), cfg.asyncExecutorFactory(), cfg.connectionPool());
return cfg2;
}
}
@@ -17,14 +17,23 @@
package org.keycloak.models.sessions.infinispan.remotestore;
import java.util.Optional;
import java.util.concurrent.Executor;
import org.infinispan.client.hotrod.Flag;
import org.infinispan.commons.CacheException;
import org.infinispan.commons.configuration.ConfiguredBy;
import org.infinispan.configuration.cache.ConfigurationBuilder;
import org.infinispan.configuration.cache.PersistenceConfigurationBuilder;
import org.infinispan.configuration.cache.StoreConfiguration;
import org.infinispan.filter.KeyFilter;
import org.infinispan.manager.EmbeddedCacheManager;
import org.infinispan.marshall.core.MarshalledEntry;
import org.infinispan.metadata.InternalMetadata;
import org.infinispan.persistence.InitializationContextImpl;
import org.infinispan.persistence.remote.RemoteStore;
import org.infinispan.persistence.remote.configuration.RemoteStoreConfiguration;
import org.infinispan.persistence.remote.configuration.RemoteStoreConfigurationBuilder;
import org.infinispan.persistence.spi.InitializationContext;
import org.infinispan.persistence.spi.PersistenceException;
import org.jboss.logging.Logger;
import org.keycloak.models.sessions.infinispan.changes.SessionEntityWrapper;
@@ -33,26 +42,68 @@ import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@ConfiguredBy(KcRemoteStoreConfiguration.class)
public class KcRemoteStore extends RemoteStore {
@ConfiguredBy(KeycloakRemoteStoreConfiguration.class)
public class KeycloakRemoteStore extends RemoteStore {
protected static final Logger logger = Logger.getLogger(KcRemoteStore.class);
protected static final Logger logger = Logger.getLogger(KeycloakRemoteStore.class);
private String cacheName;
private String remoteCacheName;
@Override
public void start() throws PersistenceException {
this.remoteCacheName = getConfiguration().remoteCacheName();
String cacheTemplateName = getConfiguration().useConfigTemplateFromCache();
if (cacheTemplateName != null) {
logger.debugf("Will override configuration of cache '%s' from template of cache '%s'", ctx.getCache().getName(), cacheTemplateName);
// Just to ensure that dependent cache is started and it's configuration fully loaded
EmbeddedCacheManager cacheManager = ctx.getCache().getCacheManager();
cacheManager.getCache(cacheTemplateName, true);
Optional<StoreConfiguration> optional = cacheManager.getCacheConfiguration(cacheTemplateName).persistence().stores().stream().filter((StoreConfiguration storeConfig) -> {
return storeConfig instanceof RemoteStoreConfiguration;
}).findFirst();
if (!optional.isPresent()) {
throw new CacheException("Unable to find remoteStore on cache '" + cacheTemplateName + ".");
}
RemoteStoreConfiguration templateConfig = (RemoteStoreConfiguration) optional.get();
// We have template configuration, so create new configuration from it. Override just remoteCacheName
PersistenceConfigurationBuilder readPersistenceBuilder = new ConfigurationBuilder().read(ctx.getCache().getCacheConfiguration()).persistence();
RemoteStoreConfigurationBuilder configBuilder = new RemoteStoreConfigurationBuilder(readPersistenceBuilder);
configBuilder.read(templateConfig);
configBuilder.remoteCacheName(this.remoteCacheName);
RemoteStoreConfiguration newCfg1 = configBuilder.create();
KeycloakRemoteStoreConfiguration newCfg = new KeycloakRemoteStoreConfiguration(newCfg1);
InitializationContext ctx = new InitializationContextImpl(newCfg, this.ctx.getCache(), this.ctx.getMarshaller(), this.ctx.getTimeService(),
this.ctx.getByteBufferFactory(), this.ctx.getMarshalledEntryFactory());
init(ctx);
} else {
logger.debugf("Skip overriding configuration from template for cache '%s'", ctx.getCache().getName());
}
super.start();
if (getRemoteCache() == null) {
String cacheName = getConfiguration().remoteCacheName();
throw new IllegalStateException("Remote cache '" + cacheName + "' is not available.");
throw new CacheException("Remote cache '" + cacheName + "' is not available.");
}
this.cacheName = getRemoteCache().getName();
}
@Override
public MarshalledEntry load(Object key) throws PersistenceException {
logger.debugf("Calling load: '%s' for remote cache '%s'", key, cacheName);
logger.debugf("Calling load: '%s' for remote cache '%s'", key, remoteCacheName);
MarshalledEntry entry = super.load(key);
if (entry == null) {
@@ -74,7 +125,7 @@ public class KcRemoteStore extends RemoteStore {
// Don't do anything. Iterate over remoteCache.keySet() can have big performance impact. We handle bulk load by ourselves if needed.
@Override
public void process(KeyFilter filter, CacheLoaderTask task, Executor executor, boolean fetchValue, boolean fetchMetadata) {
logger.debugf("Skip calling process with filter '%s' on cache '%s'", filter, cacheName);
logger.debugf("Skip calling process with filter '%s' on cache '%s'", filter, remoteCacheName);
// super.process(filter, task, executor, fetchValue, fetchMetadata);
}
@@ -87,7 +138,7 @@ public class KcRemoteStore extends RemoteStore {
@Override
public boolean delete(Object key) throws PersistenceException {
logger.debugf("Calling delete for key '%s' on cache '%s'", key, cacheName);
logger.debugf("Calling delete for key '%s' on cache '%s'", key, remoteCacheName);
// Optimization - we don't need to know the previous value.
// TODO: For some usecases (bulk removal of user sessions), it may be better for performance to call removeAsync and wait for all futures to be finished
@@ -101,5 +152,8 @@ public class KcRemoteStore extends RemoteStore {
}
@Override
public KeycloakRemoteStoreConfiguration getConfiguration() {
return (KeycloakRemoteStoreConfiguration) super.getConfiguration();
}
}
@@ -19,22 +19,30 @@ package org.keycloak.models.sessions.infinispan.remotestore;
import org.infinispan.commons.configuration.BuiltBy;
import org.infinispan.commons.configuration.ConfigurationFor;
import org.infinispan.commons.configuration.attributes.AttributeSet;
import org.infinispan.configuration.cache.AsyncStoreConfiguration;
import org.infinispan.configuration.cache.SingletonStoreConfiguration;
import org.infinispan.persistence.remote.configuration.ConnectionPoolConfiguration;
import org.infinispan.persistence.remote.configuration.ExecutorFactoryConfiguration;
import org.infinispan.commons.configuration.attributes.Attribute;
import org.infinispan.commons.configuration.attributes.AttributeDefinition;
import org.infinispan.persistence.remote.configuration.RemoteStoreConfiguration;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@BuiltBy(KcRemoteStoreConfigurationBuilder.class)
@ConfigurationFor(KcRemoteStore.class)
public class KcRemoteStoreConfiguration extends RemoteStoreConfiguration {
@BuiltBy(KeycloakRemoteStoreConfigurationBuilder.class)
@ConfigurationFor(KeycloakRemoteStore.class)
public class KeycloakRemoteStoreConfiguration extends RemoteStoreConfiguration {
public KcRemoteStoreConfiguration(AttributeSet attributes, AsyncStoreConfiguration async, SingletonStoreConfiguration singletonStore,
ExecutorFactoryConfiguration asyncExecutorFactory, ConnectionPoolConfiguration connectionPool) {
super(attributes, async, singletonStore, asyncExecutorFactory, connectionPool);
static final AttributeDefinition<String> USE_CONFIG_TEMPLATE_FROM_CACHE = AttributeDefinition.builder("useConfigTemplateFromCache", null, String.class).immutable().build();
private final Attribute<String> useConfigTemplateFromCache;
public KeycloakRemoteStoreConfiguration(RemoteStoreConfiguration other) {
super(other.attributes(), other.async(), other.singletonStore(), other.asyncExecutorFactory(), other.connectionPool());
useConfigTemplateFromCache = attributes.attribute(USE_CONFIG_TEMPLATE_FROM_CACHE.name());
}
public String useConfigTemplateFromCache() {
return useConfigTemplateFromCache==null ? null : useConfigTemplateFromCache.get();
}
}
@@ -0,0 +1,61 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models.sessions.infinispan.remotestore;
import java.lang.reflect.Field;
import java.util.Map;
import org.infinispan.commons.CacheConfigurationException;
import org.infinispan.commons.configuration.attributes.Attribute;
import org.infinispan.commons.configuration.attributes.AttributeDefinition;
import org.infinispan.commons.configuration.attributes.AttributeSet;
import org.infinispan.configuration.cache.PersistenceConfigurationBuilder;
import org.infinispan.persistence.remote.configuration.RemoteStoreConfiguration;
import org.infinispan.persistence.remote.configuration.RemoteStoreConfigurationBuilder;
import org.keycloak.common.util.reflections.Reflections;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class KeycloakRemoteStoreConfigurationBuilder extends RemoteStoreConfigurationBuilder {
public KeycloakRemoteStoreConfigurationBuilder(PersistenceConfigurationBuilder builder) {
super(builder);
// No better way to add new attribute definition to superclass :/
try {
AttributeDefinition<String> def = KeycloakRemoteStoreConfiguration.USE_CONFIG_TEMPLATE_FROM_CACHE;
Attribute<String> attribute = def.toAttribute();
Field f = Reflections.findDeclaredField(AttributeSet.class, "attributes");
f.setAccessible(true);
Map<String, Attribute<? extends Object>> attributesInternal = (Map<String, Attribute<? extends Object>>) f.get(this.attributes);
attributesInternal.put(def.name(), attribute);
} catch (IllegalAccessException iae) {
throw new CacheConfigurationException(iae);
}
}
@Override
public KeycloakRemoteStoreConfiguration create() {
RemoteStoreConfiguration cfg = super.create();
KeycloakRemoteStoreConfiguration cfg2 = new KeycloakRemoteStoreConfiguration(cfg);
return cfg2;
}
}
@@ -55,13 +55,13 @@ public class RemoteCacheInvoker {
}
public <S extends SessionEntity> void runTask(KeycloakSession kcSession, RealmModel realm, String cacheName, String key, SessionUpdateTask<S> task, SessionEntityWrapper<S> sessionWrapper) {
public <K, V extends SessionEntity> void runTask(KeycloakSession kcSession, RealmModel realm, String cacheName, K key, SessionUpdateTask<V> task, SessionEntityWrapper<V> sessionWrapper) {
RemoteCacheContext context = remoteCaches.get(cacheName);
if (context == null) {
return;
}
S session = sessionWrapper.getEntity();
V session = sessionWrapper.getEntity();
SessionUpdateTask.CacheOperation operation = task.getOperation(session);
SessionUpdateTask.CrossDCMessageStatus status = task.getCrossDCMessageStatus(sessionWrapper);
@@ -82,8 +82,8 @@ public class RemoteCacheInvoker {
}
private <S extends SessionEntity> void runOnRemoteCache(RemoteCache remoteCache, long maxIdleMs, String key, SessionUpdateTask<S> task, SessionEntityWrapper<S> sessionWrapper) {
S session = sessionWrapper.getEntity();
private <K, V extends SessionEntity> void runOnRemoteCache(RemoteCache<K, V> remoteCache, long maxIdleMs, K key, SessionUpdateTask<V> task, SessionEntityWrapper<V> sessionWrapper) {
V session = sessionWrapper.getEntity();
SessionUpdateTask.CacheOperation operation = task.getOperation(session);
switch (operation) {
@@ -96,13 +96,16 @@ public class RemoteCacheInvoker {
break;
case ADD_IF_ABSENT:
final int currentTime = Time.currentTime();
SessionEntity existing = (SessionEntity) remoteCache
SessionEntity existing = remoteCache
.withFlags(Flag.FORCE_RETURN_VALUE)
.putIfAbsent(key, session, -1, TimeUnit.MILLISECONDS, maxIdleMs, TimeUnit.MILLISECONDS);
if (existing != null) {
throw new IllegalStateException("There is already existing value in cache for key " + key);
logger.debugf("Existing entity in remote cache for key: %s . Will update it", key);
replace(remoteCache, task.getLifespanMs(), maxIdleMs, key, task);
} else {
sessionWrapper.putLocalMetadataNoteInt(UserSessionEntity.LAST_SESSION_REFRESH_REMOTE, currentTime);
}
sessionWrapper.putLocalMetadataNoteInt(UserSessionEntity.LAST_SESSION_REFRESH_REMOTE, currentTime);
break;
case REPLACE:
replace(remoteCache, task.getLifespanMs(), maxIdleMs, key, task);
@@ -113,16 +116,16 @@ public class RemoteCacheInvoker {
}
private <S extends SessionEntity> void replace(RemoteCache remoteCache, long lifespanMs, long maxIdleMs, String key, SessionUpdateTask<S> task) {
private <K, V extends SessionEntity> void replace(RemoteCache<K, V> remoteCache, long lifespanMs, long maxIdleMs, K key, SessionUpdateTask<V> task) {
boolean replaced = false;
while (!replaced) {
VersionedValue<S> versioned = remoteCache.getVersioned(key);
VersionedValue<V> versioned = remoteCache.getVersioned(key);
if (versioned == null) {
logger.warnf("Not found entity to replace for key '%s'", key);
return;
}
S session = versioned.getValue();
V session = versioned.getValue();
// Run task on the remote session
task.runUpdate(session);
@@ -44,12 +44,12 @@ import org.infinispan.client.hotrod.VersionedValue;
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@ClientListener
public class RemoteCacheSessionListener {
public class RemoteCacheSessionListener<K, V extends SessionEntity> {
protected static final Logger logger = Logger.getLogger(RemoteCacheSessionListener.class);
private Cache<String, SessionEntityWrapper> cache;
private RemoteCache remoteCache;
private Cache<K, SessionEntityWrapper<V>> cache;
private RemoteCache<K, V> remoteCache;
private boolean distributed;
private String myAddress;
@@ -58,7 +58,7 @@ public class RemoteCacheSessionListener {
}
protected void init(KeycloakSession session, Cache<String, SessionEntityWrapper> cache, RemoteCache remoteCache) {
protected void init(KeycloakSession session, Cache<K, SessionEntityWrapper<V>> cache, RemoteCache<K, V> remoteCache) {
this.cache = cache;
this.remoteCache = remoteCache;
@@ -73,7 +73,7 @@ public class RemoteCacheSessionListener {
@ClientCacheEntryCreated
public void created(ClientCacheEntryCreatedEvent event) {
String key = (String) event.getKey();
K key = (K) event.getKey();
if (shouldUpdateLocalCache(event.getType(), key, event.isCommandRetried())) {
// Should load it from remoteStore
@@ -84,7 +84,7 @@ public class RemoteCacheSessionListener {
@ClientCacheEntryModified
public void updated(ClientCacheEntryModifiedEvent event) {
String key = (String) event.getKey();
K key = (K) event.getKey();
if (shouldUpdateLocalCache(event.getType(), key, event.isCommandRetried())) {
@@ -94,7 +94,7 @@ public class RemoteCacheSessionListener {
private static final int MAXIMUM_REPLACE_RETRIES = 10;
private void replaceRemoteEntityInCache(String key, long eventVersion) {
private void replaceRemoteEntityInCache(K key, long eventVersion) {
// TODO can be optimized and remoteSession sent in the event itself?
boolean replaced = false;
int replaceRetries = 0;
@@ -102,8 +102,8 @@ public class RemoteCacheSessionListener {
do {
replaceRetries++;
SessionEntityWrapper localEntityWrapper = cache.get(key);
VersionedValue remoteSessionVersioned = remoteCache.getVersioned(key);
SessionEntityWrapper<V> localEntityWrapper = cache.get(key);
VersionedValue<V> remoteSessionVersioned = remoteCache.getVersioned(key);
if (remoteSessionVersioned == null || remoteSessionVersioned.getVersion() < eventVersion) {
try {
logger.debugf("Got replace remote entity event prematurely, will try again. Event version: %d, got: %d",
@@ -120,7 +120,7 @@ public class RemoteCacheSessionListener {
logger.debugf("Read session%s. Entity read from remote cache: %s", replaceRetries > 1 ? "" : " again", remoteSession);
SessionEntityWrapper sessionWrapper = remoteSession.mergeRemoteEntityWithLocalEntity(localEntityWrapper);
SessionEntityWrapper<V> sessionWrapper = remoteSession.mergeRemoteEntityWithLocalEntity(localEntityWrapper);
// We received event from remoteCache, so we won't update it back
replaced = cache.getAdvancedCache().withFlags(Flag.SKIP_CACHE_STORE, Flag.SKIP_CACHE_LOAD, Flag.IGNORE_RETURN_VALUES)
@@ -135,7 +135,7 @@ public class RemoteCacheSessionListener {
@ClientCacheEntryRemoved
public void removed(ClientCacheEntryRemovedEvent event) {
String key = (String) event.getKey();
K key = (K) event.getKey();
if (shouldUpdateLocalCache(event.getType(), key, event.isCommandRetried())) {
// We received event from remoteCache, so we won't update it back
@@ -152,7 +152,7 @@ public class RemoteCacheSessionListener {
// For distributed caches, ensure that local modification is executed just on owner OR if event.isCommandRetried
protected boolean shouldUpdateLocalCache(ClientEvent.Type type, String key, boolean commandRetried) {
protected boolean shouldUpdateLocalCache(ClientEvent.Type type, K key, boolean commandRetried) {
boolean result;
// Case when cache is stopping or stopped already
@@ -184,7 +184,7 @@ public class RemoteCacheSessionListener {
}
public static RemoteCacheSessionListener createListener(KeycloakSession session, Cache<String, SessionEntityWrapper> cache, RemoteCache remoteCache) {
public static <K, V extends SessionEntity> RemoteCacheSessionListener createListener(KeycloakSession session, Cache<K, SessionEntityWrapper<V>> cache, RemoteCache<K, V> remoteCache) {
/*boolean isCoordinator = InfinispanUtil.isCoordinator(cache);
// Just cluster coordinator will fetch userSessions from remote cache.
@@ -198,7 +198,7 @@ public class RemoteCacheSessionListener {
listener = new DontFetchInitialStateCacheListener();
}*/
RemoteCacheSessionListener listener = new RemoteCacheSessionListener();
RemoteCacheSessionListener<K, V> listener = new RemoteCacheSessionListener<>();
listener.init(session, cache, remoteCache);
return listener;
@@ -18,10 +18,12 @@
package org.keycloak.models.sessions.infinispan.remotestore;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import org.infinispan.Cache;
import org.infinispan.client.hotrod.RemoteCache;
import org.infinispan.commons.marshall.Marshaller;
import org.infinispan.context.Flag;
import org.jboss.logging.Logger;
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
@@ -40,8 +42,33 @@ public class RemoteCacheSessionsLoader implements SessionLoader {
private static final Logger log = Logger.getLogger(RemoteCacheSessionsLoader.class);
// Hardcoded limit for now. See if needs to be configurable (or if preloading can be enabled/disabled in configuration)
public static final int LIMIT = 100000;
// Javascript to be executed on remote infinispan server (Flag CACHE_MODE_LOCAL assumes that remoteCache is replicated)
private static final String REMOTE_SCRIPT_FOR_LOAD_SESSIONS =
"function loadSessions() {" +
" var flagClazz = cache.getClass().getClassLoader().loadClass(\"org.infinispan.context.Flag\"); \n" +
" var localFlag = java.lang.Enum.valueOf(flagClazz, \"CACHE_MODE_LOCAL\"); \n" +
" var cacheStream = cache.getAdvancedCache().withFlags([ localFlag ]).entrySet().stream();\n" +
" var result = cacheStream.skip(first).limit(max).collect(java.util.stream.Collectors.toMap(\n" +
" new java.util.function.Function() {\n" +
" apply: function(entry) {\n" +
" return entry.getKey();\n" +
" }\n" +
" },\n" +
" new java.util.function.Function() {\n" +
" apply: function(entry) {\n" +
" return entry.getValue();\n" +
" }\n" +
" }\n" +
" ));\n" +
"\n" +
" cacheStream.close();\n" +
" return result;\n" +
"};\n" +
"\n" +
"loadSessions();";
private final String cacheName;
@@ -51,7 +78,15 @@ public class RemoteCacheSessionsLoader implements SessionLoader {
@Override
public void init(KeycloakSession session) {
RemoteCache remoteCache = InfinispanUtil.getRemoteCache(getCache(session));
RemoteCache<String, String> scriptCache = remoteCache.getRemoteCacheManager().getCache("___script_cache");
if (!scriptCache.containsKey("load-sessions.js")) {
scriptCache.put("load-sessions.js",
"// mode=local,language=javascript\n" +
REMOTE_SCRIPT_FOR_LOAD_SESSIONS);
}
}
@Override
@@ -67,21 +102,28 @@ public class RemoteCacheSessionsLoader implements SessionLoader {
RemoteCache<?, ?> remoteCache = InfinispanUtil.getRemoteCache(cache);
int size = remoteCache.size();
log.debugf("Will do bulk load of sessions from remote cache '%s' . First: %d, max: %d", cache.getName(), first, max);
if (size > LIMIT) {
log.infof("Skip bulk load of '%d' sessions from remote cache '%s'. Sessions will be retrieved lazily", size, cache.getName());
return true;
} else {
log.infof("Will do bulk load of '%d' sessions from remote cache '%s'", size, cache.getName());
}
Map<String, Integer> remoteParams = new HashMap<>();
remoteParams.put("first", first);
remoteParams.put("max", max);
Map<byte[], byte[]> remoteObjects = remoteCache.execute("load-sessions.js", remoteParams);
log.debugf("Successfully finished loading sessions '%s' . First: %d, max: %d", cache.getName(), first, max);
for (Map.Entry<?, ?> entry : remoteCache.getBulk().entrySet()) {
SessionEntity entity = (SessionEntity) entry.getValue();
SessionEntityWrapper entityWrapper = new SessionEntityWrapper(entity);
Marshaller marshaller = remoteCache.getRemoteCacheManager().getMarshaller();
decoratedCache.putAsync(entry.getKey(), entityWrapper);
for (Map.Entry<byte[], byte[]> entry : remoteObjects.entrySet()) {
try {
Object key = marshaller.objectFromByteBuffer(entry.getKey());
SessionEntity entity = (SessionEntity) marshaller.objectFromByteBuffer(entry.getValue());
SessionEntityWrapper entityWrapper = new SessionEntityWrapper(entity);
decoratedCache.putAsync(key, entityWrapper);
} catch (Exception e) {
log.warn("Error loading session from remote cache", e);
}
}
return true;
@@ -48,7 +48,7 @@ public class Mappers {
return new UserSessionEntityMapper();
}
public static Function<Map.Entry<LoginFailureKey, LoginFailureEntity>, LoginFailureKey> loginFailureId() {
public static Function<Map.Entry<LoginFailureKey, SessionEntityWrapper<LoginFailureEntity>>, LoginFailureKey> loginFailureId() {
return new LoginFailureIdMapper();
}
@@ -103,9 +103,9 @@ public class Mappers {
}
private static class LoginFailureIdMapper implements Function<Map.Entry<LoginFailureKey, LoginFailureEntity>, LoginFailureKey>, Serializable {
private static class LoginFailureIdMapper implements Function<Map.Entry<LoginFailureKey, SessionEntityWrapper<LoginFailureEntity>>, LoginFailureKey>, Serializable {
@Override
public LoginFailureKey apply(Map.Entry<LoginFailureKey, LoginFailureEntity> entry) {
public LoginFailureKey apply(Map.Entry<LoginFailureKey, SessionEntityWrapper<LoginFailureEntity>> entry) {
return entry.getKey();
}
}
@@ -17,6 +17,7 @@
package org.keycloak.models.sessions.infinispan.stream;
import org.keycloak.models.sessions.infinispan.changes.SessionEntityWrapper;
import org.keycloak.models.sessions.infinispan.entities.LoginFailureEntity;
import org.keycloak.models.sessions.infinispan.entities.LoginFailureKey;
@@ -27,7 +28,7 @@ import java.util.function.Predicate;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class UserLoginFailurePredicate implements Predicate<Map.Entry<LoginFailureKey, LoginFailureEntity>>, Serializable {
public class UserLoginFailurePredicate implements Predicate<Map.Entry<LoginFailureKey, SessionEntityWrapper<LoginFailureEntity>>>, Serializable {
private String realm;
@@ -40,8 +41,8 @@ public class UserLoginFailurePredicate implements Predicate<Map.Entry<LoginFailu
}
@Override
public boolean test(Map.Entry<LoginFailureKey, LoginFailureEntity> entry) {
LoginFailureEntity e = entry.getValue();
public boolean test(Map.Entry<LoginFailureKey, SessionEntityWrapper<LoginFailureEntity>> entry) {
LoginFailureEntity e = entry.getValue().getEntity();
return realm.equals(e.getRealm());
}
@@ -0,0 +1,59 @@
/*
* Copyright 2017 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models.sessions.infinispan.util;
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import org.jboss.logging.Logger;
/**
* Not thread-safe. Assumes tasks are added from single thread.
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class FuturesHelper {
private static final Logger log = Logger.getLogger(FuturesHelper.class);
private final Queue<Future> futures = new LinkedList<>();
public void addTask(Future future) {
this.futures.add(future);
}
public void waitForAllToFinish() {
for (Future future : futures) {
try {
future.get();
} catch (ExecutionException | InterruptedException ee) {
log.error("Exception when waiting for future", ee); // TODO Possibly some good mechanism to avoid swamp log with many same exceptions?
}
}
}
public int size() {
return futures.size();
}
}
@@ -29,15 +29,8 @@ import org.infinispan.client.hotrod.annotation.ClientCacheEntryModified;
import org.infinispan.client.hotrod.annotation.ClientListener;
import org.infinispan.client.hotrod.event.ClientCacheEntryCreatedEvent;
import org.infinispan.client.hotrod.event.ClientCacheEntryModifiedEvent;
import org.infinispan.configuration.cache.Configuration;
import org.infinispan.configuration.cache.ConfigurationBuilder;
import org.infinispan.configuration.global.GlobalConfigurationBuilder;
import org.infinispan.context.Flag;
import org.infinispan.manager.DefaultCacheManager;
import org.infinispan.manager.EmbeddedCacheManager;
import org.infinispan.persistence.manager.PersistenceManager;
import org.infinispan.persistence.remote.RemoteStore;
import org.infinispan.persistence.remote.configuration.ExhaustedAction;
import org.jboss.logging.Logger;
import org.junit.Assert;
import org.keycloak.common.util.Time;
@@ -46,8 +39,7 @@ import org.keycloak.models.sessions.infinispan.changes.SessionEntityWrapper;
import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionEntity;
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
import org.keycloak.models.sessions.infinispan.remotestore.KcRemoteStore;
import org.keycloak.models.sessions.infinispan.remotestore.KcRemoteStoreConfigurationBuilder;
import org.keycloak.models.sessions.infinispan.remotestore.KeycloakRemoteStoreConfigurationBuilder;
import org.keycloak.models.sessions.infinispan.util.InfinispanUtil;
/**
@@ -212,7 +204,7 @@ public class ConcurrencyJDGSessionsCacheTest {
private static EmbeddedCacheManager createManager(int threadId) {
return new TestCacheManagerFactory().createManager(threadId, InfinispanConnectionProvider.SESSION_CACHE_NAME, KcRemoteStoreConfigurationBuilder.class);
return new TestCacheManagerFactory().createManager(threadId, InfinispanConnectionProvider.SESSION_CACHE_NAME, KeycloakRemoteStoreConfigurationBuilder.class);
}
@@ -244,6 +236,10 @@ public class ConcurrencyJDGSessionsCacheTest {
SessionEntity session = (SessionEntity) remoteCache.get(cacheKey);
SessionEntityWrapper sessionWrapper = new SessionEntityWrapper(session);
if (listenerCount.get() % 100 == 0) {
logger.infof("Listener count: " + listenerCount.get());
}
// TODO: for distributed caches, ensure that it is executed just on owner OR if event.isCommandRetried
origCache
.getAdvancedCache().withFlags(Flag.SKIP_CACHE_LOAD, Flag.SKIP_CACHE_STORE)
@@ -60,8 +60,9 @@ class TestCacheManagerFactory {
private <T extends RemoteStoreConfigurationBuilder> Configuration getCacheBackedByRemoteStore(int threadId, String cacheName, Class<T> builderClass) {
ConfigurationBuilder cacheConfigBuilder = new ConfigurationBuilder();
String host = "localhost";
int port = threadId==1 ? 12232 : 13232;
//int port = 12232;
//int port = 11222;
return cacheConfigBuilder.persistence().addStore(builderClass)
.fetchPersistentState(false)
@@ -74,8 +75,8 @@ class TestCacheManagerFactory {
.forceReturnValues(false)
.marshaller(KeycloakHotRodMarshallerFactory.class.getName())
.addServer()
.host("localhost")
.port(port)
.host(host)
.port(port)
.connectionPool()
.maxActive(20)
.exhaustedAction(ExhaustedAction.CREATE_NEW)
+1 -1
View File
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>3.3.0.CR1-SNAPSHOT</version>
<version>3.4.0.CR1-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -18,41 +18,24 @@
package org.keycloak.authorization.jpa.entities;
import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.representations.idm.authorization.PolicyEnforcementMode;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import java.util.List;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
@Entity
@Table(name = "RESOURCE_SERVER", uniqueConstraints = {@UniqueConstraint(columnNames = "CLIENT_ID")})
@NamedQueries(
{
@NamedQuery(name="findResourceServerIdByClient", query="select r.id from ResourceServerEntity r where r.clientId = :clientId"),
}
)
@Table(name = "RESOURCE_SERVER")
public class ResourceServerEntity {
@Id
@Column(name="ID", length = 36)
@Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity. This avoids an extra SQL
private String id;
@Column(name = "CLIENT_ID")
private String clientId;
@Column(name = "ALLOW_RS_REMOTE_MGMT")
private boolean allowRemoteResourceManagement;
@@ -67,14 +50,6 @@ public class ResourceServerEntity {
this.id = id;
}
public String getClientId() {
return this.clientId;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public boolean isAllowRemoteResourceManagement() {
return this.allowRemoteResourceManagement;
}
@@ -22,16 +22,11 @@ import org.keycloak.authorization.jpa.entities.PolicyEntity;
import org.keycloak.authorization.jpa.entities.ResourceEntity;
import org.keycloak.authorization.jpa.entities.ResourceServerEntity;
import org.keycloak.authorization.jpa.entities.ScopeEntity;
import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.model.Resource;
import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.model.Scope;
import org.keycloak.authorization.store.ResourceServerStore;
import org.keycloak.models.utils.KeycloakModelUtils;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
import java.util.LinkedList;
import java.util.List;
@@ -53,8 +48,7 @@ public class JPAResourceServerStore implements ResourceServerStore {
public ResourceServer create(String clientId) {
ResourceServerEntity entity = new ResourceServerEntity();
entity.setId(KeycloakModelUtils.generateId());
entity.setClientId(clientId);
entity.setId(clientId);
this.entityManager.persist(entity);
@@ -116,17 +110,4 @@ public class JPAResourceServerStore implements ResourceServerStore {
if (entity == null) return null;
return new ResourceServerAdapter(entity, entityManager, provider.getStoreFactory());
}
@Override
public ResourceServer findByClient(final String clientId) {
TypedQuery<String> query = entityManager.createNamedQuery("findResourceServerIdByClient", String.class);
query.setParameter("clientId", clientId);
try {
String id = query.getSingleResult();
return provider.getStoreFactory().getResourceServerStore().findById(id);
} catch (NoResultException ex) {
return null;
}
}
}
@@ -16,7 +16,6 @@
*/
package org.keycloak.authorization.jpa.store;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.jpa.entities.PolicyEntity;
import org.keycloak.authorization.jpa.entities.ResourceEntity;
import org.keycloak.authorization.jpa.entities.ScopeEntity;
@@ -16,7 +16,6 @@
*/
package org.keycloak.authorization.jpa.store;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.jpa.entities.ResourceEntity;
import org.keycloak.authorization.jpa.entities.ScopeEntity;
import org.keycloak.authorization.model.Resource;
@@ -16,11 +16,7 @@
*/
package org.keycloak.authorization.jpa.store;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.jpa.entities.ResourceEntity;
import org.keycloak.authorization.jpa.entities.ResourceServerEntity;
import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.model.Resource;
import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.store.StoreFactory;
import org.keycloak.models.jpa.JpaModel;
@@ -53,11 +49,6 @@ public class ResourceServerAdapter implements ResourceServer, JpaModel<ResourceS
return entity.getId();
}
@Override
public String getClientId() {
return entity.getClientId();
}
@Override
public boolean isAllowRemoteResourceManagement() {
return entity.isAllowRemoteResourceManagement();
@@ -16,12 +16,10 @@
*/
package org.keycloak.authorization.jpa.store;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.jpa.entities.ScopeEntity;
import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.model.Scope;
import org.keycloak.authorization.store.StoreFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.jpa.JpaModel;
import javax.persistence.EntityManager;
@@ -112,7 +112,7 @@ public class JpaRealmProvider implements RealmProvider {
TypedQuery<String> query = em.createNamedQuery("getRealmIdByName", String.class);
query.setParameter("name", name);
List<String> entities = query.getResultList();
if (entities.size() == 0) return null;
if (entities.isEmpty()) return null;
if (entities.size() > 1) throw new IllegalStateException("Should not be more than one realm with same name");
String id = query.getResultList().get(0);
@@ -152,6 +152,10 @@ public class JpaRealmProvider implements RealmProvider {
removeRole(adapter, role);
}
for (GroupModel group : adapter.getGroups()) {
session.realms().removeGroup(adapter, group);
}
num = em.createNamedQuery("removeClientInitialAccessByRealm")
.setParameter("realm", realm).executeUpdate();
@@ -205,7 +209,7 @@ public class JpaRealmProvider implements RealmProvider {
query.setParameter("name", name);
query.setParameter("realm", realm.getId());
List<String> roles = query.getResultList();
if (roles.size() == 0) return null;
if (roles.isEmpty()) return null;
return session.realms().getRoleById(roles.get(0), realm);
}
@@ -234,7 +238,7 @@ public class JpaRealmProvider implements RealmProvider {
List<String> roles = query.getResultList();
if (roles.isEmpty()) return Collections.EMPTY_SET;
Set<RoleModel> list = new HashSet<RoleModel>();
Set<RoleModel> list = new HashSet<>();
for (String id : roles) {
list.add(session.realms().getRoleById(id, realm));
}
@@ -247,14 +251,14 @@ public class JpaRealmProvider implements RealmProvider {
query.setParameter("name", name);
query.setParameter("client", client.getId());
List<String> roles = query.getResultList();
if (roles.size() == 0) return null;
if (roles.isEmpty()) return null;
return session.realms().getRoleById(roles.get(0), realm);
}
@Override
public Set<RoleModel> getClientRoles(RealmModel realm, ClientModel client) {
Set<RoleModel> list = new HashSet<RoleModel>();
Set<RoleModel> list = new HashSet<>();
TypedQuery<String> query = em.createNamedQuery("getClientRoleIds", String.class);
query.setParameter("client", client.getId());
List<String> roles = query.getResultList();
@@ -421,9 +425,8 @@ public class JpaRealmProvider implements RealmProvider {
for (GroupModel subGroup : group.getSubGroups()) {
session.realms().removeGroup(realm, subGroup);
}
moveGroup(realm, group, null);
GroupEntity groupEntity = em.find(GroupEntity.class, group.getId());
if (!groupEntity.getRealm().getId().equals(realm.getId())) {
if ((groupEntity == null) || (!groupEntity.getRealm().getId().equals(realm.getId()))) {
return false;
}
em.createNamedQuery("deleteGroupRoleMappingsByGroup").setParameter("group", groupEntity).executeUpdate();
@@ -452,6 +455,7 @@ public class JpaRealmProvider implements RealmProvider {
RealmEntity realmEntity = em.getReference(RealmEntity.class, realm.getId());
groupEntity.setRealm(realmEntity);
em.persist(groupEntity);
em.flush();
realmEntity.getGroups().add(groupEntity);
GroupAdapter adapter = new GroupAdapter(realm, em, groupEntity);
@@ -39,7 +39,6 @@ import org.keycloak.models.UserProvider;
import org.keycloak.models.jpa.entities.CredentialAttributeEntity;
import org.keycloak.models.jpa.entities.CredentialEntity;
import org.keycloak.models.jpa.entities.FederatedIdentityEntity;
import org.keycloak.models.jpa.entities.UserAttributeEntity;
import org.keycloak.models.jpa.entities.UserConsentEntity;
import org.keycloak.models.jpa.entities.UserConsentProtocolMapperEntity;
import org.keycloak.models.jpa.entities.UserConsentRoleEntity;
@@ -363,6 +362,18 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore {
}
@Override
public void setNotBeforeForUser(RealmModel realm, UserModel user, int notBefore) {
UserEntity entity = em.getReference(UserEntity.class, user.getId());
entity.setNotBefore(notBefore);
}
@Override
public int getNotBeforeOfUser(RealmModel realm, UserModel user) {
UserEntity entity = em.getReference(UserEntity.class, user.getId());
return entity.getNotBefore();
}
@Override
public void grantToAllUsers(RealmModel realm, RoleModel role) {
int num = em.createNamedQuery("grantRoleToAllUsers")
@@ -103,6 +103,9 @@ public class UserEntity {
@Column(name="SERVICE_ACCOUNT_CLIENT_LINK")
protected String serviceAccountClientLink;
@Column(name="NOT_BEFORE")
protected int notBefore;
public String getId() {
return id;
}
@@ -224,6 +227,14 @@ public class UserEntity {
this.serviceAccountClientLink = serviceAccountClientLink;
}
public int getNotBefore() {
return notBefore;
}
public void setNotBefore(int notBefore) {
this.notBefore = notBefore;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
@@ -34,7 +34,7 @@ import java.io.Serializable;
@NamedQuery(name="deleteClientSessionsByClient", query="delete from PersistentClientSessionEntity sess where sess.clientId = :clientId"),
@NamedQuery(name="deleteClientSessionsByUser", query="delete from PersistentClientSessionEntity sess where sess.userSessionId IN (select u.userSessionId from PersistentUserSessionEntity u where u.userId = :userId)"),
@NamedQuery(name="deleteClientSessionsByUserSession", query="delete from PersistentClientSessionEntity sess where sess.userSessionId = :userSessionId and sess.offline = :offline"),
@NamedQuery(name="deleteDetachedClientSessions", query="delete from PersistentClientSessionEntity sess where sess.userSessionId NOT IN (select u.userSessionId from PersistentUserSessionEntity u)"),
@NamedQuery(name="deleteDetachedClientSessions", query="delete from PersistentClientSessionEntity sess where NOT EXISTS (select u.userSessionId from PersistentUserSessionEntity u where u.userSessionId = sess.userSessionId )"),
@NamedQuery(name="findClientSessionsByUserSession", query="select sess from PersistentClientSessionEntity sess where sess.userSessionId=:userSessionId and sess.offline = :offline"),
@NamedQuery(name="findClientSessionsByUserSessions", query="select sess from PersistentClientSessionEntity sess where sess.offline = :offline and sess.userSessionId IN (:userSessionIds) order by sess.userSessionId"),
@NamedQuery(name="updateClientSessionsTimestamps", query="update PersistentClientSessionEntity c set timestamp = :timestamp"),
@@ -34,7 +34,7 @@ import java.io.Serializable;
@NamedQueries({
@NamedQuery(name="deleteUserSessionsByRealm", query="delete from PersistentUserSessionEntity sess where sess.realmId = :realmId"),
@NamedQuery(name="deleteUserSessionsByUser", query="delete from PersistentUserSessionEntity sess where sess.userId = :userId"),
@NamedQuery(name="deleteDetachedUserSessions", query="delete from PersistentUserSessionEntity sess where sess.userSessionId NOT IN (select c.userSessionId from PersistentClientSessionEntity c)"),
@NamedQuery(name="deleteDetachedUserSessions", query="delete from PersistentUserSessionEntity sess where NOT EXISTS (select c.userSessionId from PersistentClientSessionEntity c where c.userSessionId = sess.userSessionId)"),
@NamedQuery(name="findUserSessionsCount", query="select count(sess) from PersistentUserSessionEntity sess where sess.offline = :offline"),
@NamedQuery(name="findUserSessions", query="select sess from PersistentUserSessionEntity sess where sess.offline = :offline order by sess.userSessionId"),
@NamedQuery(name="updateUserSessionsTimestamps", query="update PersistentUserSessionEntity c set lastSessionRefresh = :lastSessionRefresh"),
@@ -416,6 +416,20 @@ public class JpaUserFederatedStorageProvider implements
}
@Override
public void setNotBeforeForUser(RealmModel realm, String userId, int notBefore) {
// Track it as attribute for now
String notBeforeStr = String.valueOf(notBefore);
setSingleAttribute(realm, userId, "fedNotBefore", notBeforeStr);
}
@Override
public int getNotBeforeOfUser(RealmModel realm, String userId) {
MultivaluedHashMap<String, String> attrs = getAttributes(realm, userId);
String notBeforeStr = attrs.getFirst("fedNotBefore");
return notBeforeStr==null ? 0 : Integer.parseInt(notBeforeStr);
}
@Override
public Set<GroupModel> getGroups(RealmModel realm, String userId) {
@@ -16,7 +16,29 @@
~ limitations under the License.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
<changeSet author="keycloak" id="3.2.0">
<!--
NOTE that there was an older version of this file containing a bug.
The first changeset fixes this bug for those who were able to
lucky enough to apply it.
The second changeset is the fixed original changeset.
-->
<changeSet author="keycloak" id="3.2.0-fix">
<preConditions onFail="MARK_RAN">
<changeSetExecuted id="3.2.0" author="keycloak" changeLogFile="META-INF/jpa-changelog-3.2.0.xml"/>
</preConditions>
<addNotNullConstraint tableName="CLIENT_INITIAL_ACCESS" columnName="REALM_ID" columnDataType="VARCHAR(36)" />
</changeSet>
<changeSet author="keycloak" id="3.2.0-fixed">
<preConditions onFail="MARK_RAN">
<not>
<changeSetExecuted id="3.2.0" author="keycloak" changeLogFile="META-INF/jpa-changelog-3.2.0.xml"/>
</not>
</preConditions>
<addColumn tableName="REALM">
<column name="DOCKER_AUTH_FLOW" type="VARCHAR(36)">
<constraints nullable="true"/>
@@ -31,7 +53,9 @@
<column name="ID" type="VARCHAR(36)">
<constraints nullable="false"/>
</column>
<column name="REALM_ID" type="VARCHAR(36)"/>
<column name="REALM_ID" type="VARCHAR(36)">
<constraints nullable="false"/>
</column>
<column name="TIMESTAMP" type="INT"/>
<column name="EXPIRATION" type="INT"/>
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!--
~ Copyright 2017 Red Hat, Inc. and/or its affiliates
~ and other contributors as indicated by the @author tags.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
<changeSet author="keycloak" id="3.3.0">
<addColumn tableName="USER_ENTITY">
<column name="NOT_BEFORE" type="INT" defaultValueNumeric="0">
<constraints nullable="false"/>
</column>
</addColumn>
</changeSet>
</databaseChangeLog>
@@ -0,0 +1,115 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!--
~ * Copyright 2017 Red Hat, Inc. and/or its affiliates
~ * and other contributors as indicated by the @author tags.
~ *
~ * Licensed under the Apache License, Version 2.0 (the "License");
~ * you may not use this file except in compliance with the License.
~ * You may obtain a copy of the License at
~ *
~ * http://www.apache.org/licenses/LICENSE-2.0
~ *
~ * Unless required by applicable law or agreed to in writing, software
~ * distributed under the License is distributed on an "AS IS" BASIS,
~ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ * See the License for the specific language governing permissions and
~ * limitations under the License.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.2.xsd">
<changeSet author="glavoie@gmail.com" id="authz-3.4.0.CR1-resource-server-pk-change-part1">
<!-- Data migration to change the PK of RESOURCE_SERVER to use the CLIENT_ID. -->
<addColumn tableName="RESOURCE_SERVER_POLICY">
<column name="RESOURCE_SERVER_CLIENT_ID" type="VARCHAR(36)"/>
</addColumn>
<addColumn tableName="RESOURCE_SERVER_RESOURCE">
<column name="RESOURCE_SERVER_CLIENT_ID" type="VARCHAR(36)"/>
</addColumn>
<addColumn tableName="RESOURCE_SERVER_SCOPE">
<column name="RESOURCE_SERVER_CLIENT_ID" type="VARCHAR(36)"/>
</addColumn>
</changeSet>
<changeSet author="glavoie@gmail.com" id="authz-3.4.0.CR1-resource-server-pk-change-part2">
<preConditions onSqlOutput="TEST" onFail="MARK_RAN">
<not>
<dbms type="mssql" />
</not>
</preConditions>
<sql>
UPDATE RESOURCE_SERVER_POLICY p SET RESOURCE_SERVER_CLIENT_ID = (SELECT CLIENT_ID FROM RESOURCE_SERVER s WHERE s.ID = p.RESOURCE_SERVER_ID);
UPDATE RESOURCE_SERVER_RESOURCE p SET RESOURCE_SERVER_CLIENT_ID = (SELECT CLIENT_ID FROM RESOURCE_SERVER s WHERE s.ID = p.RESOURCE_SERVER_ID);
UPDATE RESOURCE_SERVER_SCOPE p SET RESOURCE_SERVER_CLIENT_ID = (SELECT CLIENT_ID FROM RESOURCE_SERVER s WHERE s.ID = p.RESOURCE_SERVER_ID);
</sql>
</changeSet>
<changeSet author="glavoie@gmail.com" id="authz-3.4.0.CR1-resource-server-pk-change-part2-mssql">
<preConditions onSqlOutput="TEST" onFail="MARK_RAN">
<dbms type="mssql" />
</preConditions>
<sql>
UPDATE RESOURCE_SERVER_POLICY SET RESOURCE_SERVER_CLIENT_ID = s.CLIENT_ID FROM (SELECT ID, CLIENT_ID FROM RESOURCE_SERVER) s WHERE s.ID = RESOURCE_SERVER_POLICY.RESOURCE_SERVER_ID;
UPDATE RESOURCE_SERVER_RESOURCE SET RESOURCE_SERVER_CLIENT_ID = s.CLIENT_ID FROM (SELECT ID, CLIENT_ID FROM RESOURCE_SERVER) s WHERE s.ID = RESOURCE_SERVER_RESOURCE.RESOURCE_SERVER_ID;
UPDATE RESOURCE_SERVER_SCOPE SET RESOURCE_SERVER_CLIENT_ID = s.CLIENT_ID FROM (SELECT ID, CLIENT_ID FROM RESOURCE_SERVER) s WHERE s.ID = RESOURCE_SERVER_SCOPE.RESOURCE_SERVER_ID;
</sql>
</changeSet>
<changeSet author="glavoie@gmail.com" id="authz-3.4.0.CR1-resource-server-pk-change-part3">
<addNotNullConstraint tableName="RESOURCE_SERVER_POLICY" columnName="RESOURCE_SERVER_CLIENT_ID" columnDataType="VARCHAR(36)"/>
<addNotNullConstraint tableName="RESOURCE_SERVER_RESOURCE" columnName="RESOURCE_SERVER_CLIENT_ID" columnDataType="VARCHAR(36)"/>
<addNotNullConstraint tableName="RESOURCE_SERVER_SCOPE" columnName="RESOURCE_SERVER_CLIENT_ID" columnDataType="VARCHAR(36)"/>
<dropUniqueConstraint tableName="RESOURCE_SERVER_POLICY" constraintName="UK_FRSRPT700S9V50BU18WS5HA6"/>
<dropUniqueConstraint tableName="RESOURCE_SERVER_RESOURCE" constraintName="UK_FRSR6T700S9V50BU18WS5HA6"/>
<dropUniqueConstraint tableName="RESOURCE_SERVER_SCOPE" constraintName="UK_FRSRST700S9V50BU18WS5HA6"/>
<dropForeignKeyConstraint baseTableName="RESOURCE_SERVER_POLICY" constraintName="FK_FRSRPO213XCX4WNKOG82SSRFY"/>
<dropIndex tableName="RESOURCE_SERVER_POLICY" indexName="IDX_RES_SERV_POL_RES_SERV"/>
<dropColumn tableName="RESOURCE_SERVER_POLICY" columnName="RESOURCE_SERVER_ID"/>
<dropForeignKeyConstraint baseTableName="RESOURCE_SERVER_RESOURCE" constraintName="FK_FRSRHO213XCX4WNKOG82SSRFY"/>
<dropIndex tableName="RESOURCE_SERVER_RESOURCE" indexName="IDX_RES_SRV_RES_RES_SRV"/>
<dropColumn tableName="RESOURCE_SERVER_RESOURCE" columnName="RESOURCE_SERVER_ID"/>
<dropForeignKeyConstraint baseTableName="RESOURCE_SERVER_SCOPE" constraintName="FK_FRSRSO213XCX4WNKOG82SSRFY"/>
<dropIndex tableName="RESOURCE_SERVER_SCOPE" indexName="IDX_RES_SRV_SCOPE_RES_SRV"/>
<dropColumn tableName="RESOURCE_SERVER_SCOPE" columnName="RESOURCE_SERVER_ID"/>
<dropPrimaryKey tableName="RESOURCE_SERVER" constraintName="CONSTRAINT_FARS"/>
<dropUniqueConstraint tableName="RESOURCE_SERVER" constraintName="UK_AU8TT6T700S9V50BU18WS5HA6"/>
<dropColumn tableName="RESOURCE_SERVER" columnName="ID"/>
<renameColumn tableName="RESOURCE_SERVER" oldColumnName="CLIENT_ID" newColumnName="ID" columnDataType="VARCHAR(36)"/>
<renameColumn tableName="RESOURCE_SERVER_POLICY" oldColumnName="RESOURCE_SERVER_CLIENT_ID" newColumnName="RESOURCE_SERVER_ID" columnDataType="VARCHAR(36)"/>
<renameColumn tableName="RESOURCE_SERVER_RESOURCE" oldColumnName="RESOURCE_SERVER_CLIENT_ID" newColumnName="RESOURCE_SERVER_ID" columnDataType="VARCHAR(36)"/>
<renameColumn tableName="RESOURCE_SERVER_SCOPE" oldColumnName="RESOURCE_SERVER_CLIENT_ID" newColumnName="RESOURCE_SERVER_ID" columnDataType="VARCHAR(36)"/>
<addUniqueConstraint tableName="RESOURCE_SERVER_POLICY" constraintName="UK_FRSRPT700S9V50BU18WS5HA6"
columnNames="NAME, RESOURCE_SERVER_ID"/>
<addUniqueConstraint tableName="RESOURCE_SERVER_RESOURCE" constraintName="UK_FRSR6T700S9V50BU18WS5HA6"
columnNames="NAME, OWNER, RESOURCE_SERVER_ID"/>
<addUniqueConstraint tableName="RESOURCE_SERVER_SCOPE" constraintName="UK_FRSRST700S9V50BU18WS5HA6"
columnNames="NAME, RESOURCE_SERVER_ID"/>
<createIndex indexName="IDX_RES_SERV_POL_RES_SERV" tableName="RESOURCE_SERVER_POLICY">
<column name="RESOURCE_SERVER_ID" type="VARCHAR(36)"/>
</createIndex>
<createIndex indexName="IDX_RES_SRV_RES_RES_SRV" tableName="RESOURCE_SERVER_RESOURCE">
<column name="RESOURCE_SERVER_ID" type="VARCHAR(36)"/>
</createIndex>
<createIndex indexName="IDX_RES_SRV_SCOPE_RES_SRV" tableName="RESOURCE_SERVER_SCOPE">
<column name="RESOURCE_SERVER_ID" type="VARCHAR(36)"/>
</createIndex>
<addPrimaryKey tableName="RESOURCE_SERVER" constraintName="PK_RESOURCE_SERVER" columnNames="ID"/>
<addForeignKeyConstraint constraintName="FK_FRSRPO213XCX4WNKOG82SSRFY"
baseTableName="RESOURCE_SERVER_POLICY" baseColumnNames="RESOURCE_SERVER_ID"
referencedTableName="RESOURCE_SERVER" referencedColumnNames="ID"/>
<addForeignKeyConstraint constraintName="FK_FRSRHO213XCX4WNKOG82SSRFY"
baseTableName="RESOURCE_SERVER_RESOURCE" baseColumnNames="RESOURCE_SERVER_ID"
referencedTableName="RESOURCE_SERVER" referencedColumnNames="ID"/>
<addForeignKeyConstraint constraintName="FK_FRSRSO213XCX4WNKOG82SSRFY"
baseTableName="RESOURCE_SERVER_SCOPE" baseColumnNames="RESOURCE_SERVER_ID"
referencedTableName="RESOURCE_SERVER" referencedColumnNames="ID"/>
</changeSet>
</databaseChangeLog>
@@ -48,4 +48,6 @@
<include file="META-INF/jpa-changelog-2.5.1.xml"/>
<include file="META-INF/jpa-changelog-3.0.0.xml"/>
<include file="META-INF/jpa-changelog-3.2.0.xml"/>
<include file="META-INF/jpa-changelog-3.3.0.xml"/>
<include file="META-INF/jpa-changelog-authz-3.4.0.CR1.xml"/>
</databaseChangeLog>
+1 -1
View File
@@ -20,7 +20,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>3.3.0.CR1-SNAPSHOT</version>
<version>3.4.0.CR1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<name>Keycloak Model Parent</name>