From eb5ae4aae9dafb25fadb48ba82a9fdeceef9b103 Mon Sep 17 00:00:00 2001
From: mposolda
Date: Tue, 7 Apr 2015 23:10:23 +0200
Subject: [PATCH] KEYCLOAK-1007 Fork Picketlink LDAP code. Remove picketlink
dependencies from LDAP Federation provider
---
dependencies/server-all/pom.xml | 28 -
distribution/modules/build.xml | 8 -
.../keycloak-ldap-federation/main/module.xml | 5 -
.../keycloak-picketlink-api/main/module.xml | 21 -
.../keycloak-picketlink-ldap/main/module.xml | 21 -
.../keycloak/keycloak-server/main/module.xml | 2 -
.../keycloak-services/main/module.xml | 2 -
.../WEB-INF/jboss-deployment-structure.xml | 3 -
federation/ldap/pom.xml | 27 -
.../ldap/LDAPFederationProvider.java | 238 +++---
.../ldap/LDAPFederationProviderFactory.java | 59 +-
.../ldap/LDAPIdentityStoreRegistry.java | 165 ++++
.../keycloak/federation/ldap/LDAPUtils.java | 150 ++--
.../ldap/WritableLDAPUserModelDelegate.java | 102 +--
.../idm/model/AbstractAttributedType.java | 85 ++
.../ldap/idm/model/AbstractIdentityType.java | 70 ++
.../federation/ldap/idm/model/Attribute.java | 80 ++
.../ldap/idm/model/AttributeProperty.java | 31 +
.../ldap/idm/model/AttributedType.java | 75 ++
.../ldap/idm/model/IdentityType.java | 100 +++
.../federation/ldap/idm/model/LDAPUser.java | 85 ++
.../ldap/idm/query/AttributeParameter.java | 21 +
.../federation/ldap/idm/query/Condition.java | 18 +
.../ldap/idm/query/IdentityQuery.java | 225 ++++++
.../ldap/idm/query/IdentityQueryBuilder.java | 124 +++
.../ldap/idm/query/QueryParameter.java | 12 +
.../federation/ldap/idm/query/Sort.java | 23 +
.../idm/query/internal/BetweenCondition.java | 33 +
.../query/internal/DefaultIdentityQuery.java | 207 +++++
.../query/internal/DefaultQueryBuilder.java | 89 ++
.../idm/query/internal/EqualCondition.java | 36 +
.../query/internal/GreaterThanCondition.java | 34 +
.../ldap/idm/query/internal/InCondition.java | 28 +
.../idm/query/internal/LessThanCondition.java | 34 +
.../idm/query/internal/LikeCondition.java | 28 +
.../ldap/idm/store/IdentityStore.java | 81 ++
.../idm/store/ldap/LDAPIdentityStore.java | 761 ++++++++++++++++++
.../ldap/LDAPIdentityStoreConfiguration.java | 188 +++++
.../store/ldap/LDAPMappingConfiguration.java | 231 ++++++
.../idm/store/ldap/LDAPOperationManager.java | 606 ++++++++++++++
.../ldap/idm/store/ldap/LDAPUtil.java | 158 ++++
.../org/keycloak/models/LDAPConstants.java | 35 +
.../reflection/NamedPropertyCriteria.java | 40 +
.../reflection/TypedPropertyCriteria.java | 71 ++
picketlink/keycloak-picketlink-api/pom.xml | 55 --
.../picketlink/PartitionManagerProvider.java | 14 -
.../PartitionManagerProviderFactory.java | 9 -
.../picketlink/PartitionManagerSpi.java | 25 -
.../services/org.keycloak.provider.Spi | 1 -
picketlink/keycloak-picketlink-ldap/pom.xml | 66 --
.../picketlink/idm/KeycloakEventBridge.java | 63 --
.../idm/LDAPKeycloakCredentialHandler.java | 51 --
.../ldap/LDAPPartitionManagerProvider.java | 26 -
.../LDAPPartitionManagerProviderFactory.java | 43 -
.../ldap/PartitionManagerRegistry.java | 163 ----
...picketlink.PartitionManagerProviderFactory | 1 -
picketlink/pom.xml | 24 -
pom.xml | 1 -
services/pom.xml | 6 -
.../FederationProvidersIntegrationTest.java | 36 +-
.../federation/SyncProvidersTest.java | 31 +-
61 files changed, 4042 insertions(+), 1013 deletions(-)
delete mode 100755 distribution/modules/src/main/resources/modules/org/keycloak/keycloak-picketlink-api/main/module.xml
delete mode 100755 distribution/modules/src/main/resources/modules/org/keycloak/keycloak-picketlink-ldap/main/module.xml
create mode 100644 federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPIdentityStoreRegistry.java
create mode 100644 federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/AbstractAttributedType.java
create mode 100644 federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/AbstractIdentityType.java
create mode 100644 federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/Attribute.java
create mode 100644 federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/AttributeProperty.java
create mode 100644 federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/AttributedType.java
create mode 100644 federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/IdentityType.java
create mode 100644 federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/LDAPUser.java
create mode 100644 federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/AttributeParameter.java
create mode 100644 federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/Condition.java
create mode 100644 federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/IdentityQuery.java
create mode 100644 federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/IdentityQueryBuilder.java
create mode 100644 federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/QueryParameter.java
create mode 100644 federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/Sort.java
create mode 100644 federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/BetweenCondition.java
create mode 100644 federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/DefaultIdentityQuery.java
create mode 100644 federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/DefaultQueryBuilder.java
create mode 100644 federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/EqualCondition.java
create mode 100644 federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/GreaterThanCondition.java
create mode 100644 federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/InCondition.java
create mode 100644 federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/LessThanCondition.java
create mode 100644 federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/LikeCondition.java
create mode 100644 federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/IdentityStore.java
create mode 100644 federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPIdentityStore.java
create mode 100644 federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPIdentityStoreConfiguration.java
create mode 100644 federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPMappingConfiguration.java
create mode 100644 federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPOperationManager.java
create mode 100644 federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPUtil.java
create mode 100644 model/api/src/main/java/org/keycloak/models/utils/reflection/NamedPropertyCriteria.java
create mode 100644 model/api/src/main/java/org/keycloak/models/utils/reflection/TypedPropertyCriteria.java
delete mode 100755 picketlink/keycloak-picketlink-api/pom.xml
delete mode 100644 picketlink/keycloak-picketlink-api/src/main/java/org/keycloak/picketlink/PartitionManagerProvider.java
delete mode 100644 picketlink/keycloak-picketlink-api/src/main/java/org/keycloak/picketlink/PartitionManagerProviderFactory.java
delete mode 100644 picketlink/keycloak-picketlink-api/src/main/java/org/keycloak/picketlink/PartitionManagerSpi.java
delete mode 100644 picketlink/keycloak-picketlink-api/src/main/resources/META-INF/services/org.keycloak.provider.Spi
delete mode 100755 picketlink/keycloak-picketlink-ldap/pom.xml
delete mode 100755 picketlink/keycloak-picketlink-ldap/src/main/java/org/keycloak/picketlink/idm/KeycloakEventBridge.java
delete mode 100755 picketlink/keycloak-picketlink-ldap/src/main/java/org/keycloak/picketlink/idm/LDAPKeycloakCredentialHandler.java
delete mode 100644 picketlink/keycloak-picketlink-ldap/src/main/java/org/keycloak/picketlink/ldap/LDAPPartitionManagerProvider.java
delete mode 100755 picketlink/keycloak-picketlink-ldap/src/main/java/org/keycloak/picketlink/ldap/LDAPPartitionManagerProviderFactory.java
delete mode 100755 picketlink/keycloak-picketlink-ldap/src/main/java/org/keycloak/picketlink/ldap/PartitionManagerRegistry.java
delete mode 100644 picketlink/keycloak-picketlink-ldap/src/main/resources/META-INF/services/org.keycloak.picketlink.PartitionManagerProviderFactory
delete mode 100755 picketlink/pom.xml
diff --git a/dependencies/server-all/pom.xml b/dependencies/server-all/pom.xml
index 9d8921f8ed..d124ea5447 100755
--- a/dependencies/server-all/pom.xml
+++ b/dependencies/server-all/pom.xml
@@ -144,34 +144,6 @@
keycloak-kerberos-federation${project.version}
-
- org.picketlink
- picketlink-common
-
-
- org.picketlink
- picketlink-idm-api
-
-
- org.picketlink
- picketlink-idm-impl
-
-
- org.picketlink
- picketlink-idm-simple-schema
-
-
-
-
- org.keycloak
- keycloak-picketlink-api
- ${project.version}
-
-
- org.keycloak
- keycloak-picketlink-ldap
- ${project.version}
-
diff --git a/distribution/modules/build.xml b/distribution/modules/build.xml
index 9f65cb95d4..df4bef5917 100755
--- a/distribution/modules/build.xml
+++ b/distribution/modules/build.xml
@@ -259,14 +259,6 @@
-
-
-
-
-
-
-
-
diff --git a/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-ldap-federation/main/module.xml b/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-ldap-federation/main/module.xml
index 29dfd9c7d7..5f88f37e82 100755
--- a/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-ldap-federation/main/module.xml
+++ b/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-ldap-federation/main/module.xml
@@ -10,14 +10,9 @@
-
-
-
-
-
diff --git a/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-picketlink-api/main/module.xml b/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-picketlink-api/main/module.xml
deleted file mode 100755
index b51112b48a..0000000000
--- a/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-picketlink-api/main/module.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-picketlink-ldap/main/module.xml b/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-picketlink-ldap/main/module.xml
deleted file mode 100755
index 429188fbb2..0000000000
--- a/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-picketlink-ldap/main/module.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-server/main/module.xml b/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-server/main/module.xml
index f553d24263..ddf24752d0 100755
--- a/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-server/main/module.xml
+++ b/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-server/main/module.xml
@@ -48,8 +48,6 @@
-
-
diff --git a/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-services/main/module.xml b/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-services/main/module.xml
index 86e86f492e..9864a070c1 100755
--- a/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-services/main/module.xml
+++ b/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-services/main/module.xml
@@ -49,10 +49,8 @@
-
-
diff --git a/distribution/subsystem-war/src/main/webapp/WEB-INF/jboss-deployment-structure.xml b/distribution/subsystem-war/src/main/webapp/WEB-INF/jboss-deployment-structure.xml
index 6caa2c81e5..aae18bf302 100755
--- a/distribution/subsystem-war/src/main/webapp/WEB-INF/jboss-deployment-structure.xml
+++ b/distribution/subsystem-war/src/main/webapp/WEB-INF/jboss-deployment-structure.xml
@@ -40,9 +40,6 @@
-
-
-
diff --git a/federation/ldap/pom.xml b/federation/ldap/pom.xml
index a48b36daef..72803ab6e6 100755
--- a/federation/ldap/pom.xml
+++ b/federation/ldap/pom.xml
@@ -31,12 +31,6 @@
${project.version}provided
-
- org.keycloak
- keycloak-picketlink-api
- ${project.version}
- provided
- org.jboss.resteasyresteasy-jaxrs
@@ -61,27 +55,6 @@
jboss-loggingprovided
-
-
- org.picketlink
- picketlink-common
- provided
-
-
- org.picketlink
- picketlink-idm-api
- provided
-
-
- org.picketlink
- picketlink-idm-impl
- provided
-
-
- org.picketlink
- picketlink-idm-simple-schema
- provided
-
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java
index f48880cfc9..370e0f07f8 100755
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java
@@ -3,6 +3,10 @@ package org.keycloak.federation.ldap;
import org.jboss.logging.Logger;
import org.keycloak.federation.kerberos.impl.KerberosUsernamePasswordAuthenticator;
import org.keycloak.federation.kerberos.impl.SPNEGOAuthenticator;
+import org.keycloak.federation.ldap.idm.model.LDAPUser;
+import org.keycloak.federation.ldap.idm.query.IdentityQuery;
+import org.keycloak.federation.ldap.idm.query.IdentityQueryBuilder;
+import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
import org.keycloak.federation.ldap.kerberos.LDAPProviderKerberosConfig;
import org.keycloak.models.CredentialValidationOutput;
import org.keycloak.models.KeycloakSession;
@@ -16,12 +20,6 @@ import org.keycloak.models.UserFederationProvider;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserModel;
import org.keycloak.constants.KerberosConstants;
-import org.picketlink.idm.IdentityManagementException;
-import org.picketlink.idm.IdentityManager;
-import org.picketlink.idm.PartitionManager;
-import org.picketlink.idm.model.basic.BasicModel;
-import org.picketlink.idm.model.basic.User;
-import org.picketlink.idm.query.IdentityQuery;
import java.util.Arrays;
import java.util.HashMap;
@@ -38,23 +36,21 @@ import java.util.Set;
*/
public class LDAPFederationProvider implements UserFederationProvider {
private static final Logger logger = Logger.getLogger(LDAPFederationProvider.class);
- public static final String LDAP_ID = "LDAP_ID";
- public static final String SYNC_REGISTRATIONS = "syncRegistrations";
protected LDAPFederationProviderFactory factory;
protected KeycloakSession session;
protected UserFederationProviderModel model;
- protected PartitionManager partitionManager;
+ protected LDAPIdentityStore ldapIdentityStore;
protected EditMode editMode;
protected LDAPProviderKerberosConfig kerberosConfig;
protected final Set supportedCredentialTypes = new HashSet();
- public LDAPFederationProvider(LDAPFederationProviderFactory factory, KeycloakSession session, UserFederationProviderModel model, PartitionManager partitionManager) {
+ public LDAPFederationProvider(LDAPFederationProviderFactory factory, KeycloakSession session, UserFederationProviderModel model, LDAPIdentityStore ldapIdentityStore) {
this.factory = factory;
this.session = session;
this.model = model;
- this.partitionManager = partitionManager;
+ this.ldapIdentityStore = ldapIdentityStore;
this.kerberosConfig = new LDAPProviderKerberosConfig(model);
String editModeString = model.getConfig().get(LDAPConstants.EDIT_MODE);
if (editModeString == null) {
@@ -69,16 +65,6 @@ public class LDAPFederationProvider implements UserFederationProvider {
}
}
- private ModelException convertIDMException(IdentityManagementException ie) {
- Throwable realCause = ie;
- while (realCause.getCause() != null) {
- realCause = realCause.getCause();
- }
-
- // Use the message from the realCause
- return new ModelException(realCause.getMessage(), ie);
- }
-
public KeycloakSession getSession() {
return session;
}
@@ -87,8 +73,8 @@ public class LDAPFederationProvider implements UserFederationProvider {
return model;
}
- public PartitionManager getPartitionManager() {
- return partitionManager;
+ public LDAPIdentityStore getLdapIdentityStore() {
+ return this.ldapIdentityStore;
}
@Override
@@ -125,22 +111,18 @@ public class LDAPFederationProvider implements UserFederationProvider {
@Override
public boolean synchronizeRegistrations() {
- return "true".equalsIgnoreCase(model.getConfig().get(SYNC_REGISTRATIONS)) && editMode == EditMode.WRITABLE;
+ return "true".equalsIgnoreCase(model.getConfig().get(LDAPConstants.SYNC_REGISTRATIONS)) && editMode == EditMode.WRITABLE;
}
@Override
public UserModel register(RealmModel realm, UserModel user) {
- 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");
- try {
- User picketlinkUser = LDAPUtils.addUser(this.partitionManager, user.getUsername(), user.getFirstName(), user.getLastName(), user.getEmail());
- user.setAttribute(LDAP_ID, picketlinkUser.getId());
- return proxy(user);
- } catch (IdentityManagementException ie) {
- throw convertIDMException(ie);
- }
-
+ LDAPUser ldapUser = LDAPUtils.addUser(this.ldapIdentityStore, user.getUsername(), user.getFirstName(), user.getLastName(), user.getEmail());
+ user.setAttribute(LDAPConstants.LDAP_ID, ldapUser.getId());
+ user.setAttribute(LDAPConstants.LDAP_ENTRY_DN, ldapUser.getEntryDN());
+ return proxy(user);
}
@Override
@@ -150,58 +132,53 @@ public class LDAPFederationProvider implements UserFederationProvider {
return false;
}
- try {
- return LDAPUtils.removeUser(partitionManager, user.getUsername());
- } catch (IdentityManagementException ie) {
- throw convertIDMException(ie);
- }
+ return LDAPUtils.removeUser(this.ldapIdentityStore, user.getUsername());
}
@Override
public List searchByAttributes(Map attributes, RealmModel realm, int maxResults) {
List searchResults =new LinkedList();
- try {
- Map plUsers = searchPicketlink(attributes, maxResults);
- for (User user : plUsers.values()) {
- if (session.userStorage().getUserByUsername(user.getLoginName(), realm) == null) {
- UserModel imported = importUserFromPicketlink(realm, user);
- searchResults.add(imported);
- }
+
+ Map ldapUsers = searchLDAP(attributes, maxResults);
+ for (LDAPUser ldapUser : ldapUsers.values()) {
+ if (session.userStorage().getUserByUsername(ldapUser.getLoginName(), realm) == null) {
+ UserModel imported = importUserFromLDAP(realm, ldapUser);
+ searchResults.add(imported);
}
- } catch (IdentityManagementException ie) {
- throw convertIDMException(ie);
}
+
return searchResults;
}
- protected Map searchPicketlink(Map attributes, int maxResults) {
- IdentityManager identityManager = getIdentityManager();
- Map results = new HashMap();
+ protected Map searchLDAP(Map attributes, int maxResults) {
+
+ Map results = new HashMap();
if (attributes.containsKey(USERNAME)) {
- User user = BasicModel.getUser(identityManager, attributes.get(USERNAME));
+ LDAPUser user = LDAPUtils.getUser(this.ldapIdentityStore, attributes.get(USERNAME));
if (user != null) {
results.put(user.getLoginName(), user);
}
}
if (attributes.containsKey(EMAIL)) {
- User user = queryByEmail(identityManager, attributes.get(EMAIL));
+ LDAPUser user = queryByEmail(attributes.get(EMAIL));
if (user != null) {
results.put(user.getLoginName(), user);
}
}
if (attributes.containsKey(FIRST_NAME) || attributes.containsKey(LAST_NAME)) {
- IdentityQuery query = identityManager.createIdentityQuery(User.class);
+ IdentityQueryBuilder queryBuilder = this.ldapIdentityStore.createQueryBuilder();
+ IdentityQuery query = queryBuilder.createIdentityQuery(LDAPUser.class);
if (attributes.containsKey(FIRST_NAME)) {
- query.setParameter(User.FIRST_NAME, attributes.get(FIRST_NAME));
+ query.where(queryBuilder.equal(LDAPUser.FIRST_NAME, attributes.get(FIRST_NAME)));
}
if (attributes.containsKey(LAST_NAME)) {
- query.setParameter(User.LAST_NAME, attributes.get(LAST_NAME));
+ query.where(queryBuilder.equal(LDAPUser.LAST_NAME, attributes.get(LAST_NAME)));
}
query.setLimit(maxResults);
- List agents = query.getResultList();
- for (User user : agents) {
+ List users = query.getResultList();
+ for (LDAPUser user : users) {
results.put(user.getLoginName(), user);
}
}
@@ -211,85 +188,69 @@ public class LDAPFederationProvider implements UserFederationProvider {
@Override
public boolean isValid(UserModel local) {
- try {
- User picketlinkUser = LDAPUtils.getUser(partitionManager, local.getUsername());
- if (picketlinkUser == null) {
- return false;
- }
- return picketlinkUser.getId().equals(local.getAttribute(LDAP_ID));
- } catch (IdentityManagementException ie) {
- throw convertIDMException(ie);
+ LDAPUser ldapUser = LDAPUtils.getUser(this.ldapIdentityStore, local.getUsername());
+ if (ldapUser == null) {
+ return false;
}
+ return ldapUser.getId().equals(local.getAttribute(LDAPConstants.LDAP_ID));
}
@Override
public UserModel getUserByUsername(RealmModel realm, String username) {
- try {
- User picketlinkUser = LDAPUtils.getUser(partitionManager, username);
- if (picketlinkUser == null) {
- return null;
- }
-
- // KEYCLOAK-808: Should we allow case-sensitivity to be configurable?
- if (!username.equals(picketlinkUser.getLoginName())) {
- logger.warnf("User found in LDAP but with different username. LDAP username: %s, Searched username: %s", username, picketlinkUser.getLoginName());
- return null;
- }
-
- return importUserFromPicketlink(realm, picketlinkUser);
- } catch (IdentityManagementException ie) {
- throw convertIDMException(ie);
- }
- }
-
- public IdentityManager getIdentityManager() {
- return partitionManager.createIdentityManager();
- }
-
- protected UserModel importUserFromPicketlink(RealmModel realm, User picketlinkUser) {
- String email = (picketlinkUser.getEmail() != null && picketlinkUser.getEmail().trim().length() > 0) ? picketlinkUser.getEmail() : null;
-
- if (picketlinkUser.getLoginName() == null) {
- throw new ModelException("User returned from LDAP has null username! Check configuration of your LDAP mappings. ID of user from LDAP: " + picketlinkUser.getId());
+ LDAPUser ldapUser = LDAPUtils.getUser(this.ldapIdentityStore, username);
+ if (ldapUser == null) {
+ return null;
}
- UserModel imported = session.userStorage().addUser(realm, picketlinkUser.getLoginName());
+ // KEYCLOAK-808: Should we allow case-sensitivity to be configurable?
+ if (!username.equals(ldapUser.getLoginName())) {
+ logger.warnf("User found in LDAP but with different username. LDAP username: %s, Searched username: %s", username, ldapUser.getLoginName());
+ return null;
+ }
+
+ return importUserFromLDAP(realm, ldapUser);
+ }
+
+ protected UserModel importUserFromLDAP(RealmModel realm, LDAPUser ldapUser) {
+ String email = (ldapUser.getEmail() != null && ldapUser.getEmail().trim().length() > 0) ? ldapUser.getEmail() : null;
+
+ if (ldapUser.getLoginName() == null) {
+ throw new ModelException("User returned from LDAP has null username! Check configuration of your LDAP mappings. ID of user from LDAP: " + ldapUser.getId());
+ }
+
+ UserModel imported = session.userStorage().addUser(realm, ldapUser.getLoginName());
imported.setEnabled(true);
imported.setEmail(email);
- imported.setFirstName(picketlinkUser.getFirstName());
- imported.setLastName(picketlinkUser.getLastName());
+ imported.setFirstName(ldapUser.getFirstName());
+ imported.setLastName(ldapUser.getLastName());
imported.setFederationLink(model.getId());
- imported.setAttribute(LDAP_ID, picketlinkUser.getId());
+ imported.setAttribute(LDAPConstants.LDAP_ID, ldapUser.getId());
+ imported.setAttribute(LDAPConstants.LDAP_ENTRY_DN, ldapUser.getEntryDN());
- logger.debugf("Added new user from LDAP. Username: " + imported.getUsername() + ", Email: ", imported.getEmail() + ", LDAP_ID: " + picketlinkUser.getId());
+ logger.debugf("Imported new user from LDAP to Keycloak DB. Username: [%s], Email: [%s], LDAP_ID: [%s], LDAP Entry DN: [%s]", imported.getUsername(), imported.getEmail(),
+ ldapUser.getId(), ldapUser.getEntryDN());
return proxy(imported);
}
- protected User queryByEmail(IdentityManager identityManager, String email) throws IdentityManagementException {
- return LDAPUtils.getUserByEmail(identityManager, email);
+ protected LDAPUser queryByEmail(String email) {
+ return LDAPUtils.getUserByEmail(this.ldapIdentityStore, email);
}
@Override
public UserModel getUserByEmail(RealmModel realm, String email) {
- IdentityManager identityManager = getIdentityManager();
-
- try {
- User picketlinkUser = queryByEmail(identityManager, email);
- if (picketlinkUser == null) {
- return null;
- }
-
- // KEYCLOAK-808: Should we allow case-sensitivity to be configurable?
- if (!email.equals(picketlinkUser.getEmail())) {
- logger.warnf("User found in LDAP but with different email. LDAP email: %s, Searched email: %s", email, picketlinkUser.getEmail());
- return null;
- }
-
- return importUserFromPicketlink(realm, picketlinkUser);
- } catch (IdentityManagementException ie) {
- throw convertIDMException(ie);
+ LDAPUser ldapUser = queryByEmail(email);
+ if (ldapUser == null) {
+ return null;
}
+
+ // KEYCLOAK-808: Should we allow case-sensitivity to be configurable?
+ if (!email.equals(ldapUser.getEmail())) {
+ logger.warnf("User found in LDAP but with different email. LDAP email: %s, Searched email: %s", email, ldapUser.getEmail());
+ return null;
+ }
+
+ return importUserFromLDAP(realm, ldapUser);
}
@Override
@@ -302,18 +263,14 @@ public class LDAPFederationProvider implements UserFederationProvider {
// complete I don't think we have to do anything here
}
- public boolean validPassword(String username, String password) {
+ public boolean validPassword(UserModel user, String password) {
if (kerberosConfig.isAllowKerberosAuthentication() && kerberosConfig.isUseKerberosForPasswordAuthentication()) {
// Use Kerberos JAAS (Krb5LoginModule)
KerberosUsernamePasswordAuthenticator authenticator = factory.createKerberosUsernamePasswordAuthenticator(kerberosConfig);
- return authenticator.validUser(username, password);
+ return authenticator.validUser(user.getUsername(), password);
} else {
// Use Naming LDAP API
- try {
- return LDAPUtils.validatePassword(partitionManager, username, password);
- } catch (IdentityManagementException ie) {
- throw convertIDMException(ie);
- }
+ return LDAPUtils.validatePassword(this.ldapIdentityStore, user, password);
}
}
@@ -322,7 +279,7 @@ public class LDAPFederationProvider implements UserFederationProvider {
public boolean validCredentials(RealmModel realm, UserModel user, List input) {
for (UserCredentialModel cred : input) {
if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
- return validPassword(user.getUsername(), cred.getValue());
+ return validPassword(user, cred.getValue());
} else {
return false; // invalid cred type
}
@@ -353,7 +310,7 @@ public class LDAPFederationProvider implements UserFederationProvider {
UserModel user = findOrCreateAuthenticatedUser(realm, username);
if (user == null) {
- logger.warn("Kerberos/SPNEGO authentication succeeded with username [" + username + "], but couldn't find or create user with federation provider [" + model.getDisplayName() + "]");
+ logger.warnf("Kerberos/SPNEGO authentication succeeded with username [%s], but couldn't find or create user with federation provider [%s]", username, model.getDisplayName());
return CredentialValidationOutput.failed();
} else {
String delegationCredential = spnegoAuthenticator.getSerializedDelegationCredential();
@@ -375,24 +332,23 @@ public class LDAPFederationProvider implements UserFederationProvider {
@Override
public void close() {
- //To change body of implemented methods use File | Settings | File Templates.
}
- protected void importPicketlinkUsers(RealmModel realm, List users, UserFederationProviderModel fedModel) {
- for (User picketlinkUser : users) {
- String username = picketlinkUser.getLoginName();
+ protected void importLDAPUsers(RealmModel realm, List ldapUsers, UserFederationProviderModel fedModel) {
+ for (LDAPUser ldapUser : ldapUsers) {
+ String username = ldapUser.getLoginName();
UserModel currentUser = session.userStorage().getUserByUsername(username, realm);
if (currentUser == null) {
// Add new user to Keycloak
- importUserFromPicketlink(realm, picketlinkUser);
+ importUserFromLDAP(realm, ldapUser);
} else {
- if ((fedModel.getId().equals(currentUser.getFederationLink())) && (picketlinkUser.getId().equals(currentUser.getAttribute(LDAPFederationProvider.LDAP_ID)))) {
+ if ((fedModel.getId().equals(currentUser.getFederationLink())) && (ldapUser.getId().equals(currentUser.getAttribute(LDAPConstants.LDAP_ID)))) {
// Update keycloak user
- String email = (picketlinkUser.getEmail() != null && picketlinkUser.getEmail().trim().length() > 0) ? picketlinkUser.getEmail() : null;
+ String email = (ldapUser.getEmail() != null && ldapUser.getEmail().trim().length() > 0) ? ldapUser.getEmail() : null;
currentUser.setEmail(email);
- currentUser.setFirstName(picketlinkUser.getFirstName());
- currentUser.setLastName(picketlinkUser.getLastName());
+ currentUser.setFirstName(ldapUser.getFirstName());
+ currentUser.setLastName(ldapUser.getLastName());
logger.debugf("Updated user from LDAP: %s", currentUser.getUsername());
} else {
logger.warnf("User '%s' is not updated during sync as he is not linked to federation provider '%s'", username, fedModel.getDisplayName());
@@ -404,29 +360,29 @@ public class LDAPFederationProvider implements UserFederationProvider {
/**
* Called after successful kerberos authentication
*
- * @param realm
+ * @param realm realm
* @param username username without realm prefix
- * @return
+ * @return finded or newly created user
*/
protected UserModel findOrCreateAuthenticatedUser(RealmModel realm, String username) {
UserModel user = session.userStorage().getUserByUsername(username, realm);
if (user != null) {
- logger.debug("Kerberos authenticated user " + username + " found in Keycloak storage");
+ logger.debugf("Kerberos authenticated user [%s] found in Keycloak storage", username);
if (!model.getId().equals(user.getFederationLink())) {
- logger.warn("User with username " + username + " already exists, but is not linked to provider [" + model.getDisplayName() + "]");
+ logger.warnf("User with username [%s] already exists, but is not linked to provider [%s]", username, model.getDisplayName());
return null;
} else if (isValid(user)) {
return proxy(user);
} else {
- logger.warn("User with username " + username + " already exists and is linked to provider [" + model.getDisplayName() +
- "] but is not valid. Stale LDAP_ID on local user is: " + user.getAttribute(LDAP_ID));
+ 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);
}
}
// Creating user to local storage
- logger.debug("Kerberos authenticated user " + username + " not in Keycloak storage. Creating him");
+ logger.debugf("Kerberos authenticated user [%s] not in Keycloak storage. Creating him", username);
return getUserByUsername(realm, username);
}
}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProviderFactory.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProviderFactory.java
index c197052df9..a498a93b45 100755
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProviderFactory.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProviderFactory.java
@@ -3,10 +3,15 @@ package org.keycloak.federation.ldap;
import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.federation.kerberos.CommonKerberosConfig;
-import org.keycloak.federation.kerberos.KerberosConfig;
import org.keycloak.federation.kerberos.impl.KerberosServerSubjectAuthenticator;
import org.keycloak.federation.kerberos.impl.KerberosUsernamePasswordAuthenticator;
import org.keycloak.federation.kerberos.impl.SPNEGOAuthenticator;
+import org.keycloak.federation.ldap.idm.model.IdentityType;
+import org.keycloak.federation.ldap.idm.model.LDAPUser;
+import org.keycloak.federation.ldap.idm.query.Condition;
+import org.keycloak.federation.ldap.idm.query.IdentityQuery;
+import org.keycloak.federation.ldap.idm.query.IdentityQueryBuilder;
+import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.KeycloakSessionTask;
@@ -16,16 +21,6 @@ import org.keycloak.models.UserFederationProvider;
import org.keycloak.models.UserFederationProviderFactory;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.utils.KeycloakModelUtils;
-import org.keycloak.picketlink.PartitionManagerProvider;
-import org.picketlink.idm.IdentityManager;
-import org.picketlink.idm.PartitionManager;
-import org.picketlink.idm.model.IdentityType;
-import org.picketlink.idm.model.basic.User;
-import org.picketlink.idm.query.AttributeParameter;
-import org.picketlink.idm.query.Condition;
-import org.picketlink.idm.query.IdentityQuery;
-import org.picketlink.idm.query.IdentityQueryBuilder;
-import org.picketlink.idm.query.QueryParameter;
import java.util.Collections;
import java.util.Date;
@@ -41,6 +36,8 @@ public class LDAPFederationProviderFactory implements UserFederationProviderFact
private static final Logger logger = Logger.getLogger(LDAPFederationProviderFactory.class);
public static final String PROVIDER_NAME = "ldap";
+ private LDAPIdentityStoreRegistry ldapStoreRegistry;
+
@Override
public UserFederationProvider create(KeycloakSession session) {
throw new IllegalAccessError("Illegal to call this method");
@@ -48,13 +45,13 @@ public class LDAPFederationProviderFactory implements UserFederationProviderFact
@Override
public LDAPFederationProvider getInstance(KeycloakSession session, UserFederationProviderModel model) {
- PartitionManagerProvider idmProvider = session.getProvider(PartitionManagerProvider.class);
- PartitionManager partition = idmProvider.getPartitionManager(model);
- return new LDAPFederationProvider(this, session, model, partition);
+ LDAPIdentityStore ldapIdentityStore = this.ldapStoreRegistry.getLdapStore(model);
+ return new LDAPFederationProvider(this, session, model, ldapIdentityStore);
}
@Override
public void init(Config.Scope config) {
+ this.ldapStoreRegistry = new LDAPIdentityStoreRegistry();
}
@Override
@@ -64,7 +61,7 @@ public class LDAPFederationProviderFactory implements UserFederationProviderFact
@Override
public void close() {
-
+ this.ldapStoreRegistry = null;
}
@Override
@@ -81,9 +78,8 @@ public class LDAPFederationProviderFactory implements UserFederationProviderFact
public void syncAllUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model) {
logger.infof("Sync all users from LDAP to local store: realm: %s, federation provider: %s, current time: " + new Date(), realmId, model.getDisplayName());
- PartitionManagerProvider idmProvider = sessionFactory.create().getProvider(PartitionManagerProvider.class);
- PartitionManager partitionMgr = idmProvider.getPartitionManager(model);
- IdentityQuery userQuery = partitionMgr.createIdentityManager().createIdentityQuery(User.class);
+ LDAPIdentityStore ldapIdentityStore = this.ldapStoreRegistry.getLdapStore(model);
+ IdentityQuery userQuery = ldapIdentityStore.createQueryBuilder().createIdentityQuery(LDAPUser.class);
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?
@@ -91,26 +87,23 @@ public class LDAPFederationProviderFactory implements UserFederationProviderFact
@Override
public void syncChangedUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model, Date lastSync) {
- logger.infof("Sync changed users from LDAP to local store: realm: %s, federation provider: %s, current time: " + new Date() + ", last sync time: " + lastSync, realmId, model.getDisplayName());
+ logger.infof("Sync changed users from LDAP to local store: realm: %s, federation provider: %s, current time: %s, last sync time: " + lastSync, realmId, model.getDisplayName(), new Date().toString());
- PartitionManagerProvider idmProvider = sessionFactory.create().getProvider(PartitionManagerProvider.class);
- PartitionManager partitionMgr = idmProvider.getPartitionManager(model);
+ LDAPIdentityStore ldapIdentityStore = this.ldapStoreRegistry.getLdapStore(model);
// Sync newly created users
- IdentityManager identityManager = partitionMgr.createIdentityManager();
- IdentityQueryBuilder queryBuilder = identityManager.getQueryBuilder();
+ IdentityQueryBuilder queryBuilder = ldapIdentityStore.createQueryBuilder();
Condition condition = queryBuilder.greaterThanOrEqualTo(IdentityType.CREATED_DATE, lastSync);
- IdentityQuery userQuery = queryBuilder.createIdentityQuery(User.class).where(condition);
+ IdentityQuery userQuery = queryBuilder.createIdentityQuery(LDAPUser.class).where(condition);
syncImpl(sessionFactory, userQuery, realmId, model);
// Sync updated users
- queryBuilder = identityManager.getQueryBuilder();
condition = queryBuilder.greaterThanOrEqualTo(LDAPUtils.MODIFY_DATE, lastSync);
- userQuery = queryBuilder.createIdentityQuery(User.class).where(condition);
+ userQuery = queryBuilder.createIdentityQuery(LDAPUser.class).where(condition);
syncImpl(sessionFactory, userQuery, realmId, model);
}
- protected void syncImpl(KeycloakSessionFactory sessionFactory, IdentityQuery userQuery, final String realmId, final UserFederationProviderModel fedModel) {
+ protected void syncImpl(KeycloakSessionFactory sessionFactory, IdentityQuery userQuery, final String realmId, final UserFederationProviderModel fedModel) {
boolean pagination = Boolean.parseBoolean(fedModel.getConfig().get(LDAPConstants.PAGINATION));
if (pagination) {
@@ -119,36 +112,36 @@ public class LDAPFederationProviderFactory implements UserFederationProviderFact
boolean nextPage = true;
while (nextPage) {
userQuery.setLimit(pageSize);
- final List users = userQuery.getResultList();
+ final List users = userQuery.getResultList();
nextPage = userQuery.getPaginationContext() != null;
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
@Override
public void run(KeycloakSession session) {
- importPicketlinkUsers(session, realmId, fedModel, users);
+ importLdapUsers(session, realmId, fedModel, users);
}
});
}
} else {
// LDAP pagination not available. Do everything in single transaction
- final List users = userQuery.getResultList();
+ final List users = userQuery.getResultList();
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
@Override
public void run(KeycloakSession session) {
- importPicketlinkUsers(session, realmId, fedModel, users);
+ importLdapUsers(session, realmId, fedModel, users);
}
});
}
}
- protected void importPicketlinkUsers(KeycloakSession session, String realmId, UserFederationProviderModel fedModel, List users) {
+ protected void importLdapUsers(KeycloakSession session, String realmId, UserFederationProviderModel fedModel, List ldapUsers) {
RealmModel realm = session.realms().getRealm(realmId);
LDAPFederationProvider ldapFedProvider = getInstance(session, fedModel);
- ldapFedProvider.importPicketlinkUsers(realm, users, fedModel);
+ ldapFedProvider.importLDAPUsers(realm, ldapUsers, fedModel);
}
protected SPNEGOAuthenticator createSPNEGOAuthenticator(String spnegoToken, CommonKerberosConfig kerberosConfig) {
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPIdentityStoreRegistry.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPIdentityStoreRegistry.java
new file mode 100644
index 0000000000..22aa55a6e9
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPIdentityStoreRegistry.java
@@ -0,0 +1,165 @@
+package org.keycloak.federation.ldap;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.jboss.logging.Logger;
+import org.keycloak.federation.ldap.idm.model.LDAPUser;
+import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
+import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStoreConfiguration;
+import org.keycloak.federation.ldap.idm.store.ldap.LDAPMappingConfiguration;
+import org.keycloak.models.LDAPConstants;
+import org.keycloak.models.UserFederationProviderModel;
+
+/**
+ * @author Marek Posolda
+ */
+public class LDAPIdentityStoreRegistry {
+
+ private static final Logger logger = Logger.getLogger(LDAPIdentityStoreRegistry.class);
+
+ private Map ldapStores = new ConcurrentHashMap();
+
+ public LDAPIdentityStore getLdapStore(UserFederationProviderModel model) {
+ LDAPIdentityStoreContext context = ldapStores.get(model.getId());
+
+ // Ldap config might have changed for the realm. In this case, we must re-initialize
+ Map config = model.getConfig();
+ if (context == null || !config.equals(context.config)) {
+ logLDAPConfig(model.getId(), config);
+
+ LDAPIdentityStore store = createLdapIdentityStore(config);
+ context = new LDAPIdentityStoreContext(config, store);
+ ldapStores.put(model.getId(), context);
+ }
+ return context.store;
+ }
+
+ // Don't log LDAP password
+ private void logLDAPConfig(String fedProviderId, Map ldapConfig) {
+ Map copy = new HashMap(ldapConfig);
+ copy.remove(LDAPConstants.BIND_CREDENTIAL);
+ logger.infof("Creating new LDAP based partition manager for the Federation provider: " + fedProviderId + ", LDAP Configuration: " + copy);
+ }
+
+ /**
+ * @param ldapConfig from realm
+ * @return PartitionManager instance based on LDAP store
+ */
+ public static LDAPIdentityStore createLdapIdentityStore(Map ldapConfig) {
+ Properties connectionProps = new Properties();
+ 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.initsize", "1");
+ checkSystemProperty("com.sun.jndi.ldap.connect.pool.maxsize", "1000");
+ checkSystemProperty("com.sun.jndi.ldap.connect.pool.prefsize", "5");
+ checkSystemProperty("com.sun.jndi.ldap.connect.pool.timeout", "300000");
+ checkSystemProperty("com.sun.jndi.ldap.connect.pool.protocol", "plain");
+ checkSystemProperty("com.sun.jndi.ldap.connect.pool.debug", "off");
+
+ String vendor = ldapConfig.get(LDAPConstants.VENDOR);
+
+ boolean activeDirectory = vendor != null && vendor.equals(LDAPConstants.VENDOR_ACTIVE_DIRECTORY);
+
+ String ldapLoginNameMapping = ldapConfig.get(LDAPConstants.USERNAME_LDAP_ATTRIBUTE);
+ if (ldapLoginNameMapping == null) {
+ ldapLoginNameMapping = activeDirectory ? LDAPConstants.CN : LDAPConstants.UID;
+ }
+
+ String ldapFirstNameMapping = activeDirectory ? "givenName" : LDAPConstants.CN;
+ String createTimestampMapping = activeDirectory ? "whenCreated" : LDAPConstants.CREATE_TIMESTAMP;
+ String modifyTimestampMapping = activeDirectory ? "whenChanged" : LDAPConstants.MODIFY_TIMESTAMP;
+ String[] userObjectClasses = getUserObjectClasses(ldapConfig);
+
+ boolean pagination = ldapConfig.containsKey(LDAPConstants.PAGINATION) ? Boolean.parseBoolean(ldapConfig.get(LDAPConstants.PAGINATION)) : false;
+ boolean userAccountControlsAfterPasswordUpdate = ldapConfig.containsKey(LDAPConstants.USER_ACCOUNT_CONTROLS_AFTER_PASSWORD_UPDATE) ?
+ Boolean.parseBoolean(ldapConfig.get(LDAPConstants.USER_ACCOUNT_CONTROLS_AFTER_PASSWORD_UPDATE)) : false;
+
+ // Differences of unique attribute among various vendors
+ String uniqueIdentifierAttributeName = LDAPConstants.ENTRY_UUID;
+ if (vendor != null) {
+ switch (vendor) {
+ case LDAPConstants.VENDOR_RHDS:
+ uniqueIdentifierAttributeName = "nsuniqueid";
+ break;
+ case LDAPConstants.VENDOR_TIVOLI:
+ uniqueIdentifierAttributeName = "uniqueidentifier";
+ break;
+ case LDAPConstants.VENDOR_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(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);
+ logger.infof("Using 'cn' attribute for DN of user and 'sAMAccountName' for username");
+ }
+
+ return new LDAPIdentityStore(ldapStoreConfig);
+ }
+
+ private static void checkSystemProperty(String name, String defaultValue) {
+ if (System.getProperty(name) == null) {
+ System.setProperty(name, defaultValue);
+ }
+ }
+
+ // Parse array of strings like [ "inetOrgPerson", "organizationalPerson" ] from the string like: "inetOrgPerson, organizationalPerson"
+ private static String[] getUserObjectClasses(Map ldapConfig) {
+ String objClassesCfg = ldapConfig.get(LDAPConstants.USER_OBJECT_CLASSES);
+ String objClassesStr = (objClassesCfg != null && objClassesCfg.length() > 0) ? objClassesCfg.trim() : "inetOrgPerson, organizationalPerson";
+
+ String[] objectClasses = objClassesStr.split(",");
+
+ // Trim them
+ String[] userObjectClasses = new String[objectClasses.length];
+ for (int i=0 ; i config, LDAPIdentityStore store) {
+ this.config = config;
+ this.store = store;
+ }
+
+ private Map config;
+ private LDAPIdentityStore store;
+ }
+}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPUtils.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPUtils.java
index db0e9b8ab1..97535926c9 100755
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPUtils.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPUtils.java
@@ -1,22 +1,21 @@
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.IdentityQuery;
+import org.keycloak.federation.ldap.idm.query.IdentityQueryBuilder;
+import org.keycloak.federation.ldap.idm.query.QueryParameter;
+import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
+import org.keycloak.models.LDAPConstants;
import org.keycloak.models.ModelDuplicateException;
-import org.picketlink.idm.IdentityManagementException;
-import org.picketlink.idm.IdentityManager;
-import org.picketlink.idm.PartitionManager;
-import org.picketlink.idm.credential.Credentials;
-import org.picketlink.idm.credential.Password;
-import org.picketlink.idm.credential.UsernamePasswordCredentials;
-import org.picketlink.idm.model.Attribute;
-import org.picketlink.idm.model.basic.BasicModel;
-import org.picketlink.idm.model.basic.User;
-import org.picketlink.idm.query.AttributeParameter;
-import org.picketlink.idm.query.QueryParameter;
+import org.keycloak.models.UserModel;
import java.util.List;
/**
- * Allow to directly call some operations against Picketlink IDM PartitionManager (hence LDAP).
+ * Allow to directly call some operations against LDAPIdentityStore.
+ * TODO: Is this class still needed?
*
* @author Marek Posolda
*/
@@ -24,99 +23,102 @@ public class LDAPUtils {
public static QueryParameter MODIFY_DATE = new AttributeParameter("modifyDate");
- public static User addUser(PartitionManager partitionManager, String username, String firstName, String lastName, String email) {
- IdentityManager identityManager = getIdentityManager(partitionManager);
-
- if (BasicModel.getUser(identityManager, username) != null) {
+ public static LDAPUser addUser(LDAPIdentityStore ldapIdentityStore, String username, String firstName, String lastName, String email) {
+ if (getUser(ldapIdentityStore, username) != null) {
throw new ModelDuplicateException("User with same username already exists");
}
- if (getUserByEmail(identityManager, email) != null) {
+ if (getUserByEmail(ldapIdentityStore, email) != null) {
throw new ModelDuplicateException("User with same email already exists");
}
- User picketlinkUser = new User(username);
- picketlinkUser.setFirstName(firstName);
- picketlinkUser.setLastName(lastName);
- picketlinkUser.setEmail(email);
- picketlinkUser.setAttribute(new Attribute("fullName", getFullName(username, firstName, lastName)));
- identityManager.add(picketlinkUser);
- return picketlinkUser;
+ LDAPUser ldapUser = new LDAPUser(username);
+ ldapUser.setFirstName(firstName);
+ ldapUser.setLastName(lastName);
+ ldapUser.setEmail(email);
+ ldapUser.setAttribute(new Attribute("fullName", getFullName(username, firstName, lastName)));
+ ldapIdentityStore.add(ldapUser);
+ return ldapUser;
}
- public static User updateUser(PartitionManager partitionManager, String username, String firstName, String lastName, String email) {
- IdentityManager idmManager = getIdentityManager(partitionManager);
- User picketlinkUser = BasicModel.getUser(idmManager, username);
- picketlinkUser.setFirstName(firstName);
- picketlinkUser.setLastName(lastName);
- picketlinkUser.setEmail(email);
- idmManager.update(picketlinkUser);
- return picketlinkUser;
+ public static LDAPUser updateUser(LDAPIdentityStore ldapIdentityStore, String username, String firstName, String lastName, String email) {
+ LDAPUser ldapUser = getUser(ldapIdentityStore, username);
+ ldapUser.setFirstName(firstName);
+ ldapUser.setLastName(lastName);
+ ldapUser.setEmail(email);
+ ldapIdentityStore.update(ldapUser);
+ return ldapUser;
}
- public static void updatePassword(PartitionManager partitionManager, User picketlinkUser, String password) {
- IdentityManager idmManager = getIdentityManager(partitionManager);
- idmManager.updateCredential(picketlinkUser, new Password(password.toCharArray()));
+ public static void updatePassword(LDAPIdentityStore ldapIdentityStore, UserModel user, String password) {
+ LDAPUser ldapUser = convertUserForPasswordUpdate(user);
+
+ ldapIdentityStore.updatePassword(ldapUser, password);
}
- public static boolean validatePassword(PartitionManager partitionManager, String username, String password) {
- IdentityManager idmManager = getIdentityManager(partitionManager);
+ public static void updatePassword(LDAPIdentityStore ldapIdentityStore, LDAPUser user, String password) {
+ ldapIdentityStore.updatePassword(user, password);
+ }
- UsernamePasswordCredentials credential = new UsernamePasswordCredentials();
- credential.setUsername(username);
- credential.setPassword(new Password(password.toCharArray()));
- idmManager.validateCredentials(credential);
- if (credential.getStatus() == Credentials.Status.VALID) {
- return true;
- } else {
- return false;
+ public static boolean validatePassword(LDAPIdentityStore ldapIdentityStore, UserModel user, String password) {
+ LDAPUser ldapUser = convertUserForPasswordUpdate(user);
+
+ return ldapIdentityStore.validatePassword(ldapUser, password);
+ }
+
+ public static boolean validatePassword(LDAPIdentityStore ldapIdentityStore, LDAPUser user, String password) {
+ return ldapIdentityStore.validatePassword(user, password);
+ }
+
+ public static LDAPUser getUser(LDAPIdentityStore ldapIdentityStore, String username) {
+ return ldapIdentityStore.getUser(username);
+ }
+
+ // Put just username and entryDN as these are needed by LDAPIdentityStore for passwordUpdate
+ private static LDAPUser convertUserForPasswordUpdate(UserModel kcUser) {
+ LDAPUser ldapUser = new LDAPUser(kcUser.getUsername());
+ String ldapEntryDN = kcUser.getAttribute(LDAPConstants.LDAP_ENTRY_DN);
+ if (ldapEntryDN != null) {
+ ldapUser.setEntryDN(ldapEntryDN);
}
- }
-
- public static User getUser(PartitionManager partitionManager, String username) {
- IdentityManager idmManager = getIdentityManager(partitionManager);
- return BasicModel.getUser(idmManager, username);
+ return ldapUser;
}
- public static User getUserByEmail(IdentityManager idmManager, String email) throws IdentityManagementException {
- List agents = idmManager.createIdentityQuery(User.class)
- .setParameter(User.EMAIL, email).getResultList();
+ public static LDAPUser getUserByEmail(LDAPIdentityStore ldapIdentityStore, String email) {
+ IdentityQueryBuilder queryBuilder = ldapIdentityStore.createQueryBuilder();
+ IdentityQuery query = queryBuilder.createIdentityQuery(LDAPUser.class)
+ .where(queryBuilder.equal(LDAPUser.EMAIL, email));
+ List users = query.getResultList();
- if (agents.isEmpty()) {
+ if (users.isEmpty()) {
return null;
- } else if (agents.size() == 1) {
- return agents.get(0);
+ } else if (users.size() == 1) {
+ return users.get(0);
} else {
- throw new IdentityManagementException("Error - multiple users found with same email");
+ throw new ModelDuplicateException("Error - multiple users found with same email " + email);
}
}
- public static boolean removeUser(PartitionManager partitionManager, String username) {
- IdentityManager idmManager = getIdentityManager(partitionManager);
- User picketlinkUser = BasicModel.getUser(idmManager, username);
- if (picketlinkUser == null) {
+ public static boolean removeUser(LDAPIdentityStore ldapIdentityStore, String username) {
+ LDAPUser ldapUser = getUser(ldapIdentityStore, username);
+ if (ldapUser == null) {
return false;
}
- idmManager.remove(picketlinkUser);
+ ldapIdentityStore.remove(ldapUser);
return true;
}
- public static void removeAllUsers(PartitionManager partitionManager) {
- IdentityManager idmManager = getIdentityManager(partitionManager);
- List users = idmManager.createIdentityQuery(User.class).getResultList();
+ public static void removeAllUsers(LDAPIdentityStore ldapIdentityStore) {
+ List allUsers = getAllUsers(ldapIdentityStore);
- for (User user : users) {
- idmManager.remove(user);
+ for (LDAPUser user : allUsers) {
+ ldapIdentityStore.remove(user);
}
}
- public static List getAllUsers(PartitionManager partitionManager) {
- IdentityManager idmManager = getIdentityManager(partitionManager);
- return idmManager.createIdentityQuery(User.class).getResultList();
- }
-
- private static IdentityManager getIdentityManager(PartitionManager partitionManager) {
- return partitionManager.createIdentityManager();
+ public static List getAllUsers(LDAPIdentityStore ldapIdentityStore) {
+ IdentityQuery userQuery = ldapIdentityStore.createQueryBuilder().createIdentityQuery(LDAPUser.class);
+ return userQuery.getResultList();
}
// Needed for ActiveDirectory updates
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/WritableLDAPUserModelDelegate.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/WritableLDAPUserModelDelegate.java
index 9a68f6a1ca..4debf0eaed 100755
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/WritableLDAPUserModelDelegate.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/WritableLDAPUserModelDelegate.java
@@ -1,16 +1,11 @@
package org.keycloak.federation.ldap;
import org.jboss.logging.Logger;
-import org.keycloak.models.ModelException;
+import org.keycloak.federation.ldap.idm.model.LDAPUser;
+import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.UserModelDelegate;
-import org.picketlink.idm.IdentityManagementException;
-import org.picketlink.idm.IdentityManager;
-import org.picketlink.idm.credential.Password;
-import org.picketlink.idm.credential.TOTPCredential;
-import org.picketlink.idm.model.basic.BasicModel;
-import org.picketlink.idm.model.basic.User;
/**
* @author Bill Burke
@@ -28,52 +23,43 @@ public class WritableLDAPUserModelDelegate extends UserModelDelegate implements
@Override
public void setUsername(String username) {
- IdentityManager identityManager = provider.getIdentityManager();
+ LDAPIdentityStore ldapIdentityStore = provider.getLdapIdentityStore();
- try {
- User picketlinkUser = BasicModel.getUser(identityManager, delegate.getUsername());
- if (picketlinkUser == null) {
- throw new IllegalStateException("User not found in LDAP storage!");
- }
- picketlinkUser.setLoginName(username);
- identityManager.update(picketlinkUser);
- } catch (IdentityManagementException ie) {
- throw new ModelException(ie);
+ 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) {
- IdentityManager identityManager = provider.getIdentityManager();
+ LDAPIdentityStore ldapIdentityStore = provider.getLdapIdentityStore();
- try {
- User picketlinkUser = BasicModel.getUser(identityManager, delegate.getUsername());
- if (picketlinkUser == null) {
- throw new IllegalStateException("User not found in LDAP storage!");
- }
- picketlinkUser.setLastName(lastName);
- identityManager.update(picketlinkUser);
- } catch (IdentityManagementException ie) {
- throw new ModelException(ie);
+ 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) {
- IdentityManager identityManager = provider.getIdentityManager();
+ LDAPIdentityStore ldapIdentityStore = provider.getLdapIdentityStore();
- try {
- User picketlinkUser = BasicModel.getUser(identityManager, delegate.getUsername());
- if (picketlinkUser == null) {
- throw new IllegalStateException("User not found in LDAP storage!");
- }
- picketlinkUser.setFirstName(first);
- identityManager.update(picketlinkUser);
- } catch (IdentityManagementException ie) {
- throw new ModelException(ie);
+ 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);
}
@@ -83,41 +69,31 @@ public class WritableLDAPUserModelDelegate extends UserModelDelegate implements
delegate.updateCredential(cred);
return;
}
- IdentityManager identityManager = provider.getIdentityManager();
- try {
- User picketlinkUser = BasicModel.getUser(identityManager, getUsername());
- if (picketlinkUser == null) {
- logger.debugf("User '%s' doesn't exists. Skip password update", getUsername());
- throw new IllegalStateException("User doesn't exist in LDAP storage");
- }
- if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
- identityManager.updateCredential(picketlinkUser, new Password(cred.getValue().toCharArray()));
- } else if (cred.getType().equals(UserCredentialModel.TOTP)) {
- TOTPCredential credential = new TOTPCredential(cred.getValue());
- credential.setDevice(cred.getDevice());
- identityManager.updateCredential(picketlinkUser, credential);
- }
- } catch (IdentityManagementException ie) {
- throw new ModelException(ie);
+ LDAPIdentityStore ldapIdentityStore = provider.getLdapIdentityStore();
+ LDAPUser ldapUser = LDAPUtils.getUser(ldapIdentityStore, delegate.getUsername());
+ if (ldapUser == null) {
+ throw new IllegalStateException("User " + delegate.getUsername() + " not found in LDAP storage!");
}
+ if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
+ LDAPUtils.updatePassword(ldapIdentityStore, delegate, cred.getValue());
+ } else {
+ logger.warnf("Don't know how to update credential of type [%s] for user [%s]", cred.getType(), delegate.getUsername());
+ }
}
@Override
public void setEmail(String email) {
- IdentityManager identityManager = provider.getIdentityManager();
+ LDAPIdentityStore ldapIdentityStore = provider.getLdapIdentityStore();
- try {
- User picketlinkUser = BasicModel.getUser(identityManager, delegate.getUsername());
- if (picketlinkUser == null) {
- throw new IllegalStateException("User not found in LDAP storage!");
- }
- picketlinkUser.setEmail(email);
- identityManager.update(picketlinkUser);
- } catch (IdentityManagementException ie) {
- throw new ModelException(ie);
+ 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);
}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/AbstractAttributedType.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/AbstractAttributedType.java
new file mode 100644
index 0000000000..7e6d80b0f1
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/AbstractAttributedType.java
@@ -0,0 +1,85 @@
+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> attributes =
+ new HashMap>();
+
+ 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 Attribute getAttribute(String name) {
+ return (Attribute) attributes.get(name);
+ }
+
+ public Collection> getAttributes() {
+ return unmodifiableCollection(attributes.values());
+ }
+
+ public Map> 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;
+ }
+
+}
\ No newline at end of file
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/AbstractIdentityType.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/AbstractIdentityType.java
new file mode 100644
index 0000000000..8ee8bd6bd1
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/AbstractIdentityType.java
@@ -0,0 +1,70 @@
+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();
+ }
+}
+
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/Attribute.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/Attribute.java
new file mode 100644
index 0000000000..82dac06b87
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/Attribute.java
@@ -0,0 +1,80 @@
+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
+ */
+public class Attribute 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;
+ }
+}
+
+
+
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/AttributeProperty.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/AttributeProperty.java
new file mode 100644
index 0000000000..33b8706fca
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/AttributeProperty.java
@@ -0,0 +1,31 @@
+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 {
+
+ /**
+ *
Managed properties are stored as ad-hoc attributes and mapped from and to a specific property of a type.
+ *
+ * @return
+ */
+ boolean managed() default false;
+
+}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/AttributedType.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/AttributedType.java
new file mode 100644
index 0000000000..5c374278c2
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/AttributedType.java
@@ -0,0 +1,75 @@
+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
+ */
+ Attribute getAttribute(String name);
+
+ /**
+ * Returns a Map containing all attribute values for this IdentityType instance.
+ *
+ * @return map of attribute names and their values
+ */
+ Collection> getAttributes();
+
+ public final class QUERY_ATTRIBUTE {
+ public static AttributeParameter byName(String name) {
+ return new AttributeParameter(name);
+ }
+ }
+}
+
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/IdentityType.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/IdentityType.java
new file mode 100644
index 0000000000..f8ae8a1833
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/IdentityType.java
@@ -0,0 +1,100 @@
+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();
+
+ /**
+ *
Sets the current enabled status of this {@link IdentityType}.
+ *
+ * @param enabled
+ */
+ void setEnabled(boolean enabled);
+
+ /**
+ * Returns the date that this IdentityType instance was created.
+ *
+ * @return Date value representing the creation date
+ */
+ Date getCreatedDate();
+
+ /**
+ *
Sets the date that this {@link IdentityType} was created.
+ *
+ * @param createdDate
+ */
+ void setCreatedDate(Date createdDate);
+
+ /**
+ * Returns the date that this IdentityType expires, or null if there is no expiry date.
+ *
+ * @return
+ */
+ Date getExpirationDate();
+
+ /**
+ *
Sets the date that this {@link IdentityType} expires.
+ *
+ * @param expirationDate
+ */
+ void setExpirationDate(Date expirationDate);
+
+}
+
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/LDAPUser.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/LDAPUser.java
new file mode 100644
index 0000000000..4ce7ef9516
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/LDAPUser.java
@@ -0,0 +1,85 @@
+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;
+ }
+
+}
+
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/AttributeParameter.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/AttributeParameter.java
new file mode 100644
index 0000000000..c5feea9319
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/AttributeParameter.java
@@ -0,0 +1,21 @@
+package org.keycloak.federation.ldap.idm.query;
+
+/**
+ *
This class can be used to define a query parameter for properties annotated with
+ * {@link org.keycloak.federation.ldap.idm.model.AttributeProperty}.
+ *
A {@link Condition} is used to specify how a specific {@link QueryParameter}
+ * is defined in order to filter query results.
+ *
+ * @author Pedro Igor
+ */
+public interface Condition {
+
+ /**
+ *
The {@link QueryParameter} restricted by this condition.
+ *
+ * @return
+ */
+ QueryParameter getParameter();
+
+}
\ No newline at end of file
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/IdentityQuery.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/IdentityQuery.java
new file mode 100644
index 0000000000..1a77727fa8
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/IdentityQuery.java
@@ -0,0 +1,225 @@
+package org.keycloak.federation.ldap.idm.query;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.keycloak.federation.ldap.idm.model.IdentityType;
+
+/**
+ *
An {@link IdentityQuery} is responsible for querying the underlying identity stores for instances of
+ * a given {@link IdentityType}.
+ *
+ *
Instances of this class are obtained using the {@link IdentityQueryBuilder#createIdentityQuery(Class)}
+ * method.
+ *
+ *
+ * IdentityManager identityManager = getIdentityManager();
+ *
+ * // here we get the query builder
+ * IdentityQueryBuilder builder = identityManager.getQueryBuilder();
+ *
+ * // create a condition
+ * Condition condition = builder.equal(User.LOGIN_NAME, "john");
+ *
+ * // create a query for a specific identity type using the previously created condition
+ * IdentityQuery query = builder.createIdentityQuery(User.class).where(condition);
+ *
+ * // execute the query
+ * List result = query.getResultList();
+ *
+ *
+ *
When preparing a query you may want to create conditions to filter its results and configure how they must be retrieved.
+ * For that, you can use the {@link IdentityQueryBuilder}, which provides useful methods for creating
+ * different expressions and conditions.
+ *
+ * @author Shane Bryzak
+ * @author Pedro Igor
+ */
+public interface IdentityQuery {
+
+ /**
+ * @see #setPaginationContext(Object object)
+ */
+ Object getPaginationContext();
+
+ /**
+ * Used for pagination models like LDAP when search will return some object (like cookie) for searching on next page
+ *
+ * @param object to be used for search next page
+ *
+ * @return this query
+ */
+ IdentityQuery setPaginationContext(Object object);
+
+ /**
+ * @deprecated Will be removed soon.
+ *
+ * @see #setSortParameters(QueryParameter...)
+ */
+ @Deprecated
+ QueryParameter[] getSortParameters();
+
+ /**
+ * Parameters used to sort the results. First parameter has biggest priority. For example: setSortParameter(User.LAST_NAME,
+ * User.FIRST_NAME) means that results will be sorted primarily by lastName and firstName will be used to sort only records with
+ * same lastName
+ *
+ * @param sortParameters parameters to specify sort criteria
+ *
+ * @deprecated Use {@link IdentityQuery#sortBy(Sort...)} instead. Where you can create sort conditions
+ * from the {@link IdentityQueryBuilder}.
+ *
+ * @return this query
+ */
+ @Deprecated
+ IdentityQuery setSortParameters(QueryParameter... sortParameters);
+
+ /**
+ * @deprecated Use {@link IdentityQuery#getSorting()} for a list of sorting conditions. Will be removed soon.
+ *
+ * @return true if sorting will be ascending
+ *
+ * @see #setSortAscending(boolean)
+ */
+ @Deprecated
+ boolean isSortAscending();
+
+ /**
+ * Specify if sorting will be ascending (true) or descending (false)
+ *
+ * @param sortAscending to specify if sorting will be ascending or descending
+ *
+ * @deprecated Use {@link IdentityQuery#sortBy(Sort...)} instead. Where you can create sort conditions
+ * from the {@link IdentityQueryBuilder}.
+ *
+ * @return this query
+ */
+ @Deprecated
+ IdentityQuery setSortAscending(boolean sortAscending);
+
+ /**
+ *
Set a query parameter to this query in order to filter the results.
+ *
+ *
This method always create an equality condition. For more conditions options take a look at {@link
+ * IdentityQueryBuilder} and use the {@link IdentityQuery#where(Condition...)}
+ * instead.
+ *
+ * @param param The query parameter.
+ * @param value The value to match for equality.
+ *
+ * @return
+ *
+ * @deprecated Use {@link IdentityQuery#where(Condition...)} to specify query conditions.
+ */
+ @Deprecated
+ IdentityQuery setParameter(QueryParameter param, Object... value);
+
+ /**
+ *
Add to this query the conditions that will be used to filter results.
+ *
+ *
Any condition previously added to this query will be preserved and the new conditions added. If you want to clear the
+ * conditions you must create a new query instance.
+ *
+ * @param condition One or more conditions created from {@link IdentityQueryBuilder}.
+ *
+ * @return
+ */
+ IdentityQuery where(Condition... condition);
+
+ /**
+ *
Add to this query the sorting conditions to be applied to the results.
+ *
+ * @param limit the number of instances to retrieve.
+ *
+ * @return
+ */
+ IdentityQuery setLimit(int limit);
+
+ /**
+ *
Execute the query against the underlying identity stores and returns a list containing all instances of
+ * the type (defined when creating this query instance) that match the conditions previously specified.
+ *
+ * @return
+ */
+ List getResultList();
+
+ /**
+ * Count of all query results. It takes into account query parameters, but it doesn't take into account pagination parameter
+ * like offset and limit
+ *
+ * @return count of all query results
+ */
+ int getResultCount();
+}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/IdentityQueryBuilder.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/IdentityQueryBuilder.java
new file mode 100644
index 0000000000..022063521b
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/IdentityQueryBuilder.java
@@ -0,0 +1,124 @@
+package org.keycloak.federation.ldap.idm.query;
+
+import org.keycloak.federation.ldap.idm.model.IdentityType;
+
+/**
+ *
The {@link IdentityQueryBuilder} is responsible for creating {@link IdentityQuery} instances and also
+ * provide methods to create conditions, orderings, sorting, etc.
+ *
+ * @author Pedro Igor
+ */
+public interface IdentityQueryBuilder {
+
+ /**
+ *
Create a condition for testing the whether the query parameter satisfies the given pattern..