diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanUserCacheProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanUserCacheProviderFactory.java index 6ed60570f2..24723cd924 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanUserCacheProviderFactory.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanUserCacheProviderFactory.java @@ -29,12 +29,11 @@ import org.keycloak.models.cache.UserCache; import org.keycloak.models.cache.UserCacheProviderFactory; import org.keycloak.models.cache.infinispan.entities.Revisioned; import org.keycloak.models.cache.infinispan.events.InvalidationEvent; -import org.keycloak.provider.InvalidationHandler; /** * @author Stian Thorgersen */ -public class InfinispanUserCacheProviderFactory implements UserCacheProviderFactory, InvalidationHandler { +public class InfinispanUserCacheProviderFactory implements UserCacheProviderFactory { private static final Logger log = Logger.getLogger(InfinispanUserCacheProviderFactory.class); public static final String USER_CLEAR_CACHE_EVENTS = "USER_CLEAR_CACHE_EVENTS"; @@ -79,15 +78,6 @@ public class InfinispanUserCacheProviderFactory implements UserCacheProviderFact } } - @Override - public void invalidate(KeycloakSession session, InvalidableObjectType type, Object... params) { - if (type == ObjectType.REALM || type == ObjectType.USER) { - if (this.userCache != null) { - this.userCache.clear(); - } - } - } - @Override public void init(Config.Scope config) { } diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java index eb17b8f8a0..071325397c 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java @@ -18,9 +18,32 @@ package org.keycloak.models.cache.infinispan; import org.keycloak.Config; +import org.keycloak.cluster.ClusterProvider; import org.keycloak.common.enums.SslRequired; import org.keycloak.component.ComponentModel; -import org.keycloak.models.*; +import org.keycloak.models.AbstractKeycloakTransaction; +import org.keycloak.models.AuthenticationExecutionModel; +import org.keycloak.models.AuthenticationFlowModel; +import org.keycloak.models.AuthenticatorConfigModel; +import org.keycloak.models.CibaConfig; +import org.keycloak.models.ClientInitialAccessModel; +import org.keycloak.models.ClientModel; +import org.keycloak.models.ClientScopeModel; +import org.keycloak.models.GroupModel; +import org.keycloak.models.IdentityProviderMapperModel; +import org.keycloak.models.IdentityProviderModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.OAuth2DeviceConfig; +import org.keycloak.models.OTPPolicy; +import org.keycloak.models.OrganizationModel; +import org.keycloak.models.ParConfig; +import org.keycloak.models.PasswordPolicy; +import org.keycloak.models.RealmModel; +import org.keycloak.models.RequiredActionConfigModel; +import org.keycloak.models.RequiredActionProviderModel; +import org.keycloak.models.RequiredCredentialModel; +import org.keycloak.models.RoleModel; +import org.keycloak.models.WebAuthnPolicy; import org.keycloak.models.cache.CachedRealmModel; import org.keycloak.models.cache.UserCache; import org.keycloak.models.cache.infinispan.entities.CachedRealm; @@ -28,7 +51,12 @@ import org.keycloak.storage.UserStorageProvider; import org.keycloak.storage.UserStorageUtil; import org.keycloak.storage.client.ClientStorageProvider; -import java.util.*; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Supplier; import java.util.stream.Stream; @@ -362,6 +390,22 @@ public class RealmAdapter implements CachedRealmModel { @Override public void setDuplicateEmailsAllowed(boolean duplicateEmailsAllowed) { getDelegateForUpdate(); + if (updated.isDuplicateEmailsAllowed() != duplicateEmailsAllowed) { + // If the flag changed, we need to clear all entries from the user cache as there are entries with the key of the email address which need to be re-evaluated. + // Still, this must only happen after all changes have been written to the database, therefore we enlist this to run after the completion of the transaction. + session.getTransactionManager().enlistAfterCompletion(new AbstractKeycloakTransaction() { + @Override + protected void commitImpl() { + ClusterProvider cluster = session.getProvider(ClusterProvider.class); + cluster.notify(InfinispanUserCacheProviderFactory.USER_CLEAR_CACHE_EVENTS, ClearCacheEvent.getInstance(), false, ClusterProvider.DCNotify.ALL_DCS); + } + + @Override + protected void rollbackImpl() { + + } + }); + } updated.setDuplicateEmailsAllowed(duplicateEmailsAllowed); } @@ -1074,7 +1118,7 @@ public class RealmAdapter implements CachedRealmModel { public Stream getRolesStream() { return cacheSession.getRealmRolesStream(this); } - + @Override public Stream getRolesStream(Integer first, Integer max) { return cacheSession.getRealmRolesStream(this, first, max); @@ -1084,7 +1128,7 @@ public class RealmAdapter implements CachedRealmModel { public Stream searchForRolesStream(String search, Integer first, Integer max) { return cacheSession.searchForRolesStream(this, search, first, max); } - + @Override public RoleModel addRole(String name) { return cacheSession.addRealmRole(this, name); @@ -1601,10 +1645,10 @@ public class RealmAdapter implements CachedRealmModel { public void executeEvictions(ComponentModel model) { if (model == null) return; - + // if user cache is disabled this is null UserCache userCache = UserStorageUtil.userCache(session); - if (userCache != null) { + if (userCache != null) { // If not realm component, check to see if it is a user storage provider child component (i.e. LDAP mapper) if (model.getParentId() != null && !model.getParentId().equals(getId())) { ComponentModel parent = getComponent(model.getParentId()); @@ -1613,13 +1657,13 @@ public class RealmAdapter implements CachedRealmModel { } return; } - + // invalidate entire user cache if we're dealing with user storage SPI if (UserStorageProvider.class.getName().equals(model.getProviderType())) { userCache.evict(this); } } - + // invalidate entire realm if we're dealing with client storage SPI // entire realm because of client roles, client lists, and clients if (ClientStorageProvider.class.getName().equals(model.getProviderType())) { diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java index 17ac246801..36e617c7a4 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java @@ -95,7 +95,6 @@ import org.keycloak.models.utils.RepresentationToModel; import org.keycloak.partialimport.ErrorResponseException; import org.keycloak.partialimport.PartialImportResult; import org.keycloak.partialimport.PartialImportResults; -import org.keycloak.provider.InvalidationHandler; import org.keycloak.representations.adapters.action.GlobalRequestResult; import org.keycloak.representations.idm.AdminEventRepresentation; import org.keycloak.representations.idm.ClientRepresentation; @@ -446,7 +445,6 @@ public class RealmAdminResource { } } - boolean wasDuplicateEmailsAllowed = realm.isDuplicateEmailsAllowed(); RepresentationToModel.updateRealm(rep, realm, session); // Refresh periodic sync tasks for configured federationProviders @@ -457,10 +455,6 @@ public class RealmAdminResource { adminEvent.operation(OperationType.UPDATE).representation(rep).success(); - if (rep.isDuplicateEmailsAllowed() != null && rep.isDuplicateEmailsAllowed() != wasDuplicateEmailsAllowed) { - session.invalidate(InvalidationHandler.ObjectType.REALM, realm.getId()); - } - return Response.noContent().build(); } catch (ModelDuplicateException e) { throw ErrorResponse.exists("Realm with same name exists");