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