diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/UserAttributeLDAPStorageMapper.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/UserAttributeLDAPStorageMapper.java index ee5f85fef9b..a68d3a75226 100644 --- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/UserAttributeLDAPStorageMapper.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/UserAttributeLDAPStorageMapper.java @@ -164,6 +164,12 @@ public class UserAttributeLDAPStorageMapper extends AbstractLDAPStorageMapper { email = KeycloakModelUtils.toLowerCaseSafe(email); UserModel that = UserStoragePrivateUtil.userLocalStorage(session).getUserByEmail(realm, email); + + if (that != null) { + // delete and invalidate the cache for any existing account no longer available from LDAP + that = session.users().getUserByEmail(realm, that.getEmail()); + } + if (that != null && !that.getId().equals(user.getId())) { session.getTransactionManager().setRollbackOnly(); String exceptionMessage = String.format("Can't import user '%s' from LDAP because email '%s' already exists in Keycloak. Existing user with this email is '%s'", user.getUsername(), email, that.getUsername()); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPSyncTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPSyncTest.java index 2b4198a1a84..b841519bf00 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPSyncTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPSyncTest.java @@ -23,7 +23,6 @@ import java.util.List; import java.util.Map; import java.util.stream.Collectors; -import org.hamcrest.Matchers; import org.junit.Assert; import org.junit.ClassRule; import org.junit.FixMethodOrder; @@ -41,6 +40,8 @@ import org.keycloak.models.cache.UserCache; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.representations.idm.ComponentRepresentation; import org.keycloak.representations.idm.SynchronizationResultRepresentation; +import org.keycloak.representations.idm.UserRepresentation; +import org.keycloak.storage.ldap.LDAPConfig; import org.keycloak.storage.managers.UserStorageSyncManager; import org.keycloak.storage.UserStoragePrivateUtil; import org.keycloak.storage.UserStorageUtil; @@ -63,6 +64,7 @@ import org.keycloak.testsuite.util.WaitUtils; import jakarta.ws.rs.BadRequestException; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; /** * @author Marek Posolda @@ -251,6 +253,47 @@ public class LDAPSyncTest extends AbstractLDAPTest { }); } + @Test + public void testSyncAfterDeletingUserInLDAPWithSameEmail() { + testingClient.server().run(session -> { + LDAPTestContext ctx = LDAPTestContext.init(session); + RealmModel appRealm = ctx.getRealm(); + ComponentModel ldapModel = LDAPTestUtils.getLdapProviderModel(appRealm); + LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel); + LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "luser10", "User10", "User10", "luser10@email.org", null, ""); + LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "luser11", "User11", "User11", "luser11@email.org", null, ""); + }); + + List users = testRealm().users().search("luser10"); + assertThat(1, is(users.size())); + + users = testRealm().users().search("luser11"); + assertThat(1, is(users.size())); + UserRepresentation userToDelete = users.get(0); + + testingClient.server().run(session -> { + LDAPTestContext ctx = LDAPTestContext.init(session); + LDAPStorageProvider ldapProvider = ctx.getLdapProvider(); + LDAPConfig ldapConfig = ldapProvider.getLdapIdentityStore().getConfig(); + LDAPTestUtils.removeLDAPUserByUsername(ldapProvider, ctx.getRealm(), ldapConfig, "luser11"); + }); + + String email = userToDelete.getEmail(); + + testingClient.server().run(session -> { + LDAPTestContext ctx = LDAPTestContext.init(session); + LDAPObject user = ctx.getLdapProvider().loadLDAPUserByUsername(ctx.getRealm(), "luser10"); + user.setSingleAttribute(LDAPConstants.EMAIL, email); + ctx.getLdapProvider().getLdapIdentityStore().update(user); + }); + + testingClient.server().run(session -> { + LDAPTestContext ctx = LDAPTestContext.init(session); + SynchronizationResult result = new UserStorageSyncManager().syncAllUsers(session.getKeycloakSessionFactory(), ctx.getRealm().getId(), ctx.getLdapModel()); + Assert.assertEquals(0, result.getFailed()); + }); + } + @Test public void test03LDAPSyncWhenUsernameChanged() { @@ -386,7 +429,7 @@ public class LDAPSyncTest extends AbstractLDAPTest { LDAPObject streetUser = LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), ctx.getRealm(), "user8", "User8FN", "User8LN", "user8@email.org", "user8street", "126"); // Change name of username attribute name to street - String origUsernameAttrNamee = ctx.getLdapModel().get(LDAPConstants.USERNAME_LDAP_ATTRIBUTE); + String origUsernameAttrNamee = LDAPConstants.UID; ctx.getLdapModel().getConfig().putSingle(LDAPConstants.USERNAME_LDAP_ATTRIBUTE, "street"); // Need to change this due to ApacheDS pagination bug (For other LDAP servers, pagination works fine) TODO: Remove once ApacheDS upgraded and pagination is fixed @@ -405,6 +448,8 @@ public class LDAPSyncTest extends AbstractLDAPTest { SynchronizationResult syncResult = new UserStorageSyncManager().syncAllUsers(sessionFactory, ctx.getRealm().getId(), ctx.getLdapModel()); Assert.assertEquals(1, syncResult.getAdded()); Assert.assertTrue(syncResult.getFailed() > 0); + UserModel invalidUser = session.users().getUserByUsername(ctx.getRealm(), "user8street"); + session.users().removeUser(ctx.getRealm(), invalidUser); }); // Revert config changes @@ -501,7 +546,7 @@ public class LDAPSyncTest extends AbstractLDAPTest { // sync to Keycloak should pass without an error SynchronizationResult syncResult = new GroupLDAPStorageMapperFactory().create(session, mapperModel).syncDataFromFederationProviderToKeycloak(appRealm); - assertThat(syncResult.getFailed(), Matchers.is(0)); + assertThat(syncResult.getFailed(), is(0)); }); testingClient.server().run(session -> {