commit
214cd2ac22
26 changed files with 393 additions and 66 deletions
|
@ -131,7 +131,12 @@ public class LDAPConfig {
|
||||||
|
|
||||||
public boolean isPagination() {
|
public boolean isPagination() {
|
||||||
String pagination = config.get(LDAPConstants.PAGINATION);
|
String pagination = config.get(LDAPConstants.PAGINATION);
|
||||||
return pagination==null ? false : Boolean.parseBoolean(pagination);
|
return Boolean.parseBoolean(pagination);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBatchSizeForSync() {
|
||||||
|
String pageSizeConfig = config.get(LDAPConstants.BATCH_SIZE_FOR_SYNC);
|
||||||
|
return pageSizeConfig!=null ? Integer.parseInt(pageSizeConfig) : LDAPConstants.DEFAULT_BATCH_SIZE_FOR_SYNC;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getUsernameLdapAttribute() {
|
public String getUsernameLdapAttribute() {
|
||||||
|
|
|
@ -34,6 +34,7 @@ import org.keycloak.federation.ldap.mappers.LDAPFederationMapper;
|
||||||
import org.keycloak.federation.ldap.mappers.UserAttributeLDAPFederationMapper;
|
import org.keycloak.federation.ldap.mappers.UserAttributeLDAPFederationMapper;
|
||||||
import org.keycloak.federation.ldap.mappers.UserAttributeLDAPFederationMapperFactory;
|
import org.keycloak.federation.ldap.mappers.UserAttributeLDAPFederationMapperFactory;
|
||||||
import org.keycloak.federation.ldap.mappers.msad.MSADUserAccountControlMapperFactory;
|
import org.keycloak.federation.ldap.mappers.msad.MSADUserAccountControlMapperFactory;
|
||||||
|
import org.keycloak.mappers.FederationConfigValidationException;
|
||||||
import org.keycloak.mappers.UserFederationMapper;
|
import org.keycloak.mappers.UserFederationMapper;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
@ -46,6 +47,7 @@ import org.keycloak.models.UserFederationMapperModel;
|
||||||
import org.keycloak.models.UserFederationProvider;
|
import org.keycloak.models.UserFederationProvider;
|
||||||
import org.keycloak.models.UserFederationProviderModel;
|
import org.keycloak.models.UserFederationProviderModel;
|
||||||
import org.keycloak.models.UserFederationSyncResult;
|
import org.keycloak.models.UserFederationSyncResult;
|
||||||
|
import org.keycloak.models.UserFederationValidatingProviderFactory;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
|
|
||||||
|
@ -59,7 +61,7 @@ import java.util.Set;
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public class LDAPFederationProviderFactory extends UserFederationEventAwareProviderFactory {
|
public class LDAPFederationProviderFactory extends UserFederationEventAwareProviderFactory implements UserFederationValidatingProviderFactory {
|
||||||
private static final Logger logger = Logger.getLogger(LDAPFederationProviderFactory.class);
|
private static final Logger logger = Logger.getLogger(LDAPFederationProviderFactory.class);
|
||||||
public static final String PROVIDER_NAME = LDAPConstants.LDAP_PROVIDER;
|
public static final String PROVIDER_NAME = LDAPConstants.LDAP_PROVIDER;
|
||||||
|
|
||||||
|
@ -76,6 +78,13 @@ public class LDAPFederationProviderFactory extends UserFederationEventAwareProvi
|
||||||
return new LDAPFederationProvider(this, session, model, ldapIdentityStore);
|
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
|
@Override
|
||||||
public void init(Config.Scope config) {
|
public void init(Config.Scope config) {
|
||||||
this.ldapStoreRegistry = new LDAPIdentityStoreRegistry();
|
this.ldapStoreRegistry = new LDAPIdentityStoreRegistry();
|
||||||
|
@ -156,7 +165,8 @@ public class LDAPFederationProviderFactory extends UserFederationEventAwareProvi
|
||||||
// For read-only LDAP, we map "cn" as full name
|
// For read-only LDAP, we map "cn" as full name
|
||||||
mapperModel = KeycloakModelUtils.createUserFederationMapperModel("full name", newProviderModel.getId(), FullNameLDAPFederationMapperFactory.PROVIDER_ID,
|
mapperModel = KeycloakModelUtils.createUserFederationMapperModel("full name", newProviderModel.getId(), FullNameLDAPFederationMapperFactory.PROVIDER_ID,
|
||||||
FullNameLDAPFederationMapper.LDAP_FULL_NAME_ATTRIBUTE, LDAPConstants.CN,
|
FullNameLDAPFederationMapper.LDAP_FULL_NAME_ATTRIBUTE, LDAPConstants.CN,
|
||||||
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly);
|
FullNameLDAPFederationMapper.READ_ONLY, readOnly,
|
||||||
|
FullNameLDAPFederationMapper.WRITE_ONLY, "false");
|
||||||
realm.addUserFederationMapper(mapperModel);
|
realm.addUserFederationMapper(mapperModel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -274,11 +284,10 @@ public class LDAPFederationProviderFactory extends UserFederationEventAwareProvi
|
||||||
|
|
||||||
final UserFederationSyncResult syncResult = new UserFederationSyncResult();
|
final UserFederationSyncResult syncResult = new UserFederationSyncResult();
|
||||||
|
|
||||||
boolean pagination = Boolean.parseBoolean(fedModel.getConfig().get(LDAPConstants.PAGINATION));
|
LDAPConfig ldapConfig = new LDAPConfig(fedModel.getConfig());
|
||||||
|
boolean pagination = ldapConfig.isPagination();
|
||||||
if (pagination) {
|
if (pagination) {
|
||||||
|
int pageSize = ldapConfig.getBatchSizeForSync();
|
||||||
String pageSizeConfig = fedModel.getConfig().get(LDAPConstants.BATCH_SIZE_FOR_SYNC);
|
|
||||||
int pageSize = pageSizeConfig!=null ? Integer.parseInt(pageSizeConfig) : LDAPConstants.DEFAULT_BATCH_SIZE_FOR_SYNC;
|
|
||||||
|
|
||||||
boolean nextPage = true;
|
boolean nextPage = true;
|
||||||
while (nextPage) {
|
while (nextPage) {
|
||||||
|
|
|
@ -31,6 +31,7 @@ import org.keycloak.federation.ldap.idm.query.internal.LDAPQueryConditionsBuilde
|
||||||
import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
|
import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
|
||||||
import org.keycloak.federation.ldap.mappers.LDAPFederationMapper;
|
import org.keycloak.federation.ldap.mappers.LDAPFederationMapper;
|
||||||
import org.keycloak.federation.ldap.mappers.membership.MembershipType;
|
import org.keycloak.federation.ldap.mappers.membership.MembershipType;
|
||||||
|
import org.keycloak.mappers.FederationConfigValidationException;
|
||||||
import org.keycloak.models.LDAPConstants;
|
import org.keycloak.models.LDAPConstants;
|
||||||
import org.keycloak.models.ModelException;
|
import org.keycloak.models.ModelException;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
|
@ -226,4 +227,19 @@ public class LDAPUtils {
|
||||||
public static String getMemberValueOfChildObject(LDAPObject ldapUser, MembershipType membershipType) {
|
public static String getMemberValueOfChildObject(LDAPObject ldapUser, MembershipType membershipType) {
|
||||||
return membershipType == MembershipType.DN ? ldapUser.getDn().toString() : ldapUser.getAttributeAsString(ldapUser.getRdnAttributeName());
|
return membershipType == MembershipType.DN ? ldapUser.getDn().toString() : ldapUser.getAttributeAsString(ldapUser.getRdnAttributeName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void validateCustomLdapFilter(String customFilter) throws FederationConfigValidationException {
|
||||||
|
if (customFilter != null) {
|
||||||
|
|
||||||
|
customFilter = customFilter.trim();
|
||||||
|
if (customFilter.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!customFilter.startsWith("(") || !customFilter.endsWith(")")) {
|
||||||
|
throw new FederationConfigValidationException("ldapErrorInvalidCustomFilter");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,9 +61,6 @@ public class LDAPQueryConditionsBuilder {
|
||||||
|
|
||||||
public Condition addCustomLDAPFilter(String filter) {
|
public Condition addCustomLDAPFilter(String filter) {
|
||||||
filter = filter.trim();
|
filter = filter.trim();
|
||||||
if (!filter.startsWith("(") || !filter.endsWith(")")) {
|
|
||||||
throw new ModelException("Custom filter doesn't start with ( or doesn't end with ). ");
|
|
||||||
}
|
|
||||||
return new CustomLDAPFilter(filter);
|
return new CustomLDAPFilter(filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ package org.keycloak.federation.ldap.mappers;
|
||||||
import org.keycloak.Config;
|
import org.keycloak.Config;
|
||||||
import org.keycloak.federation.ldap.LDAPFederationProvider;
|
import org.keycloak.federation.ldap.LDAPFederationProvider;
|
||||||
import org.keycloak.federation.ldap.LDAPFederationProviderFactory;
|
import org.keycloak.federation.ldap.LDAPFederationProviderFactory;
|
||||||
import org.keycloak.mappers.MapperConfigValidationException;
|
import org.keycloak.mappers.FederationConfigValidationException;
|
||||||
import org.keycloak.mappers.UserFederationMapper;
|
import org.keycloak.mappers.UserFederationMapper;
|
||||||
import org.keycloak.mappers.UserFederationMapperFactory;
|
import org.keycloak.mappers.UserFederationMapperFactory;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
@ -85,10 +85,10 @@ public abstract class AbstractLDAPFederationMapperFactory implements UserFederat
|
||||||
return configProperty;
|
return configProperty;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void checkMandatoryConfigAttribute(String name, String displayName, UserFederationMapperModel mapperModel) throws MapperConfigValidationException {
|
protected void checkMandatoryConfigAttribute(String name, String displayName, UserFederationMapperModel mapperModel) throws FederationConfigValidationException {
|
||||||
String attrConfigValue = mapperModel.getConfig().get(name);
|
String attrConfigValue = mapperModel.getConfig().get(name);
|
||||||
if (attrConfigValue == null || attrConfigValue.trim().isEmpty()) {
|
if (attrConfigValue == null || attrConfigValue.trim().isEmpty()) {
|
||||||
throw new MapperConfigValidationException("Missing configuration for '" + displayName + "'");
|
throw new FederationConfigValidationException("Missing configuration for '" + displayName + "'");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,6 +40,8 @@ public class FullNameLDAPFederationMapper extends AbstractLDAPFederationMapper {
|
||||||
|
|
||||||
public static final String LDAP_FULL_NAME_ATTRIBUTE = "ldap.full.name.attribute";
|
public static final String LDAP_FULL_NAME_ATTRIBUTE = "ldap.full.name.attribute";
|
||||||
public static final String READ_ONLY = "read.only";
|
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 FullNameLDAPFederationMapper(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, RealmModel realm) {
|
||||||
super(mapperModel, ldapProvider, realm);
|
super(mapperModel, ldapProvider, realm);
|
||||||
|
@ -47,6 +49,10 @@ public class FullNameLDAPFederationMapper extends AbstractLDAPFederationMapper {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onImportUserFromLDAP(LDAPObject ldapUser, UserModel user, boolean isCreate) {
|
public void onImportUserFromLDAP(LDAPObject ldapUser, UserModel user, boolean isCreate) {
|
||||||
|
if (isWriteOnly()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
String ldapFullNameAttrName = getLdapFullNameAttrName();
|
String ldapFullNameAttrName = getLdapFullNameAttrName();
|
||||||
String fullName = ldapUser.getAttributeAsString(ldapFullNameAttrName);
|
String fullName = ldapUser.getAttributeAsString(ldapFullNameAttrName);
|
||||||
if (fullName == null) {
|
if (fullName == null) {
|
||||||
|
@ -117,6 +123,10 @@ public class FullNameLDAPFederationMapper extends AbstractLDAPFederationMapper {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void beforeLDAPQuery(LDAPQuery query) {
|
public void beforeLDAPQuery(LDAPQuery query) {
|
||||||
|
if (isWriteOnly()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
String ldapFullNameAttrName = getLdapFullNameAttrName();
|
String ldapFullNameAttrName = getLdapFullNameAttrName();
|
||||||
query.addReturningLdapAttribute(ldapFullNameAttrName);
|
query.addReturningLdapAttribute(ldapFullNameAttrName);
|
||||||
|
|
||||||
|
@ -178,4 +188,8 @@ public class FullNameLDAPFederationMapper extends AbstractLDAPFederationMapper {
|
||||||
private boolean isReadOnly() {
|
private boolean isReadOnly() {
|
||||||
return parseBooleanParameter(mapperModel, READ_ONLY);
|
return parseBooleanParameter(mapperModel, READ_ONLY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isWriteOnly() {
|
||||||
|
return parseBooleanParameter(mapperModel, WRITE_ONLY);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ import java.util.Map;
|
||||||
|
|
||||||
import org.keycloak.federation.ldap.LDAPConfig;
|
import org.keycloak.federation.ldap.LDAPConfig;
|
||||||
import org.keycloak.federation.ldap.LDAPFederationProvider;
|
import org.keycloak.federation.ldap.LDAPFederationProvider;
|
||||||
import org.keycloak.mappers.MapperConfigValidationException;
|
import org.keycloak.mappers.FederationConfigValidationException;
|
||||||
import org.keycloak.models.LDAPConstants;
|
import org.keycloak.models.LDAPConstants;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserFederationMapperModel;
|
import org.keycloak.models.UserFederationMapperModel;
|
||||||
|
@ -43,12 +43,17 @@ public class FullNameLDAPFederationMapperFactory extends AbstractLDAPFederationM
|
||||||
|
|
||||||
static {
|
static {
|
||||||
ProviderConfigProperty userModelAttribute = createConfigProperty(FullNameLDAPFederationMapper.LDAP_FULL_NAME_ATTRIBUTE, "LDAP Full Name Attribute",
|
ProviderConfigProperty userModelAttribute = createConfigProperty(FullNameLDAPFederationMapper.LDAP_FULL_NAME_ATTRIBUTE, "LDAP Full Name Attribute",
|
||||||
"Name of LDAP attribute, which contains fullName of user. In most cases it will be 'cn' ", ProviderConfigProperty.STRING_TYPE, null);
|
"Name of LDAP attribute, which contains fullName of user. Usually it will be 'cn' ", ProviderConfigProperty.STRING_TYPE, null);
|
||||||
configProperties.add(userModelAttribute);
|
configProperties.add(userModelAttribute);
|
||||||
|
|
||||||
ProviderConfigProperty readOnly = createConfigProperty(UserAttributeLDAPFederationMapper.READ_ONLY, "Read Only",
|
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);
|
"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);
|
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
|
@Override
|
||||||
|
@ -78,8 +83,11 @@ public class FullNameLDAPFederationMapperFactory extends AbstractLDAPFederationM
|
||||||
|
|
||||||
defaultValues.put(FullNameLDAPFederationMapper.LDAP_FULL_NAME_ATTRIBUTE, LDAPConstants.CN);
|
defaultValues.put(FullNameLDAPFederationMapper.LDAP_FULL_NAME_ATTRIBUTE, LDAPConstants.CN);
|
||||||
|
|
||||||
String readOnly = config.getEditMode() == UserFederationProvider.EditMode.WRITABLE ? "false" : "true";
|
boolean readOnly = config.getEditMode() != UserFederationProvider.EditMode.WRITABLE;
|
||||||
defaultValues.put(UserAttributeLDAPFederationMapper.READ_ONLY, readOnly);
|
defaultValues.put(FullNameLDAPFederationMapper.READ_ONLY, String.valueOf(readOnly));
|
||||||
|
|
||||||
|
String writeOnly = String.valueOf(!readOnly);
|
||||||
|
defaultValues.put(FullNameLDAPFederationMapper.WRITE_ONLY, writeOnly);
|
||||||
|
|
||||||
return defaultValues;
|
return defaultValues;
|
||||||
}
|
}
|
||||||
|
@ -90,8 +98,21 @@ public class FullNameLDAPFederationMapperFactory extends AbstractLDAPFederationM
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void validateConfig(RealmModel realm, UserFederationMapperModel mapperModel) throws MapperConfigValidationException {
|
public void validateConfig(RealmModel realm, UserFederationProviderModel fedProviderModel, UserFederationMapperModel mapperModel) throws FederationConfigValidationException {
|
||||||
checkMandatoryConfigAttribute(FullNameLDAPFederationMapper.LDAP_FULL_NAME_ATTRIBUTE, "LDAP Full Name Attribute", mapperModel);
|
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
|
@Override
|
||||||
|
|
|
@ -23,7 +23,7 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.keycloak.federation.ldap.LDAPFederationProvider;
|
import org.keycloak.federation.ldap.LDAPFederationProvider;
|
||||||
import org.keycloak.mappers.MapperConfigValidationException;
|
import org.keycloak.mappers.FederationConfigValidationException;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
import org.keycloak.models.UserFederationMapperModel;
|
import org.keycloak.models.UserFederationMapperModel;
|
||||||
|
@ -77,14 +77,14 @@ public class HardcodedLDAPRoleMapperFactory extends AbstractLDAPFederationMapper
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void validateConfig(RealmModel realm, UserFederationMapperModel mapperModel) throws MapperConfigValidationException {
|
public void validateConfig(RealmModel realm, UserFederationProviderModel fedProviderModel, UserFederationMapperModel mapperModel) throws FederationConfigValidationException {
|
||||||
String roleName = mapperModel.getConfig().get(HardcodedLDAPRoleMapper.ROLE);
|
String roleName = mapperModel.getConfig().get(HardcodedLDAPRoleMapper.ROLE);
|
||||||
if (roleName == null) {
|
if (roleName == null) {
|
||||||
throw new MapperConfigValidationException("Role can't be null");
|
throw new FederationConfigValidationException("Role can't be null");
|
||||||
}
|
}
|
||||||
RoleModel role = KeycloakModelUtils.getRoleFromString(realm, roleName);
|
RoleModel role = KeycloakModelUtils.getRoleFromString(realm, roleName);
|
||||||
if (role == null) {
|
if (role == null) {
|
||||||
throw new MapperConfigValidationException("There is no role corresponding to configured value");
|
throw new FederationConfigValidationException("There is no role corresponding to configured value");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ import java.util.Map;
|
||||||
|
|
||||||
import org.keycloak.federation.ldap.LDAPConfig;
|
import org.keycloak.federation.ldap.LDAPConfig;
|
||||||
import org.keycloak.federation.ldap.LDAPFederationProvider;
|
import org.keycloak.federation.ldap.LDAPFederationProvider;
|
||||||
import org.keycloak.mappers.MapperConfigValidationException;
|
import org.keycloak.mappers.FederationConfigValidationException;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserFederationMapperModel;
|
import org.keycloak.models.UserFederationMapperModel;
|
||||||
import org.keycloak.models.UserFederationProvider;
|
import org.keycloak.models.UserFederationProvider;
|
||||||
|
@ -101,7 +101,7 @@ public class UserAttributeLDAPFederationMapperFactory extends AbstractLDAPFedera
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void validateConfig(RealmModel realm, UserFederationMapperModel mapperModel) throws MapperConfigValidationException {
|
public void validateConfig(RealmModel realm, UserFederationProviderModel fedProviderModel, UserFederationMapperModel mapperModel) throws FederationConfigValidationException {
|
||||||
checkMandatoryConfigAttribute(UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, "User Model Attribute", mapperModel);
|
checkMandatoryConfigAttribute(UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, "User Model Attribute", mapperModel);
|
||||||
checkMandatoryConfigAttribute(UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, "LDAP Attribute", mapperModel);
|
checkMandatoryConfigAttribute(UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, "LDAP Attribute", mapperModel);
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.federation.ldap.LDAPConfig;
|
||||||
import org.keycloak.federation.ldap.LDAPFederationProvider;
|
import org.keycloak.federation.ldap.LDAPFederationProvider;
|
||||||
import org.keycloak.federation.ldap.LDAPUtils;
|
import org.keycloak.federation.ldap.LDAPUtils;
|
||||||
import org.keycloak.federation.ldap.idm.model.LDAPDn;
|
import org.keycloak.federation.ldap.idm.model.LDAPDn;
|
||||||
|
@ -41,9 +42,11 @@ import org.keycloak.federation.ldap.mappers.membership.LDAPGroupMapperMode;
|
||||||
import org.keycloak.federation.ldap.mappers.membership.MembershipType;
|
import org.keycloak.federation.ldap.mappers.membership.MembershipType;
|
||||||
import org.keycloak.federation.ldap.mappers.membership.UserRolesRetrieveStrategy;
|
import org.keycloak.federation.ldap.mappers.membership.UserRolesRetrieveStrategy;
|
||||||
import org.keycloak.models.GroupModel;
|
import org.keycloak.models.GroupModel;
|
||||||
|
import org.keycloak.models.LDAPConstants;
|
||||||
import org.keycloak.models.ModelException;
|
import org.keycloak.models.ModelException;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserFederationMapperModel;
|
import org.keycloak.models.UserFederationMapperModel;
|
||||||
|
import org.keycloak.models.UserFederationProviderModel;
|
||||||
import org.keycloak.models.UserFederationSyncResult;
|
import org.keycloak.models.UserFederationSyncResult;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
|
@ -149,8 +152,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().getDisplayName());
|
||||||
|
|
||||||
// Get all LDAP groups
|
// Get all LDAP groups
|
||||||
LDAPQuery ldapQuery = createGroupQuery();
|
List<LDAPObject> ldapGroups = getAllLDAPGroups();
|
||||||
List<LDAPObject> ldapGroups = ldapQuery.getResultList();
|
|
||||||
|
|
||||||
// Convert to internal format
|
// Convert to internal format
|
||||||
Map<String, LDAPObject> ldapGroupsMap = new HashMap<>();
|
Map<String, LDAPObject> ldapGroupsMap = new HashMap<>();
|
||||||
|
@ -286,29 +288,46 @@ public class GroupLDAPFederationMapper extends AbstractLDAPFederationMapper impl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Override if better effectivity or different algorithm is needed
|
|
||||||
protected GroupModel findKcGroupByLDAPGroup(LDAPObject ldapGroup) {
|
protected GroupModel findKcGroupByLDAPGroup(LDAPObject ldapGroup) {
|
||||||
String groupNameAttr = config.getGroupNameLdapAttribute();
|
String groupNameAttr = config.getGroupNameLdapAttribute();
|
||||||
String groupName = ldapGroup.getAttributeAsString(groupNameAttr);
|
String groupName = ldapGroup.getAttributeAsString(groupNameAttr);
|
||||||
|
|
||||||
List<GroupModel> groups = realm.getGroups();
|
if (config.isPreserveGroupsInheritance()) {
|
||||||
for (GroupModel group : groups) {
|
// Override if better effectivity or different algorithm is needed
|
||||||
if (group.getName().equals(groupName)) {
|
List<GroupModel> groups = realm.getGroups();
|
||||||
return group;
|
for (GroupModel group : groups) {
|
||||||
|
if (group.getName().equals(groupName)) {
|
||||||
|
return group;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
} else {
|
||||||
|
// Without preserved inheritance, it's always top-level group
|
||||||
|
return KeycloakModelUtils.findGroupByPath(realm, "/" + groupName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected GroupModel findKcGroupOrSyncFromLDAP(LDAPObject ldapGroup, UserModel user) {
|
protected GroupModel findKcGroupOrSyncFromLDAP(LDAPObject ldapGroup, UserModel user) {
|
||||||
GroupModel kcGroup = findKcGroupByLDAPGroup(ldapGroup);
|
GroupModel kcGroup = findKcGroupByLDAPGroup(ldapGroup);
|
||||||
|
|
||||||
if (kcGroup == null) {
|
if (kcGroup == null) {
|
||||||
// Sync groups from LDAP
|
|
||||||
if (!syncFromLDAPPerformedInThisTransaction) {
|
if (config.isPreserveGroupsInheritance()) {
|
||||||
syncDataFromFederationProviderToKeycloak();
|
|
||||||
kcGroup = findKcGroupByLDAPGroup(ldapGroup);
|
// Better to sync all groups from LDAP with preserved inheritance
|
||||||
|
if (!syncFromLDAPPerformedInThisTransaction) {
|
||||||
|
syncDataFromFederationProviderToKeycloak();
|
||||||
|
kcGroup = findKcGroupByLDAPGroup(ldapGroup);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
String groupNameAttr = config.getGroupNameLdapAttribute();
|
||||||
|
String groupName = ldapGroup.getAttributeAsString(groupNameAttr);
|
||||||
|
|
||||||
|
kcGroup = realm.createGroup(groupName);
|
||||||
|
updateAttributesOfKCGroup(kcGroup, ldapGroup);
|
||||||
|
realm.moveGroup(kcGroup, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Could theoretically happen on some LDAP servers if 'memberof' style is used and 'memberof' attribute of user references non-existing group
|
// Could theoretically happen on some LDAP servers if 'memberof' style is used and 'memberof' attribute of user references non-existing group
|
||||||
|
@ -321,6 +340,33 @@ public class GroupLDAPFederationMapper extends AbstractLDAPFederationMapper impl
|
||||||
return kcGroup;
|
return kcGroup;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Send LDAP query to retrieve all groups
|
||||||
|
protected List<LDAPObject> getAllLDAPGroups() {
|
||||||
|
LDAPQuery ldapGroupQuery = createGroupQuery();
|
||||||
|
|
||||||
|
LDAPConfig ldapConfig = ldapProvider.getLdapIdentityStore().getConfig();
|
||||||
|
boolean pagination = ldapConfig.isPagination();
|
||||||
|
if (pagination) {
|
||||||
|
// For now reuse globally configured batch size in LDAP provider page
|
||||||
|
int pageSize = ldapConfig.getBatchSizeForSync();
|
||||||
|
|
||||||
|
List<LDAPObject> result = new LinkedList<>();
|
||||||
|
boolean nextPage = true;
|
||||||
|
|
||||||
|
while (nextPage) {
|
||||||
|
ldapGroupQuery.setLimit(pageSize);
|
||||||
|
final List<LDAPObject> currentPageGroups = ldapGroupQuery.getResultList();
|
||||||
|
result.addAll(currentPageGroups);
|
||||||
|
nextPage = ldapGroupQuery.getPaginationContext() != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
// LDAP pagination not available. Do everything in single transaction
|
||||||
|
return ldapGroupQuery.getResultList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Sync from Keycloak to LDAP
|
// Sync from Keycloak to LDAP
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ import java.util.Map;
|
||||||
|
|
||||||
import org.keycloak.federation.ldap.LDAPConfig;
|
import org.keycloak.federation.ldap.LDAPConfig;
|
||||||
import org.keycloak.federation.ldap.LDAPFederationProvider;
|
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.AbstractLDAPFederationMapper;
|
||||||
import org.keycloak.federation.ldap.mappers.AbstractLDAPFederationMapperFactory;
|
import org.keycloak.federation.ldap.mappers.AbstractLDAPFederationMapperFactory;
|
||||||
import org.keycloak.federation.ldap.mappers.membership.CommonLDAPGroupMapperConfig;
|
import org.keycloak.federation.ldap.mappers.membership.CommonLDAPGroupMapperConfig;
|
||||||
|
@ -33,7 +34,7 @@ import org.keycloak.federation.ldap.mappers.membership.LDAPGroupMapperMode;
|
||||||
import org.keycloak.federation.ldap.mappers.membership.MembershipType;
|
import org.keycloak.federation.ldap.mappers.membership.MembershipType;
|
||||||
import org.keycloak.federation.ldap.mappers.membership.UserRolesRetrieveStrategy;
|
import org.keycloak.federation.ldap.mappers.membership.UserRolesRetrieveStrategy;
|
||||||
import org.keycloak.federation.ldap.mappers.membership.role.RoleMapperConfig;
|
import org.keycloak.federation.ldap.mappers.membership.role.RoleMapperConfig;
|
||||||
import org.keycloak.mappers.MapperConfigValidationException;
|
import org.keycloak.mappers.FederationConfigValidationException;
|
||||||
import org.keycloak.models.LDAPConstants;
|
import org.keycloak.models.LDAPConstants;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserFederationMapperModel;
|
import org.keycloak.models.UserFederationMapperModel;
|
||||||
|
@ -185,7 +186,7 @@ public class GroupLDAPFederationMapperFactory extends AbstractLDAPFederationMapp
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void validateConfig(RealmModel realm, UserFederationMapperModel mapperModel) throws MapperConfigValidationException {
|
public void validateConfig(RealmModel realm, UserFederationProviderModel fedProviderModel, UserFederationMapperModel mapperModel) throws FederationConfigValidationException {
|
||||||
checkMandatoryConfigAttribute(GroupMapperConfig.GROUPS_DN, "LDAP Groups DN", mapperModel);
|
checkMandatoryConfigAttribute(GroupMapperConfig.GROUPS_DN, "LDAP Groups DN", mapperModel);
|
||||||
checkMandatoryConfigAttribute(GroupMapperConfig.MODE, "Mode", mapperModel);
|
checkMandatoryConfigAttribute(GroupMapperConfig.MODE, "Mode", mapperModel);
|
||||||
|
|
||||||
|
@ -193,8 +194,10 @@ public class GroupLDAPFederationMapperFactory extends AbstractLDAPFederationMapp
|
||||||
MembershipType membershipType = mt==null ? MembershipType.DN : Enum.valueOf(MembershipType.class, mt);
|
MembershipType membershipType = mt==null ? MembershipType.DN : Enum.valueOf(MembershipType.class, mt);
|
||||||
boolean preserveGroupInheritance = Boolean.parseBoolean(mapperModel.getConfig().get(GroupMapperConfig.PRESERVE_GROUP_INHERITANCE));
|
boolean preserveGroupInheritance = Boolean.parseBoolean(mapperModel.getConfig().get(GroupMapperConfig.PRESERVE_GROUP_INHERITANCE));
|
||||||
if (preserveGroupInheritance && membershipType != MembershipType.DN) {
|
if (preserveGroupInheritance && membershipType != MembershipType.DN) {
|
||||||
throw new MapperConfigValidationException("Not possible to preserve group inheritance and use UID membership type together");
|
throw new FederationConfigValidationException("ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LDAPUtils.validateCustomLdapFilter(mapperModel.getConfig().get(GroupMapperConfig.GROUPS_LDAP_FILTER));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -26,12 +26,14 @@ import java.util.Map;
|
||||||
|
|
||||||
import org.keycloak.federation.ldap.LDAPConfig;
|
import org.keycloak.federation.ldap.LDAPConfig;
|
||||||
import org.keycloak.federation.ldap.LDAPFederationProvider;
|
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.AbstractLDAPFederationMapper;
|
||||||
import org.keycloak.federation.ldap.mappers.AbstractLDAPFederationMapperFactory;
|
import org.keycloak.federation.ldap.mappers.AbstractLDAPFederationMapperFactory;
|
||||||
import org.keycloak.federation.ldap.mappers.membership.LDAPGroupMapperMode;
|
import org.keycloak.federation.ldap.mappers.membership.LDAPGroupMapperMode;
|
||||||
import org.keycloak.federation.ldap.mappers.membership.MembershipType;
|
import org.keycloak.federation.ldap.mappers.membership.MembershipType;
|
||||||
import org.keycloak.federation.ldap.mappers.membership.UserRolesRetrieveStrategy;
|
import org.keycloak.federation.ldap.mappers.membership.UserRolesRetrieveStrategy;
|
||||||
import org.keycloak.mappers.MapperConfigValidationException;
|
import org.keycloak.federation.ldap.mappers.membership.group.GroupMapperConfig;
|
||||||
|
import org.keycloak.mappers.FederationConfigValidationException;
|
||||||
import org.keycloak.models.LDAPConstants;
|
import org.keycloak.models.LDAPConstants;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserFederationMapperModel;
|
import org.keycloak.models.UserFederationMapperModel;
|
||||||
|
@ -178,7 +180,7 @@ public class RoleLDAPFederationMapperFactory extends AbstractLDAPFederationMappe
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void validateConfig(RealmModel realm, UserFederationMapperModel mapperModel) throws MapperConfigValidationException {
|
public void validateConfig(RealmModel realm, UserFederationProviderModel fedProviderModel, UserFederationMapperModel mapperModel) throws FederationConfigValidationException {
|
||||||
checkMandatoryConfigAttribute(RoleMapperConfig.ROLES_DN, "LDAP Roles DN", mapperModel);
|
checkMandatoryConfigAttribute(RoleMapperConfig.ROLES_DN, "LDAP Roles DN", mapperModel);
|
||||||
checkMandatoryConfigAttribute(RoleMapperConfig.MODE, "Mode", mapperModel);
|
checkMandatoryConfigAttribute(RoleMapperConfig.MODE, "Mode", mapperModel);
|
||||||
|
|
||||||
|
@ -187,14 +189,11 @@ public class RoleLDAPFederationMapperFactory extends AbstractLDAPFederationMappe
|
||||||
if (!useRealmMappings) {
|
if (!useRealmMappings) {
|
||||||
String clientId = mapperModel.getConfig().get(RoleMapperConfig.CLIENT_ID);
|
String clientId = mapperModel.getConfig().get(RoleMapperConfig.CLIENT_ID);
|
||||||
if (clientId == null || clientId.trim().isEmpty()) {
|
if (clientId == null || clientId.trim().isEmpty()) {
|
||||||
throw new MapperConfigValidationException("Client ID needs to be provided in config when Realm Roles Mapping is not used");
|
throw new FederationConfigValidationException("ldapErrorMissingClientId");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String customLdapFilter = mapperModel.getConfig().get(RoleMapperConfig.ROLES_LDAP_FILTER);
|
LDAPUtils.validateCustomLdapFilter(mapperModel.getConfig().get(RoleMapperConfig.ROLES_LDAP_FILTER));
|
||||||
if ((customLdapFilter != null && customLdapFilter.trim().length() > 0) && (!customLdapFilter.startsWith("(") || !customLdapFilter.endsWith(")"))) {
|
|
||||||
throw new MapperConfigValidationException("Custom Roles LDAP filter must starts with '(' and ends with ')'");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -25,7 +25,7 @@ import java.util.Map;
|
||||||
import org.keycloak.federation.ldap.LDAPFederationProvider;
|
import org.keycloak.federation.ldap.LDAPFederationProvider;
|
||||||
import org.keycloak.federation.ldap.mappers.AbstractLDAPFederationMapper;
|
import org.keycloak.federation.ldap.mappers.AbstractLDAPFederationMapper;
|
||||||
import org.keycloak.federation.ldap.mappers.AbstractLDAPFederationMapperFactory;
|
import org.keycloak.federation.ldap.mappers.AbstractLDAPFederationMapperFactory;
|
||||||
import org.keycloak.mappers.MapperConfigValidationException;
|
import org.keycloak.mappers.FederationConfigValidationException;
|
||||||
import org.keycloak.models.LDAPConstants;
|
import org.keycloak.models.LDAPConstants;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserFederationMapperModel;
|
import org.keycloak.models.UserFederationMapperModel;
|
||||||
|
@ -75,7 +75,7 @@ public class MSADUserAccountControlMapperFactory extends AbstractLDAPFederationM
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void validateConfig(RealmModel realm, UserFederationMapperModel mapperModel) throws MapperConfigValidationException {
|
public void validateConfig(RealmModel realm, UserFederationProviderModel fedProviderModel, UserFederationMapperModel mapperModel) throws FederationConfigValidationException {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -20,13 +20,28 @@ package org.keycloak.mappers;
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
*/
|
*/
|
||||||
public class MapperConfigValidationException extends Exception {
|
public class FederationConfigValidationException extends Exception {
|
||||||
|
|
||||||
public MapperConfigValidationException(String message) {
|
private Object[] parameters;
|
||||||
|
|
||||||
|
public FederationConfigValidationException(String message) {
|
||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MapperConfigValidationException(String message, Throwable cause) {
|
public FederationConfigValidationException(String message, Throwable cause) {
|
||||||
super(message, cause);
|
super(message, cause);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public FederationConfigValidationException(String message, Object ... parameters) {
|
||||||
|
super(message);
|
||||||
|
this.parameters = parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object[] getParameters() {
|
||||||
|
return parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setParameters(Object[] parameters) {
|
||||||
|
this.parameters = parameters;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -52,10 +52,12 @@ public interface UserFederationMapperFactory extends ProviderFactory<UserFederat
|
||||||
/**
|
/**
|
||||||
* Called when instance of mapperModel is created for this factory through admin endpoint
|
* Called when instance of mapperModel is created for this factory through admin endpoint
|
||||||
*
|
*
|
||||||
|
* @param realm
|
||||||
|
* @param fedProviderModel
|
||||||
* @param mapperModel
|
* @param mapperModel
|
||||||
* @throws MapperConfigValidationException if configuration provided in mapperModel is not valid
|
* @throws FederationConfigValidationException if configuration provided in mapperModel is not valid
|
||||||
*/
|
*/
|
||||||
void validateConfig(RealmModel realm, UserFederationMapperModel mapperModel) throws MapperConfigValidationException;
|
void validateConfig(RealmModel realm, UserFederationProviderModel fedProviderModel, UserFederationMapperModel mapperModel) throws FederationConfigValidationException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to detect what are default values for ProviderConfigProperties specified during mapper creation
|
* Used to detect what are default values for ProviderConfigProperties specified during mapper creation
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* 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.models;
|
||||||
|
|
||||||
|
import org.keycloak.mappers.FederationConfigValidationException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: Merge with UserFederationProviderFactory and add "default" method validateConfig with empty body once we move to source level 1.8
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public interface UserFederationValidatingProviderFactory extends UserFederationProviderFactory {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when instance of mapperModel is created for this factory through admin endpoint
|
||||||
|
*
|
||||||
|
* @param realm
|
||||||
|
* @param providerModel
|
||||||
|
* @throws FederationConfigValidationException if configuration provided in mapperModel is not valid
|
||||||
|
*/
|
||||||
|
void validateConfig(RealmModel realm, UserFederationProviderModel providerModel) throws FederationConfigValidationException;
|
||||||
|
}
|
|
@ -411,8 +411,12 @@ public final class KeycloakModelUtils {
|
||||||
return mapperModel;
|
return mapperModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static UserFederationProviderFactory getFederationProviderFactory(KeycloakSession session, UserFederationProviderModel model) {
|
||||||
|
return (UserFederationProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(UserFederationProvider.class, model.getProviderName());
|
||||||
|
}
|
||||||
|
|
||||||
public static UserFederationProvider getFederationProviderInstance(KeycloakSession session, UserFederationProviderModel model) {
|
public static UserFederationProvider getFederationProviderInstance(KeycloakSession session, UserFederationProviderModel model) {
|
||||||
UserFederationProviderFactory factory = (UserFederationProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(UserFederationProvider.class, model.getProviderName());
|
UserFederationProviderFactory factory = getFederationProviderFactory(session, model);
|
||||||
return factory.getInstance(session, model);
|
return factory.getInstance(session, model);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,12 +16,14 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.services.resources.admin;
|
package org.keycloak.services.resources.admin;
|
||||||
|
|
||||||
|
import java.text.MessageFormat;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
import javax.ws.rs.Consumes;
|
import javax.ws.rs.Consumes;
|
||||||
import javax.ws.rs.DELETE;
|
import javax.ws.rs.DELETE;
|
||||||
|
@ -40,7 +42,7 @@ import javax.ws.rs.core.UriInfo;
|
||||||
import org.jboss.resteasy.annotations.cache.NoCache;
|
import org.jboss.resteasy.annotations.cache.NoCache;
|
||||||
import org.jboss.resteasy.spi.NotFoundException;
|
import org.jboss.resteasy.spi.NotFoundException;
|
||||||
import org.keycloak.events.admin.OperationType;
|
import org.keycloak.events.admin.OperationType;
|
||||||
import org.keycloak.mappers.MapperConfigValidationException;
|
import org.keycloak.mappers.FederationConfigValidationException;
|
||||||
import org.keycloak.mappers.UserFederationMapper;
|
import org.keycloak.mappers.UserFederationMapper;
|
||||||
import org.keycloak.mappers.UserFederationMapperFactory;
|
import org.keycloak.mappers.UserFederationMapperFactory;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
@ -63,7 +65,6 @@ import org.keycloak.representations.idm.UserFederationProviderRepresentation;
|
||||||
import org.keycloak.services.ErrorResponseException;
|
import org.keycloak.services.ErrorResponseException;
|
||||||
import org.keycloak.services.ServicesLogger;
|
import org.keycloak.services.ServicesLogger;
|
||||||
import org.keycloak.services.managers.UsersSyncManager;
|
import org.keycloak.services.managers.UsersSyncManager;
|
||||||
import org.keycloak.timer.TimerProvider;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
@ -105,6 +106,9 @@ public class UserFederationProviderResource {
|
||||||
}
|
}
|
||||||
UserFederationProviderModel model = new UserFederationProviderModel(rep.getId(), rep.getProviderName(), rep.getConfig(), rep.getPriority(), displayName,
|
UserFederationProviderModel model = new UserFederationProviderModel(rep.getId(), rep.getProviderName(), rep.getConfig(), rep.getPriority(), displayName,
|
||||||
rep.getFullSyncPeriod(), rep.getChangedSyncPeriod(), rep.getLastSync());
|
rep.getFullSyncPeriod(), rep.getChangedSyncPeriod(), rep.getLastSync());
|
||||||
|
|
||||||
|
UserFederationProvidersResource.validateFederationProviderConfig(session, auth, realm, model);
|
||||||
|
|
||||||
realm.updateUserFederationProvider(model);
|
realm.updateUserFederationProvider(model);
|
||||||
new UsersSyncManager().notifyToRefreshPeriodicSync(session, realm, model, false);
|
new UsersSyncManager().notifyToRefreshPeriodicSync(session, realm, model, false);
|
||||||
boolean kerberosCredsAdded = UserFederationProvidersResource.checkKerberosCredential(session, realm, model);
|
boolean kerberosCredsAdded = UserFederationProvidersResource.checkKerberosCredential(session, realm, model);
|
||||||
|
@ -369,9 +373,12 @@ public class UserFederationProviderResource {
|
||||||
private void validateModel(UserFederationMapperModel model) {
|
private void validateModel(UserFederationMapperModel model) {
|
||||||
try {
|
try {
|
||||||
UserFederationMapperFactory mapperFactory = (UserFederationMapperFactory) session.getKeycloakSessionFactory().getProviderFactory(UserFederationMapper.class, model.getFederationMapperType());
|
UserFederationMapperFactory mapperFactory = (UserFederationMapperFactory) session.getKeycloakSessionFactory().getProviderFactory(UserFederationMapper.class, model.getFederationMapperType());
|
||||||
mapperFactory.validateConfig(realm, model);
|
mapperFactory.validateConfig(realm, federationProviderModel, model);
|
||||||
} catch (MapperConfigValidationException ex) {
|
} catch (FederationConfigValidationException ex) {
|
||||||
throw new ErrorResponseException("Validation error", ex.getMessage(), Response.Status.BAD_REQUEST);
|
logger.error(ex.getMessage());
|
||||||
|
Properties messages = AdminRoot.getMessages(session, realm, auth.getAuth().getToken().getLocale());
|
||||||
|
throw new ErrorResponseException(ex.getMessage(), MessageFormat.format(messages.getProperty(ex.getMessage(), ex.getMessage()), ex.getParameters()),
|
||||||
|
Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,11 +21,13 @@ import org.jboss.resteasy.spi.NotFoundException;
|
||||||
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
||||||
import org.keycloak.common.constants.KerberosConstants;
|
import org.keycloak.common.constants.KerberosConstants;
|
||||||
import org.keycloak.events.admin.OperationType;
|
import org.keycloak.events.admin.OperationType;
|
||||||
|
import org.keycloak.mappers.FederationConfigValidationException;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserFederationProvider;
|
import org.keycloak.models.UserFederationProvider;
|
||||||
import org.keycloak.models.UserFederationProviderFactory;
|
import org.keycloak.models.UserFederationProviderFactory;
|
||||||
import org.keycloak.models.UserFederationProviderModel;
|
import org.keycloak.models.UserFederationProviderModel;
|
||||||
|
import org.keycloak.models.UserFederationValidatingProviderFactory;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
import org.keycloak.models.utils.ModelToRepresentation;
|
import org.keycloak.models.utils.ModelToRepresentation;
|
||||||
import org.keycloak.provider.ConfiguredProvider;
|
import org.keycloak.provider.ConfiguredProvider;
|
||||||
|
@ -35,6 +37,8 @@ import org.keycloak.representations.idm.ConfigPropertyRepresentation;
|
||||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||||
import org.keycloak.representations.idm.UserFederationProviderFactoryRepresentation;
|
import org.keycloak.representations.idm.UserFederationProviderFactoryRepresentation;
|
||||||
import org.keycloak.representations.idm.UserFederationProviderRepresentation;
|
import org.keycloak.representations.idm.UserFederationProviderRepresentation;
|
||||||
|
import org.keycloak.services.ErrorResponse;
|
||||||
|
import org.keycloak.services.ErrorResponseException;
|
||||||
import org.keycloak.services.ServicesLogger;
|
import org.keycloak.services.ServicesLogger;
|
||||||
import org.keycloak.services.managers.UsersSyncManager;
|
import org.keycloak.services.managers.UsersSyncManager;
|
||||||
import org.keycloak.timer.TimerProvider;
|
import org.keycloak.timer.TimerProvider;
|
||||||
|
@ -51,9 +55,11 @@ import javax.ws.rs.core.MediaType;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import javax.ws.rs.core.UriInfo;
|
import javax.ws.rs.core.UriInfo;
|
||||||
|
|
||||||
|
import java.text.MessageFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base resource for managing users
|
* Base resource for managing users
|
||||||
|
@ -101,6 +107,20 @@ public class UserFederationProvidersResource {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void validateFederationProviderConfig(KeycloakSession session, RealmAuth auth, RealmModel realm, UserFederationProviderModel model) {
|
||||||
|
UserFederationProviderFactory providerFactory = KeycloakModelUtils.getFederationProviderFactory(session, model);
|
||||||
|
if (providerFactory instanceof UserFederationValidatingProviderFactory) {
|
||||||
|
try {
|
||||||
|
((UserFederationValidatingProviderFactory) providerFactory).validateConfig(realm, model);
|
||||||
|
} catch (FederationConfigValidationException fcve) {
|
||||||
|
logger.error(fcve.getMessage());
|
||||||
|
Properties messages = AdminRoot.getMessages(session, realm, auth.getAuth().getToken().getLocale());
|
||||||
|
throw new ErrorResponseException(fcve.getMessage(), MessageFormat.format(messages.getProperty(fcve.getMessage(), fcve.getMessage()), fcve.getParameters()),
|
||||||
|
Response.Status.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get available provider factories
|
* Get available provider factories
|
||||||
*
|
*
|
||||||
|
@ -176,6 +196,10 @@ public class UserFederationProvidersResource {
|
||||||
if (displayName != null && displayName.trim().equals("")) {
|
if (displayName != null && displayName.trim().equals("")) {
|
||||||
displayName = null;
|
displayName = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UserFederationProviderModel tempModel = new UserFederationProviderModel(null, rep.getProviderName(), rep.getConfig(), rep.getPriority(), displayName, rep.getFullSyncPeriod(), rep.getChangedSyncPeriod(), rep.getLastSync());
|
||||||
|
validateFederationProviderConfig(session, auth, realm, tempModel);
|
||||||
|
|
||||||
UserFederationProviderModel model = realm.addUserFederationProvider(rep.getProviderName(), rep.getConfig(), rep.getPriority(), displayName,
|
UserFederationProviderModel model = realm.addUserFederationProvider(rep.getProviderName(), rep.getConfig(), rep.getPriority(), displayName,
|
||||||
rep.getFullSyncPeriod(), rep.getChangedSyncPeriod(), rep.getLastSync());
|
rep.getFullSyncPeriod(), rep.getChangedSyncPeriod(), rep.getLastSync());
|
||||||
new UsersSyncManager().notifyToRefreshPeriodicSync(session, realm, model, false);
|
new UsersSyncManager().notifyToRefreshPeriodicSync(session, realm, model, false);
|
||||||
|
|
|
@ -49,6 +49,9 @@ public class LdapUserProviderForm extends Form {
|
||||||
@FindBy(id = "ldapBindCredential")
|
@FindBy(id = "ldapBindCredential")
|
||||||
private WebElement ldapBindCredentialInput;
|
private WebElement ldapBindCredentialInput;
|
||||||
|
|
||||||
|
@FindBy(id = "customUserSearchFilter")
|
||||||
|
private WebElement customUserSearchFilterInput;
|
||||||
|
|
||||||
@FindBy(id = "searchScope")
|
@FindBy(id = "searchScope")
|
||||||
private Select searchScopeSelect;
|
private Select searchScopeSelect;
|
||||||
|
|
||||||
|
@ -155,6 +158,10 @@ public class LdapUserProviderForm extends Form {
|
||||||
setInputValue(ldapBindCredentialInput, ldapBindCredential);
|
setInputValue(ldapBindCredentialInput, ldapBindCredential);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setCustomUserSearchFilter(String customUserSearchFilter) {
|
||||||
|
setInputValue(customUserSearchFilterInput, customUserSearchFilter);
|
||||||
|
}
|
||||||
|
|
||||||
public void setKerberosRealmInput(String kerberosRealm) {
|
public void setKerberosRealmInput(String kerberosRealm) {
|
||||||
setInputValue(kerberosRealmInput, kerberosRealm);
|
setInputValue(kerberosRealmInput, kerberosRealm);
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,8 +105,25 @@ public class LdapUserFederationTest extends AbstractConsoleTest {
|
||||||
createLdapUserProvider.form().save();
|
createLdapUserProvider.form().save();
|
||||||
assertAlertDanger();
|
assertAlertDanger();
|
||||||
createLdapUserProvider.form().setLdapBindCredentialInput("secret");
|
createLdapUserProvider.form().setLdapBindCredentialInput("secret");
|
||||||
|
|
||||||
|
createLdapUserProvider.form().setCustomUserSearchFilter("foo");
|
||||||
|
createLdapUserProvider.form().save();
|
||||||
|
assertAlertDanger();
|
||||||
|
createLdapUserProvider.form().setCustomUserSearchFilter("");
|
||||||
createLdapUserProvider.form().save();
|
createLdapUserProvider.form().save();
|
||||||
assertAlertSuccess();
|
assertAlertSuccess();
|
||||||
|
|
||||||
|
// Try updating invalid Custom LDAP Filter
|
||||||
|
createLdapUserProvider.form().setCustomUserSearchFilter("(foo=bar");
|
||||||
|
createLdapUserProvider.form().save();
|
||||||
|
assertAlertDanger();
|
||||||
|
createLdapUserProvider.form().setCustomUserSearchFilter("foo=bar)");
|
||||||
|
createLdapUserProvider.form().save();
|
||||||
|
assertAlertDanger();
|
||||||
|
createLdapUserProvider.form().setCustomUserSearchFilter("(foo=bar)");
|
||||||
|
createLdapUserProvider.form().save();
|
||||||
|
assertAlertSuccess();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -29,6 +29,7 @@ import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.federation.ldap.LDAPConfig;
|
import org.keycloak.federation.ldap.LDAPConfig;
|
||||||
import org.keycloak.federation.ldap.LDAPFederationProvider;
|
import org.keycloak.federation.ldap.LDAPFederationProvider;
|
||||||
import org.keycloak.federation.ldap.LDAPFederationProviderFactory;
|
import org.keycloak.federation.ldap.LDAPFederationProviderFactory;
|
||||||
|
import org.keycloak.federation.ldap.LDAPUtils;
|
||||||
import org.keycloak.federation.ldap.idm.model.LDAPObject;
|
import org.keycloak.federation.ldap.idm.model.LDAPObject;
|
||||||
import org.keycloak.federation.ldap.mappers.FullNameLDAPFederationMapper;
|
import org.keycloak.federation.ldap.mappers.FullNameLDAPFederationMapper;
|
||||||
import org.keycloak.federation.ldap.mappers.FullNameLDAPFederationMapperFactory;
|
import org.keycloak.federation.ldap.mappers.FullNameLDAPFederationMapperFactory;
|
||||||
|
@ -521,7 +522,7 @@ public class FederationProvidersIntegrationTest {
|
||||||
|
|
||||||
UserFederationMapperModel fullNameMapperModel = KeycloakModelUtils.createUserFederationMapperModel("full name", ldapModel.getId(), FullNameLDAPFederationMapperFactory.PROVIDER_ID,
|
UserFederationMapperModel fullNameMapperModel = KeycloakModelUtils.createUserFederationMapperModel("full name", ldapModel.getId(), FullNameLDAPFederationMapperFactory.PROVIDER_ID,
|
||||||
FullNameLDAPFederationMapper.LDAP_FULL_NAME_ATTRIBUTE, ldapFirstNameAttributeName,
|
FullNameLDAPFederationMapper.LDAP_FULL_NAME_ATTRIBUTE, ldapFirstNameAttributeName,
|
||||||
UserAttributeLDAPFederationMapper.READ_ONLY, "false");
|
FullNameLDAPFederationMapper.READ_ONLY, "false");
|
||||||
appRealm.addUserFederationMapper(fullNameMapperModel);
|
appRealm.addUserFederationMapper(fullNameMapperModel);
|
||||||
} finally {
|
} finally {
|
||||||
keycloakRule.stopSession(session, true);
|
keycloakRule.stopSession(session, true);
|
||||||
|
@ -534,6 +535,36 @@ public class FederationProvidersIntegrationTest {
|
||||||
// Assert user is successfully imported in Keycloak DB now with correct firstName and lastName
|
// Assert user is successfully imported in Keycloak DB now with correct firstName and lastName
|
||||||
FederationTestUtils.assertUserImported(session.users(), appRealm, "fullname", "James", "Dee", "fullname@email.org", "4578");
|
FederationTestUtils.assertUserImported(session.users(), appRealm, "fullname", "James", "Dee", "fullname@email.org", "4578");
|
||||||
|
|
||||||
|
// change mapper to writeOnly
|
||||||
|
UserFederationMapperModel fullNameMapperModel = appRealm.getUserFederationMapperByName(ldapModel.getId(), "full name");
|
||||||
|
fullNameMapperModel.getConfig().put(FullNameLDAPFederationMapper.WRITE_ONLY, "true");
|
||||||
|
appRealm.updateUserFederationMapper(fullNameMapperModel);
|
||||||
|
} finally {
|
||||||
|
keycloakRule.stopSession(session, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Assert changing user in Keycloak will change him in LDAP too...
|
||||||
|
session = keycloakRule.startSession();
|
||||||
|
try {
|
||||||
|
RealmModel appRealm = new RealmManager(session).getRealmByName("test");
|
||||||
|
|
||||||
|
UserModel fullnameUser = session.users().getUserByUsername("fullname", appRealm);
|
||||||
|
fullnameUser.setFirstName("James2");
|
||||||
|
fullnameUser.setLastName("Dee2");
|
||||||
|
} finally {
|
||||||
|
keycloakRule.stopSession(session, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Assert changed user available in Keycloak
|
||||||
|
session = keycloakRule.startSession();
|
||||||
|
try {
|
||||||
|
RealmModel appRealm = new RealmManager(session).getRealmByName("test");
|
||||||
|
|
||||||
|
// Assert user is successfully imported in Keycloak DB now with correct firstName and lastName
|
||||||
|
FederationTestUtils.assertUserImported(session.users(), appRealm, "fullname", "James2", "Dee2", "fullname@email.org", "4578");
|
||||||
|
|
||||||
// Remove "fullnameUser" to assert he is removed from LDAP. Revert mappers to previous state
|
// Remove "fullnameUser" to assert he is removed from LDAP. Revert mappers to previous state
|
||||||
UserModel fullnameUser = session.users().getUserByUsername("fullname", appRealm);
|
UserModel fullnameUser = session.users().getUserByUsername("fullname", appRealm);
|
||||||
session.users().removeUser(appRealm, fullnameUser);
|
session.users().removeUser(appRealm, fullnameUser);
|
||||||
|
|
|
@ -61,6 +61,7 @@ public class LDAPGroupMapper2WaySyncTest {
|
||||||
Map<String,String> ldapConfig = ldapRule.getConfig();
|
Map<String,String> ldapConfig = ldapRule.getConfig();
|
||||||
ldapConfig.put(LDAPConstants.SYNC_REGISTRATIONS, "true");
|
ldapConfig.put(LDAPConstants.SYNC_REGISTRATIONS, "true");
|
||||||
ldapConfig.put(LDAPConstants.EDIT_MODE, UserFederationProvider.EditMode.WRITABLE.toString());
|
ldapConfig.put(LDAPConstants.EDIT_MODE, UserFederationProvider.EditMode.WRITABLE.toString());
|
||||||
|
ldapConfig.put(LDAPConstants.BATCH_SIZE_FOR_SYNC, "4"); // Issues with pagination on ApacheDS
|
||||||
|
|
||||||
ldapModel = appRealm.addUserFederationProvider(LDAPFederationProviderFactory.PROVIDER_NAME, ldapConfig, 0, "test-ldap", -1, -1, 0);
|
ldapModel = appRealm.addUserFederationProvider(LDAPFederationProviderFactory.PROVIDER_NAME, ldapConfig, 0, "test-ldap", -1, -1, 0);
|
||||||
LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel);
|
LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel);
|
||||||
|
|
|
@ -19,6 +19,7 @@ package org.keycloak.testsuite.federation.ldap.base;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
@ -46,6 +47,7 @@ import org.keycloak.models.UserFederationMapperModel;
|
||||||
import org.keycloak.models.UserFederationProvider;
|
import org.keycloak.models.UserFederationProvider;
|
||||||
import org.keycloak.models.UserFederationProviderModel;
|
import org.keycloak.models.UserFederationProviderModel;
|
||||||
import org.keycloak.models.UserFederationSyncResult;
|
import org.keycloak.models.UserFederationSyncResult;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
import org.keycloak.services.managers.RealmManager;
|
import org.keycloak.services.managers.RealmManager;
|
||||||
import org.keycloak.testsuite.federation.ldap.FederationTestUtils;
|
import org.keycloak.testsuite.federation.ldap.FederationTestUtils;
|
||||||
|
@ -258,4 +260,53 @@ public class LDAPGroupMapperSyncTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test04_syncNoPreserveGroupInheritanceWithLazySync() throws Exception {
|
||||||
|
KeycloakSession session = keycloakRule.startSession();
|
||||||
|
try {
|
||||||
|
RealmModel realm = session.realms().getRealmByName("test");
|
||||||
|
UserFederationMapperModel mapperModel = realm.getUserFederationMapperByName(ldapModel.getId(), "groupsMapper");
|
||||||
|
LDAPFederationProvider ldapProvider = FederationTestUtils.getLdapProvider(session, ldapModel);
|
||||||
|
GroupLDAPFederationMapper groupMapper = FederationTestUtils.getGroupMapper(mapperModel, ldapProvider, realm);
|
||||||
|
|
||||||
|
// Update group mapper to skip preserve inheritance
|
||||||
|
FederationTestUtils.updateGroupMapperConfigOptions(mapperModel, GroupMapperConfig.PRESERVE_GROUP_INHERITANCE, "false");
|
||||||
|
realm.updateUserFederationMapper(mapperModel);
|
||||||
|
|
||||||
|
// Add user to LDAP and put him as member of group11
|
||||||
|
FederationTestUtils.removeAllLDAPUsers(ldapProvider, realm);
|
||||||
|
LDAPObject johnLdap = FederationTestUtils.addLDAPUser(ldapProvider, realm, "johnkeycloak", "John", "Doe", "john@email.org", null, "1234");
|
||||||
|
FederationTestUtils.updateLDAPPassword(ldapProvider, johnLdap, "Password1");
|
||||||
|
groupMapper.addGroupMappingInLDAP("group11", johnLdap);
|
||||||
|
|
||||||
|
// Assert groups not yet imported to Keycloak DB
|
||||||
|
Assert.assertNull(KeycloakModelUtils.findGroupByPath(realm, "/group1"));
|
||||||
|
Assert.assertNull(KeycloakModelUtils.findGroupByPath(realm, "/group11"));
|
||||||
|
Assert.assertNull(KeycloakModelUtils.findGroupByPath(realm, "/group12"));
|
||||||
|
|
||||||
|
// Load user from LDAP to Keycloak DB
|
||||||
|
UserModel john = session.users().getUserByUsername("johnkeycloak", realm);
|
||||||
|
Set<GroupModel> johnGroups = john.getGroups();
|
||||||
|
|
||||||
|
// Assert just those groups, which john was memberOf exists because they were lazily created
|
||||||
|
GroupModel group1 = KeycloakModelUtils.findGroupByPath(realm, "/group1");
|
||||||
|
GroupModel group11 = KeycloakModelUtils.findGroupByPath(realm, "/group11");
|
||||||
|
GroupModel group12 = KeycloakModelUtils.findGroupByPath(realm, "/group12");
|
||||||
|
Assert.assertNull(group1);
|
||||||
|
Assert.assertNotNull(group11);
|
||||||
|
Assert.assertNull(group12);
|
||||||
|
|
||||||
|
Assert.assertEquals(1, johnGroups.size());
|
||||||
|
Assert.assertTrue(johnGroups.contains(group11));
|
||||||
|
|
||||||
|
// Delete group mapping
|
||||||
|
john.leaveGroup(group11);
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
keycloakRule.stopSession(session, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,4 +5,10 @@ invalidPasswordMinUpperCaseCharsMessage=Invalid password: must contain at least
|
||||||
invalidPasswordMinSpecialCharsMessage=Invalid password: must contain at least {0} special characters.
|
invalidPasswordMinSpecialCharsMessage=Invalid password: must contain at least {0} special characters.
|
||||||
invalidPasswordNotUsernameMessage=Invalid password: must not be equal to the username.
|
invalidPasswordNotUsernameMessage=Invalid password: must not be equal to the username.
|
||||||
invalidPasswordRegexPatternMessage=Invalid password: fails to match regex pattern(s).
|
invalidPasswordRegexPatternMessage=Invalid password: fails to match regex pattern(s).
|
||||||
invalidPasswordHistoryMessage=Invalid password: must not be equal to any of last {0} passwords.
|
invalidPasswordHistoryMessage=Invalid password: must not be equal to any of last {0} passwords.
|
||||||
|
|
||||||
|
ldapErrorInvalidCustomFilter=Custom configured LDAP filter does not start with "(" or does not end with ")".
|
||||||
|
ldapErrorMissingClientId=Client ID needs to be provided in config when Realm Roles Mapping is not used.
|
||||||
|
ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType=Not possible to preserve group inheritance and use UID membership type together.
|
||||||
|
ldapErrorCantWriteOnlyForReadOnlyLdap=Can't set write only when LDAP provider mode is not WRITABLE
|
||||||
|
ldapErrorCantWriteOnlyAndReadOnly=Can't set write-only and read-only together
|
||||||
|
|
|
@ -705,6 +705,10 @@ module.controller('GenericUserFederationCtrl', function($scope, $location, Notif
|
||||||
|
|
||||||
$location.url("/realms/" + realm.realm + "/user-federation/providers/" + $scope.instance.providerName + "/" + id);
|
$location.url("/realms/" + realm.realm + "/user-federation/providers/" + $scope.instance.providerName + "/" + id);
|
||||||
Notifications.success("The provider has been created.");
|
Notifications.success("The provider has been created.");
|
||||||
|
}, function (errorResponse) {
|
||||||
|
if (errorResponse.data && errorResponse.data['error_description']) {
|
||||||
|
Notifications.error(errorResponse.data['error_description']);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
UserFederationInstances.update({realm: realm.realm,
|
UserFederationInstances.update({realm: realm.realm,
|
||||||
|
@ -713,6 +717,10 @@ module.controller('GenericUserFederationCtrl', function($scope, $location, Notif
|
||||||
$scope.instance, function () {
|
$scope.instance, function () {
|
||||||
$route.reload();
|
$route.reload();
|
||||||
Notifications.success("The provider has been updated.");
|
Notifications.success("The provider has been updated.");
|
||||||
|
}, function (errorResponse) {
|
||||||
|
if (errorResponse.data && errorResponse.data['error_description']) {
|
||||||
|
Notifications.error(errorResponse.data['error_description']);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -909,6 +917,10 @@ module.controller('LDAPCtrl', function($scope, $location, $route, Notifications,
|
||||||
|
|
||||||
$location.url("/realms/" + realm.realm + "/user-federation/providers/" + $scope.instance.providerName + "/" + id);
|
$location.url("/realms/" + realm.realm + "/user-federation/providers/" + $scope.instance.providerName + "/" + id);
|
||||||
Notifications.success("The provider has been created.");
|
Notifications.success("The provider has been created.");
|
||||||
|
}, function (errorResponse) {
|
||||||
|
if (errorResponse.data && errorResponse.data['error_description']) {
|
||||||
|
Notifications.error(errorResponse.data['error_description']);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
UserFederationInstances.update({realm: realm.realm,
|
UserFederationInstances.update({realm: realm.realm,
|
||||||
|
@ -917,8 +929,11 @@ module.controller('LDAPCtrl', function($scope, $location, $route, Notifications,
|
||||||
$scope.instance, function () {
|
$scope.instance, function () {
|
||||||
$route.reload();
|
$route.reload();
|
||||||
Notifications.success("The provider has been updated.");
|
Notifications.success("The provider has been updated.");
|
||||||
|
}, function (errorResponse) {
|
||||||
|
if (errorResponse.data && errorResponse.data['error_description']) {
|
||||||
|
Notifications.error(errorResponse.data['error_description']);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1041,7 +1056,7 @@ module.controller('UserFederationMapperCtrl', function($scope, realm, provider,
|
||||||
Notifications.success("Your changes have been saved.");
|
Notifications.success("Your changes have been saved.");
|
||||||
}, function(error) {
|
}, function(error) {
|
||||||
if (error.status == 400 && error.data.error_description) {
|
if (error.status == 400 && error.data.error_description) {
|
||||||
Notifications.error('Error in configuration of mapper: ' + error.data.error_description);
|
Notifications.error(error.data.error_description);
|
||||||
} else {
|
} else {
|
||||||
Notifications.error('Unexpected error when creating mapper');
|
Notifications.error('Unexpected error when creating mapper');
|
||||||
}
|
}
|
||||||
|
@ -1113,7 +1128,7 @@ module.controller('UserFederationMapperCreateCtrl', function($scope, realm, prov
|
||||||
Notifications.success("Mapper has been created.");
|
Notifications.success("Mapper has been created.");
|
||||||
}, function(error) {
|
}, function(error) {
|
||||||
if (error.status == 400 && error.data.error_description) {
|
if (error.status == 400 && error.data.error_description) {
|
||||||
Notifications.error('Error in configuration of mapper: ' + error.data.error_description);
|
Notifications.error(error.data.error_description);
|
||||||
} else {
|
} else {
|
||||||
Notifications.error('Unexpected error when creating mapper');
|
Notifications.error('Unexpected error when creating mapper');
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue