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> * @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 { public class LDAPConfig {

View file

@ -19,7 +19,7 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel; import org.keycloak.models.RoleModel;
import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserCredentialValueModel; import org.keycloak.models.UserCredentialValueModel;
import org.keycloak.models.UserFederationMapper; import org.keycloak.mappers.UserFederationMapper;
import org.keycloak.models.UserFederationMapperModel; import org.keycloak.models.UserFederationMapperModel;
import org.keycloak.models.UserFederationProvider; import org.keycloak.models.UserFederationProvider;
import org.keycloak.models.UserFederationProviderModel; import org.keycloak.models.UserFederationProviderModel;
@ -114,7 +114,7 @@ public class LDAPFederationProvider implements UserFederationProvider {
List<UserFederationMapperModel> federationMappers = realm.getUserFederationMappers(); List<UserFederationMapperModel> federationMappers = realm.getUserFederationMappers();
for (UserFederationMapperModel mapperModel : federationMappers) { for (UserFederationMapperModel mapperModel : federationMappers) {
LDAPFederationMapper ldapMapper = getMapper(mapperModel); LDAPFederationMapper ldapMapper = getMapper(mapperModel);
proxied = ldapMapper.proxy(mapperModel, this, ldapObject, proxied); proxied = ldapMapper.proxy(mapperModel, this, ldapObject, proxied, realm);
} }
return proxied; return proxied;
@ -227,7 +227,7 @@ public class LDAPFederationProvider implements UserFederationProvider {
/** /**
* @param local * @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) { protected LDAPObject loadAndValidateUser(RealmModel realm, UserModel local) {
LDAPObject ldapUser = loadLDAPUserByUsername(realm, local.getUsername()); LDAPObject ldapUser = loadLDAPUserByUsername(realm, local.getUsername());
@ -271,7 +271,7 @@ public class LDAPFederationProvider implements UserFederationProvider {
List<UserFederationMapperModel> federationMappers = realm.getUserFederationMappers(); List<UserFederationMapperModel> federationMappers = realm.getUserFederationMappers();
for (UserFederationMapperModel mapperModel : federationMappers) { for (UserFederationMapperModel mapperModel : federationMappers) {
LDAPFederationMapper ldapMapper = getMapper(mapperModel); LDAPFederationMapper ldapMapper = getMapper(mapperModel);
ldapMapper.importUserFromLDAP(mapperModel, this, ldapUser, imported, true); ldapMapper.onImportUserFromLDAP(mapperModel, this, ldapUser, imported, realm, true);
} }
String userDN = ldapUser.getDn().toString(); String userDN = ldapUser.getDn().toString();
@ -314,7 +314,7 @@ public class LDAPFederationProvider implements UserFederationProvider {
@Override @Override
public void preRemove(RealmModel realm, RoleModel role) { public void preRemove(RealmModel realm, RoleModel role) {
// complete I don't think we have to do anything here // 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) { public boolean validPassword(RealmModel realm, UserModel user, String password) {
@ -407,7 +407,7 @@ public class LDAPFederationProvider implements UserFederationProvider {
List<UserFederationMapperModel> federationMappers = realm.getUserFederationMappers(); List<UserFederationMapperModel> federationMappers = realm.getUserFederationMappers();
for (UserFederationMapperModel mapperModel : federationMappers) { for (UserFederationMapperModel mapperModel : federationMappers) {
LDAPFederationMapper ldapMapper = getMapper(mapperModel); 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()); logger.debugf("Updated user from LDAP: %s", currentUser.getUsername());
@ -477,8 +477,7 @@ public class LDAPFederationProvider implements UserFederationProvider {
} }
public LDAPFederationMapper getMapper(UserFederationMapperModel mapperModel) { public LDAPFederationMapper getMapper(UserFederationMapperModel mapperModel) {
LDAPFederationMapper ldapMapper = (LDAPFederationMapper) getSession().getKeycloakSessionFactory() LDAPFederationMapper ldapMapper = (LDAPFederationMapper) getSession().getProvider(UserFederationMapper.class, mapperModel.getFederationMapperId());
.getProviderFactory(UserFederationMapper.class, mapperModel.getFederationMapperId());
if (ldapMapper == null) { if (ldapMapper == null) {
throw new ModelException("Can't find mapper type with ID: " + mapperModel.getFederationMapperId()); 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(); List<UserFederationMapperModel> federationMappers = realm.getUserFederationMappers();
for (UserFederationMapperModel mapperModel : federationMappers) { for (UserFederationMapperModel mapperModel : federationMappers) {
LDAPFederationMapper ldapMapper = ldapProvider.getMapper(mapperModel); LDAPFederationMapper ldapMapper = ldapProvider.getMapper(mapperModel);
ldapMapper.registerUserToLDAP(mapperModel, ldapProvider, ldapObject, user); ldapMapper.onRegisterUserToLDAP(mapperModel, ldapProvider, ldapObject, user, realm);
} }
LDAPUtils.computeAndSetDn(ldapConfig, ldapObject); LDAPUtils.computeAndSetDn(ldapConfig, ldapObject);
@ -161,10 +161,10 @@ public class LDAPUtils {
return fullName; 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) { public static void computeAndSetDn(LDAPConfig config, LDAPObject ldapObject) {
String rdnLdapAttrName = config.getRdnLdapAttribute(); String rdnLdapAttrName = config.getRdnLdapAttribute();
String rdnLdapAttrValue = (String) ldapObject.getAttribute(rdnLdapAttrName); String rdnLdapAttrValue = ldapObject.getAttributeAsString(rdnLdapAttrName);
if (rdnLdapAttrValue == null) { if (rdnLdapAttrValue == null) {
throw new ModelException("RDN Attribute [" + rdnLdapAttrName + "] is not filled. Filled attributes: " + ldapObject.getAttributes()); 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) { public static String getUsername(LDAPObject ldapUser, LDAPConfig config) {
String usernameAttr = config.getUsernameLdapAttribute(); String usernameAttr = config.getUsernameLdapAttribute();
return (String) ldapUser.getAttribute(usernameAttr); return ldapUser.getAttributeAsString(usernameAttr);
}
public static boolean parseBooleanParameter(UserFederationMapperModel mapperModel, String paramName) {
String readOnly = mapperModel.getConfig().get(paramName);
return Boolean.parseBoolean(readOnly);
} }
} }

View file

@ -1,14 +1,16 @@
package org.keycloak.federation.ldap.idm.model; package org.keycloak.federation.ldap.idm.model;
import java.util.Deque;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Queue;
/** /**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a> * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/ */
public class LDAPDn { 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) { public static LDAPDn fromString(String dnString) {
LDAPDn dn = new LDAPDn(); 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" * @return string like "uid=joe" from the DN like "uid=joe,dc=something,dc=org"
*/ */
public String getFirstRdn() { public String getFirstRdn() {
Entry firstEntry = entries.get(0); Entry firstEntry = entries.getFirst();
return firstEntry.attrName + "=" + firstEntry.attrValue; 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" * @return string attribute name like "uid" from the DN like "uid=joe,dc=something,dc=org"
*/ */
public String getFirstRdnAttrName() { public String getFirstRdnAttrName() {
Entry firstEntry = entries.get(0); Entry firstEntry = entries.getFirst();
return firstEntry.attrName; 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" * @return string like "dc=something,dc=org" from the DN like "uid=joe,dc=something,dc=org"
*/ */
public String getParentDn() { public String getParentDn() {
StringBuilder builder = new StringBuilder(); return new LinkedList<Entry>(entries).remove().toString();
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();
} }
public void addToHead(String rdnName, String rdnValue) { 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) { 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> objectClasses = new LinkedList<String>();
private final List<String> readOnlyAttributeNames = 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() { public String getUuid() {
return uuid; return uuid;
@ -61,7 +61,7 @@ public class LDAPObject {
this.rdnAttributeName = rdnAttributeName; this.rdnAttributeName = rdnAttributeName;
} }
public void setAttribute(String attributeName, Serializable attributeValue) { public void setAttribute(String attributeName, Object attributeValue) {
attributes.put(attributeName, 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); 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; return attributes;
} }

View file

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

View file

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

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.QueryParameter;
import org.keycloak.federation.ldap.idm.query.internal.EqualCondition; import org.keycloak.federation.ldap.idm.query.internal.EqualCondition;
import org.keycloak.federation.ldap.idm.query.internal.LDAPIdentityQuery; 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.LDAPConstants;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserFederationMapperModel; import org.keycloak.models.UserFederationMapperModel;
import org.keycloak.models.UserFederationProvider; import org.keycloak.models.UserFederationProvider;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
@ -28,24 +31,9 @@ public class FullNameLDAPFederationMapper extends AbstractLDAPFederationMapper {
public static final String READ_ONLY = "read.only"; public static final String READ_ONLY = "read.only";
@Override @Override
public String getHelpText() { public void onImportUserFromLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, UserModel user, RealmModel realm, boolean isCreate) {
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) {
String ldapFullNameAttrName = getLdapFullNameAttrName(mapperModel); String ldapFullNameAttrName = getLdapFullNameAttrName(mapperModel);
String fullName = (String) ldapObject.getAttribute(ldapFullNameAttrName); String fullName = ldapUser.getAttributeAsString(ldapFullNameAttrName);
fullName = fullName.trim(); fullName = fullName.trim();
if (fullName != null) { if (fullName != null) {
int lastSpaceIndex = fullName.lastIndexOf(" "); int lastSpaceIndex = fullName.lastIndexOf(" ");
@ -59,22 +47,22 @@ public class FullNameLDAPFederationMapper extends AbstractLDAPFederationMapper {
} }
@Override @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 ldapFullNameAttrName = getLdapFullNameAttrName(mapperModel);
String fullName = getFullName(localUser.getFirstName(), localUser.getLastName()); String fullName = getFullName(localUser.getFirstName(), localUser.getLastName());
ldapObject.setAttribute(ldapFullNameAttrName, fullName); ldapUser.setAttribute(ldapFullNameAttrName, fullName);
if (isReadOnly(mapperModel)) { if (isReadOnly(mapperModel)) {
ldapObject.addReadOnlyAttributeName(ldapFullNameAttrName); ldapUser.addReadOnlyAttributeName(ldapFullNameAttrName);
} }
} }
@Override @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)) { if (ldapProvider.getEditMode() == UserFederationProvider.EditMode.WRITABLE && !isReadOnly(mapperModel)) {
AbstractTxAwareLDAPUserModelDelegate txDelegate = new AbstractTxAwareLDAPUserModelDelegate(delegate, ldapProvider, ldapObject) { TxAwareLDAPUserModelDelegate txDelegate = new TxAwareLDAPUserModelDelegate(delegate, ldapProvider, ldapUser) {
@Override @Override
public void setFirstName(String firstName) { public void setFirstName(String firstName) {
@ -97,7 +85,7 @@ public class FullNameLDAPFederationMapper extends AbstractLDAPFederationMapper {
ensureTransactionStarted(); ensureTransactionStarted();
String ldapFullNameAttrName = getLdapFullNameAttrName(mapperModel); String ldapFullNameAttrName = getLdapFullNameAttrName(mapperModel);
ldapObject.setAttribute(ldapFullNameAttrName, fullName); ldapUser.setAttribute(ldapFullNameAttrName, fullName);
} }
}; };
@ -127,10 +115,10 @@ public class FullNameLDAPFederationMapper extends AbstractLDAPFederationMapper {
lastNameCondition = (EqualCondition) condition; lastNameCondition = (EqualCondition) condition;
query.getConditions().remove(condition); query.getConditions().remove(condition);
} else if (param.getName().equals(LDAPConstants.GIVENNAME)) { } 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; firstNameCondition = (EqualCondition) condition;
} else if (param.getName().equals(LDAPConstants.SN)) { } 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; lastNameCondition = (EqualCondition) condition;
} }
} }
@ -169,6 +157,6 @@ public class FullNameLDAPFederationMapper extends AbstractLDAPFederationMapper {
} }
private boolean isReadOnly(UserFederationMapperModel mapperModel) { 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.LDAPFederationProvider;
import org.keycloak.federation.ldap.idm.model.LDAPObject; import org.keycloak.federation.ldap.idm.model.LDAPObject;
import org.keycloak.federation.ldap.idm.query.internal.LDAPIdentityQuery; 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.UserFederationMapperModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
@ -12,17 +13,50 @@ import org.keycloak.models.UserModel;
*/ */
public interface LDAPFederationMapper extends UserFederationMapper { 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 * Called when importing user from LDAP to local keycloak DB.
void registerUserToLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapObject, UserModel localUser); *
* @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); 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> * @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 LDAPFederationProvider provider;
protected LDAPObject ldapObject; protected LDAPObject ldapUser;
private final LDAPTransaction transaction; private final LDAPTransaction transaction;
public AbstractTxAwareLDAPUserModelDelegate(UserModel delegate, LDAPFederationProvider provider, LDAPObject ldapObject) { public TxAwareLDAPUserModelDelegate(UserModel delegate, LDAPFederationProvider provider, LDAPObject ldapUser) {
super(delegate); super(delegate);
this.provider = provider; this.provider = provider;
this.ldapObject = ldapObject; this.ldapUser = ldapUser;
this.transaction = findOrCreateTransaction(); 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 // 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; UserModelDelegate delegate = this;
while (true) { while (true) {
UserModel deleg = delegate.getDelegate(); UserModel deleg = delegate.getDelegate();
if (!(deleg instanceof UserModelDelegate)) { if (!(deleg instanceof UserModelDelegate)) {
// Existing transaction not available. Need to create new
return new LDAPTransaction(); return new LDAPTransaction();
} else { } else {
delegate = (UserModelDelegate) deleg; delegate = (UserModelDelegate) deleg;
} }
// Check if it's transaction aware delegate if (delegate instanceof TxAwareLDAPUserModelDelegate) {
if (delegate instanceof AbstractTxAwareLDAPUserModelDelegate) { TxAwareLDAPUserModelDelegate txDelegate = (TxAwareLDAPUserModelDelegate) delegate;
AbstractTxAwareLDAPUserModelDelegate txDelegate = (AbstractTxAwareLDAPUserModelDelegate) delegate;
return txDelegate.getTransaction(); return txDelegate.getTransaction();
} }
} }
@ -52,7 +50,7 @@ public abstract class AbstractTxAwareLDAPUserModelDelegate extends UserModelDele
protected void ensureTransactionStarted() { protected void ensureTransactionStarted() {
if (transaction.state == TransactionState.NOT_STARTED) { if (transaction.state == TransactionState.NOT_STARTED) {
if (logger.isTraceEnabled()) { 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); this.provider.getSession().getTransaction().enlistAfterCompletion(transaction);
@ -81,10 +79,10 @@ public abstract class AbstractTxAwareLDAPUserModelDelegate extends UserModelDele
} }
if (logger.isTraceEnabled()) { 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; state = TransactionState.FINISHED;
} }
@ -94,7 +92,7 @@ public abstract class AbstractTxAwareLDAPUserModelDelegate extends UserModelDele
throw new IllegalStateException("Transaction in illegal state for rollback: " + state); 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; 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.Condition;
import org.keycloak.federation.ldap.idm.query.QueryParameter; 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.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.UserFederationMapperModel;
import org.keycloak.models.UserFederationProvider; import org.keycloak.models.UserFederationProvider;
import org.keycloak.models.UserModel; 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 USER_MODEL_ATTRIBUTE = "user.model.attribute";
public static final String LDAP_ATTRIBUTE = "ldap.attribute"; public static final String LDAP_ATTRIBUTE = "ldap.attribute";
// TODO: Merge with fullname mapper
public static final String READ_ONLY = "read.only"; public static final String READ_ONLY = "read.only";
@Override
public String getHelpText() {
return "Some help text TODO";
}
@Override @Override
public List<ProviderConfigProperty> getConfigProperties() { public void onImportUserFromLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, UserModel user, RealmModel realm, boolean isCreate) {
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) {
String userModelAttrName = mapperModel.getConfig().get(USER_MODEL_ATTRIBUTE); String userModelAttrName = mapperModel.getConfig().get(USER_MODEL_ATTRIBUTE);
String ldapAttrName = mapperModel.getConfig().get(LDAP_ATTRIBUTE); String ldapAttrName = mapperModel.getConfig().get(LDAP_ATTRIBUTE);
Serializable ldapAttrValue = ldapObject.getAttribute(ldapAttrName); Object ldapAttrValue = ldapUser.getAttribute(ldapAttrName);
if (ldapAttrValue != null) { if (ldapAttrValue != null) {
Property<Object> userModelProperty = userModelProperties.get(userModelAttrName); Property<Object> userModelProperty = userModelProperties.get(userModelAttrName);
@ -82,7 +69,7 @@ public class UserAttributeLDAPFederationMapper extends AbstractLDAPFederationMap
} }
@Override @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 userModelAttrName = mapperModel.getConfig().get(USER_MODEL_ATTRIBUTE);
String ldapAttrName = mapperModel.getConfig().get(LDAP_ATTRIBUTE); String ldapAttrName = mapperModel.getConfig().get(LDAP_ATTRIBUTE);
@ -97,46 +84,42 @@ public class UserAttributeLDAPFederationMapper extends AbstractLDAPFederationMap
attrValue = localUser.getAttribute(userModelAttrName); attrValue = localUser.getAttribute(userModelAttrName);
} }
ldapObject.setAttribute(ldapAttrName, (Serializable) attrValue); ldapUser.setAttribute(ldapAttrName, attrValue);
if (isReadOnly(mapperModel)) { if (isReadOnly(mapperModel)) {
ldapObject.addReadOnlyAttributeName(ldapAttrName); ldapUser.addReadOnlyAttributeName(ldapAttrName);
} }
} }
@Override @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)) { if (ldapProvider.getEditMode() == UserFederationProvider.EditMode.WRITABLE && !isReadOnly(mapperModel)) {
final String userModelAttrName = mapperModel.getConfig().get(USER_MODEL_ATTRIBUTE); final String userModelAttrName = mapperModel.getConfig().get(USER_MODEL_ATTRIBUTE);
final String ldapAttrName = mapperModel.getConfig().get(LDAP_ATTRIBUTE); final String ldapAttrName = mapperModel.getConfig().get(LDAP_ATTRIBUTE);
AbstractTxAwareLDAPUserModelDelegate txDelegate = new AbstractTxAwareLDAPUserModelDelegate(delegate, ldapProvider, ldapObject) { TxAwareLDAPUserModelDelegate txDelegate = new TxAwareLDAPUserModelDelegate(delegate, ldapProvider, ldapUser) {
@Override @Override
public void setAttribute(String name, String value) { public void setAttribute(String name, String value) {
setLDAPAttribute(name, value); setLDAPAttribute(name, value);
super.setAttribute(name, value); super.setAttribute(name, value);
} }
@Override @Override
public void setEmail(String email) { public void setEmail(String email) {
setLDAPAttribute(UserModel.EMAIL, email); setLDAPAttribute(UserModel.EMAIL, email);
super.setEmail(email); super.setEmail(email);
} }
@Override @Override
public void setLastName(String lastName) { public void setLastName(String lastName) {
setLDAPAttribute(UserModel.LAST_NAME, lastName); setLDAPAttribute(UserModel.LAST_NAME, lastName);
super.setLastName(lastName); super.setLastName(lastName);
} }
@Override @Override
public void setFirstName(String firstName) { public void setFirstName(String firstName) {
setLDAPAttribute(UserModel.FIRST_NAME, firstName); setLDAPAttribute(UserModel.FIRST_NAME, firstName);
super.setFirstName(firstName); super.setFirstName(firstName);
} }
@ -148,7 +131,7 @@ public class UserAttributeLDAPFederationMapper extends AbstractLDAPFederationMap
ensureTransactionStarted(); ensureTransactionStarted();
ldapObject.setAttribute(ldapAttrName, value); ldapUser.setAttribute(ldapAttrName, value);
} }
} }
@ -181,6 +164,6 @@ public class UserAttributeLDAPFederationMapper extends AbstractLDAPFederationMap
} }
private boolean isReadOnly(UserFederationMapperModel mapperModel) { 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.UserAttributeLDAPFederationMapperFactory
org.keycloak.federation.ldap.mappers.FullNameLDAPFederationMapper 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.Provider;
import org.keycloak.provider.ProviderFactory; import org.keycloak.provider.ProviderFactory;
@ -21,6 +21,6 @@ public class UserFederationMapperSpi implements Spi {
@Override @Override
public Class<? extends ProviderFactory> getProviderFactoryClass() { 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) { public static String getMasterRealmAdminApplicationClientId(RealmModel realm) {
return realm.getName() + "-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.UserFederationSpi
org.keycloak.models.UserFederationMapperSpi org.keycloak.mappers.UserFederationMapperSpi
org.keycloak.models.RealmSpi org.keycloak.models.RealmSpi
org.keycloak.models.UserSessionSpi org.keycloak.models.UserSessionSpi
org.keycloak.models.UserSpi 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.FederatedIdentityEntity;
import org.keycloak.models.entities.RoleEntity; import org.keycloak.models.entities.RoleEntity;
import org.keycloak.models.entities.UserEntity; import org.keycloak.models.entities.UserEntity;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.Pbkdf2PasswordEncoder; import org.keycloak.models.utils.Pbkdf2PasswordEncoder;
import org.keycloak.util.Time; import org.keycloak.util.Time;
@ -394,12 +395,7 @@ public class UserAdapter implements UserModel, Comparable {
@Override @Override
public boolean hasRole(RoleModel role) { public boolean hasRole(RoleModel role) {
Set<RoleModel> roles = getRoleMappings(); Set<RoleModel> roles = getRoleMappings();
if (roles.contains(role)) return true; return KeycloakModelUtils.hasRole(roles, role);
for (RoleModel mapping : roles) {
if (mapping.hasRole(role)) return true;
}
return false;
} }
@Override @Override

View file

@ -952,6 +952,7 @@ public class RealmAdapter implements RealmModel {
return null; return null;
} }
public static String LDAP_MODE = "LDAP_ONLY";
@Override @Override
public List<UserFederationMapperModel> getUserFederationMappers() { public List<UserFederationMapperModel> getUserFederationMappers() {
@ -992,6 +993,17 @@ public class RealmAdapter implements RealmModel {
"user.model.attribute", LDAPConstants.MODIFY_TIMESTAMP, "user.model.attribute", LDAPConstants.MODIFY_TIMESTAMP,
"ldap.attribute", LDAPConstants.MODIFY_TIMESTAMP, "ldap.attribute", LDAPConstants.MODIFY_TIMESTAMP,
"read.only", "true")); "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; return mappers;
} }

View file

@ -404,12 +404,7 @@ public class UserAdapter implements UserModel {
@Override @Override
public boolean hasRole(RoleModel role) { public boolean hasRole(RoleModel role) {
Set<RoleModel> roles = getRoleMappings(); Set<RoleModel> roles = getRoleMappings();
if (roles.contains(role)) return true; return KeycloakModelUtils.hasRole(roles, role);
for (RoleModel mapping : roles) {
if (mapping.hasRole(role)) return true;
}
return false;
} }
protected TypedQuery<UserRoleMappingEntity> getUserRoleMappingEntityTypedQuery(RoleModel 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.MongoUserConsentEntity;
import org.keycloak.models.mongo.keycloak.entities.MongoUserEntity; import org.keycloak.models.mongo.keycloak.entities.MongoUserEntity;
import org.keycloak.models.mongo.utils.MongoModelUtils; import org.keycloak.models.mongo.utils.MongoModelUtils;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.Pbkdf2PasswordEncoder; import org.keycloak.models.utils.Pbkdf2PasswordEncoder;
import org.keycloak.util.Time; import org.keycloak.util.Time;
@ -374,12 +375,7 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
@Override @Override
public boolean hasRole(RoleModel role) { public boolean hasRole(RoleModel role) {
Set<RoleModel> roles = getRoleMappings(); Set<RoleModel> roles = getRoleMappings();
if (roles.contains(role)) return true; return KeycloakModelUtils.hasRole(roles, role);
for (RoleModel mapping : roles) {
if (mapping.hasRole(role)) return true;
}
return false;
} }
@Override @Override

View file

@ -13,6 +13,7 @@ import org.keycloak.federation.ldap.LDAPFederationProvider;
import org.keycloak.federation.ldap.LDAPFederationProviderFactory; import org.keycloak.federation.ldap.LDAPFederationProviderFactory;
import org.keycloak.federation.ldap.LDAPUtils; import org.keycloak.federation.ldap.LDAPUtils;
import org.keycloak.federation.ldap.idm.model.LDAPObject; import org.keycloak.federation.ldap.idm.model.LDAPObject;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.LDAPConstants; import org.keycloak.models.LDAPConstants;
import org.keycloak.models.ModelReadOnlyException; import org.keycloak.models.ModelReadOnlyException;
@ -65,6 +66,9 @@ public class FederationProvidersIntegrationTest {
LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel); LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel);
LDAPUtils.removeAllUsers(ldapFedProvider, appRealm); 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"); LDAPObject john = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "johnkeycloak", "John", "Doe", "john@email.org", "1234");
ldapFedProvider.getLdapIdentityStore().updatePassword(john, "Password1"); 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.LDAPFederationProviderFactory;
import org.keycloak.federation.ldap.LDAPUtils; import org.keycloak.federation.ldap.LDAPUtils;
import org.keycloak.federation.ldap.idm.model.LDAPObject; import org.keycloak.federation.ldap.idm.model.LDAPObject;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.LDAPConstants; import org.keycloak.models.LDAPConstants;
@ -60,6 +61,9 @@ public class SyncProvidersTest {
LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel); LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel);
LDAPUtils.removeAllUsers(ldapFedProvider, appRealm); 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++) { 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); 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"); 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> * @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 String connectionPropertiesLocation;
private boolean startEmbeddedLdapLerver = true; private boolean startEmbeddedLdapLerver = true;
@ -69,7 +69,7 @@ public class LDAPConfiguration {
DEFAULT_VALUES.put(KerberosConstants.ALLOW_KERBEROS_AUTHENTICATION, "false"); DEFAULT_VALUES.put(KerberosConstants.ALLOW_KERBEROS_AUTHENTICATION, "false");
DEFAULT_VALUES.put(KerberosConstants.KERBEROS_REALM, "KEYCLOAK.ORG"); DEFAULT_VALUES.put(KerberosConstants.KERBEROS_REALM, "KEYCLOAK.ORG");
DEFAULT_VALUES.put(KerberosConstants.SERVER_PRINCIPAL, "HTTP/localhost@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(); String keyTabPath = new File(keytabUrl.getFile()).getAbsolutePath();
DEFAULT_VALUES.put(KerberosConstants.KEYTAB, keyTabPath); DEFAULT_VALUES.put(KerberosConstants.KEYTAB, keyTabPath);
DEFAULT_VALUES.put(KerberosConstants.DEBUG, "true"); DEFAULT_VALUES.put(KerberosConstants.DEBUG, "true");
@ -78,11 +78,11 @@ public class LDAPConfiguration {
DEFAULT_VALUES.put(KerberosConstants.USE_KERBEROS_FOR_PASSWORD_AUTHENTICATION, "false"); DEFAULT_VALUES.put(KerberosConstants.USE_KERBEROS_FOR_PASSWORD_AUTHENTICATION, "false");
} }
public static LDAPConfiguration readConfiguration(String connectionPropertiesLocation) { public static LDAPTestConfiguration readConfiguration(String connectionPropertiesLocation) {
LDAPConfiguration ldapConfiguration = new LDAPConfiguration(); LDAPTestConfiguration ldapTestConfiguration = new LDAPTestConfiguration();
ldapConfiguration.setConnectionPropertiesLocation(connectionPropertiesLocation); ldapTestConfiguration.setConnectionPropertiesLocation(connectionPropertiesLocation);
ldapConfiguration.loadConnectionProperties(); ldapTestConfiguration.loadConnectionProperties();
return ldapConfiguration; return ldapTestConfiguration;
} }
protected void loadConnectionProperties() { protected void loadConnectionProperties() {

View file

@ -5,7 +5,7 @@ import java.net.URL;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.testsuite.ldap.EmbeddedServersFactory; import org.keycloak.testsuite.ldap.EmbeddedServersFactory;
import org.keycloak.testsuite.ldap.LDAPConfiguration; import org.keycloak.testsuite.ldap.LDAPTestConfiguration;
import org.keycloak.testsuite.ldap.LDAPEmbeddedServer; import org.keycloak.testsuite.ldap.LDAPEmbeddedServer;
/** /**
@ -21,7 +21,7 @@ public class KerberosRule extends LDAPRule {
this.configLocation = configLocation; this.configLocation = configLocation;
// Global kerberos configuration // 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(); String krb5ConfPath = new File(krb5ConfURL.getFile()).getAbsolutePath();
log.info("Krb5.conf file location is: " + krb5ConfPath); log.info("Krb5.conf file location is: " + krb5ConfPath);
System.setProperty("java.security.krb5.conf", 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.junit.rules.ExternalResource;
import org.keycloak.testsuite.ldap.EmbeddedServersFactory; import org.keycloak.testsuite.ldap.EmbeddedServersFactory;
import org.keycloak.testsuite.ldap.LDAPConfiguration; import org.keycloak.testsuite.ldap.LDAPTestConfiguration;
import org.keycloak.testsuite.ldap.LDAPEmbeddedServer; 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"; public static final String LDAP_CONNECTION_PROPERTIES_LOCATION = "ldap/ldap-connection.properties";
protected LDAPConfiguration ldapConfiguration; protected LDAPTestConfiguration ldapTestConfiguration;
protected LDAPEmbeddedServer ldapEmbeddedServer; protected LDAPEmbeddedServer ldapEmbeddedServer;
@Override @Override
protected void before() throws Throwable { protected void before() throws Throwable {
String connectionPropsLocation = getConnectionPropertiesLocation(); String connectionPropsLocation = getConnectionPropertiesLocation();
ldapConfiguration = LDAPConfiguration.readConfiguration(connectionPropsLocation); ldapTestConfiguration = LDAPTestConfiguration.readConfiguration(connectionPropsLocation);
if (ldapConfiguration.isStartEmbeddedLdapLerver()) { if (ldapTestConfiguration.isStartEmbeddedLdapLerver()) {
EmbeddedServersFactory factory = EmbeddedServersFactory.readConfiguration(); EmbeddedServersFactory factory = EmbeddedServersFactory.readConfiguration();
ldapEmbeddedServer = createServer(factory); ldapEmbeddedServer = createServer(factory);
ldapEmbeddedServer.init(); ldapEmbeddedServer.init();
@ -36,7 +36,7 @@ public class LDAPRule extends ExternalResource {
if (ldapEmbeddedServer != null) { if (ldapEmbeddedServer != null) {
ldapEmbeddedServer.stop(); ldapEmbeddedServer.stop();
ldapEmbeddedServer = null; ldapEmbeddedServer = null;
ldapConfiguration = null; ldapTestConfiguration = null;
} }
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException("Error tearDown Embedded LDAP server.", e); throw new RuntimeException("Error tearDown Embedded LDAP server.", e);
@ -52,6 +52,6 @@ public class LDAPRule extends ExternalResource {
} }
public Map<String, String> getConfig() { public Map<String, String> getConfig() {
return ldapConfiguration.getLDAPConfig(); return ldapTestConfiguration.getLDAPConfig();
} }
} }

View file

@ -9,13 +9,31 @@ objectclass: top
objectclass: organizationalUnit objectclass: organizationalUnit
ou: People ou: People
dn: ou=Roles,dc=keycloak,dc=org dn: ou=RealmRoles,dc=keycloak,dc=org
objectclass: top objectclass: top
objectclass: organizationalUnit 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: top
objectclass: organizationalUnit objectclass: organizationalUnit
ou: Groups ou: FinanceRoles
dn: cn=financeRole1,ou=FinanceRoles,dc=keycloak,dc=org
objectclass: top
objectclass: groupOfNames
cn: financeRole1
member: