KEYCLOAK-599 Added UserFederationMappers. Added UserAttributeLDAPFederationMapper
This commit is contained in:
parent
61c35265a6
commit
1490f106f2
55 changed files with 1886 additions and 1985 deletions
|
@ -113,7 +113,7 @@ public abstract class BasePropertiesFederationProvider implements UserFederation
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean isValid(UserModel local) {
|
public boolean isValid(RealmModel realm, UserModel local) {
|
||||||
return properties.containsKey(local.getUsername());
|
return properties.containsKey(local.getUsername());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,8 +25,12 @@ public class ClasspathPropertiesFederationProvider extends BasePropertiesFederat
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public UserModel proxy(UserModel local) {
|
public UserModel validateAndProxy(RealmModel realm, UserModel local) {
|
||||||
return new ReadonlyUserModelProxy(local);
|
if (isValid(realm, local)) {
|
||||||
|
return new ReadonlyUserModelProxy(local);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -27,8 +27,12 @@ public class FilePropertiesFederationProvider extends BasePropertiesFederationPr
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public UserModel proxy(UserModel local) {
|
public UserModel validateAndProxy(RealmModel realm, UserModel local) {
|
||||||
return new WritableUserModelProxy(local, this);
|
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(), "");
|
properties.setProperty(user.getUsername(), "");
|
||||||
save();
|
save();
|
||||||
}
|
}
|
||||||
return proxy(user);
|
return validateAndProxy(realm, user);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -43,7 +43,11 @@ public class KerberosFederationProvider implements UserFederationProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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) {
|
if (kerberosConfig.getEditMode() == EditMode.READ_ONLY) {
|
||||||
return new ReadOnlyKerberosUserModelDelegate(local, this);
|
return new ReadOnlyKerberosUserModelDelegate(local, this);
|
||||||
} else {
|
} else {
|
||||||
|
@ -102,7 +106,7 @@ public class KerberosFederationProvider implements UserFederationProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isValid(UserModel local) {
|
public boolean isValid(RealmModel realm, UserModel local) {
|
||||||
// KerberosUsernamePasswordAuthenticator.isUserAvailable is an overhead, so avoid it for now
|
// KerberosUsernamePasswordAuthenticator.isUserAvailable is an overhead, so avoid it for now
|
||||||
|
|
||||||
String kerberosPrincipal = local.getUsername() + "@" + kerberosConfig.getKerberosRealm();
|
String kerberosPrincipal = local.getUsername() + "@" + kerberosConfig.getKerberosRealm();
|
||||||
|
@ -219,13 +223,16 @@ public class KerberosFederationProvider implements UserFederationProvider {
|
||||||
if (!model.getId().equals(user.getFederationLink())) {
|
if (!model.getId().equals(user.getFederationLink())) {
|
||||||
logger.warn("User with username " + username + " already exists, but is not linked to provider [" + model.getDisplayName() + "]");
|
logger.warn("User with username " + username + " already exists, but is not linked to provider [" + model.getDisplayName() + "]");
|
||||||
return null;
|
return null;
|
||||||
} else if (isValid(user)) {
|
|
||||||
return proxy(user);
|
|
||||||
} else {
|
} else {
|
||||||
logger.warn("User with username " + username + " already exists and is linked to provider [" + model.getDisplayName() +
|
UserModel proxied = validateAndProxy(realm, user);
|
||||||
"] but kerberos principal is not correct. Kerberos principal on user is: " + user.getAttribute(KERBEROS_PRINCIPAL));
|
if (proxied != null) {
|
||||||
logger.warn("Will re-create user");
|
return proxied;
|
||||||
session.userStorage().removeUser(realm, 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -248,6 +255,6 @@ public class KerberosFederationProvider implements UserFederationProvider {
|
||||||
user.addRequiredAction(UserModel.RequiredAction.UPDATE_PROFILE);
|
user.addRequiredAction(UserModel.RequiredAction.UPDATE_PROFILE);
|
||||||
}
|
}
|
||||||
|
|
||||||
return proxy(user);
|
return validateAndProxy(realm, user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,11 +3,14 @@ package org.keycloak.federation.ldap;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.federation.kerberos.impl.KerberosUsernamePasswordAuthenticator;
|
import org.keycloak.federation.kerberos.impl.KerberosUsernamePasswordAuthenticator;
|
||||||
import org.keycloak.federation.kerberos.impl.SPNEGOAuthenticator;
|
import org.keycloak.federation.kerberos.impl.SPNEGOAuthenticator;
|
||||||
import org.keycloak.federation.ldap.idm.model.LDAPUser;
|
import org.keycloak.federation.ldap.idm.model.LDAPObject;
|
||||||
import org.keycloak.federation.ldap.idm.query.internal.IdentityQuery;
|
import org.keycloak.federation.ldap.idm.query.Condition;
|
||||||
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.federation.ldap.idm.store.ldap.LDAPIdentityStore;
|
||||||
import org.keycloak.federation.ldap.kerberos.LDAPProviderKerberosConfig;
|
import org.keycloak.federation.ldap.kerberos.LDAPProviderKerberosConfig;
|
||||||
|
import org.keycloak.federation.ldap.mappers.LDAPFederationMapper;
|
||||||
import org.keycloak.models.CredentialValidationOutput;
|
import org.keycloak.models.CredentialValidationOutput;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.LDAPConstants;
|
import org.keycloak.models.LDAPConstants;
|
||||||
|
@ -16,12 +19,15 @@ import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
import org.keycloak.models.UserCredentialModel;
|
import org.keycloak.models.UserCredentialModel;
|
||||||
import org.keycloak.models.UserCredentialValueModel;
|
import org.keycloak.models.UserCredentialValueModel;
|
||||||
|
import org.keycloak.models.UserFederationMapper;
|
||||||
|
import org.keycloak.models.UserFederationMapperModel;
|
||||||
import org.keycloak.models.UserFederationProvider;
|
import org.keycloak.models.UserFederationProvider;
|
||||||
import org.keycloak.models.UserFederationProviderModel;
|
import org.keycloak.models.UserFederationProviderModel;
|
||||||
import org.keycloak.models.UserFederationSyncResult;
|
import org.keycloak.models.UserFederationSyncResult;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.constants.KerberosConstants;
|
import org.keycloak.constants.KerberosConstants;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
@ -78,17 +84,40 @@ public class LDAPFederationProvider implements UserFederationProvider {
|
||||||
return this.ldapIdentityStore;
|
return this.ldapIdentityStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public EditMode getEditMode() {
|
||||||
|
return editMode;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserModel proxy(UserModel local) {
|
public UserModel validateAndProxy(RealmModel realm, UserModel local) {
|
||||||
switch (editMode) {
|
LDAPObject ldapObject = loadAndValidateUser(realm, local);
|
||||||
case READ_ONLY:
|
if (ldapObject == null) {
|
||||||
return new ReadonlyLDAPUserModelDelegate(local, this);
|
return null;
|
||||||
case WRITABLE:
|
}
|
||||||
return new WritableLDAPUserModelDelegate(local, this);
|
|
||||||
case UNSYNCED:
|
return proxy(realm, local, ldapObject);
|
||||||
return new UnsyncedLDAPUserModelDelegate(local, this);
|
}
|
||||||
}
|
|
||||||
return local;
|
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
|
@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 (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");
|
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());
|
LDAPObject ldapObject = LDAPUtils.addUserToLDAP(this, realm, user);
|
||||||
user.setAttribute(LDAPConstants.LDAP_ID, ldapUser.getId());
|
user.setAttribute(LDAPConstants.LDAP_ID, ldapObject.getUuid());
|
||||||
user.setAttribute(LDAPConstants.LDAP_ENTRY_DN, ldapUser.getEntryDN());
|
user.setAttribute(LDAPConstants.LDAP_ENTRY_DN, ldapObject.getDn().toString());
|
||||||
return proxy(user);
|
|
||||||
|
return proxy(realm, user, ldapObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -133,16 +163,24 @@ public class LDAPFederationProvider implements UserFederationProvider {
|
||||||
return false;
|
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
|
@Override
|
||||||
public List<UserModel> searchByAttributes(Map<String, String> attributes, RealmModel realm, int maxResults) {
|
public List<UserModel> searchByAttributes(Map<String, String> attributes, RealmModel realm, int maxResults) {
|
||||||
List<UserModel> searchResults =new LinkedList<UserModel>();
|
List<UserModel> searchResults =new LinkedList<UserModel>();
|
||||||
|
|
||||||
Map<String, LDAPUser> ldapUsers = searchLDAP(attributes, maxResults);
|
List<LDAPObject> ldapUsers = searchLDAP(realm, attributes, maxResults);
|
||||||
for (LDAPUser ldapUser : ldapUsers.values()) {
|
for (LDAPObject ldapUser : ldapUsers) {
|
||||||
if (session.userStorage().getUserByUsername(ldapUser.getLoginName(), realm) == null) {
|
String ldapUsername = LDAPUtils.getUsername(ldapUser, this.ldapIdentityStore.getConfig());
|
||||||
|
if (session.userStorage().getUserByUsername(ldapUsername, realm) == null) {
|
||||||
UserModel imported = importUserFromLDAP(realm, ldapUser);
|
UserModel imported = importUserFromLDAP(realm, ldapUser);
|
||||||
searchResults.add(imported);
|
searchResults.add(imported);
|
||||||
}
|
}
|
||||||
|
@ -151,106 +189,120 @@ public class LDAPFederationProvider implements UserFederationProvider {
|
||||||
return searchResults;
|
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)) {
|
if (attributes.containsKey(USERNAME)) {
|
||||||
LDAPUser user = LDAPUtils.getUser(this.ldapIdentityStore, attributes.get(USERNAME));
|
LDAPObject user = loadLDAPUserByUsername(realm, attributes.get(USERNAME));
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
results.put(user.getLoginName(), user);
|
results.add(user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attributes.containsKey(EMAIL)) {
|
if (attributes.containsKey(EMAIL)) {
|
||||||
LDAPUser user = queryByEmail(attributes.get(EMAIL));
|
LDAPObject user = queryByEmail(realm, attributes.get(EMAIL));
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
results.put(user.getLoginName(), user);
|
results.add(user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attributes.containsKey(FIRST_NAME) || attributes.containsKey(LAST_NAME)) {
|
if (attributes.containsKey(FIRST_NAME) || attributes.containsKey(LAST_NAME)) {
|
||||||
IdentityQueryBuilder queryBuilder = this.ldapIdentityStore.createQueryBuilder();
|
LDAPIdentityQuery ldapQuery = LDAPUtils.createQueryForUserSearch(this, realm);
|
||||||
IdentityQuery<LDAPUser> query = queryBuilder.createIdentityQuery(LDAPUser.class);
|
LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder();
|
||||||
|
|
||||||
|
// Mapper should replace parameter with correct LDAP mapped attributes
|
||||||
if (attributes.containsKey(FIRST_NAME)) {
|
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)) {
|
if (attributes.containsKey(LAST_NAME)) {
|
||||||
query.where(queryBuilder.equal(LDAPUser.LAST_NAME, attributes.get(LAST_NAME)));
|
ldapQuery.where(conditionsBuilder.equal(new QueryParameter(LAST_NAME), attributes.get(LAST_NAME)));
|
||||||
}
|
|
||||||
query.setLimit(maxResults);
|
|
||||||
List<LDAPUser> users = query.getResultList();
|
|
||||||
for (LDAPUser user : users) {
|
|
||||||
results.put(user.getLoginName(), user);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<LDAPObject> ldapObjects = ldapQuery.getResultList();
|
||||||
|
results.addAll(ldapObjects);
|
||||||
}
|
}
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
public boolean isValid(UserModel local) {
|
* @param local
|
||||||
LDAPUser ldapUser = LDAPUtils.getUser(this.ldapIdentityStore, local.getUsername());
|
* @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) {
|
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
|
@Override
|
||||||
public UserModel getUserByUsername(RealmModel realm, String username) {
|
public UserModel getUserByUsername(RealmModel realm, String username) {
|
||||||
LDAPUser ldapUser = LDAPUtils.getUser(this.ldapIdentityStore, username);
|
LDAPObject ldapUser = loadLDAPUserByUsername(realm, username);
|
||||||
if (ldapUser == null) {
|
if (ldapUser == null) {
|
||||||
return 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);
|
return importUserFromLDAP(realm, ldapUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected UserModel importUserFromLDAP(RealmModel realm, LDAPUser ldapUser) {
|
protected UserModel importUserFromLDAP(RealmModel realm, LDAPObject ldapUser) {
|
||||||
String email = (ldapUser.getEmail() != null && ldapUser.getEmail().trim().length() > 0) ? ldapUser.getEmail() : null;
|
String ldapUsername = LDAPUtils.getUsername(ldapUser, ldapIdentityStore.getConfig());
|
||||||
|
|
||||||
if (ldapUser.getLoginName() == null) {
|
if (ldapUsername == null) {
|
||||||
throw new ModelException("User returned from LDAP has null username! Check configuration of your LDAP mappings. ID of user from LDAP: " + ldapUser.getId());
|
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.setEnabled(true);
|
||||||
imported.setEmail(email);
|
|
||||||
imported.setFirstName(ldapUser.getFirstName());
|
List<UserFederationMapperModel> federationMappers = realm.getUserFederationMappers();
|
||||||
imported.setLastName(ldapUser.getLastName());
|
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.setFederationLink(model.getId());
|
||||||
imported.setAttribute(LDAPConstants.LDAP_ID, ldapUser.getId());
|
imported.setAttribute(LDAPConstants.LDAP_ID, ldapUser.getUuid());
|
||||||
imported.setAttribute(LDAPConstants.LDAP_ENTRY_DN, ldapUser.getEntryDN());
|
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(),
|
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());
|
ldapUser.getUuid(), userDN);
|
||||||
return proxy(imported);
|
return proxy(realm, imported, ldapUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected LDAPUser queryByEmail(String email) {
|
protected LDAPObject queryByEmail(RealmModel realm, String email) {
|
||||||
return LDAPUtils.getUserByEmail(this.ldapIdentityStore, 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
|
@Override
|
||||||
public UserModel getUserByEmail(RealmModel realm, String email) {
|
public UserModel getUserByEmail(RealmModel realm, String email) {
|
||||||
LDAPUser ldapUser = queryByEmail(email);
|
LDAPObject ldapUser = queryByEmail(realm, email);
|
||||||
if (ldapUser == null) {
|
if (ldapUser == null) {
|
||||||
return 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);
|
return importUserFromLDAP(realm, ldapUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -262,16 +314,18 @@ public class LDAPFederationProvider implements UserFederationProvider {
|
||||||
@Override
|
@Override
|
||||||
public void preRemove(RealmModel realm, RoleModel role) {
|
public void preRemove(RealmModel realm, RoleModel role) {
|
||||||
// complete I don't think we have to do anything here
|
// complete I don't think we have to do anything here
|
||||||
|
// TODO: requires implementation... Maybe mappers callback
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean validPassword(UserModel user, String password) {
|
public boolean validPassword(RealmModel realm, UserModel user, String password) {
|
||||||
if (kerberosConfig.isAllowKerberosAuthentication() && kerberosConfig.isUseKerberosForPasswordAuthentication()) {
|
if (kerberosConfig.isAllowKerberosAuthentication() && kerberosConfig.isUseKerberosForPasswordAuthentication()) {
|
||||||
// Use Kerberos JAAS (Krb5LoginModule)
|
// Use Kerberos JAAS (Krb5LoginModule)
|
||||||
KerberosUsernamePasswordAuthenticator authenticator = factory.createKerberosUsernamePasswordAuthenticator(kerberosConfig);
|
KerberosUsernamePasswordAuthenticator authenticator = factory.createKerberosUsernamePasswordAuthenticator(kerberosConfig);
|
||||||
return authenticator.validUser(user.getUsername(), password);
|
return authenticator.validUser(user.getUsername(), password);
|
||||||
} else {
|
} else {
|
||||||
// Use Naming LDAP API
|
// 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) {
|
public boolean validCredentials(RealmModel realm, UserModel user, List<UserCredentialModel> input) {
|
||||||
for (UserCredentialModel cred : input) {
|
for (UserCredentialModel cred : input) {
|
||||||
if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
|
if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
|
||||||
return validPassword(user, cred.getValue());
|
return validPassword(realm, user, cred.getValue());
|
||||||
} else {
|
} else {
|
||||||
return false; // invalid cred type
|
return false; // invalid cred type
|
||||||
}
|
}
|
||||||
|
@ -335,11 +389,11 @@ public class LDAPFederationProvider implements UserFederationProvider {
|
||||||
public void close() {
|
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();
|
UserFederationSyncResult syncResult = new UserFederationSyncResult();
|
||||||
|
|
||||||
for (LDAPUser ldapUser : ldapUsers) {
|
for (LDAPObject ldapUser : ldapUsers) {
|
||||||
String username = ldapUser.getLoginName();
|
String username = LDAPUtils.getUsername(ldapUser, ldapIdentityStore.getConfig());
|
||||||
UserModel currentUser = session.userStorage().getUserByUsername(username, realm);
|
UserModel currentUser = session.userStorage().getUserByUsername(username, realm);
|
||||||
|
|
||||||
if (currentUser == null) {
|
if (currentUser == null) {
|
||||||
|
@ -347,12 +401,15 @@ public class LDAPFederationProvider implements UserFederationProvider {
|
||||||
importUserFromLDAP(realm, ldapUser);
|
importUserFromLDAP(realm, ldapUser);
|
||||||
syncResult.increaseAdded();
|
syncResult.increaseAdded();
|
||||||
} else {
|
} 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
|
// Update keycloak user
|
||||||
String email = (ldapUser.getEmail() != null && ldapUser.getEmail().trim().length() > 0) ? ldapUser.getEmail() : null;
|
List<UserFederationMapperModel> federationMappers = realm.getUserFederationMappers();
|
||||||
currentUser.setEmail(email);
|
for (UserFederationMapperModel mapperModel : federationMappers) {
|
||||||
currentUser.setFirstName(ldapUser.getFirstName());
|
LDAPFederationMapper ldapMapper = getMapper(mapperModel);
|
||||||
currentUser.setLastName(ldapUser.getLastName());
|
ldapMapper.importUserFromLDAP(mapperModel, this, ldapUser, currentUser, false);
|
||||||
|
}
|
||||||
|
|
||||||
logger.debugf("Updated user from LDAP: %s", currentUser.getUsername());
|
logger.debugf("Updated user from LDAP: %s", currentUser.getUsername());
|
||||||
syncResult.increaseUpdated();
|
syncResult.increaseUpdated();
|
||||||
} else {
|
} else {
|
||||||
|
@ -378,13 +435,16 @@ public class LDAPFederationProvider implements UserFederationProvider {
|
||||||
if (!model.getId().equals(user.getFederationLink())) {
|
if (!model.getId().equals(user.getFederationLink())) {
|
||||||
logger.warnf("User with username [%s] already exists, but is not linked to provider [%s]", username, model.getDisplayName());
|
logger.warnf("User with username [%s] already exists, but is not linked to provider [%s]", username, model.getDisplayName());
|
||||||
return null;
|
return null;
|
||||||
} else if (isValid(user)) {
|
|
||||||
return proxy(user);
|
|
||||||
} else {
|
} 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",
|
LDAPObject ldapObject = loadAndValidateUser(realm, user);
|
||||||
username, model.getDisplayName(), user.getAttribute(LDAPConstants.LDAP_ID));
|
if (ldapObject != null) {
|
||||||
logger.warn("Will re-create user");
|
return proxy(realm, user, ldapObject);
|
||||||
session.userStorage().removeUser(realm, 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -392,4 +452,37 @@ public class LDAPFederationProvider implements UserFederationProvider {
|
||||||
logger.debugf("Kerberos authenticated user [%s] not in Keycloak storage. Creating him", username);
|
logger.debugf("Kerberos authenticated user [%s] not in Keycloak storage. Creating him", username);
|
||||||
return getUserByUsername(realm, 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,11 +6,11 @@ import org.keycloak.federation.kerberos.CommonKerberosConfig;
|
||||||
import org.keycloak.federation.kerberos.impl.KerberosServerSubjectAuthenticator;
|
import org.keycloak.federation.kerberos.impl.KerberosServerSubjectAuthenticator;
|
||||||
import org.keycloak.federation.kerberos.impl.KerberosUsernamePasswordAuthenticator;
|
import org.keycloak.federation.kerberos.impl.KerberosUsernamePasswordAuthenticator;
|
||||||
import org.keycloak.federation.kerberos.impl.SPNEGOAuthenticator;
|
import org.keycloak.federation.kerberos.impl.SPNEGOAuthenticator;
|
||||||
import org.keycloak.federation.ldap.idm.model.IdentityType;
|
import org.keycloak.federation.ldap.idm.model.LDAPObject;
|
||||||
import org.keycloak.federation.ldap.idm.model.LDAPUser;
|
|
||||||
import org.keycloak.federation.ldap.idm.query.Condition;
|
import org.keycloak.federation.ldap.idm.query.Condition;
|
||||||
import org.keycloak.federation.ldap.idm.query.internal.IdentityQuery;
|
import org.keycloak.federation.ldap.idm.query.QueryParameter;
|
||||||
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.LDAPQueryConditionsBuilder;
|
||||||
import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
|
import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
@ -76,11 +76,10 @@ public class LDAPFederationProviderFactory implements UserFederationProviderFact
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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());
|
logger.infof("Sync all users from LDAP to local store: realm: %s, federation provider: %s", realmId, model.getDisplayName());
|
||||||
|
|
||||||
LDAPIdentityStore ldapIdentityStore = this.ldapStoreRegistry.getLdapStore(model);
|
LDAPIdentityQuery userQuery = createQuery(sessionFactory, realmId, model);
|
||||||
IdentityQuery<LDAPUser> userQuery = ldapIdentityStore.createQueryBuilder().createIdentityQuery(LDAPUser.class);
|
|
||||||
UserFederationSyncResult syncResult = syncImpl(sessionFactory, userQuery, 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?
|
// 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) {
|
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());
|
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
|
// Sync newly created and updated users
|
||||||
IdentityQueryBuilder queryBuilder = ldapIdentityStore.createQueryBuilder();
|
LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder();
|
||||||
Condition createCondition = queryBuilder.greaterThanOrEqualTo(IdentityType.CREATED_DATE, lastSync);
|
Condition createCondition = conditionsBuilder.greaterThanOrEqualTo(new QueryParameter(LDAPConstants.CREATE_TIMESTAMP), lastSync);
|
||||||
Condition modifyCondition = queryBuilder.greaterThanOrEqualTo(LDAPUtils.MODIFY_DATE, lastSync);
|
Condition modifyCondition = conditionsBuilder.greaterThanOrEqualTo(new QueryParameter(LDAPConstants.MODIFY_TIMESTAMP), lastSync);
|
||||||
Condition orCondition = queryBuilder.orCondition(createCondition, modifyCondition);
|
Condition orCondition = conditionsBuilder.orCondition(createCondition, modifyCondition);
|
||||||
IdentityQuery<LDAPUser> userQuery = queryBuilder.createIdentityQuery(LDAPUser.class).where(orCondition);
|
|
||||||
|
LDAPIdentityQuery userQuery = createQuery(sessionFactory, realmId, model);
|
||||||
|
userQuery.where(orCondition);
|
||||||
UserFederationSyncResult result = syncImpl(sessionFactory, userQuery, realmId, model);
|
UserFederationSyncResult result = syncImpl(sessionFactory, userQuery, realmId, model);
|
||||||
|
|
||||||
logger.infof("Sync changed users finished: %s", result.getStatus());
|
logger.infof("Sync changed users finished: %s", result.getStatus());
|
||||||
return result;
|
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();
|
final UserFederationSyncResult syncResult = new UserFederationSyncResult();
|
||||||
|
|
||||||
|
@ -120,7 +119,7 @@ public class LDAPFederationProviderFactory implements UserFederationProviderFact
|
||||||
boolean nextPage = true;
|
boolean nextPage = true;
|
||||||
while (nextPage) {
|
while (nextPage) {
|
||||||
userQuery.setLimit(pageSize);
|
userQuery.setLimit(pageSize);
|
||||||
final List<LDAPUser> users = userQuery.getResultList();
|
final List<LDAPObject> users = userQuery.getResultList();
|
||||||
nextPage = userQuery.getPaginationContext() != null;
|
nextPage = userQuery.getPaginationContext() != null;
|
||||||
|
|
||||||
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
|
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
|
||||||
|
@ -135,7 +134,7 @@ public class LDAPFederationProviderFactory implements UserFederationProviderFact
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// LDAP pagination not available. Do everything in single transaction
|
// 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() {
|
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -150,7 +149,27 @@ public class LDAPFederationProviderFactory implements UserFederationProviderFact
|
||||||
return syncResult;
|
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);
|
RealmModel realm = session.realms().getRealm(realmId);
|
||||||
LDAPFederationProvider ldapFedProvider = getInstance(session, fedModel);
|
LDAPFederationProvider ldapFedProvider = getInstance(session, fedModel);
|
||||||
return ldapFedProvider.importLDAPUsers(realm, ldapUsers, fedModel);
|
return ldapFedProvider.importLDAPUsers(realm, ldapUsers, fedModel);
|
||||||
|
|
|
@ -1,17 +1,11 @@
|
||||||
package org.keycloak.federation.ldap;
|
package org.keycloak.federation.ldap;
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Properties;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
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.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.LDAPConstants;
|
||||||
import org.keycloak.models.UserFederationProviderModel;
|
import org.keycloak.models.UserFederationProviderModel;
|
||||||
|
|
||||||
|
@ -51,10 +45,7 @@ public class LDAPIdentityStoreRegistry {
|
||||||
* @return PartitionManager instance based on LDAP store
|
* @return PartitionManager instance based on LDAP store
|
||||||
*/
|
*/
|
||||||
public static LDAPIdentityStore createLdapIdentityStore(Map<String,String> ldapConfig) {
|
public static LDAPIdentityStore createLdapIdentityStore(Map<String,String> ldapConfig) {
|
||||||
Properties connectionProps = new Properties();
|
LDAPConfig cfg = new LDAPConfig(ldapConfig);
|
||||||
if (ldapConfig.containsKey(LDAPConstants.CONNECTION_POOLING)) {
|
|
||||||
connectionProps.put("com.sun.jndi.ldap.connect.pool", ldapConfig.get(LDAPConstants.CONNECTION_POOLING));
|
|
||||||
}
|
|
||||||
|
|
||||||
checkSystemProperty("com.sun.jndi.ldap.connect.pool.authentication", "none simple");
|
checkSystemProperty("com.sun.jndi.ldap.connect.pool.authentication", "none simple");
|
||||||
checkSystemProperty("com.sun.jndi.ldap.connect.pool.initsize", "1");
|
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.protocol", "plain");
|
||||||
checkSystemProperty("com.sun.jndi.ldap.connect.pool.debug", "off");
|
checkSystemProperty("com.sun.jndi.ldap.connect.pool.debug", "off");
|
||||||
|
|
||||||
String vendor = ldapConfig.get(LDAPConstants.VENDOR);
|
/*String ldapLoginNameMapping = ldapConfig.get(LDAPConstants.USERNAME_LDAP_ATTRIBUTE);
|
||||||
|
|
||||||
boolean activeDirectory = vendor != null && vendor.equals(LDAPConstants.VENDOR_ACTIVE_DIRECTORY);
|
|
||||||
|
|
||||||
String ldapLoginNameMapping = ldapConfig.get(LDAPConstants.USERNAME_LDAP_ATTRIBUTE);
|
|
||||||
if (ldapLoginNameMapping == null) {
|
if (ldapLoginNameMapping == null) {
|
||||||
ldapLoginNameMapping = activeDirectory ? LDAPConstants.CN : LDAPConstants.UID;
|
ldapLoginNameMapping = activeDirectory ? LDAPConstants.CN : LDAPConstants.UID;
|
||||||
}
|
}
|
||||||
|
@ -76,62 +63,16 @@ public class LDAPIdentityStoreRegistry {
|
||||||
String ldapFirstNameMapping = activeDirectory ? "givenName" : LDAPConstants.CN;
|
String ldapFirstNameMapping = activeDirectory ? "givenName" : LDAPConstants.CN;
|
||||||
String createTimestampMapping = activeDirectory ? "whenCreated" : LDAPConstants.CREATE_TIMESTAMP;
|
String createTimestampMapping = activeDirectory ? "whenCreated" : LDAPConstants.CREATE_TIMESTAMP;
|
||||||
String modifyTimestampMapping = activeDirectory ? "whenChanged" : LDAPConstants.MODIFY_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
|
/* if (activeDirectory && ldapLoginNameMapping.equals("sAMAccountName")) {
|
||||||
String uniqueIdentifierAttributeName = LDAPConstants.ENTRY_UUID;
|
ldapUserMappingConfig.setBindingDnPropertyName("fullName");
|
||||||
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");
|
|
||||||
ldapUserMappingConfig.addAttributeMapping("fullName", LDAPConstants.CN);
|
ldapUserMappingConfig.addAttributeMapping("fullName", LDAPConstants.CN);
|
||||||
logger.infof("Using 'cn' attribute for DN of user and 'sAMAccountName' for username");
|
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) {
|
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"
|
// 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 objClassesCfg = ldapConfig.get(LDAPConstants.USER_OBJECT_CLASSES);
|
||||||
String objClassesStr = (objClassesCfg != null && objClassesCfg.length() > 0) ? objClassesCfg.trim() : "inetOrgPerson, organizationalPerson";
|
String objClassesStr = (objClassesCfg != null && objClassesCfg.length() > 0) ? objClassesCfg.trim() : "inetOrgPerson, organizationalPerson";
|
||||||
|
|
||||||
String[] objectClasses = objClassesStr.split(",");
|
String[] addObjectClasses = objClassesStr.split(",");
|
||||||
|
|
||||||
// Trim them
|
// Trim them
|
||||||
String[] userObjectClasses = new String[objectClasses.length];
|
String[] userObjectClasses = new String[addObjectClasses.length];
|
||||||
for (int i=0 ; i<objectClasses.length ; i++) {
|
for (int i=0 ; i<addObjectClasses.length ; i++) {
|
||||||
userObjectClasses[i] = objectClasses[i].trim();
|
userObjectClasses[i] = addObjectClasses[i].trim();
|
||||||
}
|
}
|
||||||
return userObjectClasses;
|
return userObjectClasses;
|
||||||
}
|
} */
|
||||||
|
|
||||||
private class LDAPIdentityStoreContext {
|
private class LDAPIdentityStoreContext {
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
package org.keycloak.federation.ldap;
|
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 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.
|
* Allow to directly call some operations against LDAPIdentityStore.
|
||||||
* TODO: Is this class still needed?
|
* TODO: Is this class still needed?
|
||||||
|
@ -21,26 +21,32 @@ import java.util.List;
|
||||||
*/
|
*/
|
||||||
public class LDAPUtils {
|
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) {
|
LDAPIdentityStore ldapStore = ldapProvider.getLdapIdentityStore();
|
||||||
if (getUser(ldapIdentityStore, username) != null) {
|
LDAPConfig ldapConfig = ldapStore.getConfig();
|
||||||
throw new ModelDuplicateException("User with same username already exists");
|
ldapObject.setRdnAttributeName(ldapConfig.getRdnLdapAttribute());
|
||||||
}
|
ldapObject.setObjectClasses(ldapConfig.getObjectClasses());
|
||||||
if (getUserByEmail(ldapIdentityStore, email) != null) {
|
|
||||||
throw new ModelDuplicateException("User with same email already exists");
|
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);
|
LDAPUtils.computeAndSetDn(ldapConfig, ldapObject);
|
||||||
ldapUser.setFirstName(firstName);
|
ldapStore.add(ldapObject);
|
||||||
ldapUser.setLastName(lastName);
|
return ldapObject;
|
||||||
ldapUser.setEmail(email);
|
|
||||||
ldapUser.setAttribute(new Attribute<String>("fullName", getFullName(username, firstName, lastName)));
|
|
||||||
ldapIdentityStore.add(ldapUser);
|
|
||||||
return ldapUser;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 ldapUser = getUser(ldapIdentityStore, username);
|
||||||
ldapUser.setFirstName(firstName);
|
ldapUser.setFirstName(firstName);
|
||||||
ldapUser.setLastName(lastName);
|
ldapUser.setLastName(lastName);
|
||||||
|
@ -86,7 +92,7 @@ public class LDAPUtils {
|
||||||
|
|
||||||
public static LDAPUser getUserByEmail(LDAPIdentityStore ldapIdentityStore, String email) {
|
public static LDAPUser getUserByEmail(LDAPIdentityStore ldapIdentityStore, String email) {
|
||||||
IdentityQueryBuilder queryBuilder = ldapIdentityStore.createQueryBuilder();
|
IdentityQueryBuilder queryBuilder = ldapIdentityStore.createQueryBuilder();
|
||||||
IdentityQuery<LDAPUser> query = queryBuilder.createIdentityQuery(LDAPUser.class)
|
LDAPIdentityQuery<LDAPUser> query = queryBuilder.createIdentityQuery(LDAPUser.class)
|
||||||
.where(queryBuilder.equal(LDAPUser.EMAIL, email));
|
.where(queryBuilder.equal(LDAPUser.EMAIL, email));
|
||||||
List<LDAPUser> users = query.getResultList();
|
List<LDAPUser> users = query.getResultList();
|
||||||
|
|
||||||
|
@ -106,18 +112,34 @@ public class LDAPUtils {
|
||||||
}
|
}
|
||||||
ldapIdentityStore.remove(ldapUser);
|
ldapIdentityStore.remove(ldapUser);
|
||||||
return true;
|
return true;
|
||||||
}
|
} */
|
||||||
|
|
||||||
public static void removeAllUsers(LDAPIdentityStore ldapIdentityStore) {
|
public static void removeAllUsers(LDAPFederationProvider ldapProvider, RealmModel realm) {
|
||||||
List<LDAPUser> allUsers = getAllUsers(ldapIdentityStore);
|
LDAPIdentityStore ldapStore = ldapProvider.getLdapIdentityStore();
|
||||||
|
LDAPIdentityQuery ldapQuery = LDAPUtils.createQueryForUserSearch(ldapProvider, realm);
|
||||||
|
List<LDAPObject> allUsers = ldapQuery.getResultList();
|
||||||
|
|
||||||
for (LDAPUser user : allUsers) {
|
for (LDAPObject ldapUser : allUsers) {
|
||||||
ldapIdentityStore.remove(user);
|
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) {
|
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();
|
return userQuery.getResultList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,5 +160,23 @@ public class LDAPUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
return fullName;
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package org.keycloak.federation.ldap;
|
package org.keycloak.federation.ldap;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
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.federation.ldap.idm.store.ldap.LDAPIdentityStore;
|
||||||
import org.keycloak.models.UserCredentialModel;
|
import org.keycloak.models.UserCredentialModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
|
@ -15,52 +15,12 @@ public class WritableLDAPUserModelDelegate extends UserModelDelegate implements
|
||||||
private static final Logger logger = Logger.getLogger(WritableLDAPUserModelDelegate.class);
|
private static final Logger logger = Logger.getLogger(WritableLDAPUserModelDelegate.class);
|
||||||
|
|
||||||
protected LDAPFederationProvider provider;
|
protected LDAPFederationProvider provider;
|
||||||
|
protected LDAPObject ldapObject;
|
||||||
|
|
||||||
public WritableLDAPUserModelDelegate(UserModel delegate, LDAPFederationProvider provider) {
|
public WritableLDAPUserModelDelegate(UserModel delegate, LDAPFederationProvider provider, LDAPObject ldapObject) {
|
||||||
super(delegate);
|
super(delegate);
|
||||||
this.provider = provider;
|
this.provider = provider;
|
||||||
}
|
this.ldapObject = ldapObject;
|
||||||
|
|
||||||
@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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -70,31 +30,13 @@ public class WritableLDAPUserModelDelegate extends UserModelDelegate implements
|
||||||
return;
|
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)) {
|
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 {
|
} else {
|
||||||
logger.warnf("Don't know how to update credential of type [%s] for user [%s]", cred.getType(), delegate.getUsername());
|
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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;
|
|
||||||
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -7,6 +7,19 @@ package org.keycloak.federation.ldap.idm.query;
|
||||||
* @author Shane Bryzak
|
* @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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package org.keycloak.federation.ldap.idm.query.internal;
|
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.Condition;
|
||||||
import org.keycloak.federation.ldap.idm.query.QueryParameter;
|
import org.keycloak.federation.ldap.idm.query.QueryParameter;
|
||||||
|
|
||||||
|
@ -29,7 +28,7 @@ public class EqualCondition implements Condition {
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "EqualCondition{" +
|
return "EqualCondition{" +
|
||||||
"parameter=" + ((AttributeParameter) parameter).getName() +
|
"parameter=" + parameter.getName() +
|
||||||
", value=" + value +
|
", value=" + value +
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,22 +1,14 @@
|
||||||
package org.keycloak.federation.ldap.idm.query.internal;
|
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.Condition;
|
||||||
import org.keycloak.federation.ldap.idm.query.QueryParameter;
|
import org.keycloak.federation.ldap.idm.query.QueryParameter;
|
||||||
import org.keycloak.federation.ldap.idm.query.Sort;
|
import org.keycloak.federation.ldap.idm.query.Sort;
|
||||||
import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
|
|
||||||
import org.keycloak.models.ModelException;
|
import org.keycloak.models.ModelException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Pedro Igor
|
* @author Pedro Igor
|
||||||
*/
|
*/
|
||||||
public class IdentityQueryBuilder {
|
public class LDAPQueryConditionsBuilder {
|
||||||
|
|
||||||
private final LDAPIdentityStore identityStore;
|
|
||||||
|
|
||||||
public IdentityQueryBuilder(LDAPIdentityStore identityStore) {
|
|
||||||
this.identityStore = identityStore;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Condition like(QueryParameter parameter, String pattern) {
|
public Condition like(QueryParameter parameter, String pattern) {
|
||||||
return new LikeCondition(parameter, pattern);
|
return new LikeCondition(parameter, pattern);
|
||||||
|
@ -71,10 +63,6 @@ public class IdentityQueryBuilder {
|
||||||
return new Sort(parameter, false);
|
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) {
|
private void throwExceptionIfNotComparable(Object x) {
|
||||||
if (!Comparable.class.isInstance(x)) {
|
if (!Comparable.class.isInstance(x)) {
|
||||||
throw new ModelException("Query parameter value [" + x + "] must be " + Comparable.class + ".");
|
throw new ModelException("Query parameter value [" + x + "] must be " + Comparable.class + ".");
|
|
@ -2,11 +2,9 @@ package org.keycloak.federation.ldap.idm.store;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.keycloak.federation.ldap.idm.model.AttributedType;
|
import org.keycloak.federation.ldap.LDAPConfig;
|
||||||
import org.keycloak.federation.ldap.idm.model.IdentityType;
|
import org.keycloak.federation.ldap.idm.model.LDAPObject;
|
||||||
import org.keycloak.federation.ldap.idm.model.LDAPUser;
|
import org.keycloak.federation.ldap.idm.query.internal.LDAPIdentityQuery;
|
||||||
import org.keycloak.federation.ldap.idm.query.internal.IdentityQuery;
|
|
||||||
import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStoreConfiguration;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IdentityStore representation providing minimal SPI
|
* IdentityStore representation providing minimal SPI
|
||||||
|
@ -23,36 +21,36 @@ public interface IdentityStore {
|
||||||
*
|
*
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
LDAPIdentityStoreConfiguration getConfig();
|
LDAPConfig getConfig();
|
||||||
|
|
||||||
// General
|
// General
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Persists the specified IdentityType
|
* Persists the specified IdentityType
|
||||||
*
|
*
|
||||||
* @param value
|
* @param ldapObject
|
||||||
*/
|
*/
|
||||||
void add(AttributedType value);
|
void add(LDAPObject ldapObject);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the specified IdentityType
|
* Updates the specified IdentityType
|
||||||
*
|
*
|
||||||
* @param value
|
* @param ldapObject
|
||||||
*/
|
*/
|
||||||
void update(AttributedType value);
|
void update(LDAPObject ldapObject);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes the specified IdentityType
|
* Removes the specified IdentityType
|
||||||
*
|
*
|
||||||
* @param value
|
* @param ldapObject
|
||||||
*/
|
*/
|
||||||
void remove(AttributedType value);
|
void remove(LDAPObject ldapObject);
|
||||||
|
|
||||||
// Identity query
|
// 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
|
// // Relationship query
|
||||||
//
|
//
|
||||||
|
@ -68,7 +66,7 @@ public interface IdentityStore {
|
||||||
* @param user Keycloak user
|
* @param user Keycloak user
|
||||||
* @param password Ldap password
|
* @param password Ldap password
|
||||||
*/
|
*/
|
||||||
boolean validatePassword(LDAPUser user, String password);
|
boolean validatePassword(LDAPObject user, String password);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the specified credential value.
|
* Updates the specified credential value.
|
||||||
|
@ -76,6 +74,6 @@ public interface IdentityStore {
|
||||||
* @param user Keycloak user
|
* @param user Keycloak user
|
||||||
* @param password Ldap password
|
* @param password Ldap password
|
||||||
*/
|
*/
|
||||||
void updatePassword(LDAPUser user, String password);
|
void updatePassword(LDAPObject user, String password);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,10 @@ package org.keycloak.federation.ldap.idm.store.ldap;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.NoSuchElementException;
|
import java.util.NoSuchElementException;
|
||||||
|
@ -15,33 +18,25 @@ import javax.naming.directory.BasicAttribute;
|
||||||
import javax.naming.directory.BasicAttributes;
|
import javax.naming.directory.BasicAttributes;
|
||||||
import javax.naming.directory.DirContext;
|
import javax.naming.directory.DirContext;
|
||||||
import javax.naming.directory.ModificationItem;
|
import javax.naming.directory.ModificationItem;
|
||||||
|
import javax.naming.directory.SearchControls;
|
||||||
import javax.naming.directory.SearchResult;
|
import javax.naming.directory.SearchResult;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.federation.ldap.idm.model.AttributedType;
|
import org.keycloak.federation.ldap.LDAPConfig;
|
||||||
import org.keycloak.federation.ldap.idm.model.IdentityType;
|
import org.keycloak.federation.ldap.idm.model.LDAPDn;
|
||||||
import org.keycloak.federation.ldap.idm.model.LDAPUser;
|
import org.keycloak.federation.ldap.idm.model.LDAPObject;
|
||||||
import org.keycloak.federation.ldap.idm.query.AttributeParameter;
|
|
||||||
import org.keycloak.federation.ldap.idm.query.Condition;
|
import org.keycloak.federation.ldap.idm.query.Condition;
|
||||||
import org.keycloak.federation.ldap.idm.query.QueryParameter;
|
import org.keycloak.federation.ldap.idm.query.QueryParameter;
|
||||||
import org.keycloak.federation.ldap.idm.query.internal.BetweenCondition;
|
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.LDAPIdentityQuery;
|
||||||
import org.keycloak.federation.ldap.idm.query.internal.IdentityQueryBuilder;
|
|
||||||
import org.keycloak.federation.ldap.idm.query.internal.EqualCondition;
|
import org.keycloak.federation.ldap.idm.query.internal.EqualCondition;
|
||||||
import org.keycloak.federation.ldap.idm.query.internal.GreaterThanCondition;
|
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.InCondition;
|
||||||
import org.keycloak.federation.ldap.idm.query.internal.LessThanCondition;
|
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.query.internal.OrCondition;
|
||||||
import org.keycloak.federation.ldap.idm.store.IdentityStore;
|
import org.keycloak.federation.ldap.idm.store.IdentityStore;
|
||||||
import org.keycloak.models.LDAPConstants;
|
import org.keycloak.models.LDAPConstants;
|
||||||
import org.keycloak.models.ModelDuplicateException;
|
|
||||||
import org.keycloak.models.ModelException;
|
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
|
* 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);
|
private static final Logger logger = Logger.getLogger(LDAPIdentityStore.class);
|
||||||
|
|
||||||
public static final String EMPTY_ATTRIBUTE_VALUE = " ";
|
private final LDAPConfig config;
|
||||||
|
|
||||||
private final LDAPIdentityStoreConfiguration config;
|
|
||||||
private final LDAPOperationManager operationManager;
|
private final LDAPOperationManager operationManager;
|
||||||
|
|
||||||
public LDAPIdentityStore(LDAPIdentityStoreConfiguration config) {
|
public LDAPIdentityStore(LDAPConfig config) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -70,69 +63,73 @@ public class LDAPIdentityStore implements IdentityStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LDAPIdentityStoreConfiguration getConfig() {
|
public LDAPConfig getConfig() {
|
||||||
return this.config;
|
return this.config;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void add(AttributedType attributedType) {
|
public void add(LDAPObject ldapObject) {
|
||||||
// id will be assigned by the ldap server
|
// 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);
|
String entryDN = ldapObject.getDn().toString();
|
||||||
this.operationManager.createSubContext(entryDN, extractAttributes(attributedType, true));
|
this.operationManager.createSubContext(entryDN, extractAttributes(ldapObject, true));
|
||||||
addToParentAsMember(attributedType);
|
ldapObject.setUuid(getEntryIdentifier(ldapObject));
|
||||||
attributedType.setId(getEntryIdentifier(attributedType));
|
|
||||||
|
|
||||||
attributedType.setEntryDN(entryDN);
|
|
||||||
|
|
||||||
if (logger.isTraceEnabled()) {
|
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
|
@Override
|
||||||
public void update(AttributedType attributedType) {
|
public void update(LDAPObject ldapObject) {
|
||||||
BasicAttributes updatedAttributes = extractAttributes(attributedType, false);
|
BasicAttributes updatedAttributes = extractAttributes(ldapObject, false);
|
||||||
NamingEnumeration<Attribute> attributes = updatedAttributes.getAll();
|
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()) {
|
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
|
@Override
|
||||||
public void remove(AttributedType attributedType) {
|
public void remove(LDAPObject ldapObject) {
|
||||||
LDAPMappingConfiguration mappingConfig = getMappingConfig(attributedType.getClass());
|
this.operationManager.removeEntry(ldapObject.getDn().toString());
|
||||||
|
|
||||||
this.operationManager.removeEntryById(getBaseDN(attributedType), attributedType.getId(), mappingConfig);
|
|
||||||
|
|
||||||
if (logger.isTraceEnabled()) {
|
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
|
@Override
|
||||||
public <V extends IdentityType> List<V> fetchQueryResults(IdentityQuery<V> identityQuery) {
|
public List<LDAPObject> fetchQueryResults(LDAPIdentityQuery identityQuery) {
|
||||||
List<V> results = new ArrayList<V>();
|
List<LDAPObject> results = new ArrayList<LDAPObject>();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (identityQuery.getSorting() != null && !identityQuery.getSorting().isEmpty()) {
|
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()) {
|
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)) {
|
if (EqualCondition.class.isInstance(condition)) {
|
||||||
EqualCondition equalCondition = (EqualCondition) condition;
|
EqualCondition equalCondition = (EqualCondition) condition;
|
||||||
SearchResult search = this.operationManager
|
SearchResult search = this.operationManager
|
||||||
.lookupById(getConfig().getBaseDN(), equalCondition.getValue().toString(), null);
|
.lookupById(baseDN, equalCondition.getValue().toString(), identityQuery.getReturningLdapAttributes());
|
||||||
|
|
||||||
if (search != null) {
|
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) {
|
StringBuilder filter = createIdentityTypeSearchFilter(identityQuery);
|
||||||
search = this.operationManager.searchPaginated(baseDN, filter.toString(), ldapEntryConfig, identityQuery);
|
|
||||||
} else {
|
|
||||||
search = this.operationManager.search(baseDN, filter.toString(), ldapEntryConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (SearchResult result : search) {
|
List<SearchResult> search;
|
||||||
if (!result.getNameInNamespace().equals(baseDN)) {
|
if (getConfig().isPagination() && identityQuery.getLimit() > 0) {
|
||||||
results.add((V) populateAttributedType(result, null));
|
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) {
|
} catch (Exception e) {
|
||||||
|
@ -167,7 +160,7 @@ public class LDAPIdentityStore implements IdentityStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <V extends IdentityType> int countQueryResults(IdentityQuery<V> identityQuery) {
|
public int countQueryResults(LDAPIdentityQuery identityQuery) {
|
||||||
int limit = identityQuery.getLimit();
|
int limit = identityQuery.getLimit();
|
||||||
int offset = identityQuery.getOffset();
|
int offset = identityQuery.getOffset();
|
||||||
|
|
||||||
|
@ -182,18 +175,14 @@ public class LDAPIdentityStore implements IdentityStore {
|
||||||
return resultCount;
|
return resultCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IdentityQueryBuilder createQueryBuilder() {
|
|
||||||
return new IdentityQueryBuilder(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
// *************** CREDENTIALS AND USER SPECIFIC STUFF
|
// *************** CREDENTIALS AND USER SPECIFIC STUFF
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean validatePassword(LDAPUser user, String password) {
|
public boolean validatePassword(LDAPObject user, String password) {
|
||||||
String userDN = getEntryDNOfUser(user);
|
String userDN = user.getDn().toString();
|
||||||
|
|
||||||
if (logger.isDebugEnabled()) {
|
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)) {
|
if (operationManager.authenticate(userDN, password)) {
|
||||||
|
@ -204,11 +193,11 @@ public class LDAPIdentityStore implements IdentityStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updatePassword(LDAPUser user, String password) {
|
public void updatePassword(LDAPObject user, String password) {
|
||||||
String userDN = getEntryDNOfUser(user);
|
String userDN = user.getDn().toString();
|
||||||
|
|
||||||
if (logger.isDebugEnabled()) {
|
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()) {
|
if (getConfig().isActiveDirectory()) {
|
||||||
|
@ -242,6 +231,7 @@ public class LDAPIdentityStore implements IdentityStore {
|
||||||
modItems.add(new ModificationItem(DirContext.REPLACE_ATTRIBUTE, unicodePwd));
|
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
|
// 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()) {
|
if (getConfig().isUserAccountControlsAfterPasswordUpdate()) {
|
||||||
BasicAttribute userAccountControl = new BasicAttribute("userAccountControl", "512");
|
BasicAttribute userAccountControl = new BasicAttribute("userAccountControl", "512");
|
||||||
modItems.add(new ModificationItem(DirContext.REPLACE_ATTRIBUTE, userAccountControl));
|
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
|
// ************ END CREDENTIALS AND USER SPECIFIC STUFF
|
||||||
|
|
||||||
|
protected StringBuilder createIdentityTypeSearchFilter(final LDAPIdentityQuery identityQuery) {
|
||||||
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) {
|
|
||||||
StringBuilder filter = new StringBuilder();
|
StringBuilder filter = new StringBuilder();
|
||||||
|
|
||||||
for (Condition condition : identityQuery.getConditions()) {
|
for (Condition condition : identityQuery.getConditions()) {
|
||||||
applyCondition(filter, condition, ldapEntryConfig);
|
applyCondition(filter, condition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
filter.insert(0, "(&");
|
filter.insert(0, "(&");
|
||||||
filter.append(getObjectClassesFilter(ldapEntryConfig));
|
filter.append(getObjectClassesFilter(identityQuery.getObjectClasses()));
|
||||||
filter.append(")");
|
filter.append(")");
|
||||||
|
|
||||||
logger.infof("Using filter for LDAP search: %s", filter);
|
logger.infof("Using filter for LDAP search: %s", filter);
|
||||||
return filter;
|
return filter;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void applyCondition(StringBuilder filter, Condition condition, LDAPMappingConfiguration ldapEntryConfig) {
|
protected void applyCondition(StringBuilder filter, Condition condition) {
|
||||||
if (OrCondition.class.isInstance(condition)) {
|
if (OrCondition.class.isInstance(condition)) {
|
||||||
OrCondition orCondition = (OrCondition) condition;
|
OrCondition orCondition = (OrCondition) condition;
|
||||||
filter.append("(|");
|
filter.append("(|");
|
||||||
|
|
||||||
for (Condition innerCondition : orCondition.getInnerConditions()) {
|
for (Condition innerCondition : orCondition.getInnerConditions()) {
|
||||||
applyCondition(filter, innerCondition, ldapEntryConfig);
|
applyCondition(filter, innerCondition);
|
||||||
}
|
}
|
||||||
|
|
||||||
filter.append(")");
|
filter.append(")");
|
||||||
|
@ -337,91 +278,84 @@ public class LDAPIdentityStore implements IdentityStore {
|
||||||
|
|
||||||
QueryParameter queryParameter = condition.getParameter();
|
QueryParameter queryParameter = condition.getParameter();
|
||||||
|
|
||||||
if (!IdentityType.ID.equals(queryParameter)) {
|
if (!getConfig().getUuidAttributeName().equals(queryParameter.getName())) {
|
||||||
if (AttributeParameter.class.isInstance(queryParameter)) {
|
String attributeName = queryParameter.getName();
|
||||||
AttributeParameter attributeParameter = (AttributeParameter) queryParameter;
|
|
||||||
String attributeName = ldapEntryConfig.getMappedProperties().get(attributeParameter.getName());
|
|
||||||
|
|
||||||
if (attributeName != null) {
|
if (attributeName != null) {
|
||||||
if (EqualCondition.class.isInstance(condition)) {
|
if (EqualCondition.class.isInstance(condition)) {
|
||||||
EqualCondition equalCondition = (EqualCondition) condition;
|
EqualCondition equalCondition = (EqualCondition) condition;
|
||||||
Object parameterValue = equalCondition.getValue();
|
Object parameterValue = equalCondition.getValue();
|
||||||
|
|
||||||
if (Date.class.isInstance(parameterValue)) {
|
if (Date.class.isInstance(parameterValue)) {
|
||||||
parameterValue = LDAPUtil.formatDate((Date) 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 + "].");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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();
|
StringBuilder builder = new StringBuilder();
|
||||||
|
|
||||||
if (ldapEntryConfig != null && !ldapEntryConfig.getObjectClasses().isEmpty()) {
|
if (!objectClasses.isEmpty()) {
|
||||||
for (String objectClass : ldapEntryConfig.getObjectClasses()) {
|
for (String objectClass : objectClasses) {
|
||||||
builder.append("(").append(LDAPConstants.OBJECT_CLASS).append(LDAPConstants.EQUAL).append(objectClass).append(")");
|
builder.append("(").append(LDAPConstants.OBJECT_CLASS).append(LDAPConstants.EQUAL).append(objectClass).append(")");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -431,86 +365,65 @@ public class LDAPIdentityStore implements IdentityStore {
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
private AttributedType populateAttributedType(SearchResult searchResult, AttributedType attributedType) {
|
private LDAPObject populateAttributedType(SearchResult searchResult, Collection<String> readOnlyAttrNames) {
|
||||||
return populateAttributedType(searchResult, attributedType, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
private AttributedType populateAttributedType(SearchResult searchResult, AttributedType attributedType, int hierarchyDepthCount) {
|
|
||||||
try {
|
try {
|
||||||
String entryDN = searchResult.getNameInNamespace();
|
String entryDN = searchResult.getNameInNamespace();
|
||||||
Attributes attributes = searchResult.getAttributes();
|
Attributes attributes = searchResult.getAttributes();
|
||||||
|
|
||||||
if (attributedType == null) {
|
LDAPObject ldapObject = new LDAPObject();
|
||||||
attributedType = Reflections.newInstance(getConfig().getSupportedTypeByBaseDN(entryDN, getEntryObjectClasses(attributes)));
|
LDAPDn dn = LDAPDn.fromString(entryDN);
|
||||||
}
|
ldapObject.setDn(dn);
|
||||||
|
ldapObject.setRdnAttributeName(dn.getFirstRdnAttrName());
|
||||||
attributedType.setEntryDN(entryDN);
|
|
||||||
|
|
||||||
LDAPMappingConfiguration mappingConfig = getMappingConfig(attributedType.getClass());
|
|
||||||
|
|
||||||
if (hierarchyDepthCount > mappingConfig.getHierarchySearchDepth()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (logger.isTraceEnabled()) {
|
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();
|
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()) {
|
while (ldapAttributes.hasMore()) {
|
||||||
Attribute ldapAttribute = ldapAttributes.next();
|
Attribute ldapAttribute = ldapAttributes.next();
|
||||||
Object attributeValue;
|
Serializable ldapAttributeValue;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
attributeValue = ldapAttribute.get();
|
ldapAttributeValue = (Serializable) ldapAttribute.get();
|
||||||
} catch (NoSuchElementException nsee) {
|
} catch (NoSuchElementException nsee) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
String ldapAttributeName = ldapAttribute.getID();
|
String ldapAttributeName = ldapAttribute.getID();
|
||||||
|
|
||||||
if (ldapAttributeName.toLowerCase().equals(getConfig().getUniqueIdentifierAttributeName().toLowerCase())) {
|
if (ldapAttributeName.toLowerCase().equals(getConfig().getUuidAttributeName().toLowerCase())) {
|
||||||
attributedType.setId(this.operationManager.decodeEntryUUID(attributeValue));
|
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 {
|
} 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) {
|
ldapObject.setAttribute(ldapAttributeName, ldapAttributeValue);
|
||||||
// Find if it's java property or attribute
|
if (uppercasedReadOnlyAttrNames.contains(ldapAttributeName.toUpperCase())) {
|
||||||
Property<Object> property = PropertyQueries
|
ldapObject.addReadOnlyAttributeName(ldapAttributeName);
|
||||||
.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));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IdentityType.class.isInstance(attributedType)) {
|
return ldapObject;
|
||||||
IdentityType identityType = (IdentityType) attributedType;
|
|
||||||
|
|
||||||
String createdTimestamp = attributes.get(LDAPConstants.CREATE_TIMESTAMP).get().toString();
|
/*LDAPMappingConfiguration entryConfig = getMappingConfig(attributedType.getClass());
|
||||||
|
|
||||||
identityType.setCreatedDate(LDAPUtil.parseDate(createdTimestamp));
|
|
||||||
}
|
|
||||||
|
|
||||||
LDAPMappingConfiguration entryConfig = getMappingConfig(attributedType.getClass());
|
|
||||||
|
|
||||||
if (mappingConfig.getParentMembershipAttributeName() != null) {
|
if (mappingConfig.getParentMembershipAttributeName() != null) {
|
||||||
StringBuilder filter = new StringBuilder("(&");
|
StringBuilder filter = new StringBuilder("(&");
|
||||||
|
@ -539,14 +452,14 @@ public class LDAPIdentityStore implements IdentityStore {
|
||||||
if (!search.isEmpty()) {
|
if (!search.isEmpty()) {
|
||||||
SearchResult next = search.get(0);
|
SearchResult next = search.get(0);
|
||||||
|
|
||||||
Property<AttributedType> parentProperty = PropertyQueries
|
Property<IdentityType> parentProperty = PropertyQueries
|
||||||
.<AttributedType>createQuery(attributedType.getClass())
|
.<IdentityType>createQuery(attributedType.getClass())
|
||||||
.addCriteria(new TypedPropertyCriteria(attributedType.getClass())).getFirstResult();
|
.addCriteria(new TypedPropertyCriteria(attributedType.getClass())).getFirstResult();
|
||||||
|
|
||||||
if (parentProperty != null) {
|
if (parentProperty != null) {
|
||||||
String parentDN = next.getNameInNamespace();
|
String parentDN = next.getNameInNamespace();
|
||||||
String parentBaseDN = parentDN.substring(parentDN.indexOf(",") + 1);
|
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 (parentProperty.getJavaClass().isAssignableFrom(baseDNType)) {
|
||||||
if (logger.isTraceEnabled()) {
|
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());
|
logger.tracef("No parent entry found for DN [%s] using filter [%s].", entryDN, filter.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} */
|
||||||
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
} 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) {
|
protected BasicAttributes extractAttributes(LDAPObject ldapObject, boolean isCreate) {
|
||||||
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) {
|
|
||||||
BasicAttributes entryAttributes = new BasicAttributes();
|
BasicAttributes entryAttributes = new BasicAttributes();
|
||||||
LDAPMappingConfiguration mappingConfig = getMappingConfig(attributedType.getClass());
|
|
||||||
Map<String, String> mappedProperties = mappingConfig.getMappedProperties();
|
|
||||||
|
|
||||||
for (String propertyName : mappedProperties.keySet()) {
|
for (Map.Entry<String, Serializable> attrEntry : ldapObject.getAttributes().entrySet()) {
|
||||||
if (!mappingConfig.getReadOnlyAttributes().contains(propertyName) && (isCreate || !mappingConfig.getBindingProperty().getName().equals(propertyName))) {
|
String attrName = attrEntry.getKey();
|
||||||
Property<Object> property = PropertyQueries
|
Serializable attrValue = attrEntry.getValue();
|
||||||
.<Object>createQuery(attributedType.getClass())
|
if (!ldapObject.getReadOnlyAttributeNames().contains(attrName) && (isCreate || !ldapObject.getRdnAttributeName().equals(attrName))) {
|
||||||
.addCriteria(new NamedPropertyCriteria(propertyName)).getFirstResult();
|
|
||||||
|
|
||||||
Object propertyValue = null;
|
if (String.class.isInstance(attrValue)) {
|
||||||
if (property != null) {
|
entryAttributes.put(attrName, attrValue);
|
||||||
// Mapped Java property on the object
|
} else if (Collection.class.isInstance(attrValue)) {
|
||||||
propertyValue = property.getValue(attributedType);
|
BasicAttribute attr = new BasicAttribute(attrName);
|
||||||
} else {
|
Collection<String> valueCollection = (Collection<String>) attr;
|
||||||
// Not mapped property. So fallback to attribute
|
for (String val : valueCollection) {
|
||||||
org.keycloak.federation.ldap.idm.model.Attribute<?> attribute = attributedType.getAttribute(propertyName);
|
attr.add(val);
|
||||||
if (attribute != null) {
|
|
||||||
propertyValue = attribute.getValue();
|
|
||||||
}
|
}
|
||||||
}
|
entryAttributes.put(attr);
|
||||||
|
} else if (attrValue == null || attrValue.toString().trim().length() == 0) {
|
||||||
if (AttributedType.class.isInstance(propertyValue)) {
|
entryAttributes.put(attrName, LDAPConstants.EMPTY_ATTRIBUTE_VALUE);
|
||||||
AttributedType referencedType = (AttributedType) propertyValue;
|
|
||||||
propertyValue = getBindingDN(referencedType, true);
|
|
||||||
} else {
|
} else {
|
||||||
if (propertyValue == null || isNullOrEmpty(propertyValue.toString())) {
|
throw new IllegalArgumentException("Unexpected type of value of argument " + attrName + ". Value is " + attrValue);
|
||||||
propertyValue = EMPTY_ATTRIBUTE_VALUE;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
entryAttributes.put(mappedProperties.get(propertyName), propertyValue);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't extract object classes for update
|
// Don't extract object classes for update
|
||||||
if (isCreate) {
|
if (isCreate) {
|
||||||
LDAPMappingConfiguration ldapEntryConfig = getMappingConfig(attributedType.getClass());
|
|
||||||
|
|
||||||
BasicAttribute objectClassAttribute = new BasicAttribute(LDAPConstants.OBJECT_CLASS);
|
BasicAttribute objectClassAttribute = new BasicAttribute(LDAPConstants.OBJECT_CLASS);
|
||||||
|
|
||||||
for (String objectClassValue : ldapEntryConfig.getObjectClasses()) {
|
for (String objectClassValue : ldapObject.getObjectClasses()) {
|
||||||
objectClassAttribute.add(objectClassValue);
|
objectClassAttribute.add(objectClassValue);
|
||||||
|
|
||||||
if (objectClassValue.equals(LDAPConstants.GROUP_OF_NAMES)
|
if (objectClassValue.equals(LDAPConstants.GROUP_OF_NAMES)
|
||||||
|| objectClassValue.equals(LDAPConstants.GROUP_OF_ENTRIES)
|
|| objectClassValue.equals(LDAPConstants.GROUP_OF_ENTRIES)
|
||||||
|| objectClassValue.equals(LDAPConstants.GROUP_OF_UNIQUE_NAMES)) {
|
|| 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;
|
return entryAttributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Move class StringUtil from SAML module
|
/*public String getBindingDN(IdentityType attributedType, boolean appendBaseDN) {
|
||||||
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) {
|
|
||||||
LDAPMappingConfiguration mappingConfig = getMappingConfig(attributedType.getClass());
|
LDAPMappingConfiguration mappingConfig = getMappingConfig(attributedType.getClass());
|
||||||
Property<String> idProperty = mappingConfig.getIdProperty();
|
|
||||||
|
|
||||||
String baseDN;
|
String baseDN;
|
||||||
|
|
||||||
if (mappingConfig.getBaseDN() == null || !appendBaseDN) {
|
if (mappingConfig.getBaseDN() == null || !appendBaseDN) {
|
||||||
baseDN = "";
|
baseDN = "";
|
||||||
} else {
|
} else {
|
||||||
baseDN = LDAPConstants.COMMA + getBaseDN(attributedType);
|
baseDN = LDAPConstants.COMMA + getBaseDN(attributedType);
|
||||||
}
|
}
|
||||||
|
|
||||||
Property<String> bindingProperty = mappingConfig.getBindingProperty();
|
Property<String> bindingDnAttributeProperty = mappingConfig.getBindingDnProperty();
|
||||||
String bindingAttribute;
|
String bindingAttributeName = mappingConfig.getMappedAttributes().get(bindingDnAttributeProperty.getName());
|
||||||
String dn;
|
String bindingAttributeValue = mappingConfig.getBindingDnProperty().getValue(attributedType);
|
||||||
|
|
||||||
if (bindingProperty == null) {
|
return bindingAttributeName + LDAPConstants.EQUAL + bindingAttributeValue + baseDN;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getBaseDN(AttributedType attributedType) {
|
private String getBaseDN(IdentityType attributedType) {
|
||||||
LDAPMappingConfiguration mappingConfig = getMappingConfig(attributedType.getClass());
|
LDAPMappingConfiguration mappingConfig = getMappingConfig(attributedType.getClass());
|
||||||
String baseDN = mappingConfig.getBaseDN();
|
String baseDN = mappingConfig.getBaseDN();
|
||||||
String parentDN = mappingConfig.getParentMapping().get(mappingConfig.getIdProperty().getValue(attributedType));
|
String parentDN = mappingConfig.getParentMapping().get(mappingConfig.getIdProperty().getValue(attributedType));
|
||||||
|
@ -706,12 +554,12 @@ public class LDAPIdentityStore implements IdentityStore {
|
||||||
if (parentDN != null) {
|
if (parentDN != null) {
|
||||||
baseDN = parentDN;
|
baseDN = parentDN;
|
||||||
} else {
|
} else {
|
||||||
Property<AttributedType> parentProperty = PropertyQueries
|
Property<IdentityType> parentProperty = PropertyQueries
|
||||||
.<AttributedType>createQuery(attributedType.getClass())
|
.<IdentityType>createQuery(attributedType.getClass())
|
||||||
.addCriteria(new TypedPropertyCriteria(attributedType.getClass())).getFirstResult();
|
.addCriteria(new TypedPropertyCriteria(attributedType.getClass())).getFirstResult();
|
||||||
|
|
||||||
if (parentProperty != null) {
|
if (parentProperty != null) {
|
||||||
AttributedType parentType = parentProperty.getValue(attributedType);
|
IdentityType parentType = parentProperty.getValue(attributedType);
|
||||||
|
|
||||||
if (parentType != null) {
|
if (parentType != null) {
|
||||||
Property<String> parentIdProperty = getMappingConfig(parentType.getClass()).getIdProperty();
|
Property<String> parentIdProperty = getMappingConfig(parentType.getClass()).getIdProperty();
|
||||||
|
@ -729,24 +577,20 @@ public class LDAPIdentityStore implements IdentityStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (baseDN == null) {
|
|
||||||
baseDN = getConfig().getBaseDN();
|
|
||||||
}
|
|
||||||
|
|
||||||
return baseDN;
|
return baseDN;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void addToParentAsMember(final AttributedType attributedType) {
|
protected void addToParentAsMember(final IdentityType attributedType) {
|
||||||
LDAPMappingConfiguration entryConfig = getMappingConfig(attributedType.getClass());
|
LDAPMappingConfiguration entryConfig = getMappingConfig(attributedType.getClass());
|
||||||
|
|
||||||
if (entryConfig.getParentMembershipAttributeName() != null) {
|
if (entryConfig.getParentMembershipAttributeName() != null) {
|
||||||
Property<AttributedType> parentProperty = PropertyQueries
|
Property<IdentityType> parentProperty = PropertyQueries
|
||||||
.<AttributedType>createQuery(attributedType.getClass())
|
.<IdentityType>createQuery(attributedType.getClass())
|
||||||
.addCriteria(new TypedPropertyCriteria(attributedType.getClass()))
|
.addCriteria(new TypedPropertyCriteria(attributedType.getClass()))
|
||||||
.getFirstResult();
|
.getFirstResult();
|
||||||
|
|
||||||
if (parentProperty != null) {
|
if (parentProperty != null) {
|
||||||
AttributedType parentType = parentProperty.getValue(attributedType);
|
IdentityType parentType = parentProperty.getValue(attributedType);
|
||||||
|
|
||||||
if (parentType != null) {
|
if (parentType != null) {
|
||||||
Attributes attributes = this.operationManager.getAttributes(parentType.getId(), getBaseDN(parentType), entryConfig);
|
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 {
|
try {
|
||||||
// we need this to retrieve the entry's identifier from the ldap server
|
// 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()));
|
String uuidAttrName = getConfig().getUuidAttributeName();
|
||||||
Attribute id = search.get(0).getAttributes().get(getConfig().getUniqueIdentifierAttributeName());
|
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) {
|
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());
|
return this.operationManager.decodeEntryUUID(id.get());
|
||||||
} catch (NamingException ne) {
|
} catch (NamingException ne) {
|
||||||
throw new ModelException("Could not add type [" + attributedType + "].", ne);
|
throw new ModelException("Could not retrieve identifier for entry [" + ldapObject.getDn().toString() + "].");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,6 +2,7 @@ package org.keycloak.federation.ldap.idm.store.ldap;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
@ -29,13 +30,11 @@ import javax.naming.ldap.PagedResultsControl;
|
||||||
import javax.naming.ldap.PagedResultsResponseControl;
|
import javax.naming.ldap.PagedResultsResponseControl;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.federation.ldap.idm.model.IdentityType;
|
import org.keycloak.federation.ldap.LDAPConfig;
|
||||||
import org.keycloak.federation.ldap.idm.query.internal.IdentityQuery;
|
import org.keycloak.federation.ldap.idm.query.internal.LDAPIdentityQuery;
|
||||||
import org.keycloak.models.LDAPConstants;
|
import org.keycloak.models.LDAPConstants;
|
||||||
import org.keycloak.models.ModelException;
|
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>
|
* <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 static final Logger logger = Logger.getLogger(LDAPOperationManager.class);
|
||||||
|
|
||||||
private final LDAPIdentityStoreConfiguration config;
|
private final LDAPConfig config;
|
||||||
private final Map<String, Object> connectionProperties;
|
private final Map<String, Object> connectionProperties;
|
||||||
|
|
||||||
public LDAPOperationManager(LDAPIdentityStoreConfiguration config) throws NamingException {
|
public LDAPOperationManager(LDAPConfig config) throws NamingException {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.connectionProperties = Collections.unmodifiableMap(createConnectionProperties());
|
this.connectionProperties = Collections.unmodifiableMap(createConnectionProperties());
|
||||||
}
|
}
|
||||||
|
@ -121,56 +120,29 @@ public class LDAPOperationManager {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
* Searches the LDAP tree.
|
* Removes the object from the LDAP tree
|
||||||
* </p>
|
* </p>
|
||||||
*
|
|
||||||
* @param baseDN
|
|
||||||
* @param id
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
*/
|
||||||
public void removeEntryById(final String baseDN, final String id, final LDAPMappingConfiguration mappingConfiguration) {
|
public void removeEntry(final String entryDn) {
|
||||||
final String filter = getFilterById(baseDN, id);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final SearchControls cons = getSearchControls(mappingConfiguration);
|
|
||||||
|
|
||||||
execute(new LdapOperation<SearchResult>() {
|
execute(new LdapOperation<SearchResult>() {
|
||||||
@Override
|
@Override
|
||||||
public SearchResult execute(LdapContext context) throws NamingException {
|
public SearchResult execute(LdapContext context) throws NamingException {
|
||||||
NamingEnumeration<SearchResult> result = context.search(baseDN, filter, cons);
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debugf("Removing entry with DN [%s]", entryDn);
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
|
destroySubcontext(context, entryDn);
|
||||||
result.close();
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (NamingException e) {
|
} 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 List<SearchResult> result = new ArrayList<SearchResult>();
|
||||||
final SearchControls cons = getSearchControls(mappingConfiguration);
|
final SearchControls cons = getSearchControls(returningAttributes, searchScope);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return execute(new LdapOperation<List<SearchResult>>() {
|
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 List<SearchResult> result = new ArrayList<SearchResult>();
|
||||||
final SearchControls cons = getSearchControls(mappingConfiguration);
|
final SearchControls cons = getSearchControls(identityQuery.getReturningLdapAttributes(), identityQuery.getSearchScope());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return execute(new LdapOperation<List<SearchResult>>() {
|
return execute(new LdapOperation<List<SearchResult>>() {
|
||||||
@Override
|
@Override
|
||||||
public List<SearchResult> execute(LdapContext context) throws NamingException {
|
public List<SearchResult> execute(LdapContext context) throws NamingException {
|
||||||
try {
|
try {
|
||||||
byte[] cookie = (byte[])identityQuery.getPaginationContext();
|
byte[] cookie = identityQuery.getPaginationContext();
|
||||||
PagedResultsControl pagedControls = new PagedResultsControl(identityQuery.getLimit(), cookie, Control.CRITICAL);
|
PagedResultsControl pagedControls = new PagedResultsControl(identityQuery.getLimit(), cookie, Control.CRITICAL);
|
||||||
context.setRequestControls(new Control[] { pagedControls });
|
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();
|
final SearchControls cons = new SearchControls();
|
||||||
|
|
||||||
cons.setSearchScope(SUBTREE_SCOPE);
|
cons.setSearchScope(searchScope);
|
||||||
cons.setReturningObjFlag(false);
|
cons.setReturningObjFlag(false);
|
||||||
|
|
||||||
Set<String> returningAttributes = getReturningAttributes(mappingConfiguration);
|
returningAttributes = getReturningAttributes(returningAttributes);
|
||||||
|
|
||||||
cons.setReturningAttributes(returningAttributes.toArray(new String[returningAttributes.size()]));
|
cons.setReturningAttributes(returningAttributes.toArray(new String[returningAttributes.size()]));
|
||||||
return cons;
|
return cons;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getFilterById(String baseDN, String id) {
|
public String getFilterById(String id) {
|
||||||
String filter = null;
|
String filter = null;
|
||||||
|
|
||||||
if (this.config.isActiveDirectory()) {
|
if (this.config.isActiveDirectory()) {
|
||||||
|
@ -266,24 +238,24 @@ public class LDAPOperationManager {
|
||||||
|
|
||||||
byte[] objectGUID = (byte[]) attributes.get(LDAPConstants.OBJECT_GUID).get();
|
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) {
|
} catch (NamingException ne) {
|
||||||
return filter;
|
return filter;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filter == null) {
|
if (filter == null) {
|
||||||
filter = "(&(objectClass=*)(" + getUniqueIdentifierAttributeName() + LDAPConstants.EQUAL + id + "))";
|
filter = "(&(objectClass=*)(" + getUuidAttributeName() + LDAPConstants.EQUAL + id + "))";
|
||||||
}
|
}
|
||||||
|
|
||||||
return filter;
|
return filter;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SearchResult lookupById(final String baseDN, final String id, final LDAPMappingConfiguration mappingConfiguration) {
|
public SearchResult lookupById(final String baseDN, final String id, final Collection<String> returningAttributes) {
|
||||||
final String filter = getFilterById(baseDN, id);
|
final String filter = getFilterById(id);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final SearchControls cons = getSearchControls(mappingConfiguration);
|
final SearchControls cons = getSearchControls(returningAttributes, this.config.getSearchScope());
|
||||||
|
|
||||||
return execute(new LdapOperation<SearchResult>() {
|
return execute(new LdapOperation<SearchResult>() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -446,15 +418,15 @@ public class LDAPOperationManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getUniqueIdentifierAttributeName() {
|
private String getUuidAttributeName() {
|
||||||
return this.config.getUniqueIdentifierAttributeName();
|
return this.config.getUuidAttributeName();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Attributes getAttributes(final String entryUUID, final String baseDN, LDAPMappingConfiguration mappingConfiguration) {
|
public Attributes getAttributes(final String entryUUID, final String baseDN, Set<String> returningAttributes) {
|
||||||
SearchResult search = lookupById(baseDN, entryUUID, mappingConfiguration);
|
SearchResult search = lookupById(baseDN, entryUUID, returningAttributes);
|
||||||
|
|
||||||
if (search == null) {
|
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();
|
return search.getAttributes();
|
||||||
|
@ -482,7 +454,7 @@ public class LDAPOperationManager {
|
||||||
env.put(Context.INITIAL_CONTEXT_FACTORY, this.config.getFactoryName());
|
env.put(Context.INITIAL_CONTEXT_FACTORY, this.config.getFactoryName());
|
||||||
env.put(Context.SECURITY_AUTHENTICATION, this.config.getAuthType());
|
env.put(Context.SECURITY_AUTHENTICATION, this.config.getAuthType());
|
||||||
|
|
||||||
String protocol = this.config.getProtocol();
|
String protocol = this.config.getSecurityProtocol();
|
||||||
|
|
||||||
if (protocol != null) {
|
if (protocol != null) {
|
||||||
env.put(Context.SECURITY_PROTOCOL, protocol);
|
env.put(Context.SECURITY_PROTOCOL, protocol);
|
||||||
|
@ -501,7 +473,7 @@ public class LDAPOperationManager {
|
||||||
env.put(Context.SECURITY_CREDENTIALS, bindCredential);
|
env.put(Context.SECURITY_CREDENTIALS, bindCredential);
|
||||||
}
|
}
|
||||||
|
|
||||||
String url = this.config.getLdapURL();
|
String url = this.config.getConnectionUrl();
|
||||||
|
|
||||||
if (url == null) {
|
if (url == null) {
|
||||||
throw new RuntimeException("url");
|
throw new RuntimeException("url");
|
||||||
|
@ -509,9 +481,13 @@ public class LDAPOperationManager {
|
||||||
|
|
||||||
env.put(Context.PROVIDER_URL, url);
|
env.put(Context.PROVIDER_URL, url);
|
||||||
|
|
||||||
// Just dump the additional properties
|
String connectionPooling = this.config.getConnectionPooling();
|
||||||
Properties additionalProperties = this.config.getConnectionProperties();
|
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) {
|
if (additionalProperties != null) {
|
||||||
for (Object key : additionalProperties.keySet()) {
|
for (Object key : additionalProperties.keySet()) {
|
||||||
env.put(key.toString(), additionalProperties.getProperty(key.toString()));
|
env.put(key.toString(), additionalProperties.getProperty(key.toString()));
|
||||||
|
@ -533,8 +509,6 @@ public class LDAPOperationManager {
|
||||||
LdapContext context = null;
|
LdapContext context = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// TODO: Remove this
|
|
||||||
logger.info("Executing operation: " + operation);
|
|
||||||
context = createLdapContext();
|
context = createLdapContext();
|
||||||
return operation.execute(context);
|
return operation.execute(context);
|
||||||
} catch (NamingException ne) {
|
} catch (NamingException ne) {
|
||||||
|
@ -555,27 +529,13 @@ public class LDAPOperationManager {
|
||||||
R execute(LdapContext context) throws NamingException;
|
R execute(LdapContext context) throws NamingException;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Set<String> getReturningAttributes(final LDAPMappingConfiguration mappingConfiguration) {
|
private Set<String> getReturningAttributes(final Collection<String> returningAttributes) {
|
||||||
Set<String> returningAttributes = new HashSet<String>();
|
Set<String> result = new HashSet<String>();
|
||||||
|
|
||||||
if (mappingConfiguration != null) {
|
result.addAll(returningAttributes);
|
||||||
returningAttributes.addAll(mappingConfiguration.getMappedProperties().values());
|
result.add(getUuidAttributeName());
|
||||||
|
result.add(LDAPConstants.OBJECT_CLASS);
|
||||||
|
|
||||||
returningAttributes.add(mappingConfiguration.getParentMembershipAttributeName());
|
return result;
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
org.keycloak.federation.ldap.mappers.UserAttributeLDAPFederationMapper
|
|
@ -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:
|
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>
|
mvn exec:java -Pkeycloak-server -Dkeycloak.theme.dir=<PATH TO THEMES DIR>
|
||||||
|
|
||||||
|
|
|
@ -13,14 +13,17 @@ public class LDAPConstants {
|
||||||
public static final String VENDOR_NOVELL_EDIRECTORY="edirectory" ;
|
public static final String VENDOR_NOVELL_EDIRECTORY="edirectory" ;
|
||||||
|
|
||||||
public static final String USERNAME_LDAP_ATTRIBUTE = "usernameLDAPAttribute";
|
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 USER_OBJECT_CLASSES = "userObjectClasses";
|
||||||
|
|
||||||
public static final String CONNECTION_URL = "connectionUrl";
|
public static final String CONNECTION_URL = "connectionUrl";
|
||||||
public static final String BASE_DN = "baseDn";
|
public static final String SECURITY_PROTOCOL = "securityProtocol";
|
||||||
public static final String USER_DN_SUFFIX = "userDnSuffix";
|
public static final String USER_DNS = "userDns";
|
||||||
public static final String BIND_DN = "bindDn";
|
public static final String BIND_DN = "bindDn";
|
||||||
public static final String BIND_CREDENTIAL = "bindCredential";
|
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 CONNECTION_POOLING = "connectionPooling";
|
||||||
public static final String PAGINATION = "pagination";
|
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_ID = "LDAP_ID";
|
||||||
public static final String LDAP_ENTRY_DN = "LDAP_ENTRY_DN";
|
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
|
// Those are forked from Picketlink
|
||||||
public static final String GIVENNAME = "givenname";
|
public static final String GIVENNAME = "givenname";
|
||||||
public static final String CN = "cn";
|
public static final String CN = "cn";
|
||||||
public static final String SN = "sn";
|
public static final String SN = "sn";
|
||||||
public static final String EMAIL = "mail";
|
public static final String EMAIL = "mail";
|
||||||
|
public static final String POSTAL_CODE = "postalCode";
|
||||||
public static final String MEMBER = "member";
|
public static final String MEMBER = "member";
|
||||||
public static final String MEMBER_OF = "memberOf";
|
public static final String MEMBER_OF = "memberOf";
|
||||||
public static final String OBJECT_CLASS = "objectclass";
|
public static final String OBJECT_CLASS = "objectclass";
|
||||||
|
@ -57,7 +63,7 @@ public class LDAPConstants {
|
||||||
|
|
||||||
public static final String COMMA = ",";
|
public static final String COMMA = ",";
|
||||||
public static final String EQUAL = "=";
|
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_ENABLED = "enabled";
|
||||||
public static final String CUSTOM_ATTRIBUTE_CREATE_DATE = "createDate";
|
public static final String CUSTOM_ATTRIBUTE_CREATE_DATE = "createDate";
|
||||||
|
|
|
@ -189,6 +189,8 @@ public interface RealmModel extends RoleContainerModel {
|
||||||
void removeUserFederationProvider(UserFederationProviderModel provider);
|
void removeUserFederationProvider(UserFederationProviderModel provider);
|
||||||
void setUserFederationProviders(List<UserFederationProviderModel> providers);
|
void setUserFederationProviders(List<UserFederationProviderModel> providers);
|
||||||
|
|
||||||
|
List<UserFederationMapperModel> getUserFederationMappers();
|
||||||
|
|
||||||
String getLoginTheme();
|
String getLoginTheme();
|
||||||
|
|
||||||
void setLoginTheme(String name);
|
void setLoginTheme(String name);
|
||||||
|
|
|
@ -85,7 +85,7 @@ public class UserFederationManager implements UserProvider {
|
||||||
|
|
||||||
protected void validateUser(RealmModel realm, UserModel user) {
|
protected void validateUser(RealmModel realm, UserModel user) {
|
||||||
UserFederationProvider link = getFederationLink(realm, user);
|
UserFederationProvider link = getFederationLink(realm, user);
|
||||||
if (link != null && !link.isValid(user)) {
|
if (link != null && !link.isValid(realm, user)) {
|
||||||
deleteInvalidUser(realm, user);
|
deleteInvalidUser(realm, user);
|
||||||
throw new IllegalStateException("Federated user no longer valid");
|
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) {
|
protected UserModel validateAndProxyUser(RealmModel realm, UserModel user) {
|
||||||
UserFederationProvider link = getFederationLink(realm, user);
|
UserFederationProvider link = getFederationLink(realm, user);
|
||||||
if (link != null) {
|
if (link != null) {
|
||||||
if (isValid(realm, user)) {
|
UserModel validatedProxyUser = link.validateAndProxy(realm, user);
|
||||||
return link.proxy(user);
|
if (validatedProxyUser != null) {
|
||||||
|
return validatedProxyUser;
|
||||||
} else {
|
} else {
|
||||||
deleteInvalidUser(realm, user);
|
deleteInvalidUser(realm, user);
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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.
|
* 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
|
* 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
|
* LDAP whenever UserModel update methods are invoked. It also overrides UserModel.updateCredential for the
|
||||||
* credential types it supports
|
* credential types it supports
|
||||||
*
|
*
|
||||||
* @param local
|
* @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?
|
* Should user registrations be synchronized with this provider?
|
||||||
|
@ -120,7 +120,7 @@ public interface UserFederationProvider extends Provider {
|
||||||
* @param local
|
* @param local
|
||||||
* @return
|
* @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
|
* 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
|
* Validate credentials of unknown user. The authenticated user is recognized based on provided credentials and returned back in CredentialValidationOutput
|
||||||
* @param realm
|
* @param realm
|
||||||
* @param input
|
* @param credential
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
CredentialValidationOutput validCredentials(RealmModel realm, UserCredentialModel credential);
|
CredentialValidationOutput validCredentials(RealmModel realm, UserCredentialModel credential);
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
org.keycloak.models.UserFederationSpi
|
org.keycloak.models.UserFederationSpi
|
||||||
|
org.keycloak.models.UserFederationMapperSpi
|
||||||
org.keycloak.models.RealmSpi
|
org.keycloak.models.RealmSpi
|
||||||
org.keycloak.models.UserSessionSpi
|
org.keycloak.models.UserSessionSpi
|
||||||
org.keycloak.models.UserSpi
|
org.keycloak.models.UserSpi
|
||||||
|
|
|
@ -27,6 +27,7 @@ import org.keycloak.models.PasswordPolicy;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RequiredCredentialModel;
|
import org.keycloak.models.RequiredCredentialModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
|
import org.keycloak.models.UserFederationMapperModel;
|
||||||
import org.keycloak.models.UserFederationProviderModel;
|
import org.keycloak.models.UserFederationProviderModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.entities.ClientEntity;
|
import org.keycloak.models.entities.ClientEntity;
|
||||||
|
@ -1184,4 +1185,9 @@ public class RealmAdapter implements RealmModel {
|
||||||
return mapping;
|
return mapping;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<UserFederationMapperModel> getUserFederationMappers() {
|
||||||
|
throw new IllegalStateException("Not yet implemented");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,11 +5,14 @@ import org.keycloak.enums.SslRequired;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.IdentityProviderMapperModel;
|
import org.keycloak.models.IdentityProviderMapperModel;
|
||||||
import org.keycloak.models.IdentityProviderModel;
|
import org.keycloak.models.IdentityProviderModel;
|
||||||
|
import org.keycloak.models.LDAPConstants;
|
||||||
import org.keycloak.models.PasswordPolicy;
|
import org.keycloak.models.PasswordPolicy;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RequiredCredentialModel;
|
import org.keycloak.models.RequiredCredentialModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
|
import org.keycloak.models.UserFederationMapperModel;
|
||||||
import org.keycloak.models.UserFederationProviderModel;
|
import org.keycloak.models.UserFederationProviderModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.cache.entities.CachedRealm;
|
import org.keycloak.models.cache.entities.CachedRealm;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
|
|
||||||
|
@ -17,6 +20,7 @@ import java.security.Key;
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
|
@ -948,4 +952,55 @@ public class RealmAdapter implements RealmModel {
|
||||||
return null;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import org.keycloak.models.PasswordPolicy;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RequiredCredentialModel;
|
import org.keycloak.models.RequiredCredentialModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
|
import org.keycloak.models.UserFederationMapperModel;
|
||||||
import org.keycloak.models.UserFederationProviderModel;
|
import org.keycloak.models.UserFederationProviderModel;
|
||||||
import org.keycloak.models.jpa.entities.ClientEntity;
|
import org.keycloak.models.jpa.entities.ClientEntity;
|
||||||
import org.keycloak.models.jpa.entities.IdentityProviderEntity;
|
import org.keycloak.models.jpa.entities.IdentityProviderEntity;
|
||||||
|
@ -1351,4 +1352,9 @@ public class RealmAdapter implements RealmModel {
|
||||||
return mapping;
|
return mapping;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<UserFederationMapperModel> getUserFederationMappers() {
|
||||||
|
throw new IllegalStateException("Not yet implemented");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -14,6 +14,7 @@ import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RealmProvider;
|
import org.keycloak.models.RealmProvider;
|
||||||
import org.keycloak.models.RequiredCredentialModel;
|
import org.keycloak.models.RequiredCredentialModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
|
import org.keycloak.models.UserFederationMapperModel;
|
||||||
import org.keycloak.models.UserFederationProviderModel;
|
import org.keycloak.models.UserFederationProviderModel;
|
||||||
import org.keycloak.models.entities.IdentityProviderEntity;
|
import org.keycloak.models.entities.IdentityProviderEntity;
|
||||||
import org.keycloak.models.entities.IdentityProviderMapperEntity;
|
import org.keycloak.models.entities.IdentityProviderMapperEntity;
|
||||||
|
@ -1213,4 +1214,8 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
|
||||||
return mapping;
|
return mapping;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<UserFederationMapperModel> getUserFederationMappers() {
|
||||||
|
throw new IllegalStateException("Not yet implemented");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ public class DummyUserFederationProvider implements UserFederationProvider {
|
||||||
private static Map<String, UserModel> users = new HashMap<String, UserModel>();
|
private static Map<String, UserModel> users = new HashMap<String, UserModel>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserModel proxy(UserModel local) {
|
public UserModel validateAndProxy(RealmModel realm, UserModel local) {
|
||||||
return local;
|
return local;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,7 +68,7 @@ public class DummyUserFederationProvider implements UserFederationProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isValid(UserModel local) {
|
public boolean isValid(RealmModel realm, UserModel local) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,8 +12,7 @@ import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.federation.ldap.LDAPFederationProvider;
|
import org.keycloak.federation.ldap.LDAPFederationProvider;
|
||||||
import org.keycloak.federation.ldap.LDAPFederationProviderFactory;
|
import org.keycloak.federation.ldap.LDAPFederationProviderFactory;
|
||||||
import org.keycloak.federation.ldap.LDAPUtils;
|
import org.keycloak.federation.ldap.LDAPUtils;
|
||||||
import org.keycloak.federation.ldap.idm.model.LDAPUser;
|
import org.keycloak.federation.ldap.idm.model.LDAPObject;
|
||||||
import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
|
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.LDAPConstants;
|
import org.keycloak.models.LDAPConstants;
|
||||||
import org.keycloak.models.ModelReadOnlyException;
|
import org.keycloak.models.ModelReadOnlyException;
|
||||||
|
@ -23,6 +22,7 @@ import org.keycloak.models.UserCredentialValueModel;
|
||||||
import org.keycloak.models.UserFederationProvider;
|
import org.keycloak.models.UserFederationProvider;
|
||||||
import org.keycloak.models.UserFederationProviderModel;
|
import org.keycloak.models.UserFederationProviderModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.models.utils.UserModelDelegate;
|
||||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||||
import org.keycloak.services.managers.RealmManager;
|
import org.keycloak.services.managers.RealmManager;
|
||||||
import org.keycloak.testsuite.OAuthClient;
|
import org.keycloak.testsuite.OAuthClient;
|
||||||
|
@ -53,7 +53,7 @@ public class FederationProvidersIntegrationTest {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
|
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();
|
Map<String,String> ldapConfig = ldapRule.getConfig();
|
||||||
ldapConfig.put(LDAPConstants.SYNC_REGISTRATIONS, "true");
|
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);
|
ldapModel = appRealm.addUserFederationProvider(LDAPFederationProviderFactory.PROVIDER_NAME, ldapConfig, 0, "test-ldap", -1, -1, 0);
|
||||||
|
|
||||||
// Delete all LDAP users and add some new for testing
|
// Delete all LDAP users and add some new for testing
|
||||||
LDAPIdentityStore ldapStore = getLdapIdentityStore(manager.getSession(), ldapModel);
|
LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel);
|
||||||
LDAPUtils.removeAllUsers(ldapStore);
|
LDAPUtils.removeAllUsers(ldapFedProvider, appRealm);
|
||||||
|
|
||||||
LDAPUser john = LDAPUtils.addUser(ldapStore, "johnkeycloak", "John", "Doe", "john@email.org");
|
LDAPObject john = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "johnkeycloak", "John", "Doe", "john@email.org", "1234");
|
||||||
LDAPUtils.updatePassword(ldapStore, john, "Password1");
|
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
|
@Test
|
||||||
public void caseSensitiveSearch() {
|
public void caseSensitiveSearch() {
|
||||||
|
@ -128,9 +116,6 @@ public class FederationProvidersIntegrationTest {
|
||||||
// This should fail for now due to case-sensitivity
|
// This should fail for now due to case-sensitivity
|
||||||
loginPage.login("johnKeycloak", "Password1");
|
loginPage.login("johnKeycloak", "Password1");
|
||||||
Assert.assertEquals("Invalid username or password.", loginPage.getError());
|
Assert.assertEquals("Invalid username or password.", loginPage.getError());
|
||||||
|
|
||||||
loginPage.login("John@email.org", "Password1");
|
|
||||||
Assert.assertEquals("Invalid username or password.", loginPage.getError());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -191,6 +176,7 @@ public class FederationProvidersIntegrationTest {
|
||||||
Assert.assertEquals("John", profilePage.getFirstName());
|
Assert.assertEquals("John", profilePage.getFirstName());
|
||||||
Assert.assertEquals("Doe", profilePage.getLastName());
|
Assert.assertEquals("Doe", profilePage.getLastName());
|
||||||
Assert.assertEquals("john@email.org", profilePage.getEmail());
|
Assert.assertEquals("john@email.org", profilePage.getEmail());
|
||||||
|
Assert.assertEquals("1234", profilePage.getPostalCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -257,7 +243,7 @@ public class FederationProvidersIntegrationTest {
|
||||||
loginPage.clickRegister();
|
loginPage.clickRegister();
|
||||||
registerPage.assertCurrent();
|
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());
|
Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||||
|
|
||||||
KeycloakSession session = keycloakRule.startSession();
|
KeycloakSession session = keycloakRule.startSession();
|
||||||
|
@ -267,6 +253,8 @@ public class FederationProvidersIntegrationTest {
|
||||||
Assert.assertNotNull(user);
|
Assert.assertNotNull(user);
|
||||||
Assert.assertNotNull(user.getFederationLink());
|
Assert.assertNotNull(user.getFederationLink());
|
||||||
Assert.assertEquals(user.getFederationLink(), ldapModel.getId());
|
Assert.assertEquals(user.getFederationLink(), ldapModel.getId());
|
||||||
|
Assert.assertEquals("78910", user.getAttribute("postal_code"));
|
||||||
|
Assert.assertEquals("non-LDAP-Mapped street", user.getAttribute("street"));
|
||||||
} finally {
|
} finally {
|
||||||
keycloakRule.stopSession(session, false);
|
keycloakRule.stopSession(session, false);
|
||||||
}
|
}
|
||||||
|
@ -346,13 +334,14 @@ public class FederationProvidersIntegrationTest {
|
||||||
@Test
|
@Test
|
||||||
public void testSearch() {
|
public void testSearch() {
|
||||||
KeycloakSession session = keycloakRule.startSession();
|
KeycloakSession session = keycloakRule.startSession();
|
||||||
LDAPIdentityStore ldapStore = getLdapIdentityStore(session, ldapModel);
|
|
||||||
try {
|
try {
|
||||||
RealmModel appRealm = session.realms().getRealmByName("test");
|
RealmModel appRealm = session.realms().getRealmByName("test");
|
||||||
LDAPUtils.addUser(ldapStore, "username1", "John1", "Doel1", "user1@email.org");
|
LDAPFederationProvider ldapProvider = FederationTestUtils.getLdapProvider(session, ldapModel);
|
||||||
LDAPUtils.addUser(ldapStore, "username2", "John2", "Doel2", "user2@email.org");
|
|
||||||
LDAPUtils.addUser(ldapStore, "username3", "John3", "Doel3", "user3@email.org");
|
FederationTestUtils.addLDAPUser(ldapProvider, appRealm, "username1", "John1", "Doel1", "user1@email.org", "121");
|
||||||
LDAPUtils.addUser(ldapStore, "username4", "John4", "Doel4", "user4@email.org");
|
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
|
// Users are not at local store at this moment
|
||||||
Assert.assertNull(session.userStorage().getUserByUsername("username1", appRealm));
|
Assert.assertNull(session.userStorage().getUserByUsername("username1", appRealm));
|
||||||
|
@ -362,19 +351,19 @@ public class FederationProvidersIntegrationTest {
|
||||||
|
|
||||||
// search by username
|
// search by username
|
||||||
session.users().searchForUser("username1", appRealm);
|
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
|
// search by email
|
||||||
session.users().searchForUser("user2@email.org", appRealm);
|
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
|
// search by lastName
|
||||||
session.users().searchForUser("Doel3", appRealm);
|
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
|
// search by firstName + lastName
|
||||||
session.users().searchForUser("John4 Doel4", appRealm);
|
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 {
|
} finally {
|
||||||
keycloakRule.stopSession(session, true);
|
keycloakRule.stopSession(session, true);
|
||||||
}
|
}
|
||||||
|
@ -402,7 +391,9 @@ public class FederationProvidersIntegrationTest {
|
||||||
Assert.assertTrue(session.users().validCredentials(appRealm, user, cred));
|
Assert.assertTrue(session.users().validCredentials(appRealm, user, cred));
|
||||||
|
|
||||||
// LDAP password is still unchanged
|
// 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?
|
// 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));
|
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"));
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,10 +7,10 @@ import org.junit.Test;
|
||||||
import org.junit.rules.RuleChain;
|
import org.junit.rules.RuleChain;
|
||||||
import org.junit.rules.TestRule;
|
import org.junit.rules.TestRule;
|
||||||
import org.junit.runners.MethodSorters;
|
import org.junit.runners.MethodSorters;
|
||||||
|
import org.keycloak.federation.ldap.LDAPFederationProvider;
|
||||||
import org.keycloak.federation.ldap.LDAPFederationProviderFactory;
|
import org.keycloak.federation.ldap.LDAPFederationProviderFactory;
|
||||||
import org.keycloak.federation.ldap.LDAPUtils;
|
import org.keycloak.federation.ldap.LDAPUtils;
|
||||||
import org.keycloak.federation.ldap.idm.model.LDAPUser;
|
import org.keycloak.federation.ldap.idm.model.LDAPObject;
|
||||||
import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
|
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
import org.keycloak.models.LDAPConstants;
|
import org.keycloak.models.LDAPConstants;
|
||||||
|
@ -57,12 +57,12 @@ public class SyncProvidersTest {
|
||||||
-1, -1, 0);
|
-1, -1, 0);
|
||||||
|
|
||||||
// Delete all LDAP users and add 5 new users for testing
|
// Delete all LDAP users and add 5 new users for testing
|
||||||
LDAPIdentityStore ldapStore = FederationProvidersIntegrationTest.getLdapIdentityStore(manager.getSession(), ldapModel);
|
LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel);
|
||||||
LDAPUtils.removeAllUsers(ldapStore);
|
LDAPUtils.removeAllUsers(ldapFedProvider, appRealm);
|
||||||
|
|
||||||
for (int i=1 ; i<=5 ; i++) {
|
for (int i=1 ; i<=5 ; i++) {
|
||||||
LDAPUser user = LDAPUtils.addUser(ldapStore, "user" + i, "User" + i + "FN", "User" + i + "LN", "user" + i + "@email.org");
|
LDAPObject ldapUser = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "user" + i, "User" + i + "FN", "User" + i + "LN", "user" + i + "@email.org", "12" + i);
|
||||||
LDAPUtils.updatePassword(ldapStore, user, "Password1");
|
ldapFedProvider.getLdapIdentityStore().updatePassword(ldapUser, "Password1");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add dummy provider
|
// Add dummy provider
|
||||||
|
@ -96,11 +96,11 @@ public class SyncProvidersTest {
|
||||||
RealmModel testRealm = session.realms().getRealm("test");
|
RealmModel testRealm = session.realms().getRealm("test");
|
||||||
UserProvider userProvider = session.userStorage();
|
UserProvider userProvider = session.userStorage();
|
||||||
// Assert users imported
|
// Assert users imported
|
||||||
assertUserImported(userProvider, testRealm, "user1", "User1FN", "User1LN", "user1@email.org");
|
FederationTestUtils.assertUserImported(userProvider, testRealm, "user1", "User1FN", "User1LN", "user1@email.org", "121");
|
||||||
assertUserImported(userProvider, testRealm, "user2", "User2FN", "User2LN", "user2@email.org");
|
FederationTestUtils.assertUserImported(userProvider, testRealm, "user2", "User2FN", "User2LN", "user2@email.org", "122");
|
||||||
assertUserImported(userProvider, testRealm, "user3", "User3FN", "User3LN", "user3@email.org");
|
FederationTestUtils.assertUserImported(userProvider, testRealm, "user3", "User3FN", "User3LN", "user3@email.org", "123");
|
||||||
assertUserImported(userProvider, testRealm, "user4", "User4FN", "User4LN", "user4@email.org");
|
FederationTestUtils.assertUserImported(userProvider, testRealm, "user4", "User4FN", "User4LN", "user4@email.org", "124");
|
||||||
assertUserImported(userProvider, testRealm, "user5", "User5FN", "User5LN", "user5@email.org");
|
FederationTestUtils.assertUserImported(userProvider, testRealm, "user5", "User5FN", "User5LN", "user5@email.org", "125");
|
||||||
|
|
||||||
// Assert lastSync time updated
|
// Assert lastSync time updated
|
||||||
Assert.assertTrue(ldapModel.getLastSync() > 0);
|
Assert.assertTrue(ldapModel.getLastSync() > 0);
|
||||||
|
@ -117,12 +117,16 @@ public class SyncProvidersTest {
|
||||||
sleep(1000);
|
sleep(1000);
|
||||||
|
|
||||||
// Add user to LDAP and update 'user5' in LDAP
|
// Add user to LDAP and update 'user5' in LDAP
|
||||||
LDAPIdentityStore ldapStore = FederationProvidersIntegrationTest.getLdapIdentityStore(session, ldapModel);
|
LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel);
|
||||||
LDAPUtils.addUser(ldapStore, "user6", "User6FN", "User6LN", "user6@email.org");
|
FederationTestUtils.addLDAPUser(ldapFedProvider, testRealm, "user6", "User6FN", "User6LN", "user6@email.org", "126");
|
||||||
LDAPUtils.updateUser(ldapStore, "user5", "User5FNUpdated", "User5LNUpdated", "user5Updated@email.org");
|
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
|
// 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));
|
Assert.assertNull(userProvider.getUserByUsername("user6", testRealm));
|
||||||
|
|
||||||
// Trigger partial sync
|
// Trigger partial sync
|
||||||
|
@ -138,8 +142,8 @@ public class SyncProvidersTest {
|
||||||
RealmModel testRealm = session.realms().getRealm("test");
|
RealmModel testRealm = session.realms().getRealm("test");
|
||||||
UserProvider userProvider = session.userStorage();
|
UserProvider userProvider = session.userStorage();
|
||||||
// Assert users updated in local provider
|
// Assert users updated in local provider
|
||||||
assertUserImported(userProvider, testRealm, "user5", "User5FNUpdated", "User5LNUpdated", "user5Updated@email.org");
|
FederationTestUtils.assertUserImported(userProvider, testRealm, "user5", "User5FN", "User5LN", "user5Updated@email.org", "521");
|
||||||
assertUserImported(userProvider, testRealm, "user6", "User6FN", "User6LN", "user6@email.org");
|
FederationTestUtils.assertUserImported(userProvider, testRealm, "user6", "User6FN", "User6LN", "user6@email.org", "126");
|
||||||
} finally {
|
} finally {
|
||||||
keycloakRule.stopSession(session, false);
|
keycloakRule.stopSession(session, false);
|
||||||
}
|
}
|
||||||
|
@ -189,12 +193,4 @@ public class SyncProvidersTest {
|
||||||
Assert.assertEquals(syncResult.getUpdated(), expectedUpdated);
|
Assert.assertEquals(syncResult.getUpdated(), expectedUpdated);
|
||||||
Assert.assertEquals(syncResult.getRemoved(), expectedRemoved);
|
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());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,10 +28,9 @@ public class LDAPConfiguration {
|
||||||
|
|
||||||
static {
|
static {
|
||||||
PROP_MAPPINGS.put(LDAPConstants.CONNECTION_URL, "idm.test.ldap.connection.url");
|
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("rolesDnSuffix", "idm.test.ldap.roles.dn.suffix");
|
||||||
PROP_MAPPINGS.put("groupDnSuffix", "idm.test.ldap.group.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_DN, "idm.test.ldap.bind.dn");
|
||||||
PROP_MAPPINGS.put(LDAPConstants.BIND_CREDENTIAL, "idm.test.ldap.bind.credential");
|
PROP_MAPPINGS.put(LDAPConstants.BIND_CREDENTIAL, "idm.test.ldap.bind.credential");
|
||||||
PROP_MAPPINGS.put(LDAPConstants.VENDOR, "idm.test.ldap.vendor");
|
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");
|
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.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("rolesDnSuffix", "ou=Roles,dc=keycloak,dc=org");
|
||||||
DEFAULT_VALUES.put("groupDnSuffix", "ou=Groups,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_DN, "uid=admin,ou=system");
|
||||||
DEFAULT_VALUES.put(LDAPConstants.BIND_CREDENTIAL, "secret");
|
DEFAULT_VALUES.put(LDAPConstants.BIND_CREDENTIAL, "secret");
|
||||||
DEFAULT_VALUES.put(LDAPConstants.VENDOR, LDAPConstants.VENDOR_OTHER);
|
DEFAULT_VALUES.put(LDAPConstants.VENDOR, LDAPConstants.VENDOR_OTHER);
|
||||||
|
|
|
@ -44,6 +44,9 @@ public class AccountUpdateProfilePage extends AbstractAccountPage {
|
||||||
@FindBy(id = "email")
|
@FindBy(id = "email")
|
||||||
private WebElement emailInput;
|
private WebElement emailInput;
|
||||||
|
|
||||||
|
@FindBy(id = "user.attributes.postal_code")
|
||||||
|
private WebElement postalCodeInput;
|
||||||
|
|
||||||
|
|
||||||
@FindBy(id = "referrer")
|
@FindBy(id = "referrer")
|
||||||
private WebElement backToApplicationLink;
|
private WebElement backToApplicationLink;
|
||||||
|
@ -84,6 +87,10 @@ public class AccountUpdateProfilePage extends AbstractAccountPage {
|
||||||
return lastNameInput.getAttribute("value");
|
return lastNameInput.getAttribute("value");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getPostalCode() {
|
||||||
|
return postalCodeInput.getAttribute("value");
|
||||||
|
}
|
||||||
|
|
||||||
public String getEmail() {
|
public String getEmail() {
|
||||||
return emailInput.getAttribute("value");
|
return emailInput.getAttribute("value");
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ package org.keycloak.testsuite.pages;
|
||||||
|
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
|
|
||||||
|
import org.openqa.selenium.By;
|
||||||
import org.openqa.selenium.NoSuchElementException;
|
import org.openqa.selenium.NoSuchElementException;
|
||||||
import org.openqa.selenium.WebElement;
|
import org.openqa.selenium.WebElement;
|
||||||
import org.openqa.selenium.support.FindBy;
|
import org.openqa.selenium.support.FindBy;
|
||||||
|
@ -56,6 +57,25 @@ public class RegisterPage extends AbstractPage {
|
||||||
@FindBy(className = "feedback-error")
|
@FindBy(className = "feedback-error")
|
||||||
private WebElement loginErrorMessage;
|
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) {
|
public void register(String firstName, String lastName, String email, String username, String password, String passwordConfirm) {
|
||||||
firstNameInput.clear();
|
firstNameInput.clear();
|
||||||
if (firstName != null) {
|
if (firstName != null) {
|
||||||
|
|
Loading…
Reference in a new issue