diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/AbstractLDAPFederationMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/AbstractLDAPFederationMapperFactory.java index 33ace78075..75e77f3c3a 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/AbstractLDAPFederationMapperFactory.java +++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/AbstractLDAPFederationMapperFactory.java @@ -35,11 +35,6 @@ public abstract class AbstractLDAPFederationMapperFactory implements UserFederat public void postInit(KeycloakSessionFactory factory) { } - @Override - public List getConfigProperties() { - throw new IllegalStateException("Method not supported for this implementation"); - } - @Override public void close() { } diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/FullNameLDAPFederationMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/FullNameLDAPFederationMapperFactory.java index d0d623005f..e26cd66ecf 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/FullNameLDAPFederationMapperFactory.java +++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/FullNameLDAPFederationMapperFactory.java @@ -46,7 +46,7 @@ public class FullNameLDAPFederationMapperFactory extends AbstractLDAPFederationM } @Override - public List getConfigProperties(RealmModel realm) { + public List getConfigProperties() { return configProperties; } diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/RoleLDAPFederationMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/RoleLDAPFederationMapperFactory.java index a5eadd99a7..b02e2d3831 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/RoleLDAPFederationMapperFactory.java +++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/RoleLDAPFederationMapperFactory.java @@ -59,7 +59,10 @@ public class RoleLDAPFederationMapperFactory extends AbstractLDAPFederationMappe "If true, then LDAP role mappings will be mapped to realm role mappings in Keycloak. Otherwise it will be mapped to client role mappings", ProviderConfigProperty.BOOLEAN_TYPE, "true"); configProperties.add(useRealmRolesMappings); - // NOTE: ClientID will be computed dynamically from available clients + ProviderConfigProperty clientIdProperty = createConfigProperty(RoleLDAPFederationMapper.CLIENT_ID, "Client ID", + "Client ID of client to which LDAP role mappings will be mapped. Applicable just if 'Use Realm Roles Mapping' is false", + ProviderConfigProperty.CLIENT_LIST_TYPE, null); + configProperties.add(clientIdProperty); } @Override @@ -78,18 +81,8 @@ public class RoleLDAPFederationMapperFactory extends AbstractLDAPFederationMappe } @Override - public List getConfigProperties(RealmModel realm) { - List props = new ArrayList(configProperties); - - Map clients = realm.getClientNameMap(); - List clientIds = new ArrayList(clients.keySet()); - - ProviderConfigProperty clientIdProperty = createConfigProperty(RoleLDAPFederationMapper.CLIENT_ID, "Client ID", - "Client ID of client to which LDAP role mappings will be mapped. Applicable just if 'Use Realm Roles Mapping' is false", - ProviderConfigProperty.LIST_TYPE, clientIds); - props.add(clientIdProperty); - - return props; + public List getConfigProperties() { + return configProperties; } @Override diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserAttributeLDAPFederationMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserAttributeLDAPFederationMapperFactory.java index c0b9d79233..90dd21a8e4 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserAttributeLDAPFederationMapperFactory.java +++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserAttributeLDAPFederationMapperFactory.java @@ -48,7 +48,7 @@ public class UserAttributeLDAPFederationMapperFactory extends AbstractLDAPFedera } @Override - public List getConfigProperties(RealmModel realm) { + public List getConfigProperties() { return configProperties; } diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js index 7a0ae1a67e..cc3092fa1a 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js @@ -984,7 +984,10 @@ module.config([ '$routeProvider', function($routeProvider) { }, mapper : function(UserFederationMapperLoader) { return UserFederationMapperLoader(); - } + }, + clients : function(ClientListLoader) { + return ClientListLoader(); + }, }, controller : 'UserFederationMapperCtrl' }) @@ -1000,6 +1003,9 @@ module.config([ '$routeProvider', function($routeProvider) { mapperTypes : function(UserFederationMapperTypesLoader) { return UserFederationMapperTypesLoader(); }, + clients : function(ClientListLoader) { + return ClientListLoader(); + } }, controller : 'UserFederationMapperCreateCtrl' }) diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js index bc723f31e5..cee2689a77 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js @@ -754,10 +754,11 @@ module.controller('UserFederationMapperListCtrl', function($scope, $location, No }); -module.controller('UserFederationMapperCtrl', function($scope, realm, provider, mapperTypes, mapper, UserFederationMapper, Notifications, Dialog, $location) { +module.controller('UserFederationMapperCtrl', function($scope, realm, provider, mapperTypes, mapper, clients, UserFederationMapper, Notifications, Dialog, $location) { console.log('UserFederationMapperCtrl'); $scope.realm = realm; $scope.provider = provider; + $scope.clients = clients; $scope.create = false; $scope.mapper = angular.copy(mapper); $scope.changed = false; @@ -780,10 +781,10 @@ module.controller('UserFederationMapperCtrl', function($scope, realm, provider, $location.url("/realms/" + realm.realm + '/user-federation/providers/' + provider.providerName + '/' + provider.id + '/mappers/' + mapper.id); Notifications.success("Your changes have been saved."); }, function(error) { - if (error.status == 400) { + if (error.status == 400 && error.data.error_description) { Notifications.error('Error in configuration of mapper: ' + error.data.error_description); } else { - Notification.error('Unexpected error when creating mapper'); + Notifications.error('Unexpected error when creating mapper'); } }); }; @@ -808,10 +809,11 @@ module.controller('UserFederationMapperCtrl', function($scope, realm, provider, }); -module.controller('UserFederationMapperCreateCtrl', function($scope, realm, provider, mapperTypes, UserFederationMapper, Notifications, Dialog, $location) { +module.controller('UserFederationMapperCreateCtrl', function($scope, realm, provider, mapperTypes, clients, UserFederationMapper, Notifications, Dialog, $location) { console.log('UserFederationMapperCreateCtrl'); $scope.realm = realm; $scope.provider = provider; + $scope.clients = clients; $scope.create = true; $scope.mapper = { federationProviderDisplayName: provider.displayName, config: {}}; $scope.mapperTypes = mapperTypes; @@ -844,10 +846,10 @@ module.controller('UserFederationMapperCreateCtrl', function($scope, realm, prov $location.url('/realms/' + realm.realm +'/user-federation/providers/' + provider.providerName + '/' + provider.id + '/mappers/' + id); Notifications.success("Mapper has been created."); }, function(error) { - if (error.status == 400) { + if (error.status == 400 && error.data.error_description) { Notifications.error('Error in configuration of mapper: ' + error.data.error_description); } else { - Notification.error('Unexpected error when creating mapper'); + Notifications.error('Unexpected error when creating mapper'); } }); }; diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/federated-mapper-detail.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/federated-mapper-detail.html index 0b9c144380..f06d032257 100644 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/federated-mapper-detail.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/federated-mapper-detail.html @@ -47,7 +47,7 @@
-
+
@@ -58,6 +58,11 @@
+
+ +
{{option.helpText}}
diff --git a/model/api/src/main/java/org/keycloak/mappers/UserFederationMapperFactory.java b/model/api/src/main/java/org/keycloak/mappers/UserFederationMapperFactory.java index 309036cb89..a4c877683e 100644 --- a/model/api/src/main/java/org/keycloak/mappers/UserFederationMapperFactory.java +++ b/model/api/src/main/java/org/keycloak/mappers/UserFederationMapperFactory.java @@ -31,6 +31,4 @@ public interface UserFederationMapperFactory extends ProviderFactory getConfigProperties(RealmModel realm); } diff --git a/model/api/src/main/java/org/keycloak/models/LDAPConstants.java b/model/api/src/main/java/org/keycloak/models/LDAPConstants.java index 94e792d35c..8e5d567e45 100644 --- a/model/api/src/main/java/org/keycloak/models/LDAPConstants.java +++ b/model/api/src/main/java/org/keycloak/models/LDAPConstants.java @@ -71,6 +71,6 @@ public class LDAPConstants { public static final String CUSTOM_ATTRIBUTE_EXPIRY_DATE = "expiryDate"; public static final String ENTRY_UUID = "entryUUID"; public static final String OBJECT_GUID = "objectGUID"; - public static final String CREATE_TIMESTAMP = "createTimeStamp"; - public static final String MODIFY_TIMESTAMP = "modifyTimeStamp"; + public static final String CREATE_TIMESTAMP = "createTimestamp"; + public static final String MODIFY_TIMESTAMP = "modifyTimestamp"; } diff --git a/model/api/src/main/java/org/keycloak/provider/ProviderConfigProperty.java b/model/api/src/main/java/org/keycloak/provider/ProviderConfigProperty.java index e3a217d1c6..fad9d6cf43 100755 --- a/model/api/src/main/java/org/keycloak/provider/ProviderConfigProperty.java +++ b/model/api/src/main/java/org/keycloak/provider/ProviderConfigProperty.java @@ -8,6 +8,7 @@ public class ProviderConfigProperty { public static final String BOOLEAN_TYPE="boolean"; public static final String STRING_TYPE="String"; public static final String LIST_TYPE="List"; + public static final String CLIENT_LIST_TYPE="ClientList"; protected String name; protected String label; diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java index c899fdfe7b..6fe3837f29 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java @@ -118,6 +118,9 @@ public class ClientsResource { if (clientModel == null) { throw new NotFoundException("Could not find client: " + name); } + + session.getContext().setClient(clientModel); + ClientResource clientResource = new ClientResource(realm, auth, clientModel, session, adminEvent); ResteasyProviderFactory.getInstance().injectProperties(clientResource); return clientResource; diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java index e2899fe0ea..820fcfb39c 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java @@ -438,8 +438,8 @@ public class RealmAdminResource { /** * Query admin events. Returns all admin events, or will query based on URL query parameters listed here * - * @param client app or oauth client name - * @param operationTypes operation type + * @param authRealm + * @param authClient * @param authUser user id * @param authIpAddress * @param resourcePath diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmsAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmsAdminResource.java index fbe401c138..2d14b4bc30 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/RealmsAdminResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmsAdminResource.java @@ -227,7 +227,8 @@ public class RealmsAdminResource { } AdminEventBuilder adminEvent = new AdminEventBuilder(realm, auth, session, clientConnection); - + session.getContext().setRealm(realm); + RealmAdminResource adminResource = new RealmAdminResource(realmAuth, realm, tokenManager, adminEvent); ResteasyProviderFactory.getInstance().injectProperties(adminResource); //resourceContext.initResource(adminResource); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UserFederationProviderResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UserFederationProviderResource.java index e5deb3d382..b8216c4b41 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/UserFederationProviderResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/UserFederationProviderResource.java @@ -171,7 +171,7 @@ public class UserFederationProviderResource { rep.setCategory(mapperFactory.getDisplayCategory()); rep.setName(mapperFactory.getDisplayType()); rep.setHelpText(mapperFactory.getHelpText()); - List configProperties = mapperFactory.getConfigProperties(realm); + List configProperties = mapperFactory.getConfigProperties(); for (ProviderConfigProperty prop : configProperties) { ConfigPropertyRepresentation propRep = new ConfigPropertyRepresentation(); propRep.setName(prop.getName()); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationProvidersIntegrationTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationProvidersIntegrationTest.java index aa61ee6974..104d627915 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationProvidersIntegrationTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationProvidersIntegrationTest.java @@ -13,6 +13,10 @@ import org.keycloak.federation.ldap.LDAPFederationProvider; import org.keycloak.federation.ldap.LDAPFederationProviderFactory; import org.keycloak.federation.ldap.LDAPUtils; import org.keycloak.federation.ldap.idm.model.LDAPObject; +import org.keycloak.federation.ldap.mappers.FullNameLDAPFederationMapper; +import org.keycloak.federation.ldap.mappers.FullNameLDAPFederationMapperFactory; +import org.keycloak.federation.ldap.mappers.UserAttributeLDAPFederationMapper; +import org.keycloak.federation.ldap.mappers.UserAttributeLDAPFederationMapperFactory; import org.keycloak.models.ClientModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.LDAPConstants; @@ -20,9 +24,11 @@ import org.keycloak.models.ModelReadOnlyException; import org.keycloak.models.RealmModel; import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserCredentialValueModel; +import org.keycloak.models.UserFederationMapperModel; import org.keycloak.models.UserFederationProvider; import org.keycloak.models.UserFederationProviderModel; import org.keycloak.models.UserModel; +import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.services.managers.RealmManager; import org.keycloak.testsuite.OAuthClient; import org.keycloak.testsuite.pages.AccountPasswordPage; @@ -36,7 +42,9 @@ import org.keycloak.testsuite.rule.WebResource; import org.keycloak.testsuite.rule.WebRule; import org.openqa.selenium.WebDriver; +import java.util.List; import java.util.Map; +import java.util.Set; /** * @author Marek Posolda @@ -264,6 +272,55 @@ public class FederationProvidersIntegrationTest { } } + @Test + public void testFullNameMapper() { + KeycloakSession session = keycloakRule.startSession(); + UserFederationMapperModel firstNameMapper = null; + + try { + RealmModel appRealm = new RealmManager(session).getRealmByName("test"); + + // assert that user "fullnameUser" is not in local DB + Assert.assertNull(session.users().getUserByUsername("fullname", appRealm)); + + // Add the user with some fullName into LDAP directly. Ensure that fullName is saved into "cn" attribute in LDAP (currently mapped to model firstName) + LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel); + FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "fullname", "James Dee", "Dee", "fullname@email.org", "4578"); + + // add fullname mapper to the provider and remove "firstNameMapper" + UserFederationMapperModel fullNameMapperModel = KeycloakModelUtils.createUserFederationMapperModel("full name", ldapModel.getId(), FullNameLDAPFederationMapperFactory.PROVIDER_ID, + FullNameLDAPFederationMapper.LDAP_FULL_NAME_ATTRIBUTE, LDAPConstants.CN, + UserAttributeLDAPFederationMapper.READ_ONLY, "false"); + appRealm.addUserFederationMapper(fullNameMapperModel); + + firstNameMapper = appRealm.getUserFederationMapperByName(ldapModel.getId(), "first name"); + appRealm.removeUserFederationMapper(firstNameMapper); + + // Assert user is successfully imported in Keycloak DB now with correct firstName and lastName + FederationTestUtils.assertUserImported(session.users(), appRealm, "fullname", "James", "Dee", "fullname@email.org", "4578"); + } finally { + keycloakRule.stopSession(session, true); + } + + session = keycloakRule.startSession(); + try { + RealmModel appRealm = new RealmManager(session).getRealmByName("test"); + + // Remove "fullnameUser" to assert he is removed from LDAP. Revert mappers to previous state + UserModel fullnameUser = session.users().getUserByUsername("fullname", appRealm); + session.users().removeUser(appRealm, fullnameUser); + + // Revert mappers + UserFederationMapperModel fullNameMapperModel = appRealm.getUserFederationMapperByName(ldapModel.getId(), "full name"); + appRealm.removeUserFederationMapper(fullNameMapperModel); + + firstNameMapper.setId(null); + appRealm.addUserFederationMapper(firstNameMapper); + } finally { + keycloakRule.stopSession(session, true); + } + } + @Test public void testReadonly() { KeycloakSession session = keycloakRule.startSession(); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationTestUtils.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationTestUtils.java index d3dd9ab373..80bf539de8 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationTestUtils.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationTestUtils.java @@ -91,9 +91,13 @@ class FederationTestUtils { } public static void addZipCodeLDAPMapper(RealmModel realm, UserFederationProviderModel providerModel) { - UserFederationMapperModel mapperModel = KeycloakModelUtils.createUserFederationMapperModel("zipCodeMapper", providerModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID, - UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, "postal_code", - UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.POSTAL_CODE, + addUserAttributeMapper(realm, providerModel, "zipCodeMapper", "postal_code", LDAPConstants.POSTAL_CODE); + } + + public static void addUserAttributeMapper(RealmModel realm, UserFederationProviderModel providerModel, String mapperName, String userModelAttributeName, String ldapAttributeName) { + UserFederationMapperModel mapperModel = KeycloakModelUtils.createUserFederationMapperModel(mapperName, providerModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID, + UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, userModelAttributeName, + UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, ldapAttributeName, UserAttributeLDAPFederationMapper.READ_ONLY, "false"); realm.addUserFederationMapper(mapperModel); }