/* * 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.storage.ldap; import org.junit.Assert; import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.component.ComponentModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.LDAPConstants; import org.keycloak.models.RealmModel; import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserModel; import org.keycloak.models.UserProvider; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.UserModelDelegate; import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.representations.idm.SynchronizationResultRepresentation; import org.keycloak.storage.UserStorageProvider; import org.keycloak.storage.ldap.LDAPStorageProvider; import org.keycloak.storage.ldap.LDAPConfig; import org.keycloak.storage.ldap.LDAPUtils; import org.keycloak.storage.ldap.idm.model.LDAPObject; import org.keycloak.storage.ldap.idm.query.internal.LDAPQuery; import org.keycloak.storage.ldap.idm.store.ldap.LDAPIdentityStore; import org.keycloak.storage.ldap.mappers.LDAPStorageMapper; import org.keycloak.storage.ldap.mappers.UserAttributeLDAPStorageMapper; import org.keycloak.storage.ldap.mappers.UserAttributeLDAPStorageMapperFactory; import org.keycloak.storage.ldap.mappers.membership.LDAPGroupMapperMode; import org.keycloak.storage.ldap.mappers.membership.group.GroupLDAPStorageMapper; import org.keycloak.storage.ldap.mappers.membership.group.GroupLDAPStorageMapperFactory; import org.keycloak.storage.ldap.mappers.membership.group.GroupMapperConfig; import org.keycloak.storage.ldap.mappers.membership.role.RoleLDAPStorageMapper; import org.keycloak.storage.ldap.mappers.membership.role.RoleLDAPStorageMapperFactory; import org.keycloak.storage.ldap.mappers.membership.role.RoleMapperConfig; import org.keycloak.storage.user.SynchronizationResult; import org.keycloak.testsuite.rule.LDAPRule; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; /** * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a> */ public class LDAPTestUtils { public static MultivaluedHashMap<String, String> getLdapRuleConfig(LDAPRule ldapRule) { Map<String,String> ldapConfig = ldapRule.getConfig(); return toComponentConfig(ldapConfig); } public static MultivaluedHashMap<String, String> toComponentConfig(Map<String, String> ldapConfig) { MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>(); for (Map.Entry<String, String> entry : ldapConfig.entrySet()) { config.add(entry.getKey(), entry.getValue()); } return config; } public static UserModel addLocalUser(KeycloakSession session, RealmModel realm, String username, String email, String password) { UserModel user = session.userLocalStorage().addUser(realm, username); user.setEmail(email); user.setEnabled(true); UserCredentialModel creds = new UserCredentialModel(); creds.setType(CredentialRepresentation.PASSWORD); creds.setValue(password); session.userCredentialManager().updateCredential(realm, user, creds); return user; } public static LDAPObject addLDAPUser(LDAPStorageProvider ldapProvider, RealmModel realm, final String username, final String firstName, final String lastName, final String email, final String street, final String... postalCode) { UserModel helperUser = new UserModelDelegate(null) { @Override public String getUsername() { return username; } @Override public String getFirstName() { return firstName; } @Override public String getLastName() { return lastName; } @Override public String getEmail() { return email; } @Override public List<String> getAttribute(String name) { if ("postal_code".equals(name) && postalCode != null && postalCode.length > 0) { return Arrays.asList(postalCode); } else if ("street".equals(name) && street != null) { return Collections.singletonList(street); } else { return Collections.emptyList(); } } }; return LDAPUtils.addUserToLDAP(ldapProvider, realm, helperUser); } public static void updateLDAPPassword(LDAPStorageProvider ldapProvider, LDAPObject ldapUser, String password) { ldapProvider.getLdapIdentityStore().updatePassword(ldapUser, password, null); // Enable MSAD user through userAccountControls if (ldapProvider.getLdapIdentityStore().getConfig().isActiveDirectory()) { ldapUser.setSingleAttribute(LDAPConstants.USER_ACCOUNT_CONTROL, "512"); ldapProvider.getLdapIdentityStore().update(ldapUser); } } public static LDAPStorageProvider getLdapProvider(KeycloakSession keycloakSession, ComponentModel ldapFedModel) { return (LDAPStorageProvider)keycloakSession.getProvider(UserStorageProvider.class, ldapFedModel); } public static UserModel assertUserImported(UserProvider userProvider, RealmModel realm, String username, String expectedFirstName, String expectedLastName, String expectedEmail, String expectedPostalCode) { UserModel user = userProvider.getUserByUsername(username, realm); Assert.assertNotNull(user); Assert.assertEquals(expectedFirstName, user.getFirstName()); Assert.assertEquals(expectedLastName, user.getLastName()); Assert.assertEquals(expectedEmail, user.getEmail()); Assert.assertEquals(expectedPostalCode, user.getFirstAttribute("postal_code")); return user; } public static void assertLoaded(UserModel user, String username, String expectedFirstName, String expectedLastName, String expectedEmail, String expectedPostalCode) { Assert.assertNotNull(user); Assert.assertEquals(expectedFirstName, user.getFirstName()); Assert.assertEquals(expectedLastName, user.getLastName()); Assert.assertEquals(expectedEmail, user.getEmail()); Assert.assertEquals(expectedPostalCode, user.getFirstAttribute("postal_code")); } // CRUD model mappers public static void addZipCodeLDAPMapper(RealmModel realm, ComponentModel providerModel) { addUserAttributeMapper(realm, providerModel, "zipCodeMapper", "postal_code", LDAPConstants.POSTAL_CODE); } public static ComponentModel addUserAttributeMapper(RealmModel realm, ComponentModel providerModel, String mapperName, String userModelAttributeName, String ldapAttributeName) { ComponentModel mapperModel = KeycloakModelUtils.createComponentModel(mapperName, providerModel.getId(), UserAttributeLDAPStorageMapperFactory.PROVIDER_ID, LDAPStorageMapper.class.getName(), UserAttributeLDAPStorageMapper.USER_MODEL_ATTRIBUTE, userModelAttributeName, UserAttributeLDAPStorageMapper.LDAP_ATTRIBUTE, ldapAttributeName, UserAttributeLDAPStorageMapper.READ_ONLY, "false", UserAttributeLDAPStorageMapper.ALWAYS_READ_VALUE_FROM_LDAP, "false", UserAttributeLDAPStorageMapper.IS_MANDATORY_IN_LDAP, "false"); return realm.addComponentModel(mapperModel); } public static void addOrUpdateRoleLDAPMappers(RealmModel realm, ComponentModel providerModel, LDAPGroupMapperMode mode) { ComponentModel mapperModel = getSubcomponentByName(realm, providerModel, "realmRolesMapper"); if (mapperModel != null) { mapperModel.getConfig().putSingle(RoleMapperConfig.MODE, mode.toString()); realm.updateComponent(mapperModel); } else { String baseDn = providerModel.getConfig().getFirst(LDAPConstants.BASE_DN); mapperModel = KeycloakModelUtils.createComponentModel("realmRolesMapper", providerModel.getId(), RoleLDAPStorageMapperFactory.PROVIDER_ID, LDAPStorageMapper.class.getName(), RoleMapperConfig.ROLES_DN, "ou=RealmRoles," + baseDn, RoleMapperConfig.USE_REALM_ROLES_MAPPING, "true", RoleMapperConfig.MODE, mode.toString()); realm.addComponentModel(mapperModel); } mapperModel = getSubcomponentByName(realm, providerModel, "financeRolesMapper"); if (mapperModel != null) { mapperModel.getConfig().putSingle(RoleMapperConfig.MODE, mode.toString()); realm.updateComponent(mapperModel); } else { String baseDn = providerModel.getConfig().getFirst(LDAPConstants.BASE_DN); mapperModel = KeycloakModelUtils.createComponentModel("financeRolesMapper", providerModel.getId(), RoleLDAPStorageMapperFactory.PROVIDER_ID, LDAPStorageMapper.class.getName(), RoleMapperConfig.ROLES_DN, "ou=FinanceRoles," + baseDn, RoleMapperConfig.USE_REALM_ROLES_MAPPING, "false", RoleMapperConfig.CLIENT_ID, "finance", RoleMapperConfig.MODE, mode.toString()); realm.addComponentModel(mapperModel); } } public static ComponentModel getSubcomponentByName(RealmModel realm, ComponentModel providerModel, String name) { List<ComponentModel> components = realm.getComponents(providerModel.getId(), LDAPStorageMapper.class.getName()); for (ComponentModel component : components) { if (component.getName().equals(name)) { return component; } } return null; } public static void addOrUpdateGroupMapper(RealmModel realm, ComponentModel providerModel, LDAPGroupMapperMode mode, String descriptionAttrName, String... otherConfigOptions) { ComponentModel mapperModel = getSubcomponentByName(realm, providerModel, "groupsMapper"); if (mapperModel != null) { mapperModel.getConfig().putSingle(GroupMapperConfig.MODE, mode.toString()); updateGroupMapperConfigOptions(mapperModel, otherConfigOptions); realm.updateComponent(mapperModel); } else { String baseDn = providerModel.getConfig().getFirst(LDAPConstants.BASE_DN); mapperModel = KeycloakModelUtils.createComponentModel("groupsMapper", providerModel.getId(), GroupLDAPStorageMapperFactory.PROVIDER_ID, LDAPStorageMapper.class.getName(), GroupMapperConfig.GROUPS_DN, "ou=Groups," + baseDn, GroupMapperConfig.MAPPED_GROUP_ATTRIBUTES, descriptionAttrName, GroupMapperConfig.PRESERVE_GROUP_INHERITANCE, "true", GroupMapperConfig.MODE, mode.toString()); updateGroupMapperConfigOptions(mapperModel, otherConfigOptions); realm.addComponentModel(mapperModel); } } public static void updateGroupMapperConfigOptions(ComponentModel mapperModel, String... configOptions) { for (int i=0 ; i<configOptions.length ; i+=2) { String cfgName = configOptions[i]; String cfgValue = configOptions[i+1]; mapperModel.getConfig().putSingle(cfgName, cfgValue); } } // End CRUD model mappers public static void syncRolesFromLDAP(RealmModel realm, LDAPStorageProvider ldapProvider, ComponentModel providerModel) { ComponentModel mapperModel = getSubcomponentByName(realm, providerModel, "realmRolesMapper"); RoleLDAPStorageMapper roleMapper = getRoleMapper(mapperModel, ldapProvider, realm); roleMapper.syncDataFromFederationProviderToKeycloak(realm); mapperModel = getSubcomponentByName(realm, providerModel, "financeRolesMapper"); roleMapper = getRoleMapper(mapperModel, ldapProvider, realm); roleMapper.syncDataFromFederationProviderToKeycloak(realm); } public static void removeAllLDAPUsers(LDAPStorageProvider ldapProvider, RealmModel realm) { LDAPIdentityStore ldapStore = ldapProvider.getLdapIdentityStore(); LDAPQuery ldapQuery = LDAPUtils.createQueryForUserSearch(ldapProvider, realm); List<LDAPObject> allUsers = ldapQuery.getResultList(); for (LDAPObject ldapUser : allUsers) { ldapStore.remove(ldapUser); } } public static void removeLDAPUserByUsername(LDAPStorageProvider ldapProvider, RealmModel realm, LDAPConfig config, String username) { LDAPIdentityStore ldapStore = ldapProvider.getLdapIdentityStore(); LDAPQuery ldapQuery = LDAPUtils.createQueryForUserSearch(ldapProvider, realm); List<LDAPObject> allUsers = ldapQuery.getResultList(); // This is ugly, we are iterating over the entire set of ldap users and deleting the one where the username matches. TODO: Find a better way! for (LDAPObject ldapUser : allUsers) { if (username.equals(LDAPUtils.getUsername(ldapUser, config))) { ldapStore.remove(ldapUser); } } } public static void removeAllLDAPRoles(KeycloakSession session, RealmModel appRealm, ComponentModel ldapModel, String mapperName) { ComponentModel mapperModel = getSubcomponentByName(appRealm, ldapModel, mapperName); LDAPStorageProvider ldapProvider = LDAPTestUtils.getLdapProvider(session, ldapModel); LDAPQuery roleQuery = getRoleMapper(mapperModel, ldapProvider, appRealm).createRoleQuery(); List<LDAPObject> ldapRoles = roleQuery.getResultList(); for (LDAPObject ldapRole : ldapRoles) { ldapProvider.getLdapIdentityStore().remove(ldapRole); } } public static void removeAllLDAPGroups(KeycloakSession session, RealmModel appRealm, ComponentModel ldapModel, String mapperName) { ComponentModel mapperModel = getSubcomponentByName(appRealm, ldapModel, mapperName); LDAPStorageProvider ldapProvider = LDAPTestUtils.getLdapProvider(session, ldapModel); LDAPQuery roleQuery = getGroupMapper(mapperModel, ldapProvider, appRealm).createGroupQuery(); List<LDAPObject> ldapRoles = roleQuery.getResultList(); for (LDAPObject ldapRole : ldapRoles) { ldapProvider.getLdapIdentityStore().remove(ldapRole); } } public static void createLDAPRole(KeycloakSession session, RealmModel appRealm, ComponentModel ldapModel, String mapperName, String roleName) { ComponentModel mapperModel = getSubcomponentByName(appRealm, ldapModel, mapperName); LDAPStorageProvider ldapProvider = LDAPTestUtils.getLdapProvider(session, ldapModel); getRoleMapper(mapperModel, ldapProvider, appRealm).createLDAPRole(roleName); } public static LDAPObject createLDAPGroup(KeycloakSession session, RealmModel appRealm, ComponentModel ldapModel, String groupName, String... additionalAttrs) { ComponentModel mapperModel = getSubcomponentByName(appRealm, ldapModel, "groupsMapper"); LDAPStorageProvider ldapProvider = LDAPTestUtils.getLdapProvider(session, ldapModel); Map<String, Set<String>> additAttrs = new HashMap<>(); for (int i=0 ; i<additionalAttrs.length ; i+=2) { String attrName = additionalAttrs[i]; String attrValue = additionalAttrs[i+1]; additAttrs.put(attrName, Collections.singleton(attrValue)); } return getGroupMapper(mapperModel, ldapProvider, appRealm).createLDAPGroup(groupName, additAttrs); } public static GroupLDAPStorageMapper getGroupMapper(ComponentModel mapperModel, LDAPStorageProvider ldapProvider, RealmModel realm) { return new GroupLDAPStorageMapper(mapperModel, ldapProvider, new GroupLDAPStorageMapperFactory()); } public static RoleLDAPStorageMapper getRoleMapper(ComponentModel mapperModel, LDAPStorageProvider ldapProvider, RealmModel realm) { return new RoleLDAPStorageMapper(mapperModel, ldapProvider, new RoleLDAPStorageMapperFactory()); } public static void assertSyncEquals(SynchronizationResult syncResult, int expectedAdded, int expectedUpdated, int expectedRemoved, int expectedFailed) { Assert.assertEquals(expectedAdded, syncResult.getAdded()); Assert.assertEquals(expectedUpdated, syncResult.getUpdated()); Assert.assertEquals(expectedRemoved, syncResult.getRemoved()); Assert.assertEquals(expectedFailed, syncResult.getFailed()); } public static void assertSyncEquals(SynchronizationResultRepresentation syncResult, int expectedAdded, int expectedUpdated, int expectedRemoved, int expectedFailed) { Assert.assertEquals(expectedAdded, syncResult.getAdded()); Assert.assertEquals(expectedUpdated, syncResult.getUpdated()); Assert.assertEquals(expectedRemoved, syncResult.getRemoved()); Assert.assertEquals(expectedFailed, syncResult.getFailed()); } }