keycloak-aplcache

Merge pull request #302 from mposolda/ldap Authentication

3/25/2014 9:42:27 AM

Changes

model/tests/pom.xml 62(+62 -0)

pom.xml 24(+22 -2)

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

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

Details

diff --git a/core/src/main/java/org/keycloak/representations/idm/AuthenticationLinkRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/AuthenticationLinkRepresentation.java
new file mode 100644
index 0000000..d59e582
--- /dev/null
+++ b/core/src/main/java/org/keycloak/representations/idm/AuthenticationLinkRepresentation.java
@@ -0,0 +1,26 @@
+package org.keycloak.representations.idm;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class AuthenticationLinkRepresentation {
+
+    private String authProvider;
+    private String authUserId;
+
+    public String getAuthProvider() {
+        return authProvider;
+    }
+
+    public void setAuthProvider(String authProvider) {
+        this.authProvider = authProvider;
+    }
+
+    public String getAuthUserId() {
+        return authUserId;
+    }
+
+    public void setAuthUserId(String authUserId) {
+        this.authUserId = authUserId;
+    }
+}
diff --git a/core/src/main/java/org/keycloak/representations/idm/AuthenticationMappingRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/AuthenticationMappingRepresentation.java
new file mode 100644
index 0000000..ecbfb8d
--- /dev/null
+++ b/core/src/main/java/org/keycloak/representations/idm/AuthenticationMappingRepresentation.java
@@ -0,0 +1,39 @@
+package org.keycloak.representations.idm;
+
+import java.util.List;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class AuthenticationMappingRepresentation {
+
+    protected String self; // link
+    protected String username;
+    protected List<AuthenticationLinkRepresentation> authenticationLinks;
+
+    public String getSelf() {
+        return self;
+    }
+
+    public void setSelf(String self) {
+        this.self = self;
+    }
+
+    public String getUsername() {
+        return username;
+    }
+
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
+    public List<AuthenticationLinkRepresentation> getAuthenticationLinks() {
+        return authenticationLinks;
+    }
+
+    public void setAuthenticationLinks(List<AuthenticationLinkRepresentation> authenticationLinks) {
+        this.authenticationLinks = authenticationLinks;
+    }
+
+
+}
diff --git a/core/src/main/java/org/keycloak/representations/idm/AuthenticationProviderRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/AuthenticationProviderRepresentation.java
new file mode 100644
index 0000000..eeb71a0
--- /dev/null
+++ b/core/src/main/java/org/keycloak/representations/idm/AuthenticationProviderRepresentation.java
@@ -0,0 +1,37 @@
+package org.keycloak.representations.idm;
+
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class AuthenticationProviderRepresentation {
+
+    private String providerName;
+    private boolean passwordUpdateSupported = true;
+    private Map<String, String> config;
+
+    public String getProviderName() {
+        return providerName;
+    }
+
+    public void setProviderName(String providerName) {
+        this.providerName = providerName;
+    }
+
+    public boolean isPasswordUpdateSupported() {
+        return passwordUpdateSupported;
+    }
+
+    public void setPasswordUpdateSupported(boolean passwordUpdateSupported) {
+        this.passwordUpdateSupported = passwordUpdateSupported;
+    }
+
+    public Map<String, String> getConfig() {
+        return config;
+    }
+
+    public void setConfig(Map<String, String> config) {
+        this.config = config;
+    }
+}
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 b3191a3..fc72ef0 100755
--- a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
@@ -39,10 +39,13 @@ public class RealmRepresentation {
     protected Map<String, List<UserRoleMappingRepresentation>> applicationRoleMappings;
     protected Map<String, List<ScopeMappingRepresentation>> applicationScopeMappings;
     protected List<SocialMappingRepresentation> socialMappings;
+    protected List<AuthenticationMappingRepresentation> authenticationMappings;
     protected List<ApplicationRepresentation> applications;
     protected List<OAuthClientRepresentation> oauthClients;
     protected Map<String, String> socialProviders;
     protected Map<String, String> smtpServer;
+    protected Map<String, String> ldapServer;
+    protected List<AuthenticationProviderRepresentation> authenticationProviders;
     protected String loginTheme;
     protected String accountTheme;
 
@@ -178,6 +181,18 @@ public class RealmRepresentation {
         return mapping;
     }
 
+    public List<AuthenticationMappingRepresentation> getAuthenticationMappings() {
+        return authenticationMappings;
+    }
+
+    public AuthenticationMappingRepresentation authenticationMapping(String username) {
+        AuthenticationMappingRepresentation mapping = new AuthenticationMappingRepresentation();
+        mapping.setUsername(username);
+        if (authenticationMappings == null) authenticationMappings = new ArrayList<AuthenticationMappingRepresentation>();
+        authenticationMappings.add(mapping);
+        return mapping;
+    }
+
     public Set<String> getRequiredCredentials() {
         return requiredCredentials;
     }
@@ -298,6 +313,22 @@ public class RealmRepresentation {
         this.smtpServer = smtpServer;
     }
 
+    public Map<String, String> getLdapServer() {
+        return ldapServer;
+    }
+
+    public void setLdapServer(Map<String, String> ldapServer) {
+        this.ldapServer = ldapServer;
+    }
+
+    public List<AuthenticationProviderRepresentation> getAuthenticationProviders() {
+        return authenticationProviders;
+    }
+
+    public void setAuthenticationProviders(List<AuthenticationProviderRepresentation> authenticationProviders) {
+        this.authenticationProviders = authenticationProviders;
+    }
+
     public List<OAuthClientRepresentation> getOauthClients() {
         return oauthClients;
     }
diff --git a/core/src/main/java/org/keycloak/util/KeycloakRegistry.java b/core/src/main/java/org/keycloak/util/KeycloakRegistry.java
new file mode 100644
index 0000000..7a9c982
--- /dev/null
+++ b/core/src/main/java/org/keycloak/util/KeycloakRegistry.java
@@ -0,0 +1,29 @@
+package org.keycloak.util;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * Set of services shared for whole Keycloak server
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class KeycloakRegistry {
+
+    private final ConcurrentMap<Class<?>, Object> services = new ConcurrentHashMap<Class<?>, Object>();
+
+    public <T> void putService(Class<T> type, T object) {
+        services.put(type, object);
+    }
+
+    public <T> T putServiceIfAbsent(Class<T> type, T object) {
+        // Put only if absent and always return the version from registry
+        services.putIfAbsent(type, object);
+        return (T) services.get(type);
+    }
+
+    public <T> T getService(Class<T> type) {
+        return (T) services.get(type);
+    }
+}
diff --git a/model/api/src/main/java/org/keycloak/models/AuthenticationLinkModel.java b/model/api/src/main/java/org/keycloak/models/AuthenticationLinkModel.java
new file mode 100644
index 0000000..1a4c52d
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/AuthenticationLinkModel.java
@@ -0,0 +1,25 @@
+package org.keycloak.models;
+
+/**
+ * Link between user and authentication provider
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class AuthenticationLinkModel {
+
+    private final String authProvider;
+    private final String authUserId;
+
+    public AuthenticationLinkModel(String authProvider, String authUserId) {
+        this.authProvider = authProvider;
+        this.authUserId = authUserId;
+    }
+
+    public String getAuthUserId() {
+        return authUserId;
+    }
+
+    public String getAuthProvider() {
+        return authProvider;
+    }
+}
diff --git a/model/api/src/main/java/org/keycloak/models/AuthenticationProviderModel.java b/model/api/src/main/java/org/keycloak/models/AuthenticationProviderModel.java
new file mode 100644
index 0000000..6a0f1c6
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/AuthenticationProviderModel.java
@@ -0,0 +1,43 @@
+package org.keycloak.models;
+
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class AuthenticationProviderModel {
+
+    private String providerName;
+    private boolean passwordUpdateSupported = true;
+    private Map<String, String> config;
+
+    public AuthenticationProviderModel(String providerName, boolean passwordUpdateSupported, Map<String, String> config) {
+        this.providerName = providerName;
+        this.passwordUpdateSupported = passwordUpdateSupported;
+        this.config = config;
+    }
+
+    public String getProviderName() {
+        return providerName;
+    }
+
+    public void setProviderName(String providerName) {
+        this.providerName = providerName;
+    }
+
+    public boolean isPasswordUpdateSupported() {
+        return passwordUpdateSupported;
+    }
+
+    public void setPasswordUpdateSupported(boolean passwordUpdateSupported) {
+        this.passwordUpdateSupported = passwordUpdateSupported;
+    }
+
+    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/RealmModel.java b/model/api/src/main/java/org/keycloak/models/RealmModel.java
index 1c02a90..e07bdb9 100755
--- a/model/api/src/main/java/org/keycloak/models/RealmModel.java
+++ b/model/api/src/main/java/org/keycloak/models/RealmModel.java
@@ -134,6 +134,12 @@ public interface RealmModel extends RoleContainerModel, RoleMapperModel, ScopeMa
 
     boolean removeSocialLink(UserModel user, String socialProvider);
 
+    UserModel getUserByAuthenticationLink(AuthenticationLinkModel authenticationLink);
+
+    Set<AuthenticationLinkModel> getAuthenticationLinks(UserModel user);
+
+    void addAuthenticationLink(UserModel user, AuthenticationLinkModel authenticationLink);
+
     boolean isSocial();
 
     void setSocial(boolean social);
@@ -164,6 +170,14 @@ public interface RealmModel extends RoleContainerModel, RoleMapperModel, ScopeMa
 
     void setSocialConfig(Map<String, String> socialConfig);
 
+    Map<String, String> getLdapServerConfig();
+
+    void setLdapServerConfig(Map<String, String> ldapServerConfig);
+
+    List<AuthenticationProviderModel> getAuthenticationProviders();
+
+    void setAuthenticationProviders(List<AuthenticationProviderModel> authenticationProviders);
+
     Set<RoleModel> getRealmRoleMappings(UserModel user);
 
     Set<RoleModel> getRealmScopeMappings(ClientModel client);
diff --git a/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java b/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
index b814d7f..939eeba 100644
--- a/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
@@ -10,7 +10,9 @@ import java.util.UUID;
 import java.util.concurrent.atomic.AtomicLong;
 
 import org.bouncycastle.openssl.PEMWriter;
+import org.keycloak.models.RealmModel;
 import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserModel;
 import org.keycloak.util.PemUtils;
 
 /**
@@ -23,8 +25,6 @@ public final class KeycloakModelUtils {
     private KeycloakModelUtils() {
     }
 
-    private static AtomicLong counter = new AtomicLong(1);
-
     public static String generateId() {
         return UUID.randomUUID().toString();
     }
@@ -84,4 +84,19 @@ public final class KeycloakModelUtils {
         }
         return false;
     }
+
+    /**
+     * Try to find user by given username. If it fails, then fallback to find him by email
+     *
+     * @param realm realm
+     * @param username username or email of user
+     * @return found user
+     */
+    public static UserModel findUserByNameOrEmail(RealmModel realm, String username) {
+        UserModel user = realm.getUser(username);
+        if (user == null && username.contains("@")) {
+            user = realm.getUserByEmail(username);
+        }
+        return user;
+    }
 }
diff --git a/model/jpa/pom.xml b/model/jpa/pom.xml
index fd4a6b8..617b8f4 100755
--- a/model/jpa/pom.xml
+++ b/model/jpa/pom.xml
@@ -64,6 +64,12 @@
             <groupId>org.keycloak</groupId>
             <artifactId>keycloak-model-tests</artifactId>
             <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-model-tests</artifactId>
+            <version>${project.version}</version>
             <classifier>tests</classifier>
             <scope>test</scope>
         </dependency>
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationLinkEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationLinkEntity.java
new file mode 100644
index 0000000..0eecfba
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationLinkEntity.java
@@ -0,0 +1,75 @@
+package org.keycloak.models.jpa.entities;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.ManyToOne;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+
+import org.hibernate.annotations.GenericGenerator;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@NamedQueries({
+        @NamedQuery(name="findAuthLinkByUser", query="select link from AuthenticationLinkEntity link where link.user = :user"),
+        @NamedQuery(name="findUserByAuthLinkAndRealm", query="select link.user from AuthenticationLinkEntity link where link.realm = :realm and link.authProvider = :authProvider and link.authUserId = :authUserId")
+})
+@Entity
+public class AuthenticationLinkEntity {
+
+    @Id
+    @GenericGenerator(name="keycloak_generator", strategy="org.keycloak.models.jpa.utils.JpaIdGenerator")
+    @GeneratedValue(generator = "keycloak_generator")
+    private String id;
+
+    @ManyToOne
+    private UserEntity user;
+
+    @ManyToOne
+    protected RealmEntity realm;
+
+    protected String authProvider;
+    protected String authUserId;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public UserEntity getUser() {
+        return user;
+    }
+
+    public void setUser(UserEntity user) {
+        this.user = user;
+    }
+
+    public RealmEntity getRealm() {
+        return realm;
+    }
+
+    public void setRealm(RealmEntity realm) {
+        this.realm = realm;
+    }
+
+    public String getAuthProvider() {
+        return authProvider;
+    }
+
+    public void setAuthProvider(String authProvider) {
+        this.authProvider = authProvider;
+    }
+
+    public String getAuthUserId() {
+        return authUserId;
+    }
+
+    public void setAuthUserId(String authUserId) {
+        this.authUserId = authUserId;
+    }
+}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationProviderEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationProviderEntity.java
new file mode 100644
index 0000000..95e113d
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationProviderEntity.java
@@ -0,0 +1,66 @@
+package org.keycloak.models.jpa.entities;
+
+import java.util.Map;
+
+import javax.persistence.CollectionTable;
+import javax.persistence.Column;
+import javax.persistence.ElementCollection;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.MapKeyColumn;
+
+import org.hibernate.annotations.GenericGenerator;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@Entity
+public class AuthenticationProviderEntity {
+
+    @Id
+    @GenericGenerator(name="keycloak_generator", strategy="org.keycloak.models.jpa.utils.JpaIdGenerator")
+    @GeneratedValue(generator = "keycloak_generator")
+    protected String id;
+
+    private String providerName;
+    private boolean passwordUpdateSupported;
+
+    @ElementCollection
+    @MapKeyColumn(name="name")
+    @Column(name="value")
+    @CollectionTable
+    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 boolean isPasswordUpdateSupported() {
+        return passwordUpdateSupported;
+    }
+
+    public void setPasswordUpdateSupported(boolean passwordUpdateSupported) {
+        this.passwordUpdateSupported = passwordUpdateSupported;
+    }
+
+    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 c8a6556..04051ed 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
@@ -63,6 +63,10 @@ public class RealmEntity {
     @JoinTable(name="User_RequiredCreds")
     Collection<RequiredCredentialEntity> requiredCredentials = new ArrayList<RequiredCredentialEntity>();
 
+    @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true)
+    @JoinTable(name="AuthProviders")
+    Collection<AuthenticationProviderEntity> authenticationProviders = new ArrayList<AuthenticationProviderEntity>();
+
     @OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
     Collection<ApplicationEntity> applications = new ArrayList<ApplicationEntity>();
 
@@ -81,6 +85,12 @@ public class RealmEntity {
     @CollectionTable
     protected Map<String, String> socialConfig = new HashMap<String, String>();
 
+    @ElementCollection
+    @MapKeyColumn(name="name")
+    @Column(name="value")
+    @CollectionTable
+    protected Map<String, String> ldapServerConfig = new HashMap<String, String>();
+
     @OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true)
     @JoinTable(name="RealmDefaultRoles")
     Collection<RoleEntity> defaultRoles = new ArrayList<RoleEntity>();
@@ -229,6 +239,14 @@ public class RealmEntity {
         this.requiredCredentials = requiredCredentials;
     }
 
+    public Collection<AuthenticationProviderEntity> getAuthenticationProviders() {
+        return authenticationProviders;
+    }
+
+    public void setAuthenticationProviders(Collection<AuthenticationProviderEntity> authenticationProviders) {
+        this.authenticationProviders = authenticationProviders;
+    }
+
     public Collection<ApplicationEntity> getApplications() {
         return applications;
     }
@@ -268,6 +286,14 @@ public class RealmEntity {
         this.socialConfig = socialConfig;
     }
 
+    public Map<String, String> getLdapServerConfig() {
+        return ldapServerConfig;
+    }
+
+    public void setLdapServerConfig(Map<String, String> ldapServerConfig) {
+        this.ldapServerConfig = ldapServerConfig;
+    }
+
     public Collection<RoleEntity> getDefaultRoles() {
         return defaultRoles;
     }
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 465f717..17c5a1a 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
@@ -1,9 +1,13 @@
 package org.keycloak.models.jpa;
 
+import org.keycloak.models.AuthenticationLinkModel;
+import org.keycloak.models.AuthenticationProviderModel;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.RoleContainerModel;
 import org.keycloak.models.jpa.entities.ApplicationEntity;
 import org.keycloak.models.jpa.entities.ApplicationRoleEntity;
+import org.keycloak.models.jpa.entities.AuthenticationLinkEntity;
+import org.keycloak.models.jpa.entities.AuthenticationProviderEntity;
 import org.keycloak.models.jpa.entities.CredentialEntity;
 import org.keycloak.models.jpa.entities.OAuthClientEntity;
 import org.keycloak.models.jpa.entities.RealmEntity;
@@ -389,6 +393,7 @@ public class RealmAdapter implements RealmModel {
     private void removeUser(UserEntity user) {
         em.createQuery("delete from " + UserRoleMappingEntity.class.getSimpleName() + " where user = :user").setParameter("user", user).executeUpdate();
         em.createQuery("delete from " + SocialLinkEntity.class.getSimpleName() + " where user = :user").setParameter("user", user).executeUpdate();
+        em.createQuery("delete from " + AuthenticationLinkEntity.class.getSimpleName() + " where user = :user").setParameter("user", user).executeUpdate();
         em.remove(user);
     }
 
@@ -607,6 +612,47 @@ public class RealmAdapter implements RealmModel {
     }
 
     @Override
+    public UserModel getUserByAuthenticationLink(AuthenticationLinkModel authenticationLink) {
+        TypedQuery<UserEntity> query = em.createNamedQuery("findUserByAuthLinkAndRealm", UserEntity.class);
+        query.setParameter("realm", realm);
+        query.setParameter("authProvider", authenticationLink.getAuthProvider());
+        query.setParameter("authUserId", authenticationLink.getAuthUserId());
+        List<UserEntity> results = query.getResultList();
+        if (results.isEmpty()) {
+            return null;
+        } else if (results.size() > 1) {
+            throw new IllegalStateException("More results found for authenticationProvider=" + authenticationLink.getAuthProvider() +
+                    ", authUserId=" + authenticationLink.getAuthUserId() + ", results=" + results);
+        } else {
+            UserEntity user = results.get(0);
+            return new UserAdapter(user);
+        }
+    }
+
+    @Override
+    public Set<AuthenticationLinkModel> getAuthenticationLinks(UserModel user) {
+        TypedQuery<AuthenticationLinkEntity> query = em.createNamedQuery("findAuthLinkByUser", AuthenticationLinkEntity.class);
+        query.setParameter("user", ((UserAdapter) user).getUser());
+        List<AuthenticationLinkEntity> results = query.getResultList();
+        Set<AuthenticationLinkModel> set = new HashSet<AuthenticationLinkModel>();
+        for (AuthenticationLinkEntity entity : results) {
+            set.add(new AuthenticationLinkModel(entity.getAuthProvider(), entity.getAuthUserId()));
+        }
+        return set;
+    }
+
+    @Override
+    public void addAuthenticationLink(UserModel user, AuthenticationLinkModel authenticationLink) {
+        AuthenticationLinkEntity entity = new AuthenticationLinkEntity();
+        entity.setRealm(realm);
+        entity.setAuthProvider(authenticationLink.getAuthProvider());
+        entity.setAuthUserId(authenticationLink.getAuthUserId());
+        entity.setUser(((UserAdapter) user).getUser());
+        em.persist(entity);
+        em.flush();
+    }
+
+    @Override
     public boolean isSocial() {
         return realm.isSocial();
     }
@@ -756,6 +802,56 @@ public class RealmAdapter implements RealmModel {
     }
 
     @Override
+    public Map<String, String> getLdapServerConfig() {
+        return realm.getLdapServerConfig();
+    }
+
+    @Override
+    public void setLdapServerConfig(Map<String, String> ldapServerConfig) {
+        realm.setLdapServerConfig(ldapServerConfig);
+        em.flush();
+    }
+
+    @Override
+    public List<AuthenticationProviderModel> getAuthenticationProviders() {
+        Collection<AuthenticationProviderEntity> entities = realm.getAuthenticationProviders();
+        List<AuthenticationProviderModel> result = new ArrayList<AuthenticationProviderModel>();
+        for (AuthenticationProviderEntity entity : entities) {
+            result.add(new AuthenticationProviderModel(entity.getProviderName(), entity.isPasswordUpdateSupported(), entity.getConfig()));
+        }
+
+        return result;
+    }
+
+    @Override
+    public void setAuthenticationProviders(List<AuthenticationProviderModel> authenticationProviders) {
+        List<AuthenticationProviderEntity> newEntities = new ArrayList<AuthenticationProviderEntity>();
+        for (AuthenticationProviderModel model : authenticationProviders) {
+            AuthenticationProviderEntity entity = new AuthenticationProviderEntity();
+            entity.setProviderName(model.getProviderName());
+            entity.setPasswordUpdateSupported(model.isPasswordUpdateSupported());
+            entity.setConfig(model.getConfig());
+            newEntities.add(entity);
+        }
+
+        // Remove all existing first
+        Collection<AuthenticationProviderEntity> existing = realm.getAuthenticationProviders();
+        Collection<AuthenticationProviderEntity> copy = new ArrayList<AuthenticationProviderEntity>(existing);
+        for (AuthenticationProviderEntity apToRemove : copy) {
+            existing.remove(apToRemove);
+            em.remove(apToRemove);
+        }
+
+        // Now create all new providers
+        for (AuthenticationProviderEntity apToAdd : newEntities) {
+            existing.add(apToAdd);
+            em.persist(apToAdd);
+        }
+
+        em.flush();
+    }
+
+    @Override
     public RoleModel getRole(String name) {
         TypedQuery<RealmRoleEntity> query = em.createNamedQuery("getRealmRoleByName", RealmRoleEntity.class);
         query.setParameter("name", name);
diff --git a/model/jpa/src/test/resources/META-INF/persistence.xml b/model/jpa/src/test/resources/META-INF/persistence.xml
index b6b99bb..e84acc8 100755
--- a/model/jpa/src/test/resources/META-INF/persistence.xml
+++ b/model/jpa/src/test/resources/META-INF/persistence.xml
@@ -10,9 +10,11 @@
         <class>org.keycloak.models.jpa.entities.OAuthClientEntity</class>
         <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.ApplicationRoleEntity</class>
         <class>org.keycloak.models.jpa.entities.RealmRoleEntity</class>
         <class>org.keycloak.models.jpa.entities.SocialLinkEntity</class>
+        <class>org.keycloak.models.jpa.entities.AuthenticationLinkEntity</class>
         <class>org.keycloak.models.jpa.entities.UserEntity</class>
         <class>org.keycloak.models.jpa.entities.UserRoleMappingEntity</class>
         <class>org.keycloak.models.jpa.entities.ScopeMappingEntity</class>
diff --git a/model/mongo/pom.xml b/model/mongo/pom.xml
index 4f9bf32..1436419 100755
--- a/model/mongo/pom.xml
+++ b/model/mongo/pom.xml
@@ -51,6 +51,12 @@
             <groupId>org.keycloak</groupId>
             <artifactId>keycloak-model-tests</artifactId>
             <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-model-tests</artifactId>
+            <version>${project.version}</version>
             <classifier>tests</classifier>
             <scope>test</scope>
         </dependency>
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoKeycloakSessionFactory.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoKeycloakSessionFactory.java
index f1d1913..dcd787d 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoKeycloakSessionFactory.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoKeycloakSessionFactory.java
@@ -8,6 +8,8 @@ import org.keycloak.models.mongo.api.MongoStore;
 import org.keycloak.models.mongo.impl.MongoStoreImpl;
 import org.keycloak.models.mongo.keycloak.config.MongoClientProvider;
 import org.keycloak.models.mongo.keycloak.entities.ApplicationEntity;
+import org.keycloak.models.mongo.keycloak.entities.AuthenticationLinkEntity;
+import org.keycloak.models.mongo.keycloak.entities.AuthenticationProviderEntity;
 import org.keycloak.models.mongo.keycloak.entities.CredentialEntity;
 import org.keycloak.models.mongo.keycloak.entities.OAuthClientEntity;
 import org.keycloak.models.mongo.keycloak.entities.RealmEntity;
@@ -29,8 +31,10 @@ public class MongoKeycloakSessionFactory implements KeycloakSessionFactory {
             UserEntity.class,
             RoleEntity.class,
             RequiredCredentialEntity.class,
+            AuthenticationProviderEntity.class,
             CredentialEntity.class,
             SocialLinkEntity.class,
+            AuthenticationLinkEntity.class,
             ApplicationEntity.class,
             OAuthClientEntity.class
     };
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 90910f9..68a604f 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
@@ -4,6 +4,8 @@ import com.mongodb.DBObject;
 import com.mongodb.QueryBuilder;
 import org.jboss.logging.Logger;
 import org.keycloak.models.ApplicationModel;
+import org.keycloak.models.AuthenticationLinkModel;
+import org.keycloak.models.AuthenticationProviderModel;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.OAuthClientModel;
 import org.keycloak.models.PasswordPolicy;
@@ -15,6 +17,8 @@ import org.keycloak.models.UserCredentialModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext;
 import org.keycloak.models.mongo.keycloak.entities.ApplicationEntity;
+import org.keycloak.models.mongo.keycloak.entities.AuthenticationLinkEntity;
+import org.keycloak.models.mongo.keycloak.entities.AuthenticationProviderEntity;
 import org.keycloak.models.mongo.keycloak.entities.CredentialEntity;
 import org.keycloak.models.mongo.keycloak.entities.OAuthClientEntity;
 import org.keycloak.models.mongo.keycloak.entities.RealmEntity;
@@ -387,7 +391,8 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
 
         UserEntity userEntity = new UserEntity();
         userEntity.setLoginName(username);
-        userEntity.setEnabled(true);
+        // Compatibility with JPA model, which has user disabled by default
+        // userEntity.setEnabled(true);
         userEntity.setRealmId(getId());
 
         getMongoStore().insertEntity(userEntity, invocationContext);
@@ -920,6 +925,44 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
         return null;
     }
 
+    @Override
+    public UserModel getUserByAuthenticationLink(AuthenticationLinkModel authenticationLink) {
+        DBObject query = new QueryBuilder()
+                .and("authenticationLinks.authProvider").is(authenticationLink.getAuthProvider())
+                .and("authenticationLinks.authUserId").is(authenticationLink.getAuthUserId())
+                .and("realmId").is(getId())
+                .get();
+        UserEntity userEntity = getMongoStore().loadSingleEntity(UserEntity.class, query, invocationContext);
+        return userEntity==null ? null : new UserAdapter(userEntity, invocationContext);
+    }
+
+    @Override
+    public Set<AuthenticationLinkModel> getAuthenticationLinks(UserModel user) {
+        UserEntity userEntity = ((UserAdapter)user).getUser();
+        List<AuthenticationLinkEntity> linkEntities = userEntity.getAuthenticationLinks();
+
+        if (linkEntities == null) {
+            return Collections.EMPTY_SET;
+        }
+
+        Set<AuthenticationLinkModel> result = new HashSet<AuthenticationLinkModel>();
+        for (AuthenticationLinkEntity authLinkEntity : linkEntities) {
+            AuthenticationLinkModel model = new AuthenticationLinkModel(authLinkEntity.getAuthProvider(), authLinkEntity.getAuthUserId());
+            result.add(model);
+        }
+        return result;
+    }
+
+    @Override
+    public void addAuthenticationLink(UserModel user, AuthenticationLinkModel authenticationLink) {
+        UserEntity userEntity = ((UserAdapter)user).getUser();
+        AuthenticationLinkEntity authLinkEntity = new AuthenticationLinkEntity();
+        authLinkEntity.setAuthProvider(authenticationLink.getAuthProvider());
+        authLinkEntity.setAuthUserId(authenticationLink.getAuthUserId());
+
+        getMongoStore().pushItemToList(userEntity, "authenticationLinks", authLinkEntity, true, invocationContext);
+    }
+
     protected void updateRealm() {
         super.updateMongoEntity();
     }
@@ -1034,6 +1077,43 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
     }
 
     @Override
+    public Map<String, String> getLdapServerConfig() {
+        return realm.getLdapServerConfig();
+    }
+
+    @Override
+    public void setLdapServerConfig(Map<String, String> ldapServerConfig) {
+        realm.setLdapServerConfig(ldapServerConfig);
+        updateRealm();
+    }
+
+    @Override
+    public List<AuthenticationProviderModel> getAuthenticationProviders() {
+        List<AuthenticationProviderEntity> entities = realm.getAuthenticationProviders();
+        List<AuthenticationProviderModel> result = new ArrayList<AuthenticationProviderModel>();
+        for (AuthenticationProviderEntity entity : entities) {
+            result.add(new AuthenticationProviderModel(entity.getProviderName(), entity.isPasswordUpdateSupported(), entity.getConfig()));
+        }
+
+        return result;
+    }
+
+    @Override
+    public void setAuthenticationProviders(List<AuthenticationProviderModel> authenticationProviders) {
+        List<AuthenticationProviderEntity> entities = new ArrayList<AuthenticationProviderEntity>();
+        for (AuthenticationProviderModel model : authenticationProviders) {
+            AuthenticationProviderEntity entity = new AuthenticationProviderEntity();
+            entity.setProviderName(model.getProviderName());
+            entity.setPasswordUpdateSupported(model.isPasswordUpdateSupported());
+            entity.setConfig(model.getConfig());
+            entities.add(entity);
+        }
+
+        realm.setAuthenticationProviders(entities);
+        updateRealm();
+    }
+
+    @Override
     public RealmEntity getMongoEntity() {
         return realm;
     }
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/AuthenticationLinkEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/AuthenticationLinkEntity.java
new file mode 100644
index 0000000..54ab0a7
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/AuthenticationLinkEntity.java
@@ -0,0 +1,31 @@
+package org.keycloak.models.mongo.keycloak.entities;
+
+import org.keycloak.models.mongo.api.MongoEntity;
+import org.keycloak.models.mongo.api.MongoField;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class AuthenticationLinkEntity implements MongoEntity {
+
+    private String authUserId;
+    private String authProvider;
+
+    @MongoField
+    public String getAuthUserId() {
+        return authUserId;
+    }
+
+    public void setAuthUserId(String authUserId) {
+        this.authUserId = authUserId;
+    }
+
+    @MongoField
+    public String getAuthProvider() {
+        return authProvider;
+    }
+
+    public void setAuthProvider(String authProvider) {
+        this.authProvider = authProvider;
+    }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/AuthenticationProviderEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/AuthenticationProviderEntity.java
new file mode 100644
index 0000000..cc4f2bf
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/AuthenticationProviderEntity.java
@@ -0,0 +1,43 @@
+package org.keycloak.models.mongo.keycloak.entities;
+
+import java.util.Map;
+
+import org.keycloak.models.mongo.api.MongoEntity;
+import org.keycloak.models.mongo.api.MongoField;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class AuthenticationProviderEntity implements MongoEntity {
+
+    private String providerName;
+    private boolean passwordUpdateSupported;
+    private Map<String, String> config;
+
+    @MongoField
+    public String getProviderName() {
+        return providerName;
+    }
+
+    public void setProviderName(String providerName) {
+        this.providerName = providerName;
+    }
+
+    @MongoField
+    public boolean isPasswordUpdateSupported() {
+        return passwordUpdateSupported;
+    }
+
+    public void setPasswordUpdateSupported(boolean passwordUpdateSupported) {
+        this.passwordUpdateSupported = passwordUpdateSupported;
+    }
+
+    @MongoField
+    public Map<String, String> getConfig() {
+        return config;
+    }
+
+    public void setConfig(Map<String, String> config) {
+        this.config = config;
+    }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/RealmEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/RealmEntity.java
index 90f39a8..cf37018 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/RealmEntity.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/RealmEntity.java
@@ -47,9 +47,11 @@ public class RealmEntity extends AbstractMongoIdentifiableEntity implements Mong
     private List<String> defaultRoles = new ArrayList<String>();
 
     private List<RequiredCredentialEntity> requiredCredentials = new ArrayList<RequiredCredentialEntity>();
+    private List<AuthenticationProviderEntity> authenticationProviders = new ArrayList<AuthenticationProviderEntity>();
 
     private Map<String, String> smtpConfig = new HashMap<String, String>();
     private Map<String, String> socialConfig = new HashMap<String, String>();
+    private Map<String, String> ldapServerConfig;
 
     @MongoField
     public String getName() {
@@ -250,6 +252,15 @@ public class RealmEntity extends AbstractMongoIdentifiableEntity implements Mong
     }
 
     @MongoField
+    public List<AuthenticationProviderEntity> getAuthenticationProviders() {
+        return authenticationProviders;
+    }
+
+    public void setAuthenticationProviders(List<AuthenticationProviderEntity> authenticationProviders) {
+        this.authenticationProviders = authenticationProviders;
+    }
+
+    @MongoField
     public Map<String, String> getSmtpConfig() {
         return smtpConfig;
     }
@@ -267,6 +278,15 @@ public class RealmEntity extends AbstractMongoIdentifiableEntity implements Mong
         this.socialConfig = socialConfig;
     }
 
+    @MongoField
+    public Map<String, String> getLdapServerConfig() {
+        return ldapServerConfig;
+    }
+
+    public void setLdapServerConfig(Map<String, String> ldapServerConfig) {
+        this.ldapServerConfig = ldapServerConfig;
+    }
+
     @Override
     public void afterRemove(MongoStoreInvocationContext context) {
         DBObject query = new QueryBuilder()
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/SocialLinkEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/SocialLinkEntity.java
index 85afa11..a000412 100644
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/SocialLinkEntity.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/SocialLinkEntity.java
@@ -58,10 +58,10 @@ public class SocialLinkEntity implements MongoEntity {
     public int hashCode() {
         int code = 1;
         if (socialUserId != null) {
-            code = code * 13;
+            code = code * socialUserId.hashCode() * 13;
         }
         if (socialProvider != null) {
-            code = code * 17;
+            code = code * socialProvider.hashCode() * 17;
         }
         return code;
     }
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/UserEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/UserEntity.java
index a2f8692..f0539df 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/UserEntity.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/UserEntity.java
@@ -33,6 +33,7 @@ public class UserEntity extends AbstractMongoIdentifiableEntity implements Mongo
     private List<UserModel.RequiredAction> requiredActions;
     private List<CredentialEntity> credentials = new ArrayList<CredentialEntity>();
     private List<SocialLinkEntity> socialLinks;
+    private List<AuthenticationLinkEntity> authenticationLinks;
 
     @MongoField
     public String getLoginName() {
@@ -160,4 +161,13 @@ public class UserEntity extends AbstractMongoIdentifiableEntity implements Mongo
     public void setSocialLinks(List<SocialLinkEntity> socialLinks) {
         this.socialLinks = socialLinks;
     }
+
+    @MongoField
+    public List<AuthenticationLinkEntity> getAuthenticationLinks() {
+        return authenticationLinks;
+    }
+
+    public void setAuthenticationLinks(List<AuthenticationLinkEntity> authenticationLinks) {
+        this.authenticationLinks = authenticationLinks;
+    }
 }

model/tests/pom.xml 62(+62 -0)

diff --git a/model/tests/pom.xml b/model/tests/pom.xml
index 57cbe95..3ce037f 100755
--- a/model/tests/pom.xml
+++ b/model/tests/pom.xml
@@ -22,6 +22,24 @@
             <scope>compile</scope>
         </dependency>
         <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-authentication-spi</artifactId>
+            <version>${project.version}</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-authentication-model</artifactId>
+            <version>${project.version}</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-authentication-picketlink</artifactId>
+            <version>${project.version}</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
             <groupId>junit</groupId>
             <artifactId>junit</artifactId>
             <scope>compile</scope>
@@ -36,6 +54,50 @@
             <artifactId>jackson-mapper-asl</artifactId>
             <scope>compile</scope>
         </dependency>
+
+        <!-- picketlink dependencies -->
+        <dependency>
+            <groupId>org.picketlink</groupId>
+            <artifactId>picketlink-common</artifactId>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.picketlink</groupId>
+            <artifactId>picketlink-idm-api</artifactId>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.picketlink</groupId>
+            <artifactId>picketlink-idm-impl</artifactId>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.picketlink</groupId>
+            <artifactId>picketlink-idm-simple-schema</artifactId>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.picketbox</groupId>
+            <artifactId>picketbox-ldap</artifactId>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.picketbox</groupId>
+            <artifactId>picketbox-ldap</artifactId>
+            <scope>compile</scope>
+            <type>test-jar</type>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-simple</artifactId>
+            <scope>compile</scope>
+        </dependency>
+
     </dependencies>
 
     <build>
diff --git a/model/tests/src/main/java/org/keycloak/model/test/LDAPEmbeddedServer.java b/model/tests/src/main/java/org/keycloak/model/test/LDAPEmbeddedServer.java
new file mode 100644
index 0000000..14663de
--- /dev/null
+++ b/model/tests/src/main/java/org/keycloak/model/test/LDAPEmbeddedServer.java
@@ -0,0 +1,210 @@
+package org.keycloak.model.test;
+
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.Properties;
+
+import javax.naming.CompositeName;
+import javax.naming.Context;
+import javax.naming.ContextNotEmptyException;
+import javax.naming.Name;
+import javax.naming.NameClassPair;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.InitialDirContext;
+
+import org.keycloak.models.RealmModel;
+import org.keycloak.spi.picketlink.impl.LdapConstants;
+import org.picketbox.test.ldap.AbstractLDAPTest;
+
+/**
+ * Forked from Picketlink project
+ *
+ * Abstract base for all LDAP test suites. It handles
+ * @author Peter Skopek: pskopek at redhat dot com
+ *
+ */
+public class LDAPEmbeddedServer extends AbstractLDAPTest {
+
+    public static final String BASE_DN = "dc=keycloak,dc=org";
+    public static final String LDAP_URL = "ldap://localhost:10389";
+    public static final String ROLES_DN_SUFFIX = "ou=Roles,dc=keycloak,dc=org";
+    public static final String GROUP_DN_SUFFIX = "ou=Groups,dc=keycloak,dc=org";
+    public static final String USER_DN_SUFFIX = "ou=People,dc=keycloak,dc=org";
+    public static final String AGENT_DN_SUFFIX = "ou=Agent,dc=keycloak,dc=org";
+    public static final String CUSTOM_ACCOUNT_DN_SUFFIX = "ou=CustomAccount,dc=keycloak,dc=org";
+
+    public static final String CONNECTION_PROPERTIES = "ldap/ldap-connection.properties";
+
+    protected String connectionUrl = LDAP_URL;
+    protected String baseDn = BASE_DN;
+    protected String userDnSuffix = USER_DN_SUFFIX;
+    protected String rolesDnSuffix = ROLES_DN_SUFFIX;
+    protected String groupDnSuffix = GROUP_DN_SUFFIX;
+    protected String agentDnSuffix = AGENT_DN_SUFFIX;
+    protected boolean startEmbeddedLdapLerver = true;
+    protected String bindDn = "uid=admin,ou=system";
+    protected String bindCredential = "secret";
+
+    public static String IDM_TEST_LDAP_CONNECTION_URL = "idm.test.ldap.connection.url";
+    public static String IDM_TEST_LDAP_BASE_DN = "idm.test.ldap.base.dn";
+    public static String IDM_TEST_LDAP_ROLES_DN_SUFFIX = "idm.test.ldap.roles.dn.suffix";
+    public static String IDM_TEST_LDAP_GROUP_DN_SUFFIX = "idm.test.ldap.group.dn.suffix";
+    public static String IDM_TEST_LDAP_USER_DN_SUFFIX = "idm.test.ldap.user.dn.suffix";
+    public static String IDM_TEST_LDAP_AGENT_DN_SUFFIX = "idm.test.ldap.agent.dn.suffix";
+    public static String IDM_TEST_LDAP_START_EMBEDDED_LDAP_SERVER = "idm.test.ldap.start.embedded.ldap.server";
+    public static String IDM_TEST_LDAP_BIND_DN = "idm.test.ldap.bind.dn";
+    public static String IDM_TEST_LDAP_BIND_CREDENTIAL = "idm.test.ldap.bind.credential";
+
+
+    public LDAPEmbeddedServer() {
+        super();
+        loadConnectionProperties();
+    }
+
+    protected void loadConnectionProperties() {
+        Properties p = new Properties();
+        try {
+            InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(CONNECTION_PROPERTIES);
+            p.load(is);
+        }
+        catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+
+        connectionUrl = p.getProperty(IDM_TEST_LDAP_CONNECTION_URL, LDAP_URL);
+        baseDn = p.getProperty(IDM_TEST_LDAP_BASE_DN, BASE_DN);
+        userDnSuffix = p.getProperty(IDM_TEST_LDAP_USER_DN_SUFFIX, USER_DN_SUFFIX);
+        rolesDnSuffix = p.getProperty(IDM_TEST_LDAP_ROLES_DN_SUFFIX, ROLES_DN_SUFFIX);
+        groupDnSuffix = p.getProperty(IDM_TEST_LDAP_GROUP_DN_SUFFIX, GROUP_DN_SUFFIX);
+        agentDnSuffix = p.getProperty(IDM_TEST_LDAP_AGENT_DN_SUFFIX, AGENT_DN_SUFFIX);
+        startEmbeddedLdapLerver = Boolean.parseBoolean(p.getProperty(IDM_TEST_LDAP_START_EMBEDDED_LDAP_SERVER, "true"));
+        bindDn = p.getProperty(IDM_TEST_LDAP_BIND_DN, bindDn);
+        bindCredential = p.getProperty(IDM_TEST_LDAP_BIND_CREDENTIAL, bindCredential);
+    }
+
+    @Override
+    public void setup() throws Exception {
+        // suppress emb. LDAP server start
+        if (isStartEmbeddedLdapLerver()) {
+            super.setup();
+        }
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+
+        // clear data left in LDAP
+        DirContext ctx = getDirContext();
+        clearSubContexts(ctx, new CompositeName(baseDn));
+
+        // suppress emb. LDAP server stop
+        if (isStartEmbeddedLdapLerver()) {
+            super.tearDown();
+        }
+    }
+
+    private DirContext getDirContext() throws NamingException {
+        Hashtable<String, String> env = new Hashtable<String, String>();
+        env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
+        env.put(Context.PROVIDER_URL, connectionUrl);
+        env.put(Context.SECURITY_PRINCIPAL, bindDn);
+        env.put(Context.SECURITY_CREDENTIALS, bindCredential);
+        DirContext ctx = new InitialDirContext(env);
+        return ctx;
+    }
+
+    public void setupLdapInRealm(RealmModel realm) {
+        Map<String,String> ldapConfig = new HashMap<String,String>();
+        ldapConfig.put(LdapConstants.CONNECTION_URL, getConnectionUrl());
+        ldapConfig.put(LdapConstants.BASE_DN, getBaseDn());
+        ldapConfig.put(LdapConstants.BIND_DN, getBindDn());
+        ldapConfig.put(LdapConstants.BIND_CREDENTIAL, getBindCredential());
+        ldapConfig.put(LdapConstants.USER_DN_SUFFIX, getUserDnSuffix());
+        realm.setLdapServerConfig(ldapConfig);
+    }
+
+
+    public static void clearSubContexts(DirContext ctx, Name name) throws NamingException {
+
+        NamingEnumeration<NameClassPair> enumeration = null;
+        try {
+            enumeration = ctx.list(name);
+            while (enumeration.hasMore()) {
+                NameClassPair pair = enumeration.next();
+                Name childName = ctx.composeName(new CompositeName(pair.getName()), name);
+                try {
+                    ctx.destroySubcontext(childName);
+                }
+                catch (ContextNotEmptyException e) {
+                    clearSubContexts(ctx, childName);
+                    ctx.destroySubcontext(childName);
+                }
+            }
+        }
+        catch (NamingException e) {
+            e.printStackTrace();
+        }
+        finally {
+            try {
+                enumeration.close();
+            }
+            catch (Exception e) {
+                // Never mind this
+            }
+        }
+    }
+
+    public String getConnectionUrl() {
+        return connectionUrl;
+    }
+
+    public String getBaseDn() {
+        return baseDn;
+    }
+
+    public String getUserDnSuffix() {
+        return userDnSuffix;
+    }
+
+    public String getRolesDnSuffix() {
+        return rolesDnSuffix;
+    }
+
+    public String getGroupDnSuffix() {
+        return groupDnSuffix;
+    }
+
+    public String getAgentDnSuffix() {
+        return agentDnSuffix;
+    }
+
+    public boolean isStartEmbeddedLdapLerver() {
+        return startEmbeddedLdapLerver;
+    }
+
+    public String getBindDn() {
+        return bindDn;
+    }
+
+    public String getBindCredential() {
+        return bindCredential;
+    }
+
+    @Override
+    public void importLDIF(String fileName) throws Exception {
+        // import LDIF only in case we are running against embedded LDAP server
+        if (isStartEmbeddedLdapLerver()) {
+            super.importLDIF(fileName);
+        }
+    }
+
+    @Override
+    protected void createBaseDN() throws Exception {
+        ds.createBaseDN("keycloak", "dc=keycloak,dc=org");
+    }
+
+}
diff --git a/model/tests/src/test/java/org/keycloak/model/test/AbstractModelTest.java b/model/tests/src/test/java/org/keycloak/model/test/AbstractModelTest.java
index 4846b57..f9f366d 100644
--- a/model/tests/src/test/java/org/keycloak/model/test/AbstractModelTest.java
+++ b/model/tests/src/test/java/org/keycloak/model/test/AbstractModelTest.java
@@ -23,8 +23,6 @@ import org.keycloak.util.JsonSerialization;
  */
 public class AbstractModelTest {
 
-    private final Logger log = Logger.getLogger(getClass());
-
     protected KeycloakSessionFactory factory;
     protected KeycloakSession identitySession;
     protected RealmManager realmManager;
diff --git a/model/tests/src/test/java/org/keycloak/model/test/ApplicationModelTest.java b/model/tests/src/test/java/org/keycloak/model/test/ApplicationModelTest.java
index ba7a03b..4d94084 100755
--- a/model/tests/src/test/java/org/keycloak/model/test/ApplicationModelTest.java
+++ b/model/tests/src/test/java/org/keycloak/model/test/ApplicationModelTest.java
@@ -22,6 +22,7 @@ public class ApplicationModelTest extends AbstractModelTest {
     private ApplicationManager appManager;
 
     @Before
+    @Override
     public void before() throws Exception {
         super.before();
         appManager = new ApplicationManager(realmManager);
diff --git a/model/tests/src/test/java/org/keycloak/model/test/AuthenticationManagerTest.java b/model/tests/src/test/java/org/keycloak/model/test/AuthenticationManagerTest.java
index 3faddc9..513c119 100755
--- a/model/tests/src/test/java/org/keycloak/model/test/AuthenticationManagerTest.java
+++ b/model/tests/src/test/java/org/keycloak/model/test/AuthenticationManagerTest.java
@@ -26,7 +26,7 @@ public class AuthenticationManagerTest extends AbstractModelTest {
 
     @Test
     public void authForm() {
-        AuthenticationStatus status = am.authenticateForm(realm, user, formData);
+        AuthenticationStatus status = am.authenticateForm(realm, formData);
         Assert.assertEquals(AuthenticationStatus.SUCCESS, status);
     }
 
@@ -35,15 +35,23 @@ public class AuthenticationManagerTest extends AbstractModelTest {
         formData.remove(CredentialRepresentation.PASSWORD);
         formData.add(CredentialRepresentation.PASSWORD, "invalid");
 
-        AuthenticationStatus status = am.authenticateForm(realm, user, formData);
+        AuthenticationStatus status = am.authenticateForm(realm, formData);
         Assert.assertEquals(AuthenticationStatus.INVALID_CREDENTIALS, status);
     }
 
     @Test
+    public void authFormMissingUsername() {
+        formData.remove("username");
+
+        AuthenticationStatus status = am.authenticateForm(realm, formData);
+        Assert.assertEquals(AuthenticationStatus.INVALID_USER, status);
+    }
+
+    @Test
     public void authFormMissingPassword() {
         formData.remove(CredentialRepresentation.PASSWORD);
 
-        AuthenticationStatus status = am.authenticateForm(realm, user, formData);
+        AuthenticationStatus status = am.authenticateForm(realm, formData);
         Assert.assertEquals(AuthenticationStatus.MISSING_PASSWORD, status);
     }
 
@@ -52,7 +60,7 @@ public class AuthenticationManagerTest extends AbstractModelTest {
         realm.addRequiredCredential(CredentialRepresentation.TOTP);
         user.addRequiredAction(RequiredAction.CONFIGURE_TOTP);
         
-        AuthenticationStatus status = am.authenticateForm(realm, user, formData);
+        AuthenticationStatus status = am.authenticateForm(realm, formData);
         Assert.assertEquals(AuthenticationStatus.ACTIONS_REQUIRED, status);
     }
 
@@ -60,7 +68,7 @@ public class AuthenticationManagerTest extends AbstractModelTest {
     public void authFormUserDisabled() {
         user.setEnabled(false);
 
-        AuthenticationStatus status = am.authenticateForm(realm, user, formData);
+        AuthenticationStatus status = am.authenticateForm(realm, formData);
         Assert.assertEquals(AuthenticationStatus.ACCOUNT_DISABLED, status);
     }
 
@@ -82,7 +90,7 @@ public class AuthenticationManagerTest extends AbstractModelTest {
 
         formData.add(CredentialRepresentation.TOTP, token);
 
-        AuthenticationStatus status = am.authenticateForm(realm, user, formData);
+        AuthenticationStatus status = am.authenticateForm(realm, formData);
         Assert.assertEquals(AuthenticationStatus.SUCCESS, status);
     }
 
@@ -93,7 +101,7 @@ public class AuthenticationManagerTest extends AbstractModelTest {
         formData.remove(CredentialRepresentation.PASSWORD);
         formData.add(CredentialRepresentation.PASSWORD, "invalid");
 
-        AuthenticationStatus status = am.authenticateForm(realm, user, formData);
+        AuthenticationStatus status = am.authenticateForm(realm, formData);
         Assert.assertEquals(AuthenticationStatus.INVALID_CREDENTIALS, status);
     }
 
@@ -104,7 +112,7 @@ public class AuthenticationManagerTest extends AbstractModelTest {
         formData.remove(CredentialRepresentation.TOTP);
         formData.add(CredentialRepresentation.TOTP, "invalid");
 
-        AuthenticationStatus status = am.authenticateForm(realm, user, formData);
+        AuthenticationStatus status = am.authenticateForm(realm, formData);
         Assert.assertEquals(AuthenticationStatus.INVALID_CREDENTIALS, status);
     }
 
@@ -114,11 +122,12 @@ public class AuthenticationManagerTest extends AbstractModelTest {
 
         formData.remove(CredentialRepresentation.TOTP);
 
-        AuthenticationStatus status = am.authenticateForm(realm, user, formData);
+        AuthenticationStatus status = am.authenticateForm(realm, formData);
         Assert.assertEquals(AuthenticationStatus.MISSING_TOTP, status);
     }
 
     @Before
+    @Override
     public void before() throws Exception {
         super.before();
         realm = realmManager.createRealm("Test");
@@ -142,6 +151,7 @@ public class AuthenticationManagerTest extends AbstractModelTest {
         realm.updateCredential(user, credential);
 
         formData = new MultivaluedHashMap<String, String>();
+        formData.add("username", "test");
         formData.add(CredentialRepresentation.PASSWORD, "password");
 
         otp = new TimeBasedOTP();
diff --git a/model/tests/src/test/java/org/keycloak/model/test/AuthProvidersConfigTest.java b/model/tests/src/test/java/org/keycloak/model/test/AuthProvidersConfigTest.java
new file mode 100644
index 0000000..0fdcea5
--- /dev/null
+++ b/model/tests/src/test/java/org/keycloak/model/test/AuthProvidersConfigTest.java
@@ -0,0 +1,76 @@
+package org.keycloak.model.test;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.keycloak.models.AuthenticationProviderModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.spi.authentication.AuthProviderConstants;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class AuthProvidersConfigTest extends AbstractModelTest {
+
+    @Test
+    public void testConfiguration() {
+        // Create realm and add some providers and ldap config. Then commit
+        RealmModel realm = realmManager.createRealm("test");
+
+        Map<String, String> ldapConfig = new HashMap<String,String>();
+        ldapConfig.put("connectionUrl", "ldap://localhost:10389");
+        ldapConfig.put("baseDn", "dc=keycloak,dc=org");
+        realm.setLdapServerConfig(ldapConfig);
+
+        AuthenticationProviderModel ap1 = new AuthenticationProviderModel(AuthProviderConstants.PROVIDER_NAME_MODEL, true, Collections.EMPTY_MAP);
+        AuthenticationProviderModel ap2 = new AuthenticationProviderModel(AuthProviderConstants.PROVIDER_NAME_EXTERNAL_MODEL, true, Collections.EMPTY_MAP);
+        AuthenticationProviderModel ap3 = new AuthenticationProviderModel(AuthProviderConstants.PROVIDER_NAME_PICKETLINK, true, Collections.EMPTY_MAP);
+
+        List<AuthenticationProviderModel> authProviders = new ArrayList<AuthenticationProviderModel>();
+        authProviders.add(ap1);
+        authProviders.add(ap2);
+        authProviders.add(ap3);
+        realm.setAuthenticationProviders(authProviders);
+
+        commit();
+
+        // Assert ldap config are same
+        RealmModel persisted = realmManager.getRealm(realm.getId());
+        Assert.assertEquals(persisted.getLdapServerConfig(), ldapConfig);
+
+        // Assert providers are same and in same order
+        List<AuthenticationProviderModel> persProviders = persisted.getAuthenticationProviders();
+        Assert.assertEquals(persProviders.size(), 3);
+        assertProviderEquals(persProviders.get(0), ap1);
+        assertProviderEquals(persProviders.get(1), ap2);
+        assertProviderEquals(persProviders.get(2), ap3);
+
+        // Update providers
+        authProviders = new ArrayList<AuthenticationProviderModel>();
+        authProviders.add(ap3);
+        authProviders.add(ap2);
+        authProviders.add(ap1);
+        persisted.setAuthenticationProviders(authProviders);
+
+        commit();
+
+        // Assert providers are same and in same order
+        persisted = realmManager.getRealm(realm.getId());
+        persProviders = persisted.getAuthenticationProviders();
+        Assert.assertEquals(persProviders.size(), 3);
+        assertProviderEquals(persProviders.get(0), ap3);
+        assertProviderEquals(persProviders.get(1), ap2);
+        assertProviderEquals(persProviders.get(2), ap1);
+    }
+
+    private void assertProviderEquals(AuthenticationProviderModel prov1, AuthenticationProviderModel prov2) {
+        Assert.assertEquals(prov1.getProviderName(), prov2.getProviderName());
+        Assert.assertEquals(prov1.isPasswordUpdateSupported(), prov2.isPasswordUpdateSupported());
+        Assert.assertEquals(prov1.getConfig(), prov2.getConfig());
+    }
+}
diff --git a/model/tests/src/test/java/org/keycloak/model/test/AuthProvidersExternalModelTest.java b/model/tests/src/test/java/org/keycloak/model/test/AuthProvidersExternalModelTest.java
new file mode 100644
index 0000000..41b1071
--- /dev/null
+++ b/model/tests/src/test/java/org/keycloak/model/test/AuthProvidersExternalModelTest.java
@@ -0,0 +1,185 @@
+package org.keycloak.model.test;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.ws.rs.core.MultivaluedHashMap;
+import javax.ws.rs.core.MultivaluedMap;
+
+import org.jboss.resteasy.spi.ResteasyProviderFactory;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.models.AuthenticationLinkModel;
+import org.keycloak.models.AuthenticationProviderModel;
+import org.keycloak.models.KeycloakSession;
+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.AuthenticationManager;
+import org.keycloak.spi.authentication.AuthProviderConstants;
+import org.keycloak.spi.authentication.AuthenticationProviderException;
+import org.keycloak.spi.authentication.AuthenticationProviderManager;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class AuthProvidersExternalModelTest extends AbstractModelTest {
+
+    private RealmModel realm1;
+    private RealmModel realm2;
+    private AuthenticationManager am;
+
+    @Before
+    @Override
+    public void  before() throws Exception {
+        super.before();
+
+        // Create 2 realms and user in realm1
+        realm1 = realmManager.createRealm("realm1");
+        realm2 = realmManager.createRealm("realm2");
+        realm1.addRequiredCredential(CredentialRepresentation.PASSWORD);
+        realm2.addRequiredCredential(CredentialRepresentation.PASSWORD);
+
+        UserModel john = realm1.addUser("john");
+        john.setEnabled(true);
+        john.setFirstName("John");
+        john.setLastName("Doe");
+        john.setEmail("john@email.org");
+
+        UserCredentialModel credential = new UserCredentialModel();
+        credential.setType(CredentialRepresentation.PASSWORD);
+        credential.setValue("password");
+        realm1.updateCredential(john, credential);
+
+        am = new AuthenticationManager();
+    }
+
+
+    @Test
+    public void testExternalModelPasswordValidation() {
+        MultivaluedMap<String, String> formData = createFormData("john", "password");
+
+        // Authenticate user with realm1
+        Assert.assertEquals(AuthenticationManager.AuthenticationStatus.SUCCESS, am.authenticateForm(realm1, formData));
+
+        // Verify that user doesn't exists in realm2 and can't authenticate here
+        Assert.assertEquals(AuthenticationManager.AuthenticationStatus.INVALID_CREDENTIALS, am.authenticateForm(realm2, formData));
+        Assert.assertNull(realm2.getUser("john"));
+
+        // Add externalModel authenticationProvider into realm2 and point to realm1
+        setupAuthenticationProviders();
+
+        try {
+            // this is needed for externalModel provider
+            ResteasyProviderFactory.pushContext(KeycloakSession.class, identitySession);
+
+            // Authenticate john in realm2 and verify that now he exists here.
+            Assert.assertEquals(AuthenticationManager.AuthenticationStatus.SUCCESS, am.authenticateForm(realm2, formData));
+            UserModel john2 = realm2.getUser("john");
+            Assert.assertNotNull(john2);
+            Assert.assertEquals("john", john2.getLoginName());
+            Assert.assertEquals("John", john2.getFirstName());
+            Assert.assertEquals("Doe", john2.getLastName());
+            Assert.assertEquals("john@email.org", john2.getEmail());
+
+            // Verify link exists
+            Set<AuthenticationLinkModel> authLinks = realm2.getAuthenticationLinks(john2);
+            Assert.assertEquals(1, authLinks.size());
+            AuthenticationLinkModel authLink = authLinks.iterator().next();
+            Assert.assertEquals(authLink.getAuthProvider(), AuthProviderConstants.PROVIDER_NAME_EXTERNAL_MODEL);
+        } finally {
+            ResteasyProviderFactory.clearContextData();
+        }
+
+    }
+
+    @Test
+    public void testExternalModelPasswordUpdate() {
+        // Add externalModel authenticationProvider into realm2 and point to realm1
+        setupAuthenticationProviders();
+
+        try {
+            // this is needed for externalModel provider
+            ResteasyProviderFactory.pushContext(KeycloakSession.class, identitySession);
+
+            // Change credential via realm2 and validate that they are changed in both realms
+            AuthenticationProviderManager authProviderManager = AuthenticationProviderManager.getManager(realm2);
+            try {
+                authProviderManager.updatePassword("john", "password-updated");
+            } catch (AuthenticationProviderException ape) {
+                ape.printStackTrace();
+                Assert.fail("Error not expected");
+            }
+            MultivaluedMap<String, String> formData = createFormData("john", "password-updated");
+            Assert.assertEquals(AuthenticationManager.AuthenticationStatus.SUCCESS, am.authenticateForm(realm1, formData));
+            Assert.assertEquals(AuthenticationManager.AuthenticationStatus.SUCCESS, am.authenticateForm(realm2, formData));
+
+
+            // Switch to disallow password update propagation to realm1
+            setPasswordUpdateForProvider(false, AuthProviderConstants.PROVIDER_NAME_EXTERNAL_MODEL, realm2);
+
+            // Change credential and validate that password is updated just for realm2
+            try {
+                authProviderManager.updatePassword("john", "password-updated2");
+            } catch (AuthenticationProviderException ape) {
+                ape.printStackTrace();
+                Assert.fail("Error not expected");
+            }
+            formData = createFormData("john", "password-updated2");
+            Assert.assertEquals(AuthenticationManager.AuthenticationStatus.INVALID_CREDENTIALS, am.authenticateForm(realm1, formData));
+            Assert.assertEquals(AuthenticationManager.AuthenticationStatus.SUCCESS, am.authenticateForm(realm2, formData));
+
+
+            // Allow passwordUpdate propagation again
+            setPasswordUpdateForProvider(true, AuthProviderConstants.PROVIDER_NAME_EXTERNAL_MODEL, realm2);
+
+            // Set passwordPolicy for realm1 and verify that password update fail
+            realm1.setPasswordPolicy(new PasswordPolicy("length(8)"));
+            try {
+                authProviderManager.updatePassword("john", "passw");
+                Assert.fail("Update not expected to pass");
+            } catch (AuthenticationProviderException ape) {
+
+            }
+
+        } finally {
+            ResteasyProviderFactory.clearContextData();
+        }
+    }
+
+    /**
+     * Setup authentication providers into realm2
+     */
+    private void setupAuthenticationProviders() {
+        AuthenticationProviderModel ap1 = new AuthenticationProviderModel(AuthProviderConstants.PROVIDER_NAME_MODEL, true, Collections.EMPTY_MAP);
+        Map<String,String> config = new HashMap<String,String>();
+        config.put(AuthProviderConstants.EXTERNAL_REALM_ID, "realm1");
+        AuthenticationProviderModel ap2 = new AuthenticationProviderModel(AuthProviderConstants.PROVIDER_NAME_EXTERNAL_MODEL, true, config);
+        realm2.setAuthenticationProviders(Arrays.asList(ap1, ap2));
+    }
+
+    public static void setPasswordUpdateForProvider(boolean isPasswordUpdate, String providerName, RealmModel realm) {
+        List<AuthenticationProviderModel> authProviders = realm.getAuthenticationProviders();
+        for (AuthenticationProviderModel authProvider : authProviders) {
+            if (providerName.equals(authProvider.getProviderName())) {
+                authProvider.setPasswordUpdateSupported(isPasswordUpdate);
+                break;
+            }
+        }
+        realm.setAuthenticationProviders(authProviders);
+    }
+
+    public static MultivaluedMap<String, String> createFormData(String username, String password) {
+        MultivaluedMap<String, String> formData = new MultivaluedHashMap<String, String>();
+        formData.add("username", username);
+        formData.add(CredentialRepresentation.PASSWORD, password);
+        return formData;
+    }
+}
diff --git a/model/tests/src/test/java/org/keycloak/model/test/AuthProvidersLDAPTest.java b/model/tests/src/test/java/org/keycloak/model/test/AuthProvidersLDAPTest.java
new file mode 100644
index 0000000..2165e2f
--- /dev/null
+++ b/model/tests/src/test/java/org/keycloak/model/test/AuthProvidersLDAPTest.java
@@ -0,0 +1,152 @@
+package org.keycloak.model.test;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Set;
+
+import javax.ws.rs.core.MultivaluedMap;
+
+import org.jboss.resteasy.spi.ResteasyProviderFactory;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.models.AuthenticationLinkModel;
+import org.keycloak.models.AuthenticationProviderModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.representations.idm.CredentialRepresentation;
+import org.keycloak.services.managers.AuthenticationManager;
+import org.keycloak.spi.authentication.AuthProviderConstants;
+import org.keycloak.spi.authentication.AuthenticationProviderException;
+import org.keycloak.spi.authentication.AuthenticationProviderManager;
+import org.keycloak.util.KeycloakRegistry;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class AuthProvidersLDAPTest extends AbstractModelTest {
+
+    private RealmModel realm;
+    private AuthenticationManager am;
+    private LDAPEmbeddedServer embeddedServer;
+
+    @Before
+    @Override
+    public void before() throws Exception {
+        super.before();
+
+        try {
+            this.embeddedServer = new LDAPEmbeddedServer();
+            this.embeddedServer.setup();
+            this.embeddedServer.importLDIF("ldap/users.ldif");
+        } catch (Exception e) {
+            throw new RuntimeException("Error starting Embedded LDAP server.", e);
+        }
+
+        // Create realm and configure ldap
+        realm = realmManager.createRealm("realm");
+        realm.addRequiredCredential(CredentialRepresentation.PASSWORD);
+        this.embeddedServer.setupLdapInRealm(realm);
+
+        am = new AuthenticationManager();
+    }
+
+    @After
+    @Override
+    public void after() throws Exception {
+        super.after();
+        try {
+            this.embeddedServer.tearDown();
+        } catch (Exception e) {
+            throw new RuntimeException("Error starting Embedded LDAP server.", e);
+        }
+    }
+
+    @Test
+    public void testLdapPasswordValidation() {
+        MultivaluedMap<String, String> formData = AuthProvidersExternalModelTest.createFormData("john", "password");
+
+        // Verify that user doesn't exists in realm2 and can't authenticate here
+        Assert.assertEquals(AuthenticationManager.AuthenticationStatus.INVALID_CREDENTIALS, am.authenticateForm(realm, formData));
+        Assert.assertNull(realm.getUser("john"));
+
+        // Add ldap authenticationProvider
+        setupAuthenticationProviders();
+
+        try {
+            // this is needed for Picketlink model provider
+            ResteasyProviderFactory.pushContext(KeycloakRegistry.class, new KeycloakRegistry());
+
+            // Authenticate john and verify that now he exists in realm
+            Assert.assertEquals(AuthenticationManager.AuthenticationStatus.SUCCESS, am.authenticateForm(realm, formData));
+            UserModel john = realm.getUser("john");
+            Assert.assertNotNull(john);
+            Assert.assertEquals("john", john.getLoginName());
+            Assert.assertEquals("John", john.getFirstName());
+            Assert.assertEquals("Doe", john.getLastName());
+            Assert.assertEquals("john@email.org", john.getEmail());
+
+            formData = AuthProvidersExternalModelTest.createFormData("john", "invalid");
+            Assert.assertEquals(AuthenticationManager.AuthenticationStatus.INVALID_CREDENTIALS, am.authenticateForm(realm, formData));
+
+            // Verify link exists
+            Set<AuthenticationLinkModel> authLinks = realm.getAuthenticationLinks(john);
+            Assert.assertEquals(1, authLinks.size());
+            AuthenticationLinkModel authLink = authLinks.iterator().next();
+            Assert.assertEquals(authLink.getAuthProvider(), AuthProviderConstants.PROVIDER_NAME_PICKETLINK);
+        } finally {
+            ResteasyProviderFactory.clearContextData();
+        }
+
+    }
+
+    @Test
+    public void testLdapPasswordUpdate() {
+        // Add ldap
+        setupAuthenticationProviders();
+
+        try {
+            // this is needed for ldap provider
+            ResteasyProviderFactory.pushContext(KeycloakRegistry.class, new KeycloakRegistry());
+
+            // Change credential and validate that user can authenticate
+            AuthenticationProviderManager authProviderManager = AuthenticationProviderManager.getManager(realm);
+            try {
+                authProviderManager.updatePassword("john", "password-updated");
+            } catch (AuthenticationProviderException ape) {
+                ape.printStackTrace();
+                Assert.fail("Error not expected");
+            }
+            MultivaluedMap<String, String> formData = AuthProvidersExternalModelTest.createFormData("john", "password-updated");
+            Assert.assertEquals(AuthenticationManager.AuthenticationStatus.SUCCESS, am.authenticateForm(realm, formData));
+
+            // Password updated just in LDAP, so validating directly in realm should fail
+            Assert.assertFalse(realm.validatePassword(realm.getUser("john"), "password-updated"));
+
+            // Switch to not allow updating passwords in ldap
+            AuthProvidersExternalModelTest.setPasswordUpdateForProvider(false, AuthProviderConstants.PROVIDER_NAME_PICKETLINK, realm);
+
+            // Change credential and validate that password is not updated
+            try {
+                authProviderManager.updatePassword("john", "password-updated2");
+            } catch (AuthenticationProviderException ape) {
+                ape.printStackTrace();
+                Assert.fail("Error not expected");
+            }
+            formData = AuthProvidersExternalModelTest.createFormData("john", "password-updated2");
+            Assert.assertEquals(AuthenticationManager.AuthenticationStatus.INVALID_CREDENTIALS, am.authenticateForm(realm, formData));
+        } finally {
+            ResteasyProviderFactory.clearContextData();
+        }
+    }
+
+    /**
+     * Setup authentication providers in realm
+     */
+    private void setupAuthenticationProviders() {
+        AuthenticationProviderModel ap1 = new AuthenticationProviderModel(AuthProviderConstants.PROVIDER_NAME_MODEL, false, Collections.EMPTY_MAP);
+        AuthenticationProviderModel ap2 = new AuthenticationProviderModel(AuthProviderConstants.PROVIDER_NAME_PICKETLINK, true, Collections.EMPTY_MAP);
+        realm.setAuthenticationProviders(Arrays.asList(ap1, ap2));
+    }
+}
diff --git a/model/tests/src/test/java/org/keycloak/model/test/CompositeRolesModelTest.java b/model/tests/src/test/java/org/keycloak/model/test/CompositeRolesModelTest.java
index 61d5621..4b872b3 100755
--- a/model/tests/src/test/java/org/keycloak/model/test/CompositeRolesModelTest.java
+++ b/model/tests/src/test/java/org/keycloak/model/test/CompositeRolesModelTest.java
@@ -19,6 +19,7 @@ import org.keycloak.services.managers.RealmManager;
 public class CompositeRolesModelTest extends AbstractModelTest {
 
     @Before
+    @Override
     public void before() throws Exception {
         super.before();
         RealmManager manager = realmManager;
diff --git a/model/tests/src/test/java/org/keycloak/model/test/ImportTest.java b/model/tests/src/test/java/org/keycloak/model/test/ImportTest.java
index c48e134..ce4ca53 100755
--- a/model/tests/src/test/java/org/keycloak/model/test/ImportTest.java
+++ b/model/tests/src/test/java/org/keycloak/model/test/ImportTest.java
@@ -6,9 +6,10 @@ import org.junit.Test;
 import org.junit.runners.MethodSorters;
 import org.keycloak.models.AccountRoles;
 import org.keycloak.models.ApplicationModel;
+import org.keycloak.models.AuthenticationLinkModel;
+import org.keycloak.models.AuthenticationProviderModel;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.Constants;
-import org.keycloak.models.OAuthClientModel;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RequiredCredentialModel;
 import org.keycloak.models.RoleModel;
@@ -16,6 +17,7 @@ import org.keycloak.models.SocialLinkModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.services.managers.RealmManager;
+import org.keycloak.spi.authentication.AuthProviderConstants;
 
 import java.util.List;
 import java.util.Map;
@@ -171,6 +173,60 @@ public class ImportTest extends AbstractModelTest {
         Assert.assertNull(realm.getSocialLink(socialUser, "facebook"));
         Assert.assertFalse(realm.removeSocialLink(socialUser, "facebook"));
 
+        // Test smtp config
+        Map<String, String> smtpConfig = realm.getSmtpConfig();
+        Assert.assertTrue(smtpConfig.size() == 3);
+        Assert.assertEquals("auto@keycloak.org", smtpConfig.get("from"));
+        Assert.assertEquals("localhost", smtpConfig.get("host"));
+        Assert.assertEquals("3025", smtpConfig.get("port"));
+
+        // Test social config
+        Map<String, String> socialConfig = realm.getSocialConfig();
+        Assert.assertTrue(socialConfig.size() == 2);
+        Assert.assertEquals("abc", socialConfig.get("google.key"));
+        Assert.assertEquals("def", socialConfig.get("google.secret"));
+
+        // Test ldap config
+        Map<String, String> ldapConfig = realm.getLdapServerConfig();
+        Assert.assertTrue(ldapConfig.size() == 5);
+        Assert.assertEquals("ldap://localhost:10389", ldapConfig.get("connectionUrl"));
+        Assert.assertEquals("dc=keycloak,dc=org", ldapConfig.get("baseDn"));
+        Assert.assertEquals("ou=People,dc=keycloak,dc=org", ldapConfig.get("userDnSuffix"));
+
+        // Test authentication providers
+        List<AuthenticationProviderModel> authProviderModels = realm.getAuthenticationProviders();
+        Assert.assertTrue(authProviderModels.size() == 3);
+        AuthenticationProviderModel authProv1 = authProviderModels.get(0);
+        AuthenticationProviderModel authProv2 = authProviderModels.get(1);
+        AuthenticationProviderModel authProv3 = authProviderModels.get(2);
+        Assert.assertEquals(AuthProviderConstants.PROVIDER_NAME_MODEL, authProv1.getProviderName());
+        Assert.assertTrue(authProv1.isPasswordUpdateSupported());
+        Assert.assertEquals(AuthProviderConstants.PROVIDER_NAME_EXTERNAL_MODEL, authProv2.getProviderName());
+        Assert.assertFalse(authProv2.isPasswordUpdateSupported());
+        Assert.assertEquals("trustedRealm", authProv2.getConfig().get("externalRealmId"));
+        Assert.assertEquals(AuthProviderConstants.PROVIDER_NAME_PICKETLINK, authProv3.getProviderName());
+        Assert.assertTrue(authProv3.isPasswordUpdateSupported());
+
+        // Test authentication linking
+        Set<AuthenticationLinkModel> authLinks = realm.getAuthenticationLinks(socialUser);
+        Assert.assertEquals(2, authLinks.size());
+        boolean plFound = false;
+        boolean extFound = false;
+        for (AuthenticationLinkModel authLinkModel : authLinks) {
+            if (AuthProviderConstants.PROVIDER_NAME_PICKETLINK.equals(authLinkModel.getAuthProvider())) {
+                plFound = true;
+                Assert.assertEquals(authLinkModel.getAuthUserId(), "myUser1");
+            } else if (AuthProviderConstants.PROVIDER_NAME_EXTERNAL_MODEL.equals(authLinkModel.getAuthProvider())) {
+                extFound = true;
+                Assert.assertEquals(authLinkModel.getAuthUserId(), "myUser11");
+            }
+        }
+        Assert.assertTrue(plFound && extFound);
+
+        UserModel foundAuthUser = realm.getUserByAuthenticationLink(new AuthenticationLinkModel(AuthProviderConstants.PROVIDER_NAME_PICKETLINK, "myUser1"));
+        Assert.assertEquals(foundAuthUser.getLoginName(), socialUser.getLoginName());
+        Assert.assertNull(realm.getUserByAuthenticationLink(new AuthenticationLinkModel(AuthProviderConstants.PROVIDER_NAME_PICKETLINK, "not-existing")));
+
         commit();
 
         realm = realmManager.getRealm("demo");
diff --git a/model/tests/src/test/java/org/keycloak/model/test/MultipleRealmsTest.java b/model/tests/src/test/java/org/keycloak/model/test/MultipleRealmsTest.java
index 109ac0a..b7e7029 100755
--- a/model/tests/src/test/java/org/keycloak/model/test/MultipleRealmsTest.java
+++ b/model/tests/src/test/java/org/keycloak/model/test/MultipleRealmsTest.java
@@ -19,6 +19,7 @@ public class MultipleRealmsTest extends AbstractModelTest {
     private RealmModel realm2;
 
     @Before
+    @Override
     public void before() throws Exception {
         super.before();
         realm1 = identitySession.createRealm("id1", "realm1");
diff --git a/model/tests/src/test/resources/ldap/ldap-connection.properties b/model/tests/src/test/resources/ldap/ldap-connection.properties
new file mode 100644
index 0000000..5ccc6ad
--- /dev/null
+++ b/model/tests/src/test/resources/ldap/ldap-connection.properties
@@ -0,0 +1,9 @@
+idm.test.ldap.connection.url=ldap\://localhost\:10389
+idm.test.ldap.base.dn=dc\=keycloak,dc\=org
+idm.test.ldap.roles.dn.suffix=ou\=Roles,dc\=keycloak,dc\=org
+idm.test.ldap.group.dn.suffix=ou\=Groups,dc\=keycloak,dc\=org
+idm.test.ldap.user.dn.suffix=ou\=People,dc\=keycloak,dc\=org
+idm.test.ldap.agent.dn.suffix=ou\=Agent,dc\=keycloak,dc\=org
+idm.test.ldap.start.embedded.ldap.server=true
+idm.test.ldap.bind.dn=uid\=admin,ou\=system
+idm.test.ldap.bind.credential=secret
\ No newline at end of file
diff --git a/model/tests/src/test/resources/ldap/users.ldif b/model/tests/src/test/resources/ldap/users.ldif
new file mode 100644
index 0000000..9f72f65
--- /dev/null
+++ b/model/tests/src/test/resources/ldap/users.ldif
@@ -0,0 +1,31 @@
+dn: dc=keycloak,dc=org
+objectclass: dcObject
+objectclass: organization
+o: Keycloak
+dc: Keycloak
+
+dn: ou=People,dc=keycloak,dc=org
+objectclass: top
+objectclass: organizationalUnit
+ou: People
+
+dn: ou=Roles,dc=keycloak,dc=org
+objectclass: top
+objectclass: organizationalUnit
+ou: Roles
+
+dn: ou=Groups,dc=keycloak,dc=org
+objectclass: top
+objectclass: organizationalUnit
+ou: Groups
+
+dn: uid=john,ou=People,dc=keycloak,dc=org
+objectclass: top
+objectclass: uidObject
+objectclass: person
+objectclass: inetOrgPerson
+uid: john
+cn: John
+sn: Doe
+mail: john@email.org
+userPassword: password
\ No newline at end of file
diff --git a/model/tests/src/test/resources/testrealm.json b/model/tests/src/test/resources/testrealm.json
index e0d6cfb..b28087d 100755
--- a/model/tests/src/test/resources/testrealm.json
+++ b/model/tests/src/test/resources/testrealm.json
@@ -7,6 +7,38 @@
     "requiredCredentials": [ "password" ],
     "defaultRoles": [ "foo", "bar" ],
     "verifyEmail" : "true",
+    "smtpServer": {
+        "from": "auto@keycloak.org",
+        "host": "localhost",
+        "port":"3025"
+    },
+    "ldapServer": {
+        "connectionUrl": "ldap://localhost:10389",
+        "baseDn": "dc=keycloak,dc=org",
+        "userDnSuffix": "ou=People,dc=keycloak,dc=org",
+        "bindDn": "uid=admin,ou=system",
+        "bindCredential": "secret"
+    },
+    "socialProviders": {
+        "google.key": "abc",
+        "google.secret": "def"
+    },
+    "authenticationProviders": [
+        {
+            "providerName": "model"
+        },
+        {
+            "providerName": "externalModel",
+            "passwordUpdateSupported": false,
+            "config": {
+                "externalRealmId": "trustedRealm"
+            }
+        },
+        {
+            "providerName": "picketlink",
+            "passwordUpdateSupported": true
+        }
+    ],
     "users": [
         {
             "username": "wburke",
@@ -68,6 +100,21 @@
             ]
         }
     ],
+    "authenticationMappings": [
+        {
+            "username": "mySocialUser",
+            "authenticationLinks": [
+                {
+                    "authProvider": "picketlink",
+                    "authUserId": "myUser1"
+                },
+                {
+                    "authProvider": "externalModel",
+                    "authUserId": "myUser11"
+                }
+            ]
+        }
+    ],
     "applications": [
         {
             "name": "Application",

pom.xml 24(+22 -2)

diff --git a/pom.xml b/pom.xml
index 00cdfeb..5246554 100755
--- a/pom.xml
+++ b/pom.xml
@@ -15,7 +15,8 @@
         <keycloak.apache.httpcomponents.version>4.1.2</keycloak.apache.httpcomponents.version>
         <resteasy.version>3.0.6.Final</resteasy.version>
         <undertow.version>1.0.0.Final</undertow.version>
-        <picketlink.version>2.5.0.Beta6</picketlink.version>
+        <picketlink.version>2.6.0.CR1</picketlink.version>
+        <picketbox.ldap.version>1.0.2.Final</picketbox.ldap.version>
         <mongo.driver.version>2.11.3</mongo.driver.version>
         <jboss.logging.version>3.1.1.GA</jboss.logging.version>
         <jboss-logging-tools.version>1.2.0.Beta1</jboss-logging-tools.version>
@@ -27,7 +28,7 @@
         <dom4j.version>1.6.1</dom4j.version>
         <xml-apis.version>1.4.01</xml-apis.version>
         <mysql.version>5.1.25</mysql.version>
-        <slf4j.version>1.6.1</slf4j.version>
+        <slf4j.version>1.5.10</slf4j.version>
         <jboss.version>7.1.1.Final</jboss.version>
         <wildfly.version>8.0.0.Final</wildfly.version>
         <json.version>20131018</json.version>
@@ -91,6 +92,7 @@
         <module>examples</module>
         <module>testsuite</module>
         <module>server</module>
+        <module>spi</module>
     </modules>
 
     <dependencyManagement>
@@ -216,6 +218,17 @@
                 <version>${picketlink.version}</version>
             </dependency>
             <dependency>
+                <groupId>org.picketbox</groupId>
+                <artifactId>picketbox-ldap</artifactId>
+                <version>${picketbox.ldap.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.picketbox</groupId>
+                <artifactId>picketbox-ldap</artifactId>
+                <version>${picketbox.ldap.version}</version>
+                <type>test-jar</type>
+            </dependency>
+            <dependency>
                 <groupId>org.jboss.logging</groupId>
                 <artifactId>jboss-logging</artifactId>
                 <version>${jboss.logging.version}</version>
@@ -339,11 +352,18 @@
                 <artifactId>xml-apis</artifactId>
                 <version>${xml-apis.version}</version>
             </dependency>
+            <!-- Older 1.5.10 binding required by embedded ApacheDS -->
             <dependency>
                 <groupId>org.slf4j</groupId>
                 <artifactId>slf4j-api</artifactId>
                 <version>${slf4j.version}</version>
             </dependency>
+            <dependency>
+                <groupId>org.slf4j</groupId>
+                <artifactId>slf4j-simple</artifactId>
+                <version>${slf4j.version}</version>
+            </dependency>
+
             <!-- Needed for picketlink perf test -->
             <dependency>
                 <groupId>mysql</groupId>
diff --git a/server/src/main/resources/META-INF/persistence.xml b/server/src/main/resources/META-INF/persistence.xml
index b193044..1e0667c 100755
--- a/server/src/main/resources/META-INF/persistence.xml
+++ b/server/src/main/resources/META-INF/persistence.xml
@@ -9,9 +9,11 @@
         <class>org.keycloak.models.jpa.entities.OAuthClientEntity</class>
         <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.ApplicationRoleEntity</class>
         <class>org.keycloak.models.jpa.entities.RealmRoleEntity</class>
         <class>org.keycloak.models.jpa.entities.SocialLinkEntity</class>
+        <class>org.keycloak.models.jpa.entities.AuthenticationLinkEntity</class>
         <class>org.keycloak.models.jpa.entities.UserEntity</class>
         <class>org.keycloak.models.jpa.entities.UserRoleMappingEntity</class>
         <class>org.keycloak.models.jpa.entities.ScopeMappingEntity</class>

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

diff --git a/services/pom.xml b/services/pom.xml
index f27b7be..0571d92 100755
--- a/services/pom.xml
+++ b/services/pom.xml
@@ -63,6 +63,12 @@
             <scope>provided</scope>
         </dependency>
         <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-authentication-spi</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
             <groupId>org.jboss.logging</groupId>
             <artifactId>jboss-logging</artifactId>
             <scope>provided</scope>
@@ -139,6 +145,12 @@
             <groupId>com.icegreen</groupId>
             <artifactId>greenmail</artifactId>
             <scope>test</scope>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.slf4j</groupId>
+                    <artifactId>slf4j-api</artifactId>
+                </exclusion>
+            </exclusions>
         </dependency>
     </dependencies>
     <build>
diff --git a/services/src/main/java/org/keycloak/services/filters/KeycloakSessionServletFilter.java b/services/src/main/java/org/keycloak/services/filters/KeycloakSessionServletFilter.java
index cb2bc5a..3f859c2 100755
--- a/services/src/main/java/org/keycloak/services/filters/KeycloakSessionServletFilter.java
+++ b/services/src/main/java/org/keycloak/services/filters/KeycloakSessionServletFilter.java
@@ -4,6 +4,7 @@ import org.jboss.resteasy.spi.ResteasyProviderFactory;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakSessionFactory;
 import org.keycloak.models.KeycloakTransaction;
+import org.keycloak.util.KeycloakRegistry;
 
 import javax.servlet.Filter;
 import javax.servlet.FilterChain;
@@ -25,7 +26,9 @@ public class KeycloakSessionServletFilter implements Filter {
 
     @Override
     public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
-        KeycloakSessionFactory factory = (KeycloakSessionFactory)servletRequest.getServletContext().getAttribute(KeycloakSessionFactory.class.getName());
+        KeycloakRegistry registry = (KeycloakRegistry)servletRequest.getServletContext().getAttribute(KeycloakRegistry.class.getName());
+        ResteasyProviderFactory.pushContext(KeycloakRegistry.class, registry);
+        KeycloakSessionFactory factory = registry.getService(KeycloakSessionFactory.class);
         if (factory == null) throw new ServletException("Factory was null");
         KeycloakSession session = factory.createSession();
         ResteasyProviderFactory.pushContext(KeycloakSession.class, session);
diff --git a/services/src/main/java/org/keycloak/services/listeners/KeycloakSessionDestroyListener.java b/services/src/main/java/org/keycloak/services/listeners/KeycloakSessionDestroyListener.java
index 456889f..b5fc31d 100644
--- a/services/src/main/java/org/keycloak/services/listeners/KeycloakSessionDestroyListener.java
+++ b/services/src/main/java/org/keycloak/services/listeners/KeycloakSessionDestroyListener.java
@@ -1,6 +1,7 @@
 package org.keycloak.services.listeners;
 
 import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.util.KeycloakRegistry;
 
 import javax.servlet.ServletContextEvent;
 import javax.servlet.ServletContextListener;
@@ -16,7 +17,8 @@ public class KeycloakSessionDestroyListener implements ServletContextListener {
 
     @Override
     public void contextDestroyed(ServletContextEvent sce) {
-        KeycloakSessionFactory factory = (KeycloakSessionFactory) sce.getServletContext().getAttribute(KeycloakSessionFactory.class.getName());
+        KeycloakRegistry registry = (KeycloakRegistry)sce.getServletContext().getAttribute(KeycloakRegistry.class.getName());
+        KeycloakSessionFactory factory = registry.getService(KeycloakSessionFactory.class);
         if (factory != null) {
             factory.close();
         }
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 e3c1e95..a9b82d2 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -6,16 +6,19 @@ import org.jboss.resteasy.spi.ResteasyProviderFactory;
 import org.keycloak.RSATokenVerifier;
 import org.keycloak.VerificationException;
 import org.keycloak.jose.jws.JWSBuilder;
+import org.keycloak.models.AuthenticationLinkModel;
 import org.keycloak.models.ClientModel;
-import org.keycloak.models.Constants;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RequiredCredentialModel;
-import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.representations.AccessToken;
 import org.keycloak.representations.idm.CredentialRepresentation;
 import org.keycloak.services.resources.RealmsResource;
+import org.keycloak.spi.authentication.AuthProviderStatus;
+import org.keycloak.spi.authentication.AuthResult;
+import org.keycloak.spi.authentication.AuthenticatedUser;
+import org.keycloak.spi.authentication.AuthenticationProviderManager;
 import org.keycloak.util.Time;
 
 import javax.ws.rs.core.Cookie;
@@ -25,7 +28,6 @@ import javax.ws.rs.core.NewCookie;
 import javax.ws.rs.core.UriInfo;
 import java.net.URI;
 import java.util.HashSet;
-import java.util.List;
 import java.util.Set;
 
 /**
@@ -178,16 +180,14 @@ public class AuthenticationManager {
         return null;
     }
 
-    public AuthenticationStatus authenticateForm(RealmModel realm, UserModel user, MultivaluedMap<String, String> formData) {
-        if (user == null) {
-            logger.debug("Not Authenticated! Incorrect user name");
+    public AuthenticationStatus authenticateForm(RealmModel realm, MultivaluedMap<String, String> formData) {
+        String username = formData.getFirst(FORM_USERNAME);
+        if (username == null) {
+            logger.warn("Username not provided");
             return AuthenticationStatus.INVALID_USER;
         }
 
-        if (!user.isEnabled()) {
-            logger.debug("Account is disabled, contact admin. " + user.getLoginName());
-            return AuthenticationStatus.ACCOUNT_DISABLED;
-        }
+        UserModel user = KeycloakModelUtils.findUserByNameOrEmail(realm, username);
 
         Set<String> types = new HashSet<String>();
 
@@ -202,22 +202,71 @@ public class AuthenticationManager {
                 return AuthenticationStatus.MISSING_PASSWORD;
             }
 
-            if (user.isTotp()) {
+            if (user == null && types.contains(CredentialRepresentation.TOTP)) {
+                logger.warn("User doesn't exists and TOTP is required for the realm");
+                return AuthenticationStatus.INVALID_USER;
+            }
+
+            if (user != null && user.isTotp()) {
                 String token = formData.getFirst(CredentialRepresentation.TOTP);
                 if (token == null) {
                     logger.warn("TOTP token not provided");
                     return AuthenticationStatus.MISSING_TOTP;
                 }
+                if (!checkEnabled(user)) {
+                    return AuthenticationStatus.ACCOUNT_DISABLED;
+                }
                 logger.debug("validating TOTP");
                 if (!realm.validateTOTP(user, password, token)) {
                     return AuthenticationStatus.INVALID_CREDENTIALS;
                 }
             } else {
-                logger.debug("validating password for user: " + user.getLoginName());
-                if (!realm.validatePassword(user, password)) {
-                    logger.debug("invalid password for user: " + user.getLoginName());
+                logger.debug("validating password for user: " + username);
+
+                AuthResult authResult = AuthenticationProviderManager.getManager(realm).validatePassword(username, password);
+                if (authResult.getAuthProviderStatus() == AuthProviderStatus.FAILED) {
+                    logger.debug("invalid password for user: " + username);
                     return AuthenticationStatus.INVALID_CREDENTIALS;
                 }
+
+                if (authResult.getAuthenticatedUser() != null) {
+                    AuthenticatedUser authUser = authResult.getAuthenticatedUser();
+                    AuthenticationLinkModel authLink = new AuthenticationLinkModel(authResult.getProviderName(), authUser.getId());
+                    user = realm.getUserByAuthenticationLink(authLink);
+                    if (user == null) {
+                        // Create new user, which has been successfully authenticated and link him with authentication provider
+                        user = realm.addUser(authUser.getUsername());
+                        user.setEnabled(true);
+                        user.setFirstName(authUser.getFirstName());
+                        user.setLastName(authUser.getLastName());
+                        user.setEmail(authUser.getEmail());
+
+                        realm.addAuthenticationLink(user, authLink);
+                        logger.info("User " + username + " successfully authenticated and created based on provider " + authResult.getProviderName());
+                    } else {
+                        // Existing user has been authenticated
+                        if (!checkEnabled(user)) {
+                            return AuthenticationStatus.ACCOUNT_DISABLED;
+                        }
+
+                        // TODO: Update of existing account?
+                    }
+
+                    // Authenticated username could be different from the "form" username. In this case, we will change it
+                    if (!username.equals(user.getLoginName())) {
+                        formData.putSingle(FORM_USERNAME, user.getLoginName());
+                        logger.debug("Existing user " + user.getLoginName() + " successfully authenticated");
+                    }
+
+                } else {
+                    // Authentication provider didn't send AuthenticatedUser. Using already retrieved user based on username from "form"
+                    if (user == null) {
+                        logger.warn("User '" + username + "' successfully authenticated, but he doesn't exists and don't know how to create him");
+                        return AuthenticationStatus.INVALID_USER;
+                    } else if (!checkEnabled(user)) {
+                        return AuthenticationStatus.ACCOUNT_DISABLED;
+                    }
+                }
             }
 
             if (!user.getRequiredActions().isEmpty()) {
@@ -242,6 +291,15 @@ public class AuthenticationManager {
         }
     }
 
+    private boolean checkEnabled(UserModel user) {
+        if (!user.isEnabled()) {
+            logger.warn("Account is disabled, contact admin. " + user.getLoginName());
+            return false;
+        } else {
+            return true;
+        }
+    }
+
     public enum AuthenticationStatus {
         SUCCESS, ACCOUNT_DISABLED, ACTIONS_REQUIRED, INVALID_USER, INVALID_CREDENTIALS, MISSING_PASSWORD, MISSING_TOTP, FAILED
     }
diff --git a/services/src/main/java/org/keycloak/services/managers/ModelToRepresentation.java b/services/src/main/java/org/keycloak/services/managers/ModelToRepresentation.java
index 48ab5d7..5f43390 100755
--- a/services/src/main/java/org/keycloak/services/managers/ModelToRepresentation.java
+++ b/services/src/main/java/org/keycloak/services/managers/ModelToRepresentation.java
@@ -1,6 +1,7 @@
 package org.keycloak.services.managers;
 
 import org.keycloak.models.ApplicationModel;
+import org.keycloak.models.AuthenticationProviderModel;
 import org.keycloak.models.ClaimMask;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.Constants;
@@ -9,6 +10,7 @@ import org.keycloak.models.RequiredCredentialModel;
 import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserCredentialModel;
 import org.keycloak.models.UserModel;
+import org.keycloak.representations.idm.AuthenticationProviderRepresentation;
 import org.keycloak.representations.idm.ClaimRepresentation;
 import org.keycloak.representations.idm.CredentialRepresentation;
 import org.keycloak.representations.idm.RealmRepresentation;
@@ -83,6 +85,7 @@ public class ModelToRepresentation {
         rep.setAccessCodeLifespanUserAction(realm.getAccessCodeLifespanUserAction());
         rep.setSmtpServer(realm.getSmtpConfig());
         rep.setSocialProviders(realm.getSocialConfig());
+        rep.setLdapServer(realm.getLdapServerConfig());
         rep.setAccountTheme(realm.getAccountTheme());
         rep.setLoginTheme(realm.getLoginTheme());
         if (realm.getPasswordPolicy() != null) {
@@ -105,6 +108,19 @@ public class ModelToRepresentation {
                 rep.getRequiredCredentials().add(cred.getType());
             }
         }
+
+        List<AuthenticationProviderModel> authProviderModels = realm.getAuthenticationProviders();
+        if (authProviderModels.size() > 0) {
+            List<AuthenticationProviderRepresentation> authProviderReps = new ArrayList<AuthenticationProviderRepresentation>();
+            for (AuthenticationProviderModel model : authProviderModels) {
+                AuthenticationProviderRepresentation authProvRep = new AuthenticationProviderRepresentation();
+                authProvRep.setProviderName(model.getProviderName());
+                authProvRep.setPasswordUpdateSupported(model.isPasswordUpdateSupported());
+                authProvRep.setConfig(model.getConfig());
+                authProviderReps.add(authProvRep);
+            }
+            rep.setAuthenticationProviders(authProviderReps);
+        }
         return rep;
     }
 
diff --git a/services/src/main/java/org/keycloak/services/managers/RealmManager.java b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
index 83cf3fb..abe3006 100755
--- a/services/src/main/java/org/keycloak/services/managers/RealmManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
@@ -4,6 +4,8 @@ import org.jboss.resteasy.logging.Logger;
 import org.keycloak.models.AccountRoles;
 import org.keycloak.models.AdminRoles;
 import org.keycloak.models.ApplicationModel;
+import org.keycloak.models.AuthenticationLinkModel;
+import org.keycloak.models.AuthenticationProviderModel;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.Config;
 import org.keycloak.models.Constants;
@@ -18,6 +20,9 @@ import org.keycloak.models.UserModel;
 import org.keycloak.models.UserModel.RequiredAction;
 import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.representations.idm.ApplicationRepresentation;
+import org.keycloak.representations.idm.AuthenticationLinkRepresentation;
+import org.keycloak.representations.idm.AuthenticationMappingRepresentation;
+import org.keycloak.representations.idm.AuthenticationProviderRepresentation;
 import org.keycloak.representations.idm.CredentialRepresentation;
 import org.keycloak.representations.idm.OAuthClientRepresentation;
 import org.keycloak.representations.idm.RealmRepresentation;
@@ -31,6 +36,7 @@ import org.keycloak.representations.idm.UserRoleMappingRepresentation;
 import java.security.KeyPair;
 import java.security.KeyPairGenerator;
 import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
@@ -145,6 +151,14 @@ public class RealmManager {
             realm.setSocialConfig(new HashMap(rep.getSocialProviders()));
         }
 
+        if (rep.getLdapServer() != null) {
+            realm.setLdapServerConfig(new HashMap(rep.getLdapServer()));
+        }
+        if (rep.getAuthenticationProviders() != null) {
+            List<AuthenticationProviderModel> authProviderModels = convertAuthenticationProviders(rep.getAuthenticationProviders());
+            realm.setAuthenticationProviders(authProviderModels);
+        }
+
         if ("GENERATE".equals(rep.getPublicKey())) {
             generateRealmKeys(realm);
         }
@@ -371,6 +385,15 @@ public class RealmManager {
                 }
             }
         }
+        if (rep.getAuthenticationMappings() != null) {
+            for (AuthenticationMappingRepresentation authMapping : rep.getAuthenticationMappings()) {
+                UserModel user = userMap.get(authMapping.getUsername());
+                for (AuthenticationLinkRepresentation link : authMapping.getAuthenticationLinks()) {
+                    AuthenticationLinkModel mappingModel = new AuthenticationLinkModel(link.getAuthProvider(), link.getAuthUserId());
+                    newRealm.addAuthenticationLink(user, mappingModel);
+                }
+            }
+        }
 
         if (rep.getSmtpServer() != null) {
             newRealm.setSmtpConfig(new HashMap(rep.getSmtpServer()));
@@ -379,6 +402,14 @@ public class RealmManager {
         if (rep.getSocialProviders() != null) {
             newRealm.setSocialConfig(new HashMap(rep.getSocialProviders()));
         }
+        if (rep.getLdapServer() != null) {
+            newRealm.setLdapServerConfig(new HashMap(rep.getLdapServer()));
+        }
+
+        if (rep.getAuthenticationProviders() != null) {
+            List<AuthenticationProviderModel> authProviderModels = convertAuthenticationProviders(rep.getAuthenticationProviders());
+            newRealm.setAuthenticationProviders(authProviderModels);
+        }
     }
 
     public void addComposites(RoleModel role, RoleRepresentation roleRep, RealmModel realm) {
@@ -490,5 +521,16 @@ public class RealmManager {
         }
     }
 
+    protected List<AuthenticationProviderModel> convertAuthenticationProviders(List<AuthenticationProviderRepresentation> authenticationProviders) {
+        List<AuthenticationProviderModel> result = new ArrayList<AuthenticationProviderModel>();
+
+        for (AuthenticationProviderRepresentation representation : authenticationProviders) {
+            AuthenticationProviderModel model = new AuthenticationProviderModel(representation.getProviderName(),
+                    representation.isPasswordUpdateSupported(), representation.getConfig());
+            result.add(model);
+        }
+        return result;
+    }
+
 
 }
diff --git a/services/src/main/java/org/keycloak/services/resources/AccountService.java b/services/src/main/java/org/keycloak/services/resources/AccountService.java
index eb53402..8a4da96 100755
--- a/services/src/main/java/org/keycloak/services/resources/AccountService.java
+++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java
@@ -42,8 +42,10 @@ import org.keycloak.services.resources.flows.Urls;
 import org.keycloak.services.validation.Validation;
 import org.keycloak.social.SocialLoader;
 import org.keycloak.social.SocialProvider;
-import org.keycloak.social.SocialProviderConfig;
 import org.keycloak.social.SocialProviderException;
+import org.keycloak.spi.authentication.AuthProviderStatus;
+import org.keycloak.spi.authentication.AuthenticationProviderException;
+import org.keycloak.spi.authentication.AuthenticationProviderManager;
 
 import javax.ws.rs.*;
 import javax.ws.rs.core.*;
@@ -237,23 +239,20 @@ public class AccountService {
             return account.setError(Messages.INVALID_PASSWORD_CONFIRM).createResponse(AccountPages.PASSWORD);
         }
 
+        AuthenticationProviderManager authProviderManager = AuthenticationProviderManager.getManager(realm);
         if (Validation.isEmpty(password)) {
             return account.setError(Messages.MISSING_PASSWORD).createResponse(AccountPages.PASSWORD);
-        } else if (!realm.validatePassword(user, password)) {
+            // TODO: This may not work in some cases. For example if ldap username is "foo" but actual loginName of user is "bar", which could theoretically happen...
+        } else if (authProviderManager.validatePassword(user.getLoginName(), password).getAuthProviderStatus() != AuthProviderStatus.SUCCESS) {
             return account.setError(Messages.INVALID_PASSWORD_EXISTING).createResponse(AccountPages.PASSWORD);
         }
 
-        String error = Validation.validatePassword(formData, realm.getPasswordPolicy());
-        if (error != null) {
-            return account.setError(error).createResponse(AccountPages.PASSWORD);
+        try {
+            authProviderManager.updatePassword(user.getLoginName(), passwordNew);
+        } catch (AuthenticationProviderException ape) {
+            return account.setError(ape.getMessage()).createResponse(AccountPages.PASSWORD);
         }
 
-        UserCredentialModel credentials = new UserCredentialModel();
-        credentials.setType(CredentialRepresentation.PASSWORD);
-        credentials.setValue(passwordNew);
-
-        realm.updateCredential(user, credentials);
-
         return account.setSuccess("accountPasswordUpdated").createResponse(AccountPages.PASSWORD);
     }
 
diff --git a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
index 6cccf25..977d7e9 100755
--- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
+++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
@@ -4,6 +4,7 @@ import org.jboss.resteasy.logging.Logger;
 import org.keycloak.SkeletonKeyContextResolver;
 import org.keycloak.models.KeycloakSessionFactory;
 import org.keycloak.models.ModelProvider;
+import org.keycloak.util.KeycloakRegistry;
 import org.keycloak.services.managers.ApplianceBootstrap;
 import org.keycloak.services.managers.SocialRequestManager;
 import org.keycloak.services.managers.TokenManager;
@@ -35,7 +36,9 @@ public class KeycloakApplication extends Application {
     public KeycloakApplication(@Context ServletContext context) {
         this.factory = createSessionFactory();
         this.contextPath = context.getContextPath();
-        context.setAttribute(KeycloakSessionFactory.class.getName(), factory);
+        KeycloakRegistry registry = new KeycloakRegistry();
+        registry.putService(KeycloakSessionFactory.class, factory);
+        context.setAttribute(KeycloakRegistry.class.getName(), registry);
         //classes.add(KeycloakSessionCleanupFilter.class);
 
         TokenManager tokenManager = new TokenManager();
diff --git a/services/src/main/java/org/keycloak/services/resources/RequiredActionsService.java b/services/src/main/java/org/keycloak/services/resources/RequiredActionsService.java
index a1409be..c070723 100755
--- a/services/src/main/java/org/keycloak/services/resources/RequiredActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/RequiredActionsService.java
@@ -42,6 +42,8 @@ import org.keycloak.services.managers.TokenManager;
 import org.keycloak.services.messages.Messages;
 import org.keycloak.services.resources.flows.Flows;
 import org.keycloak.services.validation.Validation;
+import org.keycloak.spi.authentication.AuthenticationProviderException;
+import org.keycloak.spi.authentication.AuthenticationProviderManager;
 import org.keycloak.util.Time;
 
 import javax.ws.rs.Consumes;
@@ -171,17 +173,12 @@ public class RequiredActionsService {
             return loginForms.setError(Messages.NOTMATCH_PASSWORD).createResponse(RequiredAction.UPDATE_PASSWORD);
         }
 
-        String error = realm.getPasswordPolicy().validate(passwordNew);
-        if (error != null) {
-            return loginForms.setError(error).createResponse(RequiredAction.UPDATE_PASSWORD);
+        try {
+            AuthenticationProviderManager.getManager(realm).updatePassword(user.getLoginName(), passwordNew);
+        } catch (AuthenticationProviderException ape) {
+            return loginForms.setError(ape.getMessage()).createResponse(RequiredAction.UPDATE_PASSWORD);
         }
 
-        UserCredentialModel credentials = new UserCredentialModel();
-        credentials.setType(CredentialRepresentation.PASSWORD);
-        credentials.setValue(passwordNew);
-
-        realm.updateCredential(user, credentials);
-
         logger.debug("updatePassword updated credential");
 
         user.removeRequiredAction(RequiredAction.UPDATE_PASSWORD);
diff --git a/services/src/main/java/org/keycloak/services/resources/TokenService.java b/services/src/main/java/org/keycloak/services/resources/TokenService.java
index 2ba1c3d..1506a60 100755
--- a/services/src/main/java/org/keycloak/services/resources/TokenService.java
+++ b/services/src/main/java/org/keycloak/services/resources/TokenService.java
@@ -16,6 +16,7 @@ import org.keycloak.models.RealmModel;
 import org.keycloak.models.RequiredCredentialModel;
 import org.keycloak.models.UserCredentialModel;
 import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.representations.AccessToken;
 import org.keycloak.representations.AccessTokenResponse;
 import org.keycloak.representations.idm.CredentialRepresentation;
@@ -28,6 +29,8 @@ import org.keycloak.services.messages.Messages;
 import org.keycloak.services.resources.flows.Flows;
 import org.keycloak.services.resources.flows.OAuthFlows;
 import org.keycloak.services.validation.Validation;
+import org.keycloak.spi.authentication.AuthenticationProviderException;
+import org.keycloak.spi.authentication.AuthenticationProviderManager;
 import org.keycloak.util.BasicAuthHelper;
 import org.keycloak.util.Time;
 
@@ -37,7 +40,6 @@ import javax.ws.rs.ForbiddenException;
 import javax.ws.rs.GET;
 import javax.ws.rs.HeaderParam;
 import javax.ws.rs.NotAcceptableException;
-import javax.ws.rs.NotAllowedException;
 import javax.ws.rs.NotAuthorizedException;
 import javax.ws.rs.OPTIONS;
 import javax.ws.rs.POST;
@@ -149,23 +151,18 @@ public class TokenService {
         }
 
 
-        String username = form.getFirst(AuthenticationManager.FORM_USERNAME);
-        if (username == null) {
-            throw new NotAuthorizedException("No user");
+        if (form.getFirst(AuthenticationManager.FORM_USERNAME) == null) {
+            throw new NotAuthorizedException("No username");
         }
         if (!realm.isEnabled()) {
             throw new NotAuthorizedException("Disabled realm");
         }
-        UserModel user = realm.getUser(username);
-        if (user == null) {
-            throw new NotAuthorizedException("No user");
-        }
-        if (!user.isEnabled()) {
-            throw new NotAuthorizedException("Disabled user.");
-        }
-        if (authManager.authenticateForm(realm, user, form) != AuthenticationStatus.SUCCESS) {
+
+        if (authManager.authenticateForm(realm, form) != AuthenticationStatus.SUCCESS) {
             throw new NotAuthorizedException("Auth failed");
         }
+
+        UserModel user = realm.getUser(form.getFirst(AuthenticationManager.FORM_USERNAME));
         String scope = form.getFirst(OAuth2Constants.SCOPE);
         AccessTokenResponse res = tokenManager.responseBuilder(realm, client)
                 .generateAccessToken(scope, client, user)
@@ -237,17 +234,7 @@ public class TokenService {
             return oauth.redirectError(client, "access_denied", state, redirect);
         }
 
-        String username = formData.getFirst("username");
-        UserModel user = realm.getUser(username);
-        if (user == null && username.contains("@")) {
-            user = realm.getUserByEmail(username);
-        }
-
-        if (user == null) {
-            return Flows.forms(realm, request, uriInfo).setError(Messages.INVALID_USER).setFormData(formData).createLogin();
-        }
-
-        AuthenticationStatus status = authManager.authenticateForm(realm, user, formData);
+        AuthenticationStatus status = authManager.authenticateForm(realm, formData);
 
         String rememberMe = formData.getFirst("rememberMe");
         boolean remember = rememberMe != null && rememberMe.equalsIgnoreCase("on");
@@ -262,6 +249,7 @@ public class TokenService {
         switch (status) {
             case SUCCESS:
             case ACTIONS_REQUIRED:
+                UserModel user = KeycloakModelUtils.findUserByNameOrEmail(realm, formData.getFirst(AuthenticationManager.FORM_USERNAME));
                 return oauth.processAccessCode(scopeParam, state, redirect, client, user, remember);
             case ACCOUNT_DISABLED:
                 return Flows.forms(realm, request, uriInfo).setError(Messages.ACCOUNT_DISABLED).setFormData(formData).createLogin();
@@ -317,6 +305,7 @@ public class TokenService {
             requiredCredentialTypes.add(m.getType());
         }
 
+        // Validate here, so user is not created if password doesn't validate to passwordPolicy of current realm
         String error = Validation.validateRegistrationForm(formData, requiredCredentialTypes);
         if (error == null) {
             error = Validation.validatePassword(formData, realm.getPasswordPolicy());
@@ -344,7 +333,13 @@ public class TokenService {
             UserCredentialModel credentials = new UserCredentialModel();
             credentials.setType(CredentialRepresentation.PASSWORD);
             credentials.setValue(formData.getFirst("password"));
-            realm.updateCredential(user, credentials);
+            try {
+                AuthenticationProviderManager.getManager(realm).updatePassword(username, formData.getFirst("password"));
+            } catch (AuthenticationProviderException ape) {
+                // User already registered, but force him to update password
+                user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
+                return Flows.forms(realm, request, uriInfo).setError(ape.getMessage()).createResponse(UserModel.RequiredAction.UPDATE_PASSWORD);
+            }
         }
 
         return processLogin(clientId, scopeParam, state, redirect, formData);
diff --git a/spi/authentication-model/pom.xml b/spi/authentication-model/pom.xml
new file mode 100644
index 0000000..9a30d3a
--- /dev/null
+++ b/spi/authentication-model/pom.xml
@@ -0,0 +1,74 @@
+<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-spi</artifactId>
+        <groupId>org.keycloak</groupId>
+        <version>1.0-beta-1-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>keycloak-authentication-model</artifactId>
+    <name>Keycloak Authentication Model Based</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.keycloak</groupId>
+            <artifactId>keycloak-authentication-spi</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>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>1.6</source>
+                    <target>1.6</target>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git a/spi/authentication-model/src/main/java/org/keycloak/spi/authentication/model/AbstractModelAuthenticationProvider.java b/spi/authentication-model/src/main/java/org/keycloak/spi/authentication/model/AbstractModelAuthenticationProvider.java
new file mode 100644
index 0000000..94652a5
--- /dev/null
+++ b/spi/authentication-model/src/main/java/org/keycloak/spi/authentication/model/AbstractModelAuthenticationProvider.java
@@ -0,0 +1,73 @@
+package org.keycloak.spi.authentication.model;
+
+import java.util.Map;
+
+import org.jboss.logging.Logger;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.representations.idm.CredentialRepresentation;
+import org.keycloak.spi.authentication.AuthProviderStatus;
+import org.keycloak.spi.authentication.AuthResult;
+import org.keycloak.spi.authentication.AuthenticatedUser;
+import org.keycloak.spi.authentication.AuthenticationProvider;
+import org.keycloak.spi.authentication.AuthenticationProviderException;
+
+/**
+ * Authentication provider, which delegates calling of all methods to specified realm
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public abstract class AbstractModelAuthenticationProvider implements AuthenticationProvider {
+
+    private static final Logger logger = Logger.getLogger(AbstractModelAuthenticationProvider.class);
+
+    @Override
+    public AuthResult validatePassword(RealmModel currentRealm, Map<String, String> config, String username, String password) throws AuthenticationProviderException {
+        RealmModel realm = getRealm(currentRealm, config);
+
+        UserModel user = KeycloakModelUtils.findUserByNameOrEmail(realm, username);
+
+        // Ignore if user doesn't exists, so that other providers have opportunity to authenticate (and possibly create) him
+        if (user == null) {
+            return new AuthResult(AuthProviderStatus.IGNORE);
+        }
+
+        boolean result = realm.validatePassword(user, password);
+        if (!result) {
+            return  new AuthResult(AuthProviderStatus.IGNORE);
+        }
+
+        AuthenticatedUser authUser = createAuthenticatedUserInstance(user);
+        return new AuthResult(AuthProviderStatus.SUCCESS).setProviderName(getName()).setUser(authUser);
+    }
+
+    @Override
+    public boolean updateCredential(RealmModel currentRealm, Map<String, String> config, String username, String password) throws AuthenticationProviderException {
+        RealmModel realm = getRealm(currentRealm, config);
+
+        // Validate password policy
+        String error = realm.getPasswordPolicy().validate(password);
+        if (error != null) {
+            throw new AuthenticationProviderException(error);
+        }
+
+        UserModel user = realm.getUser(username);
+        if (user == null) {
+            logger.debugf("User '%s' doesn't exists. Skip password update", username);
+            return false;
+        }
+
+        UserCredentialModel cred = new UserCredentialModel();
+        cred.setType(CredentialRepresentation.PASSWORD);
+        cred.setValue(password);
+
+        realm.updateCredential(user, cred);
+        return true;
+    }
+
+    protected abstract RealmModel getRealm(RealmModel currentRealm, Map<String, String> config) throws AuthenticationProviderException;
+
+    protected abstract AuthenticatedUser createAuthenticatedUserInstance(UserModel user);
+}
diff --git a/spi/authentication-model/src/main/java/org/keycloak/spi/authentication/model/ExternalModelAuthenticationProvider.java b/spi/authentication-model/src/main/java/org/keycloak/spi/authentication/model/ExternalModelAuthenticationProvider.java
new file mode 100644
index 0000000..a24a495
--- /dev/null
+++ b/spi/authentication-model/src/main/java/org/keycloak/spi/authentication/model/ExternalModelAuthenticationProvider.java
@@ -0,0 +1,50 @@
+package org.keycloak.spi.authentication.model;
+
+import java.util.Map;
+
+import org.jboss.resteasy.spi.ResteasyProviderFactory;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.spi.authentication.AuthProviderConstants;
+import org.keycloak.spi.authentication.AuthenticatedUser;
+import org.keycloak.spi.authentication.AuthenticationProviderException;
+
+/**
+ * AbstractModelAuthenticationProvider, which delegates authentication operations to different (external) realm
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class ExternalModelAuthenticationProvider extends AbstractModelAuthenticationProvider {
+
+    @Override
+    public String getName() {
+        return AuthProviderConstants.PROVIDER_NAME_EXTERNAL_MODEL;
+    }
+
+    @Override
+    public RealmModel getRealm(RealmModel currentRealm, Map<String, String> configuration) throws AuthenticationProviderException {
+        String realmId = configuration.get(AuthProviderConstants.EXTERNAL_REALM_ID);
+        if (realmId == null) {
+            throw new AuthenticationProviderException("Option '" + AuthProviderConstants.EXTERNAL_REALM_ID + "' not specified in configuration");
+        }
+
+        KeycloakSession session = ResteasyProviderFactory.getContextData(KeycloakSession.class);
+        if (session == null) {
+            throw new AuthenticationProviderException("KeycloakSession not available");
+        }
+
+        RealmModel realm = session.getRealm(realmId);
+        if (realm == null) {
+            throw new AuthenticationProviderException("Realm with id '" + realmId + "' doesn't exists");
+        }
+        return realm;
+    }
+
+    @Override
+    protected AuthenticatedUser createAuthenticatedUserInstance(UserModel user) {
+        return new AuthenticatedUser(user.getId(), user.getLoginName())
+                .setName(user.getFirstName(), user.getLastName())
+                .setEmail(user.getEmail());
+    }
+}
diff --git a/spi/authentication-model/src/main/java/org/keycloak/spi/authentication/model/ModelAuthenticationProvider.java b/spi/authentication-model/src/main/java/org/keycloak/spi/authentication/model/ModelAuthenticationProvider.java
new file mode 100644
index 0000000..5169489
--- /dev/null
+++ b/spi/authentication-model/src/main/java/org/keycloak/spi/authentication/model/ModelAuthenticationProvider.java
@@ -0,0 +1,32 @@
+package org.keycloak.spi.authentication.model;
+
+import java.util.Map;
+
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.spi.authentication.AuthProviderConstants;
+import org.keycloak.spi.authentication.AuthenticatedUser;
+
+/**
+ * AbstractModelAuthenticationProvider, which uses current realm to call operations on
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class ModelAuthenticationProvider extends AbstractModelAuthenticationProvider {
+
+    @Override
+    public String getName() {
+        return AuthProviderConstants.PROVIDER_NAME_MODEL;
+    }
+
+    @Override
+    protected RealmModel getRealm(RealmModel currentRealm, Map<String, String> config) {
+        return currentRealm;
+    }
+
+    @Override
+    protected AuthenticatedUser createAuthenticatedUserInstance(UserModel user) {
+        // We don't want AuthenticatedUser instance. Auto-registration won't never happen with this provider
+        return null;
+    }
+}
diff --git a/spi/authentication-model/src/main/resources/META-INF/services/org.keycloak.spi.authentication.AuthenticationProvider b/spi/authentication-model/src/main/resources/META-INF/services/org.keycloak.spi.authentication.AuthenticationProvider
new file mode 100644
index 0000000..e713779
--- /dev/null
+++ b/spi/authentication-model/src/main/resources/META-INF/services/org.keycloak.spi.authentication.AuthenticationProvider
@@ -0,0 +1,2 @@
+org.keycloak.spi.authentication.model.ModelAuthenticationProvider
+org.keycloak.spi.authentication.model.ExternalModelAuthenticationProvider
\ No newline at end of file
diff --git a/spi/authentication-picketlink/pom.xml b/spi/authentication-picketlink/pom.xml
new file mode 100644
index 0000000..ce78e8d
--- /dev/null
+++ b/spi/authentication-picketlink/pom.xml
@@ -0,0 +1,95 @@
+<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-spi</artifactId>
+        <groupId>org.keycloak</groupId>
+        <version>1.0-beta-1-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>keycloak-authentication-picketlink</artifactId>
+    <name>Keycloak Authentication Picketlink Based</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.keycloak</groupId>
+            <artifactId>keycloak-authentication-spi</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>1.6</source>
+                    <target>1.6</target>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git a/spi/authentication-picketlink/src/main/java/org/keycloak/spi/authentication/picketlink/PicketlinkAuthenticationProvider.java b/spi/authentication-picketlink/src/main/java/org/keycloak/spi/authentication/picketlink/PicketlinkAuthenticationProvider.java
new file mode 100644
index 0000000..d1343d5
--- /dev/null
+++ b/spi/authentication-picketlink/src/main/java/org/keycloak/spi/authentication/picketlink/PicketlinkAuthenticationProvider.java
@@ -0,0 +1,100 @@
+package org.keycloak.spi.authentication.picketlink;
+
+import java.util.Map;
+
+import org.jboss.logging.Logger;
+import org.jboss.resteasy.spi.ResteasyProviderFactory;
+import org.keycloak.models.RealmModel;
+import org.keycloak.spi.authentication.AuthProviderStatus;
+import org.keycloak.spi.authentication.AuthResult;
+import org.keycloak.spi.authentication.AuthProviderConstants;
+import org.keycloak.spi.authentication.AuthenticatedUser;
+import org.keycloak.spi.authentication.AuthenticationProvider;
+import org.keycloak.spi.authentication.AuthenticationProviderException;
+import org.keycloak.spi.picketlink.PartitionManagerProvider;
+import org.keycloak.util.ProviderLoader;
+import org.picketlink.idm.IdentityManager;
+import org.picketlink.idm.PartitionManager;
+import org.picketlink.idm.credential.Credentials;
+import org.picketlink.idm.credential.Password;
+import org.picketlink.idm.credential.UsernamePasswordCredentials;
+import org.picketlink.idm.model.basic.BasicModel;
+import org.picketlink.idm.model.basic.User;
+
+/**
+ * AuthenticationProvider, which delegates authentication to picketlink
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class PicketlinkAuthenticationProvider implements AuthenticationProvider {
+
+    private static final Logger logger = Logger.getLogger(PicketlinkAuthenticationProvider.class);
+
+    @Override
+    public String getName() {
+        return AuthProviderConstants.PROVIDER_NAME_PICKETLINK;
+    }
+
+    @Override
+    public AuthResult validatePassword(RealmModel realm, Map<String, String> configuration, String username, String password) throws AuthenticationProviderException {
+        IdentityManager identityManager = getIdentityManager(realm);
+
+        UsernamePasswordCredentials credential = new UsernamePasswordCredentials();
+        credential.setUsername(username);
+        credential.setPassword(new Password(password.toCharArray()));
+        identityManager.validateCredentials(credential);
+
+        AuthResult result;
+        if (credential.getStatus() == Credentials.Status.VALID) {
+            result = new AuthResult(AuthProviderStatus.SUCCESS);
+
+            User picketlinkUser = BasicModel.getUser(identityManager, username);
+            AuthenticatedUser authenticatedUser = new AuthenticatedUser(picketlinkUser.getId(), picketlinkUser.getLoginName())
+                    .setName(picketlinkUser.getFirstName(), picketlinkUser.getLastName())
+                    .setEmail(picketlinkUser.getEmail());
+            result.setUser(authenticatedUser).setProviderName(getName());
+            return result;
+        } else {
+            logger.debugf("Username: %s, Credential status: %s", username, credential.getStatus());
+            return new AuthResult(AuthProviderStatus.IGNORE);
+        }
+    }
+
+    @Override
+    public boolean updateCredential(RealmModel realm, Map<String, String> configuration, String username, String password) throws AuthenticationProviderException {
+        IdentityManager identityManager = getIdentityManager(realm);
+
+        User picketlinkUser = BasicModel.getUser(identityManager, username);
+        if (picketlinkUser == null) {
+            logger.debugf("User '%s' doesn't exists. Skip password update", username);
+            return false;
+        }
+
+        identityManager.updateCredential(picketlinkUser, new Password(password.toCharArray()));
+        return true;
+    }
+
+    protected IdentityManager getIdentityManager(RealmModel realm) throws AuthenticationProviderException {
+        IdentityManager identityManager = ResteasyProviderFactory.getContextData(IdentityManager.class);
+        if (identityManager == null) {
+            Iterable<PartitionManagerProvider> providers = ProviderLoader.load(PartitionManagerProvider.class);
+
+            // TODO: Priority?
+            PartitionManager partitionManager = null;
+            for (PartitionManagerProvider provider : providers) {
+                partitionManager = provider.getPartitionManager(realm);
+                if (partitionManager != null) {
+                    break;
+                }
+            }
+
+            if (partitionManager == null) {
+                throw new AuthenticationProviderException("Not able to locate PartitionManager with any PartitionManagerProvider");
+            }
+
+            identityManager = partitionManager.createIdentityManager();
+            ResteasyProviderFactory.pushContext(IdentityManager.class, identityManager);
+        }
+        return identityManager;
+    }
+}
diff --git a/spi/authentication-picketlink/src/main/java/org/keycloak/spi/picketlink/impl/LDAPAgentIgnoreCredentialHandler.java b/spi/authentication-picketlink/src/main/java/org/keycloak/spi/picketlink/impl/LDAPAgentIgnoreCredentialHandler.java
new file mode 100644
index 0000000..af690c0
--- /dev/null
+++ b/spi/authentication-picketlink/src/main/java/org/keycloak/spi/picketlink/impl/LDAPAgentIgnoreCredentialHandler.java
@@ -0,0 +1,27 @@
+package org.keycloak.spi.picketlink.impl;
+
+import org.picketlink.idm.IdentityManager;
+import org.picketlink.idm.ldap.internal.LDAPPlainTextPasswordCredentialHandler;
+import org.picketlink.idm.model.Account;
+import org.picketlink.idm.model.basic.User;
+import org.picketlink.idm.spi.IdentityContext;
+
+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 LDAPAgentIgnoreCredentialHandler extends LDAPPlainTextPasswordCredentialHandler {
+
+    @Override
+    protected Account 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);
+    }
+}
diff --git a/spi/authentication-picketlink/src/main/java/org/keycloak/spi/picketlink/impl/LdapConstants.java b/spi/authentication-picketlink/src/main/java/org/keycloak/spi/picketlink/impl/LdapConstants.java
new file mode 100644
index 0000000..86db5f9
--- /dev/null
+++ b/spi/authentication-picketlink/src/main/java/org/keycloak/spi/picketlink/impl/LdapConstants.java
@@ -0,0 +1,13 @@
+package org.keycloak.spi.picketlink.impl;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class LdapConstants {
+
+    public static final String CONNECTION_URL = "connectionUrl";
+    public static final String BASE_DN = "baseDn";
+    public static final String USER_DN_SUFFIX = "userDnSuffix";
+    public static final String BIND_DN = "bindDn";
+    public static final String BIND_CREDENTIAL = "bindCredential";
+}
diff --git a/spi/authentication-picketlink/src/main/java/org/keycloak/spi/picketlink/impl/PartitionManagerRegistry.java b/spi/authentication-picketlink/src/main/java/org/keycloak/spi/picketlink/impl/PartitionManagerRegistry.java
new file mode 100644
index 0000000..cf53b0f
--- /dev/null
+++ b/spi/authentication-picketlink/src/main/java/org/keycloak/spi/picketlink/impl/PartitionManagerRegistry.java
@@ -0,0 +1,88 @@
+package org.keycloak.spi.picketlink.impl;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.jboss.logging.Logger;
+import org.keycloak.models.RealmModel;
+import org.picketlink.idm.PartitionManager;
+import org.picketlink.idm.config.IdentityConfigurationBuilder;
+import org.picketlink.idm.internal.DefaultPartitionManager;
+import org.picketlink.idm.model.basic.Agent;
+import org.picketlink.idm.model.basic.User;
+
+import static org.picketlink.common.constants.LDAPConstants.CN;
+import static org.picketlink.common.constants.LDAPConstants.CREATE_TIMESTAMP;
+import static org.picketlink.common.constants.LDAPConstants.EMAIL;
+import static org.picketlink.common.constants.LDAPConstants.SN;
+import static org.picketlink.common.constants.LDAPConstants.UID;
+
+/**
+ * @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(RealmModel realm) {
+        Map<String,String> ldapConfig = realm.getLdapServerConfig();
+        if (ldapConfig == null || ldapConfig.isEmpty()) {
+            logger.warnf("Ldap configuration is missing for realm '%s'", realm.getName());
+            return null;
+        }
+
+        PartitionManagerContext context = partitionManagers.get(realm.getId());
+
+        // Ldap config might have changed for the realm. In this case, we must re-initialize
+        if (context == null || !ldapConfig.equals(context.config)) {
+            logger.infof("Creating new partition manager for the realm: %s", realm.getId());
+            PartitionManager manager = createPartitionManager(ldapConfig);
+            context = new PartitionManagerContext(ldapConfig, manager);
+            partitionManagers.put(realm.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();
+
+        // Use same mapping for User and Agent for now
+        builder
+            .named("SIMPLE_LDAP_STORE_CONFIG")
+                .stores()
+                    .ldap()
+                        .addCredentialHandler(LDAPAgentIgnoreCredentialHandler.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))
+                        .supportAllFeatures()
+                        .mapping(User.class)
+                            .baseDN(ldapConfig.get(LdapConstants.USER_DN_SUFFIX))
+                            .objectClasses("inetOrgPerson", "organizationalPerson")
+                            .attribute("loginName", UID, true)
+                            .attribute("firstName", CN)
+                            .attribute("lastName", SN)
+                            .attribute("email", EMAIL)
+                            .readOnlyAttribute("createdDate", CREATE_TIMESTAMP);
+
+        return new DefaultPartitionManager(builder.buildAll());
+    }
+
+    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/spi/authentication-picketlink/src/main/java/org/keycloak/spi/picketlink/impl/RealmPartitionManagerProvider.java b/spi/authentication-picketlink/src/main/java/org/keycloak/spi/picketlink/impl/RealmPartitionManagerProvider.java
new file mode 100644
index 0000000..596f1cc
--- /dev/null
+++ b/spi/authentication-picketlink/src/main/java/org/keycloak/spi/picketlink/impl/RealmPartitionManagerProvider.java
@@ -0,0 +1,37 @@
+package org.keycloak.spi.picketlink.impl;
+
+import org.jboss.logging.Logger;
+import org.jboss.resteasy.spi.ResteasyProviderFactory;
+import org.keycloak.models.RealmModel;
+import org.keycloak.spi.picketlink.PartitionManagerProvider;
+import org.keycloak.spi.picketlink.impl.PartitionManagerRegistry;
+import org.keycloak.util.KeycloakRegistry;
+import org.picketlink.idm.PartitionManager;
+
+/**
+ * Obtains {@link PartitionManager} instances from shared {@link PartitionManagerRegistry} and uses realm configuration for it
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class RealmPartitionManagerProvider implements PartitionManagerProvider {
+
+    private static final Logger logger = Logger.getLogger(RealmPartitionManagerProvider.class);
+
+    @Override
+    public PartitionManager getPartitionManager(RealmModel realm) {
+        KeycloakRegistry registry = ResteasyProviderFactory.getContextData(KeycloakRegistry.class) ;
+        if (registry == null) {
+            logger.warn("KeycloakRegistry not found");
+            return null;
+        }
+
+        PartitionManagerRegistry partitionManagerRegistry = registry.getService(PartitionManagerRegistry.class);
+        if (partitionManagerRegistry == null) {
+            partitionManagerRegistry = new PartitionManagerRegistry();
+            partitionManagerRegistry = registry.putServiceIfAbsent(PartitionManagerRegistry.class, partitionManagerRegistry);
+            logger.info("Pushed PartitionManagerRegistry component");
+        }
+
+        return partitionManagerRegistry.getPartitionManager(realm);
+    }
+}
diff --git a/spi/authentication-picketlink/src/main/java/org/keycloak/spi/picketlink/PartitionManagerProvider.java b/spi/authentication-picketlink/src/main/java/org/keycloak/spi/picketlink/PartitionManagerProvider.java
new file mode 100644
index 0000000..702162d
--- /dev/null
+++ b/spi/authentication-picketlink/src/main/java/org/keycloak/spi/picketlink/PartitionManagerProvider.java
@@ -0,0 +1,12 @@
+package org.keycloak.spi.picketlink;
+
+import org.keycloak.models.RealmModel;
+import org.picketlink.idm.PartitionManager;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public interface PartitionManagerProvider {
+
+    PartitionManager getPartitionManager(RealmModel realm);
+}
diff --git a/spi/authentication-picketlink/src/main/resources/META-INF/services/org.keycloak.spi.authentication.AuthenticationProvider b/spi/authentication-picketlink/src/main/resources/META-INF/services/org.keycloak.spi.authentication.AuthenticationProvider
new file mode 100644
index 0000000..8d9e9de
--- /dev/null
+++ b/spi/authentication-picketlink/src/main/resources/META-INF/services/org.keycloak.spi.authentication.AuthenticationProvider
@@ -0,0 +1 @@
+org.keycloak.spi.authentication.picketlink.PicketlinkAuthenticationProvider
\ No newline at end of file
diff --git a/spi/authentication-picketlink/src/main/resources/META-INF/services/org.keycloak.spi.picketlink.PartitionManagerProvider b/spi/authentication-picketlink/src/main/resources/META-INF/services/org.keycloak.spi.picketlink.PartitionManagerProvider
new file mode 100644
index 0000000..cc2d984
--- /dev/null
+++ b/spi/authentication-picketlink/src/main/resources/META-INF/services/org.keycloak.spi.picketlink.PartitionManagerProvider
@@ -0,0 +1 @@
+org.keycloak.spi.picketlink.impl.RealmPartitionManagerProvider
\ No newline at end of file
diff --git a/spi/authentication-spi/pom.xml b/spi/authentication-spi/pom.xml
new file mode 100644
index 0000000..1c822e4
--- /dev/null
+++ b/spi/authentication-spi/pom.xml
@@ -0,0 +1,48 @@
+<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-spi</artifactId>
+        <groupId>org.keycloak</groupId>
+        <version>1.0-beta-1-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>keycloak-authentication-spi</artifactId>
+    <name>Keycloak Authentication SPI</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.logging</groupId>
+            <artifactId>jboss-logging</artifactId>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>1.6</source>
+                    <target>1.6</target>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git a/spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthenticatedUser.java b/spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthenticatedUser.java
new file mode 100644
index 0000000..760f99c
--- /dev/null
+++ b/spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthenticatedUser.java
@@ -0,0 +1,71 @@
+package org.keycloak.spi.authentication;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class AuthenticatedUser {
+
+    private String id;
+    private String username;
+    private String firstName;
+    private String lastName;
+    private String email;
+
+    public AuthenticatedUser(String id, String username) {
+        this.id = id;
+        this.username = username;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public AuthenticatedUser setId(String id) {
+        this.id = id;
+        return this;
+    }
+
+    public String getUsername() {
+        return username;
+    }
+
+    public AuthenticatedUser setUsername(String username) {
+        this.username = username;
+        return this;
+    }
+
+    public String getFirstName() {
+        return firstName;
+    }
+
+    public AuthenticatedUser setName(String name) {
+        int i = name.lastIndexOf(' ');
+        if (i != -1) {
+            firstName  = name.substring(0, i);
+            lastName = name.substring(i + 1);
+        } else {
+            firstName = name;
+        }
+
+        return this;
+    }
+
+    public AuthenticatedUser setName(String firstName, String lastName) {
+        this.firstName = firstName;
+        this.lastName = lastName;
+        return this;
+    }
+
+    public String getLastName() {
+        return lastName;
+    }
+
+    public String getEmail() {
+        return email;
+    }
+
+    public AuthenticatedUser setEmail(String email) {
+        this.email = email;
+        return this;
+    }
+}
diff --git a/spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthenticationProvider.java b/spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthenticationProvider.java
new file mode 100644
index 0000000..a960be9
--- /dev/null
+++ b/spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthenticationProvider.java
@@ -0,0 +1,37 @@
+package org.keycloak.spi.authentication;
+
+import java.util.Map;
+
+import org.keycloak.models.RealmModel;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public interface AuthenticationProvider {
+
+    String getName();
+
+    /**
+     * Standard Authentication flow
+     *
+     * @param username
+     * @param password
+     * @return
+     */
+    AuthResult validatePassword(RealmModel realm, Map<String, String> configuration, String username, String password) throws AuthenticationProviderException;
+
+
+    /**
+     * Update credential
+     *
+     * @param realm
+     * @param configuration
+     * @param username
+     * @param password
+     * @return true if credential has been successfully updated
+     * @throws AuthenticationProviderException
+     */
+    boolean updateCredential(RealmModel realm, Map<String, String> configuration, String username, String password) throws AuthenticationProviderException;
+
+
+}
diff --git a/spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthenticationProviderException.java b/spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthenticationProviderException.java
new file mode 100644
index 0000000..301e1f9
--- /dev/null
+++ b/spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthenticationProviderException.java
@@ -0,0 +1,24 @@
+package org.keycloak.spi.authentication;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class AuthenticationProviderException extends Exception {
+
+    private static final long serialVersionUID = 15L;
+
+    protected AuthenticationProviderException() {
+    }
+
+    public AuthenticationProviderException(String message) {
+        super(message);
+    }
+
+    public AuthenticationProviderException(Throwable cause) {
+        super(cause);
+    }
+
+    public AuthenticationProviderException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthenticationProviderManager.java b/spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthenticationProviderManager.java
new file mode 100644
index 0000000..8d5cb0f
--- /dev/null
+++ b/spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthenticationProviderManager.java
@@ -0,0 +1,125 @@
+package org.keycloak.spi.authentication;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.jboss.logging.Logger;
+import org.keycloak.models.AuthenticationProviderModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.util.ProviderLoader;
+
+/**
+ * Access point to authentication SPI. It finds configured and available {@link AuthenticationProvider} instances for current realm
+ * and then delegates method call to them.
+ *
+ * Example of usage: AuthenticationProviderManager.getManager(realm).validateUser("joe", "password");
+ *
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class AuthenticationProviderManager {
+
+    private static final Logger logger = Logger.getLogger(AuthenticationProviderManager.class);
+    private static final AuthenticationProviderModel DEFAULT_PROVIDER = new AuthenticationProviderModel(AuthProviderConstants.PROVIDER_NAME_MODEL, true, Collections.EMPTY_MAP);
+
+    private final RealmModel realm;
+    private final Map<String, AuthenticationProvider> delegates;
+
+    public static AuthenticationProviderManager getManager(RealmModel realm) {
+        Iterable<AuthenticationProvider> providers = load();
+
+        Map<String, AuthenticationProvider> providersMap = new HashMap<String, AuthenticationProvider>();
+        for (AuthenticationProvider provider : providers) {
+            providersMap.put(provider.getName(), provider);
+        }
+
+        return new AuthenticationProviderManager(realm, providersMap);
+    }
+
+    private static Iterable<AuthenticationProvider> load() {
+        return ProviderLoader.load(AuthenticationProvider.class);
+    }
+
+    public AuthenticationProviderManager(RealmModel realm, Map<String, AuthenticationProvider> delegates) {
+        this.realm = realm;
+        this.delegates = delegates;
+    }
+
+    public AuthResult validatePassword(String username, String password) {
+        List<AuthenticationProviderModel> configuredProviders = getConfiguredProviders(realm);
+
+        for (AuthenticationProviderModel authProviderConfig : configuredProviders) {
+            String providerName = authProviderConfig.getProviderName();
+
+            AuthenticationProvider delegate = getDelegate(providerName);
+            if (delegate == null) {
+                continue;
+            }
+
+            try {
+                AuthResult currentResult = delegate.validatePassword(realm, authProviderConfig.getConfig(), username, password);
+                logger.debugf("Authentication provider '%s' finished with '%s' for authentication of '%s'", delegate.getName(), currentResult.toString(), username);
+
+                if (currentResult.getAuthProviderStatus() == AuthProviderStatus.SUCCESS || currentResult.getAuthProviderStatus() == AuthProviderStatus.FAILED) {
+                    return currentResult;
+                }
+            } catch (AuthenticationProviderException ape) {
+                logger.warn(ape.getMessage(), ape);
+            }
+        }
+
+        logger.debugf("Not able to authenticate '%s' with any authentication provider", username);
+
+        return new AuthResult(AuthProviderStatus.FAILED);
+    }
+
+    public void updatePassword(String username, String password) throws AuthenticationProviderException {
+        List<AuthenticationProviderModel> configuredProviders = getConfiguredProviders(realm);
+
+        for (AuthenticationProviderModel authProviderConfig : configuredProviders) {
+
+            // Update just those, which support password update
+            if (authProviderConfig.isPasswordUpdateSupported()) {
+                String providerName = authProviderConfig.getProviderName();
+                AuthenticationProvider delegate = getDelegate(providerName);
+                if (delegate == null) {
+                    continue;
+                }
+
+                try {
+                    delegate.updateCredential(realm, authProviderConfig.getConfig(), username, password);
+                    logger.debugf("Updated password in authentication provider '%s' for user '%s'", delegate.getName(), username);
+                } catch (AuthenticationProviderException ape) {
+                    // Rethrow it to upper layer
+                    logger.warn("Failed to update password", ape);
+                    throw ape;
+                }
+            } else {
+                logger.debugf("Skip password update for authentication provider '%s' for user '%s'", authProviderConfig.getProviderName(), username);
+            }
+        }
+    }
+
+    private AuthenticationProvider getDelegate(String providerName) {
+        AuthenticationProvider delegate = delegates.get(providerName);
+        if (delegate == null) {
+            logger.warnf("Configured provider with name '%s' not found", providerName);
+        }
+        return delegate;
+    }
+
+    private List<AuthenticationProviderModel> getConfiguredProviders(RealmModel realm) {
+        List<AuthenticationProviderModel> configuredProviders = realm.getAuthenticationProviders();
+
+        // Use model based authentication of current realm by default
+        if (configuredProviders == null || configuredProviders.isEmpty()) {
+            configuredProviders = new ArrayList<AuthenticationProviderModel>();
+            configuredProviders.add(DEFAULT_PROVIDER);
+        }
+
+        return configuredProviders;
+    }
+}
diff --git a/spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthProviderConstants.java b/spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthProviderConstants.java
new file mode 100644
index 0000000..cae2cb6
--- /dev/null
+++ b/spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthProviderConstants.java
@@ -0,0 +1,16 @@
+package org.keycloak.spi.authentication;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class AuthProviderConstants {
+
+    public static final String PROVIDER_NAME_MODEL = "model";
+    public static final String PROVIDER_NAME_EXTERNAL_MODEL = "externalModel";
+    public static final String PROVIDER_NAME_PICKETLINK = "picketlink";
+
+    public static final String DEFAULT_PROVIDER = PROVIDER_NAME_MODEL;
+
+    // Used in external-model provider
+    public static final String EXTERNAL_REALM_ID = "externalRealmId";
+}
diff --git a/spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthProviderStatus.java b/spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthProviderStatus.java
new file mode 100644
index 0000000..cfd16a7
--- /dev/null
+++ b/spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthProviderStatus.java
@@ -0,0 +1,12 @@
+package org.keycloak.spi.authentication;
+
+/**
+ * Result of authentication by AuthenticationProvider
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public enum AuthProviderStatus {
+
+    // Ignore means that AuthenticationProvider wasn't able to authenticate result, but it should postpone authentication to next provider (for example user didn't exists in realm)
+    SUCCESS, FAILED, IGNORE
+}
diff --git a/spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthResult.java b/spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthResult.java
new file mode 100644
index 0000000..fd52d67
--- /dev/null
+++ b/spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthResult.java
@@ -0,0 +1,44 @@
+package org.keycloak.spi.authentication;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class AuthResult {
+
+    // Status of authentication
+    private final AuthProviderStatus authProviderStatus;
+
+    // Provider, which authenticated user
+    private String providerName;
+
+    // filled usually only in case of successful authentication and just with some Authentication providers
+    private AuthenticatedUser authenticatedUser;
+
+    public AuthResult(AuthProviderStatus authProviderStatus) {
+        this.authProviderStatus = authProviderStatus;
+    }
+
+    public AuthResult setProviderName(String providerName) {
+        this.providerName = providerName;
+        return this;
+    }
+
+    public AuthResult setUser(AuthenticatedUser user) {
+        this.authenticatedUser = user;
+        return this;
+    }
+
+    public AuthProviderStatus getAuthProviderStatus() {
+        return authProviderStatus;
+    }
+
+    public String getProviderName() {
+        return providerName;
+    }
+
+    public AuthenticatedUser getAuthenticatedUser() {
+        return authenticatedUser;
+    }
+
+
+}

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

diff --git a/spi/pom.xml b/spi/pom.xml
new file mode 100644
index 0000000..ea5df57
--- /dev/null
+++ b/spi/pom.xml
@@ -0,0 +1,24 @@
+<?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-1-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <packaging>pom</packaging>
+
+    <artifactId>keycloak-spi</artifactId>
+    <name>Keycloak SPI</name>
+    <description />
+
+    <modules>
+        <module>authentication-spi</module>
+        <module>authentication-model</module>
+        <module>authentication-picketlink</module>
+    </modules>
+
+</project>
\ No newline at end of file
diff --git a/testsuite/integration/pom.xml b/testsuite/integration/pom.xml
index 1591c7f..e784d04 100755
--- a/testsuite/integration/pom.xml
+++ b/testsuite/integration/pom.xml
@@ -139,6 +139,16 @@
             <artifactId>keycloak-login-freemarker</artifactId>
             <version>${project.version}</version>
         </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-authentication-spi</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-authentication-model</artifactId>
+            <version>${project.version}</version>
+        </dependency>
 
         <dependency>
             <groupId>org.jboss.logging</groupId>
@@ -255,6 +265,12 @@
         <dependency>
             <groupId>com.icegreen</groupId>
             <artifactId>greenmail</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.slf4j</groupId>
+                    <artifactId>slf4j-api</artifactId>
+                </exclusion>
+            </exclusions>
         </dependency>
         <dependency>
             <groupId>org.seleniumhq.selenium</groupId>
@@ -284,6 +300,13 @@
             <artifactId>picketlink-common</artifactId>
         </dependency>
 
+        <!-- This adds couple of other dependencies (like picketlink) -->
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-model-tests</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
     </dependencies>
     <build>
         <plugins>
diff --git a/testsuite/integration/src/main/resources/META-INF/persistence.xml b/testsuite/integration/src/main/resources/META-INF/persistence.xml
index 8fa01f2..5ca96af 100755
--- a/testsuite/integration/src/main/resources/META-INF/persistence.xml
+++ b/testsuite/integration/src/main/resources/META-INF/persistence.xml
@@ -10,9 +10,11 @@
         <class>org.keycloak.models.jpa.entities.OAuthClientEntity</class>
         <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.ApplicationRoleEntity</class>
         <class>org.keycloak.models.jpa.entities.RealmRoleEntity</class>
         <class>org.keycloak.models.jpa.entities.SocialLinkEntity</class>
+        <class>org.keycloak.models.jpa.entities.AuthenticationLinkEntity</class>
         <class>org.keycloak.models.jpa.entities.UserEntity</class>
         <class>org.keycloak.models.jpa.entities.UserRoleMappingEntity</class>
         <class>org.keycloak.models.jpa.entities.ScopeMappingEntity</class>
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/AuthProvidersIntegrationTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/AuthProvidersIntegrationTest.java
new file mode 100644
index 0000000..f98ee63
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/AuthProvidersIntegrationTest.java
@@ -0,0 +1,186 @@
+package org.keycloak.testsuite.forms;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+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.models.AuthenticationProviderModel;
+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.spi.authentication.AuthProviderConstants;
+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.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;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class AuthProvidersIntegrationTest {
+
+    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(appRealm, "mary", "mary@test.com", "password-app");
+            addUser(adminstrationRealm, "mary", "mary@admin.com", "password-admin");
+
+            AuthenticationProviderModel modelProvider = new AuthenticationProviderModel(AuthProviderConstants.PROVIDER_NAME_MODEL, false, Collections.EMPTY_MAP);
+            AuthenticationProviderModel picketlinkProvider = new AuthenticationProviderModel(AuthProviderConstants.PROVIDER_NAME_PICKETLINK, true, Collections.EMPTY_MAP);
+
+            // Configure LDAP
+            ldapRule.getEmbeddedServer().setupLdapInRealm(appRealm);
+
+            // Delegate authentication to admin realm
+            Map<String,String> config = new HashMap<String,String>();
+            config.put(AuthProviderConstants.EXTERNAL_REALM_ID, adminstrationRealm.getId());
+            AuthenticationProviderModel externalModelProvider = new AuthenticationProviderModel(AuthProviderConstants.PROVIDER_NAME_EXTERNAL_MODEL, true, config);
+
+            appRealm.setAuthenticationProviders(Arrays.asList(modelProvider, picketlinkProvider, externalModelProvider));
+        }
+    });
+
+    @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 LoginPage loginPage;
+
+    @WebResource
+    protected AccountUpdateProfilePage profilePage;
+
+    @WebResource
+    protected AccountPasswordPage changePasswordPage;
+
+    private static UserModel addUser(RealmModel realm, String username, String email, String password) {
+        UserModel user = realm.addUser(username);
+        user.setEmail(email);
+        user.setEnabled(true);
+
+        UserCredentialModel creds = new UserCredentialModel();
+        creds.setType(CredentialRepresentation.PASSWORD);
+        creds.setValue(password);
+
+        realm.updateCredential(user, 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 loginExternalModel() {
+        loginPage.open();
+        loginPage.login("mary", "password-admin");
+
+        Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
+        Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
+    }
+
+    @Test
+    public void loginLdap() {
+        loginPage.open();
+        loginPage.login("john", "password");
+
+        Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
+        Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
+    }
+
+    @Test
+    public void passwordChangeExternalModel() {
+        // Set password-policy for admin realm
+        keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
+            @Override
+            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                adminstrationRealm.setPasswordPolicy(new PasswordPolicy("length(6)"));
+            }
+        });
+
+        try {
+            changePasswordPage.open();
+            loginPage.login("mary", "password-admin");
+
+            // Can't update to "pass" due to passwordPolicy
+            changePasswordPage.changePassword("password-admin", "pass", "pass");
+            Assert.assertEquals("Invalid password: minimum length 6", profilePage.getError());
+
+            changePasswordPage.changePassword("password-app", "password-updated", "password-updated");
+            Assert.assertEquals("Your password has been updated", profilePage.getSuccess());
+            changePasswordPage.logout();
+
+            loginPage.open();
+            loginPage.login("mary", "password-updated");
+            Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
+
+        } finally {
+            keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
+                @Override
+                public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                    adminstrationRealm.setPasswordPolicy(new PasswordPolicy(null));
+                }
+            });
+        }
+    }
+
+    @Test
+    public void passwordChangeLdap() {
+        changePasswordPage.open();
+        loginPage.login("john", "password");
+        changePasswordPage.changePassword("password", "new-password", "new-password");
+
+        Assert.assertEquals("Your password has been updated", profilePage.getSuccess());
+
+        changePasswordPage.logout();
+
+        loginPage.open();
+        loginPage.login("john", "password");
+        Assert.assertEquals("Invalid username or password.", loginPage.getError());
+
+        loginPage.open();
+        loginPage.login("john", "new-password");
+        Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
+    }
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/LDAPRule.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/LDAPRule.java
new file mode 100644
index 0000000..3409b0e
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/LDAPRule.java
@@ -0,0 +1,36 @@
+package org.keycloak.testsuite.rule;
+
+import org.junit.rules.ExternalResource;
+import org.keycloak.model.test.LDAPEmbeddedServer;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class LDAPRule extends ExternalResource {
+
+    private LDAPEmbeddedServer embeddedServer;
+
+    @Override
+    protected void before() throws Throwable {
+        try {
+            embeddedServer = new LDAPEmbeddedServer();
+            embeddedServer.setup();
+            embeddedServer.importLDIF("ldap/users.ldif");
+        } catch (Exception e) {
+            throw new RuntimeException("Error starting Embedded LDAP server.", e);
+        }
+    }
+
+    @Override
+    protected void after() {
+        try {
+            embeddedServer.tearDown();
+        } catch (Exception e) {
+            throw new RuntimeException("Error starting Embedded LDAP server.", e);
+        }
+    }
+
+    public LDAPEmbeddedServer getEmbeddedServer() {
+        return embeddedServer;
+    }
+}
diff --git a/testsuite/integration/src/test/resources/ldap/ldap-connection.properties b/testsuite/integration/src/test/resources/ldap/ldap-connection.properties
new file mode 100644
index 0000000..5ccc6ad
--- /dev/null
+++ b/testsuite/integration/src/test/resources/ldap/ldap-connection.properties
@@ -0,0 +1,9 @@
+idm.test.ldap.connection.url=ldap\://localhost\:10389
+idm.test.ldap.base.dn=dc\=keycloak,dc\=org
+idm.test.ldap.roles.dn.suffix=ou\=Roles,dc\=keycloak,dc\=org
+idm.test.ldap.group.dn.suffix=ou\=Groups,dc\=keycloak,dc\=org
+idm.test.ldap.user.dn.suffix=ou\=People,dc\=keycloak,dc\=org
+idm.test.ldap.agent.dn.suffix=ou\=Agent,dc\=keycloak,dc\=org
+idm.test.ldap.start.embedded.ldap.server=true
+idm.test.ldap.bind.dn=uid\=admin,ou\=system
+idm.test.ldap.bind.credential=secret
\ No newline at end of file
diff --git a/testsuite/integration/src/test/resources/ldap/users.ldif b/testsuite/integration/src/test/resources/ldap/users.ldif
new file mode 100644
index 0000000..9f72f65
--- /dev/null
+++ b/testsuite/integration/src/test/resources/ldap/users.ldif
@@ -0,0 +1,31 @@
+dn: dc=keycloak,dc=org
+objectclass: dcObject
+objectclass: organization
+o: Keycloak
+dc: Keycloak
+
+dn: ou=People,dc=keycloak,dc=org
+objectclass: top
+objectclass: organizationalUnit
+ou: People
+
+dn: ou=Roles,dc=keycloak,dc=org
+objectclass: top
+objectclass: organizationalUnit
+ou: Roles
+
+dn: ou=Groups,dc=keycloak,dc=org
+objectclass: top
+objectclass: organizationalUnit
+ou: Groups
+
+dn: uid=john,ou=People,dc=keycloak,dc=org
+objectclass: top
+objectclass: uidObject
+objectclass: person
+objectclass: inetOrgPerson
+uid: john
+cn: John
+sn: Doe
+mail: john@email.org
+userPassword: password
\ No newline at end of file
diff --git a/testsuite/performance/src/test/resources/META-INF/persistence.xml b/testsuite/performance/src/test/resources/META-INF/persistence.xml
index a020d60..5ca96af 100755
--- a/testsuite/performance/src/test/resources/META-INF/persistence.xml
+++ b/testsuite/performance/src/test/resources/META-INF/persistence.xml
@@ -10,12 +10,14 @@
         <class>org.keycloak.models.jpa.entities.OAuthClientEntity</class>
         <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.ApplicationRoleEntity</class>
         <class>org.keycloak.models.jpa.entities.RealmRoleEntity</class>
         <class>org.keycloak.models.jpa.entities.SocialLinkEntity</class>
+        <class>org.keycloak.models.jpa.entities.AuthenticationLinkEntity</class>
         <class>org.keycloak.models.jpa.entities.UserEntity</class>
         <class>org.keycloak.models.jpa.entities.UserRoleMappingEntity</class>
-        <class>org.keycloak.models.jpa.entities.UserScopeMappingEntity</class>
+        <class>org.keycloak.models.jpa.entities.ScopeMappingEntity</class>
 
         <exclude-unlisted-classes>true</exclude-unlisted-classes>