keycloak-aplcache

Changes

Details

diff --git a/connections/jpa/src/main/resources/META-INF/persistence.xml b/connections/jpa/src/main/resources/META-INF/persistence.xml
index 5bcb77e..a5af465 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/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 27869eb..73b9812 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"/>
@@ -21,6 +22,33 @@
             <column name="REPRESENTATION" type="VARCHAR(25500)"/>
             <column name="ERROR" type="VARCHAR(255)"/>
         </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"/>
@@ -67,6 +95,11 @@
         <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="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/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/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/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 63b5443..0a2a767 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
@@ -475,12 +475,12 @@ public class LDAPOperationManager {
 
         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);
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 ad55bc3..fcdc11c 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
@@ -111,7 +111,7 @@ public class LDAPFederationProvider implements UserFederationProvider {
                 proxied = new UnsyncedLDAPUserModelDelegate(local, this);
         }
 
-        List<UserFederationMapperModel> federationMappers = realm.getUserFederationMappers();
+        Set<UserFederationMapperModel> federationMappers = realm.getUserFederationMappers();
         for (UserFederationMapperModel mapperModel : federationMappers) {
             LDAPFederationMapper ldapMapper = getMapper(mapperModel);
             proxied = ldapMapper.proxy(mapperModel, this, ldapObject, proxied, realm);
@@ -268,7 +268,7 @@ public class LDAPFederationProvider implements UserFederationProvider {
         UserModel imported = session.userStorage().addUser(realm, ldapUsername);
         imported.setEnabled(true);
 
-        List<UserFederationMapperModel> federationMappers = realm.getUserFederationMappers();
+        Set<UserFederationMapperModel> federationMappers = realm.getUserFederationMappers();
         for (UserFederationMapperModel mapperModel : federationMappers) {
             LDAPFederationMapper ldapMapper = getMapper(mapperModel);
             ldapMapper.onImportUserFromLDAP(mapperModel, this, ldapUser, imported, realm, true);
@@ -404,7 +404,7 @@ public class LDAPFederationProvider implements UserFederationProvider {
                 if ((fedModel.getId().equals(currentUser.getFederationLink())) && (ldapUser.getUuid().equals(currentUser.getAttribute(LDAPConstants.LDAP_ID)))) {
 
                     // Update keycloak user
-                    List<UserFederationMapperModel> federationMappers = realm.getUserFederationMappers();
+                    Set<UserFederationMapperModel> federationMappers = realm.getUserFederationMappers();
                     for (UserFederationMapperModel mapperModel : federationMappers) {
                         LDAPFederationMapper ldapMapper = getMapper(mapperModel);
                         ldapMapper.onImportUserFromLDAP(mapperModel, this, ldapUser, currentUser, realm, false);
@@ -477,9 +477,9 @@ public class LDAPFederationProvider implements UserFederationProvider {
     }
 
     public LDAPFederationMapper getMapper(UserFederationMapperModel mapperModel) {
-        LDAPFederationMapper ldapMapper = (LDAPFederationMapper) getSession().getProvider(UserFederationMapper.class, mapperModel.getFederationMapperId());
+        LDAPFederationMapper ldapMapper = (LDAPFederationMapper) getSession().getProvider(UserFederationMapper.class, mapperModel.getFederationMapperType());
         if (ldapMapper == null) {
-            throw new ModelException("Can't find mapper type with ID: " + mapperModel.getFederationMapperId());
+            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/LDAPUtils.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPUtils.java
index 6c5ac93..1e9a9e3 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,6 +1,7 @@
 package org.keycloak.federation.ldap;
 
 import java.util.List;
+import java.util.Set;
 
 import org.keycloak.federation.ldap.idm.model.LDAPDn;
 import org.keycloak.federation.ldap.idm.model.LDAPObject;
@@ -34,7 +35,7 @@ public class LDAPUtils {
         ldapObject.setRdnAttributeName(ldapConfig.getRdnLdapAttribute());
         ldapObject.setObjectClasses(ldapConfig.getObjectClasses());
 
-        List<UserFederationMapperModel> federationMappers = realm.getUserFederationMappers();
+        Set<UserFederationMapperModel> federationMappers = realm.getUserFederationMappers();
         for (UserFederationMapperModel mapperModel : federationMappers) {
             LDAPFederationMapper ldapMapper = ldapProvider.getMapper(mapperModel);
             ldapMapper.onRegisterUserToLDAP(mapperModel, ldapProvider, ldapObject, user, realm);
@@ -130,7 +131,7 @@ public class LDAPUtils {
         ldapQuery.addSearchDns(config.getUserDns());
         ldapQuery.addObjectClasses(config.getObjectClasses());
 
-        List<UserFederationMapperModel> mapperModels = realm.getUserFederationMappers();
+        Set<UserFederationMapperModel> mapperModels = realm.getUserFederationMappers();
         ldapQuery.addMappers(mapperModels);
 
         return ldapQuery;
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
index 6820345..6ef6979 100644
--- 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
@@ -11,6 +11,8 @@ import org.keycloak.provider.ProviderConfigProperty;
  */
 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";
@@ -23,7 +25,7 @@ public class FullNameLDAPFederationMapperFactory extends AbstractLDAPFederationM
 
     @Override
     public String getId() {
-        return "full-name-ldap-mapper";
+        return ID;
     }
 
     @Override
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
index 321a0d5..cbae850 100644
--- 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
@@ -11,6 +11,8 @@ import org.keycloak.provider.ProviderConfigProperty;
  */
 public class RoleLDAPFederationMapperFactory extends AbstractLDAPFederationMapperFactory {
 
+    public static final String ID = "role-ldap-mapper";
+
     @Override
     public String getHelpText() {
         return "Some help text - role mapper - TODO";
@@ -23,7 +25,7 @@ public class RoleLDAPFederationMapperFactory extends AbstractLDAPFederationMappe
 
     @Override
     public String getId() {
-        return "role-ldap-mapper";
+        return ID ;
     }
 
     @Override
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
index 16d4879..564b012 100644
--- 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
@@ -11,6 +11,8 @@ import org.keycloak.provider.ProviderConfigProperty;
  */
 public class UserAttributeLDAPFederationMapperFactory extends AbstractLDAPFederationMapperFactory {
 
+    public static final String ID = "user-attribute-ldap-mapper";
+
     @Override
     public String getHelpText() {
         return "Some help text TODO";
@@ -23,7 +25,7 @@ public class UserAttributeLDAPFederationMapperFactory extends AbstractLDAPFedera
 
     @Override
     public String getId() {
-        return "user-attribute-ldap-mapper";
+        return ID;
     }
 
     @Override
diff --git a/model/api/src/main/java/org/keycloak/mappers/UserFederationMapperSpi.java b/model/api/src/main/java/org/keycloak/mappers/UserFederationMapperSpi.java
index b67a23a..f668058 100644
--- a/model/api/src/main/java/org/keycloak/mappers/UserFederationMapperSpi.java
+++ b/model/api/src/main/java/org/keycloak/mappers/UserFederationMapperSpi.java
@@ -23,4 +23,9 @@ public class UserFederationMapperSpi implements Spi {
     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 718a1c5..1b8ccf2 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>();
@@ -426,6 +427,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/RealmModel.java b/model/api/src/main/java/org/keycloak/models/RealmModel.java
index 1247bd9..bfcaefb 100755
--- a/model/api/src/main/java/org/keycloak/models/RealmModel.java
+++ b/model/api/src/main/java/org/keycloak/models/RealmModel.java
@@ -189,7 +189,13 @@ public interface RealmModel extends RoleContainerModel {
     void removeUserFederationProvider(UserFederationProviderModel provider);
     void setUserFederationProviders(List<UserFederationProviderModel> providers);
 
-    List<UserFederationMapperModel> getUserFederationMappers();
+    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();
 
diff --git a/model/api/src/main/java/org/keycloak/models/UserFederationMapperModel.java b/model/api/src/main/java/org/keycloak/models/UserFederationMapperModel.java
index 50b47c3..54a347b 100644
--- a/model/api/src/main/java/org/keycloak/models/UserFederationMapperModel.java
+++ b/model/api/src/main/java/org/keycloak/models/UserFederationMapperModel.java
@@ -9,7 +9,13 @@ public class UserFederationMapperModel {
 
     protected String id;
     protected String name;
-    protected String federationMapperId;
+
+    // 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() {
@@ -28,12 +34,20 @@ public class UserFederationMapperModel {
         this.name = name;
     }
 
-    public String getFederationMapperId() {
-        return federationMapperId;
+    public String getFederationProviderId() {
+        return federationProviderId;
+    }
+
+    public void setFederationProviderId(String federationProviderId) {
+        this.federationProviderId = federationProviderId;
+    }
+
+    public String getFederationMapperType() {
+        return federationMapperType;
     }
 
-    public void setFederationMapperId(String federationMapperId) {
-        this.federationMapperId = federationMapperId;
+    public void setFederationMapperType(String federationMapperType) {
+        this.federationMapperType = federationMapperType;
     }
 
     public Map<String, String> getConfig() {
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 64625c5..382d9c0 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,12 @@ 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.UserFederationProvider;
+import org.keycloak.models.UserFederationProviderModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.util.CertificateUtils;
 import org.keycloak.util.PemUtils;
@@ -23,6 +26,8 @@ import java.security.NoSuchAlgorithmException;
 import java.security.PrivateKey;
 import java.security.PublicKey;
 import java.security.cert.X509Certificate;
+import java.util.LinkedList;
+import java.util.List;
 import java.util.Set;
 import java.util.UUID;
 
@@ -267,4 +272,49 @@ public final class KeycloakModelUtils {
         }
         return false;
     }
+
+    /**
+     * 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;
+    }
 }
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..2d20266 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,12 +5,14 @@ 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;
@@ -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.getId(), 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 9f84087..f59f0b5 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;
@@ -240,6 +243,11 @@ public class RepresentationToModel {
             List<UserFederationProviderModel> providerModels = convertFederationProviders(rep.getUserFederationProviders());
             newRealm.setUserFederationProviders(providerModels);
         }
+        if (rep.getUserFederationMappers() != null) {
+            for (UserFederationMapperRepresentation representation : rep.getUserFederationMappers()) {
+                newRealm.addUserFederationMapper(toModel(newRealm, representation));
+            }
+        }
 
         // create users and their role mappings and social mappings
 
@@ -475,6 +483,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/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 12ace4c..4092ffa 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
@@ -35,6 +35,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;
 
@@ -812,6 +813,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);
@@ -837,6 +840,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();
             }
         }
@@ -844,6 +853,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();
@@ -889,6 +900,10 @@ 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();
@@ -1063,16 +1078,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;
@@ -1084,16 +1090,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;
@@ -1102,7 +1099,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();
@@ -1186,8 +1183,116 @@ public class RealmAdapter implements RealmModel {
     }
 
     @Override
-    public List<UserFederationMapperModel> getUserFederationMappers() {
-        throw new IllegalStateException("Not yet implemented");
+    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");
+        }
+        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/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 22f57a9..faf7ba2 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
@@ -9,6 +9,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;
@@ -70,6 +71,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>();
@@ -136,6 +138,9 @@ public class CachedRealm {
 
         requiredCredentials = model.getRequiredCredentials();
         userFederationProviders = model.getUserFederationProviders();
+        for (UserFederationMapperModel mapper : model.getUserFederationMappers()) {
+            userFederationMappers.add(mapper.getFederationProviderId(), mapper);
+        }
 
         this.identityProviders = new ArrayList<>();
 
@@ -373,6 +378,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 d62d8d8..7784724 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
@@ -952,79 +952,67 @@ public class RealmAdapter implements RealmModel {
         return null;
     }
 
-    public static String LDAP_MODE = "LDAP_ONLY";
-
-    @Override
-    public List<UserFederationMapperModel> getUserFederationMappers() {
-        // TODO: Some hardcoded stuff...
-        List<UserFederationMapperModel> mappers = new ArrayList<UserFederationMapperModel>();
-
-        mappers.add(createMapperModel("usn", "usernameMapper", "user-attribute-ldap-mapper",
-                "user.model.attribute", UserModel.USERNAME,
-                "ldap.attribute", LDAPConstants.UID));
-
-        // Uncomment this for CN + SN config
-        /*mappers.add(createMapperModel("fn", "firstNameMapper", "user-attribute-ldap-mapper",
-                "user.model.attribute", UserModel.FIRST_NAME,
-                "ldap.attribute", LDAPConstants.CN));*/
-
-        // Uncomment this for CN + SN + givenname config
-        mappers.add(createMapperModel("fn", "firstNameMapper", "user-attribute-ldap-mapper",
-                "user.model.attribute", UserModel.FIRST_NAME,
-                "ldap.attribute", LDAPConstants.GIVENNAME));
-        mappers.add(createMapperModel("fulln", "fullNameMapper", "full-name-ldap-mapper",
-                "ldap.full.name.attribute", LDAPConstants.CN));
-
-        mappers.add(createMapperModel("ln", "lastNameMapper", "user-attribute-ldap-mapper",
-                "user.model.attribute", UserModel.LAST_NAME,
-                "ldap.attribute", LDAPConstants.SN));
-
-        mappers.add(createMapperModel("emailMpr", "emailMapper", "user-attribute-ldap-mapper",
-                "user.model.attribute", UserModel.EMAIL,
-                "ldap.attribute", LDAPConstants.EMAIL));
-        mappers.add(createMapperModel("postalCodeMpr", "postalCodeMapper", "user-attribute-ldap-mapper",
-                "user.model.attribute", "postal_code",
-                "ldap.attribute", LDAPConstants.POSTAL_CODE));
-        mappers.add(createMapperModel("createdDateMpr", "createTimeStampMapper", "user-attribute-ldap-mapper",
-                "user.model.attribute", LDAPConstants.CREATE_TIMESTAMP,
-                "ldap.attribute", LDAPConstants.CREATE_TIMESTAMP,
-                "read.only", "true"));
-        mappers.add(createMapperModel("modifyDateMpr", "modifyTimeStampMapper", "user-attribute-ldap-mapper",
-                "user.model.attribute", LDAPConstants.MODIFY_TIMESTAMP,
-                "ldap.attribute", LDAPConstants.MODIFY_TIMESTAMP,
-                "read.only", "true"));
-
-        mappers.add(createMapperModel("realmRoleMpr", "realmRoleMapper", "role-ldap-mapper",
-                "roles.dn", "ou=RealmRoles,dc=keycloak,dc=org",
-                "use.realm.roles.mapping", "true",
-                "mode", LDAP_MODE));
-        mappers.add(createMapperModel("financeRoleMpr", "financeRoleMapper", "role-ldap-mapper",
-                "roles.dn", "ou=FinanceRoles,dc=keycloak,dc=org",
-                "use.realm.roles.mapping", "false",
-                "client.id", "finance",
-                "mode", LDAP_MODE));
+    @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;
     }
 
-    private static UserFederationMapperModel createMapperModel(String id, String name, String mapperId, String... config) {
-        UserFederationMapperModel mapperModel = new UserFederationMapperModel();
-        mapperModel.setId(id);
-        mapperModel.setName(name);
-        mapperModel.setFederationMapperId(mapperId);
-
-        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;
+    @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;
             }
         }
-        mapperModel.setConfig(configMap);
-        return mapperModel;
+        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;
     }
 
 }
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 cdf314c..6c28b9f 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<>();
@@ -475,6 +478,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 3dde3d3..990b087 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
@@ -5,11 +5,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.ModelException;
 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.UserFederationProvider;
 import org.keycloak.models.UserFederationProviderModel;
 import org.keycloak.models.jpa.entities.ClientEntity;
 import org.keycloak.models.jpa.entities.IdentityProviderEntity;
@@ -18,6 +21,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;
 
@@ -760,6 +764,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);
@@ -786,7 +792,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;
@@ -795,6 +808,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();
@@ -817,6 +832,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()) {
@@ -881,6 +899,15 @@ public class RealmAdapter implements RealmModel {
         }
     }
 
+    protected UserFederationProviderEntity getUserFederationProviderEntityById(String federationProviderId) {
+        for (UserFederationProviderEntity entity : realm.getUserFederationProviders()) {
+            if (entity.getId().equals(federationProviderId)) {
+                return entity;
+            }
+        }
+        return null;
+    }
+
     @Override
     public RoleModel getRole(String name) {
         TypedQuery<RoleEntity> query = em.createNamedQuery("getRealmRoleByName", RoleEntity.class);
@@ -1225,16 +1252,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;
@@ -1247,16 +1265,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;
@@ -1265,7 +1274,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();
@@ -1353,8 +1362,122 @@ public class RealmAdapter implements RealmModel {
     }
 
     @Override
-    public List<UserFederationMapperModel> getUserFederationMappers() {
-        throw new IllegalStateException("Not yet implemented");
+    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");
+        }
+        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;
     }
 
 }
\ No newline at end of file
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 9f68d8f..a327a8d 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
@@ -9,6 +9,7 @@ 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;
@@ -19,6 +20,7 @@ import org.keycloak.models.UserFederationProviderModel;
 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;
@@ -833,6 +835,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);
@@ -859,6 +863,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();
             }
         }
@@ -867,6 +877,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();
@@ -913,6 +925,10 @@ 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();
@@ -923,7 +939,7 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
             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());
@@ -1089,16 +1105,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;
@@ -1111,16 +1118,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;
@@ -1129,7 +1127,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();
@@ -1169,6 +1167,7 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
         IdentityProviderMapperEntity toDelete = getIdentityProviderMapperEntity(mapping.getId());
         if (toDelete != null) {
             this.realm.getIdentityProviderMappers().remove(toDelete);
+            updateMongoEntity();
         }
 
     }
@@ -1215,7 +1214,119 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
     }
 
     @Override
-    public List<UserFederationMapperModel> getUserFederationMappers() {
-        throw new IllegalStateException("Not yet implemented");
+    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");
+        }
+        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/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/LDAPRoleMappingsTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/LDAPRoleMappingsTest.java
index bdc3574..eff3572 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/LDAPRoleMappingsTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/LDAPRoleMappingsTest.java
@@ -144,7 +144,7 @@ public class LDAPRoleMappingsTest {
     @Test
     public void test01_ldapOnlyRoleMappings() {
         // TODO: Remove me!!!
-        RealmAdapter.LDAP_MODE = "LDAP_ONLY";
+        //RealmAdapter.LDAP_MODE = "LDAP_ONLY";
 
         KeycloakSession session = keycloakRule.startSession();
         try {
@@ -231,7 +231,7 @@ public class LDAPRoleMappingsTest {
     @Test
     public void test02_readOnlyRoleMappings() {
         // TODO: Remove me!!!
-        RealmAdapter.LDAP_MODE = "READ_ONLY";
+        //RealmAdapter.LDAP_MODE = "READ_ONLY";
 
         KeycloakSession session = keycloakRule.startSession();
         try {
@@ -293,7 +293,7 @@ public class LDAPRoleMappingsTest {
     @Test
     public void test03_importRoleMappings() {
         // TODO: Remove me!!!
-        RealmAdapter.LDAP_MODE = "IMPORT";
+        //RealmAdapter.LDAP_MODE = "IMPORT";
 
         KeycloakSession session = keycloakRule.startSession();
         try {
@@ -346,7 +346,7 @@ public class LDAPRoleMappingsTest {
     }
 
     private static UserFederationMapperModel findRoleMapperModel(RealmModel appRealm) {
-        List<UserFederationMapperModel> fedMappers = appRealm.getUserFederationMappers();
+        Set<UserFederationMapperModel> fedMappers = appRealm.getUserFederationMappers();
         for (UserFederationMapperModel mapper : fedMappers) {
             if ("realmRoleMapper".equals(mapper.getName())) {
                 return mapper;
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..492aa1d 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,6 +5,8 @@ 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;
@@ -15,6 +17,7 @@ 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;
@@ -220,6 +223,15 @@ public class ImportTest extends AbstractModelTest {
         Assert.assertEquals(1, ldap.getPriority());
         Assert.assertEquals("ldap://foo", ldap.getConfig().get("important.config"));
 
+        // Test federation mappers
+        Set<UserFederationMapperModel> fedMappers = realm.getUserFederationMappers();
+        Assert.assertTrue(fedMappers.size() == 1);
+        UserFederationMapperModel fullNameMapper = fedMappers.iterator().next();
+        Assert.assertEquals("FullNameMapper", fullNameMapper.getName());
+        Assert.assertEquals(FullNameLDAPFederationMapperFactory.ID, fullNameMapper.getFederationMapperType());
+        Assert.assertEquals(ldap.getId(), fullNameMapper.getFederationProviderId());
+        Assert.assertEquals("cn", fullNameMapper.getConfig().get(FullNameLDAPFederationMapper.LDAP_FULL_NAME_ATTRIBUTE));
+
         // Assert that federation link wasn't created during import
         UserFederationProviderFactory factory = (UserFederationProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(UserFederationProvider.class, "dummy");
         Assert.assertNull(factory.getInstance(session, null).getUserByUsername(realm, "wburke"));
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/resources/model/testrealm.json b/testsuite/integration/src/test/resources/model/testrealm.json
index 50aee59..43458c9 100755
--- a/testsuite/integration/src/test/resources/model/testrealm.json
+++ b/testsuite/integration/src/test/resources/model/testrealm.json
@@ -33,6 +33,16 @@
             }
         }
     ],
+    "userFederationMappers": [
+        {
+            "name": "FullNameMapper",
+            "federationProviderDisplayName": "MyLDAPProvider",
+            "federationMapperType": "full-name-ldap-mapper",
+            "config": {
+                "ldap.full.name.attribute": "cn"
+            }
+        }
+    ],
     "users": [
         {
             "username": "wburke",