Merge branch 'feature/group-search-and-pagination' of https://github.com/guitaro/keycloak into feature/group-search-and-pagination
This commit is contained in:
commit
d18aa44fb4
1615 files changed
+143297
-4802
No files matched your search
@@ -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>
|
||||
|
||||
+11
-7
@@ -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
|
||||
|
||||
+30
-2
@@ -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) {
|
||||
|
||||
+5
-5
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+1
-7
@@ -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();
|
||||
|
||||
+7
-7
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+12
-38
@@ -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
|
||||
|
||||
-9
@@ -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;
|
||||
}
|
||||
|
||||
+1
-1
@@ -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);
|
||||
}
|
||||
}
|
||||
+3
-5
@@ -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);
|
||||
}
|
||||
}
|
||||
+4
-2
@@ -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);
|
||||
}
|
||||
}
|
||||
Vendored
+6
-1
@@ -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;
|
||||
}
|
||||
}
|
||||
+103
-39
@@ -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());
|
||||
|
||||
+9
-5
@@ -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();
|
||||
|
||||
+54
-16
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+18
-15
@@ -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();
|
||||
}
|
||||
|
||||
+35
-30
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+36
@@ -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;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -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;
|
||||
}
|
||||
|
||||
+1
-3
@@ -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;
|
||||
|
||||
-2
@@ -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;
|
||||
|
||||
/**
|
||||
|
||||
+1
-1
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+33
@@ -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());
|
||||
}
|
||||
}
|
||||
+27
-12
@@ -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);
|
||||
}
|
||||
}
|
||||
+5
@@ -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);
|
||||
}
|
||||
}
|
||||
+5
-27
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
+27
@@ -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(),
|
||||
|
||||
+2
-2
@@ -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);
|
||||
|
||||
+1
-1
@@ -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>
|
||||
*/
|
||||
|
||||
-39
@@ -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;
|
||||
}
|
||||
}
|
||||
+65
-11
@@ -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
-11
@@ -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();
|
||||
}
|
||||
}
|
||||
+61
@@ -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;
|
||||
}
|
||||
}
|
||||
+13
-10
@@ -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);
|
||||
|
||||
+14
-14
@@ -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;
|
||||
|
||||
+55
-13
@@ -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;
|
||||
|
||||
+3
-3
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
+4
-3
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
+59
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
+6
-10
@@ -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)
|
||||
|
||||
+4
-3
@@ -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
@@ -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>
|
||||
|
||||
+1
-26
@@ -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;
|
||||
}
|
||||
|
||||
+1
-20
@@ -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;
|
||||
|
||||
-9
@@ -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;
|
||||
|
||||
+1
-1
@@ -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"),
|
||||
|
||||
+1
-1
@@ -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
@@ -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>
|
||||
|
||||
Reference in new issue
Block a user