From 21f90145ac737c67ac651dfa63f9ed105f58a5cd Mon Sep 17 00:00:00 2001 From: Christian Janker Date: Fri, 13 Sep 2024 19:39:34 +0200 Subject: [PATCH] Send UserRemovedEvent containing all user attributes Invalidate CachedUserModel before UserRemovedEvent closes #32194 Signed-off-by: Christian Janker --- .../cache/infinispan/UserCacheSession.java | 7 ++-- .../model/UserSessionProviderTest.java | 33 +++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java index 0546e21bd4..3f0dd1d7bb 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java @@ -24,7 +24,6 @@ import org.keycloak.credential.CredentialInput; import org.keycloak.models.ClientScopeModel; import org.keycloak.models.CredentialValidationOutput; import org.keycloak.models.IdentityProviderModel; -import org.keycloak.models.OrganizationModel; import org.keycloak.models.cache.infinispan.events.InvalidationEvent; import org.keycloak.common.constants.ServiceAccountConstants; import org.keycloak.component.ComponentModel; @@ -834,6 +833,10 @@ public class UserCacheSession implements UserCache, OnCreateComponent, OnUpdateC // just in case the transaction is rolled back you need to invalidate the user and all cache queries for that user protected void fullyInvalidateUser(RealmModel realm, UserModel user) { + if (user instanceof CachedUserModel) { + ((CachedUserModel) user).invalidate(); + } + Stream federatedIdentities = realm.isIdentityFederationEnabled() ? getFederatedIdentitiesStream(realm, user) : Stream.empty(); @@ -845,7 +848,7 @@ public class UserCacheSession implements UserCache, OnCreateComponent, OnUpdateC @Override public boolean removeUser(RealmModel realm, UserModel user) { - fullyInvalidateUser(realm, user); + fullyInvalidateUser(realm, user); return getDelegate().removeUser(realm, user); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java index 26df1e21db..8968f6a5bc 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java @@ -35,6 +35,7 @@ import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.ResetTimeOffsetEvent; import org.keycloak.models.utils.SessionTimeoutHelper; import org.keycloak.protocol.oidc.OIDCLoginProtocol; +import org.keycloak.provider.ProviderEventListener; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.testsuite.AbstractTestRealmKeycloakTest; import org.keycloak.testsuite.arquillian.annotation.ModelTest; @@ -830,6 +831,7 @@ public class UserSessionProviderTest extends AbstractTestRealmKeycloakTest { public void testOnUserRemoved() { testingClient.server().run(UserSessionProviderTest::testOnUserRemoved); } + public static void testOnUserRemoved(KeycloakSession session) { RealmModel realm = session.realms().getRealmByName("test"); session.getContext().setRealm(realm); @@ -857,6 +859,37 @@ public class UserSessionProviderTest extends AbstractTestRealmKeycloakTest { }); } + @Test + public void testOnUserRemovedLazyUserAttributesAreLoaded() { + testingClient.server().run(session -> { + RealmModel realm = session.realms().getRealmByName("test"); + UserModel user1 = session.users().getUserByUsername(realm, "user1"); + user1.setSingleAttribute("customAttribute", "value1"); + }); + testingClient.server().run(UserSessionProviderTest::testOnUserRemovedLazyUserAttributesAreLoaded); + } + + public static void testOnUserRemovedLazyUserAttributesAreLoaded(KeycloakSession session) { + RealmModel realm = session.realms().getRealmByName("test"); + UserModel user1 = session.users().getUserByUsername(realm, "user1"); + + Map> attributes = new HashMap<>(); + ProviderEventListener providerEventListener = event -> { + if (event instanceof UserModel.UserRemovedEvent) { + UserModel.UserRemovedEvent userRemovedEvent = (UserModel.UserRemovedEvent) event; + attributes.putAll(userRemovedEvent.getUser().getAttributes()); + } + }; + session.getKeycloakSessionFactory().register(providerEventListener); + try { + new UserManager(session).removeUser(realm, user1); + // UserModel.FIRST_NAME, UserModel.LAST_NAME, UserModel.EMAIL, UserModel.USERNAME, customAttribute; + assertEquals(5, attributes.size()); + } finally { + session.getKeycloakSessionFactory().unregister(providerEventListener); + } + } + private static AuthenticatedClientSessionModel createClientSession(KeycloakSession session, ClientModel client, UserSessionModel userSession, String redirect, String state) { RealmModel realm = session.realms().getRealmByName("test"); AuthenticatedClientSessionModel clientSession = session.sessions().createClientSession(realm, client, userSession);