diff --git a/core/src/main/java/org/keycloak/representations/idm/StorageProviderRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/StorageProviderRepresentation.java deleted file mode 100755 index 04bf174636..0000000000 --- a/core/src/main/java/org/keycloak/representations/idm/StorageProviderRepresentation.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * 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.representations.idm; - -import java.util.Map; - -/** - * @author Marek Posolda - */ -public class StorageProviderRepresentation { - - private String id; - private String displayName; - private String providerName; - private Map config; - private int priority; - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public String getDisplayName() { - return displayName; - } - - public void setDisplayName(String displayName) { - this.displayName = displayName; - } - - public String getProviderName() { - return providerName; - } - - public void setProviderName(String providerName) { - this.providerName = providerName; - } - - - public Map getConfig() { - return config; - } - - public void setConfig(Map config) { - this.config = config; - } - - public int getPriority() { - return priority; - } - - public void setPriority(int priority) { - this.priority = priority; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - StorageProviderRepresentation that = (StorageProviderRepresentation) o; - - if (!id.equals(that.id)) return false; - - return true; - } - - @Override - public int hashCode() { - return id.hashCode(); - } -} diff --git a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/CommonKerberosConfig.java b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/CommonKerberosConfig.java index 7e3d6e70a2..ed6c495014 100644 --- a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/CommonKerberosConfig.java +++ b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/CommonKerberosConfig.java @@ -18,6 +18,7 @@ package org.keycloak.federation.kerberos; import org.keycloak.common.constants.KerberosConstants; +import org.keycloak.component.ComponentModel; import org.keycloak.models.UserFederationProviderModel; import java.util.Map; @@ -29,31 +30,41 @@ import java.util.Map; */ public abstract class CommonKerberosConfig { - private final UserFederationProviderModel providerModel; + protected UserFederationProviderModel providerModel; + protected ComponentModel componentModel; public CommonKerberosConfig(UserFederationProviderModel userFederationProvider) { this.providerModel = userFederationProvider; } + public CommonKerberosConfig(ComponentModel componentModel) { + this.componentModel = componentModel; + } + // Should be always true for KerberosFederationProvider public boolean isAllowKerberosAuthentication() { - return Boolean.valueOf(getConfig().get(KerberosConstants.ALLOW_KERBEROS_AUTHENTICATION)); + if (providerModel != null) return Boolean.valueOf(getConfig().get(KerberosConstants.ALLOW_KERBEROS_AUTHENTICATION)); + else return Boolean.valueOf(componentModel.getConfig().getFirst(KerberosConstants.ALLOW_KERBEROS_AUTHENTICATION)); } public String getKerberosRealm() { - return getConfig().get(KerberosConstants.KERBEROS_REALM); + if (providerModel != null) return getConfig().get(KerberosConstants.KERBEROS_REALM); + else return componentModel.getConfig().getFirst(KerberosConstants.KERBEROS_REALM); } public String getServerPrincipal() { - return getConfig().get(KerberosConstants.SERVER_PRINCIPAL); + if (providerModel != null) return getConfig().get(KerberosConstants.SERVER_PRINCIPAL); + else return componentModel.getConfig().getFirst(KerberosConstants.SERVER_PRINCIPAL); } public String getKeyTab() { - return getConfig().get(KerberosConstants.KEYTAB); + if (providerModel != null) return getConfig().get(KerberosConstants.KEYTAB); + else return componentModel.getConfig().getFirst(KerberosConstants.KEYTAB); } public boolean isDebug() { - return Boolean.valueOf(getConfig().get(KerberosConstants.DEBUG)); + if (providerModel != null) return Boolean.valueOf(getConfig().get(KerberosConstants.DEBUG)); + else return Boolean.valueOf(componentModel.getConfig().getFirst(KerberosConstants.DEBUG)); } protected Map getConfig() { diff --git a/federation/ldap/pom.xml b/federation/ldap/pom.xml index 38fa19d3dd..5cd5eccd3f 100755 --- a/federation/ldap/pom.xml +++ b/federation/ldap/pom.xml @@ -26,9 +26,14 @@ 4.0.0 keycloak-ldap-federation - Keycloak LDAP Federation + Keycloak LDAP UserStoreProvider + + 1.8 + 1.8 + + org.keycloak diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProviderFactory.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProviderFactory.java deleted file mode 100755 index 51a3c8cb70..0000000000 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProviderFactory.java +++ /dev/null @@ -1,428 +0,0 @@ -/* - * 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.federation.ldap; - -import org.jboss.logging.Logger; -import org.keycloak.Config; -import org.keycloak.federation.kerberos.CommonKerberosConfig; -import org.keycloak.federation.kerberos.impl.KerberosServerSubjectAuthenticator; -import org.keycloak.federation.kerberos.impl.KerberosUsernamePasswordAuthenticator; -import org.keycloak.federation.kerberos.impl.SPNEGOAuthenticator; -import org.keycloak.federation.ldap.idm.model.LDAPObject; -import org.keycloak.federation.ldap.idm.query.Condition; -import org.keycloak.federation.ldap.idm.query.internal.LDAPQuery; -import org.keycloak.federation.ldap.idm.query.internal.LDAPQueryConditionsBuilder; -import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore; -import org.keycloak.federation.ldap.mappers.FullNameLDAPFederationMapper; -import org.keycloak.federation.ldap.mappers.FullNameLDAPFederationMapperFactory; -import org.keycloak.federation.ldap.mappers.LDAPFederationMapper; -import org.keycloak.federation.ldap.mappers.UserAttributeLDAPFederationMapper; -import org.keycloak.federation.ldap.mappers.UserAttributeLDAPFederationMapperFactory; -import org.keycloak.federation.ldap.mappers.msad.MSADUserAccountControlMapperFactory; -import org.keycloak.mappers.FederationConfigValidationException; -import org.keycloak.mappers.UserFederationMapper; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionFactory; -import org.keycloak.models.KeycloakSessionTask; -import org.keycloak.models.LDAPConstants; -import org.keycloak.models.ModelException; -import org.keycloak.models.RealmModel; -import org.keycloak.models.UserFederationEventAwareProviderFactory; -import org.keycloak.models.UserFederationMapperModel; -import org.keycloak.models.UserFederationProvider; -import org.keycloak.models.UserFederationProviderModel; -import org.keycloak.models.UserFederationSyncResult; -import org.keycloak.models.UserFederationValidatingProviderFactory; -import org.keycloak.models.UserModel; -import org.keycloak.models.utils.KeycloakModelUtils; - -import java.util.Collections; -import java.util.Date; -import java.util.List; -import java.util.Set; - -/** - * @author Marek Posolda - * @author Bill Burke - * @version $Revision: 1 $ - */ -public class LDAPFederationProviderFactory extends UserFederationEventAwareProviderFactory implements UserFederationValidatingProviderFactory { - private static final Logger logger = Logger.getLogger(LDAPFederationProviderFactory.class); - public static final String PROVIDER_NAME = LDAPConstants.LDAP_PROVIDER; - - private LDAPIdentityStoreRegistry ldapStoreRegistry; - - @Override - public UserFederationProvider create(KeycloakSession session) { - throw new IllegalAccessError("Illegal to call this method"); - } - - @Override - public LDAPFederationProvider getInstance(KeycloakSession session, UserFederationProviderModel model) { - LDAPIdentityStore ldapIdentityStore = this.ldapStoreRegistry.getLdapStore(model); - return new LDAPFederationProvider(this, session, model, ldapIdentityStore); - } - - @Override - public void validateConfig(RealmModel realm, UserFederationProviderModel providerModel) throws FederationConfigValidationException { - LDAPConfig cfg = new LDAPConfig(providerModel.getConfig()); - String customFilter = cfg.getCustomUserSearchFilter(); - LDAPUtils.validateCustomLdapFilter(customFilter); - } - - @Override - public void init(Config.Scope config) { - this.ldapStoreRegistry = new LDAPIdentityStoreRegistry(); - } - - @Override - public void close() { - this.ldapStoreRegistry = null; - } - - @Override - public String getId() { - return PROVIDER_NAME; - } - - @Override - public Set getConfigurationOptions() { - return Collections.emptySet(); - } - - - // Best effort to create appropriate mappers according to our LDAP config - @Override - public void onProviderModelCreated(RealmModel realm, UserFederationProviderModel newProviderModel) { - LDAPConfig ldapConfig = new LDAPConfig(newProviderModel.getConfig()); - - boolean activeDirectory = ldapConfig.isActiveDirectory(); - UserFederationProvider.EditMode editMode = ldapConfig.getEditMode(); - String readOnly = String.valueOf(editMode == UserFederationProvider.EditMode.READ_ONLY || editMode == UserFederationProvider.EditMode.UNSYNCED); - String usernameLdapAttribute = ldapConfig.getUsernameLdapAttribute(); - - String alwaysReadValueFromLDAP = String.valueOf(editMode==UserFederationProvider.EditMode.READ_ONLY || editMode== UserFederationProvider.EditMode.WRITABLE); - - UserFederationMapperModel mapperModel; - mapperModel = KeycloakModelUtils.createUserFederationMapperModel("username", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID, - UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.USERNAME, - UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, usernameLdapAttribute, - UserAttributeLDAPFederationMapper.READ_ONLY, readOnly, - UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, "false", - UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "true"); - realm.addUserFederationMapper(mapperModel); - - // CN is typically used as RDN for Active Directory deployments - if (ldapConfig.getRdnLdapAttribute().equalsIgnoreCase(LDAPConstants.CN)) { - - if (usernameLdapAttribute.equalsIgnoreCase(LDAPConstants.CN)) { - - // For AD deployments with "cn" as username, we will map "givenName" to first name - mapperModel = KeycloakModelUtils.createUserFederationMapperModel("first name", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID, - UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.FIRST_NAME, - UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.GIVENNAME, - UserAttributeLDAPFederationMapper.READ_ONLY, readOnly, - UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP, - UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "true"); - realm.addUserFederationMapper(mapperModel); - - } else { - if (editMode == UserFederationProvider.EditMode.WRITABLE) { - - // For AD deployments with "sAMAccountName" as username and writable, we need to map "cn" as username as well (this is needed so we can register new users from KC into LDAP) and we will map "givenName" to first name. - mapperModel = KeycloakModelUtils.createUserFederationMapperModel("first name", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID, - UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.FIRST_NAME, - UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.GIVENNAME, - UserAttributeLDAPFederationMapper.READ_ONLY, readOnly, - UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP, - UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "true"); - realm.addUserFederationMapper(mapperModel); - - mapperModel = KeycloakModelUtils.createUserFederationMapperModel("username-cn", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID, - UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.USERNAME, - UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.CN, - UserAttributeLDAPFederationMapper.READ_ONLY, readOnly, - UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, "false", - UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "true"); - realm.addUserFederationMapper(mapperModel); - } else { - - // For read-only LDAP, we map "cn" as full name - mapperModel = KeycloakModelUtils.createUserFederationMapperModel("full name", newProviderModel.getId(), FullNameLDAPFederationMapperFactory.PROVIDER_ID, - FullNameLDAPFederationMapper.LDAP_FULL_NAME_ATTRIBUTE, LDAPConstants.CN, - FullNameLDAPFederationMapper.READ_ONLY, readOnly, - FullNameLDAPFederationMapper.WRITE_ONLY, "false"); - realm.addUserFederationMapper(mapperModel); - } - } - } else { - mapperModel = KeycloakModelUtils.createUserFederationMapperModel("first name", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID, - UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.FIRST_NAME, - UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.CN, - UserAttributeLDAPFederationMapper.READ_ONLY, readOnly, - UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP, - UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "true"); - realm.addUserFederationMapper(mapperModel); - } - - mapperModel = KeycloakModelUtils.createUserFederationMapperModel("last name", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID, - UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.LAST_NAME, - UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.SN, - UserAttributeLDAPFederationMapper.READ_ONLY, readOnly, - UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP, - UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "true"); - realm.addUserFederationMapper(mapperModel); - - mapperModel = KeycloakModelUtils.createUserFederationMapperModel("email", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID, - UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.EMAIL, - UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.EMAIL, - UserAttributeLDAPFederationMapper.READ_ONLY, readOnly, - UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, "false", - UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "false"); - realm.addUserFederationMapper(mapperModel); - - String createTimestampLdapAttrName = activeDirectory ? "whenCreated" : LDAPConstants.CREATE_TIMESTAMP; - String modifyTimestampLdapAttrName = activeDirectory ? "whenChanged" : LDAPConstants.MODIFY_TIMESTAMP; - - // map createTimeStamp as read-only - mapperModel = KeycloakModelUtils.createUserFederationMapperModel("creation date", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID, - UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, LDAPConstants.CREATE_TIMESTAMP, - UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, createTimestampLdapAttrName, - UserAttributeLDAPFederationMapper.READ_ONLY, "true", - UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP, - UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "false"); - realm.addUserFederationMapper(mapperModel); - - // map modifyTimeStamp as read-only - mapperModel = KeycloakModelUtils.createUserFederationMapperModel("modify date", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID, - UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, LDAPConstants.MODIFY_TIMESTAMP, - UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, modifyTimestampLdapAttrName, - UserAttributeLDAPFederationMapper.READ_ONLY, "true", - UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP, - UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "false"); - realm.addUserFederationMapper(mapperModel); - - // MSAD specific mapper for account state propagation - if (activeDirectory) { - mapperModel = KeycloakModelUtils.createUserFederationMapperModel("MSAD account controls", newProviderModel.getId(), MSADUserAccountControlMapperFactory.PROVIDER_ID); - realm.addUserFederationMapper(mapperModel); - } - } - - - @Override - public UserFederationSyncResult syncAllUsers(KeycloakSessionFactory sessionFactory, final String realmId, final UserFederationProviderModel model) { - syncMappers(sessionFactory, realmId, model); - - logger.infof("Sync all users from LDAP to local store: realm: %s, federation provider: %s", realmId, model.getDisplayName()); - - LDAPQuery userQuery = createQuery(sessionFactory, realmId, model); - UserFederationSyncResult syncResult = syncImpl(sessionFactory, userQuery, realmId, model); - - // TODO: Remove all existing keycloak users, which have federation links, but are not in LDAP. Perhaps don't check users, which were just added or updated during this sync? - - logger.infof("Sync all users finished: %s", syncResult.getStatus()); - return syncResult; - } - - @Override - public UserFederationSyncResult syncChangedUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model, Date lastSync) { - syncMappers(sessionFactory, realmId, model); - - logger.infof("Sync changed users from LDAP to local store: realm: %s, federation provider: %s, last sync time: " + lastSync, realmId, model.getDisplayName()); - - // Sync newly created and updated users - LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder(); - Condition createCondition = conditionsBuilder.greaterThanOrEqualTo(LDAPConstants.CREATE_TIMESTAMP, lastSync); - Condition modifyCondition = conditionsBuilder.greaterThanOrEqualTo(LDAPConstants.MODIFY_TIMESTAMP, lastSync); - Condition orCondition = conditionsBuilder.orCondition(createCondition, modifyCondition); - - LDAPQuery userQuery = createQuery(sessionFactory, realmId, model); - userQuery.addWhereCondition(orCondition); - UserFederationSyncResult result = syncImpl(sessionFactory, userQuery, realmId, model); - - logger.infof("Sync changed users finished: %s", result.getStatus()); - return result; - } - - protected void syncMappers(KeycloakSessionFactory sessionFactory, final String realmId, final UserFederationProviderModel model) { - KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() { - - @Override - public void run(KeycloakSession session) { - LDAPFederationProvider ldapProvider = getInstance(session, model); - RealmModel realm = session.realms().getRealm(realmId); - Set mappers = realm.getUserFederationMappersByFederationProvider(model.getId()); - for (UserFederationMapperModel mapperModel : mappers) { - UserFederationMapper ldapMapper = session.getProvider(UserFederationMapper.class, mapperModel.getFederationMapperType()); - UserFederationSyncResult syncResult = ldapMapper.syncDataFromFederationProviderToKeycloak(mapperModel, ldapProvider, session, realm); - if (syncResult.getAdded() > 0 || syncResult.getUpdated() > 0 || syncResult.getRemoved() > 0 || syncResult.getFailed() > 0) { - logger.infof("Sync of federation mapper '%s' finished. Status: %s", mapperModel.getName(), syncResult.toString()); - } - } - } - - }); - } - - protected UserFederationSyncResult syncImpl(KeycloakSessionFactory sessionFactory, LDAPQuery userQuery, final String realmId, final UserFederationProviderModel fedModel) { - - final UserFederationSyncResult syncResult = new UserFederationSyncResult(); - - LDAPConfig ldapConfig = new LDAPConfig(fedModel.getConfig()); - boolean pagination = ldapConfig.isPagination(); - if (pagination) { - int pageSize = ldapConfig.getBatchSizeForSync(); - - boolean nextPage = true; - while (nextPage) { - userQuery.setLimit(pageSize); - final List users = userQuery.getResultList(); - nextPage = userQuery.getPaginationContext() != null; - UserFederationSyncResult currentPageSync = importLdapUsers(sessionFactory, realmId, fedModel, users); - syncResult.add(currentPageSync); - } - } else { - // LDAP pagination not available. Do everything in single transaction - final List users = userQuery.getResultList(); - UserFederationSyncResult currentSync = importLdapUsers(sessionFactory, realmId, fedModel, users); - syncResult.add(currentSync); - } - - return syncResult; - } - - private LDAPQuery createQuery(KeycloakSessionFactory sessionFactory, final String realmId, final UserFederationProviderModel model) { - class QueryHolder { - LDAPQuery query; - } - - final QueryHolder queryHolder = new QueryHolder(); - KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() { - - @Override - public void run(KeycloakSession session) { - LDAPFederationProvider ldapFedProvider = getInstance(session, model); - RealmModel realm = session.realms().getRealm(realmId); - queryHolder.query = LDAPUtils.createQueryForUserSearch(ldapFedProvider, realm); - } - - }); - return queryHolder.query; - } - - protected UserFederationSyncResult importLdapUsers(KeycloakSessionFactory sessionFactory, final String realmId, final UserFederationProviderModel fedModel, List ldapUsers) { - final UserFederationSyncResult syncResult = new UserFederationSyncResult(); - - class BooleanHolder { - private boolean value = true; - } - final BooleanHolder exists = new BooleanHolder(); - - for (final LDAPObject ldapUser : ldapUsers) { - - try { - - // Process each user in it's own transaction to avoid global fail - KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() { - - @Override - public void run(KeycloakSession session) { - LDAPFederationProvider ldapFedProvider = getInstance(session, fedModel); - RealmModel currentRealm = session.realms().getRealm(realmId); - - String username = LDAPUtils.getUsername(ldapUser, ldapFedProvider.getLdapIdentityStore().getConfig()); - exists.value = true; - LDAPUtils.checkUuid(ldapUser, ldapFedProvider.getLdapIdentityStore().getConfig()); - UserModel currentUser = session.userStorage().getUserByUsername(username, currentRealm); - - if (currentUser == null) { - - // Add new user to Keycloak - exists.value = false; - ldapFedProvider.importUserFromLDAP(session, currentRealm, ldapUser); - syncResult.increaseAdded(); - - } else { - if ((fedModel.getId().equals(currentUser.getFederationLink())) && (ldapUser.getUuid().equals(currentUser.getFirstAttribute(LDAPConstants.LDAP_ID)))) { - - // Update keycloak user - Set federationMappers = currentRealm.getUserFederationMappersByFederationProvider(fedModel.getId()); - List sortedMappers = ldapFedProvider.sortMappersDesc(federationMappers); - for (UserFederationMapperModel mapperModel : sortedMappers) { - LDAPFederationMapper ldapMapper = ldapFedProvider.getMapper(mapperModel); - ldapMapper.onImportUserFromLDAP(mapperModel, ldapFedProvider, ldapUser, currentUser, currentRealm, false); - } - - logger.debugf("Updated user from LDAP: %s", currentUser.getUsername()); - syncResult.increaseUpdated(); - } else { - logger.warnf("User '%s' is not updated during sync as he already exists in Keycloak database but is not linked to federation provider '%s'", username, fedModel.getDisplayName()); - syncResult.increaseFailed(); - } - } - } - - }); - } catch (ModelException me) { - logger.error("Failed during import user from LDAP", me); - syncResult.increaseFailed(); - - // Remove user if we already added him during this transaction - if (!exists.value) { - KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() { - - @Override - public void run(KeycloakSession session) { - LDAPFederationProvider ldapFedProvider = getInstance(session, fedModel); - RealmModel currentRealm = session.realms().getRealm(realmId); - String username = null; - try { - username = LDAPUtils.getUsername(ldapUser, ldapFedProvider.getLdapIdentityStore().getConfig()); - } catch (ModelException ignore) { - } - - if (username != null) { - UserModel existing = session.userStorage().getUserByUsername(username, currentRealm); - if (existing != null) { - session.userStorage().removeUser(currentRealm, existing); - } - } - } - - }); - } - } - } - - return syncResult; - } - - protected SPNEGOAuthenticator createSPNEGOAuthenticator(String spnegoToken, CommonKerberosConfig kerberosConfig) { - KerberosServerSubjectAuthenticator kerberosAuth = createKerberosSubjectAuthenticator(kerberosConfig); - return new SPNEGOAuthenticator(kerberosConfig, kerberosAuth, spnegoToken); - } - - protected KerberosServerSubjectAuthenticator createKerberosSubjectAuthenticator(CommonKerberosConfig kerberosConfig) { - return new KerberosServerSubjectAuthenticator(kerberosConfig); - } - - protected KerberosUsernamePasswordAuthenticator createKerberosUsernamePasswordAuthenticator(CommonKerberosConfig kerberosConfig) { - return new KerberosUsernamePasswordAuthenticator(kerberosConfig); - } -} 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 deleted file mode 100755 index 4c5a534a74..0000000000 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/FullNameLDAPFederationMapperFactory.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * 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.federation.ldap.mappers; - -import org.keycloak.federation.ldap.LDAPConfig; -import org.keycloak.federation.ldap.LDAPFederationProvider; -import org.keycloak.mappers.FederationConfigValidationException; -import org.keycloak.models.LDAPConstants; -import org.keycloak.models.RealmModel; -import org.keycloak.models.UserFederationMapperModel; -import org.keycloak.models.UserFederationProvider; -import org.keycloak.models.UserFederationProviderModel; -import org.keycloak.provider.ProviderConfigProperty; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * @author Marek Posolda - */ -public class FullNameLDAPFederationMapperFactory extends AbstractLDAPFederationMapperFactory { - - public static final String PROVIDER_ID = "full-name-ldap-mapper"; - - protected static final List configProperties = new ArrayList(); - - static { - ProviderConfigProperty userModelAttribute = createConfigProperty(FullNameLDAPFederationMapper.LDAP_FULL_NAME_ATTRIBUTE, "LDAP Full Name Attribute", - "Name of LDAP attribute, which contains fullName of user. Usually it will be 'cn' ", ProviderConfigProperty.STRING_TYPE, null); - configProperties.add(userModelAttribute); - - ProviderConfigProperty readOnly = createConfigProperty(FullNameLDAPFederationMapper.READ_ONLY, "Read Only", - "For Read-only is data imported from LDAP to Keycloak DB, but it's not saved back to LDAP when user is updated in Keycloak.", ProviderConfigProperty.BOOLEAN_TYPE, null); - configProperties.add(readOnly); - - ProviderConfigProperty writeOnly = createConfigProperty(FullNameLDAPFederationMapper.WRITE_ONLY, "Write Only", - "For Write-only is data propagated to LDAP when user is created or updated in Keycloak. But this mapper is not used to propagate data from LDAP back into Keycloak. " + - "This setting is useful if you configured separate firstName and lastName attribute mappers and you want to use those to read attribute from LDAP into Keycloak", ProviderConfigProperty.BOOLEAN_TYPE, null); - configProperties.add(writeOnly); - } - - @Override - public String getHelpText() { - return "Used to map full-name of user from single attribute in LDAP (usually 'cn' attribute) to firstName and lastName attributes of UserModel in Keycloak DB"; - } - - @Override - public String getDisplayCategory() { - return ATTRIBUTE_MAPPER_CATEGORY; - } - - @Override - public String getDisplayType() { - return "Full Name"; - } - - @Override - public List getConfigProperties() { - return configProperties; - } - - @Override - public Map getDefaultConfig(UserFederationProviderModel providerModel) { - Map defaultValues = new HashMap<>(); - LDAPConfig config = new LDAPConfig(providerModel.getConfig()); - - defaultValues.put(FullNameLDAPFederationMapper.LDAP_FULL_NAME_ATTRIBUTE, LDAPConstants.CN); - - boolean readOnly = config.getEditMode() != UserFederationProvider.EditMode.WRITABLE; - defaultValues.put(FullNameLDAPFederationMapper.READ_ONLY, String.valueOf(readOnly)); - - String writeOnly = String.valueOf(!readOnly); - defaultValues.put(FullNameLDAPFederationMapper.WRITE_ONLY, writeOnly); - - return defaultValues; - } - - @Override - public String getId() { - return PROVIDER_ID; - } - - @Override - public void validateConfig(RealmModel realm, UserFederationProviderModel fedProviderModel, UserFederationMapperModel mapperModel) throws FederationConfigValidationException { - checkMandatoryConfigAttribute(FullNameLDAPFederationMapper.LDAP_FULL_NAME_ATTRIBUTE, "LDAP Full Name Attribute", mapperModel); - - boolean readOnly = AbstractLDAPFederationMapper.parseBooleanParameter(mapperModel, FullNameLDAPFederationMapper.READ_ONLY); - boolean writeOnly = AbstractLDAPFederationMapper.parseBooleanParameter(mapperModel, FullNameLDAPFederationMapper.WRITE_ONLY); - - LDAPConfig cfg = new LDAPConfig(fedProviderModel.getConfig()); - UserFederationProvider.EditMode editMode = cfg.getEditMode(); - - if (writeOnly && cfg.getEditMode() != UserFederationProvider.EditMode.WRITABLE) { - throw new FederationConfigValidationException("ldapErrorCantWriteOnlyForReadOnlyLdap"); - } - if (writeOnly && readOnly) { - throw new FederationConfigValidationException("ldapErrorCantWriteOnlyAndReadOnly"); - } - } - - @Override - protected AbstractLDAPFederationMapper createMapper(UserFederationMapperModel mapperModel, LDAPFederationProvider federationProvider, RealmModel realm) { - return new FullNameLDAPFederationMapper(mapperModel, federationProvider, realm); - } -} 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 deleted file mode 100755 index e0c0f8ec57..0000000000 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserAttributeLDAPFederationMapperFactory.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * 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.federation.ldap.mappers; - -import org.keycloak.federation.ldap.LDAPConfig; -import org.keycloak.federation.ldap.LDAPFederationProvider; -import org.keycloak.mappers.FederationConfigValidationException; -import org.keycloak.models.RealmModel; -import org.keycloak.models.UserFederationMapperModel; -import org.keycloak.models.UserFederationProvider; -import org.keycloak.models.UserFederationProviderModel; -import org.keycloak.provider.ProviderConfigProperty; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * @author Marek Posolda - */ -public class UserAttributeLDAPFederationMapperFactory extends AbstractLDAPFederationMapperFactory { - - public static final String PROVIDER_ID = "user-attribute-ldap-mapper"; - protected static final List configProperties = new ArrayList(); - - static { - ProviderConfigProperty userModelAttribute = createConfigProperty(UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, "User Model Attribute", - "Name of mapped UserModel property or UserModel attribute in Keycloak DB. For example 'firstName', 'lastName, 'email', 'street' etc.", ProviderConfigProperty.STRING_TYPE, null); - configProperties.add(userModelAttribute); - - ProviderConfigProperty ldapAttribute = createConfigProperty(UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, "LDAP Attribute", - "Name of mapped attribute on LDAP object. For example 'cn', 'sn, 'mail', 'street' etc.", ProviderConfigProperty.STRING_TYPE, null); - configProperties.add(ldapAttribute); - - ProviderConfigProperty readOnly = createConfigProperty(UserAttributeLDAPFederationMapper.READ_ONLY, "Read Only", - "Read-only attribute is imported from LDAP to Keycloak DB, but it's not saved back to LDAP when user is updated in Keycloak.", ProviderConfigProperty.BOOLEAN_TYPE, null); - configProperties.add(readOnly); - - ProviderConfigProperty alwaysReadValueFromLDAP = createConfigProperty(UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, "Always Read Value From LDAP", - "If on, then during reading of the user will be value of attribute from LDAP always used instead of the value from Keycloak DB", ProviderConfigProperty.BOOLEAN_TYPE, null); - configProperties.add(alwaysReadValueFromLDAP); - - ProviderConfigProperty isMandatoryInLdap = createConfigProperty(UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "Is Mandatory In LDAP", - "If true, attribute is mandatory in LDAP. Hence if there is no value in Keycloak DB, the empty value will be set to be propagated to LDAP", ProviderConfigProperty.BOOLEAN_TYPE, null); - configProperties.add(isMandatoryInLdap); - } - - @Override - public String getHelpText() { - return "Used to map single attribute from LDAP user to attribute of UserModel in Keycloak DB"; - } - - @Override - public String getDisplayCategory() { - return ATTRIBUTE_MAPPER_CATEGORY; - } - - @Override - public String getDisplayType() { - return "User Attribute"; - } - - @Override - public List getConfigProperties() { - return configProperties; - } - - @Override - public Map getDefaultConfig(UserFederationProviderModel providerModel) { - Map defaultValues = new HashMap<>(); - LDAPConfig config = new LDAPConfig(providerModel.getConfig()); - - String readOnly = config.getEditMode() == UserFederationProvider.EditMode.WRITABLE ? "false" : "true"; - defaultValues.put(UserAttributeLDAPFederationMapper.READ_ONLY, readOnly); - - defaultValues.put(UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, "false"); - defaultValues.put(UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "false"); - - return defaultValues; - } - - @Override - public String getId() { - return PROVIDER_ID; - } - - @Override - public void validateConfig(RealmModel realm, UserFederationProviderModel fedProviderModel, UserFederationMapperModel mapperModel) throws FederationConfigValidationException { - checkMandatoryConfigAttribute(UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, "User Model Attribute", mapperModel); - checkMandatoryConfigAttribute(UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, "LDAP Attribute", mapperModel); - } - - @Override - protected AbstractLDAPFederationMapper createMapper(UserFederationMapperModel mapperModel, LDAPFederationProvider federationProvider, RealmModel realm) { - return new UserAttributeLDAPFederationMapper(mapperModel, federationProvider, realm); - } -} diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/group/GroupLDAPFederationMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/group/GroupLDAPFederationMapperFactory.java deleted file mode 100644 index a9c5056275..0000000000 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/group/GroupLDAPFederationMapperFactory.java +++ /dev/null @@ -1,212 +0,0 @@ -/* - * 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.federation.ldap.mappers.membership.group; - -import org.keycloak.federation.ldap.LDAPConfig; -import org.keycloak.federation.ldap.LDAPFederationProvider; -import org.keycloak.federation.ldap.LDAPUtils; -import org.keycloak.federation.ldap.mappers.AbstractLDAPFederationMapper; -import org.keycloak.federation.ldap.mappers.AbstractLDAPFederationMapperFactory; -import org.keycloak.federation.ldap.mappers.membership.CommonLDAPGroupMapperConfig; -import org.keycloak.federation.ldap.mappers.membership.LDAPGroupMapperMode; -import org.keycloak.federation.ldap.mappers.membership.MembershipType; -import org.keycloak.federation.ldap.mappers.membership.UserRolesRetrieveStrategy; -import org.keycloak.federation.ldap.mappers.membership.role.RoleMapperConfig; -import org.keycloak.mappers.FederationConfigValidationException; -import org.keycloak.models.LDAPConstants; -import org.keycloak.models.RealmModel; -import org.keycloak.models.UserFederationMapperModel; -import org.keycloak.models.UserFederationProvider; -import org.keycloak.models.UserFederationProviderModel; -import org.keycloak.provider.ProviderConfigProperty; -import org.keycloak.representations.idm.UserFederationMapperSyncConfigRepresentation; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; - -/** - * @author Marek Posolda - */ -public class GroupLDAPFederationMapperFactory extends AbstractLDAPFederationMapperFactory { - - public static final String PROVIDER_ID = "group-ldap-mapper"; - - protected static final List configProperties = new ArrayList<>(); - protected static final Map userGroupsStrategies = new LinkedHashMap<>(); - - // TODO: Merge with RoleLDAPFederationMapperFactory as there are lot of similar properties - static { - userGroupsStrategies.put(GroupMapperConfig.LOAD_GROUPS_BY_MEMBER_ATTRIBUTE, new UserRolesRetrieveStrategy.LoadRolesByMember()); - userGroupsStrategies.put(GroupMapperConfig.GET_GROUPS_FROM_USER_MEMBEROF_ATTRIBUTE, new UserRolesRetrieveStrategy.GetRolesFromUserMemberOfAttribute()); - userGroupsStrategies.put(GroupMapperConfig.LOAD_GROUPS_BY_MEMBER_ATTRIBUTE_RECURSIVELY, new UserRolesRetrieveStrategy.LoadRolesByMemberRecursively()); - - ProviderConfigProperty groupsDn = createConfigProperty(GroupMapperConfig.GROUPS_DN, "LDAP Groups DN", - "LDAP DN where are groups of this tree saved. For example 'ou=groups,dc=example,dc=org' ", ProviderConfigProperty.STRING_TYPE, null); - configProperties.add(groupsDn); - - ProviderConfigProperty groupNameLDAPAttribute = createConfigProperty(GroupMapperConfig.GROUP_NAME_LDAP_ATTRIBUTE, "Group Name LDAP Attribute", - "Name of LDAP attribute, which is used in group objects for name and RDN of group. Usually it will be 'cn' . In this case typical group/role object may have DN like 'cn=Group1,ou=groups,dc=example,dc=org' ", - ProviderConfigProperty.STRING_TYPE, null); - configProperties.add(groupNameLDAPAttribute); - - ProviderConfigProperty groupObjectClasses = createConfigProperty(GroupMapperConfig.GROUP_OBJECT_CLASSES, "Group Object Classes", - "Object class (or classes) of the group object. It's divided by comma if more classes needed. In typical LDAP deployment it could be 'groupOfNames' . In Active Directory it's usually 'group' ", - ProviderConfigProperty.STRING_TYPE, null); - configProperties.add(groupObjectClasses); - - ProviderConfigProperty preserveGroupInheritance = createConfigProperty(GroupMapperConfig.PRESERVE_GROUP_INHERITANCE, "Preserve Group Inheritance", - "Flag whether group inheritance from LDAP should be propagated to Keycloak. If false, then all LDAP groups will be mapped as flat top-level groups in Keycloak. Otherwise group inheritance is " + - "preserved into Keycloak, but the group sync might fail if LDAP structure contains recursions or multiple parent groups per child groups", - ProviderConfigProperty.BOOLEAN_TYPE, null); - configProperties.add(preserveGroupInheritance); - - ProviderConfigProperty membershipLDAPAttribute = createConfigProperty(GroupMapperConfig.MEMBERSHIP_LDAP_ATTRIBUTE, "Membership LDAP Attribute", - "Name of LDAP attribute on group, which is used for membership mappings. Usually it will be 'member' ", - ProviderConfigProperty.STRING_TYPE, null); - configProperties.add(membershipLDAPAttribute); - - List membershipTypes = new LinkedList<>(); - for (MembershipType membershipType : MembershipType.values()) { - membershipTypes.add(membershipType.toString()); - } - ProviderConfigProperty membershipType = createConfigProperty(GroupMapperConfig.MEMBERSHIP_ATTRIBUTE_TYPE, "Membership Attribute Type", - "DN means that LDAP group has it's members declared in form of their full DN. For example 'member: uid=john,ou=users,dc=example,dc=com' . " + - "UID means that LDAP group has it's members declared in form of pure user uids. For example 'memberUid: john' .", - ProviderConfigProperty.LIST_TYPE, membershipTypes); - configProperties.add(membershipType); - - ProviderConfigProperty ldapFilter = createConfigProperty(GroupMapperConfig.GROUPS_LDAP_FILTER, - "LDAP Filter", - "LDAP Filter adds additional custom filter to the whole query for retrieve LDAP groups. Leave this empty if no additional filtering is needed and you want to retrieve all groups from LDAP. Otherwise make sure that filter starts with '(' and ends with ')'", - ProviderConfigProperty.STRING_TYPE, null); - configProperties.add(ldapFilter); - - List modes = new LinkedList<>(); - for (LDAPGroupMapperMode mode : LDAPGroupMapperMode.values()) { - modes.add(mode.toString()); - } - ProviderConfigProperty mode = createConfigProperty(GroupMapperConfig.MODE, "Mode", - "LDAP_ONLY means that all group mappings of users are retrieved from LDAP and saved into LDAP. READ_ONLY is Read-only LDAP mode where group mappings are " + - "retrieved from both LDAP and DB and merged together. New group joins are not saved to LDAP but to DB. IMPORT is Read-only LDAP mode where group mappings are " + - "retrieved from LDAP just at the time when user is imported from LDAP and then " + - "they are saved to local keycloak DB.", - ProviderConfigProperty.LIST_TYPE, modes); - configProperties.add(mode); - - List roleRetrievers = new LinkedList<>(userGroupsStrategies.keySet()); - ProviderConfigProperty retriever = createConfigProperty(GroupMapperConfig.USER_ROLES_RETRIEVE_STRATEGY, "User Groups Retrieve Strategy", - "Specify how to retrieve groups of user. LOAD_GROUPS_BY_MEMBER_ATTRIBUTE means that roles of user will be retrieved by sending LDAP query to retrieve all groups where 'member' is our user. " + - "GET_GROUPS_FROM_USER_MEMBEROF_ATTRIBUTE means that groups of user will be retrieved from 'memberOf' attribute of our user. " + - "LOAD_GROUPS_BY_MEMBER_ATTRIBUTE_RECURSIVELY is applicable just in Active Directory and it means that groups of user will be retrieved recursively with usage of LDAP_MATCHING_RULE_IN_CHAIN Ldap extension." - , - ProviderConfigProperty.LIST_TYPE, roleRetrievers); - configProperties.add(retriever); - - ProviderConfigProperty mappedGroupAttributes = createConfigProperty(GroupMapperConfig.MAPPED_GROUP_ATTRIBUTES, "Mapped Group Attributes", - "List of names of attributes divided by comma. This points to the list of attributes on LDAP group, which will be mapped as attributes of Group in Keycloak. " + - "Leave this empty if no additional group attributes are required to be mapped in Keycloak. ", - ProviderConfigProperty.STRING_TYPE, null); - configProperties.add(mappedGroupAttributes); - - ProviderConfigProperty dropNonExistingGroupsDuringSync = createConfigProperty(GroupMapperConfig.DROP_NON_EXISTING_GROUPS_DURING_SYNC, "Drop non-existing groups during sync", - "If this flag is true, then during sync of groups from LDAP to Keycloak, we will keep just those Keycloak groups, which still exists in LDAP. Rest will be deleted", - ProviderConfigProperty.BOOLEAN_TYPE, null); - configProperties.add(dropNonExistingGroupsDuringSync); - } - - @Override - public String getHelpText() { - return "Used to map group mappings of groups from some LDAP DN to Keycloak group mappings"; - } - - @Override - public String getDisplayCategory() { - return GROUP_MAPPER_CATEGORY; - } - - @Override - public String getDisplayType() { - return "Group mappings"; - } - - @Override - public List getConfigProperties() { - return configProperties; - } - - @Override - public Map getDefaultConfig(UserFederationProviderModel providerModel) { - Map defaultValues = new HashMap<>(); - LDAPConfig config = new LDAPConfig(providerModel.getConfig()); - - defaultValues.put(GroupMapperConfig.GROUP_NAME_LDAP_ATTRIBUTE, LDAPConstants.CN); - - String roleObjectClasses = config.isActiveDirectory() ? LDAPConstants.GROUP : LDAPConstants.GROUP_OF_NAMES; - defaultValues.put(GroupMapperConfig.GROUP_OBJECT_CLASSES, roleObjectClasses); - - defaultValues.put(GroupMapperConfig.PRESERVE_GROUP_INHERITANCE, "true"); - defaultValues.put(GroupMapperConfig.MEMBERSHIP_LDAP_ATTRIBUTE, LDAPConstants.MEMBER); - defaultValues.put(GroupMapperConfig.MEMBERSHIP_ATTRIBUTE_TYPE, MembershipType.DN.toString()); - - String mode = config.getEditMode() == UserFederationProvider.EditMode.WRITABLE ? LDAPGroupMapperMode.LDAP_ONLY.toString() : LDAPGroupMapperMode.READ_ONLY.toString(); - defaultValues.put(GroupMapperConfig.MODE, mode); - defaultValues.put(RoleMapperConfig.USER_ROLES_RETRIEVE_STRATEGY, GroupMapperConfig.LOAD_GROUPS_BY_MEMBER_ATTRIBUTE); - - defaultValues.put(GroupMapperConfig.DROP_NON_EXISTING_GROUPS_DURING_SYNC, "false"); - - return defaultValues; - } - - @Override - public String getId() { - return PROVIDER_ID; - } - - @Override - public UserFederationMapperSyncConfigRepresentation getSyncConfig() { - return new UserFederationMapperSyncConfigRepresentation(true, "sync-ldap-groups-to-keycloak", true, "sync-keycloak-groups-to-ldap"); - } - - @Override - public void validateConfig(RealmModel realm, UserFederationProviderModel fedProviderModel, UserFederationMapperModel mapperModel) throws FederationConfigValidationException { - checkMandatoryConfigAttribute(GroupMapperConfig.GROUPS_DN, "LDAP Groups DN", mapperModel); - checkMandatoryConfigAttribute(GroupMapperConfig.MODE, "Mode", mapperModel); - - String mt = mapperModel.getConfig().get(CommonLDAPGroupMapperConfig.MEMBERSHIP_ATTRIBUTE_TYPE); - MembershipType membershipType = mt==null ? MembershipType.DN : Enum.valueOf(MembershipType.class, mt); - boolean preserveGroupInheritance = Boolean.parseBoolean(mapperModel.getConfig().get(GroupMapperConfig.PRESERVE_GROUP_INHERITANCE)); - if (preserveGroupInheritance && membershipType != MembershipType.DN) { - throw new FederationConfigValidationException("ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType"); - } - - LDAPUtils.validateCustomLdapFilter(mapperModel.getConfig().get(GroupMapperConfig.GROUPS_LDAP_FILTER)); - } - - @Override - protected AbstractLDAPFederationMapper createMapper(UserFederationMapperModel mapperModel, LDAPFederationProvider federationProvider, RealmModel realm) { - return new GroupLDAPFederationMapper(mapperModel, federationProvider, realm, this); - } - - protected UserRolesRetrieveStrategy getUserGroupsRetrieveStrategy(String strategyKey) { - return userGroupsStrategies.get(strategyKey); - } -} diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/role/RoleLDAPFederationMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/role/RoleLDAPFederationMapperFactory.java deleted file mode 100644 index 416e4b4c11..0000000000 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/role/RoleLDAPFederationMapperFactory.java +++ /dev/null @@ -1,206 +0,0 @@ -/* - * 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.federation.ldap.mappers.membership.role; - -import org.keycloak.federation.ldap.LDAPConfig; -import org.keycloak.federation.ldap.LDAPFederationProvider; -import org.keycloak.federation.ldap.LDAPUtils; -import org.keycloak.federation.ldap.mappers.AbstractLDAPFederationMapper; -import org.keycloak.federation.ldap.mappers.AbstractLDAPFederationMapperFactory; -import org.keycloak.federation.ldap.mappers.membership.LDAPGroupMapperMode; -import org.keycloak.federation.ldap.mappers.membership.MembershipType; -import org.keycloak.federation.ldap.mappers.membership.UserRolesRetrieveStrategy; -import org.keycloak.mappers.FederationConfigValidationException; -import org.keycloak.models.LDAPConstants; -import org.keycloak.models.RealmModel; -import org.keycloak.models.UserFederationMapperModel; -import org.keycloak.models.UserFederationProvider; -import org.keycloak.models.UserFederationProviderModel; -import org.keycloak.provider.ProviderConfigProperty; -import org.keycloak.representations.idm.UserFederationMapperSyncConfigRepresentation; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; - -/** - * @author Marek Posolda - */ -public class RoleLDAPFederationMapperFactory extends AbstractLDAPFederationMapperFactory { - - public static final String PROVIDER_ID = "role-ldap-mapper"; - - protected static final List configProperties = new ArrayList<>(); - protected static final Map userRolesStrategies = new LinkedHashMap<>(); - - - static { - userRolesStrategies.put(RoleMapperConfig.LOAD_ROLES_BY_MEMBER_ATTRIBUTE, new UserRolesRetrieveStrategy.LoadRolesByMember()); - userRolesStrategies.put(RoleMapperConfig.GET_ROLES_FROM_USER_MEMBEROF_ATTRIBUTE, new UserRolesRetrieveStrategy.GetRolesFromUserMemberOfAttribute()); - userRolesStrategies.put(RoleMapperConfig.LOAD_ROLES_BY_MEMBER_ATTRIBUTE_RECURSIVELY, new UserRolesRetrieveStrategy.LoadRolesByMemberRecursively()); - - ProviderConfigProperty rolesDn = createConfigProperty(RoleMapperConfig.ROLES_DN, "LDAP Roles DN", - "LDAP DN where are roles of this tree saved. For example 'ou=finance,dc=example,dc=org' ", ProviderConfigProperty.STRING_TYPE, null); - configProperties.add(rolesDn); - - ProviderConfigProperty roleNameLDAPAttribute = createConfigProperty(RoleMapperConfig.ROLE_NAME_LDAP_ATTRIBUTE, "Role Name LDAP Attribute", - "Name of LDAP attribute, which is used in role objects for name and RDN of role. Usually it will be 'cn' . In this case typical group/role object may have DN like 'cn=role1,ou=finance,dc=example,dc=org' ", - ProviderConfigProperty.STRING_TYPE, null); - configProperties.add(roleNameLDAPAttribute); - - ProviderConfigProperty roleObjectClasses = createConfigProperty(RoleMapperConfig.ROLE_OBJECT_CLASSES, "Role Object Classes", - "Object class (or classes) of the role object. It's divided by comma if more classes needed. In typical LDAP deployment it could be 'groupOfNames' . In Active Directory it's usually 'group' ", - ProviderConfigProperty.STRING_TYPE, null); - configProperties.add(roleObjectClasses); - - ProviderConfigProperty membershipLDAPAttribute = createConfigProperty(RoleMapperConfig.MEMBERSHIP_LDAP_ATTRIBUTE, "Membership LDAP Attribute", - "Name of LDAP attribute on role, which is used for membership mappings. Usually it will be 'member' ", - ProviderConfigProperty.STRING_TYPE, null); - configProperties.add(membershipLDAPAttribute); - - - List membershipTypes = new LinkedList<>(); - for (MembershipType membershipType : MembershipType.values()) { - membershipTypes.add(membershipType.toString()); - } - ProviderConfigProperty membershipType = createConfigProperty(RoleMapperConfig.MEMBERSHIP_ATTRIBUTE_TYPE, "Membership Attribute Type", - "DN means that LDAP role has it's members declared in form of their full DN. For example 'member: uid=john,ou=users,dc=example,dc=com' . " + - "UID means that LDAP role has it's members declared in form of pure user uids. For example 'memberUid: john' .", - ProviderConfigProperty.LIST_TYPE, membershipTypes); - configProperties.add(membershipType); - - - ProviderConfigProperty ldapFilter = createConfigProperty(RoleMapperConfig.ROLES_LDAP_FILTER, - "LDAP Filter", - "LDAP Filter adds additional custom filter to the whole query for retrieve LDAP roles. Leave this empty if no additional filtering is needed and you want to retrieve all roles from LDAP. Otherwise make sure that filter starts with '(' and ends with ')'", - ProviderConfigProperty.STRING_TYPE, null); - configProperties.add(ldapFilter); - - - List modes = new LinkedList<>(); - for (LDAPGroupMapperMode mode : LDAPGroupMapperMode.values()) { - modes.add(mode.toString()); - } - ProviderConfigProperty mode = createConfigProperty(RoleMapperConfig.MODE, "Mode", - "LDAP_ONLY means that all role mappings are retrieved from LDAP and saved into LDAP. READ_ONLY is Read-only LDAP mode where role mappings are " + - "retrieved from both LDAP and DB and merged together. New role grants are not saved to LDAP but to DB. IMPORT is Read-only LDAP mode where role mappings are retrieved from LDAP just at the time when user is imported from LDAP and then " + - "they are saved to local keycloak DB.", - ProviderConfigProperty.LIST_TYPE, modes); - configProperties.add(mode); - - - List roleRetrievers = new LinkedList<>(userRolesStrategies.keySet()); - ProviderConfigProperty retriever = createConfigProperty(RoleMapperConfig.USER_ROLES_RETRIEVE_STRATEGY, "User Roles Retrieve Strategy", - "Specify how to retrieve roles of user. LOAD_ROLES_BY_MEMBER_ATTRIBUTE means that roles of user will be retrieved by sending LDAP query to retrieve all roles where 'member' is our user. " + - "GET_ROLES_FROM_USER_MEMBEROF_ATTRIBUTE means that roles of user will be retrieved from 'memberOf' attribute of our user. " + - "LOAD_ROLES_BY_MEMBER_ATTRIBUTE_RECURSIVELY is applicable just in Active Directory and it means that roles of user will be retrieved recursively with usage of LDAP_MATCHING_RULE_IN_CHAIN Ldap extension." - , - ProviderConfigProperty.LIST_TYPE, roleRetrievers); - configProperties.add(retriever); - - - ProviderConfigProperty useRealmRolesMappings = createConfigProperty(RoleMapperConfig.USE_REALM_ROLES_MAPPING, "Use Realm Roles Mapping", - "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, null); - configProperties.add(useRealmRolesMappings); - - ProviderConfigProperty clientIdProperty = createConfigProperty(RoleMapperConfig.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 - public String getHelpText() { - return "Used to map role mappings of roles from some LDAP DN to Keycloak role mappings of either realm roles or client roles of particular client"; - } - - @Override - public String getDisplayCategory() { - return ROLE_MAPPER_CATEGORY; - } - - @Override - public String getDisplayType() { - return "Role mappings"; - } - - @Override - public List getConfigProperties() { - return configProperties; - } - - @Override - public Map getDefaultConfig(UserFederationProviderModel providerModel) { - Map defaultValues = new HashMap<>(); - LDAPConfig config = new LDAPConfig(providerModel.getConfig()); - - defaultValues.put(RoleMapperConfig.ROLE_NAME_LDAP_ATTRIBUTE, LDAPConstants.CN); - - String roleObjectClasses = config.isActiveDirectory() ? LDAPConstants.GROUP : LDAPConstants.GROUP_OF_NAMES; - defaultValues.put(RoleMapperConfig.ROLE_OBJECT_CLASSES, roleObjectClasses); - - defaultValues.put(RoleMapperConfig.MEMBERSHIP_LDAP_ATTRIBUTE, LDAPConstants.MEMBER); - defaultValues.put(RoleMapperConfig.MEMBERSHIP_ATTRIBUTE_TYPE, MembershipType.DN.toString()); - - String mode = config.getEditMode() == UserFederationProvider.EditMode.WRITABLE ? LDAPGroupMapperMode.LDAP_ONLY.toString() : LDAPGroupMapperMode.READ_ONLY.toString(); - defaultValues.put(RoleMapperConfig.MODE, mode); - - defaultValues.put(RoleMapperConfig.USER_ROLES_RETRIEVE_STRATEGY, RoleMapperConfig.LOAD_ROLES_BY_MEMBER_ATTRIBUTE); - defaultValues.put(RoleMapperConfig.USE_REALM_ROLES_MAPPING, "true"); - return defaultValues; - } - - @Override - public String getId() { - return PROVIDER_ID; - } - - @Override - public UserFederationMapperSyncConfigRepresentation getSyncConfig() { - return new UserFederationMapperSyncConfigRepresentation(true, "sync-ldap-roles-to-keycloak", true, "sync-keycloak-roles-to-ldap"); - } - - @Override - public void validateConfig(RealmModel realm, UserFederationProviderModel fedProviderModel, UserFederationMapperModel mapperModel) throws FederationConfigValidationException { - checkMandatoryConfigAttribute(RoleMapperConfig.ROLES_DN, "LDAP Roles DN", mapperModel); - checkMandatoryConfigAttribute(RoleMapperConfig.MODE, "Mode", mapperModel); - - String realmMappings = mapperModel.getConfig().get(RoleMapperConfig.USE_REALM_ROLES_MAPPING); - boolean useRealmMappings = Boolean.parseBoolean(realmMappings); - if (!useRealmMappings) { - String clientId = mapperModel.getConfig().get(RoleMapperConfig.CLIENT_ID); - if (clientId == null || clientId.trim().isEmpty()) { - throw new FederationConfigValidationException("ldapErrorMissingClientId"); - } - } - - LDAPUtils.validateCustomLdapFilter(mapperModel.getConfig().get(RoleMapperConfig.ROLES_LDAP_FILTER)); - } - - @Override - protected AbstractLDAPFederationMapper createMapper(UserFederationMapperModel mapperModel, LDAPFederationProvider federationProvider, RealmModel realm) { - return new RoleLDAPFederationMapper(mapperModel, federationProvider, realm, this); - } - - protected UserRolesRetrieveStrategy getUserRolesRetrieveStrategy(String strategyKey) { - return userRolesStrategies.get(strategyKey); - } -} diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPConfig.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPConfig.java similarity index 71% rename from federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPConfig.java rename to federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPConfig.java index 7d32e6a122..e5d497b2e0 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPConfig.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPConfig.java @@ -15,15 +15,14 @@ * limitations under the License. */ -package org.keycloak.federation.ldap; +package org.keycloak.storage.ldap; +import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.models.LDAPConstants; -import org.keycloak.models.UserFederationProvider; import javax.naming.directory.SearchControls; import java.util.Collection; import java.util.HashSet; -import java.util.Map; import java.util.Properties; import java.util.Set; @@ -33,14 +32,14 @@ import java.util.Set; */ public class LDAPConfig { - private final Map config; + private final MultivaluedHashMap config; - public LDAPConfig(Map config) { + public LDAPConfig(MultivaluedHashMap config) { this.config = config; } public String getConnectionUrl() { - return config.get(LDAPConstants.CONNECTION_URL); + return config.getFirst(LDAPConstants.CONNECTION_URL); } public String getFactoryName() { @@ -49,7 +48,7 @@ public class LDAPConfig { } public String getAuthType() { - String value = config.get(LDAPConstants.AUTH_TYPE); + String value = config.getFirst(LDAPConstants.AUTH_TYPE); if (value == null) { return LDAPConstants.AUTH_TYPE_SIMPLE; } else { @@ -58,22 +57,22 @@ public class LDAPConfig { } public String getUseTruststoreSpi() { - return config.get(LDAPConstants.USE_TRUSTSTORE_SPI); + return config.getFirst(LDAPConstants.USE_TRUSTSTORE_SPI); } public String getUsersDn() { - String usersDn = config.get(LDAPConstants.USERS_DN); + String usersDn = config.getFirst(LDAPConstants.USERS_DN); if (usersDn == null) { // Just for the backwards compatibility 1.2 -> 1.3 . Should be removed later. - usersDn = config.get("userDnSuffix"); + usersDn = config.getFirst("userDnSuffix"); } return usersDn; } public Collection getUserObjectClasses() { - String objClassesCfg = config.get(LDAPConstants.USER_OBJECT_CLASSES); + String objClassesCfg = config.getFirst(LDAPConstants.USER_OBJECT_CLASSES); String objClassesStr = (objClassesCfg != null && objClassesCfg.length() > 0) ? objClassesCfg.trim() : "inetOrgPerson,organizationalPerson"; String[] objectClasses = objClassesStr.split(","); @@ -87,15 +86,15 @@ public class LDAPConfig { } public String getBindDN() { - return config.get(LDAPConstants.BIND_DN); + return config.getFirst(LDAPConstants.BIND_DN); } public String getBindCredential() { - return config.get(LDAPConstants.BIND_CREDENTIAL); + return config.getFirst(LDAPConstants.BIND_CREDENTIAL); } public String getVendor() { - return config.get(LDAPConstants.VENDOR); + return config.getFirst(LDAPConstants.VENDOR); } public boolean isActiveDirectory() { @@ -104,7 +103,7 @@ public class LDAPConfig { } public String getConnectionPooling() { - return config.get(LDAPConstants.CONNECTION_POOLING); + return config.getFirst(LDAPConstants.CONNECTION_POOLING); } public Properties getAdditionalConnectionProperties() { @@ -113,12 +112,12 @@ public class LDAPConfig { } public int getSearchScope() { - String searchScope = config.get(LDAPConstants.SEARCH_SCOPE); + String searchScope = config.getFirst(LDAPConstants.SEARCH_SCOPE); return searchScope == null ? SearchControls.SUBTREE_SCOPE : Integer.parseInt(searchScope); } public String getUuidLDAPAttributeName() { - String uuidAttrName = config.get(LDAPConstants.UUID_LDAP_ATTRIBUTE); + String uuidAttrName = config.getFirst(LDAPConstants.UUID_LDAP_ATTRIBUTE); if (uuidAttrName == null) { // Differences of unique attribute among various vendors String vendor = getVendor(); @@ -129,17 +128,17 @@ public class LDAPConfig { } public boolean isPagination() { - String pagination = config.get(LDAPConstants.PAGINATION); + String pagination = config.getFirst(LDAPConstants.PAGINATION); return Boolean.parseBoolean(pagination); } public int getBatchSizeForSync() { - String pageSizeConfig = config.get(LDAPConstants.BATCH_SIZE_FOR_SYNC); + String pageSizeConfig = config.getFirst(LDAPConstants.BATCH_SIZE_FOR_SYNC); return pageSizeConfig!=null ? Integer.parseInt(pageSizeConfig) : LDAPConstants.DEFAULT_BATCH_SIZE_FOR_SYNC; } public String getUsernameLdapAttribute() { - String username = config.get(LDAPConstants.USERNAME_LDAP_ATTRIBUTE); + String username = config.getFirst(LDAPConstants.USERNAME_LDAP_ATTRIBUTE); if (username == null) { username = isActiveDirectory() ? LDAPConstants.CN : LDAPConstants.UID; } @@ -147,7 +146,7 @@ public class LDAPConfig { } public String getRdnLdapAttribute() { - String rdn = config.get(LDAPConstants.RDN_LDAP_ATTRIBUTE); + String rdn = config.getFirst(LDAPConstants.RDN_LDAP_ATTRIBUTE); if (rdn == null) { rdn = getUsernameLdapAttribute(); @@ -162,7 +161,7 @@ public class LDAPConfig { public String getCustomUserSearchFilter() { - String customFilter = config.get(LDAPConstants.CUSTOM_USER_SEARCH_FILTER); + String customFilter = config.getFirst(LDAPConstants.CUSTOM_USER_SEARCH_FILTER); if (customFilter != null) { customFilter = customFilter.trim(); if (customFilter.length() > 0) { @@ -172,12 +171,12 @@ public class LDAPConfig { return null; } - public UserFederationProvider.EditMode getEditMode() { - String editModeString = config.get(LDAPConstants.EDIT_MODE); + public LDAPStorageProviderFactory.EditMode getEditMode() { + String editModeString = config.getFirst(LDAPConstants.EDIT_MODE); if (editModeString == null) { - return UserFederationProvider.EditMode.READ_ONLY; + return LDAPStorageProviderFactory.EditMode.READ_ONLY; } else { - return UserFederationProvider.EditMode.valueOf(editModeString); + return LDAPStorageProviderFactory.EditMode.valueOf(editModeString); } } } diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPIdentityStoreRegistry.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPIdentityStoreRegistry.java similarity index 77% rename from federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPIdentityStoreRegistry.java rename to federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPIdentityStoreRegistry.java index 7470d4375f..7dc3086ba4 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPIdentityStoreRegistry.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPIdentityStoreRegistry.java @@ -15,14 +15,14 @@ * limitations under the License. */ -package org.keycloak.federation.ldap; +package org.keycloak.storage.ldap; import org.jboss.logging.Logger; -import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore; +import org.keycloak.common.util.MultivaluedHashMap; +import org.keycloak.component.ComponentModel; import org.keycloak.models.LDAPConstants; -import org.keycloak.models.UserFederationProviderModel; +import org.keycloak.storage.ldap.idm.store.ldap.LDAPIdentityStore; -import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -35,13 +35,13 @@ public class LDAPIdentityStoreRegistry { private Map ldapStores = new ConcurrentHashMap(); - public LDAPIdentityStore getLdapStore(UserFederationProviderModel model) { + public LDAPIdentityStore getLdapStore(ComponentModel model) { LDAPIdentityStoreContext context = ldapStores.get(model.getId()); // Ldap config might have changed for the realm. In this case, we must re-initialize - Map config = model.getConfig(); + MultivaluedHashMap config = model.getConfig(); if (context == null || !config.equals(context.config)) { - logLDAPConfig(model.getDisplayName(), config); + logLDAPConfig(model.getName(), config); LDAPIdentityStore store = createLdapIdentityStore(config); context = new LDAPIdentityStoreContext(config, store); @@ -51,8 +51,8 @@ public class LDAPIdentityStoreRegistry { } // Don't log LDAP password - private void logLDAPConfig(String fedProviderDisplayName, Map ldapConfig) { - Map copy = new HashMap(ldapConfig); + private void logLDAPConfig(String fedProviderDisplayName, MultivaluedHashMap ldapConfig) { + MultivaluedHashMap copy = new MultivaluedHashMap(ldapConfig); copy.remove(LDAPConstants.BIND_CREDENTIAL); logger.infof("Creating new LDAP based partition manager for the Federation provider: " + fedProviderDisplayName + ", LDAP Configuration: " + copy); } @@ -61,7 +61,7 @@ public class LDAPIdentityStoreRegistry { * @param ldapConfig from realm * @return PartitionManager instance based on LDAP store */ - public static LDAPIdentityStore createLdapIdentityStore(Map ldapConfig) { + public static LDAPIdentityStore createLdapIdentityStore(MultivaluedHashMap ldapConfig) { LDAPConfig cfg = new LDAPConfig(ldapConfig); checkSystemProperty("com.sun.jndi.ldap.connect.pool.authentication", "none simple"); @@ -84,12 +84,12 @@ public class LDAPIdentityStoreRegistry { private class LDAPIdentityStoreContext { - private LDAPIdentityStoreContext(Map config, LDAPIdentityStore store) { + private LDAPIdentityStoreContext(MultivaluedHashMap config, LDAPIdentityStore store) { this.config = config; this.store = store; } - private Map config; + private MultivaluedHashMap config; private LDAPIdentityStore store; } } diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPStorageProvider.java similarity index 67% rename from federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java rename to federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPStorageProvider.java index 67cfb45fe7..73923bbe22 100755 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPStorageProvider.java @@ -15,24 +15,18 @@ * limitations under the License. */ -package org.keycloak.federation.ldap; +package org.keycloak.storage.ldap; import org.jboss.logging.Logger; import org.keycloak.common.constants.KerberosConstants; +import org.keycloak.component.ComponentModel; +import org.keycloak.credential.CredentialAuthentication; import org.keycloak.credential.CredentialInput; +import org.keycloak.credential.CredentialInputUpdater; +import org.keycloak.credential.CredentialInputValidator; import org.keycloak.credential.CredentialModel; import org.keycloak.federation.kerberos.impl.KerberosUsernamePasswordAuthenticator; import org.keycloak.federation.kerberos.impl.SPNEGOAuthenticator; -import org.keycloak.federation.ldap.idm.model.LDAPObject; -import org.keycloak.federation.ldap.idm.query.Condition; -import org.keycloak.federation.ldap.idm.query.internal.LDAPQuery; -import org.keycloak.federation.ldap.idm.query.internal.LDAPQueryConditionsBuilder; -import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore; -import org.keycloak.federation.ldap.kerberos.LDAPProviderKerberosConfig; -import org.keycloak.federation.ldap.mappers.LDAPFederationMapper; -import org.keycloak.federation.ldap.mappers.LDAPMappersComparator; -import org.keycloak.federation.ldap.mappers.PasswordUpdated; -import org.keycloak.mappers.UserFederationMapper; import org.keycloak.models.CredentialValidationOutput; import org.keycloak.models.GroupModel; import org.keycloak.models.KeycloakSession; @@ -43,11 +37,23 @@ import org.keycloak.models.ModelReadOnlyException; import org.keycloak.models.RealmModel; import org.keycloak.models.RoleModel; import org.keycloak.models.UserCredentialModel; -import org.keycloak.models.UserFederationMapperModel; -import org.keycloak.models.UserFederationProvider; -import org.keycloak.models.UserFederationProviderModel; import org.keycloak.models.UserModel; import org.keycloak.models.UserManager; +import org.keycloak.storage.StorageId; +import org.keycloak.storage.UserStorageProvider; +import org.keycloak.storage.ldap.idm.model.LDAPObject; +import org.keycloak.storage.ldap.idm.query.Condition; +import org.keycloak.storage.ldap.idm.query.internal.LDAPQuery; +import org.keycloak.storage.ldap.idm.query.internal.LDAPQueryConditionsBuilder; +import org.keycloak.storage.ldap.idm.store.ldap.LDAPIdentityStore; +import org.keycloak.storage.ldap.kerberos.LDAPProviderKerberosConfig; +import org.keycloak.storage.ldap.mappers.LDAPMappersComparator; +import org.keycloak.storage.ldap.mappers.LDAPStorageMapper; +import org.keycloak.storage.ldap.mappers.PasswordUpdated; +import org.keycloak.storage.user.ImportedUserValidation; +import org.keycloak.storage.user.UserLookupProvider; +import org.keycloak.storage.user.UserQueryProvider; +import org.keycloak.storage.user.UserRegistrationProvider; import javax.naming.AuthenticationException; import java.util.ArrayList; @@ -65,20 +71,27 @@ import java.util.Set; * @author Bill Burke * @version $Revision: 1 $ */ -public class LDAPFederationProvider implements UserFederationProvider { - private static final Logger logger = Logger.getLogger(LDAPFederationProvider.class); +public class LDAPStorageProvider implements UserStorageProvider, + CredentialInputValidator, + CredentialInputUpdater, + CredentialAuthentication, + UserLookupProvider, + UserRegistrationProvider, + UserQueryProvider, + ImportedUserValidation { + private static final Logger logger = Logger.getLogger(LDAPStorageProvider.class); - protected LDAPFederationProviderFactory factory; + protected LDAPStorageProviderFactory factory; protected KeycloakSession session; - protected UserFederationProviderModel model; + protected ComponentModel model; protected LDAPIdentityStore ldapIdentityStore; - protected EditMode editMode; + protected LDAPStorageProviderFactory.EditMode editMode; protected LDAPProviderKerberosConfig kerberosConfig; protected PasswordUpdated updater; protected final Set supportedCredentialTypes = new HashSet<>(); - public LDAPFederationProvider(LDAPFederationProviderFactory factory, KeycloakSession session, UserFederationProviderModel model, LDAPIdentityStore ldapIdentityStore) { + public LDAPStorageProvider(LDAPStorageProviderFactory factory, KeycloakSession session, ComponentModel model, LDAPIdentityStore ldapIdentityStore) { this.factory = factory; this.session = session; this.model = model; @@ -100,20 +113,20 @@ public class LDAPFederationProvider implements UserFederationProvider { return session; } - public UserFederationProviderModel getModel() { - return model; - } - public LDAPIdentityStore getLdapIdentityStore() { return this.ldapIdentityStore; } - public EditMode getEditMode() { + public LDAPStorageProviderFactory.EditMode getEditMode() { return editMode; } + public ComponentModel getModel() { + return model; + } + @Override - public UserModel validateAndProxy(RealmModel realm, UserModel local) { + public UserModel validate(RealmModel realm, UserModel local) { LDAPObject ldapObject = loadAndValidateUser(realm, local); if (ldapObject == null) { return null; @@ -135,10 +148,10 @@ public class LDAPFederationProvider implements UserFederationProvider { proxied = new UnsyncedLDAPUserModelDelegate(local, this); } - Set federationMappers = realm.getUserFederationMappersByFederationProvider(model.getId()); - List sortedMappers = sortMappersAsc(federationMappers); - for (UserFederationMapperModel mapperModel : sortedMappers) { - LDAPFederationMapper ldapMapper = getMapper(mapperModel); + List mappers = realm.getComponents(model.getId(), LDAPStorageMapper.class.getName()); + List sortedMappers = sortMappersAsc(mappers); + for (ComponentModel mapperModel : sortedMappers) { + LDAPStorageMapper ldapMapper = getMapper(mapperModel); proxied = ldapMapper.proxy(mapperModel, this, ldapObject, proxied, realm); } @@ -146,20 +159,30 @@ public class LDAPFederationProvider implements UserFederationProvider { } @Override - public Set getSupportedCredentialTypes() { - return new HashSet(this.supportedCredentialTypes); + public boolean supportsCredentialAuthenticationFor(String type) { + return type.equals(CredentialModel.KERBEROS) && kerberosConfig.isAllowKerberosAuthentication(); } @Override + public List searchForUserByUserAttribute(String attrName, String attrValue, RealmModel realm) { + return Collections.EMPTY_LIST; + } + + @Override + public void grantToAllUsers(RealmModel realm, RoleModel role) { + + } + public boolean synchronizeRegistrations() { - return "true".equalsIgnoreCase(model.getConfig().get(LDAPConstants.SYNC_REGISTRATIONS)) && editMode == EditMode.WRITABLE; + return "true".equalsIgnoreCase(model.getConfig().getFirst(LDAPConstants.SYNC_REGISTRATIONS)) && editMode == LDAPStorageProviderFactory.EditMode.WRITABLE; } @Override - public UserModel register(RealmModel realm, UserModel user) { - if (editMode == EditMode.READ_ONLY || editMode == EditMode.UNSYNCED) throw new IllegalStateException("Registration is not supported by this ldap server"); + public UserModel addUser(RealmModel realm, String username) { + if (editMode == LDAPStorageProviderFactory.EditMode.READ_ONLY || editMode == LDAPStorageProviderFactory.EditMode.UNSYNCED) throw new IllegalStateException("Registration is not supported by this ldap server"); if (!synchronizeRegistrations()) throw new IllegalStateException("Registration is not supported by this ldap server"); - + UserModel user = session.userLocalStorage().addUser(realm, username); + user.setFederationLink(model.getId()); LDAPObject ldapUser = LDAPUtils.addUserToLDAP(this, realm, user); LDAPUtils.checkUuid(ldapUser, ldapIdentityStore.getConfig()); user.setSingleAttribute(LDAPConstants.LDAP_ID, ldapUser.getUuid()); @@ -170,7 +193,7 @@ public class LDAPFederationProvider implements UserFederationProvider { @Override public boolean removeUser(RealmModel realm, UserModel user) { - if (editMode == EditMode.READ_ONLY || editMode == EditMode.UNSYNCED) { + if (editMode == LDAPStorageProviderFactory.EditMode.READ_ONLY || editMode == LDAPStorageProviderFactory.EditMode.UNSYNCED) { logger.warnf("User '%s' can't be deleted in LDAP as editMode is '%s'. Deleting user just from Keycloak DB, but he will be re-imported from LDAP again once searched in Keycloak", user.getUsername(), editMode.toString()); return true; } @@ -186,13 +209,65 @@ public class LDAPFederationProvider implements UserFederationProvider { } @Override - public List searchByAttributes(Map attributes, RealmModel realm, int maxResults) { + public UserModel getUserById(String id, RealmModel realm) { + StorageId storageId = new StorageId(id); + return getUserByUsername(storageId.getExternalId(), realm); + } + + @Override + public int getUsersCount(RealmModel realm) { + return 0; + } + + @Override + public List getUsers(RealmModel realm) { + return Collections.EMPTY_LIST; + } + + @Override + public List getUsers(RealmModel realm, int firstResult, int maxResults) { + return Collections.EMPTY_LIST; + } + + @Override + public List searchForUser(String search, RealmModel realm) { + return searchForUser(search, realm, 0, Integer.MAX_VALUE - 1); + } + + @Override + public List searchForUser(String search, RealmModel realm, int firstResult, int maxResults) { + Map attributes = new HashMap(); + int spaceIndex = search.lastIndexOf(' '); + if (spaceIndex > -1) { + String firstName = search.substring(0, spaceIndex).trim(); + String lastName = search.substring(spaceIndex).trim(); + attributes.put(UserModel.FIRST_NAME, firstName); + attributes.put(UserModel.LAST_NAME, lastName); + } else if (search.indexOf('@') > -1) { + attributes.put(UserModel.USERNAME, search.trim().toLowerCase()); + attributes.put(UserModel.EMAIL, search.trim().toLowerCase()); + } else { + attributes.put(UserModel.LAST_NAME, search.trim()); + attributes.put(UserModel.USERNAME, search.trim().toLowerCase()); + } + return searchForUser(attributes, realm, firstResult, maxResults); + } + + @Override + public List searchForUser(Map params, RealmModel realm) { + return searchForUser(params, realm, 0, Integer.MAX_VALUE - 1); + } + + @Override + public List searchForUser(Map params, RealmModel realm, int firstResult, int maxResults) { List searchResults =new LinkedList(); - List ldapUsers = searchLDAP(realm, attributes, maxResults); + List ldapUsers = searchLDAP(realm, params, maxResults + firstResult); + int counter = 0; for (LDAPObject ldapUser : ldapUsers) { + if (counter++ < firstResult) continue; String ldapUsername = LDAPUtils.getUsername(ldapUser, this.ldapIdentityStore.getConfig()); - if (session.userStorage().getUserByUsername(ldapUsername, realm) == null) { + if (session.userLocalStorage().getUserByUsername(ldapUsername, realm) == null) { UserModel imported = importUserFromLDAP(session, realm, ldapUser); searchResults.add(imported); } @@ -201,11 +276,17 @@ public class LDAPFederationProvider implements UserFederationProvider { return searchResults; } + @Override + public List getGroupMembers(RealmModel realm, GroupModel group) { + return getGroupMembers(realm, group, 0, Integer.MAX_VALUE - 1); + } + @Override public List getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults) { - Set federationMappers = realm.getUserFederationMappersByFederationProvider(model.getId()); - for (UserFederationMapperModel mapperModel : federationMappers) { - LDAPFederationMapper ldapMapper = getMapper(mapperModel); + List mappers = realm.getComponents(model.getId(), LDAPStorageMapper.class.getName()); + List sortedMappers = sortMappersAsc(mappers); + for (ComponentModel mapperModel : sortedMappers) { + LDAPStorageMapper ldapMapper = getMapper(mapperModel); List users = ldapMapper.getGroupMembers(mapperModel, this, realm, group, firstResult, maxResults); // Sufficient for now @@ -213,7 +294,6 @@ public class LDAPFederationProvider implements UserFederationProvider { return users; } } - return Collections.emptyList(); } @@ -235,30 +315,30 @@ public class LDAPFederationProvider implements UserFederationProvider { protected List searchLDAP(RealmModel realm, Map attributes, int maxResults) { List results = new ArrayList(); - if (attributes.containsKey(USERNAME)) { - LDAPObject user = loadLDAPUserByUsername(realm, attributes.get(USERNAME)); + if (attributes.containsKey(UserModel.USERNAME)) { + LDAPObject user = loadLDAPUserByUsername(realm, attributes.get(UserModel.USERNAME)); if (user != null) { results.add(user); } } - if (attributes.containsKey(EMAIL)) { - LDAPObject user = queryByEmail(realm, attributes.get(EMAIL)); + if (attributes.containsKey(UserModel.EMAIL)) { + LDAPObject user = queryByEmail(realm, attributes.get(UserModel.EMAIL)); if (user != null) { results.add(user); } } - if (attributes.containsKey(FIRST_NAME) || attributes.containsKey(LAST_NAME)) { + if (attributes.containsKey(UserModel.FIRST_NAME) || attributes.containsKey(UserModel.LAST_NAME)) { LDAPQuery ldapQuery = LDAPUtils.createQueryForUserSearch(this, realm); LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder(); // Mapper should replace parameter with correct LDAP mapped attributes - if (attributes.containsKey(FIRST_NAME)) { - ldapQuery.addWhereCondition(conditionsBuilder.equal(FIRST_NAME, attributes.get(FIRST_NAME))); + if (attributes.containsKey(UserModel.FIRST_NAME)) { + ldapQuery.addWhereCondition(conditionsBuilder.equal(UserModel.FIRST_NAME, attributes.get(UserModel.FIRST_NAME))); } - if (attributes.containsKey(LAST_NAME)) { - ldapQuery.addWhereCondition(conditionsBuilder.equal(LAST_NAME, attributes.get(LAST_NAME))); + if (attributes.containsKey(UserModel.LAST_NAME)) { + ldapQuery.addWhereCondition(conditionsBuilder.equal(UserModel.LAST_NAME, attributes.get(UserModel.LAST_NAME))); } List ldapObjects = ldapQuery.getResultList(); @@ -288,12 +368,7 @@ public class LDAPFederationProvider implements UserFederationProvider { } @Override - public boolean isValid(RealmModel realm, UserModel local) { - return loadAndValidateUser(realm, local) != null; - } - - @Override - public UserModel getUserByUsername(RealmModel realm, String username) { + public UserModel getUserByUsername(String username, RealmModel realm) { LDAPObject ldapUser = loadLDAPUserByUsername(realm, username); if (ldapUser == null) { return null; @@ -306,16 +381,16 @@ public class LDAPFederationProvider implements UserFederationProvider { String ldapUsername = LDAPUtils.getUsername(ldapUser, ldapIdentityStore.getConfig()); LDAPUtils.checkUuid(ldapUser, ldapIdentityStore.getConfig()); - UserModel imported = session.userStorage().addUser(realm, ldapUsername); + UserModel imported = session.userLocalStorage().addUser(realm, ldapUsername); imported.setEnabled(true); - Set federationMappers = realm.getUserFederationMappersByFederationProvider(getModel().getId()); - List sortedMappers = sortMappersDesc(federationMappers); - for (UserFederationMapperModel mapperModel : sortedMappers) { + List mappers = realm.getComponents(model.getId(), LDAPStorageMapper.class.getName()); + List sortedMappers = sortMappersDesc(mappers); + for (ComponentModel mapperModel : sortedMappers) { if (logger.isTraceEnabled()) { logger.tracef("Using mapper %s during import user from LDAP", mapperModel); } - LDAPFederationMapper ldapMapper = getMapper(mapperModel); + LDAPStorageMapper ldapMapper = getMapper(mapperModel); ldapMapper.onImportUserFromLDAP(mapperModel, this, ldapUser, imported, realm, true); } @@ -342,7 +417,7 @@ public class LDAPFederationProvider implements UserFederationProvider { @Override - public UserModel getUserByEmail(RealmModel realm, String email) { + public UserModel getUserByEmail(String email, RealmModel realm) { LDAPObject ldapUser = queryByEmail(realm, email); if (ldapUser == null) { return null; @@ -350,7 +425,7 @@ public class LDAPFederationProvider implements UserFederationProvider { // Check here if user already exists String ldapUsername = LDAPUtils.getUsername(ldapUser, ldapIdentityStore.getConfig()); - if (session.userStorage().getUserByUsername(ldapUsername, realm) != null) { + if (session.userLocalStorage().getUserByUsername(ldapUsername, realm) != null) { throw new ModelDuplicateException("User with username '" + ldapUsername + "' already exists in Keycloak. It conflicts with LDAP user with email '" + email + "'"); } @@ -385,15 +460,16 @@ public class LDAPFederationProvider implements UserFederationProvider { ldapIdentityStore.validatePassword(ldapUser, password); return true; } catch (AuthenticationException ae) { - - // Check if any mapper provides callback for handle LDAP AuthenticationException - Set federationMappers = realm.getUserFederationMappersByFederationProvider(getModel().getId()); boolean processed = false; - for (UserFederationMapperModel mapperModel : federationMappers) { - LDAPFederationMapper ldapMapper = getMapper(mapperModel); + List mappers = realm.getComponents(model.getId(), LDAPStorageMapper.class.getName()); + List sortedMappers = sortMappersDesc(mappers); + for (ComponentModel mapperModel : sortedMappers) { + if (logger.isTraceEnabled()) { + logger.tracef("Using mapper %s during import user from LDAP", mapperModel); + } + LDAPStorageMapper ldapMapper = getMapper(mapperModel); processed = processed || ldapMapper.onAuthenticationFailure(mapperModel, this, ldapUser, user, ae, realm); } - return processed; } } @@ -403,10 +479,10 @@ public class LDAPFederationProvider implements UserFederationProvider { @Override public boolean updateCredential(RealmModel realm, UserModel user, CredentialInput input) { if (!CredentialModel.PASSWORD.equals(input.getType()) || ! (input instanceof UserCredentialModel)) return false; - if (editMode == EditMode.READ_ONLY) { + if (editMode == LDAPStorageProviderFactory.EditMode.READ_ONLY) { throw new ModelReadOnlyException("Federated storage is not writable"); - } else if (editMode == EditMode.WRITABLE) { + } else if (editMode == LDAPStorageProviderFactory.EditMode.WRITABLE) { LDAPIdentityStore ldapIdentityStore = getLdapIdentityStore(); UserCredentialModel cred = (UserCredentialModel)input; String password = cred.getValue(); @@ -429,6 +505,11 @@ public class LDAPFederationProvider implements UserFederationProvider { return Collections.EMPTY_SET; } + public Set getSupportedCredentialTypes() { + return new HashSet(this.supportedCredentialTypes); + } + + @Override public boolean supportsCredentialType(String credentialType) { return getSupportedCredentialTypes().contains(credentialType); @@ -450,7 +531,9 @@ public class LDAPFederationProvider implements UserFederationProvider { } @Override - public CredentialValidationOutput validCredentials(RealmModel realm, UserCredentialModel credential) { + public CredentialValidationOutput authenticate(RealmModel realm, CredentialInput cred) { + if (!(cred instanceof UserCredentialModel)) CredentialValidationOutput.failed(); + UserCredentialModel credential = (UserCredentialModel)cred; if (credential.getType().equals(UserCredentialModel.KERBEROS)) { if (kerberosConfig.isAllowKerberosAuthentication()) { String spnegoToken = credential.getValue(); @@ -467,7 +550,7 @@ public class LDAPFederationProvider implements UserFederationProvider { UserModel user = findOrCreateAuthenticatedUser(realm, username); if (user == null) { - logger.warnf("Kerberos/SPNEGO authentication succeeded with username [%s], but couldn't find or create user with federation provider [%s]", username, model.getDisplayName()); + logger.warnf("Kerberos/SPNEGO authentication succeeded with username [%s], but couldn't find or create user with federation provider [%s]", username, model.getName()); return CredentialValidationOutput.failed(); } else { String delegationCredential = spnegoAuthenticator.getSerializedDelegationCredential(); @@ -499,11 +582,11 @@ public class LDAPFederationProvider implements UserFederationProvider { * @return finded or newly created user */ protected UserModel findOrCreateAuthenticatedUser(RealmModel realm, String username) { - UserModel user = session.userStorage().getUserByUsername(username, realm); + UserModel user = session.userLocalStorage().getUserByUsername(username, realm); if (user != null) { logger.debugf("Kerberos authenticated user [%s] found in Keycloak storage", username); if (!model.getId().equals(user.getFederationLink())) { - logger.warnf("User with username [%s] already exists, but is not linked to provider [%s]", username, model.getDisplayName()); + logger.warnf("User with username [%s] already exists, but is not linked to provider [%s]", username, model.getName()); return null; } else { LDAPObject ldapObject = loadAndValidateUser(realm, user); @@ -511,16 +594,17 @@ public class LDAPFederationProvider implements UserFederationProvider { return proxy(realm, user, ldapObject); } else { logger.warnf("User with username [%s] aready exists and is linked to provider [%s] but is not valid. Stale LDAP_ID on local user is: %s", - username, model.getDisplayName(), user.getFirstAttribute(LDAPConstants.LDAP_ID)); + username, model.getName(), user.getFirstAttribute(LDAPConstants.LDAP_ID)); logger.warn("Will re-create user"); - new UserManager(session).removeUser(realm, user, session.userStorage()); + session.getUserCache().evict(realm, user); + new UserManager(session).removeUser(realm, user, session.userLocalStorage()); } } } // Creating user to local storage logger.debugf("Kerberos authenticated user [%s] not in Keycloak storage. Creating him", username); - return getUserByUsername(realm, username); + return getUserByUsername(username, realm); } public LDAPObject loadLDAPUserByUsername(RealmModel realm, String username) { @@ -539,21 +623,23 @@ public class LDAPFederationProvider implements UserFederationProvider { return ldapUser; } - public LDAPFederationMapper getMapper(UserFederationMapperModel mapperModel) { - LDAPFederationMapper ldapMapper = (LDAPFederationMapper) getSession().getProvider(UserFederationMapper.class, mapperModel.getFederationMapperType()); + public LDAPStorageMapper getMapper(ComponentModel mapperModel) { + LDAPStorageMapper ldapMapper = (LDAPStorageMapper) getSession().getProvider(LDAPStorageMapper.class, mapperModel); if (ldapMapper == null) { - throw new ModelException("Can't find mapper type with ID: " + mapperModel.getFederationMapperType()); + throw new ModelException("Can't find mapper type with ID: " + mapperModel.getProviderId()); } return ldapMapper; } - public List sortMappersAsc(Collection mappers) { + public List sortMappersAsc(Collection mappers) { return LDAPMappersComparator.sortAsc(getLdapIdentityStore().getConfig(), mappers); } - protected List sortMappersDesc(Collection mappers) { + protected List sortMappersDesc(Collection mappers) { return LDAPMappersComparator.sortDesc(getLdapIdentityStore().getConfig(), mappers); } + + } diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPStorageProviderFactory.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPStorageProviderFactory.java new file mode 100755 index 0000000000..2401b49b21 --- /dev/null +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPStorageProviderFactory.java @@ -0,0 +1,545 @@ +/* + * 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; + +import org.jboss.logging.Logger; +import org.keycloak.Config; +import org.keycloak.common.constants.KerberosConstants; +import org.keycloak.component.ComponentModel; +import org.keycloak.component.ComponentValidationException; +import org.keycloak.federation.kerberos.CommonKerberosConfig; +import org.keycloak.federation.kerberos.impl.KerberosServerSubjectAuthenticator; +import org.keycloak.federation.kerberos.impl.KerberosUsernamePasswordAuthenticator; +import org.keycloak.federation.kerberos.impl.SPNEGOAuthenticator; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.models.KeycloakSessionTask; +import org.keycloak.models.LDAPConstants; +import org.keycloak.models.ModelException; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; +import org.keycloak.models.utils.KeycloakModelUtils; +import org.keycloak.provider.ProviderConfigProperty; +import org.keycloak.provider.ProviderConfigurationBuilder; +import org.keycloak.storage.UserStorageProvider; +import org.keycloak.storage.UserStorageProviderFactory; +import org.keycloak.storage.UserStorageProviderModel; +import org.keycloak.storage.ldap.idm.model.LDAPObject; +import org.keycloak.storage.ldap.idm.query.Condition; +import org.keycloak.storage.ldap.idm.query.internal.LDAPQuery; +import org.keycloak.storage.ldap.idm.query.internal.LDAPQueryConditionsBuilder; +import org.keycloak.storage.ldap.idm.store.ldap.LDAPIdentityStore; +import org.keycloak.storage.ldap.mappers.FullNameLDAPStorageMapper; +import org.keycloak.storage.ldap.mappers.FullNameLDAPStorageMapperFactory; +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.msad.MSADUserAccountControlStorageMapperFactory; +import org.keycloak.storage.user.ImportSynchronization; +import org.keycloak.storage.user.SynchronizationResult; + +import java.util.Date; +import java.util.List; + +/** + * @author Marek Posolda + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class LDAPStorageProviderFactory implements UserStorageProviderFactory, ImportSynchronization { + + /** + * Optional type that can be by implementations to describe edit mode of federation storage + * + */ + public enum EditMode { + /** + * federation storage is read-only + */ + READ_ONLY, + /** + * federation storage is writable + * + */ + WRITABLE, + /** + * updates to user are stored locally and not synced with federation storage. + * + */ + UNSYNCED + } + + + private static final Logger logger = Logger.getLogger(LDAPStorageProviderFactory.class); + public static final String PROVIDER_NAME = LDAPConstants.LDAP_PROVIDER; + + private LDAPIdentityStoreRegistry ldapStoreRegistry; + + protected static final List configProperties; + + static { + configProperties = getConfigProps(null); + } + + private static List getConfigProps(ComponentModel parent) { + boolean readOnly = false; + if (parent != null) { + LDAPConfig config = new LDAPConfig(parent.getConfig()); + readOnly = config.getEditMode() != LDAPStorageProviderFactory.EditMode.WRITABLE; + } + + + return ProviderConfigurationBuilder.create() + .property().name(LDAPConstants.EDIT_MODE) + .type(ProviderConfigProperty.STRING_TYPE) + .add() + .property().name(LDAPConstants.SYNC_REGISTRATIONS) + .type(ProviderConfigProperty.BOOLEAN_TYPE) + .defaultValue("false") + .add() + .property().name(LDAPConstants.VENDOR) + .type(ProviderConfigProperty.STRING_TYPE) + .add() + .property().name(LDAPConstants.USERNAME_LDAP_ATTRIBUTE) + .type(ProviderConfigProperty.STRING_TYPE) + .add() + .property().name(LDAPConstants.RDN_LDAP_ATTRIBUTE) + .type(ProviderConfigProperty.STRING_TYPE) + .add() + .property().name(LDAPConstants.UUID_LDAP_ATTRIBUTE) + .type(ProviderConfigProperty.STRING_TYPE) + .add() + .property().name(LDAPConstants.USER_OBJECT_CLASSES) + .type(ProviderConfigProperty.STRING_TYPE) + .add() + .property().name(LDAPConstants.CONNECTION_URL) + .type(ProviderConfigProperty.STRING_TYPE) + .add() + .property().name(LDAPConstants.USERS_DN) + .type(ProviderConfigProperty.STRING_TYPE) + .add() + .property().name(LDAPConstants.AUTH_TYPE) + .type(ProviderConfigProperty.STRING_TYPE) + .defaultValue("simple") + .add() + .property().name(LDAPConstants.BIND_DN) + .type(ProviderConfigProperty.STRING_TYPE) + .add() + .property().name(LDAPConstants.BIND_CREDENTIAL) + .type(ProviderConfigProperty.PASSWORD) + .secret(true) + .add() + .property().name(LDAPConstants.CUSTOM_USER_SEARCH_FILTER) + .type(ProviderConfigProperty.STRING_TYPE) + .add() + .property().name(LDAPConstants.SEARCH_SCOPE) + .type(ProviderConfigProperty.STRING_TYPE) + .defaultValue("1") + .add() + .property().name(LDAPConstants.USE_TRUSTSTORE_SPI) + .type(ProviderConfigProperty.STRING_TYPE) + .defaultValue("ldapsOnly") + .add() + .property().name(LDAPConstants.CONNECTION_POOLING) + .type(ProviderConfigProperty.BOOLEAN_TYPE) + .defaultValue("true") + .add() + .property().name(LDAPConstants.PAGINATION) + .type(ProviderConfigProperty.BOOLEAN_TYPE) + .defaultValue("true") + .add() + .property().name(KerberosConstants.ALLOW_KERBEROS_AUTHENTICATION) + .type(ProviderConfigProperty.BOOLEAN_TYPE) + .defaultValue("false") + .add() + .property().name(KerberosConstants.SERVER_PRINCIPAL) + .type(ProviderConfigProperty.STRING_TYPE) + .add() + .property().name(KerberosConstants.KEYTAB) + .type(ProviderConfigProperty.STRING_TYPE) + .add() + .property().name(KerberosConstants.KERBEROS_REALM) + .type(ProviderConfigProperty.STRING_TYPE) + .add() + .property().name(KerberosConstants.DEBUG) + .type(ProviderConfigProperty.BOOLEAN_TYPE) + .defaultValue("false") + .add() + .property().name(KerberosConstants.USE_KERBEROS_FOR_PASSWORD_AUTHENTICATION) + .type(ProviderConfigProperty.BOOLEAN_TYPE) + .defaultValue("false") + .add() + .property().name(KerberosConstants.SERVER_PRINCIPAL) + .type(ProviderConfigProperty.STRING_TYPE) + .add() + .build(); + } + + @Override + public List getConfigProperties() { + return configProperties; + } + + @Override + public LDAPStorageProvider create(KeycloakSession session, ComponentModel model) { + LDAPIdentityStore ldapIdentityStore = this.ldapStoreRegistry.getLdapStore(model); + return new LDAPStorageProvider(this, session, model, ldapIdentityStore); + } + + @Override + public void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel config) throws ComponentValidationException { + LDAPConfig cfg = new LDAPConfig(config.getConfig()); + String customFilter = cfg.getCustomUserSearchFilter(); + LDAPUtils.validateCustomLdapFilter(customFilter); + } + + @Override + public void init(Config.Scope config) { + this.ldapStoreRegistry = new LDAPIdentityStoreRegistry(); + } + + @Override + public void close() { + this.ldapStoreRegistry = null; + } + + @Override + public String getId() { + return PROVIDER_NAME; + } + + // Best effort to create appropriate mappers according to our LDAP config + @Override + public void onCreate(KeycloakSession session, RealmModel realm, ComponentModel model) { + LDAPConfig ldapConfig = new LDAPConfig(model.getConfig()); + + boolean activeDirectory = ldapConfig.isActiveDirectory(); + EditMode editMode = ldapConfig.getEditMode(); + String readOnly = String.valueOf(editMode == EditMode.READ_ONLY || editMode == EditMode.UNSYNCED); + String usernameLdapAttribute = ldapConfig.getUsernameLdapAttribute(); + + String alwaysReadValueFromLDAP = String.valueOf(editMode==EditMode.READ_ONLY || editMode== EditMode.WRITABLE); + + ComponentModel mapperModel; + mapperModel = KeycloakModelUtils.createComponentModel("username", model.getId(), UserAttributeLDAPStorageMapperFactory.PROVIDER_ID, LDAPStorageMapper.class.getName(), + UserAttributeLDAPStorageMapper.USER_MODEL_ATTRIBUTE, UserModel.USERNAME, + UserAttributeLDAPStorageMapper.LDAP_ATTRIBUTE, usernameLdapAttribute, + UserAttributeLDAPStorageMapper.READ_ONLY, readOnly, + UserAttributeLDAPStorageMapper.ALWAYS_READ_VALUE_FROM_LDAP, "false", + UserAttributeLDAPStorageMapper.IS_MANDATORY_IN_LDAP, "true"); + realm.addComponentModel(mapperModel); + + // CN is typically used as RDN for Active Directory deployments + if (ldapConfig.getRdnLdapAttribute().equalsIgnoreCase(LDAPConstants.CN)) { + + if (usernameLdapAttribute.equalsIgnoreCase(LDAPConstants.CN)) { + + // For AD deployments with "cn" as username, we will map "givenName" to first name + mapperModel = KeycloakModelUtils.createComponentModel("first name", model.getId(), UserAttributeLDAPStorageMapperFactory.PROVIDER_ID,LDAPStorageMapper.class.getName(), + UserAttributeLDAPStorageMapper.USER_MODEL_ATTRIBUTE, UserModel.FIRST_NAME, + UserAttributeLDAPStorageMapper.LDAP_ATTRIBUTE, LDAPConstants.GIVENNAME, + UserAttributeLDAPStorageMapper.READ_ONLY, readOnly, + UserAttributeLDAPStorageMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP, + UserAttributeLDAPStorageMapper.IS_MANDATORY_IN_LDAP, "true"); + realm.addComponentModel(mapperModel); + + } else { + if (editMode == EditMode.WRITABLE) { + + // For AD deployments with "sAMAccountName" as username and writable, we need to map "cn" as username as well (this is needed so we can register new users from KC into LDAP) and we will map "givenName" to first name. + mapperModel = KeycloakModelUtils.createComponentModel("first name", model.getId(), UserAttributeLDAPStorageMapperFactory.PROVIDER_ID,LDAPStorageMapper.class.getName(), + UserAttributeLDAPStorageMapper.USER_MODEL_ATTRIBUTE, UserModel.FIRST_NAME, + UserAttributeLDAPStorageMapper.LDAP_ATTRIBUTE, LDAPConstants.GIVENNAME, + UserAttributeLDAPStorageMapper.READ_ONLY, readOnly, + UserAttributeLDAPStorageMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP, + UserAttributeLDAPStorageMapper.IS_MANDATORY_IN_LDAP, "true"); + realm.addComponentModel(mapperModel); + + mapperModel = KeycloakModelUtils.createComponentModel("username-cn", model.getId(), UserAttributeLDAPStorageMapperFactory.PROVIDER_ID,LDAPStorageMapper.class.getName(), + UserAttributeLDAPStorageMapper.USER_MODEL_ATTRIBUTE, UserModel.USERNAME, + UserAttributeLDAPStorageMapper.LDAP_ATTRIBUTE, LDAPConstants.CN, + UserAttributeLDAPStorageMapper.READ_ONLY, readOnly, + UserAttributeLDAPStorageMapper.ALWAYS_READ_VALUE_FROM_LDAP, "false", + UserAttributeLDAPStorageMapper.IS_MANDATORY_IN_LDAP, "true"); + realm.addComponentModel(mapperModel); + } else { + + // For read-only LDAP, we map "cn" as full name + mapperModel = KeycloakModelUtils.createComponentModel("full name", model.getId(), FullNameLDAPStorageMapperFactory.PROVIDER_ID,LDAPStorageMapper.class.getName(), + FullNameLDAPStorageMapper.LDAP_FULL_NAME_ATTRIBUTE, LDAPConstants.CN, + FullNameLDAPStorageMapper.READ_ONLY, readOnly, + FullNameLDAPStorageMapper.WRITE_ONLY, "false"); + realm.addComponentModel(mapperModel); + } + } + } else { + mapperModel = KeycloakModelUtils.createComponentModel("first name", model.getId(), UserAttributeLDAPStorageMapperFactory.PROVIDER_ID,LDAPStorageMapper.class.getName(), + UserAttributeLDAPStorageMapper.USER_MODEL_ATTRIBUTE, UserModel.FIRST_NAME, + UserAttributeLDAPStorageMapper.LDAP_ATTRIBUTE, LDAPConstants.CN, + UserAttributeLDAPStorageMapper.READ_ONLY, readOnly, + UserAttributeLDAPStorageMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP, + UserAttributeLDAPStorageMapper.IS_MANDATORY_IN_LDAP, "true"); + realm.addComponentModel(mapperModel); + } + + mapperModel = KeycloakModelUtils.createComponentModel("last name", model.getId(), UserAttributeLDAPStorageMapperFactory.PROVIDER_ID,LDAPStorageMapper.class.getName(), + UserAttributeLDAPStorageMapper.USER_MODEL_ATTRIBUTE, UserModel.LAST_NAME, + UserAttributeLDAPStorageMapper.LDAP_ATTRIBUTE, LDAPConstants.SN, + UserAttributeLDAPStorageMapper.READ_ONLY, readOnly, + UserAttributeLDAPStorageMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP, + UserAttributeLDAPStorageMapper.IS_MANDATORY_IN_LDAP, "true"); + realm.addComponentModel(mapperModel); + + mapperModel = KeycloakModelUtils.createComponentModel("email", model.getId(), UserAttributeLDAPStorageMapperFactory.PROVIDER_ID,LDAPStorageMapper.class.getName(), + UserAttributeLDAPStorageMapper.USER_MODEL_ATTRIBUTE, UserModel.EMAIL, + UserAttributeLDAPStorageMapper.LDAP_ATTRIBUTE, LDAPConstants.EMAIL, + UserAttributeLDAPStorageMapper.READ_ONLY, readOnly, + UserAttributeLDAPStorageMapper.ALWAYS_READ_VALUE_FROM_LDAP, "false", + UserAttributeLDAPStorageMapper.IS_MANDATORY_IN_LDAP, "false"); + realm.addComponentModel(mapperModel); + + String createTimestampLdapAttrName = activeDirectory ? "whenCreated" : LDAPConstants.CREATE_TIMESTAMP; + String modifyTimestampLdapAttrName = activeDirectory ? "whenChanged" : LDAPConstants.MODIFY_TIMESTAMP; + + // map createTimeStamp as read-only + mapperModel = KeycloakModelUtils.createComponentModel("creation date", model.getId(), UserAttributeLDAPStorageMapperFactory.PROVIDER_ID,LDAPStorageMapper.class.getName(), + UserAttributeLDAPStorageMapper.USER_MODEL_ATTRIBUTE, LDAPConstants.CREATE_TIMESTAMP, + UserAttributeLDAPStorageMapper.LDAP_ATTRIBUTE, createTimestampLdapAttrName, + UserAttributeLDAPStorageMapper.READ_ONLY, "true", + UserAttributeLDAPStorageMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP, + UserAttributeLDAPStorageMapper.IS_MANDATORY_IN_LDAP, "false"); + realm.addComponentModel(mapperModel); + + // map modifyTimeStamp as read-only + mapperModel = KeycloakModelUtils.createComponentModel("modify date", model.getId(), UserAttributeLDAPStorageMapperFactory.PROVIDER_ID,LDAPStorageMapper.class.getName(), + UserAttributeLDAPStorageMapper.USER_MODEL_ATTRIBUTE, LDAPConstants.MODIFY_TIMESTAMP, + UserAttributeLDAPStorageMapper.LDAP_ATTRIBUTE, modifyTimestampLdapAttrName, + UserAttributeLDAPStorageMapper.READ_ONLY, "true", + UserAttributeLDAPStorageMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP, + UserAttributeLDAPStorageMapper.IS_MANDATORY_IN_LDAP, "false"); + realm.addComponentModel(mapperModel); + + // MSAD specific mapper for account state propagation + if (activeDirectory) { + mapperModel = KeycloakModelUtils.createComponentModel("MSAD account controls", model.getId(), MSADUserAccountControlStorageMapperFactory.PROVIDER_ID,LDAPStorageMapper.class.getName()); + realm.addComponentModel(mapperModel); + } + } + + @Override + public SynchronizationResult sync(KeycloakSessionFactory sessionFactory, String realmId, UserStorageProviderModel model) { + syncMappers(sessionFactory, realmId, model); + + logger.infof("Sync all users from LDAP to local store: realm: %s, federation provider: %s", realmId, model.getName()); + + LDAPQuery userQuery = createQuery(sessionFactory, realmId, model); + SynchronizationResult syncResult = syncImpl(sessionFactory, userQuery, realmId, model); + + // TODO: Remove all existing keycloak users, which have federation links, but are not in LDAP. Perhaps don't check users, which were just added or updated during this sync? + + logger.infof("Sync all users finished: %s", syncResult.getStatus()); + return syncResult; + } + + @Override + public SynchronizationResult syncSince(Date lastSync, KeycloakSessionFactory sessionFactory, String realmId, UserStorageProviderModel model) { + syncMappers(sessionFactory, realmId, model); + + logger.infof("Sync changed users from LDAP to local store: realm: %s, federation provider: %s, last sync time: " + lastSync, realmId, model.getName()); + + // Sync newly created and updated users + LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder(); + Condition createCondition = conditionsBuilder.greaterThanOrEqualTo(LDAPConstants.CREATE_TIMESTAMP, lastSync); + Condition modifyCondition = conditionsBuilder.greaterThanOrEqualTo(LDAPConstants.MODIFY_TIMESTAMP, lastSync); + Condition orCondition = conditionsBuilder.orCondition(createCondition, modifyCondition); + + LDAPQuery userQuery = createQuery(sessionFactory, realmId, model); + userQuery.addWhereCondition(orCondition); + SynchronizationResult result = syncImpl(sessionFactory, userQuery, realmId, model); + + logger.infof("Sync changed users finished: %s", result.getStatus()); + return result; + } + + protected void syncMappers(KeycloakSessionFactory sessionFactory, final String realmId, final ComponentModel model) { + KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() { + + @Override + public void run(KeycloakSession session) { + LDAPStorageProvider ldapProvider = (LDAPStorageProvider)session.getProvider(UserStorageProvider.class, model); + RealmModel realm = session.realms().getRealm(realmId); + List mappers = realm.getComponents(model.getId(), LDAPStorageMapper.class.getName()); + for (ComponentModel mapperModel : mappers) { + LDAPStorageMapper ldapMapper = session.getProvider(LDAPStorageMapper.class, mapperModel); + SynchronizationResult syncResult = ldapMapper.syncDataFromFederationProviderToKeycloak(mapperModel, ldapProvider, session, realm); + if (syncResult.getAdded() > 0 || syncResult.getUpdated() > 0 || syncResult.getRemoved() > 0 || syncResult.getFailed() > 0) { + logger.infof("Sync of federation mapper '%s' finished. Status: %s", mapperModel.getName(), syncResult.toString()); + } + } + } + + }); + } + + protected SynchronizationResult syncImpl(KeycloakSessionFactory sessionFactory, LDAPQuery userQuery, final String realmId, final ComponentModel fedModel) { + + final SynchronizationResult syncResult = new SynchronizationResult(); + + LDAPConfig ldapConfig = new LDAPConfig(fedModel.getConfig()); + boolean pagination = ldapConfig.isPagination(); + if (pagination) { + int pageSize = ldapConfig.getBatchSizeForSync(); + + boolean nextPage = true; + while (nextPage) { + userQuery.setLimit(pageSize); + final List users = userQuery.getResultList(); + nextPage = userQuery.getPaginationContext() != null; + SynchronizationResult currentPageSync = importLdapUsers(sessionFactory, realmId, fedModel, users); + syncResult.add(currentPageSync); + } + } else { + // LDAP pagination not available. Do everything in single transaction + final List users = userQuery.getResultList(); + SynchronizationResult currentSync = importLdapUsers(sessionFactory, realmId, fedModel, users); + syncResult.add(currentSync); + } + + return syncResult; + } + + private LDAPQuery createQuery(KeycloakSessionFactory sessionFactory, final String realmId, final ComponentModel model) { + class QueryHolder { + LDAPQuery query; + } + + final QueryHolder queryHolder = new QueryHolder(); + KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() { + + @Override + public void run(KeycloakSession session) { + LDAPStorageProvider ldapFedProvider = (LDAPStorageProvider)session.getProvider(UserStorageProvider.class, model); + RealmModel realm = session.realms().getRealm(realmId); + queryHolder.query = LDAPUtils.createQueryForUserSearch(ldapFedProvider, realm); + } + + }); + return queryHolder.query; + } + + protected SynchronizationResult importLdapUsers(KeycloakSessionFactory sessionFactory, final String realmId, final ComponentModel fedModel, List ldapUsers) { + final SynchronizationResult syncResult = new SynchronizationResult(); + + class BooleanHolder { + private boolean value = true; + } + final BooleanHolder exists = new BooleanHolder(); + + for (final LDAPObject ldapUser : ldapUsers) { + + try { + + // Process each user in it's own transaction to avoid global fail + KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() { + + @Override + public void run(KeycloakSession session) { + LDAPStorageProvider ldapFedProvider = (LDAPStorageProvider)session.getProvider(UserStorageProvider.class, fedModel); + RealmModel currentRealm = session.realms().getRealm(realmId); + + String username = LDAPUtils.getUsername(ldapUser, ldapFedProvider.getLdapIdentityStore().getConfig()); + exists.value = true; + LDAPUtils.checkUuid(ldapUser, ldapFedProvider.getLdapIdentityStore().getConfig()); + UserModel currentUser = session.userLocalStorage().getUserByUsername(username, currentRealm); + + if (currentUser == null) { + + // Add new user to Keycloak + exists.value = false; + ldapFedProvider.importUserFromLDAP(session, currentRealm, ldapUser); + syncResult.increaseAdded(); + + } else { + if ((fedModel.getId().equals(currentUser.getFederationLink())) && (ldapUser.getUuid().equals(currentUser.getFirstAttribute(LDAPConstants.LDAP_ID)))) { + + // Update keycloak user + List federationMappers = currentRealm.getComponents(fedModel.getId(), LDAPStorageMapper.class.getName()); + List sortedMappers = ldapFedProvider.sortMappersDesc(federationMappers); + for (ComponentModel mapperModel : sortedMappers) { + LDAPStorageMapper ldapMapper = ldapFedProvider.getMapper(mapperModel); + ldapMapper.onImportUserFromLDAP(mapperModel, ldapFedProvider, ldapUser, currentUser, currentRealm, false); + } + + logger.debugf("Updated user from LDAP: %s", currentUser.getUsername()); + syncResult.increaseUpdated(); + } else { + logger.warnf("User '%s' is not updated during sync as he already exists in Keycloak database but is not linked to federation provider '%s'", username, fedModel.getName()); + syncResult.increaseFailed(); + } + } + } + + }); + } catch (ModelException me) { + logger.error("Failed during import user from LDAP", me); + syncResult.increaseFailed(); + + // Remove user if we already added him during this transaction + if (!exists.value) { + KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() { + + @Override + public void run(KeycloakSession session) { + LDAPStorageProvider ldapFedProvider = (LDAPStorageProvider)session.getProvider(UserStorageProvider.class, fedModel); + RealmModel currentRealm = session.realms().getRealm(realmId); + String username = null; + try { + username = LDAPUtils.getUsername(ldapUser, ldapFedProvider.getLdapIdentityStore().getConfig()); + } catch (ModelException ignore) { + } + + if (username != null) { + UserModel existing = session.userLocalStorage().getUserByUsername(username, currentRealm); + if (existing != null) { + session.getUserCache().evict(currentRealm, existing); + session.userLocalStorage().removeUser(currentRealm, existing); + } + } + } + + }); + } + } + } + + return syncResult; + } + + protected SPNEGOAuthenticator createSPNEGOAuthenticator(String spnegoToken, CommonKerberosConfig kerberosConfig) { + KerberosServerSubjectAuthenticator kerberosAuth = createKerberosSubjectAuthenticator(kerberosConfig); + return new SPNEGOAuthenticator(kerberosConfig, kerberosAuth, spnegoToken); + } + + protected KerberosServerSubjectAuthenticator createKerberosSubjectAuthenticator(CommonKerberosConfig kerberosConfig) { + return new KerberosServerSubjectAuthenticator(kerberosConfig); + } + + protected KerberosUsernamePasswordAuthenticator createKerberosUsernamePasswordAuthenticator(CommonKerberosConfig kerberosConfig) { + return new KerberosUsernamePasswordAuthenticator(kerberosConfig); + } +} diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPUtils.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPUtils.java similarity index 84% rename from federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPUtils.java rename to federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPUtils.java index 0c20366573..08f00a80a7 100755 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPUtils.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPUtils.java @@ -15,22 +15,23 @@ * limitations under the License. */ -package org.keycloak.federation.ldap; +package org.keycloak.storage.ldap; -import org.keycloak.federation.ldap.idm.model.LDAPDn; -import org.keycloak.federation.ldap.idm.model.LDAPObject; -import org.keycloak.federation.ldap.idm.query.Condition; -import org.keycloak.federation.ldap.idm.query.internal.LDAPQuery; -import org.keycloak.federation.ldap.idm.query.internal.LDAPQueryConditionsBuilder; -import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore; -import org.keycloak.federation.ldap.mappers.LDAPFederationMapper; -import org.keycloak.federation.ldap.mappers.membership.MembershipType; +import org.keycloak.component.ComponentModel; +import org.keycloak.component.ComponentValidationException; import org.keycloak.mappers.FederationConfigValidationException; import org.keycloak.models.LDAPConstants; import org.keycloak.models.ModelException; import org.keycloak.models.RealmModel; -import org.keycloak.models.UserFederationMapperModel; import org.keycloak.models.UserModel; +import org.keycloak.storage.ldap.idm.model.LDAPDn; +import org.keycloak.storage.ldap.idm.model.LDAPObject; +import org.keycloak.storage.ldap.idm.query.Condition; +import org.keycloak.storage.ldap.idm.query.internal.LDAPQuery; +import org.keycloak.storage.ldap.idm.query.internal.LDAPQueryConditionsBuilder; +import org.keycloak.storage.ldap.idm.store.ldap.LDAPIdentityStore; +import org.keycloak.storage.ldap.mappers.LDAPStorageMapper; +import org.keycloak.storage.ldap.mappers.membership.MembershipType; import java.util.Collection; import java.util.HashSet; @@ -52,7 +53,7 @@ public class LDAPUtils { * @param user * @return newly created LDAPObject with all the attributes, uuid and DN properly set */ - public static LDAPObject addUserToLDAP(LDAPFederationProvider ldapProvider, RealmModel realm, UserModel user) { + public static LDAPObject addUserToLDAP(LDAPStorageProvider ldapProvider, RealmModel realm, UserModel user) { LDAPObject ldapUser = new LDAPObject(); LDAPIdentityStore ldapStore = ldapProvider.getLdapIdentityStore(); @@ -60,10 +61,10 @@ public class LDAPUtils { ldapUser.setRdnAttributeName(ldapConfig.getRdnLdapAttribute()); ldapUser.setObjectClasses(ldapConfig.getUserObjectClasses()); - Set federationMappers = realm.getUserFederationMappersByFederationProvider(ldapProvider.getModel().getId()); - List sortedMappers = ldapProvider.sortMappersAsc(federationMappers); - for (UserFederationMapperModel mapperModel : sortedMappers) { - LDAPFederationMapper ldapMapper = ldapProvider.getMapper(mapperModel); + List federationMappers = realm.getComponents(ldapProvider.getModel().getId(), LDAPStorageMapper.class.getName()); + List sortedMappers = ldapProvider.sortMappersAsc(federationMappers); + for (ComponentModel mapperModel : sortedMappers) { + LDAPStorageMapper ldapMapper = ldapProvider.getMapper(mapperModel); ldapMapper.onRegisterUserToLDAP(mapperModel, ldapProvider, ldapUser, user, realm); } @@ -72,7 +73,7 @@ public class LDAPUtils { return ldapUser; } - public static LDAPQuery createQueryForUserSearch(LDAPFederationProvider ldapProvider, RealmModel realm) { + public static LDAPQuery createQueryForUserSearch(LDAPStorageProvider ldapProvider, RealmModel realm) { LDAPQuery ldapQuery = new LDAPQuery(ldapProvider); LDAPConfig config = ldapProvider.getLdapIdentityStore().getConfig(); ldapQuery.setSearchScope(config.getSearchScope()); @@ -85,7 +86,7 @@ public class LDAPUtils { ldapQuery.addWhereCondition(customFilterCondition); } - Set mapperModels = realm.getUserFederationMappersByFederationProvider(ldapProvider.getModel().getId()); + List mapperModels = realm.getComponents(ldapProvider.getModel().getId(), LDAPStorageMapper.class.getName()); ldapQuery.addMappers(mapperModels); return ldapQuery; @@ -127,7 +128,7 @@ public class LDAPUtils { // roles & groups - public static LDAPObject createLDAPGroup(LDAPFederationProvider ldapProvider, String groupName, String groupNameAttribute, Collection objectClasses, + public static LDAPObject createLDAPGroup(LDAPStorageProvider ldapProvider, String groupName, String groupNameAttribute, Collection objectClasses, String parentDn, Map> additionalAttributes) { LDAPObject ldapObject = new LDAPObject(); @@ -157,7 +158,7 @@ public class LDAPUtils { * @param ldapChild usually user (or child group or child role) * @param sendLDAPUpdateRequest if true, the method will send LDAP update request too. Otherwise it will skip it */ - public static void addMember(LDAPFederationProvider ldapProvider, MembershipType membershipType, String memberAttrName, LDAPObject ldapParent, LDAPObject ldapChild, boolean sendLDAPUpdateRequest) { + public static void addMember(LDAPStorageProvider ldapProvider, MembershipType membershipType, String memberAttrName, LDAPObject ldapParent, LDAPObject ldapChild, boolean sendLDAPUpdateRequest) { Set memberships = getExistingMemberships(memberAttrName, ldapParent); @@ -191,7 +192,7 @@ public class LDAPUtils { * @param ldapChild usually user (or child group or child role) * @param sendLDAPUpdateRequest if true, the method will send LDAP update request too. Otherwise it will skip it */ - public static void deleteMember(LDAPFederationProvider ldapProvider, MembershipType membershipType, String memberAttrName, LDAPObject ldapParent, LDAPObject ldapChild, boolean sendLDAPUpdateRequest) { + public static void deleteMember(LDAPStorageProvider ldapProvider, MembershipType membershipType, String memberAttrName, LDAPObject ldapParent, LDAPObject ldapChild, boolean sendLDAPUpdateRequest) { Set memberships = getExistingMemberships(memberAttrName, ldapParent); String userMembership = getMemberValueOfChildObject(ldapChild, membershipType); @@ -238,7 +239,7 @@ public class LDAPUtils { * @param ldapProvider * @return */ - public static List loadAllLDAPObjects(LDAPQuery ldapQuery, LDAPFederationProvider ldapProvider) { + public static List loadAllLDAPObjects(LDAPQuery ldapQuery, LDAPStorageProvider ldapProvider) { LDAPConfig ldapConfig = ldapProvider.getLdapIdentityStore().getConfig(); boolean pagination = ldapConfig.isPagination(); if (pagination) { @@ -269,7 +270,7 @@ public class LDAPUtils { * @param customFilter * @throws FederationConfigValidationException */ - public static void validateCustomLdapFilter(String customFilter) throws FederationConfigValidationException { + public static void validateCustomLdapFilter(String customFilter) throws ComponentValidationException { if (customFilter != null) { customFilter = customFilter.trim(); @@ -278,7 +279,7 @@ public class LDAPUtils { } if (!customFilter.startsWith("(") || !customFilter.endsWith(")")) { - throw new FederationConfigValidationException("ldapErrorInvalidCustomFilter"); + throw new ComponentValidationException("ldapErrorInvalidCustomFilter"); } } } diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/ReadonlyLDAPUserModelDelegate.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/ReadonlyLDAPUserModelDelegate.java similarity index 93% rename from federation/ldap/src/main/java/org/keycloak/federation/ldap/ReadonlyLDAPUserModelDelegate.java rename to federation/ldap/src/main/java/org/keycloak/storage/ldap/ReadonlyLDAPUserModelDelegate.java index 6a839b6698..18ed8e2d69 100755 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/ReadonlyLDAPUserModelDelegate.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/ReadonlyLDAPUserModelDelegate.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.keycloak.federation.ldap; +package org.keycloak.storage.ldap; import org.keycloak.models.ModelReadOnlyException; import org.keycloak.models.UserModel; @@ -27,9 +27,9 @@ import org.keycloak.models.utils.UserModelDelegate; */ public class ReadonlyLDAPUserModelDelegate extends UserModelDelegate implements UserModel { - protected LDAPFederationProvider provider; + protected LDAPStorageProvider provider; - public ReadonlyLDAPUserModelDelegate(UserModel delegate, LDAPFederationProvider provider) { + public ReadonlyLDAPUserModelDelegate(UserModel delegate, LDAPStorageProvider provider) { super(delegate); this.provider = provider; } diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/UnsyncedLDAPUserModelDelegate.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/UnsyncedLDAPUserModelDelegate.java similarity index 91% rename from federation/ldap/src/main/java/org/keycloak/federation/ldap/UnsyncedLDAPUserModelDelegate.java rename to federation/ldap/src/main/java/org/keycloak/storage/ldap/UnsyncedLDAPUserModelDelegate.java index 4d116e4134..e26104c823 100755 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/UnsyncedLDAPUserModelDelegate.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/UnsyncedLDAPUserModelDelegate.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.keycloak.federation.ldap; +package org.keycloak.storage.ldap; import org.jboss.logging.Logger; import org.keycloak.models.UserModel; @@ -28,9 +28,9 @@ import org.keycloak.models.utils.UserModelDelegate; public class UnsyncedLDAPUserModelDelegate extends UserModelDelegate implements UserModel { private static final Logger logger = Logger.getLogger(UnsyncedLDAPUserModelDelegate.class); - protected LDAPFederationProvider provider; + protected LDAPStorageProvider provider; - public UnsyncedLDAPUserModelDelegate(UserModel delegate, LDAPFederationProvider provider) { + public UnsyncedLDAPUserModelDelegate(UserModel delegate, LDAPStorageProvider provider) { super(delegate); this.provider = provider; } diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/WritableLDAPUserModelDelegate.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/WritableLDAPUserModelDelegate.java similarity index 86% rename from federation/ldap/src/main/java/org/keycloak/federation/ldap/WritableLDAPUserModelDelegate.java rename to federation/ldap/src/main/java/org/keycloak/storage/ldap/WritableLDAPUserModelDelegate.java index 7bc78014cf..6b87bb80c3 100755 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/WritableLDAPUserModelDelegate.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/WritableLDAPUserModelDelegate.java @@ -15,12 +15,12 @@ * limitations under the License. */ -package org.keycloak.federation.ldap; +package org.keycloak.storage.ldap; import org.jboss.logging.Logger; -import org.keycloak.federation.ldap.idm.model.LDAPObject; import org.keycloak.models.UserModel; import org.keycloak.models.utils.UserModelDelegate; +import org.keycloak.storage.ldap.idm.model.LDAPObject; /** * @author Bill Burke @@ -29,10 +29,10 @@ import org.keycloak.models.utils.UserModelDelegate; public class WritableLDAPUserModelDelegate extends UserModelDelegate implements UserModel { private static final Logger logger = Logger.getLogger(WritableLDAPUserModelDelegate.class); - protected LDAPFederationProvider provider; + protected LDAPStorageProvider provider; protected LDAPObject ldapObject; - public WritableLDAPUserModelDelegate(UserModel delegate, LDAPFederationProvider provider, LDAPObject ldapObject) { + public WritableLDAPUserModelDelegate(UserModel delegate, LDAPStorageProvider provider, LDAPObject ldapObject) { super(delegate); this.provider = provider; this.ldapObject = ldapObject; diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/LDAPDn.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/model/LDAPDn.java similarity index 98% rename from federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/LDAPDn.java rename to federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/model/LDAPDn.java index bfbce5964a..e95e8adafd 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/LDAPDn.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/model/LDAPDn.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.keycloak.federation.ldap.idm.model; +package org.keycloak.storage.ldap.idm.model; import javax.naming.ldap.Rdn; import java.util.Collection; diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/LDAPObject.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/model/LDAPObject.java similarity index 99% rename from federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/LDAPObject.java rename to federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/model/LDAPObject.java index 0141a451f7..64ef65fd07 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/LDAPObject.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/model/LDAPObject.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.keycloak.federation.ldap.idm.model; +package org.keycloak.storage.ldap.idm.model; import org.jboss.logging.Logger; diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/Condition.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/query/Condition.java similarity index 96% rename from federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/Condition.java rename to federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/query/Condition.java index a8d3f07182..152b0889ad 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/Condition.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/query/Condition.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.keycloak.federation.ldap.idm.query; +package org.keycloak.storage.ldap.idm.query; /** *

A {@link Condition} is used to specify how a specific query parameter diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/Sort.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/query/Sort.java similarity index 95% rename from federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/Sort.java rename to federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/query/Sort.java index da982726c3..97e381d052 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/Sort.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/query/Sort.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.keycloak.federation.ldap.idm.query; +package org.keycloak.storage.ldap.idm.query; /** * @author Pedro Igor diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/BetweenCondition.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/query/internal/BetweenCondition.java similarity index 92% rename from federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/BetweenCondition.java rename to federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/query/internal/BetweenCondition.java index ba926e9310..dedc29d71b 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/BetweenCondition.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/query/internal/BetweenCondition.java @@ -15,9 +15,9 @@ * limitations under the License. */ -package org.keycloak.federation.ldap.idm.query.internal; +package org.keycloak.storage.ldap.idm.query.internal; -import org.keycloak.federation.ldap.idm.store.ldap.LDAPUtil; +import org.keycloak.storage.ldap.idm.store.ldap.LDAPUtil; import java.util.Date; diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/CustomLDAPFilter.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/query/internal/CustomLDAPFilter.java similarity index 92% rename from federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/CustomLDAPFilter.java rename to federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/query/internal/CustomLDAPFilter.java index 45b3b1d681..c65a4754cb 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/CustomLDAPFilter.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/query/internal/CustomLDAPFilter.java @@ -15,9 +15,9 @@ * limitations under the License. */ -package org.keycloak.federation.ldap.idm.query.internal; +package org.keycloak.storage.ldap.idm.query.internal; -import org.keycloak.federation.ldap.idm.query.Condition; +import org.keycloak.storage.ldap.idm.query.Condition; /** * @author Marek Posolda diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/EqualCondition.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/query/internal/EqualCondition.java similarity index 93% rename from federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/EqualCondition.java rename to federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/query/internal/EqualCondition.java index 115e11f4da..e82fe376d2 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/EqualCondition.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/query/internal/EqualCondition.java @@ -15,10 +15,10 @@ * limitations under the License. */ -package org.keycloak.federation.ldap.idm.query.internal; +package org.keycloak.storage.ldap.idm.query.internal; -import org.keycloak.federation.ldap.idm.store.ldap.LDAPUtil; import org.keycloak.models.LDAPConstants; +import org.keycloak.storage.ldap.idm.store.ldap.LDAPUtil; import java.util.Date; diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/GreaterThanCondition.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/query/internal/GreaterThanCondition.java similarity index 92% rename from federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/GreaterThanCondition.java rename to federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/query/internal/GreaterThanCondition.java index 3ef6535719..32432e63ba 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/GreaterThanCondition.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/query/internal/GreaterThanCondition.java @@ -15,9 +15,9 @@ * limitations under the License. */ -package org.keycloak.federation.ldap.idm.query.internal; +package org.keycloak.storage.ldap.idm.query.internal; -import org.keycloak.federation.ldap.idm.store.ldap.LDAPUtil; +import org.keycloak.storage.ldap.idm.store.ldap.LDAPUtil; import java.util.Date; diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/InCondition.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/query/internal/InCondition.java similarity index 95% rename from federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/InCondition.java rename to federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/query/internal/InCondition.java index ebecd54cf6..8f5c26a0e5 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/InCondition.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/query/internal/InCondition.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.keycloak.federation.ldap.idm.query.internal; +package org.keycloak.storage.ldap.idm.query.internal; import org.keycloak.models.LDAPConstants; diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/LDAPQuery.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/query/internal/LDAPQuery.java similarity index 83% rename from federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/LDAPQuery.java rename to federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/query/internal/LDAPQuery.java index 5a57f84f3d..eb7ff1bb9a 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/LDAPQuery.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/query/internal/LDAPQuery.java @@ -15,16 +15,16 @@ * limitations under the License. */ -package org.keycloak.federation.ldap.idm.query.internal; +package org.keycloak.storage.ldap.idm.query.internal; -import org.keycloak.federation.ldap.LDAPFederationProvider; -import org.keycloak.federation.ldap.idm.model.LDAPObject; -import org.keycloak.federation.ldap.idm.query.Condition; -import org.keycloak.federation.ldap.idm.query.Sort; -import org.keycloak.federation.ldap.mappers.LDAPFederationMapper; +import org.keycloak.component.ComponentModel; import org.keycloak.models.ModelDuplicateException; import org.keycloak.models.ModelException; -import org.keycloak.models.UserFederationMapperModel; +import org.keycloak.storage.ldap.LDAPStorageProvider; +import org.keycloak.storage.ldap.idm.model.LDAPObject; +import org.keycloak.storage.ldap.idm.query.Condition; +import org.keycloak.storage.ldap.idm.query.Sort; +import org.keycloak.storage.ldap.mappers.LDAPStorageMapper; import javax.naming.directory.SearchControls; import java.util.ArrayList; @@ -44,7 +44,7 @@ import static java.util.Collections.unmodifiableSet; */ public class LDAPQuery { - private final LDAPFederationProvider ldapFedProvider; + private final LDAPStorageProvider ldapFedProvider; private int offset; private int limit; @@ -60,11 +60,11 @@ public class LDAPQuery { private final Set returningReadOnlyLdapAttributes = new LinkedHashSet(); private final Set objectClasses = new LinkedHashSet(); - private final List mappers = new ArrayList(); + private final List mappers = new ArrayList<>(); private int searchScope = SearchControls.SUBTREE_SCOPE; - public LDAPQuery(LDAPFederationProvider ldapProvider) { + public LDAPQuery(LDAPStorageProvider ldapProvider) { this.ldapFedProvider = ldapProvider; } @@ -98,7 +98,7 @@ public class LDAPQuery { return this; } - public LDAPQuery addMappers(Collection mappers) { + public LDAPQuery addMappers(Collection mappers) { this.mappers.addAll(mappers); return this; } @@ -128,7 +128,7 @@ public class LDAPQuery { return unmodifiableSet(this.returningReadOnlyLdapAttributes); } - public List getMappers() { + public List getMappers() { return mappers; } @@ -152,9 +152,9 @@ public class LDAPQuery { public List getResultList() { // Apply mappers now - List sortedMappers = ldapFedProvider.sortMappersAsc(mappers); - for (UserFederationMapperModel mapperModel : sortedMappers) { - LDAPFederationMapper fedMapper = ldapFedProvider.getMapper(mapperModel); + List sortedMappers = ldapFedProvider.sortMappersAsc(mappers); + for (ComponentModel mapperModel : sortedMappers) { + LDAPStorageMapper fedMapper = ldapFedProvider.getMapper(mapperModel); fedMapper.beforeLDAPQuery(mapperModel, this); } @@ -206,7 +206,7 @@ public class LDAPQuery { return this.conditions; } - public LDAPFederationProvider getLdapProvider() { + public LDAPStorageProvider getLdapProvider() { return ldapFedProvider; } diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/LDAPQueryConditionsBuilder.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/query/internal/LDAPQueryConditionsBuilder.java similarity index 94% rename from federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/LDAPQueryConditionsBuilder.java rename to federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/query/internal/LDAPQueryConditionsBuilder.java index 4be4ba41c5..715ec3da36 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/LDAPQueryConditionsBuilder.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/query/internal/LDAPQueryConditionsBuilder.java @@ -15,11 +15,11 @@ * limitations under the License. */ -package org.keycloak.federation.ldap.idm.query.internal; +package org.keycloak.storage.ldap.idm.query.internal; -import org.keycloak.federation.ldap.idm.query.Condition; -import org.keycloak.federation.ldap.idm.query.Sort; import org.keycloak.models.ModelException; +import org.keycloak.storage.ldap.idm.query.Condition; +import org.keycloak.storage.ldap.idm.query.Sort; /** * @author Pedro Igor diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/LessThanCondition.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/query/internal/LessThanCondition.java similarity index 92% rename from federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/LessThanCondition.java rename to federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/query/internal/LessThanCondition.java index e29b014125..a32fb27867 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/LessThanCondition.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/query/internal/LessThanCondition.java @@ -15,9 +15,9 @@ * limitations under the License. */ -package org.keycloak.federation.ldap.idm.query.internal; +package org.keycloak.storage.ldap.idm.query.internal; -import org.keycloak.federation.ldap.idm.store.ldap.LDAPUtil; +import org.keycloak.storage.ldap.idm.store.ldap.LDAPUtil; import java.util.Date; diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/NamedParameterCondition.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/query/internal/NamedParameterCondition.java similarity index 92% rename from federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/NamedParameterCondition.java rename to federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/query/internal/NamedParameterCondition.java index 50cb7723be..72a9a0cb81 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/NamedParameterCondition.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/query/internal/NamedParameterCondition.java @@ -15,9 +15,9 @@ * limitations under the License. */ -package org.keycloak.federation.ldap.idm.query.internal; +package org.keycloak.storage.ldap.idm.query.internal; -import org.keycloak.federation.ldap.idm.query.Condition; +import org.keycloak.storage.ldap.idm.query.Condition; /** * @author Marek Posolda diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/OrCondition.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/query/internal/OrCondition.java similarity index 93% rename from federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/OrCondition.java rename to federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/query/internal/OrCondition.java index 7f0e93f211..f605f9a97a 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/OrCondition.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/query/internal/OrCondition.java @@ -15,9 +15,9 @@ * limitations under the License. */ -package org.keycloak.federation.ldap.idm.query.internal; +package org.keycloak.storage.ldap.idm.query.internal; -import org.keycloak.federation.ldap.idm.query.Condition; +import org.keycloak.storage.ldap.idm.query.Condition; /** * @author Marek Posolda diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/IdentityStore.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/IdentityStore.java similarity index 91% rename from federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/IdentityStore.java rename to federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/IdentityStore.java index c14f4f03ad..4b2010b072 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/IdentityStore.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/IdentityStore.java @@ -15,11 +15,11 @@ * limitations under the License. */ -package org.keycloak.federation.ldap.idm.store; +package org.keycloak.storage.ldap.idm.store; -import org.keycloak.federation.ldap.LDAPConfig; -import org.keycloak.federation.ldap.idm.model.LDAPObject; -import org.keycloak.federation.ldap.idm.query.internal.LDAPQuery; +import org.keycloak.storage.ldap.LDAPConfig; +import org.keycloak.storage.ldap.idm.model.LDAPObject; +import org.keycloak.storage.ldap.idm.query.internal.LDAPQuery; import javax.naming.AuthenticationException; import java.util.List; diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPIdentityStore.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/ldap/LDAPIdentityStore.java similarity index 97% rename from federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPIdentityStore.java rename to federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/ldap/LDAPIdentityStore.java index 367fb04d57..6d0e2cc0b3 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPIdentityStore.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/ldap/LDAPIdentityStore.java @@ -15,18 +15,18 @@ * limitations under the License. */ -package org.keycloak.federation.ldap.idm.store.ldap; +package org.keycloak.storage.ldap.idm.store.ldap; import org.jboss.logging.Logger; -import org.keycloak.federation.ldap.LDAPConfig; -import org.keycloak.federation.ldap.idm.model.LDAPDn; -import org.keycloak.federation.ldap.idm.model.LDAPObject; -import org.keycloak.federation.ldap.idm.query.Condition; -import org.keycloak.federation.ldap.idm.query.internal.EqualCondition; -import org.keycloak.federation.ldap.idm.query.internal.LDAPQuery; -import org.keycloak.federation.ldap.idm.store.IdentityStore; import org.keycloak.models.LDAPConstants; import org.keycloak.models.ModelException; +import org.keycloak.storage.ldap.LDAPConfig; +import org.keycloak.storage.ldap.idm.model.LDAPDn; +import org.keycloak.storage.ldap.idm.model.LDAPObject; +import org.keycloak.storage.ldap.idm.query.Condition; +import org.keycloak.storage.ldap.idm.query.internal.EqualCondition; +import org.keycloak.storage.ldap.idm.query.internal.LDAPQuery; +import org.keycloak.storage.ldap.idm.store.IdentityStore; import javax.naming.AuthenticationException; import javax.naming.NamingEnumeration; diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPOperationManager.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/ldap/LDAPOperationManager.java similarity index 99% rename from federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPOperationManager.java rename to federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/ldap/LDAPOperationManager.java index 02cd122b21..4fe40020e3 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPOperationManager.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/ldap/LDAPOperationManager.java @@ -15,13 +15,13 @@ * limitations under the License. */ -package org.keycloak.federation.ldap.idm.store.ldap; +package org.keycloak.storage.ldap.idm.store.ldap; import org.jboss.logging.Logger; -import org.keycloak.federation.ldap.LDAPConfig; -import org.keycloak.federation.ldap.idm.query.internal.LDAPQuery; import org.keycloak.models.LDAPConstants; import org.keycloak.models.ModelException; +import org.keycloak.storage.ldap.LDAPConfig; +import org.keycloak.storage.ldap.idm.query.internal.LDAPQuery; import javax.naming.AuthenticationException; import javax.naming.Binding; diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPUtil.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/ldap/LDAPUtil.java similarity index 99% rename from federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPUtil.java rename to federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/ldap/LDAPUtil.java index a60d4eb988..2dc9d99ccb 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPUtil.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/ldap/LDAPUtil.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.keycloak.federation.ldap.idm.store.ldap; +package org.keycloak.storage.ldap.idm.store.ldap; import org.keycloak.models.ModelException; diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/kerberos/LDAPProviderKerberosConfig.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/kerberos/LDAPProviderKerberosConfig.java similarity index 70% rename from federation/ldap/src/main/java/org/keycloak/federation/ldap/kerberos/LDAPProviderKerberosConfig.java rename to federation/ldap/src/main/java/org/keycloak/storage/ldap/kerberos/LDAPProviderKerberosConfig.java index 71e219871b..87bfcaf137 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/kerberos/LDAPProviderKerberosConfig.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/kerberos/LDAPProviderKerberosConfig.java @@ -15,24 +15,25 @@ * limitations under the License. */ -package org.keycloak.federation.ldap.kerberos; +package org.keycloak.storage.ldap.kerberos; import org.keycloak.common.constants.KerberosConstants; +import org.keycloak.component.ComponentModel; import org.keycloak.federation.kerberos.CommonKerberosConfig; -import org.keycloak.models.UserFederationProviderModel; +import org.keycloak.storage.ldap.LDAPStorageProvider; /** - * Configuration specific to {@link org.keycloak.federation.ldap.LDAPFederationProvider} + * Configuration specific to {@link LDAPStorageProvider} * * @author Marek Posolda */ public class LDAPProviderKerberosConfig extends CommonKerberosConfig { - public LDAPProviderKerberosConfig(UserFederationProviderModel userFederationProvider) { - super(userFederationProvider); + public LDAPProviderKerberosConfig(ComponentModel componentModel) { + super(componentModel); } public boolean isUseKerberosForPasswordAuthentication() { - return Boolean.valueOf(getConfig().get(KerberosConstants.USE_KERBEROS_FOR_PASSWORD_AUTHENTICATION)); + return Boolean.valueOf(componentModel.getConfig().getFirst(KerberosConstants.USE_KERBEROS_FOR_PASSWORD_AUTHENTICATION)); } } diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/AbstractLDAPFederationMapper.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/AbstractLDAPStorageMapper.java similarity index 50% rename from federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/AbstractLDAPFederationMapper.java rename to federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/AbstractLDAPStorageMapper.java index 8d203d6815..c6edd96ef6 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/AbstractLDAPFederationMapper.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/AbstractLDAPStorageMapper.java @@ -15,19 +15,17 @@ * limitations under the License. */ -package org.keycloak.federation.ldap.mappers; +package org.keycloak.storage.ldap.mappers; -import org.keycloak.federation.ldap.LDAPFederationProvider; -import org.keycloak.federation.ldap.idm.model.LDAPObject; -import org.keycloak.federation.ldap.idm.query.internal.LDAPQuery; -import org.keycloak.mappers.UserFederationMapper; +import org.keycloak.component.ComponentModel; import org.keycloak.models.GroupModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; -import org.keycloak.models.UserFederationMapperModel; -import org.keycloak.models.UserFederationProvider; -import org.keycloak.models.UserFederationSyncResult; import org.keycloak.models.UserModel; +import org.keycloak.storage.ldap.LDAPStorageProvider; +import org.keycloak.storage.ldap.idm.model.LDAPObject; +import org.keycloak.storage.ldap.idm.query.internal.LDAPQuery; +import org.keycloak.storage.user.SynchronizationResult; import javax.naming.AuthenticationException; import java.util.Collections; @@ -38,49 +36,49 @@ import java.util.List; * * @author Marek Posolda */ -public abstract class AbstractLDAPFederationMapper { +public abstract class AbstractLDAPStorageMapper { - protected final UserFederationMapperModel mapperModel; - protected final LDAPFederationProvider ldapProvider; + protected final ComponentModel mapperModel; + protected final LDAPStorageProvider ldapProvider; protected final RealmModel realm; - public AbstractLDAPFederationMapper(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, RealmModel realm) { + public AbstractLDAPStorageMapper(ComponentModel mapperModel, LDAPStorageProvider ldapProvider, RealmModel realm) { this.mapperModel = mapperModel; this.ldapProvider = ldapProvider; this.realm = realm; } /** - * @see UserFederationMapper#syncDataFromFederationProviderToKeycloak(UserFederationMapperModel, UserFederationProvider, KeycloakSession, RealmModel) + * @see LDAPStorageMapper#syncDataFromFederationProviderToKeycloak(ComponentModel, LDAPStorageProvider, KeycloakSession, RealmModel) */ - public UserFederationSyncResult syncDataFromFederationProviderToKeycloak() { - return new UserFederationSyncResult(); + public SynchronizationResult syncDataFromFederationProviderToKeycloak() { + return new SynchronizationResult(); } /** - * @see UserFederationMapper#syncDataFromKeycloakToFederationProvider(UserFederationMapperModel, UserFederationProvider, KeycloakSession, RealmModel) + * @see LDAPStorageMapper#syncDataFromKeycloakToFederationProvider(ComponentModel, LDAPStorageProvider, KeycloakSession, RealmModel) */ - public UserFederationSyncResult syncDataFromKeycloakToFederationProvider() { - return new UserFederationSyncResult(); + public SynchronizationResult syncDataFromKeycloakToFederationProvider() { + return new SynchronizationResult(); } /** - * @see LDAPFederationMapper#beforeLDAPQuery(UserFederationMapperModel, LDAPQuery) + * @see LDAPStorageMapper#beforeLDAPQuery(ComponentModel, LDAPQuery) */ public abstract void beforeLDAPQuery(LDAPQuery query); /** - * @see LDAPFederationMapper#proxy(UserFederationMapperModel, LDAPFederationProvider, LDAPObject, UserModel, RealmModel) + * @see LDAPStorageMapper#proxy(ComponentModel, LDAPStorageProvider, LDAPObject, UserModel, RealmModel) */ public abstract UserModel proxy(LDAPObject ldapUser, UserModel delegate); /** - * @see LDAPFederationMapper#onRegisterUserToLDAP(UserFederationMapperModel, LDAPFederationProvider, LDAPObject, UserModel, RealmModel) + * @see LDAPStorageMapper#onRegisterUserToLDAP(ComponentModel, LDAPStorageProvider, LDAPObject, UserModel, RealmModel) */ public abstract void onRegisterUserToLDAP(LDAPObject ldapUser, UserModel localUser); /** - * @see LDAPFederationMapper#onImportUserFromLDAP(UserFederationMapperModel, LDAPFederationProvider, LDAPObject, UserModel, RealmModel, boolean) + * @see LDAPStorageMapper#onImportUserFromLDAP(ComponentModel, LDAPStorageProvider, LDAPObject, UserModel, RealmModel, boolean) */ public abstract void onImportUserFromLDAP(LDAPObject ldapUser, UserModel user, boolean isCreate); @@ -93,12 +91,12 @@ public abstract class AbstractLDAPFederationMapper { } - public static boolean parseBooleanParameter(UserFederationMapperModel mapperModel, String paramName) { - String paramm = mapperModel.getConfig().get(paramName); + public static boolean parseBooleanParameter(ComponentModel mapperModel, String paramName) { + String paramm = mapperModel.getConfig().getFirst(paramName); return Boolean.parseBoolean(paramm); } - public LDAPFederationProvider getLdapProvider() { + public LDAPStorageProvider getLdapProvider() { return ldapProvider; } diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/AbstractLDAPFederationMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/AbstractLDAPStorageMapperFactory.java similarity index 63% rename from federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/AbstractLDAPFederationMapperFactory.java rename to federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/AbstractLDAPStorageMapperFactory.java index ccd4aa61dc..a6ba2d2651 100755 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/AbstractLDAPFederationMapperFactory.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/AbstractLDAPStorageMapperFactory.java @@ -15,27 +15,26 @@ * limitations under the License. */ -package org.keycloak.federation.ldap.mappers; - -import java.util.List; +package org.keycloak.storage.ldap.mappers; import org.keycloak.Config; -import org.keycloak.federation.ldap.LDAPFederationProvider; -import org.keycloak.federation.ldap.LDAPFederationProviderFactory; -import org.keycloak.mappers.FederationConfigValidationException; -import org.keycloak.mappers.UserFederationMapper; -import org.keycloak.mappers.UserFederationMapperFactory; +import org.keycloak.component.ComponentModel; +import org.keycloak.component.ComponentValidationException; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.RealmModel; -import org.keycloak.models.UserFederationMapperModel; import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.representations.idm.UserFederationMapperSyncConfigRepresentation; +import org.keycloak.storage.ldap.LDAPStorageProvider; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; /** * @author Marek Posolda */ -public abstract class AbstractLDAPFederationMapperFactory implements UserFederationMapperFactory { +public abstract class AbstractLDAPStorageMapperFactory implements LDAPStorageMapperFactory { // Used to map attributes from LDAP to UserModel attributes public static final String ATTRIBUTE_MAPPER_CATEGORY = "Attribute Mapper"; @@ -52,25 +51,23 @@ public abstract class AbstractLDAPFederationMapperFactory implements UserFederat } @Override - public UserFederationMapper create(KeycloakSession session) { - return new LDAPFederationMapperBridge(this); - } + public LDAPStorageMapper create(KeycloakSession session, ComponentModel model) { + return new LDAPStorageMapperBridge(this); } // Used just by LDAPFederationMapperBridge. - protected abstract AbstractLDAPFederationMapper createMapper(UserFederationMapperModel mapperModel, LDAPFederationProvider federationProvider, RealmModel realm); - - @Override - public String getFederationProviderType() { - return LDAPFederationProviderFactory.PROVIDER_NAME; - } + protected abstract AbstractLDAPStorageMapper createMapper(ComponentModel mapperModel, LDAPStorageProvider federationProvider, RealmModel realm); @Override public void postInit(KeycloakSessionFactory factory) { } @Override - public UserFederationMapperSyncConfigRepresentation getSyncConfig() { - return new UserFederationMapperSyncConfigRepresentation(false, null, false, null); + public Map getTypeMetadata() { + Map metadata = new HashMap<>(); + metadata.put("fedToKeycloakSyncSupported", false); + metadata.put("keycloakToFedSyncSupported", false); + + return metadata; } @Override @@ -87,10 +84,10 @@ public abstract class AbstractLDAPFederationMapperFactory implements UserFederat return configProperty; } - protected void checkMandatoryConfigAttribute(String name, String displayName, UserFederationMapperModel mapperModel) throws FederationConfigValidationException { - String attrConfigValue = mapperModel.getConfig().get(name); + protected void checkMandatoryConfigAttribute(String name, String displayName, ComponentModel mapperModel) throws ComponentValidationException { + String attrConfigValue = mapperModel.getConfig().getFirst(name); if (attrConfigValue == null || attrConfigValue.trim().isEmpty()) { - throw new FederationConfigValidationException("Missing configuration for '" + displayName + "'"); + throw new ComponentValidationException("Missing configuration for '" + displayName + "'"); } } diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/FullNameLDAPFederationMapper.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/FullNameLDAPStorageMapper.java similarity index 89% rename from federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/FullNameLDAPFederationMapper.java rename to federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/FullNameLDAPStorageMapper.java index c2cabe412d..1806ecdf89 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/FullNameLDAPFederationMapper.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/FullNameLDAPStorageMapper.java @@ -15,18 +15,18 @@ * limitations under the License. */ -package org.keycloak.federation.ldap.mappers; +package org.keycloak.storage.ldap.mappers; -import org.keycloak.federation.ldap.LDAPFederationProvider; -import org.keycloak.federation.ldap.idm.model.LDAPObject; -import org.keycloak.federation.ldap.idm.query.Condition; -import org.keycloak.federation.ldap.idm.query.internal.EqualCondition; -import org.keycloak.federation.ldap.idm.query.internal.LDAPQuery; +import org.keycloak.component.ComponentModel; import org.keycloak.models.LDAPConstants; import org.keycloak.models.RealmModel; -import org.keycloak.models.UserFederationMapperModel; -import org.keycloak.models.UserFederationProvider; import org.keycloak.models.UserModel; +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.idm.query.Condition; +import org.keycloak.storage.ldap.idm.query.internal.EqualCondition; +import org.keycloak.storage.ldap.idm.query.internal.LDAPQuery; import java.util.HashSet; import java.util.Set; @@ -36,14 +36,14 @@ import java.util.Set; * * @author Marek Posolda */ -public class FullNameLDAPFederationMapper extends AbstractLDAPFederationMapper { +public class FullNameLDAPStorageMapper extends AbstractLDAPStorageMapper { public static final String LDAP_FULL_NAME_ATTRIBUTE = "ldap.full.name.attribute"; public static final String READ_ONLY = "read.only"; public static final String WRITE_ONLY = "write.only"; - public FullNameLDAPFederationMapper(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, RealmModel realm) { + public FullNameLDAPStorageMapper(ComponentModel mapperModel, LDAPStorageProvider ldapProvider, RealmModel realm) { super(mapperModel, ldapProvider, realm); } @@ -84,7 +84,7 @@ public class FullNameLDAPFederationMapper extends AbstractLDAPFederationMapper { @Override public UserModel proxy(LDAPObject ldapUser, UserModel delegate) { - if (ldapProvider.getEditMode() == UserFederationProvider.EditMode.WRITABLE && !isReadOnly()) { + if (ldapProvider.getEditMode() == LDAPStorageProviderFactory.EditMode.WRITABLE && !isReadOnly()) { TxAwareLDAPUserModelDelegate txDelegate = new TxAwareLDAPUserModelDelegate(delegate, ldapProvider, ldapUser) { @@ -169,7 +169,7 @@ public class FullNameLDAPFederationMapper extends AbstractLDAPFederationMapper { } protected String getLdapFullNameAttrName() { - String ldapFullNameAttrName = mapperModel.getConfig().get(LDAP_FULL_NAME_ATTRIBUTE); + String ldapFullNameAttrName = mapperModel.getConfig().getFirst(LDAP_FULL_NAME_ATTRIBUTE); return ldapFullNameAttrName == null ? LDAPConstants.CN : ldapFullNameAttrName; } diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/FullNameLDAPStorageMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/FullNameLDAPStorageMapperFactory.java new file mode 100755 index 0000000000..3cfa6c5204 --- /dev/null +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/FullNameLDAPStorageMapperFactory.java @@ -0,0 +1,124 @@ +/* + * 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.KeycloakSession; +import org.keycloak.models.LDAPConstants; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserFederationProvider; +import org.keycloak.models.UserFederationProviderModel; +import org.keycloak.provider.ProviderConfigProperty; +import org.keycloak.provider.ProviderConfigurationBuilder; +import org.keycloak.storage.ldap.LDAPConfig; +import org.keycloak.storage.ldap.LDAPStorageProvider; +import org.keycloak.storage.ldap.LDAPStorageProviderFactory; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author Marek Posolda + */ +public class FullNameLDAPStorageMapperFactory extends AbstractLDAPStorageMapperFactory { + + public static final String PROVIDER_ID = "full-name-ldap-mapper"; + + protected static final List configProperties; + + static { + configProperties = getConfigProps(null); + } + + private static List getConfigProps(ComponentModel parent) { + boolean readOnly = false; + if (parent != null) { + LDAPConfig config = new LDAPConfig(parent.getConfig()); + readOnly = config.getEditMode() != LDAPStorageProviderFactory.EditMode.WRITABLE; + } + + + return ProviderConfigurationBuilder.create() + .property().name(FullNameLDAPStorageMapper.LDAP_FULL_NAME_ATTRIBUTE) + .label("LDAP Full Name Attribute") + .helpText("Name of LDAP attribute, which contains fullName of user. Usually it will be 'cn' ") + .type(ProviderConfigProperty.STRING_TYPE) + .defaultValue(LDAPConstants.CN) + .add() + .property().name(FullNameLDAPStorageMapper.READ_ONLY) + .label("Read Only") + .helpText("For Read-only is data imported from LDAP to Keycloak DB, but it's not saved back to LDAP when user is updated in Keycloak.") + .type(ProviderConfigProperty.BOOLEAN_TYPE) + .defaultValue(String.valueOf(readOnly)) + .add() + .property().name(FullNameLDAPStorageMapper.WRITE_ONLY) + .label("Write Only") + .helpText("For Write-only is data propagated to LDAP when user is created or updated in Keycloak. But this mapper is not used to propagate data from LDAP back into Keycloak. " + + "This setting is useful if you configured separate firstName and lastName attribute mappers and you want to use those to read attribute from LDAP into Keycloak") + .type(ProviderConfigProperty.BOOLEAN_TYPE) + .defaultValue(String.valueOf(!readOnly)) + .add() + .build(); + } + + @Override + public String getHelpText() { + return "Used to map full-name of user from single attribute in LDAP (usually 'cn' attribute) to firstName and lastName attributes of UserModel in Keycloak DB"; + } + + @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 { + checkMandatoryConfigAttribute(FullNameLDAPStorageMapper.LDAP_FULL_NAME_ATTRIBUTE, "LDAP Full Name Attribute", config); + + + boolean readOnly = AbstractLDAPStorageMapper.parseBooleanParameter(config, FullNameLDAPStorageMapper.READ_ONLY); + boolean writeOnly = AbstractLDAPStorageMapper.parseBooleanParameter(config, FullNameLDAPStorageMapper.WRITE_ONLY); + + ComponentModel parent = realm.getComponent(config.getParentId()); + if (parent == null) { + throw new ComponentValidationException("can't find parent component model"); + + } + LDAPConfig cfg = new LDAPConfig(parent.getConfig()); + LDAPStorageProviderFactory.EditMode editMode = cfg.getEditMode(); + + if (writeOnly && cfg.getEditMode() != LDAPStorageProviderFactory.EditMode.WRITABLE) { + throw new ComponentValidationException("ldapErrorCantWriteOnlyForReadOnlyLdap"); + } + if (writeOnly && readOnly) { + throw new ComponentValidationException("ldapErrorCantWriteOnlyAndReadOnly"); + } + } + + @Override + protected AbstractLDAPStorageMapper createMapper(ComponentModel mapperModel, LDAPStorageProvider federationProvider, RealmModel realm) { + return new FullNameLDAPStorageMapper(mapperModel, federationProvider, realm); + } +} diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/HardcodedLDAPRoleMapper.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/HardcodedLDAPRoleStorageMapper.java similarity index 85% rename from federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/HardcodedLDAPRoleMapper.java rename to federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/HardcodedLDAPRoleStorageMapper.java index 67e577eebe..ca79a1905e 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/HardcodedLDAPRoleMapper.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/HardcodedLDAPRoleStorageMapper.java @@ -15,33 +15,33 @@ * limitations under the License. */ -package org.keycloak.federation.ldap.mappers; +package org.keycloak.storage.ldap.mappers; import org.jboss.logging.Logger; -import org.keycloak.federation.ldap.LDAPFederationProvider; -import org.keycloak.federation.ldap.idm.model.LDAPObject; -import org.keycloak.federation.ldap.idm.query.internal.LDAPQuery; +import org.keycloak.component.ComponentModel; import org.keycloak.models.ClientModel; import org.keycloak.models.ModelException; import org.keycloak.models.RealmModel; import org.keycloak.models.RoleModel; -import org.keycloak.models.UserFederationMapperModel; import org.keycloak.models.UserModel; 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 Marek Posolda */ -public class HardcodedLDAPRoleMapper extends AbstractLDAPFederationMapper { +public class HardcodedLDAPRoleStorageMapper extends AbstractLDAPStorageMapper { - private static final Logger logger = Logger.getLogger(HardcodedLDAPRoleMapper.class); + private static final Logger logger = Logger.getLogger(HardcodedLDAPRoleStorageMapper.class); public static final String ROLE = "role"; - public HardcodedLDAPRoleMapper(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, RealmModel realm) { + public HardcodedLDAPRoleStorageMapper(ComponentModel mapperModel, LDAPStorageProvider ldapProvider, RealmModel realm) { super(mapperModel, ldapProvider, realm); } @@ -116,7 +116,7 @@ public class HardcodedLDAPRoleMapper extends AbstractLDAPFederationMapper { } private RoleModel getRole() { - String roleName = mapperModel.getConfig().get(HardcodedLDAPRoleMapper.ROLE); + String roleName = mapperModel.getConfig().getFirst(HardcodedLDAPRoleStorageMapper.ROLE); RoleModel role = KeycloakModelUtils.getRoleFromString(realm, roleName); if (role == null) { logger.warnf("Hardcoded role '%s' configured in mapper '%s' is not available anymore"); diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/HardcodedLDAPRoleMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/HardcodedLDAPRoleStorageMapperFactory.java similarity index 58% rename from federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/HardcodedLDAPRoleMapperFactory.java rename to federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/HardcodedLDAPRoleStorageMapperFactory.java index 2f1170d11c..c833504f20 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/HardcodedLDAPRoleMapperFactory.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/HardcodedLDAPRoleStorageMapperFactory.java @@ -15,32 +15,30 @@ * limitations under the License. */ -package org.keycloak.federation.ldap.mappers; +package org.keycloak.storage.ldap.mappers; -import org.keycloak.federation.ldap.LDAPFederationProvider; -import org.keycloak.mappers.FederationConfigValidationException; +import org.keycloak.component.ComponentModel; +import org.keycloak.component.ComponentValidationException; +import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.RoleModel; -import org.keycloak.models.UserFederationMapperModel; -import org.keycloak.models.UserFederationProviderModel; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.provider.ProviderConfigProperty; +import org.keycloak.storage.ldap.LDAPStorageProvider; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; /** * @author Marek Posolda */ -public class HardcodedLDAPRoleMapperFactory extends AbstractLDAPFederationMapperFactory { +public class HardcodedLDAPRoleStorageMapperFactory extends AbstractLDAPStorageMapperFactory { public static final String PROVIDER_ID = "hardcoded-ldap-role-mapper"; protected static final List configProperties = new ArrayList(); static { - ProviderConfigProperty roleAttr = createConfigProperty(HardcodedLDAPRoleMapper.ROLE, "Role", + ProviderConfigProperty roleAttr = createConfigProperty(HardcodedLDAPRoleStorageMapper.ROLE, "Role", "Role to grant to user. Click 'Select Role' button to browse roles, or just type it in the textbox. To reference an application role the syntax is appname.approle, i.e. myapp.myrole", ProviderConfigProperty.ROLE_TYPE, null); configProperties.add(roleAttr); @@ -51,45 +49,30 @@ public class HardcodedLDAPRoleMapperFactory extends AbstractLDAPFederationMapper return "When user is imported from LDAP, he will be automatically added into this configured role."; } - @Override - public String getDisplayCategory() { - return ROLE_MAPPER_CATEGORY; - } - - @Override - public String getDisplayType() { - return "Hardcoded Role"; - } - @Override public List getConfigProperties() { return configProperties; } - @Override - public Map getDefaultConfig(UserFederationProviderModel providerModel) { - return new HashMap<>(); - } - @Override public String getId() { return PROVIDER_ID; } @Override - public void validateConfig(RealmModel realm, UserFederationProviderModel fedProviderModel, UserFederationMapperModel mapperModel) throws FederationConfigValidationException { - String roleName = mapperModel.getConfig().get(HardcodedLDAPRoleMapper.ROLE); + public void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel config) throws ComponentValidationException { + String roleName = config.getConfig().getFirst(HardcodedLDAPRoleStorageMapper.ROLE); if (roleName == null) { - throw new FederationConfigValidationException("Role can't be null"); + throw new ComponentValidationException("Role can't be null"); } RoleModel role = KeycloakModelUtils.getRoleFromString(realm, roleName); if (role == null) { - throw new FederationConfigValidationException("There is no role corresponding to configured value"); + throw new ComponentValidationException("There is no role corresponding to configured value"); } } @Override - protected AbstractLDAPFederationMapper createMapper(UserFederationMapperModel mapperModel, LDAPFederationProvider federationProvider, RealmModel realm) { - return new HardcodedLDAPRoleMapper(mapperModel, federationProvider, realm); + protected AbstractLDAPStorageMapper createMapper(ComponentModel mapperModel, LDAPStorageProvider federationProvider, RealmModel realm) { + return new HardcodedLDAPRoleStorageMapper(mapperModel, federationProvider, realm); } } diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/LDAPMappersComparator.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/LDAPMappersComparator.java similarity index 63% rename from federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/LDAPMappersComparator.java rename to federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/LDAPMappersComparator.java index 893cca765d..f659c2b3a9 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/LDAPMappersComparator.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/LDAPMappersComparator.java @@ -15,11 +15,11 @@ * limitations under the License. */ -package org.keycloak.federation.ldap.mappers; +package org.keycloak.storage.ldap.mappers; -import org.keycloak.federation.ldap.LDAPConfig; -import org.keycloak.models.UserFederationMapperModel; +import org.keycloak.component.ComponentModel; import org.keycloak.models.UserModel; +import org.keycloak.storage.ldap.LDAPConfig; import java.util.ArrayList; import java.util.Collection; @@ -28,30 +28,30 @@ import java.util.Comparator; import java.util.List; /** - * TODO: Possibly add "priority" to UserFederationMapper instead of hardcoding behaviour + * TODO: Possibly add "priority" instead of hardcoding behaviour * * @author Marek Posolda */ public class LDAPMappersComparator { - public static List sortAsc(LDAPConfig ldapConfig, Collection mappers) { - Comparator comparator = new ImportantFirstComparator(ldapConfig); + public static List sortAsc(LDAPConfig ldapConfig, Collection mappers) { + Comparator comparator = new ImportantFirstComparator(ldapConfig); - List result = new ArrayList<>(mappers); + List result = new ArrayList<>(mappers); Collections.sort(result, comparator); return result; } - public static List sortDesc(LDAPConfig ldapConfig, Collection mappers) { - Comparator comparator = new ImportantFirstComparator(ldapConfig).reversed(); + public static List sortDesc(LDAPConfig ldapConfig, Collection mappers) { + Comparator comparator = new ImportantFirstComparator(ldapConfig).reversed(); - List result = new ArrayList<>(mappers); + List result = new ArrayList<>(mappers); Collections.sort(result, comparator); return result; } - private static class ImportantFirstComparator implements Comparator { + private static class ImportantFirstComparator implements Comparator { private final LDAPConfig ldapConfig; @@ -60,10 +60,10 @@ public class LDAPMappersComparator { } @Override - public int compare(UserFederationMapperModel o1, UserFederationMapperModel o2) { + public int compare(ComponentModel o1, ComponentModel o2) { // UserAttributeLDAPFederationMapper first - boolean isO1AttrMapper = o1.getFederationMapperType().equals(UserAttributeLDAPFederationMapperFactory.PROVIDER_ID); - boolean isO2AttrMapper = o2.getFederationMapperType().equals(UserAttributeLDAPFederationMapperFactory.PROVIDER_ID); + boolean isO1AttrMapper = o1.getProviderId().equals(UserAttributeLDAPStorageMapperFactory.PROVIDER_ID); + boolean isO2AttrMapper = o2.getProviderId().equals(UserAttributeLDAPStorageMapperFactory.PROVIDER_ID); if (!isO1AttrMapper) { if (isO2AttrMapper) { return 1; @@ -75,8 +75,8 @@ public class LDAPMappersComparator { } // Mapper for "username" attribute first - String model1 = o1.getConfig().get(UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE); - String model2 = o2.getConfig().get(UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE); + String model1 = o1.getConfig().getFirst(UserAttributeLDAPStorageMapper.USER_MODEL_ATTRIBUTE); + String model2 = o2.getConfig().getFirst(UserAttributeLDAPStorageMapper.USER_MODEL_ATTRIBUTE); boolean isO1UsernameMapper = model1 != null && model1.equalsIgnoreCase(UserModel.USERNAME); boolean isO2UsernameMapper = model2 != null && model2.equalsIgnoreCase(UserModel.USERNAME); if (!isO1UsernameMapper) { @@ -90,8 +90,8 @@ public class LDAPMappersComparator { } // The username mapper corresponding to the same like configured username for federationProvider is first - String o1LdapAttr = o1.getConfig().get(UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE); - String o2LdapAttr = o2.getConfig().get(UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE); + String o1LdapAttr = o1.getConfig().getFirst(UserAttributeLDAPStorageMapper.LDAP_ATTRIBUTE); + String o2LdapAttr = o2.getConfig().getFirst(UserAttributeLDAPStorageMapper.LDAP_ATTRIBUTE); boolean isO1LdapAttr = o1LdapAttr != null && ldapConfig.getUsernameLdapAttribute().equalsIgnoreCase(o1LdapAttr); boolean isO2LdapAttr = o2LdapAttr != null && ldapConfig.getUsernameLdapAttribute().equalsIgnoreCase(o2LdapAttr); diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/LDAPFederationMapper.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/LDAPStorageMapper.java similarity index 50% rename from federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/LDAPFederationMapper.java rename to federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/LDAPStorageMapper.java index 52acbfb528..39831e1749 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/LDAPFederationMapper.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/LDAPStorageMapper.java @@ -15,23 +15,54 @@ * limitations under the License. */ -package org.keycloak.federation.ldap.mappers; +package org.keycloak.storage.ldap.mappers; -import org.keycloak.federation.ldap.LDAPFederationProvider; -import org.keycloak.federation.ldap.idm.model.LDAPObject; -import org.keycloak.federation.ldap.idm.query.internal.LDAPQuery; -import org.keycloak.mappers.UserFederationMapper; +import org.keycloak.component.ComponentModel; +import org.keycloak.models.GroupModel; +import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; -import org.keycloak.models.UserFederationMapperModel; import org.keycloak.models.UserModel; +import org.keycloak.provider.Provider; +import org.keycloak.storage.ldap.LDAPStorageProvider; +import org.keycloak.storage.ldap.idm.model.LDAPObject; +import org.keycloak.storage.ldap.idm.query.internal.LDAPQuery; +import org.keycloak.storage.user.SynchronizationResult; import javax.naming.AuthenticationException; +import java.util.List; /** * @author Marek Posolda */ -public interface LDAPFederationMapper extends UserFederationMapper { +public interface LDAPStorageMapper extends Provider { + /** + * Sync data from federated storage to Keycloak. It's useful just if mapper needs some data preloaded from federated storage (For example + * load roles from federated provider and sync them to Keycloak database) + * + * Applicable just if sync is supported + * + * @param mapperModel + * @param provider + * @param session + * @param realm + */ + SynchronizationResult syncDataFromFederationProviderToKeycloak(ComponentModel mapperModel, LDAPStorageProvider provider, KeycloakSession session, RealmModel realm); + + /** + * Sync data from Keycloak back to federated storage + * + * @param mapperModel + * @param provider + * @param session + * @param realm + */ + SynchronizationResult syncDataFromKeycloakToFederationProvider(ComponentModel mapperModel, LDAPStorageProvider provider, KeycloakSession session, RealmModel realm); + + /** + * Return empty list if doesn't support storing of groups + */ + List getGroupMembers(ComponentModel mapperModel, LDAPStorageProvider provider, RealmModel realm, GroupModel group, int firstResult, int maxResults); /** * Called when importing user from LDAP to local keycloak DB. @@ -43,7 +74,7 @@ public interface LDAPFederationMapper extends UserFederationMapper { * @param realm * @param isCreate true if we importing new user from LDAP. False if user already exists in Keycloak, but we are upgrading (syncing) it from LDAP */ - void onImportUserFromLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, UserModel user, RealmModel realm, boolean isCreate); + void onImportUserFromLDAP(ComponentModel mapperModel, LDAPStorageProvider ldapProvider, LDAPObject ldapUser, UserModel user, RealmModel realm, boolean isCreate); /** @@ -55,7 +86,7 @@ public interface LDAPFederationMapper extends UserFederationMapper { * @param localUser * @param realm */ - void onRegisterUserToLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, UserModel localUser, RealmModel realm); + void onRegisterUserToLDAP(ComponentModel mapperModel, LDAPStorageProvider ldapProvider, LDAPObject ldapUser, UserModel localUser, RealmModel realm); /** @@ -68,7 +99,7 @@ public interface LDAPFederationMapper extends UserFederationMapper { * @param realm * @return */ - UserModel proxy(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, UserModel delegate, RealmModel realm); + UserModel proxy(ComponentModel mapperModel, LDAPStorageProvider ldapProvider, LDAPObject ldapUser, UserModel delegate, RealmModel realm); /** @@ -77,7 +108,7 @@ public interface LDAPFederationMapper extends UserFederationMapper { * @param mapperModel * @param query */ - void beforeLDAPQuery(UserFederationMapperModel mapperModel, LDAPQuery query); + void beforeLDAPQuery(ComponentModel mapperModel, LDAPQuery query); /** * Called when LDAP authentication of specified user fails. If any mapper returns true from this method, AuthenticationException won't be rethrown! @@ -90,5 +121,5 @@ public interface LDAPFederationMapper extends UserFederationMapper { * @param ldapException * @return true if mapper processed the AuthenticationException and did some actions based on that. In that case, AuthenticationException won't be rethrown! */ - boolean onAuthenticationFailure(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, UserModel user, AuthenticationException ldapException, RealmModel realm); + boolean onAuthenticationFailure(ComponentModel mapperModel, LDAPStorageProvider ldapProvider, LDAPObject ldapUser, UserModel user, AuthenticationException ldapException, RealmModel realm); } diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/LDAPFederationMapperBridge.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/LDAPStorageMapperBridge.java similarity index 50% rename from federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/LDAPFederationMapperBridge.java rename to federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/LDAPStorageMapperBridge.java index 1a3521af35..f3919cc76c 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/LDAPFederationMapperBridge.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/LDAPStorageMapperBridge.java @@ -15,18 +15,17 @@ * limitations under the License. */ -package org.keycloak.federation.ldap.mappers; +package org.keycloak.storage.ldap.mappers; -import org.keycloak.federation.ldap.LDAPFederationProvider; -import org.keycloak.federation.ldap.idm.model.LDAPObject; -import org.keycloak.federation.ldap.idm.query.internal.LDAPQuery; +import org.keycloak.component.ComponentModel; import org.keycloak.models.GroupModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; -import org.keycloak.models.UserFederationMapperModel; -import org.keycloak.models.UserFederationProvider; -import org.keycloak.models.UserFederationSyncResult; import org.keycloak.models.UserModel; +import org.keycloak.storage.ldap.LDAPStorageProvider; +import org.keycloak.storage.ldap.idm.model.LDAPObject; +import org.keycloak.storage.ldap.idm.query.internal.LDAPQuery; +import org.keycloak.storage.user.SynchronizationResult; import javax.naming.AuthenticationException; import java.util.List; @@ -36,59 +35,59 @@ import java.util.List; * * @author Marek Posolda */ -public class LDAPFederationMapperBridge implements LDAPFederationMapper { +public class LDAPStorageMapperBridge implements LDAPStorageMapper { - private final AbstractLDAPFederationMapperFactory factory; + private final AbstractLDAPStorageMapperFactory factory; - public LDAPFederationMapperBridge(AbstractLDAPFederationMapperFactory factory) { + public LDAPStorageMapperBridge(AbstractLDAPStorageMapperFactory factory) { this.factory = factory; } // Sync groups from LDAP to Keycloak DB @Override - public UserFederationSyncResult syncDataFromFederationProviderToKeycloak(UserFederationMapperModel mapperModel, UserFederationProvider federationProvider, KeycloakSession session, RealmModel realm) { + public SynchronizationResult syncDataFromFederationProviderToKeycloak(ComponentModel mapperModel, LDAPStorageProvider federationProvider, KeycloakSession session, RealmModel realm) { return getDelegate(mapperModel, federationProvider, realm).syncDataFromFederationProviderToKeycloak(); } @Override - public UserFederationSyncResult syncDataFromKeycloakToFederationProvider(UserFederationMapperModel mapperModel, UserFederationProvider federationProvider, KeycloakSession session, RealmModel realm) { + public SynchronizationResult syncDataFromKeycloakToFederationProvider(ComponentModel mapperModel, LDAPStorageProvider federationProvider, KeycloakSession session, RealmModel realm) { return getDelegate(mapperModel, federationProvider, realm).syncDataFromKeycloakToFederationProvider(); } @Override - public void onImportUserFromLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, UserModel user, RealmModel realm, boolean isCreate) { + public void onImportUserFromLDAP(ComponentModel mapperModel, LDAPStorageProvider ldapProvider, LDAPObject ldapUser, UserModel user, RealmModel realm, boolean isCreate) { getDelegate(mapperModel, ldapProvider, realm).onImportUserFromLDAP(ldapUser, user, isCreate); } @Override - public void onRegisterUserToLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, UserModel localUser, RealmModel realm) { + public void onRegisterUserToLDAP(ComponentModel mapperModel, LDAPStorageProvider ldapProvider, LDAPObject ldapUser, UserModel localUser, RealmModel realm) { getDelegate(mapperModel, ldapProvider, realm).onRegisterUserToLDAP(ldapUser, localUser); } @Override - public UserModel proxy(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, UserModel delegate, RealmModel realm) { + public UserModel proxy(ComponentModel mapperModel, LDAPStorageProvider ldapProvider, LDAPObject ldapUser, UserModel delegate, RealmModel realm) { return getDelegate(mapperModel, ldapProvider, realm).proxy(ldapUser, delegate); } @Override - public void beforeLDAPQuery(UserFederationMapperModel mapperModel, LDAPQuery query) { + public void beforeLDAPQuery(ComponentModel mapperModel, LDAPQuery query) { // Improve if needed getDelegate(mapperModel, query.getLdapProvider(), null).beforeLDAPQuery(query); } @Override - public List getGroupMembers(UserFederationMapperModel mapperModel, UserFederationProvider ldapProvider, RealmModel realm, GroupModel group, int firstResult, int maxResults) { + public List getGroupMembers(ComponentModel mapperModel, LDAPStorageProvider ldapProvider, RealmModel realm, GroupModel group, int firstResult, int maxResults) { return getDelegate(mapperModel, ldapProvider, realm).getGroupMembers(group, firstResult, maxResults); } @Override - public boolean onAuthenticationFailure(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, UserModel user, AuthenticationException ldapException, RealmModel realm) { + public boolean onAuthenticationFailure(ComponentModel mapperModel, LDAPStorageProvider ldapProvider, LDAPObject ldapUser, UserModel user, AuthenticationException ldapException, RealmModel realm) { return getDelegate(mapperModel, ldapProvider, realm).onAuthenticationFailure(ldapUser, user, ldapException); } - private AbstractLDAPFederationMapper getDelegate(UserFederationMapperModel mapperModel, UserFederationProvider federationProvider, RealmModel realm) { - LDAPFederationProvider ldapProvider = (LDAPFederationProvider) federationProvider; + private AbstractLDAPStorageMapper getDelegate(ComponentModel mapperModel, LDAPStorageProvider federationProvider, RealmModel realm) { + LDAPStorageProvider ldapProvider = (LDAPStorageProvider) federationProvider; return factory.createMapper(mapperModel, ldapProvider, realm); } diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/LDAPStorageMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/LDAPStorageMapperFactory.java new file mode 100644 index 0000000000..d5e8318ed3 --- /dev/null +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/LDAPStorageMapperFactory.java @@ -0,0 +1,97 @@ +/* + * 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.Config; +import org.keycloak.component.ComponentFactory; +import org.keycloak.component.ComponentModel; +import org.keycloak.component.ComponentValidationException; +import org.keycloak.component.SubComponentFactory; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.models.RealmModel; +import org.keycloak.provider.ProviderConfigProperty; +import org.keycloak.representations.idm.UserFederationMapperSyncConfigRepresentation; + +import java.util.Collections; +import java.util.List; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface LDAPStorageMapperFactory extends SubComponentFactory { + /** + * called per Keycloak transaction. + * + * @param session + * @param model + * @return + */ + T create(KeycloakSession session, ComponentModel model); + + /** + * This is the name of the provider and will be showed in the admin console as an option. + * + * @return + */ + @Override + String getId(); + + @Override + default void init(Config.Scope config) { + + } + + @Override + default void postInit(KeycloakSessionFactory factory) { + + } + + @Override + default void close() { + + } + + @Override + default String getHelpText() { + return ""; + } + + @Override + default List getConfigProperties() { + return Collections.EMPTY_LIST; + } + + @Override + default void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel config) throws ComponentValidationException { + + } + + /** + * Called when UserStorageProviderModel is created. This allows you to do initialization of any additional configuration + * you need to add. For example, you may be introspecting a database or ldap schema to automatically create mappings. + * + * @param session + * @param realm + * @param model + */ + @Override + default void onCreate(KeycloakSession session, RealmModel realm, ComponentModel model) { + + } +} diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/LDAPStorageMapperSpi.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/LDAPStorageMapperSpi.java new file mode 100644 index 0000000000..c7b8185dbf --- /dev/null +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/LDAPStorageMapperSpi.java @@ -0,0 +1,50 @@ +/* + * 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.credential.CredentialProvider; +import org.keycloak.credential.CredentialProviderFactory; +import org.keycloak.provider.Provider; +import org.keycloak.provider.ProviderFactory; +import org.keycloak.provider.Spi; + +/** + * @author Kunal Kerkar + */ +public class LDAPStorageMapperSpi implements Spi { + + @Override + public boolean isInternal() { + return false; + } + + @Override + public String getName() { + return "ldap-mapper"; + } + + @Override + public Class getProviderClass() { + return LDAPStorageMapper.class; + } + + @Override + public Class getProviderFactoryClass() { + return LDAPStorageMapperFactory.class; + } +} diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/PasswordUpdated.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/PasswordUpdated.java similarity index 90% rename from federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/PasswordUpdated.java rename to federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/PasswordUpdated.java index 1ec5ad8e3c..c4d7b5eef8 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/PasswordUpdated.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/PasswordUpdated.java @@ -14,11 +14,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.keycloak.federation.ldap.mappers; +package org.keycloak.storage.ldap.mappers; import org.keycloak.credential.CredentialInput; -import org.keycloak.federation.ldap.idm.model.LDAPObject; import org.keycloak.models.UserModel; +import org.keycloak.storage.ldap.idm.model.LDAPObject; /** * @author Bill Burke diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/TxAwareLDAPUserModelDelegate.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/TxAwareLDAPUserModelDelegate.java similarity index 94% rename from federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/TxAwareLDAPUserModelDelegate.java rename to federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/TxAwareLDAPUserModelDelegate.java index 9126a668b8..4fc5cafb25 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/TxAwareLDAPUserModelDelegate.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/TxAwareLDAPUserModelDelegate.java @@ -15,14 +15,14 @@ * limitations under the License. */ -package org.keycloak.federation.ldap.mappers; +package org.keycloak.storage.ldap.mappers; import org.jboss.logging.Logger; -import org.keycloak.federation.ldap.LDAPFederationProvider; -import org.keycloak.federation.ldap.idm.model.LDAPObject; import org.keycloak.models.KeycloakTransaction; import org.keycloak.models.UserModel; import org.keycloak.models.utils.UserModelDelegate; +import org.keycloak.storage.ldap.LDAPStorageProvider; +import org.keycloak.storage.ldap.idm.model.LDAPObject; /** * @author Marek Posolda @@ -31,11 +31,11 @@ public abstract class TxAwareLDAPUserModelDelegate extends UserModelDelegate { public static final Logger logger = Logger.getLogger(TxAwareLDAPUserModelDelegate.class); - protected LDAPFederationProvider provider; + protected LDAPStorageProvider provider; protected LDAPObject ldapUser; private final LDAPTransaction transaction; - public TxAwareLDAPUserModelDelegate(UserModel delegate, LDAPFederationProvider provider, LDAPObject ldapUser) { + public TxAwareLDAPUserModelDelegate(UserModel delegate, LDAPStorageProvider provider, LDAPObject ldapUser) { super(delegate); this.provider = provider; this.ldapUser = ldapUser; diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserAttributeLDAPFederationMapper.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/UserAttributeLDAPStorageMapper.java similarity index 90% rename from federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserAttributeLDAPFederationMapper.java rename to federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/UserAttributeLDAPStorageMapper.java index bc1041595e..cd41c5af43 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserAttributeLDAPFederationMapper.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/UserAttributeLDAPStorageMapper.java @@ -15,25 +15,25 @@ * limitations under the License. */ -package org.keycloak.federation.ldap.mappers; +package org.keycloak.storage.ldap.mappers; import org.jboss.logging.Logger; -import org.keycloak.federation.ldap.LDAPFederationProvider; -import org.keycloak.federation.ldap.idm.model.LDAPObject; -import org.keycloak.federation.ldap.idm.query.Condition; -import org.keycloak.federation.ldap.idm.query.internal.LDAPQuery; +import org.keycloak.component.ComponentModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.LDAPConstants; import org.keycloak.models.ModelDuplicateException; import org.keycloak.models.RealmModel; -import org.keycloak.models.UserFederationMapperModel; -import org.keycloak.models.UserFederationProvider; import org.keycloak.models.UserModel; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.UserModelDelegate; import org.keycloak.models.utils.reflection.Property; import org.keycloak.models.utils.reflection.PropertyCriteria; import org.keycloak.models.utils.reflection.PropertyQueries; +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.idm.query.Condition; +import org.keycloak.storage.ldap.idm.query.internal.LDAPQuery; import java.lang.reflect.Method; import java.util.ArrayList; @@ -48,9 +48,9 @@ import java.util.Set; /** * @author Marek Posolda */ -public class UserAttributeLDAPFederationMapper extends AbstractLDAPFederationMapper { +public class UserAttributeLDAPStorageMapper extends AbstractLDAPStorageMapper { - private static final Logger logger = Logger.getLogger(UserAttributeLDAPFederationMapper.class); + private static final Logger logger = Logger.getLogger(UserAttributeLDAPStorageMapper.class); private static final Map> userModelProperties; @@ -81,14 +81,14 @@ public class UserAttributeLDAPFederationMapper extends AbstractLDAPFederationMap public static final String ALWAYS_READ_VALUE_FROM_LDAP = "always.read.value.from.ldap"; public static final String IS_MANDATORY_IN_LDAP = "is.mandatory.in.ldap"; - public UserAttributeLDAPFederationMapper(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, RealmModel realm) { + public UserAttributeLDAPStorageMapper(ComponentModel mapperModel, LDAPStorageProvider ldapProvider, RealmModel realm) { super(mapperModel, ldapProvider, realm); } @Override public void onImportUserFromLDAP(LDAPObject ldapUser, UserModel user, boolean isCreate) { - String userModelAttrName = mapperModel.getConfig().get(USER_MODEL_ATTRIBUTE); - String ldapAttrName = mapperModel.getConfig().get(LDAP_ATTRIBUTE); + String userModelAttrName = mapperModel.getConfig().getFirst(USER_MODEL_ATTRIBUTE); + String ldapAttrName = mapperModel.getConfig().getFirst(LDAP_ATTRIBUTE); Property userModelProperty = userModelProperties.get(userModelAttrName.toLowerCase()); @@ -114,8 +114,8 @@ public class UserAttributeLDAPFederationMapper extends AbstractLDAPFederationMap @Override public void onRegisterUserToLDAP(LDAPObject ldapUser, UserModel localUser) { - String userModelAttrName = mapperModel.getConfig().get(USER_MODEL_ATTRIBUTE); - String ldapAttrName = mapperModel.getConfig().get(LDAP_ATTRIBUTE); + String userModelAttrName = mapperModel.getConfig().getFirst(USER_MODEL_ATTRIBUTE); + String ldapAttrName = mapperModel.getConfig().getFirst(LDAP_ATTRIBUTE); boolean isMandatoryInLdap = parseBooleanParameter(mapperModel, IS_MANDATORY_IN_LDAP); Property userModelProperty = userModelProperties.get(userModelAttrName.toLowerCase()); @@ -157,11 +157,12 @@ public class UserAttributeLDAPFederationMapper extends AbstractLDAPFederationMap // throw ModelDuplicateException if there is different user in model with same email protected void checkDuplicateEmail(String userModelAttrName, String email, RealmModel realm, KeycloakSession session, UserModel user) { + if (email == null) return; if (UserModel.EMAIL.equalsIgnoreCase(userModelAttrName)) { // lowercase before search email = KeycloakModelUtils.toLowerCaseSafe(email); - UserModel that = session.userStorage().getUserByEmail(email, realm); + UserModel that = session.userLocalStorage().getUserByEmail(email, realm); if (that != null && !that.getId().equals(user.getId())) { session.getTransactionManager().setRollbackOnly(); String exceptionMessage = String.format("Can't import user '%s' from LDAP because email '%s' already exists in Keycloak. Existing user with this email is '%s'", user.getUsername(), email, that.getUsername()); @@ -172,13 +173,13 @@ public class UserAttributeLDAPFederationMapper extends AbstractLDAPFederationMap @Override public UserModel proxy(final LDAPObject ldapUser, UserModel delegate) { - final String userModelAttrName = mapperModel.getConfig().get(USER_MODEL_ATTRIBUTE); - final String ldapAttrName = mapperModel.getConfig().get(LDAP_ATTRIBUTE); + final String userModelAttrName = mapperModel.getConfig().getFirst(USER_MODEL_ATTRIBUTE); + final String ldapAttrName = mapperModel.getConfig().getFirst(LDAP_ATTRIBUTE); boolean isAlwaysReadValueFromLDAP = parseBooleanParameter(mapperModel, ALWAYS_READ_VALUE_FROM_LDAP); final boolean isMandatoryInLdap = parseBooleanParameter(mapperModel, IS_MANDATORY_IN_LDAP); // For writable mode, we want to propagate writing of attribute to LDAP as well - if (ldapProvider.getEditMode() == UserFederationProvider.EditMode.WRITABLE && !isReadOnly()) { + if (ldapProvider.getEditMode() == LDAPStorageProviderFactory.EditMode.WRITABLE && !isReadOnly()) { delegate = new TxAwareLDAPUserModelDelegate(delegate, ldapProvider, ldapUser) { @@ -330,8 +331,8 @@ public class UserAttributeLDAPFederationMapper extends AbstractLDAPFederationMap @Override public void beforeLDAPQuery(LDAPQuery query) { - String userModelAttrName = mapperModel.getConfig().get(USER_MODEL_ATTRIBUTE); - String ldapAttrName = mapperModel.getConfig().get(LDAP_ATTRIBUTE); + String userModelAttrName = mapperModel.getConfig().getFirst(USER_MODEL_ATTRIBUTE); + String ldapAttrName = mapperModel.getConfig().getFirst(LDAP_ATTRIBUTE); // Add mapped attribute to returning ldap attributes query.addReturningLdapAttribute(ldapAttrName); diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/UserAttributeLDAPStorageMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/UserAttributeLDAPStorageMapperFactory.java new file mode 100755 index 0000000000..99d9ea1b2f --- /dev/null +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/UserAttributeLDAPStorageMapperFactory.java @@ -0,0 +1,110 @@ +/* + * 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.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserFederationProvider; +import org.keycloak.models.UserFederationProviderModel; +import org.keycloak.provider.ProviderConfigProperty; +import org.keycloak.provider.ProviderConfigurationBuilder; +import org.keycloak.storage.ldap.LDAPConfig; +import org.keycloak.storage.ldap.LDAPStorageProvider; +import org.keycloak.storage.ldap.LDAPStorageProviderFactory; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author Marek Posolda + */ +public class UserAttributeLDAPStorageMapperFactory extends AbstractLDAPStorageMapperFactory { + + public static final String PROVIDER_ID = "user-attribute-ldap-mapper"; + protected static final List configProperties; + + static { + List props = getConfigProps(null); + configProperties = props; + } + + private static List getConfigProps(ComponentModel parent) { + String readOnly = "false"; + if (parent != null) { + LDAPConfig ldapConfig = new LDAPConfig(parent.getConfig()); + readOnly = ldapConfig.getEditMode() == LDAPStorageProviderFactory.EditMode.WRITABLE ? "false" : "true"; + } + return ProviderConfigurationBuilder.create() + .property().name(UserAttributeLDAPStorageMapper.USER_MODEL_ATTRIBUTE) + .label("User Model Attribute") + .helpText("Name of mapped UserModel property or UserModel attribute in Keycloak DB. For example 'firstName', 'lastName, 'email', 'street' etc.") + .type(ProviderConfigProperty.STRING_TYPE) + .add() + .property().name(UserAttributeLDAPStorageMapper.LDAP_ATTRIBUTE).label("LDAP Attribute").helpText("Name of mapped attribute on LDAP object. For example 'cn', 'sn, 'mail', 'street' etc.") + .type(ProviderConfigProperty.STRING_TYPE) + .add() + .property().name(UserAttributeLDAPStorageMapper.READ_ONLY).label("Read Only") + .helpText("Read-only attribute is imported from LDAP to Keycloak DB, but it's not saved back to LDAP when user is updated in Keycloak.") + .type(ProviderConfigProperty.BOOLEAN_TYPE) + .defaultValue(readOnly) + .add() + .property().name(UserAttributeLDAPStorageMapper.ALWAYS_READ_VALUE_FROM_LDAP).label("Always Read Value From LDAP") + .helpText("If on, then during reading of the user will be value of attribute from LDAP always used instead of the value from Keycloak DB") + .type(ProviderConfigProperty.BOOLEAN_TYPE).defaultValue("false").add() + .property().name(UserAttributeLDAPStorageMapper.IS_MANDATORY_IN_LDAP).label("Is Mandatory In LDAP") + .helpText("If true, attribute is mandatory in LDAP. Hence if there is no value in Keycloak DB, the empty value will be set to be propagated to LDAP") + .type(ProviderConfigProperty.BOOLEAN_TYPE) + .defaultValue("false").add() + .build(); + } + + @Override + public String getHelpText() { + return "Used to map single attribute from LDAP user to attribute of UserModel in Keycloak DB"; + } + + @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 { + checkMandatoryConfigAttribute(UserAttributeLDAPStorageMapper.USER_MODEL_ATTRIBUTE, "User Model Attribute", config); + checkMandatoryConfigAttribute(UserAttributeLDAPStorageMapper.LDAP_ATTRIBUTE, "LDAP Attribute", config); + + } + + @Override + protected AbstractLDAPStorageMapper createMapper(ComponentModel mapperModel, LDAPStorageProvider federationProvider, RealmModel realm) { + return new UserAttributeLDAPStorageMapper(mapperModel, federationProvider, realm); + } + + @Override + public List getConfigProperties(RealmModel realm, ComponentModel parent) { + return getConfigProps(parent); + } +} diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/CommonLDAPGroupMapper.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/CommonLDAPGroupMapper.java similarity index 89% rename from federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/CommonLDAPGroupMapper.java rename to federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/CommonLDAPGroupMapper.java index 949b55d590..018b0e34d4 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/CommonLDAPGroupMapper.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/CommonLDAPGroupMapper.java @@ -15,9 +15,9 @@ * limitations under the License. */ -package org.keycloak.federation.ldap.mappers.membership; +package org.keycloak.storage.ldap.mappers.membership; -import org.keycloak.federation.ldap.idm.query.internal.LDAPQuery; +import org.keycloak.storage.ldap.idm.query.internal.LDAPQuery; /** * Mapper related to mapping of LDAP groups to keycloak model objects (either keycloak roles or keycloak groups) diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/CommonLDAPGroupMapperConfig.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/CommonLDAPGroupMapperConfig.java similarity index 84% rename from federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/CommonLDAPGroupMapperConfig.java rename to federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/CommonLDAPGroupMapperConfig.java index ea50569710..a4ff1756b4 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/CommonLDAPGroupMapperConfig.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/CommonLDAPGroupMapperConfig.java @@ -15,11 +15,11 @@ * limitations under the License. */ -package org.keycloak.federation.ldap.mappers.membership; +package org.keycloak.storage.ldap.mappers.membership; +import org.keycloak.component.ComponentModel; import org.keycloak.models.LDAPConstants; import org.keycloak.models.ModelException; -import org.keycloak.models.UserFederationMapperModel; import java.util.HashSet; import java.util.Set; @@ -42,24 +42,24 @@ public abstract class CommonLDAPGroupMapperConfig { public static final String USER_ROLES_RETRIEVE_STRATEGY = "user.roles.retrieve.strategy"; - protected final UserFederationMapperModel mapperModel; + protected final ComponentModel mapperModel; - public CommonLDAPGroupMapperConfig(UserFederationMapperModel mapperModel) { + public CommonLDAPGroupMapperConfig(ComponentModel mapperModel) { this.mapperModel = mapperModel; } public String getMembershipLdapAttribute() { - String membershipAttrName = mapperModel.getConfig().get(MEMBERSHIP_LDAP_ATTRIBUTE); + String membershipAttrName = mapperModel.getConfig().getFirst(MEMBERSHIP_LDAP_ATTRIBUTE); return membershipAttrName!=null ? membershipAttrName : LDAPConstants.MEMBER; } public MembershipType getMembershipTypeLdapAttribute() { - String membershipType = mapperModel.getConfig().get(MEMBERSHIP_ATTRIBUTE_TYPE); + String membershipType = mapperModel.getConfig().getFirst(MEMBERSHIP_ATTRIBUTE_TYPE); return (membershipType!=null && !membershipType.isEmpty()) ? Enum.valueOf(MembershipType.class, membershipType) : MembershipType.DN; } public LDAPGroupMapperMode getMode() { - String modeString = mapperModel.getConfig().get(MODE); + String modeString = mapperModel.getConfig().getFirst(MODE); if (modeString == null || modeString.isEmpty()) { throw new ModelException("Mode is missing! Check your configuration"); } diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/LDAPGroupMapperMode.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/LDAPGroupMapperMode.java similarity index 96% rename from federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/LDAPGroupMapperMode.java rename to federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/LDAPGroupMapperMode.java index dc447311e1..b048c220b7 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/LDAPGroupMapperMode.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/LDAPGroupMapperMode.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.keycloak.federation.ldap.mappers.membership; +package org.keycloak.storage.ldap.mappers.membership; /** * @author Marek Posolda diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/MembershipType.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/MembershipType.java similarity index 79% rename from federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/MembershipType.java rename to federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/MembershipType.java index 4c9de3e785..f62e40eff7 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/MembershipType.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/MembershipType.java @@ -15,19 +15,19 @@ * limitations under the License. */ -package org.keycloak.federation.ldap.mappers.membership; +package org.keycloak.storage.ldap.mappers.membership; -import org.keycloak.federation.ldap.LDAPConfig; -import org.keycloak.federation.ldap.LDAPFederationProvider; -import org.keycloak.federation.ldap.LDAPUtils; -import org.keycloak.federation.ldap.idm.model.LDAPDn; -import org.keycloak.federation.ldap.idm.model.LDAPObject; -import org.keycloak.federation.ldap.idm.query.Condition; -import org.keycloak.federation.ldap.idm.query.internal.LDAPQuery; -import org.keycloak.federation.ldap.idm.query.internal.LDAPQueryConditionsBuilder; -import org.keycloak.federation.ldap.mappers.membership.group.GroupLDAPFederationMapper; import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel; +import org.keycloak.storage.ldap.LDAPConfig; +import org.keycloak.storage.ldap.LDAPStorageProvider; +import org.keycloak.storage.ldap.LDAPUtils; +import org.keycloak.storage.ldap.idm.model.LDAPDn; +import org.keycloak.storage.ldap.idm.model.LDAPObject; +import org.keycloak.storage.ldap.idm.query.Condition; +import org.keycloak.storage.ldap.idm.query.internal.LDAPQuery; +import org.keycloak.storage.ldap.idm.query.internal.LDAPQueryConditionsBuilder; +import org.keycloak.storage.ldap.mappers.membership.group.GroupLDAPStorageMapper; import java.util.ArrayList; import java.util.Collections; @@ -47,7 +47,7 @@ public enum MembershipType { DN { @Override - public Set getLDAPSubgroups(GroupLDAPFederationMapper groupMapper, LDAPObject ldapGroup) { + public Set getLDAPSubgroups(GroupLDAPStorageMapper groupMapper, LDAPObject ldapGroup) { CommonLDAPGroupMapperConfig config = groupMapper.getConfig(); return getLDAPMembersWithParent(ldapGroup, config.getMembershipLdapAttribute(), LDAPDn.fromString(config.getLDAPGroupsDn())); } @@ -68,9 +68,9 @@ public enum MembershipType { } @Override - public List getGroupMembers(GroupLDAPFederationMapper groupMapper, LDAPObject ldapGroup, int firstResult, int maxResults) { + public List getGroupMembers(GroupLDAPStorageMapper groupMapper, LDAPObject ldapGroup, int firstResult, int maxResults) { RealmModel realm = groupMapper.getRealm(); - LDAPFederationProvider ldapProvider = groupMapper.getLdapProvider(); + LDAPStorageProvider ldapProvider = groupMapper.getLdapProvider(); CommonLDAPGroupMapperConfig config = groupMapper.getConfig(); LDAPDn usersDn = LDAPDn.fromString(ldapProvider.getLdapIdentityStore().getConfig().getUsersDn()); @@ -129,12 +129,12 @@ public enum MembershipType { // Group inheritance not supported for this config @Override - public Set getLDAPSubgroups(GroupLDAPFederationMapper groupMapper, LDAPObject ldapGroup) { + public Set getLDAPSubgroups(GroupLDAPStorageMapper groupMapper, LDAPObject ldapGroup) { return Collections.emptySet(); } @Override - public List getGroupMembers(GroupLDAPFederationMapper groupMapper, LDAPObject ldapGroup, int firstResult, int maxResults) { + public List getGroupMembers(GroupLDAPStorageMapper groupMapper, LDAPObject ldapGroup, int firstResult, int maxResults) { String memberAttrName = groupMapper.getConfig().getMembershipLdapAttribute(); Set memberUids = LDAPUtils.getExistingMemberships(memberAttrName, ldapGroup); @@ -151,7 +151,7 @@ public enum MembershipType { }; - public abstract Set getLDAPSubgroups(GroupLDAPFederationMapper groupMapper, LDAPObject ldapGroup); + public abstract Set getLDAPSubgroups(GroupLDAPStorageMapper groupMapper, LDAPObject ldapGroup); - public abstract List getGroupMembers(GroupLDAPFederationMapper groupMapper, LDAPObject ldapGroup, int firstResult, int maxResults); + public abstract List getGroupMembers(GroupLDAPStorageMapper groupMapper, LDAPObject ldapGroup, int firstResult, int maxResults); } diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/UserRolesRetrieveStrategy.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/UserRolesRetrieveStrategy.java similarity index 91% rename from federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/UserRolesRetrieveStrategy.java rename to federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/UserRolesRetrieveStrategy.java index fe33e3b106..29dd1d4243 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/UserRolesRetrieveStrategy.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/UserRolesRetrieveStrategy.java @@ -15,16 +15,16 @@ * limitations under the License. */ -package org.keycloak.federation.ldap.mappers.membership; +package org.keycloak.storage.ldap.mappers.membership; -import org.keycloak.federation.ldap.LDAPUtils; -import org.keycloak.federation.ldap.idm.model.LDAPDn; -import org.keycloak.federation.ldap.idm.model.LDAPObject; -import org.keycloak.federation.ldap.idm.query.Condition; -import org.keycloak.federation.ldap.idm.query.internal.LDAPQuery; -import org.keycloak.federation.ldap.idm.query.internal.LDAPQueryConditionsBuilder; import org.keycloak.models.LDAPConstants; +import org.keycloak.storage.ldap.LDAPUtils; +import org.keycloak.storage.ldap.idm.model.LDAPDn; +import org.keycloak.storage.ldap.idm.model.LDAPObject; +import org.keycloak.storage.ldap.idm.query.Condition; +import org.keycloak.storage.ldap.idm.query.internal.LDAPQuery; +import org.keycloak.storage.ldap.idm.query.internal.LDAPQueryConditionsBuilder; import java.util.Collections; import java.util.LinkedList; diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/group/GroupLDAPFederationMapper.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/group/GroupLDAPStorageMapper.java similarity index 92% rename from federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/group/GroupLDAPFederationMapper.java rename to federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/group/GroupLDAPStorageMapper.java index d135dfa017..515ce1643d 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/group/GroupLDAPFederationMapper.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/group/GroupLDAPStorageMapper.java @@ -15,31 +15,32 @@ * limitations under the License. */ -package org.keycloak.federation.ldap.mappers.membership.group; +package org.keycloak.storage.ldap.mappers.membership.group; import org.jboss.logging.Logger; -import org.keycloak.federation.ldap.LDAPFederationProvider; -import org.keycloak.federation.ldap.LDAPUtils; -import org.keycloak.federation.ldap.idm.model.LDAPDn; -import org.keycloak.federation.ldap.idm.model.LDAPObject; -import org.keycloak.federation.ldap.idm.query.Condition; -import org.keycloak.federation.ldap.idm.query.internal.LDAPQuery; -import org.keycloak.federation.ldap.idm.query.internal.LDAPQueryConditionsBuilder; -import org.keycloak.federation.ldap.mappers.AbstractLDAPFederationMapper; -import org.keycloak.federation.ldap.mappers.membership.CommonLDAPGroupMapper; -import org.keycloak.federation.ldap.mappers.membership.CommonLDAPGroupMapperConfig; -import org.keycloak.federation.ldap.mappers.membership.LDAPGroupMapperMode; -import org.keycloak.federation.ldap.mappers.membership.MembershipType; -import org.keycloak.federation.ldap.mappers.membership.UserRolesRetrieveStrategy; +import org.keycloak.component.ComponentModel; import org.keycloak.models.GroupModel; import org.keycloak.models.ModelException; import org.keycloak.models.RealmModel; -import org.keycloak.models.UserFederationMapperModel; -import org.keycloak.models.UserFederationSyncResult; +import org.keycloak.models.RoleModel; import org.keycloak.models.UserModel; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.RoleUtils; import org.keycloak.models.utils.UserModelDelegate; +import org.keycloak.storage.ldap.LDAPStorageProvider; +import org.keycloak.storage.ldap.LDAPUtils; +import org.keycloak.storage.ldap.idm.model.LDAPDn; +import org.keycloak.storage.ldap.idm.model.LDAPObject; +import org.keycloak.storage.ldap.idm.query.Condition; +import org.keycloak.storage.ldap.idm.query.internal.LDAPQuery; +import org.keycloak.storage.ldap.idm.query.internal.LDAPQueryConditionsBuilder; +import org.keycloak.storage.ldap.mappers.AbstractLDAPStorageMapper; +import org.keycloak.storage.ldap.mappers.membership.CommonLDAPGroupMapper; +import org.keycloak.storage.ldap.mappers.membership.CommonLDAPGroupMapperConfig; +import org.keycloak.storage.ldap.mappers.membership.LDAPGroupMapperMode; +import org.keycloak.storage.ldap.mappers.membership.MembershipType; +import org.keycloak.storage.ldap.mappers.membership.UserRolesRetrieveStrategy; +import org.keycloak.storage.user.SynchronizationResult; import java.util.Collection; import java.util.Collections; @@ -49,22 +50,21 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; -import org.keycloak.models.RoleModel; /** * @author Marek Posolda */ -public class GroupLDAPFederationMapper extends AbstractLDAPFederationMapper implements CommonLDAPGroupMapper { +public class GroupLDAPStorageMapper extends AbstractLDAPStorageMapper implements CommonLDAPGroupMapper { - private static final Logger logger = Logger.getLogger(GroupLDAPFederationMapper.class); + private static final Logger logger = Logger.getLogger(GroupLDAPStorageMapper.class); private final GroupMapperConfig config; - private final GroupLDAPFederationMapperFactory factory; + private final GroupLDAPStorageMapperFactory factory; // Flag to avoid syncing multiple times per transaction private boolean syncFromLDAPPerformedInThisTransaction = false; - public GroupLDAPFederationMapper(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, RealmModel realm, GroupLDAPFederationMapperFactory factory) { + public GroupLDAPStorageMapper(ComponentModel mapperModel, LDAPStorageProvider ldapProvider, RealmModel realm, GroupLDAPStorageMapperFactory factory) { super(mapperModel, ldapProvider, realm); this.config = new GroupMapperConfig(mapperModel); this.factory = factory; @@ -138,8 +138,9 @@ public class GroupLDAPFederationMapper extends AbstractLDAPFederationMapper impl // Sync from Ldap to KC - public UserFederationSyncResult syncDataFromFederationProviderToKeycloak() { - UserFederationSyncResult syncResult = new UserFederationSyncResult() { + @Override + public SynchronizationResult syncDataFromFederationProviderToKeycloak() { + SynchronizationResult syncResult = new SynchronizationResult() { @Override public String getStatus() { @@ -148,7 +149,7 @@ public class GroupLDAPFederationMapper extends AbstractLDAPFederationMapper impl }; - logger.debugf("Syncing groups from LDAP into Keycloak DB. Mapper is [%s], LDAP provider is [%s]", mapperModel.getName(), ldapProvider.getModel().getDisplayName()); + logger.debugf("Syncing groups from LDAP into Keycloak DB. Mapper is [%s], LDAP provider is [%s]", mapperModel.getName(), ldapProvider.getModel().getName()); // Get all LDAP groups List ldapGroups = getAllLDAPGroups(); @@ -211,7 +212,7 @@ public class GroupLDAPFederationMapper extends AbstractLDAPFederationMapper impl return syncResult; } - private void updateKeycloakGroupTree(List groupTrees, Map ldapGroups, UserFederationSyncResult syncResult) { + private void updateKeycloakGroupTree(List groupTrees, Map ldapGroups, SynchronizationResult syncResult) { Set visitedGroupIds = new HashSet<>(); for (GroupTreeResolver.GroupTreeEntry groupEntry : groupTrees) { @@ -224,7 +225,7 @@ public class GroupLDAPFederationMapper extends AbstractLDAPFederationMapper impl } } - private void updateKeycloakGroupTreeEntry(GroupTreeResolver.GroupTreeEntry groupTreeEntry, Map ldapGroups, GroupModel kcParent, UserFederationSyncResult syncResult, Set visitedGroupIds) { + private void updateKeycloakGroupTreeEntry(GroupTreeResolver.GroupTreeEntry groupTreeEntry, Map ldapGroups, GroupModel kcParent, SynchronizationResult syncResult, Set visitedGroupIds) { String groupName = groupTreeEntry.getGroupName(); // Check if group already exists @@ -262,7 +263,7 @@ public class GroupLDAPFederationMapper extends AbstractLDAPFederationMapper impl } } - private void dropNonExistingKcGroups(UserFederationSyncResult syncResult, Set visitedGroupIds) { + private void dropNonExistingKcGroups(SynchronizationResult syncResult, Set visitedGroupIds) { // Remove keycloak groups, which doesn't exists in LDAP List allGroups = realm.getGroups(); for (GroupModel kcGroup : allGroups) { @@ -348,8 +349,8 @@ public class GroupLDAPFederationMapper extends AbstractLDAPFederationMapper impl // Sync from Keycloak to LDAP - public UserFederationSyncResult syncDataFromKeycloakToFederationProvider() { - UserFederationSyncResult syncResult = new UserFederationSyncResult() { + public SynchronizationResult syncDataFromKeycloakToFederationProvider() { + SynchronizationResult syncResult = new SynchronizationResult() { @Override public String getStatus() { @@ -363,7 +364,7 @@ public class GroupLDAPFederationMapper extends AbstractLDAPFederationMapper impl return syncResult; } - logger.debugf("Syncing groups from Keycloak into LDAP. Mapper is [%s], LDAP provider is [%s]", mapperModel.getName(), ldapProvider.getModel().getDisplayName()); + logger.debugf("Syncing groups from Keycloak into LDAP. Mapper is [%s], LDAP provider is [%s]", mapperModel.getName(), ldapProvider.getModel().getName()); // Query existing LDAP groups LDAPQuery ldapQuery = createGroupQuery(); @@ -410,7 +411,7 @@ public class GroupLDAPFederationMapper extends AbstractLDAPFederationMapper impl // For given kcGroup check if it exists in LDAP (map) by name // If not, create it in LDAP including attributes. Otherwise update attributes in LDAP. // Process this recursively for all subgroups of KC group - private void processLdapGroupSyncToLDAP(GroupModel kcGroup, Map ldapGroupsMap, Set ldapGroupNames, UserFederationSyncResult syncResult) { + private void processLdapGroupSyncToLDAP(GroupModel kcGroup, Map ldapGroupsMap, Set ldapGroupNames, SynchronizationResult syncResult) { String groupName = kcGroup.getName(); // extract group attributes to be updated to LDAP diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/group/GroupLDAPStorageMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/group/GroupLDAPStorageMapperFactory.java new file mode 100644 index 0000000000..b3baf50d35 --- /dev/null +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/group/GroupLDAPStorageMapperFactory.java @@ -0,0 +1,213 @@ +/* + * 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.membership.group; + +import org.keycloak.component.ComponentModel; +import org.keycloak.component.ComponentValidationException; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.LDAPConstants; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserFederationProvider; +import org.keycloak.models.UserFederationProviderModel; +import org.keycloak.provider.ProviderConfigProperty; +import org.keycloak.provider.ProviderConfigurationBuilder; +import org.keycloak.representations.idm.UserFederationMapperSyncConfigRepresentation; +import org.keycloak.storage.ldap.LDAPConfig; +import org.keycloak.storage.ldap.LDAPStorageProvider; +import org.keycloak.storage.ldap.LDAPStorageProviderFactory; +import org.keycloak.storage.ldap.LDAPUtils; +import org.keycloak.storage.ldap.mappers.AbstractLDAPStorageMapper; +import org.keycloak.storage.ldap.mappers.AbstractLDAPStorageMapperFactory; +import org.keycloak.storage.ldap.mappers.membership.CommonLDAPGroupMapperConfig; +import org.keycloak.storage.ldap.mappers.membership.LDAPGroupMapperMode; +import org.keycloak.storage.ldap.mappers.membership.MembershipType; +import org.keycloak.storage.ldap.mappers.membership.UserRolesRetrieveStrategy; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * @author Marek Posolda + */ +public class GroupLDAPStorageMapperFactory extends AbstractLDAPStorageMapperFactory { + + public static final String PROVIDER_ID = "group-ldap-mapper"; + + protected static final List configProperties; + protected static final Map userGroupsStrategies = new LinkedHashMap<>(); + protected static final List MEMBERSHIP_TYPES = new LinkedList<>(); + protected static final List MODES = new LinkedList<>(); + protected static final List ROLE_RETRIEVERS; + + // TODO: Merge with RoleLDAPFederationMapperFactory as there are lot of similar properties + static { + userGroupsStrategies.put(GroupMapperConfig.LOAD_GROUPS_BY_MEMBER_ATTRIBUTE, new UserRolesRetrieveStrategy.LoadRolesByMember()); + userGroupsStrategies.put(GroupMapperConfig.GET_GROUPS_FROM_USER_MEMBEROF_ATTRIBUTE, new UserRolesRetrieveStrategy.GetRolesFromUserMemberOfAttribute()); + userGroupsStrategies.put(GroupMapperConfig.LOAD_GROUPS_BY_MEMBER_ATTRIBUTE_RECURSIVELY, new UserRolesRetrieveStrategy.LoadRolesByMemberRecursively()); + for (MembershipType membershipType : MembershipType.values()) { + MEMBERSHIP_TYPES.add(membershipType.toString()); + } + for (LDAPGroupMapperMode mode : LDAPGroupMapperMode.values()) { + MODES.add(mode.toString()); + } + ROLE_RETRIEVERS = new LinkedList<>(userGroupsStrategies.keySet()); + + List config = getProps(null); + configProperties = config; + } + + private static List getProps(ComponentModel parent) { + String roleObjectClasses = LDAPConstants.GROUP_OF_NAMES; + String mode = LDAPGroupMapperMode.LDAP_ONLY.toString(); + if (parent != null) { + LDAPConfig config = new LDAPConfig(parent.getConfig()); + roleObjectClasses = config.isActiveDirectory() ? LDAPConstants.GROUP : LDAPConstants.GROUP_OF_NAMES; + mode = config.getEditMode() == LDAPStorageProviderFactory.EditMode.WRITABLE ? LDAPGroupMapperMode.LDAP_ONLY.toString() : LDAPGroupMapperMode.READ_ONLY.toString(); + } + return ProviderConfigurationBuilder.create() + .property().name(GroupMapperConfig.GROUPS_DN) + .label("LDAP Groups DN") + .helpText("LDAP DN where are groups of this tree saved. For example 'ou=groups,dc=example,dc=org' ") + .type(ProviderConfigProperty.STRING_TYPE) + .add() + .property().name(GroupMapperConfig.GROUP_NAME_LDAP_ATTRIBUTE) + .label("Group Name LDAP Attribute") + .helpText("Name of LDAP attribute, which is used in group objects for name and RDN of group. Usually it will be 'cn' . In this case typical group/role object may have DN like 'cn=Group1,ou=groups,dc=example,dc=org' ") + .type(ProviderConfigProperty.STRING_TYPE) + .defaultValue(LDAPConstants.CN) + .add() + .property().name(GroupMapperConfig.GROUP_OBJECT_CLASSES) + .label("Group Object Classes") + .helpText("Object class (or classes) of the group object. It's divided by comma if more classes needed. In typical LDAP deployment it could be 'groupOfNames' . In Active Directory it's usually 'group' ") + .type(ProviderConfigProperty.STRING_TYPE) + .defaultValue(roleObjectClasses) + .add() + .property().name(GroupMapperConfig.PRESERVE_GROUP_INHERITANCE) + .label("Preserve Group Inheritance") + .helpText("Flag whether group inheritance from LDAP should be propagated to Keycloak. If false, then all LDAP groups will be mapped as flat top-level groups in Keycloak. Otherwise group inheritance is " + + "preserved into Keycloak, but the group sync might fail if LDAP structure contains recursions or multiple parent groups per child groups") + .type(ProviderConfigProperty.BOOLEAN_TYPE) + .defaultValue("true") + .add() + .property().name(GroupMapperConfig.MEMBERSHIP_LDAP_ATTRIBUTE) + .label("Membership LDAP Attribute") + .helpText("Name of LDAP attribute on group, which is used for membership mappings. Usually it will be 'member' ") + .type(ProviderConfigProperty.STRING_TYPE) + .defaultValue(LDAPConstants.MEMBER) + .add() + .property().name(GroupMapperConfig.MEMBERSHIP_ATTRIBUTE_TYPE) + .label("Membership Attribute Type") + .helpText("DN means that LDAP group has it's members declared in form of their full DN. For example 'member: uid=john,ou=users,dc=example,dc=com' . " + + "UID means that LDAP group has it's members declared in form of pure user uids. For example 'memberUid: john' .") + .type(ProviderConfigProperty.LIST_TYPE) + .options(MEMBERSHIP_TYPES) + .defaultValue(MembershipType.DN.toString()) + .add() + .property().name(GroupMapperConfig.GROUPS_LDAP_FILTER) + .label("LDAP Filter") + .helpText("LDAP Filter adds additional custom filter to the whole query for retrieve LDAP groups. Leave this empty if no additional filtering is needed and you want to retrieve all groups from LDAP. Otherwise make sure that filter starts with '(' and ends with ')'") + .type(ProviderConfigProperty.STRING_TYPE) + .add() + .property().name(GroupMapperConfig.MODE) + .label("Mode") + .helpText("LDAP_ONLY means that all group mappings of users are retrieved from LDAP and saved into LDAP. READ_ONLY is Read-only LDAP mode where group mappings are " + + "retrieved from both LDAP and DB and merged together. New group joins are not saved to LDAP but to DB. IMPORT is Read-only LDAP mode where group mappings are " + + "retrieved from LDAP just at the time when user is imported from LDAP and then " + + "they are saved to local keycloak DB.") + .type(ProviderConfigProperty.LIST_TYPE) + .options(MODES) + .defaultValue(mode) + .add() + .property().name(GroupMapperConfig.USER_ROLES_RETRIEVE_STRATEGY) + .label("User Groups Retrieve Strategy") + .helpText("Specify how to retrieve groups of user. LOAD_GROUPS_BY_MEMBER_ATTRIBUTE means that roles of user will be retrieved by sending LDAP query to retrieve all groups where 'member' is our user. " + + "GET_GROUPS_FROM_USER_MEMBEROF_ATTRIBUTE means that groups of user will be retrieved from 'memberOf' attribute of our user. " + + "LOAD_GROUPS_BY_MEMBER_ATTRIBUTE_RECURSIVELY is applicable just in Active Directory and it means that groups of user will be retrieved recursively with usage of LDAP_MATCHING_RULE_IN_CHAIN Ldap extension.") + .type(ProviderConfigProperty.LIST_TYPE) + .options(ROLE_RETRIEVERS) + .defaultValue(GroupMapperConfig.LOAD_GROUPS_BY_MEMBER_ATTRIBUTE) + .add() + .property().name(GroupMapperConfig.MAPPED_GROUP_ATTRIBUTES) + .label("Mapped Group Attributes") + .helpText("List of names of attributes divided by comma. This points to the list of attributes on LDAP group, which will be mapped as attributes of Group in Keycloak. " + + "Leave this empty if no additional group attributes are required to be mapped in Keycloak. ") + .type(ProviderConfigProperty.STRING_TYPE) + .add() + .property().name(GroupMapperConfig.DROP_NON_EXISTING_GROUPS_DURING_SYNC) + .label("Drop non-existing groups during sync") + .helpText("If this flag is true, then during sync of groups from LDAP to Keycloak, we will keep just those Keycloak groups, which still exists in LDAP. Rest will be deleted") + .type(ProviderConfigProperty.BOOLEAN_TYPE) + .defaultValue("false") + .add() + .build(); + } + + @Override + public String getHelpText() { + return "Used to map group mappings of groups from some LDAP DN to Keycloak group mappings"; + } + + @Override + public List getConfigProperties() { + return configProperties; + } + + @Override + public String getId() { + return PROVIDER_ID; + } + + @Override + public Map getTypeMetadata() { + Map metadata = new HashMap<>(); + metadata.put("fedToKeycloakSyncSupported", true); + metadata.put("fedToKeycloakSyncMessage", "sync-ldap-groups-to-keycloak"); + metadata.put("keycloakToFedSyncSupported", true); + metadata.put("keycloakToFedSyncMessage", "sync-keycloak-groups-to-ldap"); + + return metadata; + } + + + @Override + public void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel config) throws ComponentValidationException { + checkMandatoryConfigAttribute(GroupMapperConfig.GROUPS_DN, "LDAP Groups DN", config); + checkMandatoryConfigAttribute(GroupMapperConfig.MODE, "Mode", config); + + String mt = config.getConfig().getFirst(CommonLDAPGroupMapperConfig.MEMBERSHIP_ATTRIBUTE_TYPE); + MembershipType membershipType = mt==null ? MembershipType.DN : Enum.valueOf(MembershipType.class, mt); + boolean preserveGroupInheritance = Boolean.parseBoolean(config.getConfig().getFirst(GroupMapperConfig.PRESERVE_GROUP_INHERITANCE)); + if (preserveGroupInheritance && membershipType != MembershipType.DN) { + throw new ComponentValidationException("ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType"); + } + + LDAPUtils.validateCustomLdapFilter(config.getConfig().getFirst(GroupMapperConfig.GROUPS_LDAP_FILTER)); + } + + @Override + protected AbstractLDAPStorageMapper createMapper(ComponentModel mapperModel, LDAPStorageProvider federationProvider, RealmModel realm) { + return new GroupLDAPStorageMapper(mapperModel, federationProvider, realm, this); + } + + protected UserRolesRetrieveStrategy getUserGroupsRetrieveStrategy(String strategyKey) { + return userGroupsStrategies.get(strategyKey); + } +} diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/group/GroupMapperConfig.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/group/GroupMapperConfig.java similarity index 76% rename from federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/group/GroupMapperConfig.java rename to federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/group/GroupMapperConfig.java index ae27a8806c..99cde4de7d 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/group/GroupMapperConfig.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/group/GroupMapperConfig.java @@ -15,14 +15,14 @@ * limitations under the License. */ -package org.keycloak.federation.ldap.mappers.membership.group; +package org.keycloak.storage.ldap.mappers.membership.group; -import org.keycloak.federation.ldap.LDAPFederationProvider; -import org.keycloak.federation.ldap.mappers.AbstractLDAPFederationMapper; -import org.keycloak.federation.ldap.mappers.membership.CommonLDAPGroupMapperConfig; +import org.keycloak.component.ComponentModel; import org.keycloak.models.LDAPConstants; import org.keycloak.models.ModelException; -import org.keycloak.models.UserFederationMapperModel; +import org.keycloak.storage.ldap.LDAPStorageProvider; +import org.keycloak.storage.ldap.mappers.AbstractLDAPStorageMapper; +import org.keycloak.storage.ldap.mappers.membership.CommonLDAPGroupMapperConfig; import java.util.Collection; import java.util.Collections; @@ -58,13 +58,13 @@ public class GroupMapperConfig extends CommonLDAPGroupMapperConfig { public static final String GET_GROUPS_FROM_USER_MEMBEROF_ATTRIBUTE = "GET_GROUPS_FROM_USER_MEMBEROF_ATTRIBUTE"; public static final String LOAD_GROUPS_BY_MEMBER_ATTRIBUTE_RECURSIVELY = "LOAD_GROUPS_BY_MEMBER_ATTRIBUTE_RECURSIVELY"; - public GroupMapperConfig(UserFederationMapperModel mapperModel) { + public GroupMapperConfig(ComponentModel mapperModel) { super(mapperModel); } public String getGroupsDn() { - String groupsDn = mapperModel.getConfig().get(GROUPS_DN); + String groupsDn = mapperModel.getConfig().getFirst(GROUPS_DN); if (groupsDn == null) { throw new ModelException("Groups DN is null! Check your configuration"); } @@ -77,7 +77,7 @@ public class GroupMapperConfig extends CommonLDAPGroupMapperConfig { } public String getGroupNameLdapAttribute() { - String rolesRdnAttr = mapperModel.getConfig().get(GROUP_NAME_LDAP_ATTRIBUTE); + String rolesRdnAttr = mapperModel.getConfig().getFirst(GROUP_NAME_LDAP_ATTRIBUTE); return rolesRdnAttr!=null ? rolesRdnAttr : LDAPConstants.CN; } @@ -87,16 +87,16 @@ public class GroupMapperConfig extends CommonLDAPGroupMapperConfig { } public boolean isPreserveGroupsInheritance() { - return AbstractLDAPFederationMapper.parseBooleanParameter(mapperModel, PRESERVE_GROUP_INHERITANCE); + return AbstractLDAPStorageMapper.parseBooleanParameter(mapperModel, PRESERVE_GROUP_INHERITANCE); } public String getMembershipLdapAttribute() { - String membershipAttrName = mapperModel.getConfig().get(MEMBERSHIP_LDAP_ATTRIBUTE); + String membershipAttrName = mapperModel.getConfig().getFirst(MEMBERSHIP_LDAP_ATTRIBUTE); return membershipAttrName!=null ? membershipAttrName : LDAPConstants.MEMBER; } - public Collection getGroupObjectClasses(LDAPFederationProvider ldapProvider) { - String objectClasses = mapperModel.getConfig().get(GROUP_OBJECT_CLASSES); + public Collection getGroupObjectClasses(LDAPStorageProvider ldapProvider) { + String objectClasses = mapperModel.getConfig().getFirst(GROUP_OBJECT_CLASSES); if (objectClasses == null) { // For Active directory, the default is 'group' . For other servers 'groupOfNames' objectClasses = ldapProvider.getLdapIdentityStore().getConfig().isActiveDirectory() ? LDAPConstants.GROUP : LDAPConstants.GROUP_OF_NAMES; @@ -106,20 +106,20 @@ public class GroupMapperConfig extends CommonLDAPGroupMapperConfig { } public Collection getGroupAttributes() { - String groupAttrs = mapperModel.getConfig().get(MAPPED_GROUP_ATTRIBUTES); + String groupAttrs = mapperModel.getConfig().getFirst(MAPPED_GROUP_ATTRIBUTES); return (groupAttrs == null) ? Collections.emptySet() : getConfigValues(groupAttrs); } public String getCustomLdapFilter() { - return mapperModel.getConfig().get(GROUPS_LDAP_FILTER); + return mapperModel.getConfig().getFirst(GROUPS_LDAP_FILTER); } public boolean isDropNonExistingGroupsDuringSync() { - return AbstractLDAPFederationMapper.parseBooleanParameter(mapperModel, DROP_NON_EXISTING_GROUPS_DURING_SYNC); + return AbstractLDAPStorageMapper.parseBooleanParameter(mapperModel, DROP_NON_EXISTING_GROUPS_DURING_SYNC); } public String getUserGroupsRetrieveStrategy() { - String strategyString = mapperModel.getConfig().get(USER_ROLES_RETRIEVE_STRATEGY); + String strategyString = mapperModel.getConfig().getFirst(USER_ROLES_RETRIEVE_STRATEGY); return strategyString!=null ? strategyString : LOAD_GROUPS_BY_MEMBER_ATTRIBUTE; } } diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/group/GroupTreeResolver.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/group/GroupTreeResolver.java similarity index 99% rename from federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/group/GroupTreeResolver.java rename to federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/group/GroupTreeResolver.java index cfe4dbfeff..d38f361cc3 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/group/GroupTreeResolver.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/group/GroupTreeResolver.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.keycloak.federation.ldap.mappers.membership.group; +package org.keycloak.storage.ldap.mappers.membership.group; import java.util.Arrays; import java.util.Collection; diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/role/RoleLDAPFederationMapper.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/role/RoleLDAPStorageMapper.java similarity index 90% rename from federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/role/RoleLDAPFederationMapper.java rename to federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/role/RoleLDAPStorageMapper.java index b45f348fab..90f9b7236a 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/role/RoleLDAPFederationMapper.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/role/RoleLDAPStorageMapper.java @@ -15,31 +15,31 @@ * limitations under the License. */ -package org.keycloak.federation.ldap.mappers.membership.role; +package org.keycloak.storage.ldap.mappers.membership.role; import org.jboss.logging.Logger; -import org.keycloak.federation.ldap.LDAPFederationProvider; -import org.keycloak.federation.ldap.LDAPUtils; -import org.keycloak.federation.ldap.idm.model.LDAPObject; -import org.keycloak.federation.ldap.idm.query.Condition; -import org.keycloak.federation.ldap.idm.query.internal.LDAPQuery; -import org.keycloak.federation.ldap.idm.query.internal.LDAPQueryConditionsBuilder; -import org.keycloak.federation.ldap.mappers.AbstractLDAPFederationMapper; -import org.keycloak.federation.ldap.mappers.membership.CommonLDAPGroupMapper; -import org.keycloak.federation.ldap.mappers.membership.CommonLDAPGroupMapperConfig; -import org.keycloak.federation.ldap.mappers.membership.LDAPGroupMapperMode; -import org.keycloak.federation.ldap.mappers.membership.UserRolesRetrieveStrategy; +import org.keycloak.component.ComponentModel; import org.keycloak.models.ClientModel; import org.keycloak.models.ModelException; import org.keycloak.models.RealmModel; import org.keycloak.models.RoleContainerModel; import org.keycloak.models.RoleModel; -import org.keycloak.models.UserFederationMapperModel; -import org.keycloak.models.UserFederationSyncResult; import org.keycloak.models.UserModel; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.RoleUtils; import org.keycloak.models.utils.UserModelDelegate; +import org.keycloak.storage.ldap.LDAPStorageProvider; +import org.keycloak.storage.ldap.LDAPUtils; +import org.keycloak.storage.ldap.idm.model.LDAPObject; +import org.keycloak.storage.ldap.idm.query.Condition; +import org.keycloak.storage.ldap.idm.query.internal.LDAPQuery; +import org.keycloak.storage.ldap.idm.query.internal.LDAPQueryConditionsBuilder; +import org.keycloak.storage.ldap.mappers.AbstractLDAPStorageMapper; +import org.keycloak.storage.ldap.mappers.membership.CommonLDAPGroupMapper; +import org.keycloak.storage.ldap.mappers.membership.CommonLDAPGroupMapperConfig; +import org.keycloak.storage.ldap.mappers.membership.LDAPGroupMapperMode; +import org.keycloak.storage.ldap.mappers.membership.UserRolesRetrieveStrategy; +import org.keycloak.storage.user.SynchronizationResult; import java.util.Collection; import java.util.Collections; @@ -52,14 +52,14 @@ import java.util.Set; * * @author Marek Posolda */ -public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper implements CommonLDAPGroupMapper { +public class RoleLDAPStorageMapper extends AbstractLDAPStorageMapper implements CommonLDAPGroupMapper { - private static final Logger logger = Logger.getLogger(RoleLDAPFederationMapper.class); + private static final Logger logger = Logger.getLogger(RoleLDAPStorageMapper.class); private final RoleMapperConfig config; - private final RoleLDAPFederationMapperFactory factory; + private final RoleLDAPStorageMapperFactory factory; - public RoleLDAPFederationMapper(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, RealmModel realm, RoleLDAPFederationMapperFactory factory) { + public RoleLDAPStorageMapper(ComponentModel mapperModel, LDAPStorageProvider ldapProvider, RealmModel realm, RoleLDAPStorageMapperFactory factory) { super(mapperModel, ldapProvider, realm); this.config = new RoleMapperConfig(mapperModel); this.factory = factory; @@ -111,8 +111,8 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper imple // Sync roles from LDAP to Keycloak DB @Override - public UserFederationSyncResult syncDataFromFederationProviderToKeycloak() { - UserFederationSyncResult syncResult = new UserFederationSyncResult() { + public SynchronizationResult syncDataFromFederationProviderToKeycloak() { + SynchronizationResult syncResult = new SynchronizationResult() { @Override public String getStatus() { @@ -121,7 +121,7 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper imple }; - logger.debugf("Syncing roles from LDAP into Keycloak DB. Mapper is [%s], LDAP provider is [%s]", mapperModel.getName(), ldapProvider.getModel().getDisplayName()); + logger.debugf("Syncing roles from LDAP into Keycloak DB. Mapper is [%s], LDAP provider is [%s]", mapperModel.getName(), ldapProvider.getModel().getName()); // Send LDAP query to load all roles LDAPQuery ldapRoleQuery = createRoleQuery(); @@ -147,8 +147,8 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper imple // Sync roles from Keycloak back to LDAP @Override - public UserFederationSyncResult syncDataFromKeycloakToFederationProvider() { - UserFederationSyncResult syncResult = new UserFederationSyncResult() { + public SynchronizationResult syncDataFromKeycloakToFederationProvider() { + SynchronizationResult syncResult = new SynchronizationResult() { @Override public String getStatus() { @@ -162,7 +162,7 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper imple return syncResult; } - logger.debugf("Syncing roles from Keycloak into LDAP. Mapper is [%s], LDAP provider is [%s]", mapperModel.getName(), ldapProvider.getModel().getDisplayName()); + logger.debugf("Syncing roles from Keycloak into LDAP. Mapper is [%s], LDAP provider is [%s]", mapperModel.getName(), ldapProvider.getModel().getName()); // Send LDAP query to see which roles exists there LDAPQuery ldapQuery = createRoleQuery(); diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/role/RoleLDAPStorageMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/role/RoleLDAPStorageMapperFactory.java new file mode 100644 index 0000000000..e2da595d2a --- /dev/null +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/role/RoleLDAPStorageMapperFactory.java @@ -0,0 +1,207 @@ +/* + * 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.membership.role; + +import org.keycloak.component.ComponentModel; +import org.keycloak.component.ComponentValidationException; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.LDAPConstants; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserFederationProvider; +import org.keycloak.models.UserFederationProviderModel; +import org.keycloak.provider.ProviderConfigProperty; +import org.keycloak.provider.ProviderConfigurationBuilder; +import org.keycloak.representations.idm.UserFederationMapperSyncConfigRepresentation; +import org.keycloak.storage.ldap.LDAPConfig; +import org.keycloak.storage.ldap.LDAPStorageProvider; +import org.keycloak.storage.ldap.LDAPStorageProviderFactory; +import org.keycloak.storage.ldap.LDAPUtils; +import org.keycloak.storage.ldap.mappers.AbstractLDAPStorageMapper; +import org.keycloak.storage.ldap.mappers.AbstractLDAPStorageMapperFactory; +import org.keycloak.storage.ldap.mappers.membership.LDAPGroupMapperMode; +import org.keycloak.storage.ldap.mappers.membership.MembershipType; +import org.keycloak.storage.ldap.mappers.membership.UserRolesRetrieveStrategy; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * @author Marek Posolda + */ +public class RoleLDAPStorageMapperFactory extends AbstractLDAPStorageMapperFactory { + + public static final String PROVIDER_ID = "role-ldap-mapper"; + + protected static final List configProperties; + protected static final Map userRolesStrategies = new LinkedHashMap<>(); + protected static final List MEMBERSHIP_TYPES = new LinkedList<>(); + protected static final List MODES = new LinkedList<>(); + protected static final List roleRetrievers; + + static { + userRolesStrategies.put(RoleMapperConfig.LOAD_ROLES_BY_MEMBER_ATTRIBUTE, new UserRolesRetrieveStrategy.LoadRolesByMember()); + userRolesStrategies.put(RoleMapperConfig.GET_ROLES_FROM_USER_MEMBEROF_ATTRIBUTE, new UserRolesRetrieveStrategy.GetRolesFromUserMemberOfAttribute()); + userRolesStrategies.put(RoleMapperConfig.LOAD_ROLES_BY_MEMBER_ATTRIBUTE_RECURSIVELY, new UserRolesRetrieveStrategy.LoadRolesByMemberRecursively()); + + for (MembershipType membershipType : MembershipType.values()) { + MEMBERSHIP_TYPES.add(membershipType.toString()); + } + + for (LDAPGroupMapperMode mode : LDAPGroupMapperMode.values()) { + MODES.add(mode.toString()); + } + roleRetrievers = new LinkedList<>(userRolesStrategies.keySet()); + + List config = getProps(null); + configProperties = config; + } + + private static List getProps(ComponentModel parent) { + String roleObjectClasses = LDAPConstants.GROUP_OF_NAMES; + String mode = LDAPGroupMapperMode.LDAP_ONLY.toString(); + if (parent != null) { + LDAPConfig config = new LDAPConfig(parent.getConfig()); + roleObjectClasses = config.isActiveDirectory() ? LDAPConstants.GROUP : LDAPConstants.GROUP_OF_NAMES; + mode = config.getEditMode() == LDAPStorageProviderFactory.EditMode.WRITABLE ? LDAPGroupMapperMode.LDAP_ONLY.toString() : LDAPGroupMapperMode.READ_ONLY.toString(); + } + return ProviderConfigurationBuilder.create() + .property().name(RoleMapperConfig.ROLES_DN) + .label("LDAP Roles DN") + .helpText("LDAP DN where are roles of this tree saved. For example 'ou=finance,dc=example,dc=org' ") + .type(ProviderConfigProperty.STRING_TYPE) + .add() + .property().name(RoleMapperConfig.ROLE_NAME_LDAP_ATTRIBUTE) + .label("Role Name LDAP Attribute") + .helpText("Name of LDAP attribute, which is used in role objects for name and RDN of role. Usually it will be 'cn' . In this case typical group/role object may have DN like 'cn=role1,ou=finance,dc=example,dc=org' ") + .type(ProviderConfigProperty.STRING_TYPE) + .defaultValue(LDAPConstants.CN) + .add() + .property().name(RoleMapperConfig.ROLE_OBJECT_CLASSES) + .label("Role Object Classes") + .helpText("Object class (or classes) of the role object. It's divided by comma if more classes needed. In typical LDAP deployment it could be 'groupOfNames' . In Active Directory it's usually 'group' ") + .type(ProviderConfigProperty.STRING_TYPE) + .defaultValue(roleObjectClasses) + .add() + .property().name(RoleMapperConfig.MEMBERSHIP_LDAP_ATTRIBUTE) + .label("Membership LDAP Attribute") + .helpText("Name of LDAP attribute on role, which is used for membership mappings. Usually it will be 'member' ") + .type(ProviderConfigProperty.STRING_TYPE) + .defaultValue(LDAPConstants.MEMBER) + .add() + .property().name(RoleMapperConfig.MEMBERSHIP_ATTRIBUTE_TYPE) + .label("Membership Attribute Type") + .helpText("DN means that LDAP role has it's members declared in form of their full DN. For example 'member: uid=john,ou=users,dc=example,dc=com' . " + + "UID means that LDAP role has it's members declared in form of pure user uids. For example 'memberUid: john' .") + .type(ProviderConfigProperty.LIST_TYPE) + .options(MEMBERSHIP_TYPES) + .defaultValue(MembershipType.DN.toString()) + .add() + .property().name(RoleMapperConfig.ROLES_LDAP_FILTER) + .label("LDAP Filter") + .helpText("LDAP Filter adds additional custom filter to the whole query for retrieve LDAP roles. Leave this empty if no additional filtering is needed and you want to retrieve all roles from LDAP. Otherwise make sure that filter starts with '(' and ends with ')'") + .type(ProviderConfigProperty.STRING_TYPE) + .add() + .property().name(RoleMapperConfig.MODE) + .label("Mode") + .helpText("LDAP_ONLY means that all role mappings are retrieved from LDAP and saved into LDAP. READ_ONLY is Read-only LDAP mode where role mappings are " + + "retrieved from both LDAP and DB and merged together. New role grants are not saved to LDAP but to DB. IMPORT is Read-only LDAP mode where role mappings are retrieved from LDAP just at the time when user is imported from LDAP and then " + + "they are saved to local keycloak DB.") + .type(ProviderConfigProperty.LIST_TYPE) + .options(MODES) + .defaultValue(mode) + .add() + .property().name(RoleMapperConfig.USER_ROLES_RETRIEVE_STRATEGY) + .label("User Roles Retrieve Strategy") + .helpText("Specify how to retrieve roles of user. LOAD_ROLES_BY_MEMBER_ATTRIBUTE means that roles of user will be retrieved by sending LDAP query to retrieve all roles where 'member' is our user. " + + "GET_ROLES_FROM_USER_MEMBEROF_ATTRIBUTE means that roles of user will be retrieved from 'memberOf' attribute of our user. " + + "LOAD_ROLES_BY_MEMBER_ATTRIBUTE_RECURSIVELY is applicable just in Active Directory and it means that roles of user will be retrieved recursively with usage of LDAP_MATCHING_RULE_IN_CHAIN Ldap extension.") + .type(ProviderConfigProperty.LIST_TYPE) + .options(roleRetrievers) + .defaultValue(RoleMapperConfig.LOAD_ROLES_BY_MEMBER_ATTRIBUTE) + .add() + .property().name(RoleMapperConfig.USE_REALM_ROLES_MAPPING) + .label("Use Realm Roles Mapping") + .helpText("If true, then LDAP role mappings will be mapped to realm role mappings in Keycloak. Otherwise it will be mapped to client role mappings") + .type(ProviderConfigProperty.BOOLEAN_TYPE) + .defaultValue("true") + .add() + .property().name(RoleMapperConfig.CLIENT_ID) + .label("Client ID") + .helpText("Client ID of client to which LDAP role mappings will be mapped. Applicable just if 'Use Realm Roles Mapping' is false") + .type(ProviderConfigProperty.CLIENT_LIST_TYPE) + .add() + .build(); + } + + + @Override + public String getHelpText() { + return "Used to map role mappings of roles from some LDAP DN to Keycloak role mappings of either realm roles or client roles of particular client"; + } + + @Override + public List getConfigProperties() { + return configProperties; + } + + @Override + public String getId() { + return PROVIDER_ID; + } + + @Override + public Map getTypeMetadata() { + Map metadata = new HashMap<>(); + metadata.put("fedToKeycloakSyncSupported", true); + metadata.put("fedToKeycloakSyncMessage", "sync-ldap-roles-to-keycloak"); + metadata.put("keycloakToFedSyncSupported", true); + metadata.put("keycloakToFedSyncMessage", "sync-keycloak-roles-to-ldap"); + + return metadata; + } + + + @Override + public void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel config) throws ComponentValidationException { + checkMandatoryConfigAttribute(RoleMapperConfig.ROLES_DN, "LDAP Roles DN", config); + checkMandatoryConfigAttribute(RoleMapperConfig.MODE, "Mode", config); + + String realmMappings = config.getConfig().getFirst(RoleMapperConfig.USE_REALM_ROLES_MAPPING); + boolean useRealmMappings = Boolean.parseBoolean(realmMappings); + if (!useRealmMappings) { + String clientId = config.getConfig().getFirst(RoleMapperConfig.CLIENT_ID); + if (clientId == null || clientId.trim().isEmpty()) { + throw new ComponentValidationException("ldapErrorMissingClientId"); + } + } + + LDAPUtils.validateCustomLdapFilter(config.getConfig().getFirst(RoleMapperConfig.ROLES_LDAP_FILTER)); + } + + @Override + protected AbstractLDAPStorageMapper createMapper(ComponentModel mapperModel, LDAPStorageProvider federationProvider, RealmModel realm) { + return new RoleLDAPStorageMapper(mapperModel, federationProvider, realm, this); + } + + protected UserRolesRetrieveStrategy getUserRolesRetrieveStrategy(String strategyKey) { + return userRolesStrategies.get(strategyKey); + } +} diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/role/RoleMapperConfig.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/role/RoleMapperConfig.java similarity index 78% rename from federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/role/RoleMapperConfig.java rename to federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/role/RoleMapperConfig.java index 950d923839..0dbb87c4d8 100755 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/role/RoleMapperConfig.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/role/RoleMapperConfig.java @@ -15,13 +15,13 @@ * limitations under the License. */ -package org.keycloak.federation.ldap.mappers.membership.role; +package org.keycloak.storage.ldap.mappers.membership.role; -import org.keycloak.federation.ldap.LDAPFederationProvider; -import org.keycloak.federation.ldap.mappers.membership.CommonLDAPGroupMapperConfig; +import org.keycloak.component.ComponentModel; import org.keycloak.models.LDAPConstants; import org.keycloak.models.ModelException; -import org.keycloak.models.UserFederationMapperModel; +import org.keycloak.storage.ldap.LDAPStorageProvider; +import org.keycloak.storage.ldap.mappers.membership.CommonLDAPGroupMapperConfig; import java.util.Collection; @@ -54,12 +54,12 @@ public class RoleMapperConfig extends CommonLDAPGroupMapperConfig { public static final String LOAD_ROLES_BY_MEMBER_ATTRIBUTE_RECURSIVELY = "LOAD_ROLES_BY_MEMBER_ATTRIBUTE_RECURSIVELY"; - public RoleMapperConfig(UserFederationMapperModel mapperModel) { + public RoleMapperConfig(ComponentModel mapperModel) { super(mapperModel); } public String getRolesDn() { - String rolesDn = mapperModel.getConfig().get(ROLES_DN); + String rolesDn = mapperModel.getConfig().getFirst(ROLES_DN); if (rolesDn == null) { throw new ModelException("Roles DN is null! Check your configuration"); } @@ -72,7 +72,7 @@ public class RoleMapperConfig extends CommonLDAPGroupMapperConfig { } public String getRoleNameLdapAttribute() { - String rolesRdnAttr = mapperModel.getConfig().get(ROLE_NAME_LDAP_ATTRIBUTE); + String rolesRdnAttr = mapperModel.getConfig().getFirst(ROLE_NAME_LDAP_ATTRIBUTE); return rolesRdnAttr!=null ? rolesRdnAttr : LDAPConstants.CN; } @@ -81,8 +81,8 @@ public class RoleMapperConfig extends CommonLDAPGroupMapperConfig { return getRoleNameLdapAttribute(); } - public Collection getRoleObjectClasses(LDAPFederationProvider ldapProvider) { - String objectClasses = mapperModel.getConfig().get(ROLE_OBJECT_CLASSES); + public Collection getRoleObjectClasses(LDAPStorageProvider ldapProvider) { + String objectClasses = mapperModel.getConfig().getFirst(ROLE_OBJECT_CLASSES); if (objectClasses == null) { // For Active directory, the default is 'group' . For other servers 'groupOfNames' objectClasses = ldapProvider.getLdapIdentityStore().getConfig().isActiveDirectory() ? LDAPConstants.GROUP : LDAPConstants.GROUP_OF_NAMES; @@ -92,21 +92,21 @@ public class RoleMapperConfig extends CommonLDAPGroupMapperConfig { } public String getCustomLdapFilter() { - return mapperModel.getConfig().get(ROLES_LDAP_FILTER); + return mapperModel.getConfig().getFirst(ROLES_LDAP_FILTER); } public boolean isRealmRolesMapping() { - String realmRolesMapping = mapperModel.getConfig().get(USE_REALM_ROLES_MAPPING); + String realmRolesMapping = mapperModel.getConfig().getFirst(USE_REALM_ROLES_MAPPING); return realmRolesMapping==null || Boolean.parseBoolean(realmRolesMapping); } public String getClientId() { - return mapperModel.getConfig().get(CLIENT_ID); + return mapperModel.getConfig().getFirst(CLIENT_ID); } public String getUserRolesRetrieveStrategy() { - String strategyString = mapperModel.getConfig().get(USER_ROLES_RETRIEVE_STRATEGY); + String strategyString = mapperModel.getConfig().getFirst(USER_ROLES_RETRIEVE_STRATEGY); return strategyString!=null ? strategyString : LOAD_ROLES_BY_MEMBER_ATTRIBUTE; } diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/msad/MSADUserAccountControlMapper.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/msad/MSADUserAccountControlStorageMapper.java similarity index 88% rename from federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/msad/MSADUserAccountControlMapper.java rename to federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/msad/MSADUserAccountControlStorageMapper.java index d974f3c21c..7ad522801e 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/msad/MSADUserAccountControlMapper.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/msad/MSADUserAccountControlStorageMapper.java @@ -15,22 +15,22 @@ * limitations under the License. */ -package org.keycloak.federation.ldap.mappers.msad; +package org.keycloak.storage.ldap.mappers.msad; import org.jboss.logging.Logger; +import org.keycloak.component.ComponentModel; import org.keycloak.credential.CredentialInput; -import org.keycloak.federation.ldap.LDAPFederationProvider; -import org.keycloak.federation.ldap.idm.model.LDAPObject; -import org.keycloak.federation.ldap.idm.query.internal.LDAPQuery; -import org.keycloak.federation.ldap.mappers.AbstractLDAPFederationMapper; -import org.keycloak.federation.ldap.mappers.PasswordUpdated; 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.UserModel; import org.keycloak.models.utils.UserModelDelegate; +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.idm.query.internal.LDAPQuery; +import org.keycloak.storage.ldap.mappers.AbstractLDAPStorageMapper; +import org.keycloak.storage.ldap.mappers.PasswordUpdated; import javax.naming.AuthenticationException; import java.util.HashSet; @@ -44,14 +44,14 @@ import java.util.regex.Pattern; * * @author Marek Posolda */ -public class MSADUserAccountControlMapper extends AbstractLDAPFederationMapper implements PasswordUpdated { +public class MSADUserAccountControlStorageMapper extends AbstractLDAPStorageMapper implements PasswordUpdated { - private static final Logger logger = Logger.getLogger(MSADUserAccountControlMapper.class); + private static final Logger logger = Logger.getLogger(MSADUserAccountControlStorageMapper.class); private static final Pattern AUTH_EXCEPTION_REGEX = Pattern.compile(".*AcceptSecurityContext error, data ([0-9a-f]*), v.*"); private static final Pattern AUTH_INVALID_NEW_PASSWORD = Pattern.compile(".*error code ([0-9a-f]+) .*WILL_NOT_PERFORM.*"); - public MSADUserAccountControlMapper(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, RealmModel realm) { + public MSADUserAccountControlStorageMapper(ComponentModel mapperModel, LDAPStorageProvider ldapProvider, RealmModel realm) { super(mapperModel, ldapProvider, realm); ldapProvider.setUpdater(this); } @@ -64,7 +64,7 @@ public class MSADUserAccountControlMapper extends AbstractLDAPFederationMapper i // This needs to be read-only and can be set to writable just on demand query.addReturningReadOnlyLdapAttribute(LDAPConstants.PWD_LAST_SET); - if (ldapProvider.getEditMode() != UserFederationProvider.EditMode.WRITABLE) { + if (ldapProvider.getEditMode() != LDAPStorageProviderFactory.EditMode.WRITABLE) { query.addReturningReadOnlyLdapAttribute(LDAPConstants.USER_ACCOUNT_CONTROL); } } @@ -119,7 +119,7 @@ public class MSADUserAccountControlMapper extends AbstractLDAPFederationMapper i protected boolean processAuthErrorCode(String errorCode, UserModel user) { logger.debugf("MSAD Error code is '%s' after failed LDAP login of user '%s'", errorCode, user.getUsername()); - if (ldapProvider.getEditMode() == UserFederationProvider.EditMode.WRITABLE) { + if (ldapProvider.getEditMode() == LDAPStorageProviderFactory.EditMode.WRITABLE) { if (errorCode.equals("532") || errorCode.equals("773")) { // User needs to change his MSAD password. Allow him to login, but add UPDATE_PASSWORD required action user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD); @@ -200,7 +200,7 @@ public class MSADUserAccountControlMapper extends AbstractLDAPFederationMapper i // Always update DB super.setEnabled(enabled); - if (ldapProvider.getEditMode() == UserFederationProvider.EditMode.WRITABLE && getPwdLastSet() > 0) { + if (ldapProvider.getEditMode() == LDAPStorageProviderFactory.EditMode.WRITABLE && getPwdLastSet() > 0) { logger.debugf("Going to propagate enabled=%s for ldapUser '%s' to MSAD", enabled, ldapUser.getDn().toString()); UserAccountControl control = getUserAccountControl(ldapUser); @@ -225,7 +225,7 @@ public class MSADUserAccountControlMapper extends AbstractLDAPFederationMapper i // Always update DB super.addRequiredAction(action); - if (ldapProvider.getEditMode() == UserFederationProvider.EditMode.WRITABLE && RequiredAction.UPDATE_PASSWORD.toString().equals(action)) { + if (ldapProvider.getEditMode() == LDAPStorageProviderFactory.EditMode.WRITABLE && RequiredAction.UPDATE_PASSWORD.toString().equals(action)) { logger.debugf("Going to propagate required action UPDATE_PASSWORD to MSAD for ldap user '%s' ", ldapUser.getDn().toString()); // Normally it's read-only @@ -247,7 +247,7 @@ public class MSADUserAccountControlMapper extends AbstractLDAPFederationMapper i // Always update DB super.removeRequiredAction(action); - if (ldapProvider.getEditMode() == UserFederationProvider.EditMode.WRITABLE && RequiredAction.UPDATE_PASSWORD.toString().equals(action)) { + if (ldapProvider.getEditMode() == LDAPStorageProviderFactory.EditMode.WRITABLE && RequiredAction.UPDATE_PASSWORD.toString().equals(action)) { // Don't set pwdLastSet in MSAD when it is new user UserAccountControl accountControl = getUserAccountControl(ldapUser); @@ -267,7 +267,7 @@ public class MSADUserAccountControlMapper extends AbstractLDAPFederationMapper i public Set getRequiredActions() { Set requiredActions = super.getRequiredActions(); - if (ldapProvider.getEditMode() == UserFederationProvider.EditMode.WRITABLE) { + if (ldapProvider.getEditMode() == LDAPStorageProviderFactory.EditMode.WRITABLE) { if (getPwdLastSet() == 0 || getUserAccountControl(ldapUser).has(UserAccountControl.PASSWORD_EXPIRED)) { requiredActions = new HashSet<>(requiredActions); requiredActions.add(RequiredAction.UPDATE_PASSWORD.toString()); diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/msad/MSADUserAccountControlMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/msad/MSADUserAccountControlStorageMapperFactory.java similarity index 58% rename from federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/msad/MSADUserAccountControlMapperFactory.java rename to federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/msad/MSADUserAccountControlStorageMapperFactory.java index a8afd1cb9f..560eff0b87 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/msad/MSADUserAccountControlMapperFactory.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/msad/MSADUserAccountControlStorageMapperFactory.java @@ -15,27 +15,23 @@ * limitations under the License. */ -package org.keycloak.federation.ldap.mappers.msad; +package org.keycloak.storage.ldap.mappers.msad; -import org.keycloak.federation.ldap.LDAPFederationProvider; -import org.keycloak.federation.ldap.mappers.AbstractLDAPFederationMapper; -import org.keycloak.federation.ldap.mappers.AbstractLDAPFederationMapperFactory; -import org.keycloak.mappers.FederationConfigValidationException; +import org.keycloak.component.ComponentModel; import org.keycloak.models.LDAPConstants; import org.keycloak.models.RealmModel; -import org.keycloak.models.UserFederationMapperModel; -import org.keycloak.models.UserFederationProviderModel; import org.keycloak.provider.ProviderConfigProperty; +import org.keycloak.storage.ldap.LDAPStorageProvider; +import org.keycloak.storage.ldap.mappers.AbstractLDAPStorageMapper; +import org.keycloak.storage.ldap.mappers.AbstractLDAPStorageMapperFactory; import java.util.ArrayList; -import java.util.Collections; import java.util.List; -import java.util.Map; /** * @author Marek Posolda */ -public class MSADUserAccountControlMapperFactory extends AbstractLDAPFederationMapperFactory { +public class MSADUserAccountControlStorageMapperFactory extends AbstractLDAPStorageMapperFactory { public static final String PROVIDER_ID = LDAPConstants.MSAD_USER_ACCOUNT_CONTROL_MAPPER; protected static final List configProperties = new ArrayList(); @@ -49,37 +45,18 @@ public class MSADUserAccountControlMapperFactory extends AbstractLDAPFederationM "For example if pwdLastSet is 0, the Keycloak user is required to update password, if userAccountControl is 514 (disabled account) the Keycloak user is disabled as well etc. Mapper is also able to handle exception code from LDAP user authentication."; } - @Override - public String getDisplayCategory() { - return ATTRIBUTE_MAPPER_CATEGORY; - } - - @Override - public String getDisplayType() { - return "MSAD User Account Control"; - } - @Override public List getConfigProperties() { return configProperties; } - @Override - public Map getDefaultConfig(UserFederationProviderModel providerModel) { - return Collections.emptyMap(); - } - @Override public String getId() { return PROVIDER_ID; } @Override - public void validateConfig(RealmModel realm, UserFederationProviderModel fedProviderModel, UserFederationMapperModel mapperModel) throws FederationConfigValidationException { - } - - @Override - protected AbstractLDAPFederationMapper createMapper(UserFederationMapperModel mapperModel, LDAPFederationProvider federationProvider, RealmModel realm) { - return new MSADUserAccountControlMapper(mapperModel, federationProvider, realm); + protected AbstractLDAPStorageMapper createMapper(ComponentModel mapperModel, LDAPStorageProvider federationProvider, RealmModel realm) { + return new MSADUserAccountControlStorageMapper(mapperModel, federationProvider, realm); } } diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/msad/UserAccountControl.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/msad/UserAccountControl.java similarity index 98% rename from federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/msad/UserAccountControl.java rename to federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/msad/UserAccountControl.java index cab01bea4b..6d24f572e5 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/msad/UserAccountControl.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/msad/UserAccountControl.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.keycloak.federation.ldap.mappers.msad; +package org.keycloak.storage.ldap.mappers.msad; /** * See https://support.microsoft.com/en-us/kb/305144 diff --git a/federation/ldap/src/main/resources/META-INF/services/org.keycloak.mappers.UserFederationMapperFactory b/federation/ldap/src/main/resources/META-INF/services/org.keycloak.mappers.UserFederationMapperFactory deleted file mode 100644 index 361b2e64dc..0000000000 --- a/federation/ldap/src/main/resources/META-INF/services/org.keycloak.mappers.UserFederationMapperFactory +++ /dev/null @@ -1,23 +0,0 @@ -# -# 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. -# - -org.keycloak.federation.ldap.mappers.UserAttributeLDAPFederationMapperFactory -org.keycloak.federation.ldap.mappers.FullNameLDAPFederationMapperFactory -org.keycloak.federation.ldap.mappers.HardcodedLDAPRoleMapperFactory -org.keycloak.federation.ldap.mappers.membership.role.RoleLDAPFederationMapperFactory -org.keycloak.federation.ldap.mappers.membership.group.GroupLDAPFederationMapperFactory -org.keycloak.federation.ldap.mappers.msad.MSADUserAccountControlMapperFactory \ No newline at end of file diff --git a/federation/ldap/src/main/resources/META-INF/services/org.keycloak.models.UserFederationProviderFactory b/federation/ldap/src/main/resources/META-INF/services/org.keycloak.provider.Spi similarity index 91% rename from federation/ldap/src/main/resources/META-INF/services/org.keycloak.models.UserFederationProviderFactory rename to federation/ldap/src/main/resources/META-INF/services/org.keycloak.provider.Spi index fe38506cfc..57843f04b5 100755 --- a/federation/ldap/src/main/resources/META-INF/services/org.keycloak.models.UserFederationProviderFactory +++ b/federation/ldap/src/main/resources/META-INF/services/org.keycloak.provider.Spi @@ -15,4 +15,4 @@ # limitations under the License. # -org.keycloak.federation.ldap.LDAPFederationProviderFactory \ No newline at end of file +org.keycloak.storage.ldap.mappers.LDAPStorageMapperSpi \ No newline at end of file diff --git a/federation/ldap/src/main/resources/META-INF/services/org.keycloak.storage.UserStorageProviderFactory b/federation/ldap/src/main/resources/META-INF/services/org.keycloak.storage.UserStorageProviderFactory new file mode 100644 index 0000000000..0d8483fb80 --- /dev/null +++ b/federation/ldap/src/main/resources/META-INF/services/org.keycloak.storage.UserStorageProviderFactory @@ -0,0 +1 @@ +org.keycloak.storage.ldap.LDAPStorageProviderFactory \ No newline at end of file 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 new file mode 100644 index 0000000000..ff1f0f6c9e --- /dev/null +++ b/federation/ldap/src/main/resources/META-INF/services/org.keycloak.storage.ldap.mappers.LDAPStorageMapperFactory @@ -0,0 +1,6 @@ +org.keycloak.storage.ldap.mappers.FullNameLDAPStorageMapperFactory +org.keycloak.storage.ldap.mappers.HardcodedLDAPRoleStorageMapperFactory +org.keycloak.storage.ldap.mappers.membership.group.GroupLDAPStorageMapperFactory +org.keycloak.storage.ldap.mappers.membership.role.RoleLDAPStorageMapperFactory +org.keycloak.storage.ldap.mappers.msad.MSADUserAccountControlStorageMapperFactory +org.keycloak.storage.ldap.mappers.UserAttributeLDAPStorageMapperFactory diff --git a/federation/ldap/src/test/java/org/keycloak/federation/ldap/idm/model/LDAPMappersComparatorTest.java b/federation/ldap/src/test/java/org/keycloak/federation/ldap/idm/model/LDAPMappersComparatorTest.java deleted file mode 100644 index 061ef50097..0000000000 --- a/federation/ldap/src/test/java/org/keycloak/federation/ldap/idm/model/LDAPMappersComparatorTest.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * 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.federation.ldap.idm.model; - -import org.junit.Assert; -import org.junit.Test; -import org.keycloak.federation.ldap.LDAPConfig; -import org.keycloak.federation.ldap.mappers.FullNameLDAPFederationMapper; -import org.keycloak.federation.ldap.mappers.FullNameLDAPFederationMapperFactory; -import org.keycloak.federation.ldap.mappers.LDAPMappersComparator; -import org.keycloak.federation.ldap.mappers.UserAttributeLDAPFederationMapper; -import org.keycloak.federation.ldap.mappers.UserAttributeLDAPFederationMapperFactory; -import org.keycloak.models.LDAPConstants; -import org.keycloak.models.UserFederationMapperModel; -import org.keycloak.models.UserModel; -import org.keycloak.models.utils.KeycloakModelUtils; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * @author Marek Posolda - */ -public class LDAPMappersComparatorTest { - - - - @Test - public void testCompareWithCNUsername() { - Map cfg = new HashMap<>(); - cfg.put(LDAPConstants.USERNAME_LDAP_ATTRIBUTE, LDAPConstants.CN); - LDAPConfig config = new LDAPConfig(cfg); - - List sorted = LDAPMappersComparator.sortAsc(config, getMappers()); - assertOrder(sorted, "username-cn", "sAMAccountName", "first name", "full name"); - - sorted = LDAPMappersComparator.sortDesc(config, getMappers()); - assertOrder(sorted, "full name", "first name", "sAMAccountName", "username-cn"); - } - - @Test - public void testCompareWithSAMAccountNameUsername() { - Map cfg = new HashMap<>(); - cfg.put(LDAPConstants.USERNAME_LDAP_ATTRIBUTE, LDAPConstants.SAM_ACCOUNT_NAME); - LDAPConfig config = new LDAPConfig(cfg); - - List sorted = LDAPMappersComparator.sortAsc(config, getMappers()); - assertOrder(sorted, "sAMAccountName", "username-cn", "first name", "full name"); - - sorted = LDAPMappersComparator.sortDesc(config, getMappers()); - assertOrder(sorted, "full name", "first name", "username-cn", "sAMAccountName"); - } - - private void assertOrder(List result, String... names) { - Assert.assertEquals(result.size(), names.length); - for (int i=0 ; i getMappers() { - Set result = new HashSet<>(); - - UserFederationMapperModel mapperModel = KeycloakModelUtils.createUserFederationMapperModel("first name", "fed-provider", UserAttributeLDAPFederationMapperFactory.PROVIDER_ID, - UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.FIRST_NAME, - UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.GIVENNAME, - UserAttributeLDAPFederationMapper.READ_ONLY, "true", - UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, "true", - UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "true"); - mapperModel.setId("idd1"); - result.add(mapperModel); - - mapperModel = KeycloakModelUtils.createUserFederationMapperModel("username-cn", "fed-provider", UserAttributeLDAPFederationMapperFactory.PROVIDER_ID, - UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.USERNAME, - UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.CN, - UserAttributeLDAPFederationMapper.READ_ONLY, "true", - UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, "false", - UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "true"); - mapperModel.setId("idd2"); - result.add(mapperModel); - - mapperModel = KeycloakModelUtils.createUserFederationMapperModel("full name", "fed-provider", FullNameLDAPFederationMapperFactory.PROVIDER_ID, - FullNameLDAPFederationMapper.LDAP_FULL_NAME_ATTRIBUTE, LDAPConstants.CN, - UserAttributeLDAPFederationMapper.READ_ONLY, "true"); - mapperModel.setId("idd3"); - result.add(mapperModel); - - mapperModel = KeycloakModelUtils.createUserFederationMapperModel("sAMAccountName", "fed-provider", UserAttributeLDAPFederationMapperFactory.PROVIDER_ID, - UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.USERNAME, - UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.SAM_ACCOUNT_NAME, - UserAttributeLDAPFederationMapper.READ_ONLY, "false", - UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, "false", - UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "true"); - mapperModel.setId("idd4"); - result.add(mapperModel); - - return result; - } -} diff --git a/federation/ldap/src/test/java/org/keycloak/federation/ldap/idm/model/GroupTreeResolverTest.java b/federation/ldap/src/test/java/org/keycloak/storage/ldap/idm/model/GroupTreeResolverTest.java similarity index 98% rename from federation/ldap/src/test/java/org/keycloak/federation/ldap/idm/model/GroupTreeResolverTest.java rename to federation/ldap/src/test/java/org/keycloak/storage/ldap/idm/model/GroupTreeResolverTest.java index 66cddf7f43..78974a2fc1 100644 --- a/federation/ldap/src/test/java/org/keycloak/federation/ldap/idm/model/GroupTreeResolverTest.java +++ b/federation/ldap/src/test/java/org/keycloak/storage/ldap/idm/model/GroupTreeResolverTest.java @@ -15,11 +15,11 @@ * limitations under the License. */ -package org.keycloak.federation.ldap.idm.model; +package org.keycloak.storage.ldap.idm.model; import org.junit.Assert; import org.junit.Test; -import org.keycloak.federation.ldap.mappers.membership.group.GroupTreeResolver; +import org.keycloak.storage.ldap.mappers.membership.group.GroupTreeResolver; import java.util.Arrays; import java.util.List; diff --git a/federation/ldap/src/test/java/org/keycloak/federation/ldap/idm/model/LDAPDnTest.java b/federation/ldap/src/test/java/org/keycloak/storage/ldap/idm/model/LDAPDnTest.java similarity index 97% rename from federation/ldap/src/test/java/org/keycloak/federation/ldap/idm/model/LDAPDnTest.java rename to federation/ldap/src/test/java/org/keycloak/storage/ldap/idm/model/LDAPDnTest.java index 620a1665af..566d6c9efb 100644 --- a/federation/ldap/src/test/java/org/keycloak/federation/ldap/idm/model/LDAPDnTest.java +++ b/federation/ldap/src/test/java/org/keycloak/storage/ldap/idm/model/LDAPDnTest.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.keycloak.federation.ldap.idm.model; +package org.keycloak.storage.ldap.idm.model; import org.junit.Assert; import org.junit.Test; diff --git a/federation/ldap/src/test/java/org/keycloak/storage/ldap/idm/model/LDAPMappersComparatorTest.java b/federation/ldap/src/test/java/org/keycloak/storage/ldap/idm/model/LDAPMappersComparatorTest.java new file mode 100644 index 0000000000..41bf3ec9b0 --- /dev/null +++ b/federation/ldap/src/test/java/org/keycloak/storage/ldap/idm/model/LDAPMappersComparatorTest.java @@ -0,0 +1,116 @@ +/* + * 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.idm.model; + +import org.junit.Assert; +import org.junit.Test; +import org.keycloak.common.util.MultivaluedHashMap; +import org.keycloak.component.ComponentModel; +import org.keycloak.models.LDAPConstants; +import org.keycloak.models.UserModel; +import org.keycloak.models.utils.KeycloakModelUtils; +import org.keycloak.storage.ldap.LDAPConfig; +import org.keycloak.storage.ldap.mappers.FullNameLDAPStorageMapper; +import org.keycloak.storage.ldap.mappers.FullNameLDAPStorageMapperFactory; +import org.keycloak.storage.ldap.mappers.LDAPMappersComparator; +import org.keycloak.storage.ldap.mappers.LDAPStorageMapper; +import org.keycloak.storage.ldap.mappers.UserAttributeLDAPStorageMapper; +import org.keycloak.storage.ldap.mappers.UserAttributeLDAPStorageMapperFactory; + +import java.util.LinkedList; +import java.util.List; + +/** + * @author Marek Posolda + */ +public class LDAPMappersComparatorTest { + + + + @Test + public void testCompareWithCNUsername() { + MultivaluedHashMap cfg = new MultivaluedHashMap<>(); + cfg.add(LDAPConstants.USERNAME_LDAP_ATTRIBUTE, LDAPConstants.CN); + LDAPConfig config = new LDAPConfig(cfg); + + List sorted = LDAPMappersComparator.sortAsc(config, getMappers()); + assertOrder(sorted, "username-cn", "sAMAccountName", "first name", "full name"); + + sorted = LDAPMappersComparator.sortDesc(config, getMappers()); + assertOrder(sorted, "full name", "first name", "sAMAccountName", "username-cn"); + } + + @Test + public void testCompareWithSAMAccountNameUsername() { + MultivaluedHashMap cfg = new MultivaluedHashMap<>(); + cfg.add(LDAPConstants.USERNAME_LDAP_ATTRIBUTE, LDAPConstants.SAM_ACCOUNT_NAME); + LDAPConfig config = new LDAPConfig(cfg); + + List sorted = LDAPMappersComparator.sortAsc(config, getMappers()); + assertOrder(sorted, "sAMAccountName", "username-cn", "first name", "full name"); + + sorted = LDAPMappersComparator.sortDesc(config, getMappers()); + assertOrder(sorted, "full name", "first name", "username-cn", "sAMAccountName"); + } + + private void assertOrder(List result, String... names) { + Assert.assertEquals(result.size(), names.length); + for (int i=0 ; i getMappers() { + List result = new LinkedList<>(); + + ComponentModel mapperModel = KeycloakModelUtils.createComponentModel("first name", "fed-provider", UserAttributeLDAPStorageMapperFactory.PROVIDER_ID, LDAPStorageMapper.class.getName(), + UserAttributeLDAPStorageMapper.USER_MODEL_ATTRIBUTE, UserModel.FIRST_NAME, + UserAttributeLDAPStorageMapper.LDAP_ATTRIBUTE, LDAPConstants.GIVENNAME, + UserAttributeLDAPStorageMapper.READ_ONLY, "true", + UserAttributeLDAPStorageMapper.ALWAYS_READ_VALUE_FROM_LDAP, "true", + UserAttributeLDAPStorageMapper.IS_MANDATORY_IN_LDAP, "true"); + mapperModel.setId("idd1"); + result.add(mapperModel); + + mapperModel = KeycloakModelUtils.createComponentModel("username-cn", "fed-provider", UserAttributeLDAPStorageMapperFactory.PROVIDER_ID,LDAPStorageMapper.class.getName(), + UserAttributeLDAPStorageMapper.USER_MODEL_ATTRIBUTE, UserModel.USERNAME, + UserAttributeLDAPStorageMapper.LDAP_ATTRIBUTE, LDAPConstants.CN, + UserAttributeLDAPStorageMapper.READ_ONLY, "true", + UserAttributeLDAPStorageMapper.ALWAYS_READ_VALUE_FROM_LDAP, "false", + UserAttributeLDAPStorageMapper.IS_MANDATORY_IN_LDAP, "true"); + mapperModel.setId("idd2"); + result.add(mapperModel); + + mapperModel = KeycloakModelUtils.createComponentModel("full name", "fed-provider", FullNameLDAPStorageMapperFactory.PROVIDER_ID,LDAPStorageMapper.class.getName(), + FullNameLDAPStorageMapper.LDAP_FULL_NAME_ATTRIBUTE, LDAPConstants.CN, + UserAttributeLDAPStorageMapper.READ_ONLY, "true"); + mapperModel.setId("idd3"); + result.add(mapperModel); + + mapperModel = KeycloakModelUtils.createComponentModel("sAMAccountName", "fed-provider", UserAttributeLDAPStorageMapperFactory.PROVIDER_ID,LDAPStorageMapper.class.getName(), + UserAttributeLDAPStorageMapper.USER_MODEL_ATTRIBUTE, UserModel.USERNAME, + UserAttributeLDAPStorageMapper.LDAP_ATTRIBUTE, LDAPConstants.SAM_ACCOUNT_NAME, + UserAttributeLDAPStorageMapper.READ_ONLY, "false", + UserAttributeLDAPStorageMapper.ALWAYS_READ_VALUE_FROM_LDAP, "false", + UserAttributeLDAPStorageMapper.IS_MANDATORY_IN_LDAP, "true"); + mapperModel.setId("idd4"); + result.add(mapperModel); + + return result; + } +} diff --git a/federation/pom.xml b/federation/pom.xml index 7942feed2e..a08ef631ef 100755 --- a/federation/pom.xml +++ b/federation/pom.xml @@ -33,8 +33,8 @@ - ldap kerberos + ldap sssd diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java index 2b2179ce01..069b34aec5 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java @@ -28,6 +28,7 @@ import org.keycloak.models.ClientTemplateModel; import org.keycloak.models.GroupModel; import org.keycloak.models.IdentityProviderMapperModel; import org.keycloak.models.IdentityProviderModel; +import org.keycloak.models.KeycloakSession; import org.keycloak.models.OTPPolicy; import org.keycloak.models.PasswordPolicy; import org.keycloak.models.RealmModel; @@ -39,6 +40,7 @@ import org.keycloak.models.UserFederationProviderModel; import org.keycloak.models.cache.CachedRealmModel; import org.keycloak.models.cache.infinispan.entities.CachedRealm; import org.keycloak.models.utils.KeycloakModelUtils; +import org.keycloak.storage.UserStorageProvider; import java.security.Key; import java.security.PrivateKey; @@ -62,10 +64,12 @@ public class RealmAdapter implements CachedRealmModel { protected RealmCacheSession cacheSession; protected RealmModel updated; protected RealmCache cache; + protected KeycloakSession session; - public RealmAdapter(CachedRealm cached, RealmCacheSession cacheSession) { + public RealmAdapter(KeycloakSession session, CachedRealm cached, RealmCacheSession cacheSession) { this.cached = cached; this.cacheSession = cacheSession; + this.session = session; } @Override @@ -1333,12 +1337,35 @@ public class RealmAdapter implements CachedRealmModel { @Override public ComponentModel addComponentModel(ComponentModel model) { getDelegateForUpdate(); + evictUsers(model); return updated.addComponentModel(model); } + @Override + public ComponentModel importComponentModel(ComponentModel model) { + getDelegateForUpdate(); + evictUsers(model); + return updated.importComponentModel(model); + } + + public void evictUsers(ComponentModel model) { + String parentId = model.getParentId(); + evictUsers(parentId); + } + + public void evictUsers(String parentId) { + if (parentId != null && !parentId.equals(getId())) { + ComponentModel parent = getComponent(parentId); + if (parent != null && UserStorageProvider.class.getName().equals(parent.getProviderType())) { + session.getUserCache().evict(this); + } + } + } + @Override public void updateComponent(ComponentModel component) { getDelegateForUpdate(); + evictUsers(component); updated.updateComponent(component); } @@ -1346,6 +1373,7 @@ public class RealmAdapter implements CachedRealmModel { @Override public void removeComponent(ComponentModel component) { getDelegateForUpdate(); + evictUsers(component); updated.removeComponent(component); } @@ -1353,6 +1381,7 @@ public class RealmAdapter implements CachedRealmModel { @Override public void removeComponents(String parentId) { getDelegateForUpdate(); + evictUsers(parentId); updated.removeComponents(parentId); } diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java index 565fd483d9..9321f47197 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java @@ -389,7 +389,7 @@ public class RealmCacheSession implements CacheRealmProvider { } else if (managedRealms.containsKey(id)) { return managedRealms.get(id); } - RealmAdapter adapter = new RealmAdapter(cached, this); + RealmAdapter adapter = new RealmAdapter(session, cached, this); if (wasCached) { CachedRealmModel.RealmCachedEvent event = new CachedRealmModel.RealmCachedEvent() { @Override diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java index 1a52aa9f94..3094521fd4 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java @@ -64,6 +64,11 @@ public class UserAdapter implements CachedUserModel { return updated; } + @Override + public boolean isMarkedForEviction() { + return updated != null; + } + @Override public void invalidate() { getDelegateForUpdate(); diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java index 0bb71c0b1b..5531de1753 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java @@ -115,7 +115,10 @@ public class UserCacheSession implements UserCache { } } - + @Override + public void evict(RealmModel realm) { + realmInvalidations.add(realm.getId()); + } protected void runInvalidations() { for (String realmId : realmInvalidations) { @@ -498,8 +501,7 @@ public class UserCacheSession implements UserCache { @Override public UserModel getServiceAccount(ClientModel client) { // Just an attempt to find the user from cache by default serviceAccount username - String username = ServiceAccountConstants.SERVICE_ACCOUNT_USER_PREFIX + client.getClientId(); - UserModel user = getUserByUsername(username, client.getRealm()); + UserModel user = findServiceAccount(client); if (user != null && user.getServiceAccountClientLink() != null && user.getServiceAccountClientLink().equals(client.getId())) { return user; } @@ -507,6 +509,60 @@ public class UserCacheSession implements UserCache { return getDelegate().getServiceAccount(client); } + public UserModel findServiceAccount(ClientModel client) { + String username = ServiceAccountConstants.SERVICE_ACCOUNT_USER_PREFIX + client.getClientId(); + logger.tracev("getServiceAccount: {0}", username); + username = username.toLowerCase(); + RealmModel realm = client.getRealm(); + if (realmInvalidations.contains(realm.getId())) { + logger.tracev("realmInvalidations"); + return getDelegate().getServiceAccount(client); + } + String cacheKey = getUserByUsernameCacheKey(realm.getId(), username); + if (invalidations.contains(cacheKey)) { + logger.tracev("invalidations"); + return getDelegate().getServiceAccount(client); + } + UserListQuery query = cache.get(cacheKey, UserListQuery.class); + + String userId = null; + if (query == null) { + logger.tracev("query null"); + Long loaded = cache.getCurrentRevision(cacheKey); + UserModel model = getDelegate().getServiceAccount(client); + if (model == null) { + logger.tracev("model from delegate null"); + return null; + } + userId = model.getId(); + if (invalidations.contains(userId)) return model; + if (managedUsers.containsKey(userId)) { + logger.tracev("return managed user"); + return managedUsers.get(userId); + } + + UserModel adapter = getUserAdapter(realm, userId, loaded, model); + if (adapter instanceof UserAdapter) { // this was cached, so we can cache query too + query = new UserListQuery(loaded, cacheKey, realm, model.getId()); + cache.addRevisioned(query, startupRevision); + } + managedUsers.put(userId, adapter); + return adapter; + } else { + userId = query.getUsers().iterator().next(); + if (invalidations.contains(userId)) { + logger.tracev("invalidated cache return delegate"); + return getDelegate().getUserByUsername(username, realm); + + } + logger.trace("return getUserById"); + return getUserById(userId, realm); + } + } + + + + @Override public List getUsers(RealmModel realm, boolean includeServiceAccounts) { return getDelegate().getUsers(realm, includeServiceAccounts); diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/custom/AbstractUserFedToComponent.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/custom/AbstractUserFedToComponent.java new file mode 100644 index 0000000000..e86788b75f --- /dev/null +++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/custom/AbstractUserFedToComponent.java @@ -0,0 +1,182 @@ +/* + * 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.connections.jpa.updater.liquibase.custom; + +import liquibase.exception.CustomChangeException; +import liquibase.statement.core.DeleteStatement; +import liquibase.statement.core.InsertStatement; +import liquibase.structure.core.Table; +import org.jboss.logging.Logger; +import org.keycloak.models.LDAPConstants; +import org.keycloak.models.utils.KeycloakModelUtils; +import org.keycloak.storage.UserStorageProvider; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public abstract class AbstractUserFedToComponent extends CustomKeycloakTask { + private final Logger logger = Logger.getLogger(getClass()); + protected void convertFedProviderToComponent(String providerId, String newMapperType) throws CustomChangeException { + try { + PreparedStatement statement = jdbcConnection.prepareStatement("select ID, REALM_ID, PRIORITY, DISPLAY_NAME, FULL_SYNC_PERIOD, CHANGED_SYNC_PERIOD, LAST_SYNC from " + getTableName("USER_FEDERATION_PROVIDER") + " WHERE PROVIDER_NAME='" + providerId + "'"); + + try { + ResultSet resultSet = statement.executeQuery(); + try { + while (resultSet.next()) { + int index = 1; + String id = resultSet.getString(index++); + String realmId = resultSet.getString(index++); + int priority = resultSet.getInt(index++); + String displayName = resultSet.getString(index++); + int fullSyncPeriod = resultSet.getInt(index++); + int changedSyncPeriod = resultSet.getInt(index++); + int lastSync = resultSet.getInt(index++); + + + InsertStatement insertComponent = new InsertStatement(null, null, database.correctObjectName("COMPONENT", Table.class)) + .addColumnValue("ID", id) + .addColumnValue("REALM_ID", realmId) + .addColumnValue("PARENT_ID", realmId) + .addColumnValue("NAME", displayName) + .addColumnValue("PROVIDER_ID", LDAPConstants.LDAP_PROVIDER) + .addColumnValue("PROVIDER_TYPE", UserStorageProvider.class.getName()); + + statements.add(insertComponent); + + statements.add(componentConfigStatement(id, "priority", Integer.toString(priority))); + statements.add(componentConfigStatement(id, "fullSyncPeriod", Integer.toString(fullSyncPeriod))); + statements.add(componentConfigStatement(id, "changedSyncPeriod", Integer.toString(changedSyncPeriod))); + statements.add(componentConfigStatement(id, "lastSync", Integer.toString(lastSync))); + PreparedStatement configStatement = jdbcConnection.prepareStatement("select name, VALUE from " + getTableName("USER_FEDERATION_CONFIG") + " WHERE USER_FEDERATION_PROVIDER_ID=?"); + configStatement.setString(1, id); + try { + ResultSet configSet = configStatement.executeQuery(); + try { + while (configSet.next()) { + String name = configSet.getString(1); + String value = configSet.getString(2); + //logger.info("adding component config: " + name + ": " + value); + statements.add(componentConfigStatement(id, name, value)); + } + } finally { + configSet.close(); + } + } finally { + configStatement.close(); + } + + if (newMapperType != null) { + convertFedMapperToComponent(realmId, id, newMapperType); + } + + DeleteStatement configDelete = new DeleteStatement(null, null, database.correctObjectName("USER_FEDERATION_CONFIG", Table.class)); + configDelete.setWhere("USER_FEDERATION_PROVIDER_ID='" + id + "'"); + statements.add(configDelete); + DeleteStatement deleteStatement = new DeleteStatement(null, null, database.correctObjectName("USER_FEDERATION_PROVIDER", Table.class)); + deleteStatement.setWhere("ID='" + id + "'"); + statements.add(deleteStatement); + + } + } finally { + resultSet.close(); + } + } finally { + statement.close(); + } + + confirmationMessage.append("Updated " + statements.size() + " records in USER_FEDERATION_PROVIDER table for " + providerId + " conversion to component model"); + } catch (Exception e) { + throw new CustomChangeException(getTaskId() + ": Exception when updating data from previous version", e); + } + } + + protected InsertStatement componentConfigStatement(String componentId, String name, String value) { + return new InsertStatement(null, null, database.correctObjectName("COMPONENT_CONFIG", Table.class)) + .addColumnValue("ID", KeycloakModelUtils.generateId()) + .addColumnValue("COMPONENT_ID", componentId) + .addColumnValue("NAME", name) + .addColumnValue("VALUE", value); + } + + protected void convertFedMapperToComponent(String realmId, String parentId, String newMapperType) throws CustomChangeException { + try { + PreparedStatement statement = jdbcConnection.prepareStatement("select ID, NAME, FEDERATION_MAPPER_TYPE from " + getTableName("USER_FEDERATION_MAPPER") + " WHERE FEDERATION_PROVIDER_ID='" + parentId + "'"); + + try { + ResultSet resultSet = statement.executeQuery(); + try { + while (resultSet.next()) { + String id = resultSet.getString(1); + String mapperName = resultSet.getString(2); + String fedMapperType = resultSet.getString(3); + + InsertStatement insertComponent = new InsertStatement(null, null, database.correctObjectName("COMPONENT", Table.class)) + .addColumnValue("ID", id) + .addColumnValue("REALM_ID", realmId) + .addColumnValue("PARENT_ID", parentId) + .addColumnValue("NAME", mapperName) + .addColumnValue("PROVIDER_ID", fedMapperType) + .addColumnValue("PROVIDER_TYPE", newMapperType); + + statements.add(insertComponent); + + + + PreparedStatement configStatement = jdbcConnection.prepareStatement("select name, VALUE from " + getTableName("USER_FEDERATION_MAPPER_CONFIG") + " WHERE USER_FEDERATION_MAPPER_ID=?"); + configStatement.setString(1, id); + try { + ResultSet configSet = configStatement.executeQuery(); + try { + while (configSet.next()) { + String name = configSet.getString(1); + String value = configSet.getString(2); + statements.add(componentConfigStatement(id, name, value)); + } + } finally { + configSet.close(); + } + } finally { + configStatement.close(); + } + DeleteStatement configDelete = new DeleteStatement(null, null, database.correctObjectName("USER_FEDERATION_MAPPER_CONFIG", Table.class)); + configDelete.setWhere("USER_FEDERATION_MAPPER_ID='" + id + "'"); + statements.add(configDelete); + DeleteStatement deleteStatement = new DeleteStatement(null, null, database.correctObjectName("USER_FEDERATION_MAPPER", Table.class)); + deleteStatement.setWhere("ID='" + id + "'"); + statements.add(deleteStatement); + + + } + } finally { + resultSet.close(); + } + } finally { + statement.close(); + } + + confirmationMessage.append("Updated " + statements.size() + " records in USER_FEDERATION_MAPPER table for " + parentId + " conversion to component model"); + } catch (Exception e) { + throw new CustomChangeException(getTaskId() + ": Exception when updating data from previous version", e); + } + } + +} diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/custom/PortLdapUserFedToComponentModel.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/custom/PortLdapUserFedToComponentModel.java new file mode 100644 index 0000000000..cf224b2eb1 --- /dev/null +++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/custom/PortLdapUserFedToComponentModel.java @@ -0,0 +1,46 @@ +/* + * 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.connections.jpa.updater.liquibase.custom; + +import liquibase.exception.CustomChangeException; +import liquibase.statement.core.InsertStatement; +import liquibase.structure.core.Table; +import org.keycloak.keys.KeyProvider; +import org.keycloak.models.LDAPConstants; +import org.keycloak.models.utils.KeycloakModelUtils; +import org.keycloak.storage.UserStorageProvider; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; + +/** + * @author Marek Posolda + */ +public class PortLdapUserFedToComponentModel extends AbstractUserFedToComponent { + + @Override + protected void generateStatementsImpl() throws CustomChangeException { + String providerId = LDAPConstants.LDAP_PROVIDER; + convertFedProviderToComponent(LDAPConstants.LDAP_PROVIDER, "org.keycloak.storage.ldap.mappers.LDAPStorageMapper"); + } + + @Override + protected String getTaskId() { + return "Update 2.4.0.Final"; + } +} diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java index bd03727522..b0ea73a6cc 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java @@ -48,6 +48,7 @@ import org.keycloak.models.jpa.entities.UserConsentRoleEntity; import org.keycloak.models.jpa.entities.UserEntity; import org.keycloak.models.utils.DefaultRoles; import org.keycloak.models.utils.KeycloakModelUtils; +import org.keycloak.storage.UserStorageProvider; import javax.persistence.EntityManager; import javax.persistence.TypedQuery; @@ -395,33 +396,38 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore { @Override public void preRemove(RealmModel realm, UserFederationProviderModel link) { + String linkId = link.getId(); + removeUserDataByLink(realm, linkId); + } + + public void removeUserDataByLink(RealmModel realm, String linkId) { int num = em.createNamedQuery("deleteUserRoleMappingsByRealmAndLink") .setParameter("realmId", realm.getId()) - .setParameter("link", link.getId()) + .setParameter("link", linkId) .executeUpdate(); num = em.createNamedQuery("deleteUserRequiredActionsByRealmAndLink") .setParameter("realmId", realm.getId()) - .setParameter("link", link.getId()) + .setParameter("link", linkId) .executeUpdate(); num = em.createNamedQuery("deleteFederatedIdentityByRealmAndLink") .setParameter("realmId", realm.getId()) - .setParameter("link", link.getId()) + .setParameter("link", linkId) .executeUpdate(); num = em.createNamedQuery("deleteCredentialAttributeByRealmAndLink") .setParameter("realmId", realm.getId()) - .setParameter("link", link.getId()) + .setParameter("link", linkId) .executeUpdate(); num = em.createNamedQuery("deleteCredentialsByRealmAndLink") .setParameter("realmId", realm.getId()) - .setParameter("link", link.getId()) + .setParameter("link", linkId) .executeUpdate(); num = em.createNamedQuery("deleteUserAttributesByRealmAndLink") .setParameter("realmId", realm.getId()) - .setParameter("link", link.getId()) + .setParameter("link", linkId) .executeUpdate(); num = em.createNamedQuery("deleteUsersByRealmAndLink") .setParameter("realmId", realm.getId()) - .setParameter("link", link.getId()) + .setParameter("link", linkId) .executeUpdate(); } @@ -718,6 +724,8 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore { @Override public void preRemove(RealmModel realm, ComponentModel component) { + if (!component.getProviderType().equals(UserStorageProvider.class.getName())) return; + removeUserDataByLink(realm, component.getId()); } diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java index ebfa5bc2e9..97aa4bd55d 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java @@ -2031,12 +2031,20 @@ public class RealmAdapter implements RealmModel, JpaModel { @Override public ComponentModel addComponentModel(ComponentModel model) { + model = importComponentModel(model); + ComponentUtil.notifyCreated(session, this, model); + + return model; + } + + @Override + public ComponentModel importComponentModel(ComponentModel model) { ComponentFactory componentFactory = ComponentUtil.getComponentFactory(session, model); if (componentFactory == null) { throw new IllegalArgumentException("Invalid component type"); } - componentFactory.validateConfiguration(session, model); + componentFactory.validateConfiguration(session, this, model); ComponentEntity c = new ComponentEntity(); if (model.getId() == null) { @@ -2046,6 +2054,10 @@ public class RealmAdapter implements RealmModel, JpaModel { } c.setName(model.getName()); c.setParentId(model.getParentId()); + if (model.getParentId() == null) { + c.setParentId(this.getId()); + model.setParentId(this.getId()); + } c.setProviderType(model.getProviderType()); c.setProviderId(model.getProviderId()); c.setSubType(model.getSubType()); @@ -2053,8 +2065,6 @@ public class RealmAdapter implements RealmModel, JpaModel { em.persist(c); setConfig(model, c); model.setId(c.getId()); - ComponentUtil.notifyCreated(session, this, model); - return model; } @@ -2077,7 +2087,7 @@ public class RealmAdapter implements RealmModel, JpaModel { @Override public void updateComponent(ComponentModel component) { - ComponentUtil.getComponentFactory(session, component).validateConfiguration(session, component); + ComponentUtil.getComponentFactory(session, component).validateConfiguration(session, this, component); ComponentEntity c = em.find(ComponentEntity.class, component.getId()); if (c == null) return; @@ -2098,6 +2108,7 @@ public class RealmAdapter implements RealmModel, JpaModel { ComponentEntity c = em.find(ComponentEntity.class, component.getId()); if (c == null) return; session.users().preRemove(this, component); + removeComponents(component.getId()); em.createNamedQuery("deleteComponentConfigByComponent").setParameter("component", c).executeUpdate(); em.remove(c); } diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-2.4.0.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-2.4.0.xml new file mode 100755 index 0000000000..0aebd9a0d2 --- /dev/null +++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-2.4.0.xml @@ -0,0 +1,25 @@ + + + + + + + + + + \ No newline at end of file diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml index dc16e0d351..ebe8cf3d54 100755 --- a/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml +++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml @@ -43,4 +43,5 @@ + diff --git a/model/mongo/src/main/java/org/keycloak/connections/mongo/updater/impl/DefaultMongoUpdaterProvider.java b/model/mongo/src/main/java/org/keycloak/connections/mongo/updater/impl/DefaultMongoUpdaterProvider.java index 3e429e2cf5..c7f5be8d52 100755 --- a/model/mongo/src/main/java/org/keycloak/connections/mongo/updater/impl/DefaultMongoUpdaterProvider.java +++ b/model/mongo/src/main/java/org/keycloak/connections/mongo/updater/impl/DefaultMongoUpdaterProvider.java @@ -34,6 +34,7 @@ import org.keycloak.connections.mongo.updater.impl.updates.Update1_7_0; import org.keycloak.connections.mongo.updater.impl.updates.Update1_8_0; import org.keycloak.connections.mongo.updater.impl.updates.Update1_9_2; import org.keycloak.connections.mongo.updater.impl.updates.Update2_3_0; +import org.keycloak.connections.mongo.updater.impl.updates.Update2_4_0; import org.keycloak.models.KeycloakSession; import java.util.Date; @@ -59,7 +60,8 @@ public class DefaultMongoUpdaterProvider implements MongoUpdaterProvider { Update1_7_0.class, Update1_8_0.class, Update1_9_2.class, - Update2_3_0.class + Update2_3_0.class, + Update2_4_0.class }; @Override diff --git a/model/mongo/src/main/java/org/keycloak/connections/mongo/updater/impl/updates/Update2_4_0.java b/model/mongo/src/main/java/org/keycloak/connections/mongo/updater/impl/updates/Update2_4_0.java new file mode 100644 index 0000000000..d0f9405092 --- /dev/null +++ b/model/mongo/src/main/java/org/keycloak/connections/mongo/updater/impl/updates/Update2_4_0.java @@ -0,0 +1,181 @@ +/* + * 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.connections.mongo.updater.impl.updates; + +import com.mongodb.BasicDBList; +import com.mongodb.BasicDBObject; +import com.mongodb.DBCollection; +import com.mongodb.DBCursor; +import org.jboss.logging.Logger; +import org.keycloak.keys.KeyProvider; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.LDAPConstants; +import org.keycloak.models.utils.KeycloakModelUtils; +import org.keycloak.storage.UserStorageProvider; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +/** + * @author Marek Posolda + */ +public class Update2_4_0 extends Update { + private final Logger logger = Logger.getLogger(getClass()); + + @Override + public String getId() { + return "2.4.0"; + } + + @Override + public void update(KeycloakSession session) { + portUserFedMappersToComponent(LDAPConstants.LDAP_PROVIDER, "org.keycloak.storage.ldap.mappers.LDAPStorageMapper"); + portUserFedToComponent(LDAPConstants.LDAP_PROVIDER); + } + + public void portUserFedToComponent(String providerId) { + DBCollection realms = db.getCollection("realms"); + DBCursor cursor = realms.find(); + while (cursor.hasNext()) { + BasicDBObject realm = (BasicDBObject) cursor.next(); + + String realmId = realm.getString("_id"); + Set removedProviders = new HashSet<>(); + + BasicDBList componentEntities = (BasicDBList) realm.get("componentEntities"); + BasicDBList federationProviders = (BasicDBList) realm.get("userFederationProviders"); + for (Object obj : federationProviders) { + BasicDBObject fedProvider = (BasicDBObject)obj; + if (fedProvider.getString("providerName").equals(providerId)) { + String id = fedProvider.getString("id"); + removedProviders.add(id); + int priority = fedProvider.getInt("priority"); + String displayName = fedProvider.getString("displayName"); + int fullSyncPeriod = fedProvider.getInt("fullSyncPeriod"); + int changedSyncPeriod = fedProvider.getInt("changedSyncPeriod"); + int lastSync = fedProvider.getInt("lastSync"); + BasicDBObject component = new BasicDBObject(); + component.put("id", id); + component.put("name", displayName); + component.put("providerType", UserStorageProvider.class.getName()); + component.put("providerId", providerId); + component.put("parentId", realmId); + + BasicDBObject config = new BasicDBObject(); + config.put("priority", Collections.singletonList(Integer.toString(priority))); + config.put("fullSyncPeriod", Collections.singletonList(Integer.toString(fullSyncPeriod))); + config.put("changedSyncPeriod", Collections.singletonList(Integer.toString(changedSyncPeriod))); + config.put("lastSync", Collections.singletonList(Integer.toString(lastSync))); + + BasicDBObject fedConfig = (BasicDBObject)fedProvider.get("config"); + if (fedConfig != null) { + for (Map.Entry attr : new HashSet<>(fedConfig.entrySet())) { + String attrName = attr.getKey(); + String attrValue = attr.getValue().toString(); + config.put(attrName, Collections.singletonList(attrValue)); + + } + } + + + component.put("config", config); + + componentEntities.add(component); + + } + } + Iterator it = federationProviders.iterator(); + while (it.hasNext()) { + BasicDBObject fedProvider = (BasicDBObject)it.next(); + String id = fedProvider.getString("id"); + if (removedProviders.contains(id)) { + it.remove(); + } + + } + realms.update(new BasicDBObject().append("_id", realmId), realm); + } + } + public void portUserFedMappersToComponent(String providerId, String mapperType) { + //logger.info("*** port mappers"); + DBCollection realms = db.getCollection("realms"); + DBCursor cursor = realms.find(); + while (cursor.hasNext()) { + BasicDBObject realm = (BasicDBObject) cursor.next(); + + String realmId = realm.getString("_id"); + Set removedProviders = new HashSet<>(); + + BasicDBList componentEntities = (BasicDBList) realm.get("componentEntities"); + BasicDBList federationProviders = (BasicDBList) realm.get("userFederationProviders"); + BasicDBList fedMappers = (BasicDBList) realm.get("userFederationMappers"); + for (Object obj : federationProviders) { + BasicDBObject fedProvider = (BasicDBObject)obj; + String providerName = fedProvider.getString("providerName"); + //logger.info("looking for mappers of fed provider: " + providerName); + if (providerName.equals(providerId)) { + String id = fedProvider.getString("id"); + //logger.info("found fed provider: " + id + ", looking at mappers"); + for (Object obj2 : fedMappers) { + BasicDBObject fedMapper = (BasicDBObject)obj2; + String federationProviderId = fedMapper.getString("federationProviderId"); + //logger.info("looking at mapper with federationProviderId: " + federationProviderId); + if (federationProviderId.equals(id)) { + String name = fedMapper.getString("name"); + String mapperId = fedMapper.getString("id"); + removedProviders.add(mapperId); + String mapperProviderId = fedMapper.getString("federationMapperType"); + BasicDBObject component = new BasicDBObject(); + component.put("id", mapperId); + component.put("name", name); + component.put("providerType", mapperType); + component.put("providerId", mapperProviderId); + component.put("parentId", id); + + BasicDBObject fedConfig = (BasicDBObject)fedMapper.get("config"); + BasicDBObject config = new BasicDBObject(); + if (fedConfig != null) { + for (Map.Entry attr : new HashSet<>(fedConfig.entrySet())) { + String attrName = attr.getKey(); + String attrValue = attr.getValue().toString(); + config.put(attrName, Collections.singletonList(attrValue)); + + } + } + component.put("config", config); + componentEntities.add(component); + } + } + } + } + Iterator it = fedMappers.iterator(); + while (it.hasNext()) { + BasicDBObject fedMapper = (BasicDBObject)it.next(); + String id = fedMapper.getString("id"); + if (removedProviders.contains(id)) { + it.remove(); + } + + } + realms.update(new BasicDBObject().append("_id", realmId), realm); + } + } +} diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java index e60055bb6d..a79c478bc7 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java @@ -1954,7 +1954,14 @@ public class RealmAdapter extends AbstractMongoAdapter impleme @Override public ComponentModel addComponentModel(ComponentModel model) { - ComponentUtil.getComponentFactory(session, model).validateConfiguration(session, model); + model = importComponentModel(model); + ComponentUtil.notifyCreated(session, this, model); + return model; + } + + @Override + public ComponentModel importComponentModel(ComponentModel model) { + ComponentUtil.getComponentFactory(session, model).validateConfiguration(session, this, model); ComponentEntity entity = new ComponentEntity(); if (model.getId() == null) { @@ -1964,15 +1971,18 @@ public class RealmAdapter extends AbstractMongoAdapter impleme } updateComponentEntity(entity, model); model.setId(entity.getId()); + if (model.getParentId() == null) { + entity.setParentId(this.getId()); + model.setParentId(this.getId()); + } realm.getComponentEntities().add(entity); updateRealm(); - ComponentUtil.notifyCreated(session, this, model); return model; } @Override public void updateComponent(ComponentModel model) { - ComponentUtil.getComponentFactory(session, model).validateConfiguration(session, model); + ComponentUtil.getComponentFactory(session, model).validateConfiguration(session, this, model); for (ComponentEntity entity : realm.getComponentEntities()) { if (entity.getId().equals(model.getId())) { @@ -1999,6 +2009,7 @@ public class RealmAdapter extends AbstractMongoAdapter impleme while(it.hasNext()) { if (it.next().getId().equals(component.getId())) { session.users().preRemove(this, component); + removeComponents(component.getId()); it.remove(); break; } diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java b/server-spi-private/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java index 4f355a504d..e26523366e 100755 --- a/server-spi-private/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java +++ b/server-spi-private/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java @@ -52,6 +52,7 @@ import org.keycloak.models.UserModel; import org.keycloak.provider.Provider; import org.keycloak.provider.ProviderFactory; import org.keycloak.representations.idm.CertificateRepresentation; +import org.keycloak.storage.UserStorageProviderModel; import org.keycloak.transaction.JtaTransactionManagerLookup; import javax.crypto.spec.SecretKeySpec; @@ -304,6 +305,52 @@ public final class KeycloakModelUtils { return null; } + public static UserStorageProviderModel findUserStorageProviderByName(String displayName, RealmModel realm) { + if (displayName == null) { + return null; + } + + for (UserStorageProviderModel fedProvider : realm.getUserStorageProviders()) { + if (displayName.equals(fedProvider.getName())) { + return fedProvider; + } + } + return null; + } + + public static UserStorageProviderModel findUserStorageProviderById(String fedProviderId, RealmModel realm) { + for (UserStorageProviderModel fedProvider : realm.getUserStorageProviders()) { + if (fedProviderId.equals(fedProvider.getId())) { + return fedProvider; + } + } + return null; + } + + public static ComponentModel createComponentModel(String name, String parentId, String providerId, String providerType, String... config) { + ComponentModel mapperModel = new ComponentModel(); + mapperModel.setParentId(parentId); + mapperModel.setName(name); + mapperModel.setProviderId(providerId); + mapperModel.setProviderType(providerType); + + String key = null; + for (String configEntry : config) { + if (key == null) { + key = configEntry; + } else { + mapperModel.getConfig().add(key, configEntry); + key = null; + } + } + if (key != null) { + throw new IllegalStateException("Invalid count of arguments for config. Maybe mistake?"); + } + + return mapperModel; + } + + public static UserFederationMapperModel createUserFederationMapperModel(String name, String federationProviderId, String mapperType, String... config) { UserFederationMapperModel mapperModel = new UserFederationMapperModel(); diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java index e35da61510..bf4a6dc29e 100755 --- a/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java +++ b/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java @@ -352,19 +352,7 @@ public class ModelToRepresentation { } } - List fedProviderModels = realm.getUserFederationProviders(); - if (fedProviderModels.size() > 0) { - List fedProviderReps = new ArrayList(); - for (UserFederationProviderModel model : fedProviderModels) { - UserFederationProviderRepresentation fedProvRep = toRepresentation(model); - fedProviderReps.add(fedProvRep); - } - rep.setUserFederationProviders(fedProviderReps); - } - - for (UserFederationMapperModel mapper : realm.getUserFederationMappers()) { - rep.addUserFederationMapper(toRepresentation(realm, mapper)); - } + exportUserFederationProvidersAndMappers(realm, rep); for (IdentityProviderModel provider : realm.getIdentityProviders()) { rep.addIdentityProvider(toRepresentation(realm, provider)); @@ -396,6 +384,22 @@ public class ModelToRepresentation { return rep; } + public static void exportUserFederationProvidersAndMappers(RealmModel realm, RealmRepresentation rep) { + List fedProviderModels = realm.getUserFederationProviders(); + if (fedProviderModels.size() > 0) { + List fedProviderReps = new ArrayList(); + for (UserFederationProviderModel model : fedProviderModels) { + UserFederationProviderRepresentation fedProvRep = toRepresentation(model); + fedProviderReps.add(fedProvRep); + } + rep.setUserFederationProviders(fedProviderReps); + } + + for (UserFederationMapperModel mapper : realm.getUserFederationMappers()) { + rep.addUserFederationMapper(toRepresentation(realm, mapper)); + } + } + public static void exportGroups(RealmModel realm, RealmRepresentation rep) { List groups = toGroupHierarchy(realm, true); rep.setGroups(groups); diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java index 1261a41631..ee4b561cc0 100755 --- a/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java +++ b/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java @@ -51,6 +51,7 @@ import org.keycloak.models.GroupModel; import org.keycloak.models.IdentityProviderMapperModel; import org.keycloak.models.IdentityProviderModel; import org.keycloak.models.KeycloakSession; +import org.keycloak.models.LDAPConstants; import org.keycloak.models.ModelException; import org.keycloak.models.OTPPolicy; import org.keycloak.models.PasswordPolicy; @@ -98,6 +99,8 @@ import org.keycloak.representations.idm.authorization.ResourceOwnerRepresentatio import org.keycloak.representations.idm.authorization.ResourceRepresentation; import org.keycloak.representations.idm.authorization.ResourceServerRepresentation; import org.keycloak.representations.idm.authorization.ScopeRepresentation; +import org.keycloak.storage.UserStorageProvider; +import org.keycloak.storage.UserStorageProviderModel; import org.keycloak.storage.federated.UserFederatedStorageProvider; import org.keycloak.util.JsonSerialization; @@ -298,36 +301,8 @@ public class RepresentationToModel { String parentId = newRealm.getId(); importComponents(newRealm, components, parentId); } + importUserFederationProvidersAndMappers(rep, newRealm); - List providerModels = null; - if (rep.getUserFederationProviders() != null) { - providerModels = convertFederationProviders(rep.getUserFederationProviders()); - newRealm.setUserFederationProviders(providerModels); - } - if (rep.getUserFederationMappers() != null) { - - // Remove builtin mappers for federation providers, which have some mappers already provided in JSON (likely due to previous export) - if (rep.getUserFederationProviders() != null) { - Set providerNames = new TreeSet(); - for (UserFederationMapperRepresentation representation : rep.getUserFederationMappers()) { - providerNames.add(representation.getFederationProviderDisplayName()); - } - for (String providerName : providerNames) { - for (UserFederationProviderModel providerModel : providerModels) { - if (providerName.equals(providerModel.getDisplayName())) { - Set toDelete = newRealm.getUserFederationMappersByFederationProvider(providerModel.getId()); - for (UserFederationMapperModel mapperModel : toDelete) { - newRealm.removeUserFederationMapper(mapperModel); - } - } - } - } - } - - for (UserFederationMapperRepresentation representation : rep.getUserFederationMappers()) { - newRealm.addUserFederationMapper(toModel(newRealm, representation)); - } - } if (rep.getGroups() != null) { importGroups(newRealm, rep); @@ -383,6 +358,64 @@ public class RepresentationToModel { } } + public static void importUserFederationProvidersAndMappers(RealmRepresentation rep, RealmModel newRealm) { + // providers to convert to component model + Set convertSet = new HashSet<>(); + convertSet.add(LDAPConstants.LDAP_PROVIDER); + Map mapperConvertSet = new HashMap<>(); + mapperConvertSet.put(LDAPConstants.LDAP_PROVIDER, "org.keycloak.storage.ldap.mappers.LDAPStorageMapper"); + + + List providerModels = null; + Map userStorageModels = new HashMap<>(); + + if (rep.getUserFederationProviders() != null) { + providerModels = new LinkedList<>(); + for (UserFederationProviderRepresentation fedRep : rep.getUserFederationProviders()) { + if (convertSet.contains(fedRep.getProviderName())) { + ComponentModel component = convertFedProviderToComponent(newRealm.getId(), fedRep); + userStorageModels.put(fedRep.getDisplayName(), newRealm.importComponentModel(component)); + } else { + providerModels.add(convertFederationProvider(fedRep)); + } + + } + newRealm.setUserFederationProviders(providerModels); + } + if (rep.getUserFederationMappers() != null) { + + // Remove builtin mappers for federation providers, which have some mappers already provided in JSON (likely due to previous export) + if (rep.getUserFederationProviders() != null) { + Set providerNames = new TreeSet(); + for (UserFederationMapperRepresentation representation : rep.getUserFederationMappers()) { + providerNames.add(representation.getFederationProviderDisplayName()); + } + for (String providerName : providerNames) { + for (UserFederationProviderModel providerModel : providerModels) { + if (providerName.equals(providerModel.getDisplayName())) { + Set toDelete = newRealm.getUserFederationMappersByFederationProvider(providerModel.getId()); + for (UserFederationMapperModel mapperModel : toDelete) { + newRealm.removeUserFederationMapper(mapperModel); + } + } + } + } + } + + for (UserFederationMapperRepresentation representation : rep.getUserFederationMappers()) { + if (userStorageModels.containsKey(representation.getFederationProviderDisplayName())) { + ComponentModel parent = userStorageModels.get(representation.getFederationProviderDisplayName()); + String newMapperType = mapperConvertSet.get(parent.getProviderId()); + ComponentModel mapper = convertFedMapperToComponent(newRealm, parent, representation, newMapperType); + newRealm.importComponentModel(mapper); + + } else { + newRealm.addUserFederationMapper(toModel(newRealm, representation)); + } + } + } + } + protected static void importComponents(RealmModel newRealm, MultivaluedHashMap components, String parentId) { for (Map.Entry> entry : components.entrySet()) { String providerType = entry.getKey(); @@ -395,7 +428,7 @@ public class RepresentationToModel { component.setProviderId(compRep.getProviderId()); component.setSubType(compRep.getSubType()); component.setParentId(parentId); - component = newRealm.addComponentModel(component); + component = newRealm.importComponentModel(component); if (compRep.getSubComponents() != null) { importComponents(newRealm, compRep.getSubComponents(), component.getId()); } @@ -858,14 +891,53 @@ public class RepresentationToModel { List result = new ArrayList(); for (UserFederationProviderRepresentation representation : providers) { - UserFederationProviderModel model = new UserFederationProviderModel(representation.getId(), representation.getProviderName(), - representation.getConfig(), representation.getPriority(), representation.getDisplayName(), - representation.getFullSyncPeriod(), representation.getChangedSyncPeriod(), representation.getLastSync()); + UserFederationProviderModel model = convertFederationProvider(representation); result.add(model); } return result; } + private static UserFederationProviderModel convertFederationProvider(UserFederationProviderRepresentation representation) { + return new UserFederationProviderModel(representation.getId(), representation.getProviderName(), + representation.getConfig(), representation.getPriority(), representation.getDisplayName(), + representation.getFullSyncPeriod(), representation.getChangedSyncPeriod(), representation.getLastSync()); + } + + public static ComponentModel convertFedProviderToComponent(String realmId, UserFederationProviderRepresentation fedModel) { + UserStorageProviderModel model = new UserStorageProviderModel(); + model.setId(fedModel.getId()); + model.setName(fedModel.getDisplayName()); + model.setParentId(realmId); + model.setProviderId(fedModel.getProviderName()); + model.setProviderType(UserStorageProvider.class.getName()); + model.setFullSyncPeriod(fedModel.getFullSyncPeriod()); + model.setPriority(fedModel.getPriority()); + model.setChangedSyncPeriod(fedModel.getChangedSyncPeriod()); + model.setLastSync(fedModel.getLastSync()); + if (fedModel.getConfig() != null) { + for (Map.Entry entry : fedModel.getConfig().entrySet()) { + model.getConfig().putSingle(entry.getKey(), entry.getValue()); + } + } + return model; + } + + public static ComponentModel convertFedMapperToComponent(RealmModel realm, ComponentModel parent, UserFederationMapperRepresentation rep, String newMapperType) { + ComponentModel mapper = new ComponentModel(); + mapper.setId(rep.getId()); + mapper.setName(rep.getName()); + mapper.setProviderId(rep.getFederationMapperType()); + mapper.setProviderType(newMapperType); + mapper.setParentId(parent.getId()); + if (rep.getConfig() != null) { + for (Map.Entry entry : rep.getConfig().entrySet()) { + mapper.getConfig().putSingle(entry.getKey(), entry.getValue()); + } + } + return mapper; + } + + public static UserFederationMapperModel toModel(RealmModel realm, UserFederationMapperRepresentation rep) { UserFederationMapperModel model = new UserFederationMapperModel(); model.setId(rep.getId()); diff --git a/server-spi/src/main/java/org/keycloak/component/ComponentFactory.java b/server-spi/src/main/java/org/keycloak/component/ComponentFactory.java index c38875636b..d519286dcb 100644 --- a/server-spi/src/main/java/org/keycloak/component/ComponentFactory.java +++ b/server-spi/src/main/java/org/keycloak/component/ComponentFactory.java @@ -25,6 +25,7 @@ import org.keycloak.provider.ProviderFactory; import java.util.Collections; import java.util.List; +import java.util.Map; /** * @author Bill Burke @@ -38,7 +39,7 @@ public interface ComponentFactory ex return null; } - void validateConfiguration(KeycloakSession session, ComponentModel model) throws ComponentValidationException; + void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel model) throws ComponentValidationException; default void onCreate(KeycloakSession session, RealmModel realm, ComponentModel model) { @@ -55,5 +56,17 @@ public interface ComponentFactory ex return Collections.EMPTY_LIST; } + /** + * This is metadata about this component type. Its really configuration information about the component type and not + * an individual instance + * + * @return + */ + default + Map getTypeMetadata() { + return Collections.EMPTY_MAP; + + } + } diff --git a/server-spi/src/main/java/org/keycloak/component/ComponentModel.java b/server-spi/src/main/java/org/keycloak/component/ComponentModel.java index dc5c68010a..f6c26d8d34 100755 --- a/server-spi/src/main/java/org/keycloak/component/ComponentModel.java +++ b/server-spi/src/main/java/org/keycloak/component/ComponentModel.java @@ -48,7 +48,7 @@ public class ComponentModel implements Serializable { this.providerType = copy.providerType; this.parentId = copy.parentId; this.subType = copy.subType; - this.config = copy.config; + this.config.addAll(copy.config); } diff --git a/server-spi/src/main/java/org/keycloak/component/SubComponentFactory.java b/server-spi/src/main/java/org/keycloak/component/SubComponentFactory.java new file mode 100644 index 0000000000..56c012d4be --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/component/SubComponentFactory.java @@ -0,0 +1,53 @@ +/* + * 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.component; + +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.provider.ConfiguredProvider; +import org.keycloak.provider.Provider; +import org.keycloak.provider.ProviderConfigProperty; +import org.keycloak.provider.ProviderFactory; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * Useful when you want to describe config properties that are effected by the parent ComponentModel + * + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface SubComponentFactory extends ComponentFactory { + default + List getConfigProperties(RealmModel realm, ComponentModel parent) { + return getConfigProperties(); + } + + /** + * This is metadata about this component type. Its really configuration information about the component type and not + * an individual instance + * + * @return + */ + default Map getTypeMetadata(RealmModel realm, ComponentModel parent) { + return getTypeMetadata(); + + } + +} diff --git a/server-spi/src/main/java/org/keycloak/models/RealmModel.java b/server-spi/src/main/java/org/keycloak/models/RealmModel.java index aa8b440021..e9df047418 100755 --- a/server-spi/src/main/java/org/keycloak/models/RealmModel.java +++ b/server-spi/src/main/java/org/keycloak/models/RealmModel.java @@ -274,7 +274,22 @@ public interface RealmModel extends RoleContainerModel { public IdentityProviderMapperModel getIdentityProviderMapperByName(String brokerAlias, String name); + /** + * Adds component model. Will call onCreate() method of ComponentFactory + * + * @param model + * @return + */ ComponentModel addComponentModel(ComponentModel model); + + /** + * Adds component model. Will NOT call onCreate() method of ComponentFactory + * + * @param model + * @return + */ + ComponentModel importComponentModel(ComponentModel model); + void updateComponent(ComponentModel component); void removeComponent(ComponentModel component); void removeComponents(String parentId); diff --git a/server-spi/src/main/java/org/keycloak/models/cache/CachedUserModel.java b/server-spi/src/main/java/org/keycloak/models/cache/CachedUserModel.java index 67431a90f9..8434d9de4c 100644 --- a/server-spi/src/main/java/org/keycloak/models/cache/CachedUserModel.java +++ b/server-spi/src/main/java/org/keycloak/models/cache/CachedUserModel.java @@ -35,6 +35,8 @@ public interface CachedUserModel extends UserModel { */ UserModel getDelegateForUpdate(); + boolean isMarkedForEviction(); + /** * Invalidate the cache for this model * diff --git a/server-spi/src/main/java/org/keycloak/models/cache/UserCache.java b/server-spi/src/main/java/org/keycloak/models/cache/UserCache.java index f309079d94..260b0bef17 100755 --- a/server-spi/src/main/java/org/keycloak/models/cache/UserCache.java +++ b/server-spi/src/main/java/org/keycloak/models/cache/UserCache.java @@ -32,5 +32,12 @@ public interface UserCache extends UserProvider { * @param user */ void evict(RealmModel realm, UserModel user); + + /** + * Evict users of a specific realm + * + * @param realm + */ + void evict(RealmModel realm); void clear(); } diff --git a/server-spi/src/main/java/org/keycloak/provider/ProviderConfigurationBuilder.java b/server-spi/src/main/java/org/keycloak/provider/ProviderConfigurationBuilder.java index 607a41b326..194e8e8fce 100644 --- a/server-spi/src/main/java/org/keycloak/provider/ProviderConfigurationBuilder.java +++ b/server-spi/src/main/java/org/keycloak/provider/ProviderConfigurationBuilder.java @@ -102,6 +102,12 @@ public class ProviderConfigurationBuilder { return this; } + public ProviderConfigPropertyBuilder options(List options) { + this.options = options; + return this; + } + + public ProviderConfigPropertyBuilder secret(boolean secret) { this.secret = secret; return this; diff --git a/server-spi/src/main/java/org/keycloak/storage/UserStorageProviderFactory.java b/server-spi/src/main/java/org/keycloak/storage/UserStorageProviderFactory.java index 121a5ea367..18e291ac6d 100755 --- a/server-spi/src/main/java/org/keycloak/storage/UserStorageProviderFactory.java +++ b/server-spi/src/main/java/org/keycloak/storage/UserStorageProviderFactory.java @@ -26,10 +26,13 @@ import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.RealmModel; import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.provider.ProviderConfigurationBuilder; +import org.keycloak.storage.user.ImportSynchronization; import java.util.Collections; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; +import java.util.Map; /** * @author Bill Burke @@ -81,7 +84,7 @@ public interface UserStorageProviderFactory exten } @Override - default void validateConfiguration(KeycloakSession session, ComponentModel config) throws ComponentValidationException { + default void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel config) throws ComponentValidationException { } @@ -108,4 +111,14 @@ public interface UserStorageProviderFactory exten List getCommonProviderConfigProperties() { return UserStorageProviderSpi.commonConfig(); } + + @Override + default + Map getTypeMetadata() { + Map metadata = new HashMap<>(); + if (this instanceof ImportSynchronization) { + metadata.put("synchronizable", true); + } + return metadata; + } } diff --git a/server-spi/src/main/java/org/keycloak/storage/UserStorageProviderSpi.java b/server-spi/src/main/java/org/keycloak/storage/UserStorageProviderSpi.java index 625adeb23a..06bc256979 100755 --- a/server-spi/src/main/java/org/keycloak/storage/UserStorageProviderSpi.java +++ b/server-spi/src/main/java/org/keycloak/storage/UserStorageProviderSpi.java @@ -64,6 +64,8 @@ public class UserStorageProviderSpi implements Spi { .property() .name("lastSync").type(ProviderConfigProperty.STRING_TYPE).add() .property() + .name("batchSizeForSync").type(ProviderConfigProperty.STRING_TYPE).add() + .property() .name("importEnabled").type(ProviderConfigProperty.BOOLEAN_TYPE).add() .property() .name("cachePolicy").type(ProviderConfigProperty.STRING_TYPE).add() diff --git a/services/pom.xml b/services/pom.xml index 0fb68f7735..f6fdb74cc7 100755 --- a/services/pom.xml +++ b/services/pom.xml @@ -68,6 +68,11 @@ keycloak-server-spi-private provided + + org.keycloak + keycloak-ldap-federation + provided + org.jboss.spec.javax.servlet jboss-servlet-api_3.0_spec diff --git a/services/src/main/java/org/keycloak/credential/OTPCredentialProvider.java b/services/src/main/java/org/keycloak/credential/OTPCredentialProvider.java index f71f84a897..96351467e6 100644 --- a/services/src/main/java/org/keycloak/credential/OTPCredentialProvider.java +++ b/services/src/main/java/org/keycloak/credential/OTPCredentialProvider.java @@ -43,8 +43,9 @@ public class OTPCredentialProvider implements CredentialProvider, CredentialInpu protected KeycloakSession session; protected List getCachedCredentials(UserModel user, String type) { - if (!(user instanceof CachedUserModel)) return Collections.EMPTY_LIST; + if (!(user instanceof CachedUserModel)) return null; CachedUserModel cached = (CachedUserModel)user; + if (cached.isMarkedForEviction()) return null; List rtn = (List)cached.getCachedWith().get(OTPCredentialProvider.class.getName() + "." + type); if (rtn == null) return Collections.EMPTY_LIST; return rtn; @@ -186,8 +187,9 @@ public class OTPCredentialProvider implements CredentialProvider, CredentialInpu } protected boolean configuredForTOTP(RealmModel realm, UserModel user) { - return !getCachedCredentials(user, CredentialModel.TOTP).isEmpty() - || !getCredentialStore().getStoredCredentialsByType(realm, user, CredentialModel.TOTP).isEmpty(); + List cachedCredentials = getCachedCredentials(user, CredentialModel.TOTP); + if (cachedCredentials == null) return !getCredentialStore().getStoredCredentialsByType(realm, user, CredentialModel.TOTP).isEmpty(); + return !cachedCredentials.isEmpty(); } public static boolean validOTP(RealmModel realm, String token, String secret) { @@ -228,7 +230,7 @@ public class OTPCredentialProvider implements CredentialProvider, CredentialInpu } else { TimeBasedOTP validator = new TimeBasedOTP(policy.getAlgorithm(), policy.getDigits(), policy.getPeriod(), policy.getLookAheadWindow()); List creds = getCachedCredentials(user, CredentialModel.TOTP); - if (creds.isEmpty()) { + if (creds == null) { creds = getCredentialStore().getStoredCredentialsByType(realm, user, CredentialModel.TOTP); } else { logger.debugv("Cache hit for TOTP for user {0}", user.getUsername()); diff --git a/services/src/main/java/org/keycloak/credential/PasswordCredentialProvider.java b/services/src/main/java/org/keycloak/credential/PasswordCredentialProvider.java index 97a92ea9a3..bdc32e70b3 100644 --- a/services/src/main/java/org/keycloak/credential/PasswordCredentialProvider.java +++ b/services/src/main/java/org/keycloak/credential/PasswordCredentialProvider.java @@ -59,7 +59,7 @@ public class PasswordCredentialProvider implements CredentialProvider, Credentia public CredentialModel getPassword(RealmModel realm, UserModel user) { List passwords = null; - if (user instanceof CachedUserModel) { + if (user instanceof CachedUserModel && !((CachedUserModel)user).isMarkedForEviction()) { CachedUserModel cached = (CachedUserModel)user; passwords = (List)cached.getCachedWith().get(PASSWORD_CACHE_KEY); diff --git a/services/src/main/java/org/keycloak/keys/AbstractRsaKeyProviderFactory.java b/services/src/main/java/org/keycloak/keys/AbstractRsaKeyProviderFactory.java index 7b2d526086..1c2af4fcab 100644 --- a/services/src/main/java/org/keycloak/keys/AbstractRsaKeyProviderFactory.java +++ b/services/src/main/java/org/keycloak/keys/AbstractRsaKeyProviderFactory.java @@ -20,6 +20,7 @@ package org.keycloak.keys; import org.keycloak.component.ComponentModel; import org.keycloak.component.ComponentValidationException; import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; import org.keycloak.provider.ConfigurationValidationHelper; import org.keycloak.provider.ProviderConfigurationBuilder; @@ -36,7 +37,7 @@ public abstract class AbstractRsaKeyProviderFactory implements KeyProviderFactor } @Override - public void validateConfiguration(KeycloakSession session, ComponentModel model) throws ComponentValidationException { + public void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel model) throws ComponentValidationException { ConfigurationValidationHelper.check(model) .checkLong(Attributes.PRIORITY_PROPERTY, false) .checkBoolean(Attributes.ENABLED_PROPERTY, false) diff --git a/services/src/main/java/org/keycloak/keys/GeneratedRsaKeyProviderFactory.java b/services/src/main/java/org/keycloak/keys/GeneratedRsaKeyProviderFactory.java index 9042b48989..cba36d0b5d 100644 --- a/services/src/main/java/org/keycloak/keys/GeneratedRsaKeyProviderFactory.java +++ b/services/src/main/java/org/keycloak/keys/GeneratedRsaKeyProviderFactory.java @@ -57,8 +57,8 @@ public class GeneratedRsaKeyProviderFactory extends AbstractRsaKeyProviderFactor } @Override - public void validateConfiguration(KeycloakSession session, ComponentModel model) throws ComponentValidationException { - super.validateConfiguration(session, model); + public void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel model) throws ComponentValidationException { + super.validateConfiguration(session, realm, model); ConfigurationValidationHelper.check(model) .checkInt(Attributes.KEY_SIZE_PROPERTY, false); @@ -75,7 +75,6 @@ public class GeneratedRsaKeyProviderFactory extends AbstractRsaKeyProviderFactor } if (!(model.contains(Attributes.PRIVATE_KEY_KEY) && model.contains(Attributes.CERTIFICATE_KEY))) { - RealmModel realm = session.realms().getRealm(model.getParentId()); generateKeys(realm, model, size); logger.debugv("Generated keys for {0}", realm.getName()); @@ -83,7 +82,6 @@ public class GeneratedRsaKeyProviderFactory extends AbstractRsaKeyProviderFactor PrivateKey privateKey = PemUtils.decodePrivateKey(model.get(Attributes.PRIVATE_KEY_KEY)); int currentSize = ((RSAPrivateKey) privateKey).getModulus().bitLength(); if (currentSize != size) { - RealmModel realm = session.realms().getRealm(model.getParentId()); generateKeys(realm, model, size); logger.debugv("Key size changed, generating new keys for {0}", realm.getName()); diff --git a/services/src/main/java/org/keycloak/keys/JavaKeystoreKeyProviderFactory.java b/services/src/main/java/org/keycloak/keys/JavaKeystoreKeyProviderFactory.java index cbc003e4e3..51b726a57b 100644 --- a/services/src/main/java/org/keycloak/keys/JavaKeystoreKeyProviderFactory.java +++ b/services/src/main/java/org/keycloak/keys/JavaKeystoreKeyProviderFactory.java @@ -22,6 +22,7 @@ import org.keycloak.component.ComponentModel; import org.keycloak.component.ComponentValidationException; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.models.RealmModel; import org.keycloak.provider.ConfigurationValidationHelper; import org.keycloak.provider.ProviderConfigProperty; @@ -65,8 +66,8 @@ public class JavaKeystoreKeyProviderFactory extends AbstractRsaKeyProviderFactor } @Override - public void validateConfiguration(KeycloakSession session, ComponentModel model) throws ComponentValidationException { - super.validateConfiguration(session, model); + public void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel model) throws ComponentValidationException { + super.validateConfiguration(session, realm, model); ConfigurationValidationHelper.check(model) .checkSingle(KEYSTORE_PROPERTY, true) diff --git a/services/src/main/java/org/keycloak/keys/RsaKeyProviderFactory.java b/services/src/main/java/org/keycloak/keys/RsaKeyProviderFactory.java index f6814dac9d..aac9fbd1df 100644 --- a/services/src/main/java/org/keycloak/keys/RsaKeyProviderFactory.java +++ b/services/src/main/java/org/keycloak/keys/RsaKeyProviderFactory.java @@ -56,8 +56,8 @@ public class RsaKeyProviderFactory extends AbstractRsaKeyProviderFactory { } @Override - public void validateConfiguration(KeycloakSession session, ComponentModel model) throws ComponentValidationException { - super.validateConfiguration(session, model); + public void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel model) throws ComponentValidationException { + super.validateConfiguration(session, realm, model); ConfigurationValidationHelper.check(model) .checkSingle(Attributes.PRIVATE_KEY_PROPERTY, true) @@ -89,7 +89,6 @@ public class RsaKeyProviderFactory extends AbstractRsaKeyProviderFactory { } } else { try { - RealmModel realm = session.realms().getRealm(model.getParentId()); Certificate certificate = CertificateUtils.generateV1SelfSignedCertificate(keyPair, realm.getName()); model.put(Attributes.CERTIFICATE_KEY, PemUtils.encodeCertificate(certificate)); } catch (Throwable t) { diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakTransactionManager.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakTransactionManager.java index 0039fa01e7..4ddcaf29b6 100755 --- a/services/src/main/java/org/keycloak/services/DefaultKeycloakTransactionManager.java +++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakTransactionManager.java @@ -92,9 +92,11 @@ public class DefaultKeycloakTransactionManager implements KeycloakTransactionMan if (jtaPolicy == JTAPolicy.REQUIRES_NEW) { JtaTransactionManagerLookup jtaLookup = session.getProvider(JtaTransactionManagerLookup.class); - TransactionManager tm = jtaLookup.getTransactionManager(); - if (tm != null) { - enlist(new JtaTransactionWrapper(tm)); + if (jtaLookup != null) { + TransactionManager tm = jtaLookup.getTransactionManager(); + if (tm != null) { + enlist(new JtaTransactionWrapper(tm)); + } } } diff --git a/services/src/main/java/org/keycloak/services/clientregistration/policy/AbstractClientRegistrationPolicyFactory.java b/services/src/main/java/org/keycloak/services/clientregistration/policy/AbstractClientRegistrationPolicyFactory.java index 572a7120f5..e29848c60b 100644 --- a/services/src/main/java/org/keycloak/services/clientregistration/policy/AbstractClientRegistrationPolicyFactory.java +++ b/services/src/main/java/org/keycloak/services/clientregistration/policy/AbstractClientRegistrationPolicyFactory.java @@ -24,6 +24,7 @@ import org.keycloak.component.ComponentModel; import org.keycloak.component.ComponentValidationException; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.models.RealmModel; import org.keycloak.provider.ProviderConfigProperty; /** @@ -47,7 +48,7 @@ public abstract class AbstractClientRegistrationPolicyFactory implements ClientR } @Override - public void validateConfiguration(KeycloakSession session, ComponentModel config) throws ComponentValidationException { + public void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel config) throws ComponentValidationException { } @Override diff --git a/services/src/main/java/org/keycloak/services/clientregistration/policy/impl/MaxClientsClientRegistrationPolicyFactory.java b/services/src/main/java/org/keycloak/services/clientregistration/policy/impl/MaxClientsClientRegistrationPolicyFactory.java index 00ebe05c3a..120f2808af 100644 --- a/services/src/main/java/org/keycloak/services/clientregistration/policy/impl/MaxClientsClientRegistrationPolicyFactory.java +++ b/services/src/main/java/org/keycloak/services/clientregistration/policy/impl/MaxClientsClientRegistrationPolicyFactory.java @@ -23,6 +23,7 @@ import java.util.List; import org.keycloak.component.ComponentModel; import org.keycloak.component.ComponentValidationException; import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; import org.keycloak.provider.ConfigurationValidationHelper; import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.services.clientregistration.policy.AbstractClientRegistrationPolicyFactory; @@ -72,7 +73,7 @@ public class MaxClientsClientRegistrationPolicyFactory extends AbstractClientReg } @Override - public void validateConfiguration(KeycloakSession session, ComponentModel config) throws ComponentValidationException { + public void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel config) throws ComponentValidationException { ConfigurationValidationHelper.check(config) .checkInt(MAX_CLIENTS_PROPERTY, true); } diff --git a/services/src/main/java/org/keycloak/services/clientregistration/policy/impl/TrustedHostClientRegistrationPolicyFactory.java b/services/src/main/java/org/keycloak/services/clientregistration/policy/impl/TrustedHostClientRegistrationPolicyFactory.java index a5436c12a2..c881f64563 100644 --- a/services/src/main/java/org/keycloak/services/clientregistration/policy/impl/TrustedHostClientRegistrationPolicyFactory.java +++ b/services/src/main/java/org/keycloak/services/clientregistration/policy/impl/TrustedHostClientRegistrationPolicyFactory.java @@ -23,6 +23,7 @@ import java.util.List; import org.keycloak.component.ComponentModel; import org.keycloak.component.ComponentValidationException; import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; import org.keycloak.provider.ConfigurationValidationHelper; import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.services.clientregistration.policy.AbstractClientRegistrationPolicyFactory; @@ -70,7 +71,7 @@ public class TrustedHostClientRegistrationPolicyFactory extends AbstractClientRe } @Override - public void validateConfiguration(KeycloakSession session, ComponentModel config) throws ComponentValidationException { + public void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel config) throws ComponentValidationException { ConfigurationValidationHelper.check(config) .checkBoolean(HOST_SENDING_REGISTRATION_REQUEST_MUST_MATCH_PROPERTY, true) .checkBoolean(CLIENT_URIS_MUST_MATCH_PROPERTY, true); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ComponentResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ComponentResource.java index 5919b2d61d..56c7ce74fd 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/ComponentResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ComponentResource.java @@ -20,15 +20,22 @@ import org.jboss.logging.Logger; import org.jboss.resteasy.annotations.cache.NoCache; import org.jboss.resteasy.spi.NotFoundException; import org.keycloak.common.ClientConnection; +import org.keycloak.component.ComponentFactory; import org.keycloak.component.ComponentModel; import org.keycloak.component.ComponentValidationException; +import org.keycloak.component.SubComponentFactory; import org.keycloak.events.admin.OperationType; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.models.utils.RepresentationToModel; import org.keycloak.models.utils.StripSecretsUtils; +import org.keycloak.provider.Provider; +import org.keycloak.provider.ProviderConfigProperty; +import org.keycloak.provider.ProviderFactory; import org.keycloak.representations.idm.ComponentRepresentation; +import org.keycloak.representations.idm.ComponentTypeRepresentation; +import org.keycloak.representations.idm.ConfigPropertyRepresentation; import org.keycloak.services.ErrorResponse; import org.keycloak.services.ErrorResponseException; @@ -53,6 +60,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.Properties; import java.util.stream.Collectors; @@ -128,7 +136,7 @@ public class ComponentResource { } catch (ComponentValidationException e) { return localizedErrorResponse(e); } catch (IllegalArgumentException e) { - throw new BadRequestException(); + throw new BadRequestException(e); } } @@ -197,4 +205,61 @@ public class ComponentResource { return ErrorResponse.error(message, Response.Status.BAD_REQUEST); } + /** + * List of subcomponent types that are available to configure for a particular parent component. + * + * @param parentId + * @param subtype + * @return + */ + @GET + @Path("{id}/sub-component-types") + @Produces(MediaType.APPLICATION_JSON) + @NoCache + public List getSubcomponentConfig(@PathParam("id") String parentId, @QueryParam("type") String subtype) { + auth.requireView(); + ComponentModel parent = realm.getComponent(parentId); + if (parent == null) { + throw new NotFoundException("Could not find parent component"); + } + if (subtype == null) { + throw new BadRequestException("must specify a subtype"); + } + Class providerClass = null; + try { + providerClass = (Class)Class.forName(subtype); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + List subcomponents = new LinkedList<>(); + for (ProviderFactory factory : session.getKeycloakSessionFactory().getProviderFactories(providerClass)) { + ComponentTypeRepresentation rep = new ComponentTypeRepresentation(); + rep.setId(factory.getId()); + if (!(factory instanceof ComponentFactory)) { + continue; + } + ComponentFactory componentFactory = (ComponentFactory)factory; + + rep.setHelpText(componentFactory.getHelpText()); + List props = null; + Map metadata = null; + if (factory instanceof SubComponentFactory) { + props = ((SubComponentFactory)factory).getConfigProperties(realm, parent); + metadata = ((SubComponentFactory)factory).getTypeMetadata(realm, parent); + + } else { + props = componentFactory.getConfigProperties(); + metadata = componentFactory.getTypeMetadata(); + } + + List propReps = ModelToRepresentation.toRepresentation(props); + rep.setProperties(propReps); + rep.setMetadata(metadata); + subcomponents.add(rep); + } + return subcomponents; + } + + + } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UserStorageProviderResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UserStorageProviderResource.java index 9fde51f7d8..8e29779d99 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/UserStorageProviderResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/UserStorageProviderResource.java @@ -24,9 +24,14 @@ import org.keycloak.component.ComponentModel; import org.keycloak.events.admin.OperationType; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; +import org.keycloak.provider.ProviderFactory; +import org.keycloak.services.ServicesLogger; import org.keycloak.services.managers.UserStorageSyncManager; import org.keycloak.storage.UserStorageProvider; import org.keycloak.storage.UserStorageProviderModel; +import org.keycloak.storage.ldap.LDAPStorageProvider; +import org.keycloak.storage.ldap.LDAPStorageProviderFactory; +import org.keycloak.storage.ldap.mappers.LDAPStorageMapper; import org.keycloak.storage.user.SynchronizationResult; import javax.ws.rs.POST; @@ -119,6 +124,47 @@ public class UserStorageProviderResource { return syncResult; } + /** + * Trigger sync of mapper data related to ldap mapper (roles, groups, ...) + * + * @return + */ + @POST + @Path("{parentId}/mappers/{id}/sync") + @NoCache + @Produces(MediaType.APPLICATION_JSON) + public SynchronizationResult syncMapperData(@PathParam("parentId") String parentId, @PathParam("id") String mapperId, @QueryParam("direction") String direction) { + auth.requireManage(); + + ComponentModel parentModel = realm.getComponent(parentId); + if (parentModel == null) throw new NotFoundException("Parent model not found"); + ComponentModel mapperModel = realm.getComponent(mapperId); + if (mapperModel == null) throw new NotFoundException("Mapper model not found"); + LDAPStorageMapper mapper = session.getProvider(LDAPStorageMapper.class, mapperModel); + ProviderFactory factory = session.getKeycloakSessionFactory().getProviderFactory(LDAPStorageProvider.class, parentModel.getProviderId()); + + LDAPStorageProviderFactory providerFactory = (LDAPStorageProviderFactory)factory; + LDAPStorageProvider federationProvider = providerFactory.create(session, parentModel); + + ServicesLogger.LOGGER.syncingDataForMapper(mapperModel.getName(), mapperModel.getProviderId(), direction); + + SynchronizationResult syncResult; + if ("fedToKeycloak".equals(direction)) { + syncResult = mapper.syncDataFromFederationProviderToKeycloak(mapperModel, federationProvider, session, realm); + } else if ("keycloakToFed".equals(direction)) { + syncResult = mapper.syncDataFromKeycloakToFederationProvider(mapperModel, federationProvider, session, realm); + } else { + throw new NotFoundException("Unknown direction: " + direction); + } + + Map eventRep = new HashMap<>(); + eventRep.put("action", direction); + eventRep.put("result", syncResult); + adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).representation(eventRep).success(); + return syncResult; + } + + } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java index 8120e7f0b4..8017b04a08 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java @@ -20,6 +20,7 @@ package org.keycloak.services.resources.admin.info; import org.keycloak.broker.provider.IdentityProvider; import org.keycloak.broker.provider.IdentityProviderFactory; import org.keycloak.broker.social.SocialIdentityProvider; +import org.keycloak.component.ComponentFactory; import org.keycloak.events.EventType; import org.keycloak.events.admin.OperationType; import org.keycloak.events.admin.ResourceType; @@ -136,8 +137,8 @@ public class ServerInfoAdminResource { List configProperties = configured.getConfigProperties(); if (configProperties == null) configProperties = Collections.EMPTY_LIST; rep.setProperties(ModelToRepresentation.toRepresentation(configProperties)); - if (pi instanceof ImportSynchronization) { - rep.getMetadata().put("synchronizable", true); + if (pi instanceof ComponentFactory) { + rep.setMetadata(((ComponentFactory)pi).getTypeMetadata()); } List reps = info.getComponentTypes().get(spi.getProviderClass().getName()); if (reps == null) { diff --git a/services/src/main/java/org/keycloak/storage/UserStorageManager.java b/services/src/main/java/org/keycloak/storage/UserStorageManager.java index 8394bbb4a2..e3eeaecb71 100755 --- a/services/src/main/java/org/keycloak/storage/UserStorageManager.java +++ b/services/src/main/java/org/keycloak/storage/UserStorageManager.java @@ -138,6 +138,12 @@ public class UserStorageManager implements UserProvider, OnUserCache { if (getFederatedStorage() != null) getFederatedStorage().preRemove(realm, user); StorageId storageId = new StorageId(user.getId()); if (storageId.getProviderId() == null) { + if (user.getFederationLink() != null) { + UserStorageProvider provider = getStorageProvider(session, realm, user.getFederationLink()); + if (provider != null && provider instanceof UserRegistrationProvider) { + ((UserRegistrationProvider)provider).removeUser(realm, user); + } + } return localStorage().removeUser(realm, user); } UserRegistrationProvider registry = (UserRegistrationProvider)getStorageProvider(session, realm, storageId.getProviderId()); diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/components/TestImplProviderFactory.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/components/TestImplProviderFactory.java index 4092501300..b3de69ee3c 100644 --- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/components/TestImplProviderFactory.java +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/components/TestImplProviderFactory.java @@ -22,6 +22,7 @@ import org.keycloak.component.ComponentModel; import org.keycloak.component.ComponentValidationException; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.models.RealmModel; import org.keycloak.provider.ConfigurationValidationHelper; import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.provider.ProviderConfigurationBuilder; @@ -50,7 +51,7 @@ public class TestImplProviderFactory implements TestProviderFactory { } @Override - public void validateConfiguration(KeycloakSession session, ComponentModel model) throws ComponentValidationException { + public void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel model) throws ComponentValidationException { ConfigurationValidationHelper.check(model) .checkRequired("required", "Required") .checkInt("number", "Number", false); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/federation/AbstractKerberosLdapAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/federation/AbstractKerberosLdapAdapterTest.java deleted file mode 100755 index e7d97b6ce7..0000000000 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/federation/AbstractKerberosLdapAdapterTest.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * 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.adapter.federation; - -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.keycloak.events.Details; -import org.keycloak.federation.kerberos.CommonKerberosConfig; -import org.keycloak.federation.ldap.LDAPFederationProviderFactory; -import org.keycloak.federation.ldap.kerberos.LDAPProviderKerberosConfig; -import org.keycloak.models.UserFederationProvider; -import org.keycloak.models.UserFederationProviderModel; -import org.keycloak.representations.idm.RealmRepresentation; -import org.keycloak.representations.idm.UserFederationProviderRepresentation; -import org.keycloak.representations.idm.UserRepresentation; -import org.keycloak.testsuite.admin.ApiUtil; - -import javax.ws.rs.core.Response; -import java.util.Arrays; -import java.util.Map; - -/** - * Test of LDAPFederationProvider (Kerberos backed by LDAP) - * - * @author Marek Posolda - */ -public abstract class AbstractKerberosLdapAdapterTest extends AbstractKerberosAdapterTest { - - private static final String PROVIDER_CONFIG_LOCATION = "kerberos-ldap-connection.properties"; - - @Before - public void init() throws Exception{ - Map ldapConfig = getConfig(); - UserFederationProviderRepresentation userFederationProviderRepresentation = new UserFederationProviderRepresentation(); - userFederationProviderRepresentation.setProviderName(LDAPFederationProviderFactory.PROVIDER_NAME); - userFederationProviderRepresentation.setConfig(ldapConfig); - userFederationProviderRepresentation.setPriority(0); - userFederationProviderRepresentation.setDisplayName("kerberos-ldap"); - userFederationProviderRepresentation.setFullSyncPeriod(-1); - userFederationProviderRepresentation.setChangedSyncPeriod(-1); - userFederationProviderRepresentation.setLastSync(0); - RealmRepresentation realmRepresentation = testRealmResource().toRepresentation(); - realmRepresentation.setUserFederationProviders(Arrays.asList(userFederationProviderRepresentation)); - realmRepresentation.setEventsEnabled(true); - testRealmResource().update(realmRepresentation); - } - - @Override - protected CommonKerberosConfig getKerberosConfig(UserFederationProviderModel model) { - return new LDAPProviderKerberosConfig(model); - } - - @Test - public void spnegoLoginTest() throws Exception { - spnegoLoginTestImpl(); - // Assert user was imported and hasn't any required action on him. Profile info is synced from LDAP - assertUser("hnelson", "hnelson@keycloak.org", "Horatio", "Nelson", false); - } - - @Test - public void writableEditModeTest() throws Exception { - - // Change editMode to WRITABLE - updateProviderEditMode(UserFederationProvider.EditMode.WRITABLE); - - // Login with username/password from kerberos - changePasswordPage.navigateTo(); - loginPage.isCurrent(); - loginPage.form().login("jduke", "theduke"); - changePasswordPage.isCurrent(); - - // Successfully change password now - changePasswordPage.changePasswords("theduke", "newPass", "newPass"); - Assert.assertTrue(driver.getPageSource().contains("Your password has been updated.")); - changePasswordPage.logOut(); - - // Login with old password doesn't work, but with new password works - loginPage.form().login("jduke", "theduke"); - loginPage.isCurrent(); - loginPage.form().login("jduke", "newPass"); - changePasswordPage.isCurrent(); - changePasswordPage.logOut(); - - // Assert SPNEGO login with the new password as mode is writable - events.clear(); - Response spnegoResponse = spnegoLogin("jduke", "newPass"); - Assert.assertEquals(302, spnegoResponse.getStatus()); - UserRepresentation user = ApiUtil.findUserByUsername(testRealmResource(), "jduke"); - events.expectLogin() - .client("kerberos-app") - .user(user != null ? user.getId() : null) - .detail(Details.REDIRECT_URI, kerberosPortal.toString()) - //.detail(Details.AUTH_METHOD, "spnego") - .detail(Details.USERNAME, "jduke") - .assertEvent(); - - // Change password back - changePasswordPage.navigateTo();; - - loginPage.form().login("jduke", "newPass"); - changePasswordPage.isCurrent(); - changePasswordPage.changePasswords("newPass", "theduke", "theduke"); - Assert.assertTrue(driver.getPageSource().contains("Your password has been updated.")); - changePasswordPage.logOut(); - - spnegoResponse.close(); - events.clear(); - } - - protected String getConnectionPropertiesLocation() { - return PROVIDER_CONFIG_LOCATION; - } -} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/PermissionsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/PermissionsTest.java index 7d005e95dd..b23194a7ac 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/PermissionsTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/PermissionsTest.java @@ -1508,77 +1508,6 @@ public class PermissionsTest extends AbstractKeycloakTest { }, Resource.IDENTITY_PROVIDER, true); } - @Test - public void userFederation() { - invoke(new Invocation() { - public void invoke(RealmResource realm) { - realm.userFederation().getProviderInstances(); - } - }, Resource.REALM, false); - invoke(new Invocation() { - public void invoke(RealmResource realm) { - realm.userFederation().getProviderFactories(); - } - }, Resource.REALM, false); - invoke(new Invocation() { - public void invoke(RealmResource realm) { - realm.userFederation().getProviderFactory("nosuch"); - } - }, Resource.REALM, false); - invoke(new InvocationWithResponse() { - public void invoke(RealmResource realm, AtomicReference response) { - UserFederationProviderRepresentation rep = new UserFederationProviderRepresentation(); - rep.setProviderName("ldap"); - response.set(realm.userFederation().create(rep)); - } - }, Resource.REALM, true); - invoke(new Invocation() { - public void invoke(RealmResource realm) { - realm.userFederation().get("nosuch").toRepresentation(); - } - }, Resource.REALM, false); - invoke(new Invocation() { - public void invoke(RealmResource realm) { - realm.userFederation().get("nosuch").update(new UserFederationProviderRepresentation()); - } - }, Resource.REALM, true); - invoke(new Invocation() { - public void invoke(RealmResource realm) { - realm.userFederation().get("nosuch").remove(); - } - }, Resource.REALM, true); - invoke(new Invocation() { - public void invoke(RealmResource realm) { - realm.userFederation().get("nosuch").syncUsers("nosuch"); - } - }, Resource.REALM, true); - invoke(new Invocation() { - public void invoke(RealmResource realm) { - realm.userFederation().get("nosuch").getMapperTypes(); - } - }, Resource.REALM, false); - invoke(new Invocation() { - public void invoke(RealmResource realm) { - realm.userFederation().get("nosuch").getMappers(); - } - }, Resource.REALM, false); - invoke(new InvocationWithResponse() { - public void invoke(RealmResource realm, AtomicReference response) { - response.set(realm.userFederation().get("nosuch").addMapper(new UserFederationMapperRepresentation())); - } - }, Resource.REALM, true); - invoke(new Invocation() { - public void invoke(RealmResource realm) { - realm.userFederation().get("nosuch").getMapperById("nosuch"); - } - }, Resource.REALM, false); - invoke(new Invocation() { - public void invoke(RealmResource realm) { - realm.userFederation().get("nosuch").syncMapperData("nosuch", "nosuch"); - } - }, Resource.REALM, true); - } - @Test public void components() { invoke(new Invocation() { diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserFederationMapperTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserStorageMapperTest.java similarity index 97% rename from testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserFederationMapperTest.java rename to testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserStorageMapperTest.java index dce490ec95..63aacc014f 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserFederationMapperTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserStorageMapperTest.java @@ -19,14 +19,11 @@ package org.keycloak.testsuite.admin; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.keycloak.admin.client.resource.UserFederationProviderResource; import org.keycloak.events.admin.OperationType; import org.keycloak.events.admin.ResourceType; -import org.keycloak.federation.ldap.mappers.UserAttributeLDAPFederationMapper; -import org.keycloak.federation.ldap.mappers.UserAttributeLDAPFederationMapperFactory; -import org.keycloak.federation.ldap.mappers.membership.role.RoleLDAPFederationMapperFactory; -import org.keycloak.federation.ldap.mappers.membership.role.RoleMapperConfig; import org.keycloak.representations.idm.ConfigPropertyRepresentation; import org.keycloak.representations.idm.UserFederationMapperRepresentation; import org.keycloak.representations.idm.UserFederationMapperTypeRepresentation; @@ -50,10 +47,12 @@ import java.util.Set; /** * @author Marek Posolda */ -public class UserFederationMapperTest extends AbstractAdminTest { +@Ignore +public class UserStorageMapperTest extends AbstractAdminTest { private String ldapProviderId; private String dummyProviderId; + /* @Before public void initFederationProviders() { @@ -304,4 +303,5 @@ public class UserFederationMapperTest extends AbstractAdminTest { } return null; } + */ } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserFederationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserStorageRestTest.java similarity index 99% rename from testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserFederationTest.java rename to testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserStorageRestTest.java index 48db1b509f..1c4b3db383 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserFederationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserStorageRestTest.java @@ -17,6 +17,7 @@ package org.keycloak.testsuite.admin; +import org.junit.Ignore; import org.junit.Test; import org.keycloak.admin.client.resource.UserFederationProvidersResource; import org.keycloak.common.constants.KerberosConstants; @@ -45,7 +46,8 @@ import java.util.Map; /** * @author Marek Posolda */ -public class UserFederationTest extends AbstractAdminTest { +@Ignore +public class UserStorageRestTest extends AbstractAdminTest { @Test public void testProviderFactories() { diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportUtil.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportUtil.java index 2c45818773..001ed86b60 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportUtil.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportUtil.java @@ -25,10 +25,11 @@ import org.keycloak.admin.client.resource.ClientTemplateResource; import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.admin.client.resource.UserResource; import org.keycloak.common.constants.KerberosConstants; -import org.keycloak.federation.ldap.mappers.FullNameLDAPFederationMapper; -import org.keycloak.federation.ldap.mappers.FullNameLDAPFederationMapperFactory; +import org.keycloak.component.ComponentModel; import org.keycloak.models.Constants; import org.keycloak.models.LDAPConstants; +import org.keycloak.models.UserFederationMapperModel; +import org.keycloak.models.UserFederationProviderModel; import org.keycloak.models.utils.DefaultAuthenticationFlows; import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper; @@ -37,6 +38,8 @@ import org.keycloak.representations.idm.AuthenticationFlowRepresentation; import org.keycloak.representations.idm.ClientMappingsRepresentation; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ClientTemplateRepresentation; +import org.keycloak.representations.idm.ComponentExportRepresentation; +import org.keycloak.representations.idm.ComponentRepresentation; import org.keycloak.representations.idm.FederatedIdentityRepresentation; import org.keycloak.representations.idm.IdentityProviderRepresentation; import org.keycloak.representations.idm.ProtocolMapperRepresentation; @@ -49,6 +52,11 @@ import org.keycloak.representations.idm.authorization.PolicyRepresentation; import org.keycloak.representations.idm.authorization.ResourceRepresentation; import org.keycloak.representations.idm.authorization.ResourceServerRepresentation; import org.keycloak.representations.idm.authorization.ScopeRepresentation; +import org.keycloak.storage.UserStorageProvider; +import org.keycloak.storage.UserStorageProviderModel; +import org.keycloak.storage.ldap.mappers.FullNameLDAPStorageMapper; +import org.keycloak.storage.ldap.mappers.FullNameLDAPStorageMapperFactory; +import org.keycloak.storage.ldap.mappers.LDAPStorageMapper; import org.keycloak.testsuite.admin.ApiUtil; import org.keycloak.testsuite.client.KeycloakTestingClient; import org.keycloak.testsuite.util.RealmRepUtil; @@ -260,33 +268,34 @@ public class ExportImportUtil { Assert.assertEquals("googleId", google.getConfig().get("clientId")); Assert.assertEquals("googleSecret", google.getConfig().get("clientSecret")); + ////////////////// // Test federation providers + // on import should convert UserfederationProviderRepresentation to Component model List fedProviders = realm.getUserFederationProviders(); - Assert.assertTrue(fedProviders.size() == 2); - UserFederationProviderRepresentation ldap1 = fedProviders.get(0); - Assert.assertEquals("MyLDAPProvider1", ldap1.getDisplayName()); - Assert.assertEquals("ldap", ldap1.getProviderName()); - Assert.assertEquals(1, ldap1.getPriority()); - Assert.assertEquals("ldap://foo", ldap1.getConfig().get(LDAPConstants.CONNECTION_URL)); + Assert.assertTrue(fedProviders == null || fedProviders.size() == 0); + List storageProviders = realmRsc.components().query(realm.getId(), UserStorageProvider.class.getName()); + Assert.assertTrue(storageProviders.size() == 2); + ComponentRepresentation ldap1 = storageProviders.get(0); + ComponentRepresentation ldap2 = storageProviders.get(1); + if (!"MyLDAPProvider1".equals(ldap1.getName())) { + ldap2 = ldap1; + ldap1 = storageProviders.get(1); + } + Assert.assertEquals("MyLDAPProvider1", ldap1.getName()); + Assert.assertEquals("ldap", ldap1.getProviderId()); + Assert.assertEquals("1", ldap1.getConfig().getFirst("priority")); + Assert.assertEquals("ldap://foo", ldap1.getConfig().getFirst(LDAPConstants.CONNECTION_URL)); - UserFederationProviderRepresentation ldap2 = fedProviders.get(1); - Assert.assertEquals("MyLDAPProvider2", ldap2.getDisplayName()); - Assert.assertEquals("ldap://bar", ldap2.getConfig().get(LDAPConstants.CONNECTION_URL)); + Assert.assertEquals("MyLDAPProvider2", ldap2.getName()); + Assert.assertEquals("ldap://bar", ldap2.getConfig().getFirst(LDAPConstants.CONNECTION_URL)); // Test federation mappers - List fedMappers1 = realmRsc.userFederation().get(ldap1.getId()).getMappers(); - Assert.assertTrue(fedMappers1.size() == 1); - UserFederationMapperRepresentation fullNameMapper = fedMappers1.iterator().next(); + List fedMappers1 = realmRsc.components().query(ldap1.getId(), LDAPStorageMapper.class.getName()); + ComponentRepresentation fullNameMapper = fedMappers1.iterator().next(); Assert.assertEquals("FullNameMapper", fullNameMapper.getName()); - Assert.assertEquals(FullNameLDAPFederationMapperFactory.PROVIDER_ID, fullNameMapper.getFederationMapperType()); - //Assert.assertEquals(ldap1.getId(), fullNameMapper.getFederationProviderId()); - Assert.assertEquals("cn", fullNameMapper.getConfig().get(FullNameLDAPFederationMapper.LDAP_FULL_NAME_ATTRIBUTE)); - - // All builtin LDAP mappers should be here - List fedMappers2 = realmRsc.userFederation().get(ldap2.getId()).getMappers(); - Assert.assertTrue(fedMappers2.size() > 3); - List allMappers = realm.getUserFederationMappers(); - Assert.assertEquals(allMappers.size(), fedMappers1.size() + fedMappers2.size()); + Assert.assertEquals(FullNameLDAPStorageMapperFactory.PROVIDER_ID, fullNameMapper.getProviderId()); + Assert.assertEquals("cn", fullNameMapper.getConfig().getFirst(FullNameLDAPStorageMapper.LDAP_FULL_NAME_ATTRIBUTE)); + ///////////////// // Assert that federation link wasn't created during import Assert.assertNull(testingClient.testing().getUserByUsernameFromFedProviderFactory(realm.getRealm(), "wburke")); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/ClientAuthSignedJWTTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/ClientAuthSignedJWTTest.java index eba2baf8d4..0e78d2e925 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/ClientAuthSignedJWTTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/ClientAuthSignedJWTTest.java @@ -158,7 +158,7 @@ public class ClientAuthSignedJWTTest extends AbstractKeycloakTest { defaultUser = UserBuilder.create() .id(KeycloakModelUtils.generateId()) - .serviceAccountId(app1.getClientId()) + //.serviceAccountId(app1.getClientId()) .username("test-user@localhost") .password("password") .build(); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/FederationTestUtils.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/FederationTestUtils.java deleted file mode 100644 index f8f83faafe..0000000000 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/FederationTestUtils.java +++ /dev/null @@ -1,287 +0,0 @@ -/* - * 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; - -import org.junit.Assert; -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.idm.query.internal.LDAPQuery; -import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore; -import org.keycloak.federation.ldap.mappers.UserAttributeLDAPFederationMapper; -import org.keycloak.federation.ldap.mappers.UserAttributeLDAPFederationMapperFactory; -import org.keycloak.federation.ldap.mappers.membership.LDAPGroupMapperMode; -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.federation.ldap.mappers.membership.role.RoleLDAPFederationMapper; -import org.keycloak.federation.ldap.mappers.membership.role.RoleLDAPFederationMapperFactory; -import org.keycloak.federation.ldap.mappers.membership.role.RoleMapperConfig; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.LDAPConstants; -import org.keycloak.models.RealmModel; -import org.keycloak.models.UserCredentialModel; -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.UserProvider; -import org.keycloak.models.utils.KeycloakModelUtils; -import org.keycloak.models.utils.UserModelDelegate; -import org.keycloak.representations.idm.CredentialRepresentation; - -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 Marek Posolda - */ -public class FederationTestUtils { - - public static UserModel addLocalUser(KeycloakSession session, RealmModel realm, String username, String email, String password) { - UserModel user = session.userStorage().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(LDAPFederationProvider 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 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(LDAPFederationProvider ldapProvider, LDAPObject ldapUser, String password) { - ldapProvider.getLdapIdentityStore().updatePassword(ldapUser, password); - - // Enable MSAD user through userAccountControls - if (ldapProvider.getLdapIdentityStore().getConfig().isActiveDirectory()) { - ldapUser.setSingleAttribute(LDAPConstants.USER_ACCOUNT_CONTROL, "512"); - ldapProvider.getLdapIdentityStore().update(ldapUser); - } - } - - public static LDAPFederationProvider getLdapProvider(KeycloakSession keycloakSession, UserFederationProviderModel ldapFedModel) { - LDAPFederationProviderFactory ldapProviderFactory = (LDAPFederationProviderFactory) keycloakSession.getKeycloakSessionFactory().getProviderFactory(UserFederationProvider.class, ldapFedModel.getProviderName()); - return ldapProviderFactory.getInstance(keycloakSession, ldapFedModel); - } - - public static void 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")); - } - - - // CRUD model mappers - - public static void addZipCodeLDAPMapper(RealmModel realm, UserFederationProviderModel providerModel) { - addUserAttributeMapper(realm, providerModel, "zipCodeMapper", "postal_code", LDAPConstants.POSTAL_CODE); - } - - public static UserFederationMapperModel 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", - UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, "false", - UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "false"); - return realm.addUserFederationMapper(mapperModel); - } - - public static void addOrUpdateRoleLDAPMappers(RealmModel realm, UserFederationProviderModel providerModel, LDAPGroupMapperMode mode) { - UserFederationMapperModel mapperModel = realm.getUserFederationMapperByName(providerModel.getId(), "realmRolesMapper"); - if (mapperModel != null) { - mapperModel.getConfig().put(RoleMapperConfig.MODE, mode.toString()); - realm.updateUserFederationMapper(mapperModel); - } else { - String baseDn = providerModel.getConfig().get(LDAPConstants.BASE_DN); - mapperModel = KeycloakModelUtils.createUserFederationMapperModel("realmRolesMapper", providerModel.getId(), RoleLDAPFederationMapperFactory.PROVIDER_ID, - RoleMapperConfig.ROLES_DN, "ou=RealmRoles," + baseDn, - RoleMapperConfig.USE_REALM_ROLES_MAPPING, "true", - RoleMapperConfig.MODE, mode.toString()); - realm.addUserFederationMapper(mapperModel); - } - - mapperModel = realm.getUserFederationMapperByName(providerModel.getId(), "financeRolesMapper"); - if (mapperModel != null) { - mapperModel.getConfig().put(RoleMapperConfig.MODE, mode.toString()); - realm.updateUserFederationMapper(mapperModel); - } else { - String baseDn = providerModel.getConfig().get(LDAPConstants.BASE_DN); - mapperModel = KeycloakModelUtils.createUserFederationMapperModel("financeRolesMapper", providerModel.getId(), RoleLDAPFederationMapperFactory.PROVIDER_ID, - RoleMapperConfig.ROLES_DN, "ou=FinanceRoles," + baseDn, - RoleMapperConfig.USE_REALM_ROLES_MAPPING, "false", - RoleMapperConfig.CLIENT_ID, "finance", - RoleMapperConfig.MODE, mode.toString()); - realm.addUserFederationMapper(mapperModel); - } - } - - public static void addOrUpdateGroupMapper(RealmModel realm, UserFederationProviderModel providerModel, LDAPGroupMapperMode mode, String descriptionAttrName, String... otherConfigOptions) { - UserFederationMapperModel mapperModel = realm.getUserFederationMapperByName(providerModel.getId(), "groupsMapper"); - if (mapperModel != null) { - mapperModel.getConfig().put(GroupMapperConfig.MODE, mode.toString()); - updateGroupMapperConfigOptions(mapperModel, otherConfigOptions); - realm.updateUserFederationMapper(mapperModel); - } else { - String baseDn = providerModel.getConfig().get(LDAPConstants.BASE_DN); - mapperModel = KeycloakModelUtils.createUserFederationMapperModel("groupsMapper", providerModel.getId(), GroupLDAPFederationMapperFactory.PROVIDER_ID, - GroupMapperConfig.GROUPS_DN, "ou=Groups," + baseDn, - GroupMapperConfig.MAPPED_GROUP_ATTRIBUTES, descriptionAttrName, - GroupMapperConfig.PRESERVE_GROUP_INHERITANCE, "true", - GroupMapperConfig.MODE, mode.toString()); - updateGroupMapperConfigOptions(mapperModel, otherConfigOptions); - realm.addUserFederationMapper(mapperModel); - } - } - - public static void updateGroupMapperConfigOptions(UserFederationMapperModel mapperModel, String... configOptions) { - for (int i=0 ; i allUsers = ldapQuery.getResultList(); - - for (LDAPObject ldapUser : allUsers) { - ldapStore.remove(ldapUser); - } - } - - public static void removeAllLDAPRoles(KeycloakSession session, RealmModel appRealm, UserFederationProviderModel ldapModel, String mapperName) { - UserFederationMapperModel mapperModel = appRealm.getUserFederationMapperByName(ldapModel.getId(), mapperName); - LDAPFederationProvider ldapProvider = FederationTestUtils.getLdapProvider(session, ldapModel); - LDAPQuery roleQuery = getRoleMapper(mapperModel, ldapProvider, appRealm).createRoleQuery(); - List ldapRoles = roleQuery.getResultList(); - for (LDAPObject ldapRole : ldapRoles) { - ldapProvider.getLdapIdentityStore().remove(ldapRole); - } - } - - public static void removeAllLDAPGroups(KeycloakSession session, RealmModel appRealm, UserFederationProviderModel ldapModel, String mapperName) { - UserFederationMapperModel mapperModel = appRealm.getUserFederationMapperByName(ldapModel.getId(), mapperName); - LDAPFederationProvider ldapProvider = FederationTestUtils.getLdapProvider(session, ldapModel); - LDAPQuery roleQuery = getGroupMapper(mapperModel, ldapProvider, appRealm).createGroupQuery(); - List ldapRoles = roleQuery.getResultList(); - for (LDAPObject ldapRole : ldapRoles) { - ldapProvider.getLdapIdentityStore().remove(ldapRole); - } - } - - public static void createLDAPRole(KeycloakSession session, RealmModel appRealm, UserFederationProviderModel ldapModel, String mapperName, String roleName) { - UserFederationMapperModel mapperModel = appRealm.getUserFederationMapperByName(ldapModel.getId(), mapperName); - LDAPFederationProvider ldapProvider = FederationTestUtils.getLdapProvider(session, ldapModel); - getRoleMapper(mapperModel, ldapProvider, appRealm).createLDAPRole(roleName); - } - - public static LDAPObject createLDAPGroup(KeycloakSession session, RealmModel appRealm, UserFederationProviderModel ldapModel, String groupName, String... additionalAttrs) { - UserFederationMapperModel mapperModel = appRealm.getUserFederationMapperByName(ldapModel.getId(), "groupsMapper"); - LDAPFederationProvider ldapProvider = FederationTestUtils.getLdapProvider(session, ldapModel); - - Map> additAttrs = new HashMap<>(); - for (int i=0 ; i ldapConfig = kerberosRule.getConfig(); - ldapModel = appRealm.addUserFederationProvider(LDAPFederationProviderFactory.PROVIDER_NAME, ldapConfig, 0, "kerberos-ldap", -1, -1, 0); + MultivaluedHashMap ldapConfig = LDAPTestUtils.toLdapConfig(kerberosRule.getConfig()); + UserStorageProviderModel model = new UserStorageProviderModel(); + model.setLastSync(0); + model.setChangedSyncPeriod(-1); + model.setFullSyncPeriod(-1); + model.setName("test-ldap"); + model.setPriority(0); + model.setProviderId(LDAPStorageProviderFactory.PROVIDER_NAME); + model.setConfig(ldapConfig); + + ldapModel = new UserStorageProviderModel(appRealm.addComponentModel(model)); } }) { @@ -119,13 +135,42 @@ public class KerberosLdapTest extends AbstractKerberosTest { super.usernamePasswordLoginTest(); } + protected void updateProviderEditMode(LDAPStorageProviderFactory.EditMode editMode) { + KeycloakRule keycloakRule = getKeycloakRule(); + + KeycloakSession session = keycloakRule.startSession(); + try { + RealmModel realm = session.realms().getRealm("test"); + ComponentModel kerberosProviderModel = realm.getComponents(realm.getId(), UserStorageProvider.class.getName()).get(0); + kerberosProviderModel.getConfig().putSingle(LDAPConstants.EDIT_MODE, editMode.toString()); + realm.updateComponent(kerberosProviderModel); + } finally { + keycloakRule.stopSession(session, true); + } + } + + @Override + protected void updateProviderEditMode(UserFederationProvider.EditMode editMode) { + switch (editMode) { + case WRITABLE: + updateProviderEditMode(LDAPStorageProviderFactory.EditMode.WRITABLE); + break; + case READ_ONLY: + updateProviderEditMode(LDAPStorageProviderFactory.EditMode.READ_ONLY); + break; + case UNSYNCED: + updateProviderEditMode(LDAPStorageProviderFactory.EditMode.UNSYNCED); + break; + } + } + @Test public void writableEditModeTest() throws Exception { KeycloakRule keycloakRule = getKeycloakRule(); AssertEvents events = getAssertEvents(); // Change editMode to WRITABLE - updateProviderEditMode(UserFederationProvider.EditMode.WRITABLE); + updateProviderEditMode(LDAPStorageProviderFactory.EditMode.WRITABLE); // Login with username/password from kerberos changePasswordPage.open(); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPExampleServlet.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPExampleServlet.java similarity index 97% rename from testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPExampleServlet.java rename to testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPExampleServlet.java index 8f6b874abd..f154b47a5d 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPExampleServlet.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPExampleServlet.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.keycloak.testsuite.federation.ldap; +package org.keycloak.testsuite.federation.storage.ldap; import org.keycloak.KeycloakSecurityContext; import org.keycloak.representations.IDToken; diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/LDAPGroupMapper2WaySyncTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPGroupMapper2WaySyncTest.java similarity index 60% rename from testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/LDAPGroupMapper2WaySyncTest.java rename to testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPGroupMapper2WaySyncTest.java index 41690d58ff..7534a93e44 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/LDAPGroupMapper2WaySyncTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPGroupMapper2WaySyncTest.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.keycloak.testsuite.federation.ldap.base; +package org.keycloak.testsuite.federation.storage.ldap; import org.junit.Assert; import org.junit.ClassRule; @@ -23,22 +23,21 @@ import org.junit.FixMethodOrder; import org.junit.Rule; import org.junit.Test; import org.junit.runners.MethodSorters; -import org.keycloak.federation.ldap.LDAPFederationProvider; -import org.keycloak.federation.ldap.LDAPFederationProviderFactory; -import org.keycloak.federation.ldap.mappers.membership.LDAPGroupMapperMode; -import org.keycloak.federation.ldap.mappers.membership.group.GroupLDAPFederationMapperFactory; -import org.keycloak.federation.ldap.mappers.membership.group.GroupMapperConfig; +import org.keycloak.common.util.MultivaluedHashMap; +import org.keycloak.component.ComponentModel; +import org.keycloak.storage.UserStorageProviderModel; +import org.keycloak.storage.ldap.LDAPStorageProvider; +import org.keycloak.storage.ldap.LDAPStorageProviderFactory; +import org.keycloak.storage.ldap.mappers.membership.LDAPGroupMapperMode; +import org.keycloak.storage.ldap.mappers.membership.group.GroupLDAPStorageMapperFactory; +import org.keycloak.storage.ldap.mappers.membership.group.GroupMapperConfig; import org.keycloak.models.GroupModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.LDAPConstants; 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.utils.KeycloakModelUtils; import org.keycloak.services.managers.RealmManager; -import org.keycloak.testsuite.federation.ldap.FederationTestUtils; +import org.keycloak.storage.user.SynchronizationResult; import org.keycloak.testsuite.rule.KeycloakRule; import org.keycloak.testsuite.rule.LDAPRule; @@ -53,7 +52,7 @@ public class LDAPGroupMapper2WaySyncTest { @ClassRule public static LDAPRule ldapRule = new LDAPRule(); - private static UserFederationProviderModel ldapModel = null; + private static ComponentModel ldapModel = null; private static String descriptionAttrName = null; @Rule @@ -61,20 +60,28 @@ public class LDAPGroupMapper2WaySyncTest { @Override public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) { - Map ldapConfig = ldapRule.getConfig(); - ldapConfig.put(LDAPConstants.SYNC_REGISTRATIONS, "true"); - ldapConfig.put(LDAPConstants.EDIT_MODE, UserFederationProvider.EditMode.WRITABLE.toString()); - ldapConfig.put(LDAPConstants.BATCH_SIZE_FOR_SYNC, "4"); // Issues with pagination on ApacheDS + MultivaluedHashMap ldapConfig = LDAPTestUtils.getLdapRuleConfig(ldapRule); + ldapConfig.putSingle(LDAPConstants.SYNC_REGISTRATIONS, "true"); + ldapConfig.putSingle(LDAPConstants.EDIT_MODE, LDAPStorageProviderFactory.EditMode.WRITABLE.toString()); + ldapConfig.putSingle(LDAPConstants.BATCH_SIZE_FOR_SYNC, "4"); // Issues with pagination on ApacheDS + UserStorageProviderModel model = new UserStorageProviderModel(); + model.setLastSync(0); + model.setChangedSyncPeriod(-1); + model.setFullSyncPeriod(-1); + model.setName("test-ldap"); + model.setPriority(0); + model.setProviderId(LDAPStorageProviderFactory.PROVIDER_NAME); + model.setConfig(ldapConfig); - ldapModel = appRealm.addUserFederationProvider(LDAPFederationProviderFactory.PROVIDER_NAME, ldapConfig, 0, "test-ldap", -1, -1, 0); - LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel); + ldapModel = appRealm.addComponentModel(model); + LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel); descriptionAttrName = ldapFedProvider.getLdapIdentityStore().getConfig().isActiveDirectory() ? "displayName" : "description"; // Add group mapper - FederationTestUtils.addOrUpdateGroupMapper(appRealm, ldapModel, LDAPGroupMapperMode.LDAP_ONLY, descriptionAttrName); + LDAPTestUtils.addOrUpdateGroupMapper(appRealm, ldapModel, LDAPGroupMapperMode.LDAP_ONLY, descriptionAttrName); // Remove all LDAP groups - FederationTestUtils.removeAllLDAPGroups(session, appRealm, ldapModel, "groupsMapper"); + LDAPTestUtils.removeAllLDAPGroups(session, appRealm, ldapModel, "groupsMapper"); // Add some groups for testing into Keycloak removeAllModelGroups(appRealm); @@ -101,16 +108,16 @@ public class LDAPGroupMapper2WaySyncTest { KeycloakSession session = keycloakRule.startSession(); try { RealmModel realm = session.realms().getRealmByName("test"); - UserFederationMapperModel mapperModel = realm.getUserFederationMapperByName(ldapModel.getId(), "groupsMapper"); - LDAPFederationProvider ldapProvider = FederationTestUtils.getLdapProvider(session, ldapModel); + ComponentModel mapperModel = LDAPTestUtils.getComponentByName(realm,ldapModel, "groupsMapper"); + LDAPStorageProvider ldapProvider = LDAPTestUtils.getLdapProvider(session, ldapModel); // Update group mapper to skip preserve inheritance and check it will pass now - FederationTestUtils.updateGroupMapperConfigOptions(mapperModel, GroupMapperConfig.PRESERVE_GROUP_INHERITANCE, "false"); - realm.updateUserFederationMapper(mapperModel); + LDAPTestUtils.updateGroupMapperConfigOptions(mapperModel, GroupMapperConfig.PRESERVE_GROUP_INHERITANCE, "false"); + realm.updateComponent(mapperModel); // Sync from Keycloak into LDAP - UserFederationSyncResult syncResult = new GroupLDAPFederationMapperFactory().create(session).syncDataFromKeycloakToFederationProvider(mapperModel, ldapProvider, session, realm); - FederationTestUtils.assertSyncEquals(syncResult, 4, 0, 0, 0); + SynchronizationResult syncResult = new GroupLDAPStorageMapperFactory().create(session, mapperModel).syncDataFromKeycloakToFederationProvider(mapperModel, ldapProvider, session, realm); + LDAPTestUtils.assertSyncEquals(syncResult, 4, 0, 0, 0); } finally { keycloakRule.stopSession(session, true); } @@ -133,12 +140,12 @@ public class LDAPGroupMapper2WaySyncTest { session = keycloakRule.startSession(); try { RealmModel realm = session.realms().getRealmByName("test"); - UserFederationMapperModel mapperModel = realm.getUserFederationMapperByName(ldapModel.getId(), "groupsMapper"); - LDAPFederationProvider ldapProvider = FederationTestUtils.getLdapProvider(session, ldapModel); + ComponentModel mapperModel = LDAPTestUtils.getComponentByName(realm,ldapModel, "groupsMapper"); + LDAPStorageProvider ldapProvider = LDAPTestUtils.getLdapProvider(session, ldapModel); // Sync from LDAP back into Keycloak - UserFederationSyncResult syncResult = new GroupLDAPFederationMapperFactory().create(session).syncDataFromFederationProviderToKeycloak(mapperModel, ldapProvider, session, realm); - FederationTestUtils.assertSyncEquals(syncResult, 4, 0, 0, 0); + SynchronizationResult syncResult = new GroupLDAPStorageMapperFactory().create(session, mapperModel).syncDataFromFederationProviderToKeycloak(mapperModel, ldapProvider, session, realm); + LDAPTestUtils.assertSyncEquals(syncResult, 4, 0, 0, 0); // Assert groups are imported to keycloak. All are at top level GroupModel kcGroup1 = KeycloakModelUtils.findGroupByPath(realm, "/group1"); @@ -165,16 +172,16 @@ public class LDAPGroupMapper2WaySyncTest { KeycloakSession session = keycloakRule.startSession(); try { RealmModel realm = session.realms().getRealmByName("test"); - UserFederationMapperModel mapperModel = realm.getUserFederationMapperByName(ldapModel.getId(), "groupsMapper"); - LDAPFederationProvider ldapProvider = FederationTestUtils.getLdapProvider(session, ldapModel); + ComponentModel mapperModel = LDAPTestUtils.getComponentByName(realm,ldapModel, "groupsMapper"); + LDAPStorageProvider ldapProvider = LDAPTestUtils.getLdapProvider(session, ldapModel); // Update group mapper to skip preserve inheritance and check it will pass now - FederationTestUtils.updateGroupMapperConfigOptions(mapperModel, GroupMapperConfig.PRESERVE_GROUP_INHERITANCE, "true"); - realm.updateUserFederationMapper(mapperModel); + LDAPTestUtils.updateGroupMapperConfigOptions(mapperModel, GroupMapperConfig.PRESERVE_GROUP_INHERITANCE, "true"); + realm.updateComponent(mapperModel); // Sync from Keycloak into LDAP - UserFederationSyncResult syncResult = new GroupLDAPFederationMapperFactory().create(session).syncDataFromKeycloakToFederationProvider(mapperModel, ldapProvider, session, realm); - FederationTestUtils.assertSyncEquals(syncResult, 4, 0, 0, 0); + SynchronizationResult syncResult = new GroupLDAPStorageMapperFactory().create(session, mapperModel).syncDataFromKeycloakToFederationProvider(mapperModel, ldapProvider, session, realm); + LDAPTestUtils.assertSyncEquals(syncResult, 4, 0, 0, 0); } finally { keycloakRule.stopSession(session, true); } @@ -197,12 +204,12 @@ public class LDAPGroupMapper2WaySyncTest { session = keycloakRule.startSession(); try { RealmModel realm = session.realms().getRealmByName("test"); - UserFederationMapperModel mapperModel = realm.getUserFederationMapperByName(ldapModel.getId(), "groupsMapper"); - LDAPFederationProvider ldapProvider = FederationTestUtils.getLdapProvider(session, ldapModel); + ComponentModel mapperModel = LDAPTestUtils.getComponentByName(realm,ldapModel, "groupsMapper"); + LDAPStorageProvider ldapProvider = LDAPTestUtils.getLdapProvider(session, ldapModel); // Sync from LDAP back into Keycloak - UserFederationSyncResult syncResult = new GroupLDAPFederationMapperFactory().create(session).syncDataFromFederationProviderToKeycloak(mapperModel, ldapProvider, session, realm); - FederationTestUtils.assertSyncEquals(syncResult, 4, 0, 0, 0); + SynchronizationResult syncResult = new GroupLDAPStorageMapperFactory().create(session, mapperModel).syncDataFromFederationProviderToKeycloak(mapperModel, ldapProvider, session, realm); + LDAPTestUtils.assertSyncEquals(syncResult, 4, 0, 0, 0); // Assert groups are imported to keycloak. All are at top level GroupModel kcGroup1 = KeycloakModelUtils.findGroupByPath(realm, "/group1"); @@ -231,22 +238,22 @@ public class LDAPGroupMapper2WaySyncTest { } } - private void testDropNonExisting(KeycloakSession session, RealmModel realm, UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider) { + private void testDropNonExisting(KeycloakSession session, RealmModel realm, ComponentModel mapperModel, LDAPStorageProvider ldapProvider) { // Put some group directly to LDAP - FederationTestUtils.createLDAPGroup(session, realm, ldapModel, "group3"); + LDAPTestUtils.createLDAPGroup(session, realm, ldapModel, "group3"); // Sync and assert our group is still in LDAP - UserFederationSyncResult syncResult = new GroupLDAPFederationMapperFactory().create(session).syncDataFromKeycloakToFederationProvider(mapperModel, ldapProvider, session, realm); - FederationTestUtils.assertSyncEquals(syncResult, 0, 4, 0, 0); - Assert.assertNotNull(FederationTestUtils.getGroupMapper(mapperModel, ldapProvider, realm).loadLDAPGroupByName("group3")); + SynchronizationResult syncResult = new GroupLDAPStorageMapperFactory().create(session, mapperModel).syncDataFromKeycloakToFederationProvider(mapperModel, ldapProvider, session, realm); + LDAPTestUtils.assertSyncEquals(syncResult, 0, 4, 0, 0); + Assert.assertNotNull(LDAPTestUtils.getGroupMapper(mapperModel, ldapProvider, realm).loadLDAPGroupByName("group3")); // Change config to drop non-existing groups - FederationTestUtils.updateGroupMapperConfigOptions(mapperModel, GroupMapperConfig.DROP_NON_EXISTING_GROUPS_DURING_SYNC, "true"); - realm.updateUserFederationMapper(mapperModel); + LDAPTestUtils.updateGroupMapperConfigOptions(mapperModel, GroupMapperConfig.DROP_NON_EXISTING_GROUPS_DURING_SYNC, "true"); + realm.updateComponent(mapperModel); // Sync and assert group removed from LDAP - syncResult = new GroupLDAPFederationMapperFactory().create(session).syncDataFromKeycloakToFederationProvider(mapperModel, ldapProvider, session, realm); - FederationTestUtils.assertSyncEquals(syncResult, 0, 4, 1, 0); - Assert.assertNull(FederationTestUtils.getGroupMapper(mapperModel, ldapProvider, realm).loadLDAPGroupByName("group3")); + syncResult = new GroupLDAPStorageMapperFactory().create(session, mapperModel).syncDataFromKeycloakToFederationProvider(mapperModel, ldapProvider, session, realm); + LDAPTestUtils.assertSyncEquals(syncResult, 0, 4, 1, 0); + Assert.assertNull(LDAPTestUtils.getGroupMapper(mapperModel, ldapProvider, realm).loadLDAPGroupByName("group3")); } } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/LDAPGroupMapperSyncTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPGroupMapperSyncTest.java similarity index 65% rename from testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/LDAPGroupMapperSyncTest.java rename to testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPGroupMapperSyncTest.java index 9a6b31baad..66321245d3 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/LDAPGroupMapperSyncTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPGroupMapperSyncTest.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.keycloak.testsuite.federation.ldap.base; +package org.keycloak.testsuite.federation.storage.ldap; import org.junit.Assert; import org.junit.Before; @@ -25,28 +25,27 @@ 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.common.util.MultivaluedHashMap; +import org.keycloak.component.ComponentModel; +import org.keycloak.storage.UserStorageProviderModel; +import org.keycloak.storage.ldap.LDAPStorageProvider; +import org.keycloak.storage.ldap.LDAPStorageProviderFactory; +import org.keycloak.storage.ldap.LDAPUtils; +import org.keycloak.storage.ldap.idm.model.LDAPObject; +import org.keycloak.storage.ldap.mappers.membership.LDAPGroupMapperMode; +import org.keycloak.storage.ldap.mappers.membership.MembershipType; +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.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.storage.user.SynchronizationResult; import org.keycloak.testsuite.rule.KeycloakRule; import org.keycloak.testsuite.rule.LDAPRule; @@ -62,31 +61,40 @@ public class LDAPGroupMapperSyncTest { private static LDAPRule ldapRule = new LDAPRule(); - private static UserFederationProviderModel ldapModel = null; + private static ComponentModel 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 ldapConfig = ldapRule.getConfig(); - ldapConfig.put(LDAPConstants.SYNC_REGISTRATIONS, "true"); - ldapConfig.put(LDAPConstants.EDIT_MODE, UserFederationProvider.EditMode.WRITABLE.toString()); + MultivaluedHashMap ldapConfig = LDAPTestUtils.getLdapRuleConfig(ldapRule); + ldapConfig.putSingle(LDAPConstants.SYNC_REGISTRATIONS, "true"); + ldapConfig.putSingle(LDAPConstants.EDIT_MODE, LDAPStorageProviderFactory.EditMode.WRITABLE.toString()); + UserStorageProviderModel model = new UserStorageProviderModel(); + model.setLastSync(0); + model.setChangedSyncPeriod(-1); + model.setFullSyncPeriod(-1); + model.setName("test-ldap"); + model.setPriority(0); + model.setProviderId(LDAPStorageProviderFactory.PROVIDER_NAME); + model.setConfig(ldapConfig); - ldapModel = appRealm.addUserFederationProvider(LDAPFederationProviderFactory.PROVIDER_NAME, ldapConfig, 0, "test-ldap", -1, -1, 0); - LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel); + ldapModel = appRealm.addComponentModel(model); + + LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel); descriptionAttrName = ldapFedProvider.getLdapIdentityStore().getConfig().isActiveDirectory() ? "displayName" : "description"; // Add group mapper - FederationTestUtils.addOrUpdateGroupMapper(appRealm, ldapModel, LDAPGroupMapperMode.LDAP_ONLY, descriptionAttrName); + LDAPTestUtils.addOrUpdateGroupMapper(appRealm, ldapModel, LDAPGroupMapperMode.LDAP_ONLY, descriptionAttrName); // Remove all LDAP groups - FederationTestUtils.removeAllLDAPGroups(session, appRealm, ldapModel, "groupsMapper"); + LDAPTestUtils.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"); + LDAPObject group1 = LDAPTestUtils.createLDAPGroup(manager.getSession(), appRealm, ldapModel, "group1", descriptionAttrName, "group1 - description"); + LDAPObject group11 = LDAPTestUtils.createLDAPGroup(manager.getSession(), appRealm, ldapModel, "group11"); + LDAPObject group12 = LDAPTestUtils.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); @@ -117,9 +125,9 @@ public class LDAPGroupMapperSyncTest { 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); + ComponentModel mapperModel = LDAPTestUtils.getComponentByName(realm,ldapModel, "groupsMapper"); + LDAPStorageProvider ldapProvider = LDAPTestUtils.getLdapProvider(session, ldapModel); + GroupLDAPStorageMapper groupMapper = LDAPTestUtils.getGroupMapper(mapperModel, ldapProvider, realm); // Add recursive group mapping to LDAP. Check that sync with preserve group inheritance will fail LDAPObject group1 = groupMapper.loadLDAPGroupByName("group1"); @@ -127,17 +135,17 @@ public class LDAPGroupMapperSyncTest { LDAPUtils.addMember(ldapProvider, MembershipType.DN, LDAPConstants.MEMBER, group12, group1, true); try { - new GroupLDAPFederationMapperFactory().create(session).syncDataFromFederationProviderToKeycloak(mapperModel, ldapProvider, session, realm); + new GroupLDAPStorageMapperFactory().create(session, mapperModel).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); + LDAPTestUtils.updateGroupMapperConfigOptions(mapperModel, GroupMapperConfig.PRESERVE_GROUP_INHERITANCE, "false"); + realm.updateComponent(mapperModel); - new GroupLDAPFederationMapperFactory().create(session).syncDataFromFederationProviderToKeycloak(mapperModel, ldapProvider, session, realm); + new GroupLDAPStorageMapperFactory().create(session, mapperModel).syncDataFromFederationProviderToKeycloak(mapperModel, ldapProvider, session, realm); // Assert groups are imported to keycloak. All are at top level GroupModel kcGroup1 = KeycloakModelUtils.findGroupByPath(realm, "/group1"); @@ -163,13 +171,13 @@ public class LDAPGroupMapperSyncTest { 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); + ComponentModel mapperModel = LDAPTestUtils.getComponentByName(realm,ldapModel, "groupsMapper"); + LDAPStorageProvider ldapProvider = LDAPTestUtils.getLdapProvider(session, ldapModel); + GroupLDAPStorageMapper groupMapper = LDAPTestUtils.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); + SynchronizationResult syncResult = new GroupLDAPStorageMapperFactory().create(session, mapperModel).syncDataFromFederationProviderToKeycloak(mapperModel, ldapProvider, session, realm); + LDAPTestUtils.assertSyncEquals(syncResult, 3, 0, 0, 0); // Assert groups are imported to keycloak including their inheritance from LDAP GroupModel kcGroup1 = KeycloakModelUtils.findGroupByPath(realm, "/group1"); @@ -194,8 +202,8 @@ public class LDAPGroupMapperSyncTest { 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); + syncResult = new GroupLDAPStorageMapperFactory().create(session, mapperModel).syncDataFromFederationProviderToKeycloak(mapperModel, ldapProvider, session, realm); + LDAPTestUtils.assertSyncEquals(syncResult, 0, 3, 0, 0); // Assert attributes changed in keycloak kcGroup1 = KeycloakModelUtils.findGroupByPath(realm, "/group1"); @@ -212,12 +220,12 @@ public class LDAPGroupMapperSyncTest { KeycloakSession session = keycloakRule.startSession(); try { RealmModel realm = session.realms().getRealmByName("test"); - UserFederationMapperModel mapperModel = realm.getUserFederationMapperByName(ldapModel.getId(), "groupsMapper"); - LDAPFederationProvider ldapProvider = FederationTestUtils.getLdapProvider(session, ldapModel); + ComponentModel mapperModel = LDAPTestUtils.getComponentByName(realm,ldapModel, "groupsMapper"); + LDAPStorageProvider ldapProvider = LDAPTestUtils.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); + SynchronizationResult syncResult = new GroupLDAPStorageMapperFactory().create(session, mapperModel).syncDataFromFederationProviderToKeycloak(mapperModel, ldapProvider, session, realm); + LDAPTestUtils.assertSyncEquals(syncResult, 3, 0, 0, 0); // Assert groups are imported to keycloak including their inheritance from LDAP GroupModel kcGroup1 = KeycloakModelUtils.findGroupByPath(realm, "/group1"); @@ -233,8 +241,8 @@ public class LDAPGroupMapperSyncTest { 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); + syncResult = new GroupLDAPStorageMapperFactory().create(session, mapperModel).syncDataFromFederationProviderToKeycloak(mapperModel, ldapProvider, session, realm); + LDAPTestUtils.assertSyncEquals(syncResult, 0, 3, 0, 0); Assert.assertNotNull(KeycloakModelUtils.findGroupByPath(realm, "/group1/group11")); Assert.assertNotNull(KeycloakModelUtils.findGroupByPath(realm, "/group1/group12")); @@ -242,11 +250,11 @@ public class LDAPGroupMapperSyncTest { 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); + LDAPTestUtils.updateGroupMapperConfigOptions(mapperModel, GroupMapperConfig.DROP_NON_EXISTING_GROUPS_DURING_SYNC, "true"); + realm.updateComponent(mapperModel); // Sync groups again from LDAP. Assert LDAP non-existing groups deleted - syncResult = new GroupLDAPFederationMapperFactory().create(session).syncDataFromFederationProviderToKeycloak(mapperModel, ldapProvider, session, realm); + syncResult = new GroupLDAPStorageMapperFactory().create(session, mapperModel).syncDataFromFederationProviderToKeycloak(mapperModel, ldapProvider, session, realm); Assert.assertEquals(3, syncResult.getUpdated()); Assert.assertTrue(syncResult.getRemoved() == 2); @@ -267,18 +275,18 @@ public class LDAPGroupMapperSyncTest { 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); + ComponentModel mapperModel = LDAPTestUtils.getComponentByName(realm,ldapModel, "groupsMapper"); + LDAPStorageProvider ldapProvider = LDAPTestUtils.getLdapProvider(session, ldapModel); + GroupLDAPStorageMapper groupMapper = LDAPTestUtils.getGroupMapper(mapperModel, ldapProvider, realm); // Update group mapper to skip preserve inheritance - FederationTestUtils.updateGroupMapperConfigOptions(mapperModel, GroupMapperConfig.PRESERVE_GROUP_INHERITANCE, "false"); - realm.updateUserFederationMapper(mapperModel); + LDAPTestUtils.updateGroupMapperConfigOptions(mapperModel, GroupMapperConfig.PRESERVE_GROUP_INHERITANCE, "false"); + realm.updateComponent(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"); + LDAPTestUtils.removeAllLDAPUsers(ldapProvider, realm); + LDAPObject johnLdap = LDAPTestUtils.addLDAPUser(ldapProvider, realm, "johnkeycloak", "John", "Doe", "john@email.org", null, "1234"); + LDAPTestUtils.updateLDAPPassword(ldapProvider, johnLdap, "Password1"); groupMapper.addGroupMappingInLDAP("group11", johnLdap); // Assert groups not yet imported to Keycloak DB diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/LDAPGroupMapperTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPGroupMapperTest.java similarity index 66% rename from testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/LDAPGroupMapperTest.java rename to testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPGroupMapperTest.java index cdbd6957e3..c15a304e8a 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/LDAPGroupMapperTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPGroupMapperTest.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.keycloak.testsuite.federation.ldap.base; +package org.keycloak.testsuite.federation.storage.ldap; import org.junit.Assert; import org.junit.ClassRule; @@ -24,29 +24,28 @@ import org.junit.Test; import org.junit.rules.RuleChain; import org.junit.rules.TestRule; import org.junit.runners.MethodSorters; -import org.keycloak.federation.ldap.LDAPConfig; -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.LDAPDn; -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.common.util.MultivaluedHashMap; +import org.keycloak.component.ComponentModel; +import org.keycloak.storage.UserStorageProviderModel; +import org.keycloak.storage.ldap.LDAPConfig; +import org.keycloak.storage.ldap.LDAPStorageProvider; +import org.keycloak.storage.ldap.LDAPStorageProviderFactory; +import org.keycloak.storage.ldap.LDAPUtils; +import org.keycloak.storage.ldap.idm.model.LDAPDn; +import org.keycloak.storage.ldap.idm.model.LDAPObject; +import org.keycloak.storage.ldap.mappers.membership.LDAPGroupMapperMode; +import org.keycloak.storage.ldap.mappers.membership.MembershipType; +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.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.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; @@ -62,57 +61,65 @@ public class LDAPGroupMapperTest { private static LDAPRule ldapRule = new LDAPRule(); - private static UserFederationProviderModel ldapModel = null; + private static ComponentModel 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) { - FederationTestUtils.addLocalUser(manager.getSession(), appRealm, "mary", "mary@test.com", "password-app"); - FederationTestUtils.addLocalUser(manager.getSession(), appRealm, "john", "john@test.com", "password-app"); + LDAPTestUtils.addLocalUser(manager.getSession(), appRealm, "mary", "mary@test.com", "password-app"); + LDAPTestUtils.addLocalUser(manager.getSession(), appRealm, "john", "john@test.com", "password-app"); - Map ldapConfig = ldapRule.getConfig(); - ldapConfig.put(LDAPConstants.SYNC_REGISTRATIONS, "true"); - ldapConfig.put(LDAPConstants.EDIT_MODE, UserFederationProvider.EditMode.WRITABLE.toString()); + MultivaluedHashMap ldapConfig = LDAPTestUtils.getLdapRuleConfig(ldapRule); + ldapConfig.putSingle(LDAPConstants.SYNC_REGISTRATIONS, "true"); + ldapConfig.putSingle(LDAPConstants.EDIT_MODE, LDAPStorageProviderFactory.EditMode.WRITABLE.toString()); + UserStorageProviderModel model = new UserStorageProviderModel(); + model.setLastSync(0); + model.setChangedSyncPeriod(-1); + model.setFullSyncPeriod(-1); + model.setName("test-ldap"); + model.setPriority(0); + model.setProviderId(LDAPStorageProviderFactory.PROVIDER_NAME); + model.setConfig(ldapConfig); - ldapModel = appRealm.addUserFederationProvider(LDAPFederationProviderFactory.PROVIDER_NAME, ldapConfig, 0, "test-ldap", -1, -1, 0); - LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel); + ldapModel = appRealm.addComponentModel(model); + LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel); descriptionAttrName = ldapFedProvider.getLdapIdentityStore().getConfig().isActiveDirectory() ? "displayName" : "description"; // Add group mapper - FederationTestUtils.addOrUpdateGroupMapper(appRealm, ldapModel, LDAPGroupMapperMode.LDAP_ONLY, descriptionAttrName); + LDAPTestUtils.addOrUpdateGroupMapper(appRealm, ldapModel, LDAPGroupMapperMode.LDAP_ONLY, descriptionAttrName); // Remove all LDAP groups - FederationTestUtils.removeAllLDAPGroups(session, appRealm, ldapModel, "groupsMapper"); + LDAPTestUtils.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"); + LDAPObject group1 = LDAPTestUtils.createLDAPGroup(manager.getSession(), appRealm, ldapModel, "group1", descriptionAttrName, "group1 - description"); + LDAPObject group11 = LDAPTestUtils.createLDAPGroup(manager.getSession(), appRealm, ldapModel, "group11"); + LDAPObject group12 = LDAPTestUtils.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); // Sync LDAP groups to Keycloak DB - UserFederationMapperModel mapperModel = appRealm.getUserFederationMapperByName(ldapModel.getId(), "groupsMapper"); - new GroupLDAPFederationMapperFactory().create(session).syncDataFromFederationProviderToKeycloak(mapperModel, ldapFedProvider, session, appRealm); + ComponentModel mapperModel = LDAPTestUtils.getComponentByName(appRealm,ldapModel, "groupsMapper"); + new GroupLDAPStorageMapperFactory().create(session, mapperModel).syncDataFromFederationProviderToKeycloak(mapperModel, ldapFedProvider, session, appRealm); // Delete all LDAP users - FederationTestUtils.removeAllLDAPUsers(ldapFedProvider, appRealm); + LDAPTestUtils.removeAllLDAPUsers(ldapFedProvider, appRealm); // Add some LDAP users for testing - LDAPObject john = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "johnkeycloak", "John", "Doe", "john@email.org", null, "1234"); - FederationTestUtils.updateLDAPPassword(ldapFedProvider, john, "Password1"); + LDAPObject john = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "johnkeycloak", "John", "Doe", "john@email.org", null, "1234"); + LDAPTestUtils.updateLDAPPassword(ldapFedProvider, john, "Password1"); - LDAPObject mary = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "marykeycloak", "Mary", "Kelly", "mary@email.org", null, "5678"); - FederationTestUtils.updateLDAPPassword(ldapFedProvider, mary, "Password1"); + LDAPObject mary = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "marykeycloak", "Mary", "Kelly", "mary@email.org", null, "5678"); + LDAPTestUtils.updateLDAPPassword(ldapFedProvider, mary, "Password1"); - LDAPObject rob = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "robkeycloak", "Rob", "Brown", "rob@email.org", null, "8910"); - FederationTestUtils.updateLDAPPassword(ldapFedProvider, rob, "Password1"); + LDAPObject rob = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "robkeycloak", "Rob", "Brown", "rob@email.org", null, "8910"); + LDAPTestUtils.updateLDAPPassword(ldapFedProvider, rob, "Password1"); - LDAPObject james = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "jameskeycloak", "James", "Brown", "james@email.org", null, "8910"); - FederationTestUtils.updateLDAPPassword(ldapFedProvider, james, "Password1"); + LDAPObject james = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "jameskeycloak", "James", "Brown", "james@email.org", null, "8910"); + LDAPTestUtils.updateLDAPPassword(ldapFedProvider, james, "Password1"); } }); @@ -128,9 +135,9 @@ public class LDAPGroupMapperTest { try { RealmModel appRealm = session.realms().getRealmByName("test"); - UserFederationMapperModel mapperModel = appRealm.getUserFederationMapperByName(ldapModel.getId(), "groupsMapper"); - FederationTestUtils.updateGroupMapperConfigOptions(mapperModel, GroupMapperConfig.MODE, LDAPGroupMapperMode.LDAP_ONLY.toString()); - appRealm.updateUserFederationMapper(mapperModel); + ComponentModel mapperModel = LDAPTestUtils.getComponentByName(appRealm,ldapModel, "groupsMapper"); + LDAPTestUtils.updateGroupMapperConfigOptions(mapperModel, GroupMapperConfig.MODE, LDAPGroupMapperMode.LDAP_ONLY.toString()); + appRealm.updateComponent(mapperModel); UserModel john = session.users().getUserByUsername("johnkeycloak", appRealm); UserModel mary = session.users().getUserByUsername("marykeycloak", appRealm); @@ -152,7 +159,7 @@ public class LDAPGroupMapperTest { // 2 - Check that group mappings are not in local Keycloak DB (They are in LDAP). - UserModel johnDb = session.userStorage().getUserByUsername("johnkeycloak", appRealm); + UserModel johnDb = session.userLocalStorage().getUserByUsername("johnkeycloak", appRealm); Set johnDbGroups = johnDb.getGroups(); Assert.assertEquals(0, johnDbGroups.size()); @@ -198,9 +205,9 @@ public class LDAPGroupMapperTest { System.out.println("starting test02_readOnlyGroupMappings"); RealmModel appRealm = session.realms().getRealmByName("test"); - UserFederationMapperModel mapperModel = appRealm.getUserFederationMapperByName(ldapModel.getId(), "groupsMapper"); - FederationTestUtils.updateGroupMapperConfigOptions(mapperModel, GroupMapperConfig.MODE, LDAPGroupMapperMode.READ_ONLY.toString()); - appRealm.updateUserFederationMapper(mapperModel); + ComponentModel mapperModel = LDAPTestUtils.getComponentByName(appRealm,ldapModel, "groupsMapper"); + LDAPTestUtils.updateGroupMapperConfigOptions(mapperModel, GroupMapperConfig.MODE, LDAPGroupMapperMode.READ_ONLY.toString()); + appRealm.updateComponent(mapperModel); UserModel mary = session.users().getUserByUsername("marykeycloak", appRealm); @@ -209,8 +216,8 @@ public class LDAPGroupMapperTest { GroupModel group12 = KeycloakModelUtils.findGroupByPath(appRealm, "/group1/group12"); // Add some group mappings directly into LDAP - LDAPFederationProvider ldapProvider = FederationTestUtils.getLdapProvider(session, ldapModel); - GroupLDAPFederationMapper groupMapper = FederationTestUtils.getGroupMapper(mapperModel, ldapProvider, appRealm); + LDAPStorageProvider ldapProvider = LDAPTestUtils.getLdapProvider(session, ldapModel); + GroupLDAPStorageMapper groupMapper = LDAPTestUtils.getGroupMapper(mapperModel, ldapProvider, appRealm); LDAPObject maryLdap = ldapProvider.loadLDAPUserByUsername(appRealm, "marykeycloak"); groupMapper.addGroupMappingInLDAP("group1", maryLdap); @@ -228,7 +235,7 @@ public class LDAPGroupMapperTest { // Assert that access through DB will have just DB mapped groups System.out.println("******"); - UserModel maryDB = session.userStorage().getUserByUsername("marykeycloak", appRealm); + UserModel maryDB = session.userLocalStorage().getUserByUsername("marykeycloak", appRealm); Set maryDBGroups = maryDB.getGroups(); Assert.assertFalse(maryDBGroups.contains(group1)); Assert.assertFalse(maryDBGroups.contains(group11)); @@ -266,13 +273,13 @@ public class LDAPGroupMapperTest { try { RealmModel appRealm = session.realms().getRealmByName("test"); - UserFederationMapperModel mapperModel = appRealm.getUserFederationMapperByName(ldapModel.getId(), "groupsMapper"); - FederationTestUtils.updateGroupMapperConfigOptions(mapperModel, GroupMapperConfig.MODE, LDAPGroupMapperMode.IMPORT.toString()); - appRealm.updateUserFederationMapper(mapperModel); + ComponentModel mapperModel = LDAPTestUtils.getComponentByName(appRealm,ldapModel, "groupsMapper"); + LDAPTestUtils.updateGroupMapperConfigOptions(mapperModel, GroupMapperConfig.MODE, LDAPGroupMapperMode.IMPORT.toString()); + appRealm.updateComponent(mapperModel); // Add some group mappings directly in LDAP - LDAPFederationProvider ldapProvider = FederationTestUtils.getLdapProvider(session, ldapModel); - GroupLDAPFederationMapper groupMapper = FederationTestUtils.getGroupMapper(mapperModel, ldapProvider, appRealm); + LDAPStorageProvider ldapProvider = LDAPTestUtils.getLdapProvider(session, ldapModel); + GroupLDAPStorageMapper groupMapper = LDAPTestUtils.getGroupMapper(mapperModel, ldapProvider, appRealm); LDAPObject robLdap = ldapProvider.loadLDAPUserByUsername(appRealm, "robkeycloak"); groupMapper.addGroupMappingInLDAP("group11", robLdap); @@ -314,21 +321,21 @@ public class LDAPGroupMapperTest { KeycloakSession session = keycloakRule.startSession(); try { // Ignoring this test on ActiveDirectory as it's not allowed to have LDAP group referencing nonexistent member. KEYCLOAK-2682 was related to OpenLDAP TODO: Better solution than programmatic... - LDAPConfig config = FederationTestUtils.getLdapProvider(session, ldapModel).getLdapIdentityStore().getConfig(); + LDAPConfig config = LDAPTestUtils.getLdapProvider(session, ldapModel).getLdapIdentityStore().getConfig(); if (config.isActiveDirectory()) { return; } RealmModel appRealm = session.realms().getRealmByName("test"); - UserFederationMapperModel mapperModel = appRealm.getUserFederationMapperByName(ldapModel.getId(), "groupsMapper"); - FederationTestUtils.updateGroupMapperConfigOptions(mapperModel, GroupMapperConfig.MODE, LDAPGroupMapperMode.LDAP_ONLY.toString()); - appRealm.updateUserFederationMapper(mapperModel); + ComponentModel mapperModel = LDAPTestUtils.getComponentByName(appRealm,ldapModel, "groupsMapper"); + LDAPTestUtils.updateGroupMapperConfigOptions(mapperModel, GroupMapperConfig.MODE, LDAPGroupMapperMode.LDAP_ONLY.toString()); + appRealm.updateComponent(mapperModel); // 1 - Add some group to LDAP for testing - LDAPFederationProvider ldapProvider = FederationTestUtils.getLdapProvider(session, ldapModel); - GroupLDAPFederationMapper groupMapper = FederationTestUtils.getGroupMapper(mapperModel, ldapProvider, appRealm); - LDAPObject group2 = FederationTestUtils.createLDAPGroup(session, appRealm, ldapModel, "group2", descriptionAttrName, "group2 - description"); + LDAPStorageProvider ldapProvider = LDAPTestUtils.getLdapProvider(session, ldapModel); + GroupLDAPStorageMapper groupMapper = LDAPTestUtils.getGroupMapper(mapperModel, ldapProvider, appRealm); + LDAPObject group2 = LDAPTestUtils.createLDAPGroup(session, appRealm, ldapModel, "group2", descriptionAttrName, "group2 - description"); // 2 - Add one existing user rob to LDAP group LDAPObject jamesLdap = ldapProvider.loadLDAPUserByUsername(appRealm, "jameskeycloak"); @@ -354,7 +361,7 @@ public class LDAPGroupMapperTest { } } - private void deleteGroupMappingsInLDAP(GroupLDAPFederationMapper groupMapper, LDAPObject ldapUser, String groupName) { + private void deleteGroupMappingsInLDAP(GroupLDAPStorageMapper groupMapper, LDAPObject ldapUser, String groupName) { LDAPObject ldapGroup = groupMapper.loadLDAPGroupByName(groupName); groupMapper.deleteGroupMappingInLDAP(ldapUser, ldapGroup); } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPLegacyImportTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPLegacyImportTest.java new file mode 100755 index 0000000000..fc34c86007 --- /dev/null +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPLegacyImportTest.java @@ -0,0 +1,184 @@ +/* + * 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.junit.ClassRule; +import org.junit.FixMethodOrder; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.RuleChain; +import org.junit.rules.TestRule; +import org.junit.runners.MethodSorters; +import org.keycloak.OAuth2Constants; +import org.keycloak.common.util.MultivaluedHashMap; +import org.keycloak.component.ComponentModel; +import org.keycloak.credential.CredentialModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.LDAPConstants; +import org.keycloak.models.ModelException; +import org.keycloak.models.ModelReadOnlyException; +import org.keycloak.models.RealmModel; +import org.keycloak.models.RoleModel; +import org.keycloak.models.UserCredentialModel; +import org.keycloak.models.UserModel; +import org.keycloak.models.utils.KeycloakModelUtils; +import org.keycloak.models.utils.RepresentationToModel; +import org.keycloak.representations.AccessToken; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.services.managers.RealmManager; +import org.keycloak.storage.UserStorageProvider; +import org.keycloak.storage.UserStorageProviderModel; +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.testsuite.OAuthClient; +import org.keycloak.testsuite.pages.AccountPasswordPage; +import org.keycloak.testsuite.pages.AccountUpdateProfilePage; +import org.keycloak.testsuite.pages.AppPage; +import org.keycloak.testsuite.pages.LoginPage; +import org.keycloak.testsuite.pages.RegisterPage; +import org.keycloak.testsuite.rule.KeycloakRule; +import org.keycloak.testsuite.rule.LDAPRule; +import org.keycloak.testsuite.rule.WebResource; +import org.keycloak.testsuite.rule.WebRule; +import org.keycloak.util.JsonSerialization; +import org.openqa.selenium.WebDriver; + +import java.io.IOException; +import java.util.List; + +import static org.junit.Assert.assertEquals; + +/** + * Tests that legacy UserFederationProvider json export is converted to ComponentModel + * + * @author Marek Posolda + */ +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class LDAPLegacyImportTest { + + private static LDAPRule ldapRule = new LDAPRule(); + + private static ComponentModel ldapModel = null; + + + private static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() { + + @Override + public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) { + LDAPTestUtils.addLocalUser(manager.getSession(), appRealm, "marykeycloak", "mary@test.com", "password-app"); + + RealmRepresentation imported = null; + try { + imported = JsonSerialization.readValue(getClass().getResourceAsStream("/ldap/fed-provider-export.json"), RealmRepresentation.class); + } catch (IOException e) { + throw new RuntimeException(e); + } + RepresentationToModel.importUserFederationProvidersAndMappers(imported, appRealm); + ldapModel = appRealm.getComponents(appRealm.getId(), UserStorageProvider.class.getName()).get(0); + // Delete all LDAP users and add some new for testing + LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel); + LDAPTestUtils.removeAllLDAPUsers(ldapFedProvider, appRealm); + + LDAPObject john = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "johnkeycloak", "John", "Doe", "john@email.org", null, "1234"); + LDAPTestUtils.updateLDAPPassword(ldapFedProvider, john, "Password1"); + + LDAPObject existing = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "existing", "Existing", "Foo", "existing@email.org", null, "5678"); + + appRealm.getClientByClientId("test-app").setDirectAccessGrantsEnabled(true); + } + }); + + @ClassRule + public static TestRule chain = RuleChain + .outerRule(ldapRule) + .around(keycloakRule); + + @Rule + public WebRule webRule = new WebRule(this); + + @WebResource + protected OAuthClient oauth; + + @WebResource + protected WebDriver driver; + + @WebResource + protected AppPage appPage; + + @WebResource + protected RegisterPage registerPage; + + @WebResource + protected LoginPage loginPage; + + @WebResource + protected AccountUpdateProfilePage profilePage; + + @WebResource + protected AccountPasswordPage changePasswordPage; + + //@Test + public void runit() throws Exception { + Thread.sleep(10000000); + + } + + private void loginSuccessAndLogout(String username, String password) { + loginPage.open(); + loginPage.login(username, password); + Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType()); + Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE)); + oauth.openLogout(); + } + + + @Test + public void loginClassic() { + loginPage.open(); + loginPage.login("marykeycloak", "password-app"); + + Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType()); + Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE)); + + } + + @Test + public void loginLdap() { + loginPage.open(); + loginPage.login("johnkeycloak", "Password1"); + + Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType()); + Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE)); + + profilePage.open(); + Assert.assertEquals("John", profilePage.getFirstName()); + Assert.assertEquals("Doe", profilePage.getLastName()); + Assert.assertEquals("john@email.org", profilePage.getEmail()); + } + + +} diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/LDAPMultipleAttributesTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPMultipleAttributesTest.java similarity index 81% rename from testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/LDAPMultipleAttributesTest.java rename to testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPMultipleAttributesTest.java index 7db180afc4..c054e08cc0 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/LDAPMultipleAttributesTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPMultipleAttributesTest.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.keycloak.testsuite.federation.ldap.base; +package org.keycloak.testsuite.federation.storage.ldap; import org.junit.Assert; import org.junit.ClassRule; @@ -26,9 +26,8 @@ import org.junit.rules.RuleChain; import org.junit.rules.TestRule; import org.junit.runners.MethodSorters; import org.keycloak.OAuth2Constants; -import org.keycloak.federation.ldap.LDAPFederationProvider; -import org.keycloak.federation.ldap.LDAPFederationProviderFactory; -import org.keycloak.federation.ldap.idm.model.LDAPObject; +import org.keycloak.common.util.MultivaluedHashMap; +import org.keycloak.component.ComponentModel; import org.keycloak.models.ClientModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.LDAPConstants; @@ -40,9 +39,11 @@ import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.protocol.oidc.OIDCLoginProtocolService; import org.keycloak.protocol.oidc.mappers.UserAttributeMapper; import org.keycloak.services.managers.RealmManager; +import org.keycloak.storage.UserStorageProviderModel; +import org.keycloak.storage.ldap.LDAPStorageProvider; +import org.keycloak.storage.ldap.LDAPStorageProviderFactory; +import org.keycloak.storage.ldap.idm.model.LDAPObject; import org.keycloak.testsuite.OAuthClient; -import org.keycloak.testsuite.federation.ldap.FederationTestUtils; -import org.keycloak.testsuite.federation.ldap.LDAPExampleServlet; import org.keycloak.testsuite.pages.LoginPage; import org.keycloak.testsuite.rule.KeycloakRule; import org.keycloak.testsuite.rule.LDAPRule; @@ -69,31 +70,40 @@ public class LDAPMultipleAttributesTest { private static LDAPRule ldapRule = new LDAPRule(); - private static UserFederationProviderModel ldapModel = null; + private static ComponentModel ldapModel = null; private static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() { @Override public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) { - Map ldapConfig = ldapRule.getConfig(); - ldapConfig.put(LDAPConstants.EDIT_MODE, UserFederationProvider.EditMode.WRITABLE.toString()); + MultivaluedHashMap ldapConfig = LDAPTestUtils.getLdapRuleConfig(ldapRule); + ldapConfig.putSingle(LDAPConstants.EDIT_MODE, LDAPStorageProviderFactory.EditMode.WRITABLE.toString()); - ldapModel = appRealm.addUserFederationProvider(LDAPFederationProviderFactory.PROVIDER_NAME, ldapConfig, 0, "test-ldap", -1, -1, 0); - FederationTestUtils.addZipCodeLDAPMapper(appRealm, ldapModel); - FederationTestUtils.addUserAttributeMapper(appRealm, ldapModel, "streetMapper", "street", LDAPConstants.STREET); + UserStorageProviderModel model = new UserStorageProviderModel(); + model.setLastSync(0); + model.setChangedSyncPeriod(-1); + model.setFullSyncPeriod(-1); + model.setName("test-ldap"); + model.setPriority(0); + model.setProviderId(LDAPStorageProviderFactory.PROVIDER_NAME); + model.setConfig(ldapConfig); + ldapModel = appRealm.addComponentModel(model); + + LDAPTestUtils.addZipCodeLDAPMapper(appRealm, ldapModel); + LDAPTestUtils.addUserAttributeMapper(appRealm, ldapModel, "streetMapper", "street", LDAPConstants.STREET); // Remove current users and add default users - LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel); - FederationTestUtils.removeAllLDAPUsers(ldapFedProvider, appRealm); + LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel); + LDAPTestUtils.removeAllLDAPUsers(ldapFedProvider, appRealm); - LDAPObject james = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "jbrown", "James", "Brown", "jbrown@keycloak.org", null, "88441"); - FederationTestUtils.updateLDAPPassword(ldapFedProvider, james, "Password1"); + LDAPObject james = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "jbrown", "James", "Brown", "jbrown@keycloak.org", null, "88441"); + LDAPTestUtils.updateLDAPPassword(ldapFedProvider, james, "Password1"); // User for testing duplicating surname and postalCode - LDAPObject bruce = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "bwilson", "Bruce", "Wilson", "bwilson@keycloak.org", "Elm 5", "88441", "77332"); + LDAPObject bruce = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "bwilson", "Bruce", "Wilson", "bwilson@keycloak.org", "Elm 5", "88441", "77332"); bruce.setAttribute("sn", new LinkedHashSet<>(Arrays.asList("Wilson", "Schneider"))); ldapFedProvider.getLdapIdentityStore().update(bruce); - FederationTestUtils.updateLDAPPassword(ldapFedProvider, bruce, "Password1"); + LDAPTestUtils.updateLDAPPassword(ldapFedProvider, bruce, "Password1"); // Create ldap-portal client ClientModel ldapClient = KeycloakModelUtils.createClient(appRealm, "ldap-portal"); @@ -137,7 +147,7 @@ public class LDAPMultipleAttributesTest { try { RealmModel appRealm = session.realms().getRealmByName("test"); - FederationTestUtils.assertUserImported(session.users(), appRealm, "jbrown", "James", "Brown", "jbrown@keycloak.org", "88441"); + LDAPTestUtils.assertUserImported(session.users(), appRealm, "jbrown", "James", "Brown", "jbrown@keycloak.org", "88441"); UserModel user = session.users().getUserByUsername("bwilson", appRealm); Assert.assertEquals("bwilson@keycloak.org", user.getEmail()); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/FederationProvidersIntegrationTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPProvidersIntegrationTest.java similarity index 67% rename from testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/FederationProvidersIntegrationTest.java rename to testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPProvidersIntegrationTest.java index 2809b97bc5..13735e6f39 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/FederationProvidersIntegrationTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPProvidersIntegrationTest.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.keycloak.testsuite.federation.ldap.base; +package org.keycloak.testsuite.federation.storage.ldap; import org.junit.Assert; import org.junit.ClassRule; @@ -26,16 +26,15 @@ import org.junit.rules.RuleChain; import org.junit.rules.TestRule; import org.junit.runners.MethodSorters; import org.keycloak.OAuth2Constants; +import org.keycloak.common.util.MultivaluedHashMap; +import org.keycloak.component.ComponentModel; import org.keycloak.credential.CredentialModel; -import org.keycloak.federation.ldap.LDAPConfig; -import org.keycloak.federation.ldap.LDAPFederationProvider; -import org.keycloak.federation.ldap.LDAPFederationProviderFactory; -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.HardcodedLDAPRoleMapper; -import org.keycloak.federation.ldap.mappers.HardcodedLDAPRoleMapperFactory; -import org.keycloak.federation.ldap.mappers.UserAttributeLDAPFederationMapper; +import org.keycloak.storage.UserStorageProvider; +import org.keycloak.storage.UserStorageProviderModel; +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.models.KeycloakSession; import org.keycloak.models.LDAPConstants; import org.keycloak.models.ModelException; @@ -43,15 +42,17 @@ import org.keycloak.models.ModelReadOnlyException; import org.keycloak.models.RealmModel; import org.keycloak.models.RoleModel; import org.keycloak.models.UserCredentialModel; -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.representations.AccessToken; import org.keycloak.services.managers.RealmManager; +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.testsuite.OAuthClient; -import org.keycloak.testsuite.federation.ldap.FederationTestUtils; import org.keycloak.testsuite.pages.AccountPasswordPage; import org.keycloak.testsuite.pages.AccountUpdateProfilePage; import org.keycloak.testsuite.pages.AppPage; @@ -72,33 +73,42 @@ import static org.junit.Assert.assertEquals; * @author Marek Posolda */ @FixMethodOrder(MethodSorters.NAME_ASCENDING) -public class FederationProvidersIntegrationTest { +public class LDAPProvidersIntegrationTest { private static LDAPRule ldapRule = new LDAPRule(); - private static UserFederationProviderModel ldapModel = null; + private static ComponentModel ldapModel = null; + private static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() { @Override public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) { - FederationTestUtils.addLocalUser(manager.getSession(), appRealm, "marykeycloak", "mary@test.com", "password-app"); + LDAPTestUtils.addLocalUser(manager.getSession(), appRealm, "marykeycloak", "mary@test.com", "password-app"); - Map ldapConfig = ldapRule.getConfig(); - ldapConfig.put(LDAPConstants.SYNC_REGISTRATIONS, "true"); - ldapConfig.put(LDAPConstants.EDIT_MODE, UserFederationProvider.EditMode.WRITABLE.toString()); + MultivaluedHashMap ldapConfig = LDAPTestUtils.getLdapRuleConfig(ldapRule); + ldapConfig.putSingle(LDAPConstants.SYNC_REGISTRATIONS, "true"); + ldapConfig.putSingle(LDAPConstants.EDIT_MODE, LDAPStorageProviderFactory.EditMode.WRITABLE.toString()); + UserStorageProviderModel model = new UserStorageProviderModel(); + model.setLastSync(0); + model.setChangedSyncPeriod(-1); + model.setFullSyncPeriod(-1); + model.setName("test-ldap"); + model.setPriority(0); + model.setProviderId(LDAPStorageProviderFactory.PROVIDER_NAME); + model.setConfig(ldapConfig); - ldapModel = appRealm.addUserFederationProvider(LDAPFederationProviderFactory.PROVIDER_NAME, ldapConfig, 0, "test-ldap", -1, -1, 0); - FederationTestUtils.addZipCodeLDAPMapper(appRealm, ldapModel); + ldapModel = appRealm.addComponentModel(model); + LDAPTestUtils.addZipCodeLDAPMapper(appRealm, ldapModel); // Delete all LDAP users and add some new for testing - LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel); - FederationTestUtils.removeAllLDAPUsers(ldapFedProvider, appRealm); + LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel); + LDAPTestUtils.removeAllLDAPUsers(ldapFedProvider, appRealm); - LDAPObject john = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "johnkeycloak", "John", "Doe", "john@email.org", null, "1234"); - FederationTestUtils.updateLDAPPassword(ldapFedProvider, john, "Password1"); + LDAPObject john = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "johnkeycloak", "John", "Doe", "john@email.org", null, "1234"); + LDAPTestUtils.updateLDAPPassword(ldapFedProvider, john, "Password1"); - LDAPObject existing = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "existing", "Existing", "Foo", "existing@email.org", null, "5678"); + LDAPObject existing = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "existing", "Existing", "Foo", "existing@email.org", null, "5678"); appRealm.getClientByClientId("test-app").setDirectAccessGrantsEnabled(true); } @@ -147,11 +157,11 @@ public class FederationProvidersIntegrationTest { try { RealmManager manager = new RealmManager(session); RealmModel appRealm = manager.getRealm("test"); - LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel); - LDAPObject jbrown2 = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "JBrown2", "John", "Brown2", "jbrown2@email.org", null, "1234"); - FederationTestUtils.updateLDAPPassword(ldapFedProvider, jbrown2, "Password1"); - LDAPObject jbrown3 = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "jbrown3", "John", "Brown3", "JBrown3@email.org", null, "1234"); - FederationTestUtils.updateLDAPPassword(ldapFedProvider, jbrown3, "Password1"); + LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel); + LDAPObject jbrown2 = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "JBrown2", "John", "Brown2", "jbrown2@email.org", null, "1234"); + LDAPTestUtils.updateLDAPPassword(ldapFedProvider, jbrown2, "Password1"); + LDAPObject jbrown3 = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "jbrown3", "John", "Brown3", "JBrown3@email.org", null, "1234"); + LDAPTestUtils.updateLDAPPassword(ldapFedProvider, jbrown3, "Password1"); } finally { keycloakRule.stopSession(session, true); } @@ -181,11 +191,11 @@ public class FederationProvidersIntegrationTest { try { RealmManager manager = new RealmManager(session); RealmModel appRealm = manager.getRealm("test"); - LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel); - LDAPObject jbrown4 = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "JBrown4", "John", "Brown4", "jbrown4@email.org", null, "1234"); - FederationTestUtils.updateLDAPPassword(ldapFedProvider, jbrown4, "Password1"); - LDAPObject jbrown5 = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "jbrown5", "John", "Brown5", "JBrown5@Email.org", null, "1234"); - FederationTestUtils.updateLDAPPassword(ldapFedProvider, jbrown5, "Password1"); + LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel); + LDAPObject jbrown4 = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "JBrown4", "John", "Brown4", "jbrown4@email.org", null, "1234"); + LDAPTestUtils.updateLDAPPassword(ldapFedProvider, jbrown4, "Password1"); + LDAPObject jbrown5 = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "jbrown5", "John", "Brown5", "JBrown5@Email.org", null, "1234"); + LDAPTestUtils.updateLDAPPassword(ldapFedProvider, jbrown5, "Password1"); } finally { keycloakRule.stopSession(session, true); } @@ -222,8 +232,8 @@ public class FederationProvidersIntegrationTest { RealmManager manager = new RealmManager(session); RealmModel appRealm = manager.getRealm("test"); - appRealm.removeUserFederationProvider(ldapModel); - Assert.assertEquals(0, appRealm.getUserFederationProviders().size()); + appRealm.removeComponent(ldapModel); + Assert.assertEquals(0, appRealm.getComponents(appRealm.getId(), UserStorageProvider.class.getName()).size()); } finally { keycloakRule.stopSession(session, true); } @@ -240,8 +250,9 @@ public class FederationProvidersIntegrationTest { RealmManager manager = new RealmManager(session); RealmModel appRealm = manager.getRealm("test"); - ldapModel = appRealm.addUserFederationProvider(ldapModel.getProviderName(), ldapModel.getConfig(), ldapModel.getPriority(), ldapModel.getDisplayName(), -1, -1, 0); - FederationTestUtils.addZipCodeLDAPMapper(appRealm, ldapModel); + ldapModel.setId(null); + ldapModel = appRealm.addComponentModel(ldapModel); + LDAPTestUtils.addZipCodeLDAPMapper(appRealm, ldapModel); } finally { keycloakRule.stopSession(session, true); } @@ -369,13 +380,13 @@ public class FederationProvidersIntegrationTest { try { RealmModel appRealm = new RealmManager(session).getRealmByName("test"); - LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel); - LDAPObject johnZip = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "johnzip", "John", "Zip", "johnzip@email.org", null, "12398"); + LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel); + LDAPObject johnZip = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "johnzip", "John", "Zip", "johnzip@email.org", null, "12398"); // Remove default zipcode mapper and add the mapper for "POstalCode" to test case sensitivity - UserFederationMapperModel currentZipMapper = appRealm.getUserFederationMapperByName(ldapModel.getId(), "zipCodeMapper"); - appRealm.removeUserFederationMapper(currentZipMapper); - FederationTestUtils.addUserAttributeMapper(appRealm, ldapModel, "zipCodeMapper-cs", "postal_code", "POstalCode"); + ComponentModel currentZipMapper = LDAPTestUtils.getComponentByName(appRealm, ldapModel, "zipCodeMapper"); + appRealm.removeComponent(currentZipMapper); + LDAPTestUtils.addUserAttributeMapper(appRealm, ldapModel, "zipCodeMapper-cs", "postal_code", "POstalCode"); // Fetch user from LDAP and check that postalCode is filled UserModel user = session.users().getUserByUsername("johnzip", appRealm); @@ -394,7 +405,7 @@ public class FederationProvidersIntegrationTest { try { RealmModel appRealm = new RealmManager(session).getRealmByName("test"); - LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel); + LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel); // Workaround as comma is not allowed in sAMAccountName on active directory. So we will skip the test for this configuration LDAPConfig config = ldapFedProvider.getLdapIdentityStore().getConfig(); @@ -403,11 +414,11 @@ public class FederationProvidersIntegrationTest { } if (!skip) { - LDAPObject johnComma = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "john,comma", "John", "Comma", "johncomma@email.org", null, "12387"); - FederationTestUtils.updateLDAPPassword(ldapFedProvider, johnComma, "Password1"); + LDAPObject johnComma = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "john,comma", "John", "Comma", "johncomma@email.org", null, "12387"); + LDAPTestUtils.updateLDAPPassword(ldapFedProvider, johnComma, "Password1"); - LDAPObject johnPlus = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "john+plus,comma", "John", "Plus", "johnplus@email.org", null, "12387"); - FederationTestUtils.updateLDAPPassword(ldapFedProvider, johnPlus, "Password1"); + LDAPObject johnPlus = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "john+plus,comma", "John", "Plus", "johnplus@email.org", null, "12387"); + LDAPTestUtils.updateLDAPPassword(ldapFedProvider, johnPlus, "Password1"); } } finally { keycloakRule.stopSession(session, false); @@ -420,15 +431,15 @@ public class FederationProvidersIntegrationTest { } } - @Test + //@Test // don't think we should support this, bburke public void testDirectLDAPUpdate() { KeycloakSession session = keycloakRule.startSession(); try { RealmModel appRealm = new RealmManager(session).getRealmByName("test"); - LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel); - LDAPObject johnDirect = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "johndirect", "John", "Direct", "johndirect@email.org", null, "12399"); + LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel); + LDAPObject johnDirect = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "johndirect", "John", "Direct", "johndirect@email.org", null, "12399"); // Fetch user from LDAP and check that postalCode is filled UserModel user = session.users().getUserByUsername("johndirect", appRealm); @@ -470,14 +481,14 @@ public class FederationProvidersIntegrationTest { RealmModel appRealm = new RealmManager(session).getRealmByName("test"); // Update postalCode mapper to always read the value from LDAP - UserFederationMapperModel zipMapper = appRealm.getUserFederationMapperByName(ldapModel.getId(), "zipCodeMapper"); - zipMapper.getConfig().put(UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, "true"); - appRealm.updateUserFederationMapper(zipMapper); + ComponentModel zipMapper = LDAPTestUtils.getComponentByName(appRealm, ldapModel, "zipCodeMapper"); + zipMapper.getConfig().putSingle(UserAttributeLDAPStorageMapper.ALWAYS_READ_VALUE_FROM_LDAP, "true"); + appRealm.updateComponent(zipMapper); // Update lastName mapper to read the value from Keycloak DB - UserFederationMapperModel lastNameMapper = appRealm.getUserFederationMapperByName(ldapModel.getId(), "last name"); - lastNameMapper.getConfig().put(UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, "false"); - appRealm.updateUserFederationMapper(lastNameMapper); + ComponentModel lastNameMapper = LDAPTestUtils.getComponentByName(appRealm, ldapModel, "last name"); + lastNameMapper.getConfig().putSingle(UserAttributeLDAPStorageMapper.ALWAYS_READ_VALUE_FROM_LDAP, "false"); + appRealm.updateComponent(lastNameMapper); // Verify that postalCode is read from LDAP now UserModel user = session.users().getUserByUsername("johndirect", appRealm); @@ -504,7 +515,7 @@ public class FederationProvidersIntegrationTest { @Test public void testFullNameMapper() { KeycloakSession session = keycloakRule.startSession(); - UserFederationMapperModel firstNameMapper = null; + ComponentModel firstNameMapper = null; try { RealmModel appRealm = new RealmManager(session).getRealmByName("test"); @@ -513,18 +524,18 @@ public class FederationProvidersIntegrationTest { 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", null, "4578"); + LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel); + LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "fullname", "James Dee", "Dee", "fullname@email.org", null, "4578"); // add fullname mapper to the provider and remove "firstNameMapper". For this test, we will simply map full name to the LDAP attribute, which was before firstName ( "givenName" on active directory, "cn" on other LDAP servers) - firstNameMapper = appRealm.getUserFederationMapperByName(ldapModel.getId(), "first name"); - String ldapFirstNameAttributeName = firstNameMapper.getConfig().get(UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE); - appRealm.removeUserFederationMapper(firstNameMapper); + firstNameMapper = LDAPTestUtils.getComponentByName(appRealm, ldapModel, "first name"); + String ldapFirstNameAttributeName = firstNameMapper.getConfig().getFirst(UserAttributeLDAPStorageMapper.LDAP_ATTRIBUTE); + appRealm.removeComponent(firstNameMapper); - UserFederationMapperModel fullNameMapperModel = KeycloakModelUtils.createUserFederationMapperModel("full name", ldapModel.getId(), FullNameLDAPFederationMapperFactory.PROVIDER_ID, - FullNameLDAPFederationMapper.LDAP_FULL_NAME_ATTRIBUTE, ldapFirstNameAttributeName, - FullNameLDAPFederationMapper.READ_ONLY, "false"); - appRealm.addUserFederationMapper(fullNameMapperModel); + ComponentModel fullNameMapperModel = KeycloakModelUtils.createComponentModel("full name", ldapModel.getId(), FullNameLDAPStorageMapperFactory.PROVIDER_ID, LDAPStorageMapper.class.getName(), + FullNameLDAPStorageMapper.LDAP_FULL_NAME_ATTRIBUTE, ldapFirstNameAttributeName, + FullNameLDAPStorageMapper.READ_ONLY, "false"); + appRealm.addComponentModel(fullNameMapperModel); } finally { keycloakRule.stopSession(session, true); } @@ -534,12 +545,12 @@ public class FederationProvidersIntegrationTest { RealmModel appRealm = new RealmManager(session).getRealmByName("test"); // 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"); + LDAPTestUtils.assertUserImported(session.users(), appRealm, "fullname", "James", "Dee", "fullname@email.org", "4578"); // change mapper to writeOnly - UserFederationMapperModel fullNameMapperModel = appRealm.getUserFederationMapperByName(ldapModel.getId(), "full name"); - fullNameMapperModel.getConfig().put(FullNameLDAPFederationMapper.WRITE_ONLY, "true"); - appRealm.updateUserFederationMapper(fullNameMapperModel); + ComponentModel fullNameMapperModel = LDAPTestUtils.getComponentByName(appRealm, ldapModel, "full name"); + fullNameMapperModel.getConfig().putSingle(FullNameLDAPStorageMapper.WRITE_ONLY, "true"); + appRealm.updateComponent(fullNameMapperModel); } finally { keycloakRule.stopSession(session, true); } @@ -564,18 +575,18 @@ public class FederationProvidersIntegrationTest { RealmModel appRealm = new RealmManager(session).getRealmByName("test"); // Assert user is successfully imported in Keycloak DB now with correct firstName and lastName - FederationTestUtils.assertUserImported(session.users(), appRealm, "fullname", "James2", "Dee2", "fullname@email.org", "4578"); + LDAPTestUtils.assertUserImported(session.users(), appRealm, "fullname", "James2", "Dee2", "fullname@email.org", "4578"); // 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); + ComponentModel fullNameMapperModel = LDAPTestUtils.getComponentByName(appRealm, ldapModel, "full name"); + appRealm.removeComponent(fullNameMapperModel); firstNameMapper.setId(null); - appRealm.addUserFederationMapper(firstNameMapper); + appRealm.addComponentModel(firstNameMapper); } finally { keycloakRule.stopSession(session, true); } @@ -584,7 +595,7 @@ public class FederationProvidersIntegrationTest { @Test public void testHardcodedRoleMapper() { KeycloakSession session = keycloakRule.startSession(); - UserFederationMapperModel firstNameMapper = null; + ComponentModel firstNameMapper = null; try { RealmModel appRealm = new RealmManager(session).getRealmByName("test"); @@ -594,9 +605,9 @@ public class FederationProvidersIntegrationTest { UserModel john = session.users().getUserByUsername("johnkeycloak", appRealm); Assert.assertFalse(john.hasRole(hardcodedRole)); - UserFederationMapperModel hardcodedMapperModel = KeycloakModelUtils.createUserFederationMapperModel("hardcoded role", ldapModel.getId(), HardcodedLDAPRoleMapperFactory.PROVIDER_ID, - HardcodedLDAPRoleMapper.ROLE, "hardcoded-role"); - appRealm.addUserFederationMapper(hardcodedMapperModel); + ComponentModel hardcodedMapperModel = KeycloakModelUtils.createComponentModel("hardcoded role", ldapModel.getId(), HardcodedLDAPRoleStorageMapperFactory.PROVIDER_ID, LDAPStorageMapper.class.getName(), + HardcodedLDAPRoleStorageMapper.ROLE, "hardcoded-role"); + appRealm.addComponentModel(hardcodedMapperModel); } finally { keycloakRule.stopSession(session, true); } @@ -618,8 +629,8 @@ public class FederationProvidersIntegrationTest { } // Revert mappers - UserFederationMapperModel hardcodedMapperModel = appRealm.getUserFederationMapperByName(ldapModel.getId(), "hardcoded role"); - appRealm.removeUserFederationMapper(hardcodedMapperModel); + ComponentModel hardcodedMapperModel = LDAPTestUtils.getComponentByName(appRealm, ldapModel, "hardcoded role"); + appRealm.removeComponent(hardcodedMapperModel); } finally { keycloakRule.stopSession(session, true); } @@ -632,11 +643,11 @@ public class FederationProvidersIntegrationTest { @Override public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) { - LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel); - FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "marykeycloak", "Mary1", "Kelly1", "mary1@email.org", null, "123"); - FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "mary-duplicatemail", "Mary2", "Kelly2", "mary@test.com", null, "123"); - LDAPObject marynoemail = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "marynoemail", "Mary1", "Kelly1", null, null, "123"); - FederationTestUtils.updateLDAPPassword(ldapFedProvider, marynoemail, "Password1"); + LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel); + LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "marykeycloak", "Mary1", "Kelly1", "mary1@email.org", null, "123"); + LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "mary-duplicatemail", "Mary2", "Kelly2", "mary@test.com", null, "123"); + LDAPObject marynoemail = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "marynoemail", "Mary1", "Kelly1", null, null, "123"); + LDAPTestUtils.updateLDAPPassword(ldapFedProvider, marynoemail, "Password1"); } }); @@ -658,10 +669,9 @@ public class FederationProvidersIntegrationTest { try { RealmModel appRealm = session.realms().getRealmByName("test"); - UserFederationProviderModel model = new UserFederationProviderModel(ldapModel.getId(), ldapModel.getProviderName(), ldapModel.getConfig(), - ldapModel.getPriority(), ldapModel.getDisplayName(), -1, -1, 0); - model.getConfig().put(LDAPConstants.EDIT_MODE, UserFederationProvider.EditMode.READ_ONLY.toString()); - appRealm.updateUserFederationProvider(model); + UserStorageProviderModel model = new UserStorageProviderModel(ldapModel); + model.getConfig().putSingle(LDAPConstants.EDIT_MODE, LDAPStorageProviderFactory.EditMode.READ_ONLY.toString()); + appRealm.updateComponent(model); UserModel user = session.users().getUserByUsername("johnkeycloak", appRealm); Assert.assertNotNull(user); Assert.assertNotNull(user.getFederationLink()); @@ -700,7 +710,7 @@ public class FederationProvidersIntegrationTest { session = keycloakRule.startSession(); try { RealmModel appRealm = session.realms().getRealmByName("test"); - Assert.assertEquals(UserFederationProvider.EditMode.WRITABLE.toString(), appRealm.getUserFederationProviders().get(0).getConfig().get(LDAPConstants.EDIT_MODE)); + Assert.assertEquals(LDAPStorageProviderFactory.EditMode.WRITABLE.toString(), appRealm.getComponent(ldapModel.getId()).getConfig().getFirst(LDAPConstants.EDIT_MODE)); } finally { keycloakRule.stopSession(session, false); } @@ -708,6 +718,17 @@ public class FederationProvidersIntegrationTest { @Test public void testRemoveFederatedUser() { + /* + { + KeycloakSession session = keycloakRule.startSession(); + RealmModel appRealm = session.realms().getRealmByName("test"); + UserModel user = session.users().getUserByUsername("registerUserSuccess2", appRealm); + keycloakRule.stopSession(session, true); + if (user == null) { + registerUserLdapSuccess(); + } + } + */ KeycloakSession session = keycloakRule.startSession(); try { RealmModel appRealm = session.realms().getRealmByName("test"); @@ -728,34 +749,34 @@ public class FederationProvidersIntegrationTest { KeycloakSession session = keycloakRule.startSession(); try { RealmModel appRealm = session.realms().getRealmByName("test"); - LDAPFederationProvider ldapProvider = FederationTestUtils.getLdapProvider(session, ldapModel); + LDAPStorageProvider ldapProvider = LDAPTestUtils.getLdapProvider(session, ldapModel); - FederationTestUtils.addLDAPUser(ldapProvider, appRealm, "username1", "John1", "Doel1", "user1@email.org", null, "121"); - FederationTestUtils.addLDAPUser(ldapProvider, appRealm, "username2", "John2", "Doel2", "user2@email.org", null, "122"); - FederationTestUtils.addLDAPUser(ldapProvider, appRealm, "username3", "John3", "Doel3", "user3@email.org", null, "123"); - FederationTestUtils.addLDAPUser(ldapProvider, appRealm, "username4", "John4", "Doel4", "user4@email.org", null, "124"); + LDAPTestUtils.addLDAPUser(ldapProvider, appRealm, "username1", "John1", "Doel1", "user1@email.org", null, "121"); + LDAPTestUtils.addLDAPUser(ldapProvider, appRealm, "username2", "John2", "Doel2", "user2@email.org", null, "122"); + LDAPTestUtils.addLDAPUser(ldapProvider, appRealm, "username3", "John3", "Doel3", "user3@email.org", null, "123"); + LDAPTestUtils.addLDAPUser(ldapProvider, appRealm, "username4", "John4", "Doel4", "user4@email.org", null, "124"); // Users are not at local store at this moment - Assert.assertNull(session.userStorage().getUserByUsername("username1", appRealm)); - Assert.assertNull(session.userStorage().getUserByUsername("username2", appRealm)); - Assert.assertNull(session.userStorage().getUserByUsername("username3", appRealm)); - Assert.assertNull(session.userStorage().getUserByUsername("username4", appRealm)); + Assert.assertNull(session.userLocalStorage().getUserByUsername("username1", appRealm)); + Assert.assertNull(session.userLocalStorage().getUserByUsername("username2", appRealm)); + Assert.assertNull(session.userLocalStorage().getUserByUsername("username3", appRealm)); + Assert.assertNull(session.userLocalStorage().getUserByUsername("username4", appRealm)); // search by username session.users().searchForUser("username1", appRealm); - FederationTestUtils.assertUserImported(session.userStorage(), appRealm, "username1", "John1", "Doel1", "user1@email.org", "121"); + LDAPTestUtils.assertUserImported(session.userLocalStorage(), appRealm, "username1", "John1", "Doel1", "user1@email.org", "121"); // search by email session.users().searchForUser("user2@email.org", appRealm); - FederationTestUtils.assertUserImported(session.userStorage(), appRealm, "username2", "John2", "Doel2", "user2@email.org", "122"); + LDAPTestUtils.assertUserImported(session.userLocalStorage(), appRealm, "username2", "John2", "Doel2", "user2@email.org", "122"); // search by lastName session.users().searchForUser("Doel3", appRealm); - FederationTestUtils.assertUserImported(session.userStorage(), appRealm, "username3", "John3", "Doel3", "user3@email.org", "123"); + LDAPTestUtils.assertUserImported(session.userLocalStorage(), appRealm, "username3", "John3", "Doel3", "user3@email.org", "123"); // search by firstName + lastName session.users().searchForUser("John4 Doel4", appRealm); - FederationTestUtils.assertUserImported(session.userStorage(), appRealm, "username4", "John4", "Doel4", "user4@email.org", "124"); + LDAPTestUtils.assertUserImported(session.userLocalStorage(), appRealm, "username4", "John4", "Doel4", "user4@email.org", "124"); } finally { keycloakRule.stopSession(session, true); } @@ -767,35 +788,35 @@ public class FederationProvidersIntegrationTest { KeycloakSession session = keycloakRule.startSession(); try { RealmModel appRealm = session.realms().getRealmByName("test"); - ldapModel.getConfig().put(LDAPConstants.CUSTOM_USER_SEARCH_FILTER, "(|(mail=user5@email.org)(mail=user6@email.org))"); - appRealm.updateUserFederationProvider(ldapModel); + ldapModel.getConfig().putSingle(LDAPConstants.CUSTOM_USER_SEARCH_FILTER, "(|(mail=user5@email.org)(mail=user6@email.org))"); + appRealm.updateComponent(ldapModel); } finally { keycloakRule.stopSession(session, true); } session = keycloakRule.startSession(); try { - LDAPFederationProvider ldapProvider = FederationTestUtils.getLdapProvider(session, ldapModel); + LDAPStorageProvider ldapProvider = LDAPTestUtils.getLdapProvider(session, ldapModel); RealmModel appRealm = session.realms().getRealmByName("test"); - FederationTestUtils.addLDAPUser(ldapProvider, appRealm, "username5", "John5", "Doel5", "user5@email.org", null, "125"); - FederationTestUtils.addLDAPUser(ldapProvider, appRealm, "username6", "John6", "Doel6", "user6@email.org", null, "126"); - FederationTestUtils.addLDAPUser(ldapProvider, appRealm, "username7", "John7", "Doel7", "user7@email.org", null, "127"); + LDAPTestUtils.addLDAPUser(ldapProvider, appRealm, "username5", "John5", "Doel5", "user5@email.org", null, "125"); + LDAPTestUtils.addLDAPUser(ldapProvider, appRealm, "username6", "John6", "Doel6", "user6@email.org", null, "126"); + LDAPTestUtils.addLDAPUser(ldapProvider, appRealm, "username7", "John7", "Doel7", "user7@email.org", null, "127"); // search by email - session.users().searchForUser("user5@email.org", appRealm); - FederationTestUtils.assertUserImported(session.userStorage(), appRealm, "username5", "John5", "Doel5", "user5@email.org", "125"); + List list = session.users().searchForUser("user5@email.org", appRealm); + LDAPTestUtils.assertUserImported(session.userLocalStorage(), appRealm, "username5", "John5", "Doel5", "user5@email.org", "125"); session.users().searchForUser("John6 Doel6", appRealm); - FederationTestUtils.assertUserImported(session.userStorage(), appRealm, "username6", "John6", "Doel6", "user6@email.org", "126"); + LDAPTestUtils.assertUserImported(session.userLocalStorage(), appRealm, "username6", "John6", "Doel6", "user6@email.org", "126"); session.users().searchForUser("user7@email.org", appRealm); session.users().searchForUser("John7 Doel7", appRealm); - Assert.assertNull(session.userStorage().getUserByUsername("username7", appRealm)); + Assert.assertNull(session.userLocalStorage().getUserByUsername("username7", appRealm)); // Remove custom filter ldapModel.getConfig().remove(LDAPConstants.CUSTOM_USER_SEARCH_FILTER); - appRealm.updateUserFederationProvider(ldapModel); + appRealm.updateComponent(ldapModel); } finally { keycloakRule.stopSession(session, true); } @@ -807,10 +828,9 @@ public class FederationProvidersIntegrationTest { try { RealmModel appRealm = session.realms().getRealmByName("test"); - UserFederationProviderModel model = new UserFederationProviderModel(ldapModel.getId(), ldapModel.getProviderName(), ldapModel.getConfig(), ldapModel.getPriority(), - ldapModel.getDisplayName(), -1, -1, 0); - model.getConfig().put(LDAPConstants.EDIT_MODE, UserFederationProvider.EditMode.UNSYNCED.toString()); - appRealm.updateUserFederationProvider(model); + UserStorageProviderModel model = new UserStorageProviderModel(ldapModel); + model.getConfig().putSingle(LDAPConstants.EDIT_MODE, LDAPStorageProviderFactory.EditMode.UNSYNCED.toString()); + appRealm.updateComponent(model); UserModel user = session.users().getUserByUsername("johnkeycloak", appRealm); Assert.assertNotNull(user); Assert.assertNotNull(user.getFederationLink()); @@ -823,7 +843,7 @@ public class FederationProvidersIntegrationTest { Assert.assertTrue(session.userCredentialManager().isValid(appRealm, user, cred)); // LDAP password is still unchanged - LDAPFederationProvider ldapProvider = FederationTestUtils.getLdapProvider(session, model); + LDAPStorageProvider ldapProvider = LDAPTestUtils.getLdapProvider(session, model); LDAPObject ldapUser = ldapProvider.loadLDAPUserByUsername(appRealm, "johnkeycloak"); ldapProvider.getLdapIdentityStore().validatePassword(ldapUser, "Password1"); @@ -831,7 +851,7 @@ public class FederationProvidersIntegrationTest { Assert.assertTrue(session.users().removeUser(appRealm, user)); // Assert user not available locally, but will be reimported from LDAP once searched - Assert.assertNull(session.userStorage().getUserByUsername("johnkeycloak", appRealm)); + Assert.assertNull(session.userLocalStorage().getUserByUsername("johnkeycloak", appRealm)); Assert.assertNotNull(session.users().getUserByUsername("johnkeycloak", appRealm)); } finally { keycloakRule.stopSession(session, false); @@ -840,7 +860,7 @@ public class FederationProvidersIntegrationTest { session = keycloakRule.startSession(); try { RealmModel appRealm = session.realms().getRealmByName("test"); - Assert.assertEquals(UserFederationProvider.EditMode.WRITABLE.toString(), appRealm.getUserFederationProviders().get(0).getConfig().get(LDAPConstants.EDIT_MODE)); + Assert.assertEquals(LDAPStorageProviderFactory.EditMode.WRITABLE.toString(), appRealm.getComponent(ldapModel.getId()).getConfig().getFirst(LDAPConstants.EDIT_MODE)); } finally { keycloakRule.stopSession(session, false); } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/LDAPRoleMappingsTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPRoleMappingsTest.java similarity index 74% rename from testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/LDAPRoleMappingsTest.java rename to testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPRoleMappingsTest.java index 0d35d21998..19c2b6b9d0 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/LDAPRoleMappingsTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPRoleMappingsTest.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.keycloak.testsuite.federation.ldap.base; +package org.keycloak.testsuite.federation.storage.ldap; import org.junit.Assert; import org.junit.ClassRule; @@ -25,11 +25,13 @@ 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.idm.model.LDAPObject; -import org.keycloak.federation.ldap.mappers.membership.LDAPGroupMapperMode; -import org.keycloak.federation.ldap.mappers.membership.role.RoleLDAPFederationMapper; +import org.keycloak.common.util.MultivaluedHashMap; +import org.keycloak.component.ComponentModel; +import org.keycloak.storage.UserStorageProviderModel; +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.membership.LDAPGroupMapperMode; import org.keycloak.models.AccountRoles; import org.keycloak.models.ClientModel; import org.keycloak.models.Constants; @@ -38,13 +40,10 @@ import org.keycloak.models.LDAPConstants; import org.keycloak.models.ModelException; import org.keycloak.models.RealmModel; import org.keycloak.models.RoleModel; -import org.keycloak.models.UserFederationMapperModel; -import org.keycloak.models.UserFederationProvider; -import org.keycloak.models.UserFederationProviderModel; import org.keycloak.models.UserModel; import org.keycloak.services.managers.RealmManager; +import org.keycloak.storage.ldap.mappers.membership.role.RoleLDAPStorageMapper; import org.keycloak.testsuite.OAuthClient; -import org.keycloak.testsuite.federation.ldap.FederationTestUtils; import org.keycloak.testsuite.pages.AppPage; import org.keycloak.testsuite.pages.LoginPage; import org.keycloak.testsuite.rule.KeycloakRule; @@ -64,49 +63,57 @@ public class LDAPRoleMappingsTest { private static LDAPRule ldapRule = new LDAPRule(); - private static UserFederationProviderModel ldapModel = null; + private static ComponentModel ldapModel = null; private static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() { @Override public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) { - FederationTestUtils.addLocalUser(manager.getSession(), appRealm, "mary", "mary@test.com", "password-app"); + LDAPTestUtils.addLocalUser(manager.getSession(), appRealm, "mary", "mary@test.com", "password-app"); - Map ldapConfig = ldapRule.getConfig(); - ldapConfig.put(LDAPConstants.SYNC_REGISTRATIONS, "true"); - ldapConfig.put(LDAPConstants.EDIT_MODE, UserFederationProvider.EditMode.WRITABLE.toString()); + MultivaluedHashMap ldapConfig = LDAPTestUtils.getLdapRuleConfig(ldapRule); + ldapConfig.putSingle(LDAPConstants.SYNC_REGISTRATIONS, "true"); + ldapConfig.putSingle(LDAPConstants.EDIT_MODE, LDAPStorageProviderFactory.EditMode.WRITABLE.toString()); + UserStorageProviderModel model = new UserStorageProviderModel(); + model.setLastSync(0); + model.setChangedSyncPeriod(-1); + model.setFullSyncPeriod(-1); + model.setName("test-ldap"); + model.setPriority(0); + model.setProviderId(LDAPStorageProviderFactory.PROVIDER_NAME); + model.setConfig(ldapConfig); - ldapModel = appRealm.addUserFederationProvider(LDAPFederationProviderFactory.PROVIDER_NAME, ldapConfig, 0, "test-ldap", -1, -1, 0); + ldapModel = appRealm.addComponentModel(model); // Delete all LDAP users - LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel); - FederationTestUtils.removeAllLDAPUsers(ldapFedProvider, appRealm); + LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel); + LDAPTestUtils.removeAllLDAPUsers(ldapFedProvider, appRealm); // Add sample application ClientModel finance = appRealm.addClient("finance"); // Delete all LDAP roles - FederationTestUtils.addOrUpdateRoleLDAPMappers(appRealm, ldapModel, LDAPGroupMapperMode.LDAP_ONLY); - FederationTestUtils.removeAllLDAPRoles(manager.getSession(), appRealm, ldapModel, "realmRolesMapper"); - FederationTestUtils.removeAllLDAPRoles(manager.getSession(), appRealm, ldapModel, "financeRolesMapper"); + LDAPTestUtils.addOrUpdateRoleLDAPMappers(appRealm, ldapModel, LDAPGroupMapperMode.LDAP_ONLY); + LDAPTestUtils.removeAllLDAPRoles(manager.getSession(), appRealm, ldapModel, "realmRolesMapper"); + LDAPTestUtils.removeAllLDAPRoles(manager.getSession(), appRealm, ldapModel, "financeRolesMapper"); // Add some users for testing - LDAPObject john = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "johnkeycloak", "John", "Doe", "john@email.org", null, "1234"); - FederationTestUtils.updateLDAPPassword(ldapFedProvider, john, "Password1"); + LDAPObject john = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "johnkeycloak", "John", "Doe", "john@email.org", null, "1234"); + LDAPTestUtils.updateLDAPPassword(ldapFedProvider, john, "Password1"); - LDAPObject mary = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "marykeycloak", "Mary", "Kelly", "mary@email.org", null, "5678"); - FederationTestUtils.updateLDAPPassword(ldapFedProvider, mary, "Password1"); + LDAPObject mary = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "marykeycloak", "Mary", "Kelly", "mary@email.org", null, "5678"); + LDAPTestUtils.updateLDAPPassword(ldapFedProvider, mary, "Password1"); - LDAPObject rob = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "robkeycloak", "Rob", "Brown", "rob@email.org", null, "8910"); - FederationTestUtils.updateLDAPPassword(ldapFedProvider, rob, "Password1"); + LDAPObject rob = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "robkeycloak", "Rob", "Brown", "rob@email.org", null, "8910"); + LDAPTestUtils.updateLDAPPassword(ldapFedProvider, rob, "Password1"); // Add some roles for testing - FederationTestUtils.createLDAPRole(manager.getSession(), appRealm, ldapModel, "realmRolesMapper", "realmRole1"); - FederationTestUtils.createLDAPRole(manager.getSession(), appRealm, ldapModel, "realmRolesMapper", "realmRole2"); - FederationTestUtils.createLDAPRole(manager.getSession(), appRealm, ldapModel, "financeRolesMapper", "financeRole1"); + LDAPTestUtils.createLDAPRole(manager.getSession(), appRealm, ldapModel, "realmRolesMapper", "realmRole1"); + LDAPTestUtils.createLDAPRole(manager.getSession(), appRealm, ldapModel, "realmRolesMapper", "realmRole2"); + LDAPTestUtils.createLDAPRole(manager.getSession(), appRealm, ldapModel, "financeRolesMapper", "financeRole1"); // Sync LDAP roles to Keycloak DB - FederationTestUtils.syncRolesFromLDAP(appRealm, ldapFedProvider, ldapModel); + LDAPTestUtils.syncRolesFromLDAP(appRealm, ldapFedProvider, ldapModel); } }); @@ -136,7 +143,7 @@ public class LDAPRoleMappingsTest { try { RealmModel appRealm = session.realms().getRealmByName("test"); - FederationTestUtils.addOrUpdateRoleLDAPMappers(appRealm, ldapModel, LDAPGroupMapperMode.LDAP_ONLY); + LDAPTestUtils.addOrUpdateRoleLDAPMappers(appRealm, ldapModel, LDAPGroupMapperMode.LDAP_ONLY); UserModel john = session.users().getUserByUsername("johnkeycloak", appRealm); UserModel mary = session.users().getUserByUsername("marykeycloak", appRealm); @@ -169,7 +176,7 @@ public class LDAPRoleMappingsTest { // 2 - Check that role mappings are not in local Keycloak DB (They are in LDAP). - UserModel johnDb = session.userStorage().getUserByUsername("johnkeycloak", appRealm); + UserModel johnDb = session.userLocalStorage().getUserByUsername("johnkeycloak", appRealm); Set johnDbRoles = johnDb.getRoleMappings(); Assert.assertFalse(johnDbRoles.contains(realmRole1)); Assert.assertFalse(johnDbRoles.contains(realmRole2)); @@ -228,7 +235,7 @@ public class LDAPRoleMappingsTest { try { RealmModel appRealm = session.realms().getRealmByName("test"); - FederationTestUtils.addOrUpdateRoleLDAPMappers(appRealm, ldapModel, LDAPGroupMapperMode.READ_ONLY); + LDAPTestUtils.addOrUpdateRoleLDAPMappers(appRealm, ldapModel, LDAPGroupMapperMode.READ_ONLY); UserModel mary = session.users().getUserByUsername("marykeycloak", appRealm); @@ -240,9 +247,9 @@ public class LDAPRoleMappingsTest { } // Add some role mappings directly into LDAP - UserFederationMapperModel roleMapperModel = appRealm.getUserFederationMapperByName(ldapModel.getId(), "realmRolesMapper"); - LDAPFederationProvider ldapProvider = FederationTestUtils.getLdapProvider(session, ldapModel); - RoleLDAPFederationMapper roleMapper = FederationTestUtils.getRoleMapper(roleMapperModel, ldapProvider, appRealm); + ComponentModel roleMapperModel = LDAPTestUtils.getComponentByName(appRealm, ldapModel, "realmRolesMapper"); + LDAPStorageProvider ldapProvider = LDAPTestUtils.getLdapProvider(session, ldapModel); + RoleLDAPStorageMapper roleMapper = LDAPTestUtils.getRoleMapper(roleMapperModel, ldapProvider, appRealm); LDAPObject maryLdap = ldapProvider.loadLDAPUserByUsername(appRealm, "marykeycloak"); roleMapper.addRoleMappingInLDAP("realmRole1", maryLdap); @@ -258,7 +265,7 @@ public class LDAPRoleMappingsTest { Assert.assertTrue(maryRoles.contains(realmRole3)); // Assert that access through DB will have just DB mapped role - UserModel maryDB = session.userStorage().getUserByUsername("marykeycloak", appRealm); + UserModel maryDB = session.userLocalStorage().getUserByUsername("marykeycloak", appRealm); Set maryDBRoles = maryDB.getRealmRoleMappings(); Assert.assertFalse(maryDBRoles.contains(realmRole1)); Assert.assertFalse(maryDBRoles.contains(realmRole2)); @@ -299,12 +306,12 @@ public class LDAPRoleMappingsTest { try { RealmModel appRealm = session.realms().getRealmByName("test"); - FederationTestUtils.addOrUpdateRoleLDAPMappers(appRealm, ldapModel, LDAPGroupMapperMode.IMPORT); + LDAPTestUtils.addOrUpdateRoleLDAPMappers(appRealm, ldapModel, LDAPGroupMapperMode.IMPORT); // Add some role mappings directly in LDAP - UserFederationMapperModel roleMapperModel = appRealm.getUserFederationMapperByName(ldapModel.getId(), "realmRolesMapper"); - LDAPFederationProvider ldapProvider = FederationTestUtils.getLdapProvider(session, ldapModel); - RoleLDAPFederationMapper roleMapper = FederationTestUtils.getRoleMapper(roleMapperModel, ldapProvider, appRealm); + ComponentModel roleMapperModel = LDAPTestUtils.getComponentByName(appRealm, ldapModel, "realmRolesMapper"); + LDAPStorageProvider ldapProvider = LDAPTestUtils.getLdapProvider(session, ldapModel); + RoleLDAPStorageMapper roleMapper = LDAPTestUtils.getRoleMapper(roleMapperModel, ldapProvider, appRealm); LDAPObject robLdap = ldapProvider.loadLDAPUserByUsername(appRealm, "robkeycloak"); roleMapper.addRoleMappingInLDAP("realmRole1", robLdap); @@ -348,7 +355,7 @@ public class LDAPRoleMappingsTest { } } - private void deleteRoleMappingsInLDAP(RoleLDAPFederationMapper roleMapper, LDAPObject ldapUser, String roleName) { + private void deleteRoleMappingsInLDAP(RoleLDAPStorageMapper roleMapper, LDAPObject ldapUser, String roleName) { LDAPObject ldapRole1 = roleMapper.loadLDAPRoleByName(roleName); roleMapper.deleteRoleMappingInLDAP(ldapUser, ldapRole1); } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/LDAPSyncTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPSyncTest.java similarity index 52% rename from testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/LDAPSyncTest.java rename to testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPSyncTest.java index e300ffd13d..c1d55c1589 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/LDAPSyncTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPSyncTest.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.keycloak.testsuite.federation.ldap.base; +package org.keycloak.testsuite.federation.storage.ldap; import org.junit.Assert; import org.junit.ClassRule; @@ -24,24 +24,23 @@ import org.junit.Test; import org.junit.rules.RuleChain; import org.junit.rules.TestRule; import org.junit.runners.MethodSorters; +import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.common.util.Time; -import org.keycloak.federation.ldap.LDAPFederationProvider; -import org.keycloak.federation.ldap.LDAPFederationProviderFactory; -import org.keycloak.federation.ldap.idm.model.LDAPObject; +import org.keycloak.component.ComponentModel; +import org.keycloak.services.managers.UserStorageSyncManager; +import org.keycloak.storage.ldap.idm.model.LDAPObject; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.LDAPConstants; 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.UserProvider; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.services.managers.RealmManager; -import org.keycloak.services.managers.UsersSyncManager; -import org.keycloak.testsuite.federation.ldap.FederationTestUtils; +import org.keycloak.storage.UserStorageProviderModel; +import org.keycloak.storage.ldap.LDAPStorageProvider; +import org.keycloak.storage.ldap.LDAPStorageProviderFactory; +import org.keycloak.storage.user.SynchronizationResult; import org.keycloak.testsuite.rule.KeycloakRule; import org.keycloak.testsuite.rule.LDAPRule; @@ -55,7 +54,7 @@ public class LDAPSyncTest { private static LDAPRule ldapRule = new LDAPRule(); - private static UserFederationProviderModel ldapModel = null; + private static UserStorageProviderModel ldapModel = null; private static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() { @@ -64,21 +63,30 @@ public class LDAPSyncTest { // Other tests may left Time offset uncleared, which could cause issues Time.setOffset(0); - Map ldapConfig = ldapRule.getConfig(); - ldapConfig.put(LDAPConstants.SYNC_REGISTRATIONS, "false"); - ldapConfig.put(LDAPConstants.EDIT_MODE, UserFederationProvider.EditMode.WRITABLE.toString()); - ldapModel = appRealm.addUserFederationProvider(LDAPFederationProviderFactory.PROVIDER_NAME, ldapConfig, 0, "test-ldap", - -1, -1, 0); + MultivaluedHashMap ldapConfig = LDAPTestUtils.getLdapRuleConfig(ldapRule); + ldapConfig.putSingle(LDAPConstants.SYNC_REGISTRATIONS, "false"); + ldapConfig.putSingle(LDAPConstants.EDIT_MODE, LDAPStorageProviderFactory.EditMode.WRITABLE.toString()); + UserStorageProviderModel model = new UserStorageProviderModel(); + model.setLastSync(0); + model.setChangedSyncPeriod(-1); + model.setFullSyncPeriod(-1); + model.setName("test-ldap"); + model.setPriority(0); + model.setProviderId(LDAPStorageProviderFactory.PROVIDER_NAME); + model.setConfig(ldapConfig); - FederationTestUtils.addZipCodeLDAPMapper(appRealm, ldapModel); + ldapModel = new UserStorageProviderModel(appRealm.addComponentModel(model)); + + + LDAPTestUtils.addZipCodeLDAPMapper(appRealm, ldapModel); // Delete all LDAP users and add 5 new users for testing - LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel); - FederationTestUtils.removeAllLDAPUsers(ldapFedProvider, appRealm); + LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel); + LDAPTestUtils.removeAllLDAPUsers(ldapFedProvider, appRealm); for (int i=1 ; i<=5 ; i++) { - LDAPObject ldapUser = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "user" + i, "User" + i + "FN", "User" + i + "LN", "user" + i + "@email.org", null, "12" + i); - FederationTestUtils.updateLDAPPassword(ldapFedProvider, ldapUser, "Password1"); + LDAPObject ldapUser = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "user" + i, "User" + i + "FN", "User" + i + "LN", "user" + i + "@email.org", null, "12" + i); + LDAPTestUtils.updateLDAPPassword(ldapFedProvider, ldapUser, "Password1"); } } }); @@ -95,7 +103,7 @@ public class LDAPSyncTest { @Test public void test01LDAPSync() { - UsersSyncManager usersSyncManager = new UsersSyncManager(); + UserStorageSyncManager usersSyncManager = new UserStorageSyncManager(); // wait a bit sleep(ldapRule.getSleepTime()); @@ -103,8 +111,8 @@ public class LDAPSyncTest { KeycloakSession session = keycloakRule.startSession(); try { KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory(); - UserFederationSyncResult syncResult = usersSyncManager.syncAllUsers(sessionFactory, "test", ldapModel); - FederationTestUtils.assertSyncEquals(syncResult, 5, 0, 0, 0); + SynchronizationResult syncResult = usersSyncManager.syncAllUsers(sessionFactory, "test", ldapModel); + LDAPTestUtils.assertSyncEquals(syncResult, 5, 0, 0, 0); } finally { keycloakRule.stopSession(session, false); } @@ -112,18 +120,18 @@ public class LDAPSyncTest { session = keycloakRule.startSession(); try { RealmModel testRealm = session.realms().getRealm("test"); - UserProvider userProvider = session.userStorage(); + UserProvider userProvider = session.userLocalStorage(); // Assert users imported - FederationTestUtils.assertUserImported(userProvider, testRealm, "user1", "User1FN", "User1LN", "user1@email.org", "121"); - FederationTestUtils.assertUserImported(userProvider, testRealm, "user2", "User2FN", "User2LN", "user2@email.org", "122"); - FederationTestUtils.assertUserImported(userProvider, testRealm, "user3", "User3FN", "User3LN", "user3@email.org", "123"); - FederationTestUtils.assertUserImported(userProvider, testRealm, "user4", "User4FN", "User4LN", "user4@email.org", "124"); - FederationTestUtils.assertUserImported(userProvider, testRealm, "user5", "User5FN", "User5LN", "user5@email.org", "125"); + LDAPTestUtils.assertUserImported(userProvider, testRealm, "user1", "User1FN", "User1LN", "user1@email.org", "121"); + LDAPTestUtils.assertUserImported(userProvider, testRealm, "user2", "User2FN", "User2LN", "user2@email.org", "122"); + LDAPTestUtils.assertUserImported(userProvider, testRealm, "user3", "User3FN", "User3LN", "user3@email.org", "123"); + LDAPTestUtils.assertUserImported(userProvider, testRealm, "user4", "User4FN", "User4LN", "user4@email.org", "124"); + LDAPTestUtils.assertUserImported(userProvider, testRealm, "user5", "User5FN", "User5LN", "user5@email.org", "125"); // Assert lastSync time updated Assert.assertTrue(ldapModel.getLastSync() > 0); - for (UserFederationProviderModel persistentFedModel : testRealm.getUserFederationProviders()) { - if (LDAPFederationProviderFactory.PROVIDER_NAME.equals(persistentFedModel.getProviderName())) { + for (UserStorageProviderModel persistentFedModel : testRealm.getUserStorageProviders()) { + if (LDAPStorageProviderFactory.PROVIDER_NAME.equals(persistentFedModel.getProviderId())) { Assert.assertTrue(persistentFedModel.getLastSync() > 0); } else { // Dummy provider has still 0 @@ -135,8 +143,8 @@ public class LDAPSyncTest { sleep(ldapRule.getSleepTime()); // Add user to LDAP and update 'user5' in LDAP - LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel); - FederationTestUtils.addLDAPUser(ldapFedProvider, testRealm, "user6", "User6FN", "User6LN", "user6@email.org", null, "126"); + LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel); + LDAPTestUtils.addLDAPUser(ldapFedProvider, testRealm, "user6", "User6FN", "User6LN", "user6@email.org", null, "126"); LDAPObject ldapUser5 = ldapFedProvider.loadLDAPUserByUsername(testRealm, "user5"); // NOTE: Changing LDAP attributes directly here ldapUser5.setSingleAttribute(LDAPConstants.EMAIL, "user5Updated@email.org"); @@ -144,13 +152,13 @@ public class LDAPSyncTest { ldapFedProvider.getLdapIdentityStore().update(ldapUser5); // Assert still old users in local provider - FederationTestUtils.assertUserImported(userProvider, testRealm, "user5", "User5FN", "User5LN", "user5@email.org", "125"); + LDAPTestUtils.assertUserImported(userProvider, testRealm, "user5", "User5FN", "User5LN", "user5@email.org", "125"); Assert.assertNull(userProvider.getUserByUsername("user6", testRealm)); // Trigger partial sync KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory(); - UserFederationSyncResult syncResult = usersSyncManager.syncChangedUsers(sessionFactory, "test", ldapModel); - FederationTestUtils.assertSyncEquals(syncResult, 1, 1, 0, 0); + SynchronizationResult syncResult = usersSyncManager.syncChangedUsers(sessionFactory, "test", ldapModel); + LDAPTestUtils.assertSyncEquals(syncResult, 1, 1, 0, 0); } finally { keycloakRule.stopSession(session, false); } @@ -158,10 +166,10 @@ public class LDAPSyncTest { session = keycloakRule.startSession(); try { RealmModel testRealm = session.realms().getRealm("test"); - UserProvider userProvider = session.userStorage(); + UserProvider userProvider = session.userLocalStorage(); // Assert users updated in local provider - FederationTestUtils.assertUserImported(userProvider, testRealm, "user5", "User5FN", "User5LN", "user5updated@email.org", "521"); - FederationTestUtils.assertUserImported(userProvider, testRealm, "user6", "User6FN", "User6LN", "user6@email.org", "126"); + LDAPTestUtils.assertUserImported(userProvider, testRealm, "user5", "User5FN", "User5LN", "user5updated@email.org", "521"); + LDAPTestUtils.assertUserImported(userProvider, testRealm, "user6", "User6FN", "User6LN", "user6@email.org", "126"); } finally { keycloakRule.stopSession(session, false); } @@ -175,11 +183,11 @@ public class LDAPSyncTest { try { RealmModel testRealm = session.realms().getRealm("test"); - FederationTestUtils.addLocalUser(session, testRealm, "user7", "user7@email.org", "password"); - LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel); + LDAPTestUtils.addLocalUser(session, testRealm, "user7", "user7@email.org", "password"); + LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel); // Add user to LDAP with duplicated username "user7" - duplicatedLdapUser = FederationTestUtils.addLDAPUser(ldapFedProvider, testRealm, "user7", "User7FN", "User7LN", "user7-something@email.org", null, "126"); + duplicatedLdapUser = LDAPTestUtils.addLDAPUser(ldapFedProvider, testRealm, "user7", "User7FN", "User7LN", "user7-something@email.org", null, "126"); } finally { keycloakRule.stopSession(session, true); @@ -190,15 +198,15 @@ public class LDAPSyncTest { RealmModel testRealm = session.realms().getRealm("test"); // Assert syncing from LDAP fails due to duplicated username - UserFederationSyncResult result = new UsersSyncManager().syncAllUsers(session.getKeycloakSessionFactory(), "test", ldapModel); + SynchronizationResult result = new UserStorageSyncManager().syncAllUsers(session.getKeycloakSessionFactory(), "test", ldapModel); Assert.assertEquals(1, result.getFailed()); // Remove "user7" from LDAP - LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel); + LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel); ldapFedProvider.getLdapIdentityStore().remove(duplicatedLdapUser); // Add user to LDAP with duplicated email "user7@email.org" - duplicatedLdapUser = FederationTestUtils.addLDAPUser(ldapFedProvider, testRealm, "user7-something", "User7FNN", "User7LNL", "user7@email.org", null, "126"); + duplicatedLdapUser = LDAPTestUtils.addLDAPUser(ldapFedProvider, testRealm, "user7-something", "User7FNN", "User7LNL", "user7@email.org", null, "126"); } finally { keycloakRule.stopSession(session, true); } @@ -208,17 +216,17 @@ public class LDAPSyncTest { RealmModel testRealm = session.realms().getRealm("test"); // Assert syncing from LDAP fails due to duplicated email - UserFederationSyncResult result = new UsersSyncManager().syncAllUsers(session.getKeycloakSessionFactory(), "test", ldapModel); + SynchronizationResult result = new UserStorageSyncManager().syncAllUsers(session.getKeycloakSessionFactory(), "test", ldapModel); Assert.assertEquals(1, result.getFailed()); - Assert.assertNull(session.userStorage().getUserByUsername("user7-something", testRealm)); + Assert.assertNull(session.userLocalStorage().getUserByUsername("user7-something", testRealm)); // Update LDAP user to avoid duplicated email duplicatedLdapUser.setSingleAttribute(LDAPConstants.EMAIL, "user7-changed@email.org"); - LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel); + LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel); ldapFedProvider.getLdapIdentityStore().update(duplicatedLdapUser); // Assert user successfully synced now - result = new UsersSyncManager().syncAllUsers(session.getKeycloakSessionFactory(), "test", ldapModel); + result = new UserStorageSyncManager().syncAllUsers(session.getKeycloakSessionFactory(), "test", ldapModel); Assert.assertEquals(0, result.getFailed()); } finally { keycloakRule.stopSession(session, true); @@ -228,7 +236,7 @@ public class LDAPSyncTest { session = keycloakRule.startSession(); try { RealmModel testRealm = session.realms().getRealm("test"); - FederationTestUtils.assertUserImported(session.userStorage(), testRealm, "user7-something", "User7FNN", "User7LNL", "user7-changed@email.org", "126"); + LDAPTestUtils.assertUserImported(session.userLocalStorage(), testRealm, "user7-something", "User7FNN", "User7LNL", "user7-changed@email.org", "126"); } finally { keycloakRule.stopSession(session, false); } @@ -244,21 +252,21 @@ public class LDAPSyncTest { RealmModel testRealm = session.realms().getRealm("test"); // Remove all users from model - for (UserModel user : session.userStorage().getUsers(testRealm, true)) { - session.userStorage().removeUser(testRealm, user); + for (UserModel user : session.userLocalStorage().getUsers(testRealm, true)) { + session.userLocalStorage().removeUser(testRealm, user); } - UserFederationProviderModel providerModel = KeycloakModelUtils.findUserFederationProviderByDisplayName(ldapModel.getDisplayName(), testRealm); + UserStorageProviderModel providerModel = KeycloakModelUtils.findUserStorageProviderByName(ldapModel.getName(), testRealm); // Change name of UUID attribute to same like usernameAttribute - LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel); + LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel); String uidAttrName = ldapFedProvider.getLdapIdentityStore().getConfig().getUsernameLdapAttribute(); - origUuidAttrName = providerModel.getConfig().get(LDAPConstants.UUID_LDAP_ATTRIBUTE); - providerModel.getConfig().put(LDAPConstants.UUID_LDAP_ATTRIBUTE, uidAttrName); + origUuidAttrName = providerModel.getConfig().getFirst(LDAPConstants.UUID_LDAP_ATTRIBUTE); + providerModel.getConfig().putSingle(LDAPConstants.UUID_LDAP_ATTRIBUTE, uidAttrName); // Need to change this due to ApacheDS pagination bug (For other LDAP servers, pagination works fine) TODO: Remove once ApacheDS upgraded and pagination is fixed - providerModel.getConfig().put(LDAPConstants.BATCH_SIZE_FOR_SYNC, "10"); - testRealm.updateUserFederationProvider(providerModel); + providerModel.getConfig().putSingle(LDAPConstants.BATCH_SIZE_FOR_SYNC, "10"); + testRealm.updateComponent(providerModel); } finally { keycloakRule.stopSession(session, true); @@ -267,10 +275,10 @@ public class LDAPSyncTest { session = keycloakRule.startSession(); try { RealmModel testRealm = session.realms().getRealm("test"); - UserFederationProviderModel providerModel = KeycloakModelUtils.findUserFederationProviderByDisplayName(ldapModel.getDisplayName(), testRealm); + UserStorageProviderModel providerModel = KeycloakModelUtils.findUserStorageProviderByName(ldapModel.getName(), testRealm); KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory(); - UserFederationSyncResult syncResult = new UsersSyncManager().syncAllUsers(sessionFactory, "test", providerModel); + SynchronizationResult syncResult = new UserStorageSyncManager().syncAllUsers(sessionFactory, "test", providerModel); Assert.assertEquals(0, syncResult.getFailed()); } finally { @@ -282,15 +290,15 @@ public class LDAPSyncTest { RealmModel testRealm = session.realms().getRealm("test"); // Assert users imported with correct LDAP_ID - FederationTestUtils.assertUserImported(session.users(), testRealm, "user1", "User1FN", "User1LN", "user1@email.org", "121"); - FederationTestUtils.assertUserImported(session.users(), testRealm, "user2", "User2FN", "User2LN", "user2@email.org", "122"); + LDAPTestUtils.assertUserImported(session.users(), testRealm, "user1", "User1FN", "User1LN", "user1@email.org", "121"); + LDAPTestUtils.assertUserImported(session.users(), testRealm, "user2", "User2FN", "User2LN", "user2@email.org", "122"); UserModel user1 = session.users().getUserByUsername("user1", testRealm); Assert.assertEquals("user1", user1.getFirstAttribute(LDAPConstants.LDAP_ID)); // Revert config changes - UserFederationProviderModel providerModel = KeycloakModelUtils.findUserFederationProviderByDisplayName(ldapModel.getDisplayName(), testRealm); - providerModel.getConfig().put(LDAPConstants.UUID_LDAP_ATTRIBUTE, origUuidAttrName); - testRealm.updateUserFederationProvider(providerModel); + UserStorageProviderModel providerModel = KeycloakModelUtils.findUserStorageProviderByName(ldapModel.getName(), testRealm); + providerModel.getConfig().putSingle(LDAPConstants.UUID_LDAP_ATTRIBUTE, origUuidAttrName); + testRealm.updateComponent(providerModel); } finally { keycloakRule.stopSession(session, true); } @@ -306,24 +314,26 @@ public class LDAPSyncTest { RealmModel testRealm = session.realms().getRealm("test"); // Remove all users from model - for (UserModel user : session.userStorage().getUsers(testRealm, true)) { - session.userStorage().removeUser(testRealm, user); + for (UserModel user : session.userLocalStorage().getUsers(testRealm, true)) { + System.out.println("trying to delete user: " + user.getUsername()); + session.getUserCache().evict(testRealm, user); + session.userLocalStorage().removeUser(testRealm, user); } - UserFederationProviderModel providerModel = KeycloakModelUtils.findUserFederationProviderByDisplayName(ldapModel.getDisplayName(), testRealm); + UserStorageProviderModel providerModel = KeycloakModelUtils.findUserStorageProviderByName(ldapModel.getName(), testRealm); // Add street mapper and add some user including street - UserFederationMapperModel streetMapper = FederationTestUtils.addUserAttributeMapper(testRealm, ldapModel, "streetMapper", "street", LDAPConstants.STREET); - LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel); - LDAPObject streetUser = FederationTestUtils.addLDAPUser(ldapFedProvider, testRealm, "user8", "User8FN", "User8LN", "user8@email.org", "user8street", "126"); + ComponentModel streetMapper = LDAPTestUtils.addUserAttributeMapper(testRealm, ldapModel, "streetMapper", "street", LDAPConstants.STREET); + LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel); + LDAPObject streetUser = LDAPTestUtils.addLDAPUser(ldapFedProvider, testRealm, "user8", "User8FN", "User8LN", "user8@email.org", "user8street", "126"); // Change name of username attribute name to street - origUsernameAttrName = providerModel.getConfig().get(LDAPConstants.USERNAME_LDAP_ATTRIBUTE); - providerModel.getConfig().put(LDAPConstants.USERNAME_LDAP_ATTRIBUTE, "street"); + origUsernameAttrName = providerModel.getConfig().getFirst(LDAPConstants.USERNAME_LDAP_ATTRIBUTE); + providerModel.getConfig().putSingle(LDAPConstants.USERNAME_LDAP_ATTRIBUTE, "street"); // Need to change this due to ApacheDS pagination bug (For other LDAP servers, pagination works fine) TODO: Remove once ApacheDS upgraded and pagination is fixed - providerModel.getConfig().put(LDAPConstants.BATCH_SIZE_FOR_SYNC, "10"); - testRealm.updateUserFederationProvider(providerModel); + providerModel.getConfig().putSingle(LDAPConstants.BATCH_SIZE_FOR_SYNC, "10"); + testRealm.updateComponent(providerModel); } finally { keycloakRule.stopSession(session, true); @@ -333,10 +343,10 @@ public class LDAPSyncTest { session = keycloakRule.startSession(); try { RealmModel testRealm = session.realms().getRealm("test"); - UserFederationProviderModel providerModel = KeycloakModelUtils.findUserFederationProviderByDisplayName(ldapModel.getDisplayName(), testRealm); + UserStorageProviderModel providerModel = KeycloakModelUtils.findUserStorageProviderByName(ldapModel.getName(), testRealm); KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory(); - UserFederationSyncResult syncResult = new UsersSyncManager().syncAllUsers(sessionFactory, "test", providerModel); + SynchronizationResult syncResult = new UserStorageSyncManager().syncAllUsers(sessionFactory, "test", providerModel); Assert.assertEquals(1, syncResult.getAdded()); Assert.assertTrue(syncResult.getFailed() > 0); } finally { @@ -348,11 +358,11 @@ public class LDAPSyncTest { RealmModel testRealm = session.realms().getRealm("test"); // Revert config changes - UserFederationProviderModel providerModel = KeycloakModelUtils.findUserFederationProviderByDisplayName(ldapModel.getDisplayName(), testRealm); - providerModel.getConfig().put(LDAPConstants.USERNAME_LDAP_ATTRIBUTE, origUsernameAttrName); - testRealm.updateUserFederationProvider(providerModel); - UserFederationMapperModel streetMapper = testRealm.getUserFederationMapperByName(providerModel.getId(), "streetMapper"); - testRealm.removeUserFederationMapper(streetMapper); + UserStorageProviderModel providerModel = KeycloakModelUtils.findUserStorageProviderByName(ldapModel.getName(), testRealm); + providerModel.getConfig().putSingle(LDAPConstants.USERNAME_LDAP_ATTRIBUTE, origUsernameAttrName); + testRealm.updateComponent(providerModel); + ComponentModel streetMapper = LDAPTestUtils.getComponentByName(testRealm, providerModel, "streetMapper"); + testRealm.removeComponent(streetMapper); } finally { keycloakRule.stopSession(session, true); } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPTestConfiguration.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPTestConfiguration.java new file mode 100644 index 0000000000..53a6294934 --- /dev/null +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPTestConfiguration.java @@ -0,0 +1,149 @@ +/* + * 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.jboss.logging.Logger; +import org.keycloak.common.constants.KerberosConstants; +import org.keycloak.models.LDAPConstants; +import org.keycloak.storage.ldap.LDAPStorageProviderFactory; + +import java.io.File; +import java.io.InputStream; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +/** + * @author Marek Posolda + */ +public class LDAPTestConfiguration { + + private static final Logger log = Logger.getLogger(LDAPTestConfiguration.class); + + private String connectionPropertiesLocation; + private int sleepTime; + private boolean startEmbeddedLdapLerver = true; + private Map config; + + protected static final Map PROP_MAPPINGS = new HashMap(); + protected static final Map DEFAULT_VALUES = new HashMap(); + + static { + PROP_MAPPINGS.put(LDAPConstants.CONNECTION_URL, "idm.test.ldap.connection.url"); + PROP_MAPPINGS.put(LDAPConstants.BASE_DN, "idm.test.ldap.base.dn"); + PROP_MAPPINGS.put(LDAPConstants.USERS_DN, "idm.test.ldap.user.dn.suffix"); + PROP_MAPPINGS.put(LDAPConstants.BIND_DN, "idm.test.ldap.bind.dn"); + PROP_MAPPINGS.put(LDAPConstants.BIND_CREDENTIAL, "idm.test.ldap.bind.credential"); + PROP_MAPPINGS.put(LDAPConstants.VENDOR, "idm.test.ldap.vendor"); + PROP_MAPPINGS.put(LDAPConstants.CONNECTION_POOLING, "idm.test.ldap.connection.pooling"); + PROP_MAPPINGS.put(LDAPConstants.PAGINATION, "idm.test.ldap.pagination"); + PROP_MAPPINGS.put(LDAPConstants.BATCH_SIZE_FOR_SYNC, "idm.test.ldap.batch.size.for.sync"); + PROP_MAPPINGS.put(LDAPConstants.USERNAME_LDAP_ATTRIBUTE, "idm.test.ldap.username.ldap.attribute"); + PROP_MAPPINGS.put(LDAPConstants.RDN_LDAP_ATTRIBUTE, "idm.test.ldap.rdn.ldap.attribute"); + PROP_MAPPINGS.put(LDAPConstants.USER_OBJECT_CLASSES, "idm.test.ldap.user.object.classes"); + PROP_MAPPINGS.put(LDAPConstants.EDIT_MODE, "idm.test.ldap.edit.mode"); + + PROP_MAPPINGS.put(KerberosConstants.ALLOW_KERBEROS_AUTHENTICATION, "idm.test.kerberos.allow.kerberos.authentication"); + PROP_MAPPINGS.put(KerberosConstants.KERBEROS_REALM, "idm.test.kerberos.realm"); + PROP_MAPPINGS.put(KerberosConstants.SERVER_PRINCIPAL, "idm.test.kerberos.server.principal"); + PROP_MAPPINGS.put(KerberosConstants.KEYTAB, "idm.test.kerberos.keytab"); + PROP_MAPPINGS.put(KerberosConstants.DEBUG, "idm.test.kerberos.debug"); + PROP_MAPPINGS.put(KerberosConstants.ALLOW_PASSWORD_AUTHENTICATION, "idm.test.kerberos.allow.password.authentication"); + PROP_MAPPINGS.put(KerberosConstants.UPDATE_PROFILE_FIRST_LOGIN, "idm.test.kerberos.update.profile.first.login"); + PROP_MAPPINGS.put(KerberosConstants.USE_KERBEROS_FOR_PASSWORD_AUTHENTICATION, "idm.test.kerberos.use.kerberos.for.password.authentication"); + + DEFAULT_VALUES.put(LDAPConstants.CONNECTION_URL, "ldap://localhost:10389"); + DEFAULT_VALUES.put(LDAPConstants.BASE_DN, "dc=keycloak,dc=org"); + DEFAULT_VALUES.put(LDAPConstants.USERS_DN, "ou=People,dc=keycloak,dc=org"); + DEFAULT_VALUES.put(LDAPConstants.BIND_DN, "uid=admin,ou=system"); + DEFAULT_VALUES.put(LDAPConstants.BIND_CREDENTIAL, "secret"); + DEFAULT_VALUES.put(LDAPConstants.VENDOR, LDAPConstants.VENDOR_OTHER); + DEFAULT_VALUES.put(LDAPConstants.CONNECTION_POOLING, "true"); + DEFAULT_VALUES.put(LDAPConstants.PAGINATION, "true"); + DEFAULT_VALUES.put(LDAPConstants.BATCH_SIZE_FOR_SYNC, String.valueOf(LDAPConstants.DEFAULT_BATCH_SIZE_FOR_SYNC)); + DEFAULT_VALUES.put(LDAPConstants.USERNAME_LDAP_ATTRIBUTE, null); + DEFAULT_VALUES.put(LDAPConstants.USER_OBJECT_CLASSES, null); + DEFAULT_VALUES.put(LDAPConstants.EDIT_MODE, LDAPStorageProviderFactory.EditMode.READ_ONLY.toString()); + + DEFAULT_VALUES.put(KerberosConstants.ALLOW_KERBEROS_AUTHENTICATION, "false"); + DEFAULT_VALUES.put(KerberosConstants.KERBEROS_REALM, "KEYCLOAK.ORG"); + DEFAULT_VALUES.put(KerberosConstants.SERVER_PRINCIPAL, "HTTP/localhost@KEYCLOAK.ORG"); + URL keytabUrl = LDAPTestConfiguration.class.getResource("/kerberos/http.keytab"); + String keyTabPath = new File(keytabUrl.getFile()).getAbsolutePath(); + DEFAULT_VALUES.put(KerberosConstants.KEYTAB, keyTabPath); + DEFAULT_VALUES.put(KerberosConstants.DEBUG, "true"); + DEFAULT_VALUES.put(KerberosConstants.ALLOW_PASSWORD_AUTHENTICATION, "true"); + DEFAULT_VALUES.put(KerberosConstants.UPDATE_PROFILE_FIRST_LOGIN, "true"); + DEFAULT_VALUES.put(KerberosConstants.USE_KERBEROS_FOR_PASSWORD_AUTHENTICATION, "false"); + } + + public static LDAPTestConfiguration readConfiguration(String connectionPropertiesLocation) { + LDAPTestConfiguration ldapTestConfiguration = new LDAPTestConfiguration(); + ldapTestConfiguration.setConnectionPropertiesLocation(connectionPropertiesLocation); + ldapTestConfiguration.loadConnectionProperties(); + return ldapTestConfiguration; + } + + protected void loadConnectionProperties() { + Properties p = new Properties(); + try { + log.info("Reading LDAP configuration from: " + connectionPropertiesLocation); + InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(connectionPropertiesLocation); + p.load(is); + } + catch (Exception e) { + throw new RuntimeException(e); + } + + config = new HashMap(); + for (Map.Entry property : PROP_MAPPINGS.entrySet()) { + String propertyName = property.getKey(); + String configName = property.getValue(); + + String value = (String) p.get(configName); + if (value == null) { + value = DEFAULT_VALUES.get(propertyName); + } + + config.put(propertyName, value); + } + + startEmbeddedLdapLerver = Boolean.parseBoolean(p.getProperty("idm.test.ldap.start.embedded.ldap.server", "true")); + sleepTime = Integer.parseInt(p.getProperty("idm.test.ldap.sleepTime", "1000")); + log.info("Start embedded server: " + startEmbeddedLdapLerver); + log.info("Read config: " + config); + } + + public Map getLDAPConfig() { + return config; + } + + public void setConnectionPropertiesLocation(String connectionPropertiesLocation) { + this.connectionPropertiesLocation = connectionPropertiesLocation; + } + + public boolean isStartEmbeddedLdapLerver() { + return startEmbeddedLdapLerver; + } + + public int getSleepTime() { + return sleepTime; + } + +} diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPTestUtils.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPTestUtils.java new file mode 100644 index 0000000000..44a3f245db --- /dev/null +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPTestUtils.java @@ -0,0 +1,313 @@ +/* + * 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.storage.UserStorageProvider; +import org.keycloak.storage.ldap.LDAPStorageProvider; +import org.keycloak.storage.ldap.LDAPStorageProviderFactory; +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 Marek Posolda + */ +public class LDAPTestUtils { + public static MultivaluedHashMap getLdapRuleConfig(LDAPRule ldapRule) { + Map ldapConfig = ldapRule.getConfig(); + return toLdapConfig(ldapConfig); + + } + + public static MultivaluedHashMap toLdapConfig(Map ldapConfig) { + MultivaluedHashMap config = new MultivaluedHashMap<>(); + for (Map.Entry 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 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); + + // 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 void 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")); + } + + + // 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 = getComponentByName(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 = getComponentByName(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 getComponentByName(RealmModel realm, ComponentModel providerModel, String name) { + List 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 = getComponentByName(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 allUsers = ldapQuery.getResultList(); + + for (LDAPObject ldapUser : allUsers) { + ldapStore.remove(ldapUser); + } + } + + public static void removeAllLDAPRoles(KeycloakSession session, RealmModel appRealm, ComponentModel ldapModel, String mapperName) { + ComponentModel mapperModel = getComponentByName(appRealm, ldapModel, mapperName); + LDAPStorageProvider ldapProvider = LDAPTestUtils.getLdapProvider(session, ldapModel); + LDAPQuery roleQuery = getRoleMapper(mapperModel, ldapProvider, appRealm).createRoleQuery(); + List 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 = getComponentByName(appRealm, ldapModel, mapperName); + LDAPStorageProvider ldapProvider = LDAPTestUtils.getLdapProvider(session, ldapModel); + LDAPQuery roleQuery = getGroupMapper(mapperModel, ldapProvider, appRealm).createGroupQuery(); + List 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 = getComponentByName(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 = getComponentByName(appRealm, ldapModel, "groupsMapper"); + LDAPStorageProvider ldapProvider = LDAPTestUtils.getLdapProvider(session, ldapModel); + + Map> additAttrs = new HashMap<>(); + for (int i=0 ; i fedProviders = realm.getUserFederationProviders(); - Assert.assertTrue(fedProviders.size() == 2); - UserFederationProviderModel ldap1 = fedProviders.get(0); - Assert.assertEquals("MyLDAPProvider1", ldap1.getDisplayName()); - Assert.assertEquals("ldap", ldap1.getProviderName()); + Assert.assertTrue(fedProviders.size() == 0); + List storageProviders = realm.getUserStorageProviders(); + Assert.assertTrue(storageProviders.size() == 2); + UserStorageProviderModel ldap1 = storageProviders.get(0); + Assert.assertEquals("MyLDAPProvider1", ldap1.getName()); + Assert.assertEquals("ldap", ldap1.getProviderId()); Assert.assertEquals(1, ldap1.getPriority()); - Assert.assertEquals("ldap://foo", ldap1.getConfig().get(LDAPConstants.CONNECTION_URL)); + Assert.assertEquals("ldap://foo", ldap1.getConfig().getFirst(LDAPConstants.CONNECTION_URL)); - UserFederationProviderModel ldap2 = fedProviders.get(1); - Assert.assertEquals("MyLDAPProvider2", ldap2.getDisplayName()); - Assert.assertEquals("ldap://bar", ldap2.getConfig().get(LDAPConstants.CONNECTION_URL)); + UserStorageProviderModel ldap2 = storageProviders.get(1); + Assert.assertEquals("MyLDAPProvider2", ldap2.getName()); + Assert.assertEquals("ldap://bar", ldap2.getConfig().getFirst(LDAPConstants.CONNECTION_URL)); // Test federation mappers - Set fedMappers1 = realm.getUserFederationMappersByFederationProvider(ldap1.getId()); - Assert.assertTrue(fedMappers1.size() == 1); - UserFederationMapperModel fullNameMapper = fedMappers1.iterator().next(); + Set userFedMappers1 = realm.getUserFederationMappers(); + Assert.assertTrue(userFedMappers1.size() == 0); + List fedMappers1 = realm.getComponents(ldap1.getId()); + ComponentModel fullNameMapper = fedMappers1.iterator().next(); Assert.assertEquals("FullNameMapper", fullNameMapper.getName()); - Assert.assertEquals(FullNameLDAPFederationMapperFactory.PROVIDER_ID, fullNameMapper.getFederationMapperType()); - Assert.assertEquals(ldap1.getId(), fullNameMapper.getFederationProviderId()); - Assert.assertEquals("cn", fullNameMapper.getConfig().get(FullNameLDAPFederationMapper.LDAP_FULL_NAME_ATTRIBUTE)); - - // All builtin LDAP mappers should be here - Set fedMappers2 = realm.getUserFederationMappersByFederationProvider(ldap2.getId()); - Assert.assertTrue(fedMappers2.size() > 3); - Set allMappers = realm.getUserFederationMappers(); - Assert.assertEquals(allMappers.size(), fedMappers1.size() + fedMappers2.size()); + Assert.assertEquals(FullNameLDAPStorageMapperFactory.PROVIDER_ID, fullNameMapper.getProviderId()); + Assert.assertEquals(ldap1.getId(), fullNameMapper.getParentId()); + Assert.assertEquals("cn", fullNameMapper.getConfig().getFirst(FullNameLDAPStorageMapper.LDAP_FULL_NAME_ATTRIBUTE)); // Assert that federation link wasn't created during import UserFederationProviderFactory factory = (UserFederationProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(UserFederationProvider.class, "dummy"); diff --git a/testsuite/integration/src/test/resources/ldap/fed-provider-export.json b/testsuite/integration/src/test/resources/ldap/fed-provider-export.json new file mode 100644 index 0000000000..4bbe374dbb --- /dev/null +++ b/testsuite/integration/src/test/resources/ldap/fed-provider-export.json @@ -0,0 +1,622 @@ +{ + "id": "test", + "realm": "test", + "notBefore": 0, + "revokeRefreshToken": false, + "accessTokenLifespan": 300, + "accessTokenLifespanForImplicitFlow": 900, + "ssoSessionIdleTimeout": 1800, + "ssoSessionMaxLifespan": 36000, + "offlineSessionIdleTimeout": 2592000, + "accessCodeLifespan": 60, + "accessCodeLifespanUserAction": 300, + "accessCodeLifespanLogin": 1800, + "enabled": true, + "sslRequired": "external", + "registrationAllowed": true, + "registrationEmailAsUsername": false, + "rememberMe": false, + "verifyEmail": false, + "resetPasswordAllowed": true, + "editUsernameAllowed": true, + "bruteForceProtected": false, + "maxFailureWaitSeconds": 900, + "minimumQuickLoginWaitSeconds": 60, + "waitIncrementSeconds": 60, + "quickLoginCheckMilliSeconds": 1000, + "maxDeltaTimeSeconds": 43200, + "failureFactor": 30, + "groups": [ + { + "id": "2aa57ddd-e48f-4a62-bb8e-53ebe2ff1057", + "name": "topGroup", + "path": "/topGroup", + "attributes": { + "topAttribute": [ + "true" + ] + }, + "realmRoles": [ + "user" + ], + "clientRoles": {}, + "subGroups": [ + { + "id": "8e91afd4-b8e4-4de4-ba37-1edc7298d518", + "name": "level2group", + "path": "/topGroup/level2group", + "attributes": { + "level2Attribute": [ + "true" + ] + }, + "realmRoles": [ + "admin" + ], + "clientRoles": { + "test-app": [ + "customer-user" + ] + }, + "subGroups": [] + } + ] + } + ], + "defaultRoles": [ + "user", + "offline_access", + "uma_authorization" + ], + "requiredCredentials": [ + "password" + ], + "passwordPolicy": "hashIterations(20000)", + "otpPolicyType": "totp", + "otpPolicyAlgorithm": "HmacSHA1", + "otpPolicyInitialCounter": 0, + "otpPolicyDigits": 6, + "otpPolicyLookAheadWindow": 1, + "otpPolicyPeriod": 30, + "browserSecurityHeaders": { + "xContentTypeOptions": "nosniff", + "xFrameOptions": "SAMEORIGIN", + "contentSecurityPolicy": "frame-src 'self'" + }, + "smtpServer": { + "host": "localhost", + "from": "auto@keycloak.org", + "port": "3025" + }, + "userFederationProviders": [ + { + "id": "1fc3afd2-4c18-48dd-9055-b4bbae9229b7", + "displayName": "test-ldap", + "providerName": "ldap", + "config": { + "serverPrincipal": "HTTP/localhost@KEYCLOAK.ORG", + "debug": "true", + "pagination": "true", + "keyTab": "/Users/williamburke/jboss/keycloak/p1b-repo/keycloak/testsuite/integration/target/test-classes/kerberos/http.keytab", + "connectionPooling": "true", + "usersDn": "ou=People,dc=keycloak,dc=org", + "useKerberosForPasswordAuthentication": "false", + "kerberosRealm": "KEYCLOAK.ORG", + "bindCredential": "secret", + "bindDn": "uid=admin,ou=system", + "allowPasswordAuthentication": "true", + "vendor": "other", + "editMode": "WRITABLE", + "allowKerberosAuthentication": "false", + "connectionUrl": "ldap://localhost:10389", + "syncRegistrations": "true", + "baseDn": "dc=keycloak,dc=org", + "batchSizeForSync": "3", + "updateProfileFirstLogin": "true" + }, + "priority": 0, + "fullSyncPeriod": -1, + "changedSyncPeriod": -1, + "lastSync": 0 + } + ], + "userFederationMappers": [ + { + "id": "b2fc2d9c-2ea8-417f-96db-2565be62a646", + "name": "last name", + "federationProviderDisplayName": "test-ldap", + "federationMapperType": "user-attribute-ldap-mapper", + "config": { + "always.read.value.from.ldap": "true", + "read.only": "false", + "ldap.attribute": "sn", + "is.mandatory.in.ldap": "true", + "user.model.attribute": "lastName" + } + }, + { + "id": "6dc25318-dc20-4927-ba19-9293ab31aa28", + "name": "zipCodeMapper", + "federationProviderDisplayName": "test-ldap", + "federationMapperType": "user-attribute-ldap-mapper", + "config": { + "always.read.value.from.ldap": "false", + "read.only": "false", + "ldap.attribute": "postalCode", + "is.mandatory.in.ldap": "false", + "user.model.attribute": "postal_code" + } + }, + { + "id": "7afa12a2-f36e-4f87-b715-e941773c8534", + "name": "username", + "federationProviderDisplayName": "test-ldap", + "federationMapperType": "user-attribute-ldap-mapper", + "config": { + "always.read.value.from.ldap": "false", + "read.only": "false", + "ldap.attribute": "uid", + "is.mandatory.in.ldap": "true", + "user.model.attribute": "username" + } + }, + { + "id": "abfe054c-6d2a-4870-a239-1a312c3e5a94", + "name": "creation date", + "federationProviderDisplayName": "test-ldap", + "federationMapperType": "user-attribute-ldap-mapper", + "config": { + "always.read.value.from.ldap": "true", + "read.only": "true", + "ldap.attribute": "createTimestamp", + "is.mandatory.in.ldap": "false", + "user.model.attribute": "createTimestamp" + } + }, + { + "id": "6aef95e5-736e-4b1e-98d0-332f61f94ff9", + "name": "first name", + "federationProviderDisplayName": "test-ldap", + "federationMapperType": "user-attribute-ldap-mapper", + "config": { + "always.read.value.from.ldap": "true", + "read.only": "false", + "ldap.attribute": "cn", + "is.mandatory.in.ldap": "true", + "user.model.attribute": "firstName" + } + }, + { + "id": "0601e4a2-fd63-4f6a-ae3b-13cc6f4f4f1c", + "name": "email", + "federationProviderDisplayName": "test-ldap", + "federationMapperType": "user-attribute-ldap-mapper", + "config": { + "always.read.value.from.ldap": "false", + "read.only": "false", + "ldap.attribute": "mail", + "is.mandatory.in.ldap": "false", + "user.model.attribute": "email" + } + }, + { + "id": "fa308910-3be9-4bd8-8256-66cf04d8fcd2", + "name": "modify date", + "federationProviderDisplayName": "test-ldap", + "federationMapperType": "user-attribute-ldap-mapper", + "config": { + "always.read.value.from.ldap": "true", + "read.only": "true", + "ldap.attribute": "modifyTimestamp", + "is.mandatory.in.ldap": "false", + "user.model.attribute": "modifyTimestamp" + } + } + ], + "eventsEnabled": false, + "eventsListeners": [ + "jboss-logging" + ], + "enabledEventTypes": [], + "adminEventsEnabled": false, + "adminEventsDetailsEnabled": false, + "internationalizationEnabled": true, + "supportedLocales": [ + "de", + "en" + ], + "defaultLocale": "en", + "authenticationFlows": [ + { + "id": "b12463a9-5d33-4f27-b010-4005db77e602", + "alias": "Handle Existing Account", + "description": "Handle what to do if there is existing account with same email/username like authenticated identity provider", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-confirm-link", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "idp-email-verification", + "requirement": "ALTERNATIVE", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "requirement": "ALTERNATIVE", + "priority": 30, + "flowAlias": "Verify Existing Account by Re-authentication", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "c1684fc8-a99d-4e19-a795-478e4d793fb5", + "alias": "Verify Existing Account by Re-authentication", + "description": "Reauthentication of existing account", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-username-password-form", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "auth-otp-form", + "requirement": "OPTIONAL", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "09af30d8-8c2a-45a4-a2be-b7617e9d0185", + "alias": "browser", + "description": "browser based authentication", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "auth-cookie", + "requirement": "ALTERNATIVE", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "auth-spnego", + "requirement": "DISABLED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "identity-provider-redirector", + "requirement": "ALTERNATIVE", + "priority": 25, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "requirement": "ALTERNATIVE", + "priority": 30, + "flowAlias": "forms", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "6cdf31d0-9c91-4ea6-8e37-da6e8fa7544c", + "alias": "clients", + "description": "Base authentication for clients", + "providerId": "client-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "client-secret", + "requirement": "ALTERNATIVE", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "client-jwt", + "requirement": "ALTERNATIVE", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "c9a38de8-4c0c-496a-9936-b9753f73bfcc", + "alias": "direct grant", + "description": "OpenID Connect Resource Owner Grant", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "direct-grant-validate-username", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "direct-grant-validate-password", + "requirement": "REQUIRED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "direct-grant-validate-otp", + "requirement": "OPTIONAL", + "priority": 30, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "3755e297-7907-4c14-8c5f-d77e2bfe4b5d", + "alias": "first broker login", + "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticatorConfig": "review profile config", + "authenticator": "idp-review-profile", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticatorConfig": "create unique user config", + "authenticator": "idp-create-user-if-unique", + "requirement": "ALTERNATIVE", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "requirement": "ALTERNATIVE", + "priority": 30, + "flowAlias": "Handle Existing Account", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "f35b2f00-3e84-4f2e-b48e-3e4159d88a06", + "alias": "forms", + "description": "Username, password, otp and other auth forms.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "auth-username-password-form", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "auth-otp-form", + "requirement": "OPTIONAL", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "441b4480-1ace-483a-bffb-f0cb6659fe32", + "alias": "registration", + "description": "registration flow", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "registration-page-form", + "requirement": "REQUIRED", + "priority": 10, + "flowAlias": "registration form", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "c7de2a37-29a1-471a-9b51-699a69032b00", + "alias": "registration form", + "description": "registration form", + "providerId": "form-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "registration-user-creation", + "requirement": "REQUIRED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "registration-profile-action", + "requirement": "REQUIRED", + "priority": 40, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "registration-password-action", + "requirement": "REQUIRED", + "priority": 50, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "registration-recaptcha-action", + "requirement": "DISABLED", + "priority": 60, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "d362be0a-df20-4ce7-9288-f8448e0c4647", + "alias": "reset credentials", + "description": "Reset credentials for a user if they forgot their password or something", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "reset-credentials-choose-user", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "reset-credential-email", + "requirement": "REQUIRED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "reset-password", + "requirement": "REQUIRED", + "priority": 30, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "reset-otp", + "requirement": "OPTIONAL", + "priority": 40, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "c2d7a1ae-57c9-4f3b-a4ce-55c3f0d9869f", + "alias": "saml ecp", + "description": "SAML ECP Profile Authentication Flow", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "http-basic-authenticator", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + } + ], + "authenticatorConfig": [ + { + "id": "a2490828-becb-435f-9c3c-318b3939bf64", + "alias": "create unique user config", + "config": { + "require.password.update.after.registration": "false" + } + }, + { + "id": "78421671-f733-4901-82bc-58bf50c43206", + "alias": "review profile config", + "config": { + "update.profile.on.first.login": "missing" + } + } + ], + "requiredActions": [ + { + "alias": "CONFIGURE_TOTP", + "name": "Configure OTP", + "providerId": "CONFIGURE_TOTP", + "enabled": true, + "defaultAction": false, + "config": {} + }, + { + "alias": "UPDATE_PASSWORD", + "name": "Update Password", + "providerId": "UPDATE_PASSWORD", + "enabled": true, + "defaultAction": false, + "config": {} + }, + { + "alias": "UPDATE_PROFILE", + "name": "Update Profile", + "providerId": "UPDATE_PROFILE", + "enabled": true, + "defaultAction": false, + "config": {} + }, + { + "alias": "VERIFY_EMAIL", + "name": "Verify Email", + "providerId": "VERIFY_EMAIL", + "enabled": true, + "defaultAction": false, + "config": {} + }, + { + "alias": "terms_and_conditions", + "name": "Terms and Conditions", + "providerId": "terms_and_conditions", + "enabled": false, + "defaultAction": false, + "config": {} + } + ], + "browserFlow": "browser", + "registrationFlow": "registration", + "directGrantFlow": "direct grant", + "resetCredentialsFlow": "reset credentials", + "clientAuthenticationFlow": "clients", + "attributes": { + "_browser_header.xFrameOptions": "SAMEORIGIN", + "failureFactor": "30", + "quickLoginCheckMilliSeconds": "1000", + "maxDeltaTimeSeconds": "43200", + "_browser_header.xContentTypeOptions": "nosniff", + "bruteForceProtected": "false", + "maxFailureWaitSeconds": "900", + "_browser_header.contentSecurityPolicy": "frame-src 'self'", + "minimumQuickLoginWaitSeconds": "60", + "waitIncrementSeconds": "60" + } +} \ No newline at end of file diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties index a0b0d787a3..6c696c9261 100644 --- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties +++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties @@ -1217,6 +1217,10 @@ credential-types=Credential Types manage-user-password=Manage Password disable-credentials=Disable Credentials credential-reset-actions=Credential Reset +ldap-mappers=LDAP Mappers +create-ldap-mapper=Create LDAP mapper + + diff --git a/themes/src/main/resources/theme/base/admin/resources/js/app.js b/themes/src/main/resources/theme/base/admin/resources/js/app.js index f936fcdbe6..e3b5cc5c06 100755 --- a/themes/src/main/resources/theme/base/admin/resources/js/app.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/app.js @@ -1469,6 +1469,26 @@ module.config([ '$routeProvider', function($routeProvider) { }, controller : 'RealmSessionStatsCtrl' }) + .when('/create/user-storage/:realm/providers/ldap', { + templateUrl : resourceUrl + '/partials/user-storage-ldap.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + instance : function() { + return { + + }; + }, + providerId : function($route) { + return $route.current.params.provider; + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + } + }, + controller : 'LDAPUserStorageCtrl' + }) .when('/create/user-storage/:realm/providers/:provider', { templateUrl : resourceUrl + '/partials/user-storage-generic.html', resolve : { @@ -1489,6 +1509,24 @@ module.config([ '$routeProvider', function($routeProvider) { }, controller : 'GenericUserStorageCtrl' }) + .when('/realms/:realm/user-storage/providers/ldap/:componentId', { + templateUrl : resourceUrl + '/partials/user-storage-ldap.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + instance : function(ComponentLoader) { + return ComponentLoader(); + }, + providerId : function($route) { + return $route.current.params.provider; + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + } + }, + controller : 'LDAPUserStorageCtrl' + }) .when('/realms/:realm/user-storage/providers/:provider/:componentId', { templateUrl : resourceUrl + '/partials/user-storage-generic.html', resolve : { @@ -1507,6 +1545,60 @@ module.config([ '$routeProvider', function($routeProvider) { }, controller : 'GenericUserStorageCtrl' }) + .when('/realms/:realm/ldap-mappers/:componentId', { + templateUrl : function(params){ return resourceUrl + '/partials/user-storage-ldap-mappers.html'; }, + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + provider : function(ComponentLoader) { + return ComponentLoader(); + }, + mappers : function(ComponentsLoader, $route) { + return ComponentsLoader.loadComponents($route.current.params.componentId, 'org.keycloak.storage.ldap.mappers.LDAPStorageMapper'); + } + }, + controller : 'LDAPMapperListCtrl' + }) + .when('/create/ldap-mappers/:realm/:componentId', { + templateUrl : function(params){ return resourceUrl + '/partials/user-storage-ldap-mapper-detail.html'; }, + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + provider : function(ComponentLoader) { + return ComponentLoader(); + }, + mapperTypes : function(SubComponentTypesLoader, $route) { + return SubComponentTypesLoader.loadComponents($route.current.params.componentId, 'org.keycloak.storage.ldap.mappers.LDAPStorageMapper'); + }, + clients : function(ClientListLoader) { + return ClientListLoader(); + } + }, + controller : 'LDAPMapperCreateCtrl' + }) + .when('/realms/:realm/ldap-mappers/:componentId/mappers/:mapperId', { + templateUrl : function(params){ return resourceUrl + '/partials/user-storage-ldap-mapper-detail.html'; }, + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + provider : function(ComponentLoader) { + return ComponentLoader(); + }, + mapperTypes : function(SubComponentTypesLoader, $route) { + return SubComponentTypesLoader.loadComponents($route.current.params.componentId, 'org.keycloak.storage.ldap.mappers.LDAPStorageMapper'); + }, + mapper : function(LDAPMapperLoader) { + return LDAPMapperLoader(); + }, + clients : function(ClientListLoader) { + return ClientListLoader(); + } + }, + controller : 'LDAPMapperCtrl' + }) .when('/realms/:realm/user-federation', { templateUrl : resourceUrl + '/partials/user-federation.html', resolve : { @@ -2420,6 +2512,15 @@ module.directive('kcTabsUserFederation', function () { } }); +module.directive('kcTabsLdap', function () { + return { + scope: true, + restrict: 'E', + replace: true, + templateUrl: resourceUrl + '/templates/kc-tabs-ldap.html' + } +}); + module.controller('RoleSelectorModalCtrl', function($scope, realm, config, configName, RealmRoles, Client, ClientRole, $modalInstance) { $scope.selectedRealmRole = { role: undefined diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js index 03addd9501..c3b7e1fb89 100755 --- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js @@ -627,12 +627,14 @@ module.controller('UserFederationCtrl', function($scope, $location, $route, real for (var i = 0; i < $scope.providers.length; i++) { $scope.providers[i].isUserFederationProvider = false; } + /* UserFederationProviders.query({realm: realm.realm}, function(data) { for (var i = 0; i < data.length; i++) { data[i].isUserFederationProvider = true; $scope.providers.push(data[i]); } }); + */ $scope.addProvider = function(provider) { console.log('Add provider: ' + provider.id); @@ -761,13 +763,13 @@ module.controller('GenericUserStorageCtrl', function($scope, $location, Notifica if (providerFactory.metadata.synchronizable) { instance.config['fullSyncPeriod'] = ['-1']; instance.config['changedSyncPeriod'] = ['-1']; - instance.config['cachePolicy'] = ['DEFAULT']; - instance.config['evictionDay'] = ['']; - instance.config['evictionHour'] = ['']; - instance.config['evictionMinute'] = ['']; - instance.config['maxLifespan'] = ['']; } + instance.config['cachePolicy'] = ['DEFAULT']; + instance.config['evictionDay'] = ['']; + instance.config['evictionHour'] = ['']; + instance.config['evictionMinute'] = ['']; + instance.config['maxLifespan'] = ['']; if (providerFactory.properties) { for (var i = 0; i < providerFactory.properties.length; i++) { @@ -816,17 +818,10 @@ module.controller('GenericUserStorageCtrl', function($scope, $location, Notifica instance.config['maxLifespan'] = ['']; } - - /* - console.log('Manage instance'); - console.log(instance.name); - console.log(instance.providerId); - console.log(instance.providerType); - console.log(instance.parentId); - for (var k in instance.config) { - console.log('config[' + k + "] ="); + if (!instance.config['priority']) { + instance.config['priority'] = ['0']; } - */ + } if (providerFactory.metadata.synchronizable) { if (instance.config && instance.config['importEnabled']) { @@ -1560,4 +1555,498 @@ module.controller('UserGroupMembershipCtrl', function($scope, $route, realm, gro }); +module.controller('LDAPUserStorageCtrl', function($scope, $location, Notifications, $route, Dialog, realm, + serverInfo, instance, Components, UserStorageSync, RealmLDAPConnectionTester) { + console.log('LDAPUserStorageCtrl'); + var providerId = 'ldap'; + console.log('providerId: ' + providerId); + $scope.create = !instance.providerId; + console.log('create: ' + $scope.create); + var providers = serverInfo.componentTypes['org.keycloak.storage.UserStorageProvider']; + console.log('providers length ' + providers.length); + var providerFactory = null; + for (var i = 0; i < providers.length; i++) { + var p = providers[i]; + console.log('provider: ' + p.id); + if (p.id == providerId) { + $scope.providerFactory = p; + providerFactory = p; + break; + } + + } + + $scope.provider = instance; + $scope.showSync = false; + + $scope.ldapVendors = [ + { "id": "ad", "name": "Active Directory" }, + { "id": "rhds", "name": "Red Hat Directory Server" }, + { "id": "tivoli", "name": "Tivoli" }, + { "id": "edirectory", "name": "Novell eDirectory" }, + { "id": "other", "name": "Other" } + ]; + + $scope.authTypes = [ + { "id": "none", "name": "none" }, + { "id": "simple", "name": "simple" } + ]; + + $scope.searchScopes = [ + { "id": "1", "name": "One Level" }, + { "id": "2", "name": "Subtree" } + ]; + + $scope.useTruststoreOptions = [ + { "id": "always", "name": "Always" }, + { "id": "ldapsOnly", "name": "Only for ldaps" }, + { "id": "never", "name": "Never" } + ]; + + var DEFAULT_BATCH_SIZE = "1000"; + + + console.log("providerFactory: " + providerFactory.id); + + function initUserStorageSettings() { + if ($scope.create) { + instance.name = 'ldap'; + instance.providerId = 'ldap'; + instance.providerType = 'org.keycloak.storage.UserStorageProvider'; + instance.parentId = realm.id; + instance.config = { + + }; + instance.config['priority'] = ["0"]; + + $scope.fullSyncEnabled = false; + $scope.changedSyncEnabled = false; + instance.config['fullSyncPeriod'] = ['-1']; + instance.config['changedSyncPeriod'] = ['-1']; + instance.config['cachePolicy'] = ['DEFAULT']; + instance.config['evictionDay'] = ['']; + instance.config['evictionHour'] = ['']; + instance.config['evictionMinute'] = ['']; + instance.config['maxLifespan'] = ['']; + instance.config['batchSizeForSync'] = [DEFAULT_BATCH_SIZE]; + + if (providerFactory.properties) { + + for (var i = 0; i < providerFactory.properties.length; i++) { + var configProperty = providerFactory.properties[i]; + if (configProperty.defaultValue) { + instance.config[configProperty.name] = [configProperty.defaultValue]; + } else { + instance.config[configProperty.name] = ['']; + } + + } + } + + + } else { + $scope.fullSyncEnabled = (instance.config['fullSyncPeriod'] && instance.config['fullSyncPeriod'][0] > 0); + $scope.changedSyncEnabled = (instance.config['changedSyncPeriod'] && instance.config['changedSyncPeriod'][0]> 0); + if (!instance.config['fullSyncPeriod']) { + console.log('setting to -1'); + instance.config['fullSyncPeriod'] = ['-1']; + + } + if (!instance.config['changedSyncPeriod']) { + console.log('setting to -1'); + instance.config['changedSyncPeriod'] = ['-1']; + + } + if (!instance.config['cachePolicy']) { + instance.config['cachePolicy'] = ['DEFAULT']; + + } + if (!instance.config['evictionDay']) { + instance.config['evictionDay'] = ['']; + + } + if (!instance.config['evictionHour']) { + instance.config['evictionHour'] = ['']; + + } + if (!instance.config['evictionMinute']) { + instance.config['evictionMinute'] = ['']; + + } + if (!instance.config['maxLifespan']) { + instance.config['maxLifespan'] = ['']; + + } + if (!instance.config['priority']) { + instance.config['priority'] = ['0']; + } + + if (providerFactory.properties) { + + for (var i = 0; i < providerFactory.properties.length; i++) { + var configProperty = providerFactory.properties[i]; + if (!instance.config[configProperty.name]) { + if (configProperty.defaultValue) { + instance.config[configProperty.name] = [configProperty.defaultValue]; + } else { + instance.config[configProperty.name] = ['']; + } + } + + } + } + + for (var i=0 ; i<$scope.ldapVendors.length ; i++) { + if ($scope.ldapVendors[i].id === instance.config['vendor'][0]) { + $scope.vendorName = $scope.ldapVendors[i].name; + } + }; + + + + } + if (instance.config && instance.config['importEnabled']) { + $scope.showSync = instance.config['importEnabled'][0] == 'true'; + } else { + $scope.showSync = true; + } + + $scope.changed = false; + } + + initUserStorageSettings(); + $scope.instance = angular.copy(instance); + $scope.realm = realm; + + $scope.$watch('instance', function() { + if (!angular.equals($scope.instance, instance)) { + $scope.changed = true; + } + + if (!angular.equals($scope.instance.config['vendor'][0], $scope.lastVendor)) { + console.log("LDAP vendor changed"); + $scope.lastVendor = $scope.instance.config['vendor'][0]; + + if ($scope.lastVendor === "ad") { + $scope.instance.config['usernameLDAPAttribute'][0] = "cn"; + $scope.instance.config['userObjectClasses'][0] = "person, organizationalPerson, user"; + } else { + $scope.instance.config['usernameLDAPAttribute'][0] = "uid"; + $scope.instance.config['userObjectClasses'][0] = "inetOrgPerson, organizationalPerson"; + } + + $scope.instance.config['rdnLDAPAttribute'][0] = $scope.instance.config['usernameLDAPAttribute'][0]; + + var vendorToUUID = { + rhds: "nsuniqueid", + tivoli: "uniqueidentifier", + edirectory: "guid", + ad: "objectGUID", + other: "entryUUID" + }; + $scope.instance.config['uuidLDAPAttribute'][0] = vendorToUUID[$scope.lastVendor]; + } + + + }, true); + + $scope.$watch('fullSyncEnabled', function(newVal, oldVal) { + if (oldVal == newVal) { + return; + } + + $scope.instance.config['fullSyncPeriod'][0] = $scope.fullSyncEnabled ? "604800" : "-1"; + $scope.changed = true; + }); + + $scope.$watch('changedSyncEnabled', function(newVal, oldVal) { + if (oldVal == newVal) { + return; + } + + $scope.instance.config['changedSyncPeriod'][0] = $scope.changedSyncEnabled ? "86400" : "-1"; + $scope.changed = true; + }); + + + $scope.save = function() { + $scope.changed = false; + if (!parseInt($scope.instance.config['batchSizeForSync'[0]])) { + $scope.instance.config['batchSizeForSync'][0] = DEFAULT_BATCH_SIZE; + } else { + $scope.instance.config['batchSizeForSync'][0] = parseInt($scope.instance.config.batchSizeForSync).toString(); + } + + if ($scope.create) { + Components.save({realm: realm.realm}, $scope.instance, function (data, headers) { + var l = headers().location; + var id = l.substring(l.lastIndexOf("/") + 1); + + $location.url("/realms/" + realm.realm + "/user-storage/providers/" + $scope.instance.providerId + "/" + id); + Notifications.success("The provider has been created."); + }, function (errorResponse) { + if (errorResponse.data && errorResponse.data['error_description']) { + Notifications.error(errorResponse.data['error_description']); + } + }); + } else { + Components.update({realm: realm.realm, + componentId: instance.id + }, + $scope.instance, function () { + $route.reload(); + Notifications.success("The provider has been updated."); + }, function (errorResponse) { + if (errorResponse.data && errorResponse.data['error_description']) { + Notifications.error(errorResponse.data['error_description']); + } + }); + } + }; + + $scope.reset = function() { + initUserStorageSettings(); + $scope.instance = angular.copy(instance); + }; + + $scope.cancel = function() { + if ($scope.create) { + $location.url("/realms/" + realm.realm + "/user-storage"); + } else { + $route.reload(); + } + }; + + $scope.triggerFullSync = function() { + console.log('GenericCtrl: triggerFullSync'); + triggerSync('triggerFullSync'); + } + + $scope.triggerChangedUsersSync = function() { + console.log('GenericCtrl: triggerChangedUsersSync'); + triggerSync('triggerChangedUsersSync'); + } + + function triggerSync(action) { + UserStorageSync.save({ action: action, realm: $scope.realm.realm, componentId: $scope.instance.id }, {}, function(syncResult) { + $route.reload(); + Notifications.success("Sync of users finished successfully. " + syncResult.status); + }, function() { + $route.reload(); + Notifications.error("Error during sync of users"); + }); + } + + var initConnectionTest = function(testAction, ldapConfig) { + return { + action: testAction, + realm: $scope.realm.realm, + connectionUrl: ldapConfig.connectionUrl, + bindDn: ldapConfig.bindDn, + bindCredential: ldapConfig.bindCredential, + useTruststoreSpi: ldapConfig.useTruststoreSpi + }; + }; + + $scope.testConnection = function() { + console.log('LDAPCtrl: testConnection'); + RealmLDAPConnectionTester.get(initConnectionTest("testConnection", $scope.instance.config), function() { + Notifications.success("LDAP connection successful."); + }, function() { + Notifications.error("Error when trying to connect to LDAP. See server.log for details."); + }); + } + + $scope.testAuthentication = function() { + console.log('LDAPCtrl: testAuthentication'); + RealmLDAPConnectionTester.get(initConnectionTest("testAuthentication", $scope.instance.config), function() { + Notifications.success("LDAP authentication successful."); + }, function() { + Notifications.error("LDAP authentication failed. See server.log for details"); + }); + } + + + +}); + +module.controller('LDAPTabCtrl', function(Dialog, $scope, Current, Notifications, $location) { + $scope.removeUserFederation = function() { + Dialog.confirmDelete($scope.instance.name, 'ldap provider', function() { + $scope.instance.$remove({ + realm : Current.realm.realm, + componentId : $scope.instance.id + }, function() { + $location.url("/realms/" + Current.realm.realm + "/user-federation"); + Notifications.success("The provider has been deleted."); + }); + }); + }; +}); + + +module.controller('LDAPMapperListCtrl', function($scope, $location, Notifications, $route, Dialog, realm, provider, mappers) { + console.log('LDAPMapperListCtrl'); + + $scope.realm = realm; + $scope.provider = provider; + $scope.instance = provider; + + $scope.mappers = mappers; + +}); + +module.controller('LDAPMapperCtrl', function($scope, $route, realm, provider, mapperTypes, mapper, clients, Components, LDAPMapperSync, Notifications, Dialog, $location) { + console.log('LDAPMapperCtrl'); + $scope.realm = realm; + $scope.provider = provider; + $scope.clients = clients; + $scope.create = false; + $scope.changed = false; + + for (var i = 0; i < mapperTypes.length; i++) { + console.log('mapper.providerId: ' + mapper.providerId); + console.log('mapperTypes[i].id ' + mapperTypes[i].id); + if (mapperTypes[i].id == mapper.providerId) { + $scope.mapperType = mapperTypes[i]; + break; + } + } + + if ($scope.mapperType.properties) { + + for (var i = 0; i < $scope.mapperType.properties.length; i++) { + var configProperty = $scope.mapperType.properties[i]; + if (!mapper.config[configProperty.name]) { + if (configProperty.defaultValue) { + mapper.config[configProperty.name] = [configProperty.defaultValue]; + } else { + mapper.config[configProperty.name] = ['']; + } + } + + } + } + $scope.mapper = angular.copy(mapper); + + + $scope.$watch('mapper', function() { + if (!angular.equals($scope.mapper, mapper)) { + $scope.changed = true; + } + }, true); + + $scope.save = function() { + Components.update({realm: realm.realm, + componentId: mapper.id + }, + $scope.mapper, function () { + $route.reload(); + Notifications.success("The mapper has been updated."); + }, function (errorResponse) { + if (errorResponse.data && errorResponse.data['error_description']) { + Notifications.error(errorResponse.data['error_description']); + } + }); + }; + + $scope.reset = function() { + $scope.mapper = angular.copy(mapper); + $scope.changed = false; + }; + + $scope.remove = function() { + Dialog.confirmDelete($scope.mapper.name, 'ldap mapper', function() { + Components.remove({ + realm : realm.realm, + componentId : mapper.id + }, function() { + $location.url("/realms/" + realm.realm + '/ldap-mappers/' + provider.id); + Notifications.success("The provider has been deleted."); + }); + }); + }; + + $scope.triggerFedToKeycloakSync = function() { + triggerMapperSync("fedToKeycloak") + } + + $scope.triggerKeycloakToFedSync = function() { + triggerMapperSync("keycloakToFed"); + } + + function triggerMapperSync(direction) { + LDAPMapperSync.save({ direction: direction, realm: realm.realm, parentId: provider.id, mapperId : $scope.mapper.id }, {}, function(syncResult) { + Notifications.success("Data synced successfully. " + syncResult.status); + }, function(error) { + Notifications.error(error.data.errorMessage); + }); + } + +}); + +module.controller('LDAPMapperCreateCtrl', function($scope, realm, provider, mapperTypes, clients, Components, Notifications, Dialog, $location) { + console.log('LDAPMapperCreateCtrl'); + $scope.realm = realm; + $scope.provider = provider; + $scope.clients = clients; + $scope.create = true; + $scope.mapper = { config: {}}; + $scope.mapperTypes = mapperTypes; + $scope.mapperType = null; + $scope.changed = true; + + $scope.$watch('mapperType', function() { + if ($scope.mapperType != null) { + $scope.mapper.config = {}; + if ($scope.mapperType.properties) { + + for (var i = 0; i < $scope.mapperType.properties.length; i++) { + var configProperty = $scope.mapperType.properties[i]; + if (!$scope.mapper.config[configProperty.name]) { + if (configProperty.defaultValue) { + $scope.mapper.config[configProperty.name] = [configProperty.defaultValue]; + } else { + $scope.mapper.config[configProperty.name] = ['']; + } + } + + } + } + } + }, true); + + $scope.save = function() { + if ($scope.mapperType == null) { + Notifications.error("You need to select mapper type!"); + return; + } + + $scope.mapper.providerId = $scope.mapperType.id; + $scope.mapper.providerType = 'org.keycloak.storage.ldap.mappers.LDAPStorageMapper'; + $scope.mapper.parentId = provider.id; + + Components.save({realm: realm.realm}, $scope.mapper, function (data, headers) { + var l = headers().location; + var id = l.substring(l.lastIndexOf("/") + 1); + + $location.url("/realms/" + realm.realm + "/ldap-mappers/" + $scope.mapper.parentId + "/mappers/" + id); + Notifications.success("The mapper has been created."); + }, function (errorResponse) { + if (errorResponse.data && errorResponse.data['error_description']) { + Notifications.error(errorResponse.data['error_description']); + } + }); + }; + + $scope.reset = function() { + $location.url("/realms/" + realm.realm + '/ldap-mappers/' + provider.id); + }; + + +}); + + + + diff --git a/themes/src/main/resources/theme/base/admin/resources/js/loaders.js b/themes/src/main/resources/theme/base/admin/resources/js/loaders.js index 78fb99fb0c..6d8c3faca9 100755 --- a/themes/src/main/resources/theme/base/admin/resources/js/loaders.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/loaders.js @@ -143,6 +143,15 @@ module.factory('ComponentLoader', function(Loader, Components, $route, $q) { }); }); +module.factory('LDAPMapperLoader', function(Loader, Components, $route, $q) { + return Loader.get(Components, function() { + return { + realm : $route.current.params.realm, + componentId: $route.current.params.mapperId + } + }); +}); + module.factory('ComponentsLoader', function(Loader, Components, $route, $q) { var componentsLoader = {}; @@ -159,6 +168,22 @@ module.factory('ComponentsLoader', function(Loader, Components, $route, $q) { return componentsLoader; }); +module.factory('SubComponentTypesLoader', function(Loader, SubComponentTypes, $route, $q) { + var componentsLoader = {}; + + componentsLoader.loadComponents = function(parent, componentType) { + return Loader.query(SubComponentTypes, function() { + return { + realm : $route.current.params.realm, + componentId : parent, + type: componentType + } + })(); + }; + + return componentsLoader; +}); + module.factory('UserFederationInstanceLoader', function(Loader, UserFederationInstances, $route, $q) { return Loader.get(UserFederationInstances, function() { return { diff --git a/themes/src/main/resources/theme/base/admin/resources/js/services.js b/themes/src/main/resources/theme/base/admin/resources/js/services.js index 5b4438245a..abacb079c2 100755 --- a/themes/src/main/resources/theme/base/admin/resources/js/services.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/services.js @@ -1699,6 +1699,13 @@ module.factory('DefaultGroups', function($resource) { }); }); +module.factory('SubComponentTypes', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/components/:componentId/sub-component-types', { + realm: '@realm', + componentId: '@componentId' + }); +}); + module.factory('Components', function($resource, ComponentUtils) { return $resource(authUrl + '/admin/realms/:realm/components/:componentId', { realm : '@realm', @@ -1742,4 +1749,14 @@ module.factory('ClientRegistrationPolicyProviders', function($resource) { }); }); +module.factory('LDAPMapperSync', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/user-storage/:parentId/mappers/:mapperId/sync', { + realm : '@realm', + componentId : '@componentId', + mapperId: '@mapperId' + }); +}); + + + diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/user-credentials.html b/themes/src/main/resources/theme/base/admin/resources/partials/user-credentials.html index ccfb203881..1b545f74fa 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/user-credentials.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/user-credentials.html @@ -62,7 +62,7 @@ {{:: 'credentials.disable.tooltip' | translate}} - +
{{:: 'credential-reset-actions' | translate}}
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/user-storage-ldap-mapper-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/user-storage-ldap-mapper-detail.html new file mode 100644 index 0000000000..72cfea43a9 --- /dev/null +++ b/themes/src/main/resources/theme/base/admin/resources/partials/user-storage-ldap-mapper-detail.html @@ -0,0 +1,64 @@ +
+ + +

{{mapper.name|capitalize}}

+

{{:: 'add-user-federation-mapper' | translate}}

+ +
+
+
+ +
+ +
+
+
+ +
+ +
+ {{:: 'mapper.name.tooltip' | translate}} +
+
+ +
+
+ +
+
+ {{mapperType.helpText}} +
+
+ +
+ +
+ {{mapperType.helpText}} +
+ + + +
+ +
+
+ + + + +
+
+
+
+ + \ No newline at end of file diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/user-storage-ldap-mappers.html b/themes/src/main/resources/theme/base/admin/resources/partials/user-storage-ldap-mappers.html new file mode 100644 index 0000000000..794e83e567 --- /dev/null +++ b/themes/src/main/resources/theme/base/admin/resources/partials/user-storage-ldap-mappers.html @@ -0,0 +1,46 @@ +
+ + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+ +
+
+
+ +
+
{{:: 'name' | translate}}{{:: 'type' | translate}}
{{mapper.name}}{{mapper.providerId}}
{{:: 'no-mappers-available' | translate}}
+
+ + \ No newline at end of file diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/user-storage-ldap.html b/themes/src/main/resources/theme/base/admin/resources/partials/user-storage-ldap.html new file mode 100755 index 0000000000..060d7d70ac --- /dev/null +++ b/themes/src/main/resources/theme/base/admin/resources/partials/user-storage-ldap.html @@ -0,0 +1,449 @@ +
+ + + + +
+ + + +
+ {{:: 'required-settings' | translate}} +
+ +
+ +
+
+
+ +
+ +
+ {{:: 'console-display-name.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'priority.tooltip' | translate}} +
+ +
+ +
+
+ +
+
+ {{:: 'ldap.edit-mode.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'ldap.sync-registrations.tooltip' | translate}} +
+
+ +
+
+ +
+
+ +
+
+ {{:: 'ldap.vendor.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'username-ldap-attribute.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'rdn-ldap-attribute.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'uuid-ldap-attribute.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'ldap.user-object-classes.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'ldap.connection-url.tooltip' | translate}} + +
+
+ +
+ +
+ {{:: 'ldap.users-dn.tooltip' | translate}} +
+
+ +
+
+ +
+
+ {{:: 'ldap.authentication-type.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'ldap.bind-dn.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'ldap.bind-credential.tooltip' | translate}} + +
+
+ +
+ +
+ {{:: 'ldap.custom-user-ldap-filter.tooltip' | translate}} +
+
+ +
+
+ +
+
+ {{:: 'ldap.search-scope.tooltip' | translate}} +
+
+ +
+
+ +
+
+ {{:: 'ldap.use-truststore-spi.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'ldap.connection-pooling.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'ldap.pagination.tooltip' | translate}} +
+
+ +
+ {{:: 'kerberos-integration' | translate}} +
+ +
+ +
+ {{:: 'ldap.allow-kerberos-authentication.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'kerberos-realm.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'server-principal.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'keytab.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'debug.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'ldap.use-kerberos-for-password-authentication.tooltip' | translate}} +
+
+ +
+ {{:: 'sync-settings' | translate}} +
+ +
+ +
+ {{:: 'ldap.batch-size.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'ldap.periodic-full-sync.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'full-sync-period.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'ldap.periodic-changed-users-sync.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'ldap.changed-users-sync-period.tooltip' | translate}} +
+
+ +
+ {{:: 'user-storage-cache-policy' | translate}} +
+ +
+
+ +
+
+ {{:: 'userStorage.cachePolicy.tooltip' | translate}} +
+
+ +
+
+ +
+
+ {{:: 'userStorage.cachePolicy.evictionDay.tooltip' | translate}} +
+
+ +
+
+ +
+
+ {{:: 'userStorage.cachePolicy.evictionHour.tooltip' | translate}} +
+
+ +
+
+ +
+
+ {{:: 'userStorage.cachePolicy.evictionMinute.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'userStorage.cachePolicy.maxLifespan.tooltip' | translate}} +
+
+ + +
+
+ + +
+
+ +
+
+ + + + +
+
+
+
+ + \ No newline at end of file diff --git a/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-ldap.html b/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-ldap.html new file mode 100644 index 0000000000..089a65f442 --- /dev/null +++ b/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-ldap.html @@ -0,0 +1,12 @@ +
+

+ {{instance.displayName|capitalize}} + +

+

{{:: 'add-user-federation-provider' | translate}}

+ + +