keycloak-aplcache

Changes

model/api/src/main/java/org/keycloak/models/UserFederationMapperEventImpl.java 33(+0 -33)

Details

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 67cb127..e76e93f 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
@@ -37,6 +37,7 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro
             "org.keycloak.models.mongo.keycloak.entities.MongoRoleEntity",
             "org.keycloak.models.mongo.keycloak.entities.MongoGroupEntity",
             "org.keycloak.models.mongo.keycloak.entities.MongoClientEntity",
+            "org.keycloak.models.mongo.keycloak.entities.MongoClientTemplateEntity",
             "org.keycloak.models.mongo.keycloak.entities.MongoUserConsentEntity",
             "org.keycloak.models.mongo.keycloak.entities.MongoMigrationModelEntity",
             "org.keycloak.models.mongo.keycloak.entities.MongoOnlineUserSessionEntity",
diff --git a/core/src/main/java/org/keycloak/representations/idm/UserFederationMapperSyncConfigRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/UserFederationMapperSyncConfigRepresentation.java
new file mode 100644
index 0000000..709a616
--- /dev/null
+++ b/core/src/main/java/org/keycloak/representations/idm/UserFederationMapperSyncConfigRepresentation.java
@@ -0,0 +1,56 @@
+package org.keycloak.representations.idm;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class UserFederationMapperSyncConfigRepresentation {
+
+    private Boolean fedToKeycloakSyncSupported;
+    private String fedToKeycloakSyncMessage; // applicable just if fedToKeycloakSyncSupported is true
+
+    private Boolean keycloakToFedSyncSupported;
+    private String keycloakToFedSyncMessage; // applicable just if keycloakToFedSyncSupported is true
+
+    public UserFederationMapperSyncConfigRepresentation() {
+    }
+
+    public UserFederationMapperSyncConfigRepresentation(boolean fedToKeycloakSyncSupported, String fedToKeycloakSyncMessage,
+                                                        boolean keycloakToFedSyncSupported, String keycloakToFedSyncMessage) {
+        this.fedToKeycloakSyncSupported = fedToKeycloakSyncSupported;
+        this.fedToKeycloakSyncMessage = fedToKeycloakSyncMessage;
+        this.keycloakToFedSyncSupported = keycloakToFedSyncSupported;
+        this.keycloakToFedSyncMessage = keycloakToFedSyncMessage;
+    }
+
+    public Boolean isFedToKeycloakSyncSupported() {
+        return fedToKeycloakSyncSupported;
+    }
+
+    public void setFedToKeycloakSyncSupported(Boolean fedToKeycloakSyncSupported) {
+        this.fedToKeycloakSyncSupported = fedToKeycloakSyncSupported;
+    }
+
+    public String getFedToKeycloakSyncMessage() {
+        return fedToKeycloakSyncMessage;
+    }
+
+    public void setFedToKeycloakSyncMessage(String fedToKeycloakSyncMessage) {
+        this.fedToKeycloakSyncMessage = fedToKeycloakSyncMessage;
+    }
+
+    public Boolean isKeycloakToFedSyncSupported() {
+        return keycloakToFedSyncSupported;
+    }
+
+    public void setKeycloakToFedSyncSupported(Boolean keycloakToFedSyncSupported) {
+        this.keycloakToFedSyncSupported = keycloakToFedSyncSupported;
+    }
+
+    public String getKeycloakToFedSyncMessage() {
+        return keycloakToFedSyncMessage;
+    }
+
+    public void setKeycloakToFedSyncMessage(String keycloakToFedSyncMessage) {
+        this.keycloakToFedSyncMessage = keycloakToFedSyncMessage;
+    }
+}
diff --git a/core/src/main/java/org/keycloak/representations/idm/UserFederationMapperTypeRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/UserFederationMapperTypeRepresentation.java
index f7f594a..48a0054 100644
--- a/core/src/main/java/org/keycloak/representations/idm/UserFederationMapperTypeRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/UserFederationMapperTypeRepresentation.java
@@ -12,6 +12,8 @@ public class UserFederationMapperTypeRepresentation {
     protected String category;
     protected String helpText;
 
+    protected UserFederationMapperSyncConfigRepresentation syncConfig;
+
     protected List<ConfigPropertyRepresentation> properties  = new LinkedList<>();
 
     public String getId() {
@@ -46,6 +48,14 @@ public class UserFederationMapperTypeRepresentation {
         this.helpText = helpText;
     }
 
+    public UserFederationMapperSyncConfigRepresentation getSyncConfig() {
+        return syncConfig;
+    }
+
+    public void setSyncConfig(UserFederationMapperSyncConfigRepresentation syncConfig) {
+        this.syncConfig = syncConfig;
+    }
+
     public List<ConfigPropertyRepresentation> getProperties() {
         return properties;
     }
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-login-freemarker/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-login-freemarker/main/module.xml
index 3b0e961..155a65d 100755
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-login-freemarker/main/module.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-login-freemarker/main/module.xml
@@ -16,6 +16,7 @@
         <module name="org.keycloak.keycloak-core"/>
         <module name="org.keycloak.keycloak-services"/>
         <module name="org.keycloak.keycloak-social-core"/>
+        <module name="org.keycloak.keycloak-broker-core"/>
         <module name="javax.ws.rs.api"/>
         <module name="org.jboss.logging"/>
         <module name="org.freemarker"/>
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-saml-protocol/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-saml-protocol/main/module.xml
index fbd65fd..81cd365 100755
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-saml-protocol/main/module.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-saml-protocol/main/module.xml
@@ -26,6 +26,7 @@
         <module name="org.keycloak.keycloak-connections-http-client" services="import"/>
 
         <module name="javax.api"/>
+        <module name="javax.xml.soap.api"/>
     </dependencies>
 
 </module>
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/LDAPDn.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/LDAPDn.java
index a7cf098..be5e6b9 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/LDAPDn.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/LDAPDn.java
@@ -63,6 +63,14 @@ public class LDAPDn {
     }
 
     /**
+     * @return string attribute value like "joe" from the DN like "uid=joe,dc=something,dc=org"
+     */
+    public String getFirstRdnAttrValue() {
+        Entry firstEntry = entries.getFirst();
+        return firstEntry.attrValue;
+    }
+
+    /**
      *
      * @return string like "dc=something,dc=org" from the DN like "uid=joe,dc=something,dc=org"
      */
@@ -72,6 +80,21 @@ public class LDAPDn {
         return toString(parentDnEntries);
     }
 
+    public boolean isDescendantOf(LDAPDn expectedParentDn) {
+        int parentEntriesCount = expectedParentDn.entries.size();
+
+        Deque<Entry> myEntries = new LinkedList<>(this.entries);
+        boolean someRemoved = false;
+        while (myEntries.size() > parentEntriesCount) {
+            myEntries.removeFirst();
+            someRemoved = true;
+        }
+
+        String myEntriesParentStr = toString(myEntries).toLowerCase();
+        String expectedParentDnStr = expectedParentDn.toString().toLowerCase();
+        return someRemoved && myEntriesParentStr.equals(expectedParentDnStr);
+    }
+
     public void addFirst(String rdnName, String rdnValue) {
         rdnValue = escape(rdnValue);
         entries.addFirst(new Entry(rdnName, rdnValue));
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/AbstractLDAPFederationMapper.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/AbstractLDAPFederationMapper.java
index 6daa011..fd05cf8 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/AbstractLDAPFederationMapper.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/AbstractLDAPFederationMapper.java
@@ -1,6 +1,10 @@
 package org.keycloak.federation.ldap.mappers;
 
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserFederationMapperModel;
+import org.keycloak.models.UserFederationProvider;
+import org.keycloak.models.UserFederationSyncResult;
 
 /**
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@@ -8,6 +12,16 @@ import org.keycloak.models.UserFederationMapperModel;
 public abstract class AbstractLDAPFederationMapper implements LDAPFederationMapper {
 
     @Override
+    public UserFederationSyncResult syncDataFromFederationProviderToKeycloak(UserFederationMapperModel mapperModel, UserFederationProvider federationProvider, KeycloakSession session, RealmModel realm) {
+        throw new IllegalStateException("Not supported");
+    }
+
+    @Override
+    public UserFederationSyncResult syncDataFromKeycloakToFederationProvider(UserFederationMapperModel mapperModel, UserFederationProvider federationProvider, KeycloakSession session, RealmModel realm) {
+        throw new IllegalStateException("Not supported");
+    }
+
+    @Override
     public void close() {
 
     }
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/AbstractLDAPFederationMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/AbstractLDAPFederationMapperFactory.java
index 75e77f3..8dfce50 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/AbstractLDAPFederationMapperFactory.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/AbstractLDAPFederationMapperFactory.java
@@ -10,6 +10,7 @@ import org.keycloak.mappers.UserFederationMapperFactory;
 import org.keycloak.models.KeycloakSessionFactory;
 import org.keycloak.models.UserFederationMapperModel;
 import org.keycloak.provider.ProviderConfigProperty;
+import org.keycloak.representations.idm.UserFederationMapperSyncConfigRepresentation;
 
 /**
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@@ -36,6 +37,11 @@ public abstract class AbstractLDAPFederationMapperFactory implements UserFederat
     }
 
     @Override
+    public UserFederationMapperSyncConfigRepresentation getSyncConfig() {
+        return new UserFederationMapperSyncConfigRepresentation(false, null, false, null);
+    }
+
+    @Override
     public void close() {
     }
 
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/RoleLDAPFederationMapper.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/RoleLDAPFederationMapper.java
index 6c45481..0960bb9 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/RoleLDAPFederationMapper.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/RoleLDAPFederationMapper.java
@@ -14,12 +14,15 @@ import org.keycloak.federation.ldap.idm.query.Condition;
 import org.keycloak.federation.ldap.idm.query.internal.LDAPQuery;
 import org.keycloak.federation.ldap.idm.query.internal.LDAPQueryConditionsBuilder;
 import org.keycloak.models.ClientModel;
+import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.LDAPConstants;
 import org.keycloak.models.ModelException;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RoleContainerModel;
 import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserFederationMapperModel;
+import org.keycloak.models.UserFederationProvider;
+import org.keycloak.models.UserFederationSyncResult;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.models.utils.UserModelDelegate;
@@ -39,11 +42,14 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper {
     // Name of LDAP attribute, which is used in role objects for name and RDN of role. Usually it will be "cn"
     public static final String ROLE_NAME_LDAP_ATTRIBUTE = "role.name.ldap.attribute";
 
+    // Object classes of the role object.
+    public static final String ROLE_OBJECT_CLASSES = "role.object.classes";
+
     // Name of LDAP attribute on role, which is used for membership mappings. Usually it will be "member"
     public static final String MEMBERSHIP_LDAP_ATTRIBUTE = "membership.ldap.attribute";
 
-    // Object classes of the role object.
-    public static final String ROLE_OBJECT_CLASSES = "role.object.classes";
+    // See docs for MembershipType enum
+    public static final String MEMBERSHIP_ATTRIBUTE_TYPE = "membership.attribute.type";
 
     // Boolean option. If true, we will map LDAP roles to realm roles. If false, we will map to client roles (client specified by option CLIENT_ID)
     public static final String USE_REALM_ROLES_MAPPING = "use.realm.roles.mapping";
@@ -53,19 +59,15 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper {
 
     // See docs for Mode enum
     public static final String MODE = "mode";
+
+    // See docs for UserRolesRetriever enum
+    public static final String USER_ROLES_RETRIEVE_STRATEGY = "user.roles.retrieve.strategy";
     
     // Customized LDAP filter which is added to the whole LDAP query
     public static final String ROLES_LDAP_FILTER = "roles.ldap.filter";
 
-
-    // List of IDs of UserFederationMapperModels where syncRolesFromLDAP was already called in this KeycloakSession. This is to improve performance
-    // TODO: Rather address this with caching at LDAPIdentityStore level?
-    private Set<String> rolesSyncedModels = new TreeSet<>();
-
     @Override
     public void onImportUserFromLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, UserModel user, RealmModel realm, boolean isCreate) {
-        syncRolesFromLDAP(mapperModel, ldapProvider, realm);
-
         Mode mode = getMode(mapperModel);
 
         // For now, import LDAP role mappings just during create
@@ -89,34 +91,91 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper {
 
     @Override
     public void onRegisterUserToLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, UserModel localUser, RealmModel realm) {
-        syncRolesFromLDAP(mapperModel, ldapProvider, realm);
     }
 
-    // Sync roles from LDAP tree and create them in local Keycloak DB (if they don't exist here yet)
-    protected void syncRolesFromLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, RealmModel realm) {
-        if (!rolesSyncedModels.contains(mapperModel.getId())) {
-            logger.debugf("Syncing roles from LDAP into Keycloak DB. Mapper is [%s], LDAP provider is [%s]", mapperModel.getName(), ldapProvider.getModel().getDisplayName());
 
-            LDAPQuery ldapQuery = createRoleQuery(mapperModel, ldapProvider);
+    // Sync roles from LDAP to Keycloak DB
+    @Override
+    public UserFederationSyncResult syncDataFromFederationProviderToKeycloak(UserFederationMapperModel mapperModel, UserFederationProvider federationProvider, KeycloakSession session, RealmModel realm) {
+        LDAPFederationProvider ldapProvider = (LDAPFederationProvider) federationProvider;
+        UserFederationSyncResult syncResult = new UserFederationSyncResult() {
 
-            // Send query
-            List<LDAPObject> ldapRoles = ldapQuery.getResultList();
+            @Override
+            public String getStatus() {
+                return String.format("%d imported roles, %d roles already exists in Keycloak", getAdded(), getUpdated());
+            }
 
-            RoleContainerModel roleContainer = getTargetRoleContainer(mapperModel, realm);
-            String rolesRdnAttr = getRoleNameLdapAttribute(mapperModel);
-            for (LDAPObject ldapRole : ldapRoles) {
-                String roleName = ldapRole.getAttributeAsString(rolesRdnAttr);
+        };
 
-                if (roleContainer.getRole(roleName) == null) {
-                    logger.debugf("Syncing role [%s] from LDAP to keycloak DB", roleName);
-                    roleContainer.addRole(roleName);
-                }
+        logger.debugf("Syncing roles from LDAP into Keycloak DB. Mapper is [%s], LDAP provider is [%s]", mapperModel.getName(), ldapProvider.getModel().getDisplayName());
+
+        // Send LDAP query
+        LDAPQuery ldapQuery = createRoleQuery(mapperModel, ldapProvider);
+        List<LDAPObject> ldapRoles = ldapQuery.getResultList();
+
+        RoleContainerModel roleContainer = getTargetRoleContainer(mapperModel, realm);
+        String rolesRdnAttr = getRoleNameLdapAttribute(mapperModel);
+        for (LDAPObject ldapRole : ldapRoles) {
+            String roleName = ldapRole.getAttributeAsString(rolesRdnAttr);
+
+            if (roleContainer.getRole(roleName) == null) {
+                logger.debugf("Syncing role [%s] from LDAP to keycloak DB", roleName);
+                roleContainer.addRole(roleName);
+                syncResult.increaseAdded();
+            } else {
+                syncResult.increaseUpdated();
+            }
+        }
+
+        return syncResult;
+    }
+
+
+    // Sync roles from Keycloak back to LDAP
+    @Override
+    public UserFederationSyncResult syncDataFromKeycloakToFederationProvider(UserFederationMapperModel mapperModel, UserFederationProvider federationProvider, KeycloakSession session, RealmModel realm) {
+        LDAPFederationProvider ldapProvider = (LDAPFederationProvider) federationProvider;
+        UserFederationSyncResult syncResult = new UserFederationSyncResult() {
+
+            @Override
+            public String getStatus() {
+                return String.format("%d roles imported to LDAP, %d roles already existed in LDAP", getAdded(), getUpdated());
             }
 
-            rolesSyncedModels.add(mapperModel.getId());
+        };
+
+        logger.debugf("Syncing roles from Keycloak into LDAP. Mapper is [%s], LDAP provider is [%s]", mapperModel.getName(), ldapProvider.getModel().getDisplayName());
+
+        // Send LDAP query to see which roles exists there
+        LDAPQuery ldapQuery = createRoleQuery(mapperModel, ldapProvider);
+        List<LDAPObject> ldapRoles = ldapQuery.getResultList();
+
+        Set<String> ldapRoleNames = new HashSet<>();
+        String rolesRdnAttr = getRoleNameLdapAttribute(mapperModel);
+        for (LDAPObject ldapRole : ldapRoles) {
+            String roleName = ldapRole.getAttributeAsString(rolesRdnAttr);
+            ldapRoleNames.add(roleName);
+        }
+
+
+        RoleContainerModel roleContainer = getTargetRoleContainer(mapperModel, realm);
+        Set<RoleModel> keycloakRoles = roleContainer.getRoles();
+
+        for (RoleModel keycloakRole : keycloakRoles) {
+            String roleName = keycloakRole.getName();
+            if (ldapRoleNames.contains(roleName)) {
+                syncResult.increaseUpdated();
+            } else {
+                logger.debugf("Syncing role [%s] from Keycloak to LDAP", roleName);
+                createLDAPRole(mapperModel, roleName, ldapProvider);
+                syncResult.increaseAdded();
+            }
         }
+
+        return syncResult;
     }
 
+
     public LDAPQuery createRoleQuery(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider) {
         LDAPQuery ldapQuery = new LDAPQuery(ldapProvider);
 
@@ -179,6 +238,15 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper {
         return membershipAttrName!=null ? membershipAttrName : LDAPConstants.MEMBER;
     }
 
+    protected MembershipType getMembershipTypeLdapAttribute(UserFederationMapperModel mapperModel) {
+        String membershipType = mapperModel.getConfig().get(MEMBERSHIP_ATTRIBUTE_TYPE);
+        return (membershipType!=null && !membershipType.isEmpty()) ? Enum.valueOf(MembershipType.class, membershipType) : MembershipType.DN;
+    }
+
+    protected String getMembershipFromUser(LDAPObject ldapUser, MembershipType membershipType) {
+        return membershipType == MembershipType.DN ? ldapUser.getDn().toString() : ldapUser.getAttributeAsString(ldapUser.getRdnAttributeName());
+    }
+
     protected Collection<String> getRoleObjectClasses(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider) {
         String objectClasses = mapperModel.getConfig().get(ROLE_OBJECT_CLASSES);
         if (objectClasses == null) {
@@ -206,6 +274,11 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper {
         return Enum.valueOf(Mode.class, modeString.toUpperCase());
     }
 
+    private UserRolesRetrieveStrategy getUserRolesRetrieveStrategy(UserFederationMapperModel mapperModel) {
+        String strategyString = mapperModel.getConfig().get(USER_ROLES_RETRIEVE_STRATEGY);
+        return (strategyString!=null && !strategyString.isEmpty()) ? Enum.valueOf(UserRolesRetrieveStrategy.class, strategyString) : UserRolesRetrieveStrategy.LOAD_ROLES_BY_MEMBER_ATTRIBUTE;
+    }
+
     public LDAPObject createLDAPRole(UserFederationMapperModel mapperModel, String roleName, LDAPFederationProvider ldapProvider) {
         LDAPObject ldapObject = new LDAPObject();
         String roleNameAttribute = getRoleNameLdapAttribute(mapperModel);
@@ -228,17 +301,23 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper {
             ldapRole = createLDAPRole(mapperModel, roleName, ldapProvider);
         }
 
+        MembershipType membershipType = getMembershipTypeLdapAttribute(mapperModel);
+
         Set<String> memberships = getExistingMemberships(mapperModel, ldapRole);
 
         // Remove membership placeholder if present
-        for (String membership : memberships) {
-            if (LDAPConstants.EMPTY_MEMBER_ATTRIBUTE_VALUE.equals(membership)) {
-                memberships.remove(membership);
-                break;
+        if (membershipType == MembershipType.DN) {
+            for (String membership : memberships) {
+                if (LDAPConstants.EMPTY_MEMBER_ATTRIBUTE_VALUE.equals(membership)) {
+                    memberships.remove(membership);
+                    break;
+                }
             }
         }
 
-        memberships.add(ldapUser.getDn().toString());
+        String membership = getMembershipFromUser(ldapUser, membershipType);
+
+        memberships.add(membership);
         ldapRole.setAttribute(getMembershipLdapAttribute(mapperModel), memberships);
 
         ldapProvider.getLdapIdentityStore().update(ldapRole);
@@ -246,10 +325,14 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper {
 
     public void deleteRoleMappingInLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, LDAPObject ldapRole) {
         Set<String> memberships = getExistingMemberships(mapperModel, ldapRole);
-        memberships.remove(ldapUser.getDn().toString());
+
+        MembershipType membershipType = getMembershipTypeLdapAttribute(mapperModel);
+        String userMembership = getMembershipFromUser(ldapUser, membershipType);
+
+        memberships.remove(userMembership);
 
         // Some membership placeholder needs to be always here as "member" is mandatory attribute on some LDAP servers. But not on active directory! (Placeholder, which not matches any real object is not allowed here)
-        if (memberships.size() == 0 && !ldapProvider.getLdapIdentityStore().getConfig().isActiveDirectory()) {
+        if (memberships.size() == 0 && membershipType==MembershipType.DN && !ldapProvider.getLdapIdentityStore().getConfig().isActiveDirectory()) {
             memberships.add(LDAPConstants.EMPTY_MEMBER_ATTRIBUTE_VALUE);
         }
 
@@ -274,11 +357,8 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper {
     }
 
     protected List<LDAPObject> getLDAPRoleMappings(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser) {
-        LDAPQuery ldapQuery = createRoleQuery(mapperModel, ldapProvider);
-        String membershipAttr = getMembershipLdapAttribute(mapperModel);
-        Condition membershipCondition = new LDAPQueryConditionsBuilder().equal(membershipAttr, ldapUser.getDn().toString());
-        ldapQuery.addWhereCondition(membershipCondition);
-        return ldapQuery.getResultList();
+        UserRolesRetrieveStrategy strategy = getUserRolesRetrieveStrategy(mapperModel);
+        return strategy.getLDAPRoleMappings(this, mapperModel, ldapProvider, ldapUser);
     }
 
     @Override
@@ -295,6 +375,8 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper {
 
     @Override
     public void beforeLDAPQuery(UserFederationMapperModel mapperModel, LDAPQuery query) {
+        UserRolesRetrieveStrategy strategy = getUserRolesRetrieveStrategy(mapperModel);
+        strategy.beforeUserLDAPQuery(mapperModel, query);
     }
 
 
@@ -437,7 +519,8 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper {
                 LDAPQuery ldapQuery = createRoleQuery(mapperModel, ldapProvider);
                 LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder();
                 Condition roleNameCondition = conditionsBuilder.equal(getRoleNameLdapAttribute(mapperModel), role.getName());
-                Condition membershipCondition = conditionsBuilder.equal(getMembershipLdapAttribute(mapperModel), ldapUser.getDn().toString());
+                String membershipUserAttr = getMembershipFromUser(ldapUser, getMembershipTypeLdapAttribute(mapperModel));
+                Condition membershipCondition = conditionsBuilder.equal(getMembershipLdapAttribute(mapperModel), membershipUserAttr);
                 ldapQuery.addWhereCondition(roleNameCondition).addWhereCondition(membershipCondition);
                 LDAPObject ldapRole = ldapQuery.getFirstResult();
 
@@ -462,6 +545,7 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper {
         }
     }
 
+
     public enum Mode {
         /**
          * All role mappings are retrieved from LDAP and saved into LDAP
@@ -484,4 +568,18 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper {
          */
         READ_ONLY
     }
+
+
+    public enum MembershipType {
+
+        /**
+         * Used if LDAP role has it's members declared in form of their full DN. For example ( "member: uid=john,ou=users,dc=example,dc=com" )
+         */
+        DN,
+
+        /**
+         * Used if LDAP role has it's members declared in form of pure user uids. For example ( "memberUid: john" )
+         */
+        UID
+    }
 }
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 bdd0e51..455c1db 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
@@ -5,21 +5,13 @@ import java.util.LinkedList;
 import java.util.List;
 
 import org.jboss.logging.Logger;
-import org.keycloak.federation.ldap.LDAPFederationProvider;
 import org.keycloak.mappers.MapperConfigValidationException;
 import org.keycloak.mappers.UserFederationMapper;
 import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.KeycloakSessionFactory;
 import org.keycloak.models.LDAPConstants;
-import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserFederationMapperModel;
-import org.keycloak.models.UserFederationProvider;
-import org.keycloak.models.UserFederationProviderFactory;
-import org.keycloak.models.UserFederationProviderModel;
-import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.provider.ProviderConfigProperty;
-import org.keycloak.provider.ProviderEvent;
-import org.keycloak.provider.ProviderEventListener;
+import org.keycloak.representations.idm.UserFederationMapperSyncConfigRepresentation;
 
 /**
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@@ -42,23 +34,36 @@ public class RoleLDAPFederationMapperFactory extends AbstractLDAPFederationMappe
                 ProviderConfigProperty.STRING_TYPE, LDAPConstants.CN);
         configProperties.add(roleNameLDAPAttribute);
 
+        ProviderConfigProperty roleObjectClasses = createConfigProperty(RoleLDAPFederationMapper.ROLE_OBJECT_CLASSES, "Role Object Classes",
+                "Object class (or classes) of the role object. It's divided by comma if more classes needed. In typical LDAP deployment it could be 'groupOfNames' . In Active Directory it's usually 'group' ",
+                ProviderConfigProperty.STRING_TYPE, null);
+        configProperties.add(roleObjectClasses);
+
         ProviderConfigProperty membershipLDAPAttribute = createConfigProperty(RoleLDAPFederationMapper.MEMBERSHIP_LDAP_ATTRIBUTE, "Membership LDAP Attribute",
                 "Name of LDAP attribute on role, which is used for membership mappings. Usually it will be 'member' ",
                 ProviderConfigProperty.STRING_TYPE, LDAPConstants.MEMBER);
         configProperties.add(membershipLDAPAttribute);
 
-        ProviderConfigProperty roleObjectClasses = createConfigProperty(RoleLDAPFederationMapper.ROLE_OBJECT_CLASSES, "Role Object Classes",
-                "Object class (or classes) of the role object. It's divided by comma if more classes needed. In typical LDAP deployment it could be 'groupOfNames' . In Active Directory it's usually 'group' ",
-                ProviderConfigProperty.STRING_TYPE, null);
-        configProperties.add(roleObjectClasses);
-        
+
+        List<String> membershipTypes = new LinkedList<>();
+        for (RoleLDAPFederationMapper.MembershipType membershipType : RoleLDAPFederationMapper.MembershipType.values()) {
+            membershipTypes.add(membershipType.toString());
+        }
+        ProviderConfigProperty membershipType = createConfigProperty(RoleLDAPFederationMapper.MEMBERSHIP_ATTRIBUTE_TYPE, "Membership Attribute Type",
+                "DN means that LDAP role has it's members declared in form of their full DN. For example 'member: uid=john,ou=users,dc=example,dc=com' . " +
+                        "UID means that LDAP role has it's members declared in form of pure user uids. For example 'memberUid: john' .",
+                ProviderConfigProperty.LIST_TYPE, membershipTypes);
+        configProperties.add(membershipType);
+
+
         ProviderConfigProperty ldapFilter = createConfigProperty(RoleLDAPFederationMapper.ROLES_LDAP_FILTER,
                 "LDAP Filter",
-                "LDAP Filter adds additional custom filter to the whole query. Make sure that it starts with '(' and ends with ')'",
+                "LDAP Filter adds additional custom filter to the whole query. Leave this empty if no additional filtering is needed. Otherwise make sure that filter starts with '(' and ends with ')'",
                 ProviderConfigProperty.STRING_TYPE, null);
         configProperties.add(ldapFilter);
 
-        List<String> modes = new LinkedList<String>();
+
+        List<String> modes = new LinkedList<>();
         for (RoleLDAPFederationMapper.Mode mode : RoleLDAPFederationMapper.Mode.values()) {
             modes.add(mode.toString());
         }
@@ -69,6 +74,20 @@ public class RoleLDAPFederationMapperFactory extends AbstractLDAPFederationMappe
                 ProviderConfigProperty.LIST_TYPE, modes);
         configProperties.add(mode);
 
+
+        List<String> roleRetrievers = new LinkedList<>();
+        for (UserRolesRetrieveStrategy retriever : UserRolesRetrieveStrategy.values()) {
+            roleRetrievers.add(retriever.toString());
+        }
+        ProviderConfigProperty retriever = createConfigProperty(RoleLDAPFederationMapper.USER_ROLES_RETRIEVE_STRATEGY, "User Roles Retrieve Strategy",
+                "Specify how to retrieve roles of user. LOAD_ROLES_BY_MEMBER_ATTRIBUTE means that roles of user will be retrieved by sending LDAP query to retrieve all roles where 'member' is our user. " +
+                        "GET_ROLES_FROM_USER_MEMBEROF_ATTRIBUTE means that roles of user will be retrieved from 'memberOf' attribute of our user. " +
+                        "LOAD_ROLES_BY_MEMBER_ATTRIBUTE_RECURSIVELY is applicable just in Active Directory and it means that roles of user will be retrieved recursively with usage of LDAP_MATCHING_RULE_IN_CHAIN Ldap extension."
+                ,
+                ProviderConfigProperty.LIST_TYPE, roleRetrievers);
+        configProperties.add(retriever);
+
+
         ProviderConfigProperty useRealmRolesMappings = createConfigProperty(RoleLDAPFederationMapper.USE_REALM_ROLES_MAPPING, "Use Realm Roles Mapping",
                 "If true, then LDAP role mappings will be mapped to realm role mappings in Keycloak. Otherwise it will be mapped to client role mappings", ProviderConfigProperty.BOOLEAN_TYPE, "true");
         configProperties.add(useRealmRolesMappings);
@@ -104,40 +123,9 @@ public class RoleLDAPFederationMapperFactory extends AbstractLDAPFederationMappe
         return PROVIDER_ID;
     }
 
-    // Sync roles from LDAP to Keycloak DB during creation or update of mapperModel
     @Override
-    public void postInit(KeycloakSessionFactory factory) {
-        factory.register(new ProviderEventListener() {
-
-            @Override
-            public void onEvent(ProviderEvent event) {
-                if (event instanceof RealmModel.UserFederationMapperEvent) {
-                    RealmModel.UserFederationMapperEvent mapperEvent = (RealmModel.UserFederationMapperEvent)event;
-                    UserFederationMapperModel mapperModel = mapperEvent.getFederationMapper();
-                    RealmModel realm = mapperEvent.getRealm();
-                    KeycloakSession session = mapperEvent.getSession();
-
-                    if (mapperModel.getFederationMapperType().equals(PROVIDER_ID)) {
-                        try {
-                            String federationProviderId = mapperModel.getFederationProviderId();
-                            UserFederationProviderModel providerModel = KeycloakModelUtils.findUserFederationProviderById(federationProviderId, realm);
-                            if (providerModel == null) {
-                                throw new IllegalStateException("Can't find federation provider with ID [" + federationProviderId + "] in realm " + realm.getName());
-                            }
-
-                            UserFederationProviderFactory ldapFactory = (UserFederationProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(UserFederationProvider.class, providerModel.getProviderName());
-                            LDAPFederationProvider ldapProvider = (LDAPFederationProvider) ldapFactory.getInstance(session, providerModel);
-
-                            // Sync roles
-                            new RoleLDAPFederationMapper().syncRolesFromLDAP(mapperModel, ldapProvider, realm);
-                        } catch (Exception e) {
-                            logger.warn("Exception during initial sync of roles from LDAP.", e);
-                        }
-                    }
-                }
-            }
-
-        });
+    public UserFederationMapperSyncConfigRepresentation getSyncConfig() {
+        return new UserFederationMapperSyncConfigRepresentation(true, "sync-ldap-roles-to-keycloak", true, "sync-keycloak-roles-to-ldap");
     }
 
     @Override
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserRolesRetrieveStrategy.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserRolesRetrieveStrategy.java
new file mode 100644
index 0000000..dd7cd31
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserRolesRetrieveStrategy.java
@@ -0,0 +1,124 @@
+package org.keycloak.federation.ldap.mappers;
+
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import org.keycloak.federation.ldap.LDAPFederationProvider;
+import org.keycloak.federation.ldap.idm.model.LDAPDn;
+import org.keycloak.federation.ldap.idm.model.LDAPObject;
+import org.keycloak.federation.ldap.idm.query.Condition;
+import org.keycloak.federation.ldap.idm.query.internal.LDAPQuery;
+import org.keycloak.federation.ldap.idm.query.internal.LDAPQueryConditionsBuilder;
+import org.keycloak.models.LDAPConstants;
+import org.keycloak.models.UserFederationMapperModel;
+
+/**
+ * Strategy for how to retrieve LDAP roles of user
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public enum UserRolesRetrieveStrategy {
+
+
+    /**
+     * Roles of user will be retrieved by sending LDAP query to retrieve all roles where "member" is our user
+     */
+    LOAD_ROLES_BY_MEMBER_ATTRIBUTE {
+
+        @Override
+        public List<LDAPObject> getLDAPRoleMappings(RoleLDAPFederationMapper roleMapper, UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser) {
+            LDAPQuery ldapQuery = roleMapper.createRoleQuery(mapperModel, ldapProvider);
+            String membershipAttr = roleMapper.getMembershipLdapAttribute(mapperModel);
+
+            String userMembership = roleMapper.getMembershipFromUser(ldapUser, roleMapper.getMembershipTypeLdapAttribute(mapperModel));
+
+            Condition membershipCondition = new LDAPQueryConditionsBuilder().equal(membershipAttr, userMembership);
+            ldapQuery.addWhereCondition(membershipCondition);
+            return ldapQuery.getResultList();
+        }
+
+        @Override
+        public void beforeUserLDAPQuery(UserFederationMapperModel mapperModel, LDAPQuery query) {
+        }
+
+    },
+
+
+    /**
+     * Roles of user will be retrieved from "memberOf" attribute of our user
+     */
+    GET_ROLES_FROM_USER_MEMBEROF_ATTRIBUTE {
+
+        @Override
+        public List<LDAPObject> getLDAPRoleMappings(RoleLDAPFederationMapper roleMapper, UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser) {
+            Set<String> memberOfValues = ldapUser.getAttributeAsSet(LDAPConstants.MEMBER_OF);
+            if (memberOfValues == null) {
+                return Collections.emptyList();
+            }
+
+            List<LDAPObject> roles = new LinkedList<>();
+            LDAPDn parentDn = LDAPDn.fromString(roleMapper.getRolesDn(mapperModel));
+
+            for (String roleDn : memberOfValues) {
+                LDAPDn roleDN = LDAPDn.fromString(roleDn);
+                if (roleDN.isDescendantOf(parentDn)) {
+                    LDAPObject role = new LDAPObject();
+                    role.setDn(roleDN);
+
+                    String firstDN = roleDN.getFirstRdnAttrName();
+                    if (firstDN.equalsIgnoreCase(roleMapper.getRoleNameLdapAttribute(mapperModel))) {
+                        role.setRdnAttributeName(firstDN);
+                        role.setSingleAttribute(firstDN, roleDN.getFirstRdnAttrValue());
+                        roles.add(role);
+                    }
+                }
+            }
+            return roles;
+        }
+
+        @Override
+        public void beforeUserLDAPQuery(UserFederationMapperModel mapperModel, LDAPQuery query) {
+            query.addReturningLdapAttribute(LDAPConstants.MEMBER_OF);
+            query.addReturningReadOnlyLdapAttribute(LDAPConstants.MEMBER_OF);
+        }
+
+    },
+
+
+    /**
+     * Extension specific to Active Directory. Roles of user will be retrieved by sending LDAP query to retrieve all roles where "member" is our user.
+     * The query will be able to retrieve memberships recursively
+     * (Assume "role1" has member "role2" and role2 has member "johnuser". Then searching for roles of "johnuser" will return both "role1" and "role2" )
+     *
+     * This is using AD specific extension LDAP_MATCHING_RULE_IN_CHAIN, so likely doesn't work on other LDAP servers
+     */
+    LOAD_ROLES_BY_MEMBER_ATTRIBUTE_RECURSIVELY {
+
+        @Override
+        public List<LDAPObject> getLDAPRoleMappings(RoleLDAPFederationMapper roleMapper, UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser) {
+            LDAPQuery ldapQuery = roleMapper.createRoleQuery(mapperModel, ldapProvider);
+            String membershipAttr = roleMapper.getMembershipLdapAttribute(mapperModel);
+            membershipAttr = membershipAttr + LDAPConstants.LDAP_MATCHING_RULE_IN_CHAIN;
+            String userMembership = roleMapper.getMembershipFromUser(ldapUser, roleMapper.getMembershipTypeLdapAttribute(mapperModel));
+
+            Condition membershipCondition = new LDAPQueryConditionsBuilder().equal(membershipAttr, userMembership);
+            ldapQuery.addWhereCondition(membershipCondition);
+            return ldapQuery.getResultList();
+        }
+
+        @Override
+        public void beforeUserLDAPQuery(UserFederationMapperModel mapperModel, LDAPQuery query) {
+        }
+
+    };
+
+
+
+    public abstract List<LDAPObject> getLDAPRoleMappings(RoleLDAPFederationMapper roleMapper, UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser);
+
+    public abstract void beforeUserLDAPQuery(UserFederationMapperModel mapperModel, LDAPQuery query);
+
+}
diff --git a/federation/ldap/src/test/java/org/keycloak/federation/ldap/idm/model/LDAPDnTest.java b/federation/ldap/src/test/java/org/keycloak/federation/ldap/idm/model/LDAPDnTest.java
index 77bc4ce..5631573 100644
--- a/federation/ldap/src/test/java/org/keycloak/federation/ldap/idm/model/LDAPDnTest.java
+++ b/federation/ldap/src/test/java/org/keycloak/federation/ldap/idm/model/LDAPDnTest.java
@@ -18,5 +18,14 @@ public class LDAPDnTest {
         Assert.assertEquals("uid=Johny\\,Depp,ou=People,dc=keycloak,dc=org", dn.toString());
 
         Assert.assertEquals("ou=People,dc=keycloak,dc=org", dn.getParentDn());
+
+        Assert.assertTrue(dn.isDescendantOf(LDAPDn.fromString("dc=keycloak, dc=org")));
+        Assert.assertTrue(dn.isDescendantOf(LDAPDn.fromString("dc=org")));
+        Assert.assertTrue(dn.isDescendantOf(LDAPDn.fromString("DC=keycloak, DC=org")));
+        Assert.assertFalse(dn.isDescendantOf(LDAPDn.fromString("dc=keycloakk, dc=org")));
+        Assert.assertFalse(dn.isDescendantOf(dn));
+
+        Assert.assertEquals("uid", dn.getFirstRdnAttrName());
+        Assert.assertEquals("Johny\\,Depp", dn.getFirstRdnAttrValue());
     }
 }
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
index 287abe4..f33d6b8 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
+++ b/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
@@ -472,6 +472,12 @@ social.default-scopes.tooltip=The scopes to be sent when asking for authorizatio
 key=Key
 stackoverflow.key.tooltip=The Key obtained from Stack Overflow client registration.
 
+# User federation
+sync-ldap-roles-to-keycloak=Sync LDAP Roles To Keycloak
+sync-keycloak-roles-to-ldap=Sync Keycloak Roles To LDAP
+sync-ldap-groups-to-keycloak=Sync LDAP Groups To Keycloak
+sync-keycloak-groups-to-ldap=Sync Keycloak Groups To LDAP
+
 realms=Realms
 realm=Realm
 
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js
index f42f8aa..cb3aa08 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js
@@ -986,7 +986,7 @@ module.controller('UserFederationMapperListCtrl', function($scope, $location, No
 
 });
 
-module.controller('UserFederationMapperCtrl', function($scope, realm,  provider, mapperTypes, mapper, clients, UserFederationMapper, Notifications, Dialog, $location) {
+module.controller('UserFederationMapperCtrl', function($scope, realm,  provider, mapperTypes, mapper, clients, UserFederationMapper, UserFederationMapperSync, Notifications, Dialog, $location) {
     console.log('UserFederationMapperCtrl');
     $scope.realm = realm;
     $scope.provider = provider;
@@ -1035,6 +1035,22 @@ module.controller('UserFederationMapperCtrl', function($scope, realm,  provider,
         });
     };
 
+    $scope.triggerFedToKeycloakSync = function() {
+        triggerMapperSync("fedToKeycloak")
+    }
+
+    $scope.triggerKeycloakToFedSync = function() {
+        triggerMapperSync("keycloakToFed");
+    }
+
+    function triggerMapperSync(direction) {
+        UserFederationMapperSync.save({ direction: direction, realm: realm.realm, provider: provider.id, mapperId : $scope.mapper.id }, {}, function(syncResult) {
+            Notifications.success("Data synced successfully. " + syncResult.status);
+        }, function() {
+            Notifications.error("Error during sync of data");
+        });
+    }
+
 });
 
 module.controller('UserFederationMapperCreateCtrl', function($scope, realm, provider, mapperTypes, clients, UserFederationMapper, Notifications, Dialog, $location) {
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js
index b0d9567..728ed42 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js
@@ -380,6 +380,10 @@ module.factory('UserFederationMapper', function($resource) {
     });
 });
 
+module.factory('UserFederationMapperSync', function($resource) {
+    return $resource(authUrl + '/admin/realms/:realm/user-federation/instances/:provider/mappers/:mapperId/sync');
+});
+
 
 module.factory('UserSessionStats', function($resource) {
     return $resource(authUrl + '/admin/realms/:realm/users/:user/session-stats', {
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/federated-mapper-detail.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/federated-mapper-detail.html
index 2c91c92..04d25c0 100644
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/federated-mapper-detail.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/federated-mapper-detail.html
@@ -53,6 +53,8 @@
             <div class="col-md-10 col-md-offset-2" data-ng-show="access.manageRealm">
                 <button kc-save  data-ng-disabled="!changed">Save</button>
                 <button kc-reset data-ng-disabled="!changed">Cancel</button>
+                <button class="btn btn-primary" data-ng-click="triggerFedToKeycloakSync()" data-ng-hide="!mapperType.syncConfig.fedToKeycloakSyncSupported" data-ng-disabled="changed">{{:: mapperType.syncConfig.fedToKeycloakSyncMessage | translate}}</button>
+                <button class="btn btn-primary" data-ng-click="triggerKeycloakToFedSync()" data-ng-hide="!mapperType.syncConfig.keycloakToFedSyncSupported" data-ng-disabled="changed">{{:: mapperType.syncConfig.keycloakToFedSyncMessage | translate}}</button>
             </div>
         </div>
     </form>
diff --git a/model/api/src/main/java/org/keycloak/mappers/UserFederationMapper.java b/model/api/src/main/java/org/keycloak/mappers/UserFederationMapper.java
index 509ae64..2a03499 100644
--- a/model/api/src/main/java/org/keycloak/mappers/UserFederationMapper.java
+++ b/model/api/src/main/java/org/keycloak/mappers/UserFederationMapper.java
@@ -1,5 +1,10 @@
 package org.keycloak.mappers;
 
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserFederationMapperModel;
+import org.keycloak.models.UserFederationProvider;
+import org.keycloak.models.UserFederationSyncResult;
 import org.keycloak.provider.Provider;
 
 /**
@@ -7,4 +12,28 @@ import org.keycloak.provider.Provider;
  */
 public interface UserFederationMapper extends Provider {
 
+    /**
+     * Sync data from federation storage to Keycloak. It's useful just if mapper needs some data preloaded from federation storage (For example
+     * load roles from federation provider and sync them to Keycloak database)
+     *
+     * Applicable just if sync is supported (see UserFederationMapperFactory.getSyncConfig() )
+     *
+     * @see UserFederationMapperFactory#getSyncConfig()
+     * @param mapperModel
+     * @param federationProvider
+     * @param session
+     * @param realm
+     */
+    UserFederationSyncResult syncDataFromFederationProviderToKeycloak(UserFederationMapperModel mapperModel, UserFederationProvider federationProvider, KeycloakSession session, RealmModel realm);
+
+    /**
+     * Sync data from Keycloak back to federation storage
+     *
+     * @see UserFederationMapperFactory#getSyncConfig()
+     * @param mapperModel
+     * @param federationProvider
+     * @param session
+     * @param realm
+     */
+    UserFederationSyncResult syncDataFromKeycloakToFederationProvider(UserFederationMapperModel mapperModel, UserFederationProvider federationProvider, KeycloakSession session, RealmModel realm);
 }
diff --git a/model/api/src/main/java/org/keycloak/mappers/UserFederationMapperFactory.java b/model/api/src/main/java/org/keycloak/mappers/UserFederationMapperFactory.java
index a4c8776..7690e8a 100644
--- a/model/api/src/main/java/org/keycloak/mappers/UserFederationMapperFactory.java
+++ b/model/api/src/main/java/org/keycloak/mappers/UserFederationMapperFactory.java
@@ -1,12 +1,9 @@
 package org.keycloak.mappers;
 
-import java.util.List;
-
-import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserFederationMapperModel;
 import org.keycloak.provider.ConfiguredProvider;
-import org.keycloak.provider.ProviderConfigProperty;
 import org.keycloak.provider.ProviderFactory;
+import org.keycloak.representations.idm.UserFederationMapperSyncConfigRepresentation;
 
 /**
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@@ -24,6 +21,14 @@ public interface UserFederationMapperFactory extends ProviderFactory<UserFederat
     String getDisplayType();
 
     /**
+     * Specifies if mapper supports sync data from federation storage to keycloak and viceversa.
+     * Also specifies messages to be displayed in admin console UI (For example "Sync roles from LDAP" etc)
+     *
+     * @return syncConfig representation
+     */
+    UserFederationMapperSyncConfigRepresentation getSyncConfig();
+
+    /**
      * Called when instance of mapperModel is created for this factory through admin endpoint
      *
      * @param mapperModel
diff --git a/model/api/src/main/java/org/keycloak/models/LDAPConstants.java b/model/api/src/main/java/org/keycloak/models/LDAPConstants.java
index 79bd7b5..2d403f3 100644
--- a/model/api/src/main/java/org/keycloak/models/LDAPConstants.java
+++ b/model/api/src/main/java/org/keycloak/models/LDAPConstants.java
@@ -87,6 +87,8 @@ public class LDAPConstants {
     public static final String CREATE_TIMESTAMP = "createTimestamp";
     public static final String MODIFY_TIMESTAMP = "modifyTimestamp";
 
+    public static final String LDAP_MATCHING_RULE_IN_CHAIN = ":1.2.840.113556.1.4.1941:";
+
     public static String getUuidAttributeName(String vendor) {
         if (vendor != null) {
             switch (vendor) {
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 bcc16f9..3e50b73 100755
--- a/model/api/src/main/java/org/keycloak/models/RealmModel.java
+++ b/model/api/src/main/java/org/keycloak/models/RealmModel.java
@@ -29,12 +29,6 @@ public interface RealmModel extends RoleContainerModel {
         RealmModel getRealm();
     }
 
-    interface UserFederationMapperEvent extends ProviderEvent {
-        UserFederationMapperModel getFederationMapper();
-        RealmModel getRealm();
-        KeycloakSession getSession();
-    }
-
     String getId();
 
     String getName();
diff --git a/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java b/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java
index 3a105c4..c49f5b6 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java
@@ -24,6 +24,7 @@ public class DefaultAuthenticationFlows {
     public static final String DIRECT_GRANT_FLOW = "direct grant";
     public static final String RESET_CREDENTIALS_FLOW = "reset credentials";
     public static final String LOGIN_FORMS_FLOW = "forms";
+    public static final String SAML_ECP_FLOW = "saml ecp";
 
     public static final String CLIENT_AUTHENTICATION_FLOW = "clients";
     public static final String FIRST_BROKER_LOGIN_FLOW = "first broker login";
@@ -39,6 +40,7 @@ public class DefaultAuthenticationFlows {
         if (realm.getFlowByAlias(RESET_CREDENTIALS_FLOW) == null) resetCredentialsFlow(realm);
         if (realm.getFlowByAlias(CLIENT_AUTHENTICATION_FLOW) == null) clientAuthFlow(realm);
         if (realm.getFlowByAlias(FIRST_BROKER_LOGIN_FLOW) == null) firstBrokerLoginFlow(realm, false);
+        if (realm.getFlowByAlias(SAML_ECP_FLOW) == null) samlEcpProfile(realm);
     }
     public static void migrateFlows(RealmModel realm) {
         if (realm.getFlowByAlias(BROWSER_FLOW) == null) browserFlow(realm, true);
@@ -47,6 +49,7 @@ public class DefaultAuthenticationFlows {
         if (realm.getFlowByAlias(RESET_CREDENTIALS_FLOW) == null) resetCredentialsFlow(realm);
         if (realm.getFlowByAlias(CLIENT_AUTHENTICATION_FLOW) == null) clientAuthFlow(realm);
         if (realm.getFlowByAlias(FIRST_BROKER_LOGIN_FLOW) == null) firstBrokerLoginFlow(realm, true);
+        if (realm.getFlowByAlias(SAML_ECP_FLOW) == null) samlEcpProfile(realm);
     }
 
     public static void registrationFlow(RealmModel realm) {
@@ -447,4 +450,25 @@ public class DefaultAuthenticationFlows {
         execution.setAuthenticatorFlow(false);
         realm.addAuthenticatorExecution(execution);
     }
+
+    public static void samlEcpProfile(RealmModel realm) {
+        AuthenticationFlowModel ecpFlow = new AuthenticationFlowModel();
+
+        ecpFlow.setAlias(SAML_ECP_FLOW);
+        ecpFlow.setDescription("SAML ECP Profile Authentication Flow");
+        ecpFlow.setProviderId("basic-flow");
+        ecpFlow.setTopLevel(true);
+        ecpFlow.setBuiltIn(true);
+        ecpFlow = realm.addAuthenticationFlow(ecpFlow);
+
+        AuthenticationExecutionModel execution = new AuthenticationExecutionModel();
+
+        execution.setParentFlow(ecpFlow.getId());
+        execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
+        execution.setAuthenticator("http-basic-authenticator");
+        execution.setPriority(10);
+        execution.setAuthenticatorFlow(false);
+
+        realm.addAuthenticatorExecution(execution);
+    }
 }
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 ccc3339..1530b12 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
@@ -1,6 +1,5 @@
 package org.keycloak.models.jpa;
 
-import org.keycloak.Config;
 import org.keycloak.connections.jpa.util.JpaUtils;
 import org.keycloak.common.enums.SslRequired;
 import org.keycloak.models.AuthenticationExecutionModel;
@@ -19,7 +18,6 @@ import org.keycloak.models.RealmModel;
 import org.keycloak.models.RequiredActionProviderModel;
 import org.keycloak.models.RequiredCredentialModel;
 import org.keycloak.models.RoleModel;
-import org.keycloak.models.UserFederationMapperEventImpl;
 import org.keycloak.models.UserFederationMapperModel;
 import org.keycloak.models.UserFederationProviderCreationEventImpl;
 import org.keycloak.models.UserFederationProviderModel;
@@ -1542,8 +1540,6 @@ public class RealmAdapter implements RealmModel {
         this.realm.getUserFederationMappers().add(entity);
         UserFederationMapperModel mapperModel = entityToModel(entity);
 
-        session.getKeycloakSessionFactory().publish(new UserFederationMapperEventImpl(mapperModel, this, session));
-
         return mapperModel;
     }
 
@@ -1598,8 +1594,6 @@ public class RealmAdapter implements RealmModel {
             entity.getConfig().putAll(mapper.getConfig());
         }
         em.flush();
-
-        session.getKeycloakSessionFactory().publish(new UserFederationMapperEventImpl(mapper, this, session));
     }
 
     @Override
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 c3b159a..6a1a84d 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
@@ -22,7 +22,6 @@ import org.keycloak.models.RealmProvider;
 import org.keycloak.models.RequiredActionProviderModel;
 import org.keycloak.models.RequiredCredentialModel;
 import org.keycloak.models.RoleModel;
-import org.keycloak.models.UserFederationMapperEventImpl;
 import org.keycloak.models.UserFederationMapperModel;
 import org.keycloak.models.UserFederationProviderCreationEventImpl;
 import org.keycloak.models.UserFederationProviderModel;
@@ -1930,8 +1929,6 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
         updateMongoEntity();
         UserFederationMapperModel mapperModel = entityToModel(entity);
 
-        session.getKeycloakSessionFactory().publish(new UserFederationMapperEventImpl(mapperModel, this, session));
-
         return mapperModel;
     }
 
@@ -1986,8 +1983,6 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
             entity.getConfig().putAll(mapper.getConfig());
         }
         updateMongoEntity();
-
-        session.getKeycloakSessionFactory().publish(new UserFederationMapperEventImpl(mapper, this, session));
     }
 
     @Override
diff --git a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/OnSessionCreated.java b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/OnSessionCreated.java
new file mode 100644
index 0000000..92f3b4d
--- /dev/null
+++ b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/OnSessionCreated.java
@@ -0,0 +1,9 @@
+package org.keycloak.adapters.saml;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public interface OnSessionCreated {
+
+    void onSessionCreated(SamlSession samlSession);
+}
diff --git a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/profile/AbstractSamlAuthenticationHandler.java b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/profile/AbstractSamlAuthenticationHandler.java
new file mode 100644
index 0000000..290b2d7
--- /dev/null
+++ b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/profile/AbstractSamlAuthenticationHandler.java
@@ -0,0 +1,484 @@
+package org.keycloak.adapters.saml.profile;
+
+import org.jboss.logging.Logger;
+import org.keycloak.adapters.saml.AbstractInitiateLogin;
+import org.keycloak.adapters.saml.OnSessionCreated;
+import org.keycloak.adapters.saml.SamlAuthenticationError;
+import org.keycloak.adapters.saml.SamlDeployment;
+import org.keycloak.adapters.saml.SamlPrincipal;
+import org.keycloak.adapters.saml.SamlSession;
+import org.keycloak.adapters.saml.SamlSessionStore;
+import org.keycloak.adapters.saml.SamlUtil;
+import org.keycloak.adapters.saml.profile.webbrowsersso.WebBrowserSsoAuthenticationHandler;
+import org.keycloak.adapters.spi.AuthChallenge;
+import org.keycloak.adapters.spi.AuthOutcome;
+import org.keycloak.adapters.spi.HttpFacade;
+import org.keycloak.common.VerificationException;
+import org.keycloak.common.util.KeycloakUriBuilder;
+import org.keycloak.common.util.MultivaluedHashMap;
+import org.keycloak.dom.saml.v2.assertion.AssertionType;
+import org.keycloak.dom.saml.v2.assertion.AttributeStatementType;
+import org.keycloak.dom.saml.v2.assertion.AttributeType;
+import org.keycloak.dom.saml.v2.assertion.AuthnStatementType;
+import org.keycloak.dom.saml.v2.assertion.NameIDType;
+import org.keycloak.dom.saml.v2.assertion.StatementAbstractType;
+import org.keycloak.dom.saml.v2.assertion.SubjectType;
+import org.keycloak.dom.saml.v2.protocol.LogoutRequestType;
+import org.keycloak.dom.saml.v2.protocol.RequestAbstractType;
+import org.keycloak.dom.saml.v2.protocol.ResponseType;
+import org.keycloak.dom.saml.v2.protocol.StatusCodeType;
+import org.keycloak.dom.saml.v2.protocol.StatusResponseType;
+import org.keycloak.dom.saml.v2.protocol.StatusType;
+import org.keycloak.saml.BaseSAML2BindingBuilder;
+import org.keycloak.saml.SAML2AuthnRequestBuilder;
+import org.keycloak.saml.SAMLRequestParser;
+import org.keycloak.saml.SignatureAlgorithm;
+import org.keycloak.saml.common.constants.GeneralConstants;
+import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
+import org.keycloak.saml.common.exceptions.ConfigurationException;
+import org.keycloak.saml.common.exceptions.ProcessingException;
+import org.keycloak.saml.common.util.Base64;
+import org.keycloak.saml.processing.api.saml.v2.sig.SAML2Signature;
+import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder;
+import org.keycloak.saml.processing.core.saml.v2.util.AssertionUtil;
+import org.keycloak.saml.processing.web.util.PostBindingUtil;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+
+import java.io.IOException;
+import java.net.URI;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ */
+public abstract class AbstractSamlAuthenticationHandler implements SamlAuthenticationHandler {
+
+    protected static Logger log = Logger.getLogger(WebBrowserSsoAuthenticationHandler.class);
+
+    protected final HttpFacade facade;
+    protected final SamlSessionStore sessionStore;
+    protected  final SamlDeployment deployment;
+    protected AuthChallenge challenge;
+
+    public AbstractSamlAuthenticationHandler(HttpFacade facade, SamlDeployment deployment, SamlSessionStore sessionStore) {
+        this.facade = facade;
+        this.deployment = deployment;
+        this.sessionStore = sessionStore;
+    }
+
+    public AuthOutcome doHandle(SamlInvocationContext context, OnSessionCreated onCreateSession) {
+        String samlRequest = context.getSamlRequest();
+        String samlResponse = context.getSamlResponse();
+        String relayState = context.getRelayState();
+        if (samlRequest != null) {
+            return handleSamlRequest(samlRequest, relayState);
+        } else if (samlResponse != null) {
+            return handleSamlResponse(samlResponse, relayState, onCreateSession);
+        } else if (sessionStore.isLoggedIn()) {
+            if (verifySSL()) return AuthOutcome.FAILED;
+            log.debug("AUTHENTICATED: was cached");
+            return handleRequest();
+        }
+        return initiateLogin();
+    }
+
+    protected AuthOutcome handleRequest() {
+        return AuthOutcome.AUTHENTICATED;
+    }
+
+    @Override
+    public AuthChallenge getChallenge() {
+        return this.challenge;
+    }
+
+    protected AuthOutcome handleSamlRequest(String samlRequest, String relayState) {
+        SAMLDocumentHolder holder = null;
+        boolean postBinding = false;
+        String requestUri = facade.getRequest().getURI();
+        if (facade.getRequest().getMethod().equalsIgnoreCase("GET")) {
+            // strip out query params
+            int index = requestUri.indexOf('?');
+            if (index > -1) {
+                requestUri = requestUri.substring(0, index);
+            }
+            holder = SAMLRequestParser.parseRequestRedirectBinding(samlRequest);
+        } else {
+            postBinding = true;
+            holder = SAMLRequestParser.parseRequestPostBinding(samlRequest);
+        }
+        RequestAbstractType requestAbstractType = (RequestAbstractType) holder.getSamlObject();
+        if (!requestUri.equals(requestAbstractType.getDestination().toString())) {
+            log.error("expected destination '" + requestUri + "' got '" + requestAbstractType.getDestination() + "'");
+            return AuthOutcome.FAILED;
+        }
+
+        if (requestAbstractType instanceof LogoutRequestType) {
+            if (deployment.getIDP().getSingleLogoutService().validateRequestSignature()) {
+                try {
+                    validateSamlSignature(holder, postBinding, GeneralConstants.SAML_REQUEST_KEY);
+                } catch (VerificationException e) {
+                    log.error("Failed to verify saml request signature", e);
+                    return AuthOutcome.FAILED;
+                }
+            }
+            LogoutRequestType logout = (LogoutRequestType) requestAbstractType;
+            return logoutRequest(logout, relayState);
+
+        } else {
+            log.error("unknown SAML request type");
+            return AuthOutcome.FAILED;
+        }
+    }
+
+    protected abstract AuthOutcome logoutRequest(LogoutRequestType request, String relayState);
+
+    protected AuthOutcome handleSamlResponse(String samlResponse, String relayState, OnSessionCreated onCreateSession) {
+        SAMLDocumentHolder holder = null;
+        boolean postBinding = false;
+        String requestUri = facade.getRequest().getURI();
+        if (facade.getRequest().getMethod().equalsIgnoreCase("GET")) {
+            int index = requestUri.indexOf('?');
+            if (index > -1) {
+                requestUri = requestUri.substring(0, index);
+            }
+            holder = extractRedirectBindingResponse(samlResponse);
+        } else {
+            postBinding = true;
+            holder = extractPostBindingResponse(samlResponse);
+        }
+        final StatusResponseType statusResponse = (StatusResponseType) holder.getSamlObject();
+        // validate destination
+        if (!requestUri.equals(statusResponse.getDestination())) {
+            log.error("Request URI does not match SAML request destination");
+            return AuthOutcome.FAILED;
+        }
+
+        if (statusResponse instanceof ResponseType) {
+            try {
+                if (deployment.getIDP().getSingleSignOnService().validateResponseSignature()) {
+                    try {
+                        validateSamlSignature(holder, postBinding, GeneralConstants.SAML_RESPONSE_KEY);
+                    } catch (VerificationException e) {
+                        log.error("Failed to verify saml response signature", e);
+
+                        challenge = new AuthChallenge() {
+                            @Override
+                            public boolean challenge(HttpFacade exchange) {
+                                SamlAuthenticationError error = new SamlAuthenticationError(SamlAuthenticationError.Reason.INVALID_SIGNATURE);
+                                exchange.getRequest().setError(error);
+                                exchange.getResponse().sendError(403);
+                                return true;
+                            }
+
+                            @Override
+                            public int getResponseCode() {
+                                return 403;
+                            }
+                        };
+                        return AuthOutcome.FAILED;
+                    }
+                }
+                return handleLoginResponse((ResponseType) statusResponse, onCreateSession);
+            } finally {
+                sessionStore.setCurrentAction(SamlSessionStore.CurrentAction.NONE);
+            }
+
+        } else {
+            if (sessionStore.isLoggingOut()) {
+                try {
+                    if (deployment.getIDP().getSingleLogoutService().validateResponseSignature()) {
+                        try {
+                            validateSamlSignature(holder, postBinding, GeneralConstants.SAML_RESPONSE_KEY);
+                        } catch (VerificationException e) {
+                            log.error("Failed to verify saml response signature", e);
+                            return AuthOutcome.FAILED;
+                        }
+                    }
+                    return handleLogoutResponse(holder, statusResponse, relayState);
+                } finally {
+                    sessionStore.setCurrentAction(SamlSessionStore.CurrentAction.NONE);
+                }
+
+            } else if (sessionStore.isLoggingIn()) {
+
+                try {
+                    // KEYCLOAK-2107 - handle user not authenticated due passive mode. Return special outcome so different authentication mechanisms can behave accordingly.
+                    StatusType status = statusResponse.getStatus();
+                    if(checkStatusCodeValue(status.getStatusCode(), JBossSAMLURIConstants.STATUS_RESPONDER.get()) && checkStatusCodeValue(status.getStatusCode().getStatusCode(), JBossSAMLURIConstants.STATUS_NO_PASSIVE.get())){
+                        log.debug("Not authenticated due passive mode Status found in SAML response: " + status.toString());
+                        return AuthOutcome.NOT_AUTHENTICATED;
+                    }
+
+                    challenge = new AuthChallenge() {
+                        @Override
+                        public boolean challenge(HttpFacade exchange) {
+                            SamlAuthenticationError error = new SamlAuthenticationError(SamlAuthenticationError.Reason.ERROR_STATUS, statusResponse);
+                            exchange.getRequest().setError(error);
+                            exchange.getResponse().sendError(403);
+                            return true;
+                        }
+
+                        @Override
+                        public int getResponseCode() {
+                            return 403;
+                        }
+                    };
+                    return AuthOutcome.FAILED;
+                } finally {
+                    sessionStore.setCurrentAction(SamlSessionStore.CurrentAction.NONE);
+                }
+            }
+            return AuthOutcome.NOT_ATTEMPTED;
+        }
+
+    }
+
+    private void validateSamlSignature(SAMLDocumentHolder holder, boolean postBinding, String paramKey) throws VerificationException {
+        if (postBinding) {
+            verifyPostBindingSignature(holder.getSamlDocument(), deployment.getIDP().getSignatureValidationKey());
+        } else {
+            verifyRedirectBindingSignature(deployment.getIDP().getSignatureValidationKey(), paramKey);
+        }
+    }
+
+    private boolean checkStatusCodeValue(StatusCodeType statusCode, String expectedValue){
+        if(statusCode != null && statusCode.getValue()!=null){
+            String v = statusCode.getValue().toString();
+            return expectedValue.equals(v);
+        }
+        return false;
+    }
+
+    protected AuthOutcome handleLoginResponse(ResponseType responseType, OnSessionCreated onCreateSession) {
+
+        AssertionType assertion = null;
+        try {
+            assertion = AssertionUtil.getAssertion(responseType, deployment.getDecryptionKey());
+            if (AssertionUtil.hasExpired(assertion)) {
+                return initiateLogin();
+            }
+        } catch (Exception e) {
+            log.error("Error extracting SAML assertion: " + e.getMessage());
+            challenge = new AuthChallenge() {
+                @Override
+                public boolean challenge(HttpFacade exchange) {
+                    SamlAuthenticationError error = new SamlAuthenticationError(SamlAuthenticationError.Reason.EXTRACTION_FAILURE);
+                    exchange.getRequest().setError(error);
+                    exchange.getResponse().sendError(403);
+                    return true;
+                }
+
+                @Override
+                public int getResponseCode() {
+                    return 403;
+                }
+            };
+        }
+
+        SubjectType subject = assertion.getSubject();
+        SubjectType.STSubType subType = subject.getSubType();
+        NameIDType subjectNameID = (NameIDType) subType.getBaseID();
+        String principalName = subjectNameID.getValue();
+
+        final Set<String> roles = new HashSet<>();
+        MultivaluedHashMap<String, String> attributes = new MultivaluedHashMap<>();
+        MultivaluedHashMap<String, String> friendlyAttributes = new MultivaluedHashMap<>();
+
+        Set<StatementAbstractType> statements = assertion.getStatements();
+        for (StatementAbstractType statement : statements) {
+            if (statement instanceof AttributeStatementType) {
+                AttributeStatementType attributeStatement = (AttributeStatementType) statement;
+                List<AttributeStatementType.ASTChoiceType> attList = attributeStatement.getAttributes();
+                for (AttributeStatementType.ASTChoiceType obj : attList) {
+                    AttributeType attr = obj.getAttribute();
+                    if (isRole(attr)) {
+                        List<Object> attributeValues = attr.getAttributeValue();
+                        if (attributeValues != null) {
+                            for (Object attrValue : attributeValues) {
+                                String role = getAttributeValue(attrValue);
+                                log.debugv("Add role: {0}", role);
+                                roles.add(role);
+                            }
+                        }
+                    } else {
+                        List<Object> attributeValues = attr.getAttributeValue();
+                        if (attributeValues != null) {
+                            for (Object attrValue : attributeValues) {
+                                String value = getAttributeValue(attrValue);
+                                if (attr.getName() != null) {
+                                    attributes.add(attr.getName(), value);
+                                }
+                                if (attr.getFriendlyName() != null) {
+                                    friendlyAttributes.add(attr.getFriendlyName(), value);
+                                }
+                            }
+                        }
+                    }
+
+                }
+            }
+        }
+        if (deployment.getPrincipalNamePolicy() == SamlDeployment.PrincipalNamePolicy.FROM_ATTRIBUTE) {
+            if (deployment.getPrincipalAttributeName() != null) {
+                String attribute = attributes.getFirst(deployment.getPrincipalAttributeName());
+                if (attribute != null) principalName = attribute;
+                else {
+                    attribute = friendlyAttributes.getFirst(deployment.getPrincipalAttributeName());
+                    if (attribute != null) principalName = attribute;
+                }
+            }
+        }
+
+        AuthnStatementType authn = null;
+        for (Object statement : assertion.getStatements()) {
+            if (statement instanceof AuthnStatementType) {
+                authn = (AuthnStatementType) statement;
+                break;
+            }
+        }
+
+
+        URI nameFormat = subjectNameID.getFormat();
+        String nameFormatString = nameFormat == null ? JBossSAMLURIConstants.NAMEID_FORMAT_UNSPECIFIED.get() : nameFormat.toString();
+        final SamlPrincipal principal = new SamlPrincipal(assertion, principalName, principalName, nameFormatString, attributes, friendlyAttributes);
+        String index = authn == null ? null : authn.getSessionIndex();
+        final String sessionIndex = index;
+        SamlSession account = new SamlSession(principal, roles, sessionIndex);
+        sessionStore.saveAccount(account);
+        onCreateSession.onSessionCreated(account);
+
+        // redirect to original request, it will be restored
+        String redirectUri = sessionStore.getRedirectUri();
+        if (redirectUri != null) {
+            facade.getResponse().setHeader("Location", redirectUri);
+            facade.getResponse().setStatus(302);
+            facade.getResponse().end();
+        } else {
+            log.debug("IDP initiated invocation");
+        }
+        log.debug("AUTHENTICATED authn");
+
+        return AuthOutcome.AUTHENTICATED;
+    }
+
+    private String getAttributeValue(Object attrValue) {
+        String value = null;
+        if (attrValue instanceof String) {
+            value = (String) attrValue;
+        } else if (attrValue instanceof Node) {
+            Node roleNode = (Node) attrValue;
+            value = roleNode.getFirstChild().getNodeValue();
+        } else if (attrValue instanceof NameIDType) {
+            NameIDType nameIdType = (NameIDType) attrValue;
+            value = nameIdType.getValue();
+        } else {
+            log.warn("Unable to extract unknown SAML assertion attribute value type: " + attrValue.getClass().getName());
+        }
+        return value;
+    }
+
+    protected boolean isRole(AttributeType attribute) {
+        return (attribute.getName() != null && deployment.getRoleAttributeNames().contains(attribute.getName())) || (attribute.getFriendlyName() != null && deployment.getRoleAttributeNames().contains(attribute.getFriendlyName()));
+    }
+
+    protected AuthOutcome handleLogoutResponse(SAMLDocumentHolder holder, StatusResponseType responseType, String relayState) {
+        boolean loggedIn = sessionStore.isLoggedIn();
+        if (!loggedIn || !"logout".equals(relayState)) {
+            return AuthOutcome.NOT_ATTEMPTED;
+        }
+        sessionStore.logoutAccount();
+        return AuthOutcome.LOGGED_OUT;
+    }
+
+    protected SAMLDocumentHolder extractRedirectBindingResponse(String response) {
+        return SAMLRequestParser.parseRequestRedirectBinding(response);
+    }
+
+
+    protected SAMLDocumentHolder extractPostBindingResponse(String response) {
+        byte[] samlBytes = PostBindingUtil.base64Decode(response);
+        return SAMLRequestParser.parseResponseDocument(samlBytes);
+    }
+
+
+    protected AuthOutcome initiateLogin() {
+        challenge = createChallenge();
+        return AuthOutcome.NOT_ATTEMPTED;
+    }
+
+    protected AbstractInitiateLogin createChallenge() {
+        return new AbstractInitiateLogin(deployment, sessionStore) {
+            @Override
+            protected void sendAuthnRequest(HttpFacade httpFacade, SAML2AuthnRequestBuilder authnRequestBuilder, BaseSAML2BindingBuilder binding) throws ProcessingException, ConfigurationException, IOException {
+                Document document = authnRequestBuilder.toDocument();
+                SamlDeployment.Binding samlBinding = deployment.getIDP().getSingleSignOnService().getRequestBinding();
+                SamlUtil.sendSaml(true, httpFacade, deployment.getIDP().getSingleSignOnService().getRequestBindingUrl(), binding, document, samlBinding);
+            }
+        };
+    }
+
+    protected boolean verifySSL() {
+        if (!facade.getRequest().isSecure() && deployment.getSslRequired().isRequired(facade.getRequest().getRemoteAddr())) {
+            log.warn("SSL is required to authenticate");
+            return true;
+        }
+        return false;
+    }
+
+    public void verifyPostBindingSignature(Document document, PublicKey publicKey) throws VerificationException {
+        SAML2Signature saml2Signature = new SAML2Signature();
+        try {
+            if (!saml2Signature.validate(document, publicKey)) {
+                throw new VerificationException("Invalid signature on document");
+            }
+        } catch (ProcessingException e) {
+            throw new VerificationException("Error validating signature", e);
+        }
+    }
+
+    public void verifyRedirectBindingSignature(PublicKey publicKey, String paramKey) throws VerificationException {
+        String request = facade.getRequest().getQueryParamValue(paramKey);
+        String algorithm = facade.getRequest().getQueryParamValue(GeneralConstants.SAML_SIG_ALG_REQUEST_KEY);
+        String signature = facade.getRequest().getQueryParamValue(GeneralConstants.SAML_SIGNATURE_REQUEST_KEY);
+        String decodedAlgorithm = facade.getRequest().getQueryParamValue(GeneralConstants.SAML_SIG_ALG_REQUEST_KEY);
+
+        if (request == null) {
+            throw new VerificationException("SAML Request was null");
+        }
+        if (algorithm == null) throw new VerificationException("SigAlg was null");
+        if (signature == null) throw new VerificationException("Signature was null");
+
+        // Shibboleth doesn't sign the document for redirect binding.
+        // todo maybe a flag?
+
+        String relayState = facade.getRequest().getQueryParamValue(GeneralConstants.RELAY_STATE);
+        KeycloakUriBuilder builder = KeycloakUriBuilder.fromPath("/")
+                .queryParam(paramKey, request);
+        if (relayState != null) {
+            builder.queryParam(GeneralConstants.RELAY_STATE, relayState);
+        }
+        builder.queryParam(GeneralConstants.SAML_SIG_ALG_REQUEST_KEY, algorithm);
+        String rawQuery = builder.build().getRawQuery();
+
+        try {
+            //byte[] decodedSignature = RedirectBindingUtil.urlBase64Decode(signature);
+            byte[] decodedSignature = Base64.decode(signature);
+
+            SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.getFromXmlMethod(decodedAlgorithm);
+            Signature validator = signatureAlgorithm.createSignature(); // todo plugin signature alg
+            validator.initVerify(publicKey);
+            validator.update(rawQuery.getBytes("UTF-8"));
+            if (!validator.verify(decodedSignature)) {
+                throw new VerificationException("Invalid query param signature");
+            }
+        } catch (Exception e) {
+            throw new VerificationException(e);
+        }
+    }
+}
diff --git a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/profile/ecp/EcpAuthenticationHandler.java b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/profile/ecp/EcpAuthenticationHandler.java
new file mode 100644
index 0000000..5fa99a0
--- /dev/null
+++ b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/profile/ecp/EcpAuthenticationHandler.java
@@ -0,0 +1,146 @@
+package org.keycloak.adapters.saml.profile.ecp;
+
+import org.keycloak.adapters.saml.AbstractInitiateLogin;
+import org.keycloak.adapters.saml.OnSessionCreated;
+import org.keycloak.adapters.saml.SamlDeployment;
+import org.keycloak.adapters.saml.SamlSessionStore;
+import org.keycloak.adapters.saml.profile.AbstractSamlAuthenticationHandler;
+import org.keycloak.adapters.saml.profile.SamlAuthenticationHandler;
+import org.keycloak.adapters.saml.profile.SamlInvocationContext;
+import org.keycloak.adapters.spi.AuthOutcome;
+import org.keycloak.adapters.spi.HttpFacade;
+import org.keycloak.dom.saml.v2.protocol.LogoutRequestType;
+import org.keycloak.saml.BaseSAML2BindingBuilder;
+import org.keycloak.saml.SAML2AuthnRequestBuilder;
+import org.keycloak.saml.common.constants.JBossSAMLConstants;
+import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
+import org.keycloak.saml.processing.core.saml.v2.util.DocumentUtil;
+import org.keycloak.saml.processing.web.util.PostBindingUtil;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+
+import javax.xml.soap.MessageFactory;
+import javax.xml.soap.SOAPBody;
+import javax.xml.soap.SOAPEnvelope;
+import javax.xml.soap.SOAPException;
+import javax.xml.soap.SOAPHeader;
+import javax.xml.soap.SOAPHeaderElement;
+import javax.xml.soap.SOAPMessage;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class EcpAuthenticationHandler extends AbstractSamlAuthenticationHandler {
+
+    public static final String PAOS_HEADER = "PAOS";
+    public static final String PAOS_CONTENT_TYPE = "application/vnd.paos+xml";
+    private static final String NS_PREFIX_PROFILE_ECP = "ecp";
+    private static final String NS_PREFIX_SAML_PROTOCOL = "samlp";
+    private static final String NS_PREFIX_SAML_ASSERTION = "saml";
+    private static final String NS_PREFIX_PAOS_BINDING = "paos";
+
+    public static boolean canHandle(HttpFacade httpFacade) {
+        HttpFacade.Request request = httpFacade.getRequest();
+        String acceptHeader = request.getHeader("Accept");
+        String contentTypeHeader = request.getHeader("Content-Type");
+
+        return (acceptHeader != null && acceptHeader.contains(PAOS_CONTENT_TYPE) && request.getHeader(PAOS_HEADER) != null)
+                || (contentTypeHeader != null && contentTypeHeader.contains(PAOS_CONTENT_TYPE));
+    }
+
+    public static SamlAuthenticationHandler create(HttpFacade facade, SamlDeployment deployment, SamlSessionStore sessionStore) {
+        return new EcpAuthenticationHandler(facade, deployment, sessionStore);
+    }
+
+    private  EcpAuthenticationHandler(HttpFacade facade, SamlDeployment deployment, SamlSessionStore sessionStore) {
+        super(facade, deployment, sessionStore);
+    }
+
+    @Override
+    protected AuthOutcome logoutRequest(LogoutRequestType request, String relayState) {
+        throw new RuntimeException("Not supported.");
+    }
+
+
+    @Override
+    public AuthOutcome handle(OnSessionCreated onCreateSession) {
+        String header = facade.getRequest().getHeader(PAOS_HEADER);
+
+        if (header != null) {
+            return doHandle(new SamlInvocationContext(), onCreateSession);
+        } else {
+            try {
+                MessageFactory messageFactory = MessageFactory.newInstance();
+                SOAPMessage soapMessage = messageFactory.createMessage(null, facade.getRequest().getInputStream());
+                SOAPBody soapBody = soapMessage.getSOAPBody();
+                Node authnRequestNode = soapBody.getFirstChild();
+                Document document = DocumentUtil.createDocument();
+
+                document.appendChild(document.importNode(authnRequestNode, true));
+
+                String samlResponse = PostBindingUtil.base64Encode(DocumentUtil.asString(document));
+
+                return doHandle(new SamlInvocationContext(null, samlResponse, null), onCreateSession);
+            } catch (Exception e) {
+                throw new RuntimeException("Error creating fault message.", e);
+            }
+        }
+    }
+
+    @Override
+    protected AbstractInitiateLogin createChallenge() {
+        return new AbstractInitiateLogin(deployment, sessionStore) {
+            @Override
+            protected void sendAuthnRequest(HttpFacade httpFacade, SAML2AuthnRequestBuilder authnRequestBuilder, BaseSAML2BindingBuilder binding) {
+                try {
+                    MessageFactory messageFactory = MessageFactory.newInstance();
+                    SOAPMessage message = messageFactory.createMessage();
+
+                    SOAPEnvelope envelope = message.getSOAPPart().getEnvelope();
+
+                    envelope.addNamespaceDeclaration(NS_PREFIX_SAML_ASSERTION, JBossSAMLURIConstants.ASSERTION_NSURI.get());
+                    envelope.addNamespaceDeclaration(NS_PREFIX_SAML_PROTOCOL, JBossSAMLURIConstants.PROTOCOL_NSURI.get());
+                    envelope.addNamespaceDeclaration(NS_PREFIX_PAOS_BINDING, JBossSAMLURIConstants.PAOS_BINDING.get());
+                    envelope.addNamespaceDeclaration(NS_PREFIX_PROFILE_ECP, JBossSAMLURIConstants.ECP_PROFILE.get());
+
+                    createPaosRequestHeader(envelope);
+                    createEcpRequestHeader(envelope);
+
+                    SOAPBody body = envelope.getBody();
+
+                    body.addDocument(binding.postBinding(authnRequestBuilder.toDocument()).getDocument());
+
+                    message.writeTo(httpFacade.getResponse().getOutputStream());
+                } catch (Exception e) {
+                    throw new RuntimeException("Could not create AuthnRequest.", e);
+                }
+            }
+
+            private void createEcpRequestHeader(SOAPEnvelope envelope) throws SOAPException {
+                SOAPHeader headers = envelope.getHeader();
+                SOAPHeaderElement ecpRequestHeader = headers.addHeaderElement(envelope.createQName(JBossSAMLConstants.REQUEST.get(), NS_PREFIX_PROFILE_ECP));
+
+                ecpRequestHeader.setMustUnderstand(true);
+                ecpRequestHeader.setActor("http://schemas.xmlsoap.org/soap/actor/next");
+                ecpRequestHeader.addAttribute(envelope.createName("ProviderName"), deployment.getEntityID());
+                ecpRequestHeader.addAttribute(envelope.createName("IsPassive"), "0");
+                ecpRequestHeader.addChildElement(envelope.createQName("Issuer", "saml")).setValue(deployment.getEntityID());
+                ecpRequestHeader.addChildElement(envelope.createQName("IDPList", "samlp"))
+                        .addChildElement(envelope.createQName("IDPEntry", "samlp"))
+                        .addAttribute(envelope.createName("ProviderID"), deployment.getIDP().getEntityID())
+                        .addAttribute(envelope.createName("Name"), deployment.getIDP().getEntityID())
+                        .addAttribute(envelope.createName("Loc"), deployment.getIDP().getSingleSignOnService().getRequestBindingUrl());
+            }
+
+            private void createPaosRequestHeader(SOAPEnvelope envelope) throws SOAPException {
+                SOAPHeader headers = envelope.getHeader();
+                SOAPHeaderElement paosRequestHeader = headers.addHeaderElement(envelope.createQName(JBossSAMLConstants.REQUEST.get(), NS_PREFIX_PAOS_BINDING));
+
+                paosRequestHeader.setMustUnderstand(true);
+                paosRequestHeader.setActor("http://schemas.xmlsoap.org/soap/actor/next");
+                paosRequestHeader.addAttribute(envelope.createName("service"), JBossSAMLURIConstants.ECP_PROFILE.get());
+                paosRequestHeader.addAttribute(envelope.createName("responseConsumerURL"), deployment.getAssertionConsumerServiceUrl());
+            }
+        };
+    }
+}
\ No newline at end of file
diff --git a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/profile/SamlAuthenticationHandler.java b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/profile/SamlAuthenticationHandler.java
new file mode 100644
index 0000000..4f499c7
--- /dev/null
+++ b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/profile/SamlAuthenticationHandler.java
@@ -0,0 +1,13 @@
+package org.keycloak.adapters.saml.profile;
+
+import org.keycloak.adapters.saml.OnSessionCreated;
+import org.keycloak.adapters.spi.AuthChallenge;
+import org.keycloak.adapters.spi.AuthOutcome;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public interface SamlAuthenticationHandler {
+    AuthOutcome handle(OnSessionCreated onCreateSession);
+    AuthChallenge getChallenge();
+}
diff --git a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/profile/SamlInvocationContext.java b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/profile/SamlInvocationContext.java
new file mode 100644
index 0000000..1155b0d
--- /dev/null
+++ b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/profile/SamlInvocationContext.java
@@ -0,0 +1,37 @@
+package org.keycloak.adapters.saml.profile;
+
+import org.keycloak.adapters.saml.SamlDeployment;
+import org.keycloak.adapters.saml.SamlSessionStore;
+import org.keycloak.adapters.spi.HttpFacade;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class SamlInvocationContext {
+
+    private String samlRequest;
+    private String samlResponse;
+    private String relayState;
+
+    public SamlInvocationContext() {
+        this(null, null, null);
+    }
+
+    public SamlInvocationContext(String samlRequest, String samlResponse, String relayState) {
+        this.samlRequest = samlRequest;
+        this.samlResponse = samlResponse;
+        this.relayState = relayState;
+    }
+
+    public String getSamlRequest() {
+        return this.samlRequest;
+    }
+
+    public String getSamlResponse() {
+        return this.samlResponse;
+    }
+
+    public String getRelayState() {
+        return this.relayState;
+    }
+}
diff --git a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/profile/webbrowsersso/WebBrowserSsoAuthenticationHandler.java b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/profile/webbrowsersso/WebBrowserSsoAuthenticationHandler.java
new file mode 100644
index 0000000..f3e98e5
--- /dev/null
+++ b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/profile/webbrowsersso/WebBrowserSsoAuthenticationHandler.java
@@ -0,0 +1,111 @@
+package org.keycloak.adapters.saml.profile.webbrowsersso;
+
+import org.keycloak.adapters.saml.OnSessionCreated;
+import org.keycloak.adapters.saml.SamlDeployment;
+import org.keycloak.adapters.saml.SamlSession;
+import org.keycloak.adapters.saml.SamlSessionStore;
+import org.keycloak.adapters.saml.SamlUtil;
+import org.keycloak.adapters.saml.profile.AbstractSamlAuthenticationHandler;
+import org.keycloak.adapters.saml.profile.SamlAuthenticationHandler;
+import org.keycloak.adapters.saml.profile.SamlInvocationContext;
+import org.keycloak.adapters.spi.AuthOutcome;
+import org.keycloak.adapters.spi.HttpFacade;
+import org.keycloak.dom.saml.v2.protocol.LogoutRequestType;
+import org.keycloak.saml.BaseSAML2BindingBuilder;
+import org.keycloak.saml.SAML2LogoutRequestBuilder;
+import org.keycloak.saml.SAML2LogoutResponseBuilder;
+import org.keycloak.saml.common.constants.GeneralConstants;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class WebBrowserSsoAuthenticationHandler extends AbstractSamlAuthenticationHandler {
+
+    public static SamlAuthenticationHandler create(HttpFacade facade, SamlDeployment deployment, SamlSessionStore sessionStore) {
+        return new WebBrowserSsoAuthenticationHandler(facade, deployment, sessionStore);
+    }
+
+    private WebBrowserSsoAuthenticationHandler(HttpFacade facade, SamlDeployment deployment, SamlSessionStore sessionStore) {
+        super(facade, deployment, sessionStore);
+    }
+
+    @Override
+    public AuthOutcome handle(OnSessionCreated onCreateSession) {
+        return doHandle(new SamlInvocationContext(facade.getRequest().getFirstParam(GeneralConstants.SAML_REQUEST_KEY),
+                facade.getRequest().getFirstParam(GeneralConstants.SAML_RESPONSE_KEY),
+                facade.getRequest().getFirstParam(GeneralConstants.RELAY_STATE)), onCreateSession);
+    }
+
+    @Override
+    protected AuthOutcome handleRequest() {
+        boolean globalLogout = "true".equals(facade.getRequest().getQueryParamValue("GLO"));
+
+        if (globalLogout) {
+            return globalLogout();
+        }
+
+        return AuthOutcome.AUTHENTICATED;
+    }
+
+    @Override
+    protected AuthOutcome logoutRequest(LogoutRequestType request, String relayState) {
+        if (request.getSessionIndex() == null || request.getSessionIndex().isEmpty()) {
+            sessionStore.logoutByPrincipal(request.getNameID().getValue());
+        } else {
+            sessionStore.logoutBySsoId(request.getSessionIndex());
+        }
+
+        String issuerURL = deployment.getEntityID();
+        SAML2LogoutResponseBuilder builder = new SAML2LogoutResponseBuilder();
+        builder.logoutRequestID(request.getID());
+        builder.destination(deployment.getIDP().getSingleLogoutService().getResponseBindingUrl());
+        builder.issuer(issuerURL);
+        BaseSAML2BindingBuilder binding = new BaseSAML2BindingBuilder().relayState(relayState);
+        if (deployment.getIDP().getSingleLogoutService().signResponse()) {
+            binding.signatureAlgorithm(deployment.getSignatureAlgorithm())
+                    .signWith(deployment.getSigningKeyPair())
+                    .signDocument();
+            if (deployment.getSignatureCanonicalizationMethod() != null)
+                binding.canonicalizationMethod(deployment.getSignatureCanonicalizationMethod());
+        }
+
+
+        try {
+            SamlUtil.sendSaml(false, facade, deployment.getIDP().getSingleLogoutService().getResponseBindingUrl(), binding, builder.buildDocument(),
+                    deployment.getIDP().getSingleLogoutService().getResponseBinding());
+        } catch (Exception e) {
+            log.error("Could not send logout response SAML request", e);
+            return AuthOutcome.FAILED;
+        }
+        return AuthOutcome.NOT_ATTEMPTED;
+    }
+
+    private AuthOutcome globalLogout() {
+        SamlSession account = sessionStore.getAccount();
+        if (account == null) {
+            return AuthOutcome.NOT_ATTEMPTED;
+        }
+        SAML2LogoutRequestBuilder logoutBuilder = new SAML2LogoutRequestBuilder()
+                .assertionExpiration(30)
+                .issuer(deployment.getEntityID())
+                .sessionIndex(account.getSessionIndex())
+                .userPrincipal(account.getPrincipal().getSamlSubject(), account.getPrincipal().getNameIDFormat())
+                .destination(deployment.getIDP().getSingleLogoutService().getRequestBindingUrl());
+        BaseSAML2BindingBuilder binding = new BaseSAML2BindingBuilder();
+        if (deployment.getIDP().getSingleLogoutService().signRequest()) {
+            binding.signWith(deployment.getSigningKeyPair())
+                    .signDocument();
+        }
+
+        binding.relayState("logout");
+
+        try {
+            SamlUtil.sendSaml(true, facade, deployment.getIDP().getSingleLogoutService().getRequestBindingUrl(), binding, logoutBuilder.buildDocument(), deployment.getIDP().getSingleLogoutService().getRequestBinding());
+            sessionStore.setCurrentAction(SamlSessionStore.CurrentAction.LOGGING_OUT);
+        } catch (Exception e) {
+            log.error("Could not send global logout SAML request", e);
+            return AuthOutcome.FAILED;
+        }
+        return AuthOutcome.NOT_ATTEMPTED;
+    }
+}
diff --git a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlAuthenticator.java b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlAuthenticator.java
index 02097db..cd9affd 100755
--- a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlAuthenticator.java
+++ b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlAuthenticator.java
@@ -1,530 +1,49 @@
 package org.keycloak.adapters.saml;
 
 import org.jboss.logging.Logger;
+import org.keycloak.adapters.saml.profile.SamlAuthenticationHandler;
+import org.keycloak.adapters.saml.profile.ecp.EcpAuthenticationHandler;
+import org.keycloak.adapters.saml.profile.webbrowsersso.WebBrowserSsoAuthenticationHandler;
 import org.keycloak.adapters.spi.AuthChallenge;
 import org.keycloak.adapters.spi.AuthOutcome;
 import org.keycloak.adapters.spi.HttpFacade;
-import org.keycloak.common.VerificationException;
-import org.keycloak.common.util.KeycloakUriBuilder;
-import org.keycloak.common.util.MultivaluedHashMap;
-import org.keycloak.dom.saml.v2.assertion.AssertionType;
-import org.keycloak.dom.saml.v2.assertion.AttributeStatementType;
-import org.keycloak.dom.saml.v2.assertion.AttributeType;
-import org.keycloak.dom.saml.v2.assertion.AuthnStatementType;
-import org.keycloak.dom.saml.v2.assertion.NameIDType;
-import org.keycloak.dom.saml.v2.assertion.StatementAbstractType;
-import org.keycloak.dom.saml.v2.assertion.SubjectType;
-import org.keycloak.dom.saml.v2.protocol.LogoutRequestType;
-import org.keycloak.dom.saml.v2.protocol.RequestAbstractType;
-import org.keycloak.dom.saml.v2.protocol.ResponseType;
-import org.keycloak.dom.saml.v2.protocol.StatusCodeType;
-import org.keycloak.dom.saml.v2.protocol.StatusResponseType;
-import org.keycloak.dom.saml.v2.protocol.StatusType;
-import org.keycloak.saml.BaseSAML2BindingBuilder;
-import org.keycloak.saml.SAML2LogoutRequestBuilder;
-import org.keycloak.saml.SAML2LogoutResponseBuilder;
-import org.keycloak.saml.SAMLRequestParser;
-import org.keycloak.saml.SignatureAlgorithm;
-import org.keycloak.saml.common.constants.GeneralConstants;
-import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
-import org.keycloak.saml.common.exceptions.ProcessingException;
-import org.keycloak.saml.common.util.Base64;
-import org.keycloak.saml.common.util.StringUtil;
-import org.keycloak.saml.processing.api.saml.v2.sig.SAML2Signature;
-import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder;
-import org.keycloak.saml.processing.core.saml.v2.util.AssertionUtil;
-import org.keycloak.saml.processing.web.util.PostBindingUtil;
-import org.w3c.dom.Document;
-import org.w3c.dom.Node;
-
-import java.net.URI;
-import java.security.PublicKey;
-import java.security.Signature;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
  */
 public abstract class SamlAuthenticator {
+
     protected static Logger log = Logger.getLogger(SamlAuthenticator.class);
 
-    protected HttpFacade facade;
-    protected AuthChallenge challenge;
-    protected SamlDeployment deployment;
-    protected SamlSessionStore sessionStore;
+    private final SamlAuthenticationHandler handler;
 
-    public SamlAuthenticator(HttpFacade facade, SamlDeployment deployment, SamlSessionStore sessionStore) {
-        this.facade = facade;
-        this.deployment = deployment;
-        this.sessionStore = sessionStore;
+    public SamlAuthenticator(final HttpFacade facade, final SamlDeployment deployment, final SamlSessionStore sessionStore) {
+        this.handler = createAuthenticationHandler(facade, deployment, sessionStore);
     }
 
     public AuthChallenge getChallenge() {
-        return challenge;
+        return this.handler.getChallenge();
     }
 
     public AuthOutcome authenticate() {
-
-
-        String samlRequest = facade.getRequest().getFirstParam(GeneralConstants.SAML_REQUEST_KEY);
-        String samlResponse = facade.getRequest().getFirstParam(GeneralConstants.SAML_RESPONSE_KEY);
-        String relayState = facade.getRequest().getFirstParam(GeneralConstants.RELAY_STATE);
-        boolean globalLogout = "true".equals(facade.getRequest().getQueryParamValue("GLO"));
-        if (samlRequest != null) {
-            return handleSamlRequest(samlRequest, relayState);
-        } else if (samlResponse != null) {
-            return handleSamlResponse(samlResponse, relayState);
-        } else if (sessionStore.isLoggedIn()) {
-            if (globalLogout) {
-                return globalLogout();
+        log.debugf("SamlAuthenticator is using handler [%s]", this.handler);
+        return this.handler.handle(new OnSessionCreated() {
+            @Override
+            public void onSessionCreated(SamlSession samlSession) {
+                completeAuthentication(samlSession);
             }
-            if (verifySSL()) return AuthOutcome.FAILED;
-            log.debug("AUTHENTICATED: was cached");
-            return AuthOutcome.AUTHENTICATED;
-        }
-        return initiateLogin();
+        });
     }
 
-    protected AuthOutcome globalLogout() {
-        SamlSession account = sessionStore.getAccount();
-        if (account == null) {
-            return AuthOutcome.NOT_ATTEMPTED;
-        }
-        SAML2LogoutRequestBuilder logoutBuilder = new SAML2LogoutRequestBuilder()
-                .assertionExpiration(30)
-                .issuer(deployment.getEntityID())
-                .sessionIndex(account.getSessionIndex())
-                .userPrincipal(account.getPrincipal().getSamlSubject(), account.getPrincipal().getNameIDFormat())
-                .destination(deployment.getIDP().getSingleLogoutService().getRequestBindingUrl());
-        BaseSAML2BindingBuilder binding = new BaseSAML2BindingBuilder();
-        if (deployment.getIDP().getSingleLogoutService().signRequest()) {
-            binding.signWith(deployment.getSigningKeyPair())
-                    .signDocument();
-        }
-
-        binding.relayState("logout");
+    protected abstract void completeAuthentication(SamlSession samlSession);
 
-        try {
-            SamlUtil.sendSaml(true, facade, deployment.getIDP().getSingleLogoutService().getRequestBindingUrl(), binding, logoutBuilder.buildDocument(), deployment.getIDP().getSingleLogoutService().getRequestBinding());
-            sessionStore.setCurrentAction(SamlSessionStore.CurrentAction.LOGGING_OUT);
-        } catch (Exception e) {
-            log.error("Could not send global logout SAML request", e);
-            return AuthOutcome.FAILED;
+    private SamlAuthenticationHandler createAuthenticationHandler(HttpFacade facade, SamlDeployment deployment, SamlSessionStore sessionStore) {
+        if (EcpAuthenticationHandler.canHandle(facade)) {
+            return EcpAuthenticationHandler.create(facade, deployment, sessionStore);
         }
-        return AuthOutcome.NOT_ATTEMPTED;
-    }
 
-    protected AuthOutcome handleSamlRequest(String samlRequest, String relayState) {
-        SAMLDocumentHolder holder = null;
-        boolean postBinding = false;
-        String requestUri = facade.getRequest().getURI();
-        if (facade.getRequest().getMethod().equalsIgnoreCase("GET")) {
-            // strip out query params
-            int index = requestUri.indexOf('?');
-            if (index > -1) {
-                requestUri = requestUri.substring(0, index);
-            }
-            holder = SAMLRequestParser.parseRequestRedirectBinding(samlRequest);
-        } else {
-            postBinding = true;
-            holder = SAMLRequestParser.parseRequestPostBinding(samlRequest);
-        }
-        RequestAbstractType requestAbstractType = (RequestAbstractType) holder.getSamlObject();
-        if (!requestUri.equals(requestAbstractType.getDestination().toString())) {
-            log.error("expected destination '" + requestUri + "' got '" + requestAbstractType.getDestination() + "'");
-            return AuthOutcome.FAILED;
-        }
-
-        if (requestAbstractType instanceof LogoutRequestType) {
-            if (deployment.getIDP().getSingleLogoutService().validateRequestSignature()) {
-                try {
-                    validateSamlSignature(holder, postBinding, GeneralConstants.SAML_REQUEST_KEY);
-                } catch (VerificationException e) {
-                    log.error("Failed to verify saml request signature", e);
-                    return AuthOutcome.FAILED;
-                }
-            }
-            LogoutRequestType logout = (LogoutRequestType) requestAbstractType;
-            return logoutRequest(logout, relayState);
-
-        } else {
-            log.error("unknown SAML request type");
-            return AuthOutcome.FAILED;
-        }
+        // defaults to the web browser sso profile
+        return WebBrowserSsoAuthenticationHandler.create(facade, deployment, sessionStore);
     }
-
-    protected AuthOutcome logoutRequest(LogoutRequestType request, String relayState) {
-        if (request.getSessionIndex() == null || request.getSessionIndex().isEmpty()) {
-            sessionStore.logoutByPrincipal(request.getNameID().getValue());
-        } else {
-            sessionStore.logoutBySsoId(request.getSessionIndex());
-        }
-
-        String issuerURL = deployment.getEntityID();
-        SAML2LogoutResponseBuilder builder = new SAML2LogoutResponseBuilder();
-        builder.logoutRequestID(request.getID());
-        builder.destination(deployment.getIDP().getSingleLogoutService().getResponseBindingUrl());
-        builder.issuer(issuerURL);
-        BaseSAML2BindingBuilder binding = new BaseSAML2BindingBuilder().relayState(relayState);
-        if (deployment.getIDP().getSingleLogoutService().signResponse()) {
-            binding.signatureAlgorithm(deployment.getSignatureAlgorithm())
-                    .signWith(deployment.getSigningKeyPair())
-                    .signDocument();
-            if (deployment.getSignatureCanonicalizationMethod() != null)
-                binding.canonicalizationMethod(deployment.getSignatureCanonicalizationMethod());
-        }
-
-
-        try {
-            SamlUtil.sendSaml(false, facade, deployment.getIDP().getSingleLogoutService().getResponseBindingUrl(), binding, builder.buildDocument(),
-                    deployment.getIDP().getSingleLogoutService().getResponseBinding());
-        } catch (Exception e) {
-            log.error("Could not send logout response SAML request", e);
-            return AuthOutcome.FAILED;
-        }
-        return AuthOutcome.NOT_ATTEMPTED;
-
-    }
-
-
-    protected AuthOutcome handleSamlResponse(String samlResponse, String relayState) {
-        SAMLDocumentHolder holder = null;
-        boolean postBinding = false;
-        String requestUri = facade.getRequest().getURI();
-        if (facade.getRequest().getMethod().equalsIgnoreCase("GET")) {
-            int index = requestUri.indexOf('?');
-            if (index > -1) {
-                requestUri = requestUri.substring(0, index);
-            }
-            holder = extractRedirectBindingResponse(samlResponse);
-        } else {
-            postBinding = true;
-            holder = extractPostBindingResponse(samlResponse);
-        }
-        final StatusResponseType statusResponse = (StatusResponseType) holder.getSamlObject();
-        // validate destination
-        if (!requestUri.equals(statusResponse.getDestination())) {
-            log.error("Request URI does not match SAML request destination");
-            return AuthOutcome.FAILED;
-        }
-        
-        if (statusResponse instanceof ResponseType) {            
-            try {
-                if (deployment.getIDP().getSingleSignOnService().validateResponseSignature()) {
-                    try {
-                        validateSamlSignature(holder, postBinding, GeneralConstants.SAML_RESPONSE_KEY);
-                    } catch (VerificationException e) {
-                        log.error("Failed to verify saml response signature", e);
-
-                        challenge = new AuthChallenge() {
-                            @Override
-                            public boolean challenge(HttpFacade exchange) {
-                                SamlAuthenticationError error = new SamlAuthenticationError(SamlAuthenticationError.Reason.INVALID_SIGNATURE);
-                                exchange.getRequest().setError(error);
-                                exchange.getResponse().sendError(403);
-                                return true;
-                            }
-
-                            @Override
-                            public int getResponseCode() {
-                                return 403;
-                            }
-                        };
-                        return AuthOutcome.FAILED;
-                    }
-                }
-                return handleLoginResponse((ResponseType) statusResponse);
-            } finally {
-                sessionStore.setCurrentAction(SamlSessionStore.CurrentAction.NONE);
-            }
-
-        } else {
-            if (sessionStore.isLoggingOut()) {
-                try {
-                    if (deployment.getIDP().getSingleLogoutService().validateResponseSignature()) {
-                        try {
-                            validateSamlSignature(holder, postBinding, GeneralConstants.SAML_RESPONSE_KEY);
-                        } catch (VerificationException e) {
-                            log.error("Failed to verify saml response signature", e);
-                            return AuthOutcome.FAILED;
-                        }
-                    }
-                    return handleLogoutResponse(holder, statusResponse, relayState);
-                } finally {
-                    sessionStore.setCurrentAction(SamlSessionStore.CurrentAction.NONE);
-                }
-
-            } else if (sessionStore.isLoggingIn()) {
-
-                try {
-                    // KEYCLOAK-2107 - handle user not authenticated due passive mode. Return special outcome so different authentication mechanisms can behave accordingly.
-                    StatusType status = statusResponse.getStatus();
-                    if(checkStatusCodeValue(status.getStatusCode(), JBossSAMLURIConstants.STATUS_RESPONDER.get()) && checkStatusCodeValue(status.getStatusCode().getStatusCode(), JBossSAMLURIConstants.STATUS_NO_PASSIVE.get())){
-                        log.debug("Not authenticated due passive mode Status found in SAML response: " + status.toString());
-                        return AuthOutcome.NOT_AUTHENTICATED;
-                    }
-
-                    challenge = new AuthChallenge() {
-                        @Override
-                        public boolean challenge(HttpFacade exchange) {
-                            SamlAuthenticationError error = new SamlAuthenticationError(SamlAuthenticationError.Reason.ERROR_STATUS, statusResponse);
-                            exchange.getRequest().setError(error);
-                            exchange.getResponse().sendError(403);
-                            return true;
-                        }
-
-                        @Override
-                        public int getResponseCode() {
-                            return 403;
-                        }
-                    };
-                    return AuthOutcome.FAILED;
-                } finally {
-                    sessionStore.setCurrentAction(SamlSessionStore.CurrentAction.NONE);
-                }
-            }
-            return AuthOutcome.NOT_ATTEMPTED;
-        }
-
-    }
-
-    private void validateSamlSignature(SAMLDocumentHolder holder, boolean postBinding, String paramKey) throws VerificationException {
-        if (postBinding) {
-            verifyPostBindingSignature(holder.getSamlDocument(), deployment.getIDP().getSignatureValidationKey());
-        } else {
-            verifyRedirectBindingSignature(deployment.getIDP().getSignatureValidationKey(), paramKey);
-        }
-    }
-
-    private boolean checkStatusCodeValue(StatusCodeType statusCode, String expectedValue){
-        if(statusCode != null && statusCode.getValue()!=null){
-            String v = statusCode.getValue().toString();
-            return expectedValue.equals(v);
-        }
-        return false;
-    }
-    
-    protected AuthOutcome handleLoginResponse(ResponseType responseType) {
-        
-        AssertionType assertion = null;
-        try {
-            assertion = AssertionUtil.getAssertion(responseType, deployment.getDecryptionKey());
-            if (AssertionUtil.hasExpired(assertion)) {
-                return initiateLogin();
-            }
-        } catch (Exception e) {
-            log.error("Error extracting SAML assertion: " + e.getMessage());
-            challenge = new AuthChallenge() {
-                @Override
-                public boolean challenge(HttpFacade exchange) {
-                    SamlAuthenticationError error = new SamlAuthenticationError(SamlAuthenticationError.Reason.EXTRACTION_FAILURE);
-                    exchange.getRequest().setError(error);
-                    exchange.getResponse().sendError(403);
-                    return true;
-                }
-
-                @Override
-                public int getResponseCode() {
-                    return 403;
-                }
-            };
-        }
-
-        SubjectType subject = assertion.getSubject();
-        SubjectType.STSubType subType = subject.getSubType();
-        NameIDType subjectNameID = (NameIDType) subType.getBaseID();
-        String principalName = subjectNameID.getValue();
-
-        final Set<String> roles = new HashSet<>();
-        MultivaluedHashMap<String, String> attributes = new MultivaluedHashMap<>();
-        MultivaluedHashMap<String, String> friendlyAttributes = new MultivaluedHashMap<>();
-
-        Set<StatementAbstractType> statements = assertion.getStatements();
-        for (StatementAbstractType statement : statements) {
-            if (statement instanceof AttributeStatementType) {
-                AttributeStatementType attributeStatement = (AttributeStatementType) statement;
-                List<AttributeStatementType.ASTChoiceType> attList = attributeStatement.getAttributes();
-                for (AttributeStatementType.ASTChoiceType obj : attList) {
-                    AttributeType attr = obj.getAttribute();
-                    if (isRole(attr)) {
-                        List<Object> attributeValues = attr.getAttributeValue();
-                        if (attributeValues != null) {
-                            for (Object attrValue : attributeValues) {
-                                String role = getAttributeValue(attrValue);
-                                log.debugv("Add role: {0}", role);
-                                roles.add(role);
-                            }
-                        }
-                    } else {
-                        List<Object> attributeValues = attr.getAttributeValue();
-                        if (attributeValues != null) {
-                            for (Object attrValue : attributeValues) {
-                                String value = getAttributeValue(attrValue);
-                                if (attr.getName() != null) {
-                                    attributes.add(attr.getName(), value);
-                                }
-                                if (attr.getFriendlyName() != null) {
-                                    friendlyAttributes.add(attr.getFriendlyName(), value);
-                                }
-                            }
-                        }
-                    }
-
-                }
-            }
-        }
-        if (deployment.getPrincipalNamePolicy() == SamlDeployment.PrincipalNamePolicy.FROM_ATTRIBUTE) {
-            if (deployment.getPrincipalAttributeName() != null) {
-                String attribute = attributes.getFirst(deployment.getPrincipalAttributeName());
-                if (attribute != null) principalName = attribute;
-                else {
-                    attribute = friendlyAttributes.getFirst(deployment.getPrincipalAttributeName());
-                    if (attribute != null) principalName = attribute;
-                }
-            }
-        }
-
-        AuthnStatementType authn = null;
-        for (Object statement : assertion.getStatements()) {
-            if (statement instanceof AuthnStatementType) {
-                authn = (AuthnStatementType) statement;
-                break;
-            }
-        }
-
-
-        URI nameFormat = subjectNameID.getFormat();
-        String nameFormatString = nameFormat == null ? JBossSAMLURIConstants.NAMEID_FORMAT_UNSPECIFIED.get() : nameFormat.toString();
-        final SamlPrincipal principal = new SamlPrincipal(assertion, principalName, principalName, nameFormatString, attributes, friendlyAttributes);
-        String index = authn == null ? null : authn.getSessionIndex();
-        final String sessionIndex = index;
-        SamlSession account = new SamlSession(principal, roles, sessionIndex);
-        sessionStore.saveAccount(account);
-        completeAuthentication(account);
-
-
-        // redirect to original request, it will be restored
-        String redirectUri = sessionStore.getRedirectUri();
-        if (redirectUri != null) {
-            facade.getResponse().setHeader("Location", redirectUri);
-            facade.getResponse().setStatus(302);
-            facade.getResponse().end();
-        } else {
-            log.debug("IDP initiated invocation");
-        }
-        log.debug("AUTHENTICATED authn");
-
-        return AuthOutcome.AUTHENTICATED;
-    }
-
-    protected abstract void completeAuthentication(SamlSession account);
-
-    private String getAttributeValue(Object attrValue) {
-        String value = null;
-        if (attrValue instanceof String) {
-            value = (String) attrValue;
-        } else if (attrValue instanceof Node) {
-            Node roleNode = (Node) attrValue;
-            value = roleNode.getFirstChild().getNodeValue();
-        } else if (attrValue instanceof NameIDType) {
-            NameIDType nameIdType = (NameIDType) attrValue;
-            value = nameIdType.getValue();
-        } else {
-            log.warn("Unable to extract unknown SAML assertion attribute value type: " + attrValue.getClass().getName());
-        }
-        return value;
-    }
-
-    protected boolean isRole(AttributeType attribute) {
-        return (attribute.getName() != null && deployment.getRoleAttributeNames().contains(attribute.getName())) || (attribute.getFriendlyName() != null && deployment.getRoleAttributeNames().contains(attribute.getFriendlyName()));
-    }
-
-    protected AuthOutcome handleLogoutResponse(SAMLDocumentHolder holder, StatusResponseType responseType, String relayState) {
-        boolean loggedIn = sessionStore.isLoggedIn();
-        if (!loggedIn || !"logout".equals(relayState)) {
-            return AuthOutcome.NOT_ATTEMPTED;
-        }
-        sessionStore.logoutAccount();
-        return AuthOutcome.LOGGED_OUT;
-    }
-
-    protected SAMLDocumentHolder extractRedirectBindingResponse(String response) {
-        return SAMLRequestParser.parseRequestRedirectBinding(response);
-    }
-
-    
-    protected SAMLDocumentHolder extractPostBindingResponse(String response) {
-        byte[] samlBytes = PostBindingUtil.base64Decode(response);
-        return SAMLRequestParser.parseResponseDocument(samlBytes);
-    }
-
-
-    protected AuthOutcome initiateLogin() {
-        challenge = new InitiateLogin(deployment, sessionStore);
-        return AuthOutcome.NOT_ATTEMPTED;
-    }
-
-    protected boolean verifySSL() {
-        if (!facade.getRequest().isSecure() && deployment.getSslRequired().isRequired(facade.getRequest().getRemoteAddr())) {
-            log.warn("SSL is required to authenticate");
-            return true;
-        }
-        return false;
-    }
-
-    public void verifyPostBindingSignature(Document document, PublicKey publicKey) throws VerificationException {
-        SAML2Signature saml2Signature = new SAML2Signature();
-        try {
-            if (!saml2Signature.validate(document, publicKey)) {
-                throw new VerificationException("Invalid signature on document");
-            }
-        } catch (ProcessingException e) {
-            throw new VerificationException("Error validating signature", e);
-        }
-    }
-
-    public void verifyRedirectBindingSignature(PublicKey publicKey, String paramKey) throws VerificationException {
-        String request = facade.getRequest().getQueryParamValue(paramKey);
-        String algorithm = facade.getRequest().getQueryParamValue(GeneralConstants.SAML_SIG_ALG_REQUEST_KEY);
-        String signature = facade.getRequest().getQueryParamValue(GeneralConstants.SAML_SIGNATURE_REQUEST_KEY);
-        String decodedAlgorithm = facade.getRequest().getQueryParamValue(GeneralConstants.SAML_SIG_ALG_REQUEST_KEY);
-
-        if (request == null) {
-            throw new VerificationException("SAML Request was null");
-        }
-        if (algorithm == null) throw new VerificationException("SigAlg was null");
-        if (signature == null) throw new VerificationException("Signature was null");
-
-        // Shibboleth doesn't sign the document for redirect binding.
-        // todo maybe a flag?
-
-        String relayState = facade.getRequest().getQueryParamValue(GeneralConstants.RELAY_STATE);
-        KeycloakUriBuilder builder = KeycloakUriBuilder.fromPath("/")
-                .queryParam(paramKey, request);
-        if (relayState != null) {
-            builder.queryParam(GeneralConstants.RELAY_STATE, relayState);
-        }
-        builder.queryParam(GeneralConstants.SAML_SIG_ALG_REQUEST_KEY, algorithm);
-        String rawQuery = builder.build().getRawQuery();
-
-        try {
-            //byte[] decodedSignature = RedirectBindingUtil.urlBase64Decode(signature);
-            byte[] decodedSignature = Base64.decode(signature);
-
-            SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.getFromXmlMethod(decodedAlgorithm);
-            Signature validator = signatureAlgorithm.createSignature(); // todo plugin signature alg
-            validator.initVerify(publicKey);
-            validator.update(rawQuery.getBytes("UTF-8"));
-            if (!validator.verify(decodedSignature)) {
-                throw new VerificationException("Invalid query param signature");
-            }
-        } catch (Exception e) {
-            throw new VerificationException(e);
-        }
-    }
-
-
-}
+}
\ No newline at end of file
diff --git a/saml/saml-core/src/main/java/org/keycloak/saml/common/constants/JBossSAMLConstants.java b/saml/saml-core/src/main/java/org/keycloak/saml/common/constants/JBossSAMLConstants.java
index fb90e17..219042b 100755
--- a/saml/saml-core/src/main/java/org/keycloak/saml/common/constants/JBossSAMLConstants.java
+++ b/saml/saml-core/src/main/java/org/keycloak/saml/common/constants/JBossSAMLConstants.java
@@ -65,7 +65,8 @@ public enum JBossSAMLConstants {
             "XACMLAuthzDecisionQuery"), XACML_AUTHZ_DECISION_QUERY_TYPE("XACMLAuthzDecisionQueryType"), XACML_AUTHZ_DECISION_STATEMENT_TYPE(
             "XACMLAuthzDecisionStatementType"), HTTP_POST_BINDING("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"), ONE_TIME_USE ("OneTimeUse"),
             UNSOLICITED_RESPONSE_TARGET("TARGET"), UNSOLICITED_RESPONSE_SAML_VERSION("SAML_VERSION"), UNSOLICITED_RESPONSE_SAML_BINDING("SAML_BINDING"),
-            ROLE_DESCRIPTOR("RoleDescriptor");
+            ROLE_DESCRIPTOR("RoleDescriptor"),
+            REQUEST_AUTHENTICATED("RequestAuthenticated");
 
     private String name;
 
diff --git a/saml/saml-core/src/main/java/org/keycloak/saml/common/constants/JBossSAMLURIConstants.java b/saml/saml-core/src/main/java/org/keycloak/saml/common/constants/JBossSAMLURIConstants.java
index 3833c56..ad7bee5 100755
--- a/saml/saml-core/src/main/java/org/keycloak/saml/common/constants/JBossSAMLURIConstants.java
+++ b/saml/saml-core/src/main/java/org/keycloak/saml/common/constants/JBossSAMLURIConstants.java
@@ -73,12 +73,15 @@ public enum JBossSAMLURIConstants {
             "urn:oasis:names:tc:SAML:2.0:nameid-format:entity"),
 
     PROTOCOL_NSURI("urn:oasis:names:tc:SAML:2.0:protocol"),
+    ECP_PROFILE("urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp"),
+    PAOS_BINDING("urn:liberty:paos:2003-08"),
 
     SIGNATURE_DSA_SHA1("http://www.w3.org/2000/09/xmldsig#dsa-sha1"), SIGNATURE_RSA_SHA1(
             "http://www.w3.org/2000/09/xmldsig#rsa-sha1"),
 
-    SAML_HTTP_POST_BINDING("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"), SAML_HTTP_REDIRECT_BINDING(
-            "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"),
+    SAML_HTTP_POST_BINDING("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"),
+    SAML_HTTP_SOAP_BINDING("urn:oasis:names:tc:SAML:2.0:bindings:SOAP"),
+    SAML_HTTP_REDIRECT_BINDING("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"),
 
     SAML_11_NS("urn:oasis:names:tc:SAML:1.0:assertion"),
 
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/profile/ecp/authenticator/HttpBasicAuthenticator.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/profile/ecp/authenticator/HttpBasicAuthenticator.java
new file mode 100644
index 0000000..2e15502
--- /dev/null
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/profile/ecp/authenticator/HttpBasicAuthenticator.java
@@ -0,0 +1,174 @@
+package org.keycloak.protocol.saml.profile.ecp.authenticator;
+
+import org.jboss.resteasy.spi.HttpRequest;
+import org.keycloak.Config;
+import org.keycloak.authentication.AuthenticationFlowContext;
+import org.keycloak.authentication.AuthenticationFlowError;
+import org.keycloak.authentication.Authenticator;
+import org.keycloak.authentication.AuthenticatorFactory;
+import org.keycloak.common.util.Base64;
+import org.keycloak.events.Errors;
+import org.keycloak.models.*;
+import org.keycloak.models.AuthenticationExecutionModel.Requirement;
+import org.keycloak.provider.ProviderConfigProperty;
+import org.keycloak.representations.AccessTokenResponse;
+import org.keycloak.representations.idm.CredentialRepresentation;
+
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class HttpBasicAuthenticator implements AuthenticatorFactory {
+
+    public static final String PROVIDER_ID = "http-basic-authenticator";
+
+    @Override
+    public String getDisplayType() {
+        return null;
+    }
+
+    @Override
+    public String getReferenceCategory() {
+        return null;
+    }
+
+    @Override
+    public boolean isConfigurable() {
+        return false;
+    }
+
+    @Override
+    public Requirement[] getRequirementChoices() {
+        return new Requirement[0];
+    }
+
+    @Override
+    public boolean isUserSetupAllowed() {
+        return false;
+    }
+
+    @Override
+    public String getHelpText() {
+        return null;
+    }
+
+    @Override
+    public List<ProviderConfigProperty> getConfigProperties() {
+        return null;
+    }
+
+    @Override
+    public Authenticator create(KeycloakSession session) {
+        return new Authenticator() {
+
+            private static final String BASIC = "Basic";
+            private static final String BASIC_PREFIX = BASIC + " ";
+
+            @Override
+            public void authenticate(AuthenticationFlowContext context) {
+                HttpRequest httpRequest = context.getHttpRequest();
+                HttpHeaders httpHeaders = httpRequest.getHttpHeaders();
+                String[] usernameAndPassword = getUsernameAndPassword(httpHeaders);
+
+                context.attempted();
+
+                if (usernameAndPassword != null) {
+                    RealmModel realm = context.getRealm();
+                    UserModel user = context.getSession().users().getUserByUsername(usernameAndPassword[0], realm);
+
+                    if (user != null) {
+                        String password = usernameAndPassword[1];
+                        boolean valid = context.getSession().users().validCredentials(context.getSession(), realm, user, UserCredentialModel.password(password));
+
+                        if (valid) {
+                            context.getClientSession().setAuthenticatedUser(user);
+                            context.success();
+                        } else {
+                            context.getEvent().user(user);
+                            context.getEvent().error(Errors.INVALID_USER_CREDENTIALS);
+                            context.failure(AuthenticationFlowError.INVALID_USER, Response.status(Response.Status.UNAUTHORIZED)
+                                    .header(HttpHeaders.WWW_AUTHENTICATE, BASIC_PREFIX + "realm=\"" + realm.getName() + "\"")
+                                    .build());
+                        }
+                    }
+                }
+            }
+
+            private String[] getUsernameAndPassword(HttpHeaders httpHeaders) {
+                List<String> authHeaders = httpHeaders.getRequestHeader(HttpHeaders.AUTHORIZATION);
+
+                if (authHeaders == null || authHeaders.size() == 0) {
+                    return null;
+                }
+
+                String credentials = null;
+
+                for (String authHeader : authHeaders) {
+                    if (authHeader.startsWith(BASIC_PREFIX)) {
+                        String[] split = authHeader.trim().split("\\s+");
+
+                        if (split == null || split.length != 2) return null;
+
+                        credentials = split[1];
+                    }
+                }
+
+                try {
+                    return new String(Base64.decode(credentials)).split(":");
+                } catch (IOException e) {
+                    throw new RuntimeException("Failed to parse credentials.", e);
+                }
+            }
+
+            @Override
+            public void action(AuthenticationFlowContext context) {
+
+            }
+
+            @Override
+            public boolean requiresUser() {
+                return false;
+            }
+
+            @Override
+            public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
+                return false;
+            }
+
+            @Override
+            public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
+
+            }
+
+            @Override
+            public void close() {
+
+            }
+        };
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+
+    }
+
+    @Override
+    public void close() {
+
+    }
+
+    @Override
+    public String getId() {
+        return PROVIDER_ID;
+    }
+}
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/profile/ecp/SamlEcpProfileProtocolFactory.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/profile/ecp/SamlEcpProfileProtocolFactory.java
new file mode 100644
index 0000000..dba1b29
--- /dev/null
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/profile/ecp/SamlEcpProfileProtocolFactory.java
@@ -0,0 +1,109 @@
+package org.keycloak.protocol.saml.profile.ecp;
+
+import org.keycloak.Config;
+import org.keycloak.events.EventBuilder;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.protocol.LoginProtocol;
+import org.keycloak.protocol.saml.JaxrsSAML2BindingBuilder;
+import org.keycloak.protocol.saml.SamlProtocol;
+import org.keycloak.protocol.saml.SamlProtocolFactory;
+import org.keycloak.protocol.saml.profile.ecp.util.Soap;
+import org.keycloak.protocol.saml.profile.ecp.util.Soap.SoapMessageBuilder;
+import org.keycloak.saml.SAML2LogoutResponseBuilder;
+import org.keycloak.saml.common.constants.JBossSAMLConstants;
+import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
+import org.keycloak.saml.common.exceptions.ConfigurationException;
+import org.keycloak.saml.common.exceptions.ProcessingException;
+import org.keycloak.services.managers.AuthenticationManager;
+import org.w3c.dom.Document;
+
+import javax.ws.rs.core.Response;
+import javax.xml.soap.SOAPException;
+import javax.xml.soap.SOAPHeaderElement;
+import java.io.IOException;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class SamlEcpProfileProtocolFactory extends SamlProtocolFactory {
+
+    static final String ID = "saml-ecp-profile";
+
+    private static final String NS_PREFIX_PROFILE_ECP = "ecp";
+    private static final String NS_PREFIX_SAML_PROTOCOL = "samlp";
+    private static final String NS_PREFIX_SAML_ASSERTION = "saml";
+
+    @Override
+    public Object createProtocolEndpoint(RealmModel realm, EventBuilder event, AuthenticationManager authManager) {
+        return new SamlEcpProfileService(realm, event, authManager);
+    }
+
+    @Override
+    public LoginProtocol create(KeycloakSession session) {
+        return new SamlProtocol() {
+            // method created to send a SOAP Binding response instead of a HTTP POST response
+            @Override
+            protected Response buildAuthenticatedResponse(ClientSessionModel clientSession, String redirectUri, Document samlDocument, JaxrsSAML2BindingBuilder bindingBuilder) throws ConfigurationException, ProcessingException, IOException {
+                Document document = bindingBuilder.postBinding(samlDocument).getDocument();
+
+                try {
+                    SoapMessageBuilder messageBuilder = Soap.createMessage()
+                            .addNamespace(NS_PREFIX_SAML_ASSERTION, JBossSAMLURIConstants.ASSERTION_NSURI.get())
+                            .addNamespace(NS_PREFIX_SAML_PROTOCOL, JBossSAMLURIConstants.PROTOCOL_NSURI.get())
+                            .addNamespace(NS_PREFIX_PROFILE_ECP, JBossSAMLURIConstants.ECP_PROFILE.get());
+
+                    createEcpResponseHeader(redirectUri, messageBuilder);
+                    createRequestAuthenticatedHeader(clientSession, messageBuilder);
+
+                    messageBuilder.addToBody(document);
+
+                    return messageBuilder.build();
+                } catch (Exception e) {
+                    throw new RuntimeException("Error while creating SAML response.", e);
+                }
+            }
+
+            private void createRequestAuthenticatedHeader(ClientSessionModel clientSession, SoapMessageBuilder messageBuilder) {
+                ClientModel client = clientSession.getClient();
+
+                if ("true".equals(client.getAttribute(SamlProtocol.SAML_CLIENT_SIGNATURE_ATTRIBUTE))) {
+                    SOAPHeaderElement ecpRequestAuthenticated = messageBuilder.addHeader(JBossSAMLConstants.REQUEST_AUTHENTICATED.get(), NS_PREFIX_PROFILE_ECP);
+
+                    ecpRequestAuthenticated.setMustUnderstand(true);
+                    ecpRequestAuthenticated.setActor("http://schemas.xmlsoap.org/soap/actor/next");
+                }
+            }
+
+            private void createEcpResponseHeader(String redirectUri, SoapMessageBuilder messageBuilder) throws SOAPException {
+                SOAPHeaderElement ecpResponseHeader = messageBuilder.addHeader(JBossSAMLConstants.RESPONSE.get(), NS_PREFIX_PROFILE_ECP);
+
+                ecpResponseHeader.setMustUnderstand(true);
+                ecpResponseHeader.setActor("http://schemas.xmlsoap.org/soap/actor/next");
+                ecpResponseHeader.addAttribute(messageBuilder.createName(JBossSAMLConstants.ASSERTION_CONSUMER_SERVICE_URL.get()), redirectUri);
+            }
+
+            @Override
+            protected Response buildErrorResponse(ClientSessionModel clientSession, JaxrsSAML2BindingBuilder binding, Document document) throws ConfigurationException, ProcessingException, IOException {
+                return Soap.createMessage().addToBody(document).build();
+            }
+
+            @Override
+            protected Response buildLogoutResponse(UserSessionModel userSession, String logoutBindingUri, SAML2LogoutResponseBuilder builder, JaxrsSAML2BindingBuilder binding) throws ConfigurationException, ProcessingException, IOException {
+                return Soap.createFault().reason("Logout not supported.").build();
+            }
+        }.setSession(session);
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+    }
+
+    @Override
+    public String getId() {
+        return ID;
+    }
+}
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/profile/ecp/SamlEcpProfileService.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/profile/ecp/SamlEcpProfileService.java
new file mode 100644
index 0000000..c16b997
--- /dev/null
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/profile/ecp/SamlEcpProfileService.java
@@ -0,0 +1,70 @@
+package org.keycloak.protocol.saml.profile.ecp;
+
+import org.keycloak.dom.saml.v2.protocol.AuthnRequestType;
+import org.keycloak.events.EventBuilder;
+import org.keycloak.models.AuthenticationFlowModel;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.utils.DefaultAuthenticationFlows;
+import org.keycloak.protocol.saml.SamlProtocol;
+import org.keycloak.protocol.saml.SamlService;
+import org.keycloak.protocol.saml.profile.ecp.util.Soap;
+import org.keycloak.services.managers.AuthenticationManager;
+
+import javax.ws.rs.core.Response;
+import java.io.InputStream;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class SamlEcpProfileService extends SamlService {
+
+    public SamlEcpProfileService(RealmModel realm, EventBuilder event, AuthenticationManager authManager) {
+        super(realm, event, authManager);
+    }
+
+    public Response authenticate(InputStream inputStream) {
+        try {
+            return new PostBindingProtocol() {
+                @Override
+                protected String getBindingType(AuthnRequestType requestAbstractType) {
+                    return SamlProtocol.SAML_SOAP_BINDING;
+                }
+
+                @Override
+                protected Response loginRequest(String relayState, AuthnRequestType requestAbstractType, ClientModel client) {
+                    // force passive authentication when executing this profile
+                    requestAbstractType.setIsPassive(true);
+                    requestAbstractType.setDestination(uriInfo.getAbsolutePath());
+                    return super.loginRequest(relayState, requestAbstractType, client);
+                }
+            }.execute(Soap.toSamlHttpPostMessage(inputStream), null, null);
+        } catch (Exception e) {
+            String reason = "Some error occurred while processing the AuthnRequest.";
+            String detail = e.getMessage();
+
+            if (detail == null) {
+                detail = reason;
+            }
+
+            return Soap.createFault().reason(reason).detail(detail).build();
+        }
+    }
+
+    @Override
+    protected String getLoginProtocol() {
+        return SamlEcpProfileProtocolFactory.ID;
+    }
+
+    @Override
+    protected AuthenticationFlowModel getAuthenticationFlow() {
+        for (AuthenticationFlowModel flowModel : realm.getAuthenticationFlows()) {
+            if (flowModel.getAlias().equals(DefaultAuthenticationFlows.SAML_ECP_FLOW)) {
+                return flowModel;
+            }
+        }
+
+        throw new RuntimeException("Could not resolve authentication flow for SAML ECP Profile.");
+    }
+}
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/profile/ecp/util/Soap.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/profile/ecp/util/Soap.java
new file mode 100644
index 0000000..4bdf76a
--- /dev/null
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/profile/ecp/util/Soap.java
@@ -0,0 +1,177 @@
+package org.keycloak.protocol.saml.profile.ecp.util;
+
+import org.keycloak.saml.common.constants.JBossSAMLConstants;
+import org.keycloak.saml.processing.core.saml.v2.util.DocumentUtil;
+import org.keycloak.saml.processing.web.util.PostBindingUtil;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+import javax.xml.soap.MessageFactory;
+import javax.xml.soap.Name;
+import javax.xml.soap.SOAPBody;
+import javax.xml.soap.SOAPEnvelope;
+import javax.xml.soap.SOAPException;
+import javax.xml.soap.SOAPFault;
+import javax.xml.soap.SOAPHeaderElement;
+import javax.xml.soap.SOAPMessage;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.util.Locale;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public final class Soap {
+
+    public static SoapFaultBuilder createFault() {
+        return new SoapFaultBuilder();
+    }
+
+    public static SoapMessageBuilder createMessage() {
+        return new SoapMessageBuilder();
+    }
+
+    /**
+     * <p>Returns a string encoded accordingly with the SAML HTTP POST Binding specification based on the
+     * given <code>inputStream</code> which must contain a valid SOAP message.
+     *
+     * <p>The resulting string is based on the Body of the SOAP message, which should map to a valid SAML message.
+     *
+     * @param inputStream the input stream containing a valid SOAP message with a Body that contains a SAML message
+     *
+     * @return a string encoded accordingly with the SAML HTTP POST Binding specification
+     */
+    public static String toSamlHttpPostMessage(InputStream inputStream) {
+        try {
+            MessageFactory messageFactory = MessageFactory.newInstance();
+            SOAPMessage soapMessage = messageFactory.createMessage(null, inputStream);
+            SOAPBody soapBody = soapMessage.getSOAPBody();
+            Node authnRequestNode = soapBody.getFirstChild();
+            Document document = DocumentUtil.createDocument();
+
+            document.appendChild(document.importNode(authnRequestNode, true));
+
+            return PostBindingUtil.base64Encode(DocumentUtil.asString(document));
+        } catch (Exception e) {
+            throw new RuntimeException("Error creating fault message.", e);
+        }
+    }
+
+    public static class SoapMessageBuilder {
+        private final SOAPMessage message;
+        private final SOAPBody body;
+        private final SOAPEnvelope envelope;
+
+        private SoapMessageBuilder() {
+            try {
+                this.message = MessageFactory.newInstance().createMessage();
+                this.envelope = message.getSOAPPart().getEnvelope();
+                this.body = message.getSOAPBody();
+            } catch (Exception e) {
+                throw new RuntimeException("Error creating fault message.", e);
+            }
+        }
+
+        public SoapMessageBuilder addToBody(Document document) {
+            try {
+                this.body.addDocument(document);
+            } catch (SOAPException e) {
+                throw new RuntimeException("Could not add document to SOAP body.", e);
+            }
+            return this;
+        }
+
+        public SoapMessageBuilder addNamespace(String prefix, String ns) {
+            try {
+                envelope.addNamespaceDeclaration(prefix, ns);
+            } catch (SOAPException e) {
+                throw new RuntimeException("Could not add namespace to SOAP Envelope.", e);
+            }
+            return this;
+        }
+
+        public SOAPHeaderElement addHeader(String name, String prefix) {
+            try {
+                return this.envelope.getHeader().addHeaderElement(envelope.createQName(name, prefix));
+            } catch (SOAPException e) {
+                throw new RuntimeException("Could not add SOAP Header.", e);
+            }
+        }
+
+        public Name createName(String name) {
+            try {
+                return this.envelope.createName(name);
+            } catch (SOAPException e) {
+                throw new RuntimeException("Could not create Name.", e);
+            }
+        }
+
+        public Response build() {
+            return build(Status.OK);
+        }
+
+        Response build(Status status) {
+            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+
+            try {
+                this.message.writeTo(outputStream);
+            } catch (Exception e) {
+                throw new RuntimeException("Error while building SOAP Fault.", e);
+            }
+
+            return Response.status(status).entity(outputStream.toByteArray()).build();
+        }
+
+        SOAPMessage getMessage() {
+            return this.message;
+        }
+    }
+
+    public static class SoapFaultBuilder {
+
+        private final SOAPFault fault;
+        private final SoapMessageBuilder messageBuilder;
+
+        private SoapFaultBuilder() {
+            this.messageBuilder = createMessage();
+            try {
+                this.fault = messageBuilder.getMessage().getSOAPBody().addFault();
+            } catch (SOAPException e) {
+                throw new RuntimeException("Could not create SOAP Fault.", e);
+            }
+        }
+
+        public SoapFaultBuilder detail(String detail) {
+            try {
+                this.fault.addDetail().setValue(detail);
+            } catch (SOAPException e) {
+                throw new RuntimeException("Error creating fault message.", e);
+            }
+            return this;
+        }
+
+        public SoapFaultBuilder reason(String reason) {
+            try {
+                this.fault.setFaultString(reason);
+            } catch (SOAPException e) {
+                throw new RuntimeException("Error creating fault message.", e);
+            }
+            return this;
+        }
+
+        public SoapFaultBuilder code(String code) {
+            try {
+                this.fault.setFaultCode(code);
+            } catch (SOAPException e) {
+                throw new RuntimeException("Error creating fault message.", e);
+            }
+            return this;
+        }
+
+        public Response build() {
+            return this.messageBuilder.build(Status.INTERNAL_SERVER_ERROR);
+        }
+    }
+}
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
index 0bc3ede..07c528d 100755
--- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
@@ -84,6 +84,7 @@ public class SamlProtocol implements LoginProtocol {
     public static final String SAML_BINDING = "saml_binding";
     public static final String SAML_IDP_INITIATED_LOGIN = "saml_idp_initiated_login";
     public static final String SAML_POST_BINDING = "post";
+    public static final String SAML_SOAP_BINDING = "soap";
     public static final String SAML_REDIRECT_BINDING = "get";
     public static final String SAML_SERVER_SIGNATURE = "saml.server.signature";
     public static final String SAML_ASSERTION_SIGNATURE = "saml.assertion.signature";
@@ -165,11 +166,7 @@ public class SamlProtocol implements LoginProtocol {
                 try {
                     JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder().relayState(clientSession.getNote(GeneralConstants.RELAY_STATE));
                     Document document = builder.buildDocument();
-                    if (isPostBinding(clientSession)) {
-                        return binding.postBinding(document).response(clientSession.getRedirectUri());
-                    } else {
-                        return binding.redirectBinding(document).response(clientSession.getRedirectUri());
-                    }
+                    return buildErrorResponse(clientSession, binding, document);
                 } catch (Exception e) {
                     return ErrorPage.error(session, Messages.FAILED_TO_PROCESS_RESPONSE);
                 }
@@ -180,6 +177,14 @@ public class SamlProtocol implements LoginProtocol {
         }
     }
 
+    protected Response buildErrorResponse(ClientSessionModel clientSession, JaxrsSAML2BindingBuilder binding, Document document) throws ConfigurationException, ProcessingException, IOException {
+        if (isPostBinding(clientSession)) {
+            return binding.postBinding(document).response(clientSession.getRedirectUri());
+        } else {
+            return binding.redirectBinding(document).response(clientSession.getRedirectUri());
+        }
+    }
+
     private JBossSAMLURIConstants translateErrorToSAMLStatus(Error error) {
         switch (error) {
         case CANCELLED_BY_USER:
@@ -390,17 +395,21 @@ public class SamlProtocol implements LoginProtocol {
             bindingBuilder.encrypt(publicKey);
         }
         try {
-            if (isPostBinding(clientSession)) {
-                return bindingBuilder.postBinding(samlDocument).response(redirectUri);
-            } else {
-                return bindingBuilder.redirectBinding(samlDocument).response(redirectUri);
-            }
+            return buildAuthenticatedResponse(clientSession, redirectUri, samlDocument, bindingBuilder);
         } catch (Exception e) {
             logger.error("failed", e);
             return ErrorPage.error(session, Messages.FAILED_TO_PROCESS_RESPONSE);
         }
     }
 
+    protected Response buildAuthenticatedResponse(ClientSessionModel clientSession, String redirectUri, Document samlDocument, JaxrsSAML2BindingBuilder bindingBuilder) throws ConfigurationException, ProcessingException, IOException {
+        if (isPostBinding(clientSession)) {
+            return bindingBuilder.postBinding(samlDocument).response(redirectUri);
+        } else {
+            return bindingBuilder.redirectBinding(samlDocument).response(redirectUri);
+        }
+    }
+
     public static boolean requiresRealmSignature(ClientModel client) {
         return "true".equals(client.getAttribute(SAML_SERVER_SIGNATURE));
     }
@@ -544,11 +553,7 @@ public class SamlProtocol implements LoginProtocol {
         }
 
         try {
-            if (isLogoutPostBindingForInitiator(userSession)) {
-                return binding.postBinding(builder.buildDocument()).response(logoutBindingUri);
-            } else {
-                return binding.redirectBinding(builder.buildDocument()).response(logoutBindingUri);
-            }
+            return buildLogoutResponse(userSession, logoutBindingUri, builder, binding);
         } catch (ConfigurationException e) {
             throw new RuntimeException(e);
         } catch (ProcessingException e) {
@@ -558,6 +563,14 @@ public class SamlProtocol implements LoginProtocol {
         }
     }
 
+    protected Response buildLogoutResponse(UserSessionModel userSession, String logoutBindingUri, SAML2LogoutResponseBuilder builder, JaxrsSAML2BindingBuilder binding) throws ConfigurationException, ProcessingException, IOException {
+        if (isLogoutPostBindingForInitiator(userSession)) {
+            return binding.postBinding(builder.buildDocument()).response(logoutBindingUri);
+        } else {
+            return binding.redirectBinding(builder.buildDocument()).response(logoutBindingUri);
+        }
+    }
+
     @Override
     public void backchannelLogout(UserSessionModel userSession, ClientSessionModel clientSession) {
         ClientModel client = clientSession.getClient();
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocolFactory.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocolFactory.java
index a7a86ed..7dcc866 100755
--- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocolFactory.java
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocolFactory.java
@@ -42,7 +42,7 @@ public class SamlProtocolFactory extends AbstractLoginProtocolFactory {
 
     @Override
     public String getId() {
-        return "saml";
+        return SamlProtocol.LOGIN_PROTOCOL;
     }
 
     @Override
@@ -90,8 +90,9 @@ public class SamlProtocolFactory extends AbstractLoginProtocolFactory {
 
     @Override
     protected void addDefaults(ClientModel client) {
-        for (ProtocolMapperModel model : defaultBuiltins) client.addProtocolMapper(model);
-
+        for (ProtocolMapperModel model : defaultBuiltins) {
+            model.setProtocol(getId());
+            client.addProtocolMapper(model);
+        }
     }
-
 }
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java
index 3402593..f9aa30b 100755
--- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java
@@ -16,6 +16,7 @@ import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 
 import org.jboss.logging.Logger;
+import org.jboss.resteasy.spi.ResteasyProviderFactory;
 import org.keycloak.common.VerificationException;
 import org.keycloak.common.util.StreamUtil;
 import org.keycloak.dom.saml.v2.SAML2Object;
@@ -34,7 +35,9 @@ import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserSessionModel;
 import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.protocol.AuthorizationEndpointBase;
+import org.keycloak.protocol.LoginProtocol;
 import org.keycloak.protocol.oidc.utils.RedirectUtils;
+import org.keycloak.protocol.saml.profile.ecp.SamlEcpProfileService;
 import org.keycloak.saml.SAML2LogoutResponseBuilder;
 import org.keycloak.saml.SAMLRequestParser;
 import org.keycloak.saml.SignatureAlgorithm;
@@ -221,7 +224,7 @@ public class SamlService extends AuthorizationEndpointBase {
             }
 
             ClientSessionModel clientSession = session.sessions().createClientSession(realm, client);
-            clientSession.setAuthMethod(SamlProtocol.LOGIN_PROTOCOL);
+            clientSession.setAuthMethod(getLoginProtocol());
             clientSession.setRedirectUri(redirect);
             clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
             clientSession.setNote(ClientSessionCode.ACTION_KEY, KeycloakModelUtils.generateCodeSecret());
@@ -246,7 +249,7 @@ public class SamlService extends AuthorizationEndpointBase {
             return newBrowserAuthentication(clientSession, requestAbstractType.isIsPassive());
         }
 
-        private String getBindingType(AuthnRequestType requestAbstractType) {
+        protected String getBindingType(AuthnRequestType requestAbstractType) {
             URI requestedProtocolBinding = requestAbstractType.getProtocolBinding();
 
             if (requestedProtocolBinding != null) {
@@ -370,7 +373,7 @@ public class SamlService extends AuthorizationEndpointBase {
         }
     }
 
-    protected class PostBindingProtocol extends BindingProtocol {
+    public class PostBindingProtocol extends BindingProtocol {
 
         @Override
         protected void verifySignature(SAMLDocumentHolder documentHolder, ClientModel client) throws VerificationException {
@@ -443,7 +446,12 @@ public class SamlService extends AuthorizationEndpointBase {
     }
 
     protected Response newBrowserAuthentication(ClientSessionModel clientSession, boolean isPassive) {
-        return handleBrowserAuthenticationRequest(clientSession, new SamlProtocol().setEventBuilder(event).setHttpHeaders(headers).setRealm(realm).setSession(session).setUriInfo(uriInfo), isPassive);
+        LoginProtocol protocol = session.getProvider(LoginProtocol.class, clientSession.getAuthMethod());
+        protocol.setRealm(realm)
+                .setHttpHeaders(request.getHttpHeaders())
+                .setUriInfo(uriInfo)
+                .setEventBuilder(event);
+        return handleBrowserAuthenticationRequest(clientSession, protocol, isPassive);
     }
 
     /**
@@ -463,6 +471,16 @@ public class SamlService extends AuthorizationEndpointBase {
         return new PostBindingProtocol().execute(samlRequest, samlResponse, relayState);
     }
 
+    @POST
+    @Consumes("application/soap+xml")
+    public Response soapBinding(InputStream inputStream) {
+        SamlEcpProfileService bindingService = new SamlEcpProfileService(realm, event, authManager);
+
+        ResteasyProviderFactory.getInstance().injectProperties(bindingService);
+
+        return bindingService.authenticate(inputStream);
+    }
+
     @GET
     @Path("descriptor")
     @Produces(MediaType.APPLICATION_XML)
@@ -519,7 +537,7 @@ public class SamlService extends AuthorizationEndpointBase {
         }
 
         ClientSessionModel clientSession = session.sessions().createClientSession(realm, client);
-        clientSession.setAuthMethod(SamlProtocol.LOGIN_PROTOCOL);
+        clientSession.setAuthMethod(getLoginProtocol());
         clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
         clientSession.setNote(ClientSessionCode.ACTION_KEY, KeycloakModelUtils.generateCodeSecret());
         clientSession.setNote(SamlProtocol.SAML_BINDING, SamlProtocol.SAML_POST_BINDING);
@@ -537,4 +555,8 @@ public class SamlService extends AuthorizationEndpointBase {
 
     }
 
+    protected String getLoginProtocol() {
+        return SamlProtocol.LOGIN_PROTOCOL;
+    }
+
 }
diff --git a/saml/saml-protocol/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory b/saml/saml-protocol/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory
new file mode 100755
index 0000000..9ac8020
--- /dev/null
+++ b/saml/saml-protocol/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory
@@ -0,0 +1 @@
+org.keycloak.protocol.saml.profile.ecp.authenticator.HttpBasicAuthenticator
\ No newline at end of file
diff --git a/saml/saml-protocol/src/main/resources/META-INF/services/org.keycloak.protocol.LoginProtocolFactory b/saml/saml-protocol/src/main/resources/META-INF/services/org.keycloak.protocol.LoginProtocolFactory
index d0a2dd0..ae434f6 100755
--- a/saml/saml-protocol/src/main/resources/META-INF/services/org.keycloak.protocol.LoginProtocolFactory
+++ b/saml/saml-protocol/src/main/resources/META-INF/services/org.keycloak.protocol.LoginProtocolFactory
@@ -1 +1,2 @@
-org.keycloak.protocol.saml.SamlProtocolFactory
\ No newline at end of file
+org.keycloak.protocol.saml.SamlProtocolFactory
+org.keycloak.protocol.saml.profile.ecp.SamlEcpProfileProtocolFactory
\ No newline at end of file
diff --git a/services/src/main/java/org/keycloak/protocol/AuthorizationEndpointBase.java b/services/src/main/java/org/keycloak/protocol/AuthorizationEndpointBase.java
index a1fc4a7..9dc5548 100644
--- a/services/src/main/java/org/keycloak/protocol/AuthorizationEndpointBase.java
+++ b/services/src/main/java/org/keycloak/protocol/AuthorizationEndpointBase.java
@@ -87,7 +87,7 @@ public abstract class AuthorizationEndpointBase {
             }
         }
 
-        AuthenticationFlowModel flow = realm.getBrowserFlow();
+        AuthenticationFlowModel flow = getAuthenticationFlow();
         String flowId = flow.getId();
         AuthenticationProcessor processor = createProcessor(clientSession, flowId, LoginActionsService.AUTHENTICATE_PATH);
 
@@ -127,6 +127,10 @@ public abstract class AuthorizationEndpointBase {
         }
     }
 
+    protected AuthenticationFlowModel getAuthenticationFlow() {
+        return realm.getBrowserFlow();
+    }
+
     protected Response buildRedirectToIdentityProvider(String providerId, String accessCode) {
         logger.debug("Automatically redirect to identity provider: " + providerId);
         return Response.temporaryRedirect(
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UserFederationProviderResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UserFederationProviderResource.java
index 6f7bedf..3c4226a 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UserFederationProviderResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UserFederationProviderResource.java
@@ -32,8 +32,11 @@ import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakSessionFactory;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserFederationMapperModel;
+import org.keycloak.models.UserFederationProvider;
+import org.keycloak.models.UserFederationProviderFactory;
 import org.keycloak.models.UserFederationProviderModel;
 import org.keycloak.models.UserFederationSyncResult;
+import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.models.utils.ModelToRepresentation;
 import org.keycloak.models.utils.RepresentationToModel;
 import org.keycloak.provider.ProviderConfigProperty;
@@ -138,11 +141,13 @@ public class UserFederationProviderResource {
         auth.requireManage();
 
         UsersSyncManager syncManager = new UsersSyncManager();
-        UserFederationSyncResult syncResult = null;
+        UserFederationSyncResult syncResult;
         if ("triggerFullSync".equals(action)) {
             syncResult = syncManager.syncAllUsers(session.getKeycloakSessionFactory(), realm.getId(), this.federationProviderModel);
         } else if ("triggerChangedUsersSync".equals(action)) {
             syncResult = syncManager.syncChangedUsers(session.getKeycloakSessionFactory(), realm.getId(), this.federationProviderModel);
+        } else {
+            throw new NotFoundException("Unknown action: " + action);
         }
 
         adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
@@ -172,6 +177,7 @@ public class UserFederationProviderResource {
                 rep.setCategory(mapperFactory.getDisplayCategory());
                 rep.setName(mapperFactory.getDisplayType());
                 rep.setHelpText(mapperFactory.getHelpText());
+                rep.setSyncConfig(mapperFactory.getSyncConfig());
                 List<ProviderConfigProperty> configProperties = mapperFactory.getConfigProperties();
                 for (ProviderConfigProperty prop : configProperties) {
                     ConfigPropertyRepresentation propRep = new ConfigPropertyRepresentation();
@@ -307,6 +313,41 @@ public class UserFederationProviderResource {
 
     }
 
+    /**
+     * Trigger sync of mapper data related to federationMapper (roles, groups, ...)
+     *
+     * @return
+     */
+    @POST
+    @Path("mappers/{id}/sync")
+    @NoCache
+    public UserFederationSyncResult syncMapperData(@PathParam("id") String mapperId, @QueryParam("direction") String direction) {
+        auth.requireManage();
+
+        UserFederationMapperModel mapperModel = realm.getUserFederationMapperById(mapperId);
+        if (mapperModel == null) throw new NotFoundException("Mapper model not found");
+        UserFederationMapper mapper = session.getProvider(UserFederationMapper.class, mapperModel.getFederationMapperType());
+
+        UserFederationProviderModel providerModel = KeycloakModelUtils.findUserFederationProviderById(mapperModel.getFederationProviderId(), realm);
+        if (providerModel == null) throw new NotFoundException("Provider model not found");
+        UserFederationProviderFactory providerFactory = (UserFederationProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(UserFederationProvider.class, providerModel.getProviderName());
+        UserFederationProvider federationProvider = providerFactory.getInstance(session, providerModel);
+
+        logger.infof("Syncing data for mapper '%s' of type '%s'. Direction: %s", mapperModel.getName(), mapperModel.getFederationMapperType(), direction);
+
+        UserFederationSyncResult syncResult;
+        if ("fedToKeycloak".equals(direction)) {
+            syncResult = mapper.syncDataFromFederationProviderToKeycloak(mapperModel, federationProvider, session, realm);
+        } else if ("keycloakToFed".equals(direction)) {
+            syncResult = mapper.syncDataFromKeycloakToFederationProvider(mapperModel, federationProvider, session, realm);
+        } else {
+            throw new NotFoundException("Unknown direction: " + direction);
+        }
+
+        adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
+        return syncResult;
+    }
+
     private void validateModel(UserFederationMapperModel model) {
         try {
             UserFederationMapperFactory mapperFactory = (UserFederationMapperFactory) session.getKeycloakSessionFactory().getProviderFactory(UserFederationMapper.class, model.getFederationMapperType());
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationProvidersIntegrationTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationProvidersIntegrationTest.java
index ef1a721..7a8a01a 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationProvidersIntegrationTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationProvidersIntegrationTest.java
@@ -684,10 +684,11 @@ public class FederationProvidersIntegrationTest {
             session.users().searchForUser("user5@email.org", appRealm);
             FederationTestUtils.assertUserImported(session.userStorage(), appRealm, "username5", "John5", "Doel5", "user5@email.org", "125");
 
-            session.users().searchForUser("user6@email.org", appRealm);
+            session.users().searchForUser("John6 Doel6", appRealm);
             FederationTestUtils.assertUserImported(session.userStorage(), appRealm, "username6", "John6", "Doel6", "user6@email.org", "126");
 
             session.users().searchForUser("user7@email.org", appRealm);
+            session.users().searchForUser("John7 Doel7", appRealm);
             Assert.assertNull(session.userStorage().getUserByUsername("username7", appRealm));
 
             // Remove custom filter
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationTestUtils.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationTestUtils.java
index 63a27e6..580b345 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationTestUtils.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationTestUtils.java
@@ -141,6 +141,16 @@ class FederationTestUtils {
         }
     }
 
+    public static void syncRolesFromLDAP(RealmModel realm, LDAPFederationProvider ldapProvider, UserFederationProviderModel providerModel) {
+        RoleLDAPFederationMapper roleMapper = new RoleLDAPFederationMapper();
+
+        UserFederationMapperModel mapperModel = realm.getUserFederationMapperByName(providerModel.getId(), "realmRolesMapper");
+        roleMapper.syncDataFromFederationProviderToKeycloak(mapperModel, ldapProvider, ldapProvider.getSession(), realm);
+
+        mapperModel = realm.getUserFederationMapperByName(providerModel.getId(), "financeRolesMapper");
+        roleMapper.syncDataFromFederationProviderToKeycloak(mapperModel, ldapProvider, ldapProvider.getSession(), realm);
+    }
+
     public static void removeAllLDAPUsers(LDAPFederationProvider ldapProvider, RealmModel realm) {
         LDAPIdentityStore ldapStore = ldapProvider.getLdapIdentityStore();
         LDAPQuery ldapQuery = LDAPUtils.createQueryForUserSearch(ldapProvider, realm);
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 5bb2291..2d6eac9 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
@@ -88,6 +88,9 @@ public class LDAPRoleMappingsTest {
             FederationTestUtils.createLDAPRole(manager.getSession(), appRealm, ldapModel, "realmRolesMapper", "realmRole1");
             FederationTestUtils.createLDAPRole(manager.getSession(), appRealm, ldapModel, "realmRolesMapper", "realmRole2");
             FederationTestUtils.createLDAPRole(manager.getSession(), appRealm, ldapModel, "financeRolesMapper", "financeRole1");
+
+            // Sync LDAP roles to Keycloak DB
+            FederationTestUtils.syncRolesFromLDAP(appRealm, ldapFedProvider, ldapModel);
         }
     });
 
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/saml/SamlEcpProfileTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/saml/SamlEcpProfileTest.java
new file mode 100755
index 0000000..c02deeb
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/saml/SamlEcpProfileTest.java
@@ -0,0 +1,230 @@
+package org.keycloak.testsuite.saml;
+
+import org.jboss.resteasy.util.Base64;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.keycloak.dom.saml.v2.protocol.ResponseType;
+import org.keycloak.dom.saml.v2.protocol.StatusCodeType;
+import org.keycloak.dom.saml.v2.protocol.StatusResponseType;
+import org.keycloak.saml.common.constants.JBossSAMLConstants;
+import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
+import org.keycloak.saml.common.util.DocumentUtil;
+import org.keycloak.saml.processing.core.parsers.saml.SAMLParser;
+import org.keycloak.testsuite.samlfilter.SamlAdapterTest;
+import org.w3c.dom.Document;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.Invocation.Builder;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.NewCookie;
+import javax.ws.rs.core.Response;
+import javax.xml.namespace.QName;
+import javax.xml.soap.MessageFactory;
+import javax.xml.soap.SOAPHeader;
+import javax.xml.soap.SOAPHeaderElement;
+import javax.xml.soap.SOAPMessage;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Source;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.stream.StreamResult;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.util.Iterator;
+import java.util.Map;
+
+import static javax.ws.rs.core.Response.Status.OK;
+import static org.junit.Assert.*;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class SamlEcpProfileTest {
+
+    protected String APP_SERVER_BASE_URL = "http://localhost:8081";
+
+    @ClassRule
+    public static org.keycloak.testsuite.samlfilter.SamlKeycloakRule keycloakRule = new org.keycloak.testsuite.samlfilter.SamlKeycloakRule() {
+        @Override
+        public void initWars() {
+            ClassLoader classLoader = SamlAdapterTest.class.getClassLoader();
+
+            initializeSamlSecuredWar("/keycloak-saml/ecp/ecp-sp", "/ecp-sp",  "ecp-sp.war", classLoader);
+        }
+
+        @Override
+        public String getRealmJson() {
+            return "/keycloak-saml/ecp/testsamlecp.json";
+        }
+    };
+
+    @Test
+    public void testSuccessfulEcpFlow() throws Exception {
+        Response authnRequestResponse = ClientBuilder.newClient().target(APP_SERVER_BASE_URL + "/ecp-sp/").request()
+                .header("Accept", "text/html; application/vnd.paos+xml")
+                .header("PAOS", "ver='urn:liberty:paos:2003-08' ;'urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp'")
+                .get();
+
+        SOAPMessage authnRequestMessage = MessageFactory.newInstance().createMessage(null, new ByteArrayInputStream(authnRequestResponse.readEntity(byte[].class)));
+
+        printDocument(authnRequestMessage.getSOAPPart().getContent(), System.out);
+
+        Iterator<SOAPHeaderElement> it = authnRequestMessage.getSOAPHeader().<SOAPHeaderElement>getChildElements(new QName("urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp", "Request"));
+        SOAPHeaderElement ecpRequestHeader = it.next();
+        NodeList idpList = ecpRequestHeader.getElementsByTagNameNS("urn:oasis:names:tc:SAML:2.0:protocol", "IDPList");
+
+        assertEquals("No IDPList returned from Service Provider", 1, idpList.getLength());
+
+        NodeList idpEntries = idpList.item(0).getChildNodes();
+
+        assertEquals("No IDPEntry returned from Service Provider", 1, idpEntries.getLength());
+
+        String singleSignOnService = null;
+
+        for (int i = 0; i < idpEntries.getLength(); i++) {
+            Node item = idpEntries.item(i);
+            NamedNodeMap attributes = item.getAttributes();
+            Node location = attributes.getNamedItem("Loc");
+
+            singleSignOnService = location.getNodeValue();
+        }
+
+        assertNotNull("Could not obtain SSO Service URL", singleSignOnService);
+
+        Document authenticationRequest = authnRequestMessage.getSOAPBody().getFirstChild().getOwnerDocument();
+        String username = "pedroigor";
+        String password = "password";
+        String pair = username + ":" + password;
+        String authHeader = "Basic " + new String(Base64.encodeBytes(pair.getBytes()));
+
+        Response authenticationResponse = ClientBuilder.newClient().target(singleSignOnService).request()
+                .header(HttpHeaders.AUTHORIZATION, authHeader)
+                .post(Entity.entity(DocumentUtil.asString(authenticationRequest), "application/soap+xml"));
+
+        assertEquals(OK.getStatusCode(), authenticationResponse.getStatus());
+
+        SOAPMessage responseMessage  = MessageFactory.newInstance().createMessage(null, new ByteArrayInputStream(authenticationResponse.readEntity(byte[].class)));
+
+        printDocument(responseMessage.getSOAPPart().getContent(), System.out);
+
+        SOAPHeader responseMessageHeaders = responseMessage.getSOAPHeader();
+
+        NodeList ecpResponse = responseMessageHeaders.getElementsByTagNameNS(JBossSAMLURIConstants.ECP_PROFILE.get(), JBossSAMLConstants.RESPONSE.get());
+
+        assertEquals("No ECP Response", 1, ecpResponse.getLength());
+
+        Node samlResponse = responseMessage.getSOAPBody().getFirstChild();
+
+        assertNotNull(samlResponse);
+
+        ResponseType responseType = (ResponseType) new SAMLParser().parse(DocumentUtil.getNodeAsStream(samlResponse));
+        StatusCodeType statusCode = responseType.getStatus().getStatusCode();
+
+        assertEquals(statusCode.getValue().toString(), JBossSAMLURIConstants.STATUS_SUCCESS.get());
+        assertEquals("http://localhost:8081/ecp-sp/", responseType.getDestination());
+        assertNotNull(responseType.getSignature());
+        assertEquals(1, responseType.getAssertions().size());
+
+        SOAPMessage samlResponseRequest = MessageFactory.newInstance().createMessage();
+
+        samlResponseRequest.getSOAPBody().addDocument(responseMessage.getSOAPBody().extractContentAsDocument());
+
+        ByteArrayOutputStream os = new ByteArrayOutputStream();
+
+        samlResponseRequest.writeTo(os);
+
+        Response serviceProviderFinalResponse = ClientBuilder.newClient().target(responseType.getDestination()).request()
+                .post(Entity.entity(os.toByteArray(), "application/vnd.paos+xml"));
+
+        Map<String, NewCookie> cookies = serviceProviderFinalResponse.getCookies();
+
+        Builder resourceRequest = ClientBuilder.newClient().target(responseType.getDestination() + "/index.html").request();
+
+        for (NewCookie cookie : cookies.values()) {
+            resourceRequest.cookie(cookie);
+        }
+
+        Response resourceResponse = resourceRequest.get();
+
+        assertTrue(resourceResponse.readEntity(String.class).contains("pedroigor"));
+    }
+
+    @Test
+    public void testInvalidCredentials() throws Exception {
+        Response authnRequestResponse = ClientBuilder.newClient().target(APP_SERVER_BASE_URL + "/ecp-sp/").request()
+                .header("Accept", "text/html; application/vnd.paos+xml")
+                .header("PAOS", "ver='urn:liberty:paos:2003-08' ;'urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp'")
+                .get();
+
+        SOAPMessage authnRequestMessage = MessageFactory.newInstance().createMessage(null, new ByteArrayInputStream(authnRequestResponse.readEntity(byte[].class)));
+        Iterator<SOAPHeaderElement> it = authnRequestMessage.getSOAPHeader().<SOAPHeaderElement>getChildElements(new QName("urn:liberty:paos:2003-08", "Request"));
+
+        it.next();
+
+        it = authnRequestMessage.getSOAPHeader().<SOAPHeaderElement>getChildElements(new QName("urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp", "Request"));
+        SOAPHeaderElement ecpRequestHeader = it.next();
+        NodeList idpList = ecpRequestHeader.getElementsByTagNameNS("urn:oasis:names:tc:SAML:2.0:protocol", "IDPList");
+
+        assertEquals("No IDPList returned from Service Provider", 1, idpList.getLength());
+
+        NodeList idpEntries = idpList.item(0).getChildNodes();
+
+        assertEquals("No IDPEntry returned from Service Provider", 1, idpEntries.getLength());
+
+        String singleSignOnService = null;
+
+        for (int i = 0; i < idpEntries.getLength(); i++) {
+            Node item = idpEntries.item(i);
+            NamedNodeMap attributes = item.getAttributes();
+            Node location = attributes.getNamedItem("Loc");
+
+            singleSignOnService = location.getNodeValue();
+        }
+
+        assertNotNull("Could not obtain SSO Service URL", singleSignOnService);
+
+        Document authenticationRequest = authnRequestMessage.getSOAPBody().getFirstChild().getOwnerDocument();
+        String username = "pedroigor";
+        String password = "baspassword";
+        String pair = username + ":" + password;
+        String authHeader = "Basic " + new String(Base64.encodeBytes(pair.getBytes()));
+
+        Response authenticationResponse = ClientBuilder.newClient().target(singleSignOnService).request()
+                .header(HttpHeaders.AUTHORIZATION, authHeader)
+                .post(Entity.entity(DocumentUtil.asString(authenticationRequest), "application/soap+xml"));
+
+        assertEquals(OK.getStatusCode(), authenticationResponse.getStatus());
+
+        SOAPMessage responseMessage  = MessageFactory.newInstance().createMessage(null, new ByteArrayInputStream(authenticationResponse.readEntity(byte[].class)));
+        Node samlResponse = responseMessage.getSOAPBody().getFirstChild();
+
+        assertNotNull(samlResponse);
+
+        StatusResponseType responseType = (StatusResponseType) new SAMLParser().parse(DocumentUtil.getNodeAsStream(samlResponse));
+        StatusCodeType statusCode = responseType.getStatus().getStatusCode();
+
+        assertNotEquals(statusCode.getStatusCode().getValue().toString(), JBossSAMLURIConstants.STATUS_SUCCESS.get());
+    }
+
+    public static void printDocument(Source doc, OutputStream out) throws IOException, TransformerException {
+        TransformerFactory tf = TransformerFactory.newInstance();
+        Transformer transformer = tf.newTransformer();
+        transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
+        transformer.setOutputProperty(OutputKeys.METHOD, "xml");
+        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+        transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
+        transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
+
+        transformer.transform(doc,
+                new StreamResult(new OutputStreamWriter(out, "UTF-8")));
+    }
+}
diff --git a/testsuite/integration/src/test/resources/keycloak-saml/ecp/ecp-sp/WEB-INF/keycloak-saml.xml b/testsuite/integration/src/test/resources/keycloak-saml/ecp/ecp-sp/WEB-INF/keycloak-saml.xml
new file mode 100755
index 0000000..df39712
--- /dev/null
+++ b/testsuite/integration/src/test/resources/keycloak-saml/ecp/ecp-sp/WEB-INF/keycloak-saml.xml
@@ -0,0 +1,40 @@
+<keycloak-saml-adapter>
+    <SP entityID="http://localhost:8081/ecp-sp/"
+        sslPolicy="EXTERNAL"
+        nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
+        logoutPage="/logout.jsp"
+        forceAuthentication="false">
+        <Keys>
+            <Key signing="true" >
+                <KeyStore resource="/WEB-INF/keystore.jks" password="store123">
+                    <PrivateKey alias="http://localhost:8080/sales-post-sig/" password="test123"/>
+                    <Certificate alias="http://localhost:8080/sales-post-sig/"/>
+                </KeyStore>
+            </Key>
+        </Keys>
+        <PrincipalNameMapping policy="FROM_NAME_ID"/>
+        <RoleIdentifiers>
+            <Attribute name="Role"/>
+        </RoleIdentifiers>
+        <IDP entityID="idp"
+             signaturesRequired="true">
+        <SingleSignOnService requestBinding="POST"
+                             bindingUrl="http://localhost:8081/auth/realms/demo/protocol/saml"
+                    />
+
+            <SingleLogoutService
+                    requestBinding="POST"
+                    responseBinding="POST"
+                    postBindingUrl="http://localhost:8081/auth/realms/demo/protocol/saml"
+                    redirectBindingUrl="http://localhost:8081/auth/realms/demo/protocol/saml"
+                    />
+            <Keys>
+                <Key signing="true">
+                    <KeyStore resource="/WEB-INF/keystore.jks" password="store123">
+                        <Certificate alias="demo"/>
+                    </KeyStore>
+                </Key>
+            </Keys>
+        </IDP>
+     </SP>
+</keycloak-saml-adapter>
\ No newline at end of file
diff --git a/testsuite/integration/src/test/resources/keycloak-saml/ecp/ecp-sp/WEB-INF/keystore.jks b/testsuite/integration/src/test/resources/keycloak-saml/ecp/ecp-sp/WEB-INF/keystore.jks
new file mode 100755
index 0000000..144830b
Binary files /dev/null and b/testsuite/integration/src/test/resources/keycloak-saml/ecp/ecp-sp/WEB-INF/keystore.jks differ
diff --git a/testsuite/integration/src/test/resources/keycloak-saml/ecp/testsamlecp.json b/testsuite/integration/src/test/resources/keycloak-saml/ecp/testsamlecp.json
new file mode 100755
index 0000000..981cbda
--- /dev/null
+++ b/testsuite/integration/src/test/resources/keycloak-saml/ecp/testsamlecp.json
@@ -0,0 +1,67 @@
+{
+    "id": "demo",
+    "realm": "demo",
+    "enabled": true,
+    "sslRequired": "external",
+    "registrationAllowed": true,
+    "resetPasswordAllowed": true,
+    "privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
+    "publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
+    "requiredCredentials": [ "password" ],
+    "defaultRoles": [ "user" ],
+    "smtpServer": {
+        "from": "auto@keycloak.org",
+        "host": "localhost",
+        "port":"3025"
+    },
+    "users" : [
+        {
+            "username" : "pedroigor",
+            "enabled": true,
+            "email" : "psilva@redhat.com",
+            "credentials" : [
+                { "type" : "password",
+                    "value" : "password" }
+            ],
+            "attributes" : {
+                "phone": "617"
+            },
+            "realmRoles": ["manager", "user"]
+        }
+    ],
+    "applications": [
+        {
+            "name": "http://localhost:8081/ecp-sp/",
+            "enabled": true,
+            "protocol": "saml",
+            "fullScopeAllowed": true,
+            "baseUrl": "http://localhost:8081/ecp-sp",
+            "redirectUris": [
+                "http://localhost:8081/ecp-sp/*"
+            ],
+            "attributes": {
+                "saml_assertion_consumer_url_post": "http://localhost:8081/ecp-sp/",
+                "saml_assertion_consumer_url_redirect": "http://localhost:8081/ecp-sp/",
+                "saml_single_logout_service_url_post": "http://localhost:8081/ecp-sp/",
+                "saml_single_logout_service_url_redirect": "http://localhost:8081/ecp-sp/",
+                "saml.server.signature": "true",
+                "saml.signature.algorithm": "RSA_SHA256",
+                "saml.client.signature": "true",
+                "saml.authnstatement": "true",
+                "saml.signing.certificate": "MIIB1DCCAT0CBgFJGP5dZDANBgkqhkiG9w0BAQsFADAwMS4wLAYDVQQDEyVodHRwOi8vbG9jYWxob3N0OjgwODAvc2FsZXMtcG9zdC1zaWcvMB4XDTE0MTAxNjEyNDQyM1oXDTI0MTAxNjEyNDYwM1owMDEuMCwGA1UEAxMlaHR0cDovL2xvY2FsaG9zdDo4MDgwL3NhbGVzLXBvc3Qtc2lnLzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1RvGu8RjemSJA23nnMksoHA37MqY1DDTxOECY4rPAd9egr7GUNIXE0y1MokaR5R2crNpN8RIRwR8phQtQDjXL82c6W+NLQISxztarQJ7rdNJIYwHY0d5ri1XRpDP8zAuxubPYiMAVYcDkIcvlbBpwh/dRM5I2eElRK+eSiaMkCUCAwEAATANBgkqhkiG9w0BAQsFAAOBgQCLms6htnPaY69k1ntm9a5jgwSn/K61cdai8R8B0ccY7zvinn9AfRD7fiROQpFyY29wKn8WCLrJ86NBXfgFUGyR5nLNHVy3FghE36N2oHy53uichieMxffE6vhkKJ4P8ChfJMMOZlmCPsQPDvjoAghHt4mriFiQgRdPgIy/zDjSNw=="
+            }
+        }
+    ],
+    "roles" : {
+        "realm" : [
+            {
+                "name": "manager",
+                "description": "Have Manager privileges"
+            },
+            {
+                "name": "user",
+                "description": "Have User privileges"
+            }
+        ]
+    }
+}
diff --git a/testsuite/integration-arquillian/servers/wildfly/src/main/xslt/datasource.xsl b/testsuite/integration-arquillian/servers/wildfly/src/main/xslt/datasource.xsl
index c06899f..0c6b3e2 100644
--- a/testsuite/integration-arquillian/servers/wildfly/src/main/xslt/datasource.xsl
+++ b/testsuite/integration-arquillian/servers/wildfly/src/main/xslt/datasource.xsl
@@ -1,7 +1,7 @@
 <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                 xmlns:xalan="http://xml.apache.org/xalan"
-                xmlns:j="urn:jboss:domain:3.0"
-                xmlns:ds="urn:jboss:domain:datasources:3.0"
+                xmlns:j="urn:jboss:domain:4.0"
+                xmlns:ds="urn:jboss:domain:datasources:4.0"
                 xmlns:k="urn:jboss:domain:keycloak:1.1"
                 xmlns:sec="urn:jboss:domain:security:1.2"
                 version="2.0"
diff --git a/testsuite/integration-arquillian/servers/wildfly/src/main/xslt/security.xsl b/testsuite/integration-arquillian/servers/wildfly/src/main/xslt/security.xsl
index 2960ebd..2d4d7a5 100644
--- a/testsuite/integration-arquillian/servers/wildfly/src/main/xslt/security.xsl
+++ b/testsuite/integration-arquillian/servers/wildfly/src/main/xslt/security.xsl
@@ -1,10 +1,10 @@
 <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                 xmlns:xalan="http://xml.apache.org/xalan"
-                xmlns:j="urn:jboss:domain:3.0"
-                xmlns:ds="urn:jboss:domain:datasources:3.0"
+                xmlns:j="urn:jboss:domain:4.0"
+                xmlns:ds="urn:jboss:domain:datasources:4.0"
                 xmlns:k="urn:jboss:domain:keycloak:1.1"
                 xmlns:sec="urn:jboss:domain:security:1.2"
-                xmlns:u="urn:jboss:domain:undertow:2.0"
+                xmlns:u="urn:jboss:domain:undertow:3.0"
                 version="2.0"
                 exclude-result-prefixes="xalan j ds k sec">
 
diff --git a/testsuite/integration-arquillian/servers/wildfly/src/main/xslt/standalone.xsl b/testsuite/integration-arquillian/servers/wildfly/src/main/xslt/standalone.xsl
index 9239d67..f711ed9 100644
--- a/testsuite/integration-arquillian/servers/wildfly/src/main/xslt/standalone.xsl
+++ b/testsuite/integration-arquillian/servers/wildfly/src/main/xslt/standalone.xsl
@@ -1,7 +1,7 @@
 <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                 xmlns:xalan="http://xml.apache.org/xalan"
-                xmlns:j="urn:jboss:domain:3.0"
-                xmlns:ds="urn:jboss:domain:datasources:3.0"
+                xmlns:j="urn:jboss:domain:4.0"
+                xmlns:ds="urn:jboss:domain:datasources:4.0"
                 xmlns:k="urn:jboss:domain:keycloak:1.1"
                 xmlns:sec="urn:jboss:domain:security:1.2"
                 version="2.0"
diff --git a/testsuite/integration-arquillian/tests/adapters/wildfly/src/main/xslt/security.xsl b/testsuite/integration-arquillian/tests/adapters/wildfly/src/main/xslt/security.xsl
index a3880c2..2bf3924 100644
--- a/testsuite/integration-arquillian/tests/adapters/wildfly/src/main/xslt/security.xsl
+++ b/testsuite/integration-arquillian/tests/adapters/wildfly/src/main/xslt/security.xsl
@@ -1,10 +1,10 @@
 <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                 xmlns:xalan="http://xml.apache.org/xalan"
-                xmlns:j="urn:jboss:domain:3.0"
-                xmlns:ds="urn:jboss:domain:datasources:3.0"
+                xmlns:j="urn:jboss:domain:4.0"
+                xmlns:ds="urn:jboss:domain:datasources:4.0"
                 xmlns:k="urn:jboss:domain:keycloak:1.1"
                 xmlns:sec="urn:jboss:domain:security:1.2"
-                xmlns:u="urn:jboss:domain:undertow:2.0"
+                xmlns:u="urn:jboss:domain:undertow:3.0"
                 version="2.0"
                 exclude-result-prefixes="xalan j ds k sec">
 
diff --git a/testsuite/integration-arquillian/tests/adapters/wildfly/src/main/xslt/standalone.xsl b/testsuite/integration-arquillian/tests/adapters/wildfly/src/main/xslt/standalone.xsl
index a483717..c9d71ed 100644
--- a/testsuite/integration-arquillian/tests/adapters/wildfly/src/main/xslt/standalone.xsl
+++ b/testsuite/integration-arquillian/tests/adapters/wildfly/src/main/xslt/standalone.xsl
@@ -1,7 +1,7 @@
 <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                 xmlns:xalan="http://xml.apache.org/xalan"
-                xmlns:j="urn:jboss:domain:3.0"
-                xmlns:ds="urn:jboss:domain:datasources:3.0"
+                xmlns:j="urn:jboss:domain:4.0"
+                xmlns:ds="urn:jboss:domain:datasources:4.0"
                 xmlns:k="urn:jboss:domain:keycloak:1.1"
                 xmlns:sec="urn:jboss:domain:security:1.2"
                 version="2.0"