keycloak-aplcache

merge

5/22/2015 5:15:55 PM

Changes

broker/pom.xml 2(+1 -1)

core/pom.xml 2(+1 -1)

docbook/pom.xml 2(+1 -1)

events/pom.xml 2(+1 -1)

examples/pom.xml 2(+1 -1)

federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/AbstractAttributedType.java 85(+0 -85)

federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/AbstractIdentityType.java 70(+0 -70)

federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/Attribute.java 80(+0 -80)

federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/AttributedType.java 75(+0 -75)

federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/AttributeProperty.java 31(+0 -31)

federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/IdentityType.java 100(+0 -100)

federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/LDAPUser.java 85(+0 -85)

federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/AttributeParameter.java 21(+0 -21)

federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/IdentityQuery.java 225(+0 -225)

federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/IdentityQueryBuilder.java 124(+0 -124)

federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/DefaultIdentityQuery.java 207(+0 -207)

federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPIdentityStoreConfiguration.java 188(+0 -188)

federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPMappingConfiguration.java 231(+0 -231)

forms/pom.xml 2(+1 -1)

integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/AbstractAddStepHandlerWithAttributes.java 57(+0 -57)

integration/keycloak-as7-subsystem/src/main/resources/META-INF/services/org.jboss.as.controller.Extension 1(+0 -1)

model/pom.xml 2(+1 -1)

pom.xml 84(+44 -40)

proxy/pom.xml 2(+1 -1)

saml/pom.xml 2(+1 -1)

services/pom.xml 2(+1 -1)

social/pom.xml 2(+1 -1)

timer/pom.xml 2(+1 -1)

Details

diff --git a/broker/core/pom.xml b/broker/core/pom.xml
index c5b5bb8..41d236e 100755
--- a/broker/core/pom.xml
+++ b/broker/core/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/broker/oidc/pom.xml b/broker/oidc/pom.xml
index 0d5533e..982a63c 100755
--- a/broker/oidc/pom.xml
+++ b/broker/oidc/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>

broker/pom.xml 2(+1 -1)

diff --git a/broker/pom.xml b/broker/pom.xml
index 286185f..489e16c 100755
--- a/broker/pom.xml
+++ b/broker/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/broker/saml/pom.xml b/broker/saml/pom.xml
index 055e2aa..f87fb51 100755
--- a/broker/saml/pom.xml
+++ b/broker/saml/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/connections/file/pom.xml b/connections/file/pom.xml
index f15fc87..13e3e6c 100755
--- a/connections/file/pom.xml
+++ b/connections/file/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/connections/http-client/pom.xml b/connections/http-client/pom.xml
index e0ba01e..2306cf7 100755
--- a/connections/http-client/pom.xml
+++ b/connections/http-client/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/connections/infinispan/pom.xml b/connections/infinispan/pom.xml
index b22469c..8febd5b 100755
--- a/connections/infinispan/pom.xml
+++ b/connections/infinispan/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/connections/jpa/pom.xml b/connections/jpa/pom.xml
index 62e0d05..1ef75f2 100755
--- a/connections/jpa/pom.xml
+++ b/connections/jpa/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/connections/jpa/src/main/resources/META-INF/persistence.xml b/connections/jpa/src/main/resources/META-INF/persistence.xml
index 6399049..be9c281 100755
--- a/connections/jpa/src/main/resources/META-INF/persistence.xml
+++ b/connections/jpa/src/main/resources/META-INF/persistence.xml
@@ -9,6 +9,7 @@
         <class>org.keycloak.models.jpa.entities.RealmAttributeEntity</class>
         <class>org.keycloak.models.jpa.entities.RequiredCredentialEntity</class>
         <class>org.keycloak.models.jpa.entities.UserFederationProviderEntity</class>
+        <class>org.keycloak.models.jpa.entities.UserFederationMapperEntity</class>
         <class>org.keycloak.models.jpa.entities.RoleEntity</class>
         <class>org.keycloak.models.jpa.entities.FederatedIdentityEntity</class>
         <class>org.keycloak.models.jpa.entities.MigrationModelEntity</class>
diff --git a/connections/jpa-liquibase/pom.xml b/connections/jpa-liquibase/pom.xml
index 0c662f3..37e921e 100755
--- a/connections/jpa-liquibase/pom.xml
+++ b/connections/jpa-liquibase/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.3.0.Beta1.xml b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.3.0.Beta1.xml
index 516b9f4..c469581 100755
--- a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.3.0.Beta1.xml
+++ b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.3.0.Beta1.xml
@@ -2,6 +2,7 @@
 <databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
     <changeSet author="bburke@redhat.com" id="1.3.0.Beta1">
         <delete tableName="CLIENT_SESSION_ROLE"/>
+        <delete tableName="CLIENT_SESSION_PROT_MAPPER"/>
         <delete tableName="CLIENT_SESSION_NOTE"/>
         <delete tableName="CLIENT_SESSION"/>
         <delete tableName="USER_SESSION_NOTE"/>
@@ -59,6 +60,33 @@
                 <constraints nullable="false"/>
             </column>
         </createTable>
+        <createTable tableName="USER_FEDERATION_MAPPER">
+            <column name="ID" type="VARCHAR(36)">
+                <constraints nullable="false"/>
+            </column>
+            <column name="NAME" type="VARCHAR(255)">
+                <constraints nullable="false"/>
+            </column>
+            <column name="FEDERATION_PROVIDER_ID" type="VARCHAR(36)">
+                <constraints nullable="false"/>
+            </column>
+            <column name="FEDERATION_MAPPER_TYPE" type="VARCHAR(255)">
+                <constraints nullable="false"/>
+            </column>
+            <column name="REALM_ID" type="VARCHAR(36)">
+                <constraints nullable="false"/>
+            </column>
+        </createTable>
+        <createTable tableName="USER_FEDERATION_MAPPER_CONFIG">
+            <column name="USER_FEDERATION_MAPPER_ID" type="VARCHAR(36)">
+                <constraints nullable="false"/>
+            </column>
+            <column name="VALUE" type="VARCHAR(255)"/>
+            <column name="NAME" type="VARCHAR(255)">
+                <constraints nullable="false"/>
+            </column>
+        </createTable>
+
         <addColumn tableName="REALM">
             <column name="ADMIN_EVENTS_ENABLED" type="BOOLEAN" defaultValueBoolean="false">
                 <constraints nullable="false"/>
@@ -109,10 +137,15 @@
         <dropColumn tableName="USER_REQUIRED_ACTION" columnName="ACTION"/>
         <addPrimaryKey columnNames="REQUIRED_ACTION, USER_ID" constraintName="CONSTRAINT_REQUIRED_ACTION" tableName="USER_REQUIRED_ACTION"/>
         <addPrimaryKey columnNames="CLIENT_SESSION, AUTHENTICATOR" constraintName="CONSTRAINT_AUTH_STATUS_PK" tableName="CLIENT_SESSION_AUTH_STATUS"/>
+        <addPrimaryKey columnNames="ID" constraintName="CONSTRAINT_FEDMAPPERPM" tableName="USER_FEDERATION_MAPPER"/>
+        <addPrimaryKey columnNames="USER_FEDERATION_MAPPER_ID, NAME" constraintName="CONSTRAINT_FEDMAPPER_CFG_PM" tableName="USER_FEDERATION_MAPPER_CONFIG"/>
         <addForeignKeyConstraint baseColumnNames="CLIENT_SESSION" baseTableName="CLIENT_SESSION_AUTH_STATUS" constraintName="AUTH_STATUS_CONSTRAINT" referencedColumnNames="ID" referencedTableName="CLIENT_SESSION"/>
         <addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="AUTHENTICATOR" constraintName="FK_AUTHENTICATOR_REALM" referencedColumnNames="ID" referencedTableName="REALM"/>
         <addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="AUTHENTICATION_FLOW" constraintName="FK_AUTHENTICATION_FLOW_REALM" referencedColumnNames="ID" referencedTableName="REALM"/>
         <addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="AUTHENTICATION_EXECUTION" constraintName="FK_AUTHENTICATION_EXECUTION_REALM" referencedColumnNames="ID" referencedTableName="REALM"/>
         <addForeignKeyConstraint baseColumnNames="FLOW_ID" baseTableName="AUTHENTICATION_EXECUTION" constraintName="FK_AUTHENTICATION_EXECUTION_FLOW" referencedColumnNames="ID" referencedTableName="AUTHENTICATION_FLOW"/>
+        <addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="USER_FEDERATION_MAPPER" constraintName="FK_FEDMAPPERPM_REALM" referencedColumnNames="ID" referencedTableName="REALM"/>
+        <addForeignKeyConstraint baseColumnNames="FEDERATION_PROVIDER_ID" baseTableName="USER_FEDERATION_MAPPER" constraintName="FK_FEDMAPPERPM_FEDPRV" referencedColumnNames="ID" referencedTableName="USER_FEDERATION_PROVIDER"/>
+        <addForeignKeyConstraint baseColumnNames="USER_FEDERATION_MAPPER_ID" baseTableName="USER_FEDERATION_MAPPER_CONFIG" constraintName="FK_FEDMAPPER_CFG" referencedColumnNames="ID" referencedTableName="USER_FEDERATION_MAPPER"/>
     </changeSet>
 </databaseChangeLog>
diff --git a/connections/mongo/pom.xml b/connections/mongo/pom.xml
index 593e3c7..2497188 100755
--- a/connections/mongo/pom.xml
+++ b/connections/mongo/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/connections/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java b/connections/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java
index 73d6b84..ed6844c 100755
--- a/connections/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java
+++ b/connections/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java
@@ -39,6 +39,7 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro
             "org.keycloak.models.sessions.mongo.entities.MongoUserSessionEntity",
             "org.keycloak.models.sessions.mongo.entities.MongoClientSessionEntity",
             "org.keycloak.models.entities.UserFederationProviderEntity",
+            "org.keycloak.models.entities.UserFederationMapperEntity",
             "org.keycloak.models.entities.ProtocolMapperEntity",
             "org.keycloak.models.entities.IdentityProviderMapperEntity",
             "org.keycloak.models.mongo.keycloak.entities.MongoUserConsentEntity",
diff --git a/connections/mongo-update/pom.xml b/connections/mongo-update/pom.xml
index 620f935..a3a1eae 100755
--- a/connections/mongo-update/pom.xml
+++ b/connections/mongo-update/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/connections/pom.xml b/connections/pom.xml
index 8d3c82a..5139c00 100755
--- a/connections/pom.xml
+++ b/connections/pom.xml
@@ -3,7 +3,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
     </parent>
     <name>Connections Parent</name>
     <description/>

core/pom.xml 2(+1 -1)

diff --git a/core/pom.xml b/core/pom.xml
index f7cdce2..9cbe5bd 100755
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
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 0240a8d..6ff027c 100755
--- a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
@@ -53,6 +53,7 @@ public class RealmRepresentation {
     protected Map<String, String> browserSecurityHeaders;
     protected Map<String, String> smtpServer;
     protected List<UserFederationProviderRepresentation> userFederationProviders;
+    protected List<UserFederationMapperRepresentation> userFederationMappers;
     protected String loginTheme;
     protected String accountTheme;
     protected String adminTheme;
@@ -536,6 +537,19 @@ public class RealmRepresentation {
         this.userFederationProviders = userFederationProviders;
     }
 
+    public List<UserFederationMapperRepresentation> getUserFederationMappers() {
+        return userFederationMappers;
+    }
+
+    public void setUserFederationMappers(List<UserFederationMapperRepresentation> userFederationMappers) {
+        this.userFederationMappers = userFederationMappers;
+    }
+
+    public void addUserFederationMapper(UserFederationMapperRepresentation userFederationMapper) {
+        if (userFederationMappers == null) userFederationMappers = new LinkedList<>();
+        userFederationMappers.add(userFederationMapper);
+    }
+
     public List<IdentityProviderRepresentation> getIdentityProviders() {
         return identityProviders;
     }
diff --git a/core/src/main/java/org/keycloak/representations/idm/UserFederationMapperRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/UserFederationMapperRepresentation.java
new file mode 100644
index 0000000..2e87e4e
--- /dev/null
+++ b/core/src/main/java/org/keycloak/representations/idm/UserFederationMapperRepresentation.java
@@ -0,0 +1,56 @@
+package org.keycloak.representations.idm;
+
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class UserFederationMapperRepresentation {
+
+    protected String id;
+    protected String name;
+    protected String federationProviderDisplayName;
+    protected String federationMapperType;
+    protected Map<String, String> config;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getFederationProviderDisplayName() {
+        return federationProviderDisplayName;
+    }
+
+    public void setFederationProviderDisplayName(String federationProviderDisplayName) {
+        this.federationProviderDisplayName = federationProviderDisplayName;
+    }
+
+    public String getFederationMapperType() {
+        return federationMapperType;
+    }
+
+    public void setFederationMapperType(String federationMapperType) {
+        this.federationMapperType = federationMapperType;
+    }
+
+    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/UserFederationMapperTypeRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/UserFederationMapperTypeRepresentation.java
new file mode 100644
index 0000000..f03b376
--- /dev/null
+++ b/core/src/main/java/org/keycloak/representations/idm/UserFederationMapperTypeRepresentation.java
@@ -0,0 +1,55 @@
+package org.keycloak.representations.idm;
+
+import java.util.List;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class UserFederationMapperTypeRepresentation {
+    protected String id;
+    protected String name;
+    protected String category;
+    protected String helpText;
+
+    protected List<ConfigPropertyRepresentation> properties;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getCategory() {
+        return category;
+    }
+
+    public void setCategory(String category) {
+        this.category = category;
+    }
+
+    public String getHelpText() {
+        return helpText;
+    }
+
+    public void setHelpText(String helpText) {
+        this.helpText = helpText;
+    }
+
+    public List<ConfigPropertyRepresentation> getProperties() {
+        return properties;
+    }
+
+    public void setProperties(List<ConfigPropertyRepresentation> properties) {
+        this.properties = properties;
+    }
+}
diff --git a/core-jaxrs/pom.xml b/core-jaxrs/pom.xml
index a564cf1..5a8b2c8 100755
--- a/core-jaxrs/pom.xml
+++ b/core-jaxrs/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/dependencies/pom.xml b/dependencies/pom.xml
index 4991453..374a4f8 100755
--- a/dependencies/pom.xml
+++ b/dependencies/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
     </parent>
 
     <modelVersion>4.0.0</modelVersion>
diff --git a/dependencies/server-all/pom.xml b/dependencies/server-all/pom.xml
index 5d4de4d..707933a 100755
--- a/dependencies/server-all/pom.xml
+++ b/dependencies/server-all/pom.xml
@@ -4,7 +4,7 @@
 	<parent>
 		<artifactId>keycloak-parent</artifactId>
 		<groupId>org.keycloak</groupId>
-		<version>1.3.0.Beta1-SNAPSHOT</version>
+		<version>1.3.0.Final-SNAPSHOT</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<modelVersion>4.0.0</modelVersion>
diff --git a/dependencies/server-min/pom.xml b/dependencies/server-min/pom.xml
index f22c995..ae24777 100755
--- a/dependencies/server-min/pom.xml
+++ b/dependencies/server-min/pom.xml
@@ -4,7 +4,7 @@
 	<parent>
 		<artifactId>keycloak-parent</artifactId>
 		<groupId>org.keycloak</groupId>
-		<version>1.3.0.Beta1-SNAPSHOT</version>
+		<version>1.3.0.Final-SNAPSHOT</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<modelVersion>4.0.0</modelVersion>
diff --git a/distribution/adapters/as7-adapter-zip/assembly.xml b/distribution/adapters/as7-adapter-zip/assembly.xml
index 9eacaec..7c141ff 100755
--- a/distribution/adapters/as7-adapter-zip/assembly.xml
+++ b/distribution/adapters/as7-adapter-zip/assembly.xml
@@ -17,7 +17,6 @@
                 <include>org/keycloak/keycloak-core/**</include>
                 <include>org/keycloak/keycloak-adapter-core/**</include>
                 <include>org/keycloak/keycloak-jboss-adapter-core/**</include>
-                <include>org/keycloak/keycloak-undertow-adapter/**</include>
                 <include>org/keycloak/keycloak-as7-adapter/**</include>
                 <include>org/keycloak/keycloak-as7-subsystem/**</include>
             </includes>
diff --git a/distribution/adapters/as7-adapter-zip/pom.xml b/distribution/adapters/as7-adapter-zip/pom.xml
index 4562981..7a5921d 100755
--- a/distribution/adapters/as7-adapter-zip/pom.xml
+++ b/distribution/adapters/as7-adapter-zip/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../../pom.xml</relativePath>
     </parent>
 
diff --git a/distribution/adapters/eap6-adapter-zip/assembly.xml b/distribution/adapters/eap6-adapter-zip/assembly.xml
index 1720476..6247e93 100755
--- a/distribution/adapters/eap6-adapter-zip/assembly.xml
+++ b/distribution/adapters/eap6-adapter-zip/assembly.xml
@@ -11,15 +11,14 @@
         <fileSet>
             <directory>${project.build.directory}/unpacked</directory>
             <includes>
-                <include>net/iharder/base64/**</include>
                 <include>org/bouncycastle/**</include>
+                <include>net/iharder/base64/**</include>
                 <include>org/apache/httpcomponents/**</include>
                 <include>org/keycloak/keycloak-core/**</include>
                 <include>org/keycloak/keycloak-adapter-core/**</include>
                 <include>org/keycloak/keycloak-jboss-adapter-core/**</include>
-                <include>org/keycloak/keycloak-undertow-adapter/**</include>
                 <include>org/keycloak/keycloak-as7-adapter/**</include>
-                <include>org/keycloak/keycloak-subsystem/**</include>
+                <include>org/keycloak/keycloak-as7-subsystem/**</include>
             </includes>
             <excludes>
                 <exclude>**/*.war</exclude>
diff --git a/distribution/adapters/eap6-adapter-zip/pom.xml b/distribution/adapters/eap6-adapter-zip/pom.xml
index a9fd63a..2bc83d6 100755
--- a/distribution/adapters/eap6-adapter-zip/pom.xml
+++ b/distribution/adapters/eap6-adapter-zip/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../../pom.xml</relativePath>
     </parent>
 
diff --git a/distribution/adapters/jetty81-adapter-zip/pom.xml b/distribution/adapters/jetty81-adapter-zip/pom.xml
index 9ed03b7..233d0ff 100755
--- a/distribution/adapters/jetty81-adapter-zip/pom.xml
+++ b/distribution/adapters/jetty81-adapter-zip/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../../pom.xml</relativePath>
     </parent>
 
diff --git a/distribution/adapters/jetty91-adapter-zip/pom.xml b/distribution/adapters/jetty91-adapter-zip/pom.xml
index c57e1c3..75b455b 100755
--- a/distribution/adapters/jetty91-adapter-zip/pom.xml
+++ b/distribution/adapters/jetty91-adapter-zip/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../../pom.xml</relativePath>
     </parent>
 
diff --git a/distribution/adapters/jetty92-adapter-zip/pom.xml b/distribution/adapters/jetty92-adapter-zip/pom.xml
index 9134f0c..f26c50e 100755
--- a/distribution/adapters/jetty92-adapter-zip/pom.xml
+++ b/distribution/adapters/jetty92-adapter-zip/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../../pom.xml</relativePath>
     </parent>
 
diff --git a/distribution/adapters/js-adapter-zip/pom.xml b/distribution/adapters/js-adapter-zip/pom.xml
index 1cbdc8c..090d401 100755
--- a/distribution/adapters/js-adapter-zip/pom.xml
+++ b/distribution/adapters/js-adapter-zip/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../../pom.xml</relativePath>
     </parent>
 
diff --git a/distribution/adapters/osgi/features/pom.xml b/distribution/adapters/osgi/features/pom.xml
index 198d907..850e2ed 100755
--- a/distribution/adapters/osgi/features/pom.xml
+++ b/distribution/adapters/osgi/features/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../../../pom.xml</relativePath>
     </parent>
     <name>Keycloak OSGI Features</name>
diff --git a/distribution/adapters/osgi/jaas/pom.xml b/distribution/adapters/osgi/jaas/pom.xml
index 4906bbb..9e79791 100755
--- a/distribution/adapters/osgi/jaas/pom.xml
+++ b/distribution/adapters/osgi/jaas/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../../../pom.xml</relativePath>
     </parent>
     <name>Keycloak OSGI JAAS Realm Configuration</name>
diff --git a/distribution/adapters/osgi/pom.xml b/distribution/adapters/osgi/pom.xml
index 06cf390..dd73ecb 100755
--- a/distribution/adapters/osgi/pom.xml
+++ b/distribution/adapters/osgi/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../../pom.xml</relativePath>
     </parent>
     <name>Keycloak OSGI Integration</name>
diff --git a/distribution/adapters/osgi/thirdparty/pom.xml b/distribution/adapters/osgi/thirdparty/pom.xml
index 89eb967..7a3546e 100755
--- a/distribution/adapters/osgi/thirdparty/pom.xml
+++ b/distribution/adapters/osgi/thirdparty/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../../../pom.xml</relativePath>
     </parent>
 
diff --git a/distribution/adapters/pom.xml b/distribution/adapters/pom.xml
index c85ee02..612ecae 100755
--- a/distribution/adapters/pom.xml
+++ b/distribution/adapters/pom.xml
@@ -3,7 +3,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
 
diff --git a/distribution/adapters/tomcat6-adapter-zip/pom.xml b/distribution/adapters/tomcat6-adapter-zip/pom.xml
index e040a3c..604fb26 100755
--- a/distribution/adapters/tomcat6-adapter-zip/pom.xml
+++ b/distribution/adapters/tomcat6-adapter-zip/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../../pom.xml</relativePath>
     </parent>
 
diff --git a/distribution/adapters/tomcat7-adapter-zip/pom.xml b/distribution/adapters/tomcat7-adapter-zip/pom.xml
index 3997e64..89d336b 100755
--- a/distribution/adapters/tomcat7-adapter-zip/pom.xml
+++ b/distribution/adapters/tomcat7-adapter-zip/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../../pom.xml</relativePath>
     </parent>
 
diff --git a/distribution/adapters/tomcat8-adapter-zip/pom.xml b/distribution/adapters/tomcat8-adapter-zip/pom.xml
index 685f662..a45c548 100755
--- a/distribution/adapters/tomcat8-adapter-zip/pom.xml
+++ b/distribution/adapters/tomcat8-adapter-zip/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../../pom.xml</relativePath>
     </parent>
 
diff --git a/distribution/adapters/wildfly-adapter-zip/pom.xml b/distribution/adapters/wildfly-adapter-zip/pom.xml
index e88ac4c..23e4efa 100755
--- a/distribution/adapters/wildfly-adapter-zip/pom.xml
+++ b/distribution/adapters/wildfly-adapter-zip/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../../pom.xml</relativePath>
     </parent>
 
diff --git a/distribution/demo-dist/assembly.xml b/distribution/demo-dist/assembly.xml
index b45f881..f00bfea 100755
--- a/distribution/demo-dist/assembly.xml
+++ b/distribution/demo-dist/assembly.xml
@@ -14,7 +14,6 @@
             <outputDirectory>keycloak</outputDirectory>
             <excludes>
                 <exclude>**/*.sh</exclude>
-                <exclude>standalone/configuration/standalone.xml</exclude>
                 <exclude>standalone/configuration/standalone-keycloak.xml</exclude>
             </excludes>
         </fileSet>
@@ -36,11 +35,4 @@
         </fileSet>
     </fileSets>
 
-    <files>
-        <file>
-            <source>${project.build.directory}/unpacked/wildfly-${wildfly.version}/standalone/configuration/standalone-keycloak.xml</source>
-            <outputDirectory>keycloak/standalone/configuration</outputDirectory>
-            <destName>standalone.xml</destName>
-        </file>
-    </files>
 </assembly>
diff --git a/distribution/demo-dist/pom.xml b/distribution/demo-dist/pom.xml
index c946871..37e4bdc 100755
--- a/distribution/demo-dist/pom.xml
+++ b/distribution/demo-dist/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
 
@@ -115,6 +115,32 @@
                     </execution>
                 </executions>
             </plugin>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>xml-maven-plugin</artifactId>
+                <version>1.0</version>
+                <executions>
+                    <execution>
+                        <id>generate-resources</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>transform</goal>
+                        </goals>
+                        <configuration>
+                            <transformationSets>
+                                <transformationSet>
+                                    <dir>${project.build.directory}/unpacked/wildfly-${wildfly.version}/standalone/configuration</dir>
+                                    <stylesheet>src/main/xslt/standalone.xsl</stylesheet>
+                                    <includes>
+                                        <include>standalone.xml</include>
+                                    </includes>
+                                    <outputDir>${project.build.directory}/unpacked/wildfly-${wildfly.version}/standalone/configuration</outputDir>
+                                </transformationSet>
+                            </transformationSets>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
 
             <plugin>
                 <artifactId>maven-assembly-plugin</artifactId>
diff --git a/distribution/demo-dist/src/main/keycloak-server.json b/distribution/demo-dist/src/main/keycloak-server.json
new file mode 100644
index 0000000..9f0d03e
--- /dev/null
+++ b/distribution/demo-dist/src/main/keycloak-server.json
@@ -0,0 +1,72 @@
+{
+    "admin": {
+        "realm": "master"
+    },
+
+    "eventsStore": {
+        "provider": "jpa",
+        "jpa": {
+            "exclude-events": [ "REFRESH_TOKEN" ]
+        }
+    },
+
+    "realm": {
+        "provider": "jpa"
+    },
+
+    "user": {
+        "provider": "jpa"
+    },
+
+    "userSessions": {
+        "provider" : "mem"
+    },
+
+    "realmCache": {
+        "provider": "mem"
+    },
+
+    "userCache": {
+        "provider": "mem",
+        "mem": {
+            "maxSize": 20000
+        }
+    },
+
+    "timer": {
+        "provider": "basic"
+    },
+
+    "theme": {
+        "default": "keycloak",
+        "staticMaxAge": 2592000,
+        "cacheTemplates": true,
+        "cacheThemes": true,
+        "folder": {
+          "dir": "${jboss.server.config.dir}/themes"
+        }
+    },
+
+    "login": {
+        "provider": "freemarker"
+    },
+
+    "account": {
+        "provider": "freemarker"
+    },
+
+    "email": {
+        "provider": "freemarker"
+    },
+
+    "scheduled": {
+        "interval": 900
+    },
+
+    "connectionsJpa": {
+        "default": {
+            "dataSource": "java:jboss/datasources/KeycloakDS",
+            "databaseSchema": "update"
+        }
+    }
+}
\ No newline at end of file
diff --git a/distribution/demo-dist/src/main/providers/README.txt b/distribution/demo-dist/src/main/providers/README.txt
new file mode 100644
index 0000000..a6d523b
--- /dev/null
+++ b/distribution/demo-dist/src/main/providers/README.txt
@@ -0,0 +1,2 @@
+Any provider implementation jars and libraries in this folder will be loaded by Keycloak. See the providers
+section in the documentation for more details.
\ No newline at end of file
diff --git a/distribution/demo-dist/src/main/themes/README.txt b/distribution/demo-dist/src/main/themes/README.txt
new file mode 100644
index 0000000..705b73a
--- /dev/null
+++ b/distribution/demo-dist/src/main/themes/README.txt
@@ -0,0 +1,3 @@
+Themes to configure the look and feel of login pages and account management console. It's not recommended to
+modify existing the built-in themes, instead you should create a new theme that extends a built-in theme. See the theme
+section in the documentation for more details.
\ No newline at end of file
diff --git a/distribution/demo-dist/src/main/xslt/standalone.xsl b/distribution/demo-dist/src/main/xslt/standalone.xsl
new file mode 100755
index 0000000..5de72af
--- /dev/null
+++ b/distribution/demo-dist/src/main/xslt/standalone.xsl
@@ -0,0 +1,72 @@
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+                xmlns:xalan="http://xml.apache.org/xalan"
+                xmlns:j="urn:jboss:domain:3.0"
+                xmlns:ds="urn:jboss:domain:datasources:3.0"
+                xmlns:k="urn:jboss:domain:keycloak:1.1"
+                xmlns:sec="urn:jboss:domain:security:1.2"
+                version="2.0"
+                exclude-result-prefixes="xalan j ds k sec">
+
+    <xsl:param name="config"/>
+
+    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" xalan:indent-amount="4" standalone="no"/>
+    <xsl:strip-space elements="*"/>
+
+    <xsl:template match="//j:extensions">
+        <xsl:copy>
+            <xsl:apply-templates select="node()|@*"/>
+            <extension module="org.keycloak.keycloak-server-subsystem"/>
+            <extension module="org.keycloak.keycloak-adapter-subsystem"/>
+        </xsl:copy>
+    </xsl:template>
+
+    <xsl:template match="//ds:datasources">
+        <xsl:copy>
+            <xsl:apply-templates select="node()[name(.)='datasource']"/>
+            <datasource jndi-name="java:jboss/datasources/KeycloakDS" pool-name="KeycloakDS" enabled="true" use-java-context="true">
+                <connection-url>jdbc:h2:${jboss.server.data.dir}/keycloak;AUTO_SERVER=TRUE</connection-url>
+                <driver>h2</driver>
+                <security>
+                    <user-name>sa</user-name>
+                    <password>sa</password>
+                </security>
+            </datasource>
+            <xsl:apply-templates select="node()[name(.)='drivers']"/>
+        </xsl:copy>
+    </xsl:template>
+
+    <xsl:template match="//j:profile">
+        <xsl:copy>
+            <xsl:apply-templates select="node()|@*"/>
+            <subsystem xmlns="urn:jboss:domain:keycloak-server:1.1">
+                <auth-server name="main-auth-server">
+                    <enabled>true</enabled>
+                    <web-context>auth</web-context>
+                </auth-server>
+            </subsystem>
+        </xsl:copy>
+    </xsl:template>
+
+    <xsl:template match="//sec:security-domains">
+        <xsl:copy>
+            <xsl:apply-templates select="node()[name(.)='security-domain']"/>
+            <security-domain name="keycloak">
+                <authentication>
+                    <login-module code="org.keycloak.adapters.jboss.KeycloakLoginModule" flag="required"/>
+                </authentication>
+            </security-domain>
+            <security-domain name="sp" cache-type="default">
+                <authentication>
+                    <login-module code="org.picketlink.identity.federation.bindings.wildfly.SAML2LoginModule" flag="required"/>
+                </authentication>
+            </security-domain>
+        </xsl:copy>
+    </xsl:template>
+
+    <xsl:template match="@*|node()">
+        <xsl:copy>
+            <xsl:apply-templates select="@*|node()" />
+        </xsl:copy>
+    </xsl:template>
+
+</xsl:stylesheet>
\ No newline at end of file
diff --git a/distribution/docs-dist/pom.xml b/distribution/docs-dist/pom.xml
index d5e093b..1f0f665 100755
--- a/distribution/docs-dist/pom.xml
+++ b/distribution/docs-dist/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
 
diff --git a/distribution/examples-dist/pom.xml b/distribution/examples-dist/pom.xml
index 09a46b0..3ffe6db 100755
--- a/distribution/examples-dist/pom.xml
+++ b/distribution/examples-dist/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
 
diff --git a/distribution/feature-pack-builds/adapter-only/adapter-only-build/pom.xml b/distribution/feature-pack-builds/adapter-only/adapter-only-build/pom.xml
index 95f05ec..179daf1 100644
--- a/distribution/feature-pack-builds/adapter-only/adapter-only-build/pom.xml
+++ b/distribution/feature-pack-builds/adapter-only/adapter-only-build/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.keycloak</groupId>
         <artifactId>adapter-only-builds</artifactId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/distribution/feature-pack-builds/adapter-only/adapter-only-dist/pom.xml b/distribution/feature-pack-builds/adapter-only/adapter-only-dist/pom.xml
index 4f1ca1a..a3cad64 100644
--- a/distribution/feature-pack-builds/adapter-only/adapter-only-dist/pom.xml
+++ b/distribution/feature-pack-builds/adapter-only/adapter-only-dist/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.keycloak</groupId>
         <artifactId>adapter-only-builds</artifactId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/distribution/feature-pack-builds/adapter-only/pom.xml b/distribution/feature-pack-builds/adapter-only/pom.xml
index bd57e0b..74caef4 100644
--- a/distribution/feature-pack-builds/adapter-only/pom.xml
+++ b/distribution/feature-pack-builds/adapter-only/pom.xml
@@ -3,7 +3,7 @@
     <parent>
         <artifactId>feature-pack-builds</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <name>Adapter Only Builds</name>
diff --git a/distribution/feature-pack-builds/pom.xml b/distribution/feature-pack-builds/pom.xml
index a35b3f7..f529441 100644
--- a/distribution/feature-pack-builds/pom.xml
+++ b/distribution/feature-pack-builds/pom.xml
@@ -3,7 +3,7 @@
     <parent>
         <artifactId>distribution-pom</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <name>Feature Pack Builds</name>
diff --git a/distribution/feature-pack-builds/server-and-adapter/pom.xml b/distribution/feature-pack-builds/server-and-adapter/pom.xml
index 9616217..ed6c704 100644
--- a/distribution/feature-pack-builds/server-and-adapter/pom.xml
+++ b/distribution/feature-pack-builds/server-and-adapter/pom.xml
@@ -3,7 +3,7 @@
     <parent>
         <artifactId>feature-pack-builds</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <name>Builds with both server and adapter</name>
diff --git a/distribution/feature-pack-builds/server-and-adapter/server-and-adapter-build/pom.xml b/distribution/feature-pack-builds/server-and-adapter/server-and-adapter-build/pom.xml
index 2bc3a0a..774595e 100644
--- a/distribution/feature-pack-builds/server-and-adapter/server-and-adapter-build/pom.xml
+++ b/distribution/feature-pack-builds/server-and-adapter/server-and-adapter-build/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.keycloak</groupId>
         <artifactId>server-and-adapter</artifactId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/distribution/feature-pack-builds/server-and-adapter/server-and-adapter-dist/pom.xml b/distribution/feature-pack-builds/server-and-adapter/server-and-adapter-dist/pom.xml
index 4a492fd..1009ea3 100644
--- a/distribution/feature-pack-builds/server-and-adapter/server-and-adapter-dist/pom.xml
+++ b/distribution/feature-pack-builds/server-and-adapter/server-and-adapter-dist/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.keycloak</groupId>
         <artifactId>server-and-adapter</artifactId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/distribution/feature-pack-builds/server-only/pom.xml b/distribution/feature-pack-builds/server-only/pom.xml
index af41711..0dd99e6 100644
--- a/distribution/feature-pack-builds/server-only/pom.xml
+++ b/distribution/feature-pack-builds/server-only/pom.xml
@@ -3,7 +3,7 @@
     <parent>
         <artifactId>feature-pack-builds</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <name>Server Only Builds</name>
diff --git a/distribution/feature-pack-builds/server-only/server-only-build/pom.xml b/distribution/feature-pack-builds/server-only/server-only-build/pom.xml
index 5d88347..f496a3d 100644
--- a/distribution/feature-pack-builds/server-only/server-only-build/pom.xml
+++ b/distribution/feature-pack-builds/server-only/server-only-build/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.keycloak</groupId>
         <artifactId>server-only-builds</artifactId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/distribution/feature-pack-builds/server-only/server-only-dist/pom.xml b/distribution/feature-pack-builds/server-only/server-only-dist/pom.xml
index 4f13fbc..ab7c51a 100644
--- a/distribution/feature-pack-builds/server-only/server-only-dist/pom.xml
+++ b/distribution/feature-pack-builds/server-only/server-only-dist/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.keycloak</groupId>
         <artifactId>server-only-builds</artifactId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/distribution/feature-packs/adapter-feature-pack/pom.xml b/distribution/feature-packs/adapter-feature-pack/pom.xml
index 1fa74ff..4668542 100644
--- a/distribution/feature-packs/adapter-feature-pack/pom.xml
+++ b/distribution/feature-packs/adapter-feature-pack/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.keycloak</groupId>
         <artifactId>feature-packs-parent</artifactId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/distribution/feature-packs/pom.xml b/distribution/feature-packs/pom.xml
index dcc6c0c..06f0e5c 100644
--- a/distribution/feature-packs/pom.xml
+++ b/distribution/feature-packs/pom.xml
@@ -3,7 +3,7 @@
     <parent>
         <artifactId>distribution-pom</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <name>Feature Pack Builds</name>
@@ -14,7 +14,7 @@
     <packaging>pom</packaging>
 
     <modules>
-        <module>adapter-feature-pack</module>
+        <!--<module>adapter-feature-pack</module>-->
         <module>server-feature-pack</module>
     </modules>
 </project>
diff --git a/distribution/feature-packs/server-feature-pack/assembly.xml b/distribution/feature-packs/server-feature-pack/assembly.xml
index eaee2ca..47c914f 100644
--- a/distribution/feature-packs/server-feature-pack/assembly.xml
+++ b/distribution/feature-packs/server-feature-pack/assembly.xml
@@ -49,5 +49,19 @@
                <exclude>**/module.xml</exclude>
             </excludes>
         </fileSet>
+        <fileSet>
+            <directory>../../../</directory>
+            <includes>
+                <include>License.html</include>
+            </includes>
+            <outputDirectory>content</outputDirectory>
+        </fileSet>
+        <fileSet>
+            <directory>../../../forms/common-themes/src/main/resources/theme</directory>
+            <outputDirectory>content/standalone/configuration/themes</outputDirectory>
+            <includes>
+                <include>**/**</include>
+            </includes>
+        </fileSet>
     </fileSets>
 </assembly>
diff --git a/distribution/feature-packs/server-feature-pack/pom.xml b/distribution/feature-packs/server-feature-pack/pom.xml
index c5bfc3e..6a51d96 100644
--- a/distribution/feature-packs/server-feature-pack/pom.xml
+++ b/distribution/feature-packs/server-feature-pack/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.keycloak</groupId>
         <artifactId>feature-packs-parent</artifactId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/configuration/standalone/subsystems.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/configuration/standalone/subsystems.xml
index fad3c38..d20bf2e 100644
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/configuration/standalone/subsystems.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/configuration/standalone/subsystems.xml
@@ -3,33 +3,21 @@
 <config>
    <subsystems>
       <subsystem>logging.xml</subsystem>
-      <subsystem>batch.xml</subsystem>
+      <!-- resteasy complains if bean-validation is missing -->
       <subsystem>bean-validation.xml</subsystem>
       <subsystem>keycloak-datasources.xml</subsystem>
-      <subsystem>deployment-scanner.xml</subsystem>
-      <subsystem>ee.xml</subsystem>
-      <subsystem>ejb3.xml</subsystem>
+      <subsystem supplement="web-build">ee.xml</subsystem>
       <subsystem>io.xml</subsystem>
       <subsystem>infinispan.xml</subsystem>
       <subsystem>jaxrs.xml</subsystem>
       <subsystem>jca.xml</subsystem>
-      <subsystem>jdr.xml</subsystem>
-      <subsystem>jmx.xml</subsystem>
       <subsystem>jpa.xml</subsystem>
-      <subsystem>jsf.xml</subsystem>
-      <subsystem>mail.xml</subsystem>
-      <subsystem>naming.xml</subsystem>
-      <subsystem>pojo.xml</subsystem>
-      <subsystem>remoting.xml</subsystem>
-      <subsystem>resource-adapters.xml</subsystem>
+      <subsystem supplement="web-build">naming.xml</subsystem>
       <subsystem>request-controller.xml</subsystem>
-      <subsystem>sar.xml</subsystem>
       <subsystem>security-manager.xml</subsystem>
       <subsystem>security.xml</subsystem>
       <subsystem>transactions.xml</subsystem>
       <subsystem>undertow.xml</subsystem>
-      <subsystem>webservices.xml</subsystem>
-      <subsystem>weld.xml</subsystem>
       <subsystem>keycloak-server.xml</subsystem>
    </subsystems>
 </config>
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/content/standalone/configuration/providers/README.txt b/distribution/feature-packs/server-feature-pack/src/main/resources/content/standalone/configuration/providers/README.txt
new file mode 100644
index 0000000..a6d523b
--- /dev/null
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/content/standalone/configuration/providers/README.txt
@@ -0,0 +1,2 @@
+Any provider implementation jars and libraries in this folder will be loaded by Keycloak. See the providers
+section in the documentation for more details.
\ No newline at end of file
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/content/standalone/configuration/themes/README.txt b/distribution/feature-packs/server-feature-pack/src/main/resources/content/standalone/configuration/themes/README.txt
new file mode 100644
index 0000000..705b73a
--- /dev/null
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/content/standalone/configuration/themes/README.txt
@@ -0,0 +1,3 @@
+Themes to configure the look and feel of login pages and account management console. It's not recommended to
+modify existing the built-in themes, instead you should create a new theme that extends a built-in theme. See the theme
+section in the documentation for more details.
\ No newline at end of file
diff --git a/distribution/modules/pom.xml b/distribution/modules/pom.xml
index 501e422..0830aba 100755
--- a/distribution/modules/pom.xml
+++ b/distribution/modules/pom.xml
@@ -8,7 +8,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
 
diff --git a/distribution/pom.xml b/distribution/pom.xml
index 8eeaafc..3b7a66f 100755
--- a/distribution/pom.xml
+++ b/distribution/pom.xml
@@ -3,7 +3,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
 
@@ -28,13 +28,13 @@
 
     <modules>
         <module>adapters</module>
-        <module>demo-dist</module>
+        <!--<module>demo-dist</module>-->
         <module>docs-dist</module>
         <module>examples-dist</module>
         <module>modules</module>
         <module>proxy-dist</module>
         <module>server-dist</module>
-        <module>server-overlay</module>
+        <!--<module>server-overlay</module>-->
         <module>src-dist</module>
         <module>subsystem-war</module>
         <module>feature-packs</module>
diff --git a/distribution/proxy-dist/pom.xml b/distribution/proxy-dist/pom.xml
index 4edf89b..02b179a 100755
--- a/distribution/proxy-dist/pom.xml
+++ b/distribution/proxy-dist/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
 
diff --git a/distribution/server-dist/assembly.xml b/distribution/server-dist/assembly.xml
index 6f0c028..6a846af 100755
--- a/distribution/server-dist/assembly.xml
+++ b/distribution/server-dist/assembly.xml
@@ -8,7 +8,6 @@
 
     <includeBaseDirectory>true</includeBaseDirectory>
 
-    <!-- If war is filtered it will get corrupted. Only need to filter module.xml -->
     <fileSets>
         <fileSet>
             <directory>target/${project.build.finalName}</directory>
@@ -38,30 +37,5 @@
                 <include>*.*</include>
             </includes>
         </fileSet>
-        <fileSet>
-            <directory>../../forms/common-themes/src/main/resources/theme</directory>
-            <outputDirectory>standalone/configuration/themes</outputDirectory>
-            <includes>
-                <include>**/**</include>
-            </includes>
-        </fileSet>
-        <fileSet>
-            <directory>../../</directory>
-            <includes>
-                <include>License.html</include>
-            </includes>
-            <outputDirectory></outputDirectory>
-        </fileSet>
     </fileSets>
-
-    <files>
-        <file>
-            <source>../server-overlay/src/main/themes/README.txt</source>
-            <outputDirectory>standalone/configuration/themes</outputDirectory>
-        </file>
-        <file>
-            <source>../server-overlay/src/main/providers/README.txt</source>
-            <outputDirectory>standalone/configuration/providers</outputDirectory>
-        </file>
-    </files>
 </assembly>
diff --git a/distribution/server-dist/pom.xml b/distribution/server-dist/pom.xml
index cc76727..428b122 100755
--- a/distribution/server-dist/pom.xml
+++ b/distribution/server-dist/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
 
@@ -16,12 +16,7 @@
     <dependencies>
         <dependency>
             <groupId>org.keycloak</groupId>
-            <artifactId>keycloak-server-overlay</artifactId>
-            <type>zip</type>
-        </dependency>
-        <dependency>
-            <groupId>org.wildfly</groupId>
-            <artifactId>wildfly-dist</artifactId>
+            <artifactId>keycloak-server-feature-pack</artifactId>
             <type>zip</type>
         </dependency>
     </dependencies>
diff --git a/distribution/server-overlay/assembly.xml b/distribution/server-overlay/assembly.xml
index 26f4d8c..f8424b1 100755
--- a/distribution/server-overlay/assembly.xml
+++ b/distribution/server-overlay/assembly.xml
@@ -5,12 +5,17 @@
         <format>zip</format>
         <format>tar.gz</format>
     </formats>
+
     <includeBaseDirectory>false</includeBaseDirectory>
 
     <fileSets>
         <fileSet>
             <directory>${project.build.directory}/unpacked/modules</directory>
-            <outputDirectory>modules/system/layers/base</outputDirectory>
+            <outputDirectory>modules</outputDirectory>
+        </fileSet>
+        <fileSet>
+            <directory>${project.build.directory}/unpacked/content</directory>
+            <outputDirectory></outputDirectory>
         </fileSet>
         <fileSet>
             <directory>../../forms/common-themes/src/main/resources/theme</directory>
@@ -35,7 +40,7 @@
             <destName>standalone-keycloak.xml</destName>
         </file>
         <file>
-            <source>../subsystem-war/src/main/resources/META-INF/keycloak-server.json</source>
+            <source>src/main/keycloak-server.json</source>
             <outputDirectory>standalone/configuration</outputDirectory>
         </file>
         <file>
diff --git a/distribution/server-overlay/pom.xml b/distribution/server-overlay/pom.xml
index f68f678..a066b2c 100755
--- a/distribution/server-overlay/pom.xml
+++ b/distribution/server-overlay/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
 
diff --git a/distribution/server-overlay/src/main/keycloak-server.json b/distribution/server-overlay/src/main/keycloak-server.json
new file mode 100644
index 0000000..9f0d03e
--- /dev/null
+++ b/distribution/server-overlay/src/main/keycloak-server.json
@@ -0,0 +1,72 @@
+{
+    "admin": {
+        "realm": "master"
+    },
+
+    "eventsStore": {
+        "provider": "jpa",
+        "jpa": {
+            "exclude-events": [ "REFRESH_TOKEN" ]
+        }
+    },
+
+    "realm": {
+        "provider": "jpa"
+    },
+
+    "user": {
+        "provider": "jpa"
+    },
+
+    "userSessions": {
+        "provider" : "mem"
+    },
+
+    "realmCache": {
+        "provider": "mem"
+    },
+
+    "userCache": {
+        "provider": "mem",
+        "mem": {
+            "maxSize": 20000
+        }
+    },
+
+    "timer": {
+        "provider": "basic"
+    },
+
+    "theme": {
+        "default": "keycloak",
+        "staticMaxAge": 2592000,
+        "cacheTemplates": true,
+        "cacheThemes": true,
+        "folder": {
+          "dir": "${jboss.server.config.dir}/themes"
+        }
+    },
+
+    "login": {
+        "provider": "freemarker"
+    },
+
+    "account": {
+        "provider": "freemarker"
+    },
+
+    "email": {
+        "provider": "freemarker"
+    },
+
+    "scheduled": {
+        "interval": 900
+    },
+
+    "connectionsJpa": {
+        "default": {
+            "dataSource": "java:jboss/datasources/KeycloakDS",
+            "databaseSchema": "update"
+        }
+    }
+}
\ No newline at end of file
diff --git a/distribution/server-overlay/src/main/xslt/standalone.xsl b/distribution/server-overlay/src/main/xslt/standalone.xsl
index 9e7d309..dd17b23 100755
--- a/distribution/server-overlay/src/main/xslt/standalone.xsl
+++ b/distribution/server-overlay/src/main/xslt/standalone.xsl
@@ -1,12 +1,10 @@
 <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                 xmlns:xalan="http://xml.apache.org/xalan"
-                xmlns:j="urn:jboss:domain:2.2"
-                xmlns:ds="urn:jboss:domain:datasources:2.0"
-                xmlns:dep="urn:jboss:domain:deployment-scanner:2.0"
-                xmlns:k="urn:jboss:domain:keycloak:1.0"
-                xmlns:sec="urn:jboss:domain:security:1.2"
+                xmlns:j="urn:jboss:domain:3.0"
+                xmlns:ds="urn:jboss:domain:datasources:3.0"
+                xmlns:k="urn:jboss:domain:keycloak:1.1"
                 version="2.0"
-                exclude-result-prefixes="xalan j ds dep k sec">
+                exclude-result-prefixes="xalan j ds k">
 
     <xsl:param name="config"/>
 
@@ -17,7 +15,6 @@
         <xsl:copy>
             <xsl:apply-templates select="node()|@*"/>
             <extension module="org.keycloak.keycloak-server-subsystem"/>
-            <extension module="org.keycloak.keycloak-adapter-subsystem"/>
         </xsl:copy>
     </xsl:template>
 
@@ -48,22 +45,6 @@
         </xsl:copy>
     </xsl:template>
 
-    <xsl:template match="//sec:security-domains">
-        <xsl:copy>
-            <xsl:apply-templates select="node()[name(.)='security-domain']"/>
-            <security-domain name="keycloak">
-                <authentication>
-                    <login-module code="org.keycloak.adapters.jboss.KeycloakLoginModule" flag="required"/>
-                </authentication>
-            </security-domain>
-            <security-domain name="sp" cache-type="default">
-                <authentication>
-                    <login-module code="org.picketlink.identity.federation.bindings.wildfly.SAML2LoginModule" flag="required"/>
-                </authentication>
-            </security-domain>
-        </xsl:copy>
-    </xsl:template>
-
     <xsl:template match="@*|node()">
         <xsl:copy>
             <xsl:apply-templates select="@*|node()" />
diff --git a/distribution/src-dist/pom.xml b/distribution/src-dist/pom.xml
index 48f6fb9..14d63e3 100755
--- a/distribution/src-dist/pom.xml
+++ b/distribution/src-dist/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
 
diff --git a/distribution/subsystem-war/pom.xml b/distribution/subsystem-war/pom.xml
index 71677d8..f1ea3f6 100755
--- a/distribution/subsystem-war/pom.xml
+++ b/distribution/subsystem-war/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
 

docbook/pom.xml 2(+1 -1)

diff --git a/docbook/pom.xml b/docbook/pom.xml
index f47a000..009b220 100755
--- a/docbook/pom.xml
+++ b/docbook/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
 
diff --git a/events/api/pom.xml b/events/api/pom.xml
index 5f0181a..a70987f 100755
--- a/events/api/pom.xml
+++ b/events/api/pom.xml
@@ -3,7 +3,7 @@
     <parent>
         <artifactId>keycloak-events-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
     </parent>
 
     <modelVersion>4.0.0</modelVersion>
diff --git a/events/email/pom.xml b/events/email/pom.xml
index 0ce04f1..cd449b0 100755
--- a/events/email/pom.xml
+++ b/events/email/pom.xml
@@ -3,7 +3,7 @@
     <parent>
         <artifactId>keycloak-events-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
     </parent>
 
     <modelVersion>4.0.0</modelVersion>
diff --git a/events/jboss-logging/pom.xml b/events/jboss-logging/pom.xml
index 9f64c75..e575603 100755
--- a/events/jboss-logging/pom.xml
+++ b/events/jboss-logging/pom.xml
@@ -3,7 +3,7 @@
     <parent>
         <artifactId>keycloak-events-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
     </parent>
 
     <modelVersion>4.0.0</modelVersion>
diff --git a/events/jpa/pom.xml b/events/jpa/pom.xml
index 15e9217..260e76c 100755
--- a/events/jpa/pom.xml
+++ b/events/jpa/pom.xml
@@ -3,7 +3,7 @@
     <parent>
         <artifactId>keycloak-events-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
     </parent>
 
     <modelVersion>4.0.0</modelVersion>
diff --git a/events/mongo/pom.xml b/events/mongo/pom.xml
index ee659f2..84de0f7 100755
--- a/events/mongo/pom.xml
+++ b/events/mongo/pom.xml
@@ -3,7 +3,7 @@
     <parent>
         <artifactId>keycloak-events-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
     </parent>
 
     <modelVersion>4.0.0</modelVersion>

events/pom.xml 2(+1 -1)

diff --git a/events/pom.xml b/events/pom.xml
index 072774d..f7d5a35 100755
--- a/events/pom.xml
+++ b/events/pom.xml
@@ -3,7 +3,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
 
diff --git a/events/syslog/pom.xml b/events/syslog/pom.xml
index ee502b4..cbe9b9c 100755
--- a/events/syslog/pom.xml
+++ b/events/syslog/pom.xml
@@ -3,7 +3,7 @@
     <parent>
         <artifactId>keycloak-events-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
     </parent>
 
     <modelVersion>4.0.0</modelVersion>
diff --git a/examples/admin-client/pom.xml b/examples/admin-client/pom.xml
index d03600d..78aea2d 100755
--- a/examples/admin-client/pom.xml
+++ b/examples/admin-client/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <artifactId>keycloak-examples-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
     </parent>
 
     <name>Keycloak Examples - Admin Client</name>
diff --git a/examples/basic-auth/pom.xml b/examples/basic-auth/pom.xml
index 30faeb9..bd5c681 100755
--- a/examples/basic-auth/pom.xml
+++ b/examples/basic-auth/pom.xml
@@ -6,7 +6,7 @@
     <parent>
         <artifactId>keycloak-examples-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
     </parent>
 
     <name>Keycloak Examples - Basic Auth</name>
diff --git a/examples/broker/facebook-authentication/pom.xml b/examples/broker/facebook-authentication/pom.xml
index 148d1ec..e28820c 100755
--- a/examples/broker/facebook-authentication/pom.xml
+++ b/examples/broker/facebook-authentication/pom.xml
@@ -6,7 +6,7 @@
     <parent>
         <artifactId>keycloak-examples-broker-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
     </parent>
 
     <name>Keycloak Broker Examples - Facebook Authentication</name>
diff --git a/examples/broker/google-authentication/pom.xml b/examples/broker/google-authentication/pom.xml
index a20a23a..3fea3c2 100755
--- a/examples/broker/google-authentication/pom.xml
+++ b/examples/broker/google-authentication/pom.xml
@@ -6,7 +6,7 @@
     <parent>
         <artifactId>keycloak-examples-broker-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
     </parent>
 
     <name>Keycloak Broker Examples - Google Authentication</name>
diff --git a/examples/broker/pom.xml b/examples/broker/pom.xml
index 27c4a94..7d328b4 100755
--- a/examples/broker/pom.xml
+++ b/examples/broker/pom.xml
@@ -3,7 +3,7 @@
     <parent>
         <artifactId>keycloak-examples-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
     </parent>
 
     <name>Broker Examples</name>
diff --git a/examples/broker/saml-broker-authentication/pom.xml b/examples/broker/saml-broker-authentication/pom.xml
index e157013..bc12452 100755
--- a/examples/broker/saml-broker-authentication/pom.xml
+++ b/examples/broker/saml-broker-authentication/pom.xml
@@ -6,7 +6,7 @@
     <parent>
         <artifactId>keycloak-examples-broker-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
     </parent>
 
     <name>Keycloak Broker Examples - SAML Identity Provider Brokering</name>
diff --git a/examples/broker/twitter-authentication/pom.xml b/examples/broker/twitter-authentication/pom.xml
index 2072954..0896245 100755
--- a/examples/broker/twitter-authentication/pom.xml
+++ b/examples/broker/twitter-authentication/pom.xml
@@ -6,7 +6,7 @@
     <parent>
         <artifactId>keycloak-examples-broker-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
     </parent>
 
     <name>Keycloak Broker Examples - Twitter Authentication</name>
diff --git a/examples/cors/angular-product-app/pom.xml b/examples/cors/angular-product-app/pom.xml
index bdd1c09..4b69a88 100755
--- a/examples/cors/angular-product-app/pom.xml
+++ b/examples/cors/angular-product-app/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-examples-cors-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
     </parent>
 
     <modelVersion>4.0.0</modelVersion>
diff --git a/examples/cors/database-service/pom.xml b/examples/cors/database-service/pom.xml
index b0fb096..057e50b 100755
--- a/examples/cors/database-service/pom.xml
+++ b/examples/cors/database-service/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-examples-cors-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
     </parent>
 
     <modelVersion>4.0.0</modelVersion>
diff --git a/examples/cors/pom.xml b/examples/cors/pom.xml
index 1b4b995..fdfa514 100755
--- a/examples/cors/pom.xml
+++ b/examples/cors/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-examples-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
     </parent>
 
     <name>Keycloak Examples - CORS</name>
diff --git a/examples/demo-template/admin-access-app/pom.xml b/examples/demo-template/admin-access-app/pom.xml
index 9ae6a72..719c513 100755
--- a/examples/demo-template/admin-access-app/pom.xml
+++ b/examples/demo-template/admin-access-app/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-examples-demo-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
     </parent>
 
     <modelVersion>4.0.0</modelVersion>
diff --git a/examples/demo-template/angular-product-app/pom.xml b/examples/demo-template/angular-product-app/pom.xml
index d4e7c54..671283e 100755
--- a/examples/demo-template/angular-product-app/pom.xml
+++ b/examples/demo-template/angular-product-app/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-examples-demo-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
     </parent>
 
     <modelVersion>4.0.0</modelVersion>
diff --git a/examples/demo-template/customer-app/pom.xml b/examples/demo-template/customer-app/pom.xml
index ad81118..9bbe7a4 100755
--- a/examples/demo-template/customer-app/pom.xml
+++ b/examples/demo-template/customer-app/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-examples-demo-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
     </parent>
 
     <modelVersion>4.0.0</modelVersion>
diff --git a/examples/demo-template/customer-app-cli/pom.xml b/examples/demo-template/customer-app-cli/pom.xml
index d6be2d4..b5ac17a 100755
--- a/examples/demo-template/customer-app-cli/pom.xml
+++ b/examples/demo-template/customer-app-cli/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-examples-demo-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
     </parent>
 
     <modelVersion>4.0.0</modelVersion>
diff --git a/examples/demo-template/customer-app-js/pom.xml b/examples/demo-template/customer-app-js/pom.xml
index 03bf216..c64eac2 100755
--- a/examples/demo-template/customer-app-js/pom.xml
+++ b/examples/demo-template/customer-app-js/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-examples-demo-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
     </parent>
 
     <modelVersion>4.0.0</modelVersion>
diff --git a/examples/demo-template/database-service/pom.xml b/examples/demo-template/database-service/pom.xml
index 842c1bc..3a09784 100755
--- a/examples/demo-template/database-service/pom.xml
+++ b/examples/demo-template/database-service/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-examples-demo-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
     </parent>
 
     <modelVersion>4.0.0</modelVersion>
diff --git a/examples/demo-template/example-ear/pom.xml b/examples/demo-template/example-ear/pom.xml
index 19f49ac..bfb6bff 100755
--- a/examples/demo-template/example-ear/pom.xml
+++ b/examples/demo-template/example-ear/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-examples-demo-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
     </parent>
 
     <modelVersion>4.0.0</modelVersion>
diff --git a/examples/demo-template/pom.xml b/examples/demo-template/pom.xml
index eb179f0..b002be1 100755
--- a/examples/demo-template/pom.xml
+++ b/examples/demo-template/pom.xml
@@ -3,7 +3,7 @@
     <parent>
         <artifactId>keycloak-examples-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
     </parent>
 
     <name>Examples</name>
diff --git a/examples/demo-template/product-app/pom.xml b/examples/demo-template/product-app/pom.xml
index c753ccc..675ff09 100755
--- a/examples/demo-template/product-app/pom.xml
+++ b/examples/demo-template/product-app/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-examples-demo-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
     </parent>
 
     <modelVersion>4.0.0</modelVersion>
diff --git a/examples/demo-template/third-party/pom.xml b/examples/demo-template/third-party/pom.xml
index 5299abb..c2f23b3 100755
--- a/examples/demo-template/third-party/pom.xml
+++ b/examples/demo-template/third-party/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-examples-demo-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
     </parent>
 
     <modelVersion>4.0.0</modelVersion>
diff --git a/examples/demo-template/third-party-cdi/pom.xml b/examples/demo-template/third-party-cdi/pom.xml
index b874b56..7ac1d8d 100755
--- a/examples/demo-template/third-party-cdi/pom.xml
+++ b/examples/demo-template/third-party-cdi/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-examples-demo-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
     </parent>
 
     <modelVersion>4.0.0</modelVersion>
diff --git a/examples/fuse/camel/pom.xml b/examples/fuse/camel/pom.xml
index e86f25d..fca482e 100755
--- a/examples/fuse/camel/pom.xml
+++ b/examples/fuse/camel/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-examples-fuse-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
     </parent>
 
     <modelVersion>4.0.0</modelVersion>
diff --git a/examples/fuse/customer-app-fuse/pom.xml b/examples/fuse/customer-app-fuse/pom.xml
index 70d22b5..de0c0d8 100755
--- a/examples/fuse/customer-app-fuse/pom.xml
+++ b/examples/fuse/customer-app-fuse/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-examples-fuse-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
     </parent>
 
     <modelVersion>4.0.0</modelVersion>
diff --git a/examples/fuse/cxf-jaxrs/pom.xml b/examples/fuse/cxf-jaxrs/pom.xml
index 01572ec..b97346e 100755
--- a/examples/fuse/cxf-jaxrs/pom.xml
+++ b/examples/fuse/cxf-jaxrs/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-examples-fuse-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
     </parent>
 
     <modelVersion>4.0.0</modelVersion>
diff --git a/examples/fuse/cxf-jaxws/pom.xml b/examples/fuse/cxf-jaxws/pom.xml
index 1cd02ba..594b519 100755
--- a/examples/fuse/cxf-jaxws/pom.xml
+++ b/examples/fuse/cxf-jaxws/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-examples-fuse-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
     </parent>
 
     <modelVersion>4.0.0</modelVersion>
diff --git a/examples/fuse/features/pom.xml b/examples/fuse/features/pom.xml
index 5c55233..d06af85 100755
--- a/examples/fuse/features/pom.xml
+++ b/examples/fuse/features/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-examples-fuse-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
     </parent>
 
     <modelVersion>4.0.0</modelVersion>
diff --git a/examples/fuse/pom.xml b/examples/fuse/pom.xml
index 0a5786c..f60ca2a 100755
--- a/examples/fuse/pom.xml
+++ b/examples/fuse/pom.xml
@@ -3,7 +3,7 @@
     <parent>
         <artifactId>keycloak-examples-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
     </parent>
 
     <name>Fuse examples</name>
diff --git a/examples/fuse/product-app-fuse/pom.xml b/examples/fuse/product-app-fuse/pom.xml
index 535063d..5231405 100755
--- a/examples/fuse/product-app-fuse/pom.xml
+++ b/examples/fuse/product-app-fuse/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-examples-fuse-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
     </parent>
 
     <modelVersion>4.0.0</modelVersion>
diff --git a/examples/js-console/pom.xml b/examples/js-console/pom.xml
index 0ffd809..7434c5c 100755
--- a/examples/js-console/pom.xml
+++ b/examples/js-console/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-examples-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
     </parent>
 
     <modelVersion>4.0.0</modelVersion>
diff --git a/examples/kerberos/pom.xml b/examples/kerberos/pom.xml
index 5f87b10..2ed56cd 100755
--- a/examples/kerberos/pom.xml
+++ b/examples/kerberos/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <artifactId>keycloak-examples-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
     </parent>
 
     <name>Keycloak Examples - Kerberos Credential Delegation</name>
diff --git a/examples/multi-tenant/pom.xml b/examples/multi-tenant/pom.xml
index 4ac5314..d5fb0d5 100755
--- a/examples/multi-tenant/pom.xml
+++ b/examples/multi-tenant/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-examples-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
     </parent>
 
     <name>Keycloak Examples - Multi Tenant</name>

examples/pom.xml 2(+1 -1)

diff --git a/examples/pom.xml b/examples/pom.xml
index 58f699b..5921ea2 100755
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -3,7 +3,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
     </parent>
 
     <name>Examples</name>
diff --git a/examples/providers/event-listener-sysout/pom.xml b/examples/providers/event-listener-sysout/pom.xml
index 69a4e20..06504a1 100755
--- a/examples/providers/event-listener-sysout/pom.xml
+++ b/examples/providers/event-listener-sysout/pom.xml
@@ -3,7 +3,7 @@
     <parent>
         <artifactId>keycloak-examples-providers-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
     </parent>
 
     <name>Event Listener System.out Example</name>
diff --git a/examples/providers/event-store-mem/pom.xml b/examples/providers/event-store-mem/pom.xml
index bb317c4..c1d40ff 100755
--- a/examples/providers/event-store-mem/pom.xml
+++ b/examples/providers/event-store-mem/pom.xml
@@ -3,7 +3,7 @@
     <parent>
         <artifactId>keycloak-examples-providers-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
     </parent>
 
     <name>Event Store In-Mem Example</name>
diff --git a/examples/providers/federation-provider/pom.xml b/examples/providers/federation-provider/pom.xml
index 7896f18..a29520a 100755
--- a/examples/providers/federation-provider/pom.xml
+++ b/examples/providers/federation-provider/pom.xml
@@ -3,7 +3,7 @@
     <parent>
         <artifactId>keycloak-examples-providers-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
     </parent>
 
     <name>Properties Authentication Provider Example</name>
diff --git a/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/BasePropertiesFederationFactory.java b/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/BasePropertiesFederationFactory.java
index 24f30b4..5c585f3 100755
--- a/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/BasePropertiesFederationFactory.java
+++ b/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/BasePropertiesFederationFactory.java
@@ -8,6 +8,7 @@ import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserFederationProvider;
 import org.keycloak.models.UserFederationProviderFactory;
 import org.keycloak.models.UserFederationProviderModel;
+import org.keycloak.models.UserFederationSyncResult;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserProvider;
 import org.keycloak.models.utils.KeycloakModelUtils;
@@ -98,7 +99,9 @@ public abstract class BasePropertiesFederationFactory implements UserFederationP
     }
 
     @Override
-    public void syncAllUsers(KeycloakSessionFactory sessionFactory, final String realmId, final UserFederationProviderModel model) {
+    public UserFederationSyncResult syncAllUsers(KeycloakSessionFactory sessionFactory, final String realmId, final UserFederationProviderModel model) {
+        final UserFederationSyncResult syncResult = new UserFederationSyncResult();
+
         KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
 
             @Override
@@ -112,16 +115,21 @@ public abstract class BasePropertiesFederationFactory implements UserFederationP
 
                     if (localUser == null) {
                         // New user, let's import him
-                        federationProvider.getUserByUsername(realm, username);
+                        UserModel imported = federationProvider.getUserByUsername(realm, username);
+                        if (imported != null) {
+                            syncResult.increaseAdded();
+                        }
                     }
                 }
             }
 
         });
+
+        return syncResult;
     }
 
     @Override
-    public void syncChangedUsers(KeycloakSessionFactory sessionFactory, final String realmId, final UserFederationProviderModel model, Date lastSync) {
-        syncAllUsers(sessionFactory, realmId, model);
+    public UserFederationSyncResult syncChangedUsers(KeycloakSessionFactory sessionFactory, final String realmId, final UserFederationProviderModel model, Date lastSync) {
+        return syncAllUsers(sessionFactory, realmId, model);
     }
 }
diff --git a/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/BasePropertiesFederationProvider.java b/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/BasePropertiesFederationProvider.java
index 964560c..9c4d01f 100755
--- a/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/BasePropertiesFederationProvider.java
+++ b/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/BasePropertiesFederationProvider.java
@@ -113,7 +113,7 @@ public abstract class BasePropertiesFederationProvider implements UserFederation
      * @return
      */
     @Override
-    public boolean isValid(UserModel local) {
+    public boolean isValid(RealmModel realm, UserModel local) {
         return properties.containsKey(local.getUsername());
     }
 
diff --git a/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/ClasspathPropertiesFederationProvider.java b/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/ClasspathPropertiesFederationProvider.java
index 46081f8..781331f 100755
--- a/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/ClasspathPropertiesFederationProvider.java
+++ b/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/ClasspathPropertiesFederationProvider.java
@@ -25,8 +25,12 @@ public class ClasspathPropertiesFederationProvider extends BasePropertiesFederat
      * @return
      */
     @Override
-    public UserModel proxy(UserModel local) {
-        return new ReadonlyUserModelProxy(local);
+    public UserModel validateAndProxy(RealmModel realm, UserModel local) {
+        if (isValid(realm, local)) {
+            return new ReadonlyUserModelProxy(local);
+        } else {
+            return null;
+        }
     }
 
     /**
diff --git a/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/FilePropertiesFederationProvider.java b/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/FilePropertiesFederationProvider.java
index aa24553..0b6941f 100755
--- a/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/FilePropertiesFederationProvider.java
+++ b/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/FilePropertiesFederationProvider.java
@@ -27,8 +27,12 @@ public class FilePropertiesFederationProvider extends BasePropertiesFederationPr
      * @return
      */
     @Override
-    public UserModel proxy(UserModel local) {
-        return new WritableUserModelProxy(local, this);
+    public UserModel validateAndProxy(RealmModel realm, UserModel local) {
+        if (isValid(realm, local)) {
+            return new WritableUserModelProxy(local, this);
+        } else {
+            return null;
+        }
     }
 
     /**
@@ -65,7 +69,7 @@ public class FilePropertiesFederationProvider extends BasePropertiesFederationPr
             properties.setProperty(user.getUsername(), "");
             save();
         }
-        return proxy(user);
+        return validateAndProxy(realm, user);
     }
 
     @Override
diff --git a/examples/providers/pom.xml b/examples/providers/pom.xml
index 526a94b..21458de 100755
--- a/examples/providers/pom.xml
+++ b/examples/providers/pom.xml
@@ -3,7 +3,7 @@
     <parent>
         <artifactId>keycloak-examples-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
     </parent>
 
     <name>Provider Examples</name>
diff --git a/examples/saml/pom.xml b/examples/saml/pom.xml
index d97300d..c62cd5f 100755
--- a/examples/saml/pom.xml
+++ b/examples/saml/pom.xml
@@ -3,7 +3,7 @@
     <parent>
         <artifactId>keycloak-examples-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
     </parent>
 
     <name>Provider Examples</name>
diff --git a/examples/themes/pom.xml b/examples/themes/pom.xml
index 45a6ff1..ccc7f55 100755
--- a/examples/themes/pom.xml
+++ b/examples/themes/pom.xml
@@ -3,7 +3,7 @@
     <parent>
         <artifactId>keycloak-examples-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
     </parent>
 
     <name>Themes Examples</name>
diff --git a/export-import/export-import-api/pom.xml b/export-import/export-import-api/pom.xml
index 19fe36e..9c8e7e2 100755
--- a/export-import/export-import-api/pom.xml
+++ b/export-import/export-import-api/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-export-import-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/export-import/export-import-dir/pom.xml b/export-import/export-import-dir/pom.xml
index 424aefb..c709225 100755
--- a/export-import/export-import-dir/pom.xml
+++ b/export-import/export-import-dir/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-export-import-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/export-import/export-import-single-file/pom.xml b/export-import/export-import-single-file/pom.xml
index 47df66b..fe701aa 100755
--- a/export-import/export-import-single-file/pom.xml
+++ b/export-import/export-import-single-file/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-export-import-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/export-import/export-import-zip/pom.xml b/export-import/export-import-zip/pom.xml
index aa5dbcc..1d7ba18 100755
--- a/export-import/export-import-zip/pom.xml
+++ b/export-import/export-import-zip/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-export-import-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/export-import/pom.xml b/export-import/pom.xml
index 8785acb..dc3060f 100755
--- a/export-import/pom.xml
+++ b/export-import/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/federation/kerberos/pom.xml b/federation/kerberos/pom.xml
index 088a23f..2c8ac7e 100755
--- a/federation/kerberos/pom.xml
+++ b/federation/kerberos/pom.xml
@@ -3,7 +3,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProvider.java b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProvider.java
index 40094ef..c43b58b 100644
--- a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProvider.java
+++ b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProvider.java
@@ -43,7 +43,11 @@ public class KerberosFederationProvider implements UserFederationProvider {
     }
 
     @Override
-    public UserModel proxy(UserModel local) {
+    public UserModel validateAndProxy(RealmModel realm, UserModel local) {
+        if (!isValid(realm, local)) {
+            return null;
+        }
+
         if (kerberosConfig.getEditMode() == EditMode.READ_ONLY) {
             return new ReadOnlyKerberosUserModelDelegate(local, this);
         } else {
@@ -63,8 +67,7 @@ public class KerberosFederationProvider implements UserFederationProvider {
 
     @Override
     public boolean removeUser(RealmModel realm, UserModel user) {
-        // TODO: Not sure if federation provider is expected to delete user in localStorage. Looks rather like a bug in UserFederationManager.removeUser .
-        return session.userStorage().removeUser(realm, user);
+        return true;
     }
 
     @Override
@@ -103,7 +106,7 @@ public class KerberosFederationProvider implements UserFederationProvider {
     }
 
     @Override
-    public boolean isValid(UserModel local) {
+    public boolean isValid(RealmModel realm, UserModel local) {
         // KerberosUsernamePasswordAuthenticator.isUserAvailable is an overhead, so avoid it for now
 
         String kerberosPrincipal = local.getUsername() + "@" + kerberosConfig.getKerberosRealm();
@@ -220,13 +223,16 @@ public class KerberosFederationProvider implements UserFederationProvider {
             if (!model.getId().equals(user.getFederationLink())) {
                 logger.warn("User with username " + username + " already exists, but is not linked to provider [" + model.getDisplayName() + "]");
                 return null;
-            } else if (isValid(user)) {
-                return proxy(user);
             } else {
-                logger.warn("User with username " + username + " already exists and is linked to provider [" + model.getDisplayName() +
-                        "] but kerberos principal is not correct. Kerberos principal on user is: " + user.getAttribute(KERBEROS_PRINCIPAL));
-                logger.warn("Will re-create user");
-                session.userStorage().removeUser(realm, user);
+                UserModel proxied = validateAndProxy(realm, user);
+                if (proxied != null) {
+                    return proxied;
+                } else {
+                    logger.warn("User with username " + username + " already exists and is linked to provider [" + model.getDisplayName() +
+                            "] but kerberos principal is not correct. Kerberos principal on user is: " + user.getAttribute(KERBEROS_PRINCIPAL));
+                    logger.warn("Will re-create user");
+                    session.userStorage().removeUser(realm, user);
+                }
             }
         }
 
@@ -249,6 +255,6 @@ public class KerberosFederationProvider implements UserFederationProvider {
             user.addRequiredAction(UserModel.RequiredAction.UPDATE_PROFILE);
         }
 
-        return proxy(user);
+        return validateAndProxy(realm, user);
     }
 }
diff --git a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProviderFactory.java b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProviderFactory.java
index 375f2cf..b7b6c92 100755
--- a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProviderFactory.java
+++ b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProviderFactory.java
@@ -14,6 +14,7 @@ import org.keycloak.models.KeycloakSessionFactory;
 import org.keycloak.models.UserFederationProvider;
 import org.keycloak.models.UserFederationProviderFactory;
 import org.keycloak.models.UserFederationProviderModel;
+import org.keycloak.models.UserFederationSyncResult;
 
 /**
  * Factory for standalone Kerberos federation provider. Standalone means that it's not backed by LDAP. For Kerberos backed by LDAP (like MS AD or ApacheDS environment)
@@ -41,13 +42,15 @@ public class KerberosFederationProviderFactory implements UserFederationProvider
     }
 
     @Override
-    public void syncAllUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model) {
+    public UserFederationSyncResult syncAllUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model) {
         logger.warn("Sync users not supported for this provider");
+        return UserFederationSyncResult.empty();
     }
 
     @Override
-    public void syncChangedUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model, Date lastSync) {
+    public UserFederationSyncResult syncChangedUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model, Date lastSync) {
         logger.warn("Sync users not supported for this provider");
+        return UserFederationSyncResult.empty();
     }
 
     @Override
diff --git a/federation/ldap/pom.xml b/federation/ldap/pom.xml
index db2a5e6..0e4afe8 100755
--- a/federation/ldap/pom.xml
+++ b/federation/ldap/pom.xml
@@ -3,7 +3,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/LDAPDn.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/LDAPDn.java
new file mode 100644
index 0000000..5d58438
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/LDAPDn.java
@@ -0,0 +1,86 @@
+package org.keycloak.federation.ldap.idm.model;
+
+import java.util.Deque;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class LDAPDn {
+
+    private final Deque<Entry> entries = new LinkedList<Entry>();
+
+    public static LDAPDn fromString(String dnString) {
+        LDAPDn dn = new LDAPDn();
+
+        String[] rdns = dnString.split(",");
+        for (String entryStr : rdns) {
+            String[] rdn = entryStr.split("=");
+            dn.addToBottom(rdn[0].trim(), rdn[1].trim());
+        }
+
+        return dn;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+
+        boolean first = true;
+        for (Entry rdn : entries) {
+            if (first) {
+                first = false;
+            } else {
+                builder.append(",");
+            }
+            builder.append(rdn.attrName).append("=").append(rdn.attrValue);
+        }
+
+        return builder.toString();
+    }
+
+    /**
+     * @return string like "uid=joe" from the DN like "uid=joe,dc=something,dc=org"
+     */
+    public String getFirstRdn() {
+        Entry firstEntry = entries.getFirst();
+        return firstEntry.attrName + "=" + firstEntry.attrValue;
+    }
+
+    /**
+     * @return string attribute name like "uid" from the DN like "uid=joe,dc=something,dc=org"
+     */
+    public String getFirstRdnAttrName() {
+        Entry firstEntry = entries.getFirst();
+        return firstEntry.attrName;
+    }
+
+    /**
+     *
+     * @return string like "dc=something,dc=org" from the DN like "uid=joe,dc=something,dc=org"
+     */
+    public String getParentDn() {
+        return new LinkedList<Entry>(entries).remove().toString();
+    }
+
+    public void addToHead(String rdnName, String rdnValue) {
+        entries.addFirst(new Entry(rdnName, rdnValue));
+    }
+
+    public void addToBottom(String rdnName, String rdnValue) {
+        entries.addLast(new Entry(rdnName, rdnValue));
+    }
+
+
+    private static class Entry {
+        private final String attrName;
+        private final String attrValue;
+
+        private Entry(String attrName, String attrValue) {
+            this.attrName = attrName;
+            this.attrValue = attrValue;
+        }
+    }
+}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/LDAPObject.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/LDAPObject.java
new file mode 100644
index 0000000..587a81f
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/LDAPObject.java
@@ -0,0 +1,112 @@
+package org.keycloak.federation.ldap.idm.model;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class LDAPObject {
+
+    private String uuid;
+    private LDAPDn dn;
+    private String rdnAttributeName;
+
+    private final List<String> objectClasses = new LinkedList<String>();
+    private final List<String> readOnlyAttributeNames = new LinkedList<String>();
+    private final Map<String, Object> attributes = new HashMap<String, Object>();
+
+    public String getUuid() {
+        return uuid;
+    }
+
+    public void setUuid(String uuid) {
+        this.uuid = uuid;
+    }
+
+    public LDAPDn getDn() {
+        return dn;
+    }
+
+    public void setDn(LDAPDn dn) {
+        this.dn = dn;
+    }
+
+    public List<String> getObjectClasses() {
+        return objectClasses;
+    }
+
+    public void setObjectClasses(Collection<String> objectClasses) {
+        this.objectClasses.clear();
+        this.objectClasses.addAll(objectClasses);
+    }
+
+    public List<String> getReadOnlyAttributeNames() {
+        return readOnlyAttributeNames;
+    }
+
+    public void addReadOnlyAttributeName(String readOnlyAttribute) {
+        readOnlyAttributeNames.add(readOnlyAttribute);
+    }
+
+    public String getRdnAttributeName() {
+        return rdnAttributeName;
+    }
+
+    public void setRdnAttributeName(String rdnAttributeName) {
+        this.rdnAttributeName = rdnAttributeName;
+    }
+
+    public void setAttribute(String attributeName, Object attributeValue) {
+        attributes.put(attributeName, attributeValue);
+    }
+
+    public void removeAttribute(String name) {
+        attributes.remove(name);
+    }
+
+
+    public Object getAttribute(String name) {
+        return attributes.get(name);
+    }
+
+    public String getAttributeAsString(String name) {
+        Object attrValue = attributes.get(name);
+        if (attrValue != null && !(attrValue instanceof String)) {
+            throw new IllegalStateException("Expected String but attribute was " + attrValue + " of type " + attrValue.getClass().getName());
+        }
+
+        return (String) attrValue;
+    }
+
+
+    public Map<String, Object> getAttributes() {
+        return attributes;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null) {
+            return false;
+        }
+
+        if (!getClass().isInstance(obj)) {
+            return false;
+        }
+
+        LDAPObject other = (LDAPObject) obj;
+
+        return getUuid() != null && other.getUuid() != null && getUuid().equals(other.getUuid());
+    }
+
+    @Override
+    public int hashCode() {
+        int result = getUuid() != null ? getUuid().hashCode() : 0;
+        result = 31 * result + (getUuid() != null ? getUuid().hashCode() : 0);
+        return result;
+    }
+}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/EqualCondition.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/EqualCondition.java
index a3fee26..9f6e6e7 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/EqualCondition.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/EqualCondition.java
@@ -1,6 +1,5 @@
 package org.keycloak.federation.ldap.idm.query.internal;
 
-import org.keycloak.federation.ldap.idm.query.AttributeParameter;
 import org.keycloak.federation.ldap.idm.query.Condition;
 import org.keycloak.federation.ldap.idm.query.QueryParameter;
 
@@ -29,7 +28,7 @@ public class EqualCondition implements Condition {
     @Override
     public String toString() {
         return "EqualCondition{" +
-                "parameter=" + ((AttributeParameter) parameter).getName() +
+                "parameter=" + parameter.getName() +
                 ", value=" + value +
                 '}';
     }
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/LDAPIdentityQuery.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/LDAPIdentityQuery.java
new file mode 100644
index 0000000..1d62a3c
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/LDAPIdentityQuery.java
@@ -0,0 +1,192 @@
+package org.keycloak.federation.ldap.idm.query.internal;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.naming.directory.SearchControls;
+
+import org.keycloak.federation.ldap.LDAPFederationProvider;
+import org.keycloak.federation.ldap.idm.model.LDAPObject;
+import org.keycloak.federation.ldap.idm.query.Condition;
+import org.keycloak.federation.ldap.idm.query.Sort;
+import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
+import org.keycloak.federation.ldap.mappers.LDAPFederationMapper;
+import org.keycloak.models.ModelDuplicateException;
+import org.keycloak.models.ModelException;
+import org.keycloak.models.UserFederationMapperModel;
+
+import static java.util.Collections.unmodifiableSet;
+
+/**
+ * Default IdentityQuery implementation.
+ *
+ *
+ * @author Shane Bryzak
+ */
+public class LDAPIdentityQuery {
+
+    private final LDAPFederationProvider ldapFedProvider;
+
+    private int offset;
+    private int limit;
+    private byte[] paginationContext;
+    private final Set<Condition> conditions = new LinkedHashSet<Condition>();
+    private final Set<Sort> ordering = new LinkedHashSet<Sort>();
+
+    private final Set<String> searchDns = new LinkedHashSet<String>();
+    private final Set<String> returningLdapAttributes = new LinkedHashSet<String>();
+
+    // Contains just those returningLdapAttributes, which are read-only. They will be marked as read-only in returned LDAPObject instances as well
+    private final Set<String> returningReadOnlyLdapAttributes = new LinkedHashSet<String>();
+    private final Set<String> objectClasses = new LinkedHashSet<String>();
+
+    private final List<UserFederationMapperModel> mappers = new ArrayList<UserFederationMapperModel>();
+
+    private int searchScope = SearchControls.SUBTREE_SCOPE;
+
+    public LDAPIdentityQuery(LDAPFederationProvider ldapProvider) {
+        this.ldapFedProvider = ldapProvider;
+    }
+
+    public LDAPIdentityQuery where(Condition... condition) {
+        this.conditions.addAll(Arrays.asList(condition));
+        return this;
+    }
+
+    public LDAPIdentityQuery sortBy(Sort... sorts) {
+        this.ordering.addAll(Arrays.asList(sorts));
+        return this;
+    }
+
+    public LDAPIdentityQuery addSearchDns(Collection<String> searchDns) {
+        this.searchDns.addAll(searchDns);
+        return this;
+    }
+
+    public LDAPIdentityQuery addObjectClasses(Collection<String> objectClasses) {
+        this.objectClasses.addAll(objectClasses);
+        return this;
+    }
+
+    public LDAPIdentityQuery addReturningLdapAttribute(String ldapAttributeName) {
+        this.returningLdapAttributes.add(ldapAttributeName);
+        return this;
+    }
+
+    public LDAPIdentityQuery addReturningReadOnlyLdapAttribute(String ldapAttributeName) {
+        this.returningReadOnlyLdapAttributes.add(ldapAttributeName);
+        return this;
+    }
+
+    public LDAPIdentityQuery addMappers(Collection<UserFederationMapperModel> mappers) {
+        this.mappers.addAll(mappers);
+        return this;
+    }
+
+    public LDAPIdentityQuery setSearchScope(int searchScope) {
+        this.searchScope = searchScope;
+        return this;
+    }
+
+    public Set<Sort> getSorting() {
+        return unmodifiableSet(this.ordering);
+    }
+
+    public Set<String> getSearchDns() {
+        return unmodifiableSet(this.searchDns);
+    }
+
+    public Set<String> getObjectClasses() {
+        return unmodifiableSet(this.objectClasses);
+    }
+
+    public Set<String> getReturningLdapAttributes() {
+        return unmodifiableSet(this.returningLdapAttributes);
+    }
+
+    public Set<String> getReturningReadOnlyLdapAttributes() {
+        return unmodifiableSet(this.returningReadOnlyLdapAttributes);
+    }
+
+    public List<UserFederationMapperModel> getMappers() {
+        return mappers;
+    }
+
+    public int getSearchScope() {
+        return searchScope;
+    }
+
+    public int getLimit() {
+        return limit;
+    }
+
+    public int getOffset() {
+        return offset;
+    }
+
+    public byte[] getPaginationContext() {
+        return paginationContext;
+    }
+
+
+    public List<LDAPObject> getResultList() {
+
+        // Apply mappers now
+        for (UserFederationMapperModel mapperModel : mappers) {
+            LDAPFederationMapper fedMapper = ldapFedProvider.getMapper(mapperModel);
+            fedMapper.beforeLDAPQuery(mapperModel, this);
+        }
+
+        List<LDAPObject> result = new ArrayList<LDAPObject>();
+
+        try {
+            for (LDAPObject ldapObject : ldapFedProvider.getLdapIdentityStore().fetchQueryResults(this)) {
+                result.add(ldapObject);
+            }
+        } catch (Exception e) {
+            throw new ModelException("LDAP Query failed", e);
+        }
+
+        return result;
+    }
+
+    public LDAPObject getFirstResult() {
+        List<LDAPObject> results = getResultList();
+
+        if (results.isEmpty()) {
+            return null;
+        } else if (results.size() == 1) {
+            return results.get(0);
+        } else {
+            throw new ModelDuplicateException("Error - multiple LDAP objects found but expected just one");
+        }
+    }
+
+    public int getResultCount() {
+        return ldapFedProvider.getLdapIdentityStore().countQueryResults(this);
+    }
+
+    public LDAPIdentityQuery setOffset(int offset) {
+        this.offset = offset;
+        return this;
+    }
+
+    public LDAPIdentityQuery setLimit(int limit) {
+        this.limit = limit;
+        return this;
+    }
+
+    public LDAPIdentityQuery setPaginationContext(byte[] paginationContext) {
+        this.paginationContext = paginationContext;
+        return this;
+    }
+
+    public Set<Condition> getConditions() {
+        return this.conditions;
+    }
+
+}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/OrCondition.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/OrCondition.java
new file mode 100644
index 0000000..436355b
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/OrCondition.java
@@ -0,0 +1,27 @@
+package org.keycloak.federation.ldap.idm.query.internal;
+
+import java.util.List;
+
+import org.keycloak.federation.ldap.idm.query.Condition;
+import org.keycloak.federation.ldap.idm.query.QueryParameter;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class OrCondition implements Condition {
+
+    private final Condition[] innerConditions;
+
+    public OrCondition(Condition... innerConditions) {
+        this.innerConditions = innerConditions;
+    }
+
+    public Condition[] getInnerConditions() {
+        return innerConditions;
+    }
+
+    @Override
+    public QueryParameter getParameter() {
+        return null;
+    }
+}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/QueryParameter.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/QueryParameter.java
index ae2bbdf..bfc58f7 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/QueryParameter.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/QueryParameter.java
@@ -7,6 +7,19 @@ package org.keycloak.federation.ldap.idm.query;
  * @author Shane Bryzak
  *
  */
-public interface QueryParameter {
+public class QueryParameter {
 
+    private String name;
+
+    public QueryParameter(String name) {
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
 }
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/IdentityStore.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/IdentityStore.java
index 7fef705..01d85a6 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/IdentityStore.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/IdentityStore.java
@@ -2,11 +2,9 @@ package org.keycloak.federation.ldap.idm.store;
 
 import java.util.List;
 
-import org.keycloak.federation.ldap.idm.model.AttributedType;
-import org.keycloak.federation.ldap.idm.model.IdentityType;
-import org.keycloak.federation.ldap.idm.model.LDAPUser;
-import org.keycloak.federation.ldap.idm.query.IdentityQuery;
-import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStoreConfiguration;
+import org.keycloak.federation.ldap.LDAPConfig;
+import org.keycloak.federation.ldap.idm.model.LDAPObject;
+import org.keycloak.federation.ldap.idm.query.internal.LDAPIdentityQuery;
 
 /**
  * IdentityStore representation providing minimal SPI
@@ -23,36 +21,36 @@ public interface IdentityStore {
      *
      * @return
      */
-    LDAPIdentityStoreConfiguration getConfig();
+    LDAPConfig getConfig();
 
     // General
 
     /**
      * Persists the specified IdentityType
      *
-     * @param value
+     * @param ldapObject
      */
-    void add(AttributedType value);
+    void add(LDAPObject ldapObject);
 
     /**
      * Updates the specified IdentityType
      *
-     * @param value
+     * @param ldapObject
      */
-    void update(AttributedType value);
+    void update(LDAPObject ldapObject);
 
     /**
      * Removes the specified IdentityType
      *
-     * @param value
+     * @param ldapObject
      */
-    void remove(AttributedType value);
+    void remove(LDAPObject ldapObject);
 
     // Identity query
 
-    <V extends IdentityType> List<V> fetchQueryResults(IdentityQuery<V> identityQuery);
+    List<LDAPObject> fetchQueryResults(LDAPIdentityQuery LDAPIdentityQuery);
 
-    <V extends IdentityType> int countQueryResults(IdentityQuery<V> identityQuery);
+    int countQueryResults(LDAPIdentityQuery LDAPIdentityQuery);
 
 //    // Relationship query
 //
@@ -68,7 +66,7 @@ public interface IdentityStore {
      * @param user Keycloak user
      * @param password Ldap password
      */
-    boolean validatePassword(LDAPUser user, String password);
+    boolean validatePassword(LDAPObject user, String password);
 
     /**
      * Updates the specified credential value.
@@ -76,6 +74,6 @@ public interface IdentityStore {
      * @param user Keycloak user
      * @param password Ldap password
      */
-    void updatePassword(LDAPUser user, String password);
+    void updatePassword(LDAPObject user, String password);
 
 }
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPIdentityStore.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPIdentityStore.java
index 8b91240..552d298 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPIdentityStore.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPIdentityStore.java
@@ -1,11 +1,14 @@
 package org.keycloak.federation.ldap.idm.store.ldap;
 
-import java.io.Serializable;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.Date;
 import java.util.List;
 import java.util.Map;
 import java.util.NoSuchElementException;
+import java.util.Set;
+import java.util.TreeSet;
 
 import javax.naming.NamingEnumeration;
 import javax.naming.NamingException;
@@ -15,33 +18,25 @@ import javax.naming.directory.BasicAttribute;
 import javax.naming.directory.BasicAttributes;
 import javax.naming.directory.DirContext;
 import javax.naming.directory.ModificationItem;
+import javax.naming.directory.SearchControls;
 import javax.naming.directory.SearchResult;
 
 import org.jboss.logging.Logger;
-import org.keycloak.federation.ldap.idm.model.AttributedType;
-import org.keycloak.federation.ldap.idm.model.IdentityType;
-import org.keycloak.federation.ldap.idm.model.LDAPUser;
-import org.keycloak.federation.ldap.idm.query.AttributeParameter;
+import org.keycloak.federation.ldap.LDAPConfig;
+import org.keycloak.federation.ldap.idm.model.LDAPDn;
+import org.keycloak.federation.ldap.idm.model.LDAPObject;
 import org.keycloak.federation.ldap.idm.query.Condition;
-import org.keycloak.federation.ldap.idm.query.IdentityQuery;
-import org.keycloak.federation.ldap.idm.query.IdentityQueryBuilder;
 import org.keycloak.federation.ldap.idm.query.QueryParameter;
 import org.keycloak.federation.ldap.idm.query.internal.BetweenCondition;
-import org.keycloak.federation.ldap.idm.query.internal.DefaultQueryBuilder;
+import org.keycloak.federation.ldap.idm.query.internal.LDAPIdentityQuery;
 import org.keycloak.federation.ldap.idm.query.internal.EqualCondition;
 import org.keycloak.federation.ldap.idm.query.internal.GreaterThanCondition;
 import org.keycloak.federation.ldap.idm.query.internal.InCondition;
 import org.keycloak.federation.ldap.idm.query.internal.LessThanCondition;
-import org.keycloak.federation.ldap.idm.query.internal.LikeCondition;
+import org.keycloak.federation.ldap.idm.query.internal.OrCondition;
 import org.keycloak.federation.ldap.idm.store.IdentityStore;
 import org.keycloak.models.LDAPConstants;
-import org.keycloak.models.ModelDuplicateException;
 import org.keycloak.models.ModelException;
-import org.keycloak.models.utils.reflection.NamedPropertyCriteria;
-import org.keycloak.models.utils.reflection.Property;
-import org.keycloak.models.utils.reflection.PropertyQueries;
-import org.keycloak.models.utils.reflection.TypedPropertyCriteria;
-import org.keycloak.util.reflections.Reflections;
 
 /**
  * An IdentityStore implementation backed by an LDAP directory
@@ -54,12 +49,10 @@ public class LDAPIdentityStore implements IdentityStore {
 
     private static final Logger logger = Logger.getLogger(LDAPIdentityStore.class);
 
-    public static final String EMPTY_ATTRIBUTE_VALUE = " ";
-
-    private final LDAPIdentityStoreConfiguration config;
+    private final LDAPConfig config;
     private final LDAPOperationManager operationManager;
 
-    public LDAPIdentityStore(LDAPIdentityStoreConfiguration config) {
+    public LDAPIdentityStore(LDAPConfig config) {
         this.config = config;
 
         try {
@@ -70,69 +63,73 @@ public class LDAPIdentityStore implements IdentityStore {
     }
 
     @Override
-    public LDAPIdentityStoreConfiguration getConfig() {
+    public LDAPConfig getConfig() {
         return this.config;
     }
 
     @Override
-    public void add(AttributedType attributedType) {
+    public void add(LDAPObject ldapObject) {
         // id will be assigned by the ldap server
-        attributedType.setId(null);
-
-        String entryDN = getBindingDN(attributedType, true);
-        this.operationManager.createSubContext(entryDN, extractAttributes(attributedType, true));
-        addToParentAsMember(attributedType);
-        attributedType.setId(getEntryIdentifier(attributedType));
+        if (ldapObject.getUuid() != null) {
+            throw new IllegalStateException("Can't add object with already assigned uuid");
+        }
 
-        attributedType.setEntryDN(entryDN);
+        String entryDN = ldapObject.getDn().toString();
+        this.operationManager.createSubContext(entryDN, extractAttributes(ldapObject, true));
+        ldapObject.setUuid(getEntryIdentifier(ldapObject));
 
         if (logger.isTraceEnabled()) {
-            logger.tracef("Type with identifier [%s] successfully added to identity store [%s].", attributedType.getId(), this);
+            logger.tracef("Type with identifier [%s] and dn [%s] successfully added to LDAP store.", ldapObject.getUuid(), entryDN);
         }
     }
 
     @Override
-    public void update(AttributedType attributedType) {
-        BasicAttributes updatedAttributes = extractAttributes(attributedType, false);
+    public void update(LDAPObject ldapObject) {
+        BasicAttributes updatedAttributes = extractAttributes(ldapObject, false);
         NamingEnumeration<Attribute> attributes = updatedAttributes.getAll();
 
-        this.operationManager.modifyAttributes(getBindingDN(attributedType, true), attributes);
+        String entryDn = ldapObject.getDn().toString();
+        this.operationManager.modifyAttributes(entryDn, attributes);
 
         if (logger.isTraceEnabled()) {
-            logger.tracef("Type with identifier [%s] successfully updated to identity store [%s].", attributedType.getId(), this);
+            logger.tracef("Type with identifier [%s] and DN [%s] successfully updated to LDAP store.", ldapObject.getUuid(), entryDn);
         }
     }
 
     @Override
-    public void remove(AttributedType attributedType) {
-        LDAPMappingConfiguration mappingConfig = getMappingConfig(attributedType.getClass());
-
-        this.operationManager.removeEntryById(getBaseDN(attributedType), attributedType.getId(), mappingConfig);
+    public void remove(LDAPObject ldapObject) {
+        this.operationManager.removeEntry(ldapObject.getDn().toString());
 
         if (logger.isTraceEnabled()) {
-            logger.tracef("Type with identifier [%s] successfully removed from identity store [%s].", attributedType.getId(), this);
+            logger.tracef("Type with identifier [%s] and DN [%s] successfully removed from LDAP store.", ldapObject.getUuid(), ldapObject.getDn().toString());
         }
     }
 
+
     @Override
-    public <V extends IdentityType> List<V> fetchQueryResults(IdentityQuery<V> identityQuery) {
-        List<V> results = new ArrayList<V>();
+    public List<LDAPObject> fetchQueryResults(LDAPIdentityQuery identityQuery) {
+        List<LDAPObject> results = new ArrayList<LDAPObject>();
 
         try {
             if (identityQuery.getSorting() != null && !identityQuery.getSorting().isEmpty()) {
-                throw new ModelException("LDAP Identity Store does not support sorted queries.");
+                throw new ModelException("LDAP Identity Store does not yet support sorted queries.");
             }
 
+            // TODO: proper support for search by more DNs
+            String baseDN = identityQuery.getSearchDns().iterator().next();
+
             for (Condition condition : identityQuery.getConditions()) {
 
-                if (IdentityType.ID.equals(condition.getParameter())) {
+                // Check if we are searching by ID
+                String uuidAttrName = getConfig().getUuidLDAPAttributeName();
+                if (condition.getParameter() != null && condition.getParameter().getName().equals(uuidAttrName)) {
                     if (EqualCondition.class.isInstance(condition)) {
                         EqualCondition equalCondition = (EqualCondition) condition;
                         SearchResult search = this.operationManager
-                                .lookupById(getConfig().getBaseDN(), equalCondition.getValue().toString(), null);
+                                .lookupById(baseDN, equalCondition.getValue().toString(), identityQuery.getReturningLdapAttributes());
 
                         if (search != null) {
-                            results.add((V) populateAttributedType(search, null));
+                            results.add(populateAttributedType(search, identityQuery.getReturningReadOnlyLdapAttributes()));
                         }
                     }
 
@@ -140,23 +137,19 @@ public class LDAPIdentityStore implements IdentityStore {
                 }
             }
 
-            if (!IdentityType.class.equals(identityQuery.getIdentityType())) {
-                // the ldap store does not support queries based on root types. Except if based on the identifier.
-                LDAPMappingConfiguration ldapEntryConfig = getMappingConfig(identityQuery.getIdentityType());
-                StringBuilder filter = createIdentityTypeSearchFilter(identityQuery, ldapEntryConfig);
-                String baseDN = getBaseDN(ldapEntryConfig);
-                List<SearchResult> search;
 
-                if (getConfig().isPagination() && identityQuery.getLimit() > 0) {
-                    search = this.operationManager.searchPaginated(baseDN, filter.toString(), ldapEntryConfig, identityQuery);
-                } else {
-                    search = this.operationManager.search(baseDN, filter.toString(), ldapEntryConfig);
-                }
+            StringBuilder filter = createIdentityTypeSearchFilter(identityQuery);
 
-                for (SearchResult result : search) {
-                    if (!result.getNameInNamespace().equals(baseDN)) {
-                        results.add((V) populateAttributedType(result, null));
-                    }
+            List<SearchResult> search;
+            if (getConfig().isPagination() && identityQuery.getLimit() > 0) {
+                search = this.operationManager.searchPaginated(baseDN, filter.toString(), identityQuery);
+            } else {
+                search = this.operationManager.search(baseDN, filter.toString(), identityQuery.getReturningLdapAttributes(), identityQuery.getSearchScope());
+            }
+
+            for (SearchResult result : search) {
+                if (!result.getNameInNamespace().equals(baseDN)) {
+                    results.add(populateAttributedType(result, identityQuery.getReturningReadOnlyLdapAttributes()));
                 }
             }
         } catch (Exception e) {
@@ -167,7 +160,7 @@ public class LDAPIdentityStore implements IdentityStore {
     }
 
     @Override
-    public <V extends IdentityType> int countQueryResults(IdentityQuery<V> identityQuery) {
+    public int countQueryResults(LDAPIdentityQuery identityQuery) {
         int limit = identityQuery.getLimit();
         int offset = identityQuery.getOffset();
 
@@ -182,18 +175,14 @@ public class LDAPIdentityStore implements IdentityStore {
         return resultCount;
     }
 
-    public IdentityQueryBuilder createQueryBuilder() {
-        return new DefaultQueryBuilder(this);
-    }
-
     // *************** CREDENTIALS AND USER SPECIFIC STUFF
 
     @Override
-    public boolean validatePassword(LDAPUser user, String password) {
-        String userDN = getEntryDNOfUser(user);
+    public boolean validatePassword(LDAPObject user, String password) {
+        String userDN = user.getDn().toString();
 
         if (logger.isDebugEnabled()) {
-            logger.debugf("Using DN [%s] for authentication of user [%s]", userDN, user.getLoginName());
+            logger.debugf("Using DN [%s] for authentication of user", userDN);
         }
 
         if (operationManager.authenticate(userDN, password)) {
@@ -204,11 +193,11 @@ public class LDAPIdentityStore implements IdentityStore {
     }
 
     @Override
-    public void updatePassword(LDAPUser user, String password) {
-        String userDN = getEntryDNOfUser(user);
+    public void updatePassword(LDAPObject user, String password) {
+        String userDN = user.getDn().toString();
 
         if (logger.isDebugEnabled()) {
-            logger.debugf("Using DN [%s] for updating LDAP password of user [%s]", userDN, user.getLoginName());
+            logger.debugf("Using DN [%s] for updating LDAP password of user", userDN);
         }
 
         if (getConfig().isActiveDirectory()) {
@@ -242,6 +231,7 @@ public class LDAPIdentityStore implements IdentityStore {
             modItems.add(new ModificationItem(DirContext.REPLACE_ATTRIBUTE, unicodePwd));
 
             // Used in ActiveDirectory to put account into "enabled" state (aka userAccountControl=512, see http://support.microsoft.com/kb/305144/en ) after password update. If value is -1, it's ignored
+            // TODO: Remove and use mapper instead
             if (getConfig().isUserAccountControlsAfterPasswordUpdate()) {
                 BasicAttribute userAccountControl = new BasicAttribute("userAccountControl", "512");
                 modItems.add(new ModificationItem(DirContext.REPLACE_ATTRIBUTE, userAccountControl));
@@ -255,156 +245,117 @@ public class LDAPIdentityStore implements IdentityStore {
         }
     }
 
+    // ************ END CREDENTIALS AND USER SPECIFIC STUFF
 
-    private String getEntryDNOfUser(LDAPUser user) {
-        // First try if user already has entryDN on him
-        String entryDN = user.getEntryDN();
-        if (entryDN != null) {
-            return entryDN;
-        }
+    protected StringBuilder createIdentityTypeSearchFilter(final LDAPIdentityQuery identityQuery) {
+        StringBuilder filter = new StringBuilder();
 
-        // Need to find user in LDAP
-        String username = user.getLoginName();
-        user = getUser(username);
-        if (user == null) {
-            throw new ModelException("No LDAP user found with username " + username);
+        for (Condition condition : identityQuery.getConditions()) {
+            applyCondition(filter, condition);
         }
 
-        return user.getEntryDN();
-    }
-
 
-    public LDAPUser getUser(String username) {
+        filter.insert(0, "(&");
+        filter.append(getObjectClassesFilter(identityQuery.getObjectClasses()));
+        filter.append(")");
 
-        if (isNullOrEmpty(username)) {
-            return null;
-        }
-
-        IdentityQueryBuilder queryBuilder = createQueryBuilder();
-        List<LDAPUser> agents = queryBuilder.createIdentityQuery(LDAPUser.class)
-                .where(queryBuilder.equal(LDAPUser.LOGIN_NAME, username)).getResultList();
-
-        if (agents.isEmpty()) {
-            return null;
-        } else if (agents.size() == 1) {
-            return agents.get(0);
-        } else {
-            throw new ModelDuplicateException("Error - multiple Agent objects found with same login name");
-        }
+        logger.infof("Using filter for LDAP search: %s", filter);
+        return filter;
     }
 
-    // ************ END CREDENTIALS AND USER SPECIFIC STUFF
-
+    protected void applyCondition(StringBuilder filter, Condition condition) {
+        if (OrCondition.class.isInstance(condition)) {
+            OrCondition orCondition = (OrCondition) condition;
+            filter.append("(|");
 
-    private String getBaseDN(final LDAPMappingConfiguration ldapEntryConfig) {
-        String baseDN = getConfig().getBaseDN();
+            for (Condition innerCondition : orCondition.getInnerConditions()) {
+                applyCondition(filter, innerCondition);
+            }
 
-        if (ldapEntryConfig.getBaseDN() != null) {
-            baseDN = ldapEntryConfig.getBaseDN();
+            filter.append(")");
+            return;
         }
 
-        return baseDN;
-    }
+        QueryParameter queryParameter = condition.getParameter();
 
-    protected <V extends IdentityType> StringBuilder createIdentityTypeSearchFilter(final IdentityQuery<V> identityQuery, final LDAPMappingConfiguration ldapEntryConfig) {
-        StringBuilder filter = new StringBuilder();
-
-        for (Condition condition : identityQuery.getConditions()) {
-            QueryParameter queryParameter = condition.getParameter();
-
-            if (!IdentityType.ID.equals(queryParameter)) {
-                if (AttributeParameter.class.isInstance(queryParameter)) {
-                    AttributeParameter attributeParameter = (AttributeParameter) queryParameter;
-                    String attributeName = ldapEntryConfig.getMappedProperties().get(attributeParameter.getName());
+        if (!getConfig().getUuidLDAPAttributeName().equals(queryParameter.getName())) {
+            String attributeName = queryParameter.getName();
 
-                    if (attributeName != null) {
-                        if (EqualCondition.class.isInstance(condition)) {
-                            EqualCondition equalCondition = (EqualCondition) condition;
-                            Object parameterValue = equalCondition.getValue();
+            if (attributeName != null) {
+                if (EqualCondition.class.isInstance(condition)) {
+                    EqualCondition equalCondition = (EqualCondition) condition;
+                    Object parameterValue = equalCondition.getValue();
 
-                            if (Date.class.isInstance(parameterValue)) {
-                                parameterValue = LDAPUtil.formatDate((Date) parameterValue);
-                            }
-
-                            filter.append("(").append(attributeName).append(LDAPConstants.EQUAL).append(parameterValue).append(")");
-                        } else if (LikeCondition.class.isInstance(condition)) {
-                            LikeCondition likeCondition = (LikeCondition) condition;
-                            String parameterValue = (String) likeCondition.getValue();
+                    if (Date.class.isInstance(parameterValue)) {
+                        parameterValue = LDAPUtil.formatDate((Date) parameterValue);
+                    }
 
-                        } else if (GreaterThanCondition.class.isInstance(condition)) {
-                            GreaterThanCondition greaterThanCondition = (GreaterThanCondition) condition;
-                            Comparable parameterValue = (Comparable) greaterThanCondition.getValue();
+                    filter.append("(").append(attributeName).append(LDAPConstants.EQUAL).append(parameterValue).append(")");
+                } else if (GreaterThanCondition.class.isInstance(condition)) {
+                    GreaterThanCondition greaterThanCondition = (GreaterThanCondition) condition;
+                    Comparable parameterValue = greaterThanCondition.getValue();
 
-                            if (Date.class.isInstance(parameterValue)) {
-                                parameterValue = LDAPUtil.formatDate((Date) parameterValue);
-                            }
-
-                            if (greaterThanCondition.isOrEqual()) {
-                                filter.append("(").append(attributeName).append(">=").append(parameterValue).append(")");
-                            } else {
-                                filter.append("(").append(attributeName).append(">").append(parameterValue).append(")");
-                            }
-                        } else if (LessThanCondition.class.isInstance(condition)) {
-                            LessThanCondition lessThanCondition = (LessThanCondition) condition;
-                            Comparable parameterValue = (Comparable) lessThanCondition.getValue();
+                    if (Date.class.isInstance(parameterValue)) {
+                        parameterValue = LDAPUtil.formatDate((Date) parameterValue);
+                    }
 
-                            if (Date.class.isInstance(parameterValue)) {
-                                parameterValue = LDAPUtil.formatDate((Date) parameterValue);
-                            }
+                    if (greaterThanCondition.isOrEqual()) {
+                        filter.append("(").append(attributeName).append(">=").append(parameterValue).append(")");
+                    } else {
+                        filter.append("(").append(attributeName).append(">").append(parameterValue).append(")");
+                    }
+                } else if (LessThanCondition.class.isInstance(condition)) {
+                    LessThanCondition lessThanCondition = (LessThanCondition) condition;
+                    Comparable parameterValue = lessThanCondition.getValue();
 
-                            if (lessThanCondition.isOrEqual()) {
-                                filter.append("(").append(attributeName).append("<=").append(parameterValue).append(")");
-                            } else {
-                                filter.append("(").append(attributeName).append("<").append(parameterValue).append(")");
-                            }
-                        } else if (BetweenCondition.class.isInstance(condition)) {
-                            BetweenCondition betweenCondition = (BetweenCondition) condition;
-                            Comparable x = betweenCondition.getX();
-                            Comparable y = betweenCondition.getY();
+                    if (Date.class.isInstance(parameterValue)) {
+                        parameterValue = LDAPUtil.formatDate((Date) parameterValue);
+                    }
 
-                            if (Date.class.isInstance(x)) {
-                                x = LDAPUtil.formatDate((Date) x);
-                            }
+                    if (lessThanCondition.isOrEqual()) {
+                        filter.append("(").append(attributeName).append("<=").append(parameterValue).append(")");
+                    } else {
+                        filter.append("(").append(attributeName).append("<").append(parameterValue).append(")");
+                    }
+                } else if (BetweenCondition.class.isInstance(condition)) {
+                    BetweenCondition betweenCondition = (BetweenCondition) condition;
+                    Comparable x = betweenCondition.getX();
+                    Comparable y = betweenCondition.getY();
 
-                            if (Date.class.isInstance(y)) {
-                                y = LDAPUtil.formatDate((Date) y);
-                            }
+                    if (Date.class.isInstance(x)) {
+                        x = LDAPUtil.formatDate((Date) x);
+                    }
 
-                            filter.append("(").append(x).append("<=").append(attributeName).append("<=").append(y).append(")");
-                        } else if (InCondition.class.isInstance(condition)) {
-                            InCondition inCondition = (InCondition) condition;
-                            Object[] valuesToCompare = inCondition.getValue();
+                    if (Date.class.isInstance(y)) {
+                        y = LDAPUtil.formatDate((Date) y);
+                    }
 
-                            filter.append("(&(");
+                    filter.append("(").append(x).append("<=").append(attributeName).append("<=").append(y).append(")");
+                } else if (InCondition.class.isInstance(condition)) {
+                    InCondition inCondition = (InCondition) condition;
+                    Object[] valuesToCompare = inCondition.getValue();
 
-                            for (int i = 0; i< valuesToCompare.length; i++) {
-                                Object value = valuesToCompare[i];
+                    filter.append("(&(");
 
-                                filter.append("(").append(attributeName).append(LDAPConstants.EQUAL).append(value).append(")");
-                            }
+                    for (int i = 0; i< valuesToCompare.length; i++) {
+                        Object value = valuesToCompare[i];
 
-                            filter.append("))");
-                        } else {
-                            throw new ModelException("Unsupported query condition [" + condition + "].");
-                        }
+                        filter.append("(").append(attributeName).append(LDAPConstants.EQUAL).append(value).append(")");
                     }
+
+                    filter.append("))");
+                } else {
+                    throw new ModelException("Unsupported query condition [" + condition + "].");
                 }
             }
         }
-
-
-        filter.insert(0, "(&(");
-        filter.append(getObjectClassesFilter(ldapEntryConfig));
-        filter.append("))");
-
-        return filter;
     }
 
-    private StringBuilder getObjectClassesFilter(final LDAPMappingConfiguration ldapEntryConfig) {
+    private StringBuilder getObjectClassesFilter(Collection<String> objectClasses) {
         StringBuilder builder = new StringBuilder();
 
-        if (ldapEntryConfig != null && !ldapEntryConfig.getObjectClasses().isEmpty()) {
-            for (String objectClass : ldapEntryConfig.getObjectClasses()) {
+        if (!objectClasses.isEmpty()) {
+            for (String objectClass : objectClasses) {
                 builder.append("(").append(LDAPConstants.OBJECT_CLASS).append(LDAPConstants.EQUAL).append(objectClass).append(")");
             }
         } else {
@@ -414,86 +365,72 @@ public class LDAPIdentityStore implements IdentityStore {
         return builder;
     }
 
-    private AttributedType populateAttributedType(SearchResult searchResult, AttributedType attributedType) {
-        return populateAttributedType(searchResult, attributedType, 0);
-    }
-
-    private AttributedType populateAttributedType(SearchResult searchResult, AttributedType attributedType, int hierarchyDepthCount) {
+    private LDAPObject populateAttributedType(SearchResult searchResult, Collection<String> readOnlyAttrNames) {
         try {
             String entryDN = searchResult.getNameInNamespace();
             Attributes attributes = searchResult.getAttributes();
 
-            if (attributedType == null) {
-                attributedType = Reflections.newInstance(getConfig().getSupportedTypeByBaseDN(entryDN, getEntryObjectClasses(attributes)));
-            }
-
-            attributedType.setEntryDN(entryDN);
-
-            LDAPMappingConfiguration mappingConfig = getMappingConfig(attributedType.getClass());
-
-            if (hierarchyDepthCount > mappingConfig.getHierarchySearchDepth()) {
-                return null;
-            }
+            LDAPObject ldapObject = new LDAPObject();
+            LDAPDn dn = LDAPDn.fromString(entryDN);
+            ldapObject.setDn(dn);
+            ldapObject.setRdnAttributeName(dn.getFirstRdnAttrName());
 
             if (logger.isTraceEnabled()) {
-                logger.tracef("Populating attributed type [%s] from DN [%s]", attributedType, entryDN);
+                logger.tracef("Populating LDAP Object from DN [%s]", entryDN);
             }
 
             NamingEnumeration<? extends Attribute> ldapAttributes = attributes.getAll();
 
+            // Exact name of attributes might be different
+            List<String> uppercasedReadOnlyAttrNames = new ArrayList<String>();
+            for (String readonlyAttr : readOnlyAttrNames) {
+                uppercasedReadOnlyAttrNames.add(readonlyAttr.toUpperCase());
+            }
+
             while (ldapAttributes.hasMore()) {
                 Attribute ldapAttribute = ldapAttributes.next();
-                Object attributeValue;
 
                 try {
-                    attributeValue = ldapAttribute.get();
+                    ldapAttribute.get();
                 } catch (NoSuchElementException nsee) {
                     continue;
                 }
 
                 String ldapAttributeName = ldapAttribute.getID();
 
-                if (ldapAttributeName.toLowerCase().equals(getConfig().getUniqueIdentifierAttributeName().toLowerCase())) {
-                    attributedType.setId(this.operationManager.decodeEntryUUID(attributeValue));
+                if (ldapAttributeName.toLowerCase().equals(getConfig().getUuidLDAPAttributeName().toLowerCase())) {
+                    Object uuidValue = ldapAttribute.get();
+                    ldapObject.setUuid(this.operationManager.decodeEntryUUID(uuidValue));
                 } else {
-                    String attributeName = findAttributeName(mappingConfig.getMappedProperties(), ldapAttributeName);
-
-                    if (attributeName != null) {
-                        // Find if it's java property or attribute
-                        Property<Object> property = PropertyQueries
-                                .createQuery(attributedType.getClass())
-                                .addCriteria(new NamedPropertyCriteria(attributeName)).getFirstResult();
-
-                        if (property != null) {
-                            if (logger.isTraceEnabled()) {
-                                logger.tracef("Populating property [%s] from ldap attribute [%s] with value [%s] from DN [%s].", property.getName(), ldapAttributeName, attributeValue, entryDN);
-                            }
+                    Set<String> attrValues = new TreeSet<String>();
+                    NamingEnumeration<?> enumm = ldapAttribute.getAll();
+                    while (enumm.hasMoreElements()) {
+                        String objectClass = enumm.next().toString();
+                        attrValues.add(objectClass);
+                    }
 
-                            if (property.getJavaClass().equals(Date.class)) {
-                                property.setValue(attributedType, LDAPUtil.parseDate(attributeValue.toString()));
-                            } else {
-                                property.setValue(attributedType, attributeValue);
-                            }
+                    if (ldapAttributeName.toLowerCase().equals(LDAPConstants.OBJECT_CLASS)) {
+                        ldapObject.setObjectClasses(attrValues);
+                    } else {
+                        if (logger.isTraceEnabled()) {
+                            logger.tracef("Populating ldap attribute [%s] with value [%s] for DN [%s].", ldapAttributeName, attrValues.toString(), entryDN);
+                        }
+                        if (attrValues.size() == 1) {
+                            ldapObject.setAttribute(ldapAttributeName, attrValues.iterator().next());
                         } else {
-                            if (logger.isTraceEnabled()) {
-                                logger.tracef("Populating attribute [%s] from ldap attribute [%s] with value [%s] from DN [%s].", attributeName, ldapAttributeName, attributeValue, entryDN);
-                            }
+                            ldapObject.setAttribute(ldapAttributeName, attrValues);
+                        }
 
-                            attributedType.setAttribute(new org.keycloak.federation.ldap.idm.model.Attribute(attributeName, (Serializable) attributeValue));
+                        if (uppercasedReadOnlyAttrNames.contains(ldapAttributeName.toUpperCase())) {
+                            ldapObject.addReadOnlyAttributeName(ldapAttributeName);
                         }
                     }
                 }
             }
 
-            if (IdentityType.class.isInstance(attributedType)) {
-                IdentityType identityType = (IdentityType) attributedType;
-
-                String createdTimestamp = attributes.get(LDAPConstants.CREATE_TIMESTAMP).get().toString();
-
-                identityType.setCreatedDate(LDAPUtil.parseDate(createdTimestamp));
-            }
+            return ldapObject;
 
-            LDAPMappingConfiguration entryConfig = getMappingConfig(attributedType.getClass());
+            /*LDAPMappingConfiguration entryConfig = getMappingConfig(attributedType.getClass());
 
             if (mappingConfig.getParentMembershipAttributeName() != null) {
                 StringBuilder filter = new StringBuilder("(&");
@@ -522,14 +459,14 @@ public class LDAPIdentityStore implements IdentityStore {
                 if (!search.isEmpty()) {
                     SearchResult next = search.get(0);
 
-                    Property<AttributedType> parentProperty = PropertyQueries
-                            .<AttributedType>createQuery(attributedType.getClass())
+                    Property<IdentityType> parentProperty = PropertyQueries
+                            .<IdentityType>createQuery(attributedType.getClass())
                             .addCriteria(new TypedPropertyCriteria(attributedType.getClass())).getFirstResult();
 
                     if (parentProperty != null) {
                         String parentDN = next.getNameInNamespace();
                         String parentBaseDN = parentDN.substring(parentDN.indexOf(",") + 1);
-                        Class<? extends AttributedType> baseDNType = getConfig().getSupportedTypeByBaseDN(parentBaseDN, getEntryObjectClasses(attributes));
+                        Class<? extends IdentityType> baseDNType = getConfig().getSupportedTypeByBaseDN(parentBaseDN, getEntryObjectClasses(attributes));
 
                         if (parentProperty.getJavaClass().isAssignableFrom(baseDNType)) {
                             if (logger.isTraceEnabled()) {
@@ -546,90 +483,53 @@ public class LDAPIdentityStore implements IdentityStore {
                         logger.tracef("No parent entry found for DN [%s] using filter [%s].", entryDN, filter.toString());
                     }
                 }
-            }
-        } catch (Exception e) {
-            throw new ModelException("Could not populate attribute type " + attributedType + ".", e);
-        }
+            }  */
 
-        return attributedType;
-    }
 
-    private String findAttributeName(Map<String, String> attrMapping, String ldapAttributeName) {
-        for (Map.Entry<String,String> currentAttr : attrMapping.entrySet()) {
-            if (currentAttr.getValue().equalsIgnoreCase(ldapAttributeName)) {
-                return currentAttr.getKey();
-            }
+        } catch (Exception e) {
+            throw new ModelException("Could not populate attribute type " + searchResult.getNameInNamespace() + ".", e);
         }
-
-        return null;
     }
 
-    private List<String> getEntryObjectClasses(final Attributes attributes) throws NamingException {
-        Attribute objectClassesAttribute = attributes.get(LDAPConstants.OBJECT_CLASS);
-        List<String> objectClasses = new ArrayList<String>();
-
-        if (objectClassesAttribute == null) {
-            return objectClasses;
-        }
-
-        NamingEnumeration<?> all = objectClassesAttribute.getAll();
-
-        while (all.hasMore()) {
-            objectClasses.add(all.next().toString());
-        }
+    protected BasicAttributes extractAttributes(LDAPObject ldapObject, boolean isCreate) {
+        BasicAttributes entryAttributes = new BasicAttributes();
 
-        return objectClasses;
-    }
+        for (Map.Entry<String, Object> attrEntry : ldapObject.getAttributes().entrySet()) {
+            String attrName = attrEntry.getKey();
+            Object attrValue = attrEntry.getValue();
+            if (!ldapObject.getReadOnlyAttributeNames().contains(attrName) && (isCreate || !ldapObject.getRdnAttributeName().equals(attrName))) {
 
-    protected BasicAttributes extractAttributes(AttributedType attributedType, boolean isCreate) {
-        BasicAttributes entryAttributes = new BasicAttributes();
-        LDAPMappingConfiguration mappingConfig = getMappingConfig(attributedType.getClass());
-        Map<String, String> mappedProperties = mappingConfig.getMappedProperties();
-
-        for (String propertyName : mappedProperties.keySet()) {
-            if (!mappingConfig.getReadOnlyAttributes().contains(propertyName) && (isCreate || !mappingConfig.getBindingProperty().getName().equals(propertyName))) {
-                Property<Object> property = PropertyQueries
-                        .<Object>createQuery(attributedType.getClass())
-                        .addCriteria(new NamedPropertyCriteria(propertyName)).getFirstResult();
-
-                Object propertyValue = null;
-                if (property != null) {
-                    // Mapped Java property on the object
-                    propertyValue = property.getValue(attributedType);
-                } else {
-                    // Not mapped property. So fallback to attribute
-                    org.keycloak.federation.ldap.idm.model.Attribute<?> attribute = attributedType.getAttribute(propertyName);
-                    if (attribute != null) {
-                        propertyValue = attribute.getValue();
+                if (String.class.isInstance(attrValue)) {
+                    if (attrValue.toString().trim().length() == 0) {
+                        attrValue = LDAPConstants.EMPTY_ATTRIBUTE_VALUE;
                     }
-                }
-
-                if (AttributedType.class.isInstance(propertyValue)) {
-                    AttributedType referencedType = (AttributedType) propertyValue;
-                    propertyValue = getBindingDN(referencedType, true);
-                } else {
-                    if (propertyValue == null || isNullOrEmpty(propertyValue.toString())) {
-                        propertyValue = EMPTY_ATTRIBUTE_VALUE;
+                    entryAttributes.put(attrName, attrValue);
+                } else if (Collection.class.isInstance(attrValue)) {
+                    BasicAttribute attr = new BasicAttribute(attrName);
+                    Collection<String> valueCollection = (Collection<String>) attrValue;
+                    for (String val : valueCollection) {
+                        attr.add(val);
                     }
+                    entryAttributes.put(attr);
+                } else if (attrValue == null || attrValue.toString().trim().length() == 0) {
+                    entryAttributes.put(attrName, LDAPConstants.EMPTY_ATTRIBUTE_VALUE);
+                } else {
+                    throw new IllegalArgumentException("Unexpected type of value of argument " + attrName + ". Value is " + attrValue);
                 }
-
-                entryAttributes.put(mappedProperties.get(propertyName), propertyValue);
             }
         }
 
         // Don't extract object classes for update
         if (isCreate) {
-            LDAPMappingConfiguration ldapEntryConfig = getMappingConfig(attributedType.getClass());
-
             BasicAttribute objectClassAttribute = new BasicAttribute(LDAPConstants.OBJECT_CLASS);
 
-            for (String objectClassValue : ldapEntryConfig.getObjectClasses()) {
+            for (String objectClassValue : ldapObject.getObjectClasses()) {
                 objectClassAttribute.add(objectClassValue);
 
                 if (objectClassValue.equals(LDAPConstants.GROUP_OF_NAMES)
                         || objectClassValue.equals(LDAPConstants.GROUP_OF_ENTRIES)
                         || objectClassValue.equals(LDAPConstants.GROUP_OF_UNIQUE_NAMES)) {
-                    entryAttributes.put(LDAPConstants.MEMBER, EMPTY_ATTRIBUTE_VALUE);
+                    entryAttributes.put(LDAPConstants.MEMBER, LDAPConstants.EMPTY_ATTRIBUTE_VALUE);
                 }
             }
 
@@ -639,49 +539,24 @@ public class LDAPIdentityStore implements IdentityStore {
         return entryAttributes;
     }
 
-    // TODO: Move class StringUtil from SAML module
-    public static boolean isNullOrEmpty(String str) {
-        return str == null || str.isEmpty();
-    }
-
-    private LDAPMappingConfiguration getMappingConfig(Class<? extends AttributedType> attributedType) {
-        LDAPMappingConfiguration mappingConfig = getConfig().getMappingConfig(attributedType);
-
-        if (mappingConfig == null) {
-            throw new ModelException("Not mapped type [" + attributedType + "].");
-        }
-
-        return mappingConfig;
-    }
-
-    public String getBindingDN(AttributedType attributedType, boolean appendBaseDN) {
+    /*public String getBindingDN(IdentityType attributedType, boolean appendBaseDN) {
         LDAPMappingConfiguration mappingConfig = getMappingConfig(attributedType.getClass());
-        Property<String> idProperty = mappingConfig.getIdProperty();
 
         String baseDN;
-
         if (mappingConfig.getBaseDN() == null || !appendBaseDN) {
             baseDN = "";
         } else {
             baseDN = LDAPConstants.COMMA + getBaseDN(attributedType);
         }
 
-        Property<String> bindingProperty = mappingConfig.getBindingProperty();
-        String bindingAttribute;
-        String dn;
-
-        if (bindingProperty == null) {
-            bindingAttribute = mappingConfig.getMappedProperties().get(idProperty.getName());
-            dn = idProperty.getValue(attributedType);
-        } else {
-            bindingAttribute = mappingConfig.getMappedProperties().get(bindingProperty.getName());
-            dn = mappingConfig.getBindingProperty().getValue(attributedType);
-        }
+        Property<String> bindingDnAttributeProperty = mappingConfig.getBindingDnProperty();
+        String bindingAttributeName = mappingConfig.getMappedAttributes().get(bindingDnAttributeProperty.getName());
+        String bindingAttributeValue = mappingConfig.getBindingDnProperty().getValue(attributedType);
 
-        return bindingAttribute + LDAPConstants.EQUAL + dn + baseDN;
+        return bindingAttributeName + LDAPConstants.EQUAL + bindingAttributeValue + baseDN;
     }
 
-    private String getBaseDN(AttributedType attributedType) {
+    private String getBaseDN(IdentityType attributedType) {
         LDAPMappingConfiguration mappingConfig = getMappingConfig(attributedType.getClass());
         String baseDN = mappingConfig.getBaseDN();
         String parentDN = mappingConfig.getParentMapping().get(mappingConfig.getIdProperty().getValue(attributedType));
@@ -689,12 +564,12 @@ public class LDAPIdentityStore implements IdentityStore {
         if (parentDN != null) {
             baseDN = parentDN;
         } else {
-            Property<AttributedType> parentProperty = PropertyQueries
-                    .<AttributedType>createQuery(attributedType.getClass())
+            Property<IdentityType> parentProperty = PropertyQueries
+                    .<IdentityType>createQuery(attributedType.getClass())
                     .addCriteria(new TypedPropertyCriteria(attributedType.getClass())).getFirstResult();
 
             if (parentProperty != null) {
-                AttributedType parentType = parentProperty.getValue(attributedType);
+                IdentityType parentType = parentProperty.getValue(attributedType);
 
                 if (parentType != null) {
                     Property<String> parentIdProperty = getMappingConfig(parentType.getClass()).getIdProperty();
@@ -712,24 +587,20 @@ public class LDAPIdentityStore implements IdentityStore {
             }
         }
 
-        if (baseDN == null) {
-            baseDN = getConfig().getBaseDN();
-        }
-
         return baseDN;
     }
 
-    protected void addToParentAsMember(final AttributedType attributedType) {
+    protected void addToParentAsMember(final IdentityType attributedType) {
         LDAPMappingConfiguration entryConfig = getMappingConfig(attributedType.getClass());
 
         if (entryConfig.getParentMembershipAttributeName() != null) {
-            Property<AttributedType> parentProperty = PropertyQueries
-                    .<AttributedType>createQuery(attributedType.getClass())
+            Property<IdentityType> parentProperty = PropertyQueries
+                    .<IdentityType>createQuery(attributedType.getClass())
                     .addCriteria(new TypedPropertyCriteria(attributedType.getClass()))
                     .getFirstResult();
 
             if (parentProperty != null) {
-                AttributedType parentType = parentProperty.getValue(attributedType);
+                IdentityType parentType = parentProperty.getValue(attributedType);
 
                 if (parentType != null) {
                     Attributes attributes = this.operationManager.getAttributes(parentType.getId(), getBaseDN(parentType), entryConfig);
@@ -741,21 +612,22 @@ public class LDAPIdentityStore implements IdentityStore {
                 }
             }
         }
-    }
+    }   */
 
-    protected String getEntryIdentifier(final AttributedType attributedType) {
+    protected String getEntryIdentifier(final LDAPObject ldapObject) {
         try {
             // we need this to retrieve the entry's identifier from the ldap server
-            List<SearchResult> search = this.operationManager.search(getBaseDN(attributedType), "(" + getBindingDN(attributedType, false) + ")", getMappingConfig(attributedType.getClass()));
-            Attribute id = search.get(0).getAttributes().get(getConfig().getUniqueIdentifierAttributeName());
+            String uuidAttrName = getConfig().getUuidLDAPAttributeName();
+            List<SearchResult> search = this.operationManager.search(ldapObject.getDn().toString(), "(" + ldapObject.getDn().getFirstRdn() + ")", Arrays.asList(uuidAttrName), SearchControls.OBJECT_SCOPE);
+            Attribute id = search.get(0).getAttributes().get(getConfig().getUuidLDAPAttributeName());
 
             if (id == null) {
-                throw new ModelException("Could not retrieve identifier for entry [" + getBindingDN(attributedType, true) + "].");
+                throw new ModelException("Could not retrieve identifier for entry [" + ldapObject.getDn().toString() + "].");
             }
 
             return this.operationManager.decodeEntryUUID(id.get());
         } catch (NamingException ne) {
-            throw new ModelException("Could not add type [" + attributedType + "].", ne);
+            throw new ModelException("Could not retrieve identifier for entry [" + ldapObject.getDn().toString() + "].");
         }
     }
 }
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPOperationManager.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPOperationManager.java
index 507d61f..0a9f4dc 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPOperationManager.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPOperationManager.java
@@ -2,12 +2,15 @@ package org.keycloak.federation.ldap.idm.store.ldap;
 
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Hashtable;
 import java.util.List;
 import java.util.Map;
 import java.util.Properties;
+import java.util.Set;
 
 import javax.naming.Binding;
 import javax.naming.Context;
@@ -27,13 +30,11 @@ import javax.naming.ldap.PagedResultsControl;
 import javax.naming.ldap.PagedResultsResponseControl;
 
 import org.jboss.logging.Logger;
-import org.keycloak.federation.ldap.idm.model.IdentityType;
-import org.keycloak.federation.ldap.idm.query.IdentityQuery;
+import org.keycloak.federation.ldap.LDAPConfig;
+import org.keycloak.federation.ldap.idm.query.internal.LDAPIdentityQuery;
 import org.keycloak.models.LDAPConstants;
 import org.keycloak.models.ModelException;
 
-import static javax.naming.directory.SearchControls.SUBTREE_SCOPE;
-
 /**
  * <p>This class provides a set of operations to manage LDAP trees.</p>
  *
@@ -44,10 +45,10 @@ public class LDAPOperationManager {
 
     private static final Logger logger = Logger.getLogger(LDAPOperationManager.class);
 
-    private final LDAPIdentityStoreConfiguration config;
+    private final LDAPConfig config;
     private final Map<String, Object> connectionProperties;
 
-    public LDAPOperationManager(LDAPIdentityStoreConfiguration config) throws NamingException {
+    public LDAPOperationManager(LDAPConfig config) throws NamingException {
         this.config = config;
         this.connectionProperties = Collections.unmodifiableMap(createConnectionProperties());
     }
@@ -119,56 +120,29 @@ public class LDAPOperationManager {
 
     /**
      * <p>
-     * Searches the LDAP tree.
+     * Removes the object from the LDAP tree
      * </p>
-     *
-     * @param baseDN
-     * @param id
-     *
-     * @return
      */
-    public void removeEntryById(final String baseDN, final String id, final LDAPMappingConfiguration mappingConfiguration) {
-        final String filter = getFilterById(baseDN, id);
-
+    public void removeEntry(final String entryDn) {
         try {
-            final SearchControls cons = getSearchControls(mappingConfiguration);
-
             execute(new LdapOperation<SearchResult>() {
                 @Override
                 public SearchResult execute(LdapContext context) throws NamingException {
-                    NamingEnumeration<SearchResult> result = context.search(baseDN, filter, cons);
-
-                    if (result.hasMore()) {
-                        SearchResult sr = result.next();
-                        if (logger.isDebugEnabled()) {
-                            logger.debugf("Removing entry [%s] with attributes: [", sr.getNameInNamespace());
-
-                            NamingEnumeration<? extends Attribute> all = sr.getAttributes().getAll();
-
-                            while (all.hasMore()) {
-                                Attribute attribute = all.next();
-
-                                logger.debugf("  %s = %s", attribute.getID(), attribute.get());
-                            }
-
-                            logger.debugf("]");
-                        }
-                        destroySubcontext(context, sr.getNameInNamespace());
+                    if (logger.isDebugEnabled()) {
+                        logger.debugf("Removing entry with DN [%s]", entryDn);
                     }
-
-                    result.close();
-
+                    destroySubcontext(context, entryDn);
                     return null;
                 }
             });
         } catch (NamingException e) {
-            throw new ModelException("Could not remove entry from DN [" + baseDN + "] and id [" + id + "]", e);
+            throw new ModelException("Could not remove entry from DN [" + entryDn + "]", e);
         }
     }
 
-    public List<SearchResult> search(final String baseDN, final String filter, LDAPMappingConfiguration mappingConfiguration) throws NamingException {
+    public List<SearchResult> search(final String baseDN, final String filter, Collection<String> returningAttributes, int searchScope) throws NamingException {
         final List<SearchResult> result = new ArrayList<SearchResult>();
-        final SearchControls cons = getSearchControls(mappingConfiguration);
+        final SearchControls cons = getSearchControls(returningAttributes, searchScope);
 
         try {
             return execute(new LdapOperation<List<SearchResult>>() {
@@ -191,16 +165,16 @@ public class LDAPOperationManager {
         }
     }
 
-    public <V extends IdentityType> List<SearchResult> searchPaginated(final String baseDN, final String filter, LDAPMappingConfiguration mappingConfiguration, final IdentityQuery<V> identityQuery) throws NamingException {
+    public List<SearchResult> searchPaginated(final String baseDN, final String filter, final LDAPIdentityQuery identityQuery) throws NamingException {
         final List<SearchResult> result = new ArrayList<SearchResult>();
-        final SearchControls cons = getSearchControls(mappingConfiguration);
+        final SearchControls cons = getSearchControls(identityQuery.getReturningLdapAttributes(), identityQuery.getSearchScope());
 
         try {
             return execute(new LdapOperation<List<SearchResult>>() {
                 @Override
                 public List<SearchResult> execute(LdapContext context) throws NamingException {
                     try {
-                        byte[] cookie = (byte[])identityQuery.getPaginationContext();
+                        byte[] cookie = identityQuery.getPaginationContext();
                         PagedResultsControl pagedControls = new PagedResultsControl(identityQuery.getLimit(), cookie, Control.CRITICAL);
                         context.setRequestControls(new Control[] { pagedControls });
 
@@ -236,19 +210,19 @@ public class LDAPOperationManager {
         }
     }
 
-    private SearchControls getSearchControls(LDAPMappingConfiguration mappingConfiguration) {
+    private SearchControls getSearchControls(Collection<String> returningAttributes, int searchScope) {
         final SearchControls cons = new SearchControls();
 
-        cons.setSearchScope(SUBTREE_SCOPE);
+        cons.setSearchScope(searchScope);
         cons.setReturningObjFlag(false);
 
-        List<String> returningAttributes = getReturningAttributes(mappingConfiguration);
+        returningAttributes = getReturningAttributes(returningAttributes);
 
         cons.setReturningAttributes(returningAttributes.toArray(new String[returningAttributes.size()]));
         return cons;
     }
 
-    public String getFilterById(String baseDN, String id) {
+    public String getFilterById(String id) {
         String filter = null;
 
         if (this.config.isActiveDirectory()) {
@@ -264,24 +238,24 @@ public class LDAPOperationManager {
 
                 byte[] objectGUID = (byte[]) attributes.get(LDAPConstants.OBJECT_GUID).get();
 
-                filter = "(&(objectClass=*)(" + getUniqueIdentifierAttributeName() + LDAPConstants.EQUAL + LDAPUtil.convertObjectGUIToByteString(objectGUID) + "))";
+                filter = "(&(objectClass=*)(" + getUuidAttributeName() + LDAPConstants.EQUAL + LDAPUtil.convertObjectGUIToByteString(objectGUID) + "))";
             } catch (NamingException ne) {
                 return filter;
             }
         }
 
         if (filter == null) {
-            filter = "(&(objectClass=*)(" + getUniqueIdentifierAttributeName() + LDAPConstants.EQUAL + id + "))";
+            filter = "(&(objectClass=*)(" + getUuidAttributeName() + LDAPConstants.EQUAL + id + "))";
         }
 
         return filter;
     }
 
-    public SearchResult lookupById(final String baseDN, final String id, final LDAPMappingConfiguration mappingConfiguration) {
-        final String filter = getFilterById(baseDN, id);
+    public SearchResult lookupById(final String baseDN, final String id, final Collection<String> returningAttributes) {
+        final String filter = getFilterById(id);
 
         try {
-            final SearchControls cons = getSearchControls(mappingConfiguration);
+            final SearchControls cons = getSearchControls(returningAttributes, this.config.getSearchScope());
 
             return execute(new LdapOperation<SearchResult>() {
                 @Override
@@ -444,44 +418,15 @@ public class LDAPOperationManager {
         }
     }
 
-    private String getUniqueIdentifierAttributeName() {
-        return this.config.getUniqueIdentifierAttributeName();
-    }
-
-    private NamingEnumeration<SearchResult> createEmptyEnumeration() {
-        return new NamingEnumeration<SearchResult>() {
-            @Override
-            public SearchResult next() throws NamingException {
-                return null;  //To change body of implemented methods use File | Settings | File Templates.
-            }
-
-            @Override
-            public boolean hasMore() throws NamingException {
-                return false;  //To change body of implemented methods use File | Settings | File Templates.
-            }
-
-            @Override
-            public void close() throws NamingException {
-                //To change body of implemented methods use File | Settings | File Templates.
-            }
-
-            @Override
-            public boolean hasMoreElements() {
-                return false;  //To change body of implemented methods use File | Settings | File Templates.
-            }
-
-            @Override
-            public SearchResult nextElement() {
-                return null;  //To change body of implemented methods use File | Settings | File Templates.
-            }
-        };
+    private String getUuidAttributeName() {
+        return this.config.getUuidLDAPAttributeName();
     }
 
-    public Attributes getAttributes(final String entryUUID, final String baseDN, LDAPMappingConfiguration mappingConfiguration) {
-        SearchResult search = lookupById(baseDN, entryUUID, mappingConfiguration);
+    public Attributes getAttributes(final String entryUUID, final String baseDN, Set<String> returningAttributes) {
+        SearchResult search = lookupById(baseDN, entryUUID, returningAttributes);
 
         if (search == null) {
-            throw new ModelException("Couldn't find item with entryUUID [" + entryUUID + "] and baseDN [" + baseDN + "]");
+            throw new ModelException("Couldn't find item with ID [" + entryUUID + " under base DN [" + baseDN + "]");
         }
 
         return search.getAttributes();
@@ -509,7 +454,7 @@ public class LDAPOperationManager {
         env.put(Context.INITIAL_CONTEXT_FACTORY, this.config.getFactoryName());
         env.put(Context.SECURITY_AUTHENTICATION, this.config.getAuthType());
 
-        String protocol = this.config.getProtocol();
+        String protocol = this.config.getSecurityProtocol();
 
         if (protocol != null) {
             env.put(Context.SECURITY_PROTOCOL, protocol);
@@ -528,17 +473,21 @@ public class LDAPOperationManager {
             env.put(Context.SECURITY_CREDENTIALS, bindCredential);
         }
 
-        String url = this.config.getLdapURL();
+        String url = this.config.getConnectionUrl();
 
-        if (url == null) {
-            throw new RuntimeException("url");
+        if (url != null) {
+            env.put(Context.PROVIDER_URL, url);
+        } else {
+            logger.warn("LDAP URL is null. LDAPOperationManager won't work correctly");
         }
 
-        env.put(Context.PROVIDER_URL, url);
+        String connectionPooling = this.config.getConnectionPooling();
+        if (connectionPooling != null) {
+            env.put("com.sun.jndi.ldap.connect.pool", connectionPooling);
+        }
 
         // Just dump the additional properties
-        Properties additionalProperties = this.config.getConnectionProperties();
-
+        Properties additionalProperties = this.config.getAdditionalConnectionProperties();
         if (additionalProperties != null) {
             for (Object key : additionalProperties.keySet()) {
                 env.put(key.toString(), additionalProperties.getProperty(key.toString()));
@@ -580,27 +529,13 @@ public class LDAPOperationManager {
         R execute(LdapContext context) throws NamingException;
     }
 
-    private List<String> getReturningAttributes(final LDAPMappingConfiguration mappingConfiguration) {
-        List<String> returningAttributes = new ArrayList<String>();
-
-        if (mappingConfiguration != null) {
-            returningAttributes.addAll(mappingConfiguration.getMappedProperties().values());
-
-            returningAttributes.add(mappingConfiguration.getParentMembershipAttributeName());
-
-//            for (LDAPMappingConfiguration relationshipConfig : this.config.getRelationshipConfigs()) {
-//                if (relationshipConfig.getRelatedAttributedType().equals(mappingConfiguration.getMappedClass())) {
-//                    returningAttributes.addAll(relationshipConfig.getMappedProperties().values());
-//                }
-//            }
-        } else {
-            returningAttributes.add("*");
-        }
+    private Set<String> getReturningAttributes(final Collection<String> returningAttributes) {
+        Set<String> result = new HashSet<String>();
 
-        returningAttributes.add(getUniqueIdentifierAttributeName());
-        returningAttributes.add(LDAPConstants.CREATE_TIMESTAMP);
-        returningAttributes.add(LDAPConstants.OBJECT_CLASS);
+        result.addAll(returningAttributes);
+        result.add(getUuidAttributeName());
+        result.add(LDAPConstants.OBJECT_CLASS);
 
-        return returningAttributes;
+        return result;
     }
 }
\ No newline at end of file
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPConfig.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPConfig.java
new file mode 100644
index 0000000..580a556
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPConfig.java
@@ -0,0 +1,175 @@
+package org.keycloak.federation.ldap;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+import javax.naming.directory.SearchControls;
+
+import org.keycloak.models.LDAPConstants;
+import org.keycloak.models.UserFederationProvider;
+import org.keycloak.models.UserFederationProviderModel;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ *
+ * TODO: init properties at constructor instead of always compute them
+ */
+public class LDAPConfig {
+
+    private final Map<String, String> config;
+
+    public LDAPConfig(Map<String, String> config) {
+        this.config = config;
+    }
+
+    public String getConnectionUrl() {
+        return config.get(LDAPConstants.CONNECTION_URL);
+    }
+
+    public String getFactoryName() {
+        // hardcoded for now
+        return "com.sun.jndi.ldap.LdapCtxFactory";
+    }
+
+    public String getAuthType() {
+        // hardcoded for now
+        return "simple";
+    }
+
+    public String getSecurityProtocol() {
+        // hardcoded for now
+        return config.get(LDAPConstants.SECURITY_PROTOCOL);
+    }
+
+    public Collection<String> getUserDns() {
+        String value = config.get(LDAPConstants.USER_DNS);
+        if (value == null) {
+            return Collections.emptyList();
+        } else {
+            return Arrays.asList(value.split(LDAPConstants.CONFIG_DIVIDER));
+        }
+    }
+
+    public String getSingleUserDn() {
+        Collection<String> dns = getUserDns();
+        if (dns.size() == 0) {
+            throw new IllegalStateException("No user DN configured. User DNS value is " + config.get(LDAPConstants.USER_DNS));
+        }
+        return dns.iterator().next();
+    }
+
+    public Collection<String> getUserObjectClasses() {
+        String objClassesCfg = config.get(LDAPConstants.USER_OBJECT_CLASSES);
+        String objClassesStr = (objClassesCfg != null && objClassesCfg.length() > 0) ? objClassesCfg.trim() : "inetOrgPerson,organizationalPerson";
+
+        String[] objectClasses = objClassesStr.split(",");
+
+        // Trim them
+        Set<String> userObjClasses = new HashSet<String>();
+        for (int i=0 ; i<objectClasses.length ; i++) {
+            userObjClasses.add(objectClasses[i].trim());
+        }
+        return userObjClasses;
+    }
+
+    public String getBindDN() {
+        return config.get(LDAPConstants.BIND_DN);
+    }
+
+    public String getBindCredential() {
+        return config.get(LDAPConstants.BIND_CREDENTIAL);
+    }
+
+    public String getVendor() {
+        return config.get(LDAPConstants.VENDOR);
+    }
+
+    public boolean isActiveDirectory() {
+        String vendor = getVendor();
+        return vendor != null && vendor.equals(LDAPConstants.VENDOR_ACTIVE_DIRECTORY);
+    }
+
+    public String getConnectionPooling() {
+        return config.get(LDAPConstants.CONNECTION_POOLING);
+    }
+
+    public Properties getAdditionalConnectionProperties() {
+        // not supported for now
+        return null;
+    }
+
+    public int getSearchScope() {
+        String searchScope = config.get(LDAPConstants.SEARCH_SCOPE);
+        return searchScope == null ? SearchControls.SUBTREE_SCOPE : Integer.parseInt(searchScope);
+    }
+
+    public String getUuidLDAPAttributeName() {
+        String uuidAttrName = config.get(LDAPConstants.UUID_LDAP_ATTRIBUTE);
+        if (uuidAttrName == null) {
+            // Differences of unique attribute among various vendors
+            String vendor = getVendor();
+            if (vendor != null) {
+                switch (vendor) {
+                    case LDAPConstants.VENDOR_RHDS:
+                        uuidAttrName = "nsuniqueid";
+                        break;
+                    case LDAPConstants.VENDOR_TIVOLI:
+                        uuidAttrName = "uniqueidentifier";
+                        break;
+                    case LDAPConstants.VENDOR_NOVELL_EDIRECTORY:
+                        uuidAttrName = "guid";
+                        break;
+                    case LDAPConstants.VENDOR_ACTIVE_DIRECTORY:
+                        uuidAttrName = LDAPConstants.OBJECT_GUID;
+                }
+            }
+
+            if (uuidAttrName == null) {
+                uuidAttrName = LDAPConstants.ENTRY_UUID;
+            }
+        }
+
+        return uuidAttrName;
+    }
+
+    // TODO: Remove and use mapper instead
+    public boolean isUserAccountControlsAfterPasswordUpdate() {
+        String userAccountCtrls = config.get(LDAPConstants.USER_ACCOUNT_CONTROLS_AFTER_PASSWORD_UPDATE);
+        return userAccountCtrls==null ? false : Boolean.parseBoolean(userAccountCtrls);
+    }
+
+    public boolean isPagination() {
+        String pagination = config.get(LDAPConstants.PAGINATION);
+        return pagination==null ? false : Boolean.parseBoolean(pagination);
+    }
+
+    public String getUsernameLdapAttribute() {
+        String username = config.get(LDAPConstants.USERNAME_LDAP_ATTRIBUTE);
+        if (username == null) {
+            username = isActiveDirectory() ? LDAPConstants.CN : LDAPConstants.UID;
+        }
+        return username;
+    }
+
+    public String getRdnLdapAttribute() {
+        String rdn = config.get(LDAPConstants.RDN_LDAP_ATTRIBUTE);
+        if (rdn == null) {
+            rdn = getUsernameLdapAttribute();
+        }
+        return rdn;
+    }
+
+    public UserFederationProvider.EditMode getEditMode() {
+        String editModeString = config.get(LDAPConstants.EDIT_MODE);
+        if (editModeString == null) {
+            return UserFederationProvider.EditMode.READ_ONLY;
+        } else {
+            return UserFederationProvider.EditMode.valueOf(editModeString);
+        }
+    }
+}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java
index 370e0f0..2168282 100755
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java
@@ -3,11 +3,14 @@ package org.keycloak.federation.ldap;
 import org.jboss.logging.Logger;
 import org.keycloak.federation.kerberos.impl.KerberosUsernamePasswordAuthenticator;
 import org.keycloak.federation.kerberos.impl.SPNEGOAuthenticator;
-import org.keycloak.federation.ldap.idm.model.LDAPUser;
-import org.keycloak.federation.ldap.idm.query.IdentityQuery;
-import org.keycloak.federation.ldap.idm.query.IdentityQueryBuilder;
+import org.keycloak.federation.ldap.idm.model.LDAPObject;
+import org.keycloak.federation.ldap.idm.query.Condition;
+import org.keycloak.federation.ldap.idm.query.QueryParameter;
+import org.keycloak.federation.ldap.idm.query.internal.LDAPIdentityQuery;
+import org.keycloak.federation.ldap.idm.query.internal.LDAPQueryConditionsBuilder;
 import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
 import org.keycloak.federation.ldap.kerberos.LDAPProviderKerberosConfig;
+import org.keycloak.federation.ldap.mappers.LDAPFederationMapper;
 import org.keycloak.models.CredentialValidationOutput;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.LDAPConstants;
@@ -16,11 +19,15 @@ import org.keycloak.models.RealmModel;
 import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserCredentialModel;
 import org.keycloak.models.UserCredentialValueModel;
+import org.keycloak.mappers.UserFederationMapper;
+import org.keycloak.models.UserFederationMapperModel;
 import org.keycloak.models.UserFederationProvider;
 import org.keycloak.models.UserFederationProviderModel;
+import org.keycloak.models.UserFederationSyncResult;
 import org.keycloak.models.UserModel;
 import org.keycloak.constants.KerberosConstants;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -52,12 +59,7 @@ public class LDAPFederationProvider implements UserFederationProvider {
         this.model = model;
         this.ldapIdentityStore = ldapIdentityStore;
         this.kerberosConfig = new LDAPProviderKerberosConfig(model);
-        String editModeString = model.getConfig().get(LDAPConstants.EDIT_MODE);
-        if (editModeString == null) {
-            editMode = EditMode.READ_ONLY;
-        } else {
-            editMode = EditMode.valueOf(editModeString);
-        }
+        this.editMode = ldapIdentityStore.getConfig().getEditMode();
 
         supportedCredentialTypes.add(UserCredentialModel.PASSWORD);
         if (kerberosConfig.isAllowKerberosAuthentication()) {
@@ -77,17 +79,40 @@ public class LDAPFederationProvider implements UserFederationProvider {
         return this.ldapIdentityStore;
     }
 
+    public EditMode getEditMode() {
+        return editMode;
+    }
+
     @Override
-    public UserModel proxy(UserModel local) {
-         switch (editMode) {
-             case READ_ONLY:
-                return new ReadonlyLDAPUserModelDelegate(local, this);
-             case WRITABLE:
-                return new WritableLDAPUserModelDelegate(local, this);
-             case UNSYNCED:
-                return new UnsyncedLDAPUserModelDelegate(local, this);
-         }
-        return local;
+    public UserModel validateAndProxy(RealmModel realm, UserModel local) {
+        LDAPObject ldapObject = loadAndValidateUser(realm, local);
+        if (ldapObject == null) {
+            return null;
+        }
+
+        return proxy(realm, local, ldapObject);
+    }
+
+    protected UserModel proxy(RealmModel realm, UserModel local, LDAPObject ldapObject) {
+        UserModel proxied = local;
+        switch (editMode) {
+            case READ_ONLY:
+                proxied = new ReadonlyLDAPUserModelDelegate(local, this);
+                break;
+            case WRITABLE:
+                proxied = new WritableLDAPUserModelDelegate(local, this, ldapObject);
+                break;
+            case UNSYNCED:
+                proxied = new UnsyncedLDAPUserModelDelegate(local, this);
+        }
+
+        Set<UserFederationMapperModel> federationMappers = realm.getUserFederationMappers();
+        for (UserFederationMapperModel mapperModel : federationMappers) {
+            LDAPFederationMapper ldapMapper = getMapper(mapperModel);
+            proxied = ldapMapper.proxy(mapperModel, this, ldapObject, proxied, realm);
+        }
+
+        return proxied;
     }
 
     @Override
@@ -119,10 +144,11 @@ public class LDAPFederationProvider implements UserFederationProvider {
         if (editMode == EditMode.READ_ONLY || editMode == EditMode.UNSYNCED) throw new IllegalStateException("Registration is not supported by this ldap server");
         if (!synchronizeRegistrations()) throw new IllegalStateException("Registration is not supported by this ldap server");
 
-        LDAPUser ldapUser = LDAPUtils.addUser(this.ldapIdentityStore, user.getUsername(), user.getFirstName(), user.getLastName(), user.getEmail());
-        user.setAttribute(LDAPConstants.LDAP_ID, ldapUser.getId());
-        user.setAttribute(LDAPConstants.LDAP_ENTRY_DN, ldapUser.getEntryDN());
-        return proxy(user);
+        LDAPObject ldapObject = LDAPUtils.addUserToLDAP(this, realm, user);
+        user.setAttribute(LDAPConstants.LDAP_ID, ldapObject.getUuid());
+        user.setAttribute(LDAPConstants.LDAP_ENTRY_DN, ldapObject.getDn().toString());
+
+        return proxy(realm, user, ldapObject);
     }
 
     @Override
@@ -132,16 +158,24 @@ public class LDAPFederationProvider implements UserFederationProvider {
             return false;
         }
 
-        return LDAPUtils.removeUser(this.ldapIdentityStore, user.getUsername());
+        LDAPObject ldapObject = loadAndValidateUser(realm, user);
+        if (ldapObject == null) {
+            logger.warnf("User '%s' can't be deleted from LDAP as it doesn't exist here", user.getUsername());
+            return false;
+        }
+
+        ldapIdentityStore.remove(ldapObject);
+        return true;
     }
 
     @Override
     public List<UserModel> searchByAttributes(Map<String, String> attributes, RealmModel realm, int maxResults) {
         List<UserModel> searchResults =new LinkedList<UserModel>();
 
-        Map<String, LDAPUser> ldapUsers = searchLDAP(attributes, maxResults);
-        for (LDAPUser ldapUser : ldapUsers.values()) {
-            if (session.userStorage().getUserByUsername(ldapUser.getLoginName(), realm) == null) {
+        List<LDAPObject> ldapUsers = searchLDAP(realm, attributes, maxResults);
+        for (LDAPObject ldapUser : ldapUsers) {
+            String ldapUsername = LDAPUtils.getUsername(ldapUser, this.ldapIdentityStore.getConfig());
+            if (session.userStorage().getUserByUsername(ldapUsername, realm) == null) {
                 UserModel imported = importUserFromLDAP(realm, ldapUser);
                 searchResults.add(imported);
             }
@@ -150,106 +184,120 @@ public class LDAPFederationProvider implements UserFederationProvider {
         return searchResults;
     }
 
-    protected Map<String, LDAPUser> searchLDAP(Map<String, String> attributes, int maxResults) {
+    protected List<LDAPObject> searchLDAP(RealmModel realm, Map<String, String> attributes, int maxResults) {
 
-        Map<String, LDAPUser> results = new HashMap<String, LDAPUser>();
+        List<LDAPObject> results = new ArrayList<LDAPObject>();
         if (attributes.containsKey(USERNAME)) {
-            LDAPUser user = LDAPUtils.getUser(this.ldapIdentityStore, attributes.get(USERNAME));
+            LDAPObject user = loadLDAPUserByUsername(realm, attributes.get(USERNAME));
             if (user != null) {
-                results.put(user.getLoginName(), user);
+                results.add(user);
             }
         }
 
         if (attributes.containsKey(EMAIL)) {
-            LDAPUser user = queryByEmail(attributes.get(EMAIL));
+            LDAPObject user = queryByEmail(realm, attributes.get(EMAIL));
             if (user != null) {
-                results.put(user.getLoginName(), user);
+                results.add(user);
             }
         }
 
         if (attributes.containsKey(FIRST_NAME) || attributes.containsKey(LAST_NAME)) {
-            IdentityQueryBuilder queryBuilder = this.ldapIdentityStore.createQueryBuilder();
-            IdentityQuery<LDAPUser> query = queryBuilder.createIdentityQuery(LDAPUser.class);
+            LDAPIdentityQuery ldapQuery = LDAPUtils.createQueryForUserSearch(this, realm);
+            LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder();
+
+            // Mapper should replace parameter with correct LDAP mapped attributes
             if (attributes.containsKey(FIRST_NAME)) {
-                query.where(queryBuilder.equal(LDAPUser.FIRST_NAME, attributes.get(FIRST_NAME)));
+                ldapQuery.where(conditionsBuilder.equal(new QueryParameter(FIRST_NAME), attributes.get(FIRST_NAME)));
             }
             if (attributes.containsKey(LAST_NAME)) {
-                query.where(queryBuilder.equal(LDAPUser.LAST_NAME, attributes.get(LAST_NAME)));
-            }
-            query.setLimit(maxResults);
-            List<LDAPUser> users = query.getResultList();
-            for (LDAPUser user : users) {
-                results.put(user.getLoginName(), user);
+                ldapQuery.where(conditionsBuilder.equal(new QueryParameter(LAST_NAME), attributes.get(LAST_NAME)));
             }
+
+            List<LDAPObject> ldapObjects = ldapQuery.getResultList();
+            results.addAll(ldapObjects);
         }
 
         return results;
     }
 
-    @Override
-    public boolean isValid(UserModel local) {
-        LDAPUser ldapUser = LDAPUtils.getUser(this.ldapIdentityStore, local.getUsername());
+    /**
+     * @param local
+     * @return ldapUser corresponding to local user or null if user is no longer in LDAP
+     */
+    protected LDAPObject loadAndValidateUser(RealmModel realm, UserModel local) {
+        LDAPObject ldapUser = loadLDAPUserByUsername(realm, local.getUsername());
         if (ldapUser == null) {
-            return false;
+            return null;
         }
-        return ldapUser.getId().equals(local.getAttribute(LDAPConstants.LDAP_ID));
+        if (ldapUser.getUuid().equals(local.getAttribute(LDAPConstants.LDAP_ID))) {
+            return ldapUser;
+        } else {
+            logger.warnf("LDAP User invalid. ID doesn't match. ID from LDAP [%s], ID from local DB: [%s]", ldapUser.getUuid(), local.getAttribute(LDAPConstants.LDAP_ID));
+            return null;
+        }
+    }
+
+    @Override
+    public boolean isValid(RealmModel realm, UserModel local) {
+        return loadAndValidateUser(realm, local) != null;
     }
 
     @Override
     public UserModel getUserByUsername(RealmModel realm, String username) {
-        LDAPUser ldapUser = LDAPUtils.getUser(this.ldapIdentityStore, username);
+        LDAPObject ldapUser = loadLDAPUserByUsername(realm, username);
         if (ldapUser == null) {
             return null;
         }
 
-        // KEYCLOAK-808: Should we allow case-sensitivity to be configurable?
-        if (!username.equals(ldapUser.getLoginName())) {
-            logger.warnf("User found in LDAP but with different username. LDAP username: %s, Searched username: %s", username, ldapUser.getLoginName());
-            return null;
-        }
-
         return importUserFromLDAP(realm, ldapUser);
     }
 
-    protected UserModel importUserFromLDAP(RealmModel realm, LDAPUser ldapUser) {
-        String email = (ldapUser.getEmail() != null && ldapUser.getEmail().trim().length() > 0) ? ldapUser.getEmail() : null;
+    protected UserModel importUserFromLDAP(RealmModel realm, LDAPObject ldapUser) {
+        String ldapUsername = LDAPUtils.getUsername(ldapUser, ldapIdentityStore.getConfig());
 
-        if (ldapUser.getLoginName() == null) {
-            throw new ModelException("User returned from LDAP has null username! Check configuration of your LDAP mappings. ID of user from LDAP: " + ldapUser.getId());
+        if (ldapUsername == null) {
+            throw new ModelException("User returned from LDAP has null username! Check configuration of your LDAP mappings. Mapped username LDAP attribute: " +
+                    ldapIdentityStore.getConfig().getUsernameLdapAttribute() + ", attributes from LDAP: " + ldapUser.getAttributes());
         }
 
-        UserModel imported = session.userStorage().addUser(realm, ldapUser.getLoginName());
+        UserModel imported = session.userStorage().addUser(realm, ldapUsername);
         imported.setEnabled(true);
-        imported.setEmail(email);
-        imported.setFirstName(ldapUser.getFirstName());
-        imported.setLastName(ldapUser.getLastName());
+
+        Set<UserFederationMapperModel> federationMappers = realm.getUserFederationMappers();
+        for (UserFederationMapperModel mapperModel : federationMappers) {
+            LDAPFederationMapper ldapMapper = getMapper(mapperModel);
+            ldapMapper.onImportUserFromLDAP(mapperModel, this, ldapUser, imported, realm, true);
+        }
+
+        String userDN = ldapUser.getDn().toString();
         imported.setFederationLink(model.getId());
-        imported.setAttribute(LDAPConstants.LDAP_ID, ldapUser.getId());
-        imported.setAttribute(LDAPConstants.LDAP_ENTRY_DN, ldapUser.getEntryDN());
+        imported.setAttribute(LDAPConstants.LDAP_ID, ldapUser.getUuid());
+        imported.setAttribute(LDAPConstants.LDAP_ENTRY_DN, userDN);
 
         logger.debugf("Imported new user from LDAP to Keycloak DB. Username: [%s], Email: [%s], LDAP_ID: [%s], LDAP Entry DN: [%s]", imported.getUsername(), imported.getEmail(),
-                ldapUser.getId(), ldapUser.getEntryDN());
-        return proxy(imported);
+                ldapUser.getUuid(), userDN);
+        return proxy(realm, imported, ldapUser);
     }
 
-    protected LDAPUser queryByEmail(String email) {
-        return LDAPUtils.getUserByEmail(this.ldapIdentityStore, email);
+    protected LDAPObject queryByEmail(RealmModel realm, String email) {
+        LDAPIdentityQuery ldapQuery = LDAPUtils.createQueryForUserSearch(this, realm);
+        LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder();
+
+        // Mapper should replace "email" in parameter name with correct LDAP mapped attribute
+        Condition emailCondition = conditionsBuilder.equal(new QueryParameter(UserModel.EMAIL), email);
+        ldapQuery.where(emailCondition);
+
+        return ldapQuery.getFirstResult();
     }
 
 
     @Override
     public UserModel getUserByEmail(RealmModel realm, String email) {
-        LDAPUser ldapUser = queryByEmail(email);
+        LDAPObject ldapUser = queryByEmail(realm, email);
         if (ldapUser == null) {
             return null;
         }
 
-        // KEYCLOAK-808: Should we allow case-sensitivity to be configurable?
-        if (!email.equals(ldapUser.getEmail())) {
-            logger.warnf("User found in LDAP but with different email. LDAP email: %s, Searched email: %s", email, ldapUser.getEmail());
-            return null;
-        }
-
         return importUserFromLDAP(realm, ldapUser);
     }
 
@@ -261,16 +309,18 @@ public class LDAPFederationProvider implements UserFederationProvider {
     @Override
     public void preRemove(RealmModel realm, RoleModel role) {
         // complete I don't think we have to do anything here
+        // TODO: requires implementation... Maybe mappers callback to ensure role deletion propagated to LDAP by RoleLDAPFederationMapper
     }
 
-    public boolean validPassword(UserModel user, String password) {
+    public boolean validPassword(RealmModel realm, UserModel user, String password) {
         if (kerberosConfig.isAllowKerberosAuthentication() && kerberosConfig.isUseKerberosForPasswordAuthentication()) {
             // Use Kerberos JAAS (Krb5LoginModule)
             KerberosUsernamePasswordAuthenticator authenticator = factory.createKerberosUsernamePasswordAuthenticator(kerberosConfig);
             return authenticator.validUser(user.getUsername(), password);
         } else {
             // Use Naming LDAP API
-            return LDAPUtils.validatePassword(this.ldapIdentityStore, user, password);
+            LDAPObject ldapUser = loadAndValidateUser(realm, user);
+            return ldapIdentityStore.validatePassword(ldapUser, password);
         }
     }
 
@@ -279,7 +329,7 @@ public class LDAPFederationProvider implements UserFederationProvider {
     public boolean validCredentials(RealmModel realm, UserModel user, List<UserCredentialModel> input) {
         for (UserCredentialModel cred : input) {
             if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
-                return validPassword(user, cred.getValue());
+                return validPassword(realm, user, cred.getValue());
             } else {
                 return false; // invalid cred type
             }
@@ -334,27 +384,36 @@ public class LDAPFederationProvider implements UserFederationProvider {
     public void close() {
     }
 
-    protected void importLDAPUsers(RealmModel realm, List<LDAPUser> ldapUsers, UserFederationProviderModel fedModel) {
-        for (LDAPUser ldapUser : ldapUsers) {
-            String username = ldapUser.getLoginName();
+    protected UserFederationSyncResult importLDAPUsers(RealmModel realm, List<LDAPObject> ldapUsers, UserFederationProviderModel fedModel) {
+        UserFederationSyncResult syncResult = new UserFederationSyncResult();
+
+        for (LDAPObject ldapUser : ldapUsers) {
+            String username = LDAPUtils.getUsername(ldapUser, ldapIdentityStore.getConfig());
             UserModel currentUser = session.userStorage().getUserByUsername(username, realm);
 
             if (currentUser == null) {
                 // Add new user to Keycloak
                 importUserFromLDAP(realm, ldapUser);
+                syncResult.increaseAdded();
             } else {
-                if ((fedModel.getId().equals(currentUser.getFederationLink())) && (ldapUser.getId().equals(currentUser.getAttribute(LDAPConstants.LDAP_ID)))) {
+                if ((fedModel.getId().equals(currentUser.getFederationLink())) && (ldapUser.getUuid().equals(currentUser.getAttribute(LDAPConstants.LDAP_ID)))) {
+
                     // Update keycloak user
-                    String email = (ldapUser.getEmail() != null && ldapUser.getEmail().trim().length() > 0) ? ldapUser.getEmail() : null;
-                    currentUser.setEmail(email);
-                    currentUser.setFirstName(ldapUser.getFirstName());
-                    currentUser.setLastName(ldapUser.getLastName());
+                    Set<UserFederationMapperModel> federationMappers = realm.getUserFederationMappers();
+                    for (UserFederationMapperModel mapperModel : federationMappers) {
+                        LDAPFederationMapper ldapMapper = getMapper(mapperModel);
+                        ldapMapper.onImportUserFromLDAP(mapperModel, this, ldapUser, currentUser, realm, false);
+                    }
+
                     logger.debugf("Updated user from LDAP: %s", currentUser.getUsername());
+                    syncResult.increaseUpdated();
                 } else {
                     logger.warnf("User '%s' is not updated during sync as he is not linked to federation provider '%s'", username, fedModel.getDisplayName());
                 }
             }
         }
+
+        return syncResult;
     }
 
     /**
@@ -371,13 +430,16 @@ public class LDAPFederationProvider implements UserFederationProvider {
             if (!model.getId().equals(user.getFederationLink())) {
                 logger.warnf("User with username [%s] already exists, but is not linked to provider [%s]", username, model.getDisplayName());
                 return null;
-            } else if (isValid(user)) {
-                return proxy(user);
             } else {
-                logger.warnf("User with username [%s] aready exists and is linked to provider [%s] but is not valid. Stale LDAP_ID on local user is: %s",
-                        username,  model.getDisplayName(), user.getAttribute(LDAPConstants.LDAP_ID));
-                logger.warn("Will re-create user");
-                session.userStorage().removeUser(realm, user);
+                LDAPObject ldapObject = loadAndValidateUser(realm, user);
+                if (ldapObject != null) {
+                    return proxy(realm, user, ldapObject);
+                } else {
+                    logger.warnf("User with username [%s] aready exists and is linked to provider [%s] but is not valid. Stale LDAP_ID on local user is: %s",
+                            username,  model.getDisplayName(), user.getAttribute(LDAPConstants.LDAP_ID));
+                    logger.warn("Will re-create user");
+                    session.userStorage().removeUser(realm, user);
+                }
             }
         }
 
@@ -385,4 +447,36 @@ public class LDAPFederationProvider implements UserFederationProvider {
         logger.debugf("Kerberos authenticated user [%s] not in Keycloak storage. Creating him", username);
         return getUserByUsername(realm, username);
     }
+
+    public LDAPObject loadLDAPUserByUsername(RealmModel realm, String username) {
+        LDAPIdentityQuery ldapQuery = LDAPUtils.createQueryForUserSearch(this, realm);
+        LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder();
+
+        String usernameMappedAttribute = this.ldapIdentityStore.getConfig().getUsernameLdapAttribute();
+        Condition usernameCondition = conditionsBuilder.equal(new QueryParameter(usernameMappedAttribute), username);
+        ldapQuery.where(usernameCondition);
+
+        LDAPObject ldapUser = ldapQuery.getFirstResult();
+        if (ldapUser == null) {
+            return null;
+        }
+
+        // KEYCLOAK-808: Should we allow case-sensitivity to be configurable?
+        String ldapUsername = LDAPUtils.getUsername(ldapUser, ldapIdentityStore.getConfig());
+        if (!username.equals(ldapUsername)) {
+            logger.warnf("User found in LDAP but with different username. LDAP username: %s, Searched username: %s", username, ldapUsername);
+            return null;
+        }
+
+        return ldapUser;
+    }
+
+    public LDAPFederationMapper getMapper(UserFederationMapperModel mapperModel) {
+        LDAPFederationMapper ldapMapper = (LDAPFederationMapper) getSession().getProvider(UserFederationMapper.class, mapperModel.getFederationMapperType());
+        if (ldapMapper == null) {
+            throw new ModelException("Can't find mapper type with ID: " + mapperModel.getFederationMapperType());
+        }
+
+        return ldapMapper;
+    }
 }
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProviderFactory.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProviderFactory.java
index a498a93..166c5bf 100755
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProviderFactory.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProviderFactory.java
@@ -6,20 +6,28 @@ import org.keycloak.federation.kerberos.CommonKerberosConfig;
 import org.keycloak.federation.kerberos.impl.KerberosServerSubjectAuthenticator;
 import org.keycloak.federation.kerberos.impl.KerberosUsernamePasswordAuthenticator;
 import org.keycloak.federation.kerberos.impl.SPNEGOAuthenticator;
-import org.keycloak.federation.ldap.idm.model.IdentityType;
-import org.keycloak.federation.ldap.idm.model.LDAPUser;
+import org.keycloak.federation.ldap.idm.model.LDAPObject;
 import org.keycloak.federation.ldap.idm.query.Condition;
-import org.keycloak.federation.ldap.idm.query.IdentityQuery;
-import org.keycloak.federation.ldap.idm.query.IdentityQueryBuilder;
+import org.keycloak.federation.ldap.idm.query.QueryParameter;
+import org.keycloak.federation.ldap.idm.query.internal.LDAPIdentityQuery;
+import org.keycloak.federation.ldap.idm.query.internal.LDAPQueryConditionsBuilder;
 import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
+import org.keycloak.federation.ldap.mappers.FullNameLDAPFederationMapper;
+import org.keycloak.federation.ldap.mappers.FullNameLDAPFederationMapperFactory;
+import org.keycloak.federation.ldap.mappers.UserAttributeLDAPFederationMapper;
+import org.keycloak.federation.ldap.mappers.UserAttributeLDAPFederationMapperFactory;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakSessionFactory;
 import org.keycloak.models.KeycloakSessionTask;
 import org.keycloak.models.LDAPConstants;
 import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserFederationEventAwareProviderFactory;
+import org.keycloak.models.UserFederationMapperModel;
 import org.keycloak.models.UserFederationProvider;
 import org.keycloak.models.UserFederationProviderFactory;
 import org.keycloak.models.UserFederationProviderModel;
+import org.keycloak.models.UserFederationSyncResult;
+import org.keycloak.models.UserModel;
 import org.keycloak.models.utils.KeycloakModelUtils;
 
 import java.util.Collections;
@@ -32,7 +40,7 @@ import java.util.Set;
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
  */
-public class LDAPFederationProviderFactory implements UserFederationProviderFactory {
+public class LDAPFederationProviderFactory extends UserFederationEventAwareProviderFactory {
     private static final Logger logger = Logger.getLogger(LDAPFederationProviderFactory.class);
     public static final String PROVIDER_NAME = "ldap";
 
@@ -55,11 +63,6 @@ public class LDAPFederationProviderFactory implements UserFederationProviderFact
     }
 
     @Override
-    public void postInit(KeycloakSessionFactory factory) {
-
-    }
-
-    @Override
     public void close() {
         this.ldapStoreRegistry = null;
     }
@@ -74,74 +77,167 @@ public class LDAPFederationProviderFactory implements UserFederationProviderFact
         return Collections.emptySet();
     }
 
+
+    // Best effort to create appropriate mappers according to our LDAP config
     @Override
-    public void syncAllUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model) {
-        logger.infof("Sync all users from LDAP to local store: realm: %s, federation provider: %s, current time: " + new Date(), realmId, model.getDisplayName());
+    protected void onProviderModelCreated(RealmModel realm, UserFederationProviderModel newProviderModel) {
+        LDAPConfig ldapConfig = new LDAPConfig(newProviderModel.getConfig());
+
+        boolean activeDirectory = ldapConfig.isActiveDirectory();
+        UserFederationProvider.EditMode editMode = ldapConfig.getEditMode();
+        String readOnly = String.valueOf(editMode==UserFederationProvider.EditMode.READ_ONLY || editMode== UserFederationProvider.EditMode.UNSYNCED);
+        String usernameLdapAttribute = ldapConfig.getUsernameLdapAttribute();
+
+        UserFederationMapperModel mapperModel;
+        mapperModel = KeycloakModelUtils.createUserFederationMapperModel("usernameMapper", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.ID,
+                UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.USERNAME,
+                UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, usernameLdapAttribute,
+                UserAttributeLDAPFederationMapper.READ_ONLY, readOnly);
+        realm.addUserFederationMapper(mapperModel);
+
+        // For AD deployments with sAMAccountName is probably more common to map "cn" to full name of user
+        if (activeDirectory && usernameLdapAttribute.equalsIgnoreCase(LDAPConstants.SAM_ACCOUNT_NAME)) {
+            mapperModel = KeycloakModelUtils.createUserFederationMapperModel("fullNameMapper", newProviderModel.getId(), FullNameLDAPFederationMapperFactory.ID,
+                    FullNameLDAPFederationMapper.LDAP_FULL_NAME_ATTRIBUTE, LDAPConstants.CN,
+                    UserAttributeLDAPFederationMapper.READ_ONLY, readOnly);
+            realm.addUserFederationMapper(mapperModel);
+        } else {
+            mapperModel = KeycloakModelUtils.createUserFederationMapperModel("firstNameMapper", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.ID,
+                    UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.FIRST_NAME,
+                    UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.CN,
+                    UserAttributeLDAPFederationMapper.READ_ONLY, readOnly);
+            realm.addUserFederationMapper(mapperModel);
+        }
 
-        LDAPIdentityStore ldapIdentityStore = this.ldapStoreRegistry.getLdapStore(model);
-        IdentityQuery<LDAPUser> userQuery = ldapIdentityStore.createQueryBuilder().createIdentityQuery(LDAPUser.class);
-        syncImpl(sessionFactory, userQuery, realmId, model);
+        mapperModel = KeycloakModelUtils.createUserFederationMapperModel("lastNameMapper", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.ID,
+                UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.LAST_NAME,
+                UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.SN,
+                UserAttributeLDAPFederationMapper.READ_ONLY, readOnly);
+        realm.addUserFederationMapper(mapperModel);
+
+        mapperModel = KeycloakModelUtils.createUserFederationMapperModel("emailMapper", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.ID,
+                UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.EMAIL,
+                UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.EMAIL,
+                UserAttributeLDAPFederationMapper.READ_ONLY, readOnly);
+        realm.addUserFederationMapper(mapperModel);
+
+        String createTimestampLdapAttrName = activeDirectory ? "whenCreated" : LDAPConstants.CREATE_TIMESTAMP;
+        String modifyTimestampLdapAttrName = activeDirectory ? "whenChanged" : LDAPConstants.MODIFY_TIMESTAMP;
+
+        // map createTimeStamp as read-only
+        mapperModel = KeycloakModelUtils.createUserFederationMapperModel("creationDateMapper", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.ID,
+                UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, LDAPConstants.CREATE_TIMESTAMP,
+                UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, createTimestampLdapAttrName,
+                UserAttributeLDAPFederationMapper.READ_ONLY, "true");
+        realm.addUserFederationMapper(mapperModel);
+
+        // map modifyTimeStamp as read-only
+        mapperModel = KeycloakModelUtils.createUserFederationMapperModel("modifyDateMapper", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.ID,
+                UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, LDAPConstants.MODIFY_TIMESTAMP,
+                UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, modifyTimestampLdapAttrName,
+                UserAttributeLDAPFederationMapper.READ_ONLY, "true");
+        realm.addUserFederationMapper(mapperModel);
+    }
+
+
+    @Override
+    public UserFederationSyncResult syncAllUsers(KeycloakSessionFactory sessionFactory, final String realmId, final UserFederationProviderModel model) {
+        logger.infof("Sync all users from LDAP to local store: realm: %s, federation provider: %s", realmId, model.getDisplayName());
+
+        LDAPIdentityQuery userQuery = createQuery(sessionFactory, realmId, model);
+        UserFederationSyncResult syncResult = syncImpl(sessionFactory, userQuery, realmId, model);
 
         // TODO: Remove all existing keycloak users, which have federation links, but are not in LDAP. Perhaps don't check users, which were just added or updated during this sync?
+
+        logger.infof("Sync all users finished: %s", syncResult.getStatus());
+        return syncResult;
     }
 
     @Override
-    public void syncChangedUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model, Date lastSync) {
-        logger.infof("Sync changed users from LDAP to local store: realm: %s, federation provider: %s, current time: %s, last sync time: " + lastSync, realmId, model.getDisplayName(), new Date().toString());
+    public UserFederationSyncResult syncChangedUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model, Date lastSync) {
+        logger.infof("Sync changed users from LDAP to local store: realm: %s, federation provider: %s, last sync time: " + lastSync, realmId, model.getDisplayName());
 
-        LDAPIdentityStore ldapIdentityStore = this.ldapStoreRegistry.getLdapStore(model);
+        // Sync newly created and updated users
+        LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder();
+        Condition createCondition = conditionsBuilder.greaterThanOrEqualTo(new QueryParameter(LDAPConstants.CREATE_TIMESTAMP), lastSync);
+        Condition modifyCondition = conditionsBuilder.greaterThanOrEqualTo(new QueryParameter(LDAPConstants.MODIFY_TIMESTAMP), lastSync);
+        Condition orCondition = conditionsBuilder.orCondition(createCondition, modifyCondition);
 
-        // Sync newly created users
-        IdentityQueryBuilder queryBuilder = ldapIdentityStore.createQueryBuilder();
-        Condition condition = queryBuilder.greaterThanOrEqualTo(IdentityType.CREATED_DATE, lastSync);
-        IdentityQuery<LDAPUser> userQuery = queryBuilder.createIdentityQuery(LDAPUser.class).where(condition);
-        syncImpl(sessionFactory, userQuery, realmId, model);
+        LDAPIdentityQuery userQuery = createQuery(sessionFactory, realmId, model);
+        userQuery.where(orCondition);
+        UserFederationSyncResult result = syncImpl(sessionFactory, userQuery, realmId, model);
 
-        // Sync updated users
-        condition = queryBuilder.greaterThanOrEqualTo(LDAPUtils.MODIFY_DATE, lastSync);
-        userQuery = queryBuilder.createIdentityQuery(LDAPUser.class).where(condition);
-        syncImpl(sessionFactory, userQuery, realmId, model);
+        logger.infof("Sync changed users finished: %s", result.getStatus());
+        return result;
     }
 
-    protected void syncImpl(KeycloakSessionFactory sessionFactory, IdentityQuery<LDAPUser> userQuery, final String realmId, final UserFederationProviderModel fedModel) {
-        boolean pagination = Boolean.parseBoolean(fedModel.getConfig().get(LDAPConstants.PAGINATION));
+    protected UserFederationSyncResult syncImpl(KeycloakSessionFactory sessionFactory, LDAPIdentityQuery userQuery, final String realmId, final UserFederationProviderModel fedModel) {
+
+        final UserFederationSyncResult syncResult = new UserFederationSyncResult();
 
+        boolean pagination = Boolean.parseBoolean(fedModel.getConfig().get(LDAPConstants.PAGINATION));
         if (pagination) {
+
             String pageSizeConfig = fedModel.getConfig().get(LDAPConstants.BATCH_SIZE_FOR_SYNC);
             int pageSize = pageSizeConfig!=null ? Integer.parseInt(pageSizeConfig) : LDAPConstants.DEFAULT_BATCH_SIZE_FOR_SYNC;
+
             boolean nextPage = true;
             while (nextPage) {
                 userQuery.setLimit(pageSize);
-                final List<LDAPUser> users = userQuery.getResultList();
+                final List<LDAPObject> users = userQuery.getResultList();
                 nextPage = userQuery.getPaginationContext() != null;
 
                 KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
 
                     @Override
                     public void run(KeycloakSession session) {
-                        importLdapUsers(session, realmId, fedModel, users);
+                        UserFederationSyncResult currentPageSync = importLdapUsers(session, realmId, fedModel, users);
+                        syncResult.add(currentPageSync);
                     }
 
                 });
             }
         } else {
             // LDAP pagination not available. Do everything in single transaction
-            final List<LDAPUser> users = userQuery.getResultList();
+            final List<LDAPObject> users = userQuery.getResultList();
             KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
 
                 @Override
                 public void run(KeycloakSession session) {
-                    importLdapUsers(session, realmId, fedModel, users);
+                    UserFederationSyncResult currentSync = importLdapUsers(session, realmId, fedModel, users);
+                    syncResult.add(currentSync);
                 }
 
             });
         }
+
+        return syncResult;
+    }
+
+    private LDAPIdentityQuery createQuery(KeycloakSessionFactory sessionFactory, final String realmId, final UserFederationProviderModel model) {
+        class QueryHolder {
+            LDAPIdentityQuery query;
+        }
+
+        final QueryHolder queryHolder = new QueryHolder();
+        KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
+
+            @Override
+            public void run(KeycloakSession session) {
+                LDAPFederationProvider ldapFedProvider = getInstance(session, model);
+                RealmModel realm = session.realms().getRealm(realmId);
+                queryHolder.query = LDAPUtils.createQueryForUserSearch(ldapFedProvider, realm);
+            }
+
+        });
+        return queryHolder.query;
     }
 
-    protected void importLdapUsers(KeycloakSession session, String realmId, UserFederationProviderModel fedModel, List<LDAPUser> ldapUsers) {
+
+    protected UserFederationSyncResult importLdapUsers(KeycloakSession session, String realmId, UserFederationProviderModel fedModel, List<LDAPObject> ldapUsers) {
         RealmModel realm = session.realms().getRealm(realmId);
         LDAPFederationProvider ldapFedProvider = getInstance(session, fedModel);
-        ldapFedProvider.importLDAPUsers(realm, ldapUsers, fedModel);
+        return ldapFedProvider.importLDAPUsers(realm, ldapUsers, fedModel);
     }
 
     protected SPNEGOAuthenticator createSPNEGOAuthenticator(String spnegoToken, CommonKerberosConfig kerberosConfig) {
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPIdentityStoreRegistry.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPIdentityStoreRegistry.java
index b818ace..97f347b 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPIdentityStoreRegistry.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPIdentityStoreRegistry.java
@@ -1,17 +1,11 @@
 package org.keycloak.federation.ldap;
 
-import java.util.Arrays;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Map;
-import java.util.Properties;
 import java.util.concurrent.ConcurrentHashMap;
 
 import org.jboss.logging.Logger;
-import org.keycloak.federation.ldap.idm.model.LDAPUser;
 import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
-import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStoreConfiguration;
-import org.keycloak.federation.ldap.idm.store.ldap.LDAPMappingConfiguration;
 import org.keycloak.models.LDAPConstants;
 import org.keycloak.models.UserFederationProviderModel;
 
@@ -51,10 +45,7 @@ public class LDAPIdentityStoreRegistry {
      * @return PartitionManager instance based on LDAP store
      */
     public static LDAPIdentityStore createLdapIdentityStore(Map<String,String> ldapConfig) {
-        Properties connectionProps = new Properties();
-        if (ldapConfig.containsKey(LDAPConstants.CONNECTION_POOLING)) {
-            connectionProps.put("com.sun.jndi.ldap.connect.pool", ldapConfig.get(LDAPConstants.CONNECTION_POOLING));
-        }
+        LDAPConfig cfg = new LDAPConfig(ldapConfig);
 
         checkSystemProperty("com.sun.jndi.ldap.connect.pool.authentication", "none simple");
         checkSystemProperty("com.sun.jndi.ldap.connect.pool.initsize", "1");
@@ -64,11 +55,7 @@ public class LDAPIdentityStoreRegistry {
         checkSystemProperty("com.sun.jndi.ldap.connect.pool.protocol", "plain");
         checkSystemProperty("com.sun.jndi.ldap.connect.pool.debug", "off");
 
-        String vendor = ldapConfig.get(LDAPConstants.VENDOR);
-
-        boolean activeDirectory = vendor != null && vendor.equals(LDAPConstants.VENDOR_ACTIVE_DIRECTORY);
-
-        String ldapLoginNameMapping = ldapConfig.get(LDAPConstants.USERNAME_LDAP_ATTRIBUTE);
+        /*String ldapLoginNameMapping = ldapConfig.get(LDAPConstants.USERNAME_LDAP_ATTRIBUTE);
         if (ldapLoginNameMapping == null) {
             ldapLoginNameMapping = activeDirectory ? LDAPConstants.CN : LDAPConstants.UID;
         }
@@ -76,62 +63,16 @@ public class LDAPIdentityStoreRegistry {
         String ldapFirstNameMapping = activeDirectory ?  "givenName" : LDAPConstants.CN;
         String createTimestampMapping = activeDirectory ? "whenCreated" : LDAPConstants.CREATE_TIMESTAMP;
         String modifyTimestampMapping = activeDirectory ? "whenChanged" : LDAPConstants.MODIFY_TIMESTAMP;
-        String[] userObjectClasses = getUserObjectClasses(ldapConfig);
-
-        boolean pagination = ldapConfig.containsKey(LDAPConstants.PAGINATION) ? Boolean.parseBoolean(ldapConfig.get(LDAPConstants.PAGINATION)) : false;
-        boolean userAccountControlsAfterPasswordUpdate = ldapConfig.containsKey(LDAPConstants.USER_ACCOUNT_CONTROLS_AFTER_PASSWORD_UPDATE) ?
-                Boolean.parseBoolean(ldapConfig.get(LDAPConstants.USER_ACCOUNT_CONTROLS_AFTER_PASSWORD_UPDATE)) : false;
-
-        // Differences of unique attribute among various vendors
-        String uniqueIdentifierAttributeName = LDAPConstants.ENTRY_UUID;
-        if (vendor != null) {
-            switch (vendor) {
-                case LDAPConstants.VENDOR_RHDS:
-                    uniqueIdentifierAttributeName = "nsuniqueid";
-                    break;
-                case LDAPConstants.VENDOR_TIVOLI:
-                    uniqueIdentifierAttributeName = "uniqueidentifier";
-                    break;
-                case LDAPConstants.VENDOR_NOVELL_EDIRECTORY:
-                    uniqueIdentifierAttributeName = "guid";
-                    break;
-                case LDAPConstants.VENDOR_ACTIVE_DIRECTORY:
-                    uniqueIdentifierAttributeName = LDAPConstants.OBJECT_GUID;
-            }
-        }
+        String[] userObjectClasses = getUserObjectClasses(ldapConfig);  */
+
 
-        LDAPIdentityStoreConfiguration ldapStoreConfig = new LDAPIdentityStoreConfiguration()
-                .setConnectionProperties(connectionProps)
-                .setBaseDN(ldapConfig.get(LDAPConstants.BASE_DN))
-                .setBindDN(ldapConfig.get(LDAPConstants.BIND_DN))
-                .setBindCredential(ldapConfig.get(LDAPConstants.BIND_CREDENTIAL))
-                .setLdapURL(ldapConfig.get(LDAPConstants.CONNECTION_URL))
-                .setActiveDirectory(activeDirectory)
-                .setPagination(pagination)
-                .setUniqueIdentifierAttributeName(uniqueIdentifierAttributeName)
-                .setFactoryName("com.sun.jndi.ldap.LdapCtxFactory")
-                .setAuthType("simple")
-                .setUserAccountControlsAfterPasswordUpdate(userAccountControlsAfterPasswordUpdate);
-
-        LDAPMappingConfiguration ldapUserMappingConfig = ldapStoreConfig
-                .mappingConfig(LDAPUser.class)
-                .setBaseDN(ldapConfig.get(LDAPConstants.USER_DN_SUFFIX))
-                .setObjectClasses(new HashSet<String>(Arrays.asList(userObjectClasses)))
-                .setIdPropertyName("loginName")
-                .addAttributeMapping("loginName", ldapLoginNameMapping)
-                .addAttributeMapping("firstName", ldapFirstNameMapping)
-                .addAttributeMapping("lastName", LDAPConstants.SN)
-                .addAttributeMapping("email", LDAPConstants.EMAIL)
-                .addReadOnlyAttributeMapping("createdDate", createTimestampMapping)
-                .addReadOnlyAttributeMapping("modifyDate", modifyTimestampMapping);
-
-        if (activeDirectory && ldapLoginNameMapping.equals("sAMAccountName")) {
-            ldapUserMappingConfig.setBindingPropertyName("fullName");
+/*        if (activeDirectory && ldapLoginNameMapping.equals("sAMAccountName")) {
+            ldapUserMappingConfig.setBindingDnPropertyName("fullName");
             ldapUserMappingConfig.addAttributeMapping("fullName", LDAPConstants.CN);
             logger.infof("Using 'cn' attribute for DN of user and 'sAMAccountName' for username");
-        }
+        }    */
 
-        return new LDAPIdentityStore(ldapStoreConfig);
+        return new LDAPIdentityStore(cfg);
     }
 
     private static void checkSystemProperty(String name, String defaultValue) {
@@ -140,20 +81,6 @@ public class LDAPIdentityStoreRegistry {
         }
     }
 
-    // Parse array of strings like [ "inetOrgPerson", "organizationalPerson" ] from the string like: "inetOrgPerson, organizationalPerson"
-    private static String[] getUserObjectClasses(Map<String,String> ldapConfig) {
-        String objClassesCfg = ldapConfig.get(LDAPConstants.USER_OBJECT_CLASSES);
-        String objClassesStr = (objClassesCfg != null && objClassesCfg.length() > 0) ? objClassesCfg.trim() : "inetOrgPerson, organizationalPerson";
-
-        String[] objectClasses = objClassesStr.split(",");
-
-        // Trim them
-        String[] userObjectClasses = new String[objectClasses.length];
-        for (int i=0 ; i<objectClasses.length ; i++) {
-            userObjectClasses[i] = objectClasses[i].trim();
-        }
-        return userObjectClasses;
-    }
 
     private class LDAPIdentityStoreContext {
 
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPUtils.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPUtils.java
index 9753592..37e110c 100755
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPUtils.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPUtils.java
@@ -1,142 +1,88 @@
 package org.keycloak.federation.ldap;
 
-import org.keycloak.federation.ldap.idm.model.Attribute;
-import org.keycloak.federation.ldap.idm.model.LDAPUser;
-import org.keycloak.federation.ldap.idm.query.AttributeParameter;
-import org.keycloak.federation.ldap.idm.query.IdentityQuery;
-import org.keycloak.federation.ldap.idm.query.IdentityQueryBuilder;
-import org.keycloak.federation.ldap.idm.query.QueryParameter;
+import java.util.List;
+import java.util.Set;
+
+import org.keycloak.federation.ldap.idm.model.LDAPDn;
+import org.keycloak.federation.ldap.idm.model.LDAPObject;
+import org.keycloak.federation.ldap.idm.query.internal.LDAPIdentityQuery;
 import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
-import org.keycloak.models.LDAPConstants;
-import org.keycloak.models.ModelDuplicateException;
+import org.keycloak.federation.ldap.mappers.LDAPFederationMapper;
+import org.keycloak.models.ModelException;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserFederationMapperModel;
 import org.keycloak.models.UserModel;
 
-import java.util.List;
-
 /**
  * Allow to directly call some operations against LDAPIdentityStore.
- * TODO: Is this class still needed?
  *
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
  */
 public class LDAPUtils {
 
-    public static QueryParameter MODIFY_DATE = new AttributeParameter("modifyDate");
-
-    public static LDAPUser addUser(LDAPIdentityStore ldapIdentityStore, String username, String firstName, String lastName, String email) {
-        if (getUser(ldapIdentityStore, username) != null) {
-            throw new ModelDuplicateException("User with same username already exists");
-        }
-        if (getUserByEmail(ldapIdentityStore, email) != null) {
-            throw new ModelDuplicateException("User with same email already exists");
+    /**
+     * @param ldapProvider
+     * @param realm
+     * @param user
+     * @return newly created LDAPObject with all the attributes, uuid and DN properly set
+     */
+    public static LDAPObject addUserToLDAP(LDAPFederationProvider ldapProvider, RealmModel realm, UserModel user) {
+        LDAPObject ldapUser = new LDAPObject();
+
+        LDAPIdentityStore ldapStore = ldapProvider.getLdapIdentityStore();
+        LDAPConfig ldapConfig = ldapStore.getConfig();
+        ldapUser.setRdnAttributeName(ldapConfig.getRdnLdapAttribute());
+        ldapUser.setObjectClasses(ldapConfig.getUserObjectClasses());
+
+        Set<UserFederationMapperModel> federationMappers = realm.getUserFederationMappers();
+        for (UserFederationMapperModel mapperModel : federationMappers) {
+            LDAPFederationMapper ldapMapper = ldapProvider.getMapper(mapperModel);
+            ldapMapper.onRegisterUserToLDAP(mapperModel, ldapProvider, ldapUser, user, realm);
         }
 
-        LDAPUser ldapUser = new LDAPUser(username);
-        ldapUser.setFirstName(firstName);
-        ldapUser.setLastName(lastName);
-        ldapUser.setEmail(email);
-        ldapUser.setAttribute(new Attribute<String>("fullName", getFullName(username, firstName, lastName)));
-        ldapIdentityStore.add(ldapUser);
-        return ldapUser;
-    }
-
-    public static LDAPUser updateUser(LDAPIdentityStore ldapIdentityStore, String username, String firstName, String lastName, String email) {
-        LDAPUser ldapUser = getUser(ldapIdentityStore, username);
-        ldapUser.setFirstName(firstName);
-        ldapUser.setLastName(lastName);
-        ldapUser.setEmail(email);
-        ldapIdentityStore.update(ldapUser);
+        LDAPUtils.computeAndSetDn(ldapConfig, ldapUser);
+        ldapStore.add(ldapUser);
         return ldapUser;
     }
 
-    public static void updatePassword(LDAPIdentityStore ldapIdentityStore, UserModel user, String password) {
-        LDAPUser ldapUser = convertUserForPasswordUpdate(user);
-
-        ldapIdentityStore.updatePassword(ldapUser, password);
-    }
-
-    public static void updatePassword(LDAPIdentityStore ldapIdentityStore, LDAPUser user, String password) {
-        ldapIdentityStore.updatePassword(user, password);
-    }
-
-    public static boolean validatePassword(LDAPIdentityStore ldapIdentityStore, UserModel user, String password) {
-        LDAPUser ldapUser = convertUserForPasswordUpdate(user);
-
-        return ldapIdentityStore.validatePassword(ldapUser, password);
-    }
+    public static void removeAllUsers(LDAPFederationProvider ldapProvider, RealmModel realm) {
+        LDAPIdentityStore ldapStore = ldapProvider.getLdapIdentityStore();
+        LDAPIdentityQuery ldapQuery = LDAPUtils.createQueryForUserSearch(ldapProvider, realm);
+        List<LDAPObject> allUsers = ldapQuery.getResultList();
 
-    public static boolean validatePassword(LDAPIdentityStore ldapIdentityStore, LDAPUser user, String password) {
-        return ldapIdentityStore.validatePassword(user, password);
-    }
-
-    public static LDAPUser getUser(LDAPIdentityStore ldapIdentityStore, String username) {
-        return ldapIdentityStore.getUser(username);
-    }
-
-    // Put just username and entryDN as these are needed by LDAPIdentityStore for passwordUpdate
-    private static LDAPUser convertUserForPasswordUpdate(UserModel kcUser) {
-        LDAPUser ldapUser = new LDAPUser(kcUser.getUsername());
-        String ldapEntryDN = kcUser.getAttribute(LDAPConstants.LDAP_ENTRY_DN);
-        if (ldapEntryDN != null) {
-            ldapUser.setEntryDN(ldapEntryDN);
+        for (LDAPObject ldapUser : allUsers) {
+            ldapStore.remove(ldapUser);
         }
-        return ldapUser;
     }
 
+    public static LDAPIdentityQuery createQueryForUserSearch(LDAPFederationProvider ldapProvider, RealmModel realm) {
+        LDAPIdentityQuery ldapQuery = new LDAPIdentityQuery(ldapProvider);
+        LDAPConfig config = ldapProvider.getLdapIdentityStore().getConfig();
+        ldapQuery.setSearchScope(config.getSearchScope());
+        ldapQuery.addSearchDns(config.getUserDns());
+        ldapQuery.addObjectClasses(config.getUserObjectClasses());
 
-    public static LDAPUser getUserByEmail(LDAPIdentityStore ldapIdentityStore, String email) {
-        IdentityQueryBuilder queryBuilder = ldapIdentityStore.createQueryBuilder();
-        IdentityQuery<LDAPUser> query = queryBuilder.createIdentityQuery(LDAPUser.class)
-                .where(queryBuilder.equal(LDAPUser.EMAIL, email));
-        List<LDAPUser> users = query.getResultList();
+        Set<UserFederationMapperModel> mapperModels = realm.getUserFederationMappers();
+        ldapQuery.addMappers(mapperModels);
 
-        if (users.isEmpty()) {
-            return null;
-        } else if (users.size() == 1) {
-            return users.get(0);
-        } else {
-            throw new ModelDuplicateException("Error - multiple users found with same email " + email);
-        }
+        return ldapQuery;
     }
 
-    public static boolean removeUser(LDAPIdentityStore ldapIdentityStore, String username) {
-        LDAPUser ldapUser = getUser(ldapIdentityStore, username);
-        if (ldapUser == null) {
-            return false;
+    // ldapUser has filled attributes, but doesn't have filled dn
+    public static void computeAndSetDn(LDAPConfig config, LDAPObject ldapObject) {
+        String rdnLdapAttrName = config.getRdnLdapAttribute();
+        String rdnLdapAttrValue = ldapObject.getAttributeAsString(rdnLdapAttrName);
+        if (rdnLdapAttrValue == null) {
+            throw new ModelException("RDN Attribute [" + rdnLdapAttrName + "] is not filled. Filled attributes: " + ldapObject.getAttributes());
         }
-        ldapIdentityStore.remove(ldapUser);
-        return true;
-    }
-
-    public static void removeAllUsers(LDAPIdentityStore ldapIdentityStore) {
-        List<LDAPUser> allUsers = getAllUsers(ldapIdentityStore);
 
-        for (LDAPUser user : allUsers) {
-            ldapIdentityStore.remove(user);
-        }
-    }
-
-    public static List<LDAPUser> getAllUsers(LDAPIdentityStore ldapIdentityStore) {
-        IdentityQuery<LDAPUser> userQuery = ldapIdentityStore.createQueryBuilder().createIdentityQuery(LDAPUser.class);
-        return userQuery.getResultList();
+        LDAPDn dn = LDAPDn.fromString(config.getSingleUserDn());
+        dn.addToHead(rdnLdapAttrName, rdnLdapAttrValue);
+        ldapObject.setDn(dn);
     }
 
-    // Needed for ActiveDirectory updates
-    private static String getFullName(String username, String firstName, String lastName) {
-        String fullName;
-        if (firstName != null && lastName != null) {
-            fullName = firstName + " " + lastName;
-        } else if (firstName != null && firstName.trim().length() > 0) {
-            fullName = firstName;
-        } else {
-            fullName = lastName;
-        }
-
-        // Fallback to loginName
-        if (fullName == null || fullName.trim().length() == 0) {
-            fullName = username;
-        }
-
-        return fullName;
+    public static String getUsername(LDAPObject ldapUser, LDAPConfig config) {
+        String usernameAttr = config.getUsernameLdapAttribute();
+        return ldapUser.getAttributeAsString(usernameAttr);
     }
 }
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/AbstractLDAPFederationMapper.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/AbstractLDAPFederationMapper.java
new file mode 100644
index 0000000..6daa011
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/AbstractLDAPFederationMapper.java
@@ -0,0 +1,19 @@
+package org.keycloak.federation.ldap.mappers;
+
+import org.keycloak.models.UserFederationMapperModel;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public abstract class AbstractLDAPFederationMapper implements LDAPFederationMapper {
+
+    @Override
+    public void close() {
+
+    }
+
+    protected boolean parseBooleanParameter(UserFederationMapperModel mapperModel, String paramName) {
+        String paramm = mapperModel.getConfig().get(paramName);
+        return Boolean.parseBoolean(paramm);
+    }
+}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/AbstractLDAPFederationMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/AbstractLDAPFederationMapperFactory.java
new file mode 100644
index 0000000..6b8f186
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/AbstractLDAPFederationMapperFactory.java
@@ -0,0 +1,25 @@
+package org.keycloak.federation.ldap.mappers;
+
+import org.keycloak.Config;
+import org.keycloak.mappers.UserFederationMapperFactory;
+import org.keycloak.models.KeycloakSessionFactory;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public abstract class AbstractLDAPFederationMapperFactory implements UserFederationMapperFactory {
+
+    @Override
+    public void init(Config.Scope config) {
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+    }
+
+    @Override
+    public void close() {
+    }
+
+
+}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/FullNameLDAPFederationMapper.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/FullNameLDAPFederationMapper.java
new file mode 100644
index 0000000..109b0b0
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/FullNameLDAPFederationMapper.java
@@ -0,0 +1,162 @@
+package org.keycloak.federation.ldap.mappers;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.keycloak.federation.ldap.LDAPFederationProvider;
+import org.keycloak.federation.ldap.LDAPUtils;
+import org.keycloak.federation.ldap.idm.model.LDAPObject;
+import org.keycloak.federation.ldap.idm.query.Condition;
+import org.keycloak.federation.ldap.idm.query.QueryParameter;
+import org.keycloak.federation.ldap.idm.query.internal.EqualCondition;
+import org.keycloak.federation.ldap.idm.query.internal.LDAPIdentityQuery;
+import org.keycloak.mappers.UserFederationMapper;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.LDAPConstants;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserFederationMapperModel;
+import org.keycloak.models.UserFederationProvider;
+import org.keycloak.models.UserModel;
+import org.keycloak.provider.ProviderConfigProperty;
+
+/**
+ * Mapper useful for the LDAP deployments when some attribute (usually CN) is mapped to full name of user
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class FullNameLDAPFederationMapper extends AbstractLDAPFederationMapper {
+
+    public static final String LDAP_FULL_NAME_ATTRIBUTE = "ldap.full.name.attribute";
+    public static final String READ_ONLY = "read.only";
+
+    @Override
+    public void onImportUserFromLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, UserModel user, RealmModel realm, boolean isCreate) {
+        String ldapFullNameAttrName = getLdapFullNameAttrName(mapperModel);
+        String fullName = ldapUser.getAttributeAsString(ldapFullNameAttrName);
+        fullName = fullName.trim();
+        if (fullName != null) {
+            int lastSpaceIndex = fullName.lastIndexOf(" ");
+            if (lastSpaceIndex == -1) {
+                user.setLastName(fullName);
+            } else {
+                user.setFirstName(fullName.substring(0, lastSpaceIndex));
+                user.setLastName(fullName.substring(lastSpaceIndex + 1));
+            }
+        }
+    }
+
+    @Override
+    public void onRegisterUserToLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, UserModel localUser, RealmModel realm) {
+        String ldapFullNameAttrName = getLdapFullNameAttrName(mapperModel);
+        String fullName = getFullName(localUser.getFirstName(), localUser.getLastName());
+        ldapUser.setAttribute(ldapFullNameAttrName, fullName);
+
+        if (isReadOnly(mapperModel)) {
+            ldapUser.addReadOnlyAttributeName(ldapFullNameAttrName);
+        }
+    }
+
+    @Override
+    public UserModel proxy(final UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, UserModel delegate, RealmModel realm) {
+        if (ldapProvider.getEditMode() == UserFederationProvider.EditMode.WRITABLE && !isReadOnly(mapperModel)) {
+
+
+            TxAwareLDAPUserModelDelegate txDelegate = new TxAwareLDAPUserModelDelegate(delegate, ldapProvider, ldapUser) {
+
+                @Override
+                public void setFirstName(String firstName) {
+                    super.setFirstName(firstName);
+                    setFullNameToLDAPObject();
+                }
+
+                @Override
+                public void setLastName(String lastName) {
+                    super.setLastName(lastName);
+                    setFullNameToLDAPObject();
+                }
+
+                private void setFullNameToLDAPObject() {
+                    String fullName = getFullName(getFirstName(), getLastName());
+                    if (logger.isTraceEnabled()) {
+                        logger.tracef("Pushing full name attribute to LDAP. Full name: %s", fullName);
+                    }
+
+                    ensureTransactionStarted();
+
+                    String ldapFullNameAttrName = getLdapFullNameAttrName(mapperModel);
+                    ldapUser.setAttribute(ldapFullNameAttrName, fullName);
+                }
+
+            };
+
+            return txDelegate;
+        } else {
+            return delegate;
+        }
+    }
+
+    @Override
+    public void beforeLDAPQuery(UserFederationMapperModel mapperModel, LDAPIdentityQuery query) {
+        String ldapFullNameAttrName = getLdapFullNameAttrName(mapperModel);
+        query.addReturningLdapAttribute(ldapFullNameAttrName);
+
+        // Change conditions and compute condition for fullName from the conditions for firstName and lastName. Right now just "equal" condition is supported
+        EqualCondition firstNameCondition = null;
+        EqualCondition lastNameCondition = null;
+        Set<Condition> conditionsCopy = new HashSet<Condition>(query.getConditions());
+        for (Condition condition : conditionsCopy) {
+            QueryParameter param = condition.getParameter();
+            if (param != null) {
+                if (param.getName().equals(UserModel.FIRST_NAME)) {
+                    firstNameCondition = (EqualCondition) condition;
+                    query.getConditions().remove(condition);
+                } else if (param.getName().equals(UserModel.LAST_NAME)) {
+                    lastNameCondition = (EqualCondition) condition;
+                    query.getConditions().remove(condition);
+                } else if (param.getName().equals(LDAPConstants.GIVENNAME)) {
+                    // Some previous mapper already converted it to LDAP name
+                    firstNameCondition = (EqualCondition) condition;
+                } else if (param.getName().equals(LDAPConstants.SN)) {
+                    // Some previous mapper already converted it to LDAP name
+                    lastNameCondition = (EqualCondition) condition;
+                }
+            }
+        }
+
+
+        String fullName = null;
+        if (firstNameCondition != null && lastNameCondition != null) {
+            fullName = firstNameCondition.getValue() + " " + lastNameCondition.getValue();
+        } else if (firstNameCondition != null) {
+            fullName = (String) firstNameCondition.getValue();
+        } else if (firstNameCondition != null) {
+            fullName = (String) lastNameCondition.getValue();
+        } else {
+            return;
+        }
+        EqualCondition fullNameCondition = new EqualCondition(new QueryParameter(ldapFullNameAttrName), fullName);
+        query.getConditions().add(fullNameCondition);
+    }
+
+    protected String getLdapFullNameAttrName(UserFederationMapperModel mapperModel) {
+        String ldapFullNameAttrName = mapperModel.getConfig().get(LDAP_FULL_NAME_ATTRIBUTE);
+        return ldapFullNameAttrName == null ? LDAPConstants.CN : ldapFullNameAttrName;
+    }
+
+    protected String getFullName(String firstName, String lastName) {
+        if (firstName != null && lastName != null) {
+            return firstName + " " + lastName;
+        } else if (firstName != null) {
+            return firstName;
+        } else if (lastName != null) {
+            return lastName;
+        } else {
+            return LDAPConstants.EMPTY_ATTRIBUTE_VALUE;
+        }
+    }
+
+    private boolean isReadOnly(UserFederationMapperModel mapperModel) {
+        return parseBooleanParameter(mapperModel, READ_ONLY);
+    }
+}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/FullNameLDAPFederationMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/FullNameLDAPFederationMapperFactory.java
new file mode 100644
index 0000000..6ef6979
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/FullNameLDAPFederationMapperFactory.java
@@ -0,0 +1,35 @@
+package org.keycloak.federation.ldap.mappers;
+
+import java.util.List;
+
+import org.keycloak.mappers.UserFederationMapper;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.provider.ProviderConfigProperty;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class FullNameLDAPFederationMapperFactory extends AbstractLDAPFederationMapperFactory {
+
+    public static final String ID =  "full-name-ldap-mapper";
+
+    @Override
+    public String getHelpText() {
+        return "Some help text - full name mapper - TODO";
+    }
+
+    @Override
+    public List<ProviderConfigProperty> getConfigProperties() {
+        return null;
+    }
+
+    @Override
+    public String getId() {
+        return ID;
+    }
+
+    @Override
+    public UserFederationMapper create(KeycloakSession session) {
+        return new FullNameLDAPFederationMapper();
+    }
+}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/LDAPFederationMapper.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/LDAPFederationMapper.java
new file mode 100644
index 0000000..4676a6b
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/LDAPFederationMapper.java
@@ -0,0 +1,62 @@
+package org.keycloak.federation.ldap.mappers;
+
+import org.keycloak.federation.ldap.LDAPFederationProvider;
+import org.keycloak.federation.ldap.idm.model.LDAPObject;
+import org.keycloak.federation.ldap.idm.query.internal.LDAPIdentityQuery;
+import org.keycloak.models.RealmModel;
+import org.keycloak.mappers.UserFederationMapper;
+import org.keycloak.models.UserFederationMapperModel;
+import org.keycloak.models.UserModel;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public interface LDAPFederationMapper extends UserFederationMapper {
+
+
+    /**
+     * Called when importing user from LDAP to local keycloak DB.
+     *
+     * @param mapperModel
+     * @param ldapProvider
+     * @param ldapUser
+     * @param user
+     * @param realm
+     * @param isCreate true if we importing new user from LDAP. False if user already exists in Keycloak, but we are upgrading (syncing) it from LDAP
+     */
+    void onImportUserFromLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, UserModel user, RealmModel realm, boolean isCreate);
+
+
+    /**
+     * Called when register new user to LDAP - just after user was created in Keycloak DB
+     *
+     * @param mapperModel
+     * @param ldapProvider
+     * @param ldapUser
+     * @param localUser
+     * @param realm
+     */
+    void onRegisterUserToLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, UserModel localUser, RealmModel realm);
+
+
+    /**
+     * Called when invoke proxy on LDAP federation provider
+     *
+     * @param mapperModel
+     * @param ldapProvider
+     * @param ldapUser
+     * @param delegate
+     * @param realm
+     * @return
+     */
+    UserModel proxy(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, UserModel delegate, RealmModel realm);
+
+
+    /**
+     * Called before LDAP Identity query for retrieve LDAP users was executed. It allows to change query somehow (add returning attributes from LDAP, change conditions etc)
+     *
+     * @param mapperModel
+     * @param query
+     */
+    void beforeLDAPQuery(UserFederationMapperModel mapperModel, LDAPIdentityQuery query);
+}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/RoleLDAPFederationMapper.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/RoleLDAPFederationMapper.java
new file mode 100644
index 0000000..47f288d
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/RoleLDAPFederationMapper.java
@@ -0,0 +1,473 @@
+package org.keycloak.federation.ldap.mappers;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+
+import javax.naming.directory.SearchControls;
+
+import org.jboss.logging.Logger;
+import org.keycloak.federation.ldap.LDAPFederationProvider;
+import org.keycloak.federation.ldap.idm.model.LDAPDn;
+import org.keycloak.federation.ldap.idm.model.LDAPObject;
+import org.keycloak.federation.ldap.idm.query.Condition;
+import org.keycloak.federation.ldap.idm.query.QueryParameter;
+import org.keycloak.federation.ldap.idm.query.internal.LDAPIdentityQuery;
+import org.keycloak.federation.ldap.idm.query.internal.LDAPQueryConditionsBuilder;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.LDAPConstants;
+import org.keycloak.models.ModelException;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleContainerModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserFederationMapperModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.models.utils.UserModelDelegate;
+
+/**
+ * Map realm roles or roles of particular client to LDAP roles
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper {
+
+    private static final Logger logger = Logger.getLogger(RoleLDAPFederationMapper.class);
+
+    // LDAP DN where are roles of this tree saved.
+    public static final String ROLES_DN = "roles.dn";
+
+    // Name of LDAP attribute, which is used in role objects for name and RDN of role. Usually it will be "cn"
+    public static final String ROLE_NAME_LDAP_ATTRIBUTE = "role.name.ldap.attribute";
+
+    // Name of LDAP attribute on role, which is used for membership mappings. Usually it will be "member"
+    public static final String MEMBERSHIP_LDAP_ATTRIBUTE = "membership.ldap.attribute";
+
+    // Object classes of the role object.
+    public static final String ROLE_OBJECT_CLASSES = "role.object.classes";
+
+    // Boolean option. If true, we will map LDAP roles to realm roles. If false, we will map to client roles (client specified by option CLIENT_ID)
+    public static final String USE_REALM_ROLES_MAPPING = "use.realm.roles.mapping";
+
+    // ClientId, which we want to map roles. Applicable just if "USE_REALM_ROLES_MAPPING" is false
+    public static final String CLIENT_ID = "client.id";
+
+    // See docs for Mode enum
+    public static final String MODE = "mode";
+
+
+    // List of IDs of UserFederationMapperModels where syncRolesFromLDAP was already called in this KeycloakSession. This is to improve performance
+    // TODO: Rather address this with caching at LDAPIdentityStore level?
+    private Set<String> rolesSyncedModels = new TreeSet<String>();
+
+    @Override
+    public void onImportUserFromLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, UserModel user, RealmModel realm, boolean isCreate) {
+        syncRolesFromLDAP(mapperModel, ldapProvider, realm);
+
+        Mode mode = getMode(mapperModel);
+
+        // For now, import LDAP role mappings just during create
+        if (mode == Mode.IMPORT && isCreate) {
+
+            List<LDAPObject> ldapRoles = getLDAPRoleMappings(mapperModel, ldapProvider, ldapUser);
+
+            // Import role mappings from LDAP into Keycloak DB
+            String roleNameAttr = getRoleNameLdapAttribute(mapperModel);
+            for (LDAPObject ldapRole : ldapRoles) {
+                String roleName = ldapRole.getAttributeAsString(roleNameAttr);
+
+                RoleContainerModel roleContainer = getTargetRoleContainer(mapperModel, realm);
+                RoleModel role = roleContainer.getRole(roleName);
+
+                // TODO: debug
+                logger.infof("Granting role [%s] to user [%s] during import from LDAP", roleName, user.getUsername());
+                user.grantRole(role);
+            }
+        }
+    }
+
+    @Override
+    public void onRegisterUserToLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, UserModel localUser, RealmModel realm) {
+        syncRolesFromLDAP(mapperModel, ldapProvider, realm);
+    }
+
+    // Sync roles from LDAP tree and create them in local Keycloak DB (if they don't exist here yet)
+    protected void syncRolesFromLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, RealmModel realm) {
+        if (!rolesSyncedModels.contains(mapperModel.getId())) {
+            LDAPIdentityQuery ldapQuery = createRoleQuery(mapperModel, ldapProvider);
+
+            // Send query
+            List<LDAPObject> ldapRoles = ldapQuery.getResultList();
+
+            RoleContainerModel roleContainer = getTargetRoleContainer(mapperModel, realm);
+            String rolesRdnAttr = getRoleNameLdapAttribute(mapperModel);
+            for (LDAPObject ldapRole : ldapRoles) {
+                String roleName = ldapRole.getAttributeAsString(rolesRdnAttr);
+
+                if (roleContainer.getRole(roleName) == null) {
+                    // TODO: rather change to debug
+                    logger.infof("Syncing role [%s] from LDAP to keycloak DB", roleName);
+                    roleContainer.addRole(roleName);
+                }
+            }
+
+            rolesSyncedModels.add(mapperModel.getId());
+        }
+    }
+
+    public LDAPIdentityQuery createRoleQuery(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider) {
+        LDAPIdentityQuery ldapQuery = new LDAPIdentityQuery(ldapProvider);
+        ldapQuery.setSearchScope(SearchControls.ONELEVEL_SCOPE);
+
+        String rolesDn = getRolesDn(mapperModel);
+        ldapQuery.addSearchDns(Arrays.asList(rolesDn));
+
+        Collection<String> roleObjectClasses = getRoleObjectClasses(mapperModel);
+        ldapQuery.addObjectClasses(roleObjectClasses);
+
+        String rolesRdnAttr = getRoleNameLdapAttribute(mapperModel);
+        String membershipAttr = getMembershipLdapAttribute(mapperModel);
+        ldapQuery.addReturningLdapAttribute(rolesRdnAttr);
+        ldapQuery.addReturningLdapAttribute(membershipAttr);
+
+        return ldapQuery;
+    }
+
+    protected RoleContainerModel getTargetRoleContainer(UserFederationMapperModel mapperModel, RealmModel realm) {
+        boolean realmRolesMapping = parseBooleanParameter(mapperModel, USE_REALM_ROLES_MAPPING);
+        if (realmRolesMapping) {
+            return realm;
+        } else {
+            String clientId = mapperModel.getConfig().get(CLIENT_ID);
+            if (clientId == null) {
+                throw new IllegalStateException("Using client roles mapping is requested, but parameter client.id not found!");
+            }
+            ClientModel client = realm.getClientByClientId(clientId);
+            if (client == null) {
+                throw new IllegalStateException("Can't found requested client with clientId: " + clientId);
+            }
+            return client;
+        }
+    }
+
+    protected String getRolesDn(UserFederationMapperModel mapperModel) {
+        String rolesDn = mapperModel.getConfig().get(ROLES_DN);
+        if (rolesDn == null) {
+            throw new IllegalStateException("Roles DN is null! Check your configuration");
+        }
+        return rolesDn;
+    }
+
+    protected String getRoleNameLdapAttribute(UserFederationMapperModel mapperModel) {
+        String rolesRdnAttr = mapperModel.getConfig().get(ROLE_NAME_LDAP_ATTRIBUTE);
+        return rolesRdnAttr!=null ? rolesRdnAttr : LDAPConstants.CN;
+    }
+
+    protected String getMembershipLdapAttribute(UserFederationMapperModel mapperModel) {
+        String membershipAttrName = mapperModel.getConfig().get(MEMBERSHIP_LDAP_ATTRIBUTE);
+        return membershipAttrName!=null ? membershipAttrName : LDAPConstants.MEMBER;
+    }
+
+    protected Collection<String> getRoleObjectClasses(UserFederationMapperModel mapperModel) {
+        String objectClasses = mapperModel.getConfig().get(ROLE_OBJECT_CLASSES);
+        if (objectClasses == null) {
+            objectClasses = "groupOfNames";
+        }
+        String[] objClasses = objectClasses.split(",");
+
+        // TODO: util method for trim and convert array to collection?
+        Set<String> trimmed = new HashSet<String>();
+        for (String objectClass : objClasses) {
+            objectClass = objectClass.trim();
+            if (objectClass.length() > 0) {
+                trimmed.add(objectClass);
+            }
+        }
+        return trimmed;
+    }
+
+    private Mode getMode(UserFederationMapperModel mapperModel) {
+        String modeString = mapperModel.getConfig().get(MODE);
+        if (modeString == null || modeString.trim().length() == 0) {
+            return Mode.LDAP_ONLY;
+        }
+
+        return Enum.valueOf(Mode.class, modeString.toUpperCase());
+    }
+
+    protected LDAPObject createLDAPRole(UserFederationMapperModel mapperModel, String roleName, LDAPFederationProvider ldapProvider) {
+        LDAPObject ldapObject = new LDAPObject();
+        String roleNameAttribute = getRoleNameLdapAttribute(mapperModel);
+        ldapObject.setRdnAttributeName(roleNameAttribute);
+        ldapObject.setObjectClasses(getRoleObjectClasses(mapperModel));
+        ldapObject.setAttribute(roleNameAttribute, roleName);
+
+        LDAPDn roleDn = LDAPDn.fromString(getRolesDn(mapperModel));
+        roleDn.addToHead(roleNameAttribute, roleName);
+        ldapObject.setDn(roleDn);
+
+        // TODO: debug
+        logger.infof("Creating role to [%s] to LDAP with DN [%s]", roleName, roleDn.toString());
+        ldapProvider.getLdapIdentityStore().add(ldapObject);
+        return ldapObject;
+    }
+
+    public void addRoleMappingInLDAP(UserFederationMapperModel mapperModel, String roleName, LDAPFederationProvider ldapProvider, LDAPObject ldapUser) {
+        LDAPObject ldapRole = loadLDAPRoleByName(mapperModel, ldapProvider, roleName);
+        if (ldapRole == null) {
+            ldapRole = createLDAPRole(mapperModel, roleName, ldapProvider);
+        }
+
+        Set<String> memberships = getExistingMemberships(mapperModel, ldapRole);
+        memberships.add(ldapUser.getDn().toString());
+        ldapRole.setAttribute(getMembershipLdapAttribute(mapperModel), memberships);
+
+        ldapProvider.getLdapIdentityStore().update(ldapRole);
+    }
+
+    public void deleteRoleMappingInLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, LDAPObject ldapRole) {
+        Set<String> memberships = getExistingMemberships(mapperModel, ldapRole);
+        memberships.remove(ldapUser.getDn().toString());
+
+        // Some membership placeholder needs to be always here as "member" is mandatory attribute on some LDAP servers
+        if (memberships.size() == 0) {
+            memberships.add(LDAPConstants.EMPTY_ATTRIBUTE_VALUE);
+        }
+
+        ldapRole.setAttribute(getMembershipLdapAttribute(mapperModel), memberships);
+        ldapProvider.getLdapIdentityStore().update(ldapRole);
+    }
+
+    public LDAPObject loadLDAPRoleByName(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, String roleName) {
+        LDAPIdentityQuery ldapQuery = createRoleQuery(mapperModel, ldapProvider);
+        Condition roleNameCondition = new LDAPQueryConditionsBuilder().equal(new QueryParameter(getRoleNameLdapAttribute(mapperModel)), roleName);
+        ldapQuery.where(roleNameCondition);
+        return ldapQuery.getFirstResult();
+    }
+
+    protected Set<String> getExistingMemberships(UserFederationMapperModel mapperModel, LDAPObject ldapRole) {
+        String memberAttrName = getMembershipLdapAttribute(mapperModel);
+        Set<String> memberships = new TreeSet<String>();
+        Object existingMemberships = ldapRole.getAttribute(memberAttrName);
+
+        if (existingMemberships != null) {
+            if (existingMemberships instanceof String) {
+                String existingMembership = existingMemberships.toString().trim();
+                if (existingMemberships != null && existingMembership.length() > 0) {
+                    memberships.add(existingMembership);
+                }
+            } else if (existingMemberships instanceof Collection) {
+                Collection<String> exMemberships = (Collection<String>) existingMemberships;
+                for (String membership : exMemberships) {
+                    if (membership.trim().length() > 0) {
+                        memberships.add(membership);
+                    }
+                }
+            }
+        }
+        return memberships;
+    }
+
+    protected List<LDAPObject> getLDAPRoleMappings(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser) {
+        LDAPIdentityQuery ldapQuery = createRoleQuery(mapperModel, ldapProvider);
+        String membershipAttr = getMembershipLdapAttribute(mapperModel);
+        Condition membershipCondition = new LDAPQueryConditionsBuilder().equal(new QueryParameter(membershipAttr), ldapUser.getDn().toString());
+        ldapQuery.where(membershipCondition);
+        return ldapQuery.getResultList();
+    }
+
+    protected Set<RoleModel> getLDAPRoleMappingsConverted(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, RoleContainerModel roleContainer) {
+        List<LDAPObject> ldapRoles = getLDAPRoleMappings(mapperModel, ldapProvider, ldapUser);
+
+        Set<RoleModel> roles = new HashSet<RoleModel>();
+        String roleNameLdapAttr = getRoleNameLdapAttribute(mapperModel);
+        for (LDAPObject role : ldapRoles) {
+            String roleName = role.getAttributeAsString(roleNameLdapAttr);
+            RoleModel modelRole = roleContainer.getRole(roleName);
+            if (modelRole == null) {
+                // Add role to local DB
+                modelRole = roleContainer.addRole(roleName);
+            }
+            roles.add(modelRole);
+        }
+        return roles;
+    }
+
+    @Override
+    public UserModel proxy(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, UserModel delegate, RealmModel realm) {
+        final Mode mode = getMode(mapperModel);
+
+        // For IMPORT mode, all operations are performed against local DB
+        if (mode == Mode.IMPORT) {
+            return delegate;
+        } else {
+            return new LDAPRoleMappingsUserDelegate(delegate, mapperModel, ldapProvider, ldapUser, realm, mode);
+        }
+    }
+
+    @Override
+    public void beforeLDAPQuery(UserFederationMapperModel mapperModel, LDAPIdentityQuery query) {
+    }
+
+
+
+    public class LDAPRoleMappingsUserDelegate extends UserModelDelegate {
+
+        private final UserFederationMapperModel mapperModel;
+        private final LDAPFederationProvider ldapProvider;
+        private final LDAPObject ldapUser;
+        private final RealmModel realm;
+        private final Mode mode;
+
+        public LDAPRoleMappingsUserDelegate(UserModel user, UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser,
+                                            RealmModel realm, Mode mode) {
+            super(user);
+            this.mapperModel = mapperModel;
+            this.ldapProvider = ldapProvider;
+            this.ldapUser = ldapUser;
+            this.realm = realm;
+            this.mode = mode;
+        }
+
+        @Override
+        public Set<RoleModel> getRealmRoleMappings() {
+            RoleContainerModel roleContainer = getTargetRoleContainer(mapperModel, realm);
+            if (roleContainer.equals(realm)) {
+                Set<RoleModel> ldapRoleMappings = getLDAPRoleMappingsConverted(mapperModel, ldapProvider, ldapUser, roleContainer);
+
+                if (mode == Mode.LDAP_ONLY) {
+                    // Use just role mappings from LDAP
+                    return ldapRoleMappings;
+                } else {
+                    // Merge mappings from both DB and LDAP
+                    Set<RoleModel> modelRoleMappings = super.getRealmRoleMappings();
+                    ldapRoleMappings.addAll(modelRoleMappings);
+                    return ldapRoleMappings;
+                }
+            } else {
+                return super.getRealmRoleMappings();
+            }
+        }
+
+        @Override
+        public Set<RoleModel> getClientRoleMappings(ClientModel client) {
+            RoleContainerModel roleContainer = getTargetRoleContainer(mapperModel, realm);
+            if (roleContainer.equals(client)) {
+                Set<RoleModel> ldapRoleMappings = getLDAPRoleMappingsConverted(mapperModel, ldapProvider, ldapUser, roleContainer);
+
+                if (mode == Mode.LDAP_ONLY) {
+                    // Use just role mappings from LDAP
+                    return ldapRoleMappings;
+                } else {
+                    // Merge mappings from both DB and LDAP
+                    Set<RoleModel> modelRoleMappings = super.getClientRoleMappings(client);
+                    ldapRoleMappings.addAll(modelRoleMappings);
+                    return ldapRoleMappings;
+                }
+            } else {
+                return super.getClientRoleMappings(client);
+            }
+        }
+
+        @Override
+        public boolean hasRole(RoleModel role) {
+            Set<RoleModel> roles = getRoleMappings();
+            return KeycloakModelUtils.hasRole(roles, role);
+        }
+
+        @Override
+        public void grantRole(RoleModel role) {
+            if (mode == Mode.LDAP_ONLY) {
+                RoleContainerModel roleContainer = getTargetRoleContainer(mapperModel, realm);
+
+                if (role.getContainer().equals(roleContainer)) {
+
+                    // We need to create new role mappings in LDAP
+                    addRoleMappingInLDAP(mapperModel, role.getName(), ldapProvider, ldapUser);
+                } else {
+                    super.grantRole(role);
+                }
+            } else {
+                super.grantRole(role);
+            }
+        }
+
+        @Override
+        public Set<RoleModel> getRoleMappings() {
+            Set<RoleModel> modelRoleMappings = super.getRoleMappings();
+
+            RoleContainerModel targetRoleContainer = getTargetRoleContainer(mapperModel, realm);
+            Set<RoleModel> ldapRoleMappings = getLDAPRoleMappingsConverted(mapperModel, ldapProvider, ldapUser, targetRoleContainer);
+
+            if (mode == Mode.LDAP_ONLY) {
+                // For LDAP-only we want to retrieve role mappings of target container just from LDAP
+                Set<RoleModel> modelRolesCopy = new HashSet<RoleModel>(modelRoleMappings);
+                for (RoleModel role : modelRolesCopy) {
+                    if (role.getContainer().equals(targetRoleContainer)) {
+                        modelRoleMappings.remove(role);
+                    }
+                }
+            }
+
+            modelRoleMappings.addAll(ldapRoleMappings);
+            return modelRoleMappings;
+        }
+
+        @Override
+        public void deleteRoleMapping(RoleModel role) {
+            RoleContainerModel roleContainer = getTargetRoleContainer(mapperModel, realm);
+            if (role.getContainer().equals(roleContainer)) {
+
+                LDAPIdentityQuery ldapQuery = createRoleQuery(mapperModel, ldapProvider);
+                LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder();
+                Condition roleNameCondition = conditionsBuilder.equal(new QueryParameter(getRoleNameLdapAttribute(mapperModel)), role.getName());
+                Condition membershipCondition = conditionsBuilder.equal(new QueryParameter(getMembershipLdapAttribute(mapperModel)), ldapUser.getDn().toString());
+                ldapQuery.where(roleNameCondition).where(membershipCondition);
+                LDAPObject ldapRole = ldapQuery.getFirstResult();
+
+                if (ldapRole == null) {
+                    // Role mapping doesn't exist in LDAP. For LDAP_ONLY mode, we don't need to do anything. For READ_ONLY, delete it in local DB.
+                    if (mode == Mode.READ_ONLY) {
+                        super.deleteRoleMapping(role);
+                    }
+                } else {
+                    // Role mappings exists in LDAP. For LDAP_ONLY mode, we can just delete it in LDAP. For READ_ONLY we can't delete it -> throw error
+                    if (mode == Mode.READ_ONLY) {
+                        throw new ModelException("Not possible to delete LDAP role mappings as mapper mode is READ_ONLY");
+                    } else {
+                        // Delete ldap role mappings
+                        deleteRoleMappingInLDAP(mapperModel, ldapProvider, ldapUser, ldapRole);
+                    }
+                }
+            } else {
+                super.deleteRoleMapping(role);
+            }
+        }
+    }
+
+    public enum Mode {
+        /**
+         * All role mappings are retrieved from LDAP and saved into LDAP
+         */
+        LDAP_ONLY,
+
+        /**
+         * Read-only LDAP mode. Role mappings are retrieved from LDAP for particular user just at the time when he is imported and then
+         * they are saved to local keycloak DB. Then all role mappings are always retrieved from keycloak DB, never from LDAP.
+         * Creating or deleting of role mapping is propagated only to DB.
+         *
+         * This is read-only mode LDAP mode and it's good for performance, but when user is put to some role directly in LDAP, it
+         * won't be seen by Keycloak
+         */
+        IMPORT,
+
+        /**
+         * Read-only LDAP mode. Role mappings are retrieved from both LDAP and DB and merged together. New role grants are not saved to LDAP but to DB.
+         * Deleting role mappings, which is mapped to LDAP, will throw an error.
+         */
+        READ_ONLY
+    }
+}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/RoleLDAPFederationMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/RoleLDAPFederationMapperFactory.java
new file mode 100644
index 0000000..cbae850
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/RoleLDAPFederationMapperFactory.java
@@ -0,0 +1,35 @@
+package org.keycloak.federation.ldap.mappers;
+
+import java.util.List;
+
+import org.keycloak.mappers.UserFederationMapper;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.provider.ProviderConfigProperty;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class RoleLDAPFederationMapperFactory extends AbstractLDAPFederationMapperFactory {
+
+    public static final String ID = "role-ldap-mapper";
+
+    @Override
+    public String getHelpText() {
+        return "Some help text - role mapper - TODO";
+    }
+
+    @Override
+    public List<ProviderConfigProperty> getConfigProperties() {
+        return null;
+    }
+
+    @Override
+    public String getId() {
+        return ID ;
+    }
+
+    @Override
+    public UserFederationMapper create(KeycloakSession session) {
+        return new RoleLDAPFederationMapper();
+    }
+}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/TxAwareLDAPUserModelDelegate.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/TxAwareLDAPUserModelDelegate.java
new file mode 100644
index 0000000..ebdba95
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/TxAwareLDAPUserModelDelegate.java
@@ -0,0 +1,119 @@
+package org.keycloak.federation.ldap.mappers;
+
+import org.jboss.logging.Logger;
+import org.keycloak.federation.ldap.LDAPFederationProvider;
+import org.keycloak.federation.ldap.idm.model.LDAPObject;
+import org.keycloak.models.KeycloakTransaction;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.UserModelDelegate;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public abstract class TxAwareLDAPUserModelDelegate extends UserModelDelegate {
+
+    public static final Logger logger = Logger.getLogger(TxAwareLDAPUserModelDelegate.class);
+
+    protected LDAPFederationProvider provider;
+    protected LDAPObject ldapUser;
+    private final LDAPTransaction transaction;
+
+    public TxAwareLDAPUserModelDelegate(UserModel delegate, LDAPFederationProvider provider, LDAPObject ldapUser) {
+        super(delegate);
+        this.provider = provider;
+        this.ldapUser = ldapUser;
+        this.transaction = findOrCreateTransaction();
+    }
+
+    public LDAPTransaction getTransaction() {
+        return transaction;
+    }
+
+    // Try to find transaction in any delegate. We want to enlist just single transaction per all delegates
+    private LDAPTransaction findOrCreateTransaction() {
+        UserModelDelegate delegate = this;
+        while (true) {
+            UserModel deleg = delegate.getDelegate();
+            if (!(deleg instanceof UserModelDelegate)) {
+                return new LDAPTransaction();
+            } else {
+                delegate = (UserModelDelegate) deleg;
+            }
+
+            if (delegate instanceof TxAwareLDAPUserModelDelegate) {
+                TxAwareLDAPUserModelDelegate txDelegate = (TxAwareLDAPUserModelDelegate) delegate;
+                return txDelegate.getTransaction();
+            }
+        }
+    }
+
+    protected void ensureTransactionStarted() {
+        if (transaction.state == TransactionState.NOT_STARTED) {
+            if (logger.isTraceEnabled()) {
+                logger.trace("Starting and enlisting transaction for object " + ldapUser.getDn().toString());
+            }
+
+            this.provider.getSession().getTransaction().enlistAfterCompletion(transaction);
+        }
+    }
+
+
+
+    protected class LDAPTransaction implements KeycloakTransaction {
+
+        protected TransactionState state = TransactionState.NOT_STARTED;
+
+        @Override
+        public void begin() {
+            if (state != TransactionState.NOT_STARTED) {
+                throw new IllegalStateException("Transaction already started");
+            }
+
+            state = TransactionState.STARTED;
+        }
+
+        @Override
+        public void commit() {
+            if (state != TransactionState.STARTED) {
+                throw new IllegalStateException("Transaction in illegal state for commit: " + state);
+            }
+
+            if (logger.isTraceEnabled()) {
+                logger.trace("Transaction commit! Updating LDAP attributes for object " + ldapUser.getDn().toString() + ", attributes: " + ldapUser.getAttributes());
+            }
+
+            provider.getLdapIdentityStore().update(ldapUser);
+            state = TransactionState.FINISHED;
+        }
+
+        @Override
+        public void rollback() {
+            if (state != TransactionState.STARTED && state != TransactionState.ROLLBACK_ONLY) {
+                throw new IllegalStateException("Transaction in illegal state for rollback: " + state);
+            }
+
+            logger.warn("Transaction rollback! Ignoring LDAP updates for object " + ldapUser.getDn().toString());
+            state = TransactionState.FINISHED;
+        }
+
+        @Override
+        public void setRollbackOnly() {
+            state = TransactionState.ROLLBACK_ONLY;
+        }
+
+        @Override
+        public boolean getRollbackOnly() {
+            return state == TransactionState.ROLLBACK_ONLY;
+        }
+
+        @Override
+        public boolean isActive() {
+            return state == TransactionState.STARTED || state == TransactionState.ROLLBACK_ONLY;
+        }
+    }
+
+    protected enum TransactionState {
+        NOT_STARTED, STARTED, ROLLBACK_ONLY, FINISHED
+    }
+
+}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserAttributeLDAPFederationMapper.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserAttributeLDAPFederationMapper.java
new file mode 100644
index 0000000..c4c3029
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserAttributeLDAPFederationMapper.java
@@ -0,0 +1,169 @@
+package org.keycloak.federation.ldap.mappers;
+
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.Map;
+
+import org.keycloak.federation.ldap.LDAPFederationProvider;
+import org.keycloak.federation.ldap.LDAPUtils;
+import org.keycloak.federation.ldap.idm.model.LDAPObject;
+import org.keycloak.federation.ldap.idm.query.Condition;
+import org.keycloak.federation.ldap.idm.query.QueryParameter;
+import org.keycloak.federation.ldap.idm.query.internal.LDAPIdentityQuery;
+import org.keycloak.mappers.UserFederationMapper;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserFederationMapperModel;
+import org.keycloak.models.UserFederationProvider;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.reflection.Property;
+import org.keycloak.models.utils.reflection.PropertyCriteria;
+import org.keycloak.models.utils.reflection.PropertyQueries;
+import org.keycloak.provider.ProviderConfigProperty;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class UserAttributeLDAPFederationMapper extends AbstractLDAPFederationMapper {
+
+    private static final Map<String, Property<Object>> userModelProperties;
+
+    static {
+        userModelProperties = PropertyQueries.createQuery(UserModel.class).addCriteria(new PropertyCriteria() {
+
+            @Override
+            public boolean methodMatches(Method m) {
+                if ((m.getName().startsWith("get") || m.getName().startsWith("is")) && m.getParameterTypes().length > 0) {
+                    return false;
+                }
+
+                return true;
+            }
+
+        }).getResultList();
+    }
+
+    public static final String USER_MODEL_ATTRIBUTE = "user.model.attribute";
+    public static final String LDAP_ATTRIBUTE = "ldap.attribute";
+    public static final String READ_ONLY = "read.only";
+
+
+    @Override
+    public void onImportUserFromLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, UserModel user, RealmModel realm, boolean isCreate) {
+        String userModelAttrName = mapperModel.getConfig().get(USER_MODEL_ATTRIBUTE);
+        String ldapAttrName = mapperModel.getConfig().get(LDAP_ATTRIBUTE);
+
+        Object ldapAttrValue = ldapUser.getAttribute(ldapAttrName);
+        if (ldapAttrValue != null) {
+            Property<Object> userModelProperty = userModelProperties.get(userModelAttrName);
+
+            if (userModelProperty != null) {
+                // we have java property on UserModel
+                userModelProperty.setValue(user, ldapAttrValue);
+            } else {
+                // we don't have java property. Let's just setAttribute
+                user.setAttribute(userModelAttrName, (String) ldapAttrValue);
+            }
+        }
+    }
+
+    @Override
+    public void onRegisterUserToLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, UserModel localUser, RealmModel realm) {
+        String userModelAttrName = mapperModel.getConfig().get(USER_MODEL_ATTRIBUTE);
+        String ldapAttrName = mapperModel.getConfig().get(LDAP_ATTRIBUTE);
+
+        Property<Object> userModelProperty = userModelProperties.get(userModelAttrName);
+
+        Object attrValue;
+        if (userModelProperty != null) {
+            // we have java property on UserModel
+            attrValue = userModelProperty.getValue(localUser);
+        } else {
+            // we don't have java property. Let's just setAttribute
+            attrValue = localUser.getAttribute(userModelAttrName);
+        }
+
+        ldapUser.setAttribute(ldapAttrName, attrValue);
+        if (isReadOnly(mapperModel)) {
+            ldapUser.addReadOnlyAttributeName(ldapAttrName);
+        }
+    }
+
+    @Override
+    public UserModel proxy(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, UserModel delegate, RealmModel realm) {
+        if (ldapProvider.getEditMode() == UserFederationProvider.EditMode.WRITABLE && !isReadOnly(mapperModel)) {
+
+            final String userModelAttrName = mapperModel.getConfig().get(USER_MODEL_ATTRIBUTE);
+            final String ldapAttrName = mapperModel.getConfig().get(LDAP_ATTRIBUTE);
+
+            TxAwareLDAPUserModelDelegate txDelegate = new TxAwareLDAPUserModelDelegate(delegate, ldapProvider, ldapUser) {
+
+                @Override
+                public void setAttribute(String name, String value) {
+                    setLDAPAttribute(name, value);
+                    super.setAttribute(name, value);
+                }
+
+                @Override
+                public void setEmail(String email) {
+                    setLDAPAttribute(UserModel.EMAIL, email);
+                    super.setEmail(email);
+                }
+
+                @Override
+                public void setLastName(String lastName) {
+                    setLDAPAttribute(UserModel.LAST_NAME, lastName);
+                    super.setLastName(lastName);
+                }
+
+                @Override
+                public void setFirstName(String firstName) {
+                    setLDAPAttribute(UserModel.FIRST_NAME, firstName);
+                    super.setFirstName(firstName);
+                }
+
+                protected void setLDAPAttribute(String modelAttrName, String value) {
+                    if (modelAttrName.equals(userModelAttrName)) {
+                        if (logger.isTraceEnabled()) {
+                            logger.tracef("Pushing user attribute to LDAP. Model attribute name: %s, LDAP attribute name: %s, Attribute value: %s", modelAttrName, ldapAttrName, value);
+                        }
+
+                        ensureTransactionStarted();
+
+                        ldapUser.setAttribute(ldapAttrName, value);
+                    }
+                }
+
+            };
+
+            return txDelegate;
+        } else {
+            return delegate;
+        }
+    }
+
+    @Override
+    public void beforeLDAPQuery(UserFederationMapperModel mapperModel, LDAPIdentityQuery query) {
+        String userModelAttrName = mapperModel.getConfig().get(USER_MODEL_ATTRIBUTE);
+        String ldapAttrName = mapperModel.getConfig().get(LDAP_ATTRIBUTE);
+
+        // Add mapped attribute to returning ldap attributes
+        query.addReturningLdapAttribute(ldapAttrName);
+        if (isReadOnly(mapperModel)) {
+            query.addReturningReadOnlyLdapAttribute(ldapAttrName);
+        }
+
+        // Change conditions and use ldapAttribute instead of userModel
+        for (Condition condition : query.getConditions()) {
+            QueryParameter param = condition.getParameter();
+            if (param != null && param.getName().equals(userModelAttrName)) {
+                param.setName(ldapAttrName);
+            }
+        }
+    }
+
+    private boolean isReadOnly(UserFederationMapperModel mapperModel) {
+        return parseBooleanParameter(mapperModel, READ_ONLY);
+    }
+}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserAttributeLDAPFederationMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserAttributeLDAPFederationMapperFactory.java
new file mode 100644
index 0000000..564b012
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserAttributeLDAPFederationMapperFactory.java
@@ -0,0 +1,35 @@
+package org.keycloak.federation.ldap.mappers;
+
+import java.util.List;
+
+import org.keycloak.mappers.UserFederationMapper;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.provider.ProviderConfigProperty;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class UserAttributeLDAPFederationMapperFactory extends AbstractLDAPFederationMapperFactory {
+
+    public static final String ID = "user-attribute-ldap-mapper";
+
+    @Override
+    public String getHelpText() {
+        return "Some help text TODO";
+    }
+
+    @Override
+    public List<ProviderConfigProperty> getConfigProperties() {
+        return null;
+    }
+
+    @Override
+    public String getId() {
+        return ID;
+    }
+
+    @Override
+    public UserFederationMapper create(KeycloakSession session) {
+        return new UserAttributeLDAPFederationMapper();
+    }
+}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/WritableLDAPUserModelDelegate.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/WritableLDAPUserModelDelegate.java
index 4debf0e..4e6ba40 100755
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/WritableLDAPUserModelDelegate.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/WritableLDAPUserModelDelegate.java
@@ -1,7 +1,7 @@
 package org.keycloak.federation.ldap;
 
 import org.jboss.logging.Logger;
-import org.keycloak.federation.ldap.idm.model.LDAPUser;
+import org.keycloak.federation.ldap.idm.model.LDAPObject;
 import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
 import org.keycloak.models.UserCredentialModel;
 import org.keycloak.models.UserModel;
@@ -15,52 +15,12 @@ public class WritableLDAPUserModelDelegate extends UserModelDelegate implements 
     private static final Logger logger = Logger.getLogger(WritableLDAPUserModelDelegate.class);
 
     protected LDAPFederationProvider provider;
+    protected LDAPObject ldapObject;
 
-    public WritableLDAPUserModelDelegate(UserModel delegate, LDAPFederationProvider provider) {
+    public WritableLDAPUserModelDelegate(UserModel delegate, LDAPFederationProvider provider, LDAPObject ldapObject) {
         super(delegate);
         this.provider = provider;
-    }
-
-    @Override
-    public void setUsername(String username) {
-        LDAPIdentityStore ldapIdentityStore = provider.getLdapIdentityStore();
-
-        LDAPUser ldapUser = LDAPUtils.getUser(ldapIdentityStore, delegate.getUsername());
-        if (ldapUser == null) {
-            throw new IllegalStateException("User not found in LDAP storage!");
-        }
-        ldapUser.setLoginName(username);
-        ldapIdentityStore.update(ldapUser);
-
-        delegate.setUsername(username);
-    }
-
-    @Override
-    public void setLastName(String lastName) {
-        LDAPIdentityStore ldapIdentityStore = provider.getLdapIdentityStore();
-
-        LDAPUser ldapUser = LDAPUtils.getUser(ldapIdentityStore, delegate.getUsername());
-        if (ldapUser == null) {
-            throw new IllegalStateException("User not found in LDAP storage!");
-        }
-        ldapUser.setLastName(lastName);
-        ldapIdentityStore.update(ldapUser);
-
-        delegate.setLastName(lastName);
-    }
-
-    @Override
-    public void setFirstName(String first) {
-        LDAPIdentityStore ldapIdentityStore = provider.getLdapIdentityStore();
-
-        LDAPUser ldapUser = LDAPUtils.getUser(ldapIdentityStore, delegate.getUsername());
-        if (ldapUser == null) {
-            throw new IllegalStateException("User not found in LDAP storage!");
-        }
-        ldapUser.setFirstName(first);
-        ldapIdentityStore.update(ldapUser);
-
-        delegate.setFirstName(first);
+        this.ldapObject = ldapObject;
     }
 
     @Override
@@ -70,31 +30,13 @@ public class WritableLDAPUserModelDelegate extends UserModelDelegate implements 
             return;
         }
 
-        LDAPIdentityStore ldapIdentityStore = provider.getLdapIdentityStore();
-        LDAPUser ldapUser = LDAPUtils.getUser(ldapIdentityStore, delegate.getUsername());
-        if (ldapUser == null) {
-            throw new IllegalStateException("User " + delegate.getUsername() + " not found in LDAP storage!");
-        }
-
         if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
-            LDAPUtils.updatePassword(ldapIdentityStore, delegate, cred.getValue());
+            LDAPIdentityStore ldapIdentityStore = provider.getLdapIdentityStore();
+            String password = cred.getValue();
+            ldapIdentityStore.updatePassword(ldapObject, password);
         } else {
             logger.warnf("Don't know how to update credential of type [%s] for user [%s]", cred.getType(), delegate.getUsername());
         }
     }
 
-    @Override
-    public void setEmail(String email) {
-        LDAPIdentityStore ldapIdentityStore = provider.getLdapIdentityStore();
-
-        LDAPUser ldapUser = LDAPUtils.getUser(ldapIdentityStore, delegate.getUsername());
-        if (ldapUser == null) {
-            throw new IllegalStateException("User not found in LDAP storage!");
-        }
-        ldapUser.setEmail(email);
-        ldapIdentityStore.update(ldapUser);
-
-        delegate.setEmail(email);
-    }
-
 }
diff --git a/federation/ldap/src/main/resources/META-INF/services/org.keycloak.mappers.UserFederationMapperFactory b/federation/ldap/src/main/resources/META-INF/services/org.keycloak.mappers.UserFederationMapperFactory
new file mode 100644
index 0000000..d9e3631
--- /dev/null
+++ b/federation/ldap/src/main/resources/META-INF/services/org.keycloak.mappers.UserFederationMapperFactory
@@ -0,0 +1,3 @@
+org.keycloak.federation.ldap.mappers.UserAttributeLDAPFederationMapperFactory
+org.keycloak.federation.ldap.mappers.FullNameLDAPFederationMapperFactory
+org.keycloak.federation.ldap.mappers.RoleLDAPFederationMapperFactory
\ No newline at end of file
diff --git a/federation/pom.xml b/federation/pom.xml
index 2345030..b2954f2 100755
--- a/federation/pom.xml
+++ b/federation/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/forms/account-api/pom.xml b/forms/account-api/pom.xml
index cd7e2c3..44319aa 100755
--- a/forms/account-api/pom.xml
+++ b/forms/account-api/pom.xml
@@ -4,7 +4,7 @@
 	<parent>
 		<artifactId>keycloak-forms-parent</artifactId>
 		<groupId>org.keycloak</groupId>
-		<version>1.3.0.Beta1-SNAPSHOT</version>
+		<version>1.3.0.Final-SNAPSHOT</version>
 		<relativePath>../pom.xml</relativePath>
 	</parent>
 	<modelVersion>4.0.0</modelVersion>
diff --git a/forms/account-freemarker/pom.xml b/forms/account-freemarker/pom.xml
index 9e8b013..9d33498 100755
--- a/forms/account-freemarker/pom.xml
+++ b/forms/account-freemarker/pom.xml
@@ -4,7 +4,7 @@
 	<parent>
 		<artifactId>keycloak-forms-parent</artifactId>
 		<groupId>org.keycloak</groupId>
-		<version>1.3.0.Beta1-SNAPSHOT</version>
+		<version>1.3.0.Final-SNAPSHOT</version>
 		<relativePath>../pom.xml</relativePath>
 	</parent>
 	<modelVersion>4.0.0</modelVersion>
diff --git a/forms/common-freemarker/pom.xml b/forms/common-freemarker/pom.xml
index 907ef1a..2457710 100755
--- a/forms/common-freemarker/pom.xml
+++ b/forms/common-freemarker/pom.xml
@@ -4,7 +4,7 @@
 	<parent>
 		<artifactId>keycloak-forms-parent</artifactId>
 		<groupId>org.keycloak</groupId>
-		<version>1.3.0.Beta1-SNAPSHOT</version>
+		<version>1.3.0.Final-SNAPSHOT</version>
 		<relativePath>../pom.xml</relativePath>
 	</parent>
 	<modelVersion>4.0.0</modelVersion>
diff --git a/forms/common-themes/pom.xml b/forms/common-themes/pom.xml
index 2d0aef9..afb9894 100755
--- a/forms/common-themes/pom.xml
+++ b/forms/common-themes/pom.xml
@@ -4,7 +4,7 @@
 	<parent>
 		<artifactId>keycloak-forms-parent</artifactId>
 		<groupId>org.keycloak</groupId>
-		<version>1.3.0.Beta1-SNAPSHOT</version>
+		<version>1.3.0.Final-SNAPSHOT</version>
 		<relativePath>../pom.xml</relativePath>
 	</parent>
 	<modelVersion>4.0.0</modelVersion>
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/messages/messages_en.properties b/forms/common-themes/src/main/resources/theme/base/admin/messages/messages_en.properties
new file mode 100644
index 0000000..ab297dd
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/admin/messages/messages_en.properties
@@ -0,0 +1,8 @@
+invalidPasswordMinLengthMessage=Invalid password: minimum length {0}.
+invalidPasswordMinLowerCaseCharsMessage=Invalid password: must contain at least {0} lower case characters.
+invalidPasswordMinDigitsMessage=Invalid password: must contain at least {0} numerical digits.
+invalidPasswordMinUpperCaseCharsMessage=Invalid password: must contain at least {0} upper case characters.
+invalidPasswordMinSpecialCharsMessage=Invalid password: must contain at least {0} special characters.
+invalidPasswordNotUsernameMessage=Invalid password: must not be equal to the username.
+invalidPasswordRegexPatternMessage=Invalid password: fails to match regex pattern(s).
+invalidPasswordHistoryMessage=Invalid password: must not be equal to any of last {0} passwords.
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js
index bf5662b..2444be4 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js
@@ -308,8 +308,12 @@ module.controller('UserCredentialsCtrl', function($scope, realm, user, User, Use
                 Notifications.success("The password has been reset");
                 $scope.password = null;
                 $scope.confirmPassword = null;
-            }, function() {
-                Notifications.error("Failed to reset user password");
+            }, function(response) {
+                if (response.data && response.data.errorMessage) {
+                    Notifications.error(response.data.errorMessage);
+                } else {
+                    Notifications.error("Failed to reset user password");
+                }
             });
         }, function() {
             $scope.password = null;
@@ -538,6 +542,7 @@ module.controller('LDAPCtrl', function($scope, $location, $route, Notifications,
             instance.config.useKerberosForPasswordAuthentication = false;
 
             instance.config.batchSizeForSync = DEFAULT_BATCH_SIZE;
+            instance.config.searchScope = "1";
 
             $scope.fullSyncEnabled = false;
             $scope.changedSyncEnabled = false;
@@ -554,6 +559,9 @@ module.controller('LDAPCtrl', function($scope, $location, $route, Notifications,
             if (!instance.config.batchSizeForSync) {
                 instance.config.batchSizeForSync = DEFAULT_BATCH_SIZE;
             }
+            if (!instance.config.searchScope) {
+                instance.config.searchScope = "1";
+            }
             $scope.fullSyncEnabled = (instance.fullSyncPeriod && instance.fullSyncPeriod > 0);
             $scope.changedSyncEnabled = (instance.changedSyncPeriod && instance.changedSyncPeriod > 0);
         }
@@ -573,6 +581,11 @@ module.controller('LDAPCtrl', function($scope, $location, $route, Notifications,
         { "id": "other", "name": "Other" }
     ];
 
+    $scope.searchScopes = [
+        { "id": "1", "name": "One Level" },
+        { "id": "2", "name": "Subtree" }
+    ];
+
     $scope.realm = realm;
 
     $scope.$watch('fullSyncEnabled', function(newVal, oldVal) {
@@ -603,12 +616,23 @@ module.controller('LDAPCtrl', function($scope, $location, $route, Notifications,
             $scope.lastVendor = $scope.instance.config.vendor;
 
             if ($scope.lastVendor === "ad") {
-                $scope.instance.config.usernameLDAPAttribute = "sAMAccountName";
+                $scope.instance.config.usernameLDAPAttribute = "cn";
                 $scope.instance.config.userObjectClasses = "person, organizationalPerson, user";
             } else {
                 $scope.instance.config.usernameLDAPAttribute = "uid";
                 $scope.instance.config.userObjectClasses = "inetOrgPerson, organizationalPerson";
             }
+
+            $scope.instance.config.rdnLDAPAttribute = $scope.instance.config.usernameLDAPAttribute;
+
+            var vendorToUUID = {
+                rhds: "nsuniqueid",
+                tivoli: "uniqueidentifier",
+                edirectory: "guid",
+                ad: "objectGUID",
+                other: "entryUUID"
+            };
+            $scope.instance.config.uuidLDAPAttribute = vendorToUUID[$scope.lastVendor];
         }
     }, true);
 
@@ -701,8 +725,8 @@ module.controller('LDAPCtrl', function($scope, $location, $route, Notifications,
     }
 
     function triggerSync(action) {
-        UserFederationSync.get({ action: action, realm: $scope.realm.realm, provider: $scope.instance.id }, function() {
-            Notifications.success("Sync of users finished successfully");
+        UserFederationSync.save({ action: action, realm: $scope.realm.realm, provider: $scope.instance.id }, {}, function(syncResult) {
+            Notifications.success("Sync of users finished successfully. " + syncResult.status);
         }, function() {
             Notifications.error("Error during sync of users");
         });
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/federated-ldap.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/federated-ldap.html
index 64728a3..ec916a0 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/federated-ldap.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/federated-ldap.html
@@ -69,11 +69,25 @@
             <div class="form-group clearfix">
                 <label class="col-md-2 control-label" for="usernameLDAPAttribute"><span class="required">*</span> Username LDAP attribute</label>
                 <div class="col-md-6">
-                    <input class="form-control" id="usernameLDAPAttribute" type="text" ng-model="instance.config.usernameLDAPAttribute" placeholder="LDAP attribute for uid" required>
+                    <input class="form-control" id="usernameLDAPAttribute" type="text" ng-model="instance.config.usernameLDAPAttribute" placeholder="LDAP attribute name for username" required>
                 </div>
                 <kc-tooltip>Name of LDAP attribute, which is mapped as Keycloak username. For many LDAP server vendors it's 'uid'. For Active directory it's usually 'sAMAccountName' or 'cn'</kc-tooltip>
             </div>
             <div class="form-group clearfix">
+                <label class="col-md-2 control-label" for="rdnLDAPAttribute"><span class="required">*</span> RDN LDAP attribute</label>
+                <div class="col-md-6">
+                    <input class="form-control" id="rdnLDAPAttribute" type="text" ng-model="instance.config.rdnLDAPAttribute" placeholder="LDAP attribute name for user RDN" required>
+                </div>
+                <kc-tooltip>Name of LDAP attribute, which is used as RDN (top attribute) of typical user DN. Usually it's the same as Username LDAP attribute, however for Active directory it could be 'cn' when username attribute might be 'sAMAccountName' </kc-tooltip>
+            </div>
+            <div class="form-group clearfix">
+                <label class="col-md-2 control-label" for="uuidLDAPAttribute"><span class="required">*</span> UUID LDAP attribute</label>
+                <div class="col-md-6">
+                    <input class="form-control" id="uuidLDAPAttribute" type="text" ng-model="instance.config.uuidLDAPAttribute" placeholder="LDAP attribute name for UUID" required>
+                </div>
+                <kc-tooltip>Name of LDAP attribute, which is used as unique object identifier (UUID) for objects in LDAP. For many LDAP server vendors it's 'entryUUID' however some are different. For example for Active directory it should be 'objectGUID' </kc-tooltip>
+            </div>
+            <div class="form-group clearfix">
                 <label class="col-md-2 control-label" for="userObjectClasses"><span class="required">*</span> User Object Classes</label>
                 <div class="col-md-6">
                     <input class="form-control" id="userObjectClasses" type="text" ng-model="instance.config.userObjectClasses" placeholder="LDAP User Object Classes (div. by comma)" required>
@@ -91,18 +105,13 @@
                 </div>
             </div>
             <div class="form-group clearfix">
-                <label class="col-md-2 control-label" for="ldapBaseDn"><span class="required">*</span> Base DN</label>
-                <div class="col-md-6">
-                    <input class="form-control" id="ldapBaseDn" type="text" ng-model="instance.config.baseDn" placeholder="LDAP Base DN" required>
-                </div>
-                <kc-tooltip>Base DN of LDAP tree where your data are. Base DN is usually ancestor of User DN Suffix</kc-tooltip>
-            </div>
-            <div class="form-group clearfix">
-                <label class="col-md-2 control-label" for="ldapUserDnSuffix"><span class="required">*</span> User DN Suffix</label>
+                <label class="col-md-2 control-label" for="ldapUserDns"><span class="required">*</span> User DN</label>
                 <div class="col-md-6">
-                    <input class="form-control" id="ldapUserDnSuffix" type="text" ng-model="instance.config.userDnSuffix" placeholder="LDAP User DN Suffix" required>
+                    <input class="form-control" id="ldapUserDns" type="text" ng-model="instance.config.userDns" placeholder="LDAP User DN" required>
                 </div>
-                <kc-tooltip>Base DN of LDAP tree where your users are. This DN is parent of all DNs of LDAP users</kc-tooltip>
+                <kc-tooltip>Full DN of LDAP tree where your users are. This DN is parent of LDAP users. It could be for example 'ou=users,dc=example,dc=com' assuming
+                    that your typical user will have DN like 'uid=john,ou=users,dc=example,dc=com'
+                </kc-tooltip>
             </div>
             <div class="form-group clearfix">
                 <label class="col-md-2 control-label" for="ldapBindDn"><span class="required">*</span> Bind DN</label>
@@ -121,6 +130,19 @@
                     <a class="btn btn-primary" data-ng-click="testAuthentication()">Test authentication</a>
                 </div>
             </div>
+            <div class="form-group">
+                <label class="col-md-2 control-label" for="searchScope">Search scope</label>
+                <div class="col-md-6">
+                    <div>
+                        <select class="form-control" id="searchScope"
+                                ng-model="instance.config.searchScope"
+                                ng-options="searchScope.id as searchScope.name for searchScope in searchScopes"
+                                required>
+                        </select>
+                    </div>
+                </div>
+                <kc-tooltip>For one level, we search for users just in DNs specified by User DNs. For subtree, we search in whole of their subtree. See LDAP documentation for more details</kc-tooltip>
+            </div>
             <div class="form-group clearfix">
                 <label class="col-md-2 control-label" for="connectionPooling">Connection pooling</label>
                 <div class="col-md-6">
diff --git a/forms/email-api/pom.xml b/forms/email-api/pom.xml
index 4f5fb88..17f17b6 100755
--- a/forms/email-api/pom.xml
+++ b/forms/email-api/pom.xml
@@ -4,7 +4,7 @@
 	<parent>
 		<artifactId>keycloak-forms-parent</artifactId>
 		<groupId>org.keycloak</groupId>
-		<version>1.3.0.Beta1-SNAPSHOT</version>
+		<version>1.3.0.Final-SNAPSHOT</version>
 		<relativePath>../pom.xml</relativePath>
 	</parent>
 	<modelVersion>4.0.0</modelVersion>
diff --git a/forms/email-freemarker/pom.xml b/forms/email-freemarker/pom.xml
index 0b3a66b..d38e348 100755
--- a/forms/email-freemarker/pom.xml
+++ b/forms/email-freemarker/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-forms-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/forms/login-api/pom.xml b/forms/login-api/pom.xml
index 0292b81..90ddbdc 100755
--- a/forms/login-api/pom.xml
+++ b/forms/login-api/pom.xml
@@ -4,7 +4,7 @@
 	<parent>
 		<artifactId>keycloak-forms-parent</artifactId>
 		<groupId>org.keycloak</groupId>
-		<version>1.3.0.Beta1-SNAPSHOT</version>
+		<version>1.3.0.Final-SNAPSHOT</version>
 		<relativePath>../pom.xml</relativePath>
 	</parent>
 	<modelVersion>4.0.0</modelVersion>
diff --git a/forms/login-freemarker/pom.xml b/forms/login-freemarker/pom.xml
index 2fcf180..d580f17 100755
--- a/forms/login-freemarker/pom.xml
+++ b/forms/login-freemarker/pom.xml
@@ -4,7 +4,7 @@
 	<parent>
 		<artifactId>keycloak-forms-parent</artifactId>
 		<groupId>org.keycloak</groupId>
-		<version>1.3.0.Beta1-SNAPSHOT</version>
+		<version>1.3.0.Final-SNAPSHOT</version>
 		<relativePath>../pom.xml</relativePath>
 	</parent>
 	<modelVersion>4.0.0</modelVersion>

forms/pom.xml 2(+1 -1)

diff --git a/forms/pom.xml b/forms/pom.xml
index 1ead9b3..f070014 100755
--- a/forms/pom.xml
+++ b/forms/pom.xml
@@ -4,7 +4,7 @@
 	<parent>
 		<artifactId>keycloak-parent</artifactId>
 		<groupId>org.keycloak</groupId>
-		<version>1.3.0.Beta1-SNAPSHOT</version>
+		<version>1.3.0.Final-SNAPSHOT</version>
 		<relativePath>../pom.xml</relativePath>
 	</parent>
 	<modelVersion>4.0.0</modelVersion>
diff --git a/integration/adapter-core/pom.xml b/integration/adapter-core/pom.xml
index 1533bdf..b519e47 100755
--- a/integration/adapter-core/pom.xml
+++ b/integration/adapter-core/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/integration/admin-client/pom.xml b/integration/admin-client/pom.xml
index ee70df5..75cf5bf 100755
--- a/integration/admin-client/pom.xml
+++ b/integration/admin-client/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/integration/as7-eap6/adapter/pom.xml b/integration/as7-eap6/adapter/pom.xml
index 6a5aaba..e84c60f 100755
--- a/integration/as7-eap6/adapter/pom.xml
+++ b/integration/as7-eap6/adapter/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/integration/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/KeycloakDependencyProcessorAS7.java b/integration/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/KeycloakDependencyProcessorAS7.java
new file mode 100644
index 0000000..c8935d4
--- /dev/null
+++ b/integration/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/KeycloakDependencyProcessorAS7.java
@@ -0,0 +1,19 @@
+package org.keycloak.subsystem.as7;
+
+import org.jboss.as.server.deployment.module.ModuleDependency;
+import org.jboss.as.server.deployment.module.ModuleSpecification;
+import org.jboss.modules.ModuleIdentifier;
+import org.jboss.modules.ModuleLoader;
+
+/**
+ * @author <a href="mailto:marko.strukelj@gmail.com">Marko Strukelj</a>
+ */
+public class KeycloakDependencyProcessorAS7 extends KeycloakDependencyProcessor {
+
+    private static final ModuleIdentifier KEYCLOAK_AS7_ADAPTER = ModuleIdentifier.create("org.keycloak.keycloak-as7-adapter");
+
+    @Override
+    protected void addPlatformSpecificModules(ModuleSpecification moduleSpecification, ModuleLoader moduleLoader) {
+        // ModuleDependency(ModuleLoader moduleLoader, ModuleIdentifier identifier, boolean optional, boolean export, boolean importServices, boolean userSpecified)
+        moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_AS7_ADAPTER, false, false, true, false));
+    }}
diff --git a/integration/as7-subsystem/src/main/resources/META-INF/services/org.jboss.as.controller.Extension b/integration/as7-subsystem/src/main/resources/META-INF/services/org.jboss.as.controller.Extension
new file mode 100755
index 0000000..1615116
--- /dev/null
+++ b/integration/as7-subsystem/src/main/resources/META-INF/services/org.jboss.as.controller.Extension
@@ -0,0 +1 @@
+org.keycloak.subsystem.as7.KeycloakExtension
diff --git a/integration/installed/pom.xml b/integration/installed/pom.xml
index 2f94dfd..034c897 100755
--- a/integration/installed/pom.xml
+++ b/integration/installed/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/integration/jaxrs-oauth-client/pom.xml b/integration/jaxrs-oauth-client/pom.xml
index cd3c362..e12f4d1 100755
--- a/integration/jaxrs-oauth-client/pom.xml
+++ b/integration/jaxrs-oauth-client/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/integration/jboss-adapter-core/pom.xml b/integration/jboss-adapter-core/pom.xml
index da0fd91..0e919ec 100755
--- a/integration/jboss-adapter-core/pom.xml
+++ b/integration/jboss-adapter-core/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/integration/jetty/jetty8.1/pom.xml b/integration/jetty/jetty8.1/pom.xml
index f38099a..de8ac85 100755
--- a/integration/jetty/jetty8.1/pom.xml
+++ b/integration/jetty/jetty8.1/pom.xml
@@ -4,7 +4,7 @@
     <parent>
 		<artifactId>keycloak-parent</artifactId>
 		<groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<modelVersion>4.0.0</modelVersion>
diff --git a/integration/jetty/jetty9.1/pom.xml b/integration/jetty/jetty9.1/pom.xml
index 4cbee0a..b10db5f 100755
--- a/integration/jetty/jetty9.1/pom.xml
+++ b/integration/jetty/jetty9.1/pom.xml
@@ -4,7 +4,7 @@
     <parent>
 		<artifactId>keycloak-parent</artifactId>
 		<groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<modelVersion>4.0.0</modelVersion>
diff --git a/integration/jetty/jetty9.2/pom.xml b/integration/jetty/jetty9.2/pom.xml
index ba00dcf..700172a 100755
--- a/integration/jetty/jetty9.2/pom.xml
+++ b/integration/jetty/jetty9.2/pom.xml
@@ -4,7 +4,7 @@
     <parent>
 		<artifactId>keycloak-parent</artifactId>
 		<groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<modelVersion>4.0.0</modelVersion>
diff --git a/integration/jetty/jetty-core/pom.xml b/integration/jetty/jetty-core/pom.xml
index 8296595..9c48413 100755
--- a/integration/jetty/jetty-core/pom.xml
+++ b/integration/jetty/jetty-core/pom.xml
@@ -4,7 +4,7 @@
     <parent>
 		<artifactId>keycloak-parent</artifactId>
 		<groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<modelVersion>4.0.0</modelVersion>
diff --git a/integration/jetty/pom.xml b/integration/jetty/pom.xml
index 13ee697..c8ca65e 100755
--- a/integration/jetty/pom.xml
+++ b/integration/jetty/pom.xml
@@ -3,7 +3,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <name>Keycloak Jetty Integration</name>
diff --git a/integration/js/pom.xml b/integration/js/pom.xml
index 8f9f24c..2e3d13c 100755
--- a/integration/js/pom.xml
+++ b/integration/js/pom.xml
@@ -4,7 +4,7 @@
 	<parent>
 		<artifactId>keycloak-parent</artifactId>
 		<groupId>org.keycloak</groupId>
-		<version>1.3.0.Beta1-SNAPSHOT</version>
+		<version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
 	</parent>
 	<modelVersion>4.0.0</modelVersion>
diff --git a/integration/osgi-adapter/pom.xml b/integration/osgi-adapter/pom.xml
index b5c2f4c..045966e 100755
--- a/integration/osgi-adapter/pom.xml
+++ b/integration/osgi-adapter/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/integration/pom.xml b/integration/pom.xml
index 5c1f132..0b248be 100755
--- a/integration/pom.xml
+++ b/integration/pom.xml
@@ -3,7 +3,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <name>Keycloak Integration</name>
@@ -23,7 +23,7 @@
         <module>jetty</module>
         <module>undertow</module>
         <module>wildfly</module>
-        <module>keycloak-as7-subsystem</module>
+        <module>as7-subsystem</module>
         <module>js</module>
         <module>installed</module>
         <module>admin-client</module>
diff --git a/integration/servlet-oauth-client/pom.xml b/integration/servlet-oauth-client/pom.xml
index 4650a28..7df8cbe 100755
--- a/integration/servlet-oauth-client/pom.xml
+++ b/integration/servlet-oauth-client/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/integration/spring-boot/pom.xml b/integration/spring-boot/pom.xml
index 646cc1c..26296e8 100755
--- a/integration/spring-boot/pom.xml
+++ b/integration/spring-boot/pom.xml
@@ -4,7 +4,7 @@
   <parent>
     <artifactId>keycloak-parent</artifactId>
     <groupId>org.keycloak</groupId>
-    <version>1.3.0.Beta1-SNAPSHOT</version>
+    <version>1.3.0.Final-SNAPSHOT</version>
     <relativePath>../../pom.xml</relativePath>
   </parent>
   <modelVersion>4.0.0</modelVersion>
diff --git a/integration/spring-security/pom.xml b/integration/spring-security/pom.xml
index c1a3e54..9bd7ae4 100755
--- a/integration/spring-security/pom.xml
+++ b/integration/spring-security/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/integration/tomcat/pom.xml b/integration/tomcat/pom.xml
index 933f0bc..e267cbd 100755
--- a/integration/tomcat/pom.xml
+++ b/integration/tomcat/pom.xml
@@ -3,7 +3,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <name>Keycloak Tomcat Integration</name>
diff --git a/integration/tomcat/tomcat6/pom.xml b/integration/tomcat/tomcat6/pom.xml
index dc3f13b..bba65d2 100755
--- a/integration/tomcat/tomcat6/pom.xml
+++ b/integration/tomcat/tomcat6/pom.xml
@@ -4,7 +4,7 @@
     <parent>
 		<artifactId>keycloak-parent</artifactId>
 		<groupId>org.keycloak</groupId>
-		<version>1.3.0.Beta1-SNAPSHOT</version>
+		<version>1.3.0.Final-SNAPSHOT</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<modelVersion>4.0.0</modelVersion>
diff --git a/integration/tomcat/tomcat7/pom.xml b/integration/tomcat/tomcat7/pom.xml
index cd86b37..bf5009a 100755
--- a/integration/tomcat/tomcat7/pom.xml
+++ b/integration/tomcat/tomcat7/pom.xml
@@ -4,7 +4,7 @@
     <parent>
 		<artifactId>keycloak-parent</artifactId>
 		<groupId>org.keycloak</groupId>
-		<version>1.3.0.Beta1-SNAPSHOT</version>
+		<version>1.3.0.Final-SNAPSHOT</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<modelVersion>4.0.0</modelVersion>
diff --git a/integration/tomcat/tomcat8/pom.xml b/integration/tomcat/tomcat8/pom.xml
index 12216f4..678f02a 100755
--- a/integration/tomcat/tomcat8/pom.xml
+++ b/integration/tomcat/tomcat8/pom.xml
@@ -4,7 +4,7 @@
     <parent>
 		<artifactId>keycloak-parent</artifactId>
 		<groupId>org.keycloak</groupId>
-		<version>1.3.0.Beta1-SNAPSHOT</version>
+		<version>1.3.0.Final-SNAPSHOT</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<modelVersion>4.0.0</modelVersion>
diff --git a/integration/tomcat/tomcat-core/pom.xml b/integration/tomcat/tomcat-core/pom.xml
index 30b75c5..b96aed0 100755
--- a/integration/tomcat/tomcat-core/pom.xml
+++ b/integration/tomcat/tomcat-core/pom.xml
@@ -4,7 +4,7 @@
     <parent>
 		<artifactId>keycloak-parent</artifactId>
 		<groupId>org.keycloak</groupId>
-		<version>1.3.0.Beta1-SNAPSHOT</version>
+		<version>1.3.0.Final-SNAPSHOT</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<modelVersion>4.0.0</modelVersion>
diff --git a/integration/undertow/pom.xml b/integration/undertow/pom.xml
index 4a6e8ea..83ea952 100755
--- a/integration/undertow/pom.xml
+++ b/integration/undertow/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/integration/wildfly/pom.xml b/integration/wildfly/pom.xml
index 25df76d..16a4d03 100644
--- a/integration/wildfly/pom.xml
+++ b/integration/wildfly/pom.xml
@@ -3,7 +3,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <name>Keycloak WildFly Integration</name>
diff --git a/integration/wildfly/wildfly-adapter/pom.xml b/integration/wildfly/wildfly-adapter/pom.xml
index bd61b44..4c5ca3a 100755
--- a/integration/wildfly/wildfly-adapter/pom.xml
+++ b/integration/wildfly/wildfly-adapter/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/integration/wildfly/wildfly-adapter-subsystem/pom.xml b/integration/wildfly/wildfly-adapter-subsystem/pom.xml
index 0a28a65..3357370 100755
--- a/integration/wildfly/wildfly-adapter-subsystem/pom.xml
+++ b/integration/wildfly/wildfly-adapter-subsystem/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.keycloak</groupId>
         <artifactId>keycloak-parent</artifactId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../../pom.xml</relativePath>
     </parent>
 
diff --git a/integration/wildfly/wildfly-extensions/pom.xml b/integration/wildfly/wildfly-extensions/pom.xml
index 5bdf23f..30c3452 100755
--- a/integration/wildfly/wildfly-extensions/pom.xml
+++ b/integration/wildfly/wildfly-extensions/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.keycloak</groupId>
         <artifactId>keycloak-parent</artifactId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../../pom.xml</relativePath>
     </parent>
 
diff --git a/integration/wildfly/wildfly-server-subsystem/pom.xml b/integration/wildfly/wildfly-server-subsystem/pom.xml
index 71bb0fd..7bc5305 100755
--- a/integration/wildfly/wildfly-server-subsystem/pom.xml
+++ b/integration/wildfly/wildfly-server-subsystem/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.keycloak</groupId>
         <artifactId>keycloak-parent</artifactId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../../pom.xml</relativePath>
     </parent>
 
diff --git a/integration/wildfly/wildfly-server-subsystem/src/main/resources/subsystem-templates/keycloak-datasources.xml b/integration/wildfly/wildfly-server-subsystem/src/main/resources/subsystem-templates/keycloak-datasources.xml
index 9f05130..114545f 100644
--- a/integration/wildfly/wildfly-server-subsystem/src/main/resources/subsystem-templates/keycloak-datasources.xml
+++ b/integration/wildfly/wildfly-server-subsystem/src/main/resources/subsystem-templates/keycloak-datasources.xml
@@ -4,14 +4,6 @@
     <extension-module>org.jboss.as.connector</extension-module>
     <subsystem xmlns="urn:jboss:domain:datasources:3.0">
         <datasources>
-            <datasource jndi-name="java:jboss/datasources/ExampleDS" pool-name="ExampleDS" enabled="true" use-java-context="true">
-                <connection-url>jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE</connection-url>
-                <driver>h2</driver>
-                <security>
-                    <user-name>sa</user-name>
-                    <password>sa</password>
-                </security>
-            </datasource>
             <datasource jndi-name="java:jboss/datasources/KeycloakDS" pool-name="KeycloakDS" enabled="true" use-java-context="true">
                 <connection-url>jdbc:h2:${jboss.server.data.dir}/keycloak;AUTO_SERVER=TRUE</connection-url>
                 <driver>h2</driver>
diff --git a/misc/ReleaseProcess.md b/misc/ReleaseProcess.md
index 6cf5c9d..d7dd7c1 100644
--- a/misc/ReleaseProcess.md
+++ b/misc/ReleaseProcess.md
@@ -26,18 +26,16 @@ $ mvn -Pjboss-release deploy
 
 * Upload src and distro zips to sf.net/projects/keycloak.  This includes appliance, war-dist, each adapter, and proxy distros.  You need to create an adapters folder on sf.net and each uploaded adapter there.
 
-* Upload documentation to docs.jboss.org
+* Upload documentation to http://keycloak.github.io/
 ```
-$ sftp keycloak@filemgmt.jboss.org
-> cd docs_htdocs/keycloak/docs
-> mkdir 1.0.0.Final (or whatever version)
-> quit
-
-$ unzip distribution/examples-docs-zip/target/keycloak-examples-docs-dist.zip
-$ cd docs
-$ rsync -rv --protocol=28 * keycloak@filemgmt.jboss.org:/docs_htdocs/keycloak/docs/1.0.0.Final
+$ git clone https://github.com/keycloak/keycloak.github.io.git
+$ cd keycloak.github.io.git/docs
+$ rm -rf *
+$ unzip distribution/docs-dist/target/keycloak-docs-1.0.0.Final.zip
+$ git add --all
+$ git commit
+$ git push
 ```
-
 * tag release
 ```
 $ git tag -a -m "1.0.0.Final" 1.0.0.Final
diff --git a/model/api/pom.xml b/model/api/pom.xml
index 673b3d0..ec4325f 100755
--- a/model/api/pom.xml
+++ b/model/api/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/model/api/src/main/java/org/keycloak/mappers/UserFederationMapper.java b/model/api/src/main/java/org/keycloak/mappers/UserFederationMapper.java
new file mode 100644
index 0000000..509ae64
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/mappers/UserFederationMapper.java
@@ -0,0 +1,10 @@
+package org.keycloak.mappers;
+
+import org.keycloak.provider.Provider;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public interface UserFederationMapper extends Provider {
+
+}
diff --git a/model/api/src/main/java/org/keycloak/mappers/UserFederationMapperFactory.java b/model/api/src/main/java/org/keycloak/mappers/UserFederationMapperFactory.java
new file mode 100644
index 0000000..386ce13
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/mappers/UserFederationMapperFactory.java
@@ -0,0 +1,10 @@
+package org.keycloak.mappers;
+
+import org.keycloak.provider.ConfiguredProvider;
+import org.keycloak.provider.ProviderFactory;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public interface UserFederationMapperFactory extends ProviderFactory<UserFederationMapper>, ConfiguredProvider {
+}
diff --git a/model/api/src/main/java/org/keycloak/mappers/UserFederationMapperSpi.java b/model/api/src/main/java/org/keycloak/mappers/UserFederationMapperSpi.java
new file mode 100644
index 0000000..f668058
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/mappers/UserFederationMapperSpi.java
@@ -0,0 +1,31 @@
+package org.keycloak.mappers;
+
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderFactory;
+import org.keycloak.provider.Spi;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class UserFederationMapperSpi implements Spi {
+
+    @Override
+    public String getName() {
+        return "userFederationMapper";
+    }
+
+    @Override
+    public Class<? extends Provider> getProviderClass() {
+        return UserFederationMapper.class;
+    }
+
+    @Override
+    public Class<? extends ProviderFactory> getProviderFactoryClass() {
+        return UserFederationMapperFactory.class;
+    }
+
+    @Override
+    public boolean isPrivate() {
+        return false;
+    }
+}
diff --git a/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java b/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
index 822edc4..7c393bb 100755
--- a/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
+++ b/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
@@ -53,6 +53,7 @@ public class RealmEntity extends AbstractIdentifiableEntity {
 
     private List<RequiredCredentialEntity> requiredCredentials = new ArrayList<RequiredCredentialEntity>();
     private List<UserFederationProviderEntity> userFederationProviders = new ArrayList<UserFederationProviderEntity>();
+    private List<UserFederationMapperEntity> userFederationMappers = new ArrayList<UserFederationMapperEntity>();
     private List<IdentityProviderEntity> identityProviders = new ArrayList<IdentityProviderEntity>();
 
     private Map<String, String> browserSecurityHeaders = new HashMap<String, String>();
@@ -428,6 +429,14 @@ public class RealmEntity extends AbstractIdentifiableEntity {
         this.userFederationProviders = userFederationProviders;
     }
 
+    public List<UserFederationMapperEntity> getUserFederationMappers() {
+        return userFederationMappers;
+    }
+
+    public void setUserFederationMappers(List<UserFederationMapperEntity> userFederationMappers) {
+        this.userFederationMappers = userFederationMappers;
+    }
+
     public List<IdentityProviderEntity> getIdentityProviders() {
         return identityProviders;
     }
diff --git a/model/api/src/main/java/org/keycloak/models/entities/UserFederationMapperEntity.java b/model/api/src/main/java/org/keycloak/models/entities/UserFederationMapperEntity.java
new file mode 100644
index 0000000..a920047
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/entities/UserFederationMapperEntity.java
@@ -0,0 +1,55 @@
+package org.keycloak.models.entities;
+
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class UserFederationMapperEntity {
+
+    protected String id;
+    protected String name;
+    protected String federationProviderId;
+    protected String federationMapperType;
+    protected Map<String, String> config;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getFederationProviderId() {
+        return federationProviderId;
+    }
+
+    public void setFederationProviderId(String federationProviderId) {
+        this.federationProviderId = federationProviderId;
+    }
+
+    public String getFederationMapperType() {
+        return federationMapperType;
+    }
+
+    public void setFederationMapperType(String federationMapperType) {
+        this.federationMapperType = federationMapperType;
+    }
+
+    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/LDAPConstants.java b/model/api/src/main/java/org/keycloak/models/LDAPConstants.java
index 14aa55d..94e792d 100644
--- a/model/api/src/main/java/org/keycloak/models/LDAPConstants.java
+++ b/model/api/src/main/java/org/keycloak/models/LDAPConstants.java
@@ -13,14 +13,17 @@ public class LDAPConstants {
     public static final String VENDOR_NOVELL_EDIRECTORY="edirectory" ;
 
     public static final String USERNAME_LDAP_ATTRIBUTE = "usernameLDAPAttribute";
+    public static final String RDN_LDAP_ATTRIBUTE = "rdnLDAPAttribute";
+    public static final String UUID_LDAP_ATTRIBUTE = "uuidLDAPAttribute";
     public static final String USER_OBJECT_CLASSES = "userObjectClasses";
 
     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 SECURITY_PROTOCOL = "securityProtocol";
+    public static final String USER_DNS = "userDns";
     public static final String BIND_DN = "bindDn";
     public static final String BIND_CREDENTIAL = "bindCredential";
 
+    public static final String SEARCH_SCOPE = "searchScope";
     public static final String CONNECTION_POOLING = "connectionPooling";
     public static final String PAGINATION = "pagination";
 
@@ -40,12 +43,16 @@ public class LDAPConstants {
     public static final String LDAP_ID = "LDAP_ID";
     public static final String LDAP_ENTRY_DN = "LDAP_ENTRY_DN";
 
+    // String used in config to divide more possible values (for example more userDns), which are saved in DB as single string
+    public static final String CONFIG_DIVIDER = ":::";
 
     // Those are forked from Picketlink
     public static final String GIVENNAME = "givenname";
     public static final String CN = "cn";
     public static final String SN = "sn";
+    public static final String SAM_ACCOUNT_NAME = "sAMAccountName";
     public static final String EMAIL = "mail";
+    public static final String POSTAL_CODE = "postalCode";
     public static final String MEMBER = "member";
     public static final String MEMBER_OF = "memberOf";
     public static final String OBJECT_CLASS = "objectclass";
@@ -57,7 +64,7 @@ public class LDAPConstants {
 
     public static final String COMMA = ",";
     public static final String EQUAL = "=";
-    public static final String SPACE_STRING = " ";
+    public static final String EMPTY_ATTRIBUTE_VALUE = " ";
 
     public static final String CUSTOM_ATTRIBUTE_ENABLED = "enabled";
     public static final String CUSTOM_ATTRIBUTE_CREATE_DATE = "createDate";
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 90afbb3..43eaa30 100755
--- a/model/api/src/main/java/org/keycloak/models/RealmModel.java
+++ b/model/api/src/main/java/org/keycloak/models/RealmModel.java
@@ -24,6 +24,11 @@ public interface RealmModel extends RoleContainerModel {
         ClientModel getCreatedClient();
     }
 
+    interface UserFederationProviderCreationEvent extends ProviderEvent {
+        UserFederationProviderModel getCreatedFederationProvider();
+        RealmModel getRealm();
+    }
+
     String getId();
 
     String getName();
@@ -208,6 +213,14 @@ public interface RealmModel extends RoleContainerModel {
     void removeUserFederationProvider(UserFederationProviderModel provider);
     void setUserFederationProviders(List<UserFederationProviderModel> providers);
 
+    Set<UserFederationMapperModel> getUserFederationMappers();
+    Set<UserFederationMapperModel> getUserFederationMappersByFederationProvider(String federationProviderId);
+    UserFederationMapperModel addUserFederationMapper(UserFederationMapperModel mapper);
+    void removeUserFederationMapper(UserFederationMapperModel mapper);
+    void updateUserFederationMapper(UserFederationMapperModel mapper);
+    UserFederationMapperModel getUserFederationMapperById(String id);
+    UserFederationMapperModel getUserFederationMapperByName(String federationProviderId, String name);
+
     String getLoginTheme();
 
     void setLoginTheme(String name);
diff --git a/model/api/src/main/java/org/keycloak/models/UserFederationEventAwareProviderFactory.java b/model/api/src/main/java/org/keycloak/models/UserFederationEventAwareProviderFactory.java
new file mode 100644
index 0000000..866bf79
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/UserFederationEventAwareProviderFactory.java
@@ -0,0 +1,33 @@
+package org.keycloak.models;
+
+import org.keycloak.provider.ProviderEvent;
+import org.keycloak.provider.ProviderEventListener;
+
+/**
+ * Provides "onProviderModelCreated" callback  invoked when UserFederationProviderModel for this factory implementation is created in realm
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public abstract class UserFederationEventAwareProviderFactory implements UserFederationProviderFactory {
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+        factory.register(new ProviderEventListener() {
+
+            @Override
+            public void onEvent(ProviderEvent event) {
+                if (event instanceof RealmModel.UserFederationProviderCreationEvent) {
+                    RealmModel.UserFederationProviderCreationEvent fedCreationEvent = (RealmModel.UserFederationProviderCreationEvent)event;
+                    UserFederationProviderModel providerModel = fedCreationEvent.getCreatedFederationProvider();
+
+                    if (providerModel.getProviderName().equals(getId())) {
+                        onProviderModelCreated(fedCreationEvent.getRealm(), providerModel);
+                    }
+                }
+            }
+
+        });
+    }
+
+    protected abstract void onProviderModelCreated(RealmModel realm, UserFederationProviderModel createdProviderModel);
+}
diff --git a/model/api/src/main/java/org/keycloak/models/UserFederationManager.java b/model/api/src/main/java/org/keycloak/models/UserFederationManager.java
index b111433..bd07b77 100755
--- a/model/api/src/main/java/org/keycloak/models/UserFederationManager.java
+++ b/model/api/src/main/java/org/keycloak/models/UserFederationManager.java
@@ -67,7 +67,17 @@ public class UserFederationManager implements UserProvider {
     public boolean removeUser(RealmModel realm, UserModel user) {
         UserFederationProvider link = getFederationLink(realm, user);
         if (link != null) {
-            return link.removeUser(realm, user);
+            boolean fedRemoved = link.removeUser(realm, user);
+            if (fedRemoved) {
+                boolean localRemoved = session.userStorage().removeUser(realm, user);
+                if (!localRemoved) {
+                    logger.warn("User removed from federation provider, but failed to remove him from keycloak model");
+                }
+                return localRemoved;
+            } else {
+                logger.warn("Failed to remove user from federation provider");
+                return false;
+            }
         }
         return session.userStorage().removeUser(realm, user);
 
@@ -75,7 +85,7 @@ public class UserFederationManager implements UserProvider {
 
     protected void validateUser(RealmModel realm, UserModel user) {
         UserFederationProvider link = getFederationLink(realm, user);
-        if (link != null  && !link.isValid(user)) {
+        if (link != null  && !link.isValid(realm, user)) {
             deleteInvalidUser(realm, user);
             throw new IllegalStateException("Federated user no longer valid");
         }
@@ -97,18 +107,13 @@ public class UserFederationManager implements UserProvider {
         }
     }
 
-    protected boolean isValid(RealmModel realm, UserModel user) {
-        UserFederationProvider link = getFederationLink(realm, user);
-        if (link != null) return link.isValid(user);
-        return true;
-    }
-
 
     protected UserModel validateAndProxyUser(RealmModel realm, UserModel user) {
         UserFederationProvider link = getFederationLink(realm, user);
         if (link != null) {
-            if (isValid(realm, user)) {
-                return link.proxy(user);
+            UserModel validatedProxyUser = link.validateAndProxy(realm, user);
+            if (validatedProxyUser != null) {
+                return validatedProxyUser;
             } else {
                 deleteInvalidUser(realm, user);
                 return null;
diff --git a/model/api/src/main/java/org/keycloak/models/UserFederationMapperModel.java b/model/api/src/main/java/org/keycloak/models/UserFederationMapperModel.java
new file mode 100644
index 0000000..54a347b
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/UserFederationMapperModel.java
@@ -0,0 +1,77 @@
+package org.keycloak.models;
+
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class UserFederationMapperModel {
+
+    protected String id;
+    protected String name;
+
+    // Refers to DB ID of federation provider
+    protected String federationProviderId;
+
+    // Refers to ID of UserFederationMapper implementation ( UserFederationMapperFactory.getId )
+    protected String federationMapperType;
+
+    protected Map<String, String> config;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getFederationProviderId() {
+        return federationProviderId;
+    }
+
+    public void setFederationProviderId(String federationProviderId) {
+        this.federationProviderId = federationProviderId;
+    }
+
+    public String getFederationMapperType() {
+        return federationMapperType;
+    }
+
+    public void setFederationMapperType(String federationMapperType) {
+        this.federationMapperType = federationMapperType;
+    }
+
+    public Map<String, String> getConfig() {
+        return config;
+    }
+
+    public void setConfig(Map<String, String> config) {
+        this.config = config;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        UserFederationMapperModel that = (UserFederationMapperModel) o;
+
+        if (!id.equals(that.id)) return false;
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return id.hashCode();
+    }
+}
diff --git a/model/api/src/main/java/org/keycloak/models/UserFederationProvider.java b/model/api/src/main/java/org/keycloak/models/UserFederationProvider.java
index 948e8c9..68b0834 100755
--- a/model/api/src/main/java/org/keycloak/models/UserFederationProvider.java
+++ b/model/api/src/main/java/org/keycloak/models/UserFederationProvider.java
@@ -42,16 +42,16 @@ public interface UserFederationProvider extends Provider {
 
 
     /**
-     * Gives the provider an option to proxy UserModels loaded from local storage.
+     * Gives the provider an option to validate if user still exists in federation backend and then proxy UserModel loaded from local storage.
      * This method is called whenever a UserModel is pulled from local storage.
      * For example, the LDAP provider proxies the UserModel and does on-demand synchronization with
      * LDAP whenever UserModel update methods are invoked.  It also overrides UserModel.updateCredential for the
      * credential types it supports
      *
      * @param local
-     * @return
+     * @return null if user is no longer valid or proxy object otherwise
      */
-    UserModel proxy(UserModel local);
+    UserModel validateAndProxy(RealmModel realm, UserModel local);
 
     /**
      * Should user registrations be synchronized with this provider?
@@ -120,7 +120,7 @@ public interface UserFederationProvider extends Provider {
      * @param local
      * @return
      */
-    boolean isValid(UserModel local);
+    boolean isValid(RealmModel realm, UserModel local);
 
     /**
      * What UserCredentialModel types should be handled by this provider for this user?  Keycloak will only call
@@ -153,7 +153,7 @@ public interface UserFederationProvider extends Provider {
     /**
      * Validate credentials of unknown user. The authenticated user is recognized based on provided credentials and returned back in CredentialValidationOutput
      * @param realm
-     * @param input
+     * @param credential
      * @return
      */
     CredentialValidationOutput validCredentials(RealmModel realm, UserCredentialModel credential);
diff --git a/model/api/src/main/java/org/keycloak/models/UserFederationProviderCreationEventImpl.java b/model/api/src/main/java/org/keycloak/models/UserFederationProviderCreationEventImpl.java
new file mode 100644
index 0000000..995ddc8
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/UserFederationProviderCreationEventImpl.java
@@ -0,0 +1,25 @@
+package org.keycloak.models;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class UserFederationProviderCreationEventImpl implements RealmModel.UserFederationProviderCreationEvent {
+
+    private final UserFederationProviderModel createdFederationProvider;
+    private final RealmModel realm;
+
+    public UserFederationProviderCreationEventImpl(RealmModel realm, UserFederationProviderModel createdFederationProvider) {
+        this.realm = realm;
+        this.createdFederationProvider = createdFederationProvider;
+    }
+
+    @Override
+    public UserFederationProviderModel getCreatedFederationProvider() {
+        return createdFederationProvider;
+    }
+
+    @Override
+    public RealmModel getRealm() {
+        return realm;
+    }
+}
diff --git a/model/api/src/main/java/org/keycloak/models/UserFederationProviderFactory.java b/model/api/src/main/java/org/keycloak/models/UserFederationProviderFactory.java
index 4247649..746b222 100755
--- a/model/api/src/main/java/org/keycloak/models/UserFederationProviderFactory.java
+++ b/model/api/src/main/java/org/keycloak/models/UserFederationProviderFactory.java
@@ -35,24 +35,25 @@ public interface UserFederationProviderFactory extends ProviderFactory<UserFeder
     String getId();
 
     /**
-     * Sync all users from the provider storage to Keycloak storage.
+     * Sync all users from the provider storage to Keycloak storage. Alternatively can update existing users or remove keycloak users, which are no longer
+     * available in federation storage (depends on the implementation)
      *
      * @param sessionFactory
      * @param realmId
      * @param model
+     * @return result with count of added/updated/removed users
      */
-    void syncAllUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model);
+    UserFederationSyncResult syncAllUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model);
 
     /**
      * Sync just changed (added / updated / removed) users from the provider storage to Keycloak storage. This is useful in case
      * that your storage supports "changelogs" (Tracking what users changed since specified date). It's implementation specific to
-     * decide what exactly will be changed (For example LDAP supports tracking of added / updated users, but not removed users. So
-     * removed users are not synced)
+     * decide what exactly will be changed
      *
      * @param sessionFactory
      * @param realmId
      * @param model
      * @param lastSync
      */
-    void syncChangedUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model, Date lastSync);
+    UserFederationSyncResult syncChangedUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model, Date lastSync);
 }
diff --git a/model/api/src/main/java/org/keycloak/models/UserFederationProviderModel.java b/model/api/src/main/java/org/keycloak/models/UserFederationProviderModel.java
index 9c3bf1c..494670d 100755
--- a/model/api/src/main/java/org/keycloak/models/UserFederationProviderModel.java
+++ b/model/api/src/main/java/org/keycloak/models/UserFederationProviderModel.java
@@ -20,7 +20,7 @@ public class UserFederationProviderModel {
     private int changedSyncPeriod = -1; // In seconds. -1 means that periodic changed sync is disabled
     private int lastSync;               // Date when last sync was done for this provider
 
-    public UserFederationProviderModel() {};
+    public UserFederationProviderModel() {}
 
     public UserFederationProviderModel(String id, String providerName, Map<String, String> config, int priority, String displayName, int fullSyncPeriod, int changedSyncPeriod, int lastSync) {
         this.id = id;
@@ -39,6 +39,10 @@ public class UserFederationProviderModel {
         return id;
     }
 
+    public void setId(String id) {
+        this.id = id;
+    }
+
     public String getProviderName() {
         return providerName;
     }
diff --git a/model/api/src/main/java/org/keycloak/models/UserFederationSyncResult.java b/model/api/src/main/java/org/keycloak/models/UserFederationSyncResult.java
new file mode 100644
index 0000000..e6d465b
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/UserFederationSyncResult.java
@@ -0,0 +1,66 @@
+package org.keycloak.models;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class UserFederationSyncResult {
+
+    private int added;
+    private int updated;
+    private int removed;
+
+    public int getAdded() {
+        return added;
+    }
+
+    public void setAdded(int added) {
+        this.added = added;
+    }
+
+    public int getUpdated() {
+        return updated;
+    }
+
+    public void setUpdated(int updated) {
+        this.updated = updated;
+    }
+
+    public int getRemoved() {
+        return removed;
+    }
+
+    public void setRemoved(int removed) {
+        this.removed = removed;
+    }
+
+    public void increaseAdded() {
+        added++;
+    }
+
+    public void increaseUpdated() {
+        updated++;
+    }
+
+    public void increaseRemoved() {
+        removed++;
+    }
+
+    public void add(UserFederationSyncResult other) {
+        added += other.added;
+        updated += other.updated;
+        removed += other.removed;
+    }
+
+    public String getStatus() {
+        return String.format("%d imported users, %d updated users, %d removed users", added, updated, removed);
+    }
+
+    @Override
+    public String toString() {
+        return String.format("UserFederationSyncResult [ %s ]", getStatus());
+    }
+
+    public static UserFederationSyncResult empty() {
+        return new UserFederationSyncResult();
+    }
+}
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 44f14c2..988d9ec 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
@@ -6,9 +6,13 @@ import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakSessionFactory;
 import org.keycloak.models.KeycloakSessionTask;
 import org.keycloak.models.KeycloakTransaction;
+import org.keycloak.models.ModelDuplicateException;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserFederationMapperModel;
+import org.keycloak.models.UserFederationProvider;
+import org.keycloak.models.UserFederationProviderModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.util.CertificateUtils;
 import org.keycloak.util.PemUtils;
@@ -23,6 +27,10 @@ import java.security.NoSuchAlgorithmException;
 import java.security.PrivateKey;
 import java.security.PublicKey;
 import java.security.cert.X509Certificate;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.UUID;
 
@@ -252,4 +260,93 @@ public final class KeycloakModelUtils {
     public static String getMasterRealmAdminApplicationClientId(RealmModel realm) {
         return realm.getName() + "-realm";
     }
+
+    /**
+     *
+     * @param roles
+     * @param targetRole
+     * @return true if targetRole is in roles (directly or indirectly via composite role)
+     */
+    public static boolean hasRole(Set<RoleModel> roles, RoleModel targetRole) {
+        if (roles.contains(targetRole)) return true;
+
+        for (RoleModel mapping : roles) {
+            if (mapping.hasRole(targetRole)) return true;
+        }
+        return false;
+    }
+
+    // USER FEDERATION RELATED STUFF
+
+    /**
+     * Ensure that displayName of myProvider (if not null) is unique and there is no other provider with same displayName in the list.
+     *
+     * @param displayName to check for duplications
+     * @param myProvider provider, which is excluded from the list (if present)
+     * @param federationProviders
+     * @throws ModelDuplicateException if there is other provider with same displayName
+     */
+    public static void ensureUniqueDisplayName(String displayName, UserFederationProviderModel myProvider, List<UserFederationProviderModel> federationProviders) throws ModelDuplicateException {
+        if (displayName != null) {
+
+            for (UserFederationProviderModel federationProvider : federationProviders) {
+                if (myProvider != null && (myProvider.equals(federationProvider) || (myProvider.getId() != null && myProvider.getId().equals(federationProvider.getId())))) {
+                    continue;
+                }
+
+                if (displayName.equals(federationProvider.getDisplayName())) {
+                    throw new ModelDuplicateException("There is already existing federation provider with display name: " + displayName);
+                }
+            }
+        }
+    }
+
+
+    public static UserFederationProviderModel findUserFederationProviderByDisplayName(String displayName, RealmModel realm) {
+        if (displayName == null) {
+            return null;
+        }
+
+        for (UserFederationProviderModel fedProvider : realm.getUserFederationProviders()) {
+            if (displayName.equals(fedProvider.getDisplayName())) {
+                return fedProvider;
+            }
+        }
+        return null;
+    }
+
+
+    public static UserFederationProviderModel findUserFederationProviderById(String fedProviderId, RealmModel realm) {
+        for (UserFederationProviderModel fedProvider : realm.getUserFederationProviders()) {
+            if (fedProviderId.equals(fedProvider.getId())) {
+                return fedProvider;
+            }
+        }
+        return null;
+    }
+
+
+    public static UserFederationMapperModel createUserFederationMapperModel(String name, String federationProviderId, String mapperType, String... config) {
+        UserFederationMapperModel mapperModel = new UserFederationMapperModel();
+        mapperModel.setName(name);
+        mapperModel.setFederationProviderId(federationProviderId);
+        mapperModel.setFederationMapperType(mapperType);
+
+        Map<String, String> configMap = new HashMap<String, String>();
+        String key = null;
+        for (String configEntry : config) {
+            if (key == null) {
+                key = configEntry;
+            } else {
+                configMap.put(key, configEntry);
+                key = null;
+            }
+        }
+        if (key != null) {
+            throw new IllegalStateException("Invalid count of arguments for config. Maybe mistake?");
+        }
+        mapperModel.setConfig(configMap);
+
+        return mapperModel;
+    }
 }
diff --git a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
index 84248fd..67d5932 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
@@ -5,16 +5,18 @@ import org.keycloak.models.ClientSessionModel;
 import org.keycloak.models.FederatedIdentityModel;
 import org.keycloak.models.IdentityProviderMapperModel;
 import org.keycloak.models.IdentityProviderModel;
+import org.keycloak.models.ModelException;
 import org.keycloak.models.ProtocolMapperModel;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RequiredCredentialModel;
 import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserConsentModel;
 import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserFederationMapperModel;
 import org.keycloak.models.UserFederationProviderModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserSessionModel;
-
+
 import org.keycloak.representations.idm.ClientRepresentation;
 import org.keycloak.representations.idm.CredentialRepresentation;
 import org.keycloak.representations.idm.FederatedIdentityRepresentation;
@@ -25,6 +27,7 @@ import org.keycloak.representations.idm.RealmEventsConfigRepresentation;
 import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.representations.idm.RoleRepresentation;
 import org.keycloak.representations.idm.UserConsentRepresentation;
+import org.keycloak.representations.idm.UserFederationMapperRepresentation;
 import org.keycloak.representations.idm.UserFederationProviderRepresentation;
 import org.keycloak.representations.idm.UserRepresentation;
 import org.keycloak.representations.idm.UserSessionRepresentation;
@@ -162,6 +165,10 @@ public class ModelToRepresentation {
             rep.setUserFederationProviders(fedProviderReps);
         }
 
+        for (UserFederationMapperModel mapper : realm.getUserFederationMappers()) {
+            rep.addUserFederationMapper(toRepresentation(realm, mapper));
+        }
+
         for (IdentityProviderModel provider : realm.getIdentityProviders()) {
             rep.addIdentityProvider(toRepresentation(provider));
         }
@@ -291,6 +298,24 @@ public class ModelToRepresentation {
         return rep;
     }
 
+    public static UserFederationMapperRepresentation toRepresentation(RealmModel realm, UserFederationMapperModel model) {
+        UserFederationMapperRepresentation rep = new UserFederationMapperRepresentation();
+        rep.setId(model.getId());
+        rep.setName(model.getName());
+        rep.setFederationMapperType(model.getFederationMapperType());
+        Map<String, String> config = new HashMap<String, String>();
+        config.putAll(model.getConfig());
+        rep.setConfig(config);
+
+        UserFederationProviderModel fedProvider = KeycloakModelUtils.findUserFederationProviderById(model.getFederationProviderId(), realm);
+        if (fedProvider == null) {
+            throw new ModelException("Couldn't find federation provider with ID " + model.getId());
+        }
+        rep.setFederationProviderDisplayName(fedProvider.getDisplayName());
+
+        return rep;
+    }
+
     public static IdentityProviderRepresentation toRepresentation(IdentityProviderModel identityProviderModel) {
         IdentityProviderRepresentation providerRep = new IdentityProviderRepresentation();
 
diff --git a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
index 578ecc9..715b14a 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
@@ -11,6 +11,7 @@ import org.keycloak.models.FederatedIdentityModel;
 import org.keycloak.models.IdentityProviderMapperModel;
 import org.keycloak.models.IdentityProviderModel;
 import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ModelException;
 import org.keycloak.models.PasswordPolicy;
 import org.keycloak.models.ProtocolMapperModel;
 import org.keycloak.models.RealmModel;
@@ -18,6 +19,7 @@ import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserConsentModel;
 import org.keycloak.models.UserCredentialModel;
 import org.keycloak.models.UserCredentialValueModel;
+import org.keycloak.models.UserFederationMapperModel;
 import org.keycloak.models.UserFederationProviderModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.representations.idm.ApplicationRepresentation;
@@ -34,6 +36,7 @@ import org.keycloak.representations.idm.RoleRepresentation;
 import org.keycloak.representations.idm.ScopeMappingRepresentation;
 import org.keycloak.representations.idm.SocialLinkRepresentation;
 import org.keycloak.representations.idm.UserConsentRepresentation;
+import org.keycloak.representations.idm.UserFederationMapperRepresentation;
 import org.keycloak.representations.idm.UserFederationProviderRepresentation;
 import org.keycloak.representations.idm.UserRepresentation;
 import org.keycloak.util.UriUtils;
@@ -46,6 +49,7 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.TreeSet;
 
 public class RepresentationToModel {
 
@@ -67,6 +71,8 @@ public class RepresentationToModel {
         if (rep.isEventsEnabled() != null) newRealm.setEventsEnabled(rep.isEventsEnabled());
         if (rep.getEventsExpiration() != null) newRealm.setEventsExpiration(rep.getEventsExpiration());
         if (rep.getEventsListeners() != null) newRealm.setEventsListeners(new HashSet<>(rep.getEventsListeners()));
+        if (rep.isAdminEventsEnabled() != null) newRealm.setAdminEventsEnabled(rep.isAdminEventsEnabled());
+        if (rep.isAdminEventsDetailsEnabled() != null) newRealm.setAdminEventsDetailsEnabled(rep.isAdminEventsDetailsEnabled());
 
         if (rep.getNotBefore() != null) newRealm.setNotBefore(rep.getNotBefore());
 
@@ -234,10 +240,35 @@ public class RepresentationToModel {
             newRealm.setBrowserSecurityHeaders(BrowserSecurityHeaders.defaultHeaders);
         }
 
+        List<UserFederationProviderModel> providerModels = null;
         if (rep.getUserFederationProviders() != null) {
-            List<UserFederationProviderModel> providerModels = convertFederationProviders(rep.getUserFederationProviders());
+            providerModels = convertFederationProviders(rep.getUserFederationProviders());
             newRealm.setUserFederationProviders(providerModels);
         }
+        if (rep.getUserFederationMappers() != null) {
+
+            // Remove builtin mappers for federation providers, which have some mappers already provided in JSON (likely due to previous export)
+            if (rep.getUserFederationProviders() != null) {
+                Set<String> providerNames = new TreeSet<String>();
+                for (UserFederationMapperRepresentation representation : rep.getUserFederationMappers()) {
+                    providerNames.add(representation.getFederationProviderDisplayName());
+                }
+                for (String providerName : providerNames) {
+                    for (UserFederationProviderModel providerModel : providerModels) {
+                        if (providerName.equals(providerModel.getDisplayName())) {
+                            Set<UserFederationMapperModel> toDelete = newRealm.getUserFederationMappersByFederationProvider(providerModel.getId());
+                            for (UserFederationMapperModel mapperModel : toDelete) {
+                                newRealm.removeUserFederationMapper(mapperModel);
+                            }
+                        }
+                    }
+                }
+            }
+
+            for (UserFederationMapperRepresentation representation : rep.getUserFederationMappers()) {
+                newRealm.addUserFederationMapper(toModel(newRealm, representation));
+            }
+        }
 
         // create users and their role mappings and social mappings
 
@@ -473,6 +504,23 @@ public class RepresentationToModel {
         return result;
     }
 
+    public static UserFederationMapperModel toModel(RealmModel realm, UserFederationMapperRepresentation rep) {
+        UserFederationMapperModel model = new UserFederationMapperModel();
+        model.setId(rep.getId());
+        model.setName(rep.getName());
+        model.setFederationMapperType(rep.getFederationMapperType());
+        model.setConfig(rep.getConfig());
+
+        UserFederationProviderModel fedProvider = KeycloakModelUtils.findUserFederationProviderByDisplayName(rep.getFederationProviderDisplayName(), realm);
+        if (fedProvider == null) {
+            throw new ModelException("Couldn't find federation provider with display name [" + rep.getFederationProviderDisplayName() + "] referenced from mapper ["
+                    + rep.getName());
+        }
+        model.setFederationProviderId(fedProvider.getId());
+
+        return model;
+    }
+
     // Roles
 
     public static void createRole(RealmModel newRealm, RoleRepresentation roleRep) {
diff --git a/model/api/src/main/java/org/keycloak/models/utils/UserModelDelegate.java b/model/api/src/main/java/org/keycloak/models/utils/UserModelDelegate.java
index f025647..3f6bec4 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/UserModelDelegate.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/UserModelDelegate.java
@@ -227,4 +227,7 @@ public class UserModelDelegate implements UserModel {
         return delegate.revokeConsentForClient(clientId);
     }
 
+    public UserModel getDelegate() {
+        return delegate;
+    }
 }
diff --git a/model/api/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/model/api/src/main/resources/META-INF/services/org.keycloak.provider.Spi
index 9b36900..37844e0 100755
--- a/model/api/src/main/resources/META-INF/services/org.keycloak.provider.Spi
+++ b/model/api/src/main/resources/META-INF/services/org.keycloak.provider.Spi
@@ -1,4 +1,5 @@
 org.keycloak.models.UserFederationSpi
+org.keycloak.mappers.UserFederationMapperSpi
 org.keycloak.models.RealmSpi
 org.keycloak.models.UserSessionSpi
 org.keycloak.models.UserSpi
diff --git a/model/file/pom.xml b/model/file/pom.xml
index 7be6c43..564201f 100755
--- a/model/file/pom.xml
+++ b/model/file/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java b/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java
index feae198..26e09bb 100755
--- a/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java
+++ b/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java
@@ -30,6 +30,8 @@ import org.keycloak.models.PasswordPolicy;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RequiredCredentialModel;
 import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserFederationMapperModel;
+import org.keycloak.models.UserFederationProviderCreationEventImpl;
 import org.keycloak.models.UserFederationProviderModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.entities.AuthenticationExecutionEntity;
@@ -40,6 +42,7 @@ import org.keycloak.models.entities.IdentityProviderMapperEntity;
 import org.keycloak.models.entities.RealmEntity;
 import org.keycloak.models.entities.RequiredCredentialEntity;
 import org.keycloak.models.entities.RoleEntity;
+import org.keycloak.models.entities.UserFederationMapperEntity;
 import org.keycloak.models.entities.UserFederationProviderEntity;
 import org.keycloak.models.utils.KeycloakModelUtils;
 
@@ -817,6 +820,8 @@ public class RealmAdapter implements RealmModel {
 
     @Override
     public UserFederationProviderModel addUserFederationProvider(String providerName, Map<String, String> config, int priority, String displayName, int fullSyncPeriod, int changedSyncPeriod, int lastSync) {
+        KeycloakModelUtils.ensureUniqueDisplayName(displayName, null, getUserFederationProviders());
+
         UserFederationProviderEntity entity = new UserFederationProviderEntity();
         entity.setId(KeycloakModelUtils.generateId());
         entity.setPriority(priority);
@@ -831,7 +836,9 @@ public class RealmAdapter implements RealmModel {
         entity.setLastSync(lastSync);
         realm.getUserFederationProviders().add(entity);
 
-        return new UserFederationProviderModel(entity.getId(), providerName, config, priority, displayName, fullSyncPeriod, changedSyncPeriod, lastSync);
+        UserFederationProviderModel providerModel = new UserFederationProviderModel(entity.getId(), providerName, config, priority, displayName, fullSyncPeriod, changedSyncPeriod, lastSync);
+        session.getKeycloakSessionFactory().publish(new UserFederationProviderCreationEventImpl(this, providerModel));
+        return providerModel;
     }
 
     @Override
@@ -842,6 +849,12 @@ public class RealmAdapter implements RealmModel {
             if (entity.getId().equals(provider.getId())) {
                 session.users().preRemove(this, new UserFederationProviderModel(entity.getId(), entity.getProviderName(), entity.getConfig(), entity.getPriority(), entity.getDisplayName(),
                         entity.getFullSyncPeriod(), entity.getChangedSyncPeriod(), entity.getLastSync()));
+
+                Set<UserFederationMapperEntity> mappers = getUserFederationMapperEntitiesByFederationProvider(provider.getId());
+                for (UserFederationMapperEntity mapper : mappers) {
+                    realm.getUserFederationMappers().remove(mapper);
+                }
+
                 it.remove();
             }
         }
@@ -849,6 +862,8 @@ public class RealmAdapter implements RealmModel {
 
     @Override
     public void updateUserFederationProvider(UserFederationProviderModel model) {
+        KeycloakModelUtils.ensureUniqueDisplayName(model.getDisplayName(), model, getUserFederationProviders());
+
         Iterator<UserFederationProviderEntity> it = realm.getUserFederationProviders().iterator();
         while (it.hasNext()) {
             UserFederationProviderEntity entity = it.next();
@@ -894,11 +909,20 @@ public class RealmAdapter implements RealmModel {
 
     @Override
     public void setUserFederationProviders(List<UserFederationProviderModel> providers) {
+        for (UserFederationProviderModel currentProvider : providers) {
+            KeycloakModelUtils.ensureUniqueDisplayName(currentProvider.getDisplayName(), currentProvider, providers);
+        }
+
         List<UserFederationProviderEntity> entities = new LinkedList<UserFederationProviderEntity>();
         for (UserFederationProviderModel model : providers) {
             UserFederationProviderEntity entity = new UserFederationProviderEntity();
-            if (model.getId() != null) entity.setId(model.getId());
-            else entity.setId(KeycloakModelUtils.generateId());
+            if (model.getId() != null) {
+                entity.setId(model.getId());
+            } else {
+                String id = KeycloakModelUtils.generateId();
+                entity.setId(id);
+                model.setId(id);
+            }
             entity.setProviderName(model.getProviderName());
             entity.setConfig(model.getConfig());
             entity.setPriority(model.getPriority());
@@ -911,6 +935,7 @@ public class RealmAdapter implements RealmModel {
             entity.setChangedSyncPeriod(model.getChangedSyncPeriod());
             entity.setLastSync(model.getLastSync());
             entities.add(entity);
+            session.getKeycloakSessionFactory().publish(new UserFederationProviderCreationEventImpl(this, model));
         }
 
         realm.setUserFederationProviders(entities);
@@ -1068,16 +1093,7 @@ public class RealmAdapter implements RealmModel {
     public Set<IdentityProviderMapperModel> getIdentityProviderMappers() {
         Set<IdentityProviderMapperModel> mappings = new HashSet<>();
         for (IdentityProviderMapperEntity entity : this.realm.getIdentityProviderMappers()) {
-            IdentityProviderMapperModel mapping = new IdentityProviderMapperModel();
-            mapping.setId(entity.getId());
-            mapping.setName(entity.getName());
-            mapping.setIdentityProviderAlias(entity.getIdentityProviderAlias());
-            mapping.setIdentityProviderMapper(entity.getIdentityProviderMapper());
-            Map<String, String> config = new HashMap<String, String>();
-            if (entity.getConfig() != null) {
-                config.putAll(entity.getConfig());
-            }
-            mapping.setConfig(config);
+            IdentityProviderMapperModel mapping = entityToModel(entity);
             mappings.add(mapping);
         }
         return mappings;
@@ -1089,16 +1105,7 @@ public class RealmAdapter implements RealmModel {
             if (!entity.getIdentityProviderAlias().equals(brokerAlias)) {
                 continue;
             }
-            IdentityProviderMapperModel mapping = new IdentityProviderMapperModel();
-            mapping.setId(entity.getId());
-            mapping.setName(entity.getName());
-            mapping.setIdentityProviderAlias(entity.getIdentityProviderAlias());
-            mapping.setIdentityProviderMapper(entity.getIdentityProviderMapper());
-            Map<String, String> config = new HashMap<String, String>();
-            if (entity.getConfig() != null) {
-                config.putAll(entity.getConfig());
-            }
-            mapping.setConfig(config);
+            IdentityProviderMapperModel mapping = entityToModel(entity);
             mappings.add(mapping);
         }
         return mappings;
@@ -1107,7 +1114,7 @@ public class RealmAdapter implements RealmModel {
     @Override
     public IdentityProviderMapperModel addIdentityProviderMapper(IdentityProviderMapperModel model) {
         if (getIdentityProviderMapperByName(model.getIdentityProviderAlias(), model.getIdentityProviderMapper()) != null) {
-            throw new RuntimeException("protocol mapper name must be unique per protocol");
+            throw new RuntimeException("identity provider mapper name must be unique per identity provider");
         }
         String id = KeycloakModelUtils.generateId();
         IdentityProviderMapperEntity entity = new IdentityProviderMapperEntity();
@@ -1418,4 +1425,117 @@ public class RealmAdapter implements RealmModel {
     }
 
 
+    @Override
+    public Set<UserFederationMapperModel> getUserFederationMappers() {
+        Set<UserFederationMapperModel> mappers = new HashSet<UserFederationMapperModel>();
+        for (UserFederationMapperEntity entity : this.realm.getUserFederationMappers()) {
+            UserFederationMapperModel mapper = entityToModel(entity);
+            mappers.add(mapper);
+        }
+        return mappers;
+    }
+
+    @Override
+    public Set<UserFederationMapperModel> getUserFederationMappersByFederationProvider(String federationProviderId) {
+        Set<UserFederationMapperModel> mappers = new HashSet<UserFederationMapperModel>();
+        Set<UserFederationMapperEntity> mapperEntities = getUserFederationMapperEntitiesByFederationProvider(federationProviderId);
+        for (UserFederationMapperEntity entity : mapperEntities) {
+            mappers.add(entityToModel(entity));
+        }
+        return mappers;
+    }
+
+    @Override
+    public UserFederationMapperModel addUserFederationMapper(UserFederationMapperModel model) {
+        if (getUserFederationMapperByName(model.getFederationProviderId(), model.getName()) != null) {
+            throw new ModelDuplicateException("User federation mapper must be unique per federation provider. There is already: " + model.getName());
+        }
+        String id = KeycloakModelUtils.generateId();
+        UserFederationMapperEntity entity = new UserFederationMapperEntity();
+        entity.setId(id);
+        entity.setName(model.getName());
+        entity.setFederationProviderId(model.getFederationProviderId());
+        entity.setFederationMapperType(model.getFederationMapperType());
+        entity.setConfig(model.getConfig());
+
+        this.realm.getUserFederationMappers().add(entity);
+        return entityToModel(entity);
+    }
+
+    protected UserFederationMapperEntity getUserFederationMapperEntity(String id) {
+        for (UserFederationMapperEntity entity : this.realm.getUserFederationMappers()) {
+            if (entity.getId().equals(id)) {
+                return entity;
+            }
+        }
+        return null;
+
+    }
+
+    protected UserFederationMapperEntity getUserFederationMapperEntityByName(String federationProviderId, String name) {
+        for (UserFederationMapperEntity entity : this.realm.getUserFederationMappers()) {
+            if (entity.getFederationProviderId().equals(federationProviderId) && entity.getName().equals(name)) {
+                return entity;
+            }
+        }
+        return null;
+
+    }
+
+    protected Set<UserFederationMapperEntity> getUserFederationMapperEntitiesByFederationProvider(String federationProviderId) {
+        Set<UserFederationMapperEntity> mappers = new HashSet<UserFederationMapperEntity>();
+        for (UserFederationMapperEntity entity : this.realm.getUserFederationMappers()) {
+            if (federationProviderId.equals(entity.getFederationProviderId())) {
+                mappers.add(entity);
+            }
+        }
+        return mappers;
+    }
+
+    @Override
+    public void removeUserFederationMapper(UserFederationMapperModel mapper) {
+        UserFederationMapperEntity toDelete = getUserFederationMapperEntity(mapper.getId());
+        if (toDelete != null) {
+            this.realm.getUserFederationMappers().remove(toDelete);
+        }
+    }
+
+    @Override
+    public void updateUserFederationMapper(UserFederationMapperModel mapper) {
+        UserFederationMapperEntity entity = getUserFederationMapperEntity(mapper.getId());
+        entity.setFederationProviderId(mapper.getFederationProviderId());
+        entity.setFederationMapperType(mapper.getFederationMapperType());
+        if (entity.getConfig() == null) {
+            entity.setConfig(mapper.getConfig());
+        } else {
+            entity.getConfig().clear();
+            entity.getConfig().putAll(mapper.getConfig());
+        }
+    }
+
+    @Override
+    public UserFederationMapperModel getUserFederationMapperById(String id) {
+        UserFederationMapperEntity entity = getUserFederationMapperEntity(id);
+        if (entity == null) return null;
+        return entityToModel(entity);
+    }
+
+    @Override
+    public UserFederationMapperModel getUserFederationMapperByName(String federationProviderId, String name) {
+        UserFederationMapperEntity entity = getUserFederationMapperEntityByName(federationProviderId, name);
+        if (entity == null) return null;
+        return entityToModel(entity);
+    }
+
+    protected UserFederationMapperModel entityToModel(UserFederationMapperEntity entity) {
+        UserFederationMapperModel mapper = new UserFederationMapperModel();
+        mapper.setId(entity.getId());
+        mapper.setName(entity.getName());
+        mapper.setFederationProviderId(entity.getFederationProviderId());
+        mapper.setFederationMapperType(entity.getFederationMapperType());
+        Map<String, String> config = new HashMap<String, String>();
+        if (entity.getConfig() != null) config.putAll(entity.getConfig());
+        mapper.setConfig(config);
+        return mapper;
+    }
 }
diff --git a/model/file/src/main/java/org/keycloak/models/file/adapter/UserAdapter.java b/model/file/src/main/java/org/keycloak/models/file/adapter/UserAdapter.java
index eb4277f..b39fc6d 100755
--- a/model/file/src/main/java/org/keycloak/models/file/adapter/UserAdapter.java
+++ b/model/file/src/main/java/org/keycloak/models/file/adapter/UserAdapter.java
@@ -33,6 +33,7 @@ import org.keycloak.models.entities.CredentialEntity;
 import org.keycloak.models.entities.FederatedIdentityEntity;
 import org.keycloak.models.entities.RoleEntity;
 import org.keycloak.models.entities.UserEntity;
+import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.models.utils.Pbkdf2PasswordEncoder;
 import org.keycloak.util.Time;
 
@@ -394,12 +395,7 @@ public class UserAdapter implements UserModel, Comparable {
     @Override
     public boolean hasRole(RoleModel role) {
         Set<RoleModel> roles = getRoleMappings();
-        if (roles.contains(role)) return true;
-
-        for (RoleModel mapping : roles) {
-            if (mapping.hasRole(role)) return true;
-        }
-        return false;
+        return KeycloakModelUtils.hasRole(roles, role);
     }
 
     @Override
diff --git a/model/invalidation-cache/infinispan/pom.xml b/model/invalidation-cache/infinispan/pom.xml
index 0d4ebf4..cb9ccea 100755
--- a/model/invalidation-cache/infinispan/pom.xml
+++ b/model/invalidation-cache/infinispan/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/model/invalidation-cache/model-adapters/pom.xml b/model/invalidation-cache/model-adapters/pom.xml
index 7d44d62..1be7be4 100755
--- a/model/invalidation-cache/model-adapters/pom.xml
+++ b/model/invalidation-cache/model-adapters/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
index 5329a9c..d93acf6 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
@@ -12,6 +12,7 @@ import org.keycloak.models.RealmModel;
 import org.keycloak.models.RealmProvider;
 import org.keycloak.models.RequiredCredentialModel;
 import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserFederationMapperModel;
 import org.keycloak.models.UserFederationProviderModel;
 import org.keycloak.models.cache.RealmCache;
 import org.keycloak.util.MultivaluedHashMap;
@@ -73,6 +74,7 @@ public class CachedRealm {
 
     private List<RequiredCredentialModel> requiredCredentials = new ArrayList<RequiredCredentialModel>();
     private List<UserFederationProviderModel> userFederationProviders = new ArrayList<UserFederationProviderModel>();
+    private MultivaluedHashMap<String, UserFederationMapperModel> userFederationMappers = new MultivaluedHashMap<String, UserFederationMapperModel>();
     private List<IdentityProviderModel> identityProviders = new ArrayList<IdentityProviderModel>();
 
     private Map<String, String> browserSecurityHeaders = new HashMap<String, String>();
@@ -143,6 +145,9 @@ public class CachedRealm {
 
         requiredCredentials = model.getRequiredCredentials();
         userFederationProviders = model.getUserFederationProviders();
+        for (UserFederationMapperModel mapper : model.getUserFederationMappers()) {
+            userFederationMappers.add(mapper.getFederationProviderId(), mapper);
+        }
 
         this.identityProviders = new ArrayList<>();
 
@@ -390,6 +395,10 @@ public class CachedRealm {
         return userFederationProviders;
     }
 
+    public MultivaluedHashMap<String, UserFederationMapperModel> getUserFederationMappers() {
+        return userFederationMappers;
+    }
+
     public String getCertificatePem() {
         return certificatePem;
     }
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java
index 2f6f8d6..afb1c2c 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java
@@ -8,11 +8,14 @@ import org.keycloak.models.AuthenticatorModel;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.IdentityProviderMapperModel;
 import org.keycloak.models.IdentityProviderModel;
+import org.keycloak.models.LDAPConstants;
 import org.keycloak.models.PasswordPolicy;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RequiredCredentialModel;
 import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserFederationMapperModel;
 import org.keycloak.models.UserFederationProviderModel;
+import org.keycloak.models.UserModel;
 import org.keycloak.models.cache.entities.CachedRealm;
 import org.keycloak.models.utils.KeycloakModelUtils;
 
@@ -953,6 +956,69 @@ public class RealmAdapter implements RealmModel {
     }
 
     @Override
+    public Set<UserFederationMapperModel> getUserFederationMappers() {
+        if (updated != null) return updated.getUserFederationMappers();
+        Set<UserFederationMapperModel> mappers = new HashSet<UserFederationMapperModel>();
+        for (List<UserFederationMapperModel> models : cached.getUserFederationMappers().values()) {
+            for (UserFederationMapperModel model : models) {
+                mappers.add(model);
+            }
+        }
+        return mappers;
+    }
+
+    @Override
+    public Set<UserFederationMapperModel> getUserFederationMappersByFederationProvider(String federationProviderId) {
+        if (updated != null) return updated.getUserFederationMappersByFederationProvider(federationProviderId);
+        Set<UserFederationMapperModel> mappers = new HashSet<>();
+        List<UserFederationMapperModel> list = cached.getUserFederationMappers().getList(federationProviderId);
+        for (UserFederationMapperModel entity : list) {
+            mappers.add(entity);
+        }
+        return mappers;
+    }
+
+    @Override
+    public UserFederationMapperModel addUserFederationMapper(UserFederationMapperModel mapper) {
+        getDelegateForUpdate();
+        return updated.addUserFederationMapper(mapper);
+    }
+
+    @Override
+    public void removeUserFederationMapper(UserFederationMapperModel mapper) {
+        getDelegateForUpdate();
+        updated.removeUserFederationMapper(mapper);
+    }
+
+    @Override
+    public void updateUserFederationMapper(UserFederationMapperModel mapper) {
+        getDelegateForUpdate();
+        updated.updateUserFederationMapper(mapper);
+    }
+
+    @Override
+    public UserFederationMapperModel getUserFederationMapperById(String id) {
+        if (updated != null) return updated.getUserFederationMapperById(id);
+        for (List<UserFederationMapperModel> models : cached.getUserFederationMappers().values()) {
+            for (UserFederationMapperModel model : models) {
+                if (model.getId().equals(id)) return model;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public UserFederationMapperModel getUserFederationMapperByName(String federationProviderId, String name) {
+        if (updated != null) return updated.getUserFederationMapperByName(federationProviderId, name);
+        List<UserFederationMapperModel> models = cached.getUserFederationMappers().getList(federationProviderId);
+        if (models == null) return null;
+        for (UserFederationMapperModel model : models) {
+            if (model.getName().equals(name)) return model;
+        }
+        return null;
+    }
+
+    @Override
     public List<AuthenticationFlowModel> getAuthenticationFlows() {
         if (updated != null) return updated.getAuthenticationFlows();
         List<AuthenticationFlowModel> models = new ArrayList<>();
diff --git a/model/invalidation-cache/pom.xml b/model/invalidation-cache/pom.xml
index 5c9fc35..c79a2a3 100755
--- a/model/invalidation-cache/pom.xml
+++ b/model/invalidation-cache/pom.xml
@@ -3,7 +3,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <name>Model Parent</name>
diff --git a/model/jpa/pom.xml b/model/jpa/pom.xml
index 96391e4..4e64488 100755
--- a/model/jpa/pom.xml
+++ b/model/jpa/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
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 9ac1c3e..089cd67 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
@@ -103,6 +103,9 @@ public class RealmEntity {
     @JoinTable(name="FED_PROVIDERS")
     List<UserFederationProviderEntity> userFederationProviders = new ArrayList<UserFederationProviderEntity>();
 
+    @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
+    Collection<UserFederationMapperEntity> userFederationMappers = new ArrayList<UserFederationMapperEntity>();
+
     @OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true)
     @JoinTable(name="REALM_CLIENT", joinColumns={ @JoinColumn(name="REALM_ID") }, inverseJoinColumns={ @JoinColumn(name="CLIENT_ID") })
     Collection<ClientEntity> clients = new ArrayList<>();
@@ -481,6 +484,14 @@ public class RealmEntity {
         this.userFederationProviders = userFederationProviders;
     }
 
+    public Collection<UserFederationMapperEntity> getUserFederationMappers() {
+        return userFederationMappers;
+    }
+
+    public void setUserFederationMappers(Collection<UserFederationMapperEntity> userFederationMappers) {
+        this.userFederationMappers = userFederationMappers;
+    }
+
     public Collection<RealmAttributeEntity> getAttributes() {
         return attributes;
     }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserFederationMapperEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserFederationMapperEntity.java
new file mode 100644
index 0000000..4aa5e6e
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserFederationMapperEntity.java
@@ -0,0 +1,113 @@
+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.FetchType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.MapKeyColumn;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@Entity
+@Table(name="USER_FEDERATION_MAPPER")
+public class UserFederationMapperEntity {
+
+    @Id
+    @Column(name="ID", length = 36)
+    protected String id;
+
+    @Column(name="NAME")
+    protected String name;
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "FEDERATION_PROVIDER_ID")
+    protected UserFederationProviderEntity federationProvider;
+
+    @Column(name = "FEDERATION_MAPPER_TYPE")
+    protected String federationMapperType;
+
+    @ElementCollection
+    @MapKeyColumn(name="NAME")
+    @Column(name="VALUE")
+    @CollectionTable(name="USER_FEDERATION_MAPPER_CONFIG", joinColumns={ @JoinColumn(name="USER_FEDERATION_MAPPER_ID") })
+    private Map<String, String> config;
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "REALM_ID")
+    private RealmEntity realm;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public UserFederationProviderEntity getFederationProvider() {
+        return federationProvider;
+    }
+
+    public void setFederationProvider(UserFederationProviderEntity federationProvider) {
+        this.federationProvider = federationProvider;
+    }
+
+    public String getFederationMapperType() {
+        return federationMapperType;
+    }
+
+    public void setFederationMapperType(String federationMapperType) {
+        this.federationMapperType = federationMapperType;
+    }
+
+    public Map<String, String> getConfig() {
+        return config;
+    }
+
+    public void setConfig(Map<String, String> config) {
+        this.config = config;
+    }
+
+    public RealmEntity getRealm() {
+        return realm;
+    }
+
+    public void setRealm(RealmEntity realm) {
+        this.realm = realm;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        UserFederationMapperEntity that = (UserFederationMapperEntity) o;
+
+        if (!id.equals(that.id)) return false;
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return id.hashCode();
+    }
+}
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 9a7eb97..6689a24 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
@@ -8,10 +8,13 @@ import org.keycloak.models.ClientModel;
 import org.keycloak.models.IdentityProviderMapperModel;
 import org.keycloak.models.IdentityProviderModel;
 import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ModelDuplicateException;
 import org.keycloak.models.PasswordPolicy;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RequiredCredentialModel;
 import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserFederationMapperModel;
+import org.keycloak.models.UserFederationProviderCreationEventImpl;
 import org.keycloak.models.UserFederationProviderModel;
 import org.keycloak.models.jpa.entities.AuthenticationExecutionEntity;
 import org.keycloak.models.jpa.entities.AuthenticationFlowEntity;
@@ -23,6 +26,7 @@ import org.keycloak.models.jpa.entities.RealmAttributeEntity;
 import org.keycloak.models.jpa.entities.RealmEntity;
 import org.keycloak.models.jpa.entities.RequiredCredentialEntity;
 import org.keycloak.models.jpa.entities.RoleEntity;
+import org.keycloak.models.jpa.entities.UserFederationMapperEntity;
 import org.keycloak.models.jpa.entities.UserFederationProviderEntity;
 import org.keycloak.models.utils.KeycloakModelUtils;
 
@@ -765,6 +769,8 @@ public class RealmAdapter implements RealmModel {
 
     @Override
     public UserFederationProviderModel addUserFederationProvider(String providerName, Map<String, String> config, int priority, String displayName, int fullSyncPeriod, int changedSyncPeriod, int lastSync) {
+        KeycloakModelUtils.ensureUniqueDisplayName(displayName, null, getUserFederationProviders());
+
         String id = KeycloakModelUtils.generateId();
         UserFederationProviderEntity entity = new UserFederationProviderEntity();
         entity.setId(id);
@@ -782,7 +788,9 @@ public class RealmAdapter implements RealmModel {
         em.persist(entity);
         realm.getUserFederationProviders().add(entity);
         em.flush();
-        return new UserFederationProviderModel(entity.getId(), providerName, config, priority, displayName, fullSyncPeriod, changedSyncPeriod, lastSync);
+        UserFederationProviderModel providerModel = new UserFederationProviderModel(entity.getId(), providerName, config, priority, displayName, fullSyncPeriod, changedSyncPeriod, lastSync);
+        session.getKeycloakSessionFactory().publish(new UserFederationProviderCreationEventImpl(this, providerModel));
+        return providerModel;
     }
 
     @Override
@@ -791,7 +799,14 @@ public class RealmAdapter implements RealmModel {
         while (it.hasNext()) {
             UserFederationProviderEntity entity = it.next();
             if (entity.getId().equals(provider.getId())) {
+
                 session.users().preRemove(this, provider);
+
+                Set<UserFederationMapperEntity> mappers = getUserFederationMapperEntitiesByFederationProvider(provider.getId());
+                for (UserFederationMapperEntity mapper : mappers) {
+                    realm.getUserFederationMappers().remove(mapper);
+                    em.remove(mapper);
+                }
                 it.remove();
                 em.remove(entity);
                 return;
@@ -800,6 +815,8 @@ public class RealmAdapter implements RealmModel {
     }
     @Override
     public void updateUserFederationProvider(UserFederationProviderModel model) {
+        KeycloakModelUtils.ensureUniqueDisplayName(model.getDisplayName(), model, getUserFederationProviders());
+
         Iterator<UserFederationProviderEntity> it = realm.getUserFederationProviders().iterator();
         while (it.hasNext()) {
             UserFederationProviderEntity entity = it.next();
@@ -822,6 +839,9 @@ public class RealmAdapter implements RealmModel {
 
     @Override
     public void setUserFederationProviders(List<UserFederationProviderModel> providers) {
+        for (UserFederationProviderModel currentProvider : providers) {
+            KeycloakModelUtils.ensureUniqueDisplayName(currentProvider.getDisplayName(), currentProvider, providers);
+        }
 
         Iterator<UserFederationProviderEntity> it = realm.getUserFederationProviders().iterator();
         while (it.hasNext()) {
@@ -866,8 +886,13 @@ public class RealmAdapter implements RealmModel {
 
         for (UserFederationProviderModel model : add) {
             UserFederationProviderEntity entity = new UserFederationProviderEntity();
-            if (model.getId() != null) entity.setId(model.getId());
-            else entity.setId(KeycloakModelUtils.generateId());
+            if (model.getId() != null) {
+                entity.setId(model.getId());
+            } else {
+                String id = KeycloakModelUtils.generateId();
+                entity.setId(id);
+                model.setId(id);
+            }
             entity.setConfig(model.getConfig());
             entity.setPriority(model.getPriority());
             entity.setProviderName(model.getProviderName());
@@ -882,8 +907,17 @@ public class RealmAdapter implements RealmModel {
             entity.setLastSync(model.getLastSync());
             em.persist(entity);
             realm.getUserFederationProviders().add(entity);
+            session.getKeycloakSessionFactory().publish(new UserFederationProviderCreationEventImpl(this, model));
+        }
+    }
 
+    protected UserFederationProviderEntity getUserFederationProviderEntityById(String federationProviderId) {
+        for (UserFederationProviderEntity entity : realm.getUserFederationProviders()) {
+            if (entity.getId().equals(federationProviderId)) {
+                return entity;
+            }
         }
+        return null;
     }
 
     @Override
@@ -1230,16 +1264,7 @@ public class RealmAdapter implements RealmModel {
     public Set<IdentityProviderMapperModel> getIdentityProviderMappers() {
         Set<IdentityProviderMapperModel> mappings = new HashSet<IdentityProviderMapperModel>();
         for (IdentityProviderMapperEntity entity : this.realm.getIdentityProviderMappers()) {
-            IdentityProviderMapperModel mapping = new IdentityProviderMapperModel();
-            mapping.setId(entity.getId());
-            mapping.setName(entity.getName());
-            mapping.setIdentityProviderAlias(entity.getIdentityProviderAlias());
-            mapping.setIdentityProviderMapper(entity.getIdentityProviderMapper());
-            Map<String, String> config = new HashMap<String, String>();
-            if (entity.getConfig() != null) {
-                config.putAll(entity.getConfig());
-            }
-            mapping.setConfig(config);
+            IdentityProviderMapperModel mapping = entityToModel(entity);
             mappings.add(mapping);
         }
         return mappings;
@@ -1252,16 +1277,7 @@ public class RealmAdapter implements RealmModel {
             if (!entity.getIdentityProviderAlias().equals(brokerAlias)) {
                 continue;
             }
-            IdentityProviderMapperModel mapping = new IdentityProviderMapperModel();
-            mapping.setId(entity.getId());
-            mapping.setName(entity.getName());
-            mapping.setIdentityProviderAlias(entity.getIdentityProviderAlias());
-            mapping.setIdentityProviderMapper(entity.getIdentityProviderMapper());
-            Map<String, String> config = new HashMap<String, String>();
-            if (entity.getConfig() != null) {
-                config.putAll(entity.getConfig());
-            }
-            mapping.setConfig(config);
+            IdentityProviderMapperModel mapping = entityToModel(entity);
             mappings.add(mapping);
         }
         return mappings;
@@ -1270,7 +1286,7 @@ public class RealmAdapter implements RealmModel {
     @Override
     public IdentityProviderMapperModel addIdentityProviderMapper(IdentityProviderMapperModel model) {
         if (getIdentityProviderMapperByName(model.getIdentityProviderAlias(), model.getIdentityProviderMapper()) != null) {
-            throw new RuntimeException("protocol mapper name must be unique per protocol");
+            throw new RuntimeException("identity provider mapper name must be unique per identity provider");
         }
         String id = KeycloakModelUtils.generateId();
         IdentityProviderMapperEntity entity = new IdentityProviderMapperEntity();
@@ -1358,6 +1374,125 @@ public class RealmAdapter implements RealmModel {
     }
 
     @Override
+    public Set<UserFederationMapperModel> getUserFederationMappers() {
+        Set<UserFederationMapperModel> mappers = new HashSet<UserFederationMapperModel>();
+        for (UserFederationMapperEntity entity : this.realm.getUserFederationMappers()) {
+            UserFederationMapperModel mapper = entityToModel(entity);
+            mappers.add(mapper);
+        }
+        return mappers;
+    }
+
+    @Override
+    public Set<UserFederationMapperModel> getUserFederationMappersByFederationProvider(String federationProviderId) {
+        Set<UserFederationMapperModel> mappers = new HashSet<UserFederationMapperModel>();
+        Set<UserFederationMapperEntity> mapperEntities = getUserFederationMapperEntitiesByFederationProvider(federationProviderId);
+        for (UserFederationMapperEntity entity : mapperEntities) {
+            UserFederationMapperModel mapper = entityToModel(entity);
+            mappers.add(mapper);
+        }
+        return mappers;
+    }
+
+    @Override
+    public UserFederationMapperModel addUserFederationMapper(UserFederationMapperModel model) {
+        if (getUserFederationMapperByName(model.getFederationProviderId(), model.getName()) != null) {
+            throw new ModelDuplicateException("User federation mapper must be unique per federation provider. There is already: " + model.getName());
+        }
+        String id = KeycloakModelUtils.generateId();
+        UserFederationMapperEntity entity = new UserFederationMapperEntity();
+        entity.setId(id);
+        entity.setName(model.getName());
+        entity.setFederationProvider(getUserFederationProviderEntityById(model.getFederationProviderId()));
+        entity.setFederationMapperType(model.getFederationMapperType());
+        entity.setRealm(this.realm);
+        entity.setConfig(model.getConfig());
+
+        em.persist(entity);
+        this.realm.getUserFederationMappers().add(entity);
+        return entityToModel(entity);
+    }
+
+    @Override
+    public void removeUserFederationMapper(UserFederationMapperModel mapper) {
+        UserFederationMapperEntity toDelete = getUserFederationMapperEntity(mapper.getId());
+        if (toDelete != null) {
+            this.realm.getUserFederationMappers().remove(toDelete);
+            em.remove(toDelete);
+        }
+    }
+
+    protected UserFederationMapperEntity getUserFederationMapperEntity(String id) {
+        for (UserFederationMapperEntity entity : this.realm.getUserFederationMappers()) {
+            if (entity.getId().equals(id)) {
+                return entity;
+            }
+        }
+        return null;
+
+    }
+
+    protected UserFederationMapperEntity getUserFederationMapperEntityByName(String federationProviderId, String name) {
+        for (UserFederationMapperEntity entity : this.realm.getUserFederationMappers()) {
+            if (federationProviderId.equals(entity.getFederationProvider().getId()) && entity.getName().equals(name)) {
+                return entity;
+            }
+        }
+        return null;
+
+    }
+
+    protected Set<UserFederationMapperEntity> getUserFederationMapperEntitiesByFederationProvider(String federationProviderId) {
+        Set<UserFederationMapperEntity> mappers = new HashSet<UserFederationMapperEntity>();
+        for (UserFederationMapperEntity entity : this.realm.getUserFederationMappers()) {
+            if (federationProviderId.equals(entity.getFederationProvider().getId())) {
+                mappers.add(entity);
+            }
+        }
+        return mappers;
+    }
+
+    @Override
+    public void updateUserFederationMapper(UserFederationMapperModel mapper) {
+        UserFederationMapperEntity entity = getUserFederationMapperEntity(mapper.getId());
+        entity.setFederationProvider(getUserFederationProviderEntityById(mapper.getFederationProviderId()));
+        entity.setFederationMapperType(mapper.getFederationMapperType());
+        if (entity.getConfig() == null) {
+            entity.setConfig(mapper.getConfig());
+        } else {
+            entity.getConfig().clear();
+            entity.getConfig().putAll(mapper.getConfig());
+        }
+        em.flush();
+    }
+
+    @Override
+    public UserFederationMapperModel getUserFederationMapperById(String id) {
+        UserFederationMapperEntity entity = getUserFederationMapperEntity(id);
+        if (entity == null) return null;
+        return entityToModel(entity);
+    }
+
+    @Override
+    public UserFederationMapperModel getUserFederationMapperByName(String federationProviderId, String name) {
+        UserFederationMapperEntity entity = getUserFederationMapperEntityByName(federationProviderId, name);
+        if (entity == null) return null;
+        return entityToModel(entity);
+    }
+
+    protected UserFederationMapperModel entityToModel(UserFederationMapperEntity entity) {
+        UserFederationMapperModel mapper = new UserFederationMapperModel();
+        mapper.setId(entity.getId());
+        mapper.setName(entity.getName());
+        mapper.setFederationProviderId(entity.getFederationProvider().getId());
+        mapper.setFederationMapperType(entity.getFederationMapperType());
+        Map<String, String> config = new HashMap<String, String>();
+        if (entity.getConfig() != null) config.putAll(entity.getConfig());
+        mapper.setConfig(config);
+        return mapper;
+    }
+
+    @Override
     public List<AuthenticationFlowModel> getAuthenticationFlows() {
         TypedQuery<AuthenticationFlowEntity> query = em.createNamedQuery("getAuthenticationFlowsByRealm", AuthenticationFlowEntity.class);
         query.setParameter("realm", realm);
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
index 7c9086b..977a4f5 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
@@ -404,12 +404,7 @@ public class UserAdapter implements UserModel {
     @Override
     public boolean hasRole(RoleModel role) {
         Set<RoleModel> roles = getRoleMappings();
-        if (roles.contains(role)) return true;
-
-        for (RoleModel mapping : roles) {
-            if (mapping.hasRole(role)) return true;
-        }
-        return false;
+        return KeycloakModelUtils.hasRole(roles, role);
     }
 
     protected TypedQuery<UserRoleMappingEntity> getUserRoleMappingEntityTypedQuery(RoleModel role) {
diff --git a/model/mongo/pom.xml b/model/mongo/pom.xml
index c0d9517..a8f4376 100755
--- a/model/mongo/pom.xml
+++ b/model/mongo/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
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 579aea0..9b57238 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
@@ -12,11 +12,14 @@ import org.keycloak.models.ClientModel;
 import org.keycloak.models.IdentityProviderMapperModel;
 import org.keycloak.models.IdentityProviderModel;
 import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ModelDuplicateException;
 import org.keycloak.models.PasswordPolicy;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RealmProvider;
 import org.keycloak.models.RequiredCredentialModel;
 import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserFederationMapperModel;
+import org.keycloak.models.UserFederationProviderCreationEventImpl;
 import org.keycloak.models.UserFederationProviderModel;
 import org.keycloak.models.entities.AuthenticationExecutionEntity;
 import org.keycloak.models.entities.AuthenticationFlowEntity;
@@ -24,6 +27,7 @@ import org.keycloak.models.entities.AuthenticatorEntity;
 import org.keycloak.models.entities.IdentityProviderEntity;
 import org.keycloak.models.entities.IdentityProviderMapperEntity;
 import org.keycloak.models.entities.RequiredCredentialEntity;
+import org.keycloak.models.entities.UserFederationMapperEntity;
 import org.keycloak.models.entities.UserFederationProviderEntity;
 import org.keycloak.models.mongo.keycloak.entities.MongoClientEntity;
 import org.keycloak.models.mongo.keycloak.entities.MongoRealmEntity;
@@ -838,6 +842,8 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
 
     @Override
     public UserFederationProviderModel addUserFederationProvider(String providerName, Map<String, String> config, int priority, String displayName, int fullSyncPeriod, int changedSyncPeriod, int lastSync) {
+        KeycloakModelUtils.ensureUniqueDisplayName(displayName, null, getUserFederationProviders());
+
         UserFederationProviderEntity entity = new UserFederationProviderEntity();
         entity.setId(KeycloakModelUtils.generateId());
         entity.setPriority(priority);
@@ -853,7 +859,9 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
         realm.getUserFederationProviders().add(entity);
         updateRealm();
 
-        return new UserFederationProviderModel(entity.getId(), providerName, config, priority, displayName, fullSyncPeriod, changedSyncPeriod, lastSync);
+        UserFederationProviderModel providerModel = new UserFederationProviderModel(entity.getId(), providerName, config, priority, displayName, fullSyncPeriod, changedSyncPeriod, lastSync);
+        session.getKeycloakSessionFactory().publish(new UserFederationProviderCreationEventImpl(this, providerModel));
+        return providerModel;
     }
 
     @Override
@@ -864,6 +872,12 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
             if (entity.getId().equals(provider.getId())) {
                 session.users().preRemove(this, new UserFederationProviderModel(entity.getId(), entity.getProviderName(), entity.getConfig(), entity.getPriority(), entity.getDisplayName(),
                         entity.getFullSyncPeriod(), entity.getChangedSyncPeriod(), entity.getLastSync()));
+
+                Set<UserFederationMapperEntity> mappers = getUserFederationMapperEntitiesByFederationProvider(provider.getId());
+                for (UserFederationMapperEntity mapper : mappers) {
+                    getMongoEntity().getUserFederationMappers().remove(mapper);
+                }
+
                 it.remove();
             }
         }
@@ -872,6 +886,8 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
 
     @Override
     public void updateUserFederationProvider(UserFederationProviderModel model) {
+        KeycloakModelUtils.ensureUniqueDisplayName(model.getDisplayName(), model, getUserFederationProviders());
+
         Iterator<UserFederationProviderEntity> it = realm.getUserFederationProviders().iterator();
         while (it.hasNext()) {
             UserFederationProviderEntity entity = it.next();
@@ -918,23 +934,33 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
 
     @Override
     public void setUserFederationProviders(List<UserFederationProviderModel> providers) {
+        for (UserFederationProviderModel currentProvider : providers) {
+            KeycloakModelUtils.ensureUniqueDisplayName(currentProvider.getDisplayName(), currentProvider, providers);
+        }
+
         List<UserFederationProviderEntity> entities = new LinkedList<UserFederationProviderEntity>();
         for (UserFederationProviderModel model : providers) {
             UserFederationProviderEntity entity = new UserFederationProviderEntity();
-            if (model.getId() != null) entity.setId(model.getId());
-            else entity.setId(KeycloakModelUtils.generateId());
+            if (model.getId() != null) {
+                entity.setId(model.getId());
+            } else {
+                String id = KeycloakModelUtils.generateId();
+                entity.setId(id);
+                model.setId(id);
+            }
             entity.setProviderName(model.getProviderName());
             entity.setConfig(model.getConfig());
             entity.setPriority(model.getPriority());
             String displayName = model.getDisplayName();
             if (displayName == null) {
-                entity.setDisplayName(entity.getId());
+                displayName = entity.getId();
             }
             entity.setDisplayName(displayName);
             entity.setFullSyncPeriod(model.getFullSyncPeriod());
             entity.setChangedSyncPeriod(model.getChangedSyncPeriod());
             entity.setLastSync(model.getLastSync());
             entities.add(entity);
+            session.getKeycloakSessionFactory().publish(new UserFederationProviderCreationEventImpl(this, model));
         }
 
         realm.setUserFederationProviders(entities);
@@ -1094,16 +1120,7 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
     public Set<IdentityProviderMapperModel> getIdentityProviderMappers() {
         Set<IdentityProviderMapperModel> mappings = new HashSet<IdentityProviderMapperModel>();
         for (IdentityProviderMapperEntity entity : getMongoEntity().getIdentityProviderMappers()) {
-            IdentityProviderMapperModel mapping = new IdentityProviderMapperModel();
-            mapping.setId(entity.getId());
-            mapping.setName(entity.getName());
-            mapping.setIdentityProviderAlias(entity.getIdentityProviderAlias());
-            mapping.setIdentityProviderMapper(entity.getIdentityProviderMapper());
-            Map<String, String> config = new HashMap<String, String>();
-            if (entity.getConfig() != null) {
-                config.putAll(entity.getConfig());
-            }
-            mapping.setConfig(config);
+            IdentityProviderMapperModel mapping = entityToModel(entity);
             mappings.add(mapping);
         }
         return mappings;
@@ -1116,16 +1133,7 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
             if (!entity.getIdentityProviderAlias().equals(brokerAlias)) {
                 continue;
             }
-            IdentityProviderMapperModel mapping = new IdentityProviderMapperModel();
-            mapping.setId(entity.getId());
-            mapping.setName(entity.getName());
-            mapping.setIdentityProviderAlias(entity.getIdentityProviderAlias());
-            mapping.setIdentityProviderMapper(entity.getIdentityProviderMapper());
-            Map<String, String> config = new HashMap<String, String>();
-            if (entity.getConfig() != null) {
-                config.putAll(entity.getConfig());
-            }
-            mapping.setConfig(config);
+            IdentityProviderMapperModel mapping = entityToModel(entity);
             mappings.add(mapping);
         }
         return mappings;
@@ -1134,7 +1142,7 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
     @Override
     public IdentityProviderMapperModel addIdentityProviderMapper(IdentityProviderMapperModel model) {
         if (getIdentityProviderMapperByName(model.getIdentityProviderAlias(), model.getIdentityProviderMapper()) != null) {
-            throw new RuntimeException("protocol mapper name must be unique per protocol");
+            throw new RuntimeException("identity provider mapper name must be unique per identity provider");
         }
         String id = KeycloakModelUtils.generateId();
         IdentityProviderMapperEntity entity = new IdentityProviderMapperEntity();
@@ -1174,9 +1182,8 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
         IdentityProviderMapperEntity toDelete = getIdentityProviderMapperEntity(mapping.getId());
         if (toDelete != null) {
             this.realm.getIdentityProviderMappers().remove(toDelete);
+            updateMongoEntity();
         }
-        updateMongoEntity();
-
     }
 
     @Override
@@ -1457,4 +1464,120 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
     }
 
 
+    @Override
+    public Set<UserFederationMapperModel> getUserFederationMappers() {
+        Set<UserFederationMapperModel> mappers = new HashSet<UserFederationMapperModel>();
+        for (UserFederationMapperEntity entity : getMongoEntity().getUserFederationMappers()) {
+            UserFederationMapperModel mapper = entityToModel(entity);
+            mappers.add(mapper);
+        }
+        return mappers;
+    }
+
+    @Override
+    public Set<UserFederationMapperModel> getUserFederationMappersByFederationProvider(String federationProviderId) {
+        Set<UserFederationMapperModel> mappers = new HashSet<UserFederationMapperModel>();
+        Set<UserFederationMapperEntity> mapperEntities = getUserFederationMapperEntitiesByFederationProvider(federationProviderId);
+        for (UserFederationMapperEntity entity : mapperEntities) {
+            mappers.add(entityToModel(entity));
+        }
+        return mappers;
+    }
+
+    @Override
+    public UserFederationMapperModel addUserFederationMapper(UserFederationMapperModel model) {
+        if (getUserFederationMapperByName(model.getFederationProviderId(), model.getName()) != null) {
+            throw new ModelDuplicateException("User federation mapper must be unique per federation provider. There is already: " + model.getName());
+        }
+        String id = KeycloakModelUtils.generateId();
+        UserFederationMapperEntity entity = new UserFederationMapperEntity();
+        entity.setId(id);
+        entity.setName(model.getName());
+        entity.setFederationProviderId(model.getFederationProviderId());
+        entity.setFederationMapperType(model.getFederationMapperType());
+        entity.setConfig(model.getConfig());
+
+        getMongoEntity().getUserFederationMappers().add(entity);
+        updateMongoEntity();
+        return entityToModel(entity);
+    }
+
+    protected UserFederationMapperEntity getUserFederationMapperEntity(String id) {
+        for (UserFederationMapperEntity entity : getMongoEntity().getUserFederationMappers()) {
+            if (entity.getId().equals(id)) {
+                return entity;
+            }
+        }
+        return null;
+
+    }
+
+    protected UserFederationMapperEntity getUserFederationMapperEntityByName(String federationProviderId, String name) {
+        for (UserFederationMapperEntity entity : getMongoEntity().getUserFederationMappers()) {
+            if (entity.getFederationProviderId().equals(federationProviderId) && entity.getName().equals(name)) {
+                return entity;
+            }
+        }
+        return null;
+
+    }
+
+    protected Set<UserFederationMapperEntity> getUserFederationMapperEntitiesByFederationProvider(String federationProviderId) {
+        Set<UserFederationMapperEntity> mappers = new HashSet<UserFederationMapperEntity>();
+        for (UserFederationMapperEntity entity : getMongoEntity().getUserFederationMappers()) {
+            if (federationProviderId.equals(entity.getFederationProviderId())) {
+                mappers.add(entity);
+            }
+        }
+        return mappers;
+    }
+
+    @Override
+    public void removeUserFederationMapper(UserFederationMapperModel mapper) {
+        UserFederationMapperEntity toDelete = getUserFederationMapperEntity(mapper.getId());
+        if (toDelete != null) {
+            this.realm.getUserFederationMappers().remove(toDelete);
+            updateMongoEntity();
+        }
+    }
+
+    @Override
+    public void updateUserFederationMapper(UserFederationMapperModel mapper) {
+        UserFederationMapperEntity entity = getUserFederationMapperEntity(mapper.getId());
+        entity.setFederationProviderId(mapper.getFederationProviderId());
+        entity.setFederationMapperType(mapper.getFederationMapperType());
+        if (entity.getConfig() == null) {
+            entity.setConfig(mapper.getConfig());
+        } else {
+            entity.getConfig().clear();
+            entity.getConfig().putAll(mapper.getConfig());
+        }
+        updateMongoEntity();
+    }
+
+    @Override
+    public UserFederationMapperModel getUserFederationMapperById(String id) {
+        UserFederationMapperEntity entity = getUserFederationMapperEntity(id);
+        if (entity == null) return null;
+        return entityToModel(entity);
+    }
+
+    @Override
+    public UserFederationMapperModel getUserFederationMapperByName(String federationProviderId, String name) {
+        UserFederationMapperEntity entity = getUserFederationMapperEntityByName(federationProviderId, name);
+        if (entity == null) return null;
+        return entityToModel(entity);
+    }
+
+    protected UserFederationMapperModel entityToModel(UserFederationMapperEntity entity) {
+        UserFederationMapperModel mapper = new UserFederationMapperModel();
+        mapper.setId(entity.getId());
+        mapper.setName(entity.getName());
+        mapper.setFederationProviderId(entity.getFederationProviderId());
+        mapper.setFederationMapperType(entity.getFederationMapperType());
+        Map<String, String> config = new HashMap<String, String>();
+        if (entity.getConfig() != null) config.putAll(entity.getConfig());
+        mapper.setConfig(config);
+        return mapper;
+    }
 }
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
index f7895f2..813faab 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
@@ -23,6 +23,7 @@ import org.keycloak.models.mongo.keycloak.entities.MongoRoleEntity;
 import org.keycloak.models.mongo.keycloak.entities.MongoUserConsentEntity;
 import org.keycloak.models.mongo.keycloak.entities.MongoUserEntity;
 import org.keycloak.models.mongo.utils.MongoModelUtils;
+import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.models.utils.Pbkdf2PasswordEncoder;
 import org.keycloak.util.Time;
 
@@ -374,12 +375,7 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
     @Override
     public boolean hasRole(RoleModel role) {
         Set<RoleModel> roles = getRoleMappings();
-        if (roles.contains(role)) return true;
-
-        for (RoleModel mapping : roles) {
-            if (mapping.hasRole(role)) return true;
-        }
-        return false;
+        return KeycloakModelUtils.hasRole(roles, role);
     }
 
     @Override

model/pom.xml 2(+1 -1)

diff --git a/model/pom.xml b/model/pom.xml
index 5620af4..2e0bb73 100755
--- a/model/pom.xml
+++ b/model/pom.xml
@@ -3,7 +3,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <name>Model Parent</name>
diff --git a/model/sessions-infinispan/pom.xml b/model/sessions-infinispan/pom.xml
index c3b932e..56b15c5 100755
--- a/model/sessions-infinispan/pom.xml
+++ b/model/sessions-infinispan/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/LoginFailureEntity.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/LoginFailureEntity.java
index e742bbc..8bb05e7 100644
--- a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/LoginFailureEntity.java
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/LoginFailureEntity.java
@@ -1,9 +1,11 @@
 package org.keycloak.models.sessions.infinispan.entities;
 
+import java.io.Serializable;
+
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
-public class LoginFailureEntity {
+public class LoginFailureEntity implements Serializable {
 
     private String username;
     private String realm;
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/LoginFailureKey.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/LoginFailureKey.java
index f8dbe87..69d2e05 100644
--- a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/LoginFailureKey.java
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/LoginFailureKey.java
@@ -1,9 +1,11 @@
 package org.keycloak.models.sessions.infinispan.entities;
 
+import java.io.Serializable;
+
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
-public class LoginFailureKey {
+public class LoginFailureKey implements Serializable {
 
     private final String realm;
     private final String username;
diff --git a/model/sessions-jpa/pom.xml b/model/sessions-jpa/pom.xml
index a04814e..f8f5289 100755
--- a/model/sessions-jpa/pom.xml
+++ b/model/sessions-jpa/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/model/sessions-mem/pom.xml b/model/sessions-mem/pom.xml
index 1db9ab2..86b9559 100755
--- a/model/sessions-mem/pom.xml
+++ b/model/sessions-mem/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/model/sessions-mongo/pom.xml b/model/sessions-mongo/pom.xml
index 735dcd9..b856155 100755
--- a/model/sessions-mongo/pom.xml
+++ b/model/sessions-mongo/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>

pom.xml 84(+44 -40)

diff --git a/pom.xml b/pom.xml
index 796918a..5b846b3 100755
--- a/pom.xml
+++ b/pom.xml
@@ -14,7 +14,7 @@
     </description>
     <groupId>org.keycloak</groupId>
     <artifactId>keycloak-parent</artifactId>
-    <version>1.3.0.Beta1-SNAPSHOT</version>
+    <version>1.3.0.Final-SNAPSHOT</version>
     <packaging>pom</packaging>
 
     <properties>
@@ -52,7 +52,12 @@
         <wildfly.version>9.0.0.CR1</wildfly.version>
         <wildfly.core.version>1.0.0.CR1</wildfly.core.version>
         <wildfly.build-tools.version>1.0.0.Alpha8</wildfly.build-tools.version>
-        <jboss.version>7.1.1.Final</jboss.version>
+
+        <!-- this is EAP 6.4 alpha, publicly available -->
+        <jboss.version>7.5.0.Final-redhat-15</jboss.version>
+
+        <!--jboss.version>7.1.1.Final</jboss.version-->
+
         <servlet.api.30.version>1.0.2.Final</servlet.api.30.version>
         <google.zxing.version>2.2</google.zxing.version>
         <google.client.version>1.14.1-beta</google.client.version>
@@ -1130,6 +1135,12 @@
             </dependency>
             <dependency>
                 <groupId>org.keycloak</groupId>
+                <artifactId>keycloak-server-feature-pack</artifactId>
+                    <version>${project.version}</version>
+                    <type>zip</type>
+            </dependency>
+            <dependency>
+                <groupId>org.keycloak</groupId>
                 <artifactId>keycloak-testsuite-integration</artifactId>
                 <version>${project.version}</version>
             </dependency>
@@ -1242,44 +1253,6 @@
                     <artifactId>liquibase-maven-plugin</artifactId>
                     <version>${liquibase.version}</version>
                 </plugin>
-                <!--plugin>
-                    <groupId>org.codehaus.mojo</groupId>
-                    <artifactId>buildnumber-maven-plugin</artifactId>
-                    <version>1.3</version>
-                </plugin>
-                <plugin>
-                    <groupId>org.apache.felix</groupId>
-                    <artifactId>maven-bundle-plugin</artifactId>
-                    <version>2.3.7</version>
-                </plugin>
-                <plugin>
-                    <groupId>org.codehaus.mojo</groupId>
-                    <artifactId>build-helper-maven-plugin</artifactId>
-                    <version>1.7</version>
-                </plugin>
-                <plugin>
-                    <groupId>org.apache.maven.plugins</groupId>
-                    <artifactId>maven-resources-plugin</artifactId>
-                    <version>2.6</version>
-                    <configuration>
-                        <encoding>${project.build.sourceEncoding}</encoding>
-                        <nonFilteredFileExtensions>
-                          <nonFilteredFileExtension>jar</nonFilteredFileExtension>
-                          <nonFilteredFileExtension>war</nonFilteredFileExtension>
-                          <nonFilteredFileExtension>ear</nonFilteredFileExtension>
-                          <nonFilteredFileExtension>pdf</nonFilteredFileExtension>
-                          <nonFilteredFileExtension>swf</nonFilteredFileExtension>
-                          <nonFilteredFileExtension>zip</nonFilteredFileExtension>
-                          <nonFilteredFileExtension>bz2</nonFilteredFileExtension>
-                          <nonFilteredFileExtension>gz</nonFilteredFileExtension>
-                          <nonFilteredFileExtension>acp</nonFilteredFileExtension>
-                          <nonFilteredFileExtension>bin</nonFilteredFileExtension>
-                          <nonFilteredFileExtension>odt</nonFilteredFileExtension>
-                          <nonFilteredFileExtension>doc</nonFilteredFileExtension>
-                          <nonFilteredFileExtension>xls</nonFilteredFileExtension>
-                        </nonFilteredFileExtensions>
-                    </configuration>
-                </plugin-->
                 <plugin>
                     <groupId>org.wildfly.build</groupId>
                     <artifactId>wildfly-feature-pack-build-maven-plugin</artifactId>
@@ -1295,6 +1268,37 @@
     </build>
 
     <profiles>
+        <!-- Configure the JBoss Early Access Maven repository -->
+        <profile>
+            <id>jboss-earlyaccess-repository</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <repositories>
+                <repository>
+                    <id>jboss-earlyaccess-repository</id>
+                    <url>http://maven.repository.redhat.com/earlyaccess/all/</url>
+                    <releases>
+                        <enabled>true</enabled>
+                    </releases>
+                    <snapshots>
+                        <enabled>false</enabled>
+                    </snapshots>
+                </repository>
+            </repositories>
+            <pluginRepositories>
+                <pluginRepository>
+                    <id>jboss-earlyaccess-plugin-repository</id>
+                    <url>http://maven.repository.redhat.com/earlyaccess/all/</url>
+                    <releases>
+                        <enabled>true</enabled>
+                    </releases>
+                    <snapshots>
+                        <enabled>false</enabled>
+                    </snapshots>
+                </pluginRepository>
+            </pluginRepositories>
+        </profile>
         <profile>
             <id>distribution</id>
             <modules>
diff --git a/proxy/launcher/pom.xml b/proxy/launcher/pom.xml
index 0a79fa2..5108980 100755
--- a/proxy/launcher/pom.xml
+++ b/proxy/launcher/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>

proxy/pom.xml 2(+1 -1)

diff --git a/proxy/pom.xml b/proxy/pom.xml
index 7213767..3ca449f 100755
--- a/proxy/pom.xml
+++ b/proxy/pom.xml
@@ -3,7 +3,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <name>Model Parent</name>
diff --git a/proxy/proxy-server/pom.xml b/proxy/proxy-server/pom.xml
index 734311f..0ea7a76 100755
--- a/proxy/proxy-server/pom.xml
+++ b/proxy/proxy-server/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>

saml/pom.xml 2(+1 -1)

diff --git a/saml/pom.xml b/saml/pom.xml
index 00f4cbc..2fb6a88 100755
--- a/saml/pom.xml
+++ b/saml/pom.xml
@@ -3,7 +3,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <name>Keycloak SAML Integration</name>
diff --git a/saml/saml-core/pom.xml b/saml/saml-core/pom.xml
index bb85f5f..0254ab6 100755
--- a/saml/saml-core/pom.xml
+++ b/saml/saml-core/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/saml/saml-protocol/pom.xml b/saml/saml-protocol/pom.xml
index d9d8622..d7b3ebb 100755
--- a/saml/saml-protocol/pom.xml
+++ b/saml/saml-protocol/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>

services/pom.xml 2(+1 -1)

diff --git a/services/pom.xml b/services/pom.xml
index 1ca2621..e39e846 100755
--- a/services/pom.xml
+++ b/services/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/services/src/main/java/org/keycloak/messages/MessagesProvider.java b/services/src/main/java/org/keycloak/messages/MessagesProvider.java
new file mode 100644
index 0000000..ddb72dd
--- /dev/null
+++ b/services/src/main/java/org/keycloak/messages/MessagesProvider.java
@@ -0,0 +1,12 @@
+package org.keycloak.messages;
+
+import org.keycloak.provider.Provider;
+
+/**
+ * @author <a href="mailto:leonardo.zanivan@gmail.com">Leonardo Zanivan</a>
+ */
+public interface MessagesProvider extends Provider {
+
+    String getMessage(String messageKey, Object... parameters);
+
+}
diff --git a/services/src/main/java/org/keycloak/messages/MessagesProviderFactory.java b/services/src/main/java/org/keycloak/messages/MessagesProviderFactory.java
new file mode 100644
index 0000000..92c0df8
--- /dev/null
+++ b/services/src/main/java/org/keycloak/messages/MessagesProviderFactory.java
@@ -0,0 +1,10 @@
+package org.keycloak.messages;
+
+import org.keycloak.provider.ProviderFactory;
+
+/**
+ * @author <a href="mailto:leonardo.zanivan@gmail.com">Leonardo Zanivan</a>
+ */
+public interface MessagesProviderFactory extends ProviderFactory<MessagesProvider> {
+
+}
diff --git a/services/src/main/java/org/keycloak/messages/MessagesSpi.java b/services/src/main/java/org/keycloak/messages/MessagesSpi.java
new file mode 100644
index 0000000..6e82006
--- /dev/null
+++ b/services/src/main/java/org/keycloak/messages/MessagesSpi.java
@@ -0,0 +1,32 @@
+package org.keycloak.messages;
+
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderFactory;
+import org.keycloak.provider.Spi;
+
+/**
+ * @author <a href="mailto:leonardo.zanivan@gmail.com">Leonardo Zanivan</a>
+ */
+public class MessagesSpi implements Spi {
+
+    @Override
+    public boolean isPrivate() {
+        return true;
+    }
+
+    @Override
+    public String getName() {
+        return "messages";
+    }
+
+    @Override
+    public Class<? extends Provider> getProviderClass() {
+        return MessagesProvider.class;
+    }
+
+    @Override
+    public Class<? extends ProviderFactory> getProviderFactoryClass() {
+        return MessagesProviderFactory.class;
+    }
+
+}
diff --git a/services/src/main/java/org/keycloak/services/managers/UsersSyncManager.java b/services/src/main/java/org/keycloak/services/managers/UsersSyncManager.java
index 9761b72..bf29b78 100755
--- a/services/src/main/java/org/keycloak/services/managers/UsersSyncManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/UsersSyncManager.java
@@ -8,6 +8,7 @@ import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserFederationProvider;
 import org.keycloak.models.UserFederationProviderFactory;
 import org.keycloak.models.UserFederationProviderModel;
+import org.keycloak.models.UserFederationSyncResult;
 import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.timer.TimerProvider;
 import org.keycloak.util.Time;
@@ -43,19 +44,19 @@ public class UsersSyncManager {
         });
     }
 
-    public void syncAllUsers(final KeycloakSessionFactory sessionFactory, String realmId, final UserFederationProviderModel fedProvider) {
+    public UserFederationSyncResult syncAllUsers(final KeycloakSessionFactory sessionFactory, String realmId, final UserFederationProviderModel fedProvider) {
         final UserFederationProviderFactory fedProviderFactory = (UserFederationProviderFactory) sessionFactory.getProviderFactory(UserFederationProvider.class, fedProvider.getProviderName());
         updateLastSyncInterval(sessionFactory, fedProvider, realmId);
-        fedProviderFactory.syncAllUsers(sessionFactory, realmId, fedProvider);
+        return fedProviderFactory.syncAllUsers(sessionFactory, realmId, fedProvider);
     }
 
-    public void syncChangedUsers(final KeycloakSessionFactory sessionFactory, String realmId, final UserFederationProviderModel fedProvider) {
+    public UserFederationSyncResult syncChangedUsers(final KeycloakSessionFactory sessionFactory, String realmId, final UserFederationProviderModel fedProvider) {
         final UserFederationProviderFactory fedProviderFactory = (UserFederationProviderFactory) sessionFactory.getProviderFactory(UserFederationProvider.class, fedProvider.getProviderName());
 
         // See when we did last sync.
         int oldLastSync = fedProvider.getLastSync();
         updateLastSyncInterval(sessionFactory, fedProvider, realmId);
-        fedProviderFactory.syncChangedUsers(sessionFactory, realmId, fedProvider, Time.toDate(oldLastSync));
+        return fedProviderFactory.syncChangedUsers(sessionFactory, realmId, fedProvider, Time.toDate(oldLastSync));
     }
 
     public void refreshPeriodicSyncForProvider(final KeycloakSessionFactory sessionFactory, TimerProvider timer, final UserFederationProviderModel fedProvider, final String realmId) {
diff --git a/services/src/main/java/org/keycloak/services/messages/AdminMessagesProvider.java b/services/src/main/java/org/keycloak/services/messages/AdminMessagesProvider.java
new file mode 100644
index 0000000..1da7bfb
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/messages/AdminMessagesProvider.java
@@ -0,0 +1,59 @@
+package org.keycloak.services.messages;
+
+import java.io.IOException;
+import java.net.URL;
+import java.text.MessageFormat;
+import java.util.Locale;
+import java.util.Properties;
+import org.jboss.logging.Logger;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.messages.MessagesProvider;
+
+/**
+ * @author <a href="mailto:leonardo.zanivan@gmail.com">Leonardo Zanivan</a>
+ */
+public class AdminMessagesProvider implements MessagesProvider {
+
+    private static final Logger logger = Logger.getLogger(AdminMessagesProvider.class);
+
+    private KeycloakSession session;
+    private Locale locale;
+    private Properties messagesBundle;
+
+    public AdminMessagesProvider(KeycloakSession session, Locale locale) {
+        this.session = session;
+        this.locale = locale;
+        this.messagesBundle = getMessagesBundle(locale);
+    }
+
+    @Override
+    public String getMessage(String messageKey, Object... parameters) {
+        String message = messagesBundle.getProperty(messageKey, messageKey);
+        return new MessageFormat(message, locale).format(parameters);
+    }
+
+    @Override
+    public void close() {
+    }
+
+    private Properties getMessagesBundle(Locale locale) {
+        Properties properties = new Properties();
+
+        if (locale == null) {
+            return properties;
+        }
+
+        URL url = getClass().getClassLoader().getResource(
+                "theme/base/admin/messages/messages_" + locale.toString() + ".properties");
+        if (url != null) {
+            try {
+                properties.load(url.openStream());
+            } catch (IOException ex) {
+                logger.warn("Failed to load messages", ex);
+            }
+        }
+
+        return properties;
+    }
+
+}
diff --git a/services/src/main/java/org/keycloak/services/messages/AdminMessagesProviderFactory.java b/services/src/main/java/org/keycloak/services/messages/AdminMessagesProviderFactory.java
new file mode 100644
index 0000000..7fda4fd
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/messages/AdminMessagesProviderFactory.java
@@ -0,0 +1,37 @@
+package org.keycloak.services.messages;
+
+import java.util.Locale;
+import org.keycloak.Config;
+import org.keycloak.messages.MessagesProvider;
+import org.keycloak.messages.MessagesProviderFactory;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+
+/**
+ * @author <a href="mailto:leonardo.zanivan@gmail.com">Leonardo Zanivan</a>
+ */
+public class AdminMessagesProviderFactory implements MessagesProviderFactory {
+
+    @Override
+    public MessagesProvider create(KeycloakSession session) {
+        return new AdminMessagesProvider(session, Locale.ENGLISH);
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+    }
+
+    @Override
+    public void close() {
+    }
+
+    @Override
+    public String getId() {
+        return "admin";
+    }
+
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AdminEventBuilder.java b/services/src/main/java/org/keycloak/services/resources/admin/AdminEventBuilder.java
index a0c15f3..b9d2036 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/AdminEventBuilder.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/AdminEventBuilder.java
@@ -25,6 +25,8 @@ import org.keycloak.representations.idm.IdentityProviderRepresentation;
 import org.keycloak.util.JsonSerialization;
 import org.keycloak.util.Time;
 
+import javax.ws.rs.core.UriInfo;
+
 public class AdminEventBuilder {
     
     private static final Logger log = Logger.getLogger(AdminEventBuilder.class);
@@ -92,18 +94,6 @@ public class AdminEventBuilder {
         return this;
     }
 
-    public AdminEventBuilder authRealm(String realmId) {
-        AuthDetails authDetails = adminEvent.getAuthDetails();
-        if(authDetails == null) {
-            authDetails =  new AuthDetails();
-            authDetails.setRealmId(realmId);
-        } else {
-            authDetails.setRealmId(realmId);
-        }
-        adminEvent.setAuthDetails(authDetails);
-        return this;
-    }
-
     public AdminEventBuilder authClient(ClientModel client) {
         AuthDetails authDetails = adminEvent.getAuthDetails();
         if(authDetails == null) {
@@ -116,18 +106,6 @@ public class AdminEventBuilder {
         return this;
     }
 
-    public AdminEventBuilder authClient(String clientId) {
-        AuthDetails authDetails = adminEvent.getAuthDetails();
-        if(authDetails == null) {
-            authDetails =  new AuthDetails();
-            authDetails.setClientId(clientId);
-        } else {
-            authDetails.setClientId(clientId);
-        }
-        adminEvent.setAuthDetails(authDetails);
-        return this;
-    }
-
     public AdminEventBuilder authUser(UserModel user) {
         AuthDetails authDetails = adminEvent.getAuthDetails();
         if(authDetails == null) {
@@ -140,18 +118,6 @@ public class AdminEventBuilder {
         return this;
     }
 
-    public AdminEventBuilder authUser(String userId) {
-        AuthDetails authDetails = adminEvent.getAuthDetails();
-        if(authDetails == null) {
-            authDetails =  new AuthDetails();
-            authDetails.setUserId(userId);
-        } else {
-            authDetails.setUserId(userId);
-        }
-        adminEvent.setAuthDetails(authDetails);
-        return this;
-    }
-
     public AdminEventBuilder authIpAddress(String ipAddress) {
         AuthDetails authDetails = adminEvent.getAuthDetails();
         if(authDetails == null) {
@@ -163,59 +129,45 @@ public class AdminEventBuilder {
         adminEvent.setAuthDetails(authDetails);
         return this;
     }
-    
-    public AdminEventBuilder resourcePath(String resourcePath) {
-        adminEvent.setResourcePath(resourcePath);
-        return this;
-    }
-    
-    public AdminEventBuilder resourcePath(String resourcePath, boolean segment) {
-        if(segment) {
-            int index = resourcePath.lastIndexOf('/');
-            int subIndex = resourcePath.lastIndexOf('/', index - 1);
-            adminEvent.setResourcePath(resourcePath.substring(subIndex));
-        } else {
-            adminEvent.setResourcePath(resourcePath.substring(resourcePath.lastIndexOf('/')));
-        }
-        return this;
-    }
-    
-    public AdminEventBuilder resourcePath(Object model) {
-        StringBuilder sb = new StringBuilder();
-        sb.append(getResourcePath(model));
-        adminEvent.setResourcePath(sb.toString());
-        return this;
-    }
-    
-    public AdminEventBuilder resourcePath(Object model, String resourcePath) {
-        StringBuilder sb = new StringBuilder();
-        sb.append(getResourcePath(model));
-        sb.append(resourcePath.substring(resourcePath.lastIndexOf('/')));
-        adminEvent.setResourcePath(sb.toString());
+
+    public AdminEventBuilder resourcePath(UriInfo uriInfo) {
+        String path = getResourcePath(uriInfo);
+        adminEvent.setResourcePath(path);
         return this;
     }
-    
-    public AdminEventBuilder resourcePath(Object model, String resourcePath, boolean segment) {
+
+    public AdminEventBuilder resourcePath(UriInfo uriInfo, String id) {
         StringBuilder sb = new StringBuilder();
-        sb.append(getResourcePath(model));
-        int index = resourcePath.lastIndexOf('/');
-        int subIndex = resourcePath.lastIndexOf('/', index - 1);
-        sb.append(resourcePath.substring(subIndex));
+        sb.append(getResourcePath(uriInfo));
+        sb.append("/");
+        sb.append(id);
         adminEvent.setResourcePath(sb.toString());
         return this;
     }
-    
-    public AdminEventBuilder resourcePath(Object model, Object subModel, String resourcePath) {
+
+    private String getResourcePath(UriInfo uriInfo) {
+        String path = uriInfo.getPath();
+
         StringBuilder sb = new StringBuilder();
-        sb.append(getResourcePath(model));
-        int index = resourcePath.lastIndexOf('/');
-        int subIndex = resourcePath.lastIndexOf('/', index - 1);
-        sb.append(resourcePath.substring(subIndex, index+1));
-        sb.append(getResourcePath(subModel));
-        adminEvent.setResourcePath(sb.toString());
-        return this;
+        sb.append("/realms/");
+        sb.append(realm.getName());
+        sb.append("/");
+        String realmRelative = sb.toString();
+
+        path = path.substring(path.indexOf(realmRelative) + realmRelative.length());
+
+        if (path.contains("clients-by-id")) {
+            path = path.replaceAll("clients-by-id", "clients");
+        } else if (path.contains("roles-by-id")) {
+            path = path.replaceAll("roles-by-id", "roles");
+        } else if (path.contains("role-mappings/realm")) {
+            path = path.replaceFirst("role-mappings/realm", "role-mappings");
+        } else if (path.contains("role-mappings/clients")) {
+            path = path.replaceFirst("role-mappings/clients", "role-mappings");
+        }
+
+        return path;
     }
-    
 
     public void error(String error) {
         adminEvent.setOperationType(OperationType.valueOf(adminEvent.getOperationType().name() + "_ERROR"));
@@ -268,47 +220,5 @@ public class AdminEventBuilder {
             }
         }
     }
-    
-    private String getResourcePath(Object model) {
-
-        StringBuilder sb = new StringBuilder();
-
-        if (model instanceof RealmModel) {
-            RealmModel realm = (RealmModel) model;
-            sb.append("realms/" + realm.getId());
-        } else if (model instanceof ClientModel) {
-            ClientModel client = (ClientModel) model;
-            sb.append("clients/" + client.getId());
-        } else if (model instanceof UserModel) {
-            UserModel user = (UserModel) model;
-            sb.append("users/" + user.getId());
-
-        } else if (model instanceof IdentityProviderModel) {
-            IdentityProviderModel provider = (IdentityProviderModel) model;
-            sb.append("identity-Providers/" + provider.getProviderId());
-        } else if (model instanceof IdentityProviderRepresentation) {
-            IdentityProviderRepresentation provider = (IdentityProviderRepresentation) model;
-            sb.append("identity-Providers/" + provider.getProviderId());
-        } else if (model instanceof IdentityProviderMapperModel) {
-            IdentityProviderMapperModel provider = (IdentityProviderMapperModel) model;
-            sb.append("identity-Provider-Mappers/" + provider.getId());
-        } else if (model instanceof IdentityProviderFactory) {
-            IdentityProviderFactory provider = (IdentityProviderFactory) model;
-            sb.append("identity-Provider-Factory/" + provider.getId());
-
-        } else if (model instanceof ProtocolMapperModel) {
-            ProtocolMapperModel mapper = (ProtocolMapperModel) model;
-            sb.append("protocol-Mappers/" + mapper.getId());
 
-        } else if (model instanceof UserFederationProviderModel) {
-            UserFederationProviderModel provider = (UserFederationProviderModel) model;
-            sb.append("user-Federation-Providers/" + provider.getId());
-        
-        } else if (model instanceof RoleModel) {
-            RoleModel role = (RoleModel) model;
-            sb.append("roles/" + role.getId());
-        }
-
-        return sb.toString();
-    }
 }
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java
index 3b91e3e..c43f6d9 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java
@@ -138,9 +138,8 @@ public class ClientAttributeCertificateResource {
         info.setCertificate(client.getAttribute(certificateAttribute));
         info.setPrivateKey(client.getAttribute(privateAttribute));
         
-        adminEvent.operation(OperationType.ACTION)
-        .resourcePath(client, session.getContext().getUri().getPath()).representation(info).success();
-        
+        adminEvent.operation(OperationType.ACTION).resourcePath(session.getContext().getUri()).representation(info).success();
+
         return info;
     }
 
@@ -198,7 +197,7 @@ public class ClientAttributeCertificateResource {
             info.setCertificate(certPem);
         }
         
-        adminEvent.operation(OperationType.ACTION).resourcePath(client, uriInfo.getPath()).representation(info).success();
+        adminEvent.operation(OperationType.ACTION).resourcePath(session.getContext().getUri()).representation(info).success();
         return info;
     }
 
@@ -324,10 +323,6 @@ public class ClientAttributeCertificateResource {
             stream.flush();
             stream.close();
             byte[] rtn = stream.toByteArray();
-            
-            adminEvent.operation(OperationType.ACTION)
-            .resourcePath(client, session.getContext().getUri().getPath()).success();
-            
             return rtn;
         } catch (Exception e) {
             throw new RuntimeException(e);
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
index d9ab8dc..2a9bcfc 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
@@ -102,7 +102,7 @@ public class ClientResource {
 
         try {
             RepresentationToModel.updateClient(rep, client);
-            adminEvent.operation(OperationType.UPDATE).resourcePath(client).representation(rep).success();
+            adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo).representation(rep).success();
             return Response.noContent().build();
         } catch (ModelDuplicateException e) {
             return ErrorResponse.exists("Client " + rep.getClientId() + " already exists");
@@ -149,8 +149,6 @@ public class ClientResource {
 
         ClientManager clientManager = new ClientManager(new RealmManager(session));
         Object rep = clientManager.toInstallationRepresentation(realm, client, getKeycloakApplication().getBaseUri(uriInfo));
-        
-        adminEvent.operation(OperationType.ACTION).resourcePath(client, uriInfo.getPath(), true).success();
 
         // TODO Temporary solution to pretty-print
         return JsonSerialization.mapper.writerWithDefaultPrettyPrinter().writeValueAsString(rep);
@@ -170,9 +168,6 @@ public class ClientResource {
         auth.requireView();
 
         ClientManager clientManager = new ClientManager(new RealmManager(session));
-
-        adminEvent.operation(OperationType.ACTION).resourcePath(client, uriInfo.getPath(), true).success();
-
         return clientManager.toJBossSubsystemConfig(realm, client, getKeycloakApplication().getBaseUri(uriInfo));
     }
 
@@ -185,7 +180,7 @@ public class ClientResource {
     public void deleteClient() {
         auth.requireManage();
         new ClientManager(new RealmManager(session)).removeClient(realm, client);
-        adminEvent.operation(OperationType.DELETE).resourcePath(client).success();
+        adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo).success();
     }
 
 
@@ -204,7 +199,7 @@ public class ClientResource {
         logger.debug("regenerateSecret");
         UserCredentialModel cred = KeycloakModelUtils.generateSecret(client);
         CredentialRepresentation rep = ModelToRepresentation.toRepresentation(cred);
-        adminEvent.operation(OperationType.ACTION).resourcePath(client, uriInfo.getPath()).representation(rep).success();
+        adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).representation(rep).success();
         return rep;
     }
 
@@ -238,7 +233,7 @@ public class ClientResource {
 
     @Path("roles")
     public RoleContainerResource getRoleContainerResource() {
-        return new RoleContainerResource(realm, auth, client, adminEvent);
+        return new RoleContainerResource(uriInfo, realm, auth, client, adminEvent);
     }
 
     /**
@@ -271,7 +266,7 @@ public class ClientResource {
         auth.requireManage();
 
         client.setWebOrigins(allowedOrigins);
-        adminEvent.operation(OperationType.UPDATE).resourcePath(client, uriInfo.getPath()).representation(client).success();
+        adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo).representation(client).success();
     }
 
     /**
@@ -290,7 +285,7 @@ public class ClientResource {
         for (String origin : allowedOrigins) {
             client.removeWebOrigin(origin);
         }
-        adminEvent.operation(OperationType.DELETE).resourcePath(client, uriInfo.getPath()).success();
+        adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo).success();
     }
 
     /**
@@ -301,8 +296,8 @@ public class ClientResource {
     @POST
     public GlobalRequestResult pushRevocation() {
         auth.requireManage();
-        adminEvent.operation(OperationType.ACTION).resourcePath(client, uriInfo.getPath()).success();
-        return new ResourceAdminManager(session).pushClientRevocationPolicy(uriInfo.getRequestUri(), realm, client);
+        adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
+        return new ResourceAdminManager(session).pushClientRevocationPolicy(uriInfo.getRequestUri(), realm, client);
     
     }
     
@@ -355,9 +350,9 @@ public class ClientResource {
     @POST
     public GlobalRequestResult logoutAll() {
         auth.requireManage();
-        adminEvent.operation(OperationType.ACTION).resourcePath(client, uriInfo.getPath()).success();
+        adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
         return new ResourceAdminManager(session).logoutClient(uriInfo.getRequestUri(), realm, client);
-
+
     }
 
     /**
@@ -372,9 +367,9 @@ public class ClientResource {
         if (user == null) {
             throw new NotFoundException("User not found");
         }
-        adminEvent.operation(OperationType.ACTION).resourcePath(client, uriInfo.getPath(), true).success();
-        new ResourceAdminManager(session).logoutUserFromClient(uriInfo.getRequestUri(), realm, client, user);
-
+        adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
+        new ResourceAdminManager(session).logoutUserFromClient(uriInfo.getRequestUri(), realm, client, user);
+
     }
 
     /**
@@ -394,7 +389,7 @@ public class ClientResource {
         }
         if (logger.isDebugEnabled()) logger.debug("Register node: " + node);
         client.registerNode(node, Time.currentTime());
-        adminEvent.operation(OperationType.ACTION).resourcePath(client, uriInfo.getPath()).success();
+        adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
     }
 
     /**
@@ -414,7 +409,7 @@ public class ClientResource {
             throw new NotFoundException("Client does not have a node " + node);
         }
         client.unregisterNode(node);
-        adminEvent.operation(OperationType.DELETE).resourcePath(client, uriInfo.getPath(), true).success();
+        adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo).success();
     }
 
     /**
@@ -427,10 +422,10 @@ public class ClientResource {
     @NoCache
     public GlobalRequestResult testNodesAvailable() {
         auth.requireManage();
-        logger.debug("Test availability of cluster nodes");
-        adminEvent.operation(OperationType.ACTION).resourcePath(client, uriInfo.getPath()).success();
+        logger.debug("Test availability of cluster nodes");
+        adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
         return new ResourceAdminManager(session).testNodesAvailability(uriInfo.getRequestUri(), realm, client);
-
+
     }
 
 }
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java
index 51c509f..c899fdf 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java
@@ -94,7 +94,7 @@ public class ClientsResource {
         try {
             ClientModel clientModel = RepresentationToModel.createClient(session, realm, rep, true);
             
-            adminEvent.operation(OperationType.CREATE).resourcePath(clientModel).representation(rep).success();
+            adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, clientModel.getId()).representation(rep).success();
             
             return Response.created(uriInfo.getAbsolutePathBuilder().path(getClientPath(clientModel)).build()).build();
         } catch (ModelDuplicateException e) {
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/IdentityProviderResource.java b/services/src/main/java/org/keycloak/services/resources/admin/IdentityProviderResource.java
index 9b058a5..c97e9af 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/IdentityProviderResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/IdentityProviderResource.java
@@ -86,7 +86,7 @@ public class IdentityProviderResource {
 
         this.realm.removeIdentityProviderByAlias(this.identityProviderModel.getAlias());
         
-        adminEvent.operation(OperationType.DELETE).resourcePath(identityProviderModel).success();
+        adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo).success();
         
         return Response.noContent().build();
     }
@@ -112,7 +112,7 @@ public class IdentityProviderResource {
                 updateUsersAfterProviderAliasChange(this.session.users().getUsers(this.realm), oldProviderId, newProviderId);
             }
             
-            adminEvent.operation(OperationType.UPDATE).resourcePath(providerRep).representation(providerRep).success();
+            adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo).representation(providerRep).success();
             
             return Response.noContent().build();
         } catch (ModelDuplicateException e) {
@@ -169,7 +169,6 @@ public class IdentityProviderResource {
         try {
             this.auth.requireView();
             IdentityProviderFactory factory = getIdentityProviderFactory();
-            adminEvent.operation(OperationType.ACTION).resourcePath(identityProviderModel, uriInfo.getPath()).success();
             return factory.create(identityProviderModel).export(uriInfo, realm, format);
         } catch (Exception e) {
             return ErrorResponse.error("Could not export public broker configuration for identity provider [" + identityProviderModel.getProviderId() + "].", Response.Status.NOT_FOUND);
@@ -232,7 +231,7 @@ public class IdentityProviderResource {
         IdentityProviderMapperModel model = RepresentationToModel.toModel(mapper);
         model = realm.addIdentityProviderMapper(model);
         
-        adminEvent.operation(OperationType.CREATE).resourcePath(model, uriInfo.getPath())
+        adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, model.getId())
             .representation(mapper).success();
         
         return Response.created(uriInfo.getAbsolutePathBuilder().path(model.getId()).build()).build();
@@ -260,7 +259,7 @@ public class IdentityProviderResource {
         if (model == null) throw new NotFoundException("Model not found");
         model = RepresentationToModel.toModel(rep);
         realm.updateIdentityProviderMapper(model);
-        adminEvent.operation(OperationType.UPDATE).resourcePath(model).representation(rep).success();
+        adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo).representation(rep).success();
 
     }
 
@@ -272,7 +271,7 @@ public class IdentityProviderResource {
         IdentityProviderMapperModel model = realm.getIdentityProviderMapperById(id);
         if (model == null) throw new NotFoundException("Model not found");
         realm.removeIdentityProviderMapper(model);
-        adminEvent.operation(OperationType.DELETE).resourcePath(model).success();
+        adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo).success();
 
     }
 
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java
index 3617631..9b5bebb 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java
@@ -82,9 +82,6 @@ public class IdentityProvidersResource {
         InputStream inputStream = file.getBody(InputStream.class, null);
         IdentityProviderFactory providerFactory = getProviderFactorytById(providerId);
         Map<String, String> config = providerFactory.parseConfig(inputStream);
-        
-        adminEvent.operation(OperationType.CREATE).resourcePath(providerFactory, uriInfo.getPath()).representation(config).success();
-
         return config;
     }
 
@@ -102,7 +99,6 @@ public class IdentityProvidersResource {
             IdentityProviderFactory providerFactory = getProviderFactorytById(providerId);
             Map<String, String> config;
             config = providerFactory.parseConfig(inputStream);
-            adminEvent.operation(OperationType.CREATE).resourcePath(providerFactory, uriInfo.getPath()).representation(config).success();
             return config;
         } finally {
             try {
@@ -137,7 +133,7 @@ public class IdentityProvidersResource {
             IdentityProviderModel identityProvider = RepresentationToModel.toModel(representation);
             this.realm.addIdentityProvider(identityProvider);
 
-            adminEvent.operation(OperationType.CREATE).resourcePath(identityProvider)
+            adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, identityProvider.getInternalId())
                     .representation(representation).success();
             
             return Response.created(uriInfo.getAbsolutePathBuilder().path(representation.getProviderId()).build()).build();
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ProtocolMappersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ProtocolMappersResource.java
index 1f59b68..da58006 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ProtocolMappersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ProtocolMappersResource.java
@@ -89,7 +89,7 @@ public class ProtocolMappersResource {
         auth.requireManage();
         ProtocolMapperModel model = RepresentationToModel.toModel(rep);
         model = client.addProtocolMapper(model);
-        adminEvent.operation(OperationType.CREATE).resourcePath(model).representation(rep).success();
+        adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, model.getId()).representation(rep).success();
         return Response.created(uriInfo.getAbsolutePathBuilder().path(model.getId()).build()).build();
     }
     /**
@@ -107,7 +107,7 @@ public class ProtocolMappersResource {
             model = RepresentationToModel.toModel(rep);
             model = client.addProtocolMapper(model);
         }
-        adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo.getPath(), false).representation(reps).success();
+        adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo).representation(reps).success();
     }
 
     @GET
@@ -144,7 +144,7 @@ public class ProtocolMappersResource {
         if (model == null) throw new NotFoundException("Model not found");
         model = RepresentationToModel.toModel(rep);
         client.updateProtocolMapper(model);
-        adminEvent.operation(OperationType.UPDATE).resourcePath(model).representation(rep).success();
+        adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo).representation(rep).success();
     }
 
     @DELETE
@@ -155,7 +155,7 @@ public class ProtocolMappersResource {
         ProtocolMapperModel model = client.getProtocolMapperById(id);
         if (model == null) throw new NotFoundException("Model not found");
         client.removeProtocolMapper(model);
-        adminEvent.operation(OperationType.DELETE).resourcePath(model).success();
+        adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo).success();
 
     }
 
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
index 9f2a12f..c2a4730 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
@@ -132,7 +132,7 @@ public class RealmAdminResource {
      */
     @Path("roles")
     public RoleContainerResource getRoleContainerResource() {
-        return new RoleContainerResource(realm, auth, realm, adminEvent);
+        return new RoleContainerResource(uriInfo, realm, auth, realm, adminEvent);
     }
 
     /**
@@ -263,7 +263,7 @@ public class RealmAdminResource {
     @POST
     public GlobalRequestResult pushRevocation() {
         auth.requireManage();
-        adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo.getPath(), false).success();
+        adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
         return new ResourceAdminManager(session).pushRealmRevocationPolicy(uriInfo.getRequestUri(), realm);
     }
 
@@ -276,7 +276,7 @@ public class RealmAdminResource {
     @POST
     public GlobalRequestResult logoutAll() {
         session.sessions().removeUserSessions(realm);
-        adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo.getPath(), false).success();
+        adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
         return new ResourceAdminManager(session).logoutAll(uriInfo.getRequestUri(), realm);
     }
 
@@ -292,7 +292,7 @@ public class RealmAdminResource {
         UserSessionModel userSession = session.sessions().getUserSession(realm, sessionId);
         if (userSession == null) throw new NotFoundException("Sesssion not found");
         AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, connection, headers, true);
-        adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo.getPath(), true).success();
+        adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo).success();
 
     }
 
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RoleByIdResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RoleByIdResource.java
index 4ca1667..2f912f5 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/RoleByIdResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/RoleByIdResource.java
@@ -39,7 +39,10 @@ public class RoleByIdResource extends RoleResource {
     private AdminEventBuilder adminEvent;
 
     @Context
-    protected KeycloakSession session;
+    private KeycloakSession session;
+
+    @Context
+    private UriInfo uriInfo;
 
     public RoleByIdResource(RealmModel realm, RealmAuth auth, AdminEventBuilder adminEvent) {
         super(realm);
@@ -95,7 +98,7 @@ public class RoleByIdResource extends RoleResource {
         RoleModel role = getRoleModel(id);
         auth.requireManage();
         deleteRole(role);
-        adminEvent.operation(OperationType.DELETE).resourcePath(role).success();
+        adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo).success();
     }
 
     /**
@@ -111,7 +114,7 @@ public class RoleByIdResource extends RoleResource {
         RoleModel role = getRoleModel(id);
         auth.requireManage();
         updateRole(rep, role);
-        adminEvent.operation(OperationType.UPDATE).resourcePath(role).representation(rep).success();
+        adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo).representation(rep).success();
     }
 
     /**
@@ -126,11 +129,7 @@ public class RoleByIdResource extends RoleResource {
     public void addComposites(final @PathParam("role-id") String id, List<RoleRepresentation> roles) {
         RoleModel role = getRoleModel(id);
         auth.requireManage();
-        addComposites(roles, role);
-        
-        adminEvent.operation(OperationType.ACTION)
-            .resourcePath(role, session.getContext().getUri().getPath()).representation(roles).success();
-        
+        addComposites(adminEvent, uriInfo, roles, role);
     }
 
     /**
@@ -227,8 +226,7 @@ public class RoleByIdResource extends RoleResource {
         auth.requireManage();
         deleteComposites(roles, role);
         
-        adminEvent.operation(OperationType.DELETE)
-            .resourcePath(role, session.getContext().getUri().getPath()).representation(roles).success();
+        adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo).representation(roles).success();
     }
 
 }
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RoleContainerResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RoleContainerResource.java
index de2e3b4..111942d 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/RoleContainerResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/RoleContainerResource.java
@@ -39,9 +39,11 @@ public class RoleContainerResource extends RoleResource {
     private final RealmAuth auth;
     protected RoleContainerModel roleContainer;
     private AdminEventBuilder adminEvent;
+    private UriInfo uriInfo;
 
-    public RoleContainerResource(RealmModel realm, RealmAuth auth, RoleContainerModel roleContainer, AdminEventBuilder adminEvent) {
+    public RoleContainerResource(UriInfo uriInfo, RealmModel realm, RealmAuth auth, RoleContainerModel roleContainer, AdminEventBuilder adminEvent) {
         super(realm);
+        this.uriInfo = uriInfo;
         this.realm = realm;
         this.auth = auth;
         this.roleContainer = roleContainer;
@@ -56,7 +58,7 @@ public class RoleContainerResource extends RoleResource {
     @GET
     @NoCache
     @Produces(MediaType.APPLICATION_JSON)
-    public List<RoleRepresentation> getRoles(@Context final UriInfo uriInfo) {
+    public List<RoleRepresentation> getRoles() {
         auth.requireAny();
 
         Set<RoleModel> roleModels = roleContainer.getRoles();
@@ -70,20 +72,19 @@ public class RoleContainerResource extends RoleResource {
     /**
      * Create a new role for this realm or client
      *
-     * @param uriInfo
      * @param rep
      * @return
      */
     @POST
     @Consumes(MediaType.APPLICATION_JSON)
-    public Response createRole(final @Context UriInfo uriInfo, final RoleRepresentation rep) {
+    public Response createRole(final RoleRepresentation rep) {
         auth.requireManage();
 
         try {
             RoleModel role = roleContainer.addRole(rep.getName());
             role.setDescription(rep.getDescription());
 
-            adminEvent.operation(OperationType.CREATE).resourcePath(role).representation(rep).success();
+            adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, role.getId()).representation(rep).success();
 
             return Response.created(uriInfo.getAbsolutePathBuilder().path(role.getName()).build()).build();
         } catch (ModelDuplicateException e) {
@@ -101,7 +102,7 @@ public class RoleContainerResource extends RoleResource {
     @GET
     @NoCache
     @Produces(MediaType.APPLICATION_JSON)
-    public RoleRepresentation getRole(@Context final UriInfo uriInfo, final @PathParam("role-name") String roleName) {
+    public RoleRepresentation getRole(final @PathParam("role-name") String roleName) {
         auth.requireView();
 
         RoleModel roleModel = roleContainer.getRole(roleName);
@@ -120,17 +121,17 @@ public class RoleContainerResource extends RoleResource {
     @Path("{role-name}")
     @DELETE
     @NoCache
-    public void deleteRole(@Context final UriInfo uriInfo, final @PathParam("role-name") String roleName) {
+    public void deleteRole(final @PathParam("role-name") String roleName) {
         auth.requireManage();
 
-        RoleRepresentation rep = getRole(uriInfo, roleName);
+        RoleRepresentation rep = getRole(roleName);
         RoleModel role = roleContainer.getRole(roleName);
         if (role == null) {
             throw new NotFoundException("Could not find role: " + roleName);
         }
         deleteRole(role);
 
-        adminEvent.operation(OperationType.DELETE).resourcePath(role).success();
+        adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo).success();
 
     }
 
@@ -144,7 +145,7 @@ public class RoleContainerResource extends RoleResource {
     @Path("{role-name}")
     @PUT
     @Consumes(MediaType.APPLICATION_JSON)
-    public Response updateRole(@Context final UriInfo uriInfo, final @PathParam("role-name") String roleName, final RoleRepresentation rep) {
+    public Response updateRole(final @PathParam("role-name") String roleName, final RoleRepresentation rep) {
         auth.requireManage();
 
         RoleModel role = roleContainer.getRole(roleName);
@@ -154,7 +155,7 @@ public class RoleContainerResource extends RoleResource {
         try {
             updateRole(rep, role);
 
-            adminEvent.operation(OperationType.UPDATE).resourcePath(role).representation(rep).success();
+            adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo).representation(rep).success();
 
             return Response.noContent().build();
         } catch (ModelDuplicateException e) {
@@ -171,16 +172,14 @@ public class RoleContainerResource extends RoleResource {
     @Path("{role-name}/composites")
     @POST
     @Consumes(MediaType.APPLICATION_JSON)
-    public void addComposites(@Context final UriInfo uriInfo, final @PathParam("role-name") String roleName, List<RoleRepresentation> roles) {
+    public void addComposites(final @PathParam("role-name") String roleName, List<RoleRepresentation> roles) {
         auth.requireManage();
 
         RoleModel role = roleContainer.getRole(roleName);
         if (role == null) {
             throw new NotFoundException("Could not find role: " + roleName);
         }
-        addComposites(roles, role);
-        adminEvent.operation(OperationType.ACTION).resourcePath(role, uriInfo.getPath()).representation(roles).success();
-
+        addComposites(adminEvent, uriInfo, roles, role);
     }
 
     /**
@@ -193,7 +192,7 @@ public class RoleContainerResource extends RoleResource {
     @GET
     @NoCache
     @Produces(MediaType.APPLICATION_JSON)
-    public Set<RoleRepresentation> getRoleComposites(@Context final UriInfo uriInfo, final @PathParam("role-name") String roleName) {
+    public Set<RoleRepresentation> getRoleComposites(final @PathParam("role-name") String roleName) {
         auth.requireManage();
 
         RoleModel role = roleContainer.getRole(roleName);
@@ -213,7 +212,7 @@ public class RoleContainerResource extends RoleResource {
     @GET
     @NoCache
     @Produces(MediaType.APPLICATION_JSON)
-    public Set<RoleRepresentation> getRealmRoleComposites(@Context final UriInfo uriInfo, final @PathParam("role-name") String roleName) {
+    public Set<RoleRepresentation> getRealmRoleComposites(final @PathParam("role-name") String roleName) {
         auth.requireManage();
 
         RoleModel role = roleContainer.getRole(roleName);
@@ -234,7 +233,7 @@ public class RoleContainerResource extends RoleResource {
     @GET
     @NoCache
     @Produces(MediaType.APPLICATION_JSON)
-    public Set<RoleRepresentation> getClientRoleComposites(@Context final UriInfo uriInfo, 
+    public Set<RoleRepresentation> getClientRoleComposites(
                                                            final @PathParam("role-name") String roleName,
                                                            final @PathParam("clientId") String clientId) {
         auth.requireManage();
@@ -290,7 +289,7 @@ public class RoleContainerResource extends RoleResource {
     @Path("{role-name}/composites")
     @DELETE
     @Consumes(MediaType.APPLICATION_JSON)
-    public void deleteComposites(@Context final UriInfo uriInfo, 
+    public void deleteComposites(
                                    final @PathParam("role-name") String roleName,
                                    List<RoleRepresentation> roles) {
         auth.requireManage();
@@ -300,7 +299,7 @@ public class RoleContainerResource extends RoleResource {
             throw new NotFoundException("Could not find role: " + roleName);
         }
         deleteComposites(roles, role);
-        adminEvent.operation(OperationType.DELETE).resourcePath(role, uriInfo.getPath()).success();
+        adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo).success();
     }
 
 }
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RoleResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RoleResource.java
index 8196528..ee6c73a 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/RoleResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/RoleResource.java
@@ -1,12 +1,14 @@
 package org.keycloak.services.resources.admin;
 
 import org.jboss.resteasy.spi.NotFoundException;
+import org.keycloak.events.admin.OperationType;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RoleModel;
 import org.keycloak.models.utils.ModelToRepresentation;
 import org.keycloak.representations.idm.RoleRepresentation;
 
+import javax.ws.rs.core.UriInfo;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
@@ -38,13 +40,15 @@ public abstract class RoleResource {
         role.setDescription(rep.getDescription());
     }
 
-    protected void addComposites(List<RoleRepresentation> roles, RoleModel role) {
+    protected void addComposites(AdminEventBuilder adminEvent, UriInfo uriInfo, List<RoleRepresentation> roles, RoleModel role) {
         for (RoleRepresentation rep : roles) {
             RoleModel composite = realm.getRoleById(rep.getId());
             if (composite == null) {
                 throw new NotFoundException("Could not find composite role: " + rep.getName());
             }
             role.addCompositeRole(composite);
+
+            adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, rep.getId()).representation(roles).success();
         }
     }
 
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ScopeMappedClientResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ScopeMappedClientResource.java
index 7f195c5..6a5c6a4 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ScopeMappedClientResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ScopeMappedClientResource.java
@@ -110,7 +110,7 @@ public class ScopeMappedClientResource {
                 throw new NotFoundException("Role not found");
             }
             client.addScopeMapping(roleModel);
-            adminEvent.operation(OperationType.CREATE).resourcePath(client, "/roles").representation(roles).success();
+            adminEvent.operation(OperationType.CREATE).resourcePath(session.getContext().getUri(), roleModel.getId()).representation(roles).success();
         }
     }
 
@@ -129,7 +129,7 @@ public class ScopeMappedClientResource {
             for (RoleModel roleModel : roleModels) {
                 client.deleteScopeMapping(roleModel);
             }
-
+            adminEvent.operation(OperationType.DELETE).resourcePath(session.getContext().getUri()).representation(roles).success();
         } else {
             for (RoleRepresentation role : roles) {
                 RoleModel roleModel = scopedClient.getRole(role.getName());
@@ -137,8 +137,8 @@ public class ScopeMappedClientResource {
                     throw new NotFoundException("Role not found");
                 }
                 client.deleteScopeMapping(roleModel);
+                adminEvent.operation(OperationType.DELETE).resourcePath(session.getContext().getUri(), roleModel.getId()).representation(roles).success();
             }
         }
-        adminEvent.operation(OperationType.DELETE).resourcePath(client, "/roles").representation(roles).success();
     }
 }
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ScopeMappedResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ScopeMappedResource.java
index 97b5e3b..587114d 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ScopeMappedResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ScopeMappedResource.java
@@ -181,9 +181,8 @@ public class ScopeMappedResource {
                 throw new NotFoundException("Role not found");
             }
             client.addScopeMapping(roleModel);
+            adminEvent.operation(OperationType.CREATE).resourcePath(session.getContext().getUri(), role.getId()).representation(roles).success();
         }
-        adminEvent.operation(OperationType.CREATE).resourcePath(client, "/roles").representation(roles).success();
-
     }
 
     /**
@@ -202,17 +201,17 @@ public class ScopeMappedResource {
             for (RoleModel roleModel : roleModels) {
                 client.deleteScopeMapping(roleModel);
             }
-
-        } else {
+            adminEvent.operation(OperationType.DELETE).resourcePath(session.getContext().getUri()).representation(roles).success();
+       } else {
             for (RoleRepresentation role : roles) {
                 RoleModel roleModel = realm.getRoleById(role.getId());
                 if (roleModel == null) {
                     throw new NotFoundException("Client not found");
                 }
                 client.deleteScopeMapping(roleModel);
+                adminEvent.operation(OperationType.DELETE).resourcePath(session.getContext().getUri(), roleModel.getId()).representation(roles).success();
             }
         }
-        adminEvent.operation(OperationType.DELETE).resourcePath(client, "/roles").representation(roles).success();
 
     }
 
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UserClientRoleMappingsResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UserClientRoleMappingsResource.java
index 9bd8160..34aafb7 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UserClientRoleMappingsResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UserClientRoleMappingsResource.java
@@ -39,12 +39,10 @@ public class UserClientRoleMappingsResource {
     protected UserModel user;
     protected ClientModel client;
     protected AdminEventBuilder adminEvent;
-    
-    @Context
-    protected KeycloakSession session;
-    
+    private UriInfo uriInfo;
 
-    public UserClientRoleMappingsResource(RealmModel realm, RealmAuth auth, UserModel user, ClientModel client, AdminEventBuilder adminEvent) {
+    public UserClientRoleMappingsResource(UriInfo uriInfo, RealmModel realm, RealmAuth auth, UserModel user, ClientModel client, AdminEventBuilder adminEvent) {
+        this.uriInfo = uriInfo;
         this.realm = realm;
         this.auth = auth;
         this.user = user;
@@ -138,7 +136,7 @@ public class UserClientRoleMappingsResource {
             }
             user.grantRole(roleModel);
         }
-        adminEvent.operation(OperationType.CREATE).resourcePath(client, user, "/roles/").representation(roles).success();
+        adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo).representation(roles).success();
 
     }
 
@@ -171,6 +169,6 @@ public class UserClientRoleMappingsResource {
                 user.deleteRoleMapping(roleModel);
             }
         }
-        adminEvent.operation(OperationType.DELETE).resourcePath(client, user, "/roles/").representation(roles).success();
+        adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo).representation(roles).success();
     }
 }
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UserFederationResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UserFederationResource.java
index 7bc54a9..842fc54 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UserFederationResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UserFederationResource.java
@@ -12,6 +12,7 @@ import org.keycloak.models.UserCredentialModel;
 import org.keycloak.models.UserFederationProvider;
 import org.keycloak.models.UserFederationProviderFactory;
 import org.keycloak.models.UserFederationProviderModel;
+import org.keycloak.models.UserFederationSyncResult;
 import org.keycloak.models.utils.ModelToRepresentation;
 import org.keycloak.provider.ProviderFactory;
 import org.keycloak.representations.idm.UserFederationProviderFactoryRepresentation;
@@ -131,7 +132,7 @@ public class UserFederationResource {
         new UsersSyncManager().refreshPeriodicSyncForProvider(session.getKeycloakSessionFactory(), session.getProvider(TimerProvider.class), model, realm.getId());
         checkKerberosCredential(model);
         
-        adminEvent.operation(OperationType.CREATE).resourcePath(model).representation(rep).success();
+        adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo).representation(rep).success();
 
         return Response.created(uriInfo.getAbsolutePathBuilder().path(model.getId()).build()).build();
     }
@@ -157,7 +158,7 @@ public class UserFederationResource {
         new UsersSyncManager().refreshPeriodicSyncForProvider(session.getKeycloakSessionFactory(), session.getProvider(TimerProvider.class), model, realm.getId());
         checkKerberosCredential(model);
         
-        adminEvent.operation(OperationType.UPDATE).resourcePath(model).representation(rep).success();
+        adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo).representation(rep).success();
 
     }
 
@@ -195,7 +196,7 @@ public class UserFederationResource {
         realm.removeUserFederationProvider(model);
         new UsersSyncManager().removePeriodicSyncForProvider(session.getProvider(TimerProvider.class), model);
         
-        adminEvent.operation(OperationType.DELETE).resourcePath(model).success();
+        adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo).success();
 
     }
 
@@ -224,23 +225,25 @@ public class UserFederationResource {
      *
      * @return
      */
-    @GET
+    @POST
     @Path("sync/{id}")
     @NoCache
-    public Response syncUsers(@PathParam("id") String providerId, @QueryParam("action") String action) {
+    public UserFederationSyncResult syncUsers(@PathParam("id") String providerId, @QueryParam("action") String action) {
         logger.debug("Syncing users");
         auth.requireManage();
 
         for (UserFederationProviderModel model : realm.getUserFederationProviders()) {
             if (model.getId().equals(providerId)) {
                 UsersSyncManager syncManager = new UsersSyncManager();
+                UserFederationSyncResult syncResult = null;
                 if ("triggerFullSync".equals(action)) {
-                    syncManager.syncAllUsers(session.getKeycloakSessionFactory(), realm.getId(), model);
+                    syncResult = syncManager.syncAllUsers(session.getKeycloakSessionFactory(), realm.getId(), model);
                 } else if ("triggerChangedUsersSync".equals(action)) {
-                    syncManager.syncChangedUsers(session.getKeycloakSessionFactory(), realm.getId(), model);
+                    syncResult = syncManager.syncChangedUsers(session.getKeycloakSessionFactory(), realm.getId(), model);
                 }
-                adminEvent.operation(OperationType.ACTION).resourcePath(model, "/sync").success();
-                return Response.noContent().build();
+
+                adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
+                return syncResult;
             }
         }
 
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
index c84b635..fa6b901 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
@@ -97,7 +97,7 @@ public class UsersResource {
     public UsersResource(RealmModel realm, RealmAuth auth, TokenManager tokenManager, AdminEventBuilder adminEvent) {
         this.auth = auth;
         this.realm = realm;
-        this.adminEvent = adminEvent;
+        this.adminEvent = adminEvent;
 
         auth.init(RealmAuth.Resource.USER);
     }
@@ -121,7 +121,7 @@ public class UsersResource {
                 throw new NotFoundException("User not found");
             }
             updateUserFromRep(user, rep);
-            adminEvent.operation(OperationType.UPDATE).resourcePath(user).representation(rep).success();
+            adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo).representation(rep).success();
 
             if (session.getTransaction().isActive()) {
                 session.getTransaction().commit();
@@ -158,7 +158,7 @@ public class UsersResource {
             UserModel user = session.users().addUser(realm, rep.getUsername());
             updateUserFromRep(user, rep);
             
-            adminEvent.operation(OperationType.CREATE).resourcePath(user).representation(rep).success();
+            adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, user.getId()).representation(rep).success();
             
             if (session.getTransaction().isActive()) {
                 session.getTransaction().commit();
@@ -312,7 +312,7 @@ public class UsersResource {
 
         FederatedIdentityModel socialLink = new FederatedIdentityModel(provider, rep.getUserId(), rep.getUserName());
         session.users().addFederatedIdentity(realm, user, socialLink);
-        adminEvent.operation(OperationType.CREATE).resourcePath(user, uriInfo.getPath(), true).representation(rep).success();
+        adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo).representation(rep).success();
         return Response.noContent().build();
     }
 
@@ -328,7 +328,7 @@ public class UsersResource {
         if (!session.users().removeFederatedIdentity(realm, user, provider)) {
             throw new NotFoundException("Link not found");
         }
-        adminEvent.operation(OperationType.DELETE).resourcePath(user, uriInfo.getPath(), true).success();
+        adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo).success();
     }
 
     /**
@@ -382,7 +382,7 @@ public class UsersResource {
         } else {
             throw new NotFoundException("Consent not found for user " + username + " and client " + clientId);
         }
-        adminEvent.operation(OperationType.ACTION).resourcePath(user, client, uriInfo.getPath()).success();
+        adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
     }
 
     /**
@@ -404,7 +404,7 @@ public class UsersResource {
         for (UserSessionModel userSession : userSessions) {
             AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, true);
         }
-        adminEvent.operation(OperationType.ACTION).resourcePath(user, uriInfo.getPath()).success();
+        adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
     }
 
     /**
@@ -426,7 +426,7 @@ public class UsersResource {
 
         boolean removed = new UserManager(session).removeUser(realm, user);
         if (removed) {
-            adminEvent.operation(OperationType.DELETE).resourcePath(user).success();
+            adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo).success();
             return Response.noContent().build();
         } else {
             return ErrorResponse.error("User couldn't be deleted", Response.Status.BAD_REQUEST);
@@ -638,10 +638,8 @@ public class UsersResource {
                 throw new NotFoundException("Role not found");
             }
             user.grantRole(roleModel);
+            adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, role.getId()).representation(roles).success();
         }
-        
-        adminEvent.operation(OperationType.CREATE).resourcePath(user, realm, uriInfo.getPath()).representation(roles).success();
-
     }
 
     /**
@@ -667,7 +665,7 @@ public class UsersResource {
             for (RoleModel roleModel : roleModels) {
                 user.deleteRoleMapping(roleModel);
             }
-
+            adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo).representation(roles).success();
         } else {
             for (RoleRepresentation role : roles) {
                 RoleModel roleModel = realm.getRole(role.getName());
@@ -675,10 +673,11 @@ public class UsersResource {
                     throw new NotFoundException("Role not found");
                 }
                 user.deleteRoleMapping(roleModel);
+
+                adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo, role.getId()).representation(roles).success();
             }
         }
         
-        adminEvent.operation(OperationType.DELETE).resourcePath(user, realm, uriInfo.getPath()).representation(roles).success();
     }
 
     @Path("{username}/role-mappings/clients/{clientId}")
@@ -693,7 +692,7 @@ public class UsersResource {
         if (client == null) {
             throw new NotFoundException("Client not found");
         }
-        return new UserClientRoleMappingsResource(realm, auth, user, client, adminEvent);
+        return new UserClientRoleMappingsResource(uriInfo, realm, auth, user, client, adminEvent);
 
     }
     @Path("{username}/role-mappings/clients-by-id/{id}")
@@ -709,7 +708,7 @@ public class UsersResource {
             throw new NotFoundException("Client not found");
         }
         
-        return new UserClientRoleMappingsResource(realm, auth, user, client, adminEvent);
+        return new UserClientRoleMappingsResource(uriInfo, realm, auth, user, client, adminEvent);
 
     }
     /**
@@ -743,7 +742,7 @@ public class UsersResource {
         }
         if (pass.isTemporary()) user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
         
-        adminEvent.operation(OperationType.ACTION).resourcePath(user, uriInfo.getPath()).success();
+        adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
     }
 
     /**
@@ -763,7 +762,7 @@ public class UsersResource {
         }
 
         user.setTotp(false);
-        adminEvent.operation(OperationType.ACTION).resourcePath(user, uriInfo.getPath()).success();
+        adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
     }
 
     /**
@@ -840,7 +839,7 @@ public class UsersResource {
 
             //audit.user(user).detail(Details.EMAIL, user.getEmail()).detail(Details.CODE_ID, accessCode.getCodeId()).success();
 
-            adminEvent.operation(OperationType.ACTION).resourcePath(user, uriInfo.getPath()).success();
+            adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
 
             return Response.ok().build();
         } catch (EmailException e) {
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 b0f02ad..e4c821c 100755
--- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
+++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
@@ -39,7 +39,6 @@ import java.io.InputStream;
 import java.net.URI;
 import java.net.URL;
 import java.util.HashSet;
-import java.util.Map;
 import java.util.Properties;
 import java.util.Set;
 import java.util.StringTokenizer;
@@ -75,6 +74,7 @@ public class KeycloakApplication extends Application {
         singletons.add(new ServerVersionResource());
         singletons.add(new RealmsResource());
         singletons.add(new AdminRoot());
+        singletons.add(new ModelExceptionMapper());
         classes.add(SkeletonKeyContextResolver.class);
         classes.add(QRCodeResource.class);
         classes.add(ThemeResource.class);
diff --git a/services/src/main/java/org/keycloak/services/resources/ModelExceptionMapper.java b/services/src/main/java/org/keycloak/services/resources/ModelExceptionMapper.java
new file mode 100644
index 0000000..c5cc88e
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/resources/ModelExceptionMapper.java
@@ -0,0 +1,27 @@
+package org.keycloak.services.resources;
+
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+import org.keycloak.messages.MessagesProvider;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ModelException;
+import org.keycloak.services.ErrorResponse;
+
+/**
+ * @author <a href="mailto:leonardo.zanivan@gmail.com">Leonardo Zanivan</a>
+ */
+@Provider
+public class ModelExceptionMapper implements ExceptionMapper<ModelException> {
+
+    @Context
+    private KeycloakSession session;
+
+    @Override
+    public Response toResponse(ModelException ex) {
+        String message = session.getProvider(MessagesProvider.class, "admin")
+                .getMessage(ex.getMessage(), ex.getParameters());
+        return ErrorResponse.error(message, Response.Status.BAD_REQUEST);
+    }
+}
diff --git a/services/src/main/resources/META-INF/services/org.keycloak.messages.MessagesProviderFactory b/services/src/main/resources/META-INF/services/org.keycloak.messages.MessagesProviderFactory
new file mode 100644
index 0000000..341e768
--- /dev/null
+++ b/services/src/main/resources/META-INF/services/org.keycloak.messages.MessagesProviderFactory
@@ -0,0 +1 @@
+org.keycloak.services.messages.AdminMessagesProviderFactory
\ No newline at end of file
diff --git a/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi
index e1c0b91..a9b5a54 100755
--- a/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi
+++ b/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi
@@ -1,4 +1,5 @@
 org.keycloak.protocol.LoginProtocolSpi
 org.keycloak.protocol.ProtocolMapperSpi
 org.keycloak.exportimport.ClientImportSpi
-org.keycloak.wellknown.WellKnownSpi
\ No newline at end of file
+org.keycloak.wellknown.WellKnownSpi
+org.keycloak.messages.MessagesSpi
\ No newline at end of file
diff --git a/social/core/pom.xml b/social/core/pom.xml
index cc15299..5b67273 100755
--- a/social/core/pom.xml
+++ b/social/core/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-social-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/social/facebook/pom.xml b/social/facebook/pom.xml
index 3503c69..7c9c02f 100755
--- a/social/facebook/pom.xml
+++ b/social/facebook/pom.xml
@@ -3,7 +3,7 @@
     <parent>
         <artifactId>keycloak-social-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/social/github/pom.xml b/social/github/pom.xml
index d8f0f1f..3bd1354 100755
--- a/social/github/pom.xml
+++ b/social/github/pom.xml
@@ -3,7 +3,7 @@
     <parent>
         <artifactId>keycloak-social-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/social/google/pom.xml b/social/google/pom.xml
index ba900f0..8689171 100755
--- a/social/google/pom.xml
+++ b/social/google/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-social-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/social/linkedin/pom.xml b/social/linkedin/pom.xml
index 10c76fc..01232bd 100755
--- a/social/linkedin/pom.xml
+++ b/social/linkedin/pom.xml
@@ -3,7 +3,7 @@
     <parent>
         <artifactId>keycloak-social-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>

social/pom.xml 2(+1 -1)

diff --git a/social/pom.xml b/social/pom.xml
index 8a960f7..557d449 100755
--- a/social/pom.xml
+++ b/social/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/social/stackoverflow/pom.xml b/social/stackoverflow/pom.xml
index 465d5e5..97e4dbf 100755
--- a/social/stackoverflow/pom.xml
+++ b/social/stackoverflow/pom.xml
@@ -3,7 +3,7 @@
     <parent>
         <artifactId>keycloak-social-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/social/twitter/pom.xml b/social/twitter/pom.xml
index ef01a05..43943fd 100755
--- a/social/twitter/pom.xml
+++ b/social/twitter/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-social-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/testsuite/docker-cluster/pom.xml b/testsuite/docker-cluster/pom.xml
index 6d8fde7..bfe532b 100755
--- a/testsuite/docker-cluster/pom.xml
+++ b/testsuite/docker-cluster/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-testsuite-pom</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
 
diff --git a/testsuite/integration/pom.xml b/testsuite/integration/pom.xml
index d0f94c0..0b0b2a6 100755
--- a/testsuite/integration/pom.xml
+++ b/testsuite/integration/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-testsuite-pom</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/DummyUserFederationProvider.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/DummyUserFederationProvider.java
index 4e85972..865ffa0 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/DummyUserFederationProvider.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/DummyUserFederationProvider.java
@@ -22,7 +22,7 @@ public class DummyUserFederationProvider implements UserFederationProvider {
     private static Map<String, UserModel> users = new HashMap<String, UserModel>();
 
     @Override
-    public UserModel proxy(UserModel local) {
+    public UserModel validateAndProxy(RealmModel realm, UserModel local) {
         return local;
     }
 
@@ -68,7 +68,7 @@ public class DummyUserFederationProvider implements UserFederationProvider {
     }
 
     @Override
-    public boolean isValid(UserModel local) {
+    public boolean isValid(RealmModel realm, UserModel local) {
         return false;
     }
 
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/DummyUserFederationProviderFactory.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/DummyUserFederationProviderFactory.java
index 536a5b7..b8ec10a 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/DummyUserFederationProviderFactory.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/DummyUserFederationProviderFactory.java
@@ -7,6 +7,7 @@ import org.keycloak.models.KeycloakSessionFactory;
 import org.keycloak.models.UserFederationProvider;
 import org.keycloak.models.UserFederationProviderFactory;
 import org.keycloak.models.UserFederationProviderModel;
+import org.keycloak.models.UserFederationSyncResult;
 
 import java.util.Date;
 import java.util.HashSet;
@@ -63,15 +64,17 @@ public class DummyUserFederationProviderFactory implements UserFederationProvide
     }
 
     @Override
-    public void syncAllUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model) {
+    public UserFederationSyncResult syncAllUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model) {
         logger.info("syncAllUsers invoked");
         fullSyncCounter.incrementAndGet();
+        return UserFederationSyncResult.empty();
     }
 
     @Override
-    public void syncChangedUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model, Date lastSync) {
+    public UserFederationSyncResult syncChangedUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model, Date lastSync) {
         logger.info("syncChangedUsers invoked");
         changedSyncCounter.incrementAndGet();
+        return UserFederationSyncResult.empty();
     }
 
     public int getFullSyncCounter() {
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationProvidersIntegrationTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationProvidersIntegrationTest.java
index 4af2a6c..aa61ee6 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationProvidersIntegrationTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationProvidersIntegrationTest.java
@@ -12,8 +12,8 @@ import org.keycloak.OAuth2Constants;
 import org.keycloak.federation.ldap.LDAPFederationProvider;
 import org.keycloak.federation.ldap.LDAPFederationProviderFactory;
 import org.keycloak.federation.ldap.LDAPUtils;
-import org.keycloak.federation.ldap.idm.model.LDAPUser;
-import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
+import org.keycloak.federation.ldap.idm.model.LDAPObject;
+import org.keycloak.models.ClientModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.LDAPConstants;
 import org.keycloak.models.ModelReadOnlyException;
@@ -23,7 +23,6 @@ import org.keycloak.models.UserCredentialValueModel;
 import org.keycloak.models.UserFederationProvider;
 import org.keycloak.models.UserFederationProviderModel;
 import org.keycloak.models.UserModel;
-import org.keycloak.representations.idm.CredentialRepresentation;
 import org.keycloak.services.managers.RealmManager;
 import org.keycloak.testsuite.OAuthClient;
 import org.keycloak.testsuite.pages.AccountPasswordPage;
@@ -53,22 +52,26 @@ public class FederationProvidersIntegrationTest {
 
         @Override
         public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
-            addUser(manager.getSession(), appRealm, "mary", "mary@test.com", "password-app");
+            FederationTestUtils.addLocalUser(manager.getSession(), appRealm, "mary", "mary@test.com", "password-app");
 
             Map<String,String> ldapConfig = ldapRule.getConfig();
             ldapConfig.put(LDAPConstants.SYNC_REGISTRATIONS, "true");
             ldapConfig.put(LDAPConstants.EDIT_MODE, UserFederationProvider.EditMode.WRITABLE.toString());
 
             ldapModel = appRealm.addUserFederationProvider(LDAPFederationProviderFactory.PROVIDER_NAME, ldapConfig, 0, "test-ldap", -1, -1, 0);
+            FederationTestUtils.addZipCodeLDAPMapper(appRealm, ldapModel);
 
             // Delete all LDAP users and add some new for testing
-            LDAPIdentityStore ldapStore = getLdapIdentityStore(manager.getSession(), ldapModel);
-            LDAPUtils.removeAllUsers(ldapStore);
+            LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel);
+            LDAPUtils.removeAllUsers(ldapFedProvider, appRealm);
 
-            LDAPUser john = LDAPUtils.addUser(ldapStore, "johnkeycloak", "John", "Doe", "john@email.org");
-            LDAPUtils.updatePassword(ldapStore, john, "Password1");
+            // Add sample application TODO: REmove this!!!! It's just temporarily needed in SyncProvidersTest until model for federation mappers is implemented
+            ClientModel finance = appRealm.addClient("finance");
 
-            LDAPUser existing = LDAPUtils.addUser(ldapStore, "existing", "Existing", "Foo", "existing@email.org");
+            LDAPObject john = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "johnkeycloak", "John", "Doe", "john@email.org", "1234");
+            ldapFedProvider.getLdapIdentityStore().updatePassword(john, "Password1");
+
+            LDAPObject existing = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "existing", "Existing", "Foo", "existing@email.org", "5678");
         }
     });
 
@@ -108,18 +111,6 @@ public class FederationProvidersIntegrationTest {
 //
 //    }
 
-    static UserModel addUser(KeycloakSession session, RealmModel realm, String username, String email, String password) {
-        UserModel user = session.users().addUser(realm, username);
-        user.setEmail(email);
-        user.setEnabled(true);
-
-        UserCredentialModel creds = new UserCredentialModel();
-        creds.setType(CredentialRepresentation.PASSWORD);
-        creds.setValue(password);
-
-        user.updateCredential(creds);
-        return user;
-    }
 
     @Test
     public void caseSensitiveSearch() {
@@ -128,9 +119,6 @@ public class FederationProvidersIntegrationTest {
         // This should fail for now due to case-sensitivity
         loginPage.login("johnKeycloak", "Password1");
         Assert.assertEquals("Invalid username or password.", loginPage.getError());
-
-        loginPage.login("John@email.org", "Password1");
-        Assert.assertEquals("Invalid username or password.", loginPage.getError());
     }
 
     @Test
@@ -161,6 +149,7 @@ public class FederationProvidersIntegrationTest {
 
                 RealmModel appRealm = manager.getRealm("test");
                 ldapModel = appRealm.addUserFederationProvider(ldapModel.getProviderName(), ldapModel.getConfig(), ldapModel.getPriority(), ldapModel.getDisplayName(), -1, -1, 0);
+                FederationTestUtils.addZipCodeLDAPMapper(appRealm, ldapModel);
             } finally {
                 keycloakRule.stopSession(session, true);
             }
@@ -191,6 +180,7 @@ public class FederationProvidersIntegrationTest {
         Assert.assertEquals("John", profilePage.getFirstName());
         Assert.assertEquals("Doe", profilePage.getLastName());
         Assert.assertEquals("john@email.org", profilePage.getEmail());
+        Assert.assertEquals("1234", profilePage.getPostalCode());
     }
 
     @Test
@@ -257,7 +247,7 @@ public class FederationProvidersIntegrationTest {
         loginPage.clickRegister();
         registerPage.assertCurrent();
 
-        registerPage.register("firstName", "lastName", "email2@check.cz", "registerUserSuccess2", "Password1", "Password1");
+        registerPage.register("firstName", "lastName", "email2@check.cz", "registerUserSuccess2", "Password1", "Password1", "non-LDAP-Mapped street", null, null, "78910", null);
         Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
 
         KeycloakSession session = keycloakRule.startSession();
@@ -267,6 +257,8 @@ public class FederationProvidersIntegrationTest {
             Assert.assertNotNull(user);
             Assert.assertNotNull(user.getFederationLink());
             Assert.assertEquals(user.getFederationLink(), ldapModel.getId());
+            Assert.assertEquals("78910", user.getAttribute("postal_code"));
+            Assert.assertEquals("non-LDAP-Mapped street", user.getAttribute("street"));
         } finally {
             keycloakRule.stopSession(session, false);
         }
@@ -346,13 +338,14 @@ public class FederationProvidersIntegrationTest {
     @Test
     public void testSearch() {
         KeycloakSession session = keycloakRule.startSession();
-        LDAPIdentityStore ldapStore = getLdapIdentityStore(session, ldapModel);
         try {
             RealmModel appRealm = session.realms().getRealmByName("test");
-            LDAPUtils.addUser(ldapStore, "username1", "John1", "Doel1", "user1@email.org");
-            LDAPUtils.addUser(ldapStore, "username2", "John2", "Doel2", "user2@email.org");
-            LDAPUtils.addUser(ldapStore, "username3", "John3", "Doel3", "user3@email.org");
-            LDAPUtils.addUser(ldapStore, "username4", "John4", "Doel4", "user4@email.org");
+            LDAPFederationProvider ldapProvider = FederationTestUtils.getLdapProvider(session, ldapModel);
+
+            FederationTestUtils.addLDAPUser(ldapProvider, appRealm, "username1", "John1", "Doel1", "user1@email.org", "121");
+            FederationTestUtils.addLDAPUser(ldapProvider, appRealm, "username2", "John2", "Doel2", "user2@email.org", "122");
+            FederationTestUtils.addLDAPUser(ldapProvider, appRealm, "username3", "John3", "Doel3", "user3@email.org", "123");
+            FederationTestUtils.addLDAPUser(ldapProvider, appRealm, "username4", "John4", "Doel4", "user4@email.org", "124");
 
             // Users are not at local store at this moment
             Assert.assertNull(session.userStorage().getUserByUsername("username1", appRealm));
@@ -362,19 +355,19 @@ public class FederationProvidersIntegrationTest {
 
             // search by username
             session.users().searchForUser("username1", appRealm);
-            SyncProvidersTest.assertUserImported(session.userStorage(), appRealm, "username1", "John1", "Doel1", "user1@email.org");
+            FederationTestUtils.assertUserImported(session.userStorage(), appRealm, "username1", "John1", "Doel1", "user1@email.org", "121");
 
             // search by email
             session.users().searchForUser("user2@email.org", appRealm);
-            SyncProvidersTest.assertUserImported(session.userStorage(), appRealm, "username2", "John2", "Doel2", "user2@email.org");
+            FederationTestUtils.assertUserImported(session.userStorage(), appRealm, "username2", "John2", "Doel2", "user2@email.org", "122");
 
             // search by lastName
             session.users().searchForUser("Doel3", appRealm);
-            SyncProvidersTest.assertUserImported(session.userStorage(), appRealm, "username3", "John3", "Doel3", "user3@email.org");
+            FederationTestUtils.assertUserImported(session.userStorage(), appRealm, "username3", "John3", "Doel3", "user3@email.org", "123");
 
             // search by firstName + lastName
             session.users().searchForUser("John4 Doel4", appRealm);
-            SyncProvidersTest.assertUserImported(session.userStorage(), appRealm, "username4", "John4", "Doel4", "user4@email.org");
+            FederationTestUtils.assertUserImported(session.userStorage(), appRealm, "username4", "John4", "Doel4", "user4@email.org", "124");
         } finally {
             keycloakRule.stopSession(session, true);
         }
@@ -402,7 +395,9 @@ public class FederationProvidersIntegrationTest {
             Assert.assertTrue(session.users().validCredentials(appRealm, user, cred));
 
             // LDAP password is still unchanged
-            Assert.assertTrue(LDAPUtils.validatePassword(getLdapIdentityStore(session, model), user, "Password1"));
+            LDAPFederationProvider ldapProvider = FederationTestUtils.getLdapProvider(session, model);
+            LDAPObject ldapUser = ldapProvider.loadLDAPUserByUsername(appRealm, "johnkeycloak");
+            ldapProvider.getLdapIdentityStore().validatePassword(ldapUser, "Password1");
 
             // ATM it's not permitted to delete user in unsynced mode. Should be user deleted just locally instead?
             Assert.assertFalse(session.users().removeUser(appRealm, user));
@@ -419,10 +414,4 @@ public class FederationProvidersIntegrationTest {
         }
     }
 
-    static LDAPIdentityStore getLdapIdentityStore(KeycloakSession keycloakSession, UserFederationProviderModel ldapFedModel) {
-        LDAPFederationProviderFactory ldapProviderFactory = (LDAPFederationProviderFactory) keycloakSession.getKeycloakSessionFactory().getProviderFactory(UserFederationProvider.class, ldapFedModel.getProviderName());
-        LDAPFederationProvider ldapProvider = ldapProviderFactory.getInstance(keycloakSession, ldapFedModel);
-        return ldapProvider.getLdapIdentityStore();
-    }
-
 }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationTestUtils.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationTestUtils.java
new file mode 100644
index 0000000..ddda4ff
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationTestUtils.java
@@ -0,0 +1,127 @@
+package org.keycloak.testsuite.federation;
+
+import org.junit.Assert;
+import org.keycloak.federation.ldap.LDAPFederationProvider;
+import org.keycloak.federation.ldap.LDAPFederationProviderFactory;
+import org.keycloak.federation.ldap.LDAPUtils;
+import org.keycloak.federation.ldap.idm.model.LDAPObject;
+import org.keycloak.federation.ldap.mappers.RoleLDAPFederationMapper;
+import org.keycloak.federation.ldap.mappers.RoleLDAPFederationMapperFactory;
+import org.keycloak.federation.ldap.mappers.UserAttributeLDAPFederationMapper;
+import org.keycloak.federation.ldap.mappers.UserAttributeLDAPFederationMapperFactory;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.LDAPConstants;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserFederationMapperModel;
+import org.keycloak.models.UserFederationProvider;
+import org.keycloak.models.UserFederationProviderModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.UserProvider;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.models.utils.UserModelDelegate;
+import org.keycloak.representations.idm.CredentialRepresentation;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+class FederationTestUtils {
+
+    public static UserModel addLocalUser(KeycloakSession session, RealmModel realm, String username, String email, String password) {
+        UserModel user = session.users().addUser(realm, username);
+        user.setEmail(email);
+        user.setEnabled(true);
+
+        UserCredentialModel creds = new UserCredentialModel();
+        creds.setType(CredentialRepresentation.PASSWORD);
+        creds.setValue(password);
+
+        user.updateCredential(creds);
+        return user;
+    }
+
+    public static LDAPObject addLDAPUser(LDAPFederationProvider ldapProvider, RealmModel realm, final String username,
+                                            final String firstName, final String lastName, final String email, final String postalCode) {
+        UserModel helperUser = new UserModelDelegate(null) {
+
+            @Override
+            public String getUsername() {
+                return username;
+            }
+
+            @Override
+            public String getFirstName() {
+                return firstName;
+            }
+
+            @Override
+            public String getLastName() {
+                return lastName;
+            }
+
+            @Override
+            public String getEmail() {
+                return email;
+            }
+
+            @Override
+            public String getAttribute(String name) {
+                if ("postal_code".equals(name)) {
+                    return postalCode;
+                } else {
+                    return null;
+                }
+            }
+        };
+        return LDAPUtils.addUserToLDAP(ldapProvider, realm, helperUser);
+    }
+
+    public static LDAPFederationProvider getLdapProvider(KeycloakSession keycloakSession, UserFederationProviderModel ldapFedModel) {
+        LDAPFederationProviderFactory ldapProviderFactory = (LDAPFederationProviderFactory) keycloakSession.getKeycloakSessionFactory().getProviderFactory(UserFederationProvider.class, ldapFedModel.getProviderName());
+        return ldapProviderFactory.getInstance(keycloakSession, ldapFedModel);
+    }
+
+    public static void assertUserImported(UserProvider userProvider, RealmModel realm, String username, String expectedFirstName, String expectedLastName, String expectedEmail, String expectedPostalCode) {
+        UserModel user = userProvider.getUserByUsername(username, realm);
+        Assert.assertNotNull(user);
+        Assert.assertEquals(expectedFirstName, user.getFirstName());
+        Assert.assertEquals(expectedLastName, user.getLastName());
+        Assert.assertEquals(expectedEmail, user.getEmail());
+        Assert.assertEquals(expectedPostalCode, user.getAttribute("postal_code"));
+    }
+
+    public static void addZipCodeLDAPMapper(RealmModel realm, UserFederationProviderModel providerModel) {
+        UserFederationMapperModel mapperModel = KeycloakModelUtils.createUserFederationMapperModel("zipCodeMapper", providerModel.getId(), UserAttributeLDAPFederationMapperFactory.ID,
+                UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, "postal_code",
+                UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.POSTAL_CODE,
+                UserAttributeLDAPFederationMapper.READ_ONLY, "false");
+        realm.addUserFederationMapper(mapperModel);
+    }
+
+    public static void addOrUpdateRoleLDAPMappers(RealmModel realm, UserFederationProviderModel providerModel, RoleLDAPFederationMapper.Mode mode) {
+        UserFederationMapperModel mapperModel = realm.getUserFederationMapperByName(providerModel.getId(), "realmRolesMapper");
+        if (mapperModel != null) {
+            mapperModel.getConfig().put(RoleLDAPFederationMapper.MODE, mode.toString());
+            realm.updateUserFederationMapper(mapperModel);
+        } else {
+            mapperModel = KeycloakModelUtils.createUserFederationMapperModel("realmRolesMapper", providerModel.getId(), RoleLDAPFederationMapperFactory.ID,
+                    RoleLDAPFederationMapper.ROLES_DN, "ou=RealmRoles,dc=keycloak,dc=org",
+                    RoleLDAPFederationMapper.USE_REALM_ROLES_MAPPING, "true",
+                    RoleLDAPFederationMapper.MODE, mode.toString());
+            realm.addUserFederationMapper(mapperModel);
+        }
+
+        mapperModel = realm.getUserFederationMapperByName(providerModel.getId(), "financeRolesMapper");
+        if (mapperModel != null) {
+            mapperModel.getConfig().put(RoleLDAPFederationMapper.MODE, mode.toString());
+            realm.updateUserFederationMapper(mapperModel);
+        } else {
+            mapperModel = KeycloakModelUtils.createUserFederationMapperModel("financeRolesMapper", providerModel.getId(), RoleLDAPFederationMapperFactory.ID,
+                    RoleLDAPFederationMapper.ROLES_DN, "ou=FinanceRoles,dc=keycloak,dc=org",
+                    RoleLDAPFederationMapper.USE_REALM_ROLES_MAPPING, "false",
+                    RoleLDAPFederationMapper.CLIENT_ID, "finance",
+                    RoleLDAPFederationMapper.MODE, mode.toString());
+            realm.addUserFederationMapper(mapperModel);
+        }
+    }
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/LDAPRoleMappingsTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/LDAPRoleMappingsTest.java
new file mode 100644
index 0000000..a389fe5
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/LDAPRoleMappingsTest.java
@@ -0,0 +1,346 @@
+package org.keycloak.testsuite.federation;
+
+import java.util.Map;
+import java.util.Set;
+
+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.federation.ldap.LDAPFederationProvider;
+import org.keycloak.federation.ldap.LDAPFederationProviderFactory;
+import org.keycloak.federation.ldap.LDAPUtils;
+import org.keycloak.federation.ldap.idm.model.LDAPObject;
+import org.keycloak.federation.ldap.idm.query.Condition;
+import org.keycloak.federation.ldap.idm.query.QueryParameter;
+import org.keycloak.federation.ldap.idm.query.internal.LDAPIdentityQuery;
+import org.keycloak.federation.ldap.idm.query.internal.LDAPQueryConditionsBuilder;
+import org.keycloak.federation.ldap.mappers.RoleLDAPFederationMapper;
+import org.keycloak.models.AccountRoles;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.Constants;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.LDAPConstants;
+import org.keycloak.models.ModelException;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserFederationMapperModel;
+import org.keycloak.models.UserFederationProvider;
+import org.keycloak.models.UserFederationProviderModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.services.managers.RealmManager;
+import org.keycloak.testsuite.OAuthClient;
+import org.keycloak.testsuite.pages.AccountPasswordPage;
+import org.keycloak.testsuite.pages.AccountUpdateProfilePage;
+import org.keycloak.testsuite.pages.AppPage;
+import org.keycloak.testsuite.pages.LoginPage;
+import org.keycloak.testsuite.pages.RegisterPage;
+import org.keycloak.testsuite.rule.KeycloakRule;
+import org.keycloak.testsuite.rule.LDAPRule;
+import org.keycloak.testsuite.rule.WebResource;
+import org.keycloak.testsuite.rule.WebRule;
+import org.openqa.selenium.WebDriver;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class LDAPRoleMappingsTest {
+
+    private static LDAPRule ldapRule = new LDAPRule();
+
+    private static UserFederationProviderModel ldapModel = null;
+
+    private static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() {
+
+        @Override
+        public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+            FederationTestUtils.addLocalUser(manager.getSession(), appRealm, "mary", "mary@test.com", "password-app");
+
+            Map<String,String> ldapConfig = ldapRule.getConfig();
+            ldapConfig.put(LDAPConstants.SYNC_REGISTRATIONS, "true");
+            ldapConfig.put(LDAPConstants.EDIT_MODE, UserFederationProvider.EditMode.WRITABLE.toString());
+
+            ldapModel = appRealm.addUserFederationProvider(LDAPFederationProviderFactory.PROVIDER_NAME, ldapConfig, 0, "test-ldap", -1, -1, 0);
+
+            // Delete all LDAP users and add some new for testing
+            LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel);
+            LDAPUtils.removeAllUsers(ldapFedProvider, appRealm);
+
+            // Add sample application
+            ClientModel finance = appRealm.addClient("finance");
+
+            LDAPObject john = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "johnkeycloak", "John", "Doe", "john@email.org", "1234");
+            ldapFedProvider.getLdapIdentityStore().updatePassword(john, "Password1");
+
+            LDAPObject mary = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "marykeycloak", "Mary", "Kelly", "mary@email.org", "5678");
+            ldapFedProvider.getLdapIdentityStore().updatePassword(mary, "Password1");
+
+            LDAPObject rob = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "robkeycloak", "Rob", "Brown", "rob@email.org", "8910");
+            ldapFedProvider.getLdapIdentityStore().updatePassword(rob, "Password1");
+
+        }
+    }) {
+
+        @Override
+        protected void after() {
+            // Need to cleanup some LDAP objects after the test
+            update(new KeycloakRule.KeycloakSetup() {
+
+                @Override
+                public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                    RoleLDAPFederationMapper roleMapper = new RoleLDAPFederationMapper();
+
+                    FederationTestUtils.addOrUpdateRoleLDAPMappers(appRealm, ldapModel, RoleLDAPFederationMapper.Mode.LDAP_ONLY);
+                    UserFederationMapperModel roleMapperModel = appRealm.getUserFederationMapperByName(ldapModel.getId(), "realmRolesMapper");
+                    LDAPFederationProvider ldapProvider = FederationTestUtils.getLdapProvider(session, ldapModel);
+
+                    LDAPObject ldapRole = roleMapper.loadLDAPRoleByName(roleMapperModel, ldapProvider, "realmRole3");
+                    if (ldapRole != null) {
+                        ldapProvider.getLdapIdentityStore().remove(ldapRole);
+                    }
+                }
+
+            });
+
+            super.after();
+        }
+
+    };
+
+    @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;
+
+    @Test
+    public void test01_ldapOnlyRoleMappings() {
+        KeycloakSession session = keycloakRule.startSession();
+        try {
+            RealmModel appRealm = session.realms().getRealmByName("test");
+
+            FederationTestUtils.addOrUpdateRoleLDAPMappers(appRealm, ldapModel, RoleLDAPFederationMapper.Mode.LDAP_ONLY);
+
+            UserModel john = session.users().getUserByUsername("johnkeycloak", appRealm);
+            UserModel mary = session.users().getUserByUsername("marykeycloak", appRealm);
+
+            // 1 - Grant some roles in LDAP
+
+            // This role should already exists as it was imported from LDAP
+            RoleModel realmRole1 = appRealm.getRole("realmRole1");
+            john.grantRole(realmRole1);
+
+            // This role should already exists as it was imported from LDAP
+            RoleModel realmRole2 = appRealm.getRole("realmRole2");
+            mary.grantRole(realmRole2);
+
+            RoleModel realmRole3 = appRealm.addRole("realmRole3");
+            john.grantRole(realmRole3);
+            mary.grantRole(realmRole3);
+
+            ClientModel accountApp = appRealm.getClientByClientId(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID);
+            ClientModel financeApp = appRealm.getClientByClientId("finance");
+
+            RoleModel manageAccountRole = accountApp.getRole(AccountRoles.MANAGE_ACCOUNT);
+            RoleModel financeRole1 = financeApp.getRole("financeRole1");
+            john.grantRole(financeRole1);
+
+            // 2 - Check that role mappings are not in local Keycloak DB (They are in LDAP).
+
+            UserModel johnDb = session.userStorage().getUserByUsername("johnkeycloak", appRealm);
+            Set<RoleModel> johnDbRoles = johnDb.getRoleMappings();
+            Assert.assertFalse(johnDbRoles.contains(realmRole1));
+            Assert.assertFalse(johnDbRoles.contains(realmRole2));
+            Assert.assertFalse(johnDbRoles.contains(realmRole3));
+            Assert.assertFalse(johnDbRoles.contains(financeRole1));
+            Assert.assertTrue(johnDbRoles.contains(manageAccountRole));
+
+            // 3 - Check that role mappings are in LDAP and hence available through federation
+
+            Set<RoleModel> johnRoles = john.getRoleMappings();
+            Assert.assertTrue(johnRoles.contains(realmRole1));
+            Assert.assertFalse(johnRoles.contains(realmRole2));
+            Assert.assertTrue(johnRoles.contains(realmRole3));
+            Assert.assertTrue(johnRoles.contains(financeRole1));
+            Assert.assertTrue(johnRoles.contains(manageAccountRole));
+
+            Set<RoleModel> johnRealmRoles = john.getRealmRoleMappings();
+            Assert.assertEquals(2, johnRealmRoles.size());
+            Assert.assertTrue(johnRealmRoles.contains(realmRole1));
+            Assert.assertTrue(johnRealmRoles.contains(realmRole3));
+
+            // account roles are not mapped in LDAP. Those are in Keycloak DB
+            Set<RoleModel> johnAccountRoles = john.getClientRoleMappings(accountApp);
+            Assert.assertTrue(johnAccountRoles.contains(manageAccountRole));
+
+            Set<RoleModel> johnFinanceRoles = john.getClientRoleMappings(financeApp);
+            Assert.assertEquals(1, johnFinanceRoles.size());
+            Assert.assertTrue(johnFinanceRoles.contains(financeRole1));
+
+            // 4 - Delete some role mappings and check they are deleted
+
+            john.deleteRoleMapping(realmRole3);
+            john.deleteRoleMapping(realmRole1);
+            john.deleteRoleMapping(financeRole1);
+            john.deleteRoleMapping(manageAccountRole);
+
+            johnRoles = john.getRoleMappings();
+            Assert.assertFalse(johnRoles.contains(realmRole1));
+            Assert.assertFalse(johnRoles.contains(realmRole2));
+            Assert.assertFalse(johnRoles.contains(realmRole3));
+            Assert.assertFalse(johnRoles.contains(financeRole1));
+            Assert.assertFalse(johnRoles.contains(manageAccountRole));
+
+            // Cleanup
+            mary.deleteRoleMapping(realmRole2);
+            mary.deleteRoleMapping(realmRole3);
+            john.grantRole(manageAccountRole);
+        } finally {
+            keycloakRule.stopSession(session, false);
+        }
+    }
+
+    @Test
+    public void test02_readOnlyRoleMappings() {
+        KeycloakSession session = keycloakRule.startSession();
+        try {
+            RealmModel appRealm = session.realms().getRealmByName("test");
+
+            FederationTestUtils.addOrUpdateRoleLDAPMappers(appRealm, ldapModel, RoleLDAPFederationMapper.Mode.READ_ONLY);
+
+            UserModel mary = session.users().getUserByUsername("marykeycloak", appRealm);
+
+            RoleModel realmRole1 = appRealm.getRole("realmRole1");
+            RoleModel realmRole2 = appRealm.getRole("realmRole2");
+            RoleModel realmRole3 = appRealm.getRole("realmRole3");
+            if (realmRole3 == null) {
+                realmRole3 = appRealm.addRole("realmRole3");
+            }
+
+            // Add some role mappings directly into LDAP
+            RoleLDAPFederationMapper roleMapper = new RoleLDAPFederationMapper();
+            UserFederationMapperModel roleMapperModel = appRealm.getUserFederationMapperByName(ldapModel.getId(), "realmRolesMapper");
+            LDAPFederationProvider ldapProvider = FederationTestUtils.getLdapProvider(session, ldapModel);
+            LDAPObject maryLdap = ldapProvider.loadLDAPUserByUsername(appRealm, "marykeycloak");
+            roleMapper.addRoleMappingInLDAP(roleMapperModel, "realmRole1", ldapProvider, maryLdap);
+            roleMapper.addRoleMappingInLDAP(roleMapperModel, "realmRole2", ldapProvider, maryLdap);
+
+            // Add some role to model
+            mary.grantRole(realmRole3);
+
+            // Assert that mary has both LDAP and DB mapped roles
+            Set<RoleModel> maryRoles = mary.getRealmRoleMappings();
+            Assert.assertTrue(maryRoles.contains(realmRole1));
+            Assert.assertTrue(maryRoles.contains(realmRole2));
+            Assert.assertTrue(maryRoles.contains(realmRole3));
+
+            // Assert that access through DB will have just DB mapped role
+            UserModel maryDB = session.userStorage().getUserByUsername("marykeycloak", appRealm);
+            Set<RoleModel> maryDBRoles = maryDB.getRealmRoleMappings();
+            Assert.assertFalse(maryDBRoles.contains(realmRole1));
+            Assert.assertFalse(maryDBRoles.contains(realmRole2));
+            Assert.assertTrue(maryDBRoles.contains(realmRole3));
+
+            mary.deleteRoleMapping(realmRole3);
+            try {
+                mary.deleteRoleMapping(realmRole1);
+                Assert.fail("It wasn't expected to successfully delete LDAP role mappings in READ_ONLY mode");
+            } catch (ModelException expected) {
+            }
+
+            // Delete role mappings directly in LDAP
+            deleteRoleMappingsInLDAP(roleMapperModel, roleMapper, ldapProvider, maryLdap, "realmRole1");
+            deleteRoleMappingsInLDAP(roleMapperModel, roleMapper, ldapProvider, maryLdap, "realmRole2");
+
+            // Assert role mappings is not available
+            maryRoles = mary.getRealmRoleMappings();
+            Assert.assertFalse(maryRoles.contains(realmRole1));
+            Assert.assertFalse(maryRoles.contains(realmRole2));
+            Assert.assertFalse(maryRoles.contains(realmRole3));
+        } finally {
+            keycloakRule.stopSession(session, false);
+        }
+    }
+
+    @Test
+    public void test03_importRoleMappings() {
+        KeycloakSession session = keycloakRule.startSession();
+        try {
+            RealmModel appRealm = session.realms().getRealmByName("test");
+
+            FederationTestUtils.addOrUpdateRoleLDAPMappers(appRealm, ldapModel, RoleLDAPFederationMapper.Mode.IMPORT);
+
+            // Add some role mappings directly in LDAP
+            RoleLDAPFederationMapper roleMapper = new RoleLDAPFederationMapper();
+            UserFederationMapperModel roleMapperModel = appRealm.getUserFederationMapperByName(ldapModel.getId(), "realmRolesMapper");
+            LDAPFederationProvider ldapProvider = FederationTestUtils.getLdapProvider(session, ldapModel);
+            LDAPObject robLdap = ldapProvider.loadLDAPUserByUsername(appRealm, "robkeycloak");
+            roleMapper.addRoleMappingInLDAP(roleMapperModel, "realmRole1", ldapProvider, robLdap);
+            roleMapper.addRoleMappingInLDAP(roleMapperModel, "realmRole2", ldapProvider, robLdap);
+
+            // Get user and check that he has requested roles from LDAP
+            UserModel rob = session.users().getUserByUsername("robkeycloak", appRealm);
+            RoleModel realmRole1 = appRealm.getRole("realmRole1");
+            RoleModel realmRole2 = appRealm.getRole("realmRole2");
+            RoleModel realmRole3 = appRealm.getRole("realmRole3");
+            if (realmRole3 == null) {
+                realmRole3 = appRealm.addRole("realmRole3");
+            }
+            Set<RoleModel> robRoles = rob.getRealmRoleMappings();
+            Assert.assertTrue(robRoles.contains(realmRole1));
+            Assert.assertTrue(robRoles.contains(realmRole2));
+            Assert.assertFalse(robRoles.contains(realmRole3));
+
+            // Add some role mappings in model and check that user has it
+            rob.grantRole(realmRole3);
+            robRoles = rob.getRealmRoleMappings();
+            Assert.assertTrue(robRoles.contains(realmRole3));
+
+            // Delete some role mappings in LDAP and check that it doesn't have any effect and user still has role
+            deleteRoleMappingsInLDAP(roleMapperModel, roleMapper, ldapProvider, robLdap, "realmRole1");
+            deleteRoleMappingsInLDAP(roleMapperModel, roleMapper, ldapProvider, robLdap, "realmRole2");
+            robRoles = rob.getRealmRoleMappings();
+            Assert.assertTrue(robRoles.contains(realmRole1));
+            Assert.assertTrue(robRoles.contains(realmRole2));
+
+            // Delete role mappings through model and verifies that user doesn't have them anymore
+            rob.deleteRoleMapping(realmRole1);
+            rob.deleteRoleMapping(realmRole2);
+            rob.deleteRoleMapping(realmRole3);
+            robRoles = rob.getRealmRoleMappings();
+            Assert.assertFalse(robRoles.contains(realmRole1));
+            Assert.assertFalse(robRoles.contains(realmRole2));
+            Assert.assertFalse(robRoles.contains(realmRole3));
+        } finally {
+            keycloakRule.stopSession(session, false);
+        }
+    }
+
+    private void deleteRoleMappingsInLDAP(UserFederationMapperModel roleMapperModel, RoleLDAPFederationMapper roleMapper, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, String roleName) {
+        LDAPIdentityQuery ldapQuery = roleMapper.createRoleQuery(roleMapperModel, ldapProvider);
+        LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder();
+        Condition roleNameCondition = conditionsBuilder.equal(new QueryParameter(LDAPConstants.CN), roleName);
+        ldapQuery.where(roleNameCondition);
+        LDAPObject ldapRole1 = ldapQuery.getFirstResult();
+        roleMapper.deleteRoleMappingInLDAP(roleMapperModel, ldapProvider, ldapUser, ldapRole1);
+    }
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/SyncProvidersTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/SyncProvidersTest.java
index 3aa1954..40ae50c 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/SyncProvidersTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/SyncProvidersTest.java
@@ -7,17 +7,18 @@ import org.junit.Test;
 import org.junit.rules.RuleChain;
 import org.junit.rules.TestRule;
 import org.junit.runners.MethodSorters;
+import org.keycloak.federation.ldap.LDAPFederationProvider;
 import org.keycloak.federation.ldap.LDAPFederationProviderFactory;
 import org.keycloak.federation.ldap.LDAPUtils;
-import org.keycloak.federation.ldap.idm.model.LDAPUser;
-import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
+import org.keycloak.federation.ldap.idm.model.LDAPObject;
+import org.keycloak.models.ClientModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakSessionFactory;
 import org.keycloak.models.LDAPConstants;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserFederationProvider;
 import org.keycloak.models.UserFederationProviderModel;
-import org.keycloak.models.UserModel;
+import org.keycloak.models.UserFederationSyncResult;
 import org.keycloak.models.UserProvider;
 import org.keycloak.services.managers.RealmManager;
 import org.keycloak.services.managers.UsersSyncManager;
@@ -50,18 +51,23 @@ public class SyncProvidersTest {
 
             Map<String,String> ldapConfig = ldapRule.getConfig();
             ldapConfig.put(LDAPConstants.SYNC_REGISTRATIONS, "false");
-            ldapConfig.put(LDAPConstants.EDIT_MODE, UserFederationProvider.EditMode.UNSYNCED.toString());
+            ldapConfig.put(LDAPConstants.EDIT_MODE, UserFederationProvider.EditMode.WRITABLE.toString());
 
             ldapModel = appRealm.addUserFederationProvider(LDAPFederationProviderFactory.PROVIDER_NAME, ldapConfig, 0, "test-ldap",
                     -1, -1, 0);
 
+            FederationTestUtils.addZipCodeLDAPMapper(appRealm, ldapModel);
+
             // Delete all LDAP users and add 5 new users for testing
-            LDAPIdentityStore ldapStore = FederationProvidersIntegrationTest.getLdapIdentityStore(manager.getSession(), ldapModel);
-            LDAPUtils.removeAllUsers(ldapStore);
+            LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel);
+            LDAPUtils.removeAllUsers(ldapFedProvider, appRealm);
+
+            // Add sample application TODO: REmove this!!!! It's just temporarily needed in SyncProvidersTest until model for federation mappers is implemented
+            ClientModel finance = appRealm.addClient("finance");
 
-            for (int i=1 ; i<6 ; i++) {
-                LDAPUser user = LDAPUtils.addUser(ldapStore, "user" + i, "User" + i + "FN", "User" + i + "LN", "user" + i + "@email.org");
-                LDAPUtils.updatePassword(ldapStore, user, "Password1");
+            for (int i=1 ; i<=5 ; i++) {
+                LDAPObject ldapUser = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "user" + i, "User" + i + "FN", "User" + i + "LN", "user" + i + "@email.org", "12" + i);
+                ldapFedProvider.getLdapIdentityStore().updatePassword(ldapUser, "Password1");
             }
 
             // Add dummy provider
@@ -84,7 +90,8 @@ public class SyncProvidersTest {
         KeycloakSession session = keycloakRule.startSession();
         try {
             KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
-            usersSyncManager.syncAllUsers(sessionFactory, "test", ldapModel);
+            UserFederationSyncResult syncResult = usersSyncManager.syncAllUsers(sessionFactory, "test", ldapModel);
+            assertSyncEquals(syncResult, 5, 0, 0);
         } finally {
             keycloakRule.stopSession(session, false);
         }
@@ -94,11 +101,11 @@ public class SyncProvidersTest {
             RealmModel testRealm = session.realms().getRealm("test");
             UserProvider userProvider = session.userStorage();
             // Assert users imported
-            assertUserImported(userProvider, testRealm, "user1", "User1FN", "User1LN", "user1@email.org");
-            assertUserImported(userProvider, testRealm, "user2", "User2FN", "User2LN", "user2@email.org");
-            assertUserImported(userProvider, testRealm, "user3", "User3FN", "User3LN", "user3@email.org");
-            assertUserImported(userProvider, testRealm, "user4", "User4FN", "User4LN", "user4@email.org");
-            assertUserImported(userProvider, testRealm, "user5", "User5FN", "User5LN", "user5@email.org");
+            FederationTestUtils.assertUserImported(userProvider, testRealm, "user1", "User1FN", "User1LN", "user1@email.org", "121");
+            FederationTestUtils.assertUserImported(userProvider, testRealm, "user2", "User2FN", "User2LN", "user2@email.org", "122");
+            FederationTestUtils.assertUserImported(userProvider, testRealm, "user3", "User3FN", "User3LN", "user3@email.org", "123");
+            FederationTestUtils.assertUserImported(userProvider, testRealm, "user4", "User4FN", "User4LN", "user4@email.org", "124");
+            FederationTestUtils.assertUserImported(userProvider, testRealm, "user5", "User5FN", "User5LN", "user5@email.org", "125");
 
             // Assert lastSync time updated
             Assert.assertTrue(ldapModel.getLastSync() > 0);
@@ -115,17 +122,22 @@ public class SyncProvidersTest {
             sleep(1000);
 
             // Add user to LDAP and update 'user5' in LDAP
-            LDAPIdentityStore ldapStore = FederationProvidersIntegrationTest.getLdapIdentityStore(session, ldapModel);
-            LDAPUtils.addUser(ldapStore, "user6", "User6FN", "User6LN", "user6@email.org");
-            LDAPUtils.updateUser(ldapStore, "user5", "User5FNUpdated", "User5LNUpdated", "user5Updated@email.org");
+            LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel);
+            FederationTestUtils.addLDAPUser(ldapFedProvider, testRealm, "user6", "User6FN", "User6LN", "user6@email.org", "126");
+            LDAPObject ldapUser5 = ldapFedProvider.loadLDAPUserByUsername(testRealm, "user5");
+            // NOTE: Changing LDAP attributes directly here
+            ldapUser5.setAttribute(LDAPConstants.EMAIL, "user5Updated@email.org");
+            ldapUser5.setAttribute(LDAPConstants.POSTAL_CODE, "521");
+            ldapFedProvider.getLdapIdentityStore().update(ldapUser5);
 
             // Assert still old users in local provider
-            assertUserImported(userProvider, testRealm, "user5", "User5FN", "User5LN", "user5@email.org");
+            FederationTestUtils.assertUserImported(userProvider, testRealm, "user5", "User5FN", "User5LN", "user5@email.org", "125");
             Assert.assertNull(userProvider.getUserByUsername("user6", testRealm));
 
             // Trigger partial sync
             KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
-            usersSyncManager.syncChangedUsers(sessionFactory, "test", ldapModel);
+            UserFederationSyncResult syncResult = usersSyncManager.syncChangedUsers(sessionFactory, "test", ldapModel);
+            assertSyncEquals(syncResult, 1, 1, 0);
         } finally {
             keycloakRule.stopSession(session, false);
         }
@@ -135,8 +147,8 @@ public class SyncProvidersTest {
             RealmModel testRealm = session.realms().getRealm("test");
             UserProvider userProvider = session.userStorage();
             // Assert users updated in local provider
-            assertUserImported(userProvider, testRealm, "user5", "User5FNUpdated", "User5LNUpdated", "user5Updated@email.org");
-            assertUserImported(userProvider, testRealm, "user6", "User6FN", "User6LN", "user6@email.org");
+            FederationTestUtils.assertUserImported(userProvider, testRealm, "user5", "User5FN", "User5LN", "user5Updated@email.org", "521");
+            FederationTestUtils.assertUserImported(userProvider, testRealm, "user6", "User6FN", "User6LN", "user6@email.org", "126");
         } finally {
             keycloakRule.stopSession(session, false);
         }
@@ -181,11 +193,9 @@ public class SyncProvidersTest {
         }
     }
 
-    public static void assertUserImported(UserProvider userProvider, RealmModel realm, String username, String expectedFirstName, String expectedLastName, String expectedEmail) {
-        UserModel user = userProvider.getUserByUsername(username, realm);
-        Assert.assertNotNull(user);
-        Assert.assertEquals(expectedFirstName, user.getFirstName());
-        Assert.assertEquals(expectedLastName, user.getLastName());
-        Assert.assertEquals(expectedEmail, user.getEmail());
+    private void assertSyncEquals(UserFederationSyncResult syncResult, int expectedAdded, int expectedUpdated, int expectedRemoved) {
+        Assert.assertEquals(syncResult.getAdded(), expectedAdded);
+        Assert.assertEquals(syncResult.getUpdated(), expectedUpdated);
+        Assert.assertEquals(syncResult.getRemoved(), expectedRemoved);
     }
 }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/KeycloakServer.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/KeycloakServer.java
index 2d630e9..74bd0b8 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/KeycloakServer.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/KeycloakServer.java
@@ -167,6 +167,10 @@ public class KeycloakServer {
                 System.setProperty("keycloak.theme.cacheTemplates", "false");
             }
 
+            if (!System.getProperties().containsKey("keycloak.theme.staticMaxAge")) {
+                System.setProperty("keycloak.theme.staticMaxAge", "-1");
+            }
+
             config.setResourcesHome(dir.getAbsolutePath());
         }
 
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AdapterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AdapterTest.java
index f7f48b3..8640d8e 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AdapterTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AdapterTest.java
@@ -13,6 +13,7 @@ import org.keycloak.models.RequiredCredentialModel;
 import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserCredentialModel;
 import org.keycloak.models.UserCredentialValueModel;
+import org.keycloak.models.UserFederationProviderModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserProvider;
 import org.keycloak.representations.idm.CredentialRepresentation;
@@ -23,8 +24,11 @@ import java.security.KeyPairGenerator;
 import java.security.NoSuchAlgorithmException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 import static org.junit.Assert.assertNotNull;
@@ -741,6 +745,59 @@ public class AdapterTest extends AbstractModelTest {
         resetSession();
     }
 
+    @Test
+    public void testUserFederationProviderDisplayNameCollisions() throws Exception {
+        RealmModel realm = realmManager.createRealm("JUGGLER1");
+        Map<String, String> cfg = Collections.emptyMap();
+        realm.addUserFederationProvider("ldap", cfg, 1, "providerName1", -1, -1, 0);
+        realm.addUserFederationProvider("ldap", cfg, 1, "providerName2", -1, -1, 0);
+
+        commit();
+
+        // Try to add federation provider with same display name
+        try {
+            realmManager.getRealmByName("JUGGLER1").addUserFederationProvider("ldap", cfg, 1, "providerName1", -1, -1, 0);
+            commit();
+            Assert.fail("Expected exception");
+        } catch (ModelDuplicateException e) {
+        }
+        commit(true);
+
+        // Try to rename federation provider tu duplicate display name
+        try {
+            List<UserFederationProviderModel> fedProviders = realmManager.getRealmByName("JUGGLER1").getUserFederationProviders();
+            for (UserFederationProviderModel fedProvider : fedProviders) {
+                if ("providerName1".equals(fedProvider.getDisplayName())) {
+                    fedProvider.setDisplayName("providerName2");
+                    realm.updateUserFederationProvider(fedProvider);
+                    break;
+                }
+            }
+            commit();
+            Assert.fail("Expected exception");
+        } catch (ModelDuplicateException e) {
+        }
+        commit(true);
+
+        // Try to rename federation provider tu duplicate display name
+        try {
+            List<UserFederationProviderModel> fedProviders = realmManager.getRealmByName("JUGGLER1").getUserFederationProviders();
+            for (UserFederationProviderModel fedProvider : fedProviders) {
+                if ("providerName1".equals(fedProvider.getDisplayName())) {
+                    fedProvider.setDisplayName("providerName2");
+                    break;
+                }
+            }
+
+            realm.setUserFederationProviders(fedProviders);
+            commit();
+            Assert.fail("Expected exception");
+        } catch (ModelDuplicateException e) {
+        }
+        commit(true);
+
+    }
+
     private KeyPair generateKeypair() throws NoSuchAlgorithmException {
         return KeyPairGenerator.getInstance("RSA").generateKeyPair();
     }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java
index 317c101..4d20bc8 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java
@@ -5,16 +5,20 @@ import org.junit.FixMethodOrder;
 import org.junit.Test;
 import org.junit.runners.MethodSorters;
 import org.keycloak.constants.KerberosConstants;
+import org.keycloak.federation.ldap.mappers.FullNameLDAPFederationMapper;
+import org.keycloak.federation.ldap.mappers.FullNameLDAPFederationMapperFactory;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.Constants;
 import org.keycloak.models.FederatedIdentityModel;
 import org.keycloak.models.IdentityProviderModel;
 import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.LDAPConstants;
 import org.keycloak.models.ProtocolMapperModel;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RequiredCredentialModel;
 import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserConsentModel;
+import org.keycloak.models.UserFederationMapperModel;
 import org.keycloak.models.UserFederationProvider;
 import org.keycloak.models.UserFederationProviderFactory;
 import org.keycloak.models.UserFederationProviderModel;
@@ -213,12 +217,31 @@ public class ImportTest extends AbstractModelTest {
 
         // Test federation providers
         List<UserFederationProviderModel> fedProviders = realm.getUserFederationProviders();
-        Assert.assertTrue(fedProviders.size() == 1);
-        UserFederationProviderModel ldap = fedProviders.get(0);
-        Assert.assertEquals("MyLDAPProvider", ldap.getDisplayName());
-        Assert.assertEquals("dummy", ldap.getProviderName());
-        Assert.assertEquals(1, ldap.getPriority());
-        Assert.assertEquals("ldap://foo", ldap.getConfig().get("important.config"));
+        Assert.assertTrue(fedProviders.size() == 2);
+        UserFederationProviderModel ldap1 = fedProviders.get(0);
+        Assert.assertEquals("MyLDAPProvider1", ldap1.getDisplayName());
+        Assert.assertEquals("ldap", ldap1.getProviderName());
+        Assert.assertEquals(1, ldap1.getPriority());
+        Assert.assertEquals("ldap://foo", ldap1.getConfig().get(LDAPConstants.CONNECTION_URL));
+
+        UserFederationProviderModel ldap2 = fedProviders.get(1);
+        Assert.assertEquals("MyLDAPProvider2", ldap2.getDisplayName());
+        Assert.assertEquals("ldap://bar", ldap2.getConfig().get(LDAPConstants.CONNECTION_URL));
+
+        // Test federation mappers
+        Set<UserFederationMapperModel> fedMappers1 = realm.getUserFederationMappersByFederationProvider(ldap1.getId());
+        Assert.assertTrue(fedMappers1.size() == 1);
+        UserFederationMapperModel fullNameMapper = fedMappers1.iterator().next();
+        Assert.assertEquals("FullNameMapper", fullNameMapper.getName());
+        Assert.assertEquals(FullNameLDAPFederationMapperFactory.ID, fullNameMapper.getFederationMapperType());
+        Assert.assertEquals(ldap1.getId(), fullNameMapper.getFederationProviderId());
+        Assert.assertEquals("cn", fullNameMapper.getConfig().get(FullNameLDAPFederationMapper.LDAP_FULL_NAME_ATTRIBUTE));
+
+        // All builtin LDAP mappers should be here
+        Set<UserFederationMapperModel> fedMappers2 = realm.getUserFederationMappersByFederationProvider(ldap2.getId());
+        Assert.assertTrue(fedMappers2.size() > 3);
+        Set<UserFederationMapperModel> allMappers = realm.getUserFederationMappers();
+        Assert.assertEquals(allMappers.size(), fedMappers1.size() + fedMappers2.size());
 
         // Assert that federation link wasn't created during import
         UserFederationProviderFactory factory = (UserFederationProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(UserFederationProvider.class, "dummy");
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserFederationModelTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserFederationModelTest.java
new file mode 100644
index 0000000..51fc446
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserFederationModelTest.java
@@ -0,0 +1,118 @@
+package org.keycloak.testsuite.model;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.keycloak.models.ModelDuplicateException;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserFederationMapperModel;
+import org.keycloak.models.UserFederationProviderModel;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class UserFederationModelTest extends AbstractModelTest {
+
+
+    @Test
+    public void federationMapperCrudTest() {
+        RealmModel realm = realmManager.createRealm("test-realm");
+        UserFederationProviderModel fedProvider = realm.addUserFederationProvider("dummy", new TreeMap<String, String>(), 1, "my-cool-provider", -1, -1, 0);
+        UserFederationProviderModel fedProvider2 = realm.addUserFederationProvider("dummy", new TreeMap<String, String>(), 1, "my-cool-provider2", -1, -1, 0);
+
+        UserFederationMapperModel mapperModel1 = createMapper("name1", fedProvider.getId(), "key1", "value1");
+        UserFederationMapperModel mapperModel2 = createMapper("name2", fedProvider.getId(), "key2", "value2");
+        UserFederationMapperModel mapperModel3 = createMapper("name1", fedProvider2.getId(), "key3", "value3");
+
+        mapperModel1 = realm.addUserFederationMapper(mapperModel1);
+        mapperModel2 = realm.addUserFederationMapper(mapperModel2);
+        mapperModel3 = realm.addUserFederationMapper(mapperModel3);
+
+        commit();
+
+        try {
+            UserFederationMapperModel conflictMapper = createMapper("name1", fedProvider.getId(), "key4", "value4");
+            realmManager.getRealmByName("test-realm").addUserFederationMapper(conflictMapper);
+            commit();
+            Assert.fail("Don't expect to end here");
+        } catch (ModelDuplicateException expected) {
+        }
+
+        realm = realmManager.getRealmByName("test-realm");
+        Set<UserFederationMapperModel> mappers = realm.getUserFederationMappers();
+        Assert.assertEquals(3, mappers.size());
+        Assert.assertTrue(mappers.contains(mapperModel1));
+        Assert.assertTrue(mappers.contains(mapperModel2));
+        Assert.assertTrue(mappers.contains(mapperModel3));
+
+        mappers = realm.getUserFederationMappersByFederationProvider(fedProvider.getId());
+        Assert.assertEquals(2, mappers.size());
+        Assert.assertTrue(mappers.contains(mapperModel1));
+        Assert.assertTrue(mappers.contains(mapperModel2));
+
+        mapperModel3.getConfig().put("otherKey", "otherValue");
+        realm.updateUserFederationMapper(mapperModel3);
+
+        commit();
+
+        realm = realmManager.getRealmByName("test-realm");
+        mapperModel3 = realm.getUserFederationMapperById(mapperModel3.getId());
+        Assert.assertEquals(2, mapperModel3.getConfig().size());
+        Assert.assertEquals("value3", mapperModel3.getConfig().get("key3"));
+        Assert.assertEquals("otherValue", mapperModel3.getConfig().get("otherKey"));
+    }
+
+
+    @Test
+    public void federationProviderRemovalTest() {
+        RealmModel realm = realmManager.createRealm("test-realm");
+        UserFederationProviderModel fedProvider = realm.addUserFederationProvider("dummy", new TreeMap<String, String>(), 1, "my-cool-provider", -1, -1, 0);
+        UserFederationProviderModel fedProvider2 = realm.addUserFederationProvider("dummy", new TreeMap<String, String>(), 1, "my-cool-provider2", -1, -1, 0);
+
+        UserFederationMapperModel mapperModel1 = createMapper("name1", fedProvider.getId(), "key1", "value1");
+        UserFederationMapperModel mapperModel2 = createMapper("name2", fedProvider.getId(), "key2", "value2");
+        UserFederationMapperModel mapperModel3 = createMapper("name1", fedProvider2.getId(), "key3", "value3");
+
+        mapperModel1 = realm.addUserFederationMapper(mapperModel1);
+        mapperModel2 = realm.addUserFederationMapper(mapperModel2);
+        mapperModel3 = realm.addUserFederationMapper(mapperModel3);
+
+        commit();
+
+        realmManager.getRealmByName("test-realm").removeUserFederationProvider(fedProvider);
+
+        commit();
+
+        realm = realmManager.getRealmByName("test-realm");
+        Set<UserFederationMapperModel> mappers = realm.getUserFederationMappers();
+        Assert.assertEquals(1, mappers.size());
+        Assert.assertEquals(mapperModel3, mappers.iterator().next());
+
+        realm = realmManager.getRealmByName("test-realm");
+        realmManager.removeRealm(realm);
+
+        commit();
+    }
+
+    private UserFederationMapperModel createMapper(String name, String fedProviderId, String... config) {
+        UserFederationMapperModel mapperModel = new UserFederationMapperModel();
+        mapperModel.setName(name);
+        mapperModel.setFederationMapperType("someType");
+        mapperModel.setFederationProviderId(fedProviderId);
+        Map<String, String> configMap = new TreeMap<String, String>();
+        String key = null;
+        for (String configEntry : config) {
+            if (key == null) {
+                key = configEntry;
+            } else {
+                configMap.put(key, configEntry);
+                key = null;
+            }
+        }
+        mapperModel.setConfig(configMap);
+        return mapperModel;
+    }
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java
index 3b11fda..1aa7b21 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java
@@ -44,6 +44,9 @@ public class AccountUpdateProfilePage extends AbstractAccountPage {
     @FindBy(id = "email")
     private WebElement emailInput;
 
+    @FindBy(id = "user.attributes.postal_code")
+    private WebElement postalCodeInput;
+
 
     @FindBy(id = "referrer")
     private WebElement backToApplicationLink;
@@ -84,6 +87,10 @@ public class AccountUpdateProfilePage extends AbstractAccountPage {
         return lastNameInput.getAttribute("value");
     }
 
+    public String getPostalCode() {
+        return postalCodeInput.getAttribute("value");
+    }
+
     public String getEmail() {
         return emailInput.getAttribute("value");
     }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/RegisterPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/RegisterPage.java
index 217cf19..28c3796 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/RegisterPage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/RegisterPage.java
@@ -23,6 +23,7 @@ package org.keycloak.testsuite.pages;
 
 import org.junit.Assert;
 
+import org.openqa.selenium.By;
 import org.openqa.selenium.NoSuchElementException;
 import org.openqa.selenium.WebElement;
 import org.openqa.selenium.support.FindBy;
@@ -56,6 +57,25 @@ public class RegisterPage extends AbstractPage {
     @FindBy(className = "feedback-error")
     private WebElement loginErrorMessage;
 
+    public void register(String firstName, String lastName, String email, String username, String password, String passwordConfirm,
+                         String street, String cityOrLocality, String stateOrRegion, String zipOrPostalCode, String country) {
+        fillExtendedField("street", street);
+        fillExtendedField("locality", cityOrLocality);
+        fillExtendedField("region", stateOrRegion);
+        fillExtendedField("postal_code", zipOrPostalCode);
+        fillExtendedField("country", country);
+
+        register(firstName, lastName, email, username, password, passwordConfirm);
+    }
+
+    private void fillExtendedField(String fieldName, String value) {
+        WebElement field = driver.findElement(By.id("user.attributes." + fieldName));
+        field.clear();
+        if (value != null) {
+            field.sendKeys(value);
+        }
+    }
+
     public void register(String firstName, String lastName, String email, String username, String password, String passwordConfirm) {
         firstNameInput.clear();
         if (firstName != null) {
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/KerberosRule.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/KerberosRule.java
index d2f0ed7..9026557 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/KerberosRule.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/KerberosRule.java
@@ -5,7 +5,7 @@ import java.net.URL;
 
 import org.jboss.logging.Logger;
 import org.keycloak.testsuite.ldap.EmbeddedServersFactory;
-import org.keycloak.testsuite.ldap.LDAPConfiguration;
+import org.keycloak.testsuite.ldap.LDAPTestConfiguration;
 import org.keycloak.testsuite.ldap.LDAPEmbeddedServer;
 
 /**
@@ -21,7 +21,7 @@ public class KerberosRule extends LDAPRule {
         this.configLocation = configLocation;
 
         // Global kerberos configuration
-        URL krb5ConfURL = LDAPConfiguration.class.getResource("/kerberos/test-krb5.conf");
+        URL krb5ConfURL = LDAPTestConfiguration.class.getResource("/kerberos/test-krb5.conf");
         String krb5ConfPath = new File(krb5ConfURL.getFile()).getAbsolutePath();
         log.info("Krb5.conf file location is: " + krb5ConfPath);
         System.setProperty("java.security.krb5.conf", krb5ConfPath);
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
index 290341c..1bf9b8e 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/LDAPRule.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/LDAPRule.java
@@ -4,7 +4,7 @@ import java.util.Map;
 
 import org.junit.rules.ExternalResource;
 import org.keycloak.testsuite.ldap.EmbeddedServersFactory;
-import org.keycloak.testsuite.ldap.LDAPConfiguration;
+import org.keycloak.testsuite.ldap.LDAPTestConfiguration;
 import org.keycloak.testsuite.ldap.LDAPEmbeddedServer;
 
 /**
@@ -14,15 +14,15 @@ public class LDAPRule extends ExternalResource {
 
     public static final String LDAP_CONNECTION_PROPERTIES_LOCATION = "ldap/ldap-connection.properties";
 
-    protected LDAPConfiguration ldapConfiguration;
+    protected LDAPTestConfiguration ldapTestConfiguration;
     protected LDAPEmbeddedServer ldapEmbeddedServer;
 
     @Override
     protected void before() throws Throwable {
         String connectionPropsLocation = getConnectionPropertiesLocation();
-        ldapConfiguration = LDAPConfiguration.readConfiguration(connectionPropsLocation);
+        ldapTestConfiguration = LDAPTestConfiguration.readConfiguration(connectionPropsLocation);
 
-        if (ldapConfiguration.isStartEmbeddedLdapLerver()) {
+        if (ldapTestConfiguration.isStartEmbeddedLdapLerver()) {
             EmbeddedServersFactory factory = EmbeddedServersFactory.readConfiguration();
             ldapEmbeddedServer = createServer(factory);
             ldapEmbeddedServer.init();
@@ -36,7 +36,7 @@ public class LDAPRule extends ExternalResource {
             if (ldapEmbeddedServer != null) {
                 ldapEmbeddedServer.stop();
                 ldapEmbeddedServer = null;
-                ldapConfiguration = null;
+                ldapTestConfiguration = null;
             }
         } catch (Exception e) {
             throw new RuntimeException("Error tearDown Embedded LDAP server.", e);
@@ -52,6 +52,6 @@ public class LDAPRule extends ExternalResource {
     }
 
     public Map<String, String> getConfig() {
-        return ldapConfiguration.getLDAPConfig();
+        return ldapTestConfiguration.getLDAPConfig();
     }
 }
diff --git a/testsuite/integration/src/test/resources/ldap/users.ldif b/testsuite/integration/src/test/resources/ldap/users.ldif
index b41d9f5..b1777d7 100644
--- a/testsuite/integration/src/test/resources/ldap/users.ldif
+++ b/testsuite/integration/src/test/resources/ldap/users.ldif
@@ -9,13 +9,31 @@ objectclass: top
 objectclass: organizationalUnit
 ou: People
 
-dn: ou=Roles,dc=keycloak,dc=org
+dn: ou=RealmRoles,dc=keycloak,dc=org
 objectclass: top
 objectclass: organizationalUnit
-ou: Roles
+ou: RealmRoles
 
-dn: ou=Groups,dc=keycloak,dc=org
+dn: cn=realmRole1,ou=RealmRoles,dc=keycloak,dc=org
+objectclass: top
+objectclass: groupOfNames
+cn: realmRole1
+member:
+
+dn: cn=realmRole2,ou=RealmRoles,dc=keycloak,dc=org
+objectclass: top
+objectclass: groupOfNames
+cn: realmRole2
+member:
+
+dn: ou=FinanceRoles,dc=keycloak,dc=org
 objectclass: top
 objectclass: organizationalUnit
-ou: Groups
+ou: FinanceRoles
+
+dn: cn=financeRole1,ou=FinanceRoles,dc=keycloak,dc=org
+objectclass: top
+objectclass: groupOfNames
+cn: financeRole1
+member:
 
diff --git a/testsuite/integration/src/test/resources/model/testrealm.json b/testsuite/integration/src/test/resources/model/testrealm.json
index 50aee59..0e41313 100755
--- a/testsuite/integration/src/test/resources/model/testrealm.json
+++ b/testsuite/integration/src/test/resources/model/testrealm.json
@@ -25,11 +25,29 @@
     ],
     "userFederationProviders": [
         {
-            "displayName": "MyLDAPProvider",
-            "providerName": "dummy",
+            "displayName": "MyLDAPProvider1",
+            "providerName": "ldap",
             "priority": 1,
             "config": {
-                "important.config": "ldap://foo"
+                "connectionUrl": "ldap://foo"
+            }
+        },
+        {
+            "displayName": "MyLDAPProvider2",
+            "providerName": "ldap",
+            "priority": 2,
+            "config": {
+                "connectionUrl": "ldap://bar"
+            }
+        }
+    ],
+    "userFederationMappers": [
+        {
+            "name": "FullNameMapper",
+            "federationProviderDisplayName": "MyLDAPProvider1",
+            "federationMapperType": "full-name-ldap-mapper",
+            "config": {
+                "ldap.full.name.attribute": "cn"
             }
         }
     ],
diff --git a/testsuite/jetty/jetty81/pom.xml b/testsuite/jetty/jetty81/pom.xml
index fa462ad..353d2d4 100755
--- a/testsuite/jetty/jetty81/pom.xml
+++ b/testsuite/jetty/jetty81/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-testsuite-pom</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/testsuite/jetty/jetty91/pom.xml b/testsuite/jetty/jetty91/pom.xml
index de4b06f..7d9b913 100755
--- a/testsuite/jetty/jetty91/pom.xml
+++ b/testsuite/jetty/jetty91/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-testsuite-pom</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/testsuite/jetty/jetty92/pom.xml b/testsuite/jetty/jetty92/pom.xml
index dc9e183..3f72eb1 100755
--- a/testsuite/jetty/jetty92/pom.xml
+++ b/testsuite/jetty/jetty92/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-testsuite-pom</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/testsuite/performance/pom.xml b/testsuite/performance/pom.xml
index bd79b0e..5fcb5fc 100755
--- a/testsuite/performance/pom.xml
+++ b/testsuite/performance/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <artifactId>keycloak-testsuite-pom</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/testsuite/pom.xml b/testsuite/pom.xml
index 92872a0..d3db601 100755
--- a/testsuite/pom.xml
+++ b/testsuite/pom.xml
@@ -4,7 +4,7 @@
 	<parent>
 		<artifactId>keycloak-parent</artifactId>
 		<groupId>org.keycloak</groupId>
-		<version>1.3.0.Beta1-SNAPSHOT</version>
+		<version>1.3.0.Final-SNAPSHOT</version>
 		<relativePath>../pom.xml</relativePath>
 	</parent>
 	<modelVersion>4.0.0</modelVersion>
diff --git a/testsuite/proxy/pom.xml b/testsuite/proxy/pom.xml
index 2a78fd9..1643296 100755
--- a/testsuite/proxy/pom.xml
+++ b/testsuite/proxy/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-testsuite-pom</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/testsuite/tomcat6/pom.xml b/testsuite/tomcat6/pom.xml
index 9ac52d5..d10d244 100755
--- a/testsuite/tomcat6/pom.xml
+++ b/testsuite/tomcat6/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-testsuite-pom</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/testsuite/tomcat7/pom.xml b/testsuite/tomcat7/pom.xml
index 9548426..20934d3 100755
--- a/testsuite/tomcat7/pom.xml
+++ b/testsuite/tomcat7/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-testsuite-pom</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/testsuite/tomcat8/pom.xml b/testsuite/tomcat8/pom.xml
index 9a1a2e9..0377697 100755
--- a/testsuite/tomcat8/pom.xml
+++ b/testsuite/tomcat8/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-testsuite-pom</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/testsuite/wildfly/pom.xml b/testsuite/wildfly/pom.xml
index 4fbbe04..a77104e 100644
--- a/testsuite/wildfly/pom.xml
+++ b/testsuite/wildfly/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <artifactId>keycloak-testsuite-pom</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/timer/api/pom.xml b/timer/api/pom.xml
index ce2866f..f51b947 100755
--- a/timer/api/pom.xml
+++ b/timer/api/pom.xml
@@ -3,7 +3,7 @@
     <parent>
         <artifactId>keycloak-timer-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
     </parent>
 
     <modelVersion>4.0.0</modelVersion>
diff --git a/timer/basic/pom.xml b/timer/basic/pom.xml
index 593f6cf..cde6e33 100755
--- a/timer/basic/pom.xml
+++ b/timer/basic/pom.xml
@@ -3,7 +3,7 @@
     <parent>
         <artifactId>keycloak-timer-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
     </parent>
 
     <modelVersion>4.0.0</modelVersion>

timer/pom.xml 2(+1 -1)

diff --git a/timer/pom.xml b/timer/pom.xml
index 88ff98d..912db1f 100755
--- a/timer/pom.xml
+++ b/timer/pom.xml
@@ -3,7 +3,7 @@
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
-        <version>1.3.0.Beta1-SNAPSHOT</version>
+        <version>1.3.0.Final-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>