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");