keycloak-aplcache

Changes

federation/pom.xml 22(+22 -0)

pom.xml 1(+1 -0)

Details

diff --git a/authentication/authentication-picketlink/pom.xml b/authentication/authentication-picketlink/pom.xml
index 3ca1a10..37ae937 100755
--- a/authentication/authentication-picketlink/pom.xml
+++ b/authentication/authentication-picketlink/pom.xml
@@ -4,7 +4,7 @@
         <artifactId>keycloak-authentication-parent</artifactId>
         <groupId>org.keycloak</groupId>
         <version>1.0-beta-4-SNAPSHOT</version>
-        <relativePath>../pom.xml</relativePath>
+        <relativePath>../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
diff --git a/connections/jpa/src/main/resources/META-INF/persistence.xml b/connections/jpa/src/main/resources/META-INF/persistence.xml
index 276f6ba..7511ed2 100755
--- a/connections/jpa/src/main/resources/META-INF/persistence.xml
+++ b/connections/jpa/src/main/resources/META-INF/persistence.xml
@@ -9,6 +9,7 @@
         <class>org.keycloak.models.jpa.entities.RealmEntity</class>
         <class>org.keycloak.models.jpa.entities.RequiredCredentialEntity</class>
         <class>org.keycloak.models.jpa.entities.AuthenticationProviderEntity</class>
+        <class>org.keycloak.models.jpa.entities.FederationProviderEntity</class>
         <class>org.keycloak.models.jpa.entities.RoleEntity</class>
         <class>org.keycloak.models.jpa.entities.SocialLinkEntity</class>
         <class>org.keycloak.models.jpa.entities.AuthenticationLinkEntity</class>
diff --git a/core/src/main/java/org/keycloak/representations/FederationProviderRepresentation.java b/core/src/main/java/org/keycloak/representations/FederationProviderRepresentation.java
new file mode 100755
index 0000000..e14f96e
--- /dev/null
+++ b/core/src/main/java/org/keycloak/representations/FederationProviderRepresentation.java
@@ -0,0 +1,55 @@
+package org.keycloak.representations;
+
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class FederationProviderRepresentation {
+
+    private String id;
+    private String providerName;
+    private Map<String, String> config;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getProviderName() {
+        return providerName;
+    }
+
+    public void setProviderName(String providerName) {
+        this.providerName = providerName;
+    }
+
+
+    public Map<String, String> getConfig() {
+        return config;
+    }
+
+    public void setConfig(Map<String, String> config) {
+        this.config = config;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        FederationProviderRepresentation that = (FederationProviderRepresentation) o;
+
+        if (!id.equals(that.id)) return false;
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return id.hashCode();
+    }
+}
diff --git a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
index 5a65329..584e639 100755
--- a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
@@ -1,5 +1,7 @@
 package org.keycloak.representations.idm;
 
+import org.keycloak.representations.FederationProviderRepresentation;
+
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
@@ -56,6 +58,7 @@ public class RealmRepresentation {
     protected Map<String, String> smtpServer;
     protected Map<String, String> ldapServer;
     protected List<AuthenticationProviderRepresentation> authenticationProviders;
+    protected List<FederationProviderRepresentation> federationProviders;
     protected String loginTheme;
     protected String accountTheme;
     protected String adminTheme;
@@ -467,4 +470,12 @@ public class RealmRepresentation {
     public void setAuditListeners(List<String> auditListeners) {
         this.auditListeners = auditListeners;
     }
+
+    public List<FederationProviderRepresentation> getFederationProviders() {
+        return federationProviders;
+    }
+
+    public void setFederationProviders(List<FederationProviderRepresentation> federationProviders) {
+        this.federationProviders = federationProviders;
+    }
 }
diff --git a/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java
index abc7846..2578983 100755
--- a/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java
@@ -21,6 +21,7 @@ public class UserRepresentation {
     protected String lastName;
     protected String email;
     protected AuthenticationLinkRepresentation authenticationLink;
+    protected String federationLink;
     protected Map<String, String> attributes;
     protected List<CredentialRepresentation> credentials;
     protected List<String> requiredActions;
@@ -170,4 +171,12 @@ public class UserRepresentation {
     public void setApplicationRoles(Map<String, List<String>> applicationRoles) {
         this.applicationRoles = applicationRoles;
     }
+
+    public String getFederationLink() {
+        return federationLink;
+    }
+
+    public void setFederationLink(String federationLink) {
+        this.federationLink = federationLink;
+    }
 }
diff --git a/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ExportUtils.java b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ExportUtils.java
index dd3c5dd..4c1cc5b 100755
--- a/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ExportUtils.java
+++ b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ExportUtils.java
@@ -308,6 +308,7 @@ public class ExportUtils {
             credReps.add(credRep);
         }
         userRep.setCredentials(credReps);
+        userRep.setFederationLink(user.getFederationLink());
 
         return userRep;
     }
diff --git a/federation/ldap/pom.xml b/federation/ldap/pom.xml
new file mode 100755
index 0000000..5f8f887
--- /dev/null
+++ b/federation/ldap/pom.xml
@@ -0,0 +1,88 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>keycloak-authentication-parent</artifactId>
+        <groupId>org.keycloak</groupId>
+        <version>1.0-beta-4-SNAPSHOT</version>
+        <relativePath>../../pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>keycloak-ldap-federation</artifactId>
+    <name>Keycloak LDAP Federation</name>
+    <description />
+
+    <dependencies>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-core</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-model-api</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.resteasy</groupId>
+            <artifactId>resteasy-jaxrs</artifactId>
+            <scope>provided</scope>
+            <exclusions>
+                <exclusion>
+                    <groupId>log4j</groupId>
+                    <artifactId>log4j</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.slf4j</groupId>
+                    <artifactId>slf4j-api</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.slf4j</groupId>
+                    <artifactId>slf4j-simple</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.logging</groupId>
+            <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>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>${maven.compiler.source}</source>
+                    <target>${maven.compiler.target}</target>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/KeycloakLDAPIdentityStore.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/KeycloakLDAPIdentityStore.java
new file mode 100755
index 0000000..1ff6c11
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/KeycloakLDAPIdentityStore.java
@@ -0,0 +1,89 @@
+package org.keycloak.federation.ldap;
+
+import org.keycloak.models.utils.reflection.Reflections;
+import org.picketlink.idm.config.LDAPMappingConfiguration;
+import org.picketlink.idm.ldap.internal.LDAPIdentityStore;
+import org.picketlink.idm.ldap.internal.LDAPOperationManager;
+import org.picketlink.idm.model.AttributedType;
+import org.picketlink.idm.model.basic.User;
+import org.picketlink.idm.query.IdentityQuery;
+import org.picketlink.idm.spi.IdentityContext;
+
+import javax.naming.directory.BasicAttributes;
+import java.lang.reflect.Method;
+
+import static org.picketlink.common.constants.LDAPConstants.*;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class KeycloakLDAPIdentityStore extends LDAPIdentityStore {
+
+    public static Method GET_OPERATION_MANAGER_METHOD;
+    public static Method CREATE_SEARCH_FILTER_METHOD;
+    public static Method EXTRACT_ATTRIBUTES_METHOD;
+    public static Method GET_ENTRY_IDENTIFIER_METHOD;
+
+    public static final String SAM_ACCOUNT_NAME = "sAMAccountName";
+
+    static {
+        GET_OPERATION_MANAGER_METHOD = getMethodOnLDAPStore("getOperationManager");
+        CREATE_SEARCH_FILTER_METHOD = getMethodOnLDAPStore("createIdentityTypeSearchFilter", IdentityQuery.class, LDAPMappingConfiguration.class);
+        EXTRACT_ATTRIBUTES_METHOD = getMethodOnLDAPStore("extractAttributes", AttributedType.class, boolean.class);
+        GET_ENTRY_IDENTIFIER_METHOD = getMethodOnLDAPStore("getEntryIdentifier", AttributedType.class);
+    }
+
+    @Override
+    public void addAttributedType(IdentityContext context, AttributedType attributedType) {
+        LDAPMappingConfiguration userMappingConfig = getConfig().getMappingConfig(attributedType.getClass());
+        String ldapUsernameAttrName = userMappingConfig.getMappedProperties().get(userMappingConfig.getIdProperty().getName());
+
+        if (getConfig().isActiveDirectory() && SAM_ACCOUNT_NAME.equals(ldapUsernameAttrName)) {
+            // TODO: pain, but everything in picketlink is private... Improve if possible...
+            LDAPOperationManager operationManager = Reflections.invokeMethod(false, GET_OPERATION_MANAGER_METHOD, LDAPOperationManager.class, this);
+
+            String cn = getCommonName(attributedType);
+            String bindingDN = CN + EQUAL + cn + COMMA + userMappingConfig.getBaseDN();
+
+            BasicAttributes ldapAttributes = Reflections.invokeMethod(false, EXTRACT_ATTRIBUTES_METHOD, BasicAttributes.class, this, attributedType, true);
+            ldapAttributes.put(CN, cn);
+
+            operationManager.createSubContext(bindingDN, ldapAttributes);
+
+            String ldapId = Reflections.invokeMethod(false, GET_ENTRY_IDENTIFIER_METHOD, String.class, this, attributedType);
+            attributedType.setId(ldapId);
+        } else {
+            super.addAttributedType(context, attributedType);
+        }
+    }
+
+    // Hack as methods are protected on LDAPIdentityStore :/
+    public static Method getMethodOnLDAPStore(String methodName, Class... classes) {
+        try {
+            Method m = LDAPIdentityStore.class.getDeclaredMethod(methodName, classes);
+            m.setAccessible(true);
+            return m;
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    protected String getCommonName(AttributedType aType) {
+        User user = (User)aType;
+        String fullName;
+        if (user.getFirstName() != null && user.getLastName() != null) {
+            fullName = user.getFirstName() + " " + user.getLastName();
+        } else if (user.getFirstName() != null && user.getFirstName().trim().length() > 0) {
+            fullName = user.getFirstName();
+        } else {
+            fullName = user.getLastName();
+        }
+
+        // Fallback to loginName
+        if (fullName == null || fullName.trim().length() == 0) {
+            fullName = user.getLoginName();
+        }
+
+        return fullName;
+    }
+}
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
new file mode 100755
index 0000000..4215948
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java
@@ -0,0 +1,329 @@
+package org.keycloak.federation.ldap;
+
+import org.jboss.logging.Logger;
+import org.keycloak.models.FederationProvider;
+import org.keycloak.models.FederationProviderModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ModelException;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.SocialLinkModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserModel;
+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.TOTPCredential;
+import org.picketlink.idm.credential.UsernamePasswordCredentials;
+import org.picketlink.idm.model.basic.BasicModel;
+import org.picketlink.idm.model.basic.User;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static org.picketlink.common.util.StringUtil.isNullOrEmpty;
+import static org.picketlink.idm.IDMMessages.MESSAGES;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class LDAPFederationProvider implements FederationProvider {
+    private static final Logger logger = Logger.getLogger(LDAPFederationProvider.class);
+
+    protected KeycloakSession session;
+    protected FederationProviderModel model;
+    protected PartitionManager partitionManager;
+
+    protected static final Set<String> supportedCredentialTypes = new HashSet<String>();
+
+    static
+    {
+        supportedCredentialTypes.add(UserCredentialModel.PASSWORD);
+    }
+
+    public LDAPFederationProvider(KeycloakSession session, FederationProviderModel model, PartitionManager partitionManager) {
+        this.session = session;
+        this.model = model;
+        this.partitionManager = partitionManager;
+    }
+
+    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;
+    }
+
+    public FederationProviderModel getModel() {
+        return model;
+    }
+
+    public PartitionManager getPartitionManager() {
+        return partitionManager;
+    }
+
+    @Override
+    public UserModel proxy(UserModel local) {
+        // todo
+        return new LDAPUserModelDelegate(local, this);
+    }
+
+    @Override
+    public Set<String> getSupportedCredentialTypes() {
+        return supportedCredentialTypes;
+    }
+
+    @Override
+    public UserModel addUser(RealmModel realm, UserModel user) {
+        IdentityManager identityManager = getIdentityManager();
+
+        try {
+            User picketlinkUser = new User(user.getUsername());
+            picketlinkUser.setFirstName(user.getFirstName());
+            picketlinkUser.setLastName(user.getLastName());
+            picketlinkUser.setEmail(user.getEmail());
+            identityManager.add(picketlinkUser);
+            return proxy(user);
+        } catch (IdentityManagementException ie) {
+            throw convertIDMException(ie);
+        }
+    }
+
+    @Override
+    public boolean removeUser(RealmModel realm, UserModel user) {
+        IdentityManager identityManager = getIdentityManager();
+
+        try {
+            User picketlinkUser = BasicModel.getUser(identityManager, user.getUsername());
+            if (picketlinkUser == null) {
+                return false;
+            }
+            identityManager.remove(picketlinkUser);
+            return true;
+        } catch (IdentityManagementException ie) {
+            throw convertIDMException(ie);
+        }
+    }
+
+    @Override
+    public String getAdminPage() {
+        return null;
+    }
+
+    @Override
+    public Class getAdminClass() {
+        return null;  //To change body of implemented methods use File | Settings | File Templates.
+    }
+
+    @Override
+    public UserModel addUser(RealmModel realm, String id, String username, boolean addDefaultRoles) {
+        throw new IllegalAccessError("Not allowed to call this");
+    }
+
+    @Override
+    public UserModel addUser(RealmModel realm, String username) {
+        throw new IllegalAccessError("Not allowed to call this");
+    }
+
+    @Override
+    public boolean removeUser(RealmModel realm, String name) {
+        throw new IllegalAccessError("Not allowed to call this");
+    }
+
+    @Override
+    public void addSocialLink(RealmModel realm, UserModel user, SocialLinkModel socialLink) {
+        session.userStorage().addSocialLink(realm, user, socialLink);
+    }
+
+    @Override
+    public boolean removeSocialLink(RealmModel realm, UserModel user, String socialProvider) {
+        return session.userStorage().removeSocialLink(realm, user, socialProvider);
+    }
+
+    @Override
+    public UserModel getUserById(String id, RealmModel realm) {
+        return null;
+    }
+
+
+    @Override
+    public UserModel getUserByUsername(String username, RealmModel realm) {
+        IdentityManager identityManager = getIdentityManager();
+
+        try {
+            User picketlinkUser = BasicModel.getUser(identityManager, username);
+            if (picketlinkUser == null) {
+                return null;
+            }
+
+            return importUserFromPicketlink(realm, picketlinkUser);
+        } catch (IdentityManagementException ie) {
+            throw convertIDMException(ie);
+        }
+    }
+
+    public IdentityManager getIdentityManager() {
+        return partitionManager.createIdentityManager();
+    }
+
+    protected UserModel importUserFromPicketlink(RealmModel realm, User picketlinkUser) {
+        String email = (picketlinkUser.getEmail() != null && picketlinkUser.getEmail().trim().length() > 0) ? picketlinkUser.getEmail() : null;
+        UserModel imported = session.userStorage().addUser(realm, picketlinkUser.getLoginName());
+        imported.setEnabled(true);
+        imported.setEmail(email);
+        imported.setFirstName(picketlinkUser.getFirstName());
+        imported.setLastName(picketlinkUser.getLastName());
+        imported.setFederationLink(model.getId());
+        return proxy(imported);
+    }
+
+    protected User queryByEmail(IdentityManager identityManager, String email) throws IdentityManagementException {
+        List<User> agents = identityManager.createIdentityQuery(User.class)
+                .setParameter(User.EMAIL, email).getResultList();
+
+        if (agents.isEmpty()) {
+            return null;
+        } else if (agents.size() == 1) {
+            return agents.get(0);
+        } else {
+            throw new IdentityManagementException("Error - multiple Agent objects found with same login name");
+        }
+    }
+
+
+    @Override
+    public UserModel getUserByEmail(String email, RealmModel realm) {
+        IdentityManager identityManager = getIdentityManager();
+
+        try {
+            User picketlinkUser = queryByEmail(identityManager, email);
+            if (picketlinkUser == null) {
+                return null;
+            }
+
+            return importUserFromPicketlink(realm, picketlinkUser);
+        } catch (IdentityManagementException ie) {
+            throw convertIDMException(ie);
+        }
+    }
+
+    @Override
+    public UserModel getUserBySocialLink(SocialLinkModel socialLink, RealmModel realm) {
+        return null;
+    }
+
+    @Override
+    public List<UserModel> getUsers(RealmModel realm) {
+        return null;  //To change body of implemented methods use File | Settings | File Templates.
+    }
+
+    @Override
+    public int getUsersCount(RealmModel realm) {
+        return -1;
+    }
+
+    @Override
+    public List<UserModel> getUsers(RealmModel realm, int firstResult, int maxResults) {
+        return null;  //To change body of implemented methods use File | Settings | File Templates.
+    }
+
+    @Override
+    public List<UserModel> searchForUser(String search, RealmModel realm) {
+        return null;  //To change body of implemented methods use File | Settings | File Templates.
+    }
+
+    @Override
+    public List<UserModel> searchForUser(String search, RealmModel realm, int firstResult, int maxResults) {
+        return null;  //To change body of implemented methods use File | Settings | File Templates.
+    }
+
+    @Override
+    public List<UserModel> searchForUserByAttributes(Map<String, String> attributes, RealmModel realm) {
+        return null;  //To change body of implemented methods use File | Settings | File Templates.
+    }
+
+    @Override
+    public List<UserModel> searchForUserByAttributes(Map<String, String> attributes, RealmModel realm, int firstResult, int maxResults) {
+        return null;  //To change body of implemented methods use File | Settings | File Templates.
+    }
+
+    @Override
+    public Set<SocialLinkModel> getSocialLinks(UserModel user, RealmModel realm) {
+        return null;  //To change body of implemented methods use File | Settings | File Templates.
+    }
+
+    @Override
+    public SocialLinkModel getSocialLink(UserModel user, String socialProvider, RealmModel realm) {
+        return null;  //To change body of implemented methods use File | Settings | File Templates.
+    }
+
+    @Override
+    public void preRemove(RealmModel realm) {
+        //To change body of implemented methods use File | Settings | File Templates.
+    }
+
+    @Override
+    public void preRemove(RealmModel realm, RoleModel role) {
+        //To change body of implemented methods use File | Settings | File Templates.
+    }
+
+    public boolean validPassword(String username, String password) {
+        IdentityManager identityManager = getIdentityManager();
+
+        try {
+            UsernamePasswordCredentials credential = new UsernamePasswordCredentials();
+            credential.setUsername(username);
+            credential.setPassword(new Password(password.toCharArray()));
+            identityManager.validateCredentials(credential);
+            if (credential.getStatus() == Credentials.Status.VALID) {
+                return true;
+            } else {
+                return false;
+            }
+        } catch (IdentityManagementException ie) {
+            throw convertIDMException(ie);
+        }
+    }
+
+
+    @Override
+    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());
+            } else {
+                return false; // invalid cred type
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public boolean validCredentials(RealmModel realm, UserModel user, UserCredentialModel... input) {
+        for (UserCredentialModel cred : input) {
+            if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
+                return validPassword(user.getUsername(), cred.getValue());
+            } else {
+                return false; // invalid cred type
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public void close() {
+        //To change body of implemented methods use File | Settings | File Templates.
+    }
+}
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
new file mode 100755
index 0000000..6a0c57d
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProviderFactory.java
@@ -0,0 +1,47 @@
+package org.keycloak.federation.ldap;
+
+import org.keycloak.Config;
+import org.keycloak.models.FederationProvider;
+import org.keycloak.models.FederationProviderFactory;
+import org.keycloak.models.FederationProviderModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.picketlink.idm.IdentityManager;
+import org.picketlink.idm.PartitionManager;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class LDAPFederationProviderFactory implements FederationProviderFactory {
+    public static final String PROVIDER_NAME = "ldap";
+    PartitionManagerRegistry registry;
+
+    @Override
+    public FederationProvider create(KeycloakSession session) {
+        throw new IllegalAccessError("Illegal to call this method");
+    }
+
+    @Override
+    public FederationProvider getInstance(KeycloakSession session, FederationProviderModel model) {
+        PartitionManager partition = registry.getPartitionManager(model);
+        return new LDAPFederationProvider(session, model, partition);
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+        registry = new PartitionManagerRegistry();
+    }
+
+    @Override
+    public void close() {
+
+    }
+
+    @Override
+    public String getId() {
+        return PROVIDER_NAME;
+    }
+}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPKeycloakCredentialHandler.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPKeycloakCredentialHandler.java
new file mode 100755
index 0000000..7078d87
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPKeycloakCredentialHandler.java
@@ -0,0 +1,168 @@
+package org.keycloak.federation.ldap;
+
+import org.keycloak.models.utils.reflection.Reflections;
+import org.picketlink.idm.IdentityManager;
+import org.picketlink.idm.config.LDAPMappingConfiguration;
+import org.picketlink.idm.credential.Credentials;
+import org.picketlink.idm.credential.Password;
+import org.picketlink.idm.credential.UsernamePasswordCredentials;
+import org.picketlink.idm.ldap.internal.LDAPIdentityStore;
+import org.picketlink.idm.ldap.internal.LDAPOperationManager;
+import org.picketlink.idm.ldap.internal.LDAPPlainTextPasswordCredentialHandler;
+import org.picketlink.idm.model.Account;
+import org.picketlink.idm.model.basic.User;
+import org.picketlink.idm.query.IdentityQuery;
+import org.picketlink.idm.spi.IdentityContext;
+
+import javax.naming.NamingException;
+import javax.naming.directory.BasicAttribute;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.ModificationItem;
+import javax.naming.directory.SearchResult;
+import java.io.UnsupportedEncodingException;
+import java.util.Date;
+import java.util.List;
+
+import static org.picketlink.idm.IDMLog.CREDENTIAL_LOGGER;
+import static org.picketlink.idm.model.basic.BasicModel.getUser;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class LDAPKeycloakCredentialHandler extends LDAPPlainTextPasswordCredentialHandler {
+
+    // Used just 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
+    private String userAccountControlAfterPasswordUpdate;
+
+    @Override
+    public void setup(LDAPIdentityStore store) {
+        // TODO: Don't setup it here once PLINK-508 is fixed
+        if (store.getConfig().isActiveDirectory() || Boolean.getBoolean("keycloak.ldap.ad.skipUserAccountControlAfterPasswordUpdate")) {
+            String userAccountControlProp = System.getProperty("keycloak.ldap.ad.userAccountControlAfterPasswordUpdate");
+            this.userAccountControlAfterPasswordUpdate = userAccountControlProp!=null ? userAccountControlProp : "512";
+            CREDENTIAL_LOGGER.info("Will use userAccountControl=" + userAccountControlAfterPasswordUpdate + " after password update of user in Active Directory");
+        }
+    }
+
+    // Overridden as in Keycloak, we don't have Agents
+    @Override
+    protected User getAccount(IdentityContext context, String loginName) {
+        IdentityManager identityManager = getIdentityManager(context);
+
+        if (CREDENTIAL_LOGGER.isDebugEnabled()) {
+            CREDENTIAL_LOGGER.debugf("Trying to find account [%s] using default account type [%s]", loginName, User.class);
+        }
+
+        return getUser(identityManager, loginName);
+    }
+
+    @Override
+    public void update(IdentityContext context, Account account, Password password, LDAPIdentityStore store, Date effectiveDate, Date expiryDate) {
+        if (!store.getConfig().isActiveDirectory()) {
+            super.update(context, account, password, store, effectiveDate, expiryDate);
+        } else {
+            User user = (User)account;
+            LDAPOperationManager operationManager = Reflections.invokeMethod(false, KeycloakLDAPIdentityStore.GET_OPERATION_MANAGER_METHOD, LDAPOperationManager.class, store);
+            IdentityManager identityManager = getIdentityManager(context);
+            String userDN = getDNOfUser(user, identityManager, store, operationManager);
+
+            updateADPassword(userDN, new String(password.getValue()), operationManager);
+
+            if (userAccountControlAfterPasswordUpdate != null) {
+                ModificationItem[] mods = new ModificationItem[1];
+                BasicAttribute mod0 = new BasicAttribute("userAccountControl", userAccountControlAfterPasswordUpdate);
+                mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, mod0);
+                operationManager.modifyAttribute(userDN, mod0);
+            }
+        }
+    }
+
+    protected void updateADPassword(String userDN, String password, LDAPOperationManager operationManager) {
+        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);
+            operationManager.modifyAttribute(userDN, unicodePwd);
+        } catch (UnsupportedEncodingException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public void validate(IdentityContext context, UsernamePasswordCredentials credentials,
+                         LDAPIdentityStore store) {
+        credentials.setStatus(Credentials.Status.INVALID);
+        credentials.setValidatedAccount(null);
+
+        if (CREDENTIAL_LOGGER.isDebugEnabled()) {
+            CREDENTIAL_LOGGER.debugf("Validating credentials [%s][%s] using identity store [%s] and credential handler [%s].", credentials.getClass(), credentials, store, this);
+        }
+
+        User account = getAccount(context, credentials.getUsername());
+
+        // If the user for the provided username cannot be found we fail validation
+        if (account != null) {
+            if (CREDENTIAL_LOGGER.isDebugEnabled()) {
+                CREDENTIAL_LOGGER.debugf("Found account [%s] from credentials [%s].", account, credentials);
+            }
+
+            if (account.isEnabled()) {
+                if (CREDENTIAL_LOGGER.isDebugEnabled()) {
+                    CREDENTIAL_LOGGER.debugf("Account [%s] is ENABLED.", account, credentials);
+                }
+
+                char[] password = credentials.getPassword().getValue();
+
+                // String bindingDN = store.getBindingDN(account);
+
+                LDAPOperationManager operationManager = Reflections.invokeMethod(false, KeycloakLDAPIdentityStore.GET_OPERATION_MANAGER_METHOD, LDAPOperationManager.class, store);
+                String bindingDN = getDNOfUser(account, getIdentityManager(context), store, operationManager);
+
+                if (operationManager.authenticate(bindingDN, new String(password))) {
+                    credentials.setValidatedAccount(account);
+                    credentials.setStatus(Credentials.Status.VALID);
+                }
+            } else {
+                if (CREDENTIAL_LOGGER.isDebugEnabled()) {
+                    CREDENTIAL_LOGGER.debugf("Account [%s] is DISABLED.", account, credentials);
+                }
+                credentials.setStatus(Credentials.Status.ACCOUNT_DISABLED);
+            }
+        } else {
+            if (CREDENTIAL_LOGGER.isDebugEnabled()) {
+                CREDENTIAL_LOGGER.debugf("Account NOT FOUND for credentials [%s][%s].", credentials.getClass(), credentials);
+            }
+        }
+
+        if (CREDENTIAL_LOGGER.isDebugEnabled()) {
+            CREDENTIAL_LOGGER.debugf("Credential [%s][%s] validated using identity store [%s] and credential handler [%s]. Status [%s]. Validated Account [%s]",
+                    credentials.getClass(), credentials, store, this, credentials.getStatus(), credentials.getValidatedAccount());
+        }
+    }
+
+    // TODO: remove later... It's needed just because LDAPIdentityStore.getBindingDN, which always uses idProperty as first part of DN, but in AD it doesn't work as we may have idProperty 'sAMAccountName'
+    // but DN like: cn=John Doe,OU=foo,DC=bar
+    protected String getDNOfUser(User user, IdentityManager identityManager, LDAPIdentityStore ldapStore, LDAPOperationManager operationManager) {
+
+        LDAPMappingConfiguration ldapEntryConfig = ldapStore.getConfig().getMappingConfig(User.class);
+        IdentityQuery<User> identityQuery = identityManager.createIdentityQuery(User.class)
+                .setParameter(User.LOGIN_NAME, user.getLoginName());
+        StringBuilder filter = Reflections.invokeMethod(false, KeycloakLDAPIdentityStore.CREATE_SEARCH_FILTER_METHOD, StringBuilder.class, ldapStore, identityQuery, ldapEntryConfig);
+
+        List<SearchResult> search = null;
+        try {
+            search = operationManager.search(ldapEntryConfig.getBaseDN(), filter.toString(), ldapEntryConfig);
+        } catch (NamingException ne) {
+            throw new RuntimeException(ne);
+        }
+
+        if (search.size() > 0) {
+            SearchResult sr1 = search.get(0);
+            return sr1.getNameInNamespace();
+        } else {
+            return null;
+        }
+    }
+}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPUserModelDelegate.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPUserModelDelegate.java
new file mode 100755
index 0000000..a2936d5
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPUserModelDelegate.java
@@ -0,0 +1,270 @@
+package org.keycloak.federation.ldap;
+
+import org.jboss.logging.Logger;
+import org.keycloak.models.ApplicationModel;
+import org.keycloak.models.AuthenticationLinkModel;
+import org.keycloak.models.FederationProviderModel;
+import org.keycloak.models.ModelException;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserCredentialValueModel;
+import org.keycloak.models.UserModel;
+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;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class LDAPUserModelDelegate implements UserModel {
+    private static final Logger logger = Logger.getLogger(LDAPUserModelDelegate.class);
+
+    protected UserModel delegate;
+    protected LDAPFederationProvider provider;
+
+    public LDAPUserModelDelegate(UserModel delegate, LDAPFederationProvider provider) {
+        this.delegate = delegate;
+        this.provider = provider;
+    }
+
+    @Override
+    public String getId() {
+        return delegate.getId();
+    }
+
+    @Override
+    public void setAttribute(String name, String value) {
+        delegate.setAttribute(name, value);
+    }
+
+    @Override
+    public boolean isEmailVerified() {
+        return delegate.isEmailVerified();
+    }
+
+    @Override
+    public void removeAttribute(String name) {
+        delegate.removeAttribute(name);
+    }
+
+    @Override
+    public String getLastName() {
+        return delegate.getLastName();
+    }
+
+    @Override
+    public void setFederationLink(String link) {
+        delegate.setFederationLink(link);
+    }
+
+    @Override
+    public AuthenticationLinkModel getAuthenticationLink() {
+        return delegate.getAuthenticationLink();
+    }
+
+    @Override
+    public Map<String, String> getAttributes() {
+        return delegate.getAttributes();
+    }
+
+    @Override
+    public boolean hasRole(RoleModel role) {
+        return delegate.hasRole(role);
+    }
+
+    @Override
+    public void grantRole(RoleModel role) {
+        delegate.grantRole(role);
+    }
+
+    @Override
+    public void setEnabled(boolean enabled) {
+        delegate.setEnabled(enabled);
+    }
+
+    @Override
+    public void removeRequiredAction(UserModel.RequiredAction action) {
+        delegate.removeRequiredAction(action);
+    }
+
+    @Override
+    public void deleteRoleMapping(RoleModel role) {
+        delegate.deleteRoleMapping(role);
+    }
+
+    @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);
+        }
+        delegate.setUsername(username);
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return delegate.isEnabled();
+    }
+
+    @Override
+    public String getFirstName() {
+        return delegate.getFirstName();
+    }
+
+    @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);
+        }
+        delegate.setLastName(lastName);
+    }
+
+    @Override
+    public void setEmailVerified(boolean verified) {
+        delegate.setEmailVerified(verified);
+    }
+
+    @Override
+    public void updateCredential(UserCredentialModel cred) {
+        if (!provider.getSupportedCredentialTypes().contains(cred.getType())) {
+            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);
+        }
+
+    }
+
+    @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);
+        }
+        delegate.setEmail(email);
+    }
+
+    @Override
+    public void addRequiredAction(UserModel.RequiredAction action) {
+        delegate.addRequiredAction(action);
+    }
+
+    @Override
+    public List<UserCredentialValueModel> getCredentialsDirectly() {
+        return delegate.getCredentialsDirectly();
+    }
+
+    @Override
+    public boolean isTotp() {
+        return delegate.isTotp();
+    }
+
+    @Override
+    public void setFirstName(String firstName) {
+        delegate.setFirstName(firstName);
+    }
+
+    @Override
+    public Set<UserModel.RequiredAction> getRequiredActions() {
+        return delegate.getRequiredActions();
+    }
+
+    @Override
+    public String getEmail() {
+        return delegate.getEmail();
+    }
+
+    @Override
+    public void setTotp(boolean totp) {
+        delegate.setTotp(totp);
+    }
+
+    @Override
+    public void setAuthenticationLink(AuthenticationLinkModel authenticationLink) {
+        delegate.setAuthenticationLink(authenticationLink);
+    }
+
+    @Override
+    public String getUsername() {
+        return delegate.getUsername();
+    }
+
+    @Override
+    public String getFederationLink() {
+        return delegate.getFederationLink();
+    }
+
+    @Override
+    public Set<RoleModel> getRealmRoleMappings() {
+        return delegate.getRealmRoleMappings();
+    }
+
+    @Override
+    public Set<RoleModel> getRoleMappings() {
+        return delegate.getRoleMappings();
+    }
+
+    @Override
+    public Set<RoleModel> getApplicationRoleMappings(ApplicationModel app) {
+        return delegate.getApplicationRoleMappings(app);
+    }
+
+    @Override
+    public String getAttribute(String name) {
+        return delegate.getAttribute(name);
+    }
+
+    @Override
+    public void updateCredentialDirectly(UserCredentialValueModel cred) {
+        delegate.updateCredentialDirectly(cred);
+    }
+}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/PartitionManagerRegistry.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/PartitionManagerRegistry.java
new file mode 100755
index 0000000..860fb2e
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/PartitionManagerRegistry.java
@@ -0,0 +1,160 @@
+package org.keycloak.federation.ldap;
+
+import org.jboss.logging.Logger;
+import org.keycloak.models.FederationProviderModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.LDAPConstants;
+import org.keycloak.models.RealmModel;
+import org.picketlink.idm.PartitionManager;
+import org.picketlink.idm.config.AbstractIdentityStoreConfiguration;
+import org.picketlink.idm.config.IdentityConfiguration;
+import org.picketlink.idm.config.IdentityConfigurationBuilder;
+import org.picketlink.idm.config.IdentityStoreConfiguration;
+import org.picketlink.idm.config.LDAPIdentityStoreConfiguration;
+import org.picketlink.idm.internal.DefaultPartitionManager;
+import org.picketlink.idm.model.basic.User;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.ConcurrentHashMap;
+
+import static org.picketlink.common.constants.LDAPConstants.*;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class PartitionManagerRegistry {
+
+    private static final Logger logger = Logger.getLogger(PartitionManagerRegistry.class);
+
+    private Map<String, PartitionManagerContext> partitionManagers = new ConcurrentHashMap<String, PartitionManagerContext>();
+
+    public PartitionManager getPartitionManager(FederationProviderModel model) {
+        PartitionManagerContext context = partitionManagers.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)) {
+            logger.infof("Creating new partition manager for the federation provider: %s, LDAP Connection URL: %s, LDAP Base DN: %s, LDAP Vendor: %s", model.getId(),
+                    config.get(LDAPConstants.CONNECTION_URL), config.get(LDAPConstants.BASE_DN), config.get(LDAPConstants.VENDOR));
+            PartitionManager manager = createPartitionManager(config);
+            context = new PartitionManagerContext(config, manager);
+            partitionManagers.put(model.getId(), context);
+        }
+        return context.partitionManager;
+    }
+
+    /**
+     * @param ldapConfig from realm
+     * @return PartitionManager instance based on LDAP store
+     */
+    protected PartitionManager createPartitionManager(Map<String,String> ldapConfig) {
+        IdentityConfigurationBuilder builder = new IdentityConfigurationBuilder();
+
+        Properties connectionProps = new Properties();
+        connectionProps.put("com.sun.jndi.ldap.connect.pool", "true");
+
+        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", "10");
+        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);
+
+        // RHDS is using "nsuniqueid" as unique identifier instead of "entryUUID"
+        if (vendor != null && vendor.equals(LDAPConstants.VENDOR_RHDS)) {
+            checkSystemProperty(LDAPIdentityStoreConfiguration.ENTRY_IDENTIFIER_ATTRIBUTE_NAME, "nsuniqueid");
+        }
+
+        boolean activeDirectory = vendor != null && vendor.equals(LDAPConstants.VENDOR_ACTIVE_DIRECTORY);
+
+        String ldapLoginNameMapping = ldapConfig.get(LDAPConstants.USERNAME_LDAP_ATTRIBUTE);
+        if (ldapLoginNameMapping == null) {
+            ldapLoginNameMapping = activeDirectory ? CN : UID;
+        }
+
+        // Try to compute properties based on LDAP server type, but still allow to override them through System properties TODO: Should allow better way than overriding from System properties. Perhaps init from XML?
+        ldapLoginNameMapping = getNameOfLDAPAttribute("keycloak.ldap.idm.loginName", ldapLoginNameMapping, ldapLoginNameMapping, activeDirectory);
+        String ldapFirstNameMapping = getNameOfLDAPAttribute("keycloak.ldap.idm.firstName", CN, "givenName", activeDirectory);
+        String ldapLastNameMapping = getNameOfLDAPAttribute("keycloak.ldap.idm.lastName", SN, SN, activeDirectory);
+        String ldapEmailMapping =  getNameOfLDAPAttribute("keycloak.ldap.idm.email", EMAIL, EMAIL, activeDirectory);
+
+        String[] userObjectClasses = getUserObjectClasses(ldapConfig);
+
+        logger.infof("LDAP Attributes mapping: loginName: %s, firstName: %s, lastName: %s, email: %s", ldapLoginNameMapping, ldapFirstNameMapping, ldapLastNameMapping, ldapEmailMapping);
+
+        // Use same mapping for User and Agent for now
+        builder
+            .named("SIMPLE_LDAP_STORE_CONFIG")
+                .stores()
+                    .ldap()
+                        .connectionProperties(connectionProps)
+                        .addCredentialHandler(LDAPKeycloakCredentialHandler.class)
+                        .baseDN(ldapConfig.get(LDAPConstants.BASE_DN))
+                        .bindDN(ldapConfig.get(LDAPConstants.BIND_DN))
+                        .bindCredential(ldapConfig.get(LDAPConstants.BIND_CREDENTIAL))
+                        .url(ldapConfig.get(LDAPConstants.CONNECTION_URL))
+                        .activeDirectory(activeDirectory)
+                        .supportAllFeatures()
+                        .mapping(User.class)
+                            .baseDN(ldapConfig.get(LDAPConstants.USER_DN_SUFFIX))
+                            .objectClasses(userObjectClasses)
+                            .attribute("loginName", ldapLoginNameMapping, true)
+                            .attribute("firstName", ldapFirstNameMapping)
+                            .attribute("lastName", ldapLastNameMapping)
+                            .attribute("email", ldapEmailMapping);
+
+        // Workaround to override the LDAPIdentityStore with our own :/
+        List<IdentityConfiguration> identityConfigs = builder.buildAll();
+        IdentityStoreConfiguration identityStoreConfig = identityConfigs.get(0).getStoreConfiguration().get(0);
+        ((AbstractIdentityStoreConfiguration)identityStoreConfig).setIdentityStoreType(KeycloakLDAPIdentityStore.class);
+
+        return new DefaultPartitionManager(identityConfigs);
+    }
+
+    private void checkSystemProperty(String name, String defaultValue) {
+        if (System.getProperty(name) == null) {
+            System.setProperty(name, defaultValue);
+        }
+    }
+
+    private String getNameOfLDAPAttribute(String systemPropertyName, String defaultAttrName, String defaultAttrNameInActiveDirectory, boolean activeDirectory) {
+        // System property has biggest priority if available
+        String sysProperty = System.getProperty(systemPropertyName);
+        if (sysProperty != null) {
+            return sysProperty;
+        }
+
+        return activeDirectory ? defaultAttrNameInActiveDirectory : defaultAttrName;
+    }
+
+    // Parse array of strings like [ "inetOrgPerson", "organizationalPerson" ] from the string like: "inetOrgPerson, organizationalPerson"
+    private 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 PartitionManagerContext {
+
+        private PartitionManagerContext(Map<String,String> config, PartitionManager manager) {
+            this.config = config;
+            this.partitionManager = manager;
+        }
+
+        private Map<String,String> config;
+        private PartitionManager partitionManager;
+    }
+}
diff --git a/federation/ldap/src/main/resources/META-INF/services/org.keycloak.models.FederationProviderFactory b/federation/ldap/src/main/resources/META-INF/services/org.keycloak.models.FederationProviderFactory
new file mode 100755
index 0000000..b8921e9
--- /dev/null
+++ b/federation/ldap/src/main/resources/META-INF/services/org.keycloak.models.FederationProviderFactory
@@ -0,0 +1 @@
+org.keycloak.federation.ldap.LDAPFederationProviderFactory
\ No newline at end of file

federation/pom.xml 22(+22 -0)

diff --git a/federation/pom.xml b/federation/pom.xml
new file mode 100755
index 0000000..5e15f90
--- /dev/null
+++ b/federation/pom.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>keycloak-parent</artifactId>
+        <groupId>org.keycloak</groupId>
+        <version>1.0-beta-4-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <packaging>pom</packaging>
+
+    <artifactId>keycloak-federation-parent</artifactId>
+    <name>Keycloak Federation</name>
+    <description />
+
+    <modules>
+        <module>ldap</module>
+    </modules>
+
+</project>
diff --git a/model/api/src/main/java/org/keycloak/models/entities/FederationProviderEntity.java b/model/api/src/main/java/org/keycloak/models/entities/FederationProviderEntity.java
new file mode 100755
index 0000000..0330acc
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/entities/FederationProviderEntity.java
@@ -0,0 +1,37 @@
+package org.keycloak.models.entities;
+
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class FederationProviderEntity {
+    protected String id;
+    protected String providerName;
+    private Map<String, String> config;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getProviderName() {
+        return providerName;
+    }
+
+    public void setProviderName(String providerName) {
+        this.providerName = providerName;
+    }
+
+    public Map<String, String> getConfig() {
+        return config;
+    }
+
+    public void setConfig(Map<String, String> config) {
+        this.config = config;
+    }
+}
diff --git a/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java b/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
index 3c841a5..061cfe4 100755
--- a/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
+++ b/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
@@ -51,6 +51,7 @@ public class RealmEntity extends AbstractIdentifiableEntity {
 
     private List<RequiredCredentialEntity> requiredCredentials = new ArrayList<RequiredCredentialEntity>();
     private List<AuthenticationProviderEntity> authenticationProviders = new ArrayList<AuthenticationProviderEntity>();
+    private List<FederationProviderEntity> federationProviders = new ArrayList<FederationProviderEntity>();
 
     private Map<String, String> smtpConfig = new HashMap<String, String>();
     private Map<String, String> socialConfig = new HashMap<String, String>();
@@ -381,4 +382,12 @@ public class RealmEntity extends AbstractIdentifiableEntity {
     public void setAdminAppId(String adminAppId) {
         this.adminAppId = adminAppId;
     }
+
+    public List<FederationProviderEntity> getFederationProviders() {
+        return federationProviders;
+    }
+
+    public void setFederationProviders(List<FederationProviderEntity> federationProviders) {
+        this.federationProviders = federationProviders;
+    }
 }
diff --git a/model/api/src/main/java/org/keycloak/models/entities/UserEntity.java b/model/api/src/main/java/org/keycloak/models/entities/UserEntity.java
index 6db19a7..9aa0b0b 100755
--- a/model/api/src/main/java/org/keycloak/models/entities/UserEntity.java
+++ b/model/api/src/main/java/org/keycloak/models/entities/UserEntity.java
@@ -28,6 +28,7 @@ public class UserEntity extends AbstractIdentifiableEntity {
     private List<CredentialEntity> credentials = new ArrayList<CredentialEntity>();
     private List<SocialLinkEntity> socialLinks;
     private AuthenticationLinkEntity authenticationLink;
+    private String federationLink;
 
     public String getUsername() {
         return username;
@@ -140,5 +141,13 @@ public class UserEntity extends AbstractIdentifiableEntity {
     public void setAuthenticationLink(AuthenticationLinkEntity authenticationLink) {
         this.authenticationLink = authenticationLink;
     }
+
+    public String getFederationLink() {
+        return federationLink;
+    }
+
+    public void setFederationLink(String federationLink) {
+        this.federationLink = federationLink;
+    }
 }
 
diff --git a/model/api/src/main/java/org/keycloak/models/FederationManager.java b/model/api/src/main/java/org/keycloak/models/FederationManager.java
new file mode 100755
index 0000000..8e79ba1
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/FederationManager.java
@@ -0,0 +1,354 @@
+package org.keycloak.models;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class FederationManager implements UserProvider {
+    protected KeycloakSession session;
+
+    public FederationManager(KeycloakSession session) {
+        this.session = session;
+    }
+
+    @Override
+    public UserModel addUser(RealmModel realm, String id, String username, boolean addDefaultRoles) {
+        UserModel user = session.userStorage().addUser(realm, id, username, addDefaultRoles);
+        for (FederationProviderModel federation : realm.getFederationProviders()) {
+            FederationProvider fed = session.getProvider(FederationProvider.class, federation.getProviderName());
+            return fed.addUser(realm, user);
+        }
+        return user;
+    }
+
+    protected FederationProvider getFederationProvider(FederationProviderModel model) {
+        FederationProviderFactory factory = (FederationProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(FederationProvider.class, model.getProviderName());
+        return factory.getInstance(session, model);
+
+    }
+
+    @Override
+    public UserModel addUser(RealmModel realm, String username) {
+        UserModel user = session.userStorage().addUser(realm, username);
+        for (FederationProviderModel federation : realm.getFederationProviders()) {
+            FederationProvider fed = getFederationProvider(federation);
+            return fed.addUser(realm, user);
+        }
+        return user;
+    }
+
+    protected FederationProvider getFederationLink(RealmModel realm, UserModel user) {
+        if (user.getFederationLink() == null) return null;
+        for (FederationProviderModel fed : realm.getFederationProviders()) {
+            if (fed.getId().equals(user.getFederationLink())) {
+                return getFederationProvider(fed);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public boolean removeUser(RealmModel realm, String name) {
+        UserModel user = session.userStorage().getUserByUsername(name, realm);
+        if (user == null) return false;
+        FederationProvider link = getFederationLink(realm, user);
+        if (link != null) {
+            return link.removeUser(realm, user);
+        }
+        return session.userStorage().removeUser(realm, name);
+
+    }
+
+    @Override
+    public void addSocialLink(RealmModel realm, UserModel user, SocialLinkModel socialLink) {
+        FederationProvider link = getFederationLink(realm, user);
+        if (link != null) {
+            link.addSocialLink(realm, user, socialLink);
+            return;
+        }
+        session.userStorage().addSocialLink(realm, user, socialLink);
+
+    }
+
+    @Override
+    public boolean removeSocialLink(RealmModel realm, UserModel user, String socialProvider) {
+        FederationProvider link = getFederationLink(realm, user);
+        if (link != null) {
+            return link.removeSocialLink(realm, user, socialProvider);
+        }
+        return session.userStorage().removeSocialLink(realm, user, socialProvider);
+    }
+
+    @Override
+    public UserModel getUserById(String id, RealmModel realm) {
+        UserModel user = session.userStorage().getUserById(id, realm);
+        if (user != null) {
+            FederationProvider link = getFederationLink(realm, user);
+            if (link != null) {
+                return link.proxy(user);
+            }
+            return user;
+        }
+        for (FederationProviderModel federation : realm.getFederationProviders()) {
+            FederationProvider fed = getFederationProvider(federation);
+            user = fed.getUserById(id, realm);
+            if (user != null) return user;
+        }
+        return user;
+    }
+
+    @Override
+    public UserModel getUserByUsername(String username, RealmModel realm) {
+        UserModel user = session.userStorage().getUserByUsername(username, realm);
+        if (user != null) {
+            FederationProvider link = getFederationLink(realm, user);
+            if (link != null) {
+                return link.proxy(user);
+            }
+            return user;
+        }
+        for (FederationProviderModel federation : realm.getFederationProviders()) {
+            FederationProvider fed = getFederationProvider(federation);
+            user = fed.getUserByUsername(username, realm);
+            if (user != null) return user;
+        }
+        return user;
+    }
+
+    @Override
+    public UserModel getUserByEmail(String email, RealmModel realm) {
+        UserModel user = session.userStorage().getUserByEmail(email, realm);
+        if (user != null) {
+            FederationProvider link = getFederationLink(realm, user);
+            if (link != null) {
+                return link.proxy(user);
+            }
+            return user;
+        }
+        for (FederationProviderModel federation : realm.getFederationProviders()) {
+            FederationProvider fed = getFederationProvider(federation);
+            user = fed.getUserByEmail(email, realm);
+            if (user != null) return user;
+        }
+        return user;
+    }
+
+    @Override
+    public UserModel getUserBySocialLink(SocialLinkModel socialLink, RealmModel realm) {
+        UserModel user = session.userStorage().getUserBySocialLink(socialLink, realm);
+        if (user != null) {
+            FederationProvider link = getFederationLink(realm, user);
+            if (link != null) {
+                return link.proxy(user);
+            }
+            return user;
+        }
+        for (FederationProviderModel federation : realm.getFederationProviders()) {
+            FederationProvider fed = getFederationProvider(federation);
+            user = fed.getUserBySocialLink(socialLink, realm);
+            if (user != null) return user;
+        }
+        return user;
+    }
+
+    @Override
+    public List<UserModel> getUsers(RealmModel realm) {
+        return getUsers(realm, 0, Integer.MAX_VALUE);
+
+    }
+
+    @Override
+    public int getUsersCount(RealmModel realm) {
+        return session.userStorage().getUsersCount(realm);
+    }
+
+    @Override
+    public List<UserModel> getUsers(RealmModel realm, int firstResult, int maxResults) {
+        Map<String, UserModel> users = new HashMap<String, UserModel>();
+        List<UserModel> query = session.userStorage().getUsers(realm, firstResult, maxResults);
+        for (UserModel user : query) {
+            FederationProvider link = getFederationLink(realm, user);
+            if (link != null) {
+                users.put(user.getUsername(), link.proxy(user));
+            } else {
+                users.put(user.getUsername(), user);
+            }
+        }
+        if (users.size() >= maxResults) {
+            List<UserModel> results = new ArrayList<UserModel>(users.size());
+            results.addAll(users.values());
+            return results;
+        }
+        List<FederationProviderModel> federationProviders = realm.getFederationProviders();
+        for (int i = federationProviders.size() - 1; i >= 0; i--) {
+            FederationProviderModel federation = federationProviders.get(i);
+            FederationProvider fed = getFederationProvider(federation);
+            query = fed.getUsers(realm, firstResult, maxResults);
+            for (UserModel user : query) users.put(user.getUsername(), user);
+        }
+        List<UserModel> results = new ArrayList<UserModel>(users.size());
+        results.addAll(users.values());
+        return results;
+    }
+
+    @Override
+    public List<UserModel> searchForUser(String search, RealmModel realm) {
+        return searchForUser(search, realm, 0, Integer.MAX_VALUE);
+    }
+
+    @Override
+    public List<UserModel> searchForUser(String search, RealmModel realm, int firstResult, int maxResults) {
+        Map<String, UserModel> users = new HashMap<String, UserModel>();
+        List<UserModel> query = session.userStorage().searchForUser(search, realm, firstResult, maxResults);
+        for (UserModel user : query) {
+            FederationProvider link = getFederationLink(realm, user);
+            if (link != null) {
+                users.put(user.getUsername(), link.proxy(user));
+            } else {
+                users.put(user.getUsername(), user);
+            }
+        }
+        if (users.size() >= maxResults) {
+            List<UserModel> results = new ArrayList<UserModel>(users.size());
+            results.addAll(users.values());
+            return results;
+        }
+        List<FederationProviderModel> federationProviders = realm.getFederationProviders();
+        for (int i = federationProviders.size() - 1; i >= 0; i--) {
+            FederationProviderModel federation = federationProviders.get(i);
+            FederationProvider fed = getFederationProvider(federation);
+            query = fed.searchForUser(search, realm, firstResult, maxResults);
+            for (UserModel user : query) users.put(user.getUsername(), user);
+        }
+        List<UserModel> results = new ArrayList<UserModel>(users.size());
+        results.addAll(users.values());
+        return results;
+    }
+
+    @Override
+    public List<UserModel> searchForUserByAttributes(Map<String, String> attributes, RealmModel realm) {
+        return searchForUserByAttributes(attributes, realm, 0, Integer.MAX_VALUE);
+    }
+
+    @Override
+    public List<UserModel> searchForUserByAttributes(Map<String, String> attributes, RealmModel realm, int firstResult, int maxResults) {
+        Map<String, UserModel> users = new HashMap<String, UserModel>();
+        List<UserModel> query = session.userStorage().searchForUserByAttributes(attributes, realm, firstResult, maxResults);
+        for (UserModel user : query) {
+            FederationProvider link = getFederationLink(realm, user);
+            if (link != null) {
+                users.put(user.getUsername(), link.proxy(user));
+            } else {
+                users.put(user.getUsername(), user);
+            }
+        }
+        if (users.size() >= maxResults) {
+            List<UserModel> results = new ArrayList<UserModel>(users.size());
+            results.addAll(users.values());
+            return results;
+        }
+        List<FederationProviderModel> federationProviders = realm.getFederationProviders();
+        for (int i = federationProviders.size() - 1; i >= 0; i--) {
+            FederationProviderModel federation = federationProviders.get(i);
+            FederationProvider fed = getFederationProvider(federation);
+            query = fed.searchForUserByAttributes(attributes, realm, firstResult, maxResults);
+            for (UserModel user : query) users.put(user.getUsername(), user);
+        }
+        List<UserModel> results = new ArrayList<UserModel>(users.size());
+        results.addAll(users.values());
+        return results;
+    }
+
+    @Override
+    public Set<SocialLinkModel> getSocialLinks(UserModel user, RealmModel realm) {
+        FederationProvider link = getFederationLink(realm, user);
+        if (link != null) {
+            return link.getSocialLinks(user, realm);
+        }
+        return session.userStorage().getSocialLinks(user, realm);
+    }
+
+    @Override
+    public SocialLinkModel getSocialLink(UserModel user, String socialProvider, RealmModel realm) {
+        FederationProvider link = getFederationLink(realm, user);
+        if (link != null) {
+            return link.getSocialLink(user, socialProvider, realm);
+        }
+        return session.userStorage().getSocialLink(user, socialProvider, realm);
+    }
+
+    @Override
+    public void preRemove(RealmModel realm) {
+        for (FederationProviderModel federation : realm.getFederationProviders()) {
+            FederationProvider fed = getFederationProvider(federation);
+            fed.preRemove(realm);
+        }
+        session.userStorage().preRemove(realm);
+    }
+
+    @Override
+    public void preRemove(RealmModel realm, RoleModel role) {
+        for (FederationProviderModel federation : realm.getFederationProviders()) {
+            FederationProvider fed = getFederationProvider(federation);
+            fed.preRemove(realm, role);
+        }
+        session.userStorage().preRemove(realm, role);
+    }
+
+    @Override
+    public boolean validCredentials(RealmModel realm, UserModel user, List<UserCredentialModel> input) {
+        FederationProvider link = getFederationLink(realm, user);
+        if (link != null) {
+            if (link.getSupportedCredentialTypes().size() > 0) {
+                List<UserCredentialModel> fedCreds = new ArrayList<UserCredentialModel>();
+                List<UserCredentialModel> localCreds = new ArrayList<UserCredentialModel>();
+                for (UserCredentialModel cred : input) {
+                    if (fedCreds.contains(cred.getType())) {
+                        fedCreds.add(cred);
+                    } else {
+                        localCreds.add(cred);
+                    }
+                }
+                if (!link.validCredentials(realm, user, fedCreds)) {
+                    return false;
+                }
+                return session.userStorage().validCredentials(realm, user, localCreds);
+            }
+        }
+        return session.userStorage().validCredentials(realm, user, input);
+    }
+
+    @Override
+    public boolean validCredentials(RealmModel realm, UserModel user, UserCredentialModel... input) {
+        FederationProvider link = getFederationLink(realm, user);
+        if (link != null) {
+            Set<String> supportedCredentialTypes = link.getSupportedCredentialTypes();
+            if (supportedCredentialTypes.size() > 0) {
+                List<UserCredentialModel> fedCreds = new ArrayList<UserCredentialModel>();
+                List<UserCredentialModel> localCreds = new ArrayList<UserCredentialModel>();
+                for (UserCredentialModel cred : input) {
+                    if (supportedCredentialTypes.contains(cred.getType())) {
+                        fedCreds.add(cred);
+                    } else {
+                        localCreds.add(cred);
+                    }
+                }
+                if (!link.validCredentials(realm, user, fedCreds)) {
+                    return false;
+                }
+                return session.userStorage().validCredentials(realm, user, localCreds);
+            }
+        }
+        return session.userStorage().validCredentials(realm, user, input);
+    }
+
+    @Override
+    public void close() {
+    }
+}
diff --git a/model/api/src/main/java/org/keycloak/models/FederationProvider.java b/model/api/src/main/java/org/keycloak/models/FederationProvider.java
new file mode 100755
index 0000000..a70cb25
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/FederationProvider.java
@@ -0,0 +1,17 @@
+package org.keycloak.models;
+
+import java.util.Set;
+
+/**
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface FederationProvider extends UserProvider {
+    UserModel proxy(UserModel local);
+    UserModel addUser(RealmModel realm, UserModel user);
+    boolean removeUser(RealmModel realm, UserModel user);
+    Set<String> getSupportedCredentialTypes();
+    String getAdminPage();
+    Class getAdminClass();
+}
diff --git a/model/api/src/main/java/org/keycloak/models/FederationProviderFactory.java b/model/api/src/main/java/org/keycloak/models/FederationProviderFactory.java
new file mode 100755
index 0000000..214b5b5
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/FederationProviderFactory.java
@@ -0,0 +1,11 @@
+package org.keycloak.models;
+
+import org.keycloak.provider.ProviderFactory;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface FederationProviderFactory extends ProviderFactory<FederationProvider> {
+    FederationProvider getInstance(KeycloakSession session, FederationProviderModel model);
+}
diff --git a/model/api/src/main/java/org/keycloak/models/FederationProviderModel.java b/model/api/src/main/java/org/keycloak/models/FederationProviderModel.java
new file mode 100755
index 0000000..075d75d
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/FederationProviderModel.java
@@ -0,0 +1,45 @@
+package org.keycloak.models;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ * @author <a href="mailto:bburke@redhat.com">Bill Burke</a>
+ */
+public class FederationProviderModel {
+
+    private String id;
+    private String providerName;
+    private Map<String, String> config = new HashMap<String, String>();
+
+    public FederationProviderModel() {};
+
+    public FederationProviderModel(String id, String providerName, Map<String, String> config) {
+        this.id = id;
+        this.providerName = providerName;
+        if (config != null) {
+           this.config.putAll(config);
+        }
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public String getProviderName() {
+        return providerName;
+    }
+
+    public void setProviderName(String providerName) {
+        this.providerName = providerName;
+    }
+
+    public Map<String, String> getConfig() {
+        return config;
+    }
+
+    public void setConfig(Map<String, String> config) {
+        this.config = config;
+    }
+}
diff --git a/model/api/src/main/java/org/keycloak/models/FederationSpi.java b/model/api/src/main/java/org/keycloak/models/FederationSpi.java
new file mode 100755
index 0000000..c427ec9
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/FederationSpi.java
@@ -0,0 +1,27 @@
+package org.keycloak.models;
+
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderFactory;
+import org.keycloak.provider.Spi;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class FederationSpi implements Spi {
+
+    @Override
+    public String getName() {
+        return "federation";
+    }
+
+    @Override
+    public Class<? extends Provider> getProviderClass() {
+        return FederationProvider.class;
+    }
+
+    @Override
+    public Class<? extends ProviderFactory> getProviderFactoryClass() {
+        return FederationProviderFactory.class;
+    }
+
+}
diff --git a/model/api/src/main/java/org/keycloak/models/KeycloakSession.java b/model/api/src/main/java/org/keycloak/models/KeycloakSession.java
index 16a5a67..908093e 100755
--- a/model/api/src/main/java/org/keycloak/models/KeycloakSession.java
+++ b/model/api/src/main/java/org/keycloak/models/KeycloakSession.java
@@ -20,6 +20,8 @@ public interface KeycloakSession {
 
     <T extends Provider> Set<T> getAllProviders(Class<T> clazz);
 
+    KeycloakSessionFactory getKeycloakSessionFactory();
+
     /**
      * Returns a managed provider instance.  Will start a provider transaction.  This transaction is managed by the KeycloakSession
      * transaction.
@@ -38,7 +40,19 @@ public interface KeycloakSession {
      */
     UserSessionProvider sessions();
 
+
+
     void close();
 
+    /**
+     * Possibly both cached and federated view of users depending on configuration.
+     *
+     * @return
+     */
     UserProvider users();
+
+    /**
+     *  Keycloak user storage.  Non-federated, but possibly cache (if it is on) view of users.
+     */
+    UserProvider userStorage();
 }
diff --git a/model/api/src/main/java/org/keycloak/models/KeycloakSessionFactory.java b/model/api/src/main/java/org/keycloak/models/KeycloakSessionFactory.java
index 582b7ea..af31c09 100755
--- a/model/api/src/main/java/org/keycloak/models/KeycloakSessionFactory.java
+++ b/model/api/src/main/java/org/keycloak/models/KeycloakSessionFactory.java
@@ -1,10 +1,18 @@
 package org.keycloak.models;
 
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderFactory;
+
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
  */
 public interface KeycloakSessionFactory {
     KeycloakSession create();
+
+    <T extends Provider> ProviderFactory<T> getProviderFactory(Class<T> clazz);
+
+    <T extends Provider> ProviderFactory<T> getProviderFactory(Class<T> clazz, String id);
+
     void close();
 }
diff --git a/model/api/src/main/java/org/keycloak/models/RealmModel.java b/model/api/src/main/java/org/keycloak/models/RealmModel.java
index d437c9b..3117584 100755
--- a/model/api/src/main/java/org/keycloak/models/RealmModel.java
+++ b/model/api/src/main/java/org/keycloak/models/RealmModel.java
@@ -165,6 +165,10 @@ public interface RealmModel extends RoleContainerModel {
 
     void setAuthenticationProviders(List<AuthenticationProviderModel> authenticationProviders);
 
+    List<FederationProviderModel> getFederationProviders();
+
+    void setFederationProviders(List<FederationProviderModel> providers);
+
     String getLoginTheme();
 
     void setLoginTheme(String name);
diff --git a/model/api/src/main/java/org/keycloak/models/UserModel.java b/model/api/src/main/java/org/keycloak/models/UserModel.java
index 621148e..d440c62 100755
--- a/model/api/src/main/java/org/keycloak/models/UserModel.java
+++ b/model/api/src/main/java/org/keycloak/models/UserModel.java
@@ -77,6 +77,9 @@ public interface UserModel {
     Set<RoleModel> getRoleMappings();
     void deleteRoleMapping(RoleModel role);
 
+    String getFederationLink();
+    void setFederationLink(String link);
+
 
 
 
diff --git a/model/api/src/main/java/org/keycloak/models/UserProvider.java b/model/api/src/main/java/org/keycloak/models/UserProvider.java
index 58436a2..b64f731 100755
--- a/model/api/src/main/java/org/keycloak/models/UserProvider.java
+++ b/model/api/src/main/java/org/keycloak/models/UserProvider.java
@@ -35,7 +35,7 @@ public interface UserProvider extends Provider {
     SocialLinkModel getSocialLink(UserModel user, String socialProvider, RealmModel realm);
 
     void preRemove(RealmModel realm);
-    void preRemove(RoleModel role);
+    void preRemove(RealmModel realm, RoleModel role);
 
     boolean validCredentials(RealmModel realm, UserModel user, List<UserCredentialModel> input);
     boolean validCredentials(RealmModel realm, UserModel user, UserCredentialModel... input);
diff --git a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
index c7f3e36..2272183 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
@@ -5,6 +5,7 @@ import org.keycloak.models.AuthenticationProviderModel;
 import org.keycloak.models.ClaimMask;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.Constants;
+import org.keycloak.models.FederationProviderModel;
 import org.keycloak.models.OAuthClientModel;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RequiredCredentialModel;
@@ -13,6 +14,7 @@ import org.keycloak.models.SocialLinkModel;
 import org.keycloak.models.UserCredentialModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserSessionModel;
+import org.keycloak.representations.FederationProviderRepresentation;
 import org.keycloak.representations.idm.ApplicationRepresentation;
 import org.keycloak.representations.idm.AuthenticationProviderRepresentation;
 import org.keycloak.representations.idm.ClaimRepresentation;
@@ -141,6 +143,18 @@ public class ModelToRepresentation {
             }
             rep.setAuthenticationProviders(authProviderReps);
         }
+        List<FederationProviderModel> fedProviderModels = realm.getFederationProviders();
+        if (fedProviderModels.size() > 0) {
+            List<FederationProviderRepresentation> fedProviderReps = new ArrayList<FederationProviderRepresentation>();
+            for (FederationProviderModel model : fedProviderModels) {
+                FederationProviderRepresentation fedProvRep = new FederationProviderRepresentation();
+                fedProvRep.setId(model.getId());
+                fedProvRep.setProviderName(model.getProviderName());
+                fedProvRep.setConfig(model.getConfig());
+                fedProviderReps.add(fedProvRep);
+            }
+            rep.setFederationProviders(fedProviderReps);
+        }
         return rep;
     }
 
diff --git a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
index f20cb95..5aa8888 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
@@ -7,6 +7,7 @@ import org.keycloak.models.AuthenticationLinkModel;
 import org.keycloak.models.AuthenticationProviderModel;
 import org.keycloak.models.ClaimMask;
 import org.keycloak.models.ClientModel;
+import org.keycloak.models.FederationProviderModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.OAuthClientModel;
 import org.keycloak.models.PasswordPolicy;
@@ -16,6 +17,7 @@ import org.keycloak.models.SocialLinkModel;
 import org.keycloak.models.UserCredentialModel;
 import org.keycloak.models.UserCredentialValueModel;
 import org.keycloak.models.UserModel;
+import org.keycloak.representations.FederationProviderRepresentation;
 import org.keycloak.representations.idm.ApplicationRepresentation;
 import org.keycloak.representations.idm.AuthenticationLinkRepresentation;
 import org.keycloak.representations.idm.AuthenticationProviderRepresentation;
@@ -213,6 +215,11 @@ public class RepresentationToModel {
             newRealm.setAuthenticationProviders(authProviderModels);
         }
 
+        if (rep.getFederationProviders() != null) {
+            List<FederationProviderModel> providerModels = convertFederationProviders(rep.getFederationProviders());
+            newRealm.setFederationProviders(providerModels);
+        }
+
         // create users and their role mappings and social mappings
 
         if (rep.getUsers() != null) {
@@ -280,6 +287,11 @@ public class RepresentationToModel {
             realm.setAuthenticationProviders(authProviderModels);
         }
 
+        if (rep.getFederationProviders() != null) {
+            List<FederationProviderModel> providerModels = convertFederationProviders(rep.getFederationProviders());
+            realm.setFederationProviders(providerModels);
+        }
+
         if ("GENERATE".equals(rep.getPublicKey())) {
             KeycloakModelUtils.generateRealmKeys(realm);
         }
@@ -303,6 +315,17 @@ public class RepresentationToModel {
         return result;
     }
 
+    private static List<FederationProviderModel> convertFederationProviders(List<FederationProviderRepresentation> providers) {
+        List<FederationProviderModel> result = new ArrayList<FederationProviderModel>();
+
+        for (FederationProviderRepresentation representation : providers) {
+            FederationProviderModel model = new FederationProviderModel(representation.getId(), representation.getProviderName(),
+                    representation.getConfig());
+            result.add(model);
+        }
+        return result;
+    }
+
     // Roles
 
     public static void createRole(RealmModel newRealm, RoleRepresentation roleRep) {
@@ -584,6 +607,7 @@ public class RepresentationToModel {
         user.setEmail(userRep.getEmail());
         user.setFirstName(userRep.getFirstName());
         user.setLastName(userRep.getLastName());
+        user.setFederationLink(userRep.getFederationLink());
         if (userRep.getAttributes() != null) {
             for (Map.Entry<String, String> entry : userRep.getAttributes().entrySet()) {
                 user.setAttribute(entry.getKey(), entry.getValue());
diff --git a/model/api/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/model/api/src/main/resources/META-INF/services/org.keycloak.provider.Spi
index 706ba92..58ccc7b 100755
--- a/model/api/src/main/resources/META-INF/services/org.keycloak.provider.Spi
+++ b/model/api/src/main/resources/META-INF/services/org.keycloak.provider.Spi
@@ -1,3 +1,4 @@
+org.keycloak.models.FederationSpi
 org.keycloak.models.RealmSpi
 org.keycloak.models.UserSessionSpi
 org.keycloak.models.UserSpi
\ No newline at end of file
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/DefaultCacheUserProvider.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/DefaultCacheUserProvider.java
index c405e02..68c6149 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/DefaultCacheUserProvider.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/DefaultCacheUserProvider.java
@@ -283,7 +283,7 @@ public class DefaultCacheUserProvider implements CacheUserProvider {
     }
 
     @Override
-    public void preRemove(RoleModel role) {
-        getDelegate().preRemove(role);
+    public void preRemove(RealmModel realm, RoleModel role) {
+        getDelegate().preRemove(realm, role);
     }
 }
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
index 9bb18ae..507cef4 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
@@ -2,6 +2,7 @@ package org.keycloak.models.cache.entities;
 
 import org.keycloak.models.ApplicationModel;
 import org.keycloak.models.AuthenticationProviderModel;
+import org.keycloak.models.FederationProviderModel;
 import org.keycloak.models.RealmProvider;
 import org.keycloak.models.OAuthClientModel;
 import org.keycloak.models.PasswordPolicy;
@@ -64,6 +65,7 @@ public class CachedRealm {
 
     private List<RequiredCredentialModel> requiredCredentials = new ArrayList<RequiredCredentialModel>();
     private List<AuthenticationProviderModel> authenticationProviders = new ArrayList<AuthenticationProviderModel>();
+    private List<FederationProviderModel> federationProviders = new ArrayList<FederationProviderModel>();
 
     private Map<String, String> smtpConfig = new HashMap<String, String>();
     private Map<String, String> socialConfig = new HashMap<String, String>();
@@ -120,6 +122,7 @@ public class CachedRealm {
 
         requiredCredentials = model.getRequiredCredentials();
         authenticationProviders = model.getAuthenticationProviders();
+        federationProviders = model.getFederationProviders();
 
         smtpConfig.putAll(model.getSmtpConfig());
         socialConfig.putAll(model.getSocialConfig());
@@ -328,4 +331,7 @@ public class CachedRealm {
         return auditListeners;
     }
 
+    public List<FederationProviderModel> getFederationProviders() {
+        return federationProviders;
+    }
 }
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedUser.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedUser.java
index 587bf32..83f432d 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedUser.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedUser.java
@@ -1,6 +1,7 @@
 package org.keycloak.models.cache.entities;
 
 import org.keycloak.models.AuthenticationLinkModel;
+import org.keycloak.models.FederationProviderModel;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserCredentialValueModel;
@@ -28,6 +29,7 @@ public class CachedUser {
     private boolean enabled;
     private boolean totp;
     private AuthenticationLinkModel authenticationLink;
+    private String federationLink;
     private Map<String, String> attributes = new HashMap<String, String>();
     private Set<UserModel.RequiredAction> requiredActions = new HashSet<UserModel.RequiredAction>();
     private Set<String> roleMappings = new HashSet<String>();
@@ -44,6 +46,7 @@ public class CachedUser {
         this.credentials.addAll(user.getCredentialsDirectly());
         this.enabled = user.isEnabled();
         this.totp = user.isTotp();
+        this.federationLink = user.getFederationLink();
         this.requiredActions.addAll(user.getRequiredActions());
         this.authenticationLink = user.getAuthenticationLink();
         for (RoleModel role : user.getRoleMappings()) {
@@ -102,4 +105,8 @@ public class CachedUser {
     public AuthenticationLinkModel getAuthenticationLink() {
         return authenticationLink;
     }
+
+    public String getFederationLink() {
+        return federationLink;
+    }
 }
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/NoCacheUserProvider.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/NoCacheUserProvider.java
index c49fb1f..95f7614 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/NoCacheUserProvider.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/NoCacheUserProvider.java
@@ -155,7 +155,7 @@ public class NoCacheUserProvider implements CacheUserProvider {
     }
 
     @Override
-    public void preRemove(RoleModel role) {
-        getDelegate().preRemove(role);
+    public void preRemove(RealmModel realm, RoleModel role) {
+        getDelegate().preRemove(realm, role);
     }
 }
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java
index acf9fb6..b4fa0c8 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java
@@ -3,6 +3,7 @@ package org.keycloak.models.cache;
 import org.keycloak.models.ApplicationModel;
 import org.keycloak.models.AuthenticationProviderModel;
 import org.keycloak.models.ClientModel;
+import org.keycloak.models.FederationProviderModel;
 import org.keycloak.models.utils.CredentialValidation;
 import org.keycloak.models.OAuthClientModel;
 import org.keycloak.models.PasswordPolicy;
@@ -609,6 +610,18 @@ public class RealmAdapter implements RealmModel {
     }
 
     @Override
+    public List<FederationProviderModel> getFederationProviders() {
+        if (updated != null) return updated.getFederationProviders();
+        return cached.getFederationProviders();
+    }
+
+    @Override
+    public void setFederationProviders(List<FederationProviderModel> providers) {
+        getDelegateForUpdate();
+        updated.setFederationProviders(providers);
+    }
+
+    @Override
     public String getLoginTheme() {
         if (updated != null) return updated.getLoginTheme();
         return cached.getLoginTheme();
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/UserAdapter.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/UserAdapter.java
index a094b64..a431ff9 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/UserAdapter.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/UserAdapter.java
@@ -2,6 +2,7 @@ package org.keycloak.models.cache;
 
 import org.keycloak.models.ApplicationModel;
 import org.keycloak.models.AuthenticationLinkModel;
+import org.keycloak.models.FederationProviderModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RoleContainerModel;
@@ -204,6 +205,18 @@ public class UserAdapter implements UserModel {
     }
 
     @Override
+    public String getFederationLink() {
+        if (updated != null) return updated.getFederationLink();
+        return cached.getFederationLink();
+    }
+
+    @Override
+    public void setFederationLink(String link) {
+        getDelegateForUpdate();
+        updated.setFederationLink(link);
+   }
+
+    @Override
     public Set<RoleModel> getRealmRoleMappings() {
         if (updated != null) return updated.getRealmRoleMappings();
         Set<RoleModel> roleMappings = getRoleMappings();
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/ApplicationAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/ApplicationAdapter.java
index 1874912..f83599c 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/ApplicationAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/ApplicationAdapter.java
@@ -138,7 +138,7 @@ public class ApplicationAdapter extends ClientAdapter implements ApplicationMode
         }
         if (!roleModel.getContainer().equals(this)) return false;
 
-        session.users().preRemove(roleModel);
+        session.users().preRemove(getRealm(), roleModel);
         RoleEntity role = RoleAdapter.toRoleEntity(roleModel, em);
         if (!role.isApplicationRole()) return false;
 
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/FederationProviderEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/FederationProviderEntity.java
new file mode 100755
index 0000000..830065c
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/FederationProviderEntity.java
@@ -0,0 +1,85 @@
+package org.keycloak.models.jpa.entities;
+
+import javax.persistence.CollectionTable;
+import javax.persistence.Column;
+import javax.persistence.ElementCollection;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.Id;
+import javax.persistence.IdClass;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.MapKeyColumn;
+import javax.persistence.Table;
+import java.io.Serializable;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ * @author <a href="mailto:bburke@redhat.com">Bill Burke</a>
+ */
+@Entity
+@Table(name="FEDERATION_PROVIDER")
+public class FederationProviderEntity {
+
+    @Id
+    @Column(name="ID", length = 36)
+    protected String id;
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "REALM_ID")
+    protected RealmEntity realm;
+
+    @Column(name="PROVIDER_NAME")
+    private String providerName;
+    @Column(name="PRIORITY")
+    private int priority;
+
+    @ElementCollection
+    @MapKeyColumn(name="name")
+    @Column(name="value")
+    @CollectionTable(name="FEDERATION_PROVIDER_CONFIG")
+    private Map<String, String> config;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public RealmEntity getRealm() {
+        return realm;
+    }
+
+    public void setRealm(RealmEntity realm) {
+        this.realm = realm;
+    }
+
+    public String getProviderName() {
+        return providerName;
+    }
+
+    public void setProviderName(String providerName) {
+        this.providerName = providerName;
+    }
+
+    public int getPriority() {
+        return priority;
+    }
+
+    public void setPriority(int priority) {
+        this.priority = priority;
+    }
+
+    public Map<String, String> getConfig() {
+        return config;
+    }
+
+    public void setConfig(Map<String, String> config) {
+        this.config = config;
+    }
+
+
+}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
index e77e0e5..b7af403 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
@@ -115,6 +115,10 @@ public class RealmEntity {
     @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
     List<AuthenticationProviderEntity> authenticationProviders = new ArrayList<AuthenticationProviderEntity>();
 
+    @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true)
+    @JoinTable(name="FED_PROVIDERS")
+    List<FederationProviderEntity> federationProviders = new ArrayList<FederationProviderEntity>();
+
     @OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true)
     @JoinTable(name="REALM_APPLICATION", joinColumns={ @JoinColumn(name="APPLICATION_ID") }, inverseJoinColumns={ @JoinColumn(name="REALM_ID") })
     Collection<ApplicationEntity> applications = new ArrayList<ApplicationEntity>();
@@ -509,5 +513,12 @@ public class RealmEntity {
         this.masterAdminApp = masterAdminApp;
     }
 
+    public List<FederationProviderEntity> getFederationProviders() {
+        return federationProviders;
+    }
+
+    public void setFederationProviders(List<FederationProviderEntity> federationProviders) {
+        this.federationProviders = federationProviders;
+    }
 }
 
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java
index 16d986a..31e139a 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java
@@ -82,6 +82,9 @@ public class UserEntity {
     @OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="user")
     protected Collection<CredentialEntity> credentials = new ArrayList<CredentialEntity>();
 
+    @Column(name="federation_link")
+    protected String federationLink;
+
     @OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="user")
     protected Collection<AuthenticationLinkEntity> authenticationLink;
 
@@ -198,4 +201,11 @@ public class UserEntity {
         this.authenticationLink = authenticationLink;
     }
 
+    public String getFederationLink() {
+        return federationLink;
+    }
+
+    public void setFederationLink(String federationLink) {
+        this.federationLink = federationLink;
+    }
 }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
index f6e2af6..24e6797 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
@@ -136,7 +136,7 @@ public class JpaUserProvider implements UserProvider {
     }
 
     @Override
-    public void preRemove(RoleModel role) {
+    public void preRemove(RealmModel realm, RoleModel role) {
         em.createNamedQuery("deleteUserRoleMappingsByRole").setParameter("roleId", role.getId()).executeUpdate();
     }
 
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
index 8a4a0b8..9c988d1 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
@@ -3,6 +3,8 @@ package org.keycloak.models.jpa;
 import org.keycloak.models.ApplicationModel;
 import org.keycloak.models.AuthenticationProviderModel;
 import org.keycloak.models.ClientModel;
+import org.keycloak.models.FederationProviderModel;
+import org.keycloak.models.jpa.entities.FederationProviderEntity;
 import org.keycloak.models.utils.CredentialValidation;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.OAuthClientModel;
@@ -731,6 +733,64 @@ public class RealmAdapter implements RealmModel {
     }
 
     @Override
+    public List<FederationProviderModel> getFederationProviders() {
+        List<FederationProviderEntity> entities = realm.getFederationProviders();
+        List<FederationProviderEntity> copy = new ArrayList<FederationProviderEntity>();
+        for (FederationProviderEntity entity : entities) {
+            copy.add(entity);
+
+        }
+        Collections.sort(copy, new Comparator<FederationProviderEntity>() {
+
+            @Override
+            public int compare(FederationProviderEntity o1, FederationProviderEntity o2) {
+                return o1.getPriority() - o2.getPriority();
+            }
+
+        });
+        List<FederationProviderModel> result = new ArrayList<FederationProviderModel>();
+        for (FederationProviderEntity entity : copy) {
+            result.add(new FederationProviderModel(entity.getId(), entity.getProviderName(), entity.getConfig()));
+        }
+
+        return result;
+    }
+
+    @Override
+    public void setFederationProviders(List<FederationProviderModel> providers) {
+        List<FederationProviderEntity> newEntities = new ArrayList<FederationProviderEntity>();
+        int counter = 1;
+        for (FederationProviderModel model : providers) {
+            FederationProviderEntity entity = new FederationProviderEntity();
+            entity.setId(KeycloakModelUtils.generateId());
+            entity.setRealm(realm);
+            entity.setProviderName(model.getProviderName());
+            entity.setConfig(model.getConfig());
+            entity.setPriority(counter++);
+            newEntities.add(entity);
+        }
+
+        // Remove all existing first
+        Collection<FederationProviderEntity> existing = realm.getFederationProviders();
+        Collection<FederationProviderEntity> copy = new ArrayList<FederationProviderEntity>(existing);
+        for (FederationProviderEntity apToRemove : copy) {
+            existing.remove(apToRemove);
+            em.remove(apToRemove);
+        }
+
+        em.flush();
+
+        // Now create all new providers
+        for (FederationProviderEntity apToAdd : newEntities) {
+            existing.add(apToAdd);
+            em.persist(apToAdd);
+        }
+
+        em.flush();
+    }
+
+
+    @Override
     public RoleModel getRole(String name) {
         TypedQuery<RoleEntity> query = em.createNamedQuery("getRealmRoleByName", RoleEntity.class);
         query.setParameter("name", name);
@@ -764,7 +824,7 @@ public class RealmAdapter implements RealmModel {
             return false;
         }
         if (!role.getContainer().equals(this)) return false;
-        session.users().preRemove(role);
+        session.users().preRemove(this, role);
         RoleEntity roleEntity = RoleAdapter.toRoleEntity(role, em);
         realm.getRoles().remove(role);
         realm.getDefaultRoles().remove(role);
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
index 6eee572..4687908 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
@@ -2,6 +2,7 @@ package org.keycloak.models.jpa;
 
 import org.keycloak.models.ApplicationModel;
 import org.keycloak.models.AuthenticationLinkModel;
+import org.keycloak.models.FederationProviderModel;
 import org.keycloak.models.PasswordPolicy;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RoleContainerModel;
@@ -11,6 +12,7 @@ import org.keycloak.models.UserCredentialValueModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.jpa.entities.AuthenticationLinkEntity;
 import org.keycloak.models.jpa.entities.CredentialEntity;
+import org.keycloak.models.jpa.entities.FederationProviderEntity;
 import org.keycloak.models.jpa.entities.UserAttributeEntity;
 import org.keycloak.models.jpa.entities.UserEntity;
 import org.keycloak.models.jpa.entities.UserRequiredActionEntity;
@@ -414,6 +416,16 @@ public class UserAdapter implements UserModel {
     }
 
     @Override
+    public String getFederationLink() {
+        return user.getFederationLink();
+    }
+
+    @Override
+    public void setFederationLink(String link) {
+        user.setFederationLink(link);
+    }
+
+    @Override
     public boolean equals(Object o) {
         if (this == o) return true;
         if (o == null || !(o instanceof UserModel)) return false;
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ApplicationAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ApplicationAdapter.java
index 3171e0a..2b6ef4c 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ApplicationAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ApplicationAdapter.java
@@ -141,7 +141,7 @@ public class ApplicationAdapter extends ClientAdapter<MongoApplicationEntity> im
 
     @Override
     public boolean removeRole(RoleModel role) {
-        session.users().preRemove(role);
+        session.users().preRemove(getRealm(), role);
         return getMongoStore().removeEntity(MongoRoleEntity.class, role.getId(), invocationContext);
     }
 
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java
index 95914ae..df60d25 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java
@@ -341,7 +341,7 @@ public class MongoUserProvider implements UserProvider {
     }
 
     @Override
-    public void preRemove(RoleModel role) {
+    public void preRemove(RealmModel realm, RoleModel role) {
         // todo not sure what to do for this
     }
 
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
index 00e690a..713d08c 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
@@ -7,6 +7,8 @@ import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
 import org.keycloak.models.ApplicationModel;
 import org.keycloak.models.AuthenticationProviderModel;
 import org.keycloak.models.ClientModel;
+import org.keycloak.models.FederationProviderModel;
+import org.keycloak.models.entities.FederationProviderEntity;
 import org.keycloak.models.utils.CredentialValidation;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmProvider;
@@ -475,7 +477,7 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
     public boolean removeRoleById(String id) {
         RoleModel role = getRoleById(id);
         if (role == null) return false;
-        session.users().preRemove(role);
+        session.users().preRemove(this, role);
         return getMongoStore().removeEntity(MongoRoleEntity.class, id, invocationContext);
     }
 
@@ -793,6 +795,31 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
         realm.setAuthenticationProviders(entities);
         updateRealm();
     }
+    @Override
+    public List<FederationProviderModel> getFederationProviders() {
+        List<FederationProviderEntity> entities = realm.getFederationProviders();
+        List<FederationProviderModel> result = new ArrayList<FederationProviderModel>();
+        for (FederationProviderEntity entity : entities) {
+            result.add(new FederationProviderModel(entity.getId(), entity.getProviderName(), entity.getConfig()));
+        }
+
+        return result;
+    }
+
+    @Override
+    public void setFederationProviders(List<FederationProviderModel> providers) {
+        List<FederationProviderEntity> entities = new ArrayList<FederationProviderEntity>();
+        for (FederationProviderModel model : providers) {
+            FederationProviderEntity entity = new FederationProviderEntity();
+            entity.setId(KeycloakModelUtils.generateId());
+            entity.setProviderName(model.getProviderName());
+            entity.setConfig(model.getConfig());
+            entities.add(entity);
+        }
+
+        realm.setFederationProviders(entities);
+        updateRealm();
+    }
 
     @Override
     public boolean isAuditEnabled() {
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
index ab1bd4f..1d30a5b 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
@@ -331,6 +331,17 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
     }
 
     @Override
+    public String getFederationLink() {
+        return user.getFederationLink();
+    }
+
+    @Override
+    public void setFederationLink(String link) {
+        user.setFederationLink(link);
+
+    }
+
+    @Override
     public AuthenticationLinkModel getAuthenticationLink() {
         AuthenticationLinkEntity authLinkEntity = user.getAuthenticationLink();
 

pom.xml 1(+1 -0)

diff --git a/pom.xml b/pom.xml
index 28af803..65cf8bd 100755
--- a/pom.xml
+++ b/pom.xml
@@ -104,6 +104,7 @@
         <module>model</module>
         <module>integration</module>
         <module>picketlink</module>
+        <module>federation</module>
         <module>services</module>
         <module>social</module>
         <module>forms</module>
diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java
index 8aae6b4..73299bf 100755
--- a/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java
+++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java
@@ -1,6 +1,8 @@
 package org.keycloak.services;
 
+import org.keycloak.models.FederationManager;
 import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
 import org.keycloak.models.KeycloakTransactionManager;
 import org.keycloak.models.RealmProvider;
 import org.keycloak.models.UserProvider;
@@ -26,13 +28,15 @@ public class DefaultKeycloakSession implements KeycloakSession {
     private RealmProvider model;
     private UserProvider userModel;
     private UserSessionProvider sessionProvider;
+    private FederationManager federationManager;
 
     public DefaultKeycloakSession(DefaultKeycloakSessionFactory factory) {
         this.factory = factory;
         this.transactionManager = new DefaultKeycloakTransactionManager();
+        federationManager = new FederationManager(this);
     }
 
-    private RealmProvider getModelProvider() {
+    private RealmProvider getRealmProvider() {
         if (factory.getDefaultProvider(CacheRealmProvider.class) != null) {
             return getProvider(CacheRealmProvider.class);
         } else {
@@ -53,6 +57,20 @@ public class DefaultKeycloakSession implements KeycloakSession {
         return transactionManager;
     }
 
+    @Override
+    public KeycloakSessionFactory getKeycloakSessionFactory() {
+        return factory;
+    }
+
+    @Override
+    public UserProvider userStorage() {
+        if (userModel == null) {
+            userModel = getUserProvider();
+        }
+        return userModel;
+
+    }
+
     public <T extends Provider> T getProvider(Class<T> clazz) {
         Integer hash = clazz.hashCode();
         T provider = (T) providers.get(hash);
@@ -95,17 +113,14 @@ public class DefaultKeycloakSession implements KeycloakSession {
     @Override
     public RealmProvider realms() {
         if (model == null) {
-            model = getModelProvider();
+            model = getRealmProvider();
         }
         return model;
     }
 
     @Override
     public UserProvider users() {
-        if (userModel == null) {
-            userModel = getUserProvider();
-        }
-        return userModel;
+        return federationManager;
     }
 
     @Override
diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java
index 560cc77..94b3ea7 100755
--- a/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java
+++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java
@@ -74,11 +74,13 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory {
         return provider.get(clazz);
     }
 
-    <T extends Provider> ProviderFactory<T> getProviderFactory(Class<T> clazz) {
+    @Override
+    public <T extends Provider> ProviderFactory<T> getProviderFactory(Class<T> clazz) {
          return getProviderFactory(clazz, provider.get(clazz));
     }
 
-    <T extends Provider> ProviderFactory<T> getProviderFactory(Class<T> clazz, String id) {
+    @Override
+    public <T extends Provider> ProviderFactory<T> getProviderFactory(Class<T> clazz, String id) {
          return factoriesMap.get(clazz).get(id);
     }
 
diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
index 4d71501..6b972ce 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -255,6 +255,7 @@ public class AuthenticationManager {
 
     protected AuthenticationStatus authenticateInternal(KeycloakSession session, RealmModel realm, MultivaluedMap<String, String> formData, String username) {
         UserModel user = KeycloakModelUtils.findUserByNameOrEmail(session, realm, username);
+
         if (user == null) {
             AuthUser authUser = AuthenticationProviderManager.getManager(realm, session).getUser(username);
             if (authUser != null) {
diff --git a/testsuite/integration/pom.xml b/testsuite/integration/pom.xml
index 06ae32c..3aa1ffd 100755
--- a/testsuite/integration/pom.xml
+++ b/testsuite/integration/pom.xml
@@ -99,6 +99,11 @@
         </dependency>
         <dependency>
             <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-ldap-federation</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
             <artifactId>keycloak-undertow-adapter</artifactId>
             <version>${project.version}</version>
         </dependency>
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/FederationProvidersIntegrationTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/FederationProvidersIntegrationTest.java
new file mode 100755
index 0000000..a2d0305
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/FederationProvidersIntegrationTest.java
@@ -0,0 +1,183 @@
+package org.keycloak.testsuite.forms;
+
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.FixMethodOrder;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TestRule;
+import org.junit.runners.MethodSorters;
+import org.keycloak.OAuth2Constants;
+import org.keycloak.authentication.AuthProviderConstants;
+import org.keycloak.federation.ldap.LDAPFederationProviderFactory;
+import org.keycloak.model.test.LDAPEmbeddedServer;
+import org.keycloak.model.test.LDAPTestUtils;
+import org.keycloak.models.AuthenticationProviderModel;
+import org.keycloak.models.FederationProviderModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.LDAPConstants;
+import org.keycloak.models.PasswordPolicy;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.representations.idm.CredentialRepresentation;
+import org.keycloak.services.managers.RealmManager;
+import org.keycloak.testsuite.OAuthClient;
+import org.keycloak.testsuite.pages.AccountPasswordPage;
+import org.keycloak.testsuite.pages.AccountUpdateProfilePage;
+import org.keycloak.testsuite.pages.AppPage;
+import org.keycloak.testsuite.pages.LoginPage;
+import org.keycloak.testsuite.pages.RegisterPage;
+import org.keycloak.testsuite.rule.KeycloakRule;
+import org.keycloak.testsuite.rule.LDAPRule;
+import org.keycloak.testsuite.rule.WebResource;
+import org.keycloak.testsuite.rule.WebRule;
+import org.openqa.selenium.WebDriver;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class FederationProvidersIntegrationTest {
+
+    private static LDAPRule ldapRule = new LDAPRule();
+
+    private static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() {
+
+        @Override
+        public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+            addUser(manager.getSession(), appRealm, "mary", "mary@test.com", "password-app");
+            addUser(manager.getSession(), adminstrationRealm, "mary-admin", "mary@admin.com", "password-admin");
+
+            LDAPEmbeddedServer ldapServer = ldapRule.getEmbeddedServer();
+            Map<String,String> ldapConfig = new HashMap<String,String>();
+            ldapConfig.put(LDAPConstants.CONNECTION_URL, ldapServer.getConnectionUrl());
+            ldapConfig.put(LDAPConstants.BASE_DN, ldapServer.getBaseDn());
+            ldapConfig.put(LDAPConstants.BIND_DN, ldapServer.getBindDn());
+            ldapConfig.put(LDAPConstants.BIND_CREDENTIAL, ldapServer.getBindCredential());
+            ldapConfig.put(LDAPConstants.USER_DN_SUFFIX, ldapServer.getUserDnSuffix());
+            ldapConfig.put(LDAPConstants.VENDOR, ldapServer.getVendor());
+
+
+            FederationProviderModel ldapProvider = new FederationProviderModel(null, LDAPFederationProviderFactory.PROVIDER_NAME, ldapConfig);
+            appRealm.setFederationProviders(Arrays.asList(ldapProvider));
+
+            // Configure LDAP
+            ldapRule.getEmbeddedServer().setupLdapInRealm(appRealm);
+            LDAPTestUtils.setLdapPassword(session, appRealm, "johnkeycloak", "password");
+        }
+    });
+
+    @ClassRule
+    public static TestRule chain = RuleChain
+            .outerRule(ldapRule)
+            .around(keycloakRule);
+
+    @Rule
+    public WebRule webRule = new WebRule(this);
+
+    @WebResource
+    protected OAuthClient oauth;
+
+    @WebResource
+    protected WebDriver driver;
+
+    @WebResource
+    protected AppPage appPage;
+
+    @WebResource
+    protected RegisterPage registerPage;
+
+    @WebResource
+    protected LoginPage loginPage;
+
+    @WebResource
+    protected AccountUpdateProfilePage profilePage;
+
+    @WebResource
+    protected AccountPasswordPage changePasswordPage;
+
+    private static UserModel addUser(KeycloakSession session, RealmModel realm, String username, String email, String password) {
+        UserModel user = session.users().addUser(realm, username);
+        user.setEmail(email);
+        user.setEnabled(true);
+
+        UserCredentialModel creds = new UserCredentialModel();
+        creds.setType(CredentialRepresentation.PASSWORD);
+        creds.setValue(password);
+
+        user.updateCredential(creds);
+        return user;
+    }
+
+    @Test
+    public void loginClassic() {
+        loginPage.open();
+        loginPage.login("mary", "password-app");
+
+        Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
+        Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
+
+    }
+
+    @Test
+    public void loginLdap() {
+        loginPage.open();
+        loginPage.login("johnkeycloak", "password");
+
+        Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
+        Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
+
+        profilePage.open();
+        Assert.assertEquals("John", profilePage.getFirstName());
+        Assert.assertEquals("Doe", profilePage.getLastName());
+        Assert.assertEquals("john@email.org", profilePage.getEmail());
+    }
+
+    @Test
+    public void passwordChangeLdap() throws Exception {
+        changePasswordPage.open();
+        loginPage.login("johnkeycloak", "password");
+        changePasswordPage.changePassword("password", "new-password", "new-password");
+
+        Assert.assertEquals("Your password has been updated", profilePage.getSuccess());
+
+        changePasswordPage.logout();
+
+        loginPage.open();
+        loginPage.login("johnkeycloak", "password");
+        Assert.assertEquals("Invalid username or password.", loginPage.getError());
+
+        loginPage.open();
+        loginPage.login("johnkeycloak", "new-password");
+        Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
+    }
+
+    @Test
+    public void registerExistingLdapUser() {
+        loginPage.open();
+        loginPage.clickRegister();
+        registerPage.assertCurrent();
+
+        registerPage.register("firstName", "lastName", "email", "existing", "password", "password");
+
+        registerPage.assertCurrent();
+        Assert.assertEquals("Username already exists", registerPage.getError());
+    }
+
+    @Test
+    public void registerUserLdapSuccess() {
+        loginPage.open();
+        loginPage.clickRegister();
+        registerPage.assertCurrent();
+
+        registerPage.register("firstName", "lastName", "email2", "registerUserSuccess2", "password", "password");
+        Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
+    }
+}