From 04401af470e65d1047597d1812728b4783db34fd Mon Sep 17 00:00:00 2001 From: mposolda Date: Tue, 5 Jan 2016 16:45:14 +0100 Subject: [PATCH 1/2] Fix testsuite to pass with MSAD --- testsuite/integration/pom.xml | 22 +++++++++++++++ .../federation/ldap/FederationTestUtils.java | 10 +++++++ .../FederationProvidersIntegrationTest.java | 28 +++++++++---------- .../ldap/base/LDAPGroupMapperTest.java | 6 ++-- .../ldap/base/LDAPMultipleAttributesTest.java | 8 +++--- .../ldap/base/LDAPRoleMappingsTest.java | 6 ++-- .../ldap/base/SyncProvidersTest.java | 2 +- 7 files changed, 57 insertions(+), 25 deletions(-) diff --git a/testsuite/integration/pom.xml b/testsuite/integration/pom.xml index db31b7f61f..15ca2d38a0 100755 --- a/testsuite/integration/pom.xml +++ b/testsuite/integration/pom.xml @@ -511,5 +511,27 @@ + + + + msad + + + + org.apache.maven.plugins + maven-surefire-plugin + + + org/keycloak/testsuite/federation/ldap/base/** + + + **/LDAPMultipleAttributesTest.java + + + + + + + diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/FederationTestUtils.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/FederationTestUtils.java index f189e2012c..0f357d3e39 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/FederationTestUtils.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/FederationTestUtils.java @@ -93,6 +93,16 @@ public class FederationTestUtils { return LDAPUtils.addUserToLDAP(ldapProvider, realm, helperUser); } + public static void updateLDAPPassword(LDAPFederationProvider ldapProvider, LDAPObject ldapUser, String password) { + ldapProvider.getLdapIdentityStore().updatePassword(ldapUser, password); + + // Enable MSAD user through userAccountControls + if (ldapProvider.getLdapIdentityStore().getConfig().isActiveDirectory()) { + ldapUser.setSingleAttribute(LDAPConstants.USER_ACCOUNT_CONTROL, "512"); + ldapProvider.getLdapIdentityStore().update(ldapUser); + } + } + public static LDAPFederationProvider getLdapProvider(KeycloakSession keycloakSession, UserFederationProviderModel ldapFedModel) { LDAPFederationProviderFactory ldapProviderFactory = (LDAPFederationProviderFactory) keycloakSession.getKeycloakSessionFactory().getProviderFactory(UserFederationProvider.class, ldapFedModel.getProviderName()); return ldapProviderFactory.getInstance(keycloakSession, ldapFedModel); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/FederationProvidersIntegrationTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/FederationProvidersIntegrationTest.java index b07e23dfcc..0192bbca7a 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/FederationProvidersIntegrationTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/FederationProvidersIntegrationTest.java @@ -79,7 +79,7 @@ public class FederationProvidersIntegrationTest { FederationTestUtils.removeAllLDAPUsers(ldapFedProvider, appRealm); LDAPObject john = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "johnkeycloak", "John", "Doe", "john@email.org", null, "1234"); - ldapFedProvider.getLdapIdentityStore().updatePassword(john, "Password1"); + FederationTestUtils.updateLDAPPassword(ldapFedProvider, john, "Password1"); LDAPObject existing = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "existing", "Existing", "Foo", "existing@email.org", null, "5678"); @@ -132,9 +132,9 @@ public class FederationProvidersIntegrationTest { RealmModel appRealm = manager.getRealm("test"); LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel); LDAPObject jbrown2 = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "JBrown2", "John", "Brown2", "jbrown2@email.org", null, "1234"); - ldapFedProvider.getLdapIdentityStore().updatePassword(jbrown2, "Password1"); + FederationTestUtils.updateLDAPPassword(ldapFedProvider, jbrown2, "Password1"); LDAPObject jbrown3 = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "jbrown3", "John", "Brown3", "JBrown3@email.org", null, "1234"); - ldapFedProvider.getLdapIdentityStore().updatePassword(jbrown3, "Password1"); + FederationTestUtils.updateLDAPPassword(ldapFedProvider, jbrown3, "Password1"); } finally { keycloakRule.stopSession(session, true); } @@ -165,10 +165,10 @@ public class FederationProvidersIntegrationTest { RealmManager manager = new RealmManager(session); RealmModel appRealm = manager.getRealm("test"); LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel); - LDAPObject jbrown2 = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "JBrown4", "John", "Brown4", "jbrown4@email.org", null, "1234"); - ldapFedProvider.getLdapIdentityStore().updatePassword(jbrown2, "Password1"); - LDAPObject jbrown3 = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "jbrown5", "John", "Brown5", "JBrown5@Email.org", null, "1234"); - ldapFedProvider.getLdapIdentityStore().updatePassword(jbrown3, "Password1"); + LDAPObject jbrown4 = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "JBrown4", "John", "Brown4", "jbrown4@email.org", null, "1234"); + FederationTestUtils.updateLDAPPassword(ldapFedProvider, jbrown4, "Password1"); + LDAPObject jbrown5 = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "jbrown5", "John", "Brown5", "JBrown5@Email.org", null, "1234"); + FederationTestUtils.updateLDAPPassword(ldapFedProvider, jbrown5, "Password1"); } finally { keycloakRule.stopSession(session, true); } @@ -371,7 +371,7 @@ public class FederationProvidersIntegrationTest { } @Test - public void testDotInUsername() { + public void testCommaInUsername() { KeycloakSession session = keycloakRule.startSession(); boolean skip = false; @@ -379,23 +379,23 @@ public class FederationProvidersIntegrationTest { RealmModel appRealm = new RealmManager(session).getRealmByName("test"); LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel); - // Workaround as dot is not allowed in sAMAccountName on active directory. So we will skip the test for this configuration + // Workaround as comma is not allowed in sAMAccountName on active directory. So we will skip the test for this configuration LDAPConfig config = ldapFedProvider.getLdapIdentityStore().getConfig(); if (config.isActiveDirectory() && config.getUsernameLdapAttribute().equals(LDAPConstants.SAM_ACCOUNT_NAME)) { skip = true; } if (!skip) { - LDAPObject johnDot = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "john,dot", "John", "Dot", "johndot@email.org", null, "12387"); - ldapFedProvider.getLdapIdentityStore().updatePassword(johnDot, "Password1"); + LDAPObject johnComma = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "john,comma", "John", "Comma", "johncomma@email.org", null, "12387"); + FederationTestUtils.updateLDAPPassword(ldapFedProvider, johnComma, "Password1"); } } finally { keycloakRule.stopSession(session, false); } if (!skip) { - // Try to import the user with dot in username into Keycloak - loginSuccessAndLogout("john,dot", "Password1"); + // Try to import the user with comma in username into Keycloak + loginSuccessAndLogout("john,comma", "Password1"); } } @@ -583,7 +583,7 @@ public class FederationProvidersIntegrationTest { FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "marykeycloak", "Mary1", "Kelly1", "mary1@email.org", null, "123"); FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "mary-duplicatemail", "Mary2", "Kelly2", "mary@test.com", null, "123"); LDAPObject marynoemail = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "marynoemail", "Mary1", "Kelly1", null, null, "123"); - ldapFedProvider.getLdapIdentityStore().updatePassword(marynoemail, "Password1"); + FederationTestUtils.updateLDAPPassword(ldapFedProvider, marynoemail, "Password1"); } }); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/LDAPGroupMapperTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/LDAPGroupMapperTest.java index 8778735291..de7508f1d1 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/LDAPGroupMapperTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/LDAPGroupMapperTest.java @@ -84,13 +84,13 @@ public class LDAPGroupMapperTest { // Add some LDAP users for testing LDAPObject john = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "johnkeycloak", "John", "Doe", "john@email.org", null, "1234"); - ldapFedProvider.getLdapIdentityStore().updatePassword(john, "Password1"); + FederationTestUtils.updateLDAPPassword(ldapFedProvider, john, "Password1"); LDAPObject mary = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "marykeycloak", "Mary", "Kelly", "mary@email.org", null, "5678"); - ldapFedProvider.getLdapIdentityStore().updatePassword(mary, "Password1"); + FederationTestUtils.updateLDAPPassword(ldapFedProvider, mary, "Password1"); LDAPObject rob = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "robkeycloak", "Rob", "Brown", "rob@email.org", null, "8910"); - ldapFedProvider.getLdapIdentityStore().updatePassword(rob, "Password1"); + FederationTestUtils.updateLDAPPassword(ldapFedProvider, rob, "Password1"); } }); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/LDAPMultipleAttributesTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/LDAPMultipleAttributesTest.java index 6bca1ee066..67e8447b3a 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/LDAPMultipleAttributesTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/LDAPMultipleAttributesTest.java @@ -70,13 +70,13 @@ public class LDAPMultipleAttributesTest { FederationTestUtils.removeAllLDAPUsers(ldapFedProvider, appRealm); LDAPObject james = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "jbrown", "James", "Brown", "jbrown@keycloak.org", null, "88441"); - ldapFedProvider.getLdapIdentityStore().updatePassword(james, "password"); + FederationTestUtils.updateLDAPPassword(ldapFedProvider, james, "Password1"); // User for testing duplicating surname and postalCode LDAPObject bruce = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "bwilson", "Bruce", "Wilson", "bwilson@keycloak.org", "Elm 5", "88441", "77332"); bruce.setAttribute("sn", new LinkedHashSet<>(Arrays.asList("Wilson", "Schneider"))); ldapFedProvider.getLdapIdentityStore().update(bruce); - ldapFedProvider.getLdapIdentityStore().updatePassword(bruce, "password"); + FederationTestUtils.updateLDAPPassword(ldapFedProvider, bruce, "Password1"); // Create ldap-portal client ClientModel ldapClient = KeycloakModelUtils.createClient(appRealm, "ldap-portal"); @@ -174,7 +174,7 @@ public class LDAPMultipleAttributesTest { // Login as bwilson driver.navigate().to(APP_SERVER_BASE_URL + "/ldap-portal"); Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL)); - loginPage.login("bwilson", "password"); + loginPage.login("bwilson", "Password1"); Assert.assertTrue(driver.getCurrentUrl().startsWith(APP_SERVER_BASE_URL + "/ldap-portal")); String pageSource = driver.getPageSource(); System.out.println(pageSource); @@ -190,7 +190,7 @@ public class LDAPMultipleAttributesTest { // Login as jbrown driver.navigate().to(APP_SERVER_BASE_URL + "/ldap-portal"); Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL)); - loginPage.login("jbrown", "password"); + loginPage.login("jbrown", "Password1"); Assert.assertTrue(driver.getCurrentUrl().startsWith(APP_SERVER_BASE_URL + "/ldap-portal")); pageSource = driver.getPageSource(); System.out.println(pageSource); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/LDAPRoleMappingsTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/LDAPRoleMappingsTest.java index 50cc6d2590..8aa4e49dc7 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/LDAPRoleMappingsTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/LDAPRoleMappingsTest.java @@ -75,13 +75,13 @@ public class LDAPRoleMappingsTest { // Add some users for testing LDAPObject john = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "johnkeycloak", "John", "Doe", "john@email.org", null, "1234"); - ldapFedProvider.getLdapIdentityStore().updatePassword(john, "Password1"); + FederationTestUtils.updateLDAPPassword(ldapFedProvider, john, "Password1"); LDAPObject mary = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "marykeycloak", "Mary", "Kelly", "mary@email.org", null, "5678"); - ldapFedProvider.getLdapIdentityStore().updatePassword(mary, "Password1"); + FederationTestUtils.updateLDAPPassword(ldapFedProvider, mary, "Password1"); LDAPObject rob = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "robkeycloak", "Rob", "Brown", "rob@email.org", null, "8910"); - ldapFedProvider.getLdapIdentityStore().updatePassword(rob, "Password1"); + FederationTestUtils.updateLDAPPassword(ldapFedProvider, rob, "Password1"); // Add some roles for testing FederationTestUtils.createLDAPRole(manager.getSession(), appRealm, ldapModel, "realmRolesMapper", "realmRole1"); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/SyncProvidersTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/SyncProvidersTest.java index 39ef19e5c6..a46475eb7e 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/SyncProvidersTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/SyncProvidersTest.java @@ -65,7 +65,7 @@ public class SyncProvidersTest { for (int i=1 ; i<=5 ; i++) { LDAPObject ldapUser = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "user" + i, "User" + i + "FN", "User" + i + "LN", "user" + i + "@email.org", null, "12" + i); - ldapFedProvider.getLdapIdentityStore().updatePassword(ldapUser, "Password1"); + FederationTestUtils.updateLDAPPassword(ldapFedProvider, ldapUser, "Password1"); } // Add dummy provider From 658f204d9216acb55fa57bfffefda352ce1d9091 Mon Sep 17 00:00:00 2001 From: mposolda Date: Tue, 5 Jan 2016 18:50:54 +0100 Subject: [PATCH 2/2] Documentation for new LDAP mappers --- .../en/en-US/modules/user-federation.xml | 48 +++++++++++++++++++ .../GroupLDAPFederationMapperFactory.java | 2 +- .../role/RoleLDAPFederationMapperFactory.java | 2 +- 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/user-federation.xml b/docbook/auth-server-docs/reference/en/en-US/modules/user-federation.xml index 272c2f69b8..ca82535d78 100755 --- a/docbook/auth-server-docs/reference/en/en-US/modules/user-federation.xml +++ b/docbook/auth-server-docs/reference/en/en-US/modules/user-federation.xml @@ -202,6 +202,54 @@ + + Hardcoded Role Mapper + + + This mapper will grant specified Keycloak role to each Keycloak user linked with LDAP. + + + + + Group Mapper + + + This allows to configure group mappings from LDAP into Keycloak group mappings. Group mapper can be used to map LDAP groups from particular branch of LDAP tree + into groups in Keycloak. And it will also propagate user-group mappings from LDAP into user-group mappings in Keycloak. + + + You can choose to preserve group inheritance from LDAP as well, but this may fail as Keycloak inheritance is more restrictive than LDAP + (For example in Keycloak each group can have just one parent and there is no recursion allowed. In LDAP the recursion is possible and every group can be member of more + other groups too). + + + As of now, the mapper doesn't provide mapping of LDAP roles-groups to Keycloak roles-groups + (For example when LDAP group cn=role1,ou=roles,dc=example,dc=com is member of LDAP group + cn=group1,ou=groups,dc=example,dc=com , we don't support the mapping of Keycloak role role1 imported from LDAP to corresponding Keycloak group group1 imported from LDAP). + + + + + MSAD User Account Mapper + + + Mapper specific to Microsoft Active Directory (MSAD). It's able to tightly integrate the MSAD user account state into Keycloak account state (account enabled, password is expired etc). + It's using userAccountControl and pwdLastSet LDAP attributes for that (both are specific to MSAD and are not LDAP standard). + For example if pwdLastSet is 0, the Keycloak user is required to update password (there will be UPDATE_PASSWORD required action added to him in Keycloak). Or if userAccountControl + is 514 (disabled account) the Keycloak user is disabled as well etc. + + + For writable LDAP, the mapping is bi-directional and the state from Keycloak is propagated to LDAP (For example enable user + in Keycloak admin console will update the value of userAccountControl in MSAD and effectively enable him in MSAD as well). + + + For writable LDAPs, mapper also provides mapping of error codes during MSAD user authentication to the + appropriate action in Keycloak. For example if MSAD user authentication fails due to the fact, that MSAD password is expired, + the mapper will allow user to authenticate into Keycloak, but it will add UPDATE_PASSWORD required action to the user, so user + must update his password. + + + By default, there is set of User Attribute mappers to map basic UserModel attributes username, first name, lastname and email to corresponding LDAP attributes. You are free to extend this and provide diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/group/GroupLDAPFederationMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/group/GroupLDAPFederationMapperFactory.java index fa43ac9721..c2134f114d 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/group/GroupLDAPFederationMapperFactory.java +++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/group/GroupLDAPFederationMapperFactory.java @@ -78,7 +78,7 @@ public class GroupLDAPFederationMapperFactory extends AbstractLDAPFederationMapp ProviderConfigProperty ldapFilter = createConfigProperty(GroupMapperConfig.GROUPS_LDAP_FILTER, "LDAP Filter", - "LDAP Filter adds additional custom filter to the whole query. Leave this empty if no additional filtering is needed. Otherwise make sure that filter starts with '(' and ends with ')'", + "LDAP Filter adds additional custom filter to the whole query for retrieve LDAP groups. Leave this empty if no additional filtering is needed and you want to retrieve all groups from LDAP. Otherwise make sure that filter starts with '(' and ends with ')'", ProviderConfigProperty.STRING_TYPE, null); configProperties.add(ldapFilter); diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/role/RoleLDAPFederationMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/role/RoleLDAPFederationMapperFactory.java index fe0b1e194c..58023aa9fc 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/role/RoleLDAPFederationMapperFactory.java +++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/role/RoleLDAPFederationMapperFactory.java @@ -72,7 +72,7 @@ public class RoleLDAPFederationMapperFactory extends AbstractLDAPFederationMappe ProviderConfigProperty ldapFilter = createConfigProperty(RoleMapperConfig.ROLES_LDAP_FILTER, "LDAP Filter", - "LDAP Filter adds additional custom filter to the whole query. Leave this empty if no additional filtering is needed. Otherwise make sure that filter starts with '(' and ends with ')'", + "LDAP Filter adds additional custom filter to the whole query for retrieve LDAP roles. Leave this empty if no additional filtering is needed and you want to retrieve all roles from LDAP. Otherwise make sure that filter starts with '(' and ends with ')'", ProviderConfigProperty.STRING_TYPE, null); configProperties.add(ldapFilter);