Skip to content

Commit

Permalink
KEYCLOAK-886 User Federation Mappers - admin console
Browse files Browse the repository at this point in the history
  • Loading branch information
mposolda committed May 26, 2015
1 parent 269f9fe commit dfe232c
Show file tree
Hide file tree
Showing 24 changed files with 990 additions and 156 deletions.
@@ -1,5 +1,6 @@
package org.keycloak.representations.idm; package org.keycloak.representations.idm;


import java.util.LinkedList;
import java.util.List; import java.util.List;


/** /**
Expand All @@ -11,7 +12,7 @@ public class UserFederationMapperTypeRepresentation {
protected String category; protected String category;
protected String helpText; protected String helpText;


protected List<ConfigPropertyRepresentation> properties; protected List<ConfigPropertyRepresentation> properties = new LinkedList<>();


public String getId() { public String getId() {
return id; return id;
Expand Down
Expand Up @@ -24,7 +24,6 @@
import org.keycloak.models.UserFederationEventAwareProviderFactory; import org.keycloak.models.UserFederationEventAwareProviderFactory;
import org.keycloak.models.UserFederationMapperModel; import org.keycloak.models.UserFederationMapperModel;
import org.keycloak.models.UserFederationProvider; import org.keycloak.models.UserFederationProvider;
import org.keycloak.models.UserFederationProviderFactory;
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.UserModel;
Expand Down Expand Up @@ -89,33 +88,33 @@ protected void onProviderModelCreated(RealmModel realm, UserFederationProviderMo
String usernameLdapAttribute = ldapConfig.getUsernameLdapAttribute(); String usernameLdapAttribute = ldapConfig.getUsernameLdapAttribute();


UserFederationMapperModel mapperModel; UserFederationMapperModel mapperModel;
mapperModel = KeycloakModelUtils.createUserFederationMapperModel("usernameMapper", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.ID, mapperModel = KeycloakModelUtils.createUserFederationMapperModel("username", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.USERNAME, UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.USERNAME,
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, usernameLdapAttribute, UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, usernameLdapAttribute,
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly); UserAttributeLDAPFederationMapper.READ_ONLY, readOnly);
realm.addUserFederationMapper(mapperModel); realm.addUserFederationMapper(mapperModel);


// For AD deployments with sAMAccountName is probably more common to map "cn" to full name of user // For AD deployments with sAMAccountName is probably more common to map "cn" to full name of user
if (activeDirectory && usernameLdapAttribute.equalsIgnoreCase(LDAPConstants.SAM_ACCOUNT_NAME)) { if (activeDirectory && usernameLdapAttribute.equalsIgnoreCase(LDAPConstants.SAM_ACCOUNT_NAME)) {
mapperModel = KeycloakModelUtils.createUserFederationMapperModel("fullNameMapper", newProviderModel.getId(), FullNameLDAPFederationMapperFactory.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); UserAttributeLDAPFederationMapper.READ_ONLY, readOnly);
realm.addUserFederationMapper(mapperModel); realm.addUserFederationMapper(mapperModel);
} else { } else {
mapperModel = KeycloakModelUtils.createUserFederationMapperModel("firstNameMapper", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.ID, mapperModel = KeycloakModelUtils.createUserFederationMapperModel("first name", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.FIRST_NAME, UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.FIRST_NAME,
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.CN, UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.CN,
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly); UserAttributeLDAPFederationMapper.READ_ONLY, readOnly);
realm.addUserFederationMapper(mapperModel); realm.addUserFederationMapper(mapperModel);
} }


mapperModel = KeycloakModelUtils.createUserFederationMapperModel("lastNameMapper", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.ID, mapperModel = KeycloakModelUtils.createUserFederationMapperModel("last name", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.LAST_NAME, UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.LAST_NAME,
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.SN, UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.SN,
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly); UserAttributeLDAPFederationMapper.READ_ONLY, readOnly);
realm.addUserFederationMapper(mapperModel); realm.addUserFederationMapper(mapperModel);


mapperModel = KeycloakModelUtils.createUserFederationMapperModel("emailMapper", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.ID, mapperModel = KeycloakModelUtils.createUserFederationMapperModel("email", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.EMAIL, UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.EMAIL,
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.EMAIL, UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.EMAIL,
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly); UserAttributeLDAPFederationMapper.READ_ONLY, readOnly);
Expand All @@ -125,14 +124,14 @@ protected void onProviderModelCreated(RealmModel realm, UserFederationProviderMo
String modifyTimestampLdapAttrName = activeDirectory ? "whenChanged" : LDAPConstants.MODIFY_TIMESTAMP; String modifyTimestampLdapAttrName = activeDirectory ? "whenChanged" : LDAPConstants.MODIFY_TIMESTAMP;


// map createTimeStamp as read-only // map createTimeStamp as read-only
mapperModel = KeycloakModelUtils.createUserFederationMapperModel("creationDateMapper", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.ID, mapperModel = KeycloakModelUtils.createUserFederationMapperModel("creation date", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, LDAPConstants.CREATE_TIMESTAMP, UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, LDAPConstants.CREATE_TIMESTAMP,
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, createTimestampLdapAttrName, UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, createTimestampLdapAttrName,
UserAttributeLDAPFederationMapper.READ_ONLY, "true"); UserAttributeLDAPFederationMapper.READ_ONLY, "true");
realm.addUserFederationMapper(mapperModel); realm.addUserFederationMapper(mapperModel);


// map modifyTimeStamp as read-only // map modifyTimeStamp as read-only
mapperModel = KeycloakModelUtils.createUserFederationMapperModel("modifyDateMapper", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.ID, mapperModel = KeycloakModelUtils.createUserFederationMapperModel("modify date", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, LDAPConstants.MODIFY_TIMESTAMP, UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, LDAPConstants.MODIFY_TIMESTAMP,
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, modifyTimestampLdapAttrName, UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, modifyTimestampLdapAttrName,
UserAttributeLDAPFederationMapper.READ_ONLY, "true"); UserAttributeLDAPFederationMapper.READ_ONLY, "true");
Expand Down
@@ -1,25 +1,65 @@
package org.keycloak.federation.ldap.mappers; package org.keycloak.federation.ldap.mappers;


import java.util.List;
import java.util.Map;

import org.keycloak.Config; import org.keycloak.Config;
import org.keycloak.federation.ldap.LDAPFederationProviderFactory;
import org.keycloak.mappers.MapperConfigValidationException;
import org.keycloak.mappers.UserFederationMapperFactory; import org.keycloak.mappers.UserFederationMapperFactory;
import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.UserFederationMapperModel;
import org.keycloak.provider.ProviderConfigProperty;


/** /**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a> * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/ */
public abstract class AbstractLDAPFederationMapperFactory implements UserFederationMapperFactory { public abstract class AbstractLDAPFederationMapperFactory implements UserFederationMapperFactory {


// Used to map attributes from LDAP to UserModel attributes
public static final String ATTRIBUTE_MAPPER_CATEGORY = "Attribute Mapper";

// Used to map roles from LDAP to UserModel users
public static final String ROLE_MAPPER_CATEGORY = "Role Mapper";

@Override @Override
public void init(Config.Scope config) { public void init(Config.Scope config) {
} }


@Override
public String getFederationProviderType() {
return LDAPFederationProviderFactory.PROVIDER_NAME;
}

@Override @Override
public void postInit(KeycloakSessionFactory factory) { public void postInit(KeycloakSessionFactory factory) {
} }


@Override
public List<ProviderConfigProperty> getConfigProperties() {
throw new IllegalStateException("Method not supported for this implementation");
}

@Override @Override
public void close() { public void close() {
} }


public static ProviderConfigProperty createConfigProperty(String name, String label, String helpText, String type, Object defaultValue) {
ProviderConfigProperty configProperty = new ProviderConfigProperty();
configProperty.setName(name);
configProperty.setLabel(label);
configProperty.setHelpText(helpText);
configProperty.setType(type);
configProperty.setDefaultValue(defaultValue);
return configProperty;
}

protected void checkMandatoryConfigAttribute(String name, String displayName, UserFederationMapperModel mapperModel) throws MapperConfigValidationException {
String attrConfigValue = mapperModel.getConfig().get(name);
if (attrConfigValue == null || attrConfigValue.trim().isEmpty()) {
throw new MapperConfigValidationException("Missing configuration for '" + displayName + "'");
}
}



} }
@@ -1,31 +1,63 @@
package org.keycloak.federation.ldap.mappers; package org.keycloak.federation.ldap.mappers;


import java.util.ArrayList;
import java.util.List; import java.util.List;


import org.keycloak.mappers.MapperConfigValidationException;
import org.keycloak.mappers.UserFederationMapper; import org.keycloak.mappers.UserFederationMapper;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserFederationMapperModel;
import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.provider.ProviderConfigProperty;


/** /**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a> * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/ */
public class FullNameLDAPFederationMapperFactory extends AbstractLDAPFederationMapperFactory { public class FullNameLDAPFederationMapperFactory extends AbstractLDAPFederationMapperFactory {


public static final String ID = "full-name-ldap-mapper"; 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. In most cases it will be 'cn' ", ProviderConfigProperty.STRING_TYPE, LDAPConstants.CN);
configProperties.add(userModelAttribute);

ProviderConfigProperty readOnly = createConfigProperty(UserAttributeLDAPFederationMapper.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, "false");
configProperties.add(readOnly);
}


@Override @Override
public String getHelpText() { public String getHelpText() {
return "Some help text - full name mapper - TODO"; 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 @Override
public List<ProviderConfigProperty> getConfigProperties() { public String getDisplayType() {
return null; return "Full Name";
}

@Override
public List<ProviderConfigProperty> getConfigProperties(RealmModel realm) {
return configProperties;
} }


@Override @Override
public String getId() { public String getId() {
return ID; return PROVIDER_ID;
}

@Override
public void validateConfig(UserFederationMapperModel mapperModel) throws MapperConfigValidationException {
checkMandatoryConfigAttribute(FullNameLDAPFederationMapper.LDAP_FULL_NAME_ATTRIBUTE, "LDAP Full Name Attribute", mapperModel);
} }


@Override @Override
Expand Down
Expand Up @@ -178,7 +178,6 @@ protected Collection<String> getRoleObjectClasses(UserFederationMapperModel mapp
} }
String[] objClasses = objectClasses.split(","); String[] objClasses = objectClasses.split(",");


// TODO: util method for trim and convert array to collection?
Set<String> trimmed = new HashSet<String>(); Set<String> trimmed = new HashSet<String>();
for (String objectClass : objClasses) { for (String objectClass : objClasses) {
objectClass = objectClass.trim(); objectClass = objectClass.trim();
Expand Down
@@ -1,31 +1,114 @@
package org.keycloak.federation.ldap.mappers; package org.keycloak.federation.ldap.mappers;


import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map;


import org.keycloak.mappers.MapperConfigValidationException;
import org.keycloak.mappers.UserFederationMapper; import org.keycloak.mappers.UserFederationMapper;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserFederationMapperModel;
import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.provider.ProviderConfigProperty;


/** /**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a> * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/ */
public class RoleLDAPFederationMapperFactory extends AbstractLDAPFederationMapperFactory { public class RoleLDAPFederationMapperFactory extends AbstractLDAPFederationMapperFactory {


public static final String ID = "role-ldap-mapper"; public static final String PROVIDER_ID = "role-ldap-mapper";

protected static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();

static {
ProviderConfigProperty rolesDn = createConfigProperty(RoleLDAPFederationMapper.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(RoleLDAPFederationMapper.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, LDAPConstants.CN);
configProperties.add(roleNameLDAPAttribute);

ProviderConfigProperty membershipLDAPAttribute = createConfigProperty(RoleLDAPFederationMapper.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, LDAPConstants.MEMBER);
configProperties.add(membershipLDAPAttribute);

ProviderConfigProperty roleObjectClasses = createConfigProperty(RoleLDAPFederationMapper.ROLE_OBJECT_CLASSES, "Role Object Classes",
"Object classes of the role object divided by comma (if more values needed). In typical LDAP deployment it could be 'groupOfNames' or 'groupOfEntries' ",
ProviderConfigProperty.STRING_TYPE, LDAPConstants.GROUP_OF_NAMES);
configProperties.add(roleObjectClasses);

List<String> modes = new LinkedList<String>();
for (RoleLDAPFederationMapper.Mode mode : RoleLDAPFederationMapper.Mode.values()) {
modes.add(mode.toString());
}
ProviderConfigProperty mode = createConfigProperty(RoleLDAPFederationMapper.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);

ProviderConfigProperty useRealmRolesMappings = createConfigProperty(RoleLDAPFederationMapper.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, "true");
configProperties.add(useRealmRolesMappings);

// NOTE: ClientID will be computed dynamically from available clients
}


@Override @Override
public String getHelpText() { public String getHelpText() {
return "Some help text - role mapper - TODO"; 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 @Override
public List<ProviderConfigProperty> getConfigProperties() { public String getDisplayType() {
return null; return "Role mappings";
}

@Override
public List<ProviderConfigProperty> getConfigProperties(RealmModel realm) {
List<ProviderConfigProperty> props = new ArrayList<ProviderConfigProperty>(configProperties);

Map<String, ClientModel> clients = realm.getClientNameMap();
List<String> clientIds = new ArrayList<String>(clients.keySet());

ProviderConfigProperty clientIdProperty = createConfigProperty(RoleLDAPFederationMapper.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.LIST_TYPE, clientIds);
props.add(clientIdProperty);

return props;
} }


@Override @Override
public String getId() { public String getId() {
return ID ; return PROVIDER_ID;
}

@Override
public void validateConfig(UserFederationMapperModel mapperModel) throws MapperConfigValidationException {
checkMandatoryConfigAttribute(RoleLDAPFederationMapper.ROLES_DN, "LDAP Roles DN", mapperModel);

String realmMappings = mapperModel.getConfig().get(RoleLDAPFederationMapper.USE_REALM_ROLES_MAPPING);
boolean useRealmMappings = Boolean.parseBoolean(realmMappings);
if (!useRealmMappings) {
String clientId = mapperModel.getConfig().get(RoleLDAPFederationMapper.CLIENT_ID);
if (clientId == null || clientId.trim().isEmpty()) {
throw new MapperConfigValidationException("Client ID needs to be provided in config when Realm Roles Mapping is not used");
}
}
} }


@Override @Override
Expand Down

0 comments on commit dfe232c

Please sign in to comment.