keycloak-aplcache

Merge pull request #1027 from patriot1burke/master added

3/6/2015 9:00:10 PM

Changes

saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/UserModelBasicAttributeStatementMapper.java 79(+0 -79)

Details

diff --git a/core/src/main/java/org/keycloak/representations/AccessToken.java b/core/src/main/java/org/keycloak/representations/AccessToken.java
index 4d099ca..81d3615 100755
--- a/core/src/main/java/org/keycloak/representations/AccessToken.java
+++ b/core/src/main/java/org/keycloak/representations/AccessToken.java
@@ -125,9 +125,11 @@ public class AccessToken extends IDToken {
     }
 
     public Access addAccess(String service) {
-        Access token = new Access();
-        resourceAccess.put(service, token);
-        return token;
+        Access access = resourceAccess.get(service);
+        if (access != null) return access;
+        access = new Access();
+        resourceAccess.put(service, access);
+        return access;
     }
 
     public AccessToken clientSession(String session) {
diff --git a/core/src/main/java/org/keycloak/representations/idm/ProtocolMapperTypeRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ProtocolMapperTypeRepresentation.java
index 1e510f8..779f3be 100755
--- a/core/src/main/java/org/keycloak/representations/idm/ProtocolMapperTypeRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/ProtocolMapperTypeRepresentation.java
@@ -17,7 +17,7 @@ public class ProtocolMapperTypeRepresentation {
         protected String label;
         protected String helpText;
         protected String type;
-        protected String defaultValue;
+        protected Object defaultValue;
 
         public String getName() {
             return name;
@@ -43,11 +43,11 @@ public class ProtocolMapperTypeRepresentation {
             this.type = type;
         }
 
-        public String getDefaultValue() {
+        public Object getDefaultValue() {
             return defaultValue;
         }
 
-        public void setDefaultValue(String defaultValue) {
+        public void setDefaultValue(Object defaultValue) {
             this.defaultValue = defaultValue;
         }
 
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/oauth-client-protocol-mapper-detail.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/oauth-client-protocol-mapper-detail.html
index a98bbe7..36738b6 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/oauth-client-protocol-mapper-detail.html
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/oauth-client-protocol-mapper-detail.html
@@ -78,12 +78,17 @@
                 <div data-ng-repeat="option in mapperType.properties" class="form-group">
                     <label class="col-sm-2 control-label">{{option.label}} </label>
 
-                    <div class="col-sm-4" data-ng-hide="option.type == 'boolean'">
+                    <div class="col-sm-4" data-ng-hide="option.type == 'boolean' || option.type == 'List'">
                         <input class="form-control" type="text" data-ng-model="mapper.config[ option.name ]" >
                     </div>
                     <div class="col-sm-4" data-ng-show="option.type == 'boolean'">
                         <input ng-model="mapper.config[ option.name ]" value="'true'" name="option.name" id="option.name" onoffswitchmodel />
                     </div>
+                    <div class="col-sm-4" data-ng-show="option.type == 'List'">
+                        <select ng-model="mapper.config[ option.name ]" ng-options="data for data in option.defaultValue">
+                            <option value="" selected> Select one... </option>
+                        </select>
+                    </div>
                     <span tooltip-placement="right" tooltip="{{option.helpText}}" class="fa fa-info-circle"></span>
                 </div>
 
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/protocol-mapper-detail.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/protocol-mapper-detail.html
index 25be1f1..aed7f50 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/protocol-mapper-detail.html
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/protocol-mapper-detail.html
@@ -76,14 +76,19 @@
                     <span tooltip-placement="right" tooltip="{{mapperType.helpText}}" class="fa fa-info-circle"></span>
                 </div>
                 <div data-ng-repeat="option in mapperType.properties" class="form-group">
-                    <label class="col-sm-2 control-label">{{option.label}} </label>
+                    <label class="col-sm-2 control-label">{{option.label}}</label>
 
-                    <div class="col-sm-4" data-ng-hide="option.type == 'boolean'">
+                    <div class="col-sm-4" data-ng-hide="option.type == 'boolean' || option.type == 'List'">
                         <input class="form-control" type="text" data-ng-model="mapper.config[ option.name ]" >
                     </div>
                     <div class="col-sm-4" data-ng-show="option.type == 'boolean'">
                         <input ng-model="mapper.config[ option.name ]" value="'true'" name="option.name" id="option.name" onoffswitchmodel />
                     </div>
+                    <div class="col-sm-4" data-ng-show="option.type == 'List'">
+                        <select ng-model="mapper.config[ option.name ]" ng-options="data for data in option.defaultValue">
+                            <option value="" selected> Select one... </option>
+                        </select>
+                    </div>
                     <span tooltip-placement="right" tooltip="{{option.helpText}}" class="fa fa-info-circle"></span>
                 </div>
 
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 3bbb7fc..378a0f6 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
@@ -9,6 +9,7 @@ import org.picketlink.common.constants.JBossSAMLURIConstants;
 import org.picketlink.identity.federation.saml.v2.assertion.AttributeStatementType;
 import org.picketlink.identity.federation.saml.v2.assertion.AttributeType;
 
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -18,19 +19,24 @@ import java.util.Map;
  * @version $Revision: 1 $
  */
 public class AttributeStatementHelper {
-    public static final String SAML_ATTRIBUTE_NAME = "SAML Attribute Name";
+    public static final String SAML_ATTRIBUTE_NAME = "attribute.name";
     public static final String ATTRIBUTE_STATEMENT_CATEGORY = "AttributeStatement Mapper";
-    public static final String URI_REFERENCE_LABEL = "URI Reference";
-    public static final String URI_REFERENCE_HELP_TEXT = "Attribute name for the SAML URI Reference attribute name format";
-    public static final String BASIC_LABEL = "Basic name";
-    public static final String BASIC_HELP_TEXT = "Attribute name for the SAML Basic attribute name format";
-    public static final String FRIENDLY_NAME = "Friendly Name";
+    public static final String FRIENDLY_NAME = "friendly.name";
+    public static final String FRIENDLY_NAME_LABEL = "Friendly Name";
     public static final String FRIENDLY_NAME_HELP_TEXT = "Standard SAML attribute setting.  An optional, more human-readable form of the attribute's name that can be provided if the actual attribute name is cryptic.";
+    public static final String SAML_ATTRIBUTE_NAMEFORMAT = "attribute.nameformat";
+    public static final String BASIC = "Basic";
+    public static final String URI_REFERENCE = "URI Reference";
+    public static final String UNSPECIFIED = "Unspecified";
 
     public static void addAttribute(AttributeStatementType attributeStatement, ProtocolMapperModel mappingModel,
-                                    String attributeNameFormat, String attributeValue) {
+                                    String attributeValue) {
         String attributeName = mappingModel.getConfig().get(SAML_ATTRIBUTE_NAME);
         AttributeType attribute = new AttributeType(attributeName);
+        String attributeType = mappingModel.getConfig().get(SAML_ATTRIBUTE_NAMEFORMAT);
+        String attributeNameFormat = JBossSAMLURIConstants.ATTRIBUTE_FORMAT_BASIC.get();
+        if ("URI Reference".equals(attributeType)) attributeNameFormat = JBossSAMLURIConstants.ATTRIBUTE_FORMAT_URI.get();
+        else if ("Unspecified".equals(attributeType)) attributeNameFormat = "urn:oasis:names:tc:SAML2.0:attrname-format:unspecified";
         attribute.setNameFormat(attributeNameFormat);
         String friendlyName = mappingModel.getConfig().get(FRIENDLY_NAME);
         if (friendlyName != null && !friendlyName.trim().equals("")) attribute.setFriendlyName(friendlyName);
@@ -38,43 +44,31 @@ public class AttributeStatementHelper {
         attributeStatement.addAttribute(new AttributeStatementType.ASTChoiceType(attribute));
     }
 
-    public static void addUriReferenceAttribute(AttributeStatementType attributeStatement, ProtocolMapperModel mappingModel, String attributeValue) {
-        String attributeNameFormat = JBossSAMLURIConstants.ATTRIBUTE_FORMAT_URI.get();
-        addAttribute(attributeStatement, mappingModel, attributeNameFormat, attributeValue);
-    }
-
-    public static void addBasicAttribute(AttributeStatementType attributeStatement, ProtocolMapperModel mappingModel, String attributeValue) {
-        addAttribute(attributeStatement, mappingModel, JBossSAMLURIConstants.ATTRIBUTE_FORMAT_BASIC.get(), attributeValue);
-    }
-
-    protected static void addUriReferenceProperties(List<ProtocolMapper.ConfigProperty> configProperties) {
-        ProtocolMapper.ConfigProperty property;
-        property = new ProtocolMapper.ConfigProperty();
-        property.setName(FRIENDLY_NAME);
-        property.setLabel(FRIENDLY_NAME);
-        property.setHelpText(FRIENDLY_NAME_HELP_TEXT);
+    public static void setConfigProperties(List<ProtocolMapper.ConfigProperty> configProperties) {
+        ProtocolMapper.ConfigProperty property = new ProtocolMapper.ConfigProperty();
+        property.setName(AttributeStatementHelper.FRIENDLY_NAME);
+        property.setLabel(AttributeStatementHelper.FRIENDLY_NAME_LABEL);
+        property.setHelpText(AttributeStatementHelper.FRIENDLY_NAME_HELP_TEXT);
         configProperties.add(property);
         property = new ProtocolMapper.ConfigProperty();
-        property.setName(SAML_ATTRIBUTE_NAME);
-        property.setLabel(URI_REFERENCE_LABEL);
-        property.setHelpText(URI_REFERENCE_HELP_TEXT);
+        property.setName(AttributeStatementHelper.SAML_ATTRIBUTE_NAME);
+        property.setLabel("SAML Attribute Name");
+        property.setHelpText("SAML Attribute Name");
         configProperties.add(property);
-    }
-    protected static void addBasicProperties(List<ProtocolMapper.ConfigProperty> configProperties) {
-        ProtocolMapper.ConfigProperty property;
         property = new ProtocolMapper.ConfigProperty();
-        property.setName(FRIENDLY_NAME);
-        property.setLabel(FRIENDLY_NAME);
-        property.setHelpText(FRIENDLY_NAME_HELP_TEXT);
+        property.setName(AttributeStatementHelper.SAML_ATTRIBUTE_NAMEFORMAT);
+        property.setLabel("SAML Attribute NameFormat");
+        property.setHelpText("SAML Attribute NameFormat.  Can be basic, URI reference, or unspecified.");
+        List<String> types = new ArrayList(3);
+        types.add(AttributeStatementHelper.BASIC);
+        types.add(AttributeStatementHelper.URI_REFERENCE);
+        types.add(AttributeStatementHelper.UNSPECIFIED);
+        property.setType(ProtocolMapper.ConfigProperty.LIST_TYPE);
+        property.setDefaultValue(types);
         configProperties.add(property);
-        property = new ProtocolMapper.ConfigProperty();
-        property.setName(SAML_ATTRIBUTE_NAME);
-        property.setLabel(BASIC_LABEL);
-        property.setHelpText(BASIC_HELP_TEXT);
-        configProperties.add(property);
-    }
 
-    public static ProtocolMapperModel createAttributeMapper(String name, String userAttribute, String samlAttributeName, String friendlyName, boolean consentRequired, String consentText, String mapperId) {
+    }
+    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();
         mapper.setName(name);
         mapper.setProtocolMapper(mapperId);
@@ -87,6 +81,9 @@ public class AttributeStatementHelper {
         if (friendlyName != null) {
             config.put(FRIENDLY_NAME, friendlyName);
         }
+        if (nameFormat != null) {
+            config.put(SAML_ATTRIBUTE_NAMEFORMAT, nameFormat);
+        }
         mapper.setConfig(config);
         return mapper;
     }
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/SAMLRoleListMapper.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/SAMLRoleListMapper.java
new file mode 100755
index 0000000..b2d500f
--- /dev/null
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/SAMLRoleListMapper.java
@@ -0,0 +1,17 @@
+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.picketlink.identity.federation.saml.v2.assertion.AttributeStatementType;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface SAMLRoleListMapper {
+
+    void mapRoles(AttributeStatementType roleAttributeStatement, ProtocolMapperModel mappingModel, KeycloakSession session,
+                                     UserSessionModel userSession, ClientSessionModel clientSession);
+}
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 934abe9..5f673fb 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
@@ -4,26 +4,18 @@ import org.keycloak.Config;
 import org.keycloak.events.EventBuilder;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.KeycloakSessionFactory;
 import org.keycloak.models.ProtocolMapperModel;
 import org.keycloak.models.RealmModel;
 import org.keycloak.protocol.AbstractLoginProtocolFactory;
 import org.keycloak.protocol.LoginProtocol;
-import org.keycloak.protocol.LoginProtocolFactory;
-import org.keycloak.protocol.oidc.OIDCLoginProtocol;
-import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper;
-import org.keycloak.protocol.oidc.mappers.OIDCUserModelMapper;
-import org.keycloak.protocol.saml.mappers.AttributeStatementHelper;
-import org.keycloak.protocol.saml.mappers.UserAttributeBasicAttributeStatementMapper;
-import org.keycloak.protocol.saml.mappers.UserModelUriReferenceAttributeStatementMapper;
+import org.keycloak.protocol.saml.mappers.UserPropertyAttributeStatementMapper;
 import org.keycloak.services.managers.AuthenticationManager;
+import org.picketlink.common.constants.JBossSAMLURIConstants;
 import org.picketlink.identity.federation.core.saml.v2.constants.X500SAMLProfileConstants;
 import org.picketlink.identity.federation.core.sts.PicketLinkCoreSTS;
 
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -62,19 +54,25 @@ public class SamlProtocolFactory extends AbstractLoginProtocolFactory {
 
     static {
         ProtocolMapperModel model;
-        model = UserModelUriReferenceAttributeStatementMapper.createAttributeMapper("X500 email",
+        model = UserPropertyAttributeStatementMapper.createAttributeMapper("X500 email",
                 "email",
-                X500SAMLProfileConstants.EMAIL.get(), X500SAMLProfileConstants.EMAIL.getFriendlyName(),
+                X500SAMLProfileConstants.EMAIL.get(),
+                JBossSAMLURIConstants.ATTRIBUTE_FORMAT_URI.get(),
+                X500SAMLProfileConstants.EMAIL.getFriendlyName(),
                 true, "email");
         builtins.add(model);
-        model = UserModelUriReferenceAttributeStatementMapper.createAttributeMapper("X500 givenName",
+        model = UserPropertyAttributeStatementMapper.createAttributeMapper("X500 givenName",
                 "firstName",
-                X500SAMLProfileConstants.GIVEN_NAME.get(), X500SAMLProfileConstants.GIVEN_NAME.getFriendlyName(),
+                X500SAMLProfileConstants.GIVEN_NAME.get(),
+                JBossSAMLURIConstants.ATTRIBUTE_FORMAT_URI.get(),
+                X500SAMLProfileConstants.GIVEN_NAME.getFriendlyName(),
                 true, "given name");
         builtins.add(model);
-        model = UserModelUriReferenceAttributeStatementMapper.createAttributeMapper("X500 surname",
+        model = UserPropertyAttributeStatementMapper.createAttributeMapper("X500 surname",
                 "lastName",
-                X500SAMLProfileConstants.SURNAME.get(), X500SAMLProfileConstants.SURNAME.getFriendlyName(),
+                X500SAMLProfileConstants.SURNAME.get(),
+                JBossSAMLURIConstants.ATTRIBUTE_FORMAT_URI.get(),
+                X500SAMLProfileConstants.SURNAME.getFriendlyName(),
                 true, "family name");
         builtins.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 719ddff..7d75839 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,6 +1,4 @@
-org.keycloak.protocol.saml.mappers.UserAttributeBasicAttributeStatementMapper
-org.keycloak.protocol.saml.mappers.UserModelBasicAttributeStatementMapper
-org.keycloak.protocol.saml.mappers.UserAttributeUriReferenceAttributeStatementMapper
-org.keycloak.protocol.saml.mappers.UserModelUriReferenceAttributeStatementMapper
+org.keycloak.protocol.saml.mappers.UserAttributeStatementMapper
+org.keycloak.protocol.saml.mappers.UserPropertyAttributeStatementMapper
 
 
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCAddClaimMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCAddClaimMapper.java
new file mode 100755
index 0000000..fad7bd6
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCAddClaimMapper.java
@@ -0,0 +1,127 @@
+package org.keycloak.protocol.oidc.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.keycloak.representations.AccessToken;
+import org.keycloak.representations.IDToken;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ *
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class OIDCAddClaimMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper {
+
+    private static final List<ConfigProperty> configProperties = new ArrayList<ConfigProperty>();
+
+    public static final String CLAIM_VALUE = "claim.value";
+
+    static {
+        ConfigProperty property;
+        property = new ConfigProperty();
+        property.setName(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME);
+        property.setLabel(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME_LABEL);
+        property.setType(ConfigProperty.STRING_TYPE);
+        property.setHelpText("Claim name you want to hard code into the token.  This can be a fully qualified name like 'address.street'.  In this case, a nested json object will be created.");
+        configProperties.add(property);
+        property = new ConfigProperty();
+        property.setName(CLAIM_VALUE);
+        property.setLabel("Claim value");
+        property.setType(ConfigProperty.STRING_TYPE);
+        property.setHelpText("Value of the claim you want to hard code.  'true' and 'false can be used for boolean values.");
+        configProperties.add(property);
+        property = new ConfigProperty();
+        property.setName(OIDCAttributeMapperHelper.JSON_TYPE);
+        property.setLabel(OIDCAttributeMapperHelper.JSON_TYPE);
+        property.setType(ConfigProperty.STRING_TYPE);
+        property.setDefaultValue(ConfigProperty.STRING_TYPE);
+        property.setHelpText("JSON type that should be used for the value of the claim.  long, int, boolean, and String are valid values.");
+        configProperties.add(property);
+        property = new ConfigProperty();
+        property.setName(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN);
+        property.setLabel(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN_LABEL);
+        property.setType(ConfigProperty.BOOLEAN_TYPE);
+        property.setDefaultValue("true");
+        property.setHelpText(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN_HELP_TEXT);
+        configProperties.add(property);
+        property = new ConfigProperty();
+        property.setName(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN);
+        property.setLabel(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN_LABEL);
+        property.setType(ConfigProperty.BOOLEAN_TYPE);
+        property.setDefaultValue("true");
+        property.setHelpText(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN_HELP_TEXT);
+        configProperties.add(property);
+
+    }
+
+    public static final String PROVIDER_ID = "oidc-add-claim-mapper";
+
+
+    public List<ConfigProperty> getConfigProperties() {
+        return configProperties;
+    }
+
+    @Override
+    public String getId() {
+        return PROVIDER_ID;
+    }
+
+    @Override
+    public String getDisplayType() {
+        return "Hard coded claim";
+    }
+
+    @Override
+    public String getDisplayCategory() {
+        return TOKEN_MAPPER_CATEGORY;
+    }
+
+    @Override
+    public String getHelpText() {
+        return "Hardcode a claim into the token.";
+    }
+
+    @Override
+    public AccessToken transformAccessToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
+                                            UserSessionModel userSession, ClientSessionModel clientSession) {
+        if (!OIDCAttributeMapperHelper.includeInAccessToken(mappingModel)) return token;
+
+        setClaim(token, mappingModel, userSession);
+        return token;
+    }
+
+    protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession) {
+        String attributeValue = mappingModel.getConfig().get(CLAIM_VALUE);
+        if (attributeValue == null) return;
+        OIDCAttributeMapperHelper.mapClaim(token, mappingModel, attributeValue);
+    }
+
+    @Override
+    public IDToken transformIDToken(IDToken token, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) {
+        if (!OIDCAttributeMapperHelper.includeInIDToken(mappingModel)) return token;
+        setClaim(token, mappingModel, userSession);
+        return token;
+    }
+
+    public static ProtocolMapperModel createClaimMapper(String name,
+                                      String userAttribute,
+                                      String tokenClaimName, String claimType,
+                                      boolean consentRequired, String consentText,
+                                      boolean accessToken, boolean idToken) {
+        return OIDCAttributeMapperHelper.createClaimMapper(name, userAttribute,
+                tokenClaimName, claimType,
+                consentRequired, consentText,
+                accessToken, idToken,
+                PROVIDER_ID);
+    }
+
+
+}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCAttributeMapperHelper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCAttributeMapperHelper.java
index 8819713..b91c233 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCAttributeMapperHelper.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCAttributeMapperHelper.java
@@ -15,7 +15,8 @@ import java.util.Map;
  * @version $Revision: 1 $
  */
 public class OIDCAttributeMapperHelper {
-    public static final String TOKEN_CLAIM_NAME = "Token Claim Name";
+    public static final String TOKEN_CLAIM_NAME = "claim.name";
+    public static final String TOKEN_CLAIM_NAME_LABEL = "Token Claim Name";
     public static final String JSON_TYPE = "Claim JSON Type";
     public static final String INCLUDE_IN_ACCESS_TOKEN = "access.token.claim";
     public static final String INCLUDE_IN_ACCESS_TOKEN_LABEL = "Add to access token";
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCRoleMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCRoleMapper.java
new file mode 100755
index 0000000..3ed9b61
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCRoleMapper.java
@@ -0,0 +1,101 @@
+package org.keycloak.protocol.oidc.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.keycloak.representations.AccessToken;
+import org.keycloak.representations.IDToken;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Map an assigned role to a different position and name in the token
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class OIDCRoleMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper {
+
+    private static final List<ConfigProperty> configProperties = new ArrayList<ConfigProperty>();
+
+    public static final String ROLE_CONFIG = "role";
+    public static String NEW_ROLE_NAME = "new.role.name";
+
+    static {
+        ConfigProperty property;
+        property = new ConfigProperty();
+        property.setName(ROLE_CONFIG);
+        property.setLabel("Role");
+        property.setHelpText("Role name you want changed.  To reference an application role the syntax is appname.approle, i.e. myapp.myrole");
+        property.setType(ConfigProperty.STRING_TYPE);
+        configProperties.add(property);
+        property = new ConfigProperty();
+        property.setName(NEW_ROLE_NAME);
+        property.setLabel("New Role Name");
+        property.setHelpText("The new role name.  The new name format corresponds to where in the access token the role will be mapped to.  So, a new name of 'myapp.newname' will map the role to that position in the access token.  A new name of 'newname' will map the role to the realm roles in the token.");
+        property.setType(ConfigProperty.STRING_TYPE);
+        configProperties.add(property);
+    }
+
+    public static final String PROVIDER_ID = "oidc-role-mapper";
+
+
+    public List<ConfigProperty> getConfigProperties() {
+        return configProperties;
+    }
+
+    @Override
+    public String getId() {
+        return PROVIDER_ID;
+    }
+
+    @Override
+    public String getDisplayType() {
+        return "Role Mapper";
+    }
+
+    @Override
+    public String getDisplayCategory() {
+        return TOKEN_MAPPER_CATEGORY;
+    }
+
+    @Override
+    public String getHelpText() {
+        return "Map an assigned role to a new name or position in the token.";
+    }
+
+    @Override
+    public AccessToken transformAccessToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
+                                            UserSessionModel userSession, ClientSessionModel clientSession) {
+        String role = mappingModel.getConfig().get(ROLE_CONFIG);
+        String newName = mappingModel.getConfig().get(NEW_ROLE_NAME);
+        String appName = null;
+        int scopeIndex = role.indexOf('.');
+        if (scopeIndex > -1) {
+            appName = role.substring(0, scopeIndex);
+            AccessToken.Access access = token.getResourceAccess(appName);
+            if (access == null) return token;
+
+            role = role.substring(scopeIndex + 1);
+            if (!access.getRoles().contains(role)) return token;
+            access.getRoles().remove(role);
+        } else {
+            AccessToken.Access access = token.getRealmAccess();
+            if (access == null) return token;
+            access.getRoles().remove(role);
+        }
+
+        String newAppName = null;
+        scopeIndex = newName.indexOf('.');
+        if (scopeIndex > -1) {
+            newAppName = role.substring(0, scopeIndex);
+            newName = role.substring(scopeIndex + 1);
+            token.addAccess(newAppName).addRole(newName);
+        }
+        return token;
+    }
+}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCUserAttributeMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCUserAttributeMapper.java
index f0f5657..b2ea92d 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCUserAttributeMapper.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCUserAttributeMapper.java
@@ -35,7 +35,7 @@ public class OIDCUserAttributeMapper extends AbstractOIDCProtocolMapper implemen
         configProperties.add(property);
         property = new ConfigProperty();
         property.setName(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME);
-        property.setLabel(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME);
+        property.setLabel(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME_LABEL);
         property.setType(ConfigProperty.STRING_TYPE);
         property.setHelpText("Name of the claim to insert into the token.  This can be a fully qualified name like 'address.street'.  In this case, a nested json object will be created.");
         configProperties.add(property);
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCUserModelMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCUserModelMapper.java
index cd0cd03..3b559e2 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCUserModelMapper.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCUserModelMapper.java
@@ -34,7 +34,7 @@ public class OIDCUserModelMapper extends AbstractOIDCProtocolMapper implements O
         configProperties.add(property);
         property = new ConfigProperty();
         property.setName(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME);
-        property.setLabel(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME);
+        property.setLabel(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME_LABEL);
         property.setType(ConfigProperty.STRING_TYPE);
         property.setHelpText("Name of the claim to insert into the token.  This can be a fully qualified name like 'address.street'.  In this case, a nested json object will be created.");
         configProperties.add(property);
diff --git a/services/src/main/java/org/keycloak/protocol/ProtocolMapper.java b/services/src/main/java/org/keycloak/protocol/ProtocolMapper.java
index ff6e105..d986dec 100755
--- a/services/src/main/java/org/keycloak/protocol/ProtocolMapper.java
+++ b/services/src/main/java/org/keycloak/protocol/ProtocolMapper.java
@@ -18,12 +18,13 @@ public interface ProtocolMapper extends Provider, ProviderFactory<ProtocolMapper
     public static class ConfigProperty {
         public static final String BOOLEAN_TYPE="boolean";
         public static final String STRING_TYPE="String";
+        public static final String LIST_TYPE="List";
 
         protected String name;
         protected String label;
         protected String helpText;
         protected String type;
-        protected String defaultValue;
+        protected Object defaultValue;
 
         public String getName() {
             return name;
@@ -49,11 +50,11 @@ public interface ProtocolMapper extends Provider, ProviderFactory<ProtocolMapper
             this.type = type;
         }
 
-        public String getDefaultValue() {
+        public Object getDefaultValue() {
             return defaultValue;
         }
 
-        public void setDefaultValue(String defaultValue) {
+        public void setDefaultValue(Object defaultValue) {
             this.defaultValue = defaultValue;
         }
 
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 137b29a..f8cc934 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
@@ -2,6 +2,9 @@ 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
 
 
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java
index b00bb73..15dfca3 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java
@@ -1,158 +1,158 @@
-/*
- * JBoss, Home of Professional Open Source.
- * Copyright 2012, Red Hat, Inc., and individual contributors
- * as indicated by the @author tags. See the copyright.txt file in the
- * distribution for a full listing of individual contributors.
- *
- * This is free software; you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as
- * published by the Free Software Foundation; either version 2.1 of
- * the License, or (at your option) any later version.
- *
- * This software is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this software; if not, write to the Free
- * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
- * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
- */
-package org.keycloak.testsuite.adapter;
-
-import org.junit.ClassRule;
-import org.junit.Rule;
-import org.junit.Test;
-import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.RealmModel;
-import org.keycloak.services.managers.RealmManager;
-import org.keycloak.testsuite.rule.AbstractKeycloakRule;
-
-import java.net.URL;
-import java.security.PublicKey;
-
-/**
- * Tests Undertow Adapter
- *
- * @author <a href="mailto:bburke@redhat.com">Bill Burke</a>
- */
-public class AdapterTest {
-
-    public static PublicKey realmPublicKey;
-    @ClassRule
-    public static AbstractKeycloakRule keycloakRule = new AbstractKeycloakRule() {
-        @Override
-        protected void configure(KeycloakSession session, RealmManager manager, RealmModel adminRealm) {
-            RealmModel realm = AdapterTestStrategy.baseAdapterTestInitialization(session, manager, adminRealm, getClass());
-            realmPublicKey = realm.getPublicKey();
-
-            URL url = getClass().getResource("/adapter-test/cust-app-keycloak.json");
-            deployApplication("customer-portal", "/customer-portal", CustomerServlet.class, url.getPath(), "user");
-            url = getClass().getResource("/adapter-test/secure-portal-keycloak.json");
-            deployApplication("secure-portal", "/secure-portal", CallAuthenticatedServlet.class, url.getPath(), "user", false);
-            url = getClass().getResource("/adapter-test/customer-db-keycloak.json");
-            deployApplication("customer-db", "/customer-db", CustomerDatabaseServlet.class, url.getPath(), "user");
-            url = getClass().getResource("/adapter-test/product-keycloak.json");
-            deployApplication("product-portal", "/product-portal", ProductServlet.class, url.getPath(), "user");
-
-            // Test that replacing system properties works for adapters
-            System.setProperty("app.server.base.url", "http://localhost:8081");
-            System.setProperty("my.host.name", "localhost");
-            url = getClass().getResource("/adapter-test/session-keycloak.json");
-            deployApplication("session-portal", "/session-portal", SessionServlet.class, url.getPath(), "user");
-            url = getClass().getResource("/adapter-test/input-keycloak.json");
-            deployApplication("input-portal", "/input-portal", InputServlet.class, url.getPath(), "user", true, null, "/secured/*");
-        }
-    };
-
-    @Rule
-    public AdapterTestStrategy testStrategy = new AdapterTestStrategy("http://localhost:8081/auth", "http://localhost:8081", keycloakRule);
-
-    @Test
-    public void testLoginSSOAndLogout() throws Exception {
-        testStrategy.testLoginSSOAndLogout();
-    }
-
-    @Test
-    public void testSavedPostRequest() throws Exception {
-        testStrategy.testSavedPostRequest();
-    }
-
-    @Test
-    public void testServletRequestLogout() throws Exception {
-        testStrategy.testServletRequestLogout();
-    }
-
-    @Test
-    public void testLoginSSOIdle() throws Exception {
-        testStrategy.testLoginSSOIdle();
-
-    }
-
-    @Test
-    public void testLoginSSOIdleRemoveExpiredUserSessions() throws Exception {
-        testStrategy.testLoginSSOIdleRemoveExpiredUserSessions();
-    }
-
-    @Test
-    public void testLoginSSOMax() throws Exception {
-        testStrategy.testLoginSSOMax();
-    }
-
-    /**
-     * KEYCLOAK-518
-     * @throws Exception
-     */
-    @Test
-    public void testNullBearerToken() throws Exception {
-        testStrategy.testNullBearerToken();
-    }
-
-    /**
-     * KEYCLOAK-518
-     * @throws Exception
-     */
-    @Test
-    public void testBadUser() throws Exception {
-        testStrategy.testBadUser();
-    }
-
-    @Test
-    public void testVersion() throws Exception {
-        testStrategy.testVersion();
-    }
-
-    @Test
-    public void testAuthenticated() throws Exception {
-        testStrategy.testAuthenticated();
-    }
-
-    /**
-     * KEYCLOAK-732
-     *
-     * @throws Throwable
-     */
-    @Test
-    public void testSingleSessionInvalidated() throws Throwable {
-        testStrategy.testSingleSessionInvalidated();
-    }
-
-    /**
-     * KEYCLOAK-741
-     */
-    @Test
-    public void testSessionInvalidatedAfterFailedRefresh() throws Throwable {
-        testStrategy.testSessionInvalidatedAfterFailedRefresh();
-
-    }
-
-    /**
-     * KEYCLOAK-942
-     */
-    @Test
-    public void testAdminApplicationLogout() throws Throwable {
-        testStrategy.testAdminApplicationLogout();
-    }
-
-}
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2012, Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.keycloak.testsuite.adapter;
+
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.services.managers.RealmManager;
+import org.keycloak.testsuite.rule.AbstractKeycloakRule;
+
+import java.net.URL;
+import java.security.PublicKey;
+
+/**
+ * Tests Undertow Adapter
+ *
+ * @author <a href="mailto:bburke@redhat.com">Bill Burke</a>
+ */
+public class AdapterTest {
+
+    public static PublicKey realmPublicKey;
+    @ClassRule
+    public static AbstractKeycloakRule keycloakRule = new AbstractKeycloakRule() {
+        @Override
+        protected void configure(KeycloakSession session, RealmManager manager, RealmModel adminRealm) {
+            RealmModel realm = AdapterTestStrategy.baseAdapterTestInitialization(session, manager, adminRealm, getClass());
+            realmPublicKey = realm.getPublicKey();
+
+            URL url = getClass().getResource("/adapter-test/cust-app-keycloak.json");
+            deployApplication("customer-portal", "/customer-portal", CustomerServlet.class, url.getPath(), "user");
+            url = getClass().getResource("/adapter-test/secure-portal-keycloak.json");
+            deployApplication("secure-portal", "/secure-portal", CallAuthenticatedServlet.class, url.getPath(), "user", false);
+            url = getClass().getResource("/adapter-test/customer-db-keycloak.json");
+            deployApplication("customer-db", "/customer-db", CustomerDatabaseServlet.class, url.getPath(), "user");
+            url = getClass().getResource("/adapter-test/product-keycloak.json");
+            deployApplication("product-portal", "/product-portal", ProductServlet.class, url.getPath(), "user");
+
+            // Test that replacing system properties works for adapters
+            System.setProperty("app.server.base.url", "http://localhost:8081");
+            System.setProperty("my.host.name", "localhost");
+            url = getClass().getResource("/adapter-test/session-keycloak.json");
+            deployApplication("session-portal", "/session-portal", SessionServlet.class, url.getPath(), "user");
+            url = getClass().getResource("/adapter-test/input-keycloak.json");
+            deployApplication("input-portal", "/input-portal", InputServlet.class, url.getPath(), "user", true, null, "/secured/*");
+        }
+    };
+
+    @Rule
+    public AdapterTestStrategy testStrategy = new AdapterTestStrategy("http://localhost:8081/auth", "http://localhost:8081", keycloakRule);
+
+    @Test
+    public void testLoginSSOAndLogout() throws Exception {
+        testStrategy.testLoginSSOAndLogout();
+    }
+
+    @Test
+    public void testSavedPostRequest() throws Exception {
+        testStrategy.testSavedPostRequest();
+    }
+
+    @Test
+    public void testServletRequestLogout() throws Exception {
+        testStrategy.testServletRequestLogout();
+    }
+
+    @Test
+    public void testLoginSSOIdle() throws Exception {
+        testStrategy.testLoginSSOIdle();
+
+    }
+
+    @Test
+    public void testLoginSSOIdleRemoveExpiredUserSessions() throws Exception {
+        testStrategy.testLoginSSOIdleRemoveExpiredUserSessions();
+    }
+
+    @Test
+    public void testLoginSSOMax() throws Exception {
+        testStrategy.testLoginSSOMax();
+    }
+
+    /**
+     * KEYCLOAK-518
+     * @throws Exception
+     */
+    @Test
+    public void testNullBearerToken() throws Exception {
+        testStrategy.testNullBearerToken();
+    }
+
+    /**
+     * KEYCLOAK-518
+     * @throws Exception
+     */
+    @Test
+    public void testBadUser() throws Exception {
+        testStrategy.testBadUser();
+    }
+
+    @Test
+    public void testVersion() throws Exception {
+        testStrategy.testVersion();
+    }
+
+    @Test
+    public void testAuthenticated() throws Exception {
+        testStrategy.testAuthenticated();
+    }
+
+    /**
+     * KEYCLOAK-732
+     *
+     * @throws Throwable
+     */
+    @Test
+    public void testSingleSessionInvalidated() throws Throwable {
+        testStrategy.testSingleSessionInvalidated();
+    }
+
+    /**
+     * KEYCLOAK-741
+     */
+    @Test
+    public void testSessionInvalidatedAfterFailedRefresh() throws Throwable {
+        testStrategy.testSessionInvalidatedAfterFailedRefresh();
+
+    }
+
+    /**
+     * KEYCLOAK-942
+     */
+    @Test
+    public void testAdminApplicationLogout() throws Throwable {
+        testStrategy.testAdminApplicationLogout();
+    }
+
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/AbstractKeycloakRule.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/AbstractKeycloakRule.java
index 169ff3a..d1e37b3 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/AbstractKeycloakRule.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/AbstractKeycloakRule.java
@@ -128,7 +128,7 @@ public abstract class AbstractKeycloakRule extends ExternalResource {
     }
 
 
-    private DeploymentInfo createDeploymentInfo(String name, String contextPath, Class<? extends Servlet> servletClass) {
+    public DeploymentInfo createDeploymentInfo(String name, String contextPath, Class<? extends Servlet> servletClass) {
         DeploymentInfo deploymentInfo = new DeploymentInfo();
         deploymentInfo.setClassLoader(getClass().getClassLoader());
         deploymentInfo.setDeploymentName(name);
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 db33eb1..8f8f2b4 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
@@ -21,7 +21,19 @@ import org.keycloak.testsuite.pages.LoginPage;
 import org.keycloak.testsuite.rule.WebResource;
 import org.keycloak.testsuite.rule.WebRule;
 import org.openqa.selenium.WebDriver;
-
+import org.picketlink.common.constants.JBossSAMLURIConstants;
+import org.picketlink.identity.federation.api.saml.v2.response.SAML2Response;
+import org.picketlink.identity.federation.core.saml.v2.constants.X500SAMLProfileConstants;
+import org.picketlink.identity.federation.saml.v2.assertion.AssertionType;
+import org.picketlink.identity.federation.saml.v2.assertion.AttributeStatementType;
+import org.picketlink.identity.federation.saml.v2.assertion.AttributeType;
+import org.picketlink.identity.federation.saml.v2.protocol.ResponseType;
+import org.picketlink.identity.federation.web.util.PostBindingUtil;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
 import javax.ws.rs.client.Client;
 import javax.ws.rs.client.ClientBuilder;
 import javax.ws.rs.client.ClientRequestContext;
@@ -32,6 +44,7 @@ import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriBuilder;
+import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 
@@ -54,11 +67,15 @@ public class SamlBindingTest {
             initializeSamlSecuredWar("/saml/signed-post-persistent", "/sales-post-sig-persistent",  "post-sig-persistent.war", classLoader);
             initializeSamlSecuredWar("/saml/signed-metadata", "/sales-metadata",  "post-metadata.war", classLoader);
             initializeSamlSecuredWar("/saml/signed-get", "/employee-sig",  "employee-sig.war", classLoader);
+            //initializeSamlSecuredWar("/saml/simple-get", "/employee",  "employee.war", classLoader);
             initializeSamlSecuredWar("/saml/signed-front-get", "/employee-sig-front",  "employee-sig-front.war", classLoader);
             initializeSamlSecuredWar("/saml/bad-client-signed-post", "/bad-client-sales-post-sig",  "bad-client-post-sig.war", classLoader);
             initializeSamlSecuredWar("/saml/bad-realm-signed-post", "/bad-realm-sales-post-sig",  "bad-realm-post-sig.war", classLoader);
             initializeSamlSecuredWar("/saml/encrypted-post", "/sales-post-enc",  "post-enc.war", classLoader);
             uploadSP();
+            server.getServer().deploy(createDeploymentInfo("employee.war", "/employee", SamlSPFacade.class));
+
+
 
         }
 
@@ -68,6 +85,30 @@ public class SamlBindingTest {
         }
     };
 
+    public static class SamlSPFacade extends HttpServlet {
+        public static String samlResponse;
+
+        @Override
+        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+            handler(req, resp);
+        }
+
+        @Override
+        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+            handler(req, resp);
+        }
+
+        private void handler(HttpServletRequest req, HttpServletResponse resp) {
+            System.out.println("********* HERE ******");
+            if (req.getParameterMap().isEmpty()) {
+                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;
+            }
+            samlResponse = req.getParameter("SAMLResponse");
+        }
+    }
+
     @Rule
     public WebRule webRule = new WebRule(this);
     @WebResource
@@ -151,6 +192,52 @@ public class SamlBindingTest {
         checkLoggedOut("http://localhost:8081/sales-post-sig-email/");
 
     }
+
+
+    @Test
+    public void testAttributes() throws Exception {
+        // 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;
+                }
+            }
+
+        }
+
+        Assert.assertTrue(email);
+        Assert.assertTrue(phone);
+
+    }
+
     @Test
     public void testRedirectSignedLoginLogout() {
         driver.navigate().to("http://localhost:8081/employee-sig/");
diff --git a/testsuite/integration/src/test/resources/saml/simple-get/WEB-INF/picketlink.xml b/testsuite/integration/src/test/resources/saml/simple-get/WEB-INF/picketlink.xml
index 1d170ee..7636260 100755
--- a/testsuite/integration/src/test/resources/saml/simple-get/WEB-INF/picketlink.xml
+++ b/testsuite/integration/src/test/resources/saml/simple-get/WEB-INF/picketlink.xml
@@ -12,8 +12,6 @@
 			class="org.picketlink.identity.federation.web.handlers.saml2.SAML2AuthenticationHandler">
 
             <Option Key="NAMEID_FORMAT" Value="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"/>
-			<Option Key="ASSERTION_SESSION_ATTRIBUTE_NAME" Value="org.picketlink.sp.assertion"/>
-			
 		</Handler>
 		<Handler
 			class="org.picketlink.identity.federation.web.handlers.saml2.RolesGenerationHandler" />
diff --git a/testsuite/integration/src/test/resources/saml/testsaml.json b/testsuite/integration/src/test/resources/saml/testsaml.json
index 9635c0c..0baffb5 100755
--- a/testsuite/integration/src/test/resources/saml/testsaml.json
+++ b/testsuite/integration/src/test/resources/saml/testsaml.json
@@ -24,7 +24,10 @@
                 { "type" : "password",
                   "value" : "password" }
             ],
-            "realmRoles": ["manager"]
+            "attributes" : {
+                "phone": "617"
+            },
+            "realmRoles": ["manager", "user"]
         }
     ],
     "applications": [
@@ -218,6 +221,45 @@
             }
         },
         {
+            "name": "http://localhost:8081/employee/",
+            "enabled": true,
+            "protocol": "saml",
+            "fullScopeAllowed": true,
+            "baseUrl": "http://localhost:8081/employee",
+            "redirectUris": [
+                "http://localhost:8081/employee/*"
+            ],
+            "adminUrl": "http://localhost:8081/employee/",
+            "attributes": {
+                "saml.authnstatement": "true"
+            },
+            "protocolMappers": [
+                {
+                    "name": "email",
+                    "protocol": "saml",
+                    "protocolMapper": "saml-user-property-mapper",
+                    "consentRequired": false,
+                    "config": {
+                        "user.attribute": "email",
+                        "friendly.name": "email",
+                        "attribute.name": "urn:oid:1.2.840.113549.1.9.1",
+                        "attribute.nameformat": "URI Reference"
+                    }
+                },
+                {
+                    "name": "phone",
+                    "protocol": "saml",
+                    "protocolMapper": "saml-user-attribute-mapper",
+                    "consentRequired": false,
+                    "config": {
+                        "user.attribute": "phone",
+                        "attribute.name": "phone",
+                        "attribute.nameformat": "Basic"
+                    }
+                }
+            ]
+        },
+        {
             "name": "http://localhost:8081/employee-sig-front/",
             "enabled": true,
             "protocol": "saml",
@@ -246,6 +288,10 @@
             {
                 "name": "manager",
                 "description": "Have Manager privileges"
+            },
+            {
+                "name": "user",
+                "description": "Have User privileges"
             }
         ]
     }