keycloak-aplcache

Merge pull request #1032 from patriot1burke/master mapper

3/9/2015 10:33:38 PM

Changes

Details

diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/AttributeStatementHelper.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/AttributeStatementHelper.java
index b5850a5..06b299a 100755
--- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/AttributeStatementHelper.java
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/AttributeStatementHelper.java
@@ -73,14 +73,14 @@ public class AttributeStatementHelper {
 
     }
     public static ProtocolMapperModel createAttributeMapper(String name, String userAttribute, String samlAttributeName, String nameFormat,  String friendlyName, boolean consentRequired, String consentText, String mapperId) {
-        ProtocolMapperModel mapper = mapper = new ProtocolMapperModel();
+        ProtocolMapperModel mapper = new ProtocolMapperModel();
         mapper.setName(name);
         mapper.setProtocolMapper(mapperId);
         mapper.setProtocol(SamlProtocol.LOGIN_PROTOCOL);
         mapper.setConsentRequired(consentRequired);
         mapper.setConsentText(consentText);
         Map<String, String> config = new HashMap<String, String>();
-        config.put(ProtocolMapperUtils.USER_ATTRIBUTE, userAttribute);
+        if (userAttribute != null) config.put(ProtocolMapperUtils.USER_ATTRIBUTE, userAttribute);
         config.put(SAML_ATTRIBUTE_NAME, samlAttributeName);
         if (friendlyName != null) {
             config.put(FRIENDLY_NAME, friendlyName);
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/HardcodedAttributeMapper.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/HardcodedAttributeMapper.java
new file mode 100755
index 0000000..221bcfd
--- /dev/null
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/HardcodedAttributeMapper.java
@@ -0,0 +1,85 @@
+package org.keycloak.protocol.saml.mappers;
+
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ProtocolMapperModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.protocol.ProtocolMapperUtils;
+import org.picketlink.identity.federation.saml.v2.assertion.AttributeStatementType;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Mappings UserModel property (the property name of a getter method) to an AttributeStatement.
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class HardcodedAttributeMapper extends AbstractSAMLProtocolMapper implements SAMLAttributeStatementMapper {
+    public static final String PROVIDER_ID = "saml-hardcode-attribute-mapper";
+    public static final String ATTRIBUTE_VALUE = "attribute.value";
+    private static final List<ConfigProperty> configProperties = new ArrayList<ConfigProperty>();
+
+    static {
+        ConfigProperty property;
+        property = new ConfigProperty();
+        property.setName(ProtocolMapperUtils.USER_ATTRIBUTE);
+        property.setLabel(ProtocolMapperUtils.USER_MODEL_ATTRIBUTE_LABEL);
+        property.setHelpText(ProtocolMapperUtils.USER_MODEL_ATTRIBUTE_HELP_TEXT);
+        configProperties.add(property);
+        AttributeStatementHelper.setConfigProperties(configProperties);
+        property = new ConfigProperty();
+        property.setName(ATTRIBUTE_VALUE);
+        property.setLabel("Attribute value");
+        property.setType(ConfigProperty.STRING_TYPE);
+        property.setHelpText("Value of the attribute you want to hard code.");
+        configProperties.add(property);
+
+    }
+
+
+
+    public List<ConfigProperty> getConfigProperties() {
+        return configProperties;
+    }
+    @Override
+    public String getId() {
+        return PROVIDER_ID;
+    }
+
+    @Override
+    public String getDisplayType() {
+        return "Hardcoded attribute";
+    }
+
+    @Override
+    public String getDisplayCategory() {
+        return AttributeStatementHelper.ATTRIBUTE_STATEMENT_CATEGORY;
+    }
+
+    @Override
+    public String getHelpText() {
+        return "Hardcode an attribute into the SAML Assertion.";
+    }
+
+    @Override
+    public void transformAttributeStatement(AttributeStatementType attributeStatement, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) {
+        String attributeValue = mappingModel.getConfig().get(ATTRIBUTE_VALUE);
+        AttributeStatementHelper.addAttribute(attributeStatement, mappingModel, attributeValue);
+
+    }
+
+    public static ProtocolMapperModel create(String name,
+                                             String samlAttributeName, String nameFormat, String friendlyName, String value,
+                                             boolean consentRequired, String consentText) {
+        String mapperId = PROVIDER_ID;
+        ProtocolMapperModel model = AttributeStatementHelper.createAttributeMapper(name, null, samlAttributeName, nameFormat, friendlyName,
+                consentRequired, consentText, mapperId);
+        model.getConfig().put(ATTRIBUTE_VALUE, value);
+        return model;
+
+    }
+
+}
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/HardcodedRole.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/HardcodedRole.java
new file mode 100755
index 0000000..862a0ca
--- /dev/null
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/HardcodedRole.java
@@ -0,0 +1,76 @@
+package org.keycloak.protocol.saml.mappers;
+
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ProtocolMapperModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.protocol.ProtocolMapperUtils;
+import org.keycloak.protocol.saml.SamlProtocol;
+import org.picketlink.identity.federation.saml.v2.assertion.AttributeStatementType;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Mappings UserModel property (the property name of a getter method) to an AttributeStatement.
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class HardcodedRole extends AbstractSAMLProtocolMapper {
+    public static final String PROVIDER_ID = "saml-hardcode-role-mapper";
+    public static final String ATTRIBUTE_VALUE = "attribute.value";
+    private static final List<ConfigProperty> configProperties = new ArrayList<ConfigProperty>();
+
+    static {
+        ConfigProperty property;
+        property = new ConfigProperty();
+        property.setName("role");
+        property.setLabel("Role");
+        property.setHelpText("Role name you want to hardcode.");
+        property.setType(ConfigProperty.STRING_TYPE);
+        configProperties.add(property);
+    }
+
+
+
+    public List<ConfigProperty> getConfigProperties() {
+        return configProperties;
+    }
+    @Override
+    public String getId() {
+        return PROVIDER_ID;
+    }
+
+    @Override
+    public String getDisplayType() {
+        return "Hardcoded role";
+    }
+
+    @Override
+    public String getDisplayCategory() {
+        return AttributeStatementHelper.ATTRIBUTE_STATEMENT_CATEGORY;
+    }
+
+    @Override
+    public String getHelpText() {
+        return "Hardcode role into SAML Assertion.";
+    }
+
+    public static ProtocolMapperModel create(String name,
+                                             String role) {
+        String mapperId = PROVIDER_ID;
+        ProtocolMapperModel mapper = new ProtocolMapperModel();
+        mapper.setName(name);
+        mapper.setProtocolMapper(mapperId);
+        mapper.setProtocol(SamlProtocol.LOGIN_PROTOCOL);
+       Map<String, String> config = new HashMap<String, String>();
+        config.put("role", role);
+        mapper.setConfig(config);
+        return mapper;
+
+    }
+
+}
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 83b4b1a..8ef3c80 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
@@ -9,7 +9,7 @@ import org.keycloak.models.RealmModel;
 import org.keycloak.protocol.AbstractLoginProtocolFactory;
 import org.keycloak.protocol.LoginProtocol;
 import org.keycloak.protocol.saml.mappers.AttributeStatementHelper;
-import org.keycloak.protocol.saml.mappers.SAMLBasicRoleListMapper;
+import org.keycloak.protocol.saml.mappers.RoleListMapper;
 import org.keycloak.protocol.saml.mappers.UserPropertyAttributeStatementMapper;
 import org.keycloak.services.managers.AuthenticationManager;
 import org.picketlink.common.constants.JBossSAMLURIConstants;
@@ -77,7 +77,7 @@ public class SamlProtocolFactory extends AbstractLoginProtocolFactory {
                 X500SAMLProfileConstants.SURNAME.getFriendlyName(),
                 true, "family name");
         builtins.add(model);
-        model = SAMLBasicRoleListMapper.create("role list", "Role", AttributeStatementHelper.BASIC, null, false);
+        model = RoleListMapper.create("role list", "Role", AttributeStatementHelper.BASIC, null, false);
         builtins.add(model);
         defaultBuiltins.add(model);
 
diff --git a/saml/saml-protocol/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper b/saml/saml-protocol/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper
index 62f8079..392feae 100755
--- a/saml/saml-protocol/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper
+++ b/saml/saml-protocol/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper
@@ -1,5 +1,7 @@
-org.keycloak.protocol.saml.mappers.SAMLBasicRoleListMapper
-org.keycloak.protocol.saml.mappers.SAMLBasicRoleNameMapper
+org.keycloak.protocol.saml.mappers.RoleListMapper
+org.keycloak.protocol.saml.mappers.RoleNameMapper
+org.keycloak.protocol.saml.mappers.HardcodedRole
+org.keycloak.protocol.saml.mappers.HardcodedAttributeMapper
 org.keycloak.protocol.saml.mappers.UserAttributeStatementMapper
 org.keycloak.protocol.saml.mappers.UserPropertyAttributeStatementMapper
 
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolFactory.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolFactory.java
index 1c27033..120446f 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolFactory.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolFactory.java
@@ -8,11 +8,11 @@ import org.keycloak.models.ProtocolMapperModel;
 import org.keycloak.models.RealmModel;
 import org.keycloak.protocol.AbstractLoginProtocolFactory;
 import org.keycloak.protocol.LoginProtocol;
-import org.keycloak.protocol.oidc.mappers.OIDCAddressMapper;
+import org.keycloak.protocol.oidc.mappers.AddressMapper;
+import org.keycloak.protocol.oidc.mappers.FullNameMapper;
 import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper;
-import org.keycloak.protocol.oidc.mappers.OIDCFullNameMapper;
-import org.keycloak.protocol.oidc.mappers.OIDCUserModelMapper;
-import org.keycloak.protocol.oidc.mappers.OIDCUserSessionNoteMapper;
+import org.keycloak.protocol.oidc.mappers.UserPropertyMapper;
+import org.keycloak.protocol.oidc.mappers.UserSessionNoteMapper;
 import org.keycloak.services.managers.AuthenticationManager;
 
 import java.util.ArrayList;
@@ -41,35 +41,35 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory {
     static {
 
         ProtocolMapperModel model;
-        model = OIDCUserModelMapper.createClaimMapper("username",
+        model = UserPropertyMapper.createClaimMapper("username",
                 "username",
                 "preferred_username", "String",
                 true, "username",
                 true, true);
         builtins.add(model);
         defaultBuiltins.add(model);
-        model = OIDCUserModelMapper.createClaimMapper("email",
+        model = UserPropertyMapper.createClaimMapper("email",
                 "email",
                 "email", "String",
                 true, "email",
                 true, true);
         builtins.add(model);
         defaultBuiltins.add(model);
-        model = OIDCUserModelMapper.createClaimMapper("given name",
+        model = UserPropertyMapper.createClaimMapper("given name",
                 "firstName",
                 "given_name", "String",
                 true, "given name",
                 true, true);
         builtins.add(model);
         defaultBuiltins.add(model);
-        model = OIDCUserModelMapper.createClaimMapper("family name",
+        model = UserPropertyMapper.createClaimMapper("family name",
                 "lastName",
                 "family_name", "String",
                 true, "family name",
                 true, true);
         builtins.add(model);
         defaultBuiltins.add(model);
-        model = OIDCUserModelMapper.createClaimMapper("email verified",
+        model = UserPropertyMapper.createClaimMapper("email verified",
                 "emailVerified",
                 "email_verified", "boolean",
                 false, null,
@@ -78,7 +78,7 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory {
 
         ProtocolMapperModel fullName = new ProtocolMapperModel();
         fullName.setName("full name");
-        fullName.setProtocolMapper(OIDCFullNameMapper.PROVIDER_ID);
+        fullName.setProtocolMapper(FullNameMapper.PROVIDER_ID);
         fullName.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
         fullName.setConsentRequired(true);
         fullName.setConsentText("full name");
@@ -89,10 +89,10 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory {
         builtins.add(fullName);
         defaultBuiltins.add(fullName);
 
-        ProtocolMapperModel address = OIDCAddressMapper.createAddressMapper();
+        ProtocolMapperModel address = AddressMapper.createAddressMapper();
         builtins.add(address);
 
-        model = OIDCUserSessionNoteMapper.createClaimMapper(KerberosConstants.GSS_DELEGATION_CREDENTIAL_DISPLAY_NAME,
+        model = UserSessionNoteMapper.createClaimMapper(KerberosConstants.GSS_DELEGATION_CREDENTIAL_DISPLAY_NAME,
                 KerberosConstants.GSS_DELEGATION_CREDENTIAL,
                 KerberosConstants.GSS_DELEGATION_CREDENTIAL, "String",
                 true, KerberosConstants.GSS_DELEGATION_CREDENTIAL_DISPLAY_NAME,
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolService.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolService.java
index 5bcc031..3de9a87 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolService.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolService.java
@@ -30,6 +30,7 @@ import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.OAuthClientModel;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RequiredCredentialModel;
+import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserSessionModel;
 import org.keycloak.models.UserSessionProvider;
@@ -372,85 +373,22 @@ public class OIDCLoginProtocolService {
             Map<String, String> err = new HashMap<String, String>();
             err.put(OAuth2Constants.ERROR, OAuthErrorException.INVALID_GRANT);
             err.put(OAuth2Constants.ERROR_DESCRIPTION, "Token invalid");
+            logger.error("Invalid token. Token verification failed.");
             event.error(Errors.INVALID_TOKEN);
             return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(err)
                     .build();
         }
         event.user(token.getSubject()).session(token.getSessionState()).detail(Details.VALIDATE_ACCESS_TOKEN, token.getId());
 
-        if (token.isExpired()
-                || token.getIssuedAt() < realm.getNotBefore()
-                ) {
-            Map<String, String> err = new HashMap<String, String>();
-            err.put(OAuth2Constants.ERROR, OAuthErrorException.INVALID_GRANT);
-            err.put(OAuth2Constants.ERROR_DESCRIPTION, "Token expired");
-            event.error(Errors.INVALID_TOKEN);
-            return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(err)
-                    .build();
-        }
-
-
-        UserModel user = session.users().getUserById(token.getSubject(), realm);
-        if (user == null) {
-            Map<String, String> err = new HashMap<String, String>();
-            err.put(OAuth2Constants.ERROR, OAuthErrorException.INVALID_GRANT);
-            err.put(OAuth2Constants.ERROR_DESCRIPTION, "User does not exist");
-            event.error(Errors.USER_NOT_FOUND);
-            return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(err)
-                    .build();
-        }
-
-        if (!user.isEnabled()) {
-            Map<String, String> err = new HashMap<String, String>();
-            err.put(OAuth2Constants.ERROR, OAuthErrorException.INVALID_GRANT);
-            err.put(OAuth2Constants.ERROR_DESCRIPTION, "User disabled");
-            event.error(Errors.USER_DISABLED);
-            return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(err)
-                    .build();
-        }
-
-        UserSessionModel userSession = session.sessions().getUserSession(realm, token.getSessionState());
-        if (!AuthenticationManager.isSessionValid(realm, userSession)) {
-            Map<String, String> err = new HashMap<String, String>();
-            err.put(OAuth2Constants.ERROR, OAuthErrorException.INVALID_GRANT);
-            err.put(OAuth2Constants.ERROR_DESCRIPTION, "Expired session");
-            event.error(Errors.USER_SESSION_NOT_FOUND);
-            return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(err)
-                    .build();
-        }
-
-        ClientModel client = realm.findClient(token.getIssuedFor());
-        if (client == null) {
-            Map<String, String> err = new HashMap<String, String>();
-            err.put(OAuth2Constants.ERROR, OAuthErrorException.INVALID_CLIENT);
-            err.put(OAuth2Constants.ERROR_DESCRIPTION, "Issued for client no longer exists");
-            event.error(Errors.CLIENT_NOT_FOUND);
-            return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(err)
-                    .build();
-
-        }
-
-        if (token.getIssuedAt() < client.getNotBefore()) {
-            Map<String, String> err = new HashMap<String, String>();
-            err.put(OAuth2Constants.ERROR, OAuthErrorException.INVALID_CLIENT);
-            err.put(OAuth2Constants.ERROR_DESCRIPTION, "Issued for client no longer exists");
-            event.error(Errors.INVALID_TOKEN);
-            return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(err)
-                    .build();
-        }
-
         try {
-            tokenManager.verifyAccess(token, realm, client, user);
+            tokenManager.validateToken(session, uriInfo, clientConnection, realm, token);
         } catch (OAuthErrorException e) {
-            Map<String, String> err = new HashMap<String, String>();
-            err.put(OAuth2Constants.ERROR, OAuthErrorException.INVALID_SCOPE);
-            err.put(OAuth2Constants.ERROR_DESCRIPTION, "Role mappings have changed");
+            Map<String, String> error = new HashMap<String, String>();
+            error.put(OAuth2Constants.ERROR, e.getError());
+            if (e.getDescription() != null) error.put(OAuth2Constants.ERROR_DESCRIPTION, e.getDescription());
             event.error(Errors.INVALID_TOKEN);
-            return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(err)
-                    .build();
-
+            return Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build();
         }
-
         event.success();
 
         return Response.ok(token, MediaType.APPLICATION_JSON_TYPE).build();
@@ -666,16 +604,6 @@ public class OIDCLoginProtocolService {
 
         AccessToken token = tokenManager.createClientAccessToken(session, accessCode.getRequestedRoles(), realm, client, user, userSession, clientSession);
 
-        try {
-            tokenManager.verifyAccess(token, realm, client, user);
-        } catch (OAuthErrorException e) {
-            Map<String, String> error = new HashMap<String, String>();
-            error.put(OAuth2Constants.ERROR, e.getError());
-            if (e.getDescription() != null) error.put(OAuth2Constants.ERROR_DESCRIPTION, e.getDescription());
-            event.error(Errors.INVALID_CODE);
-            return Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build();
-        }
-
         AccessTokenResponse res = tokenManager.responseBuilder(realm, client, event, session, userSession, clientSession)
                 .accessToken(token)
                 .generateIDToken()
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
index 14fb6ec..07fd828 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
@@ -59,12 +59,22 @@ public class TokenManager {
         }
     }
 
-    public AccessTokenResponse refreshAccessToken(KeycloakSession session, UriInfo uriInfo, ClientConnection connection, RealmModel realm, ClientModel client, String encodedRefreshToken, EventBuilder event) throws OAuthErrorException {
-        RefreshToken refreshToken = verifyRefreshToken(realm, encodedRefreshToken);
-
-        event.user(refreshToken.getSubject()).session(refreshToken.getSessionState()).detail(Details.REFRESH_TOKEN_ID, refreshToken.getId());
+    public static class TokenValidation {
+        public final UserModel user;
+        public final UserSessionModel userSession;
+        public final ClientSessionModel clientSession;
+        public final AccessToken newToken;
+
+        public TokenValidation(UserModel user, UserSessionModel userSession, ClientSessionModel clientSession, AccessToken newToken) {
+            this.user = user;
+            this.userSession = userSession;
+            this.clientSession = clientSession;
+            this.newToken = newToken;
+        }
+    }
 
-        UserModel user = session.users().getUserById(refreshToken.getSubject(), realm);
+    public TokenValidation validateToken(KeycloakSession session, UriInfo uriInfo, ClientConnection connection, RealmModel realm, AccessToken oldToken) throws OAuthErrorException {
+        UserModel user = session.users().getUserById(oldToken.getSubject(), realm);
         if (user == null) {
             throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid refresh token", "Unknown user");
         }
@@ -73,24 +83,14 @@ public class TokenManager {
             throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "User disabled", "User disabled");
         }
 
-        UserSessionModel userSession = session.sessions().getUserSession(realm, refreshToken.getSessionState());
-        int currentTime = Time.currentTime();
+        UserSessionModel userSession = session.sessions().getUserSession(realm, oldToken.getSessionState());
         if (!AuthenticationManager.isSessionValid(realm, userSession)) {
             AuthenticationManager.logout(session, realm, userSession, uriInfo, connection);
             throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Session not active", "Session not active");
         }
-
-        if (!client.getClientId().equals(refreshToken.getIssuedFor())) {
-            throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Unmatching clients", "Unmatching clients");
-        }
-
-        if (refreshToken.getIssuedAt() < client.getNotBefore()) {
-            throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Stale refresh token");
-        }
-
         ClientSessionModel clientSession = null;
         for (ClientSessionModel clientSessionModel : userSession.getClientSessions()) {
-            if (clientSessionModel.getId().equals(refreshToken.getClientSession())) {
+            if (clientSessionModel.getId().equals(oldToken.getClientSession())) {
                 clientSession = clientSessionModel;
                 break;
             }
@@ -98,20 +98,48 @@ public class TokenManager {
 
         if (clientSession == null) {
             throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Client session not active", "Client session not active");
+        }
+
+        ClientModel client = clientSession.getClient();
+
+        if (!client.getClientId().equals(oldToken.getIssuedFor())) {
+            throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Unmatching clients", "Unmatching clients");
+        }
 
+        if (oldToken.getIssuedAt() < client.getNotBefore()) {
+            throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Stale token");
         }
+        if (oldToken.getIssuedAt() < realm.getNotBefore()) {
+            throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Stale token");
+        }
+
+
+        // recreate token.
+        Set<RoleModel> requestedRoles = TokenManager.getAccess(null, clientSession.getClient(), user);
+        AccessToken newToken = createClientAccessToken(session, requestedRoles, realm, client, user, userSession, clientSession);
+        verifyAccess(oldToken, newToken);
+
+        return new TokenValidation(user, userSession, clientSession, newToken);
+
 
-        verifyAccess(refreshToken, realm, client, user);
+    }
+
+    public AccessTokenResponse refreshAccessToken(KeycloakSession session, UriInfo uriInfo, ClientConnection connection, RealmModel realm, ClientModel authorizedClient, String encodedRefreshToken, EventBuilder event) throws OAuthErrorException {
+        RefreshToken refreshToken = verifyRefreshToken(realm, encodedRefreshToken);
+
+        event.user(refreshToken.getSubject()).session(refreshToken.getSessionState()).detail(Details.REFRESH_TOKEN_ID, refreshToken.getId());
 
-        AccessToken accessToken = initToken(realm, client, user, userSession, clientSession);
-        accessToken.setRealmAccess(refreshToken.getRealmAccess());
-        accessToken.setResourceAccess(refreshToken.getResourceAccess());
-        accessToken = transformAccessToken(session, accessToken, realm, client, user, userSession, clientSession);
+        TokenValidation validation = validateToken(session, uriInfo, connection, realm, refreshToken);
+        // validate authorizedClient is same as validated client
+        if (!validation.clientSession.getClient().getId().equals(authorizedClient.getId())) {
+            throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid refresh token. Token client and authorized client don't match");
+        }
 
-        userSession.setLastSessionRefresh(currentTime);
+        int currentTime = Time.currentTime();
+        validation.userSession.setLastSessionRefresh(currentTime);
 
-        AccessTokenResponse res = responseBuilder(realm, client, event, session, userSession, clientSession)
-                .accessToken(accessToken)
+        AccessTokenResponse res = responseBuilder(realm, authorizedClient, event, session, validation.userSession, validation.clientSession)
+                .accessToken(validation.newToken)
                 .generateIDToken()
                 .generateRefreshToken().build();
         return res;
@@ -198,41 +226,27 @@ public class TokenManager {
         return requestedRoles;
     }
 
-    public void verifyAccess(AccessToken token, RealmModel realm, ClientModel client, UserModel user) throws OAuthErrorException {
-        ApplicationModel clientApp = (client instanceof ApplicationModel) ? (ApplicationModel)client : null;
-
-
+    public void verifyAccess(AccessToken token, AccessToken newToken) throws OAuthErrorException {
         if (token.getRealmAccess() != null) {
+            if (newToken.getRealmAccess() == null) throw new OAuthErrorException(OAuthErrorException.INVALID_SCOPE, "User no long has permission for realm roles");
+
             for (String roleName : token.getRealmAccess().getRoles()) {
-                RoleModel role = realm.getRole(roleName);
-                if (role == null) {
-                    throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid realm role " + roleName);
-                }
-                if (!user.hasRole(role)) {
+                if (!newToken.getRealmAccess().getRoles().contains(roleName)) {
                     throw new OAuthErrorException(OAuthErrorException.INVALID_SCOPE, "User no long has permission for realm role: " + roleName);
                 }
-                if (!client.hasScope(role)) {
-                    throw new OAuthErrorException(OAuthErrorException.INVALID_SCOPE, "Client no longer has realm scope: " + roleName);
-                }
             }
         }
         if (token.getResourceAccess() != null) {
             for (Map.Entry<String, AccessToken.Access> entry : token.getResourceAccess().entrySet()) {
-                ApplicationModel app = realm.getApplicationByName(entry.getKey());
-                if (app == null) {
-                    throw new OAuthErrorException(OAuthErrorException.INVALID_SCOPE, "Application no longer exists", "Application no longer exists: " + entry.getKey());
+                AccessToken.Access appAccess = newToken.getResourceAccess(entry.getKey());
+                if (appAccess == null && !entry.getValue().getRoles().isEmpty()) {
+                    throw new OAuthErrorException(OAuthErrorException.INVALID_SCOPE, "User or application no longer has role permissions for application key: " + entry.getKey());
+
                 }
                 for (String roleName : entry.getValue().getRoles()) {
-                    RoleModel role = app.getRole(roleName);
-                    if (role == null) {
-                        throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid refresh token", "Unknown application role: " + roleName);
-                    }
-                    if (!user.hasRole(role)) {
+                    if (!appAccess.getRoles().contains(roleName)) {
                         throw new OAuthErrorException(OAuthErrorException.INVALID_SCOPE, "User no long has permission for application role " + roleName);
                     }
-                    if (clientApp != null && !clientApp.equals(app) && !client.hasScope(role)) {
-                        throw new OAuthErrorException(OAuthErrorException.INVALID_SCOPE, "Client no longer has application scope" + roleName);
-                    }
                 }
             }
         }
diff --git a/services/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper b/services/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper
index f8cc934..4ea322a 100755
--- a/services/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper
+++ b/services/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper
@@ -1,10 +1,10 @@
-org.keycloak.protocol.oidc.mappers.OIDCUserAttributeMapper
-org.keycloak.protocol.oidc.mappers.OIDCFullNameMapper
-org.keycloak.protocol.oidc.mappers.OIDCUserModelMapper
-org.keycloak.protocol.oidc.mappers.OIDCAddressMapper
-org.keycloak.protocol.oidc.mappers.OIDCAddClaimMapper
-org.keycloak.protocol.oidc.mappers.OIDCAddRoleMapper
-org.keycloak.protocol.oidc.mappers.OIDCRoleMapper
-org.keycloak.protocol.oidc.mappers.OIDCUserSessionNoteMapper
+org.keycloak.protocol.oidc.mappers.UserAttributeMapper
+org.keycloak.protocol.oidc.mappers.FullNameMapper
+org.keycloak.protocol.oidc.mappers.UserPropertyMapper
+org.keycloak.protocol.oidc.mappers.AddressMapper
+org.keycloak.protocol.oidc.mappers.HardcodedClaim
+org.keycloak.protocol.oidc.mappers.HardcodedRole
+org.keycloak.protocol.oidc.mappers.RoleNameMapper
+org.keycloak.protocol.oidc.mappers.UserSessionNoteMapper
 
 
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
index 89ed153..6497cb5 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
@@ -276,8 +276,8 @@ public class AccountTest {
 
         events.expectLogin().client("account").detail(Details.REDIRECT_URI, ACCOUNT_REDIRECT).assertEvent();
 
-        Assert.assertEquals("", profilePage.getFirstName());
-        Assert.assertEquals("", profilePage.getLastName());
+        Assert.assertEquals("Tom", profilePage.getFirstName());
+        Assert.assertEquals("Brady", profilePage.getLastName());
         Assert.assertEquals("test-user@localhost", profilePage.getEmail());
 
         // All fields are required, so there should be an error when something is missing.
@@ -310,8 +310,8 @@ public class AccountTest {
 
         profilePage.clickCancel();
 
-        Assert.assertEquals("", profilePage.getFirstName());
-        Assert.assertEquals("", profilePage.getLastName());
+        Assert.assertEquals("Tom", profilePage.getFirstName());
+        Assert.assertEquals("Brady", profilePage.getLastName());
         Assert.assertEquals("test-user@localhost", profilePage.getEmail());
 
         events.assertEmpty();
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/AbstractKerberosTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/AbstractKerberosTest.java
old mode 100644
new mode 100755
index 2cbe7ae..9d59ad9
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/AbstractKerberosTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/AbstractKerberosTest.java
@@ -17,7 +17,6 @@ import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
-import org.keycloak.OAuth2Constants;
 import org.keycloak.adapters.HttpClientBuilder;
 import org.keycloak.events.Details;
 import org.keycloak.federation.kerberos.CommonKerberosConfig;
@@ -31,15 +30,11 @@ import org.keycloak.models.UserFederationProvider;
 import org.keycloak.models.UserFederationProviderModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.protocol.oidc.OIDCLoginProtocol;
-import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory;
-import org.keycloak.protocol.oidc.mappers.OIDCUserSessionNoteMapper;
+import org.keycloak.protocol.oidc.mappers.UserSessionNoteMapper;
 import org.keycloak.services.managers.RealmManager;
 import org.keycloak.testsuite.AssertEvents;
 import org.keycloak.testsuite.OAuthClient;
-import org.keycloak.testsuite.adapter.AdapterTest;
-import org.keycloak.testsuite.adapter.AdapterTestStrategy;
 import org.keycloak.testsuite.pages.AccountPasswordPage;
-import org.keycloak.testsuite.pages.AppPage;
 import org.keycloak.testsuite.pages.LoginPage;
 import org.keycloak.testsuite.rule.KeycloakRule;
 import org.keycloak.testsuite.rule.WebResource;
@@ -182,7 +177,7 @@ public abstract class AbstractKerberosTest {
 
             @Override
             public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
-                ProtocolMapperModel protocolMapper = OIDCUserSessionNoteMapper.createClaimMapper(KerberosConstants.GSS_DELEGATION_CREDENTIAL_DISPLAY_NAME,
+                ProtocolMapperModel protocolMapper = UserSessionNoteMapper.createClaimMapper(KerberosConstants.GSS_DELEGATION_CREDENTIAL_DISPLAY_NAME,
                         KerberosConstants.GSS_DELEGATION_CREDENTIAL,
                         KerberosConstants.GSS_DELEGATION_CREDENTIAL, "String",
                         true, KerberosConstants.GSS_DELEGATION_CREDENTIAL_DISPLAY_NAME,
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java
index 6fca683..0091e33 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java
@@ -40,8 +40,12 @@ import org.keycloak.models.RealmModel;
 import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
-import org.keycloak.protocol.oidc.mappers.OIDCAddressMapper;
-import org.keycloak.protocol.oidc.mappers.OIDCUserAttributeMapper;
+import org.keycloak.protocol.oidc.mappers.AddressMapper;
+import org.keycloak.protocol.oidc.mappers.FullNameMapper;
+import org.keycloak.protocol.oidc.mappers.HardcodedClaim;
+import org.keycloak.protocol.oidc.mappers.HardcodedRole;
+import org.keycloak.protocol.oidc.mappers.RoleNameMapper;
+import org.keycloak.protocol.oidc.mappers.UserAttributeMapper;
 import org.keycloak.representations.AccessToken;
 import org.keycloak.representations.IDToken;
 import org.keycloak.services.managers.RealmManager;
@@ -67,6 +71,7 @@ import javax.ws.rs.core.UriBuilder;
 import java.io.IOException;
 import java.net.URI;
 import java.util.HashMap;
+import java.util.Map;
 
 import static org.hamcrest.Matchers.*;
 import static org.junit.Assert.*;
@@ -594,9 +599,14 @@ public class AccessTokenTest {
             user.setAttribute("country", "USA");
             user.setAttribute("phone", "617-777-6666");
             ApplicationModel app = realm.getApplicationByName("test-app");
-            ProtocolMapperModel mapper = OIDCAddressMapper.createAddressMapper(true, true);
+            ProtocolMapperModel mapper = AddressMapper.createAddressMapper(true, true);
             app.addProtocolMapper(mapper);
-            app.addProtocolMapper(OIDCUserAttributeMapper.createClaimMapper("custom phone", "phone", "home_phone", "String", true, "", true, true));
+            app.addProtocolMapper(HardcodedClaim.create("hard", "hard", "coded", "String", false, null, true, true));
+            app.addProtocolMapper(HardcodedClaim.create("hard-nested", "nested.hard", "coded-nested", "String", false, null, true, true));
+            app.addProtocolMapper(UserAttributeMapper.createClaimMapper("custom phone", "phone", "home_phone", "String", true, "", true, true));
+            app.addProtocolMapper(UserAttributeMapper.createClaimMapper("nested phone", "phone", "home.phone", "String", true, "", true, true));
+            app.addProtocolMapper(HardcodedRole.create("hard-realm", "hardcoded"));
+            app.addProtocolMapper(HardcodedRole.create("hard-app", "app.hardcoded"));
             session.getTransaction().commit();
             session.close();
         }
@@ -607,15 +617,22 @@ public class AccessTokenTest {
             org.keycloak.representations.AccessTokenResponse tokenResponse = response.readEntity(org.keycloak.representations.AccessTokenResponse.class);
             IDToken idToken = getIdToken(tokenResponse);
             Assert.assertNotNull(idToken.getAddress());
+            Assert.assertEquals(idToken.getName(), "Tom Brady");
             Assert.assertEquals(idToken.getAddress().getStreetAddress(), "5 Yawkey Way");
             Assert.assertEquals(idToken.getAddress().getLocality(), "Boston");
             Assert.assertEquals(idToken.getAddress().getRegion(), "MA");
             Assert.assertEquals(idToken.getAddress().getPostalCode(), "02115");
             Assert.assertEquals(idToken.getAddress().getCountry(), "USA");
             Assert.assertNotNull(idToken.getOtherClaims().get("home_phone"));
-            //Assert.assertEquals("617-777-6666", idToken.getOtherClaims().get("home_phone"));
+            Assert.assertEquals("617-777-6666", idToken.getOtherClaims().get("home_phone"));
+            Assert.assertEquals("coded", idToken.getOtherClaims().get("hard"));
+            Map nested = (Map)idToken.getOtherClaims().get("nested");
+            Assert.assertEquals("coded-nested", nested.get("hard"));
+            nested = (Map)idToken.getOtherClaims().get("home");
+            Assert.assertEquals("617-777-6666", nested.get("phone"));
 
             AccessToken accessToken = getAccessToken(tokenResponse);
+            Assert.assertEquals(accessToken.getName(), "Tom Brady");
             Assert.assertNotNull(accessToken.getAddress());
             Assert.assertEquals(accessToken.getAddress().getStreetAddress(), "5 Yawkey Way");
             Assert.assertEquals(accessToken.getAddress().getLocality(), "Boston");
@@ -624,11 +641,41 @@ public class AccessTokenTest {
             Assert.assertEquals(accessToken.getAddress().getCountry(), "USA");
             Assert.assertNotNull(accessToken.getOtherClaims().get("home_phone"));
             Assert.assertEquals("617-777-6666", accessToken.getOtherClaims().get("home_phone"));
+            Assert.assertEquals("coded", accessToken.getOtherClaims().get("hard"));
+            nested = (Map)accessToken.getOtherClaims().get("nested");
+            Assert.assertEquals("coded-nested", nested.get("hard"));
+            nested = (Map)accessToken.getOtherClaims().get("home");
+            Assert.assertEquals("617-777-6666", nested.get("phone"));
+            Assert.assertTrue(accessToken.getRealmAccess().getRoles().contains("hardcoded"));
+            Assert.assertTrue(accessToken.getResourceAccess("app").getRoles().contains("hardcoded"));
 
 
             response.close();
         }
         client.close();
+
+        // undo mappers
+        {
+            KeycloakSession session = keycloakRule.startSession();
+            RealmModel realm = session.realms().getRealmByName("test");
+            ApplicationModel app = realm.getApplicationByName("test-app");
+            for (ProtocolMapperModel model : app.getProtocolMappers()) {
+                if (model.getName().equals("address")
+                        || model.getName().equals("hard")
+                        || model.getName().equals("hard-nested")
+                        || model.getName().equals("custom phone")
+                        || model.getName().equals("nested phone")
+                        || model.getName().equals("hard-realm")
+                        || model.getName().equals("hard-app")
+                        )   {
+                    app.removeProtocolMapper(model);
+                }
+            }
+            session.getTransaction().commit();
+            session.close();
+        }
+
+
         events.clear();
 
     }
@@ -645,7 +692,7 @@ public class AccessTokenTest {
     }
 
     private AccessToken getAccessToken(org.keycloak.representations.AccessTokenResponse tokenResponse) throws VerificationException {
-        JWSInput input = new JWSInput(tokenResponse.getIdToken());
+        JWSInput input = new JWSInput(tokenResponse.getToken());
         AccessToken idToken = null;
         try {
             idToken = input.readJsonContent(AccessToken.class);
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/saml/SamlBindingTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/saml/SamlBindingTest.java
index 8f8f2b4..b8e9e1c 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/saml/SamlBindingTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/saml/SamlBindingTest.java
@@ -3,21 +3,27 @@ package org.keycloak.testsuite.saml;
 import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataOutput;
 import org.junit.Assert;
 import org.junit.ClassRule;
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.keycloak.Config;
 import org.keycloak.models.ApplicationModel;
 import org.keycloak.models.Constants;
 import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ProtocolMapperModel;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserSessionModel;
 import org.keycloak.protocol.oidc.TokenManager;
+import org.keycloak.protocol.saml.mappers.AttributeStatementHelper;
+import org.keycloak.protocol.saml.mappers.HardcodedAttributeMapper;
+import org.keycloak.protocol.saml.mappers.HardcodedRole;
+import org.keycloak.protocol.saml.mappers.RoleListMapper;
+import org.keycloak.protocol.saml.mappers.RoleNameMapper;
 import org.keycloak.representations.AccessToken;
 import org.keycloak.services.managers.RealmManager;
 import org.keycloak.services.resources.admin.AdminRoot;
 import org.keycloak.testsuite.pages.LoginPage;
+import org.keycloak.testsuite.rule.KeycloakRule;
 import org.keycloak.testsuite.rule.WebResource;
 import org.keycloak.testsuite.rule.WebRule;
 import org.openqa.selenium.WebDriver;
@@ -101,10 +107,12 @@ public class SamlBindingTest {
         private void handler(HttpServletRequest req, HttpServletResponse resp) {
             System.out.println("********* HERE ******");
             if (req.getParameterMap().isEmpty()) {
+                System.out.println("redirecting");
                 resp.setStatus(302);
                 resp.setHeader("Location", "http://localhost:8081/auth/realms/demo/protocol/saml?SAMLRequest=jVJbT8IwFP4rS99HuwluNIwEIUYSLwugD76Y2h2kSdfOng7l31uGRn0ATfrQ9HznfJfTEYpaN3zS%2Bo1ZwGsL6KP3WhvkXaEgrTPcClTIjagBuZd8Obm55mmP8cZZb6XV5NByGiwQwXllDYkmX9epNdjW4JbgtkrC%2FeK6IBvvG06ptlLojUXPc5YnFOpG2x0AJdEsaFRG7PuPoUWwQx0IXSOtoLb0SynduyLRpXUSOs8FWQuNQKL5rCDz2VO%2FymEgIY2zlJ3H%2FSx9jkU%2BzOK0ys8yNmSSsUEAYxnsqC18tyO2MDfohfEFSVkyiNlZzM5XacrDSbJePug%2Fkqj8FHKhTKXMy%2BnIng8g5FerVRmXd8sViR7AYec8AMh4tPfDO3L3Y2%2F%2F3cT4j7BH9Mf8A1nDb8PA%2Bay0WsldNNHavk1D1D5k4V0LXbi18MclJL2ke1FVvO6gvDXYgFRrBRWh4wPp7z85%2FgA%3D");
                 return;
             }
+            System.out.println("received response");
             samlResponse = req.getParameter("SAMLResponse");
         }
     }
@@ -199,43 +207,122 @@ public class SamlBindingTest {
         // this test has a hardcoded SAMLRequest and we hack a SP face servlet to get the SAMLResponse so we can look
         // at the assertions sent.  This is because Picketlink, AFAICT, does not give you any way to get access to
         // the assertion.
-        SamlSPFacade.samlResponse = null;
-        driver.navigate().to("http://localhost:8081/employee/");
-        Assert.assertTrue(driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/demo/protocol/saml"));
-        System.out.println(driver.getCurrentUrl());
-        loginPage.login("bburke", "password");
-        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/employee/");
-        Assert.assertNotNull(SamlSPFacade.samlResponse);
-        SAML2Response saml2Response = new SAML2Response();
-        byte[] samlResponse = PostBindingUtil.base64Decode(SamlSPFacade.samlResponse);
-        ResponseType rt = saml2Response.getResponseType(new ByteArrayInputStream(samlResponse));
-        Assert.assertTrue(rt.getAssertions().size() == 1);
-        AssertionType assertion = rt.getAssertions().get(0).getAssertion();
-
-        // test attributes
-
-        boolean email = false;
-        boolean phone = false;
-        for (AttributeStatementType statement : assertion.getAttributeStatements()) {
-            for (AttributeStatementType.ASTChoiceType choice : statement.getAttributes()) {
-                AttributeType attr = choice.getAttribute();
-                if (X500SAMLProfileConstants.EMAIL.getFriendlyName().equals(attr.getFriendlyName())) {
-                    Assert.assertEquals(X500SAMLProfileConstants.EMAIL.get(), attr.getName());
-                    Assert.assertEquals(JBossSAMLURIConstants.ATTRIBUTE_FORMAT_URI.get(), attr.getNameFormat());
-                    Assert.assertEquals(attr.getAttributeValue().get(0), "bburke@redhat.com");
-                    email = true;
-                } else if (attr.getName().equals("phone")) {
-                    Assert.assertEquals(JBossSAMLURIConstants.ATTRIBUTE_FORMAT_BASIC.get(), attr.getNameFormat());
-                    Assert.assertEquals(attr.getAttributeValue().get(0), "617");
-                    phone = true;
+
+        {
+            SamlSPFacade.samlResponse = null;
+            driver.navigate().to("http://localhost:8081/employee/");
+            Assert.assertTrue(driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/demo/protocol/saml"));
+            System.out.println(driver.getCurrentUrl());
+            loginPage.login("bburke", "password");
+            Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/employee/");
+            Assert.assertNotNull(SamlSPFacade.samlResponse);
+            SAML2Response saml2Response = new SAML2Response();
+            byte[] samlResponse = PostBindingUtil.base64Decode(SamlSPFacade.samlResponse);
+            ResponseType rt = saml2Response.getResponseType(new ByteArrayInputStream(samlResponse));
+            Assert.assertTrue(rt.getAssertions().size() == 1);
+            AssertionType assertion = rt.getAssertions().get(0).getAssertion();
+
+            // test attributes and roles
+
+            boolean email = false;
+            boolean phone = false;
+            boolean userRole = false;
+            boolean managerRole = false;
+            for (AttributeStatementType statement : assertion.getAttributeStatements()) {
+                for (AttributeStatementType.ASTChoiceType choice : statement.getAttributes()) {
+                    AttributeType attr = choice.getAttribute();
+                    if (X500SAMLProfileConstants.EMAIL.getFriendlyName().equals(attr.getFriendlyName())) {
+                        Assert.assertEquals(X500SAMLProfileConstants.EMAIL.get(), attr.getName());
+                        Assert.assertEquals(JBossSAMLURIConstants.ATTRIBUTE_FORMAT_URI.get(), attr.getNameFormat());
+                        Assert.assertEquals(attr.getAttributeValue().get(0), "bburke@redhat.com");
+                        email = true;
+                    } else if (attr.getName().equals("phone")) {
+                        Assert.assertEquals(JBossSAMLURIConstants.ATTRIBUTE_FORMAT_BASIC.get(), attr.getNameFormat());
+                        Assert.assertEquals(attr.getAttributeValue().get(0), "617");
+                        phone = true;
+                    } else if (attr.getName().equals("Role")) {
+                        if (attr.getAttributeValue().get(0).equals("manager")) managerRole = true;
+                        if (attr.getAttributeValue().get(0).equals("user")) userRole = true;
+                    }
                 }
+
             }
 
+            Assert.assertTrue(email);
+            Assert.assertTrue(phone);
+            Assert.assertTrue(userRole);
+            Assert.assertTrue(managerRole);
         }
 
-        Assert.assertTrue(email);
-        Assert.assertTrue(phone);
+        keycloakRule.update(new KeycloakRule.KeycloakSetup() {
+            @Override
+            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                ApplicationModel app = appRealm.getApplicationByName("http://localhost:8081/employee/");
+                for (ProtocolMapperModel mapper : app.getProtocolMappers()) {
+                    if (mapper.getName().equals("role-list")) {
+                        app.removeProtocolMapper(mapper);
+                        mapper.setId(null);
+                        mapper.getConfig().put(RoleListMapper.SINGLE_ROLE_ATTRIBUTE, "true");
+                        mapper.getConfig().put(AttributeStatementHelper.SAML_ATTRIBUTE_NAME, "memberOf");
+                        app.addProtocolMapper(mapper);
+                    }
+                }
+                app.addProtocolMapper(HardcodedAttributeMapper.create("hardcoded-attribute", "hardcoded-attribute", "Basic", null, "hard", false, null));
+                app.addProtocolMapper(HardcodedRole.create("hardcoded-role", "hardcoded-role"));
+                app.addProtocolMapper(RoleNameMapper.create("renamed-role", "manager", "el-jefe"));
+                app.addProtocolMapper(RoleNameMapper.create("renamed-employee-role", "http://localhost:8081/employee/.employee", "pee-on"));
+            }
+        }, "demo");
+
+        System.out.println(">>>>>>>>>> single role attribute <<<<<<<<");
+
+        {
+            SamlSPFacade.samlResponse = null;
+            driver.navigate().to("http://localhost:8081/employee/");
+            System.out.println(driver.getCurrentUrl());
+            Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/employee/");
+            Assert.assertNotNull(SamlSPFacade.samlResponse);
+            SAML2Response saml2Response = new SAML2Response();
+            byte[] samlResponse = PostBindingUtil.base64Decode(SamlSPFacade.samlResponse);
+            ResponseType rt = saml2Response.getResponseType(new ByteArrayInputStream(samlResponse));
+            Assert.assertTrue(rt.getAssertions().size() == 1);
+            AssertionType assertion = rt.getAssertions().get(0).getAssertion();
+
+            // test attributes and roles
+
+            boolean userRole = false;
+            boolean managerRole = false;
+            boolean single = false;
+            boolean hardcodedRole = false;
+            boolean hardcodedAttribute = false;
+            boolean peeOn = false;
+            for (AttributeStatementType statement : assertion.getAttributeStatements()) {
+                for (AttributeStatementType.ASTChoiceType choice : statement.getAttributes()) {
+                    AttributeType attr = choice.getAttribute();
+                    if (attr.getName().equals("memberOf")) {
+                        if (single) Assert.fail("too many role attributes");
+                        single = true;
+                        for (Object value : attr.getAttributeValue()) {
+                            if (value.equals("el-jefe")) managerRole = true;
+                            if (value.equals("user")) userRole = true;
+                            if (value.equals("hardcoded-role")) hardcodedRole = true;
+                            if (value.equals("pee-on")) peeOn = true;
+                        }
+                    } else if (attr.getName().equals("hardcoded-attribute")) {
+                        hardcodedAttribute = true;
+                        Assert.assertEquals(attr.getAttributeValue().get(0), "hard");
+                    }
+                }
 
+            }
+
+            Assert.assertTrue(single);
+            Assert.assertTrue(hardcodedAttribute);
+            Assert.assertTrue(hardcodedRole);
+            Assert.assertTrue(peeOn);
+            Assert.assertTrue(userRole);
+            Assert.assertTrue(managerRole);
+        }
     }
 
     @Test
diff --git a/testsuite/integration/src/test/resources/saml/testsaml.json b/testsuite/integration/src/test/resources/saml/testsaml.json
index 3cd3342..cac873b 100755
--- a/testsuite/integration/src/test/resources/saml/testsaml.json
+++ b/testsuite/integration/src/test/resources/saml/testsaml.json
@@ -27,7 +27,10 @@
             "attributes" : {
                 "phone": "617"
             },
-            "realmRoles": ["manager", "user"]
+            "realmRoles": ["manager", "user"],
+            "applicationRoles": {
+                "http://localhost:8081/employee/": [ "employee" ]
+            }
         }
     ],
     "applications": [
@@ -304,6 +307,14 @@
                 "name": "user",
                 "description": "Have User privileges"
             }
-        ]
+        ],
+        "application" : {
+            "http://localhost:8081/employee/" : [
+                {
+                    "name": "employee",
+                    "description": "Have Employee privileges"
+                }
+            ]
+        }
     }
 }
diff --git a/testsuite/integration/src/test/resources/testrealm.json b/testsuite/integration/src/test/resources/testrealm.json
index 81a442d..17c54dc 100755
--- a/testsuite/integration/src/test/resources/testrealm.json
+++ b/testsuite/integration/src/test/resources/testrealm.json
@@ -20,6 +20,8 @@
             "username" : "test-user@localhost",
             "enabled": true,
             "email" : "test-user@localhost",
+            "firstName": "Tom",
+            "lastName": "Brady",
             "credentials" : [
                 { "type" : "password",
                   "value" : "password" }