KEYCLOAK-599 Added UserFederationMappers. Added UserAttributeLDAPFederationMapper

This commit is contained in:
mposolda 2015-05-13 08:41:55 +02:00
parent 61c35265a6
commit 1490f106f2
55 changed files with 1886 additions and 1985 deletions

View file

@ -113,7 +113,7 @@ public abstract class BasePropertiesFederationProvider implements UserFederation
* @return
*/
@Override
public boolean isValid(UserModel local) {
public boolean isValid(RealmModel realm, UserModel local) {
return properties.containsKey(local.getUsername());
}

View file

@ -25,8 +25,12 @@ public class ClasspathPropertiesFederationProvider extends BasePropertiesFederat
* @return
*/
@Override
public UserModel proxy(UserModel local) {
return new ReadonlyUserModelProxy(local);
public UserModel validateAndProxy(RealmModel realm, UserModel local) {
if (isValid(realm, local)) {
return new ReadonlyUserModelProxy(local);
} else {
return null;
}
}
/**

View file

@ -27,8 +27,12 @@ public class FilePropertiesFederationProvider extends BasePropertiesFederationPr
* @return
*/
@Override
public UserModel proxy(UserModel local) {
return new WritableUserModelProxy(local, this);
public UserModel validateAndProxy(RealmModel realm, UserModel local) {
if (isValid(realm, local)) {
return new WritableUserModelProxy(local, this);
} else {
return null;
}
}
/**
@ -65,7 +69,7 @@ public class FilePropertiesFederationProvider extends BasePropertiesFederationPr
properties.setProperty(user.getUsername(), "");
save();
}
return proxy(user);
return validateAndProxy(realm, user);
}
@Override

View file

@ -43,7 +43,11 @@ public class KerberosFederationProvider implements UserFederationProvider {
}
@Override
public UserModel proxy(UserModel local) {
public UserModel validateAndProxy(RealmModel realm, UserModel local) {
if (!isValid(realm, local)) {
return null;
}
if (kerberosConfig.getEditMode() == EditMode.READ_ONLY) {
return new ReadOnlyKerberosUserModelDelegate(local, this);
} else {
@ -102,7 +106,7 @@ public class KerberosFederationProvider implements UserFederationProvider {
}
@Override
public boolean isValid(UserModel local) {
public boolean isValid(RealmModel realm, UserModel local) {
// KerberosUsernamePasswordAuthenticator.isUserAvailable is an overhead, so avoid it for now
String kerberosPrincipal = local.getUsername() + "@" + kerberosConfig.getKerberosRealm();
@ -219,13 +223,16 @@ public class KerberosFederationProvider implements UserFederationProvider {
if (!model.getId().equals(user.getFederationLink())) {
logger.warn("User with username " + username + " already exists, but is not linked to provider [" + model.getDisplayName() + "]");
return null;
} else if (isValid(user)) {
return proxy(user);
} else {
logger.warn("User with username " + username + " already exists and is linked to provider [" + model.getDisplayName() +
"] but kerberos principal is not correct. Kerberos principal on user is: " + user.getAttribute(KERBEROS_PRINCIPAL));
logger.warn("Will re-create user");
session.userStorage().removeUser(realm, user);
UserModel proxied = validateAndProxy(realm, user);
if (proxied != null) {
return proxied;
} else {
logger.warn("User with username " + username + " already exists and is linked to provider [" + model.getDisplayName() +
"] but kerberos principal is not correct. Kerberos principal on user is: " + user.getAttribute(KERBEROS_PRINCIPAL));
logger.warn("Will re-create user");
session.userStorage().removeUser(realm, user);
}
}
}
@ -248,6 +255,6 @@ public class KerberosFederationProvider implements UserFederationProvider {
user.addRequiredAction(UserModel.RequiredAction.UPDATE_PROFILE);
}
return proxy(user);
return validateAndProxy(realm, user);
}
}

View file

@ -0,0 +1,165 @@
package org.keycloak.federation.ldap;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import javax.naming.directory.SearchControls;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.UserFederationProviderModel;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*
* TODO: init properties at startup instead of always compute them
*/
public class LDAPConfig {
private final Map<String, String> config;
public LDAPConfig(Map<String, String> config) {
this.config = config;
}
public String getConnectionUrl() {
return config.get(LDAPConstants.CONNECTION_URL);
}
public String getFactoryName() {
// hardcoded for now
return "com.sun.jndi.ldap.LdapCtxFactory";
}
public String getAuthType() {
// hardcoded for now
return "simple";
}
public String getSecurityProtocol() {
// hardcoded for now
return config.get(LDAPConstants.SECURITY_PROTOCOL);
}
public Collection<String> getUserDns() {
String value = config.get(LDAPConstants.USER_DNS);
if (value == null) {
return Collections.emptyList();
} else {
return Arrays.asList(value.split(LDAPConstants.CONFIG_DIVIDER));
}
}
public String getSingleUserDn() {
Collection<String> dns = getUserDns();
if (dns.size() == 0) {
throw new IllegalStateException("No user DN configured. User DNS value is " + config.get(LDAPConstants.USER_DNS));
}
return dns.iterator().next();
}
public Collection<String> getObjectClasses() {
String objClassesCfg = config.get(LDAPConstants.USER_OBJECT_CLASSES);
String objClassesStr = (objClassesCfg != null && objClassesCfg.length() > 0) ? objClassesCfg.trim() : "inetOrgPerson,organizationalPerson";
String[] objectClasses = objClassesStr.split(",");
// Trim them
Set<String> userObjClasses = new HashSet<String>();
for (int i=0 ; i<objectClasses.length ; i++) {
userObjClasses.add(objectClasses[i].trim());
}
return userObjClasses;
}
public String getBindDN() {
return config.get(LDAPConstants.BIND_DN);
}
public String getBindCredential() {
return config.get(LDAPConstants.BIND_CREDENTIAL);
}
public String getVendor() {
return config.get(LDAPConstants.VENDOR);
}
public boolean isActiveDirectory() {
String vendor = getVendor();
return vendor != null && vendor.equals(LDAPConstants.VENDOR_ACTIVE_DIRECTORY);
}
public String getConnectionPooling() {
return config.get(LDAPConstants.CONNECTION_POOLING);
}
public Properties getAdditionalConnectionProperties() {
// not supported for now
return null;
}
public int getSearchScope() {
String searchScope = config.get(LDAPConstants.SEARCH_SCOPE);
return searchScope == null ? SearchControls.SUBTREE_SCOPE : Integer.parseInt(searchScope);
}
public String getUuidAttributeName() {
String uuidAttrName = config.get(LDAPConstants.UUID_ATTRIBUTE_NAME);
if (uuidAttrName == null) {
// Differences of unique attribute among various vendors
String vendor = getVendor();
if (vendor != null) {
switch (vendor) {
case LDAPConstants.VENDOR_RHDS:
uuidAttrName = "nsuniqueid";
break;
case LDAPConstants.VENDOR_TIVOLI:
uuidAttrName = "uniqueidentifier";
break;
case LDAPConstants.VENDOR_NOVELL_EDIRECTORY:
uuidAttrName = "guid";
break;
case LDAPConstants.VENDOR_ACTIVE_DIRECTORY:
uuidAttrName = LDAPConstants.OBJECT_GUID;
}
}
if (uuidAttrName == null) {
uuidAttrName = LDAPConstants.ENTRY_UUID;
}
}
return uuidAttrName;
}
// TODO: Remove and use mapper instead
public boolean isUserAccountControlsAfterPasswordUpdate() {
String userAccountCtrls = config.get(LDAPConstants.USER_ACCOUNT_CONTROLS_AFTER_PASSWORD_UPDATE);
return userAccountCtrls==null ? false : Boolean.parseBoolean(userAccountCtrls);
}
public boolean isPagination() {
String pagination = config.get(LDAPConstants.PAGINATION);
return pagination==null ? false : Boolean.parseBoolean(pagination);
}
public String getUsernameLdapAttribute() {
String username = config.get(LDAPConstants.USERNAME_LDAP_ATTRIBUTE);
if (username == null) {
username = isActiveDirectory() ? LDAPConstants.CN : LDAPConstants.UID;
}
return username;
}
public String getRdnLdapAttribute() {
String rdn = config.get(LDAPConstants.RDN_LDAP_ATTRIBUTE);
if (rdn == null) {
rdn = getUsernameLdapAttribute();
}
return rdn;
}
}

View file

@ -3,11 +3,14 @@ package org.keycloak.federation.ldap;
import org.jboss.logging.Logger;
import org.keycloak.federation.kerberos.impl.KerberosUsernamePasswordAuthenticator;
import org.keycloak.federation.kerberos.impl.SPNEGOAuthenticator;
import org.keycloak.federation.ldap.idm.model.LDAPUser;
import org.keycloak.federation.ldap.idm.query.internal.IdentityQuery;
import org.keycloak.federation.ldap.idm.query.internal.IdentityQueryBuilder;
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.idm.store.ldap.LDAPIdentityStore;
import org.keycloak.federation.ldap.kerberos.LDAPProviderKerberosConfig;
import org.keycloak.federation.ldap.mappers.LDAPFederationMapper;
import org.keycloak.models.CredentialValidationOutput;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.LDAPConstants;
@ -16,12 +19,15 @@ 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.models.UserFederationMapperModel;
import org.keycloak.models.UserFederationProvider;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserFederationSyncResult;
import org.keycloak.models.UserModel;
import org.keycloak.constants.KerberosConstants;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
@ -78,17 +84,40 @@ public class LDAPFederationProvider implements UserFederationProvider {
return this.ldapIdentityStore;
}
public EditMode getEditMode() {
return editMode;
}
@Override
public UserModel proxy(UserModel local) {
switch (editMode) {
case READ_ONLY:
return new ReadonlyLDAPUserModelDelegate(local, this);
case WRITABLE:
return new WritableLDAPUserModelDelegate(local, this);
case UNSYNCED:
return new UnsyncedLDAPUserModelDelegate(local, this);
}
return local;
public UserModel validateAndProxy(RealmModel realm, UserModel local) {
LDAPObject ldapObject = loadAndValidateUser(realm, local);
if (ldapObject == null) {
return null;
}
return proxy(realm, local, ldapObject);
}
protected UserModel proxy(RealmModel realm, UserModel local, LDAPObject ldapObject) {
UserModel proxied = local;
switch (editMode) {
case READ_ONLY:
proxied = new ReadonlyLDAPUserModelDelegate(local, this);
break;
case WRITABLE:
proxied = new WritableLDAPUserModelDelegate(local, this, ldapObject);
break;
case UNSYNCED:
proxied = new UnsyncedLDAPUserModelDelegate(local, this);
}
List<UserFederationMapperModel> federationMappers = realm.getUserFederationMappers();
for (UserFederationMapperModel mapperModel : federationMappers) {
LDAPFederationMapper ldapMapper = getMapper(mapperModel);
proxied = ldapMapper.proxy(mapperModel, this, ldapObject, proxied);
}
return proxied;
}
@Override
@ -120,10 +149,11 @@ public class LDAPFederationProvider implements UserFederationProvider {
if (editMode == EditMode.READ_ONLY || editMode == EditMode.UNSYNCED) throw new IllegalStateException("Registration is not supported by this ldap server");
if (!synchronizeRegistrations()) throw new IllegalStateException("Registration is not supported by this ldap server");
LDAPUser ldapUser = LDAPUtils.addUser(this.ldapIdentityStore, user.getUsername(), user.getFirstName(), user.getLastName(), user.getEmail());
user.setAttribute(LDAPConstants.LDAP_ID, ldapUser.getId());
user.setAttribute(LDAPConstants.LDAP_ENTRY_DN, ldapUser.getEntryDN());
return proxy(user);
LDAPObject ldapObject = LDAPUtils.addUserToLDAP(this, realm, user);
user.setAttribute(LDAPConstants.LDAP_ID, ldapObject.getUuid());
user.setAttribute(LDAPConstants.LDAP_ENTRY_DN, ldapObject.getDn().toString());
return proxy(realm, user, ldapObject);
}
@Override
@ -133,16 +163,24 @@ public class LDAPFederationProvider implements UserFederationProvider {
return false;
}
return LDAPUtils.removeUser(this.ldapIdentityStore, user.getUsername());
LDAPObject ldapObject = loadAndValidateUser(realm, user);
if (ldapObject == null) {
logger.warnf("User '%s' can't be deleted from LDAP as it doesn't exist here", user.getUsername());
return false;
}
ldapIdentityStore.remove(ldapObject);
return true;
}
@Override
public List<UserModel> searchByAttributes(Map<String, String> attributes, RealmModel realm, int maxResults) {
List<UserModel> searchResults =new LinkedList<UserModel>();
Map<String, LDAPUser> ldapUsers = searchLDAP(attributes, maxResults);
for (LDAPUser ldapUser : ldapUsers.values()) {
if (session.userStorage().getUserByUsername(ldapUser.getLoginName(), realm) == null) {
List<LDAPObject> ldapUsers = searchLDAP(realm, attributes, maxResults);
for (LDAPObject ldapUser : ldapUsers) {
String ldapUsername = LDAPUtils.getUsername(ldapUser, this.ldapIdentityStore.getConfig());
if (session.userStorage().getUserByUsername(ldapUsername, realm) == null) {
UserModel imported = importUserFromLDAP(realm, ldapUser);
searchResults.add(imported);
}
@ -151,106 +189,120 @@ public class LDAPFederationProvider implements UserFederationProvider {
return searchResults;
}
protected Map<String, LDAPUser> searchLDAP(Map<String, String> attributes, int maxResults) {
protected List<LDAPObject> searchLDAP(RealmModel realm, Map<String, String> attributes, int maxResults) {
Map<String, LDAPUser> results = new HashMap<String, LDAPUser>();
List<LDAPObject> results = new ArrayList<LDAPObject>();
if (attributes.containsKey(USERNAME)) {
LDAPUser user = LDAPUtils.getUser(this.ldapIdentityStore, attributes.get(USERNAME));
LDAPObject user = loadLDAPUserByUsername(realm, attributes.get(USERNAME));
if (user != null) {
results.put(user.getLoginName(), user);
results.add(user);
}
}
if (attributes.containsKey(EMAIL)) {
LDAPUser user = queryByEmail(attributes.get(EMAIL));
LDAPObject user = queryByEmail(realm, attributes.get(EMAIL));
if (user != null) {
results.put(user.getLoginName(), user);
results.add(user);
}
}
if (attributes.containsKey(FIRST_NAME) || attributes.containsKey(LAST_NAME)) {
IdentityQueryBuilder queryBuilder = this.ldapIdentityStore.createQueryBuilder();
IdentityQuery<LDAPUser> query = queryBuilder.createIdentityQuery(LDAPUser.class);
LDAPIdentityQuery ldapQuery = LDAPUtils.createQueryForUserSearch(this, realm);
LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder();
// Mapper should replace parameter with correct LDAP mapped attributes
if (attributes.containsKey(FIRST_NAME)) {
query.where(queryBuilder.equal(LDAPUser.FIRST_NAME, attributes.get(FIRST_NAME)));
ldapQuery.where(conditionsBuilder.equal(new QueryParameter(FIRST_NAME), attributes.get(FIRST_NAME)));
}
if (attributes.containsKey(LAST_NAME)) {
query.where(queryBuilder.equal(LDAPUser.LAST_NAME, attributes.get(LAST_NAME)));
}
query.setLimit(maxResults);
List<LDAPUser> users = query.getResultList();
for (LDAPUser user : users) {
results.put(user.getLoginName(), user);
ldapQuery.where(conditionsBuilder.equal(new QueryParameter(LAST_NAME), attributes.get(LAST_NAME)));
}
List<LDAPObject> ldapObjects = ldapQuery.getResultList();
results.addAll(ldapObjects);
}
return results;
}
@Override
public boolean isValid(UserModel local) {
LDAPUser ldapUser = LDAPUtils.getUser(this.ldapIdentityStore, local.getUsername());
/**
* @param local
* @return ldapObject 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());
if (ldapUser == null) {
return false;
return null;
}
return ldapUser.getId().equals(local.getAttribute(LDAPConstants.LDAP_ID));
if (ldapUser.getUuid().equals(local.getAttribute(LDAPConstants.LDAP_ID))) {
return ldapUser;
} else {
logger.warnf("LDAP User invalid. ID doesn't match. ID from LDAP [%s], ID from local DB: [%s]", ldapUser.getUuid(), local.getAttribute(LDAPConstants.LDAP_ID));
return null;
}
}
@Override
public boolean isValid(RealmModel realm, UserModel local) {
return loadAndValidateUser(realm, local) != null;
}
@Override
public UserModel getUserByUsername(RealmModel realm, String username) {
LDAPUser ldapUser = LDAPUtils.getUser(this.ldapIdentityStore, username);
LDAPObject ldapUser = loadLDAPUserByUsername(realm, username);
if (ldapUser == null) {
return null;
}
// KEYCLOAK-808: Should we allow case-sensitivity to be configurable?
if (!username.equals(ldapUser.getLoginName())) {
logger.warnf("User found in LDAP but with different username. LDAP username: %s, Searched username: %s", username, ldapUser.getLoginName());
return null;
}
return importUserFromLDAP(realm, ldapUser);
}
protected UserModel importUserFromLDAP(RealmModel realm, LDAPUser ldapUser) {
String email = (ldapUser.getEmail() != null && ldapUser.getEmail().trim().length() > 0) ? ldapUser.getEmail() : null;
protected UserModel importUserFromLDAP(RealmModel realm, LDAPObject ldapUser) {
String ldapUsername = LDAPUtils.getUsername(ldapUser, ldapIdentityStore.getConfig());
if (ldapUser.getLoginName() == null) {
throw new ModelException("User returned from LDAP has null username! Check configuration of your LDAP mappings. ID of user from LDAP: " + ldapUser.getId());
if (ldapUsername == null) {
throw new ModelException("User returned from LDAP has null username! Check configuration of your LDAP mappings. Mapped username LDAP attribute: " +
ldapIdentityStore.getConfig().getUsernameLdapAttribute() + ", attributes from LDAP: " + ldapUser.getAttributes());
}
UserModel imported = session.userStorage().addUser(realm, ldapUser.getLoginName());
UserModel imported = session.userStorage().addUser(realm, ldapUsername);
imported.setEnabled(true);
imported.setEmail(email);
imported.setFirstName(ldapUser.getFirstName());
imported.setLastName(ldapUser.getLastName());
List<UserFederationMapperModel> federationMappers = realm.getUserFederationMappers();
for (UserFederationMapperModel mapperModel : federationMappers) {
LDAPFederationMapper ldapMapper = getMapper(mapperModel);
ldapMapper.importUserFromLDAP(mapperModel, this, ldapUser, imported, true);
}
String userDN = ldapUser.getDn().toString();
imported.setFederationLink(model.getId());
imported.setAttribute(LDAPConstants.LDAP_ID, ldapUser.getId());
imported.setAttribute(LDAPConstants.LDAP_ENTRY_DN, ldapUser.getEntryDN());
imported.setAttribute(LDAPConstants.LDAP_ID, ldapUser.getUuid());
imported.setAttribute(LDAPConstants.LDAP_ENTRY_DN, userDN);
logger.debugf("Imported new user from LDAP to Keycloak DB. Username: [%s], Email: [%s], LDAP_ID: [%s], LDAP Entry DN: [%s]", imported.getUsername(), imported.getEmail(),
ldapUser.getId(), ldapUser.getEntryDN());
return proxy(imported);
ldapUser.getUuid(), userDN);
return proxy(realm, imported, ldapUser);
}
protected LDAPUser queryByEmail(String email) {
return LDAPUtils.getUserByEmail(this.ldapIdentityStore, email);
protected LDAPObject queryByEmail(RealmModel realm, String email) {
LDAPIdentityQuery ldapQuery = LDAPUtils.createQueryForUserSearch(this, realm);
LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder();
// Mapper should replace "email" in parameter name with correct LDAP mapped attribute
Condition emailCondition = conditionsBuilder.equal(new QueryParameter(UserModel.EMAIL), email);
ldapQuery.where(emailCondition);
return ldapQuery.getFirstResult();
}
@Override
public UserModel getUserByEmail(RealmModel realm, String email) {
LDAPUser ldapUser = queryByEmail(email);
LDAPObject ldapUser = queryByEmail(realm, email);
if (ldapUser == null) {
return null;
}
// KEYCLOAK-808: Should we allow case-sensitivity to be configurable?
if (!email.equals(ldapUser.getEmail())) {
logger.warnf("User found in LDAP but with different email. LDAP email: %s, Searched email: %s", email, ldapUser.getEmail());
return null;
}
return importUserFromLDAP(realm, ldapUser);
}
@ -262,16 +314,18 @@ 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
}
public boolean validPassword(UserModel user, String password) {
public boolean validPassword(RealmModel realm, UserModel user, String password) {
if (kerberosConfig.isAllowKerberosAuthentication() && kerberosConfig.isUseKerberosForPasswordAuthentication()) {
// Use Kerberos JAAS (Krb5LoginModule)
KerberosUsernamePasswordAuthenticator authenticator = factory.createKerberosUsernamePasswordAuthenticator(kerberosConfig);
return authenticator.validUser(user.getUsername(), password);
} else {
// Use Naming LDAP API
return LDAPUtils.validatePassword(this.ldapIdentityStore, user, password);
LDAPObject ldapUser = loadAndValidateUser(realm, user);
return ldapIdentityStore.validatePassword(ldapUser, password);
}
}
@ -280,7 +334,7 @@ public class LDAPFederationProvider implements UserFederationProvider {
public boolean validCredentials(RealmModel realm, UserModel user, List<UserCredentialModel> input) {
for (UserCredentialModel cred : input) {
if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
return validPassword(user, cred.getValue());
return validPassword(realm, user, cred.getValue());
} else {
return false; // invalid cred type
}
@ -335,11 +389,11 @@ public class LDAPFederationProvider implements UserFederationProvider {
public void close() {
}
protected UserFederationSyncResult importLDAPUsers(RealmModel realm, List<LDAPUser> ldapUsers, UserFederationProviderModel fedModel) {
protected UserFederationSyncResult importLDAPUsers(RealmModel realm, List<LDAPObject> ldapUsers, UserFederationProviderModel fedModel) {
UserFederationSyncResult syncResult = new UserFederationSyncResult();
for (LDAPUser ldapUser : ldapUsers) {
String username = ldapUser.getLoginName();
for (LDAPObject ldapUser : ldapUsers) {
String username = LDAPUtils.getUsername(ldapUser, ldapIdentityStore.getConfig());
UserModel currentUser = session.userStorage().getUserByUsername(username, realm);
if (currentUser == null) {
@ -347,12 +401,15 @@ public class LDAPFederationProvider implements UserFederationProvider {
importUserFromLDAP(realm, ldapUser);
syncResult.increaseAdded();
} else {
if ((fedModel.getId().equals(currentUser.getFederationLink())) && (ldapUser.getId().equals(currentUser.getAttribute(LDAPConstants.LDAP_ID)))) {
if ((fedModel.getId().equals(currentUser.getFederationLink())) && (ldapUser.getUuid().equals(currentUser.getAttribute(LDAPConstants.LDAP_ID)))) {
// Update keycloak user
String email = (ldapUser.getEmail() != null && ldapUser.getEmail().trim().length() > 0) ? ldapUser.getEmail() : null;
currentUser.setEmail(email);
currentUser.setFirstName(ldapUser.getFirstName());
currentUser.setLastName(ldapUser.getLastName());
List<UserFederationMapperModel> federationMappers = realm.getUserFederationMappers();
for (UserFederationMapperModel mapperModel : federationMappers) {
LDAPFederationMapper ldapMapper = getMapper(mapperModel);
ldapMapper.importUserFromLDAP(mapperModel, this, ldapUser, currentUser, false);
}
logger.debugf("Updated user from LDAP: %s", currentUser.getUsername());
syncResult.increaseUpdated();
} else {
@ -378,13 +435,16 @@ public class LDAPFederationProvider implements UserFederationProvider {
if (!model.getId().equals(user.getFederationLink())) {
logger.warnf("User with username [%s] already exists, but is not linked to provider [%s]", username, model.getDisplayName());
return null;
} else if (isValid(user)) {
return proxy(user);
} else {
logger.warnf("User with username [%s] aready exists and is linked to provider [%s] but is not valid. Stale LDAP_ID on local user is: %s",
username, model.getDisplayName(), user.getAttribute(LDAPConstants.LDAP_ID));
logger.warn("Will re-create user");
session.userStorage().removeUser(realm, user);
LDAPObject ldapObject = loadAndValidateUser(realm, user);
if (ldapObject != null) {
return proxy(realm, user, ldapObject);
} else {
logger.warnf("User with username [%s] aready exists and is linked to provider [%s] but is not valid. Stale LDAP_ID on local user is: %s",
username, model.getDisplayName(), user.getAttribute(LDAPConstants.LDAP_ID));
logger.warn("Will re-create user");
session.userStorage().removeUser(realm, user);
}
}
}
@ -392,4 +452,37 @@ public class LDAPFederationProvider implements UserFederationProvider {
logger.debugf("Kerberos authenticated user [%s] not in Keycloak storage. Creating him", username);
return getUserByUsername(realm, username);
}
public LDAPObject loadLDAPUserByUsername(RealmModel realm, String username) {
LDAPIdentityQuery ldapQuery = LDAPUtils.createQueryForUserSearch(this, realm);
LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder();
String usernameMappedAttribute = this.ldapIdentityStore.getConfig().getUsernameLdapAttribute();
Condition usernameCondition = conditionsBuilder.equal(new QueryParameter(usernameMappedAttribute), username);
ldapQuery.where(usernameCondition);
LDAPObject ldapUser = ldapQuery.getFirstResult();
if (ldapUser == null) {
return null;
}
// KEYCLOAK-808: Should we allow case-sensitivity to be configurable?
String ldapUsername = LDAPUtils.getUsername(ldapUser, ldapIdentityStore.getConfig());
if (!username.equals(ldapUsername)) {
logger.warnf("User found in LDAP but with different username. LDAP username: %s, Searched username: %s", username, ldapUsername);
return null;
}
return ldapUser;
}
public LDAPFederationMapper getMapper(UserFederationMapperModel mapperModel) {
LDAPFederationMapper ldapMapper = (LDAPFederationMapper) getSession().getKeycloakSessionFactory()
.getProviderFactory(UserFederationMapper.class, mapperModel.getFederationMapperId());
if (ldapMapper == null) {
throw new ModelException("Can't find mapper type with ID: " + mapperModel.getFederationMapperId());
}
return ldapMapper;
}
}

View file

@ -6,11 +6,11 @@ import org.keycloak.federation.kerberos.CommonKerberosConfig;
import org.keycloak.federation.kerberos.impl.KerberosServerSubjectAuthenticator;
import org.keycloak.federation.kerberos.impl.KerberosUsernamePasswordAuthenticator;
import org.keycloak.federation.kerberos.impl.SPNEGOAuthenticator;
import org.keycloak.federation.ldap.idm.model.IdentityType;
import org.keycloak.federation.ldap.idm.model.LDAPUser;
import org.keycloak.federation.ldap.idm.model.LDAPObject;
import org.keycloak.federation.ldap.idm.query.Condition;
import org.keycloak.federation.ldap.idm.query.internal.IdentityQuery;
import org.keycloak.federation.ldap.idm.query.internal.IdentityQueryBuilder;
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.idm.store.ldap.LDAPIdentityStore;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
@ -76,11 +76,10 @@ public class LDAPFederationProviderFactory implements UserFederationProviderFact
}
@Override
public UserFederationSyncResult syncAllUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model) {
public UserFederationSyncResult syncAllUsers(KeycloakSessionFactory sessionFactory, final String realmId, final UserFederationProviderModel model) {
logger.infof("Sync all users from LDAP to local store: realm: %s, federation provider: %s", realmId, model.getDisplayName());
LDAPIdentityStore ldapIdentityStore = this.ldapStoreRegistry.getLdapStore(model);
IdentityQuery<LDAPUser> userQuery = ldapIdentityStore.createQueryBuilder().createIdentityQuery(LDAPUser.class);
LDAPIdentityQuery userQuery = createQuery(sessionFactory, realmId, model);
UserFederationSyncResult syncResult = syncImpl(sessionFactory, userQuery, realmId, model);
// TODO: Remove all existing keycloak users, which have federation links, but are not in LDAP. Perhaps don't check users, which were just added or updated during this sync?
@ -93,21 +92,21 @@ public class LDAPFederationProviderFactory implements UserFederationProviderFact
public UserFederationSyncResult syncChangedUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model, Date lastSync) {
logger.infof("Sync changed users from LDAP to local store: realm: %s, federation provider: %s, last sync time: " + lastSync, realmId, model.getDisplayName());
LDAPIdentityStore ldapIdentityStore = this.ldapStoreRegistry.getLdapStore(model);
// Sync newly created and updated users
IdentityQueryBuilder queryBuilder = ldapIdentityStore.createQueryBuilder();
Condition createCondition = queryBuilder.greaterThanOrEqualTo(IdentityType.CREATED_DATE, lastSync);
Condition modifyCondition = queryBuilder.greaterThanOrEqualTo(LDAPUtils.MODIFY_DATE, lastSync);
Condition orCondition = queryBuilder.orCondition(createCondition, modifyCondition);
IdentityQuery<LDAPUser> userQuery = queryBuilder.createIdentityQuery(LDAPUser.class).where(orCondition);
LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder();
Condition createCondition = conditionsBuilder.greaterThanOrEqualTo(new QueryParameter(LDAPConstants.CREATE_TIMESTAMP), lastSync);
Condition modifyCondition = conditionsBuilder.greaterThanOrEqualTo(new QueryParameter(LDAPConstants.MODIFY_TIMESTAMP), lastSync);
Condition orCondition = conditionsBuilder.orCondition(createCondition, modifyCondition);
LDAPIdentityQuery userQuery = createQuery(sessionFactory, realmId, model);
userQuery.where(orCondition);
UserFederationSyncResult result = syncImpl(sessionFactory, userQuery, realmId, model);
logger.infof("Sync changed users finished: %s", result.getStatus());
return result;
}
protected UserFederationSyncResult syncImpl(KeycloakSessionFactory sessionFactory, IdentityQuery<LDAPUser> userQuery, final String realmId, final UserFederationProviderModel fedModel) {
protected UserFederationSyncResult syncImpl(KeycloakSessionFactory sessionFactory, LDAPIdentityQuery userQuery, final String realmId, final UserFederationProviderModel fedModel) {
final UserFederationSyncResult syncResult = new UserFederationSyncResult();
@ -120,7 +119,7 @@ public class LDAPFederationProviderFactory implements UserFederationProviderFact
boolean nextPage = true;
while (nextPage) {
userQuery.setLimit(pageSize);
final List<LDAPUser> users = userQuery.getResultList();
final List<LDAPObject> users = userQuery.getResultList();
nextPage = userQuery.getPaginationContext() != null;
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
@ -135,7 +134,7 @@ public class LDAPFederationProviderFactory implements UserFederationProviderFact
}
} else {
// LDAP pagination not available. Do everything in single transaction
final List<LDAPUser> users = userQuery.getResultList();
final List<LDAPObject> users = userQuery.getResultList();
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
@Override
@ -150,7 +149,27 @@ public class LDAPFederationProviderFactory implements UserFederationProviderFact
return syncResult;
}
protected UserFederationSyncResult importLdapUsers(KeycloakSession session, String realmId, UserFederationProviderModel fedModel, List<LDAPUser> ldapUsers) {
private LDAPIdentityQuery createQuery(KeycloakSessionFactory sessionFactory, final String realmId, final UserFederationProviderModel model) {
class QueryHolder {
LDAPIdentityQuery query;
}
final QueryHolder queryHolder = new QueryHolder();
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
@Override
public void run(KeycloakSession session) {
LDAPFederationProvider ldapFedProvider = getInstance(session, model);
RealmModel realm = session.realms().getRealm(realmId);
queryHolder.query = LDAPUtils.createQueryForUserSearch(ldapFedProvider, realm);
}
});
return queryHolder.query;
}
protected UserFederationSyncResult importLdapUsers(KeycloakSession session, String realmId, UserFederationProviderModel fedModel, List<LDAPObject> ldapUsers) {
RealmModel realm = session.realms().getRealm(realmId);
LDAPFederationProvider ldapFedProvider = getInstance(session, fedModel);
return ldapFedProvider.importLDAPUsers(realm, ldapUsers, fedModel);

View file

@ -1,17 +1,11 @@
package org.keycloak.federation.ldap;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import org.jboss.logging.Logger;
import org.keycloak.federation.ldap.idm.model.LDAPUser;
import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStoreConfiguration;
import org.keycloak.federation.ldap.idm.store.ldap.LDAPMappingConfiguration;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.UserFederationProviderModel;
@ -51,10 +45,7 @@ public class LDAPIdentityStoreRegistry {
* @return PartitionManager instance based on LDAP store
*/
public static LDAPIdentityStore createLdapIdentityStore(Map<String,String> ldapConfig) {
Properties connectionProps = new Properties();
if (ldapConfig.containsKey(LDAPConstants.CONNECTION_POOLING)) {
connectionProps.put("com.sun.jndi.ldap.connect.pool", ldapConfig.get(LDAPConstants.CONNECTION_POOLING));
}
LDAPConfig cfg = new LDAPConfig(ldapConfig);
checkSystemProperty("com.sun.jndi.ldap.connect.pool.authentication", "none simple");
checkSystemProperty("com.sun.jndi.ldap.connect.pool.initsize", "1");
@ -64,11 +55,7 @@ public class LDAPIdentityStoreRegistry {
checkSystemProperty("com.sun.jndi.ldap.connect.pool.protocol", "plain");
checkSystemProperty("com.sun.jndi.ldap.connect.pool.debug", "off");
String vendor = ldapConfig.get(LDAPConstants.VENDOR);
boolean activeDirectory = vendor != null && vendor.equals(LDAPConstants.VENDOR_ACTIVE_DIRECTORY);
String ldapLoginNameMapping = ldapConfig.get(LDAPConstants.USERNAME_LDAP_ATTRIBUTE);
/*String ldapLoginNameMapping = ldapConfig.get(LDAPConstants.USERNAME_LDAP_ATTRIBUTE);
if (ldapLoginNameMapping == null) {
ldapLoginNameMapping = activeDirectory ? LDAPConstants.CN : LDAPConstants.UID;
}
@ -76,62 +63,16 @@ public class LDAPIdentityStoreRegistry {
String ldapFirstNameMapping = activeDirectory ? "givenName" : LDAPConstants.CN;
String createTimestampMapping = activeDirectory ? "whenCreated" : LDAPConstants.CREATE_TIMESTAMP;
String modifyTimestampMapping = activeDirectory ? "whenChanged" : LDAPConstants.MODIFY_TIMESTAMP;
String[] userObjectClasses = getUserObjectClasses(ldapConfig);
String[] userObjectClasses = getUserObjectClasses(ldapConfig); */
boolean pagination = ldapConfig.containsKey(LDAPConstants.PAGINATION) ? Boolean.parseBoolean(ldapConfig.get(LDAPConstants.PAGINATION)) : false;
boolean userAccountControlsAfterPasswordUpdate = ldapConfig.containsKey(LDAPConstants.USER_ACCOUNT_CONTROLS_AFTER_PASSWORD_UPDATE) ?
Boolean.parseBoolean(ldapConfig.get(LDAPConstants.USER_ACCOUNT_CONTROLS_AFTER_PASSWORD_UPDATE)) : false;
// Differences of unique attribute among various vendors
String uniqueIdentifierAttributeName = LDAPConstants.ENTRY_UUID;
if (vendor != null) {
switch (vendor) {
case LDAPConstants.VENDOR_RHDS:
uniqueIdentifierAttributeName = "nsuniqueid";
break;
case LDAPConstants.VENDOR_TIVOLI:
uniqueIdentifierAttributeName = "uniqueidentifier";
break;
case LDAPConstants.VENDOR_NOVELL_EDIRECTORY:
uniqueIdentifierAttributeName = "guid";
break;
case LDAPConstants.VENDOR_ACTIVE_DIRECTORY:
uniqueIdentifierAttributeName = LDAPConstants.OBJECT_GUID;
}
}
LDAPIdentityStoreConfiguration ldapStoreConfig = new LDAPIdentityStoreConfiguration()
.setConnectionProperties(connectionProps)
.setBaseDN(ldapConfig.get(LDAPConstants.BASE_DN))
.setBindDN(ldapConfig.get(LDAPConstants.BIND_DN))
.setBindCredential(ldapConfig.get(LDAPConstants.BIND_CREDENTIAL))
.setLdapURL(ldapConfig.get(LDAPConstants.CONNECTION_URL))
.setActiveDirectory(activeDirectory)
.setPagination(pagination)
.setUniqueIdentifierAttributeName(uniqueIdentifierAttributeName)
.setFactoryName("com.sun.jndi.ldap.LdapCtxFactory")
.setAuthType("simple")
.setUserAccountControlsAfterPasswordUpdate(userAccountControlsAfterPasswordUpdate);
LDAPMappingConfiguration ldapUserMappingConfig = ldapStoreConfig
.mappingConfig(LDAPUser.class)
.setBaseDN(ldapConfig.get(LDAPConstants.USER_DN_SUFFIX))
.setObjectClasses(new HashSet<String>(Arrays.asList(userObjectClasses)))
.setIdPropertyName("loginName")
.addAttributeMapping("loginName", ldapLoginNameMapping)
.addAttributeMapping("firstName", ldapFirstNameMapping)
.addAttributeMapping("lastName", LDAPConstants.SN)
.addAttributeMapping("email", LDAPConstants.EMAIL)
.addReadOnlyAttributeMapping("createdDate", createTimestampMapping)
.addReadOnlyAttributeMapping("modifyDate", modifyTimestampMapping);
if (activeDirectory && ldapLoginNameMapping.equals("sAMAccountName")) {
ldapUserMappingConfig.setBindingPropertyName("fullName");
/* if (activeDirectory && ldapLoginNameMapping.equals("sAMAccountName")) {
ldapUserMappingConfig.setBindingDnPropertyName("fullName");
ldapUserMappingConfig.addAttributeMapping("fullName", LDAPConstants.CN);
logger.infof("Using 'cn' attribute for DN of user and 'sAMAccountName' for username");
}
} */
return new LDAPIdentityStore(ldapStoreConfig);
return new LDAPIdentityStore(cfg);
}
private static void checkSystemProperty(String name, String defaultValue) {
@ -141,19 +82,19 @@ public class LDAPIdentityStoreRegistry {
}
// Parse array of strings like [ "inetOrgPerson", "organizationalPerson" ] from the string like: "inetOrgPerson, organizationalPerson"
private static String[] getUserObjectClasses(Map<String,String> ldapConfig) {
/*private static String[] getUserObjectClasses(Map<String,String> ldapConfig) {
String objClassesCfg = ldapConfig.get(LDAPConstants.USER_OBJECT_CLASSES);
String objClassesStr = (objClassesCfg != null && objClassesCfg.length() > 0) ? objClassesCfg.trim() : "inetOrgPerson, organizationalPerson";
String[] objectClasses = objClassesStr.split(",");
String[] addObjectClasses = objClassesStr.split(",");
// Trim them
String[] userObjectClasses = new String[objectClasses.length];
for (int i=0 ; i<objectClasses.length ; i++) {
userObjectClasses[i] = objectClasses[i].trim();
String[] userObjectClasses = new String[addObjectClasses.length];
for (int i=0 ; i<addObjectClasses.length ; i++) {
userObjectClasses[i] = addObjectClasses[i].trim();
}
return userObjectClasses;
}
} */
private class LDAPIdentityStoreContext {

View file

@ -1,18 +1,18 @@
package org.keycloak.federation.ldap;
import org.keycloak.federation.ldap.idm.model.Attribute;
import org.keycloak.federation.ldap.idm.model.LDAPUser;
import org.keycloak.federation.ldap.idm.query.AttributeParameter;
import org.keycloak.federation.ldap.idm.query.QueryParameter;
import org.keycloak.federation.ldap.idm.query.internal.IdentityQuery;
import org.keycloak.federation.ldap.idm.query.internal.IdentityQueryBuilder;
import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.UserModel;
import java.util.List;
import org.keycloak.federation.ldap.idm.model.LDAPDn;
import org.keycloak.federation.ldap.idm.model.LDAPObject;
import org.keycloak.federation.ldap.idm.query.internal.LDAPIdentityQuery;
import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
import org.keycloak.federation.ldap.mappers.LDAPFederationMapper;
import org.keycloak.models.ModelException;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserFederationMapper;
import org.keycloak.models.UserFederationMapperModel;
import org.keycloak.models.UserModel;
/**
* Allow to directly call some operations against LDAPIdentityStore.
* TODO: Is this class still needed?
@ -21,26 +21,32 @@ import java.util.List;
*/
public class LDAPUtils {
public static QueryParameter MODIFY_DATE = new AttributeParameter("modifyDate");
/**
* @param ldapProvider
* @param realm
* @param user
* @return newly created LDAPObject with all the attributes, uuid and DN properly set
*/
public static LDAPObject addUserToLDAP(LDAPFederationProvider ldapProvider, RealmModel realm, UserModel user) {
LDAPObject ldapObject = new LDAPObject();
public static LDAPUser addUser(LDAPIdentityStore ldapIdentityStore, String username, String firstName, String lastName, String email) {
if (getUser(ldapIdentityStore, username) != null) {
throw new ModelDuplicateException("User with same username already exists");
}
if (getUserByEmail(ldapIdentityStore, email) != null) {
throw new ModelDuplicateException("User with same email already exists");
LDAPIdentityStore ldapStore = ldapProvider.getLdapIdentityStore();
LDAPConfig ldapConfig = ldapStore.getConfig();
ldapObject.setRdnAttributeName(ldapConfig.getRdnLdapAttribute());
ldapObject.setObjectClasses(ldapConfig.getObjectClasses());
List<UserFederationMapperModel> federationMappers = realm.getUserFederationMappers();
for (UserFederationMapperModel mapperModel : federationMappers) {
LDAPFederationMapper ldapMapper = ldapProvider.getMapper(mapperModel);
ldapMapper.registerUserToLDAP(mapperModel, ldapProvider, ldapObject, user);
}
LDAPUser ldapUser = new LDAPUser(username);
ldapUser.setFirstName(firstName);
ldapUser.setLastName(lastName);
ldapUser.setEmail(email);
ldapUser.setAttribute(new Attribute<String>("fullName", getFullName(username, firstName, lastName)));
ldapIdentityStore.add(ldapUser);
return ldapUser;
LDAPUtils.computeAndSetDn(ldapConfig, ldapObject);
ldapStore.add(ldapObject);
return ldapObject;
}
public static LDAPUser updateUser(LDAPIdentityStore ldapIdentityStore, String username, String firstName, String lastName, String email) {
/*public static LDAPUser updateUser(LDAPIdentityStore ldapIdentityStore, String username, String firstName, String lastName, String email) {
LDAPUser ldapUser = getUser(ldapIdentityStore, username);
ldapUser.setFirstName(firstName);
ldapUser.setLastName(lastName);
@ -86,7 +92,7 @@ public class LDAPUtils {
public static LDAPUser getUserByEmail(LDAPIdentityStore ldapIdentityStore, String email) {
IdentityQueryBuilder queryBuilder = ldapIdentityStore.createQueryBuilder();
IdentityQuery<LDAPUser> query = queryBuilder.createIdentityQuery(LDAPUser.class)
LDAPIdentityQuery<LDAPUser> query = queryBuilder.createIdentityQuery(LDAPUser.class)
.where(queryBuilder.equal(LDAPUser.EMAIL, email));
List<LDAPUser> users = query.getResultList();
@ -106,18 +112,34 @@ public class LDAPUtils {
}
ldapIdentityStore.remove(ldapUser);
return true;
}
} */
public static void removeAllUsers(LDAPIdentityStore ldapIdentityStore) {
List<LDAPUser> allUsers = getAllUsers(ldapIdentityStore);
public static void removeAllUsers(LDAPFederationProvider ldapProvider, RealmModel realm) {
LDAPIdentityStore ldapStore = ldapProvider.getLdapIdentityStore();
LDAPIdentityQuery ldapQuery = LDAPUtils.createQueryForUserSearch(ldapProvider, realm);
List<LDAPObject> allUsers = ldapQuery.getResultList();
for (LDAPUser user : allUsers) {
ldapIdentityStore.remove(user);
for (LDAPObject ldapUser : allUsers) {
ldapStore.remove(ldapUser);
}
}
public static LDAPIdentityQuery createQueryForUserSearch(LDAPFederationProvider ldapProvider, RealmModel realm) {
LDAPIdentityQuery ldapQuery = new LDAPIdentityQuery(ldapProvider);
LDAPConfig config = ldapProvider.getLdapIdentityStore().getConfig();
ldapQuery.setSearchScope(config.getSearchScope());
ldapQuery.addSearchDns(config.getUserDns());
ldapQuery.addObjectClasses(config.getObjectClasses());
List<UserFederationMapperModel> mapperModels = realm.getUserFederationMappers();
ldapQuery.addMappers(mapperModels);
return ldapQuery;
}
/*
public static List<LDAPUser> getAllUsers(LDAPIdentityStore ldapIdentityStore) {
IdentityQuery<LDAPUser> userQuery = ldapIdentityStore.createQueryBuilder().createIdentityQuery(LDAPUser.class);
LDAPIdentityQuery<LDAPUser> userQuery = ldapIdentityStore.createQueryBuilder().createIdentityQuery(LDAPUser.class);
return userQuery.getResultList();
}
@ -138,5 +160,23 @@ public class LDAPUtils {
}
return fullName;
} */
// ldapObject has filled attributes, but doesn't have filled
public static void computeAndSetDn(LDAPConfig config, LDAPObject ldapObject) {
String rdnLdapAttrName = config.getRdnLdapAttribute();
String rdnLdapAttrValue = (String) ldapObject.getAttribute(rdnLdapAttrName);
if (rdnLdapAttrValue == null) {
throw new ModelException("RDN Attribute [" + rdnLdapAttrName + "] is not filled. Filled attributes: " + ldapObject.getAttributes());
}
LDAPDn dn = LDAPDn.fromString(config.getSingleUserDn());
dn.addToHead(rdnLdapAttrName, rdnLdapAttrValue);
ldapObject.setDn(dn);
}
public static String getUsername(LDAPObject ldapUser, LDAPConfig config) {
String usernameAttr = config.getUsernameLdapAttribute();
return (String) ldapUser.getAttribute(usernameAttr);
}
}

View file

@ -1,7 +1,7 @@
package org.keycloak.federation.ldap;
import org.jboss.logging.Logger;
import org.keycloak.federation.ldap.idm.model.LDAPUser;
import org.keycloak.federation.ldap.idm.model.LDAPObject;
import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel;
@ -15,52 +15,12 @@ public class WritableLDAPUserModelDelegate extends UserModelDelegate implements
private static final Logger logger = Logger.getLogger(WritableLDAPUserModelDelegate.class);
protected LDAPFederationProvider provider;
protected LDAPObject ldapObject;
public WritableLDAPUserModelDelegate(UserModel delegate, LDAPFederationProvider provider) {
public WritableLDAPUserModelDelegate(UserModel delegate, LDAPFederationProvider provider, LDAPObject ldapObject) {
super(delegate);
this.provider = provider;
}
@Override
public void setUsername(String username) {
LDAPIdentityStore ldapIdentityStore = provider.getLdapIdentityStore();
LDAPUser ldapUser = LDAPUtils.getUser(ldapIdentityStore, delegate.getUsername());
if (ldapUser == null) {
throw new IllegalStateException("User not found in LDAP storage!");
}
ldapUser.setLoginName(username);
ldapIdentityStore.update(ldapUser);
delegate.setUsername(username);
}
@Override
public void setLastName(String lastName) {
LDAPIdentityStore ldapIdentityStore = provider.getLdapIdentityStore();
LDAPUser ldapUser = LDAPUtils.getUser(ldapIdentityStore, delegate.getUsername());
if (ldapUser == null) {
throw new IllegalStateException("User not found in LDAP storage!");
}
ldapUser.setLastName(lastName);
ldapIdentityStore.update(ldapUser);
delegate.setLastName(lastName);
}
@Override
public void setFirstName(String first) {
LDAPIdentityStore ldapIdentityStore = provider.getLdapIdentityStore();
LDAPUser ldapUser = LDAPUtils.getUser(ldapIdentityStore, delegate.getUsername());
if (ldapUser == null) {
throw new IllegalStateException("User not found in LDAP storage!");
}
ldapUser.setFirstName(first);
ldapIdentityStore.update(ldapUser);
delegate.setFirstName(first);
this.ldapObject = ldapObject;
}
@Override
@ -70,31 +30,13 @@ public class WritableLDAPUserModelDelegate extends UserModelDelegate implements
return;
}
LDAPIdentityStore ldapIdentityStore = provider.getLdapIdentityStore();
LDAPUser ldapUser = LDAPUtils.getUser(ldapIdentityStore, delegate.getUsername());
if (ldapUser == null) {
throw new IllegalStateException("User " + delegate.getUsername() + " not found in LDAP storage!");
}
if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
LDAPUtils.updatePassword(ldapIdentityStore, delegate, cred.getValue());
LDAPIdentityStore ldapIdentityStore = provider.getLdapIdentityStore();
String password = cred.getValue();
ldapIdentityStore.updatePassword(ldapObject, password);
} else {
logger.warnf("Don't know how to update credential of type [%s] for user [%s]", cred.getType(), delegate.getUsername());
}
}
@Override
public void setEmail(String email) {
LDAPIdentityStore ldapIdentityStore = provider.getLdapIdentityStore();
LDAPUser ldapUser = LDAPUtils.getUser(ldapIdentityStore, delegate.getUsername());
if (ldapUser == null) {
throw new IllegalStateException("User not found in LDAP storage!");
}
ldapUser.setEmail(email);
ldapIdentityStore.update(ldapUser);
delegate.setEmail(email);
}
}

View file

@ -1,85 +0,0 @@
package org.keycloak.federation.ldap.idm.model;
import java.io.Serializable;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import static java.util.Collections.unmodifiableCollection;
import static java.util.Collections.unmodifiableMap;
/**
* Abstract base class for all AttributedType implementations
*
* @author Shane Bryzak
*
*/
public abstract class AbstractAttributedType implements AttributedType {
private static final long serialVersionUID = -6118293036241099199L;
private String id;
private String entryDN;
private Map<String, Attribute<? extends Serializable>> attributes =
new HashMap<String, Attribute<? extends Serializable>>();
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getEntryDN() {
return entryDN;
}
public void setEntryDN(String entryDN) {
this.entryDN = entryDN;
}
public void setAttribute(Attribute<? extends Serializable> attribute) {
attributes.put(attribute.getName(), attribute);
}
public void removeAttribute(String name) {
attributes.remove(name);
}
@SuppressWarnings("unchecked")
public <T extends Serializable> Attribute<T> getAttribute(String name) {
return (Attribute<T>) attributes.get(name);
}
public Collection<Attribute<? extends Serializable>> getAttributes() {
return unmodifiableCollection(attributes.values());
}
public Map<String,Attribute<? extends Serializable>> getAttributesMap() {
return unmodifiableMap(attributes);
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (!getClass().isInstance(obj)) {
return false;
}
AttributedType other = (AttributedType) obj;
return getId() != null && other.getId() != null && getId().equals(other.getId());
}
@Override
public int hashCode() {
int result = getId() != null ? getId().hashCode() : 0;
result = 31 * result + (getId() != null ? getId().hashCode() : 0);
return result;
}
}

View file

@ -1,70 +0,0 @@
package org.keycloak.federation.ldap.idm.model;
import java.util.Date;
/**
* Abstract base class for IdentityType implementations
*
* @author Shane Bryzak
*/
public abstract class AbstractIdentityType extends AbstractAttributedType implements IdentityType {
private static final long serialVersionUID = 2843998332737143820L;
private boolean enabled = true;
private Date createdDate = new Date();
private Date expirationDate = null;
public boolean isEnabled() {
return this.enabled;
}
@Override
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
@Override
@AttributeProperty
public Date getExpirationDate() {
return this.expirationDate;
}
@Override
public void setExpirationDate(Date expirationDate) {
this.expirationDate = expirationDate;
}
@Override
@AttributeProperty
public Date getCreatedDate() {
return this.createdDate;
}
@Override
public void setCreatedDate(Date createdDate) {
this.createdDate = createdDate;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (!getClass().isInstance(obj)) {
return false;
}
IdentityType other = (IdentityType) obj;
return (getId() != null && other.getId() != null)
&& (getId().equals(other.getId()));
}
@Override
public int hashCode() {
return super.hashCode();
}
}

View file

@ -1,80 +0,0 @@
package org.keycloak.federation.ldap.idm.model;
import java.io.Serializable;
/**
* Represents an attribute value, a type of metadata that can be associated with an IdentityType
*
* @author Shane Bryzak
*
* @param <T>
*/
public class Attribute<T extends Serializable> implements Serializable {
private static final long serialVersionUID = 237211288303510728L;
/**
* The name of the attribute
*/
private String name;
/**
* The attribute value.
*/
private T value;
/**
* Indicates whether this Attribute has a read-only value
*/
private boolean readOnly = false;
/**
* Indicates whether the Attribute value has been loaded
*/
private boolean loaded = false;
public Attribute(String name, T value) {
this.name = name;
this.value = value;
}
public Attribute(String name, T value, boolean readOnly) {
this(name, value);
this.readOnly = readOnly;
}
public String getName() {
return name;
}
public T getValue() {
return value;
}
public boolean isReadOnly() {
return readOnly;
}
public boolean isLoaded() {
return loaded;
}
public void setLoaded(boolean value) {
this.loaded = value;
}
/**
* Sets the value for this attribute. If the Attribute value is readOnly, a RuntimeException is thrown.
*
* @param value
*/
public void setValue(T value) {
if (readOnly) {
throw new RuntimeException("Error setting Attribute value [" + name + " ] - value is read only.");
}
this.value = value;
}
}

View file

@ -1,31 +0,0 @@
package org.keycloak.federation.ldap.idm.model;
import java.lang.annotation.Documented;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Marks a property of an IdentityType, Partition or Relationship as being an attribute of that
* IdentityType, Partition or Relationship.
*
* @author Shane Bryzak
*/
@Target({METHOD, FIELD})
@Documented
@Retention(RUNTIME)
@Inherited
public @interface AttributeProperty {
/**
* <p>Managed properties are stored as ad-hoc attributes and mapped from and to a specific property of a type.</p>
*
* @return
*/
boolean managed() default false;
}

View file

@ -1,75 +0,0 @@
package org.keycloak.federation.ldap.idm.model;
import java.io.Serializable;
import java.util.Collection;
import org.keycloak.federation.ldap.idm.query.AttributeParameter;
import org.keycloak.federation.ldap.idm.query.QueryParameter;
/**
*
* @author Shane Bryzak
*
*/
public interface AttributedType extends Serializable {
/**
* A query parameter used to set the id value.
*/
QueryParameter ID = new AttributeParameter("id");
/**
* Returns the unique identifier for this instance
* @return
*/
String getId();
/**
* Sets the unique identifier for this instance
* @return
*/
void setId(String id);
/**
* Set the specified attribute. This operation will overwrite any previous value.
*
* @param attribute to be set
*/
void setAttribute(Attribute<? extends Serializable> attribute);
/**
* Remove the attribute with given name
*
* @param name of attribute
*/
void removeAttribute(String name);
// LDAP specific stuff
void setEntryDN(String entryDN);
String getEntryDN();
/**
* Return the attribute value with the specified name
*
* @param name of attribute
* @return attribute value or null if attribute with given name doesn't exist. If given attribute has many values method
* will return first one
*/
<T extends Serializable> Attribute<T> getAttribute(String name);
/**
* Returns a Map containing all attribute values for this IdentityType instance.
*
* @return map of attribute names and their values
*/
Collection<Attribute<? extends Serializable>> getAttributes();
public final class QUERY_ATTRIBUTE {
public static AttributeParameter byName(String name) {
return new AttributeParameter(name);
}
}
}

View file

@ -1,100 +0,0 @@
package org.keycloak.federation.ldap.idm.model;
import java.util.Date;
import org.keycloak.federation.ldap.idm.query.AttributeParameter;
import org.keycloak.federation.ldap.idm.query.QueryParameter;
/**
* This interface is the base for all identity model objects. It declares a number of
* properties that must be supported by all identity types, in addition to defining the API
* for identity attribute management.
*
* @author Shane Bryzak
*/
public interface IdentityType extends AttributedType {
/**
* A query parameter used to set the enabled value.
*/
QueryParameter ENABLED = new AttributeParameter("enabled");
/**
* A query parameter used to set the createdDate value
*/
QueryParameter CREATED_DATE = new AttributeParameter("createdDate");
/**
* A query parameter used to set the created after date
*/
QueryParameter CREATED_AFTER = new AttributeParameter("createdDate");
/**
* A query parameter used to set the modified after date
*/
QueryParameter MODIFIED_AFTER = new AttributeParameter("modifyDate");
/**
* A query parameter used to set the created before date
*/
QueryParameter CREATED_BEFORE = new AttributeParameter("createdDate");
/**
* A query parameter used to set the expiryDate value
*/
QueryParameter EXPIRY_DATE = new AttributeParameter("expirationDate");
/**
* A query parameter used to set the expiration after date
*/
QueryParameter EXPIRY_AFTER = new AttributeParameter("expirationDate");
/**
* A query parameter used to set the expiration before date
*/
QueryParameter EXPIRY_BEFORE = new AttributeParameter("expirationDate");
/**
* Indicates the current enabled status of this IdentityType.
*
* @return A boolean value indicating whether this IdentityType is enabled.
*/
boolean isEnabled();
/**
* <p>Sets the current enabled status of this {@link IdentityType}.</p>
*
* @param enabled
*/
void setEnabled(boolean enabled);
/**
* Returns the date that this IdentityType instance was created.
*
* @return Date value representing the creation date
*/
Date getCreatedDate();
/**
* <p>Sets the date that this {@link IdentityType} was created.</p>
*
* @param createdDate
*/
void setCreatedDate(Date createdDate);
/**
* Returns the date that this IdentityType expires, or null if there is no expiry date.
*
* @return
*/
Date getExpirationDate();
/**
* <p>Sets the date that this {@link IdentityType} expires.</p>
*
* @param expirationDate
*/
void setExpirationDate(Date expirationDate);
}

View file

@ -0,0 +1,97 @@
package org.keycloak.federation.ldap.idm.model;
import java.util.LinkedList;
import java.util.List;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class LDAPDn {
private final List<Entry> entries = new LinkedList<Entry>();
public static LDAPDn fromString(String dnString) {
LDAPDn dn = new LDAPDn();
String[] rdns = dnString.split(",");
for (String entryStr : rdns) {
String[] rdn = entryStr.split("=");
dn.addToBottom(rdn[0].trim(), rdn[1].trim());
}
return dn;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
boolean first = true;
for (Entry rdn : entries) {
if (first) {
first = false;
} else {
builder.append(",");
}
builder.append(rdn.attrName).append("=").append(rdn.attrValue);
}
return builder.toString();
}
/**
* @return string like "uid=joe" from the DN like "uid=joe,dc=something,dc=org"
*/
public String getFirstRdn() {
Entry firstEntry = entries.get(0);
return firstEntry.attrName + "=" + firstEntry.attrValue;
}
/**
* @return string attribute name like "uid" from the DN like "uid=joe,dc=something,dc=org"
*/
public String getFirstRdnAttrName() {
Entry firstEntry = entries.get(0);
return firstEntry.attrName;
}
/**
*
* @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();
}
public void addToHead(String rdnName, String rdnValue) {
entries.add(0, new Entry(rdnName, rdnValue));
}
public void addToBottom(String rdnName, String rdnValue) {
entries.add(new Entry(rdnName, rdnValue));
}
private static class Entry {
private final String attrName;
private final String attrValue;
private Entry(String attrName, String attrValue) {
this.attrName = attrName;
this.attrValue = attrValue;
}
}
}

View file

@ -0,0 +1,103 @@
package org.keycloak.federation.ldap.idm.model;
import java.io.Serializable;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class LDAPObject {
private String uuid;
private LDAPDn dn;
private String rdnAttributeName;
private final List<String> objectClasses = new LinkedList<String>();
private final List<String> readOnlyAttributeNames = new LinkedList<String>();
private final Map<String, Serializable> attributes = new HashMap<String, Serializable>();
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
public LDAPDn getDn() {
return dn;
}
public void setDn(LDAPDn dn) {
this.dn = dn;
}
public List<String> getObjectClasses() {
return objectClasses;
}
public void setObjectClasses(Collection<String> objectClasses) {
this.objectClasses.clear();
this.objectClasses.addAll(objectClasses);
}
public List<String> getReadOnlyAttributeNames() {
return readOnlyAttributeNames;
}
public void addReadOnlyAttributeName(String readOnlyAttribute) {
readOnlyAttributeNames.add(readOnlyAttribute);
}
public String getRdnAttributeName() {
return rdnAttributeName;
}
public void setRdnAttributeName(String rdnAttributeName) {
this.rdnAttributeName = rdnAttributeName;
}
public void setAttribute(String attributeName, Serializable attributeValue) {
attributes.put(attributeName, attributeValue);
}
public void removeAttribute(String name) {
attributes.remove(name);
}
public Serializable getAttribute(String name) {
return attributes.get(name);
}
public Map<String, Serializable> getAttributes() {
return attributes;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (!getClass().isInstance(obj)) {
return false;
}
LDAPObject other = (LDAPObject) obj;
return getUuid() != null && other.getUuid() != null && getUuid().equals(other.getUuid());
}
@Override
public int hashCode() {
int result = getUuid() != null ? getUuid().hashCode() : 0;
result = 31 * result + (getUuid() != null ? getUuid().hashCode() : 0);
return result;
}
}

View file

@ -1,85 +0,0 @@
package org.keycloak.federation.ldap.idm.model;
import org.keycloak.federation.ldap.idm.query.QueryParameter;
/**
* This class represents a User; a human agent that may authenticate with the application
*
* @author Shane Bryzak
*/
public class LDAPUser extends AbstractIdentityType {
private static final long serialVersionUID = 4117586097100398485L;
public static final QueryParameter LOGIN_NAME = AttributedType.QUERY_ATTRIBUTE.byName("loginName");
/**
* A query parameter used to set the firstName value.
*/
public static final QueryParameter FIRST_NAME = QUERY_ATTRIBUTE.byName("firstName");
/**
* A query parameter used to set the lastName value.
*/
public static final QueryParameter LAST_NAME = QUERY_ATTRIBUTE.byName("lastName");
/**
* A query parameter used to set the email value.
*/
public static final QueryParameter EMAIL = QUERY_ATTRIBUTE.byName("email");
@AttributeProperty
private String loginName;
@AttributeProperty
private String firstName;
@AttributeProperty
private String lastName;
@AttributeProperty
private String email;
public LDAPUser() {
}
public LDAPUser(String loginName) {
this.loginName = loginName;
}
public String getLoginName() {
return loginName;
}
public void setLoginName(String loginName) {
this.loginName = loginName;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return this.email;
}
public void setEmail(String email) {
this.email = email;
}
}

View file

@ -1,21 +0,0 @@
package org.keycloak.federation.ldap.idm.query;
/**
* <p>This class can be used to define a query parameter for properties annotated with
* {@link org.keycloak.federation.ldap.idm.model.AttributeProperty}.
* </p>
*
* @author pedroigor
*/
public class AttributeParameter implements QueryParameter {
private final String name;
public AttributeParameter(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
}

View file

@ -7,6 +7,19 @@ package org.keycloak.federation.ldap.idm.query;
* @author Shane Bryzak
*
*/
public interface QueryParameter {
public class QueryParameter {
private String name;
public QueryParameter(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

View file

@ -1,6 +1,5 @@
package org.keycloak.federation.ldap.idm.query.internal;
import org.keycloak.federation.ldap.idm.query.AttributeParameter;
import org.keycloak.federation.ldap.idm.query.Condition;
import org.keycloak.federation.ldap.idm.query.QueryParameter;
@ -29,7 +28,7 @@ public class EqualCondition implements Condition {
@Override
public String toString() {
return "EqualCondition{" +
"parameter=" + ((AttributeParameter) parameter).getName() +
"parameter=" + parameter.getName() +
", value=" + value +
'}';
}

View file

@ -1,161 +0,0 @@
package org.keycloak.federation.ldap.idm.query.internal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.keycloak.federation.ldap.idm.model.IdentityType;
import org.keycloak.federation.ldap.idm.query.Condition;
import org.keycloak.federation.ldap.idm.query.QueryParameter;
import org.keycloak.federation.ldap.idm.query.Sort;
import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
import org.keycloak.models.ModelException;
import static java.util.Collections.unmodifiableSet;
/**
* Default IdentityQuery implementation.
*
* @param <T>
*
* @author Shane Bryzak
*/
public class IdentityQuery<T extends IdentityType> {
private final Map<QueryParameter, Object[]> parameters = new LinkedHashMap<QueryParameter, Object[]>();
private final Class<T> identityType;
private final LDAPIdentityStore identityStore;
private final IdentityQueryBuilder queryBuilder;
private int offset;
private int limit;
private Object paginationContext;
private QueryParameter[] sortParameters;
private boolean sortAscending = true;
private final Set<Condition> conditions = new LinkedHashSet<Condition>();
private final Set<Sort> ordering = new LinkedHashSet<Sort>();
public IdentityQuery(IdentityQueryBuilder queryBuilder, Class<T> identityType, LDAPIdentityStore identityStore) {
this.queryBuilder = queryBuilder;
this.identityStore = identityStore;
this.identityType = identityType;
}
public IdentityQuery<T> setParameter(QueryParameter queryParameter, Object... value) {
if (value == null || value.length == 0) {
throw new ModelException("Query Parameter values null or empty");
}
parameters.put(queryParameter, value);
if (IdentityType.CREATED_AFTER.equals(queryParameter) || IdentityType.EXPIRY_AFTER.equals(queryParameter)) {
this.conditions.add(queryBuilder.greaterThanOrEqualTo(queryParameter, value[0]));
} else if (IdentityType.CREATED_BEFORE.equals(queryParameter) || IdentityType.EXPIRY_BEFORE.equals(queryParameter)) {
this.conditions.add(queryBuilder.lessThanOrEqualTo(queryParameter, value[0]));
} else {
this.conditions.add(queryBuilder.equal(queryParameter, value[0]));
}
return this;
}
public IdentityQuery<T> where(Condition... condition) {
this.conditions.addAll(Arrays.asList(condition));
return this;
}
public IdentityQuery<T> sortBy(Sort... sorts) {
this.ordering.addAll(Arrays.asList(sorts));
return this;
}
public Set<Sort> getSorting() {
return unmodifiableSet(this.ordering);
}
public Class<T> getIdentityType() {
return identityType;
}
public int getLimit() {
return limit;
}
public int getOffset() {
return offset;
}
public Object getPaginationContext() {
return paginationContext;
}
public QueryParameter[] getSortParameters() {
return sortParameters;
}
public boolean isSortAscending() {
return sortAscending;
}
public List<T> getResultList() {
// remove this statement once deprecated methods on IdentityQuery are removed
if (this.sortParameters != null) {
for (QueryParameter parameter : this.sortParameters) {
if (isSortAscending()) {
sortBy(this.queryBuilder.asc(parameter));
} else {
sortBy(this.queryBuilder.desc(parameter));
}
}
}
List<T> result = new ArrayList<T>();
try {
for (T identityType : identityStore.fetchQueryResults(this)) {
result.add(identityType);
}
} catch (Exception e) {
throw new ModelException("LDAP Query failed", e);
}
return result;
}
public int getResultCount() {
return identityStore.countQueryResults(this);
}
public IdentityQuery<T> setOffset(int offset) {
this.offset = offset;
return this;
}
public IdentityQuery<T> setLimit(int limit) {
this.limit = limit;
return this;
}
public IdentityQuery<T> setSortParameters(QueryParameter... sortParameters) {
this.sortParameters = sortParameters;
return this;
}
public IdentityQuery<T> setSortAscending(boolean sortAscending) {
this.sortAscending = sortAscending;
return this;
}
public IdentityQuery<T> setPaginationContext(Object object) {
this.paginationContext = object;
return this;
}
public Set<Condition> getConditions() {
return unmodifiableSet(this.conditions);
}
}

View file

@ -0,0 +1,192 @@
package org.keycloak.federation.ldap.idm.query.internal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import javax.naming.directory.SearchControls;
import org.keycloak.federation.ldap.LDAPFederationProvider;
import org.keycloak.federation.ldap.idm.model.LDAPObject;
import org.keycloak.federation.ldap.idm.query.Condition;
import org.keycloak.federation.ldap.idm.query.Sort;
import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
import org.keycloak.federation.ldap.mappers.LDAPFederationMapper;
import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.ModelException;
import org.keycloak.models.UserFederationMapperModel;
import static java.util.Collections.unmodifiableSet;
/**
* Default IdentityQuery implementation.
*
*
* @author Shane Bryzak
*/
public class LDAPIdentityQuery {
private final LDAPFederationProvider ldapFedProvider;
private int offset;
private int limit;
private byte[] paginationContext;
private final Set<Condition> conditions = new LinkedHashSet<Condition>();
private final Set<Sort> ordering = new LinkedHashSet<Sort>();
private final Set<String> searchDns = new LinkedHashSet<String>();
private final Set<String> returningLdapAttributes = new LinkedHashSet<String>();
// Contains just those returningLdapAttributes, which are read-only. They will be marked as read-only in returned LDAPObject instances as well
private final Set<String> returningReadOnlyLdapAttributes = new LinkedHashSet<String>();
private final Set<String> objectClasses = new LinkedHashSet<String>();
private final List<UserFederationMapperModel> mappers = new ArrayList<UserFederationMapperModel>();
private int searchScope = SearchControls.SUBTREE_SCOPE;
public LDAPIdentityQuery(LDAPFederationProvider ldapProvider) {
this.ldapFedProvider = ldapProvider;
}
public LDAPIdentityQuery where(Condition... condition) {
this.conditions.addAll(Arrays.asList(condition));
return this;
}
public LDAPIdentityQuery sortBy(Sort... sorts) {
this.ordering.addAll(Arrays.asList(sorts));
return this;
}
public LDAPIdentityQuery addSearchDns(Collection<String> searchDns) {
this.searchDns.addAll(searchDns);
return this;
}
public LDAPIdentityQuery addObjectClasses(Collection<String> objectClasses) {
this.objectClasses.addAll(objectClasses);
return this;
}
public LDAPIdentityQuery addReturningLdapAttribute(String ldapAttributeName) {
this.returningLdapAttributes.add(ldapAttributeName);
return this;
}
public LDAPIdentityQuery addReturningReadOnlyLdapAttribute(String ldapAttributeName) {
this.returningReadOnlyLdapAttributes.add(ldapAttributeName);
return this;
}
public LDAPIdentityQuery addMappers(Collection<UserFederationMapperModel> mappers) {
this.mappers.addAll(mappers);
return this;
}
public LDAPIdentityQuery setSearchScope(int searchScope) {
this.searchScope = searchScope;
return this;
}
public Set<Sort> getSorting() {
return unmodifiableSet(this.ordering);
}
public Set<String> getSearchDns() {
return unmodifiableSet(this.searchDns);
}
public Set<String> getObjectClasses() {
return unmodifiableSet(this.objectClasses);
}
public Set<String> getReturningLdapAttributes() {
return unmodifiableSet(this.returningLdapAttributes);
}
public Set<String> getReturningReadOnlyLdapAttributes() {
return unmodifiableSet(this.returningReadOnlyLdapAttributes);
}
public List<UserFederationMapperModel> getMappers() {
return mappers;
}
public int getSearchScope() {
return searchScope;
}
public int getLimit() {
return limit;
}
public int getOffset() {
return offset;
}
public byte[] getPaginationContext() {
return paginationContext;
}
public List<LDAPObject> getResultList() {
// Apply mappers now
for (UserFederationMapperModel mapperModel : mappers) {
LDAPFederationMapper fedMapper = ldapFedProvider.getMapper(mapperModel);
fedMapper.beforeLDAPQuery(mapperModel, this);
}
List<LDAPObject> result = new ArrayList<LDAPObject>();
try {
for (LDAPObject ldapObject : ldapFedProvider.getLdapIdentityStore().fetchQueryResults(this)) {
result.add(ldapObject);
}
} catch (Exception e) {
throw new ModelException("LDAP Query failed", e);
}
return result;
}
public LDAPObject getFirstResult() {
List<LDAPObject> results = getResultList();
if (results.isEmpty()) {
return null;
} else if (results.size() == 1) {
return results.get(0);
} else {
throw new ModelDuplicateException("Error - multiple LDAP objects found but expected just one");
}
}
public int getResultCount() {
return ldapFedProvider.getLdapIdentityStore().countQueryResults(this);
}
public LDAPIdentityQuery setOffset(int offset) {
this.offset = offset;
return this;
}
public LDAPIdentityQuery setLimit(int limit) {
this.limit = limit;
return this;
}
public LDAPIdentityQuery setPaginationContext(byte[] paginationContext) {
this.paginationContext = paginationContext;
return this;
}
public Set<Condition> getConditions() {
return unmodifiableSet(this.conditions);
}
}

View file

@ -1,22 +1,14 @@
package org.keycloak.federation.ldap.idm.query.internal;
import org.keycloak.federation.ldap.idm.model.IdentityType;
import org.keycloak.federation.ldap.idm.query.Condition;
import org.keycloak.federation.ldap.idm.query.QueryParameter;
import org.keycloak.federation.ldap.idm.query.Sort;
import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
import org.keycloak.models.ModelException;
/**
* @author Pedro Igor
*/
public class IdentityQueryBuilder {
private final LDAPIdentityStore identityStore;
public IdentityQueryBuilder(LDAPIdentityStore identityStore) {
this.identityStore = identityStore;
}
public class LDAPQueryConditionsBuilder {
public Condition like(QueryParameter parameter, String pattern) {
return new LikeCondition(parameter, pattern);
@ -71,10 +63,6 @@ public class IdentityQueryBuilder {
return new Sort(parameter, false);
}
public <T extends IdentityType> IdentityQuery createIdentityQuery(Class<T> identityType) {
return new IdentityQuery(this, identityType, this.identityStore);
}
private void throwExceptionIfNotComparable(Object x) {
if (!Comparable.class.isInstance(x)) {
throw new ModelException("Query parameter value [" + x + "] must be " + Comparable.class + ".");

View file

@ -2,11 +2,9 @@ package org.keycloak.federation.ldap.idm.store;
import java.util.List;
import org.keycloak.federation.ldap.idm.model.AttributedType;
import org.keycloak.federation.ldap.idm.model.IdentityType;
import org.keycloak.federation.ldap.idm.model.LDAPUser;
import org.keycloak.federation.ldap.idm.query.internal.IdentityQuery;
import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStoreConfiguration;
import org.keycloak.federation.ldap.LDAPConfig;
import org.keycloak.federation.ldap.idm.model.LDAPObject;
import org.keycloak.federation.ldap.idm.query.internal.LDAPIdentityQuery;
/**
* IdentityStore representation providing minimal SPI
@ -23,36 +21,36 @@ public interface IdentityStore {
*
* @return
*/
LDAPIdentityStoreConfiguration getConfig();
LDAPConfig getConfig();
// General
/**
* Persists the specified IdentityType
*
* @param value
* @param ldapObject
*/
void add(AttributedType value);
void add(LDAPObject ldapObject);
/**
* Updates the specified IdentityType
*
* @param value
* @param ldapObject
*/
void update(AttributedType value);
void update(LDAPObject ldapObject);
/**
* Removes the specified IdentityType
*
* @param value
* @param ldapObject
*/
void remove(AttributedType value);
void remove(LDAPObject ldapObject);
// Identity query
<V extends IdentityType> List<V> fetchQueryResults(IdentityQuery<V> identityQuery);
List<LDAPObject> fetchQueryResults(LDAPIdentityQuery LDAPIdentityQuery);
<V extends IdentityType> int countQueryResults(IdentityQuery<V> identityQuery);
int countQueryResults(LDAPIdentityQuery LDAPIdentityQuery);
// // Relationship query
//
@ -68,7 +66,7 @@ public interface IdentityStore {
* @param user Keycloak user
* @param password Ldap password
*/
boolean validatePassword(LDAPUser user, String password);
boolean validatePassword(LDAPObject user, String password);
/**
* Updates the specified credential value.
@ -76,6 +74,6 @@ public interface IdentityStore {
* @param user Keycloak user
* @param password Ldap password
*/
void updatePassword(LDAPUser user, String password);
void updatePassword(LDAPObject user, String password);
}

View file

@ -2,7 +2,10 @@ package org.keycloak.federation.ldap.idm.store.ldap;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
@ -15,33 +18,25 @@ import javax.naming.directory.BasicAttribute;
import javax.naming.directory.BasicAttributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.ModificationItem;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import org.jboss.logging.Logger;
import org.keycloak.federation.ldap.idm.model.AttributedType;
import org.keycloak.federation.ldap.idm.model.IdentityType;
import org.keycloak.federation.ldap.idm.model.LDAPUser;
import org.keycloak.federation.ldap.idm.query.AttributeParameter;
import org.keycloak.federation.ldap.LDAPConfig;
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.BetweenCondition;
import org.keycloak.federation.ldap.idm.query.internal.IdentityQuery;
import org.keycloak.federation.ldap.idm.query.internal.IdentityQueryBuilder;
import org.keycloak.federation.ldap.idm.query.internal.LDAPIdentityQuery;
import org.keycloak.federation.ldap.idm.query.internal.EqualCondition;
import org.keycloak.federation.ldap.idm.query.internal.GreaterThanCondition;
import org.keycloak.federation.ldap.idm.query.internal.InCondition;
import org.keycloak.federation.ldap.idm.query.internal.LessThanCondition;
import org.keycloak.federation.ldap.idm.query.internal.LikeCondition;
import org.keycloak.federation.ldap.idm.query.internal.OrCondition;
import org.keycloak.federation.ldap.idm.store.IdentityStore;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.ModelException;
import org.keycloak.models.utils.reflection.NamedPropertyCriteria;
import org.keycloak.models.utils.reflection.Property;
import org.keycloak.models.utils.reflection.PropertyQueries;
import org.keycloak.models.utils.reflection.TypedPropertyCriteria;
import org.keycloak.util.reflections.Reflections;
/**
* An IdentityStore implementation backed by an LDAP directory
@ -54,12 +49,10 @@ public class LDAPIdentityStore implements IdentityStore {
private static final Logger logger = Logger.getLogger(LDAPIdentityStore.class);
public static final String EMPTY_ATTRIBUTE_VALUE = " ";
private final LDAPIdentityStoreConfiguration config;
private final LDAPConfig config;
private final LDAPOperationManager operationManager;
public LDAPIdentityStore(LDAPIdentityStoreConfiguration config) {
public LDAPIdentityStore(LDAPConfig config) {
this.config = config;
try {
@ -70,69 +63,73 @@ public class LDAPIdentityStore implements IdentityStore {
}
@Override
public LDAPIdentityStoreConfiguration getConfig() {
public LDAPConfig getConfig() {
return this.config;
}
@Override
public void add(AttributedType attributedType) {
public void add(LDAPObject ldapObject) {
// id will be assigned by the ldap server
attributedType.setId(null);
if (ldapObject.getUuid() != null) {
throw new IllegalStateException("Can't add object with already assigned uuid");
}
String entryDN = getBindingDN(attributedType, true);
this.operationManager.createSubContext(entryDN, extractAttributes(attributedType, true));
addToParentAsMember(attributedType);
attributedType.setId(getEntryIdentifier(attributedType));
attributedType.setEntryDN(entryDN);
String entryDN = ldapObject.getDn().toString();
this.operationManager.createSubContext(entryDN, extractAttributes(ldapObject, true));
ldapObject.setUuid(getEntryIdentifier(ldapObject));
if (logger.isTraceEnabled()) {
logger.tracef("Type with identifier [%s] successfully added to identity store [%s].", attributedType.getId(), this);
logger.tracef("Type with identifier [%s] and dn [%s] successfully added to LDAP store.", ldapObject.getUuid(), entryDN);
}
}
@Override
public void update(AttributedType attributedType) {
BasicAttributes updatedAttributes = extractAttributes(attributedType, false);
public void update(LDAPObject ldapObject) {
BasicAttributes updatedAttributes = extractAttributes(ldapObject, false);
NamingEnumeration<Attribute> attributes = updatedAttributes.getAll();
this.operationManager.modifyAttributes(getBindingDN(attributedType, true), attributes);
String entryDn = ldapObject.getDn().toString();
this.operationManager.modifyAttributes(entryDn, attributes);
if (logger.isTraceEnabled()) {
logger.tracef("Type with identifier [%s] successfully updated to identity store [%s].", attributedType.getId(), this);
logger.tracef("Type with identifier [%s] and DN [%s] successfully updated to LDAP store.", ldapObject.getUuid(), entryDn);
}
}
@Override
public void remove(AttributedType attributedType) {
LDAPMappingConfiguration mappingConfig = getMappingConfig(attributedType.getClass());
this.operationManager.removeEntryById(getBaseDN(attributedType), attributedType.getId(), mappingConfig);
public void remove(LDAPObject ldapObject) {
this.operationManager.removeEntry(ldapObject.getDn().toString());
if (logger.isTraceEnabled()) {
logger.tracef("Type with identifier [%s] successfully removed from identity store [%s].", attributedType.getId(), this);
logger.tracef("Type with identifier [%s] and DN [%s] successfully removed from LDAP store.", ldapObject.getUuid(), ldapObject.getDn().toString());
}
}
@Override
public <V extends IdentityType> List<V> fetchQueryResults(IdentityQuery<V> identityQuery) {
List<V> results = new ArrayList<V>();
public List<LDAPObject> fetchQueryResults(LDAPIdentityQuery identityQuery) {
List<LDAPObject> results = new ArrayList<LDAPObject>();
try {
if (identityQuery.getSorting() != null && !identityQuery.getSorting().isEmpty()) {
throw new ModelException("LDAP Identity Store does not support sorted queries.");
throw new ModelException("LDAP Identity Store does not yet support sorted queries.");
}
// TODO: proper support for search by more DNs
String baseDN = identityQuery.getSearchDns().iterator().next();
for (Condition condition : identityQuery.getConditions()) {
if (IdentityType.ID.equals(condition.getParameter())) {
// Check if we are searching by ID
String uuidAttrName = getConfig().getUuidAttributeName();
if (condition.getParameter() != null && condition.getParameter().getName().equals(uuidAttrName)) {
if (EqualCondition.class.isInstance(condition)) {
EqualCondition equalCondition = (EqualCondition) condition;
SearchResult search = this.operationManager
.lookupById(getConfig().getBaseDN(), equalCondition.getValue().toString(), null);
.lookupById(baseDN, equalCondition.getValue().toString(), identityQuery.getReturningLdapAttributes());
if (search != null) {
results.add((V) populateAttributedType(search, null));
results.add(populateAttributedType(search, identityQuery.getReturningReadOnlyLdapAttributes()));
}
}
@ -140,23 +137,19 @@ public class LDAPIdentityStore implements IdentityStore {
}
}
if (!IdentityType.class.equals(identityQuery.getIdentityType())) {
// the ldap store does not support queries based on root types. Except if based on the identifier.
LDAPMappingConfiguration ldapEntryConfig = getMappingConfig(identityQuery.getIdentityType());
StringBuilder filter = createIdentityTypeSearchFilter(identityQuery, ldapEntryConfig);
String baseDN = getBaseDN(ldapEntryConfig);
List<SearchResult> search;
if (getConfig().isPagination() && identityQuery.getLimit() > 0) {
search = this.operationManager.searchPaginated(baseDN, filter.toString(), ldapEntryConfig, identityQuery);
} else {
search = this.operationManager.search(baseDN, filter.toString(), ldapEntryConfig);
}
StringBuilder filter = createIdentityTypeSearchFilter(identityQuery);
for (SearchResult result : search) {
if (!result.getNameInNamespace().equals(baseDN)) {
results.add((V) populateAttributedType(result, null));
}
List<SearchResult> search;
if (getConfig().isPagination() && identityQuery.getLimit() > 0) {
search = this.operationManager.searchPaginated(baseDN, filter.toString(), identityQuery);
} else {
search = this.operationManager.search(baseDN, filter.toString(), identityQuery.getReturningLdapAttributes(), identityQuery.getSearchScope());
}
for (SearchResult result : search) {
if (!result.getNameInNamespace().equals(baseDN)) {
results.add(populateAttributedType(result, identityQuery.getReturningReadOnlyLdapAttributes()));
}
}
} catch (Exception e) {
@ -167,7 +160,7 @@ public class LDAPIdentityStore implements IdentityStore {
}
@Override
public <V extends IdentityType> int countQueryResults(IdentityQuery<V> identityQuery) {
public int countQueryResults(LDAPIdentityQuery identityQuery) {
int limit = identityQuery.getLimit();
int offset = identityQuery.getOffset();
@ -182,18 +175,14 @@ public class LDAPIdentityStore implements IdentityStore {
return resultCount;
}
public IdentityQueryBuilder createQueryBuilder() {
return new IdentityQueryBuilder(this);
}
// *************** CREDENTIALS AND USER SPECIFIC STUFF
@Override
public boolean validatePassword(LDAPUser user, String password) {
String userDN = getEntryDNOfUser(user);
public boolean validatePassword(LDAPObject user, String password) {
String userDN = user.getDn().toString();
if (logger.isDebugEnabled()) {
logger.debugf("Using DN [%s] for authentication of user [%s]", userDN, user.getLoginName());
logger.debugf("Using DN [%s] for authentication of user", userDN);
}
if (operationManager.authenticate(userDN, password)) {
@ -204,11 +193,11 @@ public class LDAPIdentityStore implements IdentityStore {
}
@Override
public void updatePassword(LDAPUser user, String password) {
String userDN = getEntryDNOfUser(user);
public void updatePassword(LDAPObject user, String password) {
String userDN = user.getDn().toString();
if (logger.isDebugEnabled()) {
logger.debugf("Using DN [%s] for updating LDAP password of user [%s]", userDN, user.getLoginName());
logger.debugf("Using DN [%s] for updating LDAP password of user", userDN);
}
if (getConfig().isActiveDirectory()) {
@ -242,6 +231,7 @@ public class LDAPIdentityStore implements IdentityStore {
modItems.add(new ModificationItem(DirContext.REPLACE_ATTRIBUTE, unicodePwd));
// Used in ActiveDirectory to put account into "enabled" state (aka userAccountControl=512, see http://support.microsoft.com/kb/305144/en ) after password update. If value is -1, it's ignored
// TODO: Remove and use mapper instead
if (getConfig().isUserAccountControlsAfterPasswordUpdate()) {
BasicAttribute userAccountControl = new BasicAttribute("userAccountControl", "512");
modItems.add(new ModificationItem(DirContext.REPLACE_ATTRIBUTE, userAccountControl));
@ -255,80 +245,31 @@ public class LDAPIdentityStore implements IdentityStore {
}
}
private String getEntryDNOfUser(LDAPUser user) {
// First try if user already has entryDN on him
String entryDN = user.getEntryDN();
if (entryDN != null) {
return entryDN;
}
// Need to find user in LDAP
String username = user.getLoginName();
user = getUser(username);
if (user == null) {
throw new ModelException("No LDAP user found with username " + username);
}
return user.getEntryDN();
}
public LDAPUser getUser(String username) {
if (isNullOrEmpty(username)) {
return null;
}
IdentityQueryBuilder queryBuilder = createQueryBuilder();
List<LDAPUser> agents = queryBuilder.createIdentityQuery(LDAPUser.class)
.where(queryBuilder.equal(LDAPUser.LOGIN_NAME, username)).getResultList();
if (agents.isEmpty()) {
return null;
} else if (agents.size() == 1) {
return agents.get(0);
} else {
throw new ModelDuplicateException("Error - multiple Agent objects found with same login name");
}
}
// ************ END CREDENTIALS AND USER SPECIFIC STUFF
private String getBaseDN(final LDAPMappingConfiguration ldapEntryConfig) {
String baseDN = getConfig().getBaseDN();
if (ldapEntryConfig.getBaseDN() != null) {
baseDN = ldapEntryConfig.getBaseDN();
}
return baseDN;
}
protected <V extends IdentityType> StringBuilder createIdentityTypeSearchFilter(final IdentityQuery<V> identityQuery, final LDAPMappingConfiguration ldapEntryConfig) {
protected StringBuilder createIdentityTypeSearchFilter(final LDAPIdentityQuery identityQuery) {
StringBuilder filter = new StringBuilder();
for (Condition condition : identityQuery.getConditions()) {
applyCondition(filter, condition, ldapEntryConfig);
applyCondition(filter, condition);
}
filter.insert(0, "(&");
filter.append(getObjectClassesFilter(ldapEntryConfig));
filter.append(getObjectClassesFilter(identityQuery.getObjectClasses()));
filter.append(")");
logger.infof("Using filter for LDAP search: %s", filter);
return filter;
}
protected void applyCondition(StringBuilder filter, Condition condition, LDAPMappingConfiguration ldapEntryConfig) {
protected void applyCondition(StringBuilder filter, Condition condition) {
if (OrCondition.class.isInstance(condition)) {
OrCondition orCondition = (OrCondition) condition;
filter.append("(|");
for (Condition innerCondition : orCondition.getInnerConditions()) {
applyCondition(filter, innerCondition, ldapEntryConfig);
applyCondition(filter, innerCondition);
}
filter.append(")");
@ -337,91 +278,84 @@ public class LDAPIdentityStore implements IdentityStore {
QueryParameter queryParameter = condition.getParameter();
if (!IdentityType.ID.equals(queryParameter)) {
if (AttributeParameter.class.isInstance(queryParameter)) {
AttributeParameter attributeParameter = (AttributeParameter) queryParameter;
String attributeName = ldapEntryConfig.getMappedProperties().get(attributeParameter.getName());
if (!getConfig().getUuidAttributeName().equals(queryParameter.getName())) {
String attributeName = queryParameter.getName();
if (attributeName != null) {
if (EqualCondition.class.isInstance(condition)) {
EqualCondition equalCondition = (EqualCondition) condition;
Object parameterValue = equalCondition.getValue();
if (attributeName != null) {
if (EqualCondition.class.isInstance(condition)) {
EqualCondition equalCondition = (EqualCondition) condition;
Object parameterValue = equalCondition.getValue();
if (Date.class.isInstance(parameterValue)) {
parameterValue = LDAPUtil.formatDate((Date) parameterValue);
}
filter.append("(").append(attributeName).append(LDAPConstants.EQUAL).append(parameterValue).append(")");
} else if (LikeCondition.class.isInstance(condition)) {
LikeCondition likeCondition = (LikeCondition) condition;
String parameterValue = (String) likeCondition.getValue();
} else if (GreaterThanCondition.class.isInstance(condition)) {
GreaterThanCondition greaterThanCondition = (GreaterThanCondition) condition;
Comparable parameterValue = (Comparable) greaterThanCondition.getValue();
if (Date.class.isInstance(parameterValue)) {
parameterValue = LDAPUtil.formatDate((Date) parameterValue);
}
if (greaterThanCondition.isOrEqual()) {
filter.append("(").append(attributeName).append(">=").append(parameterValue).append(")");
} else {
filter.append("(").append(attributeName).append(">").append(parameterValue).append(")");
}
} else if (LessThanCondition.class.isInstance(condition)) {
LessThanCondition lessThanCondition = (LessThanCondition) condition;
Comparable parameterValue = (Comparable) lessThanCondition.getValue();
if (Date.class.isInstance(parameterValue)) {
parameterValue = LDAPUtil.formatDate((Date) parameterValue);
}
if (lessThanCondition.isOrEqual()) {
filter.append("(").append(attributeName).append("<=").append(parameterValue).append(")");
} else {
filter.append("(").append(attributeName).append("<").append(parameterValue).append(")");
}
} else if (BetweenCondition.class.isInstance(condition)) {
BetweenCondition betweenCondition = (BetweenCondition) condition;
Comparable x = betweenCondition.getX();
Comparable y = betweenCondition.getY();
if (Date.class.isInstance(x)) {
x = LDAPUtil.formatDate((Date) x);
}
if (Date.class.isInstance(y)) {
y = LDAPUtil.formatDate((Date) y);
}
filter.append("(").append(x).append("<=").append(attributeName).append("<=").append(y).append(")");
} else if (InCondition.class.isInstance(condition)) {
InCondition inCondition = (InCondition) condition;
Object[] valuesToCompare = inCondition.getValue();
filter.append("(&(");
for (int i = 0; i< valuesToCompare.length; i++) {
Object value = valuesToCompare[i];
filter.append("(").append(attributeName).append(LDAPConstants.EQUAL).append(value).append(")");
}
filter.append("))");
} else {
throw new ModelException("Unsupported query condition [" + condition + "].");
if (Date.class.isInstance(parameterValue)) {
parameterValue = LDAPUtil.formatDate((Date) parameterValue);
}
filter.append("(").append(attributeName).append(LDAPConstants.EQUAL).append(parameterValue).append(")");
} else if (GreaterThanCondition.class.isInstance(condition)) {
GreaterThanCondition greaterThanCondition = (GreaterThanCondition) condition;
Comparable parameterValue = greaterThanCondition.getValue();
if (Date.class.isInstance(parameterValue)) {
parameterValue = LDAPUtil.formatDate((Date) parameterValue);
}
if (greaterThanCondition.isOrEqual()) {
filter.append("(").append(attributeName).append(">=").append(parameterValue).append(")");
} else {
filter.append("(").append(attributeName).append(">").append(parameterValue).append(")");
}
} else if (LessThanCondition.class.isInstance(condition)) {
LessThanCondition lessThanCondition = (LessThanCondition) condition;
Comparable parameterValue = lessThanCondition.getValue();
if (Date.class.isInstance(parameterValue)) {
parameterValue = LDAPUtil.formatDate((Date) parameterValue);
}
if (lessThanCondition.isOrEqual()) {
filter.append("(").append(attributeName).append("<=").append(parameterValue).append(")");
} else {
filter.append("(").append(attributeName).append("<").append(parameterValue).append(")");
}
} else if (BetweenCondition.class.isInstance(condition)) {
BetweenCondition betweenCondition = (BetweenCondition) condition;
Comparable x = betweenCondition.getX();
Comparable y = betweenCondition.getY();
if (Date.class.isInstance(x)) {
x = LDAPUtil.formatDate((Date) x);
}
if (Date.class.isInstance(y)) {
y = LDAPUtil.formatDate((Date) y);
}
filter.append("(").append(x).append("<=").append(attributeName).append("<=").append(y).append(")");
} else if (InCondition.class.isInstance(condition)) {
InCondition inCondition = (InCondition) condition;
Object[] valuesToCompare = inCondition.getValue();
filter.append("(&(");
for (int i = 0; i< valuesToCompare.length; i++) {
Object value = valuesToCompare[i];
filter.append("(").append(attributeName).append(LDAPConstants.EQUAL).append(value).append(")");
}
filter.append("))");
} else {
throw new ModelException("Unsupported query condition [" + condition + "].");
}
}
}
}
private StringBuilder getObjectClassesFilter(final LDAPMappingConfiguration ldapEntryConfig) {
private StringBuilder getObjectClassesFilter(Collection<String> objectClasses) {
StringBuilder builder = new StringBuilder();
if (ldapEntryConfig != null && !ldapEntryConfig.getObjectClasses().isEmpty()) {
for (String objectClass : ldapEntryConfig.getObjectClasses()) {
if (!objectClasses.isEmpty()) {
for (String objectClass : objectClasses) {
builder.append("(").append(LDAPConstants.OBJECT_CLASS).append(LDAPConstants.EQUAL).append(objectClass).append(")");
}
} else {
@ -431,86 +365,65 @@ public class LDAPIdentityStore implements IdentityStore {
return builder;
}
private AttributedType populateAttributedType(SearchResult searchResult, AttributedType attributedType) {
return populateAttributedType(searchResult, attributedType, 0);
}
private AttributedType populateAttributedType(SearchResult searchResult, AttributedType attributedType, int hierarchyDepthCount) {
private LDAPObject populateAttributedType(SearchResult searchResult, Collection<String> readOnlyAttrNames) {
try {
String entryDN = searchResult.getNameInNamespace();
Attributes attributes = searchResult.getAttributes();
if (attributedType == null) {
attributedType = Reflections.newInstance(getConfig().getSupportedTypeByBaseDN(entryDN, getEntryObjectClasses(attributes)));
}
attributedType.setEntryDN(entryDN);
LDAPMappingConfiguration mappingConfig = getMappingConfig(attributedType.getClass());
if (hierarchyDepthCount > mappingConfig.getHierarchySearchDepth()) {
return null;
}
LDAPObject ldapObject = new LDAPObject();
LDAPDn dn = LDAPDn.fromString(entryDN);
ldapObject.setDn(dn);
ldapObject.setRdnAttributeName(dn.getFirstRdnAttrName());
if (logger.isTraceEnabled()) {
logger.tracef("Populating attributed type [%s] from DN [%s]", attributedType, entryDN);
logger.tracef("Populating LDAP Object from DN [%s]", entryDN);
}
NamingEnumeration<? extends Attribute> ldapAttributes = attributes.getAll();
// Exact name of attributes might be different
List<String> uppercasedReadOnlyAttrNames = new ArrayList<String>();
for (String readonlyAttr : readOnlyAttrNames) {
uppercasedReadOnlyAttrNames.add(readonlyAttr.toUpperCase());
}
while (ldapAttributes.hasMore()) {
Attribute ldapAttribute = ldapAttributes.next();
Object attributeValue;
Serializable ldapAttributeValue;
try {
attributeValue = ldapAttribute.get();
ldapAttributeValue = (Serializable) ldapAttribute.get();
} catch (NoSuchElementException nsee) {
continue;
}
String ldapAttributeName = ldapAttribute.getID();
if (ldapAttributeName.toLowerCase().equals(getConfig().getUniqueIdentifierAttributeName().toLowerCase())) {
attributedType.setId(this.operationManager.decodeEntryUUID(attributeValue));
if (ldapAttributeName.toLowerCase().equals(getConfig().getUuidAttributeName().toLowerCase())) {
ldapObject.setUuid(this.operationManager.decodeEntryUUID(ldapAttributeValue));
} else if (ldapAttributeName.toLowerCase().equals(LDAPConstants.OBJECT_CLASS)) {
List<String> objectClasses = new LinkedList<String>();
NamingEnumeration<?> enumm = ldapAttribute.getAll();
while (enumm.hasMoreElements()) {
String objectClass = enumm.next().toString();
objectClasses.add(objectClass);
}
ldapObject.setObjectClasses(objectClasses);
} else {
String attributeName = findAttributeName(mappingConfig.getMappedProperties(), ldapAttributeName);
if (logger.isTraceEnabled()) {
logger.tracef("Populating ldap attribute [%s] with value [%s] for DN [%s].", ldapAttributeName, ldapAttributeValue, entryDN);
}
if (attributeName != null) {
// Find if it's java property or attribute
Property<Object> property = PropertyQueries
.createQuery(attributedType.getClass())
.addCriteria(new NamedPropertyCriteria(attributeName)).getFirstResult();
if (property != null) {
if (logger.isTraceEnabled()) {
logger.tracef("Populating property [%s] from ldap attribute [%s] with value [%s] from DN [%s].", property.getName(), ldapAttributeName, attributeValue, entryDN);
}
if (property.getJavaClass().equals(Date.class)) {
property.setValue(attributedType, LDAPUtil.parseDate(attributeValue.toString()));
} else {
property.setValue(attributedType, attributeValue);
}
} else {
if (logger.isTraceEnabled()) {
logger.tracef("Populating attribute [%s] from ldap attribute [%s] with value [%s] from DN [%s].", attributeName, ldapAttributeName, attributeValue, entryDN);
}
attributedType.setAttribute(new org.keycloak.federation.ldap.idm.model.Attribute(attributeName, (Serializable) attributeValue));
}
ldapObject.setAttribute(ldapAttributeName, ldapAttributeValue);
if (uppercasedReadOnlyAttrNames.contains(ldapAttributeName.toUpperCase())) {
ldapObject.addReadOnlyAttributeName(ldapAttributeName);
}
}
}
if (IdentityType.class.isInstance(attributedType)) {
IdentityType identityType = (IdentityType) attributedType;
return ldapObject;
String createdTimestamp = attributes.get(LDAPConstants.CREATE_TIMESTAMP).get().toString();
identityType.setCreatedDate(LDAPUtil.parseDate(createdTimestamp));
}
LDAPMappingConfiguration entryConfig = getMappingConfig(attributedType.getClass());
/*LDAPMappingConfiguration entryConfig = getMappingConfig(attributedType.getClass());
if (mappingConfig.getParentMembershipAttributeName() != null) {
StringBuilder filter = new StringBuilder("(&");
@ -539,14 +452,14 @@ public class LDAPIdentityStore implements IdentityStore {
if (!search.isEmpty()) {
SearchResult next = search.get(0);
Property<AttributedType> parentProperty = PropertyQueries
.<AttributedType>createQuery(attributedType.getClass())
Property<IdentityType> parentProperty = PropertyQueries
.<IdentityType>createQuery(attributedType.getClass())
.addCriteria(new TypedPropertyCriteria(attributedType.getClass())).getFirstResult();
if (parentProperty != null) {
String parentDN = next.getNameInNamespace();
String parentBaseDN = parentDN.substring(parentDN.indexOf(",") + 1);
Class<? extends AttributedType> baseDNType = getConfig().getSupportedTypeByBaseDN(parentBaseDN, getEntryObjectClasses(attributes));
Class<? extends IdentityType> baseDNType = getConfig().getSupportedTypeByBaseDN(parentBaseDN, getEntryObjectClasses(attributes));
if (parentProperty.getJavaClass().isAssignableFrom(baseDNType)) {
if (logger.isTraceEnabled()) {
@ -563,90 +476,50 @@ public class LDAPIdentityStore implements IdentityStore {
logger.tracef("No parent entry found for DN [%s] using filter [%s].", entryDN, filter.toString());
}
}
}
} */
} catch (Exception e) {
throw new ModelException("Could not populate attribute type " + attributedType + ".", e);
throw new ModelException("Could not populate attribute type " + searchResult.getNameInNamespace() + ".", e);
}
return attributedType;
}
private String findAttributeName(Map<String, String> attrMapping, String ldapAttributeName) {
for (Map.Entry<String,String> currentAttr : attrMapping.entrySet()) {
if (currentAttr.getValue().equalsIgnoreCase(ldapAttributeName)) {
return currentAttr.getKey();
}
}
return null;
}
private List<String> getEntryObjectClasses(final Attributes attributes) throws NamingException {
Attribute objectClassesAttribute = attributes.get(LDAPConstants.OBJECT_CLASS);
List<String> objectClasses = new ArrayList<String>();
if (objectClassesAttribute == null) {
return objectClasses;
}
NamingEnumeration<?> all = objectClassesAttribute.getAll();
while (all.hasMore()) {
objectClasses.add(all.next().toString());
}
return objectClasses;
}
protected BasicAttributes extractAttributes(AttributedType attributedType, boolean isCreate) {
protected BasicAttributes extractAttributes(LDAPObject ldapObject, boolean isCreate) {
BasicAttributes entryAttributes = new BasicAttributes();
LDAPMappingConfiguration mappingConfig = getMappingConfig(attributedType.getClass());
Map<String, String> mappedProperties = mappingConfig.getMappedProperties();
for (String propertyName : mappedProperties.keySet()) {
if (!mappingConfig.getReadOnlyAttributes().contains(propertyName) && (isCreate || !mappingConfig.getBindingProperty().getName().equals(propertyName))) {
Property<Object> property = PropertyQueries
.<Object>createQuery(attributedType.getClass())
.addCriteria(new NamedPropertyCriteria(propertyName)).getFirstResult();
for (Map.Entry<String, Serializable> attrEntry : ldapObject.getAttributes().entrySet()) {
String attrName = attrEntry.getKey();
Serializable attrValue = attrEntry.getValue();
if (!ldapObject.getReadOnlyAttributeNames().contains(attrName) && (isCreate || !ldapObject.getRdnAttributeName().equals(attrName))) {
Object propertyValue = null;
if (property != null) {
// Mapped Java property on the object
propertyValue = property.getValue(attributedType);
} else {
// Not mapped property. So fallback to attribute
org.keycloak.federation.ldap.idm.model.Attribute<?> attribute = attributedType.getAttribute(propertyName);
if (attribute != null) {
propertyValue = attribute.getValue();
if (String.class.isInstance(attrValue)) {
entryAttributes.put(attrName, attrValue);
} else if (Collection.class.isInstance(attrValue)) {
BasicAttribute attr = new BasicAttribute(attrName);
Collection<String> valueCollection = (Collection<String>) attr;
for (String val : valueCollection) {
attr.add(val);
}
}
if (AttributedType.class.isInstance(propertyValue)) {
AttributedType referencedType = (AttributedType) propertyValue;
propertyValue = getBindingDN(referencedType, true);
entryAttributes.put(attr);
} else if (attrValue == null || attrValue.toString().trim().length() == 0) {
entryAttributes.put(attrName, LDAPConstants.EMPTY_ATTRIBUTE_VALUE);
} else {
if (propertyValue == null || isNullOrEmpty(propertyValue.toString())) {
propertyValue = EMPTY_ATTRIBUTE_VALUE;
}
throw new IllegalArgumentException("Unexpected type of value of argument " + attrName + ". Value is " + attrValue);
}
entryAttributes.put(mappedProperties.get(propertyName), propertyValue);
}
}
// Don't extract object classes for update
if (isCreate) {
LDAPMappingConfiguration ldapEntryConfig = getMappingConfig(attributedType.getClass());
BasicAttribute objectClassAttribute = new BasicAttribute(LDAPConstants.OBJECT_CLASS);
for (String objectClassValue : ldapEntryConfig.getObjectClasses()) {
for (String objectClassValue : ldapObject.getObjectClasses()) {
objectClassAttribute.add(objectClassValue);
if (objectClassValue.equals(LDAPConstants.GROUP_OF_NAMES)
|| objectClassValue.equals(LDAPConstants.GROUP_OF_ENTRIES)
|| objectClassValue.equals(LDAPConstants.GROUP_OF_UNIQUE_NAMES)) {
entryAttributes.put(LDAPConstants.MEMBER, EMPTY_ATTRIBUTE_VALUE);
entryAttributes.put(LDAPConstants.MEMBER, LDAPConstants.EMPTY_ATTRIBUTE_VALUE);
}
}
@ -656,49 +529,24 @@ public class LDAPIdentityStore implements IdentityStore {
return entryAttributes;
}
// TODO: Move class StringUtil from SAML module
public static boolean isNullOrEmpty(String str) {
return str == null || str.isEmpty();
}
private LDAPMappingConfiguration getMappingConfig(Class<? extends AttributedType> attributedType) {
LDAPMappingConfiguration mappingConfig = getConfig().getMappingConfig(attributedType);
if (mappingConfig == null) {
throw new ModelException("Not mapped type [" + attributedType + "].");
}
return mappingConfig;
}
public String getBindingDN(AttributedType attributedType, boolean appendBaseDN) {
/*public String getBindingDN(IdentityType attributedType, boolean appendBaseDN) {
LDAPMappingConfiguration mappingConfig = getMappingConfig(attributedType.getClass());
Property<String> idProperty = mappingConfig.getIdProperty();
String baseDN;
if (mappingConfig.getBaseDN() == null || !appendBaseDN) {
baseDN = "";
} else {
baseDN = LDAPConstants.COMMA + getBaseDN(attributedType);
}
Property<String> bindingProperty = mappingConfig.getBindingProperty();
String bindingAttribute;
String dn;
Property<String> bindingDnAttributeProperty = mappingConfig.getBindingDnProperty();
String bindingAttributeName = mappingConfig.getMappedAttributes().get(bindingDnAttributeProperty.getName());
String bindingAttributeValue = mappingConfig.getBindingDnProperty().getValue(attributedType);
if (bindingProperty == null) {
bindingAttribute = mappingConfig.getMappedProperties().get(idProperty.getName());
dn = idProperty.getValue(attributedType);
} else {
bindingAttribute = mappingConfig.getMappedProperties().get(bindingProperty.getName());
dn = mappingConfig.getBindingProperty().getValue(attributedType);
}
return bindingAttribute + LDAPConstants.EQUAL + dn + baseDN;
return bindingAttributeName + LDAPConstants.EQUAL + bindingAttributeValue + baseDN;
}
private String getBaseDN(AttributedType attributedType) {
private String getBaseDN(IdentityType attributedType) {
LDAPMappingConfiguration mappingConfig = getMappingConfig(attributedType.getClass());
String baseDN = mappingConfig.getBaseDN();
String parentDN = mappingConfig.getParentMapping().get(mappingConfig.getIdProperty().getValue(attributedType));
@ -706,12 +554,12 @@ public class LDAPIdentityStore implements IdentityStore {
if (parentDN != null) {
baseDN = parentDN;
} else {
Property<AttributedType> parentProperty = PropertyQueries
.<AttributedType>createQuery(attributedType.getClass())
Property<IdentityType> parentProperty = PropertyQueries
.<IdentityType>createQuery(attributedType.getClass())
.addCriteria(new TypedPropertyCriteria(attributedType.getClass())).getFirstResult();
if (parentProperty != null) {
AttributedType parentType = parentProperty.getValue(attributedType);
IdentityType parentType = parentProperty.getValue(attributedType);
if (parentType != null) {
Property<String> parentIdProperty = getMappingConfig(parentType.getClass()).getIdProperty();
@ -729,24 +577,20 @@ public class LDAPIdentityStore implements IdentityStore {
}
}
if (baseDN == null) {
baseDN = getConfig().getBaseDN();
}
return baseDN;
}
protected void addToParentAsMember(final AttributedType attributedType) {
protected void addToParentAsMember(final IdentityType attributedType) {
LDAPMappingConfiguration entryConfig = getMappingConfig(attributedType.getClass());
if (entryConfig.getParentMembershipAttributeName() != null) {
Property<AttributedType> parentProperty = PropertyQueries
.<AttributedType>createQuery(attributedType.getClass())
Property<IdentityType> parentProperty = PropertyQueries
.<IdentityType>createQuery(attributedType.getClass())
.addCriteria(new TypedPropertyCriteria(attributedType.getClass()))
.getFirstResult();
if (parentProperty != null) {
AttributedType parentType = parentProperty.getValue(attributedType);
IdentityType parentType = parentProperty.getValue(attributedType);
if (parentType != null) {
Attributes attributes = this.operationManager.getAttributes(parentType.getId(), getBaseDN(parentType), entryConfig);
@ -758,21 +602,22 @@ public class LDAPIdentityStore implements IdentityStore {
}
}
}
}
} */
protected String getEntryIdentifier(final AttributedType attributedType) {
protected String getEntryIdentifier(final LDAPObject ldapObject) {
try {
// we need this to retrieve the entry's identifier from the ldap server
List<SearchResult> search = this.operationManager.search(getBaseDN(attributedType), "(" + getBindingDN(attributedType, false) + ")", getMappingConfig(attributedType.getClass()));
Attribute id = search.get(0).getAttributes().get(getConfig().getUniqueIdentifierAttributeName());
String uuidAttrName = getConfig().getUuidAttributeName();
List<SearchResult> search = this.operationManager.search(ldapObject.getDn().toString(), "(" + ldapObject.getDn().getFirstRdn() + ")", Arrays.asList(uuidAttrName), SearchControls.OBJECT_SCOPE);
Attribute id = search.get(0).getAttributes().get(getConfig().getUuidAttributeName());
if (id == null) {
throw new ModelException("Could not retrieve identifier for entry [" + getBindingDN(attributedType, true) + "].");
throw new ModelException("Could not retrieve identifier for entry [" + ldapObject.getDn().toString() + "].");
}
return this.operationManager.decodeEntryUUID(id.get());
} catch (NamingException ne) {
throw new ModelException("Could not add type [" + attributedType + "].", ne);
throw new ModelException("Could not retrieve identifier for entry [" + ldapObject.getDn().toString() + "].");
}
}
}

View file

@ -1,188 +0,0 @@
package org.keycloak.federation.ldap.idm.store.ldap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.keycloak.federation.ldap.idm.model.AttributedType;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.ModelException;
/**
* A configuration for the LDAP store.
*
* @author anil saldhana
* @since Sep 6, 2012
*/
public class LDAPIdentityStoreConfiguration {
private String ldapURL;
private String factoryName = "com.sun.jndi.ldap.LdapCtxFactory";
private String authType = "simple";
private String protocol;
private String bindDN;
private String bindCredential;
private boolean activeDirectory;
private Properties connectionProperties;
private boolean pagination;
private String uniqueIdentifierAttributeName;
private boolean userAccountControlsAfterPasswordUpdate;
private String baseDN;
private Map<Class<? extends AttributedType>, LDAPMappingConfiguration> mappingConfig = new HashMap<Class<? extends AttributedType>, LDAPMappingConfiguration>();
public String getLdapURL() {
return this.ldapURL;
}
public String getFactoryName() {
return this.factoryName;
}
public String getAuthType() {
return this.authType;
}
public String getBaseDN() {
return this.baseDN;
}
public String getBindDN() {
return this.bindDN;
}
public String getBindCredential() {
return this.bindCredential;
}
public boolean isActiveDirectory() {
return this.activeDirectory;
}
public Properties getConnectionProperties() {
return this.connectionProperties;
}
public LDAPMappingConfiguration mappingConfig(Class<? extends AttributedType> clazz) {
LDAPMappingConfiguration mappingConfig = new LDAPMappingConfiguration(clazz);
this.mappingConfig.put(clazz, mappingConfig);
return mappingConfig;
}
public Class<? extends AttributedType> getSupportedTypeByBaseDN(String entryDN, List<String> objectClasses) {
String entryBaseDN = entryDN.substring(entryDN.indexOf(LDAPConstants.COMMA) + 1);
for (LDAPMappingConfiguration mappingConfig : this.mappingConfig.values()) {
if (mappingConfig.getBaseDN() != null) {
if (mappingConfig.getBaseDN().equalsIgnoreCase(entryDN)
|| mappingConfig.getParentMapping().values().contains(entryDN)) {
return mappingConfig.getMappedClass();
}
if (mappingConfig.getBaseDN().equalsIgnoreCase(entryBaseDN)
|| mappingConfig.getParentMapping().values().contains(entryBaseDN)) {
return mappingConfig.getMappedClass();
}
}
}
for (LDAPMappingConfiguration mappingConfig : this.mappingConfig.values()) {
for (String objectClass : objectClasses) {
if (mappingConfig.getObjectClasses().contains(objectClass)) {
return mappingConfig.getMappedClass();
}
}
}
throw new ModelException("No type found with Base DN [" + entryDN + "] or objectClasses [" + objectClasses + ".");
}
public LDAPMappingConfiguration getMappingConfig(Class<? extends AttributedType> attributedType) {
for (LDAPMappingConfiguration mappingConfig : this.mappingConfig.values()) {
if (attributedType.equals(mappingConfig.getMappedClass())) {
return mappingConfig;
}
}
return null;
}
public String getProtocol() {
return protocol;
}
public String getUniqueIdentifierAttributeName() {
return uniqueIdentifierAttributeName;
}
public boolean isPagination() {
return pagination;
}
public boolean isUserAccountControlsAfterPasswordUpdate() {
return userAccountControlsAfterPasswordUpdate;
}
public LDAPIdentityStoreConfiguration setLdapURL(String ldapURL) {
this.ldapURL = ldapURL;
return this;
}
public LDAPIdentityStoreConfiguration setFactoryName(String factoryName) {
this.factoryName = factoryName;
return this;
}
public LDAPIdentityStoreConfiguration setAuthType(String authType) {
this.authType = authType;
return this;
}
public LDAPIdentityStoreConfiguration setProtocol(String protocol) {
this.protocol = protocol;
return this;
}
public LDAPIdentityStoreConfiguration setBindDN(String bindDN) {
this.bindDN = bindDN;
return this;
}
public LDAPIdentityStoreConfiguration setBindCredential(String bindCredential) {
this.bindCredential = bindCredential;
return this;
}
public LDAPIdentityStoreConfiguration setActiveDirectory(boolean activeDirectory) {
this.activeDirectory = activeDirectory;
return this;
}
public LDAPIdentityStoreConfiguration setPagination(boolean pagination) {
this.pagination = pagination;
return this;
}
public LDAPIdentityStoreConfiguration setConnectionProperties(Properties connectionProperties) {
this.connectionProperties = connectionProperties;
return this;
}
public LDAPIdentityStoreConfiguration setUniqueIdentifierAttributeName(String uniqueIdentifierAttributeName) {
this.uniqueIdentifierAttributeName = uniqueIdentifierAttributeName;
return this;
}
public LDAPIdentityStoreConfiguration setUserAccountControlsAfterPasswordUpdate(boolean userAccountControlsAfterPasswordUpdate) {
this.userAccountControlsAfterPasswordUpdate = userAccountControlsAfterPasswordUpdate;
return this;
}
public LDAPIdentityStoreConfiguration setBaseDN(String baseDN) {
this.baseDN = baseDN;
return this;
}
}

View file

@ -1,231 +0,0 @@
package org.keycloak.federation.ldap.idm.store.ldap;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Member;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.keycloak.federation.ldap.idm.model.Attribute;
import org.keycloak.federation.ldap.idm.model.AttributedType;
import org.keycloak.federation.ldap.idm.model.IdentityType;
import org.keycloak.models.ModelException;
import org.keycloak.models.utils.reflection.NamedPropertyCriteria;
import org.keycloak.models.utils.reflection.Property;
import org.keycloak.models.utils.reflection.PropertyQueries;
/**
* @author pedroigor
*/
public class LDAPMappingConfiguration {
private final Class<? extends AttributedType> mappedClass;
private Set<String> objectClasses;
private String baseDN;
private final Map<String, String> mappedProperties = new HashMap<String, String>();
private Property<String> idProperty;
private Class<? extends AttributedType> relatedAttributedType;
private String parentMembershipAttributeName;
private Map<String, String> parentMapping = new HashMap<String, String>();
private final Set<String> readOnlyAttributes = new HashSet<String>();
private int hierarchySearchDepth;
private Property<String> bindingProperty;
public LDAPMappingConfiguration(Class<? extends AttributedType> mappedClass) {
this.mappedClass = mappedClass;
}
public Class<? extends AttributedType> getMappedClass() {
return this.mappedClass;
}
public Set<String> getObjectClasses() {
return this.objectClasses;
}
public String getBaseDN() {
return this.baseDN;
}
public Map<String, String> getMappedProperties() {
return this.mappedProperties;
}
public Property<String> getIdProperty() {
return this.idProperty;
}
public Property<String> getBindingProperty() {
return this.bindingProperty;
}
public Class<? extends AttributedType> getRelatedAttributedType() {
return this.relatedAttributedType;
}
public String getParentMembershipAttributeName() {
return this.parentMembershipAttributeName;
}
public Map<String, String> getParentMapping() {
return this.parentMapping;
}
public Set<String> getReadOnlyAttributes() {
return this.readOnlyAttributes;
}
public int getHierarchySearchDepth() {
return this.hierarchySearchDepth;
}
private Property getBindingProperty(final String bindingPropertyName) {
Property bindingProperty = PropertyQueries
.<String>createQuery(getMappedClass())
.addCriteria(new NamedPropertyCriteria(bindingPropertyName)).getFirstResult();
// We don't have Java property, so actually delegate to setAttribute/getAttribute
if (bindingProperty == null) {
bindingProperty = new Property<String>() {
@Override
public String getName() {
return bindingPropertyName;
}
@Override
public Type getBaseType() {
return null;
}
@Override
public Class<String> getJavaClass() {
return String.class;
}
@Override
public AnnotatedElement getAnnotatedElement() {
return null;
}
@Override
public Member getMember() {
return null;
}
@Override
public String getValue(Object instance) {
if (!(instance instanceof AttributedType)) {
throw new IllegalStateException("Instance [ " + instance + " ] not an instance of AttributedType");
}
AttributedType attributedType = (AttributedType) instance;
Attribute<String> attr = attributedType.getAttribute(bindingPropertyName);
return attr!=null ? attr.getValue() : null;
}
@Override
public void setValue(Object instance, String value) {
if (!(instance instanceof AttributedType)) {
throw new IllegalStateException("Instance [ " + instance + " ] not an instance of AttributedType");
}
AttributedType attributedType = (AttributedType) instance;
attributedType.setAttribute(new Attribute(bindingPropertyName, value));
}
@Override
public Class<?> getDeclaringClass() {
return null;
}
@Override
public boolean isReadOnly() {
return false;
}
@Override
public void setAccessible() {
}
@Override
public boolean isAnnotationPresent(Class annotation) {
return false;
}
};
}
return bindingProperty;
}
public LDAPMappingConfiguration setObjectClasses(Set<String> objectClasses) {
this.objectClasses = objectClasses;
return this;
}
public LDAPMappingConfiguration setBaseDN(String baseDN) {
this.baseDN = baseDN;
return this;
}
public LDAPMappingConfiguration addAttributeMapping(String userAttributeName, String ldapAttributeName) {
this.mappedProperties.put(userAttributeName, ldapAttributeName);
return this;
}
public LDAPMappingConfiguration addReadOnlyAttributeMapping(String userAttributeName, String ldapAttributeName) {
this.mappedProperties.put(userAttributeName, ldapAttributeName);
this.readOnlyAttributes.add(userAttributeName);
return this;
}
public LDAPMappingConfiguration setIdPropertyName(String idPropertyName) {
if (idPropertyName != null) {
this.idProperty = PropertyQueries
.<String>createQuery(getMappedClass())
.addCriteria(new NamedPropertyCriteria(idPropertyName)).getFirstResult();
} else {
this.idProperty = null;
}
if (IdentityType.class.isAssignableFrom(mappedClass) && idProperty == null) {
throw new ModelException("Id attribute not mapped to any property of [" + mappedClass + "].");
}
// Binding property is idProperty by default
if (this.bindingProperty == null) {
this.bindingProperty = this.idProperty;
}
return this;
}
public LDAPMappingConfiguration setRelatedAttributedType(Class<? extends AttributedType> relatedAttributedType) {
this.relatedAttributedType = relatedAttributedType;
return this;
}
public LDAPMappingConfiguration setParentMembershipAttributeName(String parentMembershipAttributeName) {
this.parentMembershipAttributeName = parentMembershipAttributeName;
return this;
}
public LDAPMappingConfiguration setParentMapping(Map<String, String> parentMapping) {
this.parentMapping = parentMapping;
return this;
}
public LDAPMappingConfiguration setHierarchySearchDepth(int hierarchySearchDepth) {
this.hierarchySearchDepth = hierarchySearchDepth;
return this;
}
public LDAPMappingConfiguration setBindingPropertyName(String bindingPropertyName) {
this.bindingProperty = getBindingProperty(bindingPropertyName);
return this;
}
}

View file

@ -2,6 +2,7 @@ package org.keycloak.federation.ldap.idm.store.ldap;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@ -29,13 +30,11 @@ import javax.naming.ldap.PagedResultsControl;
import javax.naming.ldap.PagedResultsResponseControl;
import org.jboss.logging.Logger;
import org.keycloak.federation.ldap.idm.model.IdentityType;
import org.keycloak.federation.ldap.idm.query.internal.IdentityQuery;
import org.keycloak.federation.ldap.LDAPConfig;
import org.keycloak.federation.ldap.idm.query.internal.LDAPIdentityQuery;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.ModelException;
import static javax.naming.directory.SearchControls.SUBTREE_SCOPE;
/**
* <p>This class provides a set of operations to manage LDAP trees.</p>
*
@ -46,10 +45,10 @@ public class LDAPOperationManager {
private static final Logger logger = Logger.getLogger(LDAPOperationManager.class);
private final LDAPIdentityStoreConfiguration config;
private final LDAPConfig config;
private final Map<String, Object> connectionProperties;
public LDAPOperationManager(LDAPIdentityStoreConfiguration config) throws NamingException {
public LDAPOperationManager(LDAPConfig config) throws NamingException {
this.config = config;
this.connectionProperties = Collections.unmodifiableMap(createConnectionProperties());
}
@ -121,56 +120,29 @@ public class LDAPOperationManager {
/**
* <p>
* Searches the LDAP tree.
* Removes the object from the LDAP tree
* </p>
*
* @param baseDN
* @param id
*
* @return
*/
public void removeEntryById(final String baseDN, final String id, final LDAPMappingConfiguration mappingConfiguration) {
final String filter = getFilterById(baseDN, id);
public void removeEntry(final String entryDn) {
try {
final SearchControls cons = getSearchControls(mappingConfiguration);
execute(new LdapOperation<SearchResult>() {
@Override
public SearchResult execute(LdapContext context) throws NamingException {
NamingEnumeration<SearchResult> result = context.search(baseDN, filter, cons);
if (result.hasMore()) {
SearchResult sr = result.next();
if (logger.isDebugEnabled()) {
logger.debugf("Removing entry [%s] with attributes: [", sr.getNameInNamespace());
NamingEnumeration<? extends Attribute> all = sr.getAttributes().getAll();
while (all.hasMore()) {
Attribute attribute = all.next();
logger.debugf(" %s = %s", attribute.getID(), attribute.get());
}
logger.debugf("]");
}
destroySubcontext(context, sr.getNameInNamespace());
if (logger.isDebugEnabled()) {
logger.debugf("Removing entry with DN [%s]", entryDn);
}
result.close();
destroySubcontext(context, entryDn);
return null;
}
});
} catch (NamingException e) {
throw new ModelException("Could not remove entry from DN [" + baseDN + "] and id [" + id + "]", e);
throw new ModelException("Could not remove entry from DN [" + entryDn + "]", e);
}
}
public List<SearchResult> search(final String baseDN, final String filter, LDAPMappingConfiguration mappingConfiguration) throws NamingException {
public List<SearchResult> search(final String baseDN, final String filter, Collection<String> returningAttributes, int searchScope) throws NamingException {
final List<SearchResult> result = new ArrayList<SearchResult>();
final SearchControls cons = getSearchControls(mappingConfiguration);
final SearchControls cons = getSearchControls(returningAttributes, searchScope);
try {
return execute(new LdapOperation<List<SearchResult>>() {
@ -193,16 +165,16 @@ public class LDAPOperationManager {
}
}
public <V extends IdentityType> List<SearchResult> searchPaginated(final String baseDN, final String filter, LDAPMappingConfiguration mappingConfiguration, final IdentityQuery<V> identityQuery) throws NamingException {
public List<SearchResult> searchPaginated(final String baseDN, final String filter, final LDAPIdentityQuery identityQuery) throws NamingException {
final List<SearchResult> result = new ArrayList<SearchResult>();
final SearchControls cons = getSearchControls(mappingConfiguration);
final SearchControls cons = getSearchControls(identityQuery.getReturningLdapAttributes(), identityQuery.getSearchScope());
try {
return execute(new LdapOperation<List<SearchResult>>() {
@Override
public List<SearchResult> execute(LdapContext context) throws NamingException {
try {
byte[] cookie = (byte[])identityQuery.getPaginationContext();
byte[] cookie = identityQuery.getPaginationContext();
PagedResultsControl pagedControls = new PagedResultsControl(identityQuery.getLimit(), cookie, Control.CRITICAL);
context.setRequestControls(new Control[] { pagedControls });
@ -238,19 +210,19 @@ public class LDAPOperationManager {
}
}
private SearchControls getSearchControls(LDAPMappingConfiguration mappingConfiguration) {
private SearchControls getSearchControls(Collection<String> returningAttributes, int searchScope) {
final SearchControls cons = new SearchControls();
cons.setSearchScope(SUBTREE_SCOPE);
cons.setSearchScope(searchScope);
cons.setReturningObjFlag(false);
Set<String> returningAttributes = getReturningAttributes(mappingConfiguration);
returningAttributes = getReturningAttributes(returningAttributes);
cons.setReturningAttributes(returningAttributes.toArray(new String[returningAttributes.size()]));
return cons;
}
public String getFilterById(String baseDN, String id) {
public String getFilterById(String id) {
String filter = null;
if (this.config.isActiveDirectory()) {
@ -266,24 +238,24 @@ public class LDAPOperationManager {
byte[] objectGUID = (byte[]) attributes.get(LDAPConstants.OBJECT_GUID).get();
filter = "(&(objectClass=*)(" + getUniqueIdentifierAttributeName() + LDAPConstants.EQUAL + LDAPUtil.convertObjectGUIToByteString(objectGUID) + "))";
filter = "(&(objectClass=*)(" + getUuidAttributeName() + LDAPConstants.EQUAL + LDAPUtil.convertObjectGUIToByteString(objectGUID) + "))";
} catch (NamingException ne) {
return filter;
}
}
if (filter == null) {
filter = "(&(objectClass=*)(" + getUniqueIdentifierAttributeName() + LDAPConstants.EQUAL + id + "))";
filter = "(&(objectClass=*)(" + getUuidAttributeName() + LDAPConstants.EQUAL + id + "))";
}
return filter;
}
public SearchResult lookupById(final String baseDN, final String id, final LDAPMappingConfiguration mappingConfiguration) {
final String filter = getFilterById(baseDN, id);
public SearchResult lookupById(final String baseDN, final String id, final Collection<String> returningAttributes) {
final String filter = getFilterById(id);
try {
final SearchControls cons = getSearchControls(mappingConfiguration);
final SearchControls cons = getSearchControls(returningAttributes, this.config.getSearchScope());
return execute(new LdapOperation<SearchResult>() {
@Override
@ -446,15 +418,15 @@ public class LDAPOperationManager {
}
}
private String getUniqueIdentifierAttributeName() {
return this.config.getUniqueIdentifierAttributeName();
private String getUuidAttributeName() {
return this.config.getUuidAttributeName();
}
public Attributes getAttributes(final String entryUUID, final String baseDN, LDAPMappingConfiguration mappingConfiguration) {
SearchResult search = lookupById(baseDN, entryUUID, mappingConfiguration);
public Attributes getAttributes(final String entryUUID, final String baseDN, Set<String> returningAttributes) {
SearchResult search = lookupById(baseDN, entryUUID, returningAttributes);
if (search == null) {
throw new ModelException("Couldn't find item with entryUUID [" + entryUUID + "] and baseDN [" + baseDN + "]");
throw new ModelException("Couldn't find item with ID [" + entryUUID + " under base DN [" + baseDN + "]");
}
return search.getAttributes();
@ -482,7 +454,7 @@ public class LDAPOperationManager {
env.put(Context.INITIAL_CONTEXT_FACTORY, this.config.getFactoryName());
env.put(Context.SECURITY_AUTHENTICATION, this.config.getAuthType());
String protocol = this.config.getProtocol();
String protocol = this.config.getSecurityProtocol();
if (protocol != null) {
env.put(Context.SECURITY_PROTOCOL, protocol);
@ -501,7 +473,7 @@ public class LDAPOperationManager {
env.put(Context.SECURITY_CREDENTIALS, bindCredential);
}
String url = this.config.getLdapURL();
String url = this.config.getConnectionUrl();
if (url == null) {
throw new RuntimeException("url");
@ -509,9 +481,13 @@ public class LDAPOperationManager {
env.put(Context.PROVIDER_URL, url);
// Just dump the additional properties
Properties additionalProperties = this.config.getConnectionProperties();
String connectionPooling = this.config.getConnectionPooling();
if (connectionPooling != null) {
env.put("com.sun.jndi.ldap.connect.pool", connectionPooling);
}
// Just dump the additional properties
Properties additionalProperties = this.config.getAdditionalConnectionProperties();
if (additionalProperties != null) {
for (Object key : additionalProperties.keySet()) {
env.put(key.toString(), additionalProperties.getProperty(key.toString()));
@ -533,8 +509,6 @@ public class LDAPOperationManager {
LdapContext context = null;
try {
// TODO: Remove this
logger.info("Executing operation: " + operation);
context = createLdapContext();
return operation.execute(context);
} catch (NamingException ne) {
@ -555,27 +529,13 @@ public class LDAPOperationManager {
R execute(LdapContext context) throws NamingException;
}
private Set<String> getReturningAttributes(final LDAPMappingConfiguration mappingConfiguration) {
Set<String> returningAttributes = new HashSet<String>();
private Set<String> getReturningAttributes(final Collection<String> returningAttributes) {
Set<String> result = new HashSet<String>();
if (mappingConfiguration != null) {
returningAttributes.addAll(mappingConfiguration.getMappedProperties().values());
result.addAll(returningAttributes);
result.add(getUuidAttributeName());
result.add(LDAPConstants.OBJECT_CLASS);
returningAttributes.add(mappingConfiguration.getParentMembershipAttributeName());
// for (LDAPMappingConfiguration relationshipConfig : this.config.getRelationshipConfigs()) {
// if (relationshipConfig.getRelatedAttributedType().equals(mappingConfiguration.getMappedClass())) {
// returningAttributes.addAll(relationshipConfig.getMappedProperties().values());
// }
// }
} else {
returningAttributes.add("*");
}
returningAttributes.add(getUniqueIdentifierAttributeName());
returningAttributes.add(LDAPConstants.CREATE_TIMESTAMP);
returningAttributes.add(LDAPConstants.OBJECT_CLASS);
return returningAttributes;
return result;
}
}

View file

@ -0,0 +1,32 @@
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;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public abstract class AbstractLDAPFederationMapper implements LDAPFederationMapper {
@Override
public void close() {
}
@Override
public UserFederationMapper create(KeycloakSession session) {
throw new RuntimeException("UNSUPPORTED METHOD");
}
@Override
public void init(Config.Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
}

View file

@ -0,0 +1,28 @@
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.UserFederationMapperModel;
import org.keycloak.models.UserModel;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
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 invoke proxy on federation provider
UserModel proxy(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapObject, UserModel delegate);
// Called before any LDAPIdentityQuery is executed
void beforeLDAPQuery(UserFederationMapperModel mapperModel, LDAPIdentityQuery query);
}

View file

@ -0,0 +1,139 @@
package org.keycloak.federation.ldap.mappers;
import java.util.HashMap;
import java.util.Map;
import org.jboss.logging.Logger;
import org.keycloak.federation.ldap.LDAPFederationProvider;
import org.keycloak.federation.ldap.idm.model.LDAPObject;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakTransaction;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.UserModelDelegate;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class TxAwareLDAPUserModelDelegate extends UserModelDelegate {
private static final Logger logger = Logger.getLogger(TxAwareLDAPUserModelDelegate.class);
protected LDAPFederationProvider provider;
protected LDAPObject ldapObject;
protected LDAPTransaction transaction;
// Map of allowed writable UserModel attributes to LDAP attributes. Includes UserModel properties (firstName, lastName, email, ...)
private final Map<String, String> mappedAttributes = new HashMap<String, String>();
public TxAwareLDAPUserModelDelegate(UserModel delegate, LDAPFederationProvider provider, LDAPObject ldapObject) {
super(delegate);
this.provider = provider;
this.ldapObject = ldapObject;
}
public void addMappedAttribute(String userModelAttrName, String ldapAttrName) {
mappedAttributes.put(userModelAttrName, ldapAttrName);
}
@Override
public void setAttribute(String name, String value) {
setLDAPAttribute(name, value);
super.setAttribute(name, value);
}
@Override
public void setEmail(String email) {
setLDAPAttribute(UserModel.EMAIL, email);
super.setEmail(email);
}
@Override
public void setLastName(String lastName) {
setLDAPAttribute(UserModel.LAST_NAME, lastName);
super.setLastName(lastName);
}
@Override
public void setFirstName(String firstName) {
setLDAPAttribute(UserModel.FIRST_NAME, firstName);
super.setFirstName(firstName);
}
protected void setLDAPAttribute(String modelAttrName, String value) {
String ldapAttrName = mappedAttributes.get(modelAttrName);
if (ldapAttrName != null) {
if (logger.isTraceEnabled()) {
logger.tracef("Pushing user attribute to LDAP. Model attribute name: %s, LDAP attribute name: %s, Attribute value: %s", modelAttrName, ldapAttrName, value);
}
if (transaction == null) {
transaction = new LDAPTransaction();
provider.getSession().getTransaction().enlistAfterCompletion(transaction);
}
ldapObject.setAttribute(ldapAttrName, value);
}
}
protected class LDAPTransaction implements KeycloakTransaction {
protected TransactionState state = TransactionState.NOT_STARTED;
@Override
public void begin() {
if (state != TransactionState.NOT_STARTED) {
throw new IllegalStateException("Transaction already started");
}
state = TransactionState.STARTED;
}
@Override
public void commit() {
if (state != TransactionState.STARTED) {
throw new IllegalStateException("Transaction in illegal state for commit: " + state);
}
if (logger.isTraceEnabled()) {
logger.trace("Transaction commit! Updating LDAP attributes for object " + ldapObject.getDn().toString() + ", attributes: " + ldapObject.getAttributes());
}
provider.getLdapIdentityStore().update(ldapObject);
state = TransactionState.FINISHED;
}
@Override
public void rollback() {
if (state != TransactionState.STARTED && state != TransactionState.ROLLBACK_ONLY) {
throw new IllegalStateException("Transaction in illegal state for rollback: " + state);
}
logger.warn("Transaction rollback! Ignoring LDAP updates for object " + ldapObject.getDn().toString());
state = TransactionState.FINISHED;
}
@Override
public void setRollbackOnly() {
state = TransactionState.ROLLBACK_ONLY;
}
@Override
public boolean getRollbackOnly() {
return state == TransactionState.ROLLBACK_ONLY;
}
@Override
public boolean isActive() {
return state == TransactionState.STARTED || state == TransactionState.ROLLBACK_ONLY;
}
}
protected enum TransactionState {
NOT_STARTED, STARTED, ROLLBACK_ONLY, FINISHED
}
}

View file

@ -0,0 +1,150 @@
package org.keycloak.federation.ldap.mappers;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import org.keycloak.federation.ldap.LDAPFederationProvider;
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.models.UserFederationMapperModel;
import org.keycloak.models.UserFederationProvider;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.reflection.Property;
import org.keycloak.models.utils.reflection.PropertyCriteria;
import org.keycloak.models.utils.reflection.PropertyQueries;
import org.keycloak.provider.ProviderConfigProperty;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class UserAttributeLDAPFederationMapper extends AbstractLDAPFederationMapper {
private static final Map<String, Property<Object>> userModelProperties;
static {
userModelProperties = PropertyQueries.createQuery(UserModel.class).addCriteria(new PropertyCriteria() {
@Override
public boolean methodMatches(Method m) {
if ((m.getName().startsWith("get") || m.getName().startsWith("is")) && m.getParameterTypes().length > 0) {
return false;
}
return true;
}
}).getResultList();
}
public static final String USER_MODEL_ATTRIBUTE = "user.model.attribute";
public static final String LDAP_ATTRIBUTE = "ldap.attribute";
public static final String READ_ONLY = "read.only";
@Override
public String getHelpText() {
return "Some help text TODO";
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return null;
}
@Override
public String getId() {
return "ldap-user-attribute-mapper";
}
@Override
public void importUserFromLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapObject, UserModel user, boolean isCreate) {
String userModelAttrName = mapperModel.getConfig().get(USER_MODEL_ATTRIBUTE);
String ldapAttrName = mapperModel.getConfig().get(LDAP_ATTRIBUTE);
Serializable ldapAttrValue = ldapObject.getAttribute(ldapAttrName);
if (ldapAttrValue != null) {
Property<Object> userModelProperty = userModelProperties.get(userModelAttrName);
if (userModelProperty != null) {
// we have java property on UserModel
userModelProperty.setValue(user, ldapAttrValue);
} else {
// we don't have java property. Let's just setAttribute
user.setAttribute(userModelAttrName, (String) ldapAttrValue);
}
}
}
@Override
public void registerUserToLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapObject, UserModel localUser) {
String userModelAttrName = mapperModel.getConfig().get(USER_MODEL_ATTRIBUTE);
String ldapAttrName = mapperModel.getConfig().get(LDAP_ATTRIBUTE);
Property<Object> userModelProperty = userModelProperties.get(userModelAttrName);
Object attrValue;
if (userModelProperty != null) {
// we have java property on UserModel
attrValue = userModelProperty.getValue(localUser);
} else {
// we don't have java property. Let's just setAttribute
attrValue = localUser.getAttribute(userModelAttrName);
}
ldapObject.setAttribute(ldapAttrName, (Serializable) attrValue);
if (isReadOnly(mapperModel)) {
ldapObject.addReadOnlyAttributeName(ldapAttrName);
}
}
@Override
public UserModel proxy(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapObject, UserModel delegate) {
if (ldapProvider.getEditMode() == UserFederationProvider.EditMode.WRITABLE && !isReadOnly(mapperModel)) {
// This assumes that mappers are sorted by type! Maybe improve...
TxAwareLDAPUserModelDelegate txDelegate;
if (delegate instanceof TxAwareLDAPUserModelDelegate) {
// We will reuse already existing delegate and just register our mapped attribute in existing transaction.
txDelegate = (TxAwareLDAPUserModelDelegate) delegate;
} else {
txDelegate = new TxAwareLDAPUserModelDelegate(delegate, ldapProvider, ldapObject);
}
String userModelAttrName = mapperModel.getConfig().get(USER_MODEL_ATTRIBUTE);
String ldapAttrName = mapperModel.getConfig().get(LDAP_ATTRIBUTE);
txDelegate.addMappedAttribute(userModelAttrName, ldapAttrName);
return txDelegate;
} else {
return delegate;
}
}
@Override
public void beforeLDAPQuery(UserFederationMapperModel mapperModel, LDAPIdentityQuery query) {
String userModelAttrName = mapperModel.getConfig().get(USER_MODEL_ATTRIBUTE);
String ldapAttrName = mapperModel.getConfig().get(LDAP_ATTRIBUTE);
// Add mapped attribute to returning ldap attributes
query.addReturningLdapAttribute(ldapAttrName);
if (isReadOnly(mapperModel)) {
query.addReturningReadOnlyLdapAttribute(ldapAttrName);
}
// Change conditions and use ldapAttribute instead of userModel
for (Condition condition : query.getConditions()) {
QueryParameter param = condition.getParameter();
if (param != null && param.getName().equals(userModelAttrName)) {
param.setName(ldapAttrName);
}
}
}
private boolean isReadOnly(UserFederationMapperModel mapperModel) {
String readOnly = mapperModel.getConfig().get(READ_ONLY);
return Boolean.parseBoolean(readOnly);
}
}

View file

@ -0,0 +1 @@
org.keycloak.federation.ldap.mappers.UserAttributeLDAPFederationMapper

View file

@ -33,9 +33,9 @@ When starting the server it can also import a realm from a json file:
The Keycloak test server can load resources directly from the filesystem instead of the classpath. This allows editing html, styles and updating images without restarting the server. To make the server use resources from the filesystem start with:
mvn exec:java -Pkeycloak-server -Dresources
You can also specify the theme directory used by the server with:
You can also specify the theme directory used by the server wit
mvn exec:java -Pkeycloak-server -Dresourcesh:
mvn exec:java -Pkeycloak-server -Dkeycloak.theme.dir=<PATH TO THEMES DIR>

View file

@ -13,14 +13,17 @@ public class LDAPConstants {
public static final String VENDOR_NOVELL_EDIRECTORY="edirectory" ;
public static final String USERNAME_LDAP_ATTRIBUTE = "usernameLDAPAttribute";
public static final String RDN_LDAP_ATTRIBUTE = "rdnLDAPAttribute";
public static final String USER_OBJECT_CLASSES = "userObjectClasses";
public static final String CONNECTION_URL = "connectionUrl";
public static final String BASE_DN = "baseDn";
public static final String USER_DN_SUFFIX = "userDnSuffix";
public static final String SECURITY_PROTOCOL = "securityProtocol";
public static final String USER_DNS = "userDns";
public static final String BIND_DN = "bindDn";
public static final String BIND_CREDENTIAL = "bindCredential";
public static final String SEARCH_SCOPE = "searchScope";
public static final String UUID_ATTRIBUTE_NAME = "uuidAttributeName";
public static final String CONNECTION_POOLING = "connectionPooling";
public static final String PAGINATION = "pagination";
@ -40,12 +43,15 @@ public class LDAPConstants {
public static final String LDAP_ID = "LDAP_ID";
public static final String LDAP_ENTRY_DN = "LDAP_ENTRY_DN";
// String used in config to divide more possible values (for example more userDns), which are saved in DB as single string
public static final String CONFIG_DIVIDER = ":::";
// Those are forked from Picketlink
public static final String GIVENNAME = "givenname";
public static final String CN = "cn";
public static final String SN = "sn";
public static final String EMAIL = "mail";
public static final String POSTAL_CODE = "postalCode";
public static final String MEMBER = "member";
public static final String MEMBER_OF = "memberOf";
public static final String OBJECT_CLASS = "objectclass";
@ -57,7 +63,7 @@ public class LDAPConstants {
public static final String COMMA = ",";
public static final String EQUAL = "=";
public static final String SPACE_STRING = " ";
public static final String EMPTY_ATTRIBUTE_VALUE = " ";
public static final String CUSTOM_ATTRIBUTE_ENABLED = "enabled";
public static final String CUSTOM_ATTRIBUTE_CREATE_DATE = "createDate";

View file

@ -189,6 +189,8 @@ public interface RealmModel extends RoleContainerModel {
void removeUserFederationProvider(UserFederationProviderModel provider);
void setUserFederationProviders(List<UserFederationProviderModel> providers);
List<UserFederationMapperModel> getUserFederationMappers();
String getLoginTheme();
void setLoginTheme(String name);

View file

@ -85,7 +85,7 @@ public class UserFederationManager implements UserProvider {
protected void validateUser(RealmModel realm, UserModel user) {
UserFederationProvider link = getFederationLink(realm, user);
if (link != null && !link.isValid(user)) {
if (link != null && !link.isValid(realm, user)) {
deleteInvalidUser(realm, user);
throw new IllegalStateException("Federated user no longer valid");
}
@ -107,18 +107,13 @@ public class UserFederationManager implements UserProvider {
}
}
protected boolean isValid(RealmModel realm, UserModel user) {
UserFederationProvider link = getFederationLink(realm, user);
if (link != null) return link.isValid(user);
return true;
}
protected UserModel validateAndProxyUser(RealmModel realm, UserModel user) {
UserFederationProvider link = getFederationLink(realm, user);
if (link != null) {
if (isValid(realm, user)) {
return link.proxy(user);
UserModel validatedProxyUser = link.validateAndProxy(realm, user);
if (validatedProxyUser != null) {
return validatedProxyUser;
} else {
deleteInvalidUser(realm, user);
return null;

View file

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

View file

@ -0,0 +1,63 @@
package org.keycloak.models;
import java.util.Map;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class UserFederationMapperModel {
protected String id;
protected String name;
protected String federationMapperId;
protected Map<String, String> config;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getFederationMapperId() {
return federationMapperId;
}
public void setFederationMapperId(String federationMapperId) {
this.federationMapperId = federationMapperId;
}
public Map<String, String> getConfig() {
return config;
}
public void setConfig(Map<String, String> config) {
this.config = config;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
UserFederationMapperModel that = (UserFederationMapperModel) o;
if (!id.equals(that.id)) return false;
return true;
}
@Override
public int hashCode() {
return id.hashCode();
}
}

View file

@ -0,0 +1,26 @@
package org.keycloak.models;
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.provider.Spi;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class UserFederationMapperSpi implements Spi {
@Override
public String getName() {
return "userFederationMapper";
}
@Override
public Class<? extends Provider> getProviderClass() {
return UserFederationMapper.class;
}
@Override
public Class<? extends ProviderFactory> getProviderFactoryClass() {
return UserFederationMapper.class;
}
}

View file

@ -42,16 +42,16 @@ public interface UserFederationProvider extends Provider {
/**
* Gives the provider an option to proxy UserModels loaded from local storage.
* Gives the provider an option to validate if user still exists in federation backend and then proxy UserModel loaded from local storage.
* This method is called whenever a UserModel is pulled from local storage.
* For example, the LDAP provider proxies the UserModel and does on-demand synchronization with
* LDAP whenever UserModel update methods are invoked. It also overrides UserModel.updateCredential for the
* credential types it supports
*
* @param local
* @return
* @return null if user is no longer valid or proxy object otherwise
*/
UserModel proxy(UserModel local);
UserModel validateAndProxy(RealmModel realm, UserModel local);
/**
* Should user registrations be synchronized with this provider?
@ -120,7 +120,7 @@ public interface UserFederationProvider extends Provider {
* @param local
* @return
*/
boolean isValid(UserModel local);
boolean isValid(RealmModel realm, UserModel local);
/**
* What UserCredentialModel types should be handled by this provider for this user? Keycloak will only call
@ -153,7 +153,7 @@ public interface UserFederationProvider extends Provider {
/**
* Validate credentials of unknown user. The authenticated user is recognized based on provided credentials and returned back in CredentialValidationOutput
* @param realm
* @param input
* @param credential
* @return
*/
CredentialValidationOutput validCredentials(RealmModel realm, UserCredentialModel credential);

View file

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

View file

@ -27,6 +27,7 @@ import org.keycloak.models.PasswordPolicy;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RequiredCredentialModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserFederationMapperModel;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.entities.ClientEntity;
@ -1184,4 +1185,9 @@ public class RealmAdapter implements RealmModel {
return mapping;
}
@Override
public List<UserFederationMapperModel> getUserFederationMappers() {
throw new IllegalStateException("Not yet implemented");
}
}

View file

@ -5,11 +5,14 @@ import org.keycloak.enums.SslRequired;
import org.keycloak.models.ClientModel;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.PasswordPolicy;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RequiredCredentialModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserFederationMapperModel;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.cache.entities.CachedRealm;
import org.keycloak.models.utils.KeycloakModelUtils;
@ -17,6 +20,7 @@ import java.security.Key;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
@ -948,4 +952,55 @@ public class RealmAdapter implements RealmModel {
return null;
}
@Override
public List<UserFederationMapperModel> getUserFederationMappers() {
// TODO: Some hardcoded stuff...
List<UserFederationMapperModel> mappers = new ArrayList<UserFederationMapperModel>();
mappers.add(createMapperModel("usn", "usernameMapper", "ldap-user-attribute-mapper",
"user.model.attribute", UserModel.USERNAME,
"ldap.attribute", LDAPConstants.UID));
mappers.add(createMapperModel("fn", "firstNameMapper", "ldap-user-attribute-mapper",
"user.model.attribute", UserModel.FIRST_NAME,
"ldap.attribute", LDAPConstants.CN));
mappers.add(createMapperModel("ln", "lastNameMapper", "ldap-user-attribute-mapper",
"user.model.attribute", UserModel.LAST_NAME,
"ldap.attribute", LDAPConstants.SN));
mappers.add(createMapperModel("emailMpr", "emailMapper", "ldap-user-attribute-mapper",
"user.model.attribute", UserModel.EMAIL,
"ldap.attribute", LDAPConstants.EMAIL));
mappers.add(createMapperModel("postalCodeMpr", "postalCodeMapper", "ldap-user-attribute-mapper",
"user.model.attribute", "postal_code",
"ldap.attribute", LDAPConstants.POSTAL_CODE));
mappers.add(createMapperModel("createdDateMpr", "createTimeStampMapper", "ldap-user-attribute-mapper",
"user.model.attribute", LDAPConstants.CREATE_TIMESTAMP,
"ldap.attribute", LDAPConstants.CREATE_TIMESTAMP,
"read.only", "true"));
mappers.add(createMapperModel("modifyDateMpr", "modifyTimeStampMapper", "ldap-user-attribute-mapper",
"user.model.attribute", LDAPConstants.MODIFY_TIMESTAMP,
"ldap.attribute", LDAPConstants.MODIFY_TIMESTAMP,
"read.only", "true"));
return mappers;
}
private static UserFederationMapperModel createMapperModel(String id, String name, String mapperId, String... config) {
UserFederationMapperModel mapperModel = new UserFederationMapperModel();
mapperModel.setId(id);
mapperModel.setName(name);
mapperModel.setFederationMapperId(mapperId);
Map<String, String> configMap = new HashMap<String, String>();
String key = null;
for (String configEntry : config) {
if (key == null) {
key = configEntry;
} else {
configMap.put(key, configEntry);
key = null;
}
}
mapperModel.setConfig(configMap);
return mapperModel;
}
}

View file

@ -9,6 +9,7 @@ import org.keycloak.models.PasswordPolicy;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RequiredCredentialModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserFederationMapperModel;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.jpa.entities.ClientEntity;
import org.keycloak.models.jpa.entities.IdentityProviderEntity;
@ -1351,4 +1352,9 @@ public class RealmAdapter implements RealmModel {
return mapping;
}
@Override
public List<UserFederationMapperModel> getUserFederationMappers() {
throw new IllegalStateException("Not yet implemented");
}
}

View file

@ -14,6 +14,7 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.RealmProvider;
import org.keycloak.models.RequiredCredentialModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserFederationMapperModel;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.entities.IdentityProviderEntity;
import org.keycloak.models.entities.IdentityProviderMapperEntity;
@ -1213,4 +1214,8 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
return mapping;
}
@Override
public List<UserFederationMapperModel> getUserFederationMappers() {
throw new IllegalStateException("Not yet implemented");
}
}

View file

@ -22,7 +22,7 @@ public class DummyUserFederationProvider implements UserFederationProvider {
private static Map<String, UserModel> users = new HashMap<String, UserModel>();
@Override
public UserModel proxy(UserModel local) {
public UserModel validateAndProxy(RealmModel realm, UserModel local) {
return local;
}
@ -68,7 +68,7 @@ public class DummyUserFederationProvider implements UserFederationProvider {
}
@Override
public boolean isValid(UserModel local) {
public boolean isValid(RealmModel realm, UserModel local) {
return false;
}

View file

@ -12,8 +12,7 @@ import org.keycloak.OAuth2Constants;
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.LDAPUser;
import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
import org.keycloak.federation.ldap.idm.model.LDAPObject;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.ModelReadOnlyException;
@ -23,6 +22,7 @@ import org.keycloak.models.UserCredentialValueModel;
import org.keycloak.models.UserFederationProvider;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.UserModelDelegate;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.OAuthClient;
@ -53,7 +53,7 @@ public class FederationProvidersIntegrationTest {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
addUser(manager.getSession(), appRealm, "mary", "mary@test.com", "password-app");
FederationTestUtils.addLocalUser(manager.getSession(), appRealm, "mary", "mary@test.com", "password-app");
Map<String,String> ldapConfig = ldapRule.getConfig();
ldapConfig.put(LDAPConstants.SYNC_REGISTRATIONS, "true");
@ -62,13 +62,13 @@ public class FederationProvidersIntegrationTest {
ldapModel = appRealm.addUserFederationProvider(LDAPFederationProviderFactory.PROVIDER_NAME, ldapConfig, 0, "test-ldap", -1, -1, 0);
// Delete all LDAP users and add some new for testing
LDAPIdentityStore ldapStore = getLdapIdentityStore(manager.getSession(), ldapModel);
LDAPUtils.removeAllUsers(ldapStore);
LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel);
LDAPUtils.removeAllUsers(ldapFedProvider, appRealm);
LDAPUser john = LDAPUtils.addUser(ldapStore, "johnkeycloak", "John", "Doe", "john@email.org");
LDAPUtils.updatePassword(ldapStore, john, "Password1");
LDAPObject john = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "johnkeycloak", "John", "Doe", "john@email.org", "1234");
ldapFedProvider.getLdapIdentityStore().updatePassword(john, "Password1");
LDAPUser existing = LDAPUtils.addUser(ldapStore, "existing", "Existing", "Foo", "existing@email.org");
LDAPObject existing = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "existing", "Existing", "Foo", "existing@email.org", "5678");
}
});
@ -108,18 +108,6 @@ public class FederationProvidersIntegrationTest {
//
// }
static UserModel addUser(KeycloakSession session, RealmModel realm, String username, String email, String password) {
UserModel user = session.users().addUser(realm, username);
user.setEmail(email);
user.setEnabled(true);
UserCredentialModel creds = new UserCredentialModel();
creds.setType(CredentialRepresentation.PASSWORD);
creds.setValue(password);
user.updateCredential(creds);
return user;
}
@Test
public void caseSensitiveSearch() {
@ -128,9 +116,6 @@ public class FederationProvidersIntegrationTest {
// This should fail for now due to case-sensitivity
loginPage.login("johnKeycloak", "Password1");
Assert.assertEquals("Invalid username or password.", loginPage.getError());
loginPage.login("John@email.org", "Password1");
Assert.assertEquals("Invalid username or password.", loginPage.getError());
}
@Test
@ -191,6 +176,7 @@ public class FederationProvidersIntegrationTest {
Assert.assertEquals("John", profilePage.getFirstName());
Assert.assertEquals("Doe", profilePage.getLastName());
Assert.assertEquals("john@email.org", profilePage.getEmail());
Assert.assertEquals("1234", profilePage.getPostalCode());
}
@Test
@ -257,7 +243,7 @@ public class FederationProvidersIntegrationTest {
loginPage.clickRegister();
registerPage.assertCurrent();
registerPage.register("firstName", "lastName", "email2@check.cz", "registerUserSuccess2", "Password1", "Password1");
registerPage.register("firstName", "lastName", "email2@check.cz", "registerUserSuccess2", "Password1", "Password1", "non-LDAP-Mapped street", null, null, "78910", null);
Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
KeycloakSession session = keycloakRule.startSession();
@ -267,6 +253,8 @@ public class FederationProvidersIntegrationTest {
Assert.assertNotNull(user);
Assert.assertNotNull(user.getFederationLink());
Assert.assertEquals(user.getFederationLink(), ldapModel.getId());
Assert.assertEquals("78910", user.getAttribute("postal_code"));
Assert.assertEquals("non-LDAP-Mapped street", user.getAttribute("street"));
} finally {
keycloakRule.stopSession(session, false);
}
@ -346,13 +334,14 @@ public class FederationProvidersIntegrationTest {
@Test
public void testSearch() {
KeycloakSession session = keycloakRule.startSession();
LDAPIdentityStore ldapStore = getLdapIdentityStore(session, ldapModel);
try {
RealmModel appRealm = session.realms().getRealmByName("test");
LDAPUtils.addUser(ldapStore, "username1", "John1", "Doel1", "user1@email.org");
LDAPUtils.addUser(ldapStore, "username2", "John2", "Doel2", "user2@email.org");
LDAPUtils.addUser(ldapStore, "username3", "John3", "Doel3", "user3@email.org");
LDAPUtils.addUser(ldapStore, "username4", "John4", "Doel4", "user4@email.org");
LDAPFederationProvider ldapProvider = FederationTestUtils.getLdapProvider(session, ldapModel);
FederationTestUtils.addLDAPUser(ldapProvider, appRealm, "username1", "John1", "Doel1", "user1@email.org", "121");
FederationTestUtils.addLDAPUser(ldapProvider, appRealm, "username2", "John2", "Doel2", "user2@email.org", "122");
FederationTestUtils.addLDAPUser(ldapProvider, appRealm, "username3", "John3", "Doel3", "user3@email.org", "123");
FederationTestUtils.addLDAPUser(ldapProvider, appRealm, "username4", "John4", "Doel4", "user4@email.org", "124");
// Users are not at local store at this moment
Assert.assertNull(session.userStorage().getUserByUsername("username1", appRealm));
@ -362,19 +351,19 @@ public class FederationProvidersIntegrationTest {
// search by username
session.users().searchForUser("username1", appRealm);
SyncProvidersTest.assertUserImported(session.userStorage(), appRealm, "username1", "John1", "Doel1", "user1@email.org");
FederationTestUtils.assertUserImported(session.userStorage(), appRealm, "username1", "John1", "Doel1", "user1@email.org", "121");
// search by email
session.users().searchForUser("user2@email.org", appRealm);
SyncProvidersTest.assertUserImported(session.userStorage(), appRealm, "username2", "John2", "Doel2", "user2@email.org");
FederationTestUtils.assertUserImported(session.userStorage(), appRealm, "username2", "John2", "Doel2", "user2@email.org", "122");
// search by lastName
session.users().searchForUser("Doel3", appRealm);
SyncProvidersTest.assertUserImported(session.userStorage(), appRealm, "username3", "John3", "Doel3", "user3@email.org");
FederationTestUtils.assertUserImported(session.userStorage(), appRealm, "username3", "John3", "Doel3", "user3@email.org", "123");
// search by firstName + lastName
session.users().searchForUser("John4 Doel4", appRealm);
SyncProvidersTest.assertUserImported(session.userStorage(), appRealm, "username4", "John4", "Doel4", "user4@email.org");
FederationTestUtils.assertUserImported(session.userStorage(), appRealm, "username4", "John4", "Doel4", "user4@email.org", "124");
} finally {
keycloakRule.stopSession(session, true);
}
@ -402,7 +391,9 @@ public class FederationProvidersIntegrationTest {
Assert.assertTrue(session.users().validCredentials(appRealm, user, cred));
// LDAP password is still unchanged
Assert.assertTrue(LDAPUtils.validatePassword(getLdapIdentityStore(session, model), user, "Password1"));
LDAPFederationProvider ldapProvider = FederationTestUtils.getLdapProvider(session, model);
LDAPObject ldapUser = ldapProvider.loadLDAPUserByUsername(appRealm, "johnkeycloak");
ldapProvider.getLdapIdentityStore().validatePassword(ldapUser, "Password1");
// ATM it's not permitted to delete user in unsynced mode. Should be user deleted just locally instead?
Assert.assertFalse(session.users().removeUser(appRealm, user));
@ -419,10 +410,4 @@ public class FederationProvidersIntegrationTest {
}
}
static LDAPIdentityStore getLdapIdentityStore(KeycloakSession keycloakSession, UserFederationProviderModel ldapFedModel) {
LDAPFederationProviderFactory ldapProviderFactory = (LDAPFederationProviderFactory) keycloakSession.getKeycloakSessionFactory().getProviderFactory(UserFederationProvider.class, ldapFedModel.getProviderName());
LDAPFederationProvider ldapProvider = ldapProviderFactory.getInstance(keycloakSession, ldapFedModel);
return ldapProvider.getLdapIdentityStore();
}
}

View file

@ -0,0 +1,85 @@
package org.keycloak.testsuite.federation;
import org.junit.Assert;
import org.keycloak.federation.ldap.LDAPFederationProvider;
import org.keycloak.federation.ldap.LDAPFederationProviderFactory;
import org.keycloak.federation.ldap.LDAPUtils;
import org.keycloak.federation.ldap.idm.model.LDAPObject;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserFederationProvider;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserProvider;
import org.keycloak.models.utils.UserModelDelegate;
import org.keycloak.representations.idm.CredentialRepresentation;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
class FederationTestUtils {
public static UserModel addLocalUser(KeycloakSession session, RealmModel realm, String username, String email, String password) {
UserModel user = session.users().addUser(realm, username);
user.setEmail(email);
user.setEnabled(true);
UserCredentialModel creds = new UserCredentialModel();
creds.setType(CredentialRepresentation.PASSWORD);
creds.setValue(password);
user.updateCredential(creds);
return user;
}
public static LDAPObject addLDAPUser(LDAPFederationProvider ldapProvider, RealmModel realm, final String username,
final String firstName, final String lastName, final String email, final String postalCode) {
UserModel helperUser = new UserModelDelegate(null) {
@Override
public String getUsername() {
return username;
}
@Override
public String getFirstName() {
return firstName;
}
@Override
public String getLastName() {
return lastName;
}
@Override
public String getEmail() {
return email;
}
@Override
public String getAttribute(String name) {
if (name == "postal_code") {
return postalCode;
} else {
return null;
}
}
};
return LDAPUtils.addUserToLDAP(ldapProvider, realm, helperUser);
}
public static LDAPFederationProvider getLdapProvider(KeycloakSession keycloakSession, UserFederationProviderModel ldapFedModel) {
LDAPFederationProviderFactory ldapProviderFactory = (LDAPFederationProviderFactory) keycloakSession.getKeycloakSessionFactory().getProviderFactory(UserFederationProvider.class, ldapFedModel.getProviderName());
return ldapProviderFactory.getInstance(keycloakSession, ldapFedModel);
}
public static void assertUserImported(UserProvider userProvider, RealmModel realm, String username, String expectedFirstName, String expectedLastName, String expectedEmail, String expectedPostalCode) {
UserModel user = userProvider.getUserByUsername(username, realm);
Assert.assertNotNull(user);
Assert.assertEquals(expectedFirstName, user.getFirstName());
Assert.assertEquals(expectedLastName, user.getLastName());
Assert.assertEquals(expectedEmail, user.getEmail());
Assert.assertEquals(expectedPostalCode, user.getAttribute("postal_code"));
}
}

View file

@ -7,10 +7,10 @@ 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.LDAPUser;
import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
import org.keycloak.federation.ldap.idm.model.LDAPObject;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.LDAPConstants;
@ -57,12 +57,12 @@ public class SyncProvidersTest {
-1, -1, 0);
// Delete all LDAP users and add 5 new users for testing
LDAPIdentityStore ldapStore = FederationProvidersIntegrationTest.getLdapIdentityStore(manager.getSession(), ldapModel);
LDAPUtils.removeAllUsers(ldapStore);
LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel);
LDAPUtils.removeAllUsers(ldapFedProvider, appRealm);
for (int i=1 ; i<=5 ; i++) {
LDAPUser user = LDAPUtils.addUser(ldapStore, "user" + i, "User" + i + "FN", "User" + i + "LN", "user" + i + "@email.org");
LDAPUtils.updatePassword(ldapStore, user, "Password1");
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");
}
// Add dummy provider
@ -96,11 +96,11 @@ public class SyncProvidersTest {
RealmModel testRealm = session.realms().getRealm("test");
UserProvider userProvider = session.userStorage();
// Assert users imported
assertUserImported(userProvider, testRealm, "user1", "User1FN", "User1LN", "user1@email.org");
assertUserImported(userProvider, testRealm, "user2", "User2FN", "User2LN", "user2@email.org");
assertUserImported(userProvider, testRealm, "user3", "User3FN", "User3LN", "user3@email.org");
assertUserImported(userProvider, testRealm, "user4", "User4FN", "User4LN", "user4@email.org");
assertUserImported(userProvider, testRealm, "user5", "User5FN", "User5LN", "user5@email.org");
FederationTestUtils.assertUserImported(userProvider, testRealm, "user1", "User1FN", "User1LN", "user1@email.org", "121");
FederationTestUtils.assertUserImported(userProvider, testRealm, "user2", "User2FN", "User2LN", "user2@email.org", "122");
FederationTestUtils.assertUserImported(userProvider, testRealm, "user3", "User3FN", "User3LN", "user3@email.org", "123");
FederationTestUtils.assertUserImported(userProvider, testRealm, "user4", "User4FN", "User4LN", "user4@email.org", "124");
FederationTestUtils.assertUserImported(userProvider, testRealm, "user5", "User5FN", "User5LN", "user5@email.org", "125");
// Assert lastSync time updated
Assert.assertTrue(ldapModel.getLastSync() > 0);
@ -117,12 +117,16 @@ public class SyncProvidersTest {
sleep(1000);
// Add user to LDAP and update 'user5' in LDAP
LDAPIdentityStore ldapStore = FederationProvidersIntegrationTest.getLdapIdentityStore(session, ldapModel);
LDAPUtils.addUser(ldapStore, "user6", "User6FN", "User6LN", "user6@email.org");
LDAPUtils.updateUser(ldapStore, "user5", "User5FNUpdated", "User5LNUpdated", "user5Updated@email.org");
LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel);
FederationTestUtils.addLDAPUser(ldapFedProvider, testRealm, "user6", "User6FN", "User6LN", "user6@email.org", "126");
LDAPObject ldapUser5 = ldapFedProvider.loadLDAPUserByUsername(testRealm, "user5");
// NOTE: Changing LDAP attributes directly here
ldapUser5.setAttribute(LDAPConstants.EMAIL, "user5Updated@email.org");
ldapUser5.setAttribute(LDAPConstants.POSTAL_CODE, "521");
ldapFedProvider.getLdapIdentityStore().update(ldapUser5);
// Assert still old users in local provider
assertUserImported(userProvider, testRealm, "user5", "User5FN", "User5LN", "user5@email.org");
FederationTestUtils.assertUserImported(userProvider, testRealm, "user5", "User5FN", "User5LN", "user5@email.org", "125");
Assert.assertNull(userProvider.getUserByUsername("user6", testRealm));
// Trigger partial sync
@ -138,8 +142,8 @@ public class SyncProvidersTest {
RealmModel testRealm = session.realms().getRealm("test");
UserProvider userProvider = session.userStorage();
// Assert users updated in local provider
assertUserImported(userProvider, testRealm, "user5", "User5FNUpdated", "User5LNUpdated", "user5Updated@email.org");
assertUserImported(userProvider, testRealm, "user6", "User6FN", "User6LN", "user6@email.org");
FederationTestUtils.assertUserImported(userProvider, testRealm, "user5", "User5FN", "User5LN", "user5Updated@email.org", "521");
FederationTestUtils.assertUserImported(userProvider, testRealm, "user6", "User6FN", "User6LN", "user6@email.org", "126");
} finally {
keycloakRule.stopSession(session, false);
}
@ -189,12 +193,4 @@ public class SyncProvidersTest {
Assert.assertEquals(syncResult.getUpdated(), expectedUpdated);
Assert.assertEquals(syncResult.getRemoved(), expectedRemoved);
}
public static void assertUserImported(UserProvider userProvider, RealmModel realm, String username, String expectedFirstName, String expectedLastName, String expectedEmail) {
UserModel user = userProvider.getUserByUsername(username, realm);
Assert.assertNotNull(user);
Assert.assertEquals(expectedFirstName, user.getFirstName());
Assert.assertEquals(expectedLastName, user.getLastName());
Assert.assertEquals(expectedEmail, user.getEmail());
}
}

View file

@ -28,10 +28,9 @@ public class LDAPConfiguration {
static {
PROP_MAPPINGS.put(LDAPConstants.CONNECTION_URL, "idm.test.ldap.connection.url");
PROP_MAPPINGS.put(LDAPConstants.BASE_DN, "idm.test.ldap.base.dn");
PROP_MAPPINGS.put("rolesDnSuffix", "idm.test.ldap.roles.dn.suffix");
PROP_MAPPINGS.put("groupDnSuffix", "idm.test.ldap.group.dn.suffix");
PROP_MAPPINGS.put(LDAPConstants.USER_DN_SUFFIX, "idm.test.ldap.user.dn.suffix");
PROP_MAPPINGS.put(LDAPConstants.USER_DNS, "idm.test.ldap.user.dn.suffix");
PROP_MAPPINGS.put(LDAPConstants.BIND_DN, "idm.test.ldap.bind.dn");
PROP_MAPPINGS.put(LDAPConstants.BIND_CREDENTIAL, "idm.test.ldap.bind.credential");
PROP_MAPPINGS.put(LDAPConstants.VENDOR, "idm.test.ldap.vendor");
@ -53,10 +52,9 @@ public class LDAPConfiguration {
PROP_MAPPINGS.put(KerberosConstants.USE_KERBEROS_FOR_PASSWORD_AUTHENTICATION, "idm.test.kerberos.use.kerberos.for.password.authentication");
DEFAULT_VALUES.put(LDAPConstants.CONNECTION_URL, "ldap://localhost:10389");
DEFAULT_VALUES.put(LDAPConstants.BASE_DN, "dc=keycloak,dc=org");
DEFAULT_VALUES.put("rolesDnSuffix", "ou=Roles,dc=keycloak,dc=org");
DEFAULT_VALUES.put("groupDnSuffix", "ou=Groups,dc=keycloak,dc=org");
DEFAULT_VALUES.put(LDAPConstants.USER_DN_SUFFIX, "ou=People,dc=keycloak,dc=org");
DEFAULT_VALUES.put(LDAPConstants.USER_DNS, "ou=People,dc=keycloak,dc=org");
DEFAULT_VALUES.put(LDAPConstants.BIND_DN, "uid=admin,ou=system");
DEFAULT_VALUES.put(LDAPConstants.BIND_CREDENTIAL, "secret");
DEFAULT_VALUES.put(LDAPConstants.VENDOR, LDAPConstants.VENDOR_OTHER);

View file

@ -44,6 +44,9 @@ public class AccountUpdateProfilePage extends AbstractAccountPage {
@FindBy(id = "email")
private WebElement emailInput;
@FindBy(id = "user.attributes.postal_code")
private WebElement postalCodeInput;
@FindBy(id = "referrer")
private WebElement backToApplicationLink;
@ -84,6 +87,10 @@ public class AccountUpdateProfilePage extends AbstractAccountPage {
return lastNameInput.getAttribute("value");
}
public String getPostalCode() {
return postalCodeInput.getAttribute("value");
}
public String getEmail() {
return emailInput.getAttribute("value");
}

View file

@ -23,6 +23,7 @@ package org.keycloak.testsuite.pages;
import org.junit.Assert;
import org.openqa.selenium.By;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
@ -56,6 +57,25 @@ public class RegisterPage extends AbstractPage {
@FindBy(className = "feedback-error")
private WebElement loginErrorMessage;
public void register(String firstName, String lastName, String email, String username, String password, String passwordConfirm,
String street, String cityOrLocality, String stateOrRegion, String zipOrPostalCode, String country) {
fillExtendedField("street", street);
fillExtendedField("locality", cityOrLocality);
fillExtendedField("region", stateOrRegion);
fillExtendedField("postal_code", zipOrPostalCode);
fillExtendedField("country", country);
register(firstName, lastName, email, username, password, passwordConfirm);
}
private void fillExtendedField(String fieldName, String value) {
WebElement field = driver.findElement(By.id("user.attributes." + fieldName));
field.clear();
if (value != null) {
field.sendKeys(value);
}
}
public void register(String firstName, String lastName, String email, String username, String password, String passwordConfirm) {
firstNameInput.clear();
if (firstName != null) {