Trigger clearing the user cache when the duplicate email allowed flag changes
Closes #31045 Signed-off-by: Alexander Schwartz <aschwart@redhat.com>
This commit is contained in:
parent
0b5f42f95d
commit
6d404b86c9
3 changed files with 53 additions and 25 deletions
|
@ -29,12 +29,11 @@ import org.keycloak.models.cache.UserCache;
|
||||||
import org.keycloak.models.cache.UserCacheProviderFactory;
|
import org.keycloak.models.cache.UserCacheProviderFactory;
|
||||||
import org.keycloak.models.cache.infinispan.entities.Revisioned;
|
import org.keycloak.models.cache.infinispan.entities.Revisioned;
|
||||||
import org.keycloak.models.cache.infinispan.events.InvalidationEvent;
|
import org.keycloak.models.cache.infinispan.events.InvalidationEvent;
|
||||||
import org.keycloak.provider.InvalidationHandler;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
*/
|
*/
|
||||||
public class InfinispanUserCacheProviderFactory implements UserCacheProviderFactory, InvalidationHandler {
|
public class InfinispanUserCacheProviderFactory implements UserCacheProviderFactory {
|
||||||
|
|
||||||
private static final Logger log = Logger.getLogger(InfinispanUserCacheProviderFactory.class);
|
private static final Logger log = Logger.getLogger(InfinispanUserCacheProviderFactory.class);
|
||||||
public static final String USER_CLEAR_CACHE_EVENTS = "USER_CLEAR_CACHE_EVENTS";
|
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
|
@Override
|
||||||
public void init(Config.Scope config) {
|
public void init(Config.Scope config) {
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,9 +18,32 @@
|
||||||
package org.keycloak.models.cache.infinispan;
|
package org.keycloak.models.cache.infinispan;
|
||||||
|
|
||||||
import org.keycloak.Config;
|
import org.keycloak.Config;
|
||||||
|
import org.keycloak.cluster.ClusterProvider;
|
||||||
import org.keycloak.common.enums.SslRequired;
|
import org.keycloak.common.enums.SslRequired;
|
||||||
import org.keycloak.component.ComponentModel;
|
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.CachedRealmModel;
|
||||||
import org.keycloak.models.cache.UserCache;
|
import org.keycloak.models.cache.UserCache;
|
||||||
import org.keycloak.models.cache.infinispan.entities.CachedRealm;
|
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.UserStorageUtil;
|
||||||
import org.keycloak.storage.client.ClientStorageProvider;
|
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.concurrent.ConcurrentHashMap;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
@ -362,6 +390,22 @@ public class RealmAdapter implements CachedRealmModel {
|
||||||
@Override
|
@Override
|
||||||
public void setDuplicateEmailsAllowed(boolean duplicateEmailsAllowed) {
|
public void setDuplicateEmailsAllowed(boolean duplicateEmailsAllowed) {
|
||||||
getDelegateForUpdate();
|
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);
|
updated.setDuplicateEmailsAllowed(duplicateEmailsAllowed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1074,7 +1118,7 @@ public class RealmAdapter implements CachedRealmModel {
|
||||||
public Stream<RoleModel> getRolesStream() {
|
public Stream<RoleModel> getRolesStream() {
|
||||||
return cacheSession.getRealmRolesStream(this);
|
return cacheSession.getRealmRolesStream(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Stream<RoleModel> getRolesStream(Integer first, Integer max) {
|
public Stream<RoleModel> getRolesStream(Integer first, Integer max) {
|
||||||
return cacheSession.getRealmRolesStream(this, first, max);
|
return cacheSession.getRealmRolesStream(this, first, max);
|
||||||
|
@ -1084,7 +1128,7 @@ public class RealmAdapter implements CachedRealmModel {
|
||||||
public Stream<RoleModel> searchForRolesStream(String search, Integer first, Integer max) {
|
public Stream<RoleModel> searchForRolesStream(String search, Integer first, Integer max) {
|
||||||
return cacheSession.searchForRolesStream(this, search, first, max);
|
return cacheSession.searchForRolesStream(this, search, first, max);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RoleModel addRole(String name) {
|
public RoleModel addRole(String name) {
|
||||||
return cacheSession.addRealmRole(this, name);
|
return cacheSession.addRealmRole(this, name);
|
||||||
|
@ -1601,10 +1645,10 @@ public class RealmAdapter implements CachedRealmModel {
|
||||||
|
|
||||||
public void executeEvictions(ComponentModel model) {
|
public void executeEvictions(ComponentModel model) {
|
||||||
if (model == null) return;
|
if (model == null) return;
|
||||||
|
|
||||||
// if user cache is disabled this is null
|
// if user cache is disabled this is null
|
||||||
UserCache userCache = UserStorageUtil.userCache(session);
|
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 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())) {
|
if (model.getParentId() != null && !model.getParentId().equals(getId())) {
|
||||||
ComponentModel parent = getComponent(model.getParentId());
|
ComponentModel parent = getComponent(model.getParentId());
|
||||||
|
@ -1613,13 +1657,13 @@ public class RealmAdapter implements CachedRealmModel {
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// invalidate entire user cache if we're dealing with user storage SPI
|
// invalidate entire user cache if we're dealing with user storage SPI
|
||||||
if (UserStorageProvider.class.getName().equals(model.getProviderType())) {
|
if (UserStorageProvider.class.getName().equals(model.getProviderType())) {
|
||||||
userCache.evict(this);
|
userCache.evict(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// invalidate entire realm if we're dealing with client storage SPI
|
// invalidate entire realm if we're dealing with client storage SPI
|
||||||
// entire realm because of client roles, client lists, and clients
|
// entire realm because of client roles, client lists, and clients
|
||||||
if (ClientStorageProvider.class.getName().equals(model.getProviderType())) {
|
if (ClientStorageProvider.class.getName().equals(model.getProviderType())) {
|
||||||
|
|
|
@ -95,7 +95,6 @@ import org.keycloak.models.utils.RepresentationToModel;
|
||||||
import org.keycloak.partialimport.ErrorResponseException;
|
import org.keycloak.partialimport.ErrorResponseException;
|
||||||
import org.keycloak.partialimport.PartialImportResult;
|
import org.keycloak.partialimport.PartialImportResult;
|
||||||
import org.keycloak.partialimport.PartialImportResults;
|
import org.keycloak.partialimport.PartialImportResults;
|
||||||
import org.keycloak.provider.InvalidationHandler;
|
|
||||||
import org.keycloak.representations.adapters.action.GlobalRequestResult;
|
import org.keycloak.representations.adapters.action.GlobalRequestResult;
|
||||||
import org.keycloak.representations.idm.AdminEventRepresentation;
|
import org.keycloak.representations.idm.AdminEventRepresentation;
|
||||||
import org.keycloak.representations.idm.ClientRepresentation;
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
|
@ -446,7 +445,6 @@ public class RealmAdminResource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean wasDuplicateEmailsAllowed = realm.isDuplicateEmailsAllowed();
|
|
||||||
RepresentationToModel.updateRealm(rep, realm, session);
|
RepresentationToModel.updateRealm(rep, realm, session);
|
||||||
|
|
||||||
// Refresh periodic sync tasks for configured federationProviders
|
// Refresh periodic sync tasks for configured federationProviders
|
||||||
|
@ -457,10 +455,6 @@ public class RealmAdminResource {
|
||||||
|
|
||||||
adminEvent.operation(OperationType.UPDATE).representation(rep).success();
|
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();
|
return Response.noContent().build();
|
||||||
} catch (ModelDuplicateException e) {
|
} catch (ModelDuplicateException e) {
|
||||||
throw ErrorResponse.exists("Realm with same name exists");
|
throw ErrorResponse.exists("Realm with same name exists");
|
||||||
|
|
Loading…
Reference in a new issue