KEYCLOAK-630 Added Role Federation mapper. Fixes and refactoring

This commit is contained in:
mposolda 2015-05-19 18:24:56 +02:00
parent 975337f225
commit a9f1fda68a
34 changed files with 1213 additions and 209 deletions

View file

@ -16,7 +16,7 @@ import org.keycloak.models.UserFederationProviderModel;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*
* TODO: init properties at startup instead of always compute them
* TODO: init properties at constructor instead of always compute them
*/
public class LDAPConfig {

View file

@ -19,7 +19,7 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserCredentialValueModel;
import org.keycloak.models.UserFederationMapper;
import org.keycloak.mappers.UserFederationMapper;
import org.keycloak.models.UserFederationMapperModel;
import org.keycloak.models.UserFederationProvider;
import org.keycloak.models.UserFederationProviderModel;
@ -114,7 +114,7 @@ public class LDAPFederationProvider implements UserFederationProvider {
List<UserFederationMapperModel> federationMappers = realm.getUserFederationMappers();
for (UserFederationMapperModel mapperModel : federationMappers) {
LDAPFederationMapper ldapMapper = getMapper(mapperModel);
proxied = ldapMapper.proxy(mapperModel, this, ldapObject, proxied);
proxied = ldapMapper.proxy(mapperModel, this, ldapObject, proxied, realm);
}
return proxied;
@ -227,7 +227,7 @@ public class LDAPFederationProvider implements UserFederationProvider {
/**
* @param local
* @return ldapObject corresponding to local user or null if user is no longer in LDAP
* @return ldapUser corresponding to local user or null if user is no longer in LDAP
*/
protected LDAPObject loadAndValidateUser(RealmModel realm, UserModel local) {
LDAPObject ldapUser = loadLDAPUserByUsername(realm, local.getUsername());
@ -271,7 +271,7 @@ public class LDAPFederationProvider implements UserFederationProvider {
List<UserFederationMapperModel> federationMappers = realm.getUserFederationMappers();
for (UserFederationMapperModel mapperModel : federationMappers) {
LDAPFederationMapper ldapMapper = getMapper(mapperModel);
ldapMapper.importUserFromLDAP(mapperModel, this, ldapUser, imported, true);
ldapMapper.onImportUserFromLDAP(mapperModel, this, ldapUser, imported, realm, true);
}
String userDN = ldapUser.getDn().toString();
@ -314,7 +314,7 @@ public class LDAPFederationProvider implements UserFederationProvider {
@Override
public void preRemove(RealmModel realm, RoleModel role) {
// complete I don't think we have to do anything here
// TODO: requires implementation... Maybe mappers callback
// TODO: requires implementation... Maybe mappers callback to ensure role deletion propagated to LDAP by RoleLDAPFederationMapper
}
public boolean validPassword(RealmModel realm, UserModel user, String password) {
@ -407,7 +407,7 @@ public class LDAPFederationProvider implements UserFederationProvider {
List<UserFederationMapperModel> federationMappers = realm.getUserFederationMappers();
for (UserFederationMapperModel mapperModel : federationMappers) {
LDAPFederationMapper ldapMapper = getMapper(mapperModel);
ldapMapper.importUserFromLDAP(mapperModel, this, ldapUser, currentUser, false);
ldapMapper.onImportUserFromLDAP(mapperModel, this, ldapUser, currentUser, realm, false);
}
logger.debugf("Updated user from LDAP: %s", currentUser.getUsername());
@ -477,8 +477,7 @@ public class LDAPFederationProvider implements UserFederationProvider {
}
public LDAPFederationMapper getMapper(UserFederationMapperModel mapperModel) {
LDAPFederationMapper ldapMapper = (LDAPFederationMapper) getSession().getKeycloakSessionFactory()
.getProviderFactory(UserFederationMapper.class, mapperModel.getFederationMapperId());
LDAPFederationMapper ldapMapper = (LDAPFederationMapper) getSession().getProvider(UserFederationMapper.class, mapperModel.getFederationMapperId());
if (ldapMapper == null) {
throw new ModelException("Can't find mapper type with ID: " + mapperModel.getFederationMapperId());
}

View file

@ -37,7 +37,7 @@ public class LDAPUtils {
List<UserFederationMapperModel> federationMappers = realm.getUserFederationMappers();
for (UserFederationMapperModel mapperModel : federationMappers) {
LDAPFederationMapper ldapMapper = ldapProvider.getMapper(mapperModel);
ldapMapper.registerUserToLDAP(mapperModel, ldapProvider, ldapObject, user);
ldapMapper.onRegisterUserToLDAP(mapperModel, ldapProvider, ldapObject, user, realm);
}
LDAPUtils.computeAndSetDn(ldapConfig, ldapObject);
@ -161,10 +161,10 @@ public class LDAPUtils {
return fullName;
} */
// ldapObject has filled attributes, but doesn't have filled
// ldapUser has filled attributes, but doesn't have filled dn
public static void computeAndSetDn(LDAPConfig config, LDAPObject ldapObject) {
String rdnLdapAttrName = config.getRdnLdapAttribute();
String rdnLdapAttrValue = (String) ldapObject.getAttribute(rdnLdapAttrName);
String rdnLdapAttrValue = ldapObject.getAttributeAsString(rdnLdapAttrName);
if (rdnLdapAttrValue == null) {
throw new ModelException("RDN Attribute [" + rdnLdapAttrName + "] is not filled. Filled attributes: " + ldapObject.getAttributes());
}
@ -176,11 +176,6 @@ public class LDAPUtils {
public static String getUsername(LDAPObject ldapUser, LDAPConfig config) {
String usernameAttr = config.getUsernameLdapAttribute();
return (String) ldapUser.getAttribute(usernameAttr);
}
public static boolean parseBooleanParameter(UserFederationMapperModel mapperModel, String paramName) {
String readOnly = mapperModel.getConfig().get(paramName);
return Boolean.parseBoolean(readOnly);
return ldapUser.getAttributeAsString(usernameAttr);
}
}

View file

@ -1,14 +1,16 @@
package org.keycloak.federation.ldap.idm.model;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class LDAPDn {
private final List<Entry> entries = new LinkedList<Entry>();
private final Deque<Entry> entries = new LinkedList<Entry>();
public static LDAPDn fromString(String dnString) {
LDAPDn dn = new LDAPDn();
@ -43,7 +45,7 @@ public class LDAPDn {
* @return string like "uid=joe" from the DN like "uid=joe,dc=something,dc=org"
*/
public String getFirstRdn() {
Entry firstEntry = entries.get(0);
Entry firstEntry = entries.getFirst();
return firstEntry.attrName + "=" + firstEntry.attrValue;
}
@ -51,7 +53,7 @@ public class LDAPDn {
* @return string attribute name like "uid" from the DN like "uid=joe,dc=something,dc=org"
*/
public String getFirstRdnAttrName() {
Entry firstEntry = entries.get(0);
Entry firstEntry = entries.getFirst();
return firstEntry.attrName;
}
@ -60,28 +62,15 @@ public class LDAPDn {
* @return string like "dc=something,dc=org" from the DN like "uid=joe,dc=something,dc=org"
*/
public String getParentDn() {
StringBuilder builder = new StringBuilder();
int n = 0;
for (Entry rdn : entries) {
n++;
if (n > 2) {
builder.append(",");
}
if (n >= 2) {
builder.append(rdn.attrName).append("=").append(rdn.attrValue);
}
}
return builder.toString();
return new LinkedList<Entry>(entries).remove().toString();
}
public void addToHead(String rdnName, String rdnValue) {
entries.add(0, new Entry(rdnName, rdnValue));
entries.addFirst(new Entry(rdnName, rdnValue));
}
public void addToBottom(String rdnName, String rdnValue) {
entries.add(new Entry(rdnName, rdnValue));
entries.addLast(new Entry(rdnName, rdnValue));
}

View file

@ -18,7 +18,7 @@ public class LDAPObject {
private final List<String> objectClasses = new LinkedList<String>();
private final List<String> readOnlyAttributeNames = new LinkedList<String>();
private final Map<String, Serializable> attributes = new HashMap<String, Serializable>();
private final Map<String, Object> attributes = new HashMap<String, Object>();
public String getUuid() {
return uuid;
@ -61,7 +61,7 @@ public class LDAPObject {
this.rdnAttributeName = rdnAttributeName;
}
public void setAttribute(String attributeName, Serializable attributeValue) {
public void setAttribute(String attributeName, Object attributeValue) {
attributes.put(attributeName, attributeValue);
}
@ -70,12 +70,21 @@ public class LDAPObject {
}
public Serializable getAttribute(String name) {
public Object getAttribute(String name) {
return attributes.get(name);
}
public String getAttributeAsString(String name) {
Object attrValue = attributes.get(name);
if (attrValue != null && !(attrValue instanceof String)) {
throw new IllegalStateException("Expected String but attribute was " + attrValue + " of type " + attrValue.getClass().getName());
}
public Map<String, Serializable> getAttributes() {
return (String) attrValue;
}
public Map<String, Object> getAttributes() {
return attributes;
}

View file

@ -9,6 +9,8 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.TreeSet;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
@ -389,10 +391,9 @@ public class LDAPIdentityStore implements IdentityStore {
while (ldapAttributes.hasMore()) {
Attribute ldapAttribute = ldapAttributes.next();
Serializable ldapAttributeValue;
try {
ldapAttributeValue = (Serializable) ldapAttribute.get();
ldapAttribute.get();
} catch (NoSuchElementException nsee) {
continue;
}
@ -400,26 +401,34 @@ public class LDAPIdentityStore implements IdentityStore {
String ldapAttributeName = ldapAttribute.getID();
if (ldapAttributeName.toLowerCase().equals(getConfig().getUuidAttributeName().toLowerCase())) {
ldapObject.setUuid(this.operationManager.decodeEntryUUID(ldapAttributeValue));
} else if (ldapAttributeName.toLowerCase().equals(LDAPConstants.OBJECT_CLASS)) {
List<String> objectClasses = new LinkedList<String>();
Object uuidValue = ldapAttribute.get();
ldapObject.setUuid(this.operationManager.decodeEntryUUID(uuidValue));
} else {
Set<String> attrValues = new TreeSet<String>();
NamingEnumeration<?> enumm = ldapAttribute.getAll();
while (enumm.hasMoreElements()) {
String objectClass = enumm.next().toString();
objectClasses.add(objectClass);
}
ldapObject.setObjectClasses(objectClasses);
} else {
if (logger.isTraceEnabled()) {
logger.tracef("Populating ldap attribute [%s] with value [%s] for DN [%s].", ldapAttributeName, ldapAttributeValue, entryDN);
attrValues.add(objectClass);
}
if (ldapAttributeName.toLowerCase().equals(LDAPConstants.OBJECT_CLASS)) {
ldapObject.setObjectClasses(attrValues);
} else {
if (logger.isTraceEnabled()) {
logger.tracef("Populating ldap attribute [%s] with value [%s] for DN [%s].", ldapAttributeName, attrValues.toString(), entryDN);
}
if (attrValues.size() == 1) {
ldapObject.setAttribute(ldapAttributeName, attrValues.iterator().next());
} else {
ldapObject.setAttribute(ldapAttributeName, attrValues);
}
ldapObject.setAttribute(ldapAttributeName, ldapAttributeValue);
if (uppercasedReadOnlyAttrNames.contains(ldapAttributeName.toUpperCase())) {
ldapObject.addReadOnlyAttributeName(ldapAttributeName);
}
}
}
}
return ldapObject;
@ -487,16 +496,16 @@ public class LDAPIdentityStore implements IdentityStore {
protected BasicAttributes extractAttributes(LDAPObject ldapObject, boolean isCreate) {
BasicAttributes entryAttributes = new BasicAttributes();
for (Map.Entry<String, Serializable> attrEntry : ldapObject.getAttributes().entrySet()) {
for (Map.Entry<String, Object> attrEntry : ldapObject.getAttributes().entrySet()) {
String attrName = attrEntry.getKey();
Serializable attrValue = attrEntry.getValue();
Object attrValue = attrEntry.getValue();
if (!ldapObject.getReadOnlyAttributeNames().contains(attrName) && (isCreate || !ldapObject.getRdnAttributeName().equals(attrName))) {
if (String.class.isInstance(attrValue)) {
entryAttributes.put(attrName, attrValue);
} else if (Collection.class.isInstance(attrValue)) {
BasicAttribute attr = new BasicAttribute(attrName);
Collection<String> valueCollection = (Collection<String>) attr;
Collection<String> valueCollection = (Collection<String>) attrValue;
for (String val : valueCollection) {
attr.add(val);
}

View file

@ -1,9 +1,6 @@
package org.keycloak.federation.ldap.mappers;
import org.keycloak.Config;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.UserFederationMapper;
import org.keycloak.models.UserFederationMapperModel;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@ -15,18 +12,8 @@ public abstract class AbstractLDAPFederationMapper implements LDAPFederationMapp
}
@Override
public UserFederationMapper create(KeycloakSession session) {
throw new RuntimeException("UNSUPPORTED METHOD");
}
@Override
public void init(Config.Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
protected boolean parseBooleanParameter(UserFederationMapperModel mapperModel, String paramName) {
String paramm = mapperModel.getConfig().get(paramName);
return Boolean.parseBoolean(paramm);
}
}

View file

@ -0,0 +1,25 @@
package org.keycloak.federation.ldap.mappers;
import org.keycloak.Config;
import org.keycloak.mappers.UserFederationMapperFactory;
import org.keycloak.models.KeycloakSessionFactory;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public abstract class AbstractLDAPFederationMapperFactory implements UserFederationMapperFactory {
@Override
public void init(Config.Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
}

View file

@ -11,7 +11,10 @@ import org.keycloak.federation.ldap.idm.query.Condition;
import org.keycloak.federation.ldap.idm.query.QueryParameter;
import org.keycloak.federation.ldap.idm.query.internal.EqualCondition;
import org.keycloak.federation.ldap.idm.query.internal.LDAPIdentityQuery;
import org.keycloak.mappers.UserFederationMapper;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserFederationMapperModel;
import org.keycloak.models.UserFederationProvider;
import org.keycloak.models.UserModel;
@ -28,24 +31,9 @@ public class FullNameLDAPFederationMapper extends AbstractLDAPFederationMapper {
public static final String READ_ONLY = "read.only";
@Override
public String getHelpText() {
return "Some help text - full name mapper - TODO";
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return null;
}
@Override
public String getId() {
return "full-name-ldap-mapper";
}
@Override
public void importUserFromLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapObject, UserModel user, boolean isCreate) {
public void onImportUserFromLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, UserModel user, RealmModel realm, boolean isCreate) {
String ldapFullNameAttrName = getLdapFullNameAttrName(mapperModel);
String fullName = (String) ldapObject.getAttribute(ldapFullNameAttrName);
String fullName = ldapUser.getAttributeAsString(ldapFullNameAttrName);
fullName = fullName.trim();
if (fullName != null) {
int lastSpaceIndex = fullName.lastIndexOf(" ");
@ -59,22 +47,22 @@ public class FullNameLDAPFederationMapper extends AbstractLDAPFederationMapper {
}
@Override
public void registerUserToLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapObject, UserModel localUser) {
public void onRegisterUserToLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, UserModel localUser, RealmModel realm) {
String ldapFullNameAttrName = getLdapFullNameAttrName(mapperModel);
String fullName = getFullName(localUser.getFirstName(), localUser.getLastName());
ldapObject.setAttribute(ldapFullNameAttrName, fullName);
ldapUser.setAttribute(ldapFullNameAttrName, fullName);
if (isReadOnly(mapperModel)) {
ldapObject.addReadOnlyAttributeName(ldapFullNameAttrName);
ldapUser.addReadOnlyAttributeName(ldapFullNameAttrName);
}
}
@Override
public UserModel proxy(final UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapObject, UserModel delegate) {
public UserModel proxy(final UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, UserModel delegate, RealmModel realm) {
if (ldapProvider.getEditMode() == UserFederationProvider.EditMode.WRITABLE && !isReadOnly(mapperModel)) {
AbstractTxAwareLDAPUserModelDelegate txDelegate = new AbstractTxAwareLDAPUserModelDelegate(delegate, ldapProvider, ldapObject) {
TxAwareLDAPUserModelDelegate txDelegate = new TxAwareLDAPUserModelDelegate(delegate, ldapProvider, ldapUser) {
@Override
public void setFirstName(String firstName) {
@ -97,7 +85,7 @@ public class FullNameLDAPFederationMapper extends AbstractLDAPFederationMapper {
ensureTransactionStarted();
String ldapFullNameAttrName = getLdapFullNameAttrName(mapperModel);
ldapObject.setAttribute(ldapFullNameAttrName, fullName);
ldapUser.setAttribute(ldapFullNameAttrName, fullName);
}
};
@ -127,10 +115,10 @@ public class FullNameLDAPFederationMapper extends AbstractLDAPFederationMapper {
lastNameCondition = (EqualCondition) condition;
query.getConditions().remove(condition);
} else if (param.getName().equals(LDAPConstants.GIVENNAME)) {
// Some previous mapper already converted it
// Some previous mapper already converted it to LDAP name
firstNameCondition = (EqualCondition) condition;
} else if (param.getName().equals(LDAPConstants.SN)) {
// Some previous mapper already converted it
// Some previous mapper already converted it to LDAP name
lastNameCondition = (EqualCondition) condition;
}
}
@ -169,6 +157,6 @@ public class FullNameLDAPFederationMapper extends AbstractLDAPFederationMapper {
}
private boolean isReadOnly(UserFederationMapperModel mapperModel) {
return LDAPUtils.parseBooleanParameter(mapperModel, READ_ONLY);
return parseBooleanParameter(mapperModel, READ_ONLY);
}
}

View file

@ -0,0 +1,33 @@
package org.keycloak.federation.ldap.mappers;
import java.util.List;
import org.keycloak.mappers.UserFederationMapper;
import org.keycloak.models.KeycloakSession;
import org.keycloak.provider.ProviderConfigProperty;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class FullNameLDAPFederationMapperFactory extends AbstractLDAPFederationMapperFactory {
@Override
public String getHelpText() {
return "Some help text - full name mapper - TODO";
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return null;
}
@Override
public String getId() {
return "full-name-ldap-mapper";
}
@Override
public UserFederationMapper create(KeycloakSession session) {
return new FullNameLDAPFederationMapper();
}
}

View file

@ -3,7 +3,8 @@ package org.keycloak.federation.ldap.mappers;
import org.keycloak.federation.ldap.LDAPFederationProvider;
import org.keycloak.federation.ldap.idm.model.LDAPObject;
import org.keycloak.federation.ldap.idm.query.internal.LDAPIdentityQuery;
import org.keycloak.models.UserFederationMapper;
import org.keycloak.models.RealmModel;
import org.keycloak.mappers.UserFederationMapper;
import org.keycloak.models.UserFederationMapperModel;
import org.keycloak.models.UserModel;
@ -12,17 +13,50 @@ import org.keycloak.models.UserModel;
*/
public interface LDAPFederationMapper extends UserFederationMapper {
// TODO: rename?
// Called when importing user from federation provider to local keycloak DB. Flag "isCreate" means if we creating new user to Keycloak DB or just update existing user in Keycloak DB
void importUserFromLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapObject, UserModel user, boolean isCreate);
// TODO: rename to beforeRegister or something?
// Called when register new user to federation provider
void registerUserToLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapObject, UserModel localUser);
/**
* Called when importing user from LDAP to local keycloak DB.
*
* @param mapperModel
* @param ldapProvider
* @param ldapUser
* @param user
* @param realm
* @param isCreate true if we importing new user from LDAP. False if user already exists in Keycloak, but we are upgrading (syncing) it from LDAP
*/
void onImportUserFromLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, UserModel user, RealmModel realm, boolean isCreate);
// Called when invoke proxy on federation provider
UserModel proxy(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapObject, UserModel delegate);
// Called before any LDAPIdentityQuery is executed
/**
* Called when register new user to LDAP - just after user was created in Keycloak DB
*
* @param mapperModel
* @param ldapProvider
* @param ldapUser
* @param localUser
* @param realm
*/
void onRegisterUserToLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, UserModel localUser, RealmModel realm);
/**
* Called when invoke proxy on LDAP federation provider
*
* @param mapperModel
* @param ldapProvider
* @param ldapUser
* @param delegate
* @param realm
* @return
*/
UserModel proxy(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, UserModel delegate, RealmModel realm);
/**
* Called before LDAP Identity query for retrieve LDAP users was executed. It allows to change query somehow (add returning attributes from LDAP, change conditions etc)
*
* @param mapperModel
* @param query
*/
void beforeLDAPQuery(UserFederationMapperModel mapperModel, LDAPIdentityQuery query);
}

View file

@ -0,0 +1,473 @@
package org.keycloak.federation.ldap.mappers;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import javax.naming.directory.SearchControls;
import org.jboss.logging.Logger;
import org.keycloak.federation.ldap.LDAPFederationProvider;
import org.keycloak.federation.ldap.idm.model.LDAPDn;
import org.keycloak.federation.ldap.idm.model.LDAPObject;
import org.keycloak.federation.ldap.idm.query.Condition;
import org.keycloak.federation.ldap.idm.query.QueryParameter;
import org.keycloak.federation.ldap.idm.query.internal.LDAPIdentityQuery;
import org.keycloak.federation.ldap.idm.query.internal.LDAPQueryConditionsBuilder;
import org.keycloak.models.ClientModel;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.ModelException;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleContainerModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserFederationMapperModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.UserModelDelegate;
/**
* Map realm roles or roles of particular client to LDAP roles
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper {
private static final Logger logger = Logger.getLogger(RoleLDAPFederationMapper.class);
// LDAP DN where are roles of this tree saved.
public static final String ROLES_DN = "roles.dn";
// Name of LDAP attribute, which is used in role objects for name and RDN of role. Usually it will be "cn"
public static final String ROLE_NAME_LDAP_ATTRIBUTE = "role.name.ldap.attribute";
// Name of LDAP attribute on role, which is used for membership mappings. Usually it will be "member"
public static final String MEMBERSHIP_LDAP_ATTRIBUTE = "membership.ldap.attribute";
// Object classes of the role object.
public static final String ROLE_OBJECT_CLASSES = "role.object.classes";
// Boolean option. If true, we will map LDAP roles to realm roles. If false, we will map to client roles (client specified by option CLIENT_ID)
public static final String USE_REALM_ROLES_MAPPING = "use.realm.roles.mapping";
// ClientId, which we want to map roles. Applicable just if "USE_REALM_ROLES_MAPPING" is false
public static final String CLIENT_ID = "client.id";
// See docs for Mode enum
public static final String MODE = "mode";
// List of IDs of UserFederationMapperModels where syncRolesFromLDAP was already called in this KeycloakSession. This is to improve performance
// TODO: Rather address this with caching at LDAPIdentityStore level?
private Set<String> rolesSyncedModels = new TreeSet<String>();
@Override
public void onImportUserFromLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, UserModel user, RealmModel realm, boolean isCreate) {
syncRolesFromLDAP(mapperModel, ldapProvider, realm);
Mode mode = getMode(mapperModel);
// For now, import LDAP role mappings just during create
if (mode == Mode.IMPORT && isCreate) {
List<LDAPObject> ldapRoles = getLDAPRoleMappings(mapperModel, ldapProvider, ldapUser);
// Import role mappings from LDAP into Keycloak DB
String roleNameAttr = getRoleNameLdapAttribute(mapperModel);
for (LDAPObject ldapRole : ldapRoles) {
String roleName = ldapRole.getAttributeAsString(roleNameAttr);
RoleContainerModel roleContainer = getTargetRoleContainer(mapperModel, realm);
RoleModel role = roleContainer.getRole(roleName);
// TODO: debug
logger.infof("Granting role [%s] to user [%s] during import from LDAP", roleName, user.getUsername());
user.grantRole(role);
}
}
}
@Override
public void onRegisterUserToLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, UserModel localUser, RealmModel realm) {
syncRolesFromLDAP(mapperModel, ldapProvider, realm);
}
// Sync roles from LDAP tree and create them in local Keycloak DB (if they don't exist here yet)
protected void syncRolesFromLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, RealmModel realm) {
if (!rolesSyncedModels.contains(mapperModel.getId())) {
LDAPIdentityQuery ldapQuery = createRoleQuery(mapperModel, ldapProvider);
// Send query
List<LDAPObject> ldapRoles = ldapQuery.getResultList();
RoleContainerModel roleContainer = getTargetRoleContainer(mapperModel, realm);
String rolesRdnAttr = getRoleNameLdapAttribute(mapperModel);
for (LDAPObject ldapRole : ldapRoles) {
String roleName = ldapRole.getAttributeAsString(rolesRdnAttr);
if (roleContainer.getRole(roleName) == null) {
// TODO: rather change to debug
logger.infof("Syncing role [%s] from LDAP to keycloak DB", roleName);
roleContainer.addRole(roleName);
}
}
rolesSyncedModels.add(mapperModel.getId());
}
}
public LDAPIdentityQuery createRoleQuery(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider) {
LDAPIdentityQuery ldapQuery = new LDAPIdentityQuery(ldapProvider);
ldapQuery.setSearchScope(SearchControls.ONELEVEL_SCOPE);
String rolesDn = getRolesDn(mapperModel);
ldapQuery.addSearchDns(Arrays.asList(rolesDn));
Collection<String> roleObjectClasses = getRoleObjectClasses(mapperModel);
ldapQuery.addObjectClasses(roleObjectClasses);
String rolesRdnAttr = getRoleNameLdapAttribute(mapperModel);
String membershipAttr = getMembershipLdapAttribute(mapperModel);
ldapQuery.addReturningLdapAttribute(rolesRdnAttr);
ldapQuery.addReturningLdapAttribute(membershipAttr);
return ldapQuery;
}
protected RoleContainerModel getTargetRoleContainer(UserFederationMapperModel mapperModel, RealmModel realm) {
boolean realmRolesMapping = parseBooleanParameter(mapperModel, USE_REALM_ROLES_MAPPING);
if (realmRolesMapping) {
return realm;
} else {
String clientId = mapperModel.getConfig().get(CLIENT_ID);
if (clientId == null) {
throw new IllegalStateException("Using client roles mapping is requested, but parameter client.id not found!");
}
ClientModel client = realm.getClientByClientId(clientId);
if (client == null) {
throw new IllegalStateException("Can't found requested client with clientId: " + clientId);
}
return client;
}
}
protected String getRolesDn(UserFederationMapperModel mapperModel) {
String rolesDn = mapperModel.getConfig().get(ROLES_DN);
if (rolesDn == null) {
throw new IllegalStateException("Roles DN is null! Check your configuration");
}
return rolesDn;
}
protected String getRoleNameLdapAttribute(UserFederationMapperModel mapperModel) {
String rolesRdnAttr = mapperModel.getConfig().get(ROLE_NAME_LDAP_ATTRIBUTE);
return rolesRdnAttr!=null ? rolesRdnAttr : LDAPConstants.CN;
}
protected String getMembershipLdapAttribute(UserFederationMapperModel mapperModel) {
String membershipAttrName = mapperModel.getConfig().get(MEMBERSHIP_LDAP_ATTRIBUTE);
return membershipAttrName!=null ? membershipAttrName : LDAPConstants.MEMBER;
}
protected Collection<String> getRoleObjectClasses(UserFederationMapperModel mapperModel) {
String objectClasses = mapperModel.getConfig().get(ROLE_OBJECT_CLASSES);
if (objectClasses == null) {
objectClasses = "groupOfNames";
}
String[] objClasses = objectClasses.split(",");
// TODO: util method for trim and convert array to collection?
Set<String> trimmed = new HashSet<String>();
for (String objectClass : objClasses) {
objectClass = objectClass.trim();
if (objectClass.length() > 0) {
trimmed.add(objectClass);
}
}
return trimmed;
}
private Mode getMode(UserFederationMapperModel mapperModel) {
String modeString = mapperModel.getConfig().get(MODE);
if (modeString == null || modeString.trim().length() == 0) {
return Mode.LDAP_ONLY;
}
return Enum.valueOf(Mode.class, modeString.toUpperCase());
}
protected LDAPObject createLDAPRole(UserFederationMapperModel mapperModel, String roleName, LDAPFederationProvider ldapProvider) {
LDAPObject ldapObject = new LDAPObject();
String roleNameAttribute = getRoleNameLdapAttribute(mapperModel);
ldapObject.setRdnAttributeName(roleNameAttribute);
ldapObject.setObjectClasses(getRoleObjectClasses(mapperModel));
ldapObject.setAttribute(roleNameAttribute, roleName);
LDAPDn roleDn = LDAPDn.fromString(getRolesDn(mapperModel));
roleDn.addToHead(roleNameAttribute, roleName);
ldapObject.setDn(roleDn);
// TODO: debug
logger.infof("Creating role to [%s] to LDAP with DN [%s]", roleName, roleDn.toString());
ldapProvider.getLdapIdentityStore().add(ldapObject);
return ldapObject;
}
public void addRoleMappingInLDAP(UserFederationMapperModel mapperModel, String roleName, LDAPFederationProvider ldapProvider, LDAPObject ldapUser) {
LDAPObject ldapRole = loadLDAPRoleByName(mapperModel, ldapProvider, roleName);
if (ldapRole == null) {
ldapRole = createLDAPRole(mapperModel, roleName, ldapProvider);
}
Set<String> memberships = getExistingMemberships(mapperModel, ldapRole);
memberships.add(ldapUser.getDn().toString());
ldapRole.setAttribute(getMembershipLdapAttribute(mapperModel), memberships);
ldapProvider.getLdapIdentityStore().update(ldapRole);
}
public void deleteRoleMappingInLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, LDAPObject ldapRole) {
Set<String> memberships = getExistingMemberships(mapperModel, ldapRole);
memberships.remove(ldapUser.getDn().toString());
// Some membership placeholder needs to be always here as "member" is mandatory attribute on some LDAP servers
if (memberships.size() == 0) {
memberships.add(LDAPConstants.EMPTY_ATTRIBUTE_VALUE);
}
ldapRole.setAttribute(getMembershipLdapAttribute(mapperModel), memberships);
ldapProvider.getLdapIdentityStore().update(ldapRole);
}
public LDAPObject loadLDAPRoleByName(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, String roleName) {
LDAPIdentityQuery ldapQuery = createRoleQuery(mapperModel, ldapProvider);
Condition roleNameCondition = new LDAPQueryConditionsBuilder().equal(new QueryParameter(getRoleNameLdapAttribute(mapperModel)), roleName);
ldapQuery.where(roleNameCondition);
return ldapQuery.getFirstResult();
}
protected Set<String> getExistingMemberships(UserFederationMapperModel mapperModel, LDAPObject ldapRole) {
String memberAttrName = getMembershipLdapAttribute(mapperModel);
Set<String> memberships = new TreeSet<String>();
Object existingMemberships = ldapRole.getAttribute(memberAttrName);
if (existingMemberships != null) {
if (existingMemberships instanceof String) {
String existingMembership = existingMemberships.toString().trim();
if (existingMemberships != null && existingMembership.length() > 0) {
memberships.add(existingMembership);
}
} else if (existingMemberships instanceof Collection) {
Collection<String> exMemberships = (Collection<String>) existingMemberships;
for (String membership : exMemberships) {
if (membership.trim().length() > 0) {
memberships.add(membership);
}
}
}
}
return memberships;
}
protected List<LDAPObject> getLDAPRoleMappings(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser) {
LDAPIdentityQuery ldapQuery = createRoleQuery(mapperModel, ldapProvider);
String membershipAttr = getMembershipLdapAttribute(mapperModel);
Condition membershipCondition = new LDAPQueryConditionsBuilder().equal(new QueryParameter(membershipAttr), ldapUser.getDn().toString());
ldapQuery.where(membershipCondition);
return ldapQuery.getResultList();
}
protected Set<RoleModel> getLDAPRoleMappingsConverted(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, RoleContainerModel roleContainer) {
List<LDAPObject> ldapRoles = getLDAPRoleMappings(mapperModel, ldapProvider, ldapUser);
Set<RoleModel> roles = new HashSet<RoleModel>();
String roleNameLdapAttr = getRoleNameLdapAttribute(mapperModel);
for (LDAPObject role : ldapRoles) {
String roleName = role.getAttributeAsString(roleNameLdapAttr);
RoleModel modelRole = roleContainer.getRole(roleName);
if (modelRole == null) {
// Add role to local DB
modelRole = roleContainer.addRole(roleName);
}
roles.add(modelRole);
}
return roles;
}
@Override
public UserModel proxy(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, UserModel delegate, RealmModel realm) {
final Mode mode = getMode(mapperModel);
// For IMPORT mode, all operations are performed against local DB
if (mode == Mode.IMPORT) {
return delegate;
} else {
return new LDAPRoleMappingsUserDelegate(delegate, mapperModel, ldapProvider, ldapUser, realm, mode);
}
}
@Override
public void beforeLDAPQuery(UserFederationMapperModel mapperModel, LDAPIdentityQuery query) {
}
public class LDAPRoleMappingsUserDelegate extends UserModelDelegate {
private final UserFederationMapperModel mapperModel;
private final LDAPFederationProvider ldapProvider;
private final LDAPObject ldapUser;
private final RealmModel realm;
private final Mode mode;
public LDAPRoleMappingsUserDelegate(UserModel user, UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser,
RealmModel realm, Mode mode) {
super(user);
this.mapperModel = mapperModel;
this.ldapProvider = ldapProvider;
this.ldapUser = ldapUser;
this.realm = realm;
this.mode = mode;
}
@Override
public Set<RoleModel> getRealmRoleMappings() {
RoleContainerModel roleContainer = getTargetRoleContainer(mapperModel, realm);
if (roleContainer.equals(realm)) {
Set<RoleModel> ldapRoleMappings = getLDAPRoleMappingsConverted(mapperModel, ldapProvider, ldapUser, roleContainer);
if (mode == Mode.LDAP_ONLY) {
// Use just role mappings from LDAP
return ldapRoleMappings;
} else {
// Merge mappings from both DB and LDAP
Set<RoleModel> modelRoleMappings = super.getRealmRoleMappings();
ldapRoleMappings.addAll(modelRoleMappings);
return ldapRoleMappings;
}
} else {
return super.getRealmRoleMappings();
}
}
@Override
public Set<RoleModel> getClientRoleMappings(ClientModel client) {
RoleContainerModel roleContainer = getTargetRoleContainer(mapperModel, realm);
if (roleContainer.equals(client)) {
Set<RoleModel> ldapRoleMappings = getLDAPRoleMappingsConverted(mapperModel, ldapProvider, ldapUser, roleContainer);
if (mode == Mode.LDAP_ONLY) {
// Use just role mappings from LDAP
return ldapRoleMappings;
} else {
// Merge mappings from both DB and LDAP
Set<RoleModel> modelRoleMappings = super.getClientRoleMappings(client);
ldapRoleMappings.addAll(modelRoleMappings);
return ldapRoleMappings;
}
} else {
return super.getClientRoleMappings(client);
}
}
@Override
public boolean hasRole(RoleModel role) {
Set<RoleModel> roles = getRoleMappings();
return KeycloakModelUtils.hasRole(roles, role);
}
@Override
public void grantRole(RoleModel role) {
if (mode == Mode.LDAP_ONLY) {
RoleContainerModel roleContainer = getTargetRoleContainer(mapperModel, realm);
if (role.getContainer().equals(roleContainer)) {
// We need to create new role mappings in LDAP
addRoleMappingInLDAP(mapperModel, role.getName(), ldapProvider, ldapUser);
} else {
super.grantRole(role);
}
} else {
super.grantRole(role);
}
}
@Override
public Set<RoleModel> getRoleMappings() {
Set<RoleModel> modelRoleMappings = super.getRoleMappings();
RoleContainerModel targetRoleContainer = getTargetRoleContainer(mapperModel, realm);
Set<RoleModel> ldapRoleMappings = getLDAPRoleMappingsConverted(mapperModel, ldapProvider, ldapUser, targetRoleContainer);
if (mode == Mode.LDAP_ONLY) {
// For LDAP-only we want to retrieve role mappings of target container just from LDAP
Set<RoleModel> modelRolesCopy = new HashSet<RoleModel>(modelRoleMappings);
for (RoleModel role : modelRolesCopy) {
if (role.getContainer().equals(targetRoleContainer)) {
modelRoleMappings.remove(role);
}
}
}
modelRoleMappings.addAll(ldapRoleMappings);
return modelRoleMappings;
}
@Override
public void deleteRoleMapping(RoleModel role) {
RoleContainerModel roleContainer = getTargetRoleContainer(mapperModel, realm);
if (role.getContainer().equals(roleContainer)) {
LDAPIdentityQuery ldapQuery = createRoleQuery(mapperModel, ldapProvider);
LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder();
Condition roleNameCondition = conditionsBuilder.equal(new QueryParameter(getRoleNameLdapAttribute(mapperModel)), role.getName());
Condition membershipCondition = conditionsBuilder.equal(new QueryParameter(getMembershipLdapAttribute(mapperModel)), ldapUser.getDn().toString());
ldapQuery.where(roleNameCondition).where(membershipCondition);
LDAPObject ldapRole = ldapQuery.getFirstResult();
if (ldapRole == null) {
// Role mapping doesn't exist in LDAP. For LDAP_ONLY mode, we don't need to do anything. For READ_ONLY, delete it in local DB.
if (mode == Mode.READ_ONLY) {
super.deleteRoleMapping(role);
}
} else {
// Role mappings exists in LDAP. For LDAP_ONLY mode, we can just delete it in LDAP. For READ_ONLY we can't delete it -> throw error
if (mode == Mode.READ_ONLY) {
throw new ModelException("Not possible to delete LDAP role mappings as mapper mode is READ_ONLY");
} else {
// Delete ldap role mappings
deleteRoleMappingInLDAP(mapperModel, ldapProvider, ldapUser, ldapRole);
}
}
} else {
super.deleteRoleMapping(role);
}
}
}
public enum Mode {
/**
* All role mappings are retrieved from LDAP and saved into LDAP
*/
LDAP_ONLY,
/**
* Read-only LDAP mode. Role mappings are retrieved from LDAP for particular user just at the time when he is imported and then
* they are saved to local keycloak DB. Then all role mappings are always retrieved from keycloak DB, never from LDAP.
* Creating or deleting of role mapping is propagated only to DB.
*
* This is read-only mode LDAP mode and it's good for performance, but when user is put to some role directly in LDAP, it
* won't be seen by Keycloak
*/
IMPORT,
/**
* Read-only LDAP mode. Role mappings are retrieved from both LDAP and DB and merged together. New role grants are not saved to LDAP but to DB.
* Deleting role mappings, which is mapped to LDAP, will throw an error.
*/
READ_ONLY
}
}

View file

@ -0,0 +1,33 @@
package org.keycloak.federation.ldap.mappers;
import java.util.List;
import org.keycloak.mappers.UserFederationMapper;
import org.keycloak.models.KeycloakSession;
import org.keycloak.provider.ProviderConfigProperty;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class RoleLDAPFederationMapperFactory extends AbstractLDAPFederationMapperFactory {
@Override
public String getHelpText() {
return "Some help text - role mapper - TODO";
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return null;
}
@Override
public String getId() {
return "role-ldap-mapper";
}
@Override
public UserFederationMapper create(KeycloakSession session) {
return new RoleLDAPFederationMapper();
}
}

View file

@ -10,18 +10,18 @@ import org.keycloak.models.utils.UserModelDelegate;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public abstract class AbstractTxAwareLDAPUserModelDelegate extends UserModelDelegate {
public abstract class TxAwareLDAPUserModelDelegate extends UserModelDelegate {
public static final Logger logger = Logger.getLogger(AbstractTxAwareLDAPUserModelDelegate.class);
public static final Logger logger = Logger.getLogger(TxAwareLDAPUserModelDelegate.class);
protected LDAPFederationProvider provider;
protected LDAPObject ldapObject;
protected LDAPObject ldapUser;
private final LDAPTransaction transaction;
public AbstractTxAwareLDAPUserModelDelegate(UserModel delegate, LDAPFederationProvider provider, LDAPObject ldapObject) {
public TxAwareLDAPUserModelDelegate(UserModel delegate, LDAPFederationProvider provider, LDAPObject ldapUser) {
super(delegate);
this.provider = provider;
this.ldapObject = ldapObject;
this.ldapUser = ldapUser;
this.transaction = findOrCreateTransaction();
}
@ -30,20 +30,18 @@ public abstract class AbstractTxAwareLDAPUserModelDelegate extends UserModelDele
}
// Try to find transaction in any delegate. We want to enlist just single transaction per all delegates
protected LDAPTransaction findOrCreateTransaction() {
private LDAPTransaction findOrCreateTransaction() {
UserModelDelegate delegate = this;
while (true) {
UserModel deleg = delegate.getDelegate();
if (!(deleg instanceof UserModelDelegate)) {
// Existing transaction not available. Need to create new
return new LDAPTransaction();
} else {
delegate = (UserModelDelegate) deleg;
}
// Check if it's transaction aware delegate
if (delegate instanceof AbstractTxAwareLDAPUserModelDelegate) {
AbstractTxAwareLDAPUserModelDelegate txDelegate = (AbstractTxAwareLDAPUserModelDelegate) delegate;
if (delegate instanceof TxAwareLDAPUserModelDelegate) {
TxAwareLDAPUserModelDelegate txDelegate = (TxAwareLDAPUserModelDelegate) delegate;
return txDelegate.getTransaction();
}
}
@ -52,7 +50,7 @@ public abstract class AbstractTxAwareLDAPUserModelDelegate extends UserModelDele
protected void ensureTransactionStarted() {
if (transaction.state == TransactionState.NOT_STARTED) {
if (logger.isTraceEnabled()) {
logger.trace("Starting and enlisting transaction for object " + ldapObject.getDn().toString());
logger.trace("Starting and enlisting transaction for object " + ldapUser.getDn().toString());
}
this.provider.getSession().getTransaction().enlistAfterCompletion(transaction);
@ -81,10 +79,10 @@ public abstract class AbstractTxAwareLDAPUserModelDelegate extends UserModelDele
}
if (logger.isTraceEnabled()) {
logger.trace("Transaction commit! Updating LDAP attributes for object " + ldapObject.getDn().toString() + ", attributes: " + ldapObject.getAttributes());
logger.trace("Transaction commit! Updating LDAP attributes for object " + ldapUser.getDn().toString() + ", attributes: " + ldapUser.getAttributes());
}
provider.getLdapIdentityStore().update(ldapObject);
provider.getLdapIdentityStore().update(ldapUser);
state = TransactionState.FINISHED;
}
@ -94,7 +92,7 @@ public abstract class AbstractTxAwareLDAPUserModelDelegate extends UserModelDele
throw new IllegalStateException("Transaction in illegal state for rollback: " + state);
}
logger.warn("Transaction rollback! Ignoring LDAP updates for object " + ldapObject.getDn().toString());
logger.warn("Transaction rollback! Ignoring LDAP updates for object " + ldapUser.getDn().toString());
state = TransactionState.FINISHED;
}

View file

@ -11,6 +11,9 @@ import org.keycloak.federation.ldap.idm.model.LDAPObject;
import org.keycloak.federation.ldap.idm.query.Condition;
import org.keycloak.federation.ldap.idm.query.QueryParameter;
import org.keycloak.federation.ldap.idm.query.internal.LDAPIdentityQuery;
import org.keycloak.mappers.UserFederationMapper;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserFederationMapperModel;
import org.keycloak.models.UserFederationProvider;
import org.keycloak.models.UserModel;
@ -43,31 +46,15 @@ public class UserAttributeLDAPFederationMapper extends AbstractLDAPFederationMap
public static final String USER_MODEL_ATTRIBUTE = "user.model.attribute";
public static final String LDAP_ATTRIBUTE = "ldap.attribute";
// TODO: Merge with fullname mapper
public static final String READ_ONLY = "read.only";
@Override
public String getHelpText() {
return "Some help text TODO";
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return null;
}
@Override
public String getId() {
return "user-attribute-ldap-mapper";
}
@Override
public void importUserFromLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapObject, UserModel user, boolean isCreate) {
public void onImportUserFromLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, UserModel user, RealmModel realm, boolean isCreate) {
String userModelAttrName = mapperModel.getConfig().get(USER_MODEL_ATTRIBUTE);
String ldapAttrName = mapperModel.getConfig().get(LDAP_ATTRIBUTE);
Serializable ldapAttrValue = ldapObject.getAttribute(ldapAttrName);
Object ldapAttrValue = ldapUser.getAttribute(ldapAttrName);
if (ldapAttrValue != null) {
Property<Object> userModelProperty = userModelProperties.get(userModelAttrName);
@ -82,7 +69,7 @@ public class UserAttributeLDAPFederationMapper extends AbstractLDAPFederationMap
}
@Override
public void registerUserToLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapObject, UserModel localUser) {
public void onRegisterUserToLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, UserModel localUser, RealmModel realm) {
String userModelAttrName = mapperModel.getConfig().get(USER_MODEL_ATTRIBUTE);
String ldapAttrName = mapperModel.getConfig().get(LDAP_ATTRIBUTE);
@ -97,46 +84,42 @@ public class UserAttributeLDAPFederationMapper extends AbstractLDAPFederationMap
attrValue = localUser.getAttribute(userModelAttrName);
}
ldapObject.setAttribute(ldapAttrName, (Serializable) attrValue);
ldapUser.setAttribute(ldapAttrName, attrValue);
if (isReadOnly(mapperModel)) {
ldapObject.addReadOnlyAttributeName(ldapAttrName);
ldapUser.addReadOnlyAttributeName(ldapAttrName);
}
}
@Override
public UserModel proxy(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapObject, UserModel delegate) {
public UserModel proxy(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, UserModel delegate, RealmModel realm) {
if (ldapProvider.getEditMode() == UserFederationProvider.EditMode.WRITABLE && !isReadOnly(mapperModel)) {
final String userModelAttrName = mapperModel.getConfig().get(USER_MODEL_ATTRIBUTE);
final String ldapAttrName = mapperModel.getConfig().get(LDAP_ATTRIBUTE);
AbstractTxAwareLDAPUserModelDelegate txDelegate = new AbstractTxAwareLDAPUserModelDelegate(delegate, ldapProvider, ldapObject) {
TxAwareLDAPUserModelDelegate txDelegate = new TxAwareLDAPUserModelDelegate(delegate, ldapProvider, ldapUser) {
@Override
public void setAttribute(String name, String value) {
setLDAPAttribute(name, value);
super.setAttribute(name, value);
}
@Override
public void setEmail(String email) {
setLDAPAttribute(UserModel.EMAIL, email);
super.setEmail(email);
}
@Override
public void setLastName(String lastName) {
setLDAPAttribute(UserModel.LAST_NAME, lastName);
super.setLastName(lastName);
}
@Override
public void setFirstName(String firstName) {
setLDAPAttribute(UserModel.FIRST_NAME, firstName);
super.setFirstName(firstName);
}
@ -148,7 +131,7 @@ public class UserAttributeLDAPFederationMapper extends AbstractLDAPFederationMap
ensureTransactionStarted();
ldapObject.setAttribute(ldapAttrName, value);
ldapUser.setAttribute(ldapAttrName, value);
}
}
@ -181,6 +164,6 @@ public class UserAttributeLDAPFederationMapper extends AbstractLDAPFederationMap
}
private boolean isReadOnly(UserFederationMapperModel mapperModel) {
return LDAPUtils.parseBooleanParameter(mapperModel, READ_ONLY);
return parseBooleanParameter(mapperModel, READ_ONLY);
}
}

View file

@ -0,0 +1,33 @@
package org.keycloak.federation.ldap.mappers;
import java.util.List;
import org.keycloak.mappers.UserFederationMapper;
import org.keycloak.models.KeycloakSession;
import org.keycloak.provider.ProviderConfigProperty;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class UserAttributeLDAPFederationMapperFactory extends AbstractLDAPFederationMapperFactory {
@Override
public String getHelpText() {
return "Some help text TODO";
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return null;
}
@Override
public String getId() {
return "user-attribute-ldap-mapper";
}
@Override
public UserFederationMapper create(KeycloakSession session) {
return new UserAttributeLDAPFederationMapper();
}
}

View file

@ -1,2 +1,3 @@
org.keycloak.federation.ldap.mappers.UserAttributeLDAPFederationMapper
org.keycloak.federation.ldap.mappers.FullNameLDAPFederationMapper
org.keycloak.federation.ldap.mappers.UserAttributeLDAPFederationMapperFactory
org.keycloak.federation.ldap.mappers.FullNameLDAPFederationMapperFactory
org.keycloak.federation.ldap.mappers.RoleLDAPFederationMapperFactory

View file

@ -0,0 +1,10 @@
package org.keycloak.mappers;
import org.keycloak.provider.Provider;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public interface UserFederationMapper extends Provider {
}

View file

@ -0,0 +1,10 @@
package org.keycloak.mappers;
import org.keycloak.provider.ConfiguredProvider;
import org.keycloak.provider.ProviderFactory;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public interface UserFederationMapperFactory extends ProviderFactory<UserFederationMapper>, ConfiguredProvider {
}

View file

@ -1,4 +1,4 @@
package org.keycloak.models;
package org.keycloak.mappers;
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory;
@ -21,6 +21,6 @@ public class UserFederationMapperSpi implements Spi {
@Override
public Class<? extends ProviderFactory> getProviderFactoryClass() {
return UserFederationMapper.class;
return UserFederationMapperFactory.class;
}
}

View file

@ -1,12 +0,0 @@
package org.keycloak.models;
import org.keycloak.provider.ConfiguredProvider;
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public interface UserFederationMapper extends Provider, ProviderFactory<UserFederationMapper>, ConfiguredProvider {
}

View file

@ -252,4 +252,19 @@ public final class KeycloakModelUtils {
public static String getMasterRealmAdminApplicationClientId(RealmModel realm) {
return realm.getName() + "-realm";
}
/**
*
* @param roles
* @param targetRole
* @return true if targetRole is in roles (directly or indirectly via composite role)
*/
public static boolean hasRole(Set<RoleModel> roles, RoleModel targetRole) {
if (roles.contains(targetRole)) return true;
for (RoleModel mapping : roles) {
if (mapping.hasRole(targetRole)) return true;
}
return false;
}
}

View file

@ -1,5 +1,5 @@
org.keycloak.models.UserFederationSpi
org.keycloak.models.UserFederationMapperSpi
org.keycloak.mappers.UserFederationMapperSpi
org.keycloak.models.RealmSpi
org.keycloak.models.UserSessionSpi
org.keycloak.models.UserSpi

View file

@ -33,6 +33,7 @@ import org.keycloak.models.entities.CredentialEntity;
import org.keycloak.models.entities.FederatedIdentityEntity;
import org.keycloak.models.entities.RoleEntity;
import org.keycloak.models.entities.UserEntity;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.Pbkdf2PasswordEncoder;
import org.keycloak.util.Time;
@ -394,12 +395,7 @@ public class UserAdapter implements UserModel, Comparable {
@Override
public boolean hasRole(RoleModel role) {
Set<RoleModel> roles = getRoleMappings();
if (roles.contains(role)) return true;
for (RoleModel mapping : roles) {
if (mapping.hasRole(role)) return true;
}
return false;
return KeycloakModelUtils.hasRole(roles, role);
}
@Override

View file

@ -952,6 +952,7 @@ public class RealmAdapter implements RealmModel {
return null;
}
public static String LDAP_MODE = "LDAP_ONLY";
@Override
public List<UserFederationMapperModel> getUserFederationMappers() {
@ -992,6 +993,17 @@ public class RealmAdapter implements RealmModel {
"user.model.attribute", LDAPConstants.MODIFY_TIMESTAMP,
"ldap.attribute", LDAPConstants.MODIFY_TIMESTAMP,
"read.only", "true"));
mappers.add(createMapperModel("realmRoleMpr", "realmRoleMapper", "role-ldap-mapper",
"roles.dn", "ou=RealmRoles,dc=keycloak,dc=org",
"use.realm.roles.mapping", "true",
"mode", LDAP_MODE));
mappers.add(createMapperModel("financeRoleMpr", "financeRoleMapper", "role-ldap-mapper",
"roles.dn", "ou=FinanceRoles,dc=keycloak,dc=org",
"use.realm.roles.mapping", "false",
"client.id", "finance",
"mode", LDAP_MODE));
return mappers;
}

View file

@ -404,12 +404,7 @@ public class UserAdapter implements UserModel {
@Override
public boolean hasRole(RoleModel role) {
Set<RoleModel> roles = getRoleMappings();
if (roles.contains(role)) return true;
for (RoleModel mapping : roles) {
if (mapping.hasRole(role)) return true;
}
return false;
return KeycloakModelUtils.hasRole(roles, role);
}
protected TypedQuery<UserRoleMappingEntity> getUserRoleMappingEntityTypedQuery(RoleModel role) {

View file

@ -23,6 +23,7 @@ import org.keycloak.models.mongo.keycloak.entities.MongoRoleEntity;
import org.keycloak.models.mongo.keycloak.entities.MongoUserConsentEntity;
import org.keycloak.models.mongo.keycloak.entities.MongoUserEntity;
import org.keycloak.models.mongo.utils.MongoModelUtils;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.Pbkdf2PasswordEncoder;
import org.keycloak.util.Time;
@ -374,12 +375,7 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
@Override
public boolean hasRole(RoleModel role) {
Set<RoleModel> roles = getRoleMappings();
if (roles.contains(role)) return true;
for (RoleModel mapping : roles) {
if (mapping.hasRole(role)) return true;
}
return false;
return KeycloakModelUtils.hasRole(roles, role);
}
@Override

View file

@ -13,6 +13,7 @@ import org.keycloak.federation.ldap.LDAPFederationProvider;
import org.keycloak.federation.ldap.LDAPFederationProviderFactory;
import org.keycloak.federation.ldap.LDAPUtils;
import org.keycloak.federation.ldap.idm.model.LDAPObject;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.ModelReadOnlyException;
@ -65,6 +66,9 @@ public class FederationProvidersIntegrationTest {
LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel);
LDAPUtils.removeAllUsers(ldapFedProvider, appRealm);
// Add sample application TODO: REmove this!!!! It's just temporarily needed in SyncProvidersTest until model for federation mappers is implemented
ClientModel finance = appRealm.addClient("finance");
LDAPObject john = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "johnkeycloak", "John", "Doe", "john@email.org", "1234");
ldapFedProvider.getLdapIdentityStore().updatePassword(john, "Password1");

View file

@ -0,0 +1,367 @@
package org.keycloak.testsuite.federation;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.junit.Assert;
import org.junit.ClassRule;
import org.junit.FixMethodOrder;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.RuleChain;
import org.junit.rules.TestRule;
import org.junit.runners.MethodSorters;
import org.keycloak.federation.ldap.LDAPFederationProvider;
import org.keycloak.federation.ldap.LDAPFederationProviderFactory;
import org.keycloak.federation.ldap.LDAPUtils;
import org.keycloak.federation.ldap.idm.model.LDAPObject;
import org.keycloak.federation.ldap.idm.query.Condition;
import org.keycloak.federation.ldap.idm.query.QueryParameter;
import org.keycloak.federation.ldap.idm.query.internal.LDAPIdentityQuery;
import org.keycloak.federation.ldap.idm.query.internal.LDAPQueryConditionsBuilder;
import org.keycloak.federation.ldap.mappers.RoleLDAPFederationMapper;
import org.keycloak.models.AccountRoles;
import org.keycloak.models.ClientModel;
import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.ModelException;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserFederationMapperModel;
import org.keycloak.models.UserFederationProvider;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.cache.RealmAdapter;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.OAuthClient;
import org.keycloak.testsuite.pages.AccountPasswordPage;
import org.keycloak.testsuite.pages.AccountUpdateProfilePage;
import org.keycloak.testsuite.pages.AppPage;
import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.pages.RegisterPage;
import org.keycloak.testsuite.rule.KeycloakRule;
import org.keycloak.testsuite.rule.LDAPRule;
import org.keycloak.testsuite.rule.WebResource;
import org.keycloak.testsuite.rule.WebRule;
import org.openqa.selenium.WebDriver;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class LDAPRoleMappingsTest {
private static LDAPRule ldapRule = new LDAPRule();
private static UserFederationProviderModel ldapModel = null;
private static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
FederationTestUtils.addLocalUser(manager.getSession(), appRealm, "mary", "mary@test.com", "password-app");
Map<String,String> ldapConfig = ldapRule.getConfig();
ldapConfig.put(LDAPConstants.SYNC_REGISTRATIONS, "true");
ldapConfig.put(LDAPConstants.EDIT_MODE, UserFederationProvider.EditMode.WRITABLE.toString());
ldapModel = appRealm.addUserFederationProvider(LDAPFederationProviderFactory.PROVIDER_NAME, ldapConfig, 0, "test-ldap", -1, -1, 0);
// Delete all LDAP users and add some new for testing
LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel);
LDAPUtils.removeAllUsers(ldapFedProvider, appRealm);
// Add sample application
ClientModel finance = appRealm.addClient("finance");
LDAPObject john = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "johnkeycloak", "John", "Doe", "john@email.org", "1234");
ldapFedProvider.getLdapIdentityStore().updatePassword(john, "Password1");
LDAPObject mary = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "marykeycloak", "Mary", "Kelly", "mary@email.org", "5678");
ldapFedProvider.getLdapIdentityStore().updatePassword(mary, "Password1");
LDAPObject rob = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "robkeycloak", "Rob", "Brown", "rob@email.org", "8910");
ldapFedProvider.getLdapIdentityStore().updatePassword(rob, "Password1");
}
}) {
@Override
protected void after() {
// Need to cleanup some LDAP objects after the test
update(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
RoleLDAPFederationMapper roleMapper = new RoleLDAPFederationMapper();
UserFederationMapperModel roleMapperModel = findRoleMapperModel(appRealm);
LDAPFederationProvider ldapProvider = FederationTestUtils.getLdapProvider(session, ldapModel);
LDAPObject ldapRole = roleMapper.loadLDAPRoleByName(roleMapperModel, ldapProvider, "realmRole3");
if (ldapRole != null) {
ldapProvider.getLdapIdentityStore().remove(ldapRole);
}
}
});
super.after();
}
};
@ClassRule
public static TestRule chain = RuleChain
.outerRule(ldapRule)
.around(keycloakRule);
@Rule
public WebRule webRule = new WebRule(this);
@WebResource
protected OAuthClient oauth;
@WebResource
protected WebDriver driver;
@WebResource
protected AppPage appPage;
@WebResource
protected RegisterPage registerPage;
@WebResource
protected LoginPage loginPage;
@WebResource
protected AccountUpdateProfilePage profilePage;
@WebResource
protected AccountPasswordPage changePasswordPage;
@Test
public void test01_ldapOnlyRoleMappings() {
// TODO: Remove me!!!
RealmAdapter.LDAP_MODE = "LDAP_ONLY";
KeycloakSession session = keycloakRule.startSession();
try {
RealmModel appRealm = session.realms().getRealmByName("test");
UserModel john = session.users().getUserByUsername("johnkeycloak", appRealm);
UserModel mary = session.users().getUserByUsername("marykeycloak", appRealm);
// 1 - Grant some roles in LDAP
// This role should already exists as it was imported from LDAP
RoleModel realmRole1 = appRealm.getRole("realmRole1");
john.grantRole(realmRole1);
// This role should already exists as it was imported from LDAP
RoleModel realmRole2 = appRealm.getRole("realmRole2");
mary.grantRole(realmRole2);
RoleModel realmRole3 = appRealm.addRole("realmRole3");
john.grantRole(realmRole3);
mary.grantRole(realmRole3);
ClientModel accountApp = appRealm.getClientByClientId(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID);
ClientModel financeApp = appRealm.getClientByClientId("finance");
RoleModel manageAccountRole = accountApp.getRole(AccountRoles.MANAGE_ACCOUNT);
RoleModel financeRole1 = financeApp.getRole("financeRole1");
john.grantRole(financeRole1);
// 2 - Check that role mappings are not in local Keycloak DB (They are in LDAP).
UserModel johnDb = session.userStorage().getUserByUsername("johnkeycloak", appRealm);
Set<RoleModel> johnDbRoles = johnDb.getRoleMappings();
Assert.assertFalse(johnDbRoles.contains(realmRole1));
Assert.assertFalse(johnDbRoles.contains(realmRole2));
Assert.assertFalse(johnDbRoles.contains(realmRole3));
Assert.assertFalse(johnDbRoles.contains(financeRole1));
Assert.assertTrue(johnDbRoles.contains(manageAccountRole));
// 3 - Check that role mappings are in LDAP and hence available through federation
Set<RoleModel> johnRoles = john.getRoleMappings();
Assert.assertTrue(johnRoles.contains(realmRole1));
Assert.assertFalse(johnRoles.contains(realmRole2));
Assert.assertTrue(johnRoles.contains(realmRole3));
Assert.assertTrue(johnRoles.contains(financeRole1));
Assert.assertTrue(johnRoles.contains(manageAccountRole));
Set<RoleModel> johnRealmRoles = john.getRealmRoleMappings();
Assert.assertEquals(2, johnRealmRoles.size());
Assert.assertTrue(johnRealmRoles.contains(realmRole1));
Assert.assertTrue(johnRealmRoles.contains(realmRole3));
// account roles are not mapped in LDAP. Those are in Keycloak DB
Set<RoleModel> johnAccountRoles = john.getClientRoleMappings(accountApp);
Assert.assertTrue(johnAccountRoles.contains(manageAccountRole));
Set<RoleModel> johnFinanceRoles = john.getClientRoleMappings(financeApp);
Assert.assertEquals(1, johnFinanceRoles.size());
Assert.assertTrue(johnFinanceRoles.contains(financeRole1));
// 4 - Delete some role mappings and check they are deleted
john.deleteRoleMapping(realmRole3);
john.deleteRoleMapping(realmRole1);
john.deleteRoleMapping(financeRole1);
john.deleteRoleMapping(manageAccountRole);
johnRoles = john.getRoleMappings();
Assert.assertFalse(johnRoles.contains(realmRole1));
Assert.assertFalse(johnRoles.contains(realmRole2));
Assert.assertFalse(johnRoles.contains(realmRole3));
Assert.assertFalse(johnRoles.contains(financeRole1));
Assert.assertFalse(johnRoles.contains(manageAccountRole));
// Cleanup
mary.deleteRoleMapping(realmRole2);
mary.deleteRoleMapping(realmRole3);
john.grantRole(manageAccountRole);
} finally {
keycloakRule.stopSession(session, false);
}
}
@Test
public void test02_readOnlyRoleMappings() {
// TODO: Remove me!!!
RealmAdapter.LDAP_MODE = "READ_ONLY";
KeycloakSession session = keycloakRule.startSession();
try {
RealmModel appRealm = session.realms().getRealmByName("test");
UserModel mary = session.users().getUserByUsername("marykeycloak", appRealm);
RoleModel realmRole1 = appRealm.getRole("realmRole1");
RoleModel realmRole2 = appRealm.getRole("realmRole2");
RoleModel realmRole3 = appRealm.getRole("realmRole3");
if (realmRole3 == null) {
realmRole3 = appRealm.addRole("realmRole3");
}
// Add some role mappings directly into LDAP
RoleLDAPFederationMapper roleMapper = new RoleLDAPFederationMapper();
UserFederationMapperModel roleMapperModel = findRoleMapperModel(appRealm);
LDAPFederationProvider ldapProvider = FederationTestUtils.getLdapProvider(session, ldapModel);
LDAPObject maryLdap = ldapProvider.loadLDAPUserByUsername(appRealm, "marykeycloak");
roleMapper.addRoleMappingInLDAP(roleMapperModel, "realmRole1", ldapProvider, maryLdap);
roleMapper.addRoleMappingInLDAP(roleMapperModel, "realmRole2", ldapProvider, maryLdap);
// Add some role to model
mary.grantRole(realmRole3);
// Assert that mary has both LDAP and DB mapped roles
Set<RoleModel> maryRoles = mary.getRealmRoleMappings();
Assert.assertTrue(maryRoles.contains(realmRole1));
Assert.assertTrue(maryRoles.contains(realmRole2));
Assert.assertTrue(maryRoles.contains(realmRole3));
// Assert that access through DB will have just DB mapped role
UserModel maryDB = session.userStorage().getUserByUsername("marykeycloak", appRealm);
Set<RoleModel> maryDBRoles = maryDB.getRealmRoleMappings();
Assert.assertFalse(maryDBRoles.contains(realmRole1));
Assert.assertFalse(maryDBRoles.contains(realmRole2));
Assert.assertTrue(maryDBRoles.contains(realmRole3));
mary.deleteRoleMapping(realmRole3);
try {
mary.deleteRoleMapping(realmRole1);
Assert.fail("It wasn't expected to successfully delete LDAP role mappings in READ_ONLY mode");
} catch (ModelException expected) {
}
// Delete role mappings directly in LDAP
deleteRoleMappingsInLDAP(roleMapperModel, roleMapper, ldapProvider, maryLdap, "realmRole1");
deleteRoleMappingsInLDAP(roleMapperModel, roleMapper, ldapProvider, maryLdap, "realmRole2");
// Assert role mappings is not available
maryRoles = mary.getRealmRoleMappings();
Assert.assertFalse(maryRoles.contains(realmRole1));
Assert.assertFalse(maryRoles.contains(realmRole2));
Assert.assertFalse(maryRoles.contains(realmRole3));
} finally {
keycloakRule.stopSession(session, false);
}
}
@Test
public void test03_importRoleMappings() {
// TODO: Remove me!!!
RealmAdapter.LDAP_MODE = "IMPORT";
KeycloakSession session = keycloakRule.startSession();
try {
RealmModel appRealm = session.realms().getRealmByName("test");
// Add some role mappings directly in LDAP
RoleLDAPFederationMapper roleMapper = new RoleLDAPFederationMapper();
UserFederationMapperModel roleMapperModel = findRoleMapperModel(appRealm);
LDAPFederationProvider ldapProvider = FederationTestUtils.getLdapProvider(session, ldapModel);
LDAPObject robLdap = ldapProvider.loadLDAPUserByUsername(appRealm, "robkeycloak");
roleMapper.addRoleMappingInLDAP(roleMapperModel, "realmRole1", ldapProvider, robLdap);
roleMapper.addRoleMappingInLDAP(roleMapperModel, "realmRole2", ldapProvider, robLdap);
// Get user and check that he has requested roles from LDAP
UserModel rob = session.users().getUserByUsername("robkeycloak", appRealm);
RoleModel realmRole1 = appRealm.getRole("realmRole1");
RoleModel realmRole2 = appRealm.getRole("realmRole2");
RoleModel realmRole3 = appRealm.getRole("realmRole3");
if (realmRole3 == null) {
realmRole3 = appRealm.addRole("realmRole3");
}
Set<RoleModel> robRoles = rob.getRealmRoleMappings();
Assert.assertTrue(robRoles.contains(realmRole1));
Assert.assertTrue(robRoles.contains(realmRole2));
Assert.assertFalse(robRoles.contains(realmRole3));
// Add some role mappings in model and check that user has it
rob.grantRole(realmRole3);
robRoles = rob.getRealmRoleMappings();
Assert.assertTrue(robRoles.contains(realmRole3));
// Delete some role mappings in LDAP and check that it doesn't have any effect and user still has role
deleteRoleMappingsInLDAP(roleMapperModel, roleMapper, ldapProvider, robLdap, "realmRole1");
deleteRoleMappingsInLDAP(roleMapperModel, roleMapper, ldapProvider, robLdap, "realmRole2");
robRoles = rob.getRealmRoleMappings();
Assert.assertTrue(robRoles.contains(realmRole1));
Assert.assertTrue(robRoles.contains(realmRole2));
// Delete role mappings through model and verifies that user doesn't have them anymore
rob.deleteRoleMapping(realmRole1);
rob.deleteRoleMapping(realmRole2);
rob.deleteRoleMapping(realmRole3);
robRoles = rob.getRealmRoleMappings();
Assert.assertFalse(robRoles.contains(realmRole1));
Assert.assertFalse(robRoles.contains(realmRole2));
Assert.assertFalse(robRoles.contains(realmRole3));
} finally {
keycloakRule.stopSession(session, false);
}
}
private static UserFederationMapperModel findRoleMapperModel(RealmModel appRealm) {
List<UserFederationMapperModel> fedMappers = appRealm.getUserFederationMappers();
for (UserFederationMapperModel mapper : fedMappers) {
if ("realmRoleMapper".equals(mapper.getName())) {
return mapper;
}
}
throw new IllegalStateException("Mapper 'realmRoleMapper' not found");
}
private void deleteRoleMappingsInLDAP(UserFederationMapperModel roleMapperModel, RoleLDAPFederationMapper roleMapper, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, String roleName) {
LDAPIdentityQuery ldapQuery = roleMapper.createRoleQuery(roleMapperModel, ldapProvider);
LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder();
Condition roleNameCondition = conditionsBuilder.equal(new QueryParameter(LDAPConstants.CN), roleName);
ldapQuery.where(roleNameCondition);
LDAPObject ldapRole1 = ldapQuery.getFirstResult();
roleMapper.deleteRoleMappingInLDAP(roleMapperModel, ldapProvider, ldapUser, ldapRole1);
}
}

View file

@ -11,6 +11,7 @@ import org.keycloak.federation.ldap.LDAPFederationProvider;
import org.keycloak.federation.ldap.LDAPFederationProviderFactory;
import org.keycloak.federation.ldap.LDAPUtils;
import org.keycloak.federation.ldap.idm.model.LDAPObject;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.LDAPConstants;
@ -60,6 +61,9 @@ public class SyncProvidersTest {
LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel);
LDAPUtils.removeAllUsers(ldapFedProvider, appRealm);
// Add sample application TODO: REmove this!!!! It's just temporarily needed in SyncProvidersTest until model for federation mappers is implemented
ClientModel finance = appRealm.addClient("finance");
for (int i=1 ; i<=5 ; i++) {
LDAPObject ldapUser = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "user" + i, "User" + i + "FN", "User" + i + "LN", "user" + i + "@email.org", "12" + i);
ldapFedProvider.getLdapIdentityStore().updatePassword(ldapUser, "Password1");

View file

@ -15,9 +15,9 @@ import org.keycloak.models.UserFederationProvider;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class LDAPConfiguration {
public class LDAPTestConfiguration {
private static final Logger log = Logger.getLogger(LDAPConfiguration.class);
private static final Logger log = Logger.getLogger(LDAPTestConfiguration.class);
private String connectionPropertiesLocation;
private boolean startEmbeddedLdapLerver = true;
@ -69,7 +69,7 @@ public class LDAPConfiguration {
DEFAULT_VALUES.put(KerberosConstants.ALLOW_KERBEROS_AUTHENTICATION, "false");
DEFAULT_VALUES.put(KerberosConstants.KERBEROS_REALM, "KEYCLOAK.ORG");
DEFAULT_VALUES.put(KerberosConstants.SERVER_PRINCIPAL, "HTTP/localhost@KEYCLOAK.ORG");
URL keytabUrl = LDAPConfiguration.class.getResource("/kerberos/http.keytab");
URL keytabUrl = LDAPTestConfiguration.class.getResource("/kerberos/http.keytab");
String keyTabPath = new File(keytabUrl.getFile()).getAbsolutePath();
DEFAULT_VALUES.put(KerberosConstants.KEYTAB, keyTabPath);
DEFAULT_VALUES.put(KerberosConstants.DEBUG, "true");
@ -78,11 +78,11 @@ public class LDAPConfiguration {
DEFAULT_VALUES.put(KerberosConstants.USE_KERBEROS_FOR_PASSWORD_AUTHENTICATION, "false");
}
public static LDAPConfiguration readConfiguration(String connectionPropertiesLocation) {
LDAPConfiguration ldapConfiguration = new LDAPConfiguration();
ldapConfiguration.setConnectionPropertiesLocation(connectionPropertiesLocation);
ldapConfiguration.loadConnectionProperties();
return ldapConfiguration;
public static LDAPTestConfiguration readConfiguration(String connectionPropertiesLocation) {
LDAPTestConfiguration ldapTestConfiguration = new LDAPTestConfiguration();
ldapTestConfiguration.setConnectionPropertiesLocation(connectionPropertiesLocation);
ldapTestConfiguration.loadConnectionProperties();
return ldapTestConfiguration;
}
protected void loadConnectionProperties() {

View file

@ -5,7 +5,7 @@ import java.net.URL;
import org.jboss.logging.Logger;
import org.keycloak.testsuite.ldap.EmbeddedServersFactory;
import org.keycloak.testsuite.ldap.LDAPConfiguration;
import org.keycloak.testsuite.ldap.LDAPTestConfiguration;
import org.keycloak.testsuite.ldap.LDAPEmbeddedServer;
/**
@ -21,7 +21,7 @@ public class KerberosRule extends LDAPRule {
this.configLocation = configLocation;
// Global kerberos configuration
URL krb5ConfURL = LDAPConfiguration.class.getResource("/kerberos/test-krb5.conf");
URL krb5ConfURL = LDAPTestConfiguration.class.getResource("/kerberos/test-krb5.conf");
String krb5ConfPath = new File(krb5ConfURL.getFile()).getAbsolutePath();
log.info("Krb5.conf file location is: " + krb5ConfPath);
System.setProperty("java.security.krb5.conf", krb5ConfPath);

View file

@ -4,7 +4,7 @@ import java.util.Map;
import org.junit.rules.ExternalResource;
import org.keycloak.testsuite.ldap.EmbeddedServersFactory;
import org.keycloak.testsuite.ldap.LDAPConfiguration;
import org.keycloak.testsuite.ldap.LDAPTestConfiguration;
import org.keycloak.testsuite.ldap.LDAPEmbeddedServer;
/**
@ -14,15 +14,15 @@ public class LDAPRule extends ExternalResource {
public static final String LDAP_CONNECTION_PROPERTIES_LOCATION = "ldap/ldap-connection.properties";
protected LDAPConfiguration ldapConfiguration;
protected LDAPTestConfiguration ldapTestConfiguration;
protected LDAPEmbeddedServer ldapEmbeddedServer;
@Override
protected void before() throws Throwable {
String connectionPropsLocation = getConnectionPropertiesLocation();
ldapConfiguration = LDAPConfiguration.readConfiguration(connectionPropsLocation);
ldapTestConfiguration = LDAPTestConfiguration.readConfiguration(connectionPropsLocation);
if (ldapConfiguration.isStartEmbeddedLdapLerver()) {
if (ldapTestConfiguration.isStartEmbeddedLdapLerver()) {
EmbeddedServersFactory factory = EmbeddedServersFactory.readConfiguration();
ldapEmbeddedServer = createServer(factory);
ldapEmbeddedServer.init();
@ -36,7 +36,7 @@ public class LDAPRule extends ExternalResource {
if (ldapEmbeddedServer != null) {
ldapEmbeddedServer.stop();
ldapEmbeddedServer = null;
ldapConfiguration = null;
ldapTestConfiguration = null;
}
} catch (Exception e) {
throw new RuntimeException("Error tearDown Embedded LDAP server.", e);
@ -52,6 +52,6 @@ public class LDAPRule extends ExternalResource {
}
public Map<String, String> getConfig() {
return ldapConfiguration.getLDAPConfig();
return ldapTestConfiguration.getLDAPConfig();
}
}

View file

@ -9,13 +9,31 @@ objectclass: top
objectclass: organizationalUnit
ou: People
dn: ou=Roles,dc=keycloak,dc=org
dn: ou=RealmRoles,dc=keycloak,dc=org
objectclass: top
objectclass: organizationalUnit
ou: Roles
ou: RealmRoles
dn: ou=Groups,dc=keycloak,dc=org
dn: cn=realmRole1,ou=RealmRoles,dc=keycloak,dc=org
objectclass: top
objectclass: groupOfNames
cn: realmRole1
member:
dn: cn=realmRole2,ou=RealmRoles,dc=keycloak,dc=org
objectclass: top
objectclass: groupOfNames
cn: realmRole2
member:
dn: ou=FinanceRoles,dc=keycloak,dc=org
objectclass: top
objectclass: organizationalUnit
ou: Groups
ou: FinanceRoles
dn: cn=financeRole1,ou=FinanceRoles,dc=keycloak,dc=org
objectclass: top
objectclass: groupOfNames
cn: financeRole1
member: