diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPConfig.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPConfig.java index bebb82b473..adce4f378a 100644 --- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPConfig.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPConfig.java @@ -79,6 +79,10 @@ public class LDAPConfig { return usersDn; } + public String getBaseDn() { + return config.getFirst(LDAPConstants.BASE_DN); + } + public Collection getUserObjectClasses() { String objClassesCfg = config.getFirst(LDAPConstants.USER_OBJECT_CLASSES); String objClassesStr = (objClassesCfg != null && objClassesCfg.length() > 0) ? objClassesCfg.trim() : "inetOrgPerson,organizationalPerson"; diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/util/LDAPTestUtils.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/util/LDAPTestUtils.java index 79e2475fba..1d603e68e5 100644 --- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/util/LDAPTestUtils.java +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/util/LDAPTestUtils.java @@ -21,6 +21,7 @@ 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.ModelException; import org.keycloak.models.RealmModel; import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserModel; @@ -176,6 +177,27 @@ public class LDAPTestUtils { return ldapObject; } + public static LDAPObject addLdapOUinBaseDn(LDAPStorageProvider ldapProvider, String name) { + LDAPObject ldapObject = new LDAPObject(); + ldapObject.setRdnAttributeName("ou"); + ldapObject.setObjectClasses(Collections.singletonList("organizationalUnit")); + ldapObject.setSingleAttribute("ou", name); + LDAPDn dn = LDAPDn.fromString(ldapProvider.getLdapIdentityStore().getConfig().getBaseDn()); + dn.addFirst("ou", name); + ldapObject.setDn(dn); + + // remove OU before adding it in case it already exists + try { + ldapProvider.getLdapIdentityStore().remove(ldapObject); + } catch (ModelException e) { + // OK + } + + ldapProvider.getLdapIdentityStore().add(ldapObject); + + return ldapObject; + } + public static void updateLDAPPassword(LDAPStorageProvider ldapProvider, LDAPObject ldapUser, String password) { ldapProvider.getLdapIdentityStore().updatePassword(ldapUser, password, null); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPReadOnlyTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPReadOnlyTest.java index 6dbc717aca..b98ef3ded2 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPReadOnlyTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPReadOnlyTest.java @@ -28,8 +28,7 @@ import org.junit.Test; import org.junit.runners.MethodSorters; import org.keycloak.OAuth2Constants; import org.keycloak.admin.client.resource.UserResource; -import org.keycloak.events.Details; -import org.keycloak.events.Errors; +import org.keycloak.component.ComponentModel; import org.keycloak.models.AuthenticationExecutionModel; import org.keycloak.models.LDAPConstants; import org.keycloak.models.RealmModel; @@ -42,7 +41,9 @@ import org.keycloak.storage.StorageId; import org.keycloak.storage.UserStorageProvider; import org.keycloak.storage.ldap.LDAPStorageProvider; import org.keycloak.storage.ldap.idm.model.LDAPObject; -import org.keycloak.testsuite.AssertEvents; +import org.keycloak.storage.ldap.mappers.LDAPStorageMapper; +import org.keycloak.storage.ldap.mappers.msad.MSADUserAccountControlStorageMapper; +import org.keycloak.storage.ldap.mappers.msad.MSADUserAccountControlStorageMapperFactory; import org.keycloak.testsuite.admin.ApiUtil; import org.keycloak.testsuite.pages.AppPage; import org.keycloak.testsuite.pages.LoginConfigTotpPage; @@ -99,6 +100,16 @@ public class LDAPReadOnlyTest extends AbstractLDAPTest { LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ctx.getLdapModel()); ldapFedProvider.getModel().put(LDAPConstants.EDIT_MODE, UserStorageProvider.EditMode.READ_ONLY.toString()); + + // change MSAD mapper config "ALWAYS_READ_ENABLED_VALUE_FROM_LDAP" to false as edit mode is read only so setEnable(false) is not propagated to LDAP + ComponentModel msadMapperComponent = appRealm.getComponentsStream(ctx.getLdapModel().getId(), LDAPStorageMapper.class.getName()) + .filter(c -> MSADUserAccountControlStorageMapperFactory.PROVIDER_ID.equals(c.getProviderId())) + .findFirst().orElse(null); + if (msadMapperComponent != null) { + msadMapperComponent.getConfig().putSingle(MSADUserAccountControlStorageMapper.ALWAYS_READ_ENABLED_VALUE_FROM_LDAP, "false"); + appRealm.updateComponent(msadMapperComponent); + } + appRealm.updateComponent(ldapFedProvider.getModel()); }); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPSearchForUsersPaginationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPSearchForUsersPaginationTest.java index 5ef8013f5d..b99278e81a 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPSearchForUsersPaginationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPSearchForUsersPaginationTest.java @@ -43,6 +43,7 @@ import org.keycloak.models.UserProvider; import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.storage.DatastoreProvider; import org.keycloak.storage.datastore.DefaultDatastoreProvider; +import org.keycloak.storage.ldap.idm.model.LDAPObject; import org.keycloak.testsuite.admin.ApiUtil; import org.keycloak.testsuite.util.LDAPRule; import org.keycloak.testsuite.util.LDAPTestUtils; @@ -67,15 +68,20 @@ public class LDAPSearchForUsersPaginationTest extends AbstractLDAPTest { RealmModel appRealm = ctx.getRealm(); LDAPTestUtils.addUserAttributeMapper(appRealm, ctx.getLdapModel(), "streetMapper", LDAPConstants.STREET, LDAPConstants.STREET); - // Delete all local users and add some new for testing - session.users().searchForUserStream(appRealm, new HashMap<>()).collect(Collectors.toList()).forEach(u -> session.users().removeUser(appRealm, u)); - // Delete all LDAP users and add some new for testing LDAPTestUtils.removeAllLDAPUsers(ctx.getLdapProvider(), appRealm); - LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), appRealm, "john", "Some", "Some", "john14@email.org", "Acacia Avenue", "1234"); - LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), appRealm, "john00", "john", "Doe", "john0@email.org", "Acacia Avenue", "1234"); - LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), appRealm, "john01", "john", "Doe", "john1@email.org", "Acacia Avenue", "1234"); + // Delete all local users and add some new for testing + session.users().searchForUserStream(appRealm, new HashMap<>()).collect(Collectors.toList()).forEach(u -> session.users().removeUser(appRealm, u)); + + LDAPObject ldapUser = LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), appRealm, "john", "Some", "Some", "john14@email.org", "Acacia Avenue", "1234"); + // somehow, for MSAD it is only possible to create an enabled user by updating the password and setting USER_ACCOUNT_CONTROL attribute to 512 + LDAPTestUtils.updateLDAPPassword(ctx.getLdapProvider(), ldapUser, "password"); + ldapUser = LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), appRealm, "john00", "john", "Doe", "john0@email.org", "Acacia Avenue", "1234"); + LDAPTestUtils.updateLDAPPassword(ctx.getLdapProvider(), ldapUser, "password"); + ldapUser = LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), appRealm, "john01", "john", "Doe", "john1@email.org", "Acacia Avenue", "1234"); + LDAPTestUtils.updateLDAPPassword(ctx.getLdapProvider(), ldapUser, "password"); + LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), appRealm, "john02", "john", "Doe", "john2@email.org", null, "1234"); LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), appRealm, "john03", "john", "Doe", "john3@email.org", null, "1234"); LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), appRealm, "john04", "john", "Doe", "john4@email.org", null, "1234"); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPUserProfileTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPUserProfileTest.java index b7b1149077..c1d64111dd 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPUserProfileTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPUserProfileTest.java @@ -25,6 +25,7 @@ import java.util.Set; import org.jboss.arquillian.graphene.page.Page; import org.junit.Assert; +import org.junit.Assume; import org.junit.ClassRule; import org.junit.FixMethodOrder; import org.junit.Test; @@ -267,6 +268,12 @@ public class LDAPUserProfileTest extends AbstractLDAPTest { ldapCompModel.put(PrioritizedComponentModel.PRIORITY, "100"); testRealm.addComponentModel(ldapModel); LDAPStorageProvider ldapProvider = LDAPTestUtils.getLdapProvider(session, ldapModel); + + // if AD, create new OU in a base DN because users.ldif is ignored for AD + if (LDAPConstants.VENDOR_ACTIVE_DIRECTORY.equals(ldapModel.getConfig().getFirst(LDAPConstants.VENDOR))) { + LDAPTestUtils.addLdapOUinBaseDn(ldapProvider, "OtherPeople2"); + LDAPTestUtils.removeAllLDAPUsers(ldapProvider, testRealm); + } LDAPObject john = LDAPTestUtils.addLDAPUser(ldapProvider, testRealm, "anotherjohn", "AnotherJohn", "AnotherDoe", "anotherjohn@email.org", null, "1234"); LDAPTestUtils.updateLDAPPassword(ldapProvider, john, "Password1"); }); @@ -304,6 +311,11 @@ public class LDAPUserProfileTest extends AbstractLDAPTest { @Test public void testUsernameRespectFormatFromExternalStore() { + Assume.assumeFalse("Skip for AD", testingClient.server().fetch(session -> { + LDAPTestContext ctx = LDAPTestContext.init(session); + return LDAPConstants.VENDOR_ACTIVE_DIRECTORY.equals(ctx.getLdapModel().getConfig().getFirst(LDAPConstants.VENDOR)); + }, Boolean.class)); + String upperCaseUsername = "JOHNKEYCLOAK3"; testingClient.server().run(session -> { LDAPTestContext ctx = LDAPTestContext.init(session, "test-ldap"); @@ -347,6 +359,43 @@ public class LDAPUserProfileTest extends AbstractLDAPTest { appPage.assertCurrent(); } + @Test + public void testUsernameRespectFormatFromExternalStoreAD() { + Assume.assumeTrue("Only run for AD", testingClient.server().fetch(session -> { + LDAPTestContext ctx = LDAPTestContext.init(session); + return LDAPConstants.VENDOR_ACTIVE_DIRECTORY.equals(ctx.getLdapModel().getConfig().getFirst(LDAPConstants.VENDOR)); + }, Boolean.class)); + + String upperCaseUsername = "JOHNKEYCLOAK3"; + testingClient.server().run(session -> { + LDAPTestContext ctx = LDAPTestContext.init(session, "test-ldap"); + RealmModel appRealm = ctx.getRealm(); + + ComponentModel ldapComponentMapper = LDAPTestUtils.addUserAttributeMapper(appRealm, ctx.getLdapModel(), "username-cn-mapper", "username", LDAPConstants.CN); + ldapComponentMapper.put(UserAttributeLDAPStorageMapper.ALWAYS_READ_VALUE_FROM_LDAP, true); + appRealm.updateComponent(ldapComponentMapper); + + LDAPObject john3 = LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), appRealm, upperCaseUsername, "John", "Doe", "john3@email.org", "12345"); + LDAPTestUtils.updateLDAPPassword(ctx.getLdapProvider(), john3, "Password1"); + }); + + UserResource johnResource = ApiUtil.findUserByUsernameId(testRealm(), upperCaseUsername); + UserRepresentation john = johnResource.toRepresentation(true); + Assert.assertEquals(upperCaseUsername, john.getUsername()); + + johnResource = ApiUtil.findUserByUsernameId(testRealm(), upperCaseUsername.toLowerCase()); + john = johnResource.toRepresentation(true); + Assert.assertEquals(upperCaseUsername, john.getUsername()); + + loginPage.open(); + loginPage.login(upperCaseUsername, "Password1"); + appPage.assertCurrent(); + testRealm().users().get(john.getId()).logout(); + loginPage.open(); + loginPage.login(upperCaseUsername.toLowerCase(), "Password1"); + appPage.assertCurrent(); + } + private void setLDAPReadOnly() { testingClient.server().run(session -> { LDAPTestContext ctx = LDAPTestContext.init(session, "test-ldap");