Merge pull request #3501 from patriot1burke/master

LDAP port to UserStorageSPI
This commit is contained in:
Bill Burke 2016-11-14 17:24:39 -05:00 committed by GitHub
commit 94afba91a0
146 changed files with 6265 additions and 2984 deletions

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class StorageProviderRepresentation {
private String id;
private String displayName;
private String providerName;
private Map<String, String> 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<String, String> getConfig() {
return config;
}
public void setConfig(Map<String, String> 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();
}
}

View file

@ -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<String, String> getConfig() {

View file

@ -26,9 +26,14 @@
<modelVersion>4.0.0</modelVersion>
<artifactId>keycloak-ldap-federation</artifactId>
<name>Keycloak LDAP Federation</name>
<name>Keycloak LDAP UserStoreProvider</name>
<description />
<properties>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source>
</properties>
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @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<String> 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<UserFederationMapperModel> 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<LDAPObject> 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<LDAPObject> 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<LDAPObject> 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<UserFederationMapperModel> federationMappers = currentRealm.getUserFederationMappersByFederationProvider(fedModel.getId());
List<UserFederationMapperModel> 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);
}
}

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class FullNameLDAPFederationMapperFactory extends AbstractLDAPFederationMapperFactory {
public static final String PROVIDER_ID = "full-name-ldap-mapper";
protected static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
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<ProviderConfigProperty> getConfigProperties() {
return configProperties;
}
@Override
public Map<String, String> getDefaultConfig(UserFederationProviderModel providerModel) {
Map<String, String> 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);
}
}

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class UserAttributeLDAPFederationMapperFactory extends AbstractLDAPFederationMapperFactory {
public static final String PROVIDER_ID = "user-attribute-ldap-mapper";
protected static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
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<ProviderConfigProperty> getConfigProperties() {
return configProperties;
}
@Override
public Map<String, String> getDefaultConfig(UserFederationProviderModel providerModel) {
Map<String, String> 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);
}
}

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class GroupLDAPFederationMapperFactory extends AbstractLDAPFederationMapperFactory {
public static final String PROVIDER_ID = "group-ldap-mapper";
protected static final List<ProviderConfigProperty> configProperties = new ArrayList<>();
protected static final Map<String, UserRolesRetrieveStrategy> 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<String> 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<String> 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<String> 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<ProviderConfigProperty> getConfigProperties() {
return configProperties;
}
@Override
public Map<String, String> getDefaultConfig(UserFederationProviderModel providerModel) {
Map<String, String> 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);
}
}

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class RoleLDAPFederationMapperFactory extends AbstractLDAPFederationMapperFactory {
public static final String PROVIDER_ID = "role-ldap-mapper";
protected static final List<ProviderConfigProperty> configProperties = new ArrayList<>();
protected static final Map<String, UserRolesRetrieveStrategy> 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<String> 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<String> 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<String> 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<ProviderConfigProperty> getConfigProperties() {
return configProperties;
}
@Override
public Map<String, String> getDefaultConfig(UserFederationProviderModel providerModel) {
Map<String, String> 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);
}
}

View file

@ -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<String, String> config;
private final MultivaluedHashMap<String, String> config;
public LDAPConfig(Map<String, String> config) {
public LDAPConfig(MultivaluedHashMap<String, String> 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<String> 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);
}
}
}

View file

@ -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<String, LDAPIdentityStoreContext> ldapStores = new ConcurrentHashMap<String, LDAPIdentityStoreContext>();
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<String, String> config = model.getConfig();
MultivaluedHashMap<String, String> 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<String, String> ldapConfig) {
Map<String, String> copy = new HashMap<String, String>(ldapConfig);
private void logLDAPConfig(String fedProviderDisplayName, MultivaluedHashMap<String, String> ldapConfig) {
MultivaluedHashMap<String, String> copy = new MultivaluedHashMap<String, String>(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<String,String> ldapConfig) {
public static LDAPIdentityStore createLdapIdentityStore(MultivaluedHashMap<String, String> 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<String,String> config, LDAPIdentityStore store) {
private LDAPIdentityStoreContext(MultivaluedHashMap<String, String> config, LDAPIdentityStore store) {
this.config = config;
this.store = store;
}
private Map<String,String> config;
private MultivaluedHashMap<String, String> config;
private LDAPIdentityStore store;
}
}

View file

@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @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<String> 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<UserFederationMapperModel> federationMappers = realm.getUserFederationMappersByFederationProvider(model.getId());
List<UserFederationMapperModel> sortedMappers = sortMappersAsc(federationMappers);
for (UserFederationMapperModel mapperModel : sortedMappers) {
LDAPFederationMapper ldapMapper = getMapper(mapperModel);
List<ComponentModel> mappers = realm.getComponents(model.getId(), LDAPStorageMapper.class.getName());
List<ComponentModel> 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<String> getSupportedCredentialTypes() {
return new HashSet<String>(this.supportedCredentialTypes);
public boolean supportsCredentialAuthenticationFor(String type) {
return type.equals(CredentialModel.KERBEROS) && kerberosConfig.isAllowKerberosAuthentication();
}
@Override
public List<UserModel> 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<UserModel> searchByAttributes(Map<String, String> 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<UserModel> getUsers(RealmModel realm) {
return Collections.EMPTY_LIST;
}
@Override
public List<UserModel> getUsers(RealmModel realm, int firstResult, int maxResults) {
return Collections.EMPTY_LIST;
}
@Override
public List<UserModel> searchForUser(String search, RealmModel realm) {
return searchForUser(search, realm, 0, Integer.MAX_VALUE - 1);
}
@Override
public List<UserModel> searchForUser(String search, RealmModel realm, int firstResult, int maxResults) {
Map<String, String> attributes = new HashMap<String, String>();
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<UserModel> searchForUser(Map<String, String> params, RealmModel realm) {
return searchForUser(params, realm, 0, Integer.MAX_VALUE - 1);
}
@Override
public List<UserModel> searchForUser(Map<String, String> params, RealmModel realm, int firstResult, int maxResults) {
List<UserModel> searchResults =new LinkedList<UserModel>();
List<LDAPObject> ldapUsers = searchLDAP(realm, attributes, maxResults);
List<LDAPObject> 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<UserModel> getGroupMembers(RealmModel realm, GroupModel group) {
return getGroupMembers(realm, group, 0, Integer.MAX_VALUE - 1);
}
@Override
public List<UserModel> getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults) {
Set<UserFederationMapperModel> federationMappers = realm.getUserFederationMappersByFederationProvider(model.getId());
for (UserFederationMapperModel mapperModel : federationMappers) {
LDAPFederationMapper ldapMapper = getMapper(mapperModel);
List<ComponentModel> mappers = realm.getComponents(model.getId(), LDAPStorageMapper.class.getName());
List<ComponentModel> sortedMappers = sortMappersAsc(mappers);
for (ComponentModel mapperModel : sortedMappers) {
LDAPStorageMapper ldapMapper = getMapper(mapperModel);
List<UserModel> 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<LDAPObject> searchLDAP(RealmModel realm, Map<String, String> attributes, int maxResults) {
List<LDAPObject> results = new ArrayList<LDAPObject>();
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<LDAPObject> 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<UserFederationMapperModel> federationMappers = realm.getUserFederationMappersByFederationProvider(getModel().getId());
List<UserFederationMapperModel> sortedMappers = sortMappersDesc(federationMappers);
for (UserFederationMapperModel mapperModel : sortedMappers) {
List<ComponentModel> mappers = realm.getComponents(model.getId(), LDAPStorageMapper.class.getName());
List<ComponentModel> 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<UserFederationMapperModel> federationMappers = realm.getUserFederationMappersByFederationProvider(getModel().getId());
boolean processed = false;
for (UserFederationMapperModel mapperModel : federationMappers) {
LDAPFederationMapper ldapMapper = getMapper(mapperModel);
List<ComponentModel> mappers = realm.getComponents(model.getId(), LDAPStorageMapper.class.getName());
List<ComponentModel> 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<String> getSupportedCredentialTypes() {
return new HashSet<String>(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<UserFederationMapperModel> sortMappersAsc(Collection<UserFederationMapperModel> mappers) {
public List<ComponentModel> sortMappersAsc(Collection<ComponentModel> mappers) {
return LDAPMappersComparator.sortAsc(getLdapIdentityStore().getConfig(), mappers);
}
protected List<UserFederationMapperModel> sortMappersDesc(Collection<UserFederationMapperModel> mappers) {
protected List<ComponentModel> sortMappersDesc(Collection<ComponentModel> mappers) {
return LDAPMappersComparator.sortDesc(getLdapIdentityStore().getConfig(), mappers);
}
}

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class LDAPStorageProviderFactory implements UserStorageProviderFactory<LDAPStorageProvider>, 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<ProviderConfigProperty> configProperties;
static {
configProperties = getConfigProps(null);
}
private static List<ProviderConfigProperty> 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<ProviderConfigProperty> 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<ComponentModel> 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<LDAPObject> 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<LDAPObject> 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<LDAPObject> 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<ComponentModel> federationMappers = currentRealm.getComponents(fedModel.getId(), LDAPStorageMapper.class.getName());
List<ComponentModel> 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);
}
}

View file

@ -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<UserFederationMapperModel> federationMappers = realm.getUserFederationMappersByFederationProvider(ldapProvider.getModel().getId());
List<UserFederationMapperModel> sortedMappers = ldapProvider.sortMappersAsc(federationMappers);
for (UserFederationMapperModel mapperModel : sortedMappers) {
LDAPFederationMapper ldapMapper = ldapProvider.getMapper(mapperModel);
List<ComponentModel> federationMappers = realm.getComponents(ldapProvider.getModel().getId(), LDAPStorageMapper.class.getName());
List<ComponentModel> 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<UserFederationMapperModel> mapperModels = realm.getUserFederationMappersByFederationProvider(ldapProvider.getModel().getId());
List<ComponentModel> 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<String> objectClasses,
public static LDAPObject createLDAPGroup(LDAPStorageProvider ldapProvider, String groupName, String groupNameAttribute, Collection<String> objectClasses,
String parentDn, Map<String, Set<String>> 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<String> 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<String> memberships = getExistingMemberships(memberAttrName, ldapParent);
String userMembership = getMemberValueOfChildObject(ldapChild, membershipType);
@ -238,7 +239,7 @@ public class LDAPUtils {
* @param ldapProvider
* @return
*/
public static List<LDAPObject> loadAllLDAPObjects(LDAPQuery ldapQuery, LDAPFederationProvider ldapProvider) {
public static List<LDAPObject> 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");
}
}
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -15,7 +15,7 @@
* limitations under the License.
*/
package org.keycloak.federation.ldap.idm.query;
package org.keycloak.storage.ldap.idm.query;
/**
* <p>A {@link Condition} is used to specify how a specific query parameter

View file

@ -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

View file

@ -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;

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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<String> returningReadOnlyLdapAttributes = new LinkedHashSet<String>();
private final Set<String> objectClasses = new LinkedHashSet<String>();
private final List<UserFederationMapperModel> mappers = new ArrayList<UserFederationMapperModel>();
private final List<ComponentModel> 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<UserFederationMapperModel> mappers) {
public LDAPQuery addMappers(Collection<ComponentModel> mappers) {
this.mappers.addAll(mappers);
return this;
}
@ -128,7 +128,7 @@ public class LDAPQuery {
return unmodifiableSet(this.returningReadOnlyLdapAttributes);
}
public List<UserFederationMapperModel> getMappers() {
public List<ComponentModel> getMappers() {
return mappers;
}
@ -152,9 +152,9 @@ public class LDAPQuery {
public List<LDAPObject> getResultList() {
// Apply mappers now
List<UserFederationMapperModel> sortedMappers = ldapFedProvider.sortMappersAsc(mappers);
for (UserFederationMapperModel mapperModel : sortedMappers) {
LDAPFederationMapper fedMapper = ldapFedProvider.getMapper(mapperModel);
List<ComponentModel> 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;
}

View file

@ -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

View file

@ -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;

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
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));
}
}

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
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;
}

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public abstract class AbstractLDAPFederationMapperFactory implements UserFederationMapperFactory {
public abstract class AbstractLDAPStorageMapperFactory implements LDAPStorageMapperFactory<LDAPStorageMapper> {
// 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<String, Object> getTypeMetadata() {
Map<String, Object> 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 + "'");
}
}

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
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;
}

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class FullNameLDAPStorageMapperFactory extends AbstractLDAPStorageMapperFactory {
public static final String PROVIDER_ID = "full-name-ldap-mapper";
protected static final List<ProviderConfigProperty> configProperties;
static {
configProperties = getConfigProps(null);
}
private static List<ProviderConfigProperty> 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<ProviderConfigProperty> 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);
}
}

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
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");

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class HardcodedLDAPRoleMapperFactory extends AbstractLDAPFederationMapperFactory {
public class HardcodedLDAPRoleStorageMapperFactory extends AbstractLDAPStorageMapperFactory {
public static final String PROVIDER_ID = "hardcoded-ldap-role-mapper";
protected static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
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<ProviderConfigProperty> getConfigProperties() {
return configProperties;
}
@Override
public Map<String, String> 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);
}
}

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class LDAPMappersComparator {
public static List<UserFederationMapperModel> sortAsc(LDAPConfig ldapConfig, Collection<UserFederationMapperModel> mappers) {
Comparator<UserFederationMapperModel> comparator = new ImportantFirstComparator(ldapConfig);
public static List<ComponentModel> sortAsc(LDAPConfig ldapConfig, Collection<ComponentModel> mappers) {
Comparator<ComponentModel> comparator = new ImportantFirstComparator(ldapConfig);
List<UserFederationMapperModel> result = new ArrayList<>(mappers);
List<ComponentModel> result = new ArrayList<>(mappers);
Collections.sort(result, comparator);
return result;
}
public static List<UserFederationMapperModel> sortDesc(LDAPConfig ldapConfig, Collection<UserFederationMapperModel> mappers) {
Comparator<UserFederationMapperModel> comparator = new ImportantFirstComparator(ldapConfig).reversed();
public static List<ComponentModel> sortDesc(LDAPConfig ldapConfig, Collection<ComponentModel> mappers) {
Comparator<ComponentModel> comparator = new ImportantFirstComparator(ldapConfig).reversed();
List<UserFederationMapperModel> result = new ArrayList<>(mappers);
List<ComponentModel> result = new ArrayList<>(mappers);
Collections.sort(result, comparator);
return result;
}
private static class ImportantFirstComparator implements Comparator<UserFederationMapperModel> {
private static class ImportantFirstComparator implements Comparator<ComponentModel> {
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);

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
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<UserModel> 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);
}

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
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<UserModel> getGroupMembers(UserFederationMapperModel mapperModel, UserFederationProvider ldapProvider, RealmModel realm, GroupModel group, int firstResult, int maxResults) {
public List<UserModel> 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);
}

View file

@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public interface LDAPStorageMapperFactory<T extends LDAPStorageMapper> extends SubComponentFactory<T, LDAPStorageMapper> {
/**
* 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<ProviderConfigProperty> 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) {
}
}

View file

@ -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 <a href="mailto:me@tsudot.com">Kunal Kerkar</a>
*/
public class LDAPStorageMapperSpi implements Spi {
@Override
public boolean isInternal() {
return false;
}
@Override
public String getName() {
return "ldap-mapper";
}
@Override
public Class<? extends Provider> getProviderClass() {
return LDAPStorageMapper.class;
}
@Override
public Class<? extends ProviderFactory> getProviderFactoryClass() {
return LDAPStorageMapperFactory.class;
}
}

View file

@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@ -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;

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
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<String, Property<Object>> 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<Object> 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<Object> 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);

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class UserAttributeLDAPStorageMapperFactory extends AbstractLDAPStorageMapperFactory {
public static final String PROVIDER_ID = "user-attribute-ldap-mapper";
protected static final List<ProviderConfigProperty> configProperties;
static {
List<ProviderConfigProperty> props = getConfigProps(null);
configProperties = props;
}
private static List<ProviderConfigProperty> 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<ProviderConfigProperty> 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<ProviderConfigProperty> getConfigProperties(RealmModel realm, ComponentModel parent) {
return getConfigProps(parent);
}
}

View file

@ -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)

View file

@ -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");
}

View file

@ -15,7 +15,7 @@
* limitations under the License.
*/
package org.keycloak.federation.ldap.mappers.membership;
package org.keycloak.storage.ldap.mappers.membership;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>

View file

@ -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<LDAPDn> getLDAPSubgroups(GroupLDAPFederationMapper groupMapper, LDAPObject ldapGroup) {
public Set<LDAPDn> 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<UserModel> getGroupMembers(GroupLDAPFederationMapper groupMapper, LDAPObject ldapGroup, int firstResult, int maxResults) {
public List<UserModel> 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<LDAPDn> getLDAPSubgroups(GroupLDAPFederationMapper groupMapper, LDAPObject ldapGroup) {
public Set<LDAPDn> getLDAPSubgroups(GroupLDAPStorageMapper groupMapper, LDAPObject ldapGroup) {
return Collections.emptySet();
}
@Override
public List<UserModel> getGroupMembers(GroupLDAPFederationMapper groupMapper, LDAPObject ldapGroup, int firstResult, int maxResults) {
public List<UserModel> getGroupMembers(GroupLDAPStorageMapper groupMapper, LDAPObject ldapGroup, int firstResult, int maxResults) {
String memberAttrName = groupMapper.getConfig().getMembershipLdapAttribute();
Set<String> memberUids = LDAPUtils.getExistingMemberships(memberAttrName, ldapGroup);
@ -151,7 +151,7 @@ public enum MembershipType {
};
public abstract Set<LDAPDn> getLDAPSubgroups(GroupLDAPFederationMapper groupMapper, LDAPObject ldapGroup);
public abstract Set<LDAPDn> getLDAPSubgroups(GroupLDAPStorageMapper groupMapper, LDAPObject ldapGroup);
public abstract List<UserModel> getGroupMembers(GroupLDAPFederationMapper groupMapper, LDAPObject ldapGroup, int firstResult, int maxResults);
public abstract List<UserModel> getGroupMembers(GroupLDAPStorageMapper groupMapper, LDAPObject ldapGroup, int firstResult, int maxResults);
}

View file

@ -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;

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
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<LDAPObject> ldapGroups = getAllLDAPGroups();
@ -211,7 +212,7 @@ public class GroupLDAPFederationMapper extends AbstractLDAPFederationMapper impl
return syncResult;
}
private void updateKeycloakGroupTree(List<GroupTreeResolver.GroupTreeEntry> groupTrees, Map<String, LDAPObject> ldapGroups, UserFederationSyncResult syncResult) {
private void updateKeycloakGroupTree(List<GroupTreeResolver.GroupTreeEntry> groupTrees, Map<String, LDAPObject> ldapGroups, SynchronizationResult syncResult) {
Set<String> 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<String, LDAPObject> ldapGroups, GroupModel kcParent, UserFederationSyncResult syncResult, Set<String> visitedGroupIds) {
private void updateKeycloakGroupTreeEntry(GroupTreeResolver.GroupTreeEntry groupTreeEntry, Map<String, LDAPObject> ldapGroups, GroupModel kcParent, SynchronizationResult syncResult, Set<String> 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<String> visitedGroupIds) {
private void dropNonExistingKcGroups(SynchronizationResult syncResult, Set<String> visitedGroupIds) {
// Remove keycloak groups, which doesn't exists in LDAP
List<GroupModel> 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<String, LDAPObject> ldapGroupsMap, Set<String> ldapGroupNames, UserFederationSyncResult syncResult) {
private void processLdapGroupSyncToLDAP(GroupModel kcGroup, Map<String, LDAPObject> ldapGroupsMap, Set<String> ldapGroupNames, SynchronizationResult syncResult) {
String groupName = kcGroup.getName();
// extract group attributes to be updated to LDAP

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class GroupLDAPStorageMapperFactory extends AbstractLDAPStorageMapperFactory {
public static final String PROVIDER_ID = "group-ldap-mapper";
protected static final List<ProviderConfigProperty> configProperties;
protected static final Map<String, UserRolesRetrieveStrategy> userGroupsStrategies = new LinkedHashMap<>();
protected static final List<String> MEMBERSHIP_TYPES = new LinkedList<>();
protected static final List<String> MODES = new LinkedList<>();
protected static final List<String> 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<ProviderConfigProperty> config = getProps(null);
configProperties = config;
}
private static List<ProviderConfigProperty> 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<ProviderConfigProperty> getConfigProperties() {
return configProperties;
}
@Override
public String getId() {
return PROVIDER_ID;
}
@Override
public Map<String, Object> getTypeMetadata() {
Map<String, Object> 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);
}
}

View file

@ -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<String> getGroupObjectClasses(LDAPFederationProvider ldapProvider) {
String objectClasses = mapperModel.getConfig().get(GROUP_OBJECT_CLASSES);
public Collection<String> 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<String> getGroupAttributes() {
String groupAttrs = mapperModel.getConfig().get(MAPPED_GROUP_ATTRIBUTES);
String groupAttrs = mapperModel.getConfig().getFirst(MAPPED_GROUP_ATTRIBUTES);
return (groupAttrs == null) ? Collections.<String>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;
}
}

View file

@ -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;

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
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();

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class RoleLDAPStorageMapperFactory extends AbstractLDAPStorageMapperFactory {
public static final String PROVIDER_ID = "role-ldap-mapper";
protected static final List<ProviderConfigProperty> configProperties;
protected static final Map<String, UserRolesRetrieveStrategy> userRolesStrategies = new LinkedHashMap<>();
protected static final List<String> MEMBERSHIP_TYPES = new LinkedList<>();
protected static final List<String> MODES = new LinkedList<>();
protected static final List<String> 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<ProviderConfigProperty> config = getProps(null);
configProperties = config;
}
private static List<ProviderConfigProperty> 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<ProviderConfigProperty> getConfigProperties() {
return configProperties;
}
@Override
public String getId() {
return PROVIDER_ID;
}
@Override
public Map<String, Object> getTypeMetadata() {
Map<String, Object> 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);
}
}

View file

@ -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<String> getRoleObjectClasses(LDAPFederationProvider ldapProvider) {
String objectClasses = mapperModel.getConfig().get(ROLE_OBJECT_CLASSES);
public Collection<String> 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;
}

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
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<String> getRequiredActions() {
Set<String> 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());

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
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<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
@ -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<ProviderConfigProperty> getConfigProperties() {
return configProperties;
}
@Override
public Map<String, String> 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);
}
}

View file

@ -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

View file

@ -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

View file

@ -15,4 +15,4 @@
# limitations under the License.
#
org.keycloak.federation.ldap.LDAPFederationProviderFactory
org.keycloak.storage.ldap.mappers.LDAPStorageMapperSpi

View file

@ -0,0 +1 @@
org.keycloak.storage.ldap.LDAPStorageProviderFactory

View file

@ -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

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class LDAPMappersComparatorTest {
@Test
public void testCompareWithCNUsername() {
Map<String, String> cfg = new HashMap<>();
cfg.put(LDAPConstants.USERNAME_LDAP_ATTRIBUTE, LDAPConstants.CN);
LDAPConfig config = new LDAPConfig(cfg);
List<UserFederationMapperModel> 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<String, String> cfg = new HashMap<>();
cfg.put(LDAPConstants.USERNAME_LDAP_ATTRIBUTE, LDAPConstants.SAM_ACCOUNT_NAME);
LDAPConfig config = new LDAPConfig(cfg);
List<UserFederationMapperModel> 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<UserFederationMapperModel> result, String... names) {
Assert.assertEquals(result.size(), names.length);
for (int i=0 ; i<names.length ; i++) {
Assert.assertEquals(names[i], result.get(i).getName());
}
}
private Set<UserFederationMapperModel> getMappers() {
Set<UserFederationMapperModel> 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;
}
}

View file

@ -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;

View file

@ -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;

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class LDAPMappersComparatorTest {
@Test
public void testCompareWithCNUsername() {
MultivaluedHashMap<String, String> cfg = new MultivaluedHashMap<>();
cfg.add(LDAPConstants.USERNAME_LDAP_ATTRIBUTE, LDAPConstants.CN);
LDAPConfig config = new LDAPConfig(cfg);
List<ComponentModel> 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<String, String> cfg = new MultivaluedHashMap<>();
cfg.add(LDAPConstants.USERNAME_LDAP_ATTRIBUTE, LDAPConstants.SAM_ACCOUNT_NAME);
LDAPConfig config = new LDAPConfig(cfg);
List<ComponentModel> 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<ComponentModel> result, String... names) {
Assert.assertEquals(result.size(), names.length);
for (int i=0 ; i<names.length ; i++) {
Assert.assertEquals(names[i], result.get(i).getName());
}
}
private List<ComponentModel> getMappers() {
List<ComponentModel> 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;
}
}

View file

@ -33,8 +33,8 @@
<description />
<modules>
<module>ldap</module>
<module>kerberos</module>
<module>ldap</module>
<module>sssd</module>
</modules>

View file

@ -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);
}

View file

@ -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

View file

@ -64,6 +64,11 @@ public class UserAdapter implements CachedUserModel {
return updated;
}
@Override
public boolean isMarkedForEviction() {
return updated != null;
}
@Override
public void invalidate() {
getDelegateForUpdate();

View file

@ -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<UserModel> getUsers(RealmModel realm, boolean includeServiceAccounts) {
return getDelegate().getUsers(realm, includeServiceAccounts);

View file

@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @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);
}
}
}

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
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";
}
}

View file

@ -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());
}

View file

@ -2031,12 +2031,20 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
@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<RealmEntity> {
}
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<RealmEntity> {
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<RealmEntity> {
@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<RealmEntity> {
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);
}

View file

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!--
~ 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.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
<changeSet author="bburke@redhat.com" id="2.4.0">
<customChange class="org.keycloak.connections.jpa.updater.liquibase.custom.PortLdapUserFedToComponentModel"/>
</changeSet>
</databaseChangeLog>

View file

@ -43,4 +43,5 @@
<include file="META-INF/jpa-changelog-2.1.0.xml"/>
<include file="META-INF/jpa-changelog-2.2.0.xml"/>
<include file="META-INF/jpa-changelog-2.3.0.xml"/>
<include file="META-INF/jpa-changelog-2.4.0.xml"/>
</databaseChangeLog>

View file

@ -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

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
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<String> 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<String, Object> 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<Object> 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<String> 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<String, Object> 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<Object> 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);
}
}
}

View file

@ -1954,7 +1954,14 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> 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<MongoRealmEntity> 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<MongoRealmEntity> impleme
while(it.hasNext()) {
if (it.next().getId().equals(component.getId())) {
session.users().preRemove(this, component);
removeComponents(component.getId());
it.remove();
break;
}

View file

@ -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();

View file

@ -352,19 +352,7 @@ public class ModelToRepresentation {
}
}
List<UserFederationProviderModel> fedProviderModels = realm.getUserFederationProviders();
if (fedProviderModels.size() > 0) {
List<UserFederationProviderRepresentation> fedProviderReps = new ArrayList<UserFederationProviderRepresentation>();
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<UserFederationProviderModel> fedProviderModels = realm.getUserFederationProviders();
if (fedProviderModels.size() > 0) {
List<UserFederationProviderRepresentation> fedProviderReps = new ArrayList<UserFederationProviderRepresentation>();
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<GroupRepresentation> groups = toGroupHierarchy(realm, true);
rep.setGroups(groups);

View file

@ -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<UserFederationProviderModel> 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<String> providerNames = new TreeSet<String>();
for (UserFederationMapperRepresentation representation : rep.getUserFederationMappers()) {
providerNames.add(representation.getFederationProviderDisplayName());
}
for (String providerName : providerNames) {
for (UserFederationProviderModel providerModel : providerModels) {
if (providerName.equals(providerModel.getDisplayName())) {
Set<UserFederationMapperModel> 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<String> convertSet = new HashSet<>();
convertSet.add(LDAPConstants.LDAP_PROVIDER);
Map<String, String> mapperConvertSet = new HashMap<>();
mapperConvertSet.put(LDAPConstants.LDAP_PROVIDER, "org.keycloak.storage.ldap.mappers.LDAPStorageMapper");
List<UserFederationProviderModel> providerModels = null;
Map<String, ComponentModel> 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<String> providerNames = new TreeSet<String>();
for (UserFederationMapperRepresentation representation : rep.getUserFederationMappers()) {
providerNames.add(representation.getFederationProviderDisplayName());
}
for (String providerName : providerNames) {
for (UserFederationProviderModel providerModel : providerModels) {
if (providerName.equals(providerModel.getDisplayName())) {
Set<UserFederationMapperModel> 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<String, ComponentExportRepresentation> components, String parentId) {
for (Map.Entry<String, List<ComponentExportRepresentation>> 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<UserFederationProviderModel> result = new ArrayList<UserFederationProviderModel>();
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<String, String> 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<String, String> 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());

View file

@ -25,6 +25,7 @@ import org.keycloak.provider.ProviderFactory;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -38,7 +39,7 @@ public interface ComponentFactory<CreatedType, ProviderType extends Provider> 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<CreatedType, ProviderType extends Provider> 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<String, Object> getTypeMetadata() {
return Collections.EMPTY_MAP;
}
}

View file

@ -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);
}

View file

@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public interface SubComponentFactory<CreatedType, ProviderType extends Provider> extends ComponentFactory<CreatedType, ProviderType> {
default
List<ProviderConfigProperty> 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<String, Object> getTypeMetadata(RealmModel realm, ComponentModel parent) {
return getTypeMetadata();
}
}

View file

@ -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);

View file

@ -35,6 +35,8 @@ public interface CachedUserModel extends UserModel {
*/
UserModel getDelegateForUpdate();
boolean isMarkedForEviction();
/**
* Invalidate the cache for this model
*

View file

@ -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();
}

View file

@ -102,6 +102,12 @@ public class ProviderConfigurationBuilder {
return this;
}
public ProviderConfigPropertyBuilder options(List<String> options) {
this.options = options;
return this;
}
public ProviderConfigPropertyBuilder secret(boolean secret) {
this.secret = secret;
return this;

View file

@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -81,7 +84,7 @@ public interface UserStorageProviderFactory<T extends UserStorageProvider> 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<T extends UserStorageProvider> exten
List<ProviderConfigProperty> getCommonProviderConfigProperties() {
return UserStorageProviderSpi.commonConfig();
}
@Override
default
Map<String, Object> getTypeMetadata() {
Map<String, Object> metadata = new HashMap<>();
if (this instanceof ImportSynchronization) {
metadata.put("synchronizable", true);
}
return metadata;
}
}

View file

@ -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()

View file

@ -68,6 +68,11 @@
<artifactId>keycloak-server-spi-private</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-ldap-federation</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.servlet</groupId>
<artifactId>jboss-servlet-api_3.0_spec</artifactId>

Some files were not shown because too many files have changed in this diff Show more