KEYCLOAK-630 Added Role Federation mapper. Fixes and refactoring
This commit is contained in:
parent
975337f225
commit
a9f1fda68a
34 changed files with 1213 additions and 209 deletions
|
@ -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 {
|
||||||
|
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
|
@ -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 {
|
||||||
|
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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 {
|
|
||||||
|
|
||||||
}
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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");
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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");
|
||||||
|
|
|
@ -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() {
|
|
@ -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);
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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:
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue