/* * 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.testsuite.federation.ldap.base; import org.junit.Assert; import org.junit.Before; import org.junit.ClassRule; import org.junit.FixMethodOrder; import org.junit.Test; import org.junit.rules.RuleChain; import org.junit.rules.TestRule; import org.junit.runners.MethodSorters; 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.membership.LDAPGroupMapperMode; import org.keycloak.federation.ldap.mappers.membership.MembershipType; import org.keycloak.federation.ldap.mappers.membership.group.GroupLDAPFederationMapper; import org.keycloak.federation.ldap.mappers.membership.group.GroupLDAPFederationMapperFactory; import org.keycloak.federation.ldap.mappers.membership.group.GroupMapperConfig; import org.keycloak.models.GroupModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.LDAPConstants; import org.keycloak.models.ModelException; import org.keycloak.models.RealmModel; import org.keycloak.models.UserFederationMapperModel; import org.keycloak.models.UserFederationProvider; import org.keycloak.models.UserFederationProviderModel; import org.keycloak.models.UserFederationSyncResult; import org.keycloak.models.UserModel; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.services.managers.RealmManager; import org.keycloak.testsuite.federation.ldap.FederationTestUtils; import org.keycloak.testsuite.rule.KeycloakRule; import org.keycloak.testsuite.rule.LDAPRule; import java.util.List; import java.util.Map; import java.util.Set; /** * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a> */ @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class LDAPGroupMapperSyncTest { private static LDAPRule ldapRule = new LDAPRule(); private static UserFederationProviderModel ldapModel = null; private static String descriptionAttrName = null; private static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() { @Override public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) { Map<String,String> ldapConfig = ldapRule.getConfig(); ldapConfig.put(LDAPConstants.SYNC_REGISTRATIONS, "true"); ldapConfig.put(LDAPConstants.EDIT_MODE, UserFederationProvider.EditMode.WRITABLE.toString()); ldapModel = appRealm.addUserFederationProvider(LDAPFederationProviderFactory.PROVIDER_NAME, ldapConfig, 0, "test-ldap", -1, -1, 0); LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel); descriptionAttrName = ldapFedProvider.getLdapIdentityStore().getConfig().isActiveDirectory() ? "displayName" : "description"; // Add group mapper FederationTestUtils.addOrUpdateGroupMapper(appRealm, ldapModel, LDAPGroupMapperMode.LDAP_ONLY, descriptionAttrName); // Remove all LDAP groups FederationTestUtils.removeAllLDAPGroups(session, appRealm, ldapModel, "groupsMapper"); // Add some groups for testing LDAPObject group1 = FederationTestUtils.createLDAPGroup(manager.getSession(), appRealm, ldapModel, "group1", descriptionAttrName, "group1 - description"); LDAPObject group11 = FederationTestUtils.createLDAPGroup(manager.getSession(), appRealm, ldapModel, "group11"); LDAPObject group12 = FederationTestUtils.createLDAPGroup(manager.getSession(), appRealm, ldapModel, "group12", descriptionAttrName, "group12 - description"); LDAPUtils.addMember(ldapFedProvider, MembershipType.DN, LDAPConstants.MEMBER, group1, group11, false); LDAPUtils.addMember(ldapFedProvider, MembershipType.DN, LDAPConstants.MEMBER, group1, group12, true); } }); @ClassRule public static TestRule chain = RuleChain .outerRule(ldapRule) .around(keycloakRule); @Before public void before() { KeycloakSession session = keycloakRule.startSession(); try { RealmModel realm = session.realms().getRealmByName("test"); List<GroupModel> kcGroups = realm.getTopLevelGroups(); for (GroupModel kcGroup : kcGroups) { realm.removeGroup(kcGroup); } } finally { keycloakRule.stopSession(session, true); } } @Test public void test01_syncNoPreserveGroupInheritance() throws Exception { KeycloakSession session = keycloakRule.startSession(); try { RealmModel realm = session.realms().getRealmByName("test"); UserFederationMapperModel mapperModel = realm.getUserFederationMapperByName(ldapModel.getId(), "groupsMapper"); LDAPFederationProvider ldapProvider = FederationTestUtils.getLdapProvider(session, ldapModel); GroupLDAPFederationMapper groupMapper = FederationTestUtils.getGroupMapper(mapperModel, ldapProvider, realm); // Add recursive group mapping to LDAP. Check that sync with preserve group inheritance will fail LDAPObject group1 = groupMapper.loadLDAPGroupByName("group1"); LDAPObject group12 = groupMapper.loadLDAPGroupByName("group12"); LDAPUtils.addMember(ldapProvider, MembershipType.DN, LDAPConstants.MEMBER, group12, group1, true); try { new GroupLDAPFederationMapperFactory().create(session).syncDataFromFederationProviderToKeycloak(mapperModel, ldapProvider, session, realm); Assert.fail("Not expected group sync to pass"); } catch (ModelException expected) { Assert.assertTrue(expected.getMessage().contains("Recursion detected")); } // Update group mapper to skip preserve inheritance and check it will pass now FederationTestUtils.updateGroupMapperConfigOptions(mapperModel, GroupMapperConfig.PRESERVE_GROUP_INHERITANCE, "false"); realm.updateUserFederationMapper(mapperModel); new GroupLDAPFederationMapperFactory().create(session).syncDataFromFederationProviderToKeycloak(mapperModel, ldapProvider, session, realm); // Assert groups are imported to keycloak. All are at top level GroupModel kcGroup1 = KeycloakModelUtils.findGroupByPath(realm, "/group1"); GroupModel kcGroup11 = KeycloakModelUtils.findGroupByPath(realm, "/group11"); GroupModel kcGroup12 = KeycloakModelUtils.findGroupByPath(realm, "/group12"); Assert.assertEquals(0, kcGroup1.getSubGroups().size()); Assert.assertEquals("group1 - description", kcGroup1.getFirstAttribute(descriptionAttrName)); Assert.assertNull(kcGroup11.getFirstAttribute(descriptionAttrName)); Assert.assertEquals("group12 - description", kcGroup12.getFirstAttribute(descriptionAttrName)); // Cleanup - remove recursive mapping in LDAP LDAPUtils.deleteMember(ldapProvider, MembershipType.DN, LDAPConstants.MEMBER, group12, group1, true); } finally { keycloakRule.stopSession(session, false); } } @Test public void test02_syncWithGroupInheritance() throws Exception { KeycloakSession session = keycloakRule.startSession(); try { RealmModel realm = session.realms().getRealmByName("test"); UserFederationMapperModel mapperModel = realm.getUserFederationMapperByName(ldapModel.getId(), "groupsMapper"); LDAPFederationProvider ldapProvider = FederationTestUtils.getLdapProvider(session, ldapModel); GroupLDAPFederationMapper groupMapper = FederationTestUtils.getGroupMapper(mapperModel, ldapProvider, realm); // Sync groups with inheritance UserFederationSyncResult syncResult = new GroupLDAPFederationMapperFactory().create(session).syncDataFromFederationProviderToKeycloak(mapperModel, ldapProvider, session, realm); FederationTestUtils.assertSyncEquals(syncResult, 3, 0, 0, 0); // Assert groups are imported to keycloak including their inheritance from LDAP GroupModel kcGroup1 = KeycloakModelUtils.findGroupByPath(realm, "/group1"); Assert.assertNull(KeycloakModelUtils.findGroupByPath(realm, "/group11")); Assert.assertNull(KeycloakModelUtils.findGroupByPath(realm, "/group12")); GroupModel kcGroup11 = KeycloakModelUtils.findGroupByPath(realm, "/group1/group11"); GroupModel kcGroup12 = KeycloakModelUtils.findGroupByPath(realm, "/group1/group12"); Assert.assertEquals(2, kcGroup1.getSubGroups().size()); Assert.assertEquals("group1 - description", kcGroup1.getFirstAttribute(descriptionAttrName)); Assert.assertNull(kcGroup11.getFirstAttribute(descriptionAttrName)); Assert.assertEquals("group12 - description", kcGroup12.getFirstAttribute(descriptionAttrName)); // Update description attributes in LDAP LDAPObject group1 = groupMapper.loadLDAPGroupByName("group1"); group1.setSingleAttribute(descriptionAttrName, "group1 - changed description"); ldapProvider.getLdapIdentityStore().update(group1); LDAPObject group12 = groupMapper.loadLDAPGroupByName("group12"); group12.setAttribute(descriptionAttrName, null); ldapProvider.getLdapIdentityStore().update(group12); // Sync and assert groups updated syncResult = new GroupLDAPFederationMapperFactory().create(session).syncDataFromFederationProviderToKeycloak(mapperModel, ldapProvider, session, realm); FederationTestUtils.assertSyncEquals(syncResult, 0, 3, 0, 0); // Assert attributes changed in keycloak kcGroup1 = KeycloakModelUtils.findGroupByPath(realm, "/group1"); kcGroup12 = KeycloakModelUtils.findGroupByPath(realm, "/group1/group12"); Assert.assertEquals("group1 - changed description", kcGroup1.getFirstAttribute(descriptionAttrName)); Assert.assertNull(kcGroup12.getFirstAttribute(descriptionAttrName)); } finally { keycloakRule.stopSession(session, false); } } @Test public void test03_syncWithDropNonExistingGroups() throws Exception { KeycloakSession session = keycloakRule.startSession(); try { RealmModel realm = session.realms().getRealmByName("test"); UserFederationMapperModel mapperModel = realm.getUserFederationMapperByName(ldapModel.getId(), "groupsMapper"); LDAPFederationProvider ldapProvider = FederationTestUtils.getLdapProvider(session, ldapModel); // Sync groups with inheritance UserFederationSyncResult syncResult = new GroupLDAPFederationMapperFactory().create(session).syncDataFromFederationProviderToKeycloak(mapperModel, ldapProvider, session, realm); FederationTestUtils.assertSyncEquals(syncResult, 3, 0, 0, 0); // Assert groups are imported to keycloak including their inheritance from LDAP GroupModel kcGroup1 = KeycloakModelUtils.findGroupByPath(realm, "/group1"); Assert.assertNotNull(KeycloakModelUtils.findGroupByPath(realm, "/group1/group11")); Assert.assertNotNull(KeycloakModelUtils.findGroupByPath(realm, "/group1/group12")); Assert.assertEquals(2, kcGroup1.getSubGroups().size()); // Create some new groups in keycloak GroupModel model1 = realm.createGroup("model1"); realm.moveGroup(model1, null); GroupModel model2 = realm.createGroup("model2"); kcGroup1.addChild(model2); // Sync groups again from LDAP. Nothing deleted syncResult = new GroupLDAPFederationMapperFactory().create(session).syncDataFromFederationProviderToKeycloak(mapperModel, ldapProvider, session, realm); FederationTestUtils.assertSyncEquals(syncResult, 0, 3, 0, 0); Assert.assertNotNull(KeycloakModelUtils.findGroupByPath(realm, "/group1/group11")); Assert.assertNotNull(KeycloakModelUtils.findGroupByPath(realm, "/group1/group12")); Assert.assertNotNull(KeycloakModelUtils.findGroupByPath(realm, "/model1")); Assert.assertNotNull(KeycloakModelUtils.findGroupByPath(realm, "/group1/model2")); // Update group mapper to drop non-existing groups during sync FederationTestUtils.updateGroupMapperConfigOptions(mapperModel, GroupMapperConfig.DROP_NON_EXISTING_GROUPS_DURING_SYNC, "true"); realm.updateUserFederationMapper(mapperModel); // Sync groups again from LDAP. Assert LDAP non-existing groups deleted syncResult = new GroupLDAPFederationMapperFactory().create(session).syncDataFromFederationProviderToKeycloak(mapperModel, ldapProvider, session, realm); Assert.assertEquals(3, syncResult.getUpdated()); Assert.assertTrue(syncResult.getRemoved() == 2); // Sync and assert groups updated Assert.assertNotNull(KeycloakModelUtils.findGroupByPath(realm, "/group1/group11")); Assert.assertNotNull(KeycloakModelUtils.findGroupByPath(realm, "/group1/group12")); Assert.assertNull(KeycloakModelUtils.findGroupByPath(realm, "/model1")); Assert.assertNull(KeycloakModelUtils.findGroupByPath(realm, "/group1/model2")); } finally { keycloakRule.stopSession(session, false); } } @Test public void test04_syncNoPreserveGroupInheritanceWithLazySync() throws Exception { KeycloakSession session = keycloakRule.startSession(); try { RealmModel realm = session.realms().getRealmByName("test"); UserFederationMapperModel mapperModel = realm.getUserFederationMapperByName(ldapModel.getId(), "groupsMapper"); LDAPFederationProvider ldapProvider = FederationTestUtils.getLdapProvider(session, ldapModel); GroupLDAPFederationMapper groupMapper = FederationTestUtils.getGroupMapper(mapperModel, ldapProvider, realm); // Update group mapper to skip preserve inheritance FederationTestUtils.updateGroupMapperConfigOptions(mapperModel, GroupMapperConfig.PRESERVE_GROUP_INHERITANCE, "false"); realm.updateUserFederationMapper(mapperModel); // Add user to LDAP and put him as member of group11 FederationTestUtils.removeAllLDAPUsers(ldapProvider, realm); LDAPObject johnLdap = FederationTestUtils.addLDAPUser(ldapProvider, realm, "johnkeycloak", "John", "Doe", "john@email.org", null, "1234"); FederationTestUtils.updateLDAPPassword(ldapProvider, johnLdap, "Password1"); groupMapper.addGroupMappingInLDAP("group11", johnLdap); // Assert groups not yet imported to Keycloak DB Assert.assertNull(KeycloakModelUtils.findGroupByPath(realm, "/group1")); Assert.assertNull(KeycloakModelUtils.findGroupByPath(realm, "/group11")); Assert.assertNull(KeycloakModelUtils.findGroupByPath(realm, "/group12")); // Load user from LDAP to Keycloak DB UserModel john = session.users().getUserByUsername("johnkeycloak", realm); Set<GroupModel> johnGroups = john.getGroups(); // Assert just those groups, which john was memberOf exists because they were lazily created GroupModel group1 = KeycloakModelUtils.findGroupByPath(realm, "/group1"); GroupModel group11 = KeycloakModelUtils.findGroupByPath(realm, "/group11"); GroupModel group12 = KeycloakModelUtils.findGroupByPath(realm, "/group12"); Assert.assertNull(group1); Assert.assertNotNull(group11); Assert.assertNull(group12); Assert.assertEquals(1, johnGroups.size()); Assert.assertTrue(johnGroups.contains(group11)); // Delete group mapping john.leaveGroup(group11); } finally { keycloakRule.stopSession(session, false); } } }