keycloak-aplcache

Merge pull request #1126 from mposolda/master KEYCLOAK-1007

4/8/2015 5:52:57 PM

Changes

distribution/modules/src/main/resources/modules/org/keycloak/keycloak-picketlink-api/main/module.xml 21(+0 -21)

distribution/modules/src/main/resources/modules/org/keycloak/keycloak-picketlink-ldap/main/module.xml 21(+0 -21)

picketlink/keycloak-picketlink-api/pom.xml 55(+0 -55)

picketlink/keycloak-picketlink-api/src/main/java/org/keycloak/picketlink/PartitionManagerProvider.java 14(+0 -14)

picketlink/keycloak-picketlink-api/src/main/java/org/keycloak/picketlink/PartitionManagerProviderFactory.java 9(+0 -9)

picketlink/keycloak-picketlink-api/src/main/java/org/keycloak/picketlink/PartitionManagerSpi.java 25(+0 -25)

picketlink/keycloak-picketlink-api/src/main/resources/META-INF/services/org.keycloak.provider.Spi 1(+0 -1)

picketlink/keycloak-picketlink-ldap/pom.xml 66(+0 -66)

picketlink/keycloak-picketlink-ldap/src/main/java/org/keycloak/picketlink/idm/KeycloakEventBridge.java 63(+0 -63)

picketlink/keycloak-picketlink-ldap/src/main/java/org/keycloak/picketlink/idm/LDAPKeycloakCredentialHandler.java 51(+0 -51)

picketlink/keycloak-picketlink-ldap/src/main/java/org/keycloak/picketlink/ldap/LDAPPartitionManagerProvider.java 26(+0 -26)

picketlink/keycloak-picketlink-ldap/src/main/java/org/keycloak/picketlink/ldap/LDAPPartitionManagerProviderFactory.java 43(+0 -43)

picketlink/keycloak-picketlink-ldap/src/main/java/org/keycloak/picketlink/ldap/PartitionManagerRegistry.java 163(+0 -163)

picketlink/keycloak-picketlink-ldap/src/main/resources/META-INF/services/org.keycloak.picketlink.PartitionManagerProviderFactory 1(+0 -1)

picketlink/pom.xml 24(+0 -24)

pom.xml 1(+0 -1)

services/pom.xml 6(+0 -6)

Details

diff --git a/dependencies/server-all/pom.xml b/dependencies/server-all/pom.xml
index 9d8921f..d124ea5 100755
--- a/dependencies/server-all/pom.xml
+++ b/dependencies/server-all/pom.xml
@@ -144,34 +144,6 @@
             <artifactId>keycloak-kerberos-federation</artifactId>
             <version>${project.version}</version>
         </dependency>
-        <dependency>
-            <groupId>org.picketlink</groupId>
-            <artifactId>picketlink-common</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.picketlink</groupId>
-            <artifactId>picketlink-idm-api</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.picketlink</groupId>
-            <artifactId>picketlink-idm-impl</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.picketlink</groupId>
-            <artifactId>picketlink-idm-simple-schema</artifactId>
-        </dependency>
-
-        <!-- picketlink -->
-        <dependency>
-            <groupId>org.keycloak</groupId>
-            <artifactId>keycloak-picketlink-api</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>org.keycloak</groupId>
-            <artifactId>keycloak-picketlink-ldap</artifactId>
-            <version>${project.version}</version>
-        </dependency>
 
         <!-- saml -->
         <dependency>
diff --git a/distribution/modules/build.xml b/distribution/modules/build.xml
index 9f65cb9..df4bef5 100755
--- a/distribution/modules/build.xml
+++ b/distribution/modules/build.xml
@@ -259,14 +259,6 @@
             <maven-resource group="org.keycloak" artifact="keycloak-ldap-federation"/>
         </module-def>
 
-        <module-def name="org.keycloak.keycloak-picketlink-api">
-            <maven-resource group="org.keycloak" artifact="keycloak-picketlink-api"/>
-        </module-def>
-
-        <module-def name="org.keycloak.keycloak-picketlink-ldap">
-            <maven-resource group="org.keycloak" artifact="keycloak-picketlink-ldap"/>
-        </module-def>
-
         <module-def name="org.keycloak.keycloak-saml-core">
             <maven-resource group="org.keycloak" artifact="keycloak-saml-core"/>
         </module-def>
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 29dfd9c..5f88f37 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 @@
         <module name="org.keycloak.keycloak-core"/>
         <module name="org.keycloak.keycloak-model-api"/>
         <module name="org.keycloak.keycloak-kerberos-federation"/>
-        <module name="org.keycloak.keycloak-picketlink-api"/>
         <module name="javax.ws.rs.api"/>
         <module name="org.jboss.resteasy.resteasy-jaxrs"/>
         <module name="org.jboss.logging"/>
-        <module name="org.picketlink.common"/>
-        <module name="org.picketlink.idm.api"/>
-        <module name="org.picketlink.idm"/>
-        <module name="org.picketlink.idm.schema"/>
         <module name="javax.api"/>
     </dependencies>
 
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 f553d24..ddf2475 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 @@
         <module name="org.keycloak.keycloak-model-sessions-jpa" services="import"/>
         <module name="org.keycloak.keycloak-model-sessions-mem" services="import"/>
         <module name="org.keycloak.keycloak-model-sessions-mongo" services="import"/>
-        <module name="org.keycloak.keycloak-picketlink-api" services="import"/>
-        <module name="org.keycloak.keycloak-picketlink-ldap" services="import"/>
         <module name="org.keycloak.keycloak-saml-protocol" services="import"/>
         <module name="org.keycloak.keycloak-services" export="true" services="import"/>
         <module name="org.keycloak.keycloak-social-core" services="import"/>
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 86e86f4..9864a07 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 @@
         <module name="org.keycloak.keycloak-model-sessions-jpa" services="import"/>
         <module name="org.keycloak.keycloak-model-sessions-mem" services="import"/>
         <module name="org.keycloak.keycloak-model-sessions-mongo" services="import"/>
-        <module name="org.keycloak.keycloak-picketlink-api" services="import"/>
         <module name="org.keycloak.keycloak-wildfly-extensions" services="import"/>
 
-        <module name="org.keycloak.keycloak-picketlink-ldap" services="import"/>
         <module name="org.keycloak.keycloak-saml-protocol" services="import"/>
         <module name="org.keycloak.keycloak-services" export="true" services="import"/>
         <module name="org.keycloak.keycloak-social-core" services="import"/>
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 6caa2c8..aae18bf 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 @@
             <module name="org.keycloak.keycloak-model-sessions-jpa" services="import"/>
             <module name="org.keycloak.keycloak-model-sessions-mem" services="import"/>
             <module name="org.keycloak.keycloak-model-sessions-mongo" services="import"/>
-            <module name="org.keycloak.keycloak-picketlink-api" services="import"/>
-
-            <module name="org.keycloak.keycloak-picketlink-ldap" services="import"/>
             <module name="org.keycloak.keycloak-saml-protocol" services="import"/>
             <module name="org.keycloak.keycloak-services" export="true" services="import"/>
             <module name="org.keycloak.keycloak-social-core" services="import"/>
diff --git a/federation/ldap/pom.xml b/federation/ldap/pom.xml
index a48b36d..72803ab 100755
--- a/federation/ldap/pom.xml
+++ b/federation/ldap/pom.xml
@@ -32,12 +32,6 @@
             <scope>provided</scope>
         </dependency>
         <dependency>
-            <groupId>org.keycloak</groupId>
-            <artifactId>keycloak-picketlink-api</artifactId>
-            <version>${project.version}</version>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
             <groupId>org.jboss.resteasy</groupId>
             <artifactId>resteasy-jaxrs</artifactId>
             <scope>provided</scope>
@@ -61,27 +55,6 @@
             <artifactId>jboss-logging</artifactId>
             <scope>provided</scope>
         </dependency>
-
-        <dependency>
-            <groupId>org.picketlink</groupId>
-            <artifactId>picketlink-common</artifactId>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.picketlink</groupId>
-            <artifactId>picketlink-idm-api</artifactId>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.picketlink</groupId>
-            <artifactId>picketlink-idm-impl</artifactId>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.picketlink</groupId>
-            <artifactId>picketlink-idm-simple-schema</artifactId>
-            <scope>provided</scope>
-        </dependency>
     </dependencies>
 
     <build>
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 0000000..7e6d80b
--- /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<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;
+    }
+
+}
\ 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 0000000..8ee8bd6
--- /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 0000000..82dac06
--- /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 <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;
+    }
+}
+
+
+
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 0000000..5c37427
--- /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
+     */
+    <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);
+        }
+    }
+}
+
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 0000000..33b8706
--- /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 {
+
+    /**
+     * <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;
+
+}
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 0000000..f8ae8a1
--- /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();
+
+    /**
+     * <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);
+
+}
+
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 0000000..4ce7ef9
--- /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 0000000..c5feea9
--- /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;
+
+/**
+ * <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;
+    }
+}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/Condition.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/Condition.java
new file mode 100644
index 0000000..85d81d8
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/Condition.java
@@ -0,0 +1,18 @@
+package org.keycloak.federation.ldap.idm.query;
+
+/**
+ * <p>A {@link Condition} is used to specify how a specific {@link QueryParameter}
+ * is defined in order to filter query results.</p>
+ *
+ * @author Pedro Igor
+ */
+public interface Condition {
+
+    /**
+     * <p>The {@link QueryParameter} restricted by this condition.</p>
+     *
+     * @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 0000000..1a77727
--- /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;
+
+/**
+ * <p>An {@link IdentityQuery} is responsible for querying the underlying identity stores for instances of
+ * a given {@link IdentityType}.</p>
+ *
+ * <p>Instances of this class are obtained using the {@link IdentityQueryBuilder#createIdentityQuery(Class)}
+ * method.</p>
+ *
+ * <pre>
+ *      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<User> result = query.getResultList();
+ * </pre>
+ *
+ * <p>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.</p>
+ *
+ * @author Shane Bryzak
+ * @author Pedro Igor
+ */
+public interface IdentityQuery<T extends IdentityType> {
+
+    /**
+     * @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<T> 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<T> 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<T> setSortAscending(boolean sortAscending);
+
+    /**
+     * <p>Set a query parameter to this query in order to filter the results.</p>
+     *
+     * <p>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.</p>
+     *
+     * @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<T> setParameter(QueryParameter param, Object... value);
+
+    /**
+     * <p>Add to this query the conditions that will be used to filter results.</p>
+     *
+     * <p>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.</p>
+     *
+     * @param condition One or more conditions created from {@link IdentityQueryBuilder}.
+     *
+     * @return
+     */
+    IdentityQuery<T> where(Condition... condition);
+
+    /**
+     * <p>Add to this query the sorting conditions to be applied to the results.</p>
+     *
+     * @param sorts The ordering conditions.
+     *
+     * @return
+     */
+    IdentityQuery<T> sortBy(Sort... sorts);
+
+    /**
+     * <p>The type used to create this query.</p>
+     *
+     * @return
+     */
+    Class<T> getIdentityType();
+
+    /**
+     * <p>Returns a map with all the parameter set for this query.</p>
+     *
+     * @return
+     *
+     * @deprecated Use {@link IdentityQuery#getConditions()} instead. Will be removed.
+     */
+    @Deprecated
+    Map<QueryParameter, Object[]> getParameters();
+
+    /**
+     * <p>Returns a set containing all conditions used by this query to filter its results.</p>
+     *
+     * @return
+     */
+    Set<Condition> getConditions();
+
+    /**
+     * <p>Returns a set containing all sorting conditions used to filter the results.</p>
+     *
+     * @return
+     */
+    Set<Sort> getSorting();
+
+    /**
+     * <p>Returns the value used to restrict the given query parameter.</p>
+     *
+     * @param queryParameter
+     *
+     * @return
+     */
+    @Deprecated
+    Object[] getParameter(QueryParameter queryParameter);
+
+    @Deprecated
+    Map<QueryParameter, Object[]> getParameters(Class<?> type);
+
+    int getOffset();
+
+    /**
+     * <p>Set the position of the first result to retrieve.</p>
+     *
+     * @param offset
+     *
+     * @return
+     */
+    IdentityQuery<T> setOffset(int offset);
+
+    /**
+     * <p>Returns the number of instances to retrieve.</p>
+     *
+     * @return
+     */
+    int getLimit();
+
+    /**
+     * <p>Set the maximum number of results to retrieve.</p>
+     *
+     * @param limit the number of instances to retrieve.
+     *
+     * @return
+     */
+    IdentityQuery<T> setLimit(int limit);
+
+    /**
+     * <p>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.</p>
+     *
+     * @return
+     */
+    List<T> 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 0000000..0220635
--- /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;
+
+/**
+ * <p>The {@link IdentityQueryBuilder} is responsible for creating {@link IdentityQuery} instances and also
+ * provide methods to create conditions, orderings, sorting, etc.</p>
+ *
+ * @author Pedro Igor
+ */
+public interface IdentityQueryBuilder {
+
+    /**
+     * <p>Create a condition for testing the whether the query parameter satisfies the given pattern..</p>
+     *
+     * @param parameter The query parameter.
+     * @param pattern The pattern to match.
+     *
+     * @return
+     */
+    Condition like(QueryParameter parameter, String pattern);
+
+    /**
+     * <p>Create a condition for testing the arguments for equality.</p>
+     *
+     * @param parameter The query parameter.
+     * @param value The value to compare.
+     *
+     * @return
+     */
+    Condition equal(QueryParameter parameter, Object value);
+
+    /**
+     * <p>Create a condition for testing whether the query parameter is grater than the given value..</p>
+     *
+     * @param parameter The query parameter.
+     * @param x The value to compare.
+     *
+     * @return
+     */
+    Condition greaterThan(QueryParameter parameter, Object x);
+
+    /**
+     * <p>Create a condition for testing whether the query parameter is grater than or equal to the given value..</p>
+     *
+     * @param parameter The query parameter.
+     * @param x The value to compare.
+     *
+     * @return
+     */
+    Condition greaterThanOrEqualTo(QueryParameter parameter, Object x);
+
+    /**
+     * <p>Create a condition for testing whether the query parameter is less than the given value..</p>
+     *
+     * @param parameter The query parameter.
+     * @param x The value to compare.
+     *
+     * @return
+     */
+    Condition lessThan(QueryParameter parameter, Object x);
+
+    /**
+     * <p>Create a condition for testing whether the query parameter is less than or equal to the given value..</p>
+     *
+     * @param parameter The query parameter.
+     * @param x The value to compare.
+     *
+     * @return
+     */
+    Condition lessThanOrEqualTo(QueryParameter parameter, Object x);
+
+    /**
+     * <p>Create a condition for testing whether the query parameter is between the given values.</p>
+     *
+     * @param parameter The query parameter.
+     * @param x The first value.
+     * @param x The second value.
+     *
+     * @return
+     */
+    Condition between(QueryParameter parameter, Object x, Object y);
+
+    /**
+     * <p>Create a condition for testing whether the query parameter is contained in a list of values.</p>
+     *
+     * @param parameter The query parameter.
+     * @param values A list of values.
+     *
+     * @return
+     */
+    Condition in(QueryParameter parameter, Object... values);
+
+    /**
+     * <p>Create an ascending order for the given <code>parameter</code>. Once created, you can use it to sort the results of a
+     * query.</p>
+     *
+     * @param parameter The query parameter to sort.
+     *
+     * @return
+     */
+    Sort asc(QueryParameter parameter);
+
+    /**
+     * <p>Create an descending order for the given <code>parameter</code>. Once created, you can use it to sort the results of a
+     * query.</p>
+     *
+     * @param parameter The query parameter to sort.
+     *
+     * @return
+     */
+    Sort desc(QueryParameter parameter);
+
+    /**
+     * <p> Create an {@link IdentityQuery} that can be used to query for {@link
+     * IdentityType} instances of a the given <code>identityType</code>. </p>
+     *
+     * @param identityType The type to search. If you provide the {@link IdentityType}
+     * base interface any of its sub-types will be returned.
+     *
+     * @return
+     */
+    <T extends IdentityType> IdentityQuery<T> createIdentityQuery(Class<T> identityType);
+}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/BetweenCondition.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/BetweenCondition.java
new file mode 100644
index 0000000..672fdaa
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/BetweenCondition.java
@@ -0,0 +1,33 @@
+package org.keycloak.federation.ldap.idm.query.internal;
+
+import org.keycloak.federation.ldap.idm.query.Condition;
+import org.keycloak.federation.ldap.idm.query.QueryParameter;
+
+/**
+ * @author Pedro Igor
+ */
+public class BetweenCondition implements Condition {
+
+    private final Comparable x;
+    private final Comparable y;
+    private final QueryParameter parameter;
+
+    public BetweenCondition(QueryParameter parameter, Comparable x, Comparable y) {
+        this.parameter = parameter;
+        this.x = x;
+        this.y = y;
+    }
+
+    @Override
+    public QueryParameter getParameter() {
+        return this.parameter;
+    }
+
+    public Comparable getX() {
+        return this.x;
+    }
+
+    public Comparable getY() {
+        return this.y;
+    }
+}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/DefaultIdentityQuery.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/DefaultIdentityQuery.java
new file mode 100644
index 0000000..21b0253
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/DefaultIdentityQuery.java
@@ -0,0 +1,207 @@
+package org.keycloak.federation.ldap.idm.query.internal;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+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.IdentityQuery;
+import org.keycloak.federation.ldap.idm.query.IdentityQueryBuilder;
+import org.keycloak.federation.ldap.idm.query.QueryParameter;
+import org.keycloak.federation.ldap.idm.query.Sort;
+import org.keycloak.federation.ldap.idm.store.IdentityStore;
+import org.keycloak.models.ModelException;
+
+import static java.util.Collections.unmodifiableSet;
+
+/**
+ * Default IdentityQuery implementation.
+ *
+ * @param <T>
+ *
+ * @author Shane Bryzak
+ */
+public class DefaultIdentityQuery<T extends IdentityType> implements IdentityQuery<T> {
+
+    private final Map<QueryParameter, Object[]> parameters = new LinkedHashMap<QueryParameter, Object[]>();
+    private final Class<T> identityType;
+    private final IdentityStore 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 DefaultIdentityQuery(IdentityQueryBuilder queryBuilder, Class<T> identityType, IdentityStore identityStore) {
+        this.queryBuilder = queryBuilder;
+        this.identityStore = identityStore;
+        this.identityType = identityType;
+    }
+
+    @Override
+    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;
+    }
+
+    @Override
+    public IdentityQuery<T> where(Condition... condition) {
+        this.conditions.addAll(Arrays.asList(condition));
+        return this;
+    }
+
+    @Override
+    public IdentityQuery<T> sortBy(Sort... sorts) {
+        this.ordering.addAll(Arrays.asList(sorts));
+        return this;
+    }
+
+    @Override
+    public Set<Sort> getSorting() {
+        return unmodifiableSet(this.ordering);
+    }
+
+    @Override
+    public Class<T> getIdentityType() {
+        return identityType;
+    }
+
+    @Override
+    public Map<QueryParameter, Object[]> getParameters() {
+        return parameters;
+    }
+
+    @Override
+    public Object[] getParameter(QueryParameter queryParameter) {
+        return this.parameters.get(queryParameter);
+    }
+
+    @Override
+    public Map<QueryParameter, Object[]> getParameters(Class<?> type) {
+        Map<QueryParameter, Object[]> typedParameters = new HashMap<QueryParameter, Object[]>();
+
+        Set<Map.Entry<QueryParameter, Object[]>> entrySet = this.parameters.entrySet();
+
+        for (Map.Entry<QueryParameter, Object[]> entry : entrySet) {
+            if (type.isInstance(entry.getKey())) {
+                typedParameters.put(entry.getKey(), entry.getValue());
+            }
+        }
+
+        return typedParameters;
+    }
+
+    @Override
+    public int getLimit() {
+        return limit;
+    }
+
+    @Override
+    public int getOffset() {
+        return offset;
+    }
+
+    @Override
+    public Object getPaginationContext() {
+        return paginationContext;
+    }
+
+    @Override
+    public QueryParameter[] getSortParameters() {
+        return sortParameters;
+    }
+
+    @Override
+    public boolean isSortAscending() {
+        return sortAscending;
+    }
+
+    @Override
+    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;
+    }
+
+    @Override
+    public int getResultCount() {
+        return identityStore.countQueryResults(this);
+    }
+
+    @Override
+    public IdentityQuery<T> setOffset(int offset) {
+        this.offset = offset;
+        return this;
+    }
+
+    @Override
+    public IdentityQuery<T> setLimit(int limit) {
+        this.limit = limit;
+        return this;
+    }
+
+    @Override
+    public IdentityQuery<T> setSortParameters(QueryParameter... sortParameters) {
+        this.sortParameters = sortParameters;
+        return this;
+    }
+
+    @Override
+    public IdentityQuery<T> setSortAscending(boolean sortAscending) {
+        this.sortAscending = sortAscending;
+        return this;
+    }
+
+    @Override
+    public IdentityQuery<T> setPaginationContext(Object object) {
+        this.paginationContext = object;
+        return this;
+    }
+
+    @Override
+    public Set<Condition> getConditions() {
+        return unmodifiableSet(this.conditions);
+    }
+}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/DefaultQueryBuilder.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/DefaultQueryBuilder.java
new file mode 100644
index 0000000..5d3b72e
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/DefaultQueryBuilder.java
@@ -0,0 +1,89 @@
+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.IdentityQuery;
+import org.keycloak.federation.ldap.idm.query.IdentityQueryBuilder;
+import org.keycloak.federation.ldap.idm.query.QueryParameter;
+import org.keycloak.federation.ldap.idm.query.Sort;
+import org.keycloak.federation.ldap.idm.store.IdentityStore;
+import org.keycloak.models.ModelException;
+
+/**
+ * @author Pedro Igor
+ */
+public class DefaultQueryBuilder implements IdentityQueryBuilder {
+
+    private final IdentityStore identityStore;
+
+    public DefaultQueryBuilder(IdentityStore identityStore) {
+        this.identityStore = identityStore;
+    }
+
+    @Override
+    public Condition like(QueryParameter parameter, String pattern) {
+        return new LikeCondition(parameter, pattern);
+    }
+
+    @Override
+    public Condition equal(QueryParameter parameter, Object value) {
+        return new EqualCondition(parameter, value);
+    }
+
+    @Override
+    public Condition greaterThan(QueryParameter parameter, Object x) {
+        throwExceptionIfNotComparable(x);
+        return new GreaterThanCondition(parameter, (Comparable) x, false);
+    }
+
+    @Override
+    public Condition greaterThanOrEqualTo(QueryParameter parameter, Object x) {
+        throwExceptionIfNotComparable(x);
+        return new GreaterThanCondition(parameter, (Comparable) x, true);
+    }
+
+    @Override
+    public Condition lessThan(QueryParameter parameter, Object x) {
+        throwExceptionIfNotComparable(x);
+        return new LessThanCondition(parameter, (Comparable) x, false);
+    }
+
+    @Override
+    public Condition lessThanOrEqualTo(QueryParameter parameter, Object x) {
+        throwExceptionIfNotComparable(x);
+        return new LessThanCondition(parameter, (Comparable) x, true);
+    }
+
+    @Override
+    public Condition between(QueryParameter parameter, Object x, Object y) {
+        throwExceptionIfNotComparable(x);
+        throwExceptionIfNotComparable(y);
+        return new BetweenCondition(parameter, (Comparable) x, (Comparable) y);
+    }
+
+    @Override
+    public Condition in(QueryParameter parameter, Object... x) {
+        return new InCondition(parameter, x);
+    }
+
+    @Override
+    public Sort asc(QueryParameter parameter) {
+        return new Sort(parameter, true);
+    }
+
+    @Override
+    public Sort desc(QueryParameter parameter) {
+        return new Sort(parameter, false);
+    }
+
+    @Override
+    public <T extends IdentityType> IdentityQuery createIdentityQuery(Class<T> identityType) {
+        return new DefaultIdentityQuery(this, identityType, this.identityStore);
+    }
+
+    private void throwExceptionIfNotComparable(Object x) {
+        if (!Comparable.class.isInstance(x)) {
+            throw new ModelException("Query parameter value [" + x + "] must be " + Comparable.class + ".");
+        }
+    }
+}
\ No newline at end of file
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/EqualCondition.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/EqualCondition.java
new file mode 100644
index 0000000..a3fee26
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/EqualCondition.java
@@ -0,0 +1,36 @@
+package org.keycloak.federation.ldap.idm.query.internal;
+
+import org.keycloak.federation.ldap.idm.query.AttributeParameter;
+import org.keycloak.federation.ldap.idm.query.Condition;
+import org.keycloak.federation.ldap.idm.query.QueryParameter;
+
+/**
+ * @author Pedro Igor
+ */
+public class EqualCondition implements Condition {
+
+    private final QueryParameter parameter;
+    private final Object value;
+
+    public EqualCondition(QueryParameter parameter, Object value) {
+        this.parameter = parameter;
+        this.value = value;
+    }
+
+    @Override
+    public QueryParameter getParameter() {
+        return this.parameter;
+    }
+
+    public Object getValue() {
+        return this.value;
+    }
+
+    @Override
+    public String toString() {
+        return "EqualCondition{" +
+                "parameter=" + ((AttributeParameter) parameter).getName() +
+                ", value=" + value +
+                '}';
+    }
+}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/GreaterThanCondition.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/GreaterThanCondition.java
new file mode 100644
index 0000000..cbdf540
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/GreaterThanCondition.java
@@ -0,0 +1,34 @@
+package org.keycloak.federation.ldap.idm.query.internal;
+
+import org.keycloak.federation.ldap.idm.query.Condition;
+import org.keycloak.federation.ldap.idm.query.QueryParameter;
+
+/**
+ * @author Pedro Igor
+ */
+public class GreaterThanCondition implements Condition {
+
+    private final boolean orEqual;
+
+    private final QueryParameter parameter;
+    private final Comparable value;
+
+    public GreaterThanCondition(QueryParameter parameter, Comparable value, boolean orEqual) {
+        this.parameter = parameter;
+        this.value = value;
+        this.orEqual = orEqual;
+    }
+
+    @Override
+    public QueryParameter getParameter() {
+        return this.parameter;
+    }
+
+    public Comparable getValue() {
+        return this.value;
+    }
+
+    public boolean isOrEqual() {
+        return this.orEqual;
+    }
+}
\ No newline at end of file
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/InCondition.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/InCondition.java
new file mode 100644
index 0000000..54d2234
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/InCondition.java
@@ -0,0 +1,28 @@
+package org.keycloak.federation.ldap.idm.query.internal;
+
+import org.keycloak.federation.ldap.idm.query.Condition;
+import org.keycloak.federation.ldap.idm.query.QueryParameter;
+
+/**
+ * @author Pedro Igor
+ */
+public class InCondition implements Condition {
+
+    private final QueryParameter parameter;
+    private final Object[] value;
+
+    public InCondition(QueryParameter parameter, Object[] value) {
+        this.parameter = parameter;
+        this.value = value;
+    }
+
+    @Override
+    public QueryParameter getParameter() {
+        return this.parameter;
+    }
+
+    public Object[] getValue() {
+        return this.value;
+    }
+}
+
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/LessThanCondition.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/LessThanCondition.java
new file mode 100644
index 0000000..5906a5c
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/LessThanCondition.java
@@ -0,0 +1,34 @@
+package org.keycloak.federation.ldap.idm.query.internal;
+
+import org.keycloak.federation.ldap.idm.query.Condition;
+import org.keycloak.federation.ldap.idm.query.QueryParameter;
+
+/**
+ * @author Pedro Igor
+ */
+public class LessThanCondition implements Condition {
+
+    private final boolean orEqual;
+
+    private final QueryParameter parameter;
+    private final Comparable value;
+
+    public LessThanCondition(QueryParameter parameter, Comparable value, boolean orEqual) {
+        this.parameter = parameter;
+        this.value = value;
+        this.orEqual = orEqual;
+    }
+
+    @Override
+    public QueryParameter getParameter() {
+        return this.parameter;
+    }
+
+    public Comparable getValue() {
+        return this.value;
+    }
+
+    public boolean isOrEqual() {
+        return this.orEqual;
+    }
+}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/LikeCondition.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/LikeCondition.java
new file mode 100644
index 0000000..6c68103
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/LikeCondition.java
@@ -0,0 +1,28 @@
+package org.keycloak.federation.ldap.idm.query.internal;
+
+import org.keycloak.federation.ldap.idm.query.Condition;
+import org.keycloak.federation.ldap.idm.query.QueryParameter;
+
+/**
+ * @author Pedro Igor
+ */
+public class LikeCondition implements Condition {
+
+    private final QueryParameter parameter;
+    private final Object value;
+
+    public LikeCondition(QueryParameter parameter, Object value) {
+        this.parameter = parameter;
+        this.value = value;
+    }
+
+    @Override
+    public QueryParameter getParameter() {
+        return this.parameter;
+    }
+
+    public Object getValue() {
+        return this.value;
+    }
+
+}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/QueryParameter.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/QueryParameter.java
new file mode 100644
index 0000000..ae2bbdf
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/QueryParameter.java
@@ -0,0 +1,12 @@
+package org.keycloak.federation.ldap.idm.query;
+
+/**
+ * A marker interface indicating that the implementing class can be used as a
+ * parameter within an IdentityQuery or RelationshipQuery
+ *
+ * @author Shane Bryzak
+ *
+ */
+public interface QueryParameter {
+
+}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/Sort.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/Sort.java
new file mode 100644
index 0000000..dfd331e
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/Sort.java
@@ -0,0 +1,23 @@
+package org.keycloak.federation.ldap.idm.query;
+
+/**
+ * @author Pedro Igor
+ */
+public class Sort {
+
+    private final QueryParameter parameter;
+    private final boolean asc;
+
+    public Sort(QueryParameter parameter, boolean asc) {
+        this.parameter = parameter;
+        this.asc = asc;
+    }
+
+    public QueryParameter getParameter() {
+        return this.parameter;
+    }
+
+    public boolean isAscending() {
+        return asc;
+    }
+}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/IdentityStore.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/IdentityStore.java
new file mode 100644
index 0000000..7fef705
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/IdentityStore.java
@@ -0,0 +1,81 @@
+package org.keycloak.federation.ldap.idm.store;
+
+import java.util.List;
+
+import org.keycloak.federation.ldap.idm.model.AttributedType;
+import org.keycloak.federation.ldap.idm.model.IdentityType;
+import org.keycloak.federation.ldap.idm.model.LDAPUser;
+import org.keycloak.federation.ldap.idm.query.IdentityQuery;
+import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStoreConfiguration;
+
+/**
+ * IdentityStore representation providing minimal SPI
+ *
+ * TODO: Rather remove this abstraction
+ *
+ * @author Boleslaw Dawidowicz
+ * @author Shane Bryzak
+ */
+public interface IdentityStore {
+
+    /**
+     * Returns the configuration for this IdentityStore instance
+     *
+     * @return
+     */
+    LDAPIdentityStoreConfiguration getConfig();
+
+    // General
+
+    /**
+     * Persists the specified IdentityType
+     *
+     * @param value
+     */
+    void add(AttributedType value);
+
+    /**
+     * Updates the specified IdentityType
+     *
+     * @param value
+     */
+    void update(AttributedType value);
+
+    /**
+     * Removes the specified IdentityType
+     *
+     * @param value
+     */
+    void remove(AttributedType value);
+
+    // Identity query
+
+    <V extends IdentityType> List<V> fetchQueryResults(IdentityQuery<V> identityQuery);
+
+    <V extends IdentityType> int countQueryResults(IdentityQuery<V> identityQuery);
+
+//    // Relationship query
+//
+//    <V extends Relationship> List<V> fetchQueryResults(RelationshipQuery<V> query);
+//
+//    <V extends Relationship> int countQueryResults(RelationshipQuery<V> query);
+
+    // Credentials
+
+    /**
+     * Validates the specified credentials.
+     *
+     * @param user Keycloak user
+     * @param password Ldap password
+     */
+    boolean validatePassword(LDAPUser user, String password);
+
+    /**
+     * Updates the specified credential value.
+     *
+     * @param user Keycloak user
+     * @param password Ldap password
+     */
+    void updatePassword(LDAPUser user, String password);
+
+}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPIdentityStore.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPIdentityStore.java
new file mode 100644
index 0000000..8b91240
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPIdentityStore.java
@@ -0,0 +1,761 @@
+package org.keycloak.federation.ldap.idm.store.ldap;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.BasicAttribute;
+import javax.naming.directory.BasicAttributes;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.ModificationItem;
+import javax.naming.directory.SearchResult;
+
+import org.jboss.logging.Logger;
+import org.keycloak.federation.ldap.idm.model.AttributedType;
+import org.keycloak.federation.ldap.idm.model.IdentityType;
+import org.keycloak.federation.ldap.idm.model.LDAPUser;
+import org.keycloak.federation.ldap.idm.query.AttributeParameter;
+import org.keycloak.federation.ldap.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.query.QueryParameter;
+import org.keycloak.federation.ldap.idm.query.internal.BetweenCondition;
+import org.keycloak.federation.ldap.idm.query.internal.DefaultQueryBuilder;
+import org.keycloak.federation.ldap.idm.query.internal.EqualCondition;
+import org.keycloak.federation.ldap.idm.query.internal.GreaterThanCondition;
+import org.keycloak.federation.ldap.idm.query.internal.InCondition;
+import org.keycloak.federation.ldap.idm.query.internal.LessThanCondition;
+import org.keycloak.federation.ldap.idm.query.internal.LikeCondition;
+import org.keycloak.federation.ldap.idm.store.IdentityStore;
+import org.keycloak.models.LDAPConstants;
+import org.keycloak.models.ModelDuplicateException;
+import org.keycloak.models.ModelException;
+import org.keycloak.models.utils.reflection.NamedPropertyCriteria;
+import org.keycloak.models.utils.reflection.Property;
+import org.keycloak.models.utils.reflection.PropertyQueries;
+import org.keycloak.models.utils.reflection.TypedPropertyCriteria;
+import org.keycloak.util.reflections.Reflections;
+
+/**
+ * An IdentityStore implementation backed by an LDAP directory
+ *
+ * @author Shane Bryzak
+ * @author Anil Saldhana
+ * @author <a href="mailto:psilva@redhat.com">Pedro Silva</a>
+ */
+public class LDAPIdentityStore implements IdentityStore {
+
+    private static final Logger logger = Logger.getLogger(LDAPIdentityStore.class);
+
+    public static final String EMPTY_ATTRIBUTE_VALUE = " ";
+
+    private final LDAPIdentityStoreConfiguration config;
+    private final LDAPOperationManager operationManager;
+
+    public LDAPIdentityStore(LDAPIdentityStoreConfiguration config) {
+        this.config = config;
+
+        try {
+            this.operationManager = new LDAPOperationManager(getConfig());
+        } catch (NamingException e) {
+            throw new ModelException("Couldn't init operation manager", e);
+        }
+    }
+
+    @Override
+    public LDAPIdentityStoreConfiguration getConfig() {
+        return this.config;
+    }
+
+    @Override
+    public void add(AttributedType attributedType) {
+        // id will be assigned by the ldap server
+        attributedType.setId(null);
+
+        String entryDN = getBindingDN(attributedType, true);
+        this.operationManager.createSubContext(entryDN, extractAttributes(attributedType, true));
+        addToParentAsMember(attributedType);
+        attributedType.setId(getEntryIdentifier(attributedType));
+
+        attributedType.setEntryDN(entryDN);
+
+        if (logger.isTraceEnabled()) {
+            logger.tracef("Type with identifier [%s] successfully added to identity store [%s].", attributedType.getId(), this);
+        }
+    }
+
+    @Override
+    public void update(AttributedType attributedType) {
+        BasicAttributes updatedAttributes = extractAttributes(attributedType, false);
+        NamingEnumeration<Attribute> attributes = updatedAttributes.getAll();
+
+        this.operationManager.modifyAttributes(getBindingDN(attributedType, true), attributes);
+
+        if (logger.isTraceEnabled()) {
+            logger.tracef("Type with identifier [%s] successfully updated to identity store [%s].", attributedType.getId(), this);
+        }
+    }
+
+    @Override
+    public void remove(AttributedType attributedType) {
+        LDAPMappingConfiguration mappingConfig = getMappingConfig(attributedType.getClass());
+
+        this.operationManager.removeEntryById(getBaseDN(attributedType), attributedType.getId(), mappingConfig);
+
+        if (logger.isTraceEnabled()) {
+            logger.tracef("Type with identifier [%s] successfully removed from identity store [%s].", attributedType.getId(), this);
+        }
+    }
+
+    @Override
+    public <V extends IdentityType> List<V> fetchQueryResults(IdentityQuery<V> identityQuery) {
+        List<V> results = new ArrayList<V>();
+
+        try {
+            if (identityQuery.getSorting() != null && !identityQuery.getSorting().isEmpty()) {
+                throw new ModelException("LDAP Identity Store does not support sorted queries.");
+            }
+
+            for (Condition condition : identityQuery.getConditions()) {
+
+                if (IdentityType.ID.equals(condition.getParameter())) {
+                    if (EqualCondition.class.isInstance(condition)) {
+                        EqualCondition equalCondition = (EqualCondition) condition;
+                        SearchResult search = this.operationManager
+                                .lookupById(getConfig().getBaseDN(), equalCondition.getValue().toString(), null);
+
+                        if (search != null) {
+                            results.add((V) populateAttributedType(search, null));
+                        }
+                    }
+
+                    return results;
+                }
+            }
+
+            if (!IdentityType.class.equals(identityQuery.getIdentityType())) {
+                // the ldap store does not support queries based on root types. Except if based on the identifier.
+                LDAPMappingConfiguration ldapEntryConfig = getMappingConfig(identityQuery.getIdentityType());
+                StringBuilder filter = createIdentityTypeSearchFilter(identityQuery, ldapEntryConfig);
+                String baseDN = getBaseDN(ldapEntryConfig);
+                List<SearchResult> search;
+
+                if (getConfig().isPagination() && identityQuery.getLimit() > 0) {
+                    search = this.operationManager.searchPaginated(baseDN, filter.toString(), ldapEntryConfig, identityQuery);
+                } else {
+                    search = this.operationManager.search(baseDN, filter.toString(), ldapEntryConfig);
+                }
+
+                for (SearchResult result : search) {
+                    if (!result.getNameInNamespace().equals(baseDN)) {
+                        results.add((V) populateAttributedType(result, null));
+                    }
+                }
+            }
+        } catch (Exception e) {
+            throw new ModelException("Querying of identity type failed " + identityQuery, e);
+        }
+
+        return results;
+    }
+
+    @Override
+    public <V extends IdentityType> int countQueryResults(IdentityQuery<V> identityQuery) {
+        int limit = identityQuery.getLimit();
+        int offset = identityQuery.getOffset();
+
+        identityQuery.setLimit(0);
+        identityQuery.setOffset(0);
+
+        int resultCount = identityQuery.getResultList().size();
+
+        identityQuery.setLimit(limit);
+        identityQuery.setOffset(offset);
+
+        return resultCount;
+    }
+
+    public IdentityQueryBuilder createQueryBuilder() {
+        return new DefaultQueryBuilder(this);
+    }
+
+    // *************** CREDENTIALS AND USER SPECIFIC STUFF
+
+    @Override
+    public boolean validatePassword(LDAPUser user, String password) {
+        String userDN = getEntryDNOfUser(user);
+
+        if (logger.isDebugEnabled()) {
+            logger.debugf("Using DN [%s] for authentication of user [%s]", userDN, user.getLoginName());
+        }
+
+        if (operationManager.authenticate(userDN, password)) {
+            return true;
+        }
+
+        return false;
+    }
+
+    @Override
+    public void updatePassword(LDAPUser user, String password) {
+        String userDN = getEntryDNOfUser(user);
+
+        if (logger.isDebugEnabled()) {
+            logger.debugf("Using DN [%s] for updating LDAP password of user [%s]", userDN, user.getLoginName());
+        }
+
+        if (getConfig().isActiveDirectory()) {
+            updateADPassword(userDN, password);
+        } else {
+            ModificationItem[] mods = new ModificationItem[1];
+
+            try {
+                BasicAttribute mod0 = new BasicAttribute(LDAPConstants.USER_PASSWORD_ATTRIBUTE, password);
+
+                mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, mod0);
+
+                operationManager.modifyAttribute(userDN, mod0);
+            } catch (Exception e) {
+                throw new ModelException("Error updating password.", e);
+            }
+        }
+    }
+
+
+    private void updateADPassword(String userDN, String password) {
+        try {
+            // Replace the "unicdodePwd" attribute with a new value
+            // Password must be both Unicode and a quoted string
+            String newQuotedPassword = "\"" + password + "\"";
+            byte[] newUnicodePassword = newQuotedPassword.getBytes("UTF-16LE");
+
+            BasicAttribute unicodePwd = new BasicAttribute("unicodePwd", newUnicodePassword);
+
+            List<ModificationItem> modItems = new ArrayList<ModificationItem>();
+            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
+            if (getConfig().isUserAccountControlsAfterPasswordUpdate()) {
+                BasicAttribute userAccountControl = new BasicAttribute("userAccountControl", "512");
+                modItems.add(new ModificationItem(DirContext.REPLACE_ATTRIBUTE, userAccountControl));
+
+                logger.debugf("Attribute userAccountControls will be switched to 512 after password update of user [%s]", userDN);
+            }
+
+            operationManager.modifyAttributes(userDN, modItems.toArray(new ModificationItem[] {}));
+        } catch (Exception e) {
+            throw new ModelException(e);
+        }
+    }
+
+
+    private String getEntryDNOfUser(LDAPUser user) {
+        // First try if user already has entryDN on him
+        String entryDN = user.getEntryDN();
+        if (entryDN != null) {
+            return entryDN;
+        }
+
+        // Need to find user in LDAP
+        String username = user.getLoginName();
+        user = getUser(username);
+        if (user == null) {
+            throw new ModelException("No LDAP user found with username " + username);
+        }
+
+        return user.getEntryDN();
+    }
+
+
+    public LDAPUser getUser(String username) {
+
+        if (isNullOrEmpty(username)) {
+            return null;
+        }
+
+        IdentityQueryBuilder queryBuilder = createQueryBuilder();
+        List<LDAPUser> agents = queryBuilder.createIdentityQuery(LDAPUser.class)
+                .where(queryBuilder.equal(LDAPUser.LOGIN_NAME, username)).getResultList();
+
+        if (agents.isEmpty()) {
+            return null;
+        } else if (agents.size() == 1) {
+            return agents.get(0);
+        } else {
+            throw new ModelDuplicateException("Error - multiple Agent objects found with same login name");
+        }
+    }
+
+    // ************ END CREDENTIALS AND USER SPECIFIC STUFF
+
+
+    private String getBaseDN(final LDAPMappingConfiguration ldapEntryConfig) {
+        String baseDN = getConfig().getBaseDN();
+
+        if (ldapEntryConfig.getBaseDN() != null) {
+            baseDN = ldapEntryConfig.getBaseDN();
+        }
+
+        return baseDN;
+    }
+
+    protected <V extends IdentityType> StringBuilder createIdentityTypeSearchFilter(final IdentityQuery<V> identityQuery, final LDAPMappingConfiguration ldapEntryConfig) {
+        StringBuilder filter = new StringBuilder();
+
+        for (Condition condition : identityQuery.getConditions()) {
+            QueryParameter queryParameter = condition.getParameter();
+
+            if (!IdentityType.ID.equals(queryParameter)) {
+                if (AttributeParameter.class.isInstance(queryParameter)) {
+                    AttributeParameter attributeParameter = (AttributeParameter) queryParameter;
+                    String attributeName = ldapEntryConfig.getMappedProperties().get(attributeParameter.getName());
+
+                    if (attributeName != null) {
+                        if (EqualCondition.class.isInstance(condition)) {
+                            EqualCondition equalCondition = (EqualCondition) condition;
+                            Object parameterValue = equalCondition.getValue();
+
+                            if (Date.class.isInstance(parameterValue)) {
+                                parameterValue = LDAPUtil.formatDate((Date) parameterValue);
+                            }
+
+                            filter.append("(").append(attributeName).append(LDAPConstants.EQUAL).append(parameterValue).append(")");
+                        } else if (LikeCondition.class.isInstance(condition)) {
+                            LikeCondition likeCondition = (LikeCondition) condition;
+                            String parameterValue = (String) likeCondition.getValue();
+
+                        } else if (GreaterThanCondition.class.isInstance(condition)) {
+                            GreaterThanCondition greaterThanCondition = (GreaterThanCondition) condition;
+                            Comparable parameterValue = (Comparable) greaterThanCondition.getValue();
+
+                            if (Date.class.isInstance(parameterValue)) {
+                                parameterValue = LDAPUtil.formatDate((Date) parameterValue);
+                            }
+
+                            if (greaterThanCondition.isOrEqual()) {
+                                filter.append("(").append(attributeName).append(">=").append(parameterValue).append(")");
+                            } else {
+                                filter.append("(").append(attributeName).append(">").append(parameterValue).append(")");
+                            }
+                        } else if (LessThanCondition.class.isInstance(condition)) {
+                            LessThanCondition lessThanCondition = (LessThanCondition) condition;
+                            Comparable parameterValue = (Comparable) lessThanCondition.getValue();
+
+                            if (Date.class.isInstance(parameterValue)) {
+                                parameterValue = LDAPUtil.formatDate((Date) parameterValue);
+                            }
+
+                            if (lessThanCondition.isOrEqual()) {
+                                filter.append("(").append(attributeName).append("<=").append(parameterValue).append(")");
+                            } else {
+                                filter.append("(").append(attributeName).append("<").append(parameterValue).append(")");
+                            }
+                        } else if (BetweenCondition.class.isInstance(condition)) {
+                            BetweenCondition betweenCondition = (BetweenCondition) condition;
+                            Comparable x = betweenCondition.getX();
+                            Comparable y = betweenCondition.getY();
+
+                            if (Date.class.isInstance(x)) {
+                                x = LDAPUtil.formatDate((Date) x);
+                            }
+
+                            if (Date.class.isInstance(y)) {
+                                y = LDAPUtil.formatDate((Date) y);
+                            }
+
+                            filter.append("(").append(x).append("<=").append(attributeName).append("<=").append(y).append(")");
+                        } else if (InCondition.class.isInstance(condition)) {
+                            InCondition inCondition = (InCondition) condition;
+                            Object[] valuesToCompare = inCondition.getValue();
+
+                            filter.append("(&(");
+
+                            for (int i = 0; i< valuesToCompare.length; i++) {
+                                Object value = valuesToCompare[i];
+
+                                filter.append("(").append(attributeName).append(LDAPConstants.EQUAL).append(value).append(")");
+                            }
+
+                            filter.append("))");
+                        } else {
+                            throw new ModelException("Unsupported query condition [" + condition + "].");
+                        }
+                    }
+                }
+            }
+        }
+
+
+        filter.insert(0, "(&(");
+        filter.append(getObjectClassesFilter(ldapEntryConfig));
+        filter.append("))");
+
+        return filter;
+    }
+
+    private StringBuilder getObjectClassesFilter(final LDAPMappingConfiguration ldapEntryConfig) {
+        StringBuilder builder = new StringBuilder();
+
+        if (ldapEntryConfig != null && !ldapEntryConfig.getObjectClasses().isEmpty()) {
+            for (String objectClass : ldapEntryConfig.getObjectClasses()) {
+                builder.append("(").append(LDAPConstants.OBJECT_CLASS).append(LDAPConstants.EQUAL).append(objectClass).append(")");
+            }
+        } else {
+            builder.append("(").append(LDAPConstants.OBJECT_CLASS).append(LDAPConstants.EQUAL).append("*").append(")");
+        }
+
+        return builder;
+    }
+
+    private AttributedType populateAttributedType(SearchResult searchResult, AttributedType attributedType) {
+        return populateAttributedType(searchResult, attributedType, 0);
+    }
+
+    private AttributedType populateAttributedType(SearchResult searchResult, AttributedType attributedType, int hierarchyDepthCount) {
+        try {
+            String entryDN = searchResult.getNameInNamespace();
+            Attributes attributes = searchResult.getAttributes();
+
+            if (attributedType == null) {
+                attributedType = Reflections.newInstance(getConfig().getSupportedTypeByBaseDN(entryDN, getEntryObjectClasses(attributes)));
+            }
+
+            attributedType.setEntryDN(entryDN);
+
+            LDAPMappingConfiguration mappingConfig = getMappingConfig(attributedType.getClass());
+
+            if (hierarchyDepthCount > mappingConfig.getHierarchySearchDepth()) {
+                return null;
+            }
+
+            if (logger.isTraceEnabled()) {
+                logger.tracef("Populating attributed type [%s] from DN [%s]", attributedType, entryDN);
+            }
+
+            NamingEnumeration<? extends Attribute> ldapAttributes = attributes.getAll();
+
+            while (ldapAttributes.hasMore()) {
+                Attribute ldapAttribute = ldapAttributes.next();
+                Object attributeValue;
+
+                try {
+                    attributeValue = ldapAttribute.get();
+                } catch (NoSuchElementException nsee) {
+                    continue;
+                }
+
+                String ldapAttributeName = ldapAttribute.getID();
+
+                if (ldapAttributeName.toLowerCase().equals(getConfig().getUniqueIdentifierAttributeName().toLowerCase())) {
+                    attributedType.setId(this.operationManager.decodeEntryUUID(attributeValue));
+                } else {
+                    String attributeName = findAttributeName(mappingConfig.getMappedProperties(), ldapAttributeName);
+
+                    if (attributeName != null) {
+                        // Find if it's java property or attribute
+                        Property<Object> property = PropertyQueries
+                                .createQuery(attributedType.getClass())
+                                .addCriteria(new NamedPropertyCriteria(attributeName)).getFirstResult();
+
+                        if (property != null) {
+                            if (logger.isTraceEnabled()) {
+                                logger.tracef("Populating property [%s] from ldap attribute [%s] with value [%s] from DN [%s].", property.getName(), ldapAttributeName, attributeValue, entryDN);
+                            }
+
+                            if (property.getJavaClass().equals(Date.class)) {
+                                property.setValue(attributedType, LDAPUtil.parseDate(attributeValue.toString()));
+                            } else {
+                                property.setValue(attributedType, attributeValue);
+                            }
+                        } else {
+                            if (logger.isTraceEnabled()) {
+                                logger.tracef("Populating attribute [%s] from ldap attribute [%s] with value [%s] from DN [%s].", attributeName, ldapAttributeName, attributeValue, entryDN);
+                            }
+
+                            attributedType.setAttribute(new org.keycloak.federation.ldap.idm.model.Attribute(attributeName, (Serializable) attributeValue));
+                        }
+                    }
+                }
+            }
+
+            if (IdentityType.class.isInstance(attributedType)) {
+                IdentityType identityType = (IdentityType) attributedType;
+
+                String createdTimestamp = attributes.get(LDAPConstants.CREATE_TIMESTAMP).get().toString();
+
+                identityType.setCreatedDate(LDAPUtil.parseDate(createdTimestamp));
+            }
+
+            LDAPMappingConfiguration entryConfig = getMappingConfig(attributedType.getClass());
+
+            if (mappingConfig.getParentMembershipAttributeName() != null) {
+                StringBuilder filter = new StringBuilder("(&");
+                String entryBaseDN = entryDN.substring(entryDN.indexOf(LDAPConstants.COMMA) + 1);
+
+                filter
+                        .append("(")
+                        .append(getObjectClassesFilter(entryConfig))
+                        .append(")")
+                        .append("(")
+                        .append(mappingConfig.getParentMembershipAttributeName())
+                        .append(LDAPConstants.EQUAL).append("")
+                        .append(getBindingDN(attributedType, false))
+                        .append(LDAPConstants.COMMA)
+                        .append(entryBaseDN)
+                        .append(")");
+
+                filter.append(")");
+
+                if (logger.isTraceEnabled()) {
+                    logger.tracef("Searching parent entry for DN [%s] using filter [%s].", entryBaseDN, filter.toString());
+                }
+
+                List<SearchResult> search = this.operationManager.search(getConfig().getBaseDN(), filter.toString(), entryConfig);
+
+                if (!search.isEmpty()) {
+                    SearchResult next = search.get(0);
+
+                    Property<AttributedType> parentProperty = PropertyQueries
+                            .<AttributedType>createQuery(attributedType.getClass())
+                            .addCriteria(new TypedPropertyCriteria(attributedType.getClass())).getFirstResult();
+
+                    if (parentProperty != null) {
+                        String parentDN = next.getNameInNamespace();
+                        String parentBaseDN = parentDN.substring(parentDN.indexOf(",") + 1);
+                        Class<? extends AttributedType> baseDNType = getConfig().getSupportedTypeByBaseDN(parentBaseDN, getEntryObjectClasses(attributes));
+
+                        if (parentProperty.getJavaClass().isAssignableFrom(baseDNType)) {
+                            if (logger.isTraceEnabled()) {
+                                logger.tracef("Found parent [%s] for entry for DN [%s].", parentDN, entryDN);
+                            }
+
+                            int hierarchyDepthCount1 = ++hierarchyDepthCount;
+
+                            parentProperty.setValue(attributedType, populateAttributedType(next, null, hierarchyDepthCount1));
+                        }
+                    }
+                } else {
+                    if (logger.isTraceEnabled()) {
+                        logger.tracef("No parent entry found for DN [%s] using filter [%s].", entryDN, filter.toString());
+                    }
+                }
+            }
+        } catch (Exception e) {
+            throw new ModelException("Could not populate attribute type " + attributedType + ".", e);
+        }
+
+        return attributedType;
+    }
+
+    private String findAttributeName(Map<String, String> attrMapping, String ldapAttributeName) {
+        for (Map.Entry<String,String> currentAttr : attrMapping.entrySet()) {
+            if (currentAttr.getValue().equalsIgnoreCase(ldapAttributeName)) {
+                return currentAttr.getKey();
+            }
+        }
+
+        return null;
+    }
+
+    private List<String> getEntryObjectClasses(final Attributes attributes) throws NamingException {
+        Attribute objectClassesAttribute = attributes.get(LDAPConstants.OBJECT_CLASS);
+        List<String> objectClasses = new ArrayList<String>();
+
+        if (objectClassesAttribute == null) {
+            return objectClasses;
+        }
+
+        NamingEnumeration<?> all = objectClassesAttribute.getAll();
+
+        while (all.hasMore()) {
+            objectClasses.add(all.next().toString());
+        }
+
+        return objectClasses;
+    }
+
+    protected BasicAttributes extractAttributes(AttributedType attributedType, boolean isCreate) {
+        BasicAttributes entryAttributes = new BasicAttributes();
+        LDAPMappingConfiguration mappingConfig = getMappingConfig(attributedType.getClass());
+        Map<String, String> mappedProperties = mappingConfig.getMappedProperties();
+
+        for (String propertyName : mappedProperties.keySet()) {
+            if (!mappingConfig.getReadOnlyAttributes().contains(propertyName) && (isCreate || !mappingConfig.getBindingProperty().getName().equals(propertyName))) {
+                Property<Object> property = PropertyQueries
+                        .<Object>createQuery(attributedType.getClass())
+                        .addCriteria(new NamedPropertyCriteria(propertyName)).getFirstResult();
+
+                Object propertyValue = null;
+                if (property != null) {
+                    // Mapped Java property on the object
+                    propertyValue = property.getValue(attributedType);
+                } else {
+                    // Not mapped property. So fallback to attribute
+                    org.keycloak.federation.ldap.idm.model.Attribute<?> attribute = attributedType.getAttribute(propertyName);
+                    if (attribute != null) {
+                        propertyValue = attribute.getValue();
+                    }
+                }
+
+                if (AttributedType.class.isInstance(propertyValue)) {
+                    AttributedType referencedType = (AttributedType) propertyValue;
+                    propertyValue = getBindingDN(referencedType, true);
+                } else {
+                    if (propertyValue == null || isNullOrEmpty(propertyValue.toString())) {
+                        propertyValue = EMPTY_ATTRIBUTE_VALUE;
+                    }
+                }
+
+                entryAttributes.put(mappedProperties.get(propertyName), propertyValue);
+            }
+        }
+
+        // Don't extract object classes for update
+        if (isCreate) {
+            LDAPMappingConfiguration ldapEntryConfig = getMappingConfig(attributedType.getClass());
+
+            BasicAttribute objectClassAttribute = new BasicAttribute(LDAPConstants.OBJECT_CLASS);
+
+            for (String objectClassValue : ldapEntryConfig.getObjectClasses()) {
+                objectClassAttribute.add(objectClassValue);
+
+                if (objectClassValue.equals(LDAPConstants.GROUP_OF_NAMES)
+                        || objectClassValue.equals(LDAPConstants.GROUP_OF_ENTRIES)
+                        || objectClassValue.equals(LDAPConstants.GROUP_OF_UNIQUE_NAMES)) {
+                    entryAttributes.put(LDAPConstants.MEMBER, EMPTY_ATTRIBUTE_VALUE);
+                }
+            }
+
+            entryAttributes.put(objectClassAttribute);
+        }
+
+        return entryAttributes;
+    }
+
+    // TODO: Move class StringUtil from SAML module
+    public static boolean isNullOrEmpty(String str) {
+        return str == null || str.isEmpty();
+    }
+
+    private LDAPMappingConfiguration getMappingConfig(Class<? extends AttributedType> attributedType) {
+        LDAPMappingConfiguration mappingConfig = getConfig().getMappingConfig(attributedType);
+
+        if (mappingConfig == null) {
+            throw new ModelException("Not mapped type [" + attributedType + "].");
+        }
+
+        return mappingConfig;
+    }
+
+    public String getBindingDN(AttributedType attributedType, boolean appendBaseDN) {
+        LDAPMappingConfiguration mappingConfig = getMappingConfig(attributedType.getClass());
+        Property<String> idProperty = mappingConfig.getIdProperty();
+
+        String baseDN;
+
+        if (mappingConfig.getBaseDN() == null || !appendBaseDN) {
+            baseDN = "";
+        } else {
+            baseDN = LDAPConstants.COMMA + getBaseDN(attributedType);
+        }
+
+        Property<String> bindingProperty = mappingConfig.getBindingProperty();
+        String bindingAttribute;
+        String dn;
+
+        if (bindingProperty == null) {
+            bindingAttribute = mappingConfig.getMappedProperties().get(idProperty.getName());
+            dn = idProperty.getValue(attributedType);
+        } else {
+            bindingAttribute = mappingConfig.getMappedProperties().get(bindingProperty.getName());
+            dn = mappingConfig.getBindingProperty().getValue(attributedType);
+        }
+
+        return bindingAttribute + LDAPConstants.EQUAL + dn + baseDN;
+    }
+
+    private String getBaseDN(AttributedType attributedType) {
+        LDAPMappingConfiguration mappingConfig = getMappingConfig(attributedType.getClass());
+        String baseDN = mappingConfig.getBaseDN();
+        String parentDN = mappingConfig.getParentMapping().get(mappingConfig.getIdProperty().getValue(attributedType));
+
+        if (parentDN != null) {
+            baseDN = parentDN;
+        } else {
+            Property<AttributedType> parentProperty = PropertyQueries
+                    .<AttributedType>createQuery(attributedType.getClass())
+                    .addCriteria(new TypedPropertyCriteria(attributedType.getClass())).getFirstResult();
+
+            if (parentProperty != null) {
+                AttributedType parentType = parentProperty.getValue(attributedType);
+
+                if (parentType != null) {
+                    Property<String> parentIdProperty = getMappingConfig(parentType.getClass()).getIdProperty();
+
+                    String parentId = parentIdProperty.getValue(parentType);
+
+                    String parentBaseDN = mappingConfig.getParentMapping().get(parentId);
+
+                    if (parentBaseDN != null) {
+                        baseDN = parentBaseDN;
+                    } else {
+                        baseDN = getBaseDN(parentType);
+                    }
+                }
+            }
+        }
+
+        if (baseDN == null) {
+            baseDN = getConfig().getBaseDN();
+        }
+
+        return baseDN;
+    }
+
+    protected void addToParentAsMember(final AttributedType attributedType) {
+        LDAPMappingConfiguration entryConfig = getMappingConfig(attributedType.getClass());
+
+        if (entryConfig.getParentMembershipAttributeName() != null) {
+            Property<AttributedType> parentProperty = PropertyQueries
+                    .<AttributedType>createQuery(attributedType.getClass())
+                    .addCriteria(new TypedPropertyCriteria(attributedType.getClass()))
+                    .getFirstResult();
+
+            if (parentProperty != null) {
+                AttributedType parentType = parentProperty.getValue(attributedType);
+
+                if (parentType != null) {
+                    Attributes attributes = this.operationManager.getAttributes(parentType.getId(), getBaseDN(parentType), entryConfig);
+                    Attribute attribute = attributes.get(entryConfig.getParentMembershipAttributeName());
+
+                    attribute.add(getBindingDN(attributedType, true));
+
+                    this.operationManager.modifyAttribute(getBindingDN(parentType, true), attribute);
+                }
+            }
+        }
+    }
+
+    protected String getEntryIdentifier(final AttributedType attributedType) {
+        try {
+            // we need this to retrieve the entry's identifier from the ldap server
+            List<SearchResult> search = this.operationManager.search(getBaseDN(attributedType), "(" + getBindingDN(attributedType, false) + ")", getMappingConfig(attributedType.getClass()));
+            Attribute id = search.get(0).getAttributes().get(getConfig().getUniqueIdentifierAttributeName());
+
+            if (id == null) {
+                throw new ModelException("Could not retrieve identifier for entry [" + getBindingDN(attributedType, true) + "].");
+            }
+
+            return this.operationManager.decodeEntryUUID(id.get());
+        } catch (NamingException ne) {
+            throw new ModelException("Could not add type [" + attributedType + "].", ne);
+        }
+    }
+}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPIdentityStoreConfiguration.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPIdentityStoreConfiguration.java
new file mode 100644
index 0000000..0c0a2e1
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPIdentityStoreConfiguration.java
@@ -0,0 +1,188 @@
+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;
+    }
+}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPMappingConfiguration.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPMappingConfiguration.java
new file mode 100644
index 0000000..033d93b
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPMappingConfiguration.java
@@ -0,0 +1,231 @@
+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;
+    }
+}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPOperationManager.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPOperationManager.java
new file mode 100644
index 0000000..507d61f
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPOperationManager.java
@@ -0,0 +1,606 @@
+package org.keycloak.federation.ldap.idm.store.ldap;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import javax.naming.Binding;
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.ModificationItem;
+import javax.naming.directory.SearchControls;
+import javax.naming.directory.SearchResult;
+import javax.naming.ldap.Control;
+import javax.naming.ldap.InitialLdapContext;
+import javax.naming.ldap.LdapContext;
+import javax.naming.ldap.PagedResultsControl;
+import javax.naming.ldap.PagedResultsResponseControl;
+
+import org.jboss.logging.Logger;
+import org.keycloak.federation.ldap.idm.model.IdentityType;
+import org.keycloak.federation.ldap.idm.query.IdentityQuery;
+import org.keycloak.models.LDAPConstants;
+import org.keycloak.models.ModelException;
+
+import static javax.naming.directory.SearchControls.SUBTREE_SCOPE;
+
+/**
+ * <p>This class provides a set of operations to manage LDAP trees.</p>
+ *
+ * @author Anil Saldhana
+ * @author <a href="mailto:psilva@redhat.com">Pedro Silva</a>
+ */
+public class LDAPOperationManager {
+
+    private static final Logger logger = Logger.getLogger(LDAPOperationManager.class);
+
+    private final LDAPIdentityStoreConfiguration config;
+    private final Map<String, Object> connectionProperties;
+
+    public LDAPOperationManager(LDAPIdentityStoreConfiguration config) throws NamingException {
+        this.config = config;
+        this.connectionProperties = Collections.unmodifiableMap(createConnectionProperties());
+    }
+
+    /**
+     * <p>
+     * Modifies the given {@link javax.naming.directory.Attribute} instance using the given DN. This method performs a REPLACE_ATTRIBUTE
+     * operation.
+     * </p>
+     *
+     * @param dn
+     * @param attribute
+     */
+    public void modifyAttribute(String dn, Attribute attribute) {
+        ModificationItem[] mods = new ModificationItem[]{new ModificationItem(DirContext.REPLACE_ATTRIBUTE, attribute)};
+        modifyAttributes(dn, mods);
+    }
+
+    /**
+     * <p>
+     * Modifies the given {@link Attribute} instances using the given DN. This method performs a REPLACE_ATTRIBUTE
+     * operation.
+     * </p>
+     *
+     * @param dn
+     * @param attributes
+     */
+    public void modifyAttributes(String dn,  NamingEnumeration<Attribute> attributes) {
+        try {
+            List<ModificationItem> modItems = new ArrayList<ModificationItem>();
+            while (attributes.hasMore()) {
+                ModificationItem modItem = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, attributes.next());
+                modItems.add(modItem);
+            }
+
+            modifyAttributes(dn, modItems.toArray(new ModificationItem[] {}));
+        } catch (NamingException ne) {
+            throw new ModelException("Could not modify attributes on entry from DN [" + dn + "]", ne);
+        }
+
+    }
+
+    /**
+     * <p>
+     * Removes the given {@link Attribute} instance using the given DN. This method performs a REMOVE_ATTRIBUTE
+     * operation.
+     * </p>
+     *
+     * @param dn
+     * @param attribute
+     */
+    public void removeAttribute(String dn, Attribute attribute) {
+        ModificationItem[] mods = new ModificationItem[]{new ModificationItem(DirContext.REMOVE_ATTRIBUTE, attribute)};
+        modifyAttributes(dn, mods);
+    }
+
+    /**
+     * <p>
+     * Adds the given {@link Attribute} instance using the given DN. This method performs a ADD_ATTRIBUTE operation.
+     * </p>
+     *
+     * @param dn
+     * @param attribute
+     */
+    public void addAttribute(String dn, Attribute attribute) {
+        ModificationItem[] mods = new ModificationItem[]{new ModificationItem(DirContext.ADD_ATTRIBUTE, attribute)};
+        modifyAttributes(dn, mods);
+    }
+
+    /**
+     * <p>
+     * Searches the LDAP tree.
+     * </p>
+     *
+     * @param baseDN
+     * @param id
+     *
+     * @return
+     */
+    public void removeEntryById(final String baseDN, final String id, final LDAPMappingConfiguration mappingConfiguration) {
+        final String filter = getFilterById(baseDN, id);
+
+        try {
+            final SearchControls cons = getSearchControls(mappingConfiguration);
+
+            execute(new LdapOperation<SearchResult>() {
+                @Override
+                public SearchResult execute(LdapContext context) throws NamingException {
+                    NamingEnumeration<SearchResult> result = context.search(baseDN, filter, cons);
+
+                    if (result.hasMore()) {
+                        SearchResult sr = result.next();
+                        if (logger.isDebugEnabled()) {
+                            logger.debugf("Removing entry [%s] with attributes: [", sr.getNameInNamespace());
+
+                            NamingEnumeration<? extends Attribute> all = sr.getAttributes().getAll();
+
+                            while (all.hasMore()) {
+                                Attribute attribute = all.next();
+
+                                logger.debugf("  %s = %s", attribute.getID(), attribute.get());
+                            }
+
+                            logger.debugf("]");
+                        }
+                        destroySubcontext(context, sr.getNameInNamespace());
+                    }
+
+                    result.close();
+
+                    return null;
+                }
+            });
+        } catch (NamingException e) {
+            throw new ModelException("Could not remove entry from DN [" + baseDN + "] and id [" + id + "]", e);
+        }
+    }
+
+    public List<SearchResult> search(final String baseDN, final String filter, LDAPMappingConfiguration mappingConfiguration) throws NamingException {
+        final List<SearchResult> result = new ArrayList<SearchResult>();
+        final SearchControls cons = getSearchControls(mappingConfiguration);
+
+        try {
+            return execute(new LdapOperation<List<SearchResult>>() {
+                @Override
+                public List<SearchResult> execute(LdapContext context) throws NamingException {
+                    NamingEnumeration<SearchResult> search = context.search(baseDN, filter, cons);
+
+                    while (search.hasMoreElements()) {
+                        result.add(search.nextElement());
+                    }
+
+                    search.close();
+
+                    return result;
+                }
+            });
+        } catch (NamingException e) {
+            logger.errorf(e, "Could not query server using DN [%s] and filter [%s]", baseDN, filter);
+            throw e;
+        }
+    }
+
+    public <V extends IdentityType> List<SearchResult> searchPaginated(final String baseDN, final String filter, LDAPMappingConfiguration mappingConfiguration, final IdentityQuery<V> identityQuery) throws NamingException {
+        final List<SearchResult> result = new ArrayList<SearchResult>();
+        final SearchControls cons = getSearchControls(mappingConfiguration);
+
+        try {
+            return execute(new LdapOperation<List<SearchResult>>() {
+                @Override
+                public List<SearchResult> execute(LdapContext context) throws NamingException {
+                    try {
+                        byte[] cookie = (byte[])identityQuery.getPaginationContext();
+                        PagedResultsControl pagedControls = new PagedResultsControl(identityQuery.getLimit(), cookie, Control.CRITICAL);
+                        context.setRequestControls(new Control[] { pagedControls });
+
+                        NamingEnumeration<SearchResult> search = context.search(baseDN, filter, cons);
+
+                        while (search.hasMoreElements()) {
+                            result.add(search.nextElement());
+                        }
+
+                        search.close();
+
+                        Control[] responseControls = context.getResponseControls();
+                        if (responseControls != null) {
+                            for (Control respControl : responseControls) {
+                                if (respControl instanceof PagedResultsResponseControl) {
+                                    PagedResultsResponseControl prrc = (PagedResultsResponseControl)respControl;
+                                    cookie = prrc.getCookie();
+                                    identityQuery.setPaginationContext(cookie);
+                                }
+                            }
+                        }
+
+                        return result;
+                    } catch (IOException ioe) {
+                        logger.errorf(ioe, "Could not query server with paginated query using DN [%s], filter [%s]", baseDN, filter);
+                        throw new NamingException(ioe.getMessage());
+                    }
+                }
+            });
+        } catch (NamingException e) {
+            logger.errorf(e, "Could not query server using DN [%s] and filter [%s]", baseDN, filter);
+            throw e;
+        }
+    }
+
+    private SearchControls getSearchControls(LDAPMappingConfiguration mappingConfiguration) {
+        final SearchControls cons = new SearchControls();
+
+        cons.setSearchScope(SUBTREE_SCOPE);
+        cons.setReturningObjFlag(false);
+
+        List<String> returningAttributes = getReturningAttributes(mappingConfiguration);
+
+        cons.setReturningAttributes(returningAttributes.toArray(new String[returningAttributes.size()]));
+        return cons;
+    }
+
+    public String getFilterById(String baseDN, String id) {
+        String filter = null;
+
+        if (this.config.isActiveDirectory()) {
+            final String strObjectGUID = "<GUID=" + id + ">";
+
+            try {
+                Attributes attributes = execute(new LdapOperation<Attributes>() {
+                    @Override
+                    public Attributes execute(LdapContext context) throws NamingException {
+                        return context.getAttributes(strObjectGUID);
+                    }
+                });
+
+                byte[] objectGUID = (byte[]) attributes.get(LDAPConstants.OBJECT_GUID).get();
+
+                filter = "(&(objectClass=*)(" + getUniqueIdentifierAttributeName() + LDAPConstants.EQUAL + LDAPUtil.convertObjectGUIToByteString(objectGUID) + "))";
+            } catch (NamingException ne) {
+                return filter;
+            }
+        }
+
+        if (filter == null) {
+            filter = "(&(objectClass=*)(" + getUniqueIdentifierAttributeName() + LDAPConstants.EQUAL + id + "))";
+        }
+
+        return filter;
+    }
+
+    public SearchResult lookupById(final String baseDN, final String id, final LDAPMappingConfiguration mappingConfiguration) {
+        final String filter = getFilterById(baseDN, id);
+
+        try {
+            final SearchControls cons = getSearchControls(mappingConfiguration);
+
+            return execute(new LdapOperation<SearchResult>() {
+                @Override
+                public SearchResult execute(LdapContext context) throws NamingException {
+                    NamingEnumeration<SearchResult> search = context.search(baseDN, filter, cons);
+
+                    try {
+                        if (search.hasMoreElements()) {
+                            return search.next();
+                        }
+                    } finally {
+                        if (search != null) {
+                            search.close();
+                        }
+                    }
+
+                    return null;
+                }
+            });
+        } catch (NamingException e) {
+            throw new ModelException("Could not query server using DN [" + baseDN + "] and filter [" + filter + "]", e);
+        }
+    }
+
+    /**
+     * <p>
+     * Destroys a subcontext with the given DN from the LDAP tree.
+     * </p>
+     *
+     * @param dn
+     */
+    private void destroySubcontext(LdapContext context, final String dn) {
+        try {
+            NamingEnumeration<Binding> enumeration = null;
+
+            try {
+                enumeration = context.listBindings(dn);
+
+                while (enumeration.hasMore()) {
+                    Binding binding = enumeration.next();
+                    String name = binding.getNameInNamespace();
+
+                    destroySubcontext(context, name);
+                }
+
+                context.unbind(dn);
+            } finally {
+                try {
+                    enumeration.close();
+                } catch (Exception e) {
+                }
+            }
+        } catch (Exception e) {
+            throw new ModelException("Could not unbind DN [" + dn + "]", e);
+        }
+    }
+
+    /**
+     * <p>
+     * Performs a simple authentication using the given DN and password to bind to the authentication context.
+     * </p>
+     *
+     * @param dn
+     * @param password
+     *
+     * @return
+     */
+    public boolean authenticate(String dn, String password) {
+        InitialContext authCtx = null;
+
+        try {
+            Hashtable<String, Object> env = new Hashtable<String, Object>(this.connectionProperties);
+
+            env.put(Context.SECURITY_PRINCIPAL, dn);
+            env.put(Context.SECURITY_CREDENTIALS, password);
+
+            // Never use connection pool to prevent password caching
+            env.put("com.sun.jndi.ldap.connect.pool", "false");
+
+            authCtx = new InitialLdapContext(env, null);
+
+            return true;
+        } catch (Exception e) {
+            if (logger.isDebugEnabled()) {
+                logger.debugf(e, "Authentication failed for DN [%s]", dn);
+            }
+
+            return false;
+        } finally {
+            if (authCtx != null) {
+                try {
+                    authCtx.close();
+                } catch (NamingException e) {
+
+                }
+            }
+        }
+    }
+
+    public void modifyAttributes(final String dn, final ModificationItem[] mods) {
+        try {
+            if (logger.isDebugEnabled()) {
+                logger.debugf("Modifying attributes for entry [%s]: [", dn);
+
+                for (ModificationItem item : mods) {
+                    Object values;
+
+                    if (item.getAttribute().size() > 0) {
+                        values = item.getAttribute().get();
+                    } else {
+                        values = "No values";
+                    }
+
+                    logger.debugf("  Op [%s]: %s = %s", item.getModificationOp(), item.getAttribute().getID(), values);
+                }
+
+                logger.debugf("]");
+            }
+
+            execute(new LdapOperation<Void>() {
+                @Override
+                public Void execute(LdapContext context) throws NamingException {
+                    context.modifyAttributes(dn, mods);
+                    return null;
+                }
+            });
+        } catch (NamingException e) {
+            throw new ModelException("Could not modify attribute for DN [" + dn + "]", e);
+        }
+    }
+
+    public void createSubContext(final String name, final Attributes attributes) {
+        try {
+            if (logger.isDebugEnabled()) {
+                logger.debugf("Creating entry [%s] with attributes: [", name);
+
+                NamingEnumeration<? extends Attribute> all = attributes.getAll();
+
+                while (all.hasMore()) {
+                    Attribute attribute = all.next();
+
+                    logger.debugf("  %s = %s", attribute.getID(), attribute.get());
+                }
+
+                logger.debugf("]");
+            }
+
+            execute(new LdapOperation<Void>() {
+                @Override
+                public Void execute(LdapContext context) throws NamingException {
+                    DirContext subcontext = context.createSubcontext(name, attributes);
+
+                    subcontext.close();
+
+                    return null;
+                }
+            });
+        } catch (NamingException e) {
+            throw new ModelException("Error creating subcontext [" + name + "]", e);
+        }
+    }
+
+    private String getUniqueIdentifierAttributeName() {
+        return this.config.getUniqueIdentifierAttributeName();
+    }
+
+    private NamingEnumeration<SearchResult> createEmptyEnumeration() {
+        return new NamingEnumeration<SearchResult>() {
+            @Override
+            public SearchResult next() throws NamingException {
+                return null;  //To change body of implemented methods use File | Settings | File Templates.
+            }
+
+            @Override
+            public boolean hasMore() throws NamingException {
+                return false;  //To change body of implemented methods use File | Settings | File Templates.
+            }
+
+            @Override
+            public void close() throws NamingException {
+                //To change body of implemented methods use File | Settings | File Templates.
+            }
+
+            @Override
+            public boolean hasMoreElements() {
+                return false;  //To change body of implemented methods use File | Settings | File Templates.
+            }
+
+            @Override
+            public SearchResult nextElement() {
+                return null;  //To change body of implemented methods use File | Settings | File Templates.
+            }
+        };
+    }
+
+    public Attributes getAttributes(final String entryUUID, final String baseDN, LDAPMappingConfiguration mappingConfiguration) {
+        SearchResult search = lookupById(baseDN, entryUUID, mappingConfiguration);
+
+        if (search == null) {
+            throw new ModelException("Couldn't find item with entryUUID [" + entryUUID + "] and baseDN [" + baseDN + "]");
+        }
+
+        return search.getAttributes();
+    }
+
+    public String decodeEntryUUID(final Object entryUUID) {
+        String id;
+
+        if (this.config.isActiveDirectory()) {
+            id = LDAPUtil.decodeObjectGUID((byte[]) entryUUID);
+        } else {
+            id = entryUUID.toString();
+        }
+
+        return id;
+    }
+
+    private LdapContext createLdapContext() throws NamingException {
+        return new InitialLdapContext(new Hashtable<Object, Object>(this.connectionProperties), null);
+    }
+
+    private Map<String, Object> createConnectionProperties() {
+        HashMap<String, Object> env = new HashMap<String, Object>();
+
+        env.put(Context.INITIAL_CONTEXT_FACTORY, this.config.getFactoryName());
+        env.put(Context.SECURITY_AUTHENTICATION, this.config.getAuthType());
+
+        String protocol = this.config.getProtocol();
+
+        if (protocol != null) {
+            env.put(Context.SECURITY_PROTOCOL, protocol);
+        }
+
+        String bindDN = this.config.getBindDN();
+
+        char[] bindCredential = null;
+
+        if (this.config.getBindCredential() != null) {
+            bindCredential = this.config.getBindCredential().toCharArray();
+        }
+
+        if (bindDN != null) {
+            env.put(Context.SECURITY_PRINCIPAL, bindDN);
+            env.put(Context.SECURITY_CREDENTIALS, bindCredential);
+        }
+
+        String url = this.config.getLdapURL();
+
+        if (url == null) {
+            throw new RuntimeException("url");
+        }
+
+        env.put(Context.PROVIDER_URL, url);
+
+        // Just dump the additional properties
+        Properties additionalProperties = this.config.getConnectionProperties();
+
+        if (additionalProperties != null) {
+            for (Object key : additionalProperties.keySet()) {
+                env.put(key.toString(), additionalProperties.getProperty(key.toString()));
+            }
+        }
+
+        if (config.isActiveDirectory()) {
+            env.put("java.naming.ldap.attributes.binary", LDAPConstants.OBJECT_GUID);
+        }
+
+        if (logger.isDebugEnabled()) {
+            logger.debugf("Creating LdapContext using properties: [%s]", env);
+        }
+
+        return env;
+    }
+
+    private <R> R execute(LdapOperation<R> operation) throws NamingException {
+        LdapContext context = null;
+
+        try {
+            context = createLdapContext();
+            return operation.execute(context);
+        } catch (NamingException ne) {
+            logger.error("Could not create Ldap context or operation execution error.", ne);
+            throw ne;
+        } finally {
+            if (context != null) {
+                try {
+                    context.close();
+                } catch (NamingException ne) {
+                    logger.error("Could not close Ldap context.", ne);
+                }
+            }
+        }
+    }
+
+    private interface LdapOperation<R> {
+        R execute(LdapContext context) throws NamingException;
+    }
+
+    private List<String> getReturningAttributes(final LDAPMappingConfiguration mappingConfiguration) {
+        List<String> returningAttributes = new ArrayList<String>();
+
+        if (mappingConfiguration != null) {
+            returningAttributes.addAll(mappingConfiguration.getMappedProperties().values());
+
+            returningAttributes.add(mappingConfiguration.getParentMembershipAttributeName());
+
+//            for (LDAPMappingConfiguration relationshipConfig : this.config.getRelationshipConfigs()) {
+//                if (relationshipConfig.getRelatedAttributedType().equals(mappingConfiguration.getMappedClass())) {
+//                    returningAttributes.addAll(relationshipConfig.getMappedProperties().values());
+//                }
+//            }
+        } else {
+            returningAttributes.add("*");
+        }
+
+        returningAttributes.add(getUniqueIdentifierAttributeName());
+        returningAttributes.add(LDAPConstants.CREATE_TIMESTAMP);
+        returningAttributes.add(LDAPConstants.OBJECT_CLASS);
+
+        return returningAttributes;
+    }
+}
\ No newline at end of file
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPUtil.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPUtil.java
new file mode 100644
index 0000000..f08ff85
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPUtil.java
@@ -0,0 +1,158 @@
+package org.keycloak.federation.ldap.idm.store.ldap;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.TimeZone;
+
+import org.keycloak.models.ModelException;
+
+/**
+ * <p>Utility class for working with LDAP.</p>
+ *
+ * @author Pedro Igor
+ */
+public class LDAPUtil {
+
+    /**
+     * <p>Formats the given date.</p>
+     *
+     * @param date The Date to format.
+     *
+     * @return A String representing the formatted date.
+     */
+    public static final String formatDate(Date date) {
+        if (date == null) {
+            throw new IllegalArgumentException("You must provide a date.");
+        }
+
+        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss'.0Z'");
+
+        dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+
+        return dateFormat.format(date);
+    }
+
+    /**
+     * <p>
+     * Parses dates/time stamps stored in LDAP. Some possible values:
+     * </p>
+     * <ul>
+     *     <li>20020228150820</li>
+     *     <li>20030228150820Z</li>
+     *     <li>20050228150820.12</li>
+     *     <li>20060711011740.0Z</li>
+     * </ul>
+     *
+     * @param date The date string to parse from.
+     *
+     * @return the Date.
+     */
+    public static final Date parseDate(String date) {
+        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
+
+        try {
+            if (date.endsWith("Z")) {
+                dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+            } else {
+                dateFormat.setTimeZone(TimeZone.getDefault());
+            }
+
+            return dateFormat.parse(date);
+        } catch (Exception e) {
+            throw new ModelException("Error converting ldap date.", e);
+        }
+    }
+
+
+
+    /**
+     * <p>Creates a byte-based {@link String} representation of a raw byte array representing the value of the
+     * <code>objectGUID</code> attribute retrieved from Active Directory.</p>
+     *
+     * <p>The returned string is useful to perform queries on AD based on the <code>objectGUID</code> value. Eg.:</p>
+     *
+     * <p>
+     * String filter = "(&(objectClass=*)(objectGUID" + EQUAL + convertObjectGUIToByteString(objectGUID) + "))";
+     * </p>
+     *
+     * @param objectGUID A raw byte array representing the value of the <code>objectGUID</code> attribute retrieved from
+     * Active Directory.
+     *
+     * @return A byte-based String representation in the form of \[0]\[1]\[2]\[3]\[4]\[5]\[6]\[7]\[8]\[9]\[10]\[11]\[12]\[13]\[14]\[15]
+     */
+    public static String convertObjectGUIToByteString(byte[] objectGUID) {
+        StringBuilder result = new StringBuilder();
+
+        for (int i = 0; i < objectGUID.length; i++) {
+            String transformed = prefixZeros((int) objectGUID[i] & 0xFF);
+            result.append("\\");
+            result.append(transformed);
+        }
+
+        return result.toString();
+    }
+
+    /**
+     * <p>Decode a raw byte array representing the value of the <code>objectGUID</code> attribute retrieved from Active
+     * Directory.</p>
+     *
+     * <p>The returned string is useful to directly bind an entry. Eg.:</p>
+     *
+     * <p>
+     * String bindingString = decodeObjectGUID(objectGUID);
+     * <br/>
+     * Attributes attributes = ctx.getAttributes(bindingString);
+     * </p>
+     *
+     * @param objectGUID A raw byte array representing the value of the <code>objectGUID</code> attribute retrieved from
+     * Active Directory.
+     *
+     * @return A string representing the decoded value in the form of [3][2][1][0]-[5][4]-[7][6]-[8][9]-[10][11][12][13][14][15].
+     */
+    public static String decodeObjectGUID(byte[] objectGUID) {
+        StringBuilder displayStr = new StringBuilder();
+
+        displayStr.append(convertToDashedString(objectGUID));
+
+        return displayStr.toString();
+    }
+
+    private static String convertToDashedString(byte[] objectGUID) {
+        StringBuilder displayStr = new StringBuilder();
+
+        displayStr.append(prefixZeros((int) objectGUID[3] & 0xFF));
+        displayStr.append(prefixZeros((int) objectGUID[2] & 0xFF));
+        displayStr.append(prefixZeros((int) objectGUID[1] & 0xFF));
+        displayStr.append(prefixZeros((int) objectGUID[0] & 0xFF));
+        displayStr.append("-");
+        displayStr.append(prefixZeros((int) objectGUID[5] & 0xFF));
+        displayStr.append(prefixZeros((int) objectGUID[4] & 0xFF));
+        displayStr.append("-");
+        displayStr.append(prefixZeros((int) objectGUID[7] & 0xFF));
+        displayStr.append(prefixZeros((int) objectGUID[6] & 0xFF));
+        displayStr.append("-");
+        displayStr.append(prefixZeros((int) objectGUID[8] & 0xFF));
+        displayStr.append(prefixZeros((int) objectGUID[9] & 0xFF));
+        displayStr.append("-");
+        displayStr.append(prefixZeros((int) objectGUID[10] & 0xFF));
+        displayStr.append(prefixZeros((int) objectGUID[11] & 0xFF));
+        displayStr.append(prefixZeros((int) objectGUID[12] & 0xFF));
+        displayStr.append(prefixZeros((int) objectGUID[13] & 0xFF));
+        displayStr.append(prefixZeros((int) objectGUID[14] & 0xFF));
+        displayStr.append(prefixZeros((int) objectGUID[15] & 0xFF));
+
+        return displayStr.toString();
+    }
+
+    private static String prefixZeros(int value) {
+        if (value <= 0xF) {
+            StringBuilder sb = new StringBuilder("0");
+            sb.append(Integer.toHexString(value));
+            return sb.toString();
+        } else {
+            return Integer.toHexString(value);
+        }
+    }
+
+
+}
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 f48880c..370e0f0 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<String> supportedCredentialTypes = new HashSet<String>();
 
-    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<UserModel> searchByAttributes(Map<String, String> attributes, RealmModel realm, int maxResults) {
         List<UserModel> searchResults =new LinkedList<UserModel>();
-        try {
-            Map<String, User> 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<String, LDAPUser> 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<String, User> searchPicketlink(Map<String, String> attributes, int maxResults) {
-        IdentityManager identityManager = getIdentityManager();
-        Map<String, User> results = new HashMap<String, User>();
+    protected Map<String, LDAPUser> searchLDAP(Map<String, String> attributes, int maxResults) {
+
+        Map<String, LDAPUser> results = new HashMap<String, LDAPUser>();
         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<User> query = identityManager.createIdentityQuery(User.class);
+            IdentityQueryBuilder queryBuilder = this.ldapIdentityStore.createQueryBuilder();
+            IdentityQuery<LDAPUser> 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<User> agents = query.getResultList();
-            for (User user : agents) {
+            List<LDAPUser> 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;
-            }
+        LDAPUser ldapUser = LDAPUtils.getUser(this.ldapIdentityStore, username);
+        if (ldapUser == null) {
+            return null;
+        }
 
-            return importUserFromPicketlink(realm, picketlinkUser);
-        } catch (IdentityManagementException ie) {
-            throw convertIDMException(ie);
+        // 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;
         }
-    }
 
-    public IdentityManager getIdentityManager() {
-        return partitionManager.createIdentityManager();
+        return importUserFromLDAP(realm, ldapUser);
     }
 
-    protected UserModel importUserFromPicketlink(RealmModel realm, User picketlinkUser) {
-        String email = (picketlinkUser.getEmail() != null && picketlinkUser.getEmail().trim().length() > 0) ? picketlinkUser.getEmail() : null;
+    protected UserModel importUserFromLDAP(RealmModel realm, LDAPUser ldapUser) {
+        String email = (ldapUser.getEmail() != null && ldapUser.getEmail().trim().length() > 0) ? ldapUser.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());
+        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, picketlinkUser.getLoginName());
+        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;
-            }
+        LDAPUser ldapUser = queryByEmail(email);
+        if (ldapUser == null) {
+            return null;
+        }
 
-            return importUserFromPicketlink(realm, picketlinkUser);
-        } catch (IdentityManagementException ie) {
-            throw convertIDMException(ie);
+        // 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<UserCredentialModel> 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<User> users, UserFederationProviderModel fedModel) {
-        for (User picketlinkUser : users) {
-            String username = picketlinkUser.getLoginName();
+    protected void importLDAPUsers(RealmModel realm, List<LDAPUser> 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 c197052..a498a93 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<User> userQuery = partitionMgr.createIdentityManager().createIdentityQuery(User.class);
+        LDAPIdentityStore ldapIdentityStore = this.ldapStoreRegistry.getLdapStore(model);
+        IdentityQuery<LDAPUser> 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<User> userQuery = queryBuilder.createIdentityQuery(User.class).where(condition);
+        IdentityQuery<LDAPUser> 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<User> userQuery, final String realmId, final UserFederationProviderModel fedModel) {
+    protected void syncImpl(KeycloakSessionFactory sessionFactory, IdentityQuery<LDAPUser> 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<User> users = userQuery.getResultList();
+                final List<LDAPUser> 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<User> users = userQuery.getResultList();
+            final List<LDAPUser> 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<User> users) {
+    protected void importLdapUsers(KeycloakSession session, String realmId, UserFederationProviderModel fedModel, List<LDAPUser> 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 0000000..22aa55a
--- /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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class LDAPIdentityStoreRegistry {
+
+    private static final Logger logger = Logger.getLogger(LDAPIdentityStoreRegistry.class);
+
+    private Map<String, LDAPIdentityStoreContext> ldapStores = new ConcurrentHashMap<String, LDAPIdentityStoreContext>();
+
+    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<String, String> 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<String, String> ldapConfig) {
+        Map<String, String> copy = new HashMap<String, String>(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<String,String> ldapConfig) {
+        Properties connectionProps = new Properties();
+        if (ldapConfig.containsKey(LDAPConstants.CONNECTION_POOLING)) {
+            connectionProps.put("com.sun.jndi.ldap.connect.pool", ldapConfig.get(LDAPConstants.CONNECTION_POOLING));
+        }
+
+        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<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);
+            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<String,String> ldapConfig) {
+        String objClassesCfg = ldapConfig.get(LDAPConstants.USER_OBJECT_CLASSES);
+        String objClassesStr = (objClassesCfg != null && objClassesCfg.length() > 0) ? objClassesCfg.trim() : "inetOrgPerson, organizationalPerson";
+
+        String[] objectClasses = objClassesStr.split(",");
+
+        // Trim them
+        String[] userObjectClasses = new String[objectClasses.length];
+        for (int i=0 ; i<objectClasses.length ; i++) {
+            userObjectClasses[i] = objectClasses[i].trim();
+        }
+        return userObjectClasses;
+    }
+
+    private class LDAPIdentityStoreContext {
+
+        private LDAPIdentityStoreContext(Map<String,String> config, LDAPIdentityStore store) {
+            this.config = config;
+            this.store = store;
+        }
+
+        private Map<String,String> 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 db0e9b8..9753592 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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
  */
@@ -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<String>("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 User getUser(PartitionManager partitionManager, String username) {
-        IdentityManager idmManager = getIdentityManager(partitionManager);
-        return BasicModel.getUser(idmManager, username);
+    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);
+        }
+        return ldapUser;
+    }
 
-    public static User getUserByEmail(IdentityManager idmManager, String email) throws IdentityManagementException {
-        List<User> agents = idmManager.createIdentityQuery(User.class)
-                .setParameter(User.EMAIL, email).getResultList();
 
-        if (agents.isEmpty()) {
+    public static LDAPUser getUserByEmail(LDAPIdentityStore ldapIdentityStore, String email) {
+        IdentityQueryBuilder queryBuilder = ldapIdentityStore.createQueryBuilder();
+        IdentityQuery<LDAPUser> query = queryBuilder.createIdentityQuery(LDAPUser.class)
+                .where(queryBuilder.equal(LDAPUser.EMAIL, email));
+        List<LDAPUser> users = query.getResultList();
+
+        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<User> users = idmManager.createIdentityQuery(User.class).getResultList();
+    public static void removeAllUsers(LDAPIdentityStore ldapIdentityStore) {
+        List<LDAPUser> allUsers = getAllUsers(ldapIdentityStore);
 
-        for (User user : users) {
-            idmManager.remove(user);
+        for (LDAPUser user : allUsers) {
+            ldapIdentityStore.remove(user);
         }
     }
 
-    public static List<User> 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<LDAPUser> getAllUsers(LDAPIdentityStore ldapIdentityStore) {
+        IdentityQuery<LDAPUser> 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 9a68f6a..4debf0e 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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -28,52 +23,43 @@ public class WritableLDAPUserModelDelegate extends UserModelDelegate implements 
 
     @Override
     public void setUsername(String username) {
-        IdentityManager identityManager = provider.getIdentityManager();
-
-        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);
+        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) {
-        IdentityManager identityManager = provider.getIdentityManager();
-
-        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);
+        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) {
-        IdentityManager identityManager = provider.getIdentityManager();
-
-        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);
+        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);
     }
 
@@ -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();
-
-        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);
+        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);
     }
 
diff --git a/model/api/src/main/java/org/keycloak/models/LDAPConstants.java b/model/api/src/main/java/org/keycloak/models/LDAPConstants.java
index bf76be7..acab3e0 100644
--- a/model/api/src/main/java/org/keycloak/models/LDAPConstants.java
+++ b/model/api/src/main/java/org/keycloak/models/LDAPConstants.java
@@ -29,5 +29,40 @@ public class LDAPConstants {
     public static final String BATCH_SIZE_FOR_SYNC = "batchSizeForSync";
     public static final int DEFAULT_BATCH_SIZE_FOR_SYNC = 1000;
 
+    // Config option to specify if registrations will be synced or not
+    public static final String SYNC_REGISTRATIONS = "syncRegistrations";
+
+    // Applicable just for active directory
     public static final String USER_ACCOUNT_CONTROLS_AFTER_PASSWORD_UPDATE = "userAccountControlsAfterPasswordUpdate";
+
+    // Custom attributes on UserModel, which is mapped to LDAP
+    public static final String LDAP_ID = "LDAP_ID";
+    public static final String LDAP_ENTRY_DN = "LDAP_ENTRY_DN";
+
+
+    // Those are forked from Picketlink
+    public static final String GIVENNAME = "givenname";
+    public static final String CN = "cn";
+    public static final String SN = "sn";
+    public static final String EMAIL = "mail";
+    public static final String MEMBER = "member";
+    public static final String MEMBER_OF = "memberOf";
+    public static final String OBJECT_CLASS = "objectclass";
+    public static final String UID = "uid";
+    public static final String USER_PASSWORD_ATTRIBUTE = "userpassword";
+    public static final String GROUP_OF_NAMES = "groupOfNames";
+    public static final String GROUP_OF_ENTRIES = "groupOfEntries";
+    public static final String GROUP_OF_UNIQUE_NAMES = "groupOfUniqueNames";
+
+    public static final String COMMA = ",";
+    public static final String EQUAL = "=";
+    public static final String SPACE_STRING = " ";
+
+    public static final String CUSTOM_ATTRIBUTE_ENABLED = "enabled";
+    public static final String CUSTOM_ATTRIBUTE_CREATE_DATE = "createDate";
+    public static final String CUSTOM_ATTRIBUTE_EXPIRY_DATE = "expiryDate";
+    public static final String ENTRY_UUID = "entryUUID";
+    public static final String OBJECT_GUID = "objectGUID";
+    public static final String CREATE_TIMESTAMP = "createTimeStamp";
+    public static final String MODIFY_TIMESTAMP = "modifyTimeStamp";
 }
diff --git a/model/api/src/main/java/org/keycloak/models/utils/reflection/NamedPropertyCriteria.java b/model/api/src/main/java/org/keycloak/models/utils/reflection/NamedPropertyCriteria.java
new file mode 100644
index 0000000..fc3b538
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/utils/reflection/NamedPropertyCriteria.java
@@ -0,0 +1,40 @@
+package org.keycloak.models.utils.reflection;
+
+import java.beans.Introspector;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+/**
+ * A criteria that matches a property based on name
+ *
+ * @see PropertyCriteria
+ */
+public class NamedPropertyCriteria implements PropertyCriteria {
+    private final String[] propertyNames;
+
+    public NamedPropertyCriteria(String... propertyNames) {
+        this.propertyNames = propertyNames;
+    }
+
+    public boolean fieldMatches(Field f) {
+        for (String propertyName : propertyNames) {
+            if (propertyName.equals(f.getName())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public boolean methodMatches(Method m) {
+        String[] validPrefix = {"get", "is"};
+        for (String propertyName : propertyNames) {
+            for (String prefix : validPrefix) {
+                if (m.getName().startsWith(prefix) &&
+                        Introspector.decapitalize(m.getName().substring(prefix.length())).equals(propertyName)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+}
diff --git a/model/api/src/main/java/org/keycloak/models/utils/reflection/TypedPropertyCriteria.java b/model/api/src/main/java/org/keycloak/models/utils/reflection/TypedPropertyCriteria.java
new file mode 100644
index 0000000..93688a4
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/utils/reflection/TypedPropertyCriteria.java
@@ -0,0 +1,71 @@
+package org.keycloak.models.utils.reflection;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+/**
+ * A criteria that matches a property based on its type
+ *
+ * @see PropertyCriteria
+ */
+public class TypedPropertyCriteria implements PropertyCriteria {
+
+    /**
+     * <p> Different options can be used to match a specific property based on its type. Regardless of the option
+     * chosen, if the property type equals the <code>propertyClass</code> it will be selected. <p/> <ul> <li>SUB_TYPE:
+     * Also consider properties where its type is a subtype of <code>propertyClass</code>. .</li> <li>SUPER_TYPE: Also
+     * consider properties where its type is a superclass or superinterface of <code>propertyClass</code>. .</li> </ul>
+     * </p>
+     */
+    public static enum MatchOption {
+        SUB_TYPE, SUPER_TYPE, ALL
+    }
+
+    private final Class<?> propertyClass;
+    private final MatchOption matchOption;
+
+    public TypedPropertyCriteria(Class<?> propertyClass) {
+        this(propertyClass, null);
+    }
+
+    public TypedPropertyCriteria(Class<?> propertyClass, MatchOption matchOption) {
+        if (propertyClass == null) {
+            throw new IllegalArgumentException("Property class can not be null.");
+        }
+        this.propertyClass = propertyClass;
+        this.matchOption = matchOption;
+    }
+
+    public boolean fieldMatches(Field f) {
+        return match(f.getType());
+    }
+
+    public boolean methodMatches(Method m) {
+        return match(m.getReturnType());
+    }
+
+    private boolean match(Class<?> type) {
+        if (propertyClass.equals(type)) {
+            return true;
+        } else {
+            boolean matchSubType = propertyClass.isAssignableFrom(type);
+
+            if (MatchOption.SUB_TYPE == this.matchOption) {
+                return matchSubType;
+            }
+
+            boolean matchSuperType = type.isAssignableFrom(propertyClass);
+
+            if (MatchOption.SUPER_TYPE == this.matchOption) {
+                return matchSuperType;
+            }
+
+            if (MatchOption.ALL == this.matchOption) {
+                return matchSubType || matchSuperType;
+            }
+        }
+
+        return false;
+    }
+}
+

pom.xml 1(+0 -1)

diff --git a/pom.xml b/pom.xml
index f3f68b0..87ac607 100755
--- a/pom.xml
+++ b/pom.xml
@@ -114,7 +114,6 @@
         <module>model</module>
         <module>integration</module>
         <module>proxy</module>
-        <module>picketlink</module>
         <module>federation</module>
         <module>services</module>
         <module>saml</module>

services/pom.xml 6(+0 -6)

diff --git a/services/pom.xml b/services/pom.xml
index e5fde97..ea6e90e 100755
--- a/services/pom.xml
+++ b/services/pom.xml
@@ -103,12 +103,6 @@
             <scope>provided</scope>
         </dependency>
         <dependency>
-            <groupId>org.keycloak</groupId>
-            <artifactId>keycloak-picketlink-api</artifactId>
-            <version>${project.version}</version>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
             <groupId>org.jboss.spec.javax.servlet</groupId>
             <artifactId>jboss-servlet-api_3.0_spec</artifactId>
             <scope>provided</scope>
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationProvidersIntegrationTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationProvidersIntegrationTest.java
index cfaae07..929029e 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationProvidersIntegrationTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationProvidersIntegrationTest.java
@@ -12,6 +12,8 @@ import org.keycloak.OAuth2Constants;
 import org.keycloak.federation.ldap.LDAPFederationProvider;
 import org.keycloak.federation.ldap.LDAPFederationProviderFactory;
 import org.keycloak.federation.ldap.LDAPUtils;
+import org.keycloak.federation.ldap.idm.model.LDAPUser;
+import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.LDAPConstants;
 import org.keycloak.models.ModelReadOnlyException;
@@ -21,7 +23,6 @@ import org.keycloak.models.UserCredentialValueModel;
 import org.keycloak.models.UserFederationProvider;
 import org.keycloak.models.UserFederationProviderModel;
 import org.keycloak.models.UserModel;
-import org.keycloak.picketlink.PartitionManagerProvider;
 import org.keycloak.representations.idm.CredentialRepresentation;
 import org.keycloak.services.managers.RealmManager;
 import org.keycloak.testsuite.OAuthClient;
@@ -35,8 +36,6 @@ import org.keycloak.testsuite.rule.LDAPRule;
 import org.keycloak.testsuite.rule.WebResource;
 import org.keycloak.testsuite.rule.WebRule;
 import org.openqa.selenium.WebDriver;
-import org.picketlink.idm.PartitionManager;
-import org.picketlink.idm.model.basic.User;
 
 import java.util.Map;
 
@@ -57,19 +56,19 @@ public class FederationProvidersIntegrationTest {
             addUser(manager.getSession(), appRealm, "mary", "mary@test.com", "password-app");
 
             Map<String,String> ldapConfig = ldapRule.getConfig();
-            ldapConfig.put(LDAPFederationProvider.SYNC_REGISTRATIONS, "true");
+            ldapConfig.put(LDAPConstants.SYNC_REGISTRATIONS, "true");
             ldapConfig.put(LDAPConstants.EDIT_MODE, UserFederationProvider.EditMode.WRITABLE.toString());
 
             ldapModel = appRealm.addUserFederationProvider(LDAPFederationProviderFactory.PROVIDER_NAME, ldapConfig, 0, "test-ldap", -1, -1, 0);
 
             // Delete all LDAP users and add some new for testing
-            PartitionManager partitionManager = getPartitionManager(manager.getSession(), ldapModel);
-            LDAPUtils.removeAllUsers(partitionManager);
+            LDAPIdentityStore ldapStore = getLdapIdentityStore(manager.getSession(), ldapModel);
+            LDAPUtils.removeAllUsers(ldapStore);
 
-            User john = LDAPUtils.addUser(partitionManager, "johnkeycloak", "John", "Doe", "john@email.org");
-            LDAPUtils.updatePassword(partitionManager, john, "Password1");
+            LDAPUser john = LDAPUtils.addUser(ldapStore, "johnkeycloak", "John", "Doe", "john@email.org");
+            LDAPUtils.updatePassword(ldapStore, john, "Password1");
 
-            User existing = LDAPUtils.addUser(partitionManager, "existing", "Existing", "Foo", "existing@email.org");
+            LDAPUser existing = LDAPUtils.addUser(ldapStore, "existing", "Existing", "Foo", "existing@email.org");
         }
     });
 
@@ -339,13 +338,13 @@ public class FederationProvidersIntegrationTest {
     @Test
     public void testSearch() {
         KeycloakSession session = keycloakRule.startSession();
-        PartitionManager partitionManager = getPartitionManager(session, ldapModel);
+        LDAPIdentityStore ldapStore = getLdapIdentityStore(session, ldapModel);
         try {
             RealmModel appRealm = session.realms().getRealmByName("test");
-            LDAPUtils.addUser(partitionManager, "username1", "John1", "Doel1", "user1@email.org");
-            LDAPUtils.addUser(partitionManager, "username2", "John2", "Doel2", "user2@email.org");
-            LDAPUtils.addUser(partitionManager, "username3", "John3", "Doel3", "user3@email.org");
-            LDAPUtils.addUser(partitionManager, "username4", "John4", "Doel4", "user4@email.org");
+            LDAPUtils.addUser(ldapStore, "username1", "John1", "Doel1", "user1@email.org");
+            LDAPUtils.addUser(ldapStore, "username2", "John2", "Doel2", "user2@email.org");
+            LDAPUtils.addUser(ldapStore, "username3", "John3", "Doel3", "user3@email.org");
+            LDAPUtils.addUser(ldapStore, "username4", "John4", "Doel4", "user4@email.org");
 
             // Users are not at local store at this moment
             Assert.assertNull(session.userStorage().getUserByUsername("username1", appRealm));
@@ -395,7 +394,7 @@ public class FederationProvidersIntegrationTest {
             Assert.assertTrue(session.users().validCredentials(appRealm, user, cred));
 
             // LDAP password is still unchanged
-            Assert.assertTrue(LDAPUtils.validatePassword(getPartitionManager(session, model), "johnkeycloak", "Password1"));
+            Assert.assertTrue(LDAPUtils.validatePassword(getLdapIdentityStore(session, model), user, "Password1"));
 
             // ATM it's not permitted to delete user in unsynced mode. Should be user deleted just locally instead?
             Assert.assertFalse(session.users().removeUser(appRealm, user));
@@ -412,9 +411,10 @@ public class FederationProvidersIntegrationTest {
         }
     }
 
-    static PartitionManager getPartitionManager(KeycloakSession keycloakSession, UserFederationProviderModel ldapFedModel) {
-        PartitionManagerProvider partitionManagerProvider = keycloakSession.getProvider(PartitionManagerProvider.class);
-        return partitionManagerProvider.getPartitionManager(ldapFedModel);
+    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();
     }
 
 }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/SyncProvidersTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/SyncProvidersTest.java
index 55f68bb..f628519 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/SyncProvidersTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/SyncProvidersTest.java
@@ -7,9 +7,10 @@ import org.junit.Test;
 import org.junit.rules.RuleChain;
 import org.junit.rules.TestRule;
 import org.junit.runners.MethodSorters;
-import org.keycloak.federation.ldap.LDAPFederationProvider;
 import org.keycloak.federation.ldap.LDAPFederationProviderFactory;
 import org.keycloak.federation.ldap.LDAPUtils;
+import org.keycloak.federation.ldap.idm.model.LDAPUser;
+import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakSessionFactory;
 import org.keycloak.models.LDAPConstants;
@@ -25,8 +26,6 @@ import org.keycloak.testsuite.rule.LDAPRule;
 import org.keycloak.testutils.DummyUserFederationProviderFactory;
 import org.keycloak.timer.TimerProvider;
 import org.keycloak.util.Time;
-import org.picketlink.idm.PartitionManager;
-import org.picketlink.idm.model.basic.User;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -50,26 +49,20 @@ public class SyncProvidersTest {
             Time.setOffset(0);
 
             Map<String,String> ldapConfig = ldapRule.getConfig();
-            ldapConfig.put(LDAPFederationProvider.SYNC_REGISTRATIONS, "false");
+            ldapConfig.put(LDAPConstants.SYNC_REGISTRATIONS, "false");
             ldapConfig.put(LDAPConstants.EDIT_MODE, UserFederationProvider.EditMode.UNSYNCED.toString());
 
             ldapModel = appRealm.addUserFederationProvider(LDAPFederationProviderFactory.PROVIDER_NAME, ldapConfig, 0, "test-ldap",
                     -1, -1, 0);
 
             // Delete all LDAP users and add 5 new users for testing
-            PartitionManager partitionManager = FederationProvidersIntegrationTest.getPartitionManager(manager.getSession(), ldapModel);
-            LDAPUtils.removeAllUsers(partitionManager);
-
-            User user1 = LDAPUtils.addUser(partitionManager, "user1", "User1FN", "User1LN", "user1@email.org");
-            LDAPUtils.updatePassword(partitionManager, user1, "Password1");
-            User user2 = LDAPUtils.addUser(partitionManager, "user2", "User2FN", "User2LN", "user2@email.org");
-            LDAPUtils.updatePassword(partitionManager, user2, "Password2");
-            User user3 = LDAPUtils.addUser(partitionManager, "user3", "User3FN", "User3LN", "user3@email.org");
-            LDAPUtils.updatePassword(partitionManager, user3, "Password3");
-            User user4 = LDAPUtils.addUser(partitionManager, "user4", "User4FN", "User4LN", "user4@email.org");
-            LDAPUtils.updatePassword(partitionManager, user4, "Password4");
-            User user5 = LDAPUtils.addUser(partitionManager, "user5", "User5FN", "User5LN", "user5@email.org");
-            LDAPUtils.updatePassword(partitionManager, user5, "Password5");
+            LDAPIdentityStore ldapStore = FederationProvidersIntegrationTest.getLdapIdentityStore(manager.getSession(), ldapModel);
+            LDAPUtils.removeAllUsers(ldapStore);
+
+            for (int i=1 ; i<6 ; i++) {
+                LDAPUser user = LDAPUtils.addUser(ldapStore, "user" + i, "User" + i + "FN", "User" + i + "LN", "user" + i + "@email.org");
+                LDAPUtils.updatePassword(ldapStore, user, "Password1");
+            }
 
             // Add dummy provider
             dummyModel = appRealm.addUserFederationProvider(DummyUserFederationProviderFactory.PROVIDER_NAME, new HashMap<String, String>(), 1, "test-dummy", -1, 1, 0);
@@ -122,9 +115,9 @@ public class SyncProvidersTest {
             sleep(1000);
 
             // Add user to LDAP and update 'user5' in LDAP
-            PartitionManager partitionManager = FederationProvidersIntegrationTest.getPartitionManager(session, ldapModel);
-            LDAPUtils.addUser(partitionManager, "user6", "User6FN", "User6LN", "user6@email.org");
-            LDAPUtils.updateUser(partitionManager, "user5", "User5FNUpdated", "User5LNUpdated", "user5Updated@email.org");
+            LDAPIdentityStore ldapStore = FederationProvidersIntegrationTest.getLdapIdentityStore(session, ldapModel);
+            LDAPUtils.addUser(ldapStore, "user6", "User6FN", "User6LN", "user6@email.org");
+            LDAPUtils.updateUser(ldapStore, "user5", "User5FNUpdated", "User5LNUpdated", "user5Updated@email.org");
 
             // Assert still old users in local provider
             assertUserImported(userProvider, testRealm, "user5", "User5FN", "User5LN", "user5@email.org");