diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/HardcodedLDAPGroupStorageMapper.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/HardcodedLDAPGroupStorageMapper.java new file mode 100644 index 0000000000..0768a19351 --- /dev/null +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/HardcodedLDAPGroupStorageMapper.java @@ -0,0 +1,98 @@ +/* + * 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.storage.ldap.mappers; + +import org.jboss.logging.Logger; +import org.keycloak.component.ComponentModel; +import org.keycloak.models.*; +import org.keycloak.models.utils.KeycloakModelUtils; +import org.keycloak.models.utils.UserModelDelegate; +import org.keycloak.storage.ldap.LDAPStorageProvider; +import org.keycloak.storage.ldap.idm.model.LDAPObject; +import org.keycloak.storage.ldap.idm.query.internal.LDAPQuery; + +import java.util.Set; + +/** + * @author Jean-Loup Maillet + */ +public class HardcodedLDAPGroupStorageMapper extends AbstractLDAPStorageMapper { + + private static final Logger logger = Logger.getLogger(HardcodedLDAPGroupStorageMapper.class); + + public static final String GROUP = "group"; + + public HardcodedLDAPGroupStorageMapper(ComponentModel mapperModel, LDAPStorageProvider ldapProvider) { + super(mapperModel, ldapProvider); + } + + @Override + public void beforeLDAPQuery(LDAPQuery query) { + } + + @Override + public UserModel proxy(LDAPObject ldapUser, UserModel delegate, RealmModel realm) { + return new UserModelDelegate(delegate) { + + @Override + public Set getGroups() { + Set groups = super.getGroups(); + + GroupModel group = getGroup(realm); + if (group != null) { + groups.add(group); + } + + return groups; + } + + @Override + public boolean isMemberOf(GroupModel group) { + return super.isMemberOf(group) || group.equals(getGroup(realm)); + } + + @Override + public void leaveGroup(GroupModel group) { + if (group.equals(getGroup(realm))) { + throw new ModelException("Not possible to delete group. It's hardcoded by LDAP mapper"); + } else { + super.leaveGroup(group); + } + } + }; + } + + @Override + public void onRegisterUserToLDAP(LDAPObject ldapUser, UserModel localUser, RealmModel realm) { + + } + + @Override + public void onImportUserFromLDAP(LDAPObject ldapUser, UserModel user, RealmModel realm, boolean isCreate) { + + } + + private GroupModel getGroup(RealmModel realm) { + String groupName = mapperModel.getConfig().getFirst(HardcodedLDAPGroupStorageMapper.GROUP); + GroupModel group = KeycloakModelUtils.findGroupByPath(realm, groupName); + if (group == null) { + logger.warnf("Hardcoded group '%s' configured in mapper '%s' is not available anymore"); + } + return group; + } +} diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/HardcodedLDAPGroupStorageMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/HardcodedLDAPGroupStorageMapperFactory.java new file mode 100644 index 0000000000..4abb27dea8 --- /dev/null +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/HardcodedLDAPGroupStorageMapperFactory.java @@ -0,0 +1,79 @@ +/* + * 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.storage.ldap.mappers; + +import org.keycloak.component.ComponentModel; +import org.keycloak.component.ComponentValidationException; +import org.keycloak.models.GroupModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.utils.KeycloakModelUtils; +import org.keycloak.provider.ProviderConfigProperty; +import org.keycloak.storage.ldap.LDAPStorageProvider; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Jean-Loup Maillet + */ +public class HardcodedLDAPGroupStorageMapperFactory extends AbstractLDAPStorageMapperFactory { + + public static final String PROVIDER_ID = "hardcoded-ldap-group-mapper"; + protected static final List configProperties = new ArrayList(); + + static { + ProviderConfigProperty groupAttr = createConfigProperty(HardcodedLDAPGroupStorageMapper.GROUP, "Group", + "Group to add the user in. Click 'Select Group' button to browse groups, or just type it in the textbox.", + ProviderConfigProperty.STRING_TYPE, null); + + configProperties.add(groupAttr); + } + + @Override + public String getHelpText() { + return "When user is imported from LDAP, he will be automatically added into this configured group."; + } + + @Override + public List getConfigProperties() { + return configProperties; + } + + @Override + public String getId() { + return PROVIDER_ID; + } + + @Override + public void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel config) throws ComponentValidationException { + String groupName = config.getConfig().getFirst(HardcodedLDAPGroupStorageMapper.GROUP); + if (groupName == null) { + throw new ComponentValidationException("Group can't be null"); + } + GroupModel group = KeycloakModelUtils.findGroupByPath(realm, groupName); + if (group == null) { + throw new ComponentValidationException("There is no group corresponding to configured value"); + } + } + + @Override + protected AbstractLDAPStorageMapper createMapper(ComponentModel mapperModel, LDAPStorageProvider federationProvider) { + return new HardcodedLDAPGroupStorageMapper(mapperModel, federationProvider); + } +} diff --git a/federation/ldap/src/main/resources/META-INF/services/org.keycloak.storage.ldap.mappers.LDAPStorageMapperFactory b/federation/ldap/src/main/resources/META-INF/services/org.keycloak.storage.ldap.mappers.LDAPStorageMapperFactory index e43fdbe4ca..12fd30b378 100644 --- a/federation/ldap/src/main/resources/META-INF/services/org.keycloak.storage.ldap.mappers.LDAPStorageMapperFactory +++ b/federation/ldap/src/main/resources/META-INF/services/org.keycloak.storage.ldap.mappers.LDAPStorageMapperFactory @@ -17,6 +17,7 @@ org.keycloak.storage.ldap.mappers.FullNameLDAPStorageMapperFactory org.keycloak.storage.ldap.mappers.HardcodedLDAPRoleStorageMapperFactory +org.keycloak.storage.ldap.mappers.HardcodedLDAPGroupStorageMapperFactory org.keycloak.storage.ldap.mappers.HardcodedLDAPAttributeMapperFactory org.keycloak.storage.ldap.mappers.membership.group.GroupLDAPStorageMapperFactory org.keycloak.storage.ldap.mappers.membership.role.RoleLDAPStorageMapperFactory diff --git a/testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPProvidersIntegrationTest.java b/testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPProvidersIntegrationTest.java index 80a3d3fd0c..075b9d2a71 100755 --- a/testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPProvidersIntegrationTest.java +++ b/testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPProvidersIntegrationTest.java @@ -42,14 +42,7 @@ import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.common.util.Time; import org.keycloak.component.ComponentModel; import org.keycloak.credential.CredentialModel; -import org.keycloak.models.Constants; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.LDAPConstants; -import org.keycloak.models.ModelException; -import org.keycloak.models.RealmModel; -import org.keycloak.models.RoleModel; -import org.keycloak.models.UserCredentialModel; -import org.keycloak.models.UserModel; +import org.keycloak.models.*; import org.keycloak.models.cache.CachedUserModel; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.representations.AccessToken; @@ -61,14 +54,7 @@ import org.keycloak.storage.ldap.LDAPConfig; import org.keycloak.storage.ldap.LDAPStorageProvider; import org.keycloak.storage.ldap.LDAPStorageProviderFactory; import org.keycloak.storage.ldap.idm.model.LDAPObject; -import org.keycloak.storage.ldap.mappers.FullNameLDAPStorageMapper; -import org.keycloak.storage.ldap.mappers.FullNameLDAPStorageMapperFactory; -import org.keycloak.storage.ldap.mappers.HardcodedLDAPAttributeMapper; -import org.keycloak.storage.ldap.mappers.HardcodedLDAPAttributeMapperFactory; -import org.keycloak.storage.ldap.mappers.HardcodedLDAPRoleStorageMapper; -import org.keycloak.storage.ldap.mappers.HardcodedLDAPRoleStorageMapperFactory; -import org.keycloak.storage.ldap.mappers.LDAPStorageMapper; -import org.keycloak.storage.ldap.mappers.UserAttributeLDAPStorageMapper; +import org.keycloak.storage.ldap.mappers.*; import org.keycloak.testsuite.OAuthClient; import org.keycloak.testsuite.pages.AccountPasswordPage; import org.keycloak.testsuite.pages.AccountUpdateProfilePage; @@ -870,6 +856,50 @@ public class LDAPProvidersIntegrationTest { } } + @Test + public void testHardcodedGroupMapper() { + KeycloakSession session = keycloakRule.startSession(); + ComponentModel firstNameMapper = null; + + try { + RealmModel appRealm = new RealmManager(session).getRealmByName("test"); + GroupModel hardcodedGroup = appRealm.createGroup("hardcoded-group", "hardcoded-group"); + + // assert that user "johnkeycloak" doesn't have hardcoded group + UserModel john = session.users().getUserByUsername("johnkeycloak", appRealm); + Assert.assertFalse(john.isMemberOf(hardcodedGroup)); + + ComponentModel hardcodedMapperModel = KeycloakModelUtils.createComponentModel("hardcoded group", ldapModel.getId(), HardcodedLDAPGroupStorageMapperFactory.PROVIDER_ID, LDAPStorageMapper.class.getName(), + HardcodedLDAPGroupStorageMapper.GROUP, "hardcoded-group"); + appRealm.addComponentModel(hardcodedMapperModel); + } finally { + keycloakRule.stopSession(session, true); + } + + session = keycloakRule.startSession(); + try { + RealmModel appRealm = new RealmManager(session).getRealmByName("test"); + GroupModel hardcodedGroup = appRealm.getGroupById("hardcoded-group"); + + // Assert user is successfully imported in Keycloak DB now with correct firstName and lastName + UserModel john = session.users().getUserByUsername("johnkeycloak", appRealm); + Assert.assertTrue(john.isMemberOf(hardcodedGroup)); + + // Can't remove user from hardcoded role + try { + john.leaveGroup(hardcodedGroup); + Assert.fail("Didn't expected to leave group"); + } catch (ModelException expected) { + } + + // Revert mappers + ComponentModel hardcodedMapperModel = LDAPTestUtils.getSubcomponentByName(appRealm, ldapModel, "hardcoded group"); + appRealm.removeComponent(hardcodedMapperModel); + } finally { + keycloakRule.stopSession(session, true); + } + } + @Test public void testImportExistingUserFromLDAP() throws Exception { // Add LDAP user with same email like existing model user diff --git a/testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/federation/storage/ldap/noimport/LDAPProvidersIntegrationNoImportTest.java b/testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/federation/storage/ldap/noimport/LDAPProvidersIntegrationNoImportTest.java index 2878c7cdba..11c205a69f 100755 --- a/testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/federation/storage/ldap/noimport/LDAPProvidersIntegrationNoImportTest.java +++ b/testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/federation/storage/ldap/noimport/LDAPProvidersIntegrationNoImportTest.java @@ -32,14 +32,7 @@ import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.component.ComponentModel; import org.keycloak.credential.CredentialModel; -import org.keycloak.models.Constants; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.LDAPConstants; -import org.keycloak.models.ModelException; -import org.keycloak.models.RealmModel; -import org.keycloak.models.RoleModel; -import org.keycloak.models.UserCredentialModel; -import org.keycloak.models.UserModel; +import org.keycloak.models.*; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.representations.AccessToken; import org.keycloak.representations.idm.CredentialRepresentation; @@ -53,12 +46,7 @@ import org.keycloak.storage.ldap.LDAPConfig; import org.keycloak.storage.ldap.LDAPStorageProvider; import org.keycloak.storage.ldap.LDAPStorageProviderFactory; import org.keycloak.storage.ldap.idm.model.LDAPObject; -import org.keycloak.storage.ldap.mappers.FullNameLDAPStorageMapper; -import org.keycloak.storage.ldap.mappers.FullNameLDAPStorageMapperFactory; -import org.keycloak.storage.ldap.mappers.HardcodedLDAPRoleStorageMapper; -import org.keycloak.storage.ldap.mappers.HardcodedLDAPRoleStorageMapperFactory; -import org.keycloak.storage.ldap.mappers.LDAPStorageMapper; -import org.keycloak.storage.ldap.mappers.UserAttributeLDAPStorageMapper; +import org.keycloak.storage.ldap.mappers.*; import org.keycloak.testsuite.ApiUtil; import org.keycloak.testsuite.OAuthClient; import org.keycloak.testsuite.federation.storage.ldap.LDAPTestUtils; @@ -712,6 +700,50 @@ public class LDAPProvidersIntegrationNoImportTest { } } + @Test + public void testHardcodedGroupMapper() { + KeycloakSession session = keycloakRule.startSession(); + ComponentModel firstNameMapper = null; + + try { + RealmModel appRealm = new RealmManager(session).getRealmByName("test"); + GroupModel hardcodedGroup = appRealm.createGroup("hardcoded-group", "hardcoded-group"); + + // assert that user "johnkeycloak" doesn't have hardcoded group + UserModel john = session.users().getUserByUsername("johnkeycloak", appRealm); + Assert.assertFalse(john.isMemberOf(hardcodedGroup)); + + ComponentModel hardcodedMapperModel = KeycloakModelUtils.createComponentModel("hardcoded group", ldapModel.getId(), HardcodedLDAPGroupStorageMapperFactory.PROVIDER_ID, LDAPStorageMapper.class.getName(), + HardcodedLDAPGroupStorageMapper.GROUP, "hardcoded-group"); + appRealm.addComponentModel(hardcodedMapperModel); + } finally { + keycloakRule.stopSession(session, true); + } + + session = keycloakRule.startSession(); + try { + RealmModel appRealm = new RealmManager(session).getRealmByName("test"); + GroupModel hardcodedGroup = appRealm.getGroupById("hardcoded-group"); + + // Assert user is successfully imported in Keycloak DB now with correct firstName and lastName + UserModel john = session.users().getUserByUsername("johnkeycloak", appRealm); + Assert.assertTrue(john.isMemberOf(hardcodedGroup)); + + // Can't remove user from hardcoded role + try { + john.leaveGroup(hardcodedGroup); + Assert.fail("Didn't expected to leave group"); + } catch (ModelException expected) { + } + + // Revert mappers + ComponentModel hardcodedMapperModel = LDAPTestUtils.getSubcomponentByName(appRealm, ldapModel, "hardcoded group"); + appRealm.removeComponent(hardcodedMapperModel); + } finally { + keycloakRule.stopSession(session, true); + } + } + @Test public void testImportExistingUserFromLDAP() throws Exception { // Add LDAP user with same email like existing model user