From 39f8311484cedbe4f5c7c63eb0e9d3f04ca738ee Mon Sep 17 00:00:00 2001 From: mposolda Date: Fri, 20 Jan 2017 21:08:13 +0100 Subject: [PATCH] KEYCLOAK-2403 Cannot create user in LDAP/AD from Keycloak using Full Name User Federation Mapper --- .../storage/ldap/idm/model/LDAPDn.java | 18 +- .../idm/store/ldap/LDAPIdentityStore.java | 29 ++ .../idm/store/ldap/LDAPOperationManager.java | 60 +++ .../mappers/FullNameLDAPStorageMapper.java | 25 +- .../storage/ldap/idm/model/LDAPDnTest.java | 20 +- .../storage/ldap/MSADFullNameTest.java | 371 ++++++++++++++++++ 6 files changed, 512 insertions(+), 11 deletions(-) create mode 100644 testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/MSADFullNameTest.java diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/model/LDAPDn.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/model/LDAPDn.java index 8bfbf6d3c0..94014fa8bf 100644 --- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/model/LDAPDn.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/model/LDAPDn.java @@ -27,7 +27,15 @@ import java.util.LinkedList; */ public class LDAPDn { - private final Deque entries = new LinkedList<>(); + private final Deque entries; + + private LDAPDn() { + this.entries = new LinkedList<>(); + } + + private LDAPDn(Deque entries) { + this.entries = entries; + } public static LDAPDn fromString(String dnString) { LDAPDn dn = new LDAPDn(); @@ -115,12 +123,14 @@ public class LDAPDn { /** * - * @return string like "dc=something,dc=org" from the DN like "uid=joe,dc=something,dc=org" + * @return DN like "dc=something,dc=org" from the DN like "uid=joe,dc=something,dc=org". + * Returned DN will be new clone not related to the original DN instance. + * */ - public String getParentDn() { + public LDAPDn getParentDn() { LinkedList parentDnEntries = new LinkedList<>(entries); parentDnEntries.remove(); - return toString(parentDnEntries); + return new LDAPDn(parentDnEntries); } public boolean isDescendantOf(LDAPDn expectedParentDn) { diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/ldap/LDAPIdentityStore.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/ldap/LDAPIdentityStore.java index c0e84b0df6..249a3a0e78 100644 --- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/ldap/LDAPIdentityStore.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/ldap/LDAPIdentityStore.java @@ -103,6 +103,8 @@ public class LDAPIdentityStore implements IdentityStore { @Override public void update(LDAPObject ldapObject) { + checkRename(ldapObject); + BasicAttributes updatedAttributes = extractAttributes(ldapObject, false); NamingEnumeration attributes = updatedAttributes.getAll(); @@ -114,6 +116,33 @@ public class LDAPIdentityStore implements IdentityStore { } } + protected void checkRename(LDAPObject ldapObject) { + String rdnAttrName = ldapObject.getRdnAttributeName(); + if (ldapObject.getReadOnlyAttributeNames().contains(rdnAttrName.toLowerCase())) { + return; + } + + String rdnAttrVal = ldapObject.getAttributeAsString(rdnAttrName); + + String oldRdnAttrVal = ldapObject.getDn().getFirstRdnAttrValue(); + if (!oldRdnAttrVal.equals(rdnAttrVal)) { + LDAPDn newLdapDn = ldapObject.getDn().getParentDn(); + newLdapDn.addFirst(rdnAttrName, rdnAttrVal); + + String oldDn = ldapObject.getDn().toString(); + String newDn = newLdapDn.toString(); + + if (logger.isDebugEnabled()) { + logger.debugf("Renaming LDAP Object. Old DN: [%s], New DN: [%s]", oldDn, newDn); + } + + // In case, that there is conflict (For example already existing "CN=John Anthony"), the different DN is returned + newDn = this.operationManager.renameEntry(oldDn, newDn, true); + + ldapObject.setDn(LDAPDn.fromString(newDn)); + } + } + @Override public void remove(LDAPObject ldapObject) { this.operationManager.removeEntry(ldapObject.getDn().toString()); diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/ldap/LDAPOperationManager.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/ldap/LDAPOperationManager.java index 350b16d36a..f057fc888b 100644 --- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/ldap/LDAPOperationManager.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/ldap/LDAPOperationManager.java @@ -21,6 +21,7 @@ import org.jboss.logging.Logger; import org.keycloak.models.LDAPConstants; import org.keycloak.models.ModelException; import org.keycloak.storage.ldap.LDAPConfig; +import org.keycloak.storage.ldap.idm.model.LDAPDn; import org.keycloak.storage.ldap.idm.query.internal.LDAPQuery; import org.keycloak.storage.ldap.mappers.LDAPOperationDecorator; @@ -28,6 +29,7 @@ import javax.naming.AuthenticationException; import javax.naming.Binding; import javax.naming.Context; import javax.naming.InitialContext; +import javax.naming.NameAlreadyBoundException; import javax.naming.NamingEnumeration; import javax.naming.NamingException; import javax.naming.directory.Attribute; @@ -158,6 +160,64 @@ public class LDAPOperationManager { } } + + /** + * Rename LDAPObject name (DN) + * + * @param oldDn + * @param newDn + * @param fallback With fallback=true, we will try to find the another DN in case of conflict. For example if there is an + * attempt to rename to "CN=John Doe", but there is already existing "CN=John Doe", we will try "CN=John Doe0" + * @return the non-conflicting DN, which was used in the end + */ + public String renameEntry(String oldDn, String newDn, boolean fallback) { + try { + String newNonConflictingDn = execute(new LdapOperation() { + @Override + public String execute(LdapContext context) throws NamingException { + String dn = newDn; + + // Max 5 attempts for now + int max = 5; + for (int i=0 ; i search(final String baseDN, final String filter, Collection returningAttributes, int searchScope) throws NamingException { final List result = new ArrayList(); final SearchControls cons = getSearchControls(returningAttributes, searchScope); diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/FullNameLDAPStorageMapper.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/FullNameLDAPStorageMapper.java index 43d631ea72..223a1151de 100644 --- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/FullNameLDAPStorageMapper.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/FullNameLDAPStorageMapper.java @@ -75,7 +75,7 @@ public class FullNameLDAPStorageMapper extends AbstractLDAPStorageMapper { @Override public void onRegisterUserToLDAP(LDAPObject ldapUser, UserModel localUser, RealmModel realm) { String ldapFullNameAttrName = getLdapFullNameAttrName(); - String fullName = getFullName(localUser.getFirstName(), localUser.getLastName()); + String fullName = getFullNameForWriteToLDAP(localUser.getFirstName(), localUser.getLastName(), localUser.getUsername()); ldapUser.setSingleAttribute(ldapFullNameAttrName, fullName); if (isReadOnly()) { @@ -103,7 +103,7 @@ public class FullNameLDAPStorageMapper extends AbstractLDAPStorageMapper { } private void setFullNameToLDAPObject() { - String fullName = getFullName(getFirstName(), getLastName()); + String fullName = getFullNameForWriteToLDAP(getFirstName(), getLastName(), getUsername()); if (logger.isTraceEnabled()) { logger.tracef("Pushing full name attribute to LDAP. Full name: %s", fullName); } @@ -177,18 +177,24 @@ public class FullNameLDAPStorageMapper extends AbstractLDAPStorageMapper { return ldapFullNameAttrName == null ? LDAPConstants.CN : ldapFullNameAttrName; } - protected String getFullName(String firstName, String lastName) { - if (firstName != null && lastName != null) { + protected String getFullNameForWriteToLDAP(String firstName, String lastName, String username) { + if (!isBlank(firstName) && !isBlank(lastName)) { return firstName + " " + lastName; - } else if (firstName != null) { + } else if (!isBlank(firstName)) { return firstName; - } else if (lastName != null) { + } else if (!isBlank(lastName)) { return lastName; + } else if (isFallbackToUsername()) { + return username; } else { return LDAPConstants.EMPTY_ATTRIBUTE_VALUE; } } + private boolean isBlank(String attr) { + return attr == null || attr.trim().isEmpty(); + } + private boolean isReadOnly() { return parseBooleanParameter(mapperModel, READ_ONLY); } @@ -196,4 +202,11 @@ public class FullNameLDAPStorageMapper extends AbstractLDAPStorageMapper { private boolean isWriteOnly() { return parseBooleanParameter(mapperModel, WRITE_ONLY); } + + + // Used just in case that we have Writable LDAP and fullName is mapped to "cn", which is used as RDN (this typically happens only on MSAD) + private boolean isFallbackToUsername() { + String rdnLdapAttrConfig = getLdapProvider().getLdapIdentityStore().getConfig().getRdnLdapAttribute(); + return !isReadOnly() && getLdapFullNameAttrName().equalsIgnoreCase(rdnLdapAttrConfig); + } } diff --git a/federation/ldap/src/test/java/org/keycloak/storage/ldap/idm/model/LDAPDnTest.java b/federation/ldap/src/test/java/org/keycloak/storage/ldap/idm/model/LDAPDnTest.java index a668cd7281..d749c13530 100644 --- a/federation/ldap/src/test/java/org/keycloak/storage/ldap/idm/model/LDAPDnTest.java +++ b/federation/ldap/src/test/java/org/keycloak/storage/ldap/idm/model/LDAPDnTest.java @@ -35,7 +35,7 @@ public class LDAPDnTest { Assert.assertEquals("uid=Johny\\,Depp\\+Pepp\\\\Foo,ou=People,dc=keycloak,dc=org", dn.toString()); Assert.assertEquals(LDAPDn.fromString("uid=Johny\\,Depp\\+Pepp\\\\Foo,ou=People,dc=keycloak,dc=org"), dn); - Assert.assertEquals("ou=People,dc=keycloak,dc=org", dn.getParentDn()); + Assert.assertEquals("ou=People,dc=keycloak,dc=org", dn.getParentDn().toString()); Assert.assertTrue(dn.isDescendantOf(LDAPDn.fromString("dc=keycloak, dc=org"))); Assert.assertTrue(dn.isDescendantOf(LDAPDn.fromString("dc=org"))); @@ -46,4 +46,22 @@ public class LDAPDnTest { Assert.assertEquals("uid", dn.getFirstRdnAttrName()); Assert.assertEquals("Johny,Depp+Pepp\\Foo", dn.getFirstRdnAttrValue()); } + + @Test + public void testCorrectEscape() throws Exception { + LDAPDn dn = LDAPDn.fromString("dc=keycloak, dc=org"); + dn.addFirst("cn", "Johny,Džýa Foo"); + Assert.assertEquals("cn=Johny\\,Džýa Foo,dc=keycloak,dc=org", dn.toString()); + Assert.assertEquals("Johny,Džýa Foo", dn.getFirstRdnAttrValue()); + + dn = LDAPDn.fromString("dc=keycloak, dc=org"); + dn.addFirst("cn", "Johny,Džýa Foo "); + Assert.assertEquals("cn=Johny\\,Džýa Foo\\ ,dc=keycloak,dc=org", dn.toString()); + Assert.assertEquals("Johny,Džýa Foo ", dn.getFirstRdnAttrValue()); + + dn = LDAPDn.fromString("dc=keycloak, dc=org"); + dn.addFirst("cn", "Johny,Džýa "); + Assert.assertEquals("cn=Johny\\,Džýa\\ ,dc=keycloak,dc=org", dn.toString()); + Assert.assertEquals("Johny,Džýa ", dn.getFirstRdnAttrValue()); + } } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/MSADFullNameTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/MSADFullNameTest.java new file mode 100644 index 0000000000..fe6510d7e7 --- /dev/null +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/MSADFullNameTest.java @@ -0,0 +1,371 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.testsuite.federation.storage.ldap; + +import java.util.Map; + +import org.junit.Assert; +import org.junit.ClassRule; +import org.junit.FixMethodOrder; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.RuleChain; +import org.junit.rules.TestRule; +import org.junit.runners.MethodSorters; +import org.keycloak.common.util.MultivaluedHashMap; +import org.keycloak.component.ComponentModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.LDAPConstants; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; +import org.keycloak.models.utils.KeycloakModelUtils; +import org.keycloak.services.managers.RealmManager; +import org.keycloak.storage.UserStorageProvider; +import org.keycloak.storage.UserStorageProviderModel; +import org.keycloak.storage.ldap.LDAPStorageProvider; +import org.keycloak.storage.ldap.LDAPStorageProviderFactory; +import org.keycloak.storage.ldap.mappers.FullNameLDAPStorageMapper; +import org.keycloak.storage.ldap.mappers.FullNameLDAPStorageMapperFactory; +import org.keycloak.storage.ldap.mappers.LDAPStorageMapper; +import org.keycloak.testsuite.OAuthClient; +import org.keycloak.testsuite.pages.AccountPasswordPage; +import org.keycloak.testsuite.pages.AccountUpdateProfilePage; +import org.keycloak.testsuite.pages.AppPage; +import org.keycloak.testsuite.pages.LoginPage; +import org.keycloak.testsuite.pages.RegisterPage; +import org.keycloak.testsuite.rule.KeycloakRule; +import org.keycloak.testsuite.rule.LDAPRule; +import org.keycloak.testsuite.rule.WebResource; +import org.keycloak.testsuite.rule.WebRule; +import org.openqa.selenium.WebDriver; + +/** + * Test for the MSAD setup with usernameAttribute=sAMAccountName, rdnAttribute=cn and fullNameMapper mapped to cn + * + * @author Marek Posolda + */ +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class MSADFullNameTest { + + // Run this test just on MSAD and just when sAMAccountName is mapped to username + private static LDAPRule ldapRule = new LDAPRule((Map ldapConfig) -> { + + String vendor = ldapConfig.get(LDAPConstants.VENDOR); + if (!vendor.equals(LDAPConstants.VENDOR_ACTIVE_DIRECTORY)) { + return true; + } + + String usernameAttr = ldapConfig.get(LDAPConstants.USERNAME_LDAP_ATTRIBUTE); + return !usernameAttr.equalsIgnoreCase(LDAPConstants.SAM_ACCOUNT_NAME); + + }); + + private static ComponentModel ldapModel = null; + + private static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() { + + @Override + public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) { + LDAPTestUtils.addLocalUser(manager.getSession(), appRealm, "marykeycloak", "mary@test.com", "password-app"); + + MultivaluedHashMap ldapConfig = LDAPTestUtils.getLdapRuleConfig(ldapRule); + ldapConfig.putSingle(LDAPConstants.SYNC_REGISTRATIONS, "true"); + ldapConfig.putSingle(LDAPConstants.EDIT_MODE, UserStorageProvider.EditMode.WRITABLE.toString()); + UserStorageProviderModel model = new UserStorageProviderModel(); + model.setLastSync(0); + model.setChangedSyncPeriod(-1); + model.setFullSyncPeriod(-1); + model.setName("test-ldap"); + model.setPriority(1); + model.setProviderId(LDAPStorageProviderFactory.PROVIDER_NAME); + model.getConfig().addAll(ldapConfig); + + ldapModel = appRealm.addComponentModel(model); + LDAPTestUtils.addZipCodeLDAPMapper(appRealm, ldapModel); + + // Delete all LDAP users and add some new for testing + LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel); + LDAPTestUtils.removeAllLDAPUsers(ldapFedProvider, appRealm); + + // Remove the mapper for "username-cn" and create new mapper for fullName + ComponentModel mapperModel = LDAPTestUtils.getSubcomponentByName(appRealm, ldapModel, "username-cn"); + Assert.assertNotNull(mapperModel); + appRealm.removeComponent(mapperModel); + + mapperModel = KeycloakModelUtils.createComponentModel("fullNameWritable", ldapModel.getId(), FullNameLDAPStorageMapperFactory.PROVIDER_ID, LDAPStorageMapper.class.getName(), + FullNameLDAPStorageMapper.LDAP_FULL_NAME_ATTRIBUTE, LDAPConstants.CN, + FullNameLDAPStorageMapper.READ_ONLY, "false", + FullNameLDAPStorageMapper.WRITE_ONLY, "true"); + appRealm.addComponentModel(mapperModel); + + appRealm.getClientByClientId("test-app").setDirectAccessGrantsEnabled(true); + } + }); + + + @ClassRule + public static TestRule chain = RuleChain + .outerRule(ldapRule) + .around(keycloakRule); + + @Rule + public WebRule webRule = new WebRule(this); + + @WebResource + protected OAuthClient oauth; + + @WebResource + protected WebDriver driver; + + @WebResource + protected AppPage appPage; + + @WebResource + protected RegisterPage registerPage; + + @WebResource + protected LoginPage loginPage; + + @WebResource + protected AccountUpdateProfilePage profilePage; + + @WebResource + protected AccountPasswordPage changePasswordPage; + + + +// @Test +// public void test01Sleep() throws Exception { +// Thread.sleep(1000000); +// } + + @Test + public void test01_addUserWithoutFullName() { + KeycloakSession session = keycloakRule.startSession(); + try { + RealmManager manager = new RealmManager(session); + RealmModel appRealm = manager.getRealm("test"); + + UserModel john = session.users().addUser(appRealm, "johnkeycloak"); + john.setEmail("johnkeycloak@email.cz"); + } finally { + keycloakRule.stopSession(session, true); + } + + session = keycloakRule.startSession(); + try { + RealmManager manager = new RealmManager(session); + RealmModel appRealm = manager.getRealm("test"); + + UserModel john = session.users().getUserByUsername("johnkeycloak", appRealm); + Assert.assertNotNull(john.getFederationLink()); + assertDnStartsWith(session, john, "cn=johnkeycloak"); + + session.users().removeUser(appRealm, john); + } finally { + keycloakRule.stopSession(session, true); + } + + } + + + @Test + public void test02_registerUserWithFullName() { + + loginPage.open(); + loginPage.clickRegister(); + registerPage.assertCurrent(); + + registerPage.register("Johny", "Anthony", "johnyanth@check.cz", "johnkeycloak", "Password1", "Password1"); + Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType()); + + KeycloakSession session = keycloakRule.startSession(); + try { + RealmModel appRealm = session.realms().getRealmByName("test"); + UserModel john = session.users().getUserByUsername("johnkeycloak", appRealm); + assertUser(session, john, "johnkeycloak", "Johny", "Anthony", true, "cn=Johny Anthony"); + + session.users().removeUser(appRealm, john); + } finally { + keycloakRule.stopSession(session, true); + } + } + + + @Test + public void test03_addUserWithFirstNameOnly() { + KeycloakSession session = keycloakRule.startSession(); + try { + RealmManager manager = new RealmManager(session); + RealmModel appRealm = manager.getRealm("test"); + + UserModel john = session.users().addUser(appRealm, "johnkeycloak"); + john.setEmail("johnkeycloak@email.cz"); + john.setFirstName("Johnyyy"); + john.setEnabled(true); + } finally { + keycloakRule.stopSession(session, true); + } + + session = keycloakRule.startSession(); + try { + RealmManager manager = new RealmManager(session); + RealmModel appRealm = manager.getRealm("test"); + + UserModel john = session.users().getUserByUsername("johnkeycloak", appRealm); + assertUser(session, john, "johnkeycloak", "Johnyyy", "", true, "cn=Johnyyy"); + + session.users().removeUser(appRealm, john); + } finally { + keycloakRule.stopSession(session, true); + } + } + + + @Test + public void test04_addUserWithLastNameOnly() { + KeycloakSession session = keycloakRule.startSession(); + try { + RealmManager manager = new RealmManager(session); + RealmModel appRealm = manager.getRealm("test"); + + UserModel john = session.users().addUser(appRealm, "johnkeycloak"); + john.setEmail("johnkeycloak@email.cz"); + john.setLastName("Anthonyy"); + john.setEnabled(true); + } finally { + keycloakRule.stopSession(session, true); + } + + session = keycloakRule.startSession(); + try { + RealmManager manager = new RealmManager(session); + RealmModel appRealm = manager.getRealm("test"); + + UserModel john = session.users().getUserByUsername("johnkeycloak", appRealm); + assertUser(session, john, "johnkeycloak", "", "Anthonyy", true, "cn=Anthonyy"); + + session.users().removeUser(appRealm, john); + } finally { + keycloakRule.stopSession(session, true); + } + } + + + @Test + public void test05_registerUserWithFullNameSpecialChars() { + + loginPage.open(); + loginPage.clickRegister(); + registerPage.assertCurrent(); + + registerPage.register("Jož,o", "Baříč", "johnyanth@check.cz", "johnkeycloak", "Password1", "Password1"); + Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType()); + + KeycloakSession session = keycloakRule.startSession(); + try { + RealmModel appRealm = session.realms().getRealmByName("test"); + UserModel john = session.users().getUserByUsername("johnkeycloak", appRealm); + assertUser(session, john, "johnkeycloak", "Jož,o", "Baříč", true, "cn=Jož\\,o Baříč"); + + session.users().removeUser(appRealm, john); + } finally { + keycloakRule.stopSession(session, true); + } + } + + + @Test + public void test06_conflicts() { + KeycloakSession session = keycloakRule.startSession(); + try { + RealmManager manager = new RealmManager(session); + RealmModel appRealm = manager.getRealm("test"); + + UserModel john = session.users().addUser(appRealm, "existingkc"); + john.setFirstName("John"); + john.setLastName("Existing"); + john.setEnabled(true); + + UserModel john2 = session.users().addUser(appRealm, "existingkc1"); + john2.setEnabled(true); + } finally { + keycloakRule.stopSession(session, true); + } + + loginPage.open(); + loginPage.clickRegister(); + registerPage.assertCurrent(); + + registerPage.register("John", "Existing", "johnyanth@check.cz", "existingkc", "Password1", "Password1"); + Assert.assertEquals("Username already exists.", registerPage.getError()); + + registerPage.register("John", "Existing", "johnyanth@check.cz", "existingkc2", "Password1", "Password1"); + appPage.logout(); + + loginPage.open(); + loginPage.clickRegister(); + registerPage.assertCurrent(); + registerPage.register("John", "Existing", "johnyanth2@check.cz", "existingkc3", "Password1", "Password1"); + + session = keycloakRule.startSession(); + try { + RealmManager manager = new RealmManager(session); + RealmModel appRealm = manager.getRealm("test"); + + UserModel existingKc = session.users().getUserByUsername("existingkc", appRealm); + assertUser(session, existingKc, "existingkc", "John", "Existing", true, "cn=John Existing"); + + UserModel existingKc1 = session.users().getUserByUsername("existingkc1", appRealm); + assertUser(session, existingKc1, "existingkc1", "", "", true, "cn=existingkc1"); + + UserModel existingKc2 = session.users().getUserByUsername("existingkc2", appRealm); + assertUser(session, existingKc2, "existingkc2", "John", "Existing", true, "cn=John Existing0"); + + UserModel existingKc3 = session.users().getUserByUsername("existingkc3", appRealm); + assertUser(session, existingKc3, "existingkc3", "John", "Existing", true, "cn=John Existing1"); + + session.users().removeUser(appRealm, existingKc); + session.users().removeUser(appRealm, existingKc1); + session.users().removeUser(appRealm, existingKc2); + session.users().removeUser(appRealm, existingKc3); + } finally { + keycloakRule.stopSession(session, true); + } + } + + + private void assertUser(KeycloakSession session, UserModel user, String expectedUsername, String expectedFirstName, String expectedLastName, boolean expectedEnabled, String expectedDn) { + Assert.assertNotNull(user); + Assert.assertNotNull(user.getFederationLink()); + Assert.assertEquals(user.getFederationLink(), ldapModel.getId()); + Assert.assertEquals(expectedUsername, user.getUsername()); + Assert.assertEquals(expectedFirstName, user.getFirstName()); + Assert.assertEquals(expectedLastName, user.getLastName()); + Assert.assertEquals(expectedEnabled, user.isEnabled()); + assertDnStartsWith(session, user, expectedDn); + } + + + private void assertDnStartsWith(KeycloakSession session, UserModel user, String expectedRDn) { + String usersDn = LDAPTestUtils.getLdapProvider(session, ldapModel).getLdapIdentityStore().getConfig().getUsersDn(); + String userDN = user.getFirstAttribute(LDAPConstants.LDAP_ENTRY_DN); + Assert.assertTrue(userDN.equalsIgnoreCase(expectedRDn + "," + usersDn)); + } + +}