From 42f4e7fef8ae114fd3952b3de83752534d019dac Mon Sep 17 00:00:00 2001 From: mposolda Date: Mon, 7 Mar 2016 23:18:58 +0100 Subject: [PATCH] KEYCLOAK-2583 Synchronize users after creating ldap federation provider --- .../ldap/LDAPFederationProvider.java | 17 ++- .../ldap/LDAPFederationProviderFactory.java | 3 +- .../keycloak/federation/ldap/LDAPUtils.java | 4 +- .../ldap/idm/query/internal/LDAPQuery.java | 3 +- .../ldap/mappers/LDAPMappersComparator.java | 114 +++++++++++++++++ .../idm/model/LDAPMappersComparatorTest.java | 117 ++++++++++++++++++ 6 files changed, 253 insertions(+), 5 deletions(-) create mode 100644 federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/LDAPMappersComparator.java create mode 100644 federation/ldap/src/test/java/org/keycloak/federation/ldap/idm/model/LDAPMappersComparatorTest.java diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java index e800bc3929..010870ee2a 100755 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java +++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java @@ -27,6 +27,7 @@ import org.keycloak.federation.ldap.idm.query.internal.LDAPQueryConditionsBuilde import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore; import org.keycloak.federation.ldap.kerberos.LDAPProviderKerberosConfig; import org.keycloak.federation.ldap.mappers.LDAPFederationMapper; +import org.keycloak.federation.ldap.mappers.LDAPMappersComparator; import org.keycloak.models.CredentialValidationOutput; import org.keycloak.models.GroupModel; import org.keycloak.models.KeycloakSession; @@ -47,6 +48,7 @@ import org.keycloak.services.managers.UserManager; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -128,7 +130,8 @@ public class LDAPFederationProvider implements UserFederationProvider { } Set federationMappers = realm.getUserFederationMappersByFederationProvider(model.getId()); - for (UserFederationMapperModel mapperModel : federationMappers) { + List sortedMappers = sortMappersAsc(federationMappers); + for (UserFederationMapperModel mapperModel : sortedMappers) { LDAPFederationMapper ldapMapper = getMapper(mapperModel); proxied = ldapMapper.proxy(mapperModel, this, ldapObject, proxied, realm); } @@ -313,7 +316,8 @@ public class LDAPFederationProvider implements UserFederationProvider { imported.setEnabled(true); Set federationMappers = realm.getUserFederationMappersByFederationProvider(getModel().getId()); - for (UserFederationMapperModel mapperModel : federationMappers) { + List sortedMappers = sortMappersDesc(federationMappers); + for (UserFederationMapperModel mapperModel : sortedMappers) { if (logger.isTraceEnabled()) { logger.tracef("Using mapper %s during import user from LDAP", mapperModel); } @@ -517,4 +521,13 @@ public class LDAPFederationProvider implements UserFederationProvider { return ldapMapper; } + + + public List sortMappersAsc(Collection mappers) { + return LDAPMappersComparator.sortAsc(getLdapIdentityStore().getConfig(), mappers); + } + + protected List sortMappersDesc(Collection mappers) { + return LDAPMappersComparator.sortDesc(getLdapIdentityStore().getConfig(), mappers); + } } diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProviderFactory.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProviderFactory.java index f6e4026be7..bfc7fc2be9 100755 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProviderFactory.java +++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProviderFactory.java @@ -354,7 +354,8 @@ public class LDAPFederationProviderFactory extends UserFederationEventAwareProvi // Update keycloak user Set federationMappers = currentRealm.getUserFederationMappersByFederationProvider(fedModel.getId()); - for (UserFederationMapperModel mapperModel : federationMappers) { + List sortedMappers = ldapFedProvider.sortMappersDesc(federationMappers); + for (UserFederationMapperModel mapperModel : sortedMappers) { LDAPFederationMapper ldapMapper = ldapFedProvider.getMapper(mapperModel); ldapMapper.onImportUserFromLDAP(mapperModel, ldapFedProvider, ldapUser, currentUser, currentRealm, false); } diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPUtils.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPUtils.java index a37362532b..4d64e2c665 100755 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPUtils.java +++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPUtils.java @@ -19,6 +19,7 @@ package org.keycloak.federation.ldap; import java.util.Collection; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; @@ -58,7 +59,8 @@ public class LDAPUtils { ldapUser.setObjectClasses(ldapConfig.getUserObjectClasses()); Set federationMappers = realm.getUserFederationMappersByFederationProvider(ldapProvider.getModel().getId()); - for (UserFederationMapperModel mapperModel : federationMappers) { + List sortedMappers = ldapProvider.sortMappersAsc(federationMappers); + for (UserFederationMapperModel mapperModel : sortedMappers) { LDAPFederationMapper ldapMapper = ldapProvider.getMapper(mapperModel); ldapMapper.onRegisterUserToLDAP(mapperModel, ldapProvider, ldapUser, user, realm); } diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/LDAPQuery.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/LDAPQuery.java index dea173f075..957ba33b3a 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/LDAPQuery.java +++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/LDAPQuery.java @@ -153,7 +153,8 @@ public class LDAPQuery { public List getResultList() { // Apply mappers now - for (UserFederationMapperModel mapperModel : mappers) { + List sortedMappers = ldapFedProvider.sortMappersAsc(mappers); + for (UserFederationMapperModel mapperModel : sortedMappers) { LDAPFederationMapper fedMapper = ldapFedProvider.getMapper(mapperModel); fedMapper.beforeLDAPQuery(mapperModel, this); } diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/LDAPMappersComparator.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/LDAPMappersComparator.java new file mode 100644 index 0000000000..c5507e62c5 --- /dev/null +++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/LDAPMappersComparator.java @@ -0,0 +1,114 @@ +/* + * 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.federation.ldap.mappers; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import org.keycloak.federation.ldap.LDAPConfig; +import org.keycloak.models.UserFederationMapperModel; +import org.keycloak.models.UserModel; + +/** + * TODO: Possibly add "priority" to UserFederationMapper instead of hardcoding behaviour + * + * @author Marek Posolda + */ +public class LDAPMappersComparator { + + public static List sortAsc(LDAPConfig ldapConfig, Collection mappers) { + Comparator comparator = new ImportantFirstComparator(ldapConfig); + + List result = new ArrayList<>(mappers); + Collections.sort(result, comparator); + return result; + } + + public static List sortDesc(LDAPConfig ldapConfig, Collection mappers) { + Comparator comparator = new ImportantFirstComparator(ldapConfig).reversed(); + + List result = new ArrayList<>(mappers); + Collections.sort(result, comparator); + return result; + } + + + private static class ImportantFirstComparator implements Comparator { + + private final LDAPConfig ldapConfig; + + public ImportantFirstComparator(LDAPConfig ldapConfig) { + this.ldapConfig = ldapConfig; + } + + @Override + public int compare(UserFederationMapperModel o1, UserFederationMapperModel o2) { + // UserAttributeLDAPFederationMapper first + boolean isO1AttrMapper = o1.getFederationMapperType().equals(UserAttributeLDAPFederationMapperFactory.PROVIDER_ID); + boolean isO2AttrMapper = o2.getFederationMapperType().equals(UserAttributeLDAPFederationMapperFactory.PROVIDER_ID); + if (!isO1AttrMapper) { + if (isO2AttrMapper) { + return 1; + } else { + return 0; + } + } else if (!isO2AttrMapper) { + return -1; + } + + // Mapper for "username" attribute first + String model1 = o1.getConfig().get(UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE); + String model2 = o2.getConfig().get(UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE); + boolean isO1UsernameMapper = model1 != null && model1.equalsIgnoreCase(UserModel.USERNAME); + boolean isO2UsernameMapper = model2 != null && model2.equalsIgnoreCase(UserModel.USERNAME); + if (!isO1UsernameMapper) { + if (isO2UsernameMapper) { + return 1; + } else { + return 0; + } + } else if (!isO2UsernameMapper) { + return -1; + } + + // The username mapper corresponding to the same like configured username for federationProvider is first + String o1LdapAttr = o1.getConfig().get(UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE); + String o2LdapAttr = o2.getConfig().get(UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE); + boolean isO1LdapAttr = o1LdapAttr != null && ldapConfig.getUsernameLdapAttribute().equalsIgnoreCase(o1LdapAttr); + boolean isO2LdapAttr = o2LdapAttr != null && ldapConfig.getUsernameLdapAttribute().equalsIgnoreCase(o2LdapAttr); + + if (!isO1LdapAttr) { + if (isO2LdapAttr) { + return 1; + } else { + return 0; + } + } else if (!isO2LdapAttr) { + return -1; + } + + return 0; + } + + } + + +} diff --git a/federation/ldap/src/test/java/org/keycloak/federation/ldap/idm/model/LDAPMappersComparatorTest.java b/federation/ldap/src/test/java/org/keycloak/federation/ldap/idm/model/LDAPMappersComparatorTest.java new file mode 100644 index 0000000000..7ca7ff2a88 --- /dev/null +++ b/federation/ldap/src/test/java/org/keycloak/federation/ldap/idm/model/LDAPMappersComparatorTest.java @@ -0,0 +1,117 @@ +/* + * 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.federation.ldap.idm.model; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.junit.Assert; +import org.junit.Test; +import org.keycloak.federation.ldap.LDAPConfig; +import org.keycloak.federation.ldap.mappers.FullNameLDAPFederationMapper; +import org.keycloak.federation.ldap.mappers.FullNameLDAPFederationMapperFactory; +import org.keycloak.federation.ldap.mappers.LDAPMappersComparator; +import org.keycloak.federation.ldap.mappers.UserAttributeLDAPFederationMapper; +import org.keycloak.federation.ldap.mappers.UserAttributeLDAPFederationMapperFactory; +import org.keycloak.models.LDAPConstants; +import org.keycloak.models.UserFederationMapperModel; +import org.keycloak.models.UserModel; +import org.keycloak.models.utils.KeycloakModelUtils; + +/** + * @author Marek Posolda + */ +public class LDAPMappersComparatorTest { + + + + @Test + public void testCompareWithCNUsername() { + Map cfg = new HashMap<>(); + cfg.put(LDAPConstants.USERNAME_LDAP_ATTRIBUTE, LDAPConstants.CN); + LDAPConfig config = new LDAPConfig(cfg); + + List sorted = LDAPMappersComparator.sortAsc(config, getMappers()); + assertOrder(sorted, "username-cn", "sAMAccountName", "first name", "full name"); + + sorted = LDAPMappersComparator.sortDesc(config, getMappers()); + assertOrder(sorted, "full name", "first name", "sAMAccountName", "username-cn"); + } + + @Test + public void testCompareWithSAMAccountNameUsername() { + Map cfg = new HashMap<>(); + cfg.put(LDAPConstants.USERNAME_LDAP_ATTRIBUTE, LDAPConstants.SAM_ACCOUNT_NAME); + LDAPConfig config = new LDAPConfig(cfg); + + List sorted = LDAPMappersComparator.sortAsc(config, getMappers()); + assertOrder(sorted, "sAMAccountName", "username-cn", "first name", "full name"); + + sorted = LDAPMappersComparator.sortDesc(config, getMappers()); + assertOrder(sorted, "full name", "first name", "username-cn", "sAMAccountName"); + } + + private void assertOrder(List result, String... names) { + Assert.assertEquals(result.size(), names.length); + for (int i=0 ; i getMappers() { + Set result = new HashSet<>(); + + UserFederationMapperModel mapperModel = KeycloakModelUtils.createUserFederationMapperModel("first name", "fed-provider", UserAttributeLDAPFederationMapperFactory.PROVIDER_ID, + UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.FIRST_NAME, + UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.GIVENNAME, + UserAttributeLDAPFederationMapper.READ_ONLY, "true", + UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, "true", + UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "true"); + mapperModel.setId("idd1"); + result.add(mapperModel); + + mapperModel = KeycloakModelUtils.createUserFederationMapperModel("username-cn", "fed-provider", UserAttributeLDAPFederationMapperFactory.PROVIDER_ID, + UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.USERNAME, + UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.CN, + UserAttributeLDAPFederationMapper.READ_ONLY, "true", + UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, "false", + UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "true"); + mapperModel.setId("idd2"); + result.add(mapperModel); + + mapperModel = KeycloakModelUtils.createUserFederationMapperModel("full name", "fed-provider", FullNameLDAPFederationMapperFactory.PROVIDER_ID, + FullNameLDAPFederationMapper.LDAP_FULL_NAME_ATTRIBUTE, LDAPConstants.CN, + UserAttributeLDAPFederationMapper.READ_ONLY, "true"); + mapperModel.setId("idd3"); + result.add(mapperModel); + + mapperModel = KeycloakModelUtils.createUserFederationMapperModel("sAMAccountName", "fed-provider", UserAttributeLDAPFederationMapperFactory.PROVIDER_ID, + UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.USERNAME, + UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.SAM_ACCOUNT_NAME, + UserAttributeLDAPFederationMapper.READ_ONLY, "false", + UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, "false", + UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "true"); + mapperModel.setId("idd4"); + result.add(mapperModel); + + return result; + } +}