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:
Alexander Schwartz 2024-07-24 18:46:52 +02:00 committed by Michal Hajas
parent 0b5f42f95d
commit 6d404b86c9
3 changed files with 53 additions and 25 deletions

View file

@ -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 <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);
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) {
}

View file

@ -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);
}

View file

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