diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPConfig.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPConfig.java
index 2442d30d3d..65181e147b 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPConfig.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPConfig.java
@@ -16,7 +16,7 @@ import org.keycloak.models.UserFederationProviderModel;
/**
* @author Marek Posolda
*
- * TODO: init properties at startup instead of always compute them
+ * TODO: init properties at constructor instead of always compute them
*/
public class LDAPConfig {
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java
index 7d3ca02570..ad55bc3534 100755
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java
@@ -19,7 +19,7 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserCredentialValueModel;
-import org.keycloak.models.UserFederationMapper;
+import org.keycloak.mappers.UserFederationMapper;
import org.keycloak.models.UserFederationMapperModel;
import org.keycloak.models.UserFederationProvider;
import org.keycloak.models.UserFederationProviderModel;
@@ -114,7 +114,7 @@ public class LDAPFederationProvider implements UserFederationProvider {
List federationMappers = realm.getUserFederationMappers();
for (UserFederationMapperModel mapperModel : federationMappers) {
LDAPFederationMapper ldapMapper = getMapper(mapperModel);
- proxied = ldapMapper.proxy(mapperModel, this, ldapObject, proxied);
+ proxied = ldapMapper.proxy(mapperModel, this, ldapObject, proxied, realm);
}
return proxied;
@@ -227,7 +227,7 @@ public class LDAPFederationProvider implements UserFederationProvider {
/**
* @param local
- * @return ldapObject corresponding to local user or null if user is no longer in LDAP
+ * @return ldapUser corresponding to local user or null if user is no longer in LDAP
*/
protected LDAPObject loadAndValidateUser(RealmModel realm, UserModel local) {
LDAPObject ldapUser = loadLDAPUserByUsername(realm, local.getUsername());
@@ -271,7 +271,7 @@ public class LDAPFederationProvider implements UserFederationProvider {
List federationMappers = realm.getUserFederationMappers();
for (UserFederationMapperModel mapperModel : federationMappers) {
LDAPFederationMapper ldapMapper = getMapper(mapperModel);
- ldapMapper.importUserFromLDAP(mapperModel, this, ldapUser, imported, true);
+ ldapMapper.onImportUserFromLDAP(mapperModel, this, ldapUser, imported, realm, true);
}
String userDN = ldapUser.getDn().toString();
@@ -314,7 +314,7 @@ public class LDAPFederationProvider implements UserFederationProvider {
@Override
public void preRemove(RealmModel realm, RoleModel role) {
// complete I don't think we have to do anything here
- // TODO: requires implementation... Maybe mappers callback
+ // TODO: requires implementation... Maybe mappers callback to ensure role deletion propagated to LDAP by RoleLDAPFederationMapper
}
public boolean validPassword(RealmModel realm, UserModel user, String password) {
@@ -407,7 +407,7 @@ public class LDAPFederationProvider implements UserFederationProvider {
List federationMappers = realm.getUserFederationMappers();
for (UserFederationMapperModel mapperModel : federationMappers) {
LDAPFederationMapper ldapMapper = getMapper(mapperModel);
- ldapMapper.importUserFromLDAP(mapperModel, this, ldapUser, currentUser, false);
+ ldapMapper.onImportUserFromLDAP(mapperModel, this, ldapUser, currentUser, realm, false);
}
logger.debugf("Updated user from LDAP: %s", currentUser.getUsername());
@@ -477,8 +477,7 @@ public class LDAPFederationProvider implements UserFederationProvider {
}
public LDAPFederationMapper getMapper(UserFederationMapperModel mapperModel) {
- LDAPFederationMapper ldapMapper = (LDAPFederationMapper) getSession().getKeycloakSessionFactory()
- .getProviderFactory(UserFederationMapper.class, mapperModel.getFederationMapperId());
+ LDAPFederationMapper ldapMapper = (LDAPFederationMapper) getSession().getProvider(UserFederationMapper.class, mapperModel.getFederationMapperId());
if (ldapMapper == null) {
throw new ModelException("Can't find mapper type with ID: " + mapperModel.getFederationMapperId());
}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPUtils.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPUtils.java
index 1e9918df35..6c5ac93237 100755
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPUtils.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPUtils.java
@@ -37,7 +37,7 @@ public class LDAPUtils {
List federationMappers = realm.getUserFederationMappers();
for (UserFederationMapperModel mapperModel : federationMappers) {
LDAPFederationMapper ldapMapper = ldapProvider.getMapper(mapperModel);
- ldapMapper.registerUserToLDAP(mapperModel, ldapProvider, ldapObject, user);
+ ldapMapper.onRegisterUserToLDAP(mapperModel, ldapProvider, ldapObject, user, realm);
}
LDAPUtils.computeAndSetDn(ldapConfig, ldapObject);
@@ -161,10 +161,10 @@ public class LDAPUtils {
return fullName;
} */
- // ldapObject has filled attributes, but doesn't have filled
+ // ldapUser has filled attributes, but doesn't have filled dn
public static void computeAndSetDn(LDAPConfig config, LDAPObject ldapObject) {
String rdnLdapAttrName = config.getRdnLdapAttribute();
- String rdnLdapAttrValue = (String) ldapObject.getAttribute(rdnLdapAttrName);
+ String rdnLdapAttrValue = ldapObject.getAttributeAsString(rdnLdapAttrName);
if (rdnLdapAttrValue == null) {
throw new ModelException("RDN Attribute [" + rdnLdapAttrName + "] is not filled. Filled attributes: " + ldapObject.getAttributes());
}
@@ -176,11 +176,6 @@ public class LDAPUtils {
public static String getUsername(LDAPObject ldapUser, LDAPConfig config) {
String usernameAttr = config.getUsernameLdapAttribute();
- return (String) ldapUser.getAttribute(usernameAttr);
- }
-
- public static boolean parseBooleanParameter(UserFederationMapperModel mapperModel, String paramName) {
- String readOnly = mapperModel.getConfig().get(paramName);
- return Boolean.parseBoolean(readOnly);
+ return ldapUser.getAttributeAsString(usernameAttr);
}
}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/LDAPDn.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/LDAPDn.java
index 999f133fd7..5d58438b15 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/LDAPDn.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/LDAPDn.java
@@ -1,14 +1,16 @@
package org.keycloak.federation.ldap.idm.model;
+import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
+import java.util.Queue;
/**
* @author Marek Posolda
*/
public class LDAPDn {
- private final List entries = new LinkedList();
+ private final Deque entries = new LinkedList();
public static LDAPDn fromString(String dnString) {
LDAPDn dn = new LDAPDn();
@@ -43,7 +45,7 @@ public class LDAPDn {
* @return string like "uid=joe" from the DN like "uid=joe,dc=something,dc=org"
*/
public String getFirstRdn() {
- Entry firstEntry = entries.get(0);
+ Entry firstEntry = entries.getFirst();
return firstEntry.attrName + "=" + firstEntry.attrValue;
}
@@ -51,7 +53,7 @@ public class LDAPDn {
* @return string attribute name like "uid" from the DN like "uid=joe,dc=something,dc=org"
*/
public String getFirstRdnAttrName() {
- Entry firstEntry = entries.get(0);
+ Entry firstEntry = entries.getFirst();
return firstEntry.attrName;
}
@@ -60,28 +62,15 @@ public class LDAPDn {
* @return string like "dc=something,dc=org" from the DN like "uid=joe,dc=something,dc=org"
*/
public String getParentDn() {
- StringBuilder builder = new StringBuilder();
-
- int n = 0;
- for (Entry rdn : entries) {
- n++;
- if (n > 2) {
- builder.append(",");
- }
- if (n >= 2) {
- builder.append(rdn.attrName).append("=").append(rdn.attrValue);
- }
- }
-
- return builder.toString();
+ return new LinkedList(entries).remove().toString();
}
public void addToHead(String rdnName, String rdnValue) {
- entries.add(0, new Entry(rdnName, rdnValue));
+ entries.addFirst(new Entry(rdnName, rdnValue));
}
public void addToBottom(String rdnName, String rdnValue) {
- entries.add(new Entry(rdnName, rdnValue));
+ entries.addLast(new Entry(rdnName, rdnValue));
}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/LDAPObject.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/LDAPObject.java
index 61b631d5e0..587a81f1cf 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/LDAPObject.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/LDAPObject.java
@@ -18,7 +18,7 @@ public class LDAPObject {
private final List objectClasses = new LinkedList();
private final List readOnlyAttributeNames = new LinkedList();
- private final Map attributes = new HashMap();
+ private final Map attributes = new HashMap();
public String getUuid() {
return uuid;
@@ -61,7 +61,7 @@ public class LDAPObject {
this.rdnAttributeName = rdnAttributeName;
}
- public void setAttribute(String attributeName, Serializable attributeValue) {
+ public void setAttribute(String attributeName, Object attributeValue) {
attributes.put(attributeName, attributeValue);
}
@@ -70,12 +70,21 @@ public class LDAPObject {
}
- public Serializable getAttribute(String name) {
+ public Object getAttribute(String name) {
return attributes.get(name);
}
+ public String getAttributeAsString(String name) {
+ Object attrValue = attributes.get(name);
+ if (attrValue != null && !(attrValue instanceof String)) {
+ throw new IllegalStateException("Expected String but attribute was " + attrValue + " of type " + attrValue.getClass().getName());
+ }
- public Map getAttributes() {
+ return (String) attrValue;
+ }
+
+
+ public Map getAttributes() {
return attributes;
}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPIdentityStore.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPIdentityStore.java
index 04e6d6190c..023d143ada 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPIdentityStore.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPIdentityStore.java
@@ -9,6 +9,8 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
+import java.util.Set;
+import java.util.TreeSet;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
@@ -389,10 +391,9 @@ public class LDAPIdentityStore implements IdentityStore {
while (ldapAttributes.hasMore()) {
Attribute ldapAttribute = ldapAttributes.next();
- Serializable ldapAttributeValue;
try {
- ldapAttributeValue = (Serializable) ldapAttribute.get();
+ ldapAttribute.get();
} catch (NoSuchElementException nsee) {
continue;
}
@@ -400,23 +401,31 @@ public class LDAPIdentityStore implements IdentityStore {
String ldapAttributeName = ldapAttribute.getID();
if (ldapAttributeName.toLowerCase().equals(getConfig().getUuidAttributeName().toLowerCase())) {
- ldapObject.setUuid(this.operationManager.decodeEntryUUID(ldapAttributeValue));
- } else if (ldapAttributeName.toLowerCase().equals(LDAPConstants.OBJECT_CLASS)) {
- List objectClasses = new LinkedList();
+ Object uuidValue = ldapAttribute.get();
+ ldapObject.setUuid(this.operationManager.decodeEntryUUID(uuidValue));
+ } else {
+ Set attrValues = new TreeSet();
NamingEnumeration> enumm = ldapAttribute.getAll();
while (enumm.hasMoreElements()) {
String objectClass = enumm.next().toString();
- objectClasses.add(objectClass);
- }
- ldapObject.setObjectClasses(objectClasses);
- } else {
- if (logger.isTraceEnabled()) {
- logger.tracef("Populating ldap attribute [%s] with value [%s] for DN [%s].", ldapAttributeName, ldapAttributeValue, entryDN);
+ attrValues.add(objectClass);
}
- ldapObject.setAttribute(ldapAttributeName, ldapAttributeValue);
- if (uppercasedReadOnlyAttrNames.contains(ldapAttributeName.toUpperCase())) {
- ldapObject.addReadOnlyAttributeName(ldapAttributeName);
+ if (ldapAttributeName.toLowerCase().equals(LDAPConstants.OBJECT_CLASS)) {
+ ldapObject.setObjectClasses(attrValues);
+ } else {
+ if (logger.isTraceEnabled()) {
+ logger.tracef("Populating ldap attribute [%s] with value [%s] for DN [%s].", ldapAttributeName, attrValues.toString(), entryDN);
+ }
+ if (attrValues.size() == 1) {
+ ldapObject.setAttribute(ldapAttributeName, attrValues.iterator().next());
+ } else {
+ ldapObject.setAttribute(ldapAttributeName, attrValues);
+ }
+
+ if (uppercasedReadOnlyAttrNames.contains(ldapAttributeName.toUpperCase())) {
+ ldapObject.addReadOnlyAttributeName(ldapAttributeName);
+ }
}
}
}
@@ -487,16 +496,16 @@ public class LDAPIdentityStore implements IdentityStore {
protected BasicAttributes extractAttributes(LDAPObject ldapObject, boolean isCreate) {
BasicAttributes entryAttributes = new BasicAttributes();
- for (Map.Entry attrEntry : ldapObject.getAttributes().entrySet()) {
+ for (Map.Entry attrEntry : ldapObject.getAttributes().entrySet()) {
String attrName = attrEntry.getKey();
- Serializable attrValue = attrEntry.getValue();
+ Object attrValue = attrEntry.getValue();
if (!ldapObject.getReadOnlyAttributeNames().contains(attrName) && (isCreate || !ldapObject.getRdnAttributeName().equals(attrName))) {
if (String.class.isInstance(attrValue)) {
entryAttributes.put(attrName, attrValue);
} else if (Collection.class.isInstance(attrValue)) {
BasicAttribute attr = new BasicAttribute(attrName);
- Collection valueCollection = (Collection) attr;
+ Collection valueCollection = (Collection) attrValue;
for (String val : valueCollection) {
attr.add(val);
}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/AbstractLDAPFederationMapper.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/AbstractLDAPFederationMapper.java
index 357262e6ec..6daa011692 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/AbstractLDAPFederationMapper.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/AbstractLDAPFederationMapper.java
@@ -1,9 +1,6 @@
package org.keycloak.federation.ldap.mappers;
-import org.keycloak.Config;
-import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.KeycloakSessionFactory;
-import org.keycloak.models.UserFederationMapper;
+import org.keycloak.models.UserFederationMapperModel;
/**
* @author Marek Posolda
@@ -15,18 +12,8 @@ public abstract class AbstractLDAPFederationMapper implements LDAPFederationMapp
}
- @Override
- public UserFederationMapper create(KeycloakSession session) {
- throw new RuntimeException("UNSUPPORTED METHOD");
- }
-
- @Override
- public void init(Config.Scope config) {
-
- }
-
- @Override
- public void postInit(KeycloakSessionFactory factory) {
-
+ protected boolean parseBooleanParameter(UserFederationMapperModel mapperModel, String paramName) {
+ String paramm = mapperModel.getConfig().get(paramName);
+ return Boolean.parseBoolean(paramm);
}
}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/AbstractLDAPFederationMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/AbstractLDAPFederationMapperFactory.java
new file mode 100644
index 0000000000..6b8f186d4c
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/AbstractLDAPFederationMapperFactory.java
@@ -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 Marek Posolda
+ */
+public abstract class AbstractLDAPFederationMapperFactory implements UserFederationMapperFactory {
+
+ @Override
+ public void init(Config.Scope config) {
+ }
+
+ @Override
+ public void postInit(KeycloakSessionFactory factory) {
+ }
+
+ @Override
+ public void close() {
+ }
+
+
+}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/FullNameLDAPFederationMapper.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/FullNameLDAPFederationMapper.java
index 483d825f9d..109b0b0775 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/FullNameLDAPFederationMapper.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/FullNameLDAPFederationMapper.java
@@ -11,7 +11,10 @@ import org.keycloak.federation.ldap.idm.query.Condition;
import org.keycloak.federation.ldap.idm.query.QueryParameter;
import org.keycloak.federation.ldap.idm.query.internal.EqualCondition;
import org.keycloak.federation.ldap.idm.query.internal.LDAPIdentityQuery;
+import org.keycloak.mappers.UserFederationMapper;
+import org.keycloak.models.KeycloakSession;
import org.keycloak.models.LDAPConstants;
+import org.keycloak.models.RealmModel;
import org.keycloak.models.UserFederationMapperModel;
import org.keycloak.models.UserFederationProvider;
import org.keycloak.models.UserModel;
@@ -28,24 +31,9 @@ public class FullNameLDAPFederationMapper extends AbstractLDAPFederationMapper {
public static final String READ_ONLY = "read.only";
@Override
- public String getHelpText() {
- return "Some help text - full name mapper - TODO";
- }
-
- @Override
- public List getConfigProperties() {
- return null;
- }
-
- @Override
- public String getId() {
- return "full-name-ldap-mapper";
- }
-
- @Override
- public void importUserFromLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapObject, UserModel user, boolean isCreate) {
+ public void onImportUserFromLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, UserModel user, RealmModel realm, boolean isCreate) {
String ldapFullNameAttrName = getLdapFullNameAttrName(mapperModel);
- String fullName = (String) ldapObject.getAttribute(ldapFullNameAttrName);
+ String fullName = ldapUser.getAttributeAsString(ldapFullNameAttrName);
fullName = fullName.trim();
if (fullName != null) {
int lastSpaceIndex = fullName.lastIndexOf(" ");
@@ -59,22 +47,22 @@ public class FullNameLDAPFederationMapper extends AbstractLDAPFederationMapper {
}
@Override
- public void registerUserToLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapObject, UserModel localUser) {
+ public void onRegisterUserToLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, UserModel localUser, RealmModel realm) {
String ldapFullNameAttrName = getLdapFullNameAttrName(mapperModel);
String fullName = getFullName(localUser.getFirstName(), localUser.getLastName());
- ldapObject.setAttribute(ldapFullNameAttrName, fullName);
+ ldapUser.setAttribute(ldapFullNameAttrName, fullName);
if (isReadOnly(mapperModel)) {
- ldapObject.addReadOnlyAttributeName(ldapFullNameAttrName);
+ ldapUser.addReadOnlyAttributeName(ldapFullNameAttrName);
}
}
@Override
- public UserModel proxy(final UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapObject, UserModel delegate) {
+ public UserModel proxy(final UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, UserModel delegate, RealmModel realm) {
if (ldapProvider.getEditMode() == UserFederationProvider.EditMode.WRITABLE && !isReadOnly(mapperModel)) {
- AbstractTxAwareLDAPUserModelDelegate txDelegate = new AbstractTxAwareLDAPUserModelDelegate(delegate, ldapProvider, ldapObject) {
+ TxAwareLDAPUserModelDelegate txDelegate = new TxAwareLDAPUserModelDelegate(delegate, ldapProvider, ldapUser) {
@Override
public void setFirstName(String firstName) {
@@ -97,7 +85,7 @@ public class FullNameLDAPFederationMapper extends AbstractLDAPFederationMapper {
ensureTransactionStarted();
String ldapFullNameAttrName = getLdapFullNameAttrName(mapperModel);
- ldapObject.setAttribute(ldapFullNameAttrName, fullName);
+ ldapUser.setAttribute(ldapFullNameAttrName, fullName);
}
};
@@ -127,10 +115,10 @@ public class FullNameLDAPFederationMapper extends AbstractLDAPFederationMapper {
lastNameCondition = (EqualCondition) condition;
query.getConditions().remove(condition);
} else if (param.getName().equals(LDAPConstants.GIVENNAME)) {
- // Some previous mapper already converted it
+ // Some previous mapper already converted it to LDAP name
firstNameCondition = (EqualCondition) condition;
} else if (param.getName().equals(LDAPConstants.SN)) {
- // Some previous mapper already converted it
+ // Some previous mapper already converted it to LDAP name
lastNameCondition = (EqualCondition) condition;
}
}
@@ -169,6 +157,6 @@ public class FullNameLDAPFederationMapper extends AbstractLDAPFederationMapper {
}
private boolean isReadOnly(UserFederationMapperModel mapperModel) {
- return LDAPUtils.parseBooleanParameter(mapperModel, READ_ONLY);
+ return parseBooleanParameter(mapperModel, READ_ONLY);
}
}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/FullNameLDAPFederationMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/FullNameLDAPFederationMapperFactory.java
new file mode 100644
index 0000000000..682034551f
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/FullNameLDAPFederationMapperFactory.java
@@ -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 Marek Posolda
+ */
+public class FullNameLDAPFederationMapperFactory extends AbstractLDAPFederationMapperFactory {
+
+ @Override
+ public String getHelpText() {
+ return "Some help text - full name mapper - TODO";
+ }
+
+ @Override
+ public List getConfigProperties() {
+ return null;
+ }
+
+ @Override
+ public String getId() {
+ return "full-name-ldap-mapper";
+ }
+
+ @Override
+ public UserFederationMapper create(KeycloakSession session) {
+ return new FullNameLDAPFederationMapper();
+ }
+}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/LDAPFederationMapper.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/LDAPFederationMapper.java
index de89e925b7..4676a6b198 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/LDAPFederationMapper.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/LDAPFederationMapper.java
@@ -3,7 +3,8 @@ package org.keycloak.federation.ldap.mappers;
import org.keycloak.federation.ldap.LDAPFederationProvider;
import org.keycloak.federation.ldap.idm.model.LDAPObject;
import org.keycloak.federation.ldap.idm.query.internal.LDAPIdentityQuery;
-import org.keycloak.models.UserFederationMapper;
+import org.keycloak.models.RealmModel;
+import org.keycloak.mappers.UserFederationMapper;
import org.keycloak.models.UserFederationMapperModel;
import org.keycloak.models.UserModel;
@@ -12,17 +13,50 @@ import org.keycloak.models.UserModel;
*/
public interface LDAPFederationMapper extends UserFederationMapper {
- // TODO: rename?
- // Called when importing user from federation provider to local keycloak DB. Flag "isCreate" means if we creating new user to Keycloak DB or just update existing user in Keycloak DB
- void importUserFromLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapObject, UserModel user, boolean isCreate);
- // TODO: rename to beforeRegister or something?
- // Called when register new user to federation provider
- void registerUserToLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapObject, UserModel localUser);
+ /**
+ * Called when importing user from LDAP to local keycloak DB.
+ *
+ * @param mapperModel
+ * @param ldapProvider
+ * @param ldapUser
+ * @param user
+ * @param realm
+ * @param isCreate true if we importing new user from LDAP. False if user already exists in Keycloak, but we are upgrading (syncing) it from LDAP
+ */
+ void onImportUserFromLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, UserModel user, RealmModel realm, boolean isCreate);
- // Called when invoke proxy on federation provider
- UserModel proxy(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapObject, UserModel delegate);
- // Called before any LDAPIdentityQuery is executed
+ /**
+ * Called when register new user to LDAP - just after user was created in Keycloak DB
+ *
+ * @param mapperModel
+ * @param ldapProvider
+ * @param ldapUser
+ * @param localUser
+ * @param realm
+ */
+ void onRegisterUserToLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, UserModel localUser, RealmModel realm);
+
+
+ /**
+ * Called when invoke proxy on LDAP federation provider
+ *
+ * @param mapperModel
+ * @param ldapProvider
+ * @param ldapUser
+ * @param delegate
+ * @param realm
+ * @return
+ */
+ UserModel proxy(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, UserModel delegate, RealmModel realm);
+
+
+ /**
+ * Called before LDAP Identity query for retrieve LDAP users was executed. It allows to change query somehow (add returning attributes from LDAP, change conditions etc)
+ *
+ * @param mapperModel
+ * @param query
+ */
void beforeLDAPQuery(UserFederationMapperModel mapperModel, LDAPIdentityQuery query);
}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/RoleLDAPFederationMapper.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/RoleLDAPFederationMapper.java
new file mode 100644
index 0000000000..47f288dba1
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/RoleLDAPFederationMapper.java
@@ -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 Marek Posolda
+ */
+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 rolesSyncedModels = new TreeSet();
+
+ @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 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 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 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 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 trimmed = new HashSet();
+ 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 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 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 getExistingMemberships(UserFederationMapperModel mapperModel, LDAPObject ldapRole) {
+ String memberAttrName = getMembershipLdapAttribute(mapperModel);
+ Set memberships = new TreeSet();
+ 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 exMemberships = (Collection) existingMemberships;
+ for (String membership : exMemberships) {
+ if (membership.trim().length() > 0) {
+ memberships.add(membership);
+ }
+ }
+ }
+ }
+ return memberships;
+ }
+
+ protected List 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 getLDAPRoleMappingsConverted(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, RoleContainerModel roleContainer) {
+ List ldapRoles = getLDAPRoleMappings(mapperModel, ldapProvider, ldapUser);
+
+ Set roles = new HashSet();
+ 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 getRealmRoleMappings() {
+ RoleContainerModel roleContainer = getTargetRoleContainer(mapperModel, realm);
+ if (roleContainer.equals(realm)) {
+ Set 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 modelRoleMappings = super.getRealmRoleMappings();
+ ldapRoleMappings.addAll(modelRoleMappings);
+ return ldapRoleMappings;
+ }
+ } else {
+ return super.getRealmRoleMappings();
+ }
+ }
+
+ @Override
+ public Set getClientRoleMappings(ClientModel client) {
+ RoleContainerModel roleContainer = getTargetRoleContainer(mapperModel, realm);
+ if (roleContainer.equals(client)) {
+ Set 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 modelRoleMappings = super.getClientRoleMappings(client);
+ ldapRoleMappings.addAll(modelRoleMappings);
+ return ldapRoleMappings;
+ }
+ } else {
+ return super.getClientRoleMappings(client);
+ }
+ }
+
+ @Override
+ public boolean hasRole(RoleModel role) {
+ Set 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 getRoleMappings() {
+ Set modelRoleMappings = super.getRoleMappings();
+
+ RoleContainerModel targetRoleContainer = getTargetRoleContainer(mapperModel, realm);
+ Set 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 modelRolesCopy = new HashSet(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
+ }
+}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/RoleLDAPFederationMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/RoleLDAPFederationMapperFactory.java
new file mode 100644
index 0000000000..321a0d5fa8
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/RoleLDAPFederationMapperFactory.java
@@ -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 Marek Posolda
+ */
+public class RoleLDAPFederationMapperFactory extends AbstractLDAPFederationMapperFactory {
+
+ @Override
+ public String getHelpText() {
+ return "Some help text - role mapper - TODO";
+ }
+
+ @Override
+ public List getConfigProperties() {
+ return null;
+ }
+
+ @Override
+ public String getId() {
+ return "role-ldap-mapper";
+ }
+
+ @Override
+ public UserFederationMapper create(KeycloakSession session) {
+ return new RoleLDAPFederationMapper();
+ }
+}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/AbstractTxAwareLDAPUserModelDelegate.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/TxAwareLDAPUserModelDelegate.java
similarity index 75%
rename from federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/AbstractTxAwareLDAPUserModelDelegate.java
rename to federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/TxAwareLDAPUserModelDelegate.java
index 4518f4bebf..ebdba95060 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/AbstractTxAwareLDAPUserModelDelegate.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/TxAwareLDAPUserModelDelegate.java
@@ -10,18 +10,18 @@ import org.keycloak.models.utils.UserModelDelegate;
/**
* @author Marek Posolda
*/
-public abstract class AbstractTxAwareLDAPUserModelDelegate extends UserModelDelegate {
+public abstract class TxAwareLDAPUserModelDelegate extends UserModelDelegate {
- public static final Logger logger = Logger.getLogger(AbstractTxAwareLDAPUserModelDelegate.class);
+ public static final Logger logger = Logger.getLogger(TxAwareLDAPUserModelDelegate.class);
protected LDAPFederationProvider provider;
- protected LDAPObject ldapObject;
+ protected LDAPObject ldapUser;
private final LDAPTransaction transaction;
- public AbstractTxAwareLDAPUserModelDelegate(UserModel delegate, LDAPFederationProvider provider, LDAPObject ldapObject) {
+ public TxAwareLDAPUserModelDelegate(UserModel delegate, LDAPFederationProvider provider, LDAPObject ldapUser) {
super(delegate);
this.provider = provider;
- this.ldapObject = ldapObject;
+ this.ldapUser = ldapUser;
this.transaction = findOrCreateTransaction();
}
@@ -30,20 +30,18 @@ public abstract class AbstractTxAwareLDAPUserModelDelegate extends UserModelDele
}
// Try to find transaction in any delegate. We want to enlist just single transaction per all delegates
- protected LDAPTransaction findOrCreateTransaction() {
+ private LDAPTransaction findOrCreateTransaction() {
UserModelDelegate delegate = this;
while (true) {
UserModel deleg = delegate.getDelegate();
if (!(deleg instanceof UserModelDelegate)) {
- // Existing transaction not available. Need to create new
return new LDAPTransaction();
} else {
delegate = (UserModelDelegate) deleg;
}
- // Check if it's transaction aware delegate
- if (delegate instanceof AbstractTxAwareLDAPUserModelDelegate) {
- AbstractTxAwareLDAPUserModelDelegate txDelegate = (AbstractTxAwareLDAPUserModelDelegate) delegate;
+ if (delegate instanceof TxAwareLDAPUserModelDelegate) {
+ TxAwareLDAPUserModelDelegate txDelegate = (TxAwareLDAPUserModelDelegate) delegate;
return txDelegate.getTransaction();
}
}
@@ -52,7 +50,7 @@ public abstract class AbstractTxAwareLDAPUserModelDelegate extends UserModelDele
protected void ensureTransactionStarted() {
if (transaction.state == TransactionState.NOT_STARTED) {
if (logger.isTraceEnabled()) {
- logger.trace("Starting and enlisting transaction for object " + ldapObject.getDn().toString());
+ logger.trace("Starting and enlisting transaction for object " + ldapUser.getDn().toString());
}
this.provider.getSession().getTransaction().enlistAfterCompletion(transaction);
@@ -81,10 +79,10 @@ public abstract class AbstractTxAwareLDAPUserModelDelegate extends UserModelDele
}
if (logger.isTraceEnabled()) {
- logger.trace("Transaction commit! Updating LDAP attributes for object " + ldapObject.getDn().toString() + ", attributes: " + ldapObject.getAttributes());
+ logger.trace("Transaction commit! Updating LDAP attributes for object " + ldapUser.getDn().toString() + ", attributes: " + ldapUser.getAttributes());
}
- provider.getLdapIdentityStore().update(ldapObject);
+ provider.getLdapIdentityStore().update(ldapUser);
state = TransactionState.FINISHED;
}
@@ -94,7 +92,7 @@ public abstract class AbstractTxAwareLDAPUserModelDelegate extends UserModelDele
throw new IllegalStateException("Transaction in illegal state for rollback: " + state);
}
- logger.warn("Transaction rollback! Ignoring LDAP updates for object " + ldapObject.getDn().toString());
+ logger.warn("Transaction rollback! Ignoring LDAP updates for object " + ldapUser.getDn().toString());
state = TransactionState.FINISHED;
}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserAttributeLDAPFederationMapper.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserAttributeLDAPFederationMapper.java
index de0b8349a0..c4c30291dd 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserAttributeLDAPFederationMapper.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserAttributeLDAPFederationMapper.java
@@ -11,6 +11,9 @@ import org.keycloak.federation.ldap.idm.model.LDAPObject;
import org.keycloak.federation.ldap.idm.query.Condition;
import org.keycloak.federation.ldap.idm.query.QueryParameter;
import org.keycloak.federation.ldap.idm.query.internal.LDAPIdentityQuery;
+import org.keycloak.mappers.UserFederationMapper;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
import org.keycloak.models.UserFederationMapperModel;
import org.keycloak.models.UserFederationProvider;
import org.keycloak.models.UserModel;
@@ -43,31 +46,15 @@ public class UserAttributeLDAPFederationMapper extends AbstractLDAPFederationMap
public static final String USER_MODEL_ATTRIBUTE = "user.model.attribute";
public static final String LDAP_ATTRIBUTE = "ldap.attribute";
-
- // TODO: Merge with fullname mapper
public static final String READ_ONLY = "read.only";
- @Override
- public String getHelpText() {
- return "Some help text TODO";
- }
@Override
- public List getConfigProperties() {
- return null;
- }
-
- @Override
- public String getId() {
- return "user-attribute-ldap-mapper";
- }
-
- @Override
- public void importUserFromLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapObject, UserModel user, boolean isCreate) {
+ public void onImportUserFromLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, UserModel user, RealmModel realm, boolean isCreate) {
String userModelAttrName = mapperModel.getConfig().get(USER_MODEL_ATTRIBUTE);
String ldapAttrName = mapperModel.getConfig().get(LDAP_ATTRIBUTE);
- Serializable ldapAttrValue = ldapObject.getAttribute(ldapAttrName);
+ Object ldapAttrValue = ldapUser.getAttribute(ldapAttrName);
if (ldapAttrValue != null) {
Property