keycloak-uncached

Merge pull request #3345 from mposolda/master KEYCLOAK-3499

10/18/2016 10:28:29 AM

Changes

Details

diff --git a/server-spi/src/main/java/org/keycloak/migration/MigrationModelManager.java b/server-spi/src/main/java/org/keycloak/migration/MigrationModelManager.java
index e2b55e1..a21aa65 100755
--- a/server-spi/src/main/java/org/keycloak/migration/MigrationModelManager.java
+++ b/server-spi/src/main/java/org/keycloak/migration/MigrationModelManager.java
@@ -30,6 +30,7 @@ import org.keycloak.migration.migrators.MigrateTo1_9_2;
 import org.keycloak.migration.migrators.MigrateTo2_0_0;
 import org.keycloak.migration.migrators.MigrateTo2_1_0;
 import org.keycloak.migration.migrators.MigrateTo2_2_0;
+import org.keycloak.migration.migrators.MigrateTo2_3_0;
 import org.keycloak.migration.migrators.Migration;
 import org.keycloak.models.KeycloakSession;
 
@@ -53,6 +54,7 @@ public class MigrationModelManager {
         new MigrateTo2_0_0(),
         new MigrateTo2_1_0(),
         new MigrateTo2_2_0(),
+        new MigrateTo2_3_0(),
     };
 
     public static void migrate(KeycloakSession session) {
diff --git a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo2_3_0.java b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo2_3_0.java
new file mode 100644
index 0000000..80862ca
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo2_3_0.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.migration.migrators;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import org.keycloak.migration.ModelVersion;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientTemplateModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ProtocolMapperContainerModel;
+import org.keycloak.models.ProtocolMapperModel;
+import org.keycloak.models.RealmModel;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class MigrateTo2_3_0 implements Migration {
+
+    public static final ModelVersion VERSION = new ModelVersion("2.3.0");
+
+    @Override
+    public void migrate(KeycloakSession session) {
+        for (RealmModel realm : session.realms().getRealms()) {
+            for (ClientModel client : realm.getClients()) {
+                updateProtocolMappers(client);
+            }
+
+            for (ClientTemplateModel clientTemplate : realm.getClientTemplates()) {
+                updateProtocolMappers(clientTemplate);
+            }
+        }
+    }
+
+    private void updateProtocolMappers(ProtocolMapperContainerModel client) {
+        List<ProtocolMapperModel> toUpdate = new LinkedList<>();
+        for (ProtocolMapperModel mapper : client.getProtocolMappers()) {
+            if (!mapper.getConfig().containsKey("userinfo.token.claim") && mapper.getConfig().containsKey("id.token.claim")) {
+                mapper.getConfig().put("userinfo.token.claim", mapper.getConfig().get("id.token.claim"));
+                toUpdate.add(mapper);
+            }
+        }
+
+        for (ProtocolMapperModel mapper : toUpdate) {
+            client.updateProtocolMapper(mapper);
+        }
+    }
+
+    @Override
+    public ModelVersion getVersion() {
+        return VERSION;
+    }
+}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractOIDCProtocolMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractOIDCProtocolMapper.java
index 7c5b3b9..efe9434 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractOIDCProtocolMapper.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractOIDCProtocolMapper.java
@@ -18,10 +18,15 @@
 package org.keycloak.protocol.oidc.mappers;
 
 import org.keycloak.Config;
+import org.keycloak.models.ClientSessionModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.ProtocolMapperModel;
+import org.keycloak.models.UserSessionModel;
 import org.keycloak.protocol.ProtocolMapper;
 import org.keycloak.protocol.oidc.OIDCLoginProtocol;
+import org.keycloak.representations.AccessToken;
+import org.keycloak.representations.IDToken;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -54,4 +59,46 @@ public abstract class AbstractOIDCProtocolMapper implements ProtocolMapper {
     public void postInit(KeycloakSessionFactory factory) {
 
     }
+
+    public AccessToken transformUserInfoToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
+                                              UserSessionModel userSession, ClientSessionModel clientSession) {
+
+        if (!OIDCAttributeMapperHelper.includeInUserInfo(mappingModel)) {
+            return token;
+        }
+
+        setClaim(token, mappingModel, userSession);
+        return token;
+    }
+
+    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;
+    }
+
+    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;
+    }
+
+    /**
+     * Intended to be overridden in {@link ProtocolMapper} implementations to add claims to an token.
+     * @param token
+     * @param mappingModel
+     * @param userSession
+     */
+    protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession) {
+    }
 }
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractUserRoleMappingMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractUserRoleMappingMapper.java
index 3e639aa..de4d054 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractUserRoleMappingMapper.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractUserRoleMappingMapper.java
@@ -35,33 +35,7 @@ import java.util.Set;
  *
  * @author <a href="mailto:thomas.darimont@gmail.com">Thomas Darimont</a>
  */
-abstract class AbstractUserRoleMappingMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper {
-
-    @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;
-    }
-
-    @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;
-    }
-
-
-    protected abstract void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession);
+abstract class AbstractUserRoleMappingMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper, UserInfoTokenMapper {
 
     /**
      * Returns the role names extracted from the given {@code roleModels} while recursively traversing "Composite Roles".
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/AddressMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AddressMapper.java
index 4f5d9cf..674c9ff 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/AddressMapper.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AddressMapper.java
@@ -39,26 +39,12 @@ import java.util.Map;
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
  */
-public class AddressMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper {
+public class AddressMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper, UserInfoTokenMapper {
 
     private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
 
     static {
-        ProviderConfigProperty property;
-        property = new ProviderConfigProperty();
-        property.setName(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN);
-        property.setLabel(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN_LABEL);
-        property.setType(ProviderConfigProperty.BOOLEAN_TYPE);
-        property.setDefaultValue("true");
-        property.setHelpText(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN_HELP_TEXT);
-        configProperties.add(property);
-        property = new ProviderConfigProperty();
-        property.setName(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN);
-        property.setLabel(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN_LABEL);
-        property.setType(ProviderConfigProperty.BOOLEAN_TYPE);
-        property.setDefaultValue("true");
-        property.setHelpText(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN_HELP_TEXT);
-        configProperties.add(property);
+        OIDCAttributeMapperHelper.addIncludeInTokensConfig(configProperties, AddressMapper.class);
     }
 
     public static final String PROVIDER_ID = "oidc-address-mapper";
@@ -118,21 +104,7 @@ public class AddressMapper extends AbstractOIDCProtocolMapper implements OIDCAcc
     }
 
     @Override
-    public AccessToken transformAccessToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
-                                            UserSessionModel userSession, ClientSessionModel clientSession) {
-        if (!OIDCAttributeMapperHelper.includeInAccessToken(mappingModel)) return token;
-        setClaim(token, userSession);
-        return token;
-    }
-
-    @Override
-    public IDToken transformIDToken(IDToken token, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) {
-        if (!OIDCAttributeMapperHelper.includeInIDToken(mappingModel)) return token;
-        setClaim(token, userSession);
-        return token;
-    }
-
-    protected void setClaim(IDToken token, UserSessionModel userSession) {
+    protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession) {
         UserModel user = userSession.getUser();
         AddressClaimSet addressSet = new AddressClaimSet();
         addressSet.setStreetAddress(user.getFirstAttribute("street"));
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/FullNameMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/FullNameMapper.java
index 107e163..1e4ad9d 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/FullNameMapper.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/FullNameMapper.java
@@ -38,26 +38,12 @@ import java.util.Map;
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
  */
-public class FullNameMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper {
+public class FullNameMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper, UserInfoTokenMapper {
 
     private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
 
     static {
-        ProviderConfigProperty property;
-        property = new ProviderConfigProperty();
-        property.setName(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN);
-        property.setLabel(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN_LABEL);
-        property.setType(ProviderConfigProperty.BOOLEAN_TYPE);
-        property.setDefaultValue("true");
-        property.setHelpText(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN_HELP_TEXT);
-        configProperties.add(property);
-        property = new ProviderConfigProperty();
-        property.setName(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN);
-        property.setLabel(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN_LABEL);
-        property.setType(ProviderConfigProperty.BOOLEAN_TYPE);
-        property.setDefaultValue("true");
-        property.setHelpText(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN_HELP_TEXT);
-        configProperties.add(property);
+        OIDCAttributeMapperHelper.addIncludeInTokensConfig(configProperties, FullNameMapper.class);
 
     }
 
@@ -88,28 +74,13 @@ public class FullNameMapper extends AbstractOIDCProtocolMapper implements OIDCAc
         return "Maps the user's first and last name to the OpenID Connect 'name' claim. Format is <first> + ' ' + <last>";
     }
 
-    @Override
-    public AccessToken transformAccessToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
-                                            UserSessionModel userSession, ClientSessionModel clientSession) {
-        if (!OIDCAttributeMapperHelper.includeInAccessToken(mappingModel)) return token;
-        setClaim(token, userSession);
-        return token;
-    }
-
-    protected void setClaim(IDToken token, UserSessionModel userSession) {
+    protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession) {
         UserModel user = userSession.getUser();
         String first = user.getFirstName() == null ? "" : user.getFirstName() + " ";
         String last = user.getLastName() == null ? "" : user.getLastName();
         token.getOtherClaims().put("name", first + last);
     }
 
-    @Override
-    public IDToken transformIDToken(IDToken token, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) {
-        if (!OIDCAttributeMapperHelper.includeInIDToken(mappingModel)) return token;
-        setClaim(token, userSession);
-        return token;
-    }
-
     public static ProtocolMapperModel create(String name,
                                                         boolean consentRequired, String consentText,
                                                         boolean accessToken, boolean idToken) {
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/GroupMembershipMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/GroupMembershipMapper.java
index d1410c3..41dbb47 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/GroupMembershipMapper.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/GroupMembershipMapper.java
@@ -40,21 +40,13 @@ import java.util.Map;
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
  */
-public class GroupMembershipMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper {
+public class GroupMembershipMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper, UserInfoTokenMapper {
 
     private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
 
     static {
-        ProviderConfigProperty property;
-        ProviderConfigProperty property1;
-        property1 = new ProviderConfigProperty();
-        property1.setName(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME);
-        property1.setLabel(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME_LABEL);
-        property1.setType(ProviderConfigProperty.STRING_TYPE);
-        property1.setDefaultValue("groups");
-        property1.setHelpText(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME_TOOLTIP);
-        configProperties.add(property1);
-        property1 = new ProviderConfigProperty();
+        OIDCAttributeMapperHelper.addTokenClaimNameConfig(configProperties);
+        ProviderConfigProperty property1 = new ProviderConfigProperty();
         property1.setName("full.path");
         property1.setLabel("Full group path");
         property1.setType(ProviderConfigProperty.BOOLEAN_TYPE);
@@ -62,23 +54,7 @@ public class GroupMembershipMapper extends AbstractOIDCProtocolMapper implements
         property1.setHelpText("Include full path to group i.e. /top/level1/level2, false will just specify the group name");
         configProperties.add(property1);
 
-        property1 = new ProviderConfigProperty();
-        property1.setName(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN);
-        property1.setLabel(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN_LABEL);
-        property1.setType(ProviderConfigProperty.BOOLEAN_TYPE);
-        property1.setDefaultValue("true");
-        property1.setHelpText(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN_HELP_TEXT);
-        configProperties.add(property1);
-        property1 = new ProviderConfigProperty();
-        property1.setName(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN);
-        property1.setLabel(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN_LABEL);
-        property1.setType(ProviderConfigProperty.BOOLEAN_TYPE);
-        property1.setDefaultValue("true");
-        property1.setHelpText(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN_HELP_TEXT);
-        configProperties.add(property1);
-
-
-
+        OIDCAttributeMapperHelper.addIncludeInTokensConfig(configProperties, GroupMembershipMapper.class);
     }
 
     public static final String PROVIDER_ID = "oidc-group-membership-mapper";
@@ -113,15 +89,14 @@ public class GroupMembershipMapper extends AbstractOIDCProtocolMapper implements
     }
 
 
-    @Override
-    public AccessToken transformAccessToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
-                                            UserSessionModel userSession, ClientSessionModel clientSession) {
-        if (!OIDCAttributeMapperHelper.includeInAccessToken(mappingModel)) return token;
-        buildMembership(token, mappingModel, userSession);
-        return token;
-    }
+    /**
+     * Adds the group membership information to the {@link IDToken#otherClaims}.
+     * @param token
+     * @param mappingModel
+     * @param userSession
+     */
+    protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession) {
 
-    public void buildMembership(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession) {
         List<String> membership = new LinkedList<>();
         boolean fullPath = useFullPath(mappingModel);
         for (GroupModel group : userSession.getUser().getGroups()) {
@@ -136,13 +111,6 @@ public class GroupMembershipMapper extends AbstractOIDCProtocolMapper implements
         token.getOtherClaims().put(protocolClaim, membership);
     }
 
-    @Override
-    public IDToken transformIDToken(IDToken token, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) {
-        if (!OIDCAttributeMapperHelper.includeInIDToken(mappingModel)) return token;
-        buildMembership(token, mappingModel, userSession);
-        return token;
-    }
-
     public static ProtocolMapperModel create(String name,
                                       String tokenClaimName,
                                       boolean consentRequired, String consentText,
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/HardcodedClaim.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/HardcodedClaim.java
index ffaea82..4062824 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/HardcodedClaim.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/HardcodedClaim.java
@@ -37,53 +37,24 @@ import java.util.Map;
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
  */
-public class HardcodedClaim extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper {
+public class HardcodedClaim extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper, UserInfoTokenMapper {
 
     private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
 
     public static final String CLAIM_VALUE = "claim.value";
 
     static {
-        ProviderConfigProperty property;
-        property = new ProviderConfigProperty();
-        property.setName(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME);
-        property.setLabel(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME_LABEL);
-        property.setType(ProviderConfigProperty.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 ProviderConfigProperty();
+        OIDCAttributeMapperHelper.addTokenClaimNameConfig(configProperties);
+
+        ProviderConfigProperty property = new ProviderConfigProperty();
         property.setName(CLAIM_VALUE);
         property.setLabel("Claim value");
         property.setType(ProviderConfigProperty.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 ProviderConfigProperty();
-        property.setName(OIDCAttributeMapperHelper.JSON_TYPE);
-        property.setLabel(OIDCAttributeMapperHelper.JSON_TYPE);
-        List<String> types = new ArrayList(3);
-        types.add("String");
-        types.add("long");
-        types.add("int");
-        types.add("boolean");
-        property.setType(ProviderConfigProperty.LIST_TYPE);
-        property.setOptions(types);
-        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 ProviderConfigProperty();
-        property.setName(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN);
-        property.setLabel(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN_LABEL);
-        property.setType(ProviderConfigProperty.BOOLEAN_TYPE);
-        property.setDefaultValue("true");
-        property.setHelpText(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN_HELP_TEXT);
-        configProperties.add(property);
-        property = new ProviderConfigProperty();
-        property.setName(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN);
-        property.setLabel(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN_LABEL);
-        property.setType(ProviderConfigProperty.BOOLEAN_TYPE);
-        property.setDefaultValue("true");
-        property.setHelpText(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN_HELP_TEXT);
-        configProperties.add(property);
 
+        OIDCAttributeMapperHelper.addJsonTypeConfig(configProperties);
+        OIDCAttributeMapperHelper.addIncludeInTokensConfig(configProperties, HardcodedClaim.class);
     }
 
     public static final String PROVIDER_ID = "oidc-hardcoded-claim-mapper";
@@ -113,28 +84,13 @@ public class HardcodedClaim extends AbstractOIDCProtocolMapper implements OIDCAc
         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 create(String name,
                                       String hardcodedName,
                                       String hardcodedValue, String claimType,
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/HardcodedRole.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/HardcodedRole.java
index 5529857..03ecb91 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/HardcodedRole.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/HardcodedRole.java
@@ -83,6 +83,7 @@ public class HardcodedRole extends AbstractOIDCProtocolMapper implements OIDCAcc
     @Override
     public AccessToken transformAccessToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
                                             UserSessionModel userSession, ClientSessionModel clientSession) {
+
         String role = mappingModel.getConfig().get(ROLE_CONFIG);
         String[] scopedRole = KeycloakModelUtils.parseRole(role);
         String appName = scopedRole[0];
@@ -97,6 +98,7 @@ public class HardcodedRole extends AbstractOIDCProtocolMapper implements OIDCAcc
             }
             access.addRole(role);
         }
+
         return token;
     }
 
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 79ecec1..99b2610 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
@@ -18,6 +18,7 @@
 package org.keycloak.protocol.oidc.mappers;
 
 import org.keycloak.models.ProtocolMapperModel;
+import org.keycloak.protocol.ProtocolMapper;
 import org.keycloak.protocol.ProtocolMapperUtils;
 import org.keycloak.protocol.oidc.OIDCLoginProtocol;
 import org.keycloak.provider.ProviderConfigProperty;
@@ -123,7 +124,7 @@ public class OIDCAttributeMapperHelper {
                                                         boolean consentRequired, String consentText,
                                                         boolean accessToken, boolean idToken,
                                                         String mapperId) {
-        return createClaimMapper(name, userAttribute,tokenClaimName, claimType, consentRequired, consentText, accessToken, idToken, false, mapperId);
+        return createClaimMapper(name, userAttribute,tokenClaimName, claimType, consentRequired, consentText, accessToken, idToken, true, mapperId);
     }
 
     public static ProtocolMapperModel createClaimMapper(String name,
@@ -162,18 +163,34 @@ public class OIDCAttributeMapperHelper {
     }
 
     public static boolean includeInUserInfo(ProtocolMapperModel mappingModel){
-        return "true".equals(mappingModel.getConfig().get(INCLUDE_IN_USERINFO));
+        String includeInUserInfo = mappingModel.getConfig().get(INCLUDE_IN_USERINFO);
+
+        // Backwards compatibility
+        if (includeInUserInfo == null && includeInIDToken(mappingModel)) {
+            return true;
+        }
+
+        return "true".equals(includeInUserInfo);
     }
 
-    public static void addAttributeConfig(List<ProviderConfigProperty> configProperties) {
-        ProviderConfigProperty property;
-        property = new ProviderConfigProperty();
+    public static void addAttributeConfig(List<ProviderConfigProperty> configProperties, Class<? extends ProtocolMapper> protocolMapperClass) {
+        addTokenClaimNameConfig(configProperties);
+        addJsonTypeConfig(configProperties);
+
+        addIncludeInTokensConfig(configProperties, protocolMapperClass);
+    }
+
+    public static void addTokenClaimNameConfig(List<ProviderConfigProperty> configProperties) {
+        ProviderConfigProperty property = new ProviderConfigProperty();
         property.setName(TOKEN_CLAIM_NAME);
         property.setLabel(TOKEN_CLAIM_NAME_LABEL);
         property.setType(ProviderConfigProperty.STRING_TYPE);
         property.setHelpText(TOKEN_CLAIM_NAME_TOOLTIP);
         configProperties.add(property);
-        property = new ProviderConfigProperty();
+    }
+
+    public static void addJsonTypeConfig(List<ProviderConfigProperty> configProperties) {
+        ProviderConfigProperty property = new ProviderConfigProperty();
         property.setName(JSON_TYPE);
         property.setLabel(JSON_TYPE);
         List<String> types = new ArrayList(3);
@@ -185,26 +202,37 @@ public class OIDCAttributeMapperHelper {
         property.setOptions(types);
         property.setHelpText(JSON_TYPE_TOOLTIP);
         configProperties.add(property);
-        property = new ProviderConfigProperty();
-        property.setName(INCLUDE_IN_ID_TOKEN);
-        property.setLabel(INCLUDE_IN_ID_TOKEN_LABEL);
-        property.setType(ProviderConfigProperty.BOOLEAN_TYPE);
-        property.setDefaultValue("true");
-        property.setHelpText(INCLUDE_IN_ID_TOKEN_HELP_TEXT);
-        configProperties.add(property);
-        property = new ProviderConfigProperty();
-        property.setName(INCLUDE_IN_ACCESS_TOKEN);
-        property.setLabel(INCLUDE_IN_ACCESS_TOKEN_LABEL);
-        property.setType(ProviderConfigProperty.BOOLEAN_TYPE);
-        property.setDefaultValue("true");
-        property.setHelpText(INCLUDE_IN_ACCESS_TOKEN_HELP_TEXT);
-        configProperties.add(property);
-        property = new ProviderConfigProperty();
-        property.setName(INCLUDE_IN_USERINFO);
-        property.setLabel(INCLUDE_IN_USERINFO_LABEL);
-        property.setType(ProviderConfigProperty.BOOLEAN_TYPE);
-        property.setDefaultValue("false");
-        property.setHelpText(INCLUDE_IN_USERINFO_HELP_TEXT);
-        configProperties.add(property);
+    }
+
+    public static void addIncludeInTokensConfig(List<ProviderConfigProperty> configProperties, Class<? extends ProtocolMapper> protocolMapperClass) {
+        if (OIDCIDTokenMapper.class.isAssignableFrom(protocolMapperClass)) {
+            ProviderConfigProperty property = new ProviderConfigProperty();
+            property.setName(INCLUDE_IN_ID_TOKEN);
+            property.setLabel(INCLUDE_IN_ID_TOKEN_LABEL);
+            property.setType(ProviderConfigProperty.BOOLEAN_TYPE);
+            property.setDefaultValue("true");
+            property.setHelpText(INCLUDE_IN_ID_TOKEN_HELP_TEXT);
+            configProperties.add(property);
+        }
+
+        if (OIDCAccessTokenMapper.class.isAssignableFrom(protocolMapperClass)) {
+            ProviderConfigProperty property = new ProviderConfigProperty();
+            property.setName(INCLUDE_IN_ACCESS_TOKEN);
+            property.setLabel(INCLUDE_IN_ACCESS_TOKEN_LABEL);
+            property.setType(ProviderConfigProperty.BOOLEAN_TYPE);
+            property.setDefaultValue("true");
+            property.setHelpText(INCLUDE_IN_ACCESS_TOKEN_HELP_TEXT);
+            configProperties.add(property);
+        }
+
+        if (UserInfoTokenMapper.class.isAssignableFrom(protocolMapperClass)) {
+            ProviderConfigProperty property = new ProviderConfigProperty();
+            property.setName(INCLUDE_IN_USERINFO);
+            property.setLabel(INCLUDE_IN_USERINFO_LABEL);
+            property.setType(ProviderConfigProperty.BOOLEAN_TYPE);
+            property.setDefaultValue("true");
+            property.setHelpText(INCLUDE_IN_USERINFO_HELP_TEXT);
+            configProperties.add(property);
+        }
     }
 }
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/RoleNameMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/RoleNameMapper.java
index 5f54c07..fcdc373 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/RoleNameMapper.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/RoleNameMapper.java
@@ -25,6 +25,7 @@ import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.protocol.oidc.OIDCLoginProtocol;
 import org.keycloak.provider.ProviderConfigProperty;
 import org.keycloak.representations.AccessToken;
+import org.keycloak.representations.IDToken;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -88,8 +89,8 @@ public class RoleNameMapper extends AbstractOIDCProtocolMapper implements OIDCAc
     }
 
     @Override
-    public AccessToken transformAccessToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session2,
-                                            UserSessionModel userSession2, ClientSessionModel clientSessio2n) {
+    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);
 
@@ -120,6 +121,7 @@ public class RoleNameMapper extends AbstractOIDCProtocolMapper implements OIDCAc
         } else {
             access = token.addAccess(newAppName);
         }
+
         access.addRole(newRoleName);
         return token;
     }
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserAttributeMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserAttributeMapper.java
index 56e7a48..e6d0d20 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserAttributeMapper.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserAttributeMapper.java
@@ -51,7 +51,7 @@ public class UserAttributeMapper extends AbstractOIDCProtocolMapper implements O
         property.setHelpText(ProtocolMapperUtils.USER_MODEL_ATTRIBUTE_HELP_TEXT);
         property.setType(ProviderConfigProperty.STRING_TYPE);
         configProperties.add(property);
-        OIDCAttributeMapperHelper.addAttributeConfig(configProperties);
+        OIDCAttributeMapperHelper.addAttributeConfig(configProperties, UserAttributeMapper.class);
 
         property = new ProviderConfigProperty();
         property.setName(ProtocolMapperUtils.MULTIVALUED);
@@ -89,16 +89,8 @@ public class UserAttributeMapper extends AbstractOIDCProtocolMapper implements O
         return "Map a custom user attribute to a token claim.";
     }
 
-    @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) {
+
         UserModel user = userSession.getUser();
         String attributeName = mappingModel.getConfig().get(ProtocolMapperUtils.USER_ATTRIBUTE);
         List<String> attributeValue = KeycloakModelUtils.resolveAttribute(user, attributeName);
@@ -106,24 +98,6 @@ public class UserAttributeMapper extends AbstractOIDCProtocolMapper implements O
         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;
-    }
-
-    @Override
-    public AccessToken transformUserInfoToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) {
-
-        if (!OIDCAttributeMapperHelper.includeInUserInfo(mappingModel)) {
-            return token;
-        }
-
-        setClaim(token, mappingModel, userSession);
-        return token;
-    }
-
     public static ProtocolMapperModel createClaimMapper(String name,
                                                         String userAttribute,
                                                         String tokenClaimName, String claimType,
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserClientRoleMappingMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserClientRoleMappingMapper.java
index dcb55a8..01d47e1 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserClientRoleMappingMapper.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserClientRoleMappingMapper.java
@@ -47,7 +47,7 @@ public class UserClientRoleMappingMapper extends AbstractUserRoleMappingMapper {
         clientId.setName(ProtocolMapperUtils.USER_MODEL_CLIENT_ROLE_MAPPING_CLIENT_ID);
         clientId.setLabel(ProtocolMapperUtils.USER_MODEL_CLIENT_ROLE_MAPPING_CLIENT_ID_LABEL);
         clientId.setHelpText(ProtocolMapperUtils.USER_MODEL_CLIENT_ROLE_MAPPING_CLIENT_ID_HELP_TEXT);
-        clientId.setType(ProviderConfigProperty.STRING_TYPE);
+        clientId.setType(ProviderConfigProperty.CLIENT_LIST_TYPE);
         CONFIG_PROPERTIES.add(clientId);
 
         ProviderConfigProperty clientRolePrefix = new ProviderConfigProperty();
@@ -57,7 +57,7 @@ public class UserClientRoleMappingMapper extends AbstractUserRoleMappingMapper {
         clientRolePrefix.setType(ProviderConfigProperty.STRING_TYPE);
         CONFIG_PROPERTIES.add(clientRolePrefix);
 
-        OIDCAttributeMapperHelper.addAttributeConfig(CONFIG_PROPERTIES);
+        OIDCAttributeMapperHelper.addAttributeConfig(CONFIG_PROPERTIES, UserClientRoleMappingMapper.class);
     }
 
     public List<ProviderConfigProperty> getConfigProperties() {
@@ -100,4 +100,21 @@ public class UserClientRoleMappingMapper extends AbstractUserRoleMappingMapper {
             OIDCAttributeMapperHelper.mapClaim(token, mappingModel, clientRoleNames);
         }
     }
+
+
+    public static ProtocolMapperModel create(String clientId, String clientRolePrefix,
+                                             String name,
+                                             String tokenClaimName,
+                                             boolean accessToken, boolean idToken) {
+        ProtocolMapperModel mapper = OIDCAttributeMapperHelper.createClaimMapper(name, "foo",
+                tokenClaimName, "String",
+                true, name,
+                accessToken, idToken,
+                PROVIDER_ID);
+
+        mapper.getConfig().put(ProtocolMapperUtils.USER_MODEL_CLIENT_ROLE_MAPPING_CLIENT_ID, clientId);
+        mapper.getConfig().put(ProtocolMapperUtils.USER_MODEL_CLIENT_ROLE_MAPPING_ROLE_PREFIX, clientRolePrefix);
+        return mapper;
+
+    }
 }
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserInfoTokenMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserInfoTokenMapper.java
index a93e62b..67ac1a2 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserInfoTokenMapper.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserInfoTokenMapper.java
@@ -29,5 +29,5 @@ import org.keycloak.representations.AccessToken;
 public interface UserInfoTokenMapper {
 
     AccessToken transformUserInfoToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
-                                     UserSessionModel userSession, ClientSessionModel clientSession);
+                                               UserSessionModel userSession, ClientSessionModel clientSession);
 }
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserPropertyMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserPropertyMapper.java
index e0ea4c3..6fd6491 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserPropertyMapper.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserPropertyMapper.java
@@ -38,7 +38,7 @@ import java.util.List;
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
  */
-public class UserPropertyMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper {
+public class UserPropertyMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper, UserInfoTokenMapper {
     private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
 
     static {
@@ -49,7 +49,7 @@ public class UserPropertyMapper extends AbstractOIDCProtocolMapper implements OI
         property.setType(ProviderConfigProperty.STRING_TYPE);
         property.setHelpText(ProtocolMapperUtils.USER_MODEL_PROPERTY_HELP_TEXT);
         configProperties.add(property);
-        OIDCAttributeMapperHelper.addAttributeConfig(configProperties);
+        OIDCAttributeMapperHelper.addAttributeConfig(configProperties, UserPropertyMapper.class);
     }
 
     public static final String PROVIDER_ID = "oidc-usermodel-property-mapper";
@@ -79,24 +79,8 @@ public class UserPropertyMapper extends AbstractOIDCProtocolMapper implements OI
         return "Map a built in user property (email, firstName, lastName) to a token claim.";
     }
 
-    @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;
-    }
-
-    @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;
-    }
-
     protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession) {
+
         UserModel user = userSession.getUser();
         String propertyName = mappingModel.getConfig().get(ProtocolMapperUtils.USER_ATTRIBUTE);
         String propertyValue = ProtocolMapperUtils.getUserModelValue(user, propertyName);
@@ -114,6 +98,4 @@ public class UserPropertyMapper extends AbstractOIDCProtocolMapper implements OI
                 accessToken, idToken,
                 PROVIDER_ID);
     }
-
-
 }
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserRealmRoleMappingMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserRealmRoleMappingMapper.java
index 3346070..ef98182 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserRealmRoleMappingMapper.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserRealmRoleMappingMapper.java
@@ -21,11 +21,14 @@ import org.keycloak.models.ProtocolMapperModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserSessionModel;
 import org.keycloak.protocol.ProtocolMapperUtils;
+import org.keycloak.protocol.oidc.OIDCLoginProtocol;
 import org.keycloak.provider.ProviderConfigProperty;
 import org.keycloak.representations.IDToken;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 /**
@@ -48,7 +51,7 @@ public class UserRealmRoleMappingMapper extends AbstractUserRoleMappingMapper {
         realmRolePrefix.setType(ProviderConfigProperty.STRING_TYPE);
         CONFIG_PROPERTIES.add(realmRolePrefix);
 
-        OIDCAttributeMapperHelper.addAttributeConfig(CONFIG_PROPERTIES);
+        OIDCAttributeMapperHelper.addAttributeConfig(CONFIG_PROPERTIES, UserRealmRoleMappingMapper.class);
     }
 
     public List<ProviderConfigProperty> getConfigProperties() {
@@ -80,8 +83,23 @@ public class UserRealmRoleMappingMapper extends AbstractUserRoleMappingMapper {
         UserModel user = userSession.getUser();
 
         String rolePrefix = mappingModel.getConfig().get(ProtocolMapperUtils.USER_MODEL_REALM_ROLE_MAPPING_ROLE_PREFIX);
-        Set<String> realmRoleNames = flattenRoleModelToRoleNames(user.getRoleMappings(), rolePrefix);
+        Set<String> realmRoleNames = flattenRoleModelToRoleNames(user.getRealmRoleMappings(), rolePrefix);
 
         OIDCAttributeMapperHelper.mapClaim(token, mappingModel, realmRoleNames);
     }
+
+
+    public static ProtocolMapperModel create(String realmRolePrefix,
+                                             String name,
+                                             String tokenClaimName, boolean accessToken, boolean idToken) {
+        ProtocolMapperModel mapper = OIDCAttributeMapperHelper.createClaimMapper(name, "foo",
+                tokenClaimName, "String",
+                true, name,
+                accessToken, idToken,
+                PROVIDER_ID);
+
+        mapper.getConfig().put(ProtocolMapperUtils.USER_MODEL_REALM_ROLE_MAPPING_ROLE_PREFIX, realmRolePrefix);
+        return mapper;
+
+    }
 }
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserSessionNoteMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserSessionNoteMapper.java
index 0016103..fd6bfe1 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserSessionNoteMapper.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserSessionNoteMapper.java
@@ -49,7 +49,7 @@ public class UserSessionNoteMapper extends AbstractOIDCProtocolMapper implements
         property.setHelpText(ProtocolMapperUtils.USER_SESSION_MODEL_NOTE_HELP_TEXT);
         property.setType(ProviderConfigProperty.STRING_TYPE);
         configProperties.add(property);
-        OIDCAttributeMapperHelper.addAttributeConfig(configProperties);
+        OIDCAttributeMapperHelper.addAttributeConfig(configProperties, UserSessionNoteMapper.class);
     }
 
     public static final String PROVIDER_ID = "oidc-usersessionmodel-note-mapper";
@@ -79,29 +79,14 @@ public class UserSessionNoteMapper extends AbstractOIDCProtocolMapper implements
         return "Map a custom user session note to a token claim.";
     }
 
-    @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 noteName = mappingModel.getConfig().get(ProtocolMapperUtils.USER_SESSION_NOTE);
         String noteValue = userSession.getNote(noteName);
         if (noteValue == null) return;
         OIDCAttributeMapperHelper.mapClaim(token, mappingModel, noteValue);
     }
 
-    @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 userSessionNote,
                                                         String tokenClaimName, String jsonType,
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 308c771..c671607 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
@@ -506,10 +506,11 @@ public class TokenManager {
         for (ProtocolMapperModel mapping : mappings) {
 
             ProtocolMapper mapper = (ProtocolMapper)sessionFactory.getProviderFactory(ProtocolMapper.class, mapping.getProtocolMapper());
-            if (mapper == null || !(mapper instanceof OIDCAccessTokenMapper)) continue;
-            token = ((OIDCAccessTokenMapper)mapper).transformAccessToken(token, mapping, session, userSession, clientSession);
-
+            if (mapper instanceof OIDCAccessTokenMapper) {
+                token = ((OIDCAccessTokenMapper) mapper).transformAccessToken(token, mapping, session, userSession, clientSession);
+            }
         }
+
         return token;
     }
 
@@ -520,16 +521,11 @@ public class TokenManager {
         for (ProtocolMapperModel mapping : mappings) {
 
             ProtocolMapper mapper = (ProtocolMapper)sessionFactory.getProviderFactory(ProtocolMapper.class, mapping.getProtocolMapper());
-            if (mapper == null || !(mapper instanceof OIDCAccessTokenMapper)) continue;
-
-            if(mapper instanceof UserInfoTokenMapper){
-                token = ((UserInfoTokenMapper)mapper).transformUserInfoToken(token, mapping, session, userSession, clientSession);
-                continue;
+            if (mapper instanceof UserInfoTokenMapper) {
+                token = ((UserInfoTokenMapper) mapper).transformUserInfoToken(token, mapping, session, userSession, clientSession);
             }
-
-            token = ((OIDCAccessTokenMapper)mapper).transformAccessToken(token, mapping, session, userSession, clientSession);
-
         }
+
         return token;
     }
 
@@ -540,13 +536,12 @@ public class TokenManager {
         for (ProtocolMapperModel mapping : mappings) {
 
             ProtocolMapper mapper = (ProtocolMapper)sessionFactory.getProviderFactory(ProtocolMapper.class, mapping.getProtocolMapper());
-            if (mapper == null || !(mapper instanceof OIDCIDTokenMapper)) continue;
-            token = ((OIDCIDTokenMapper)mapper).transformIDToken(token, mapping, session, userSession, clientSession);
-
+            if (mapper instanceof OIDCIDTokenMapper) {
+                token = ((OIDCIDTokenMapper) mapper).transformIDToken(token, mapping, session, userSession, clientSession);
+            }
         }
     }
 
-
     protected AccessToken initToken(RealmModel realm, ClientModel client, UserModel user, UserSessionModel session, ClientSessionModel clientSession, UriInfo uriInfo) {
         AccessToken token = new AccessToken();
         if (clientSession != null) token.clientSession(clientSession.getId());
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java
index 9cad056..ed1e757 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java
@@ -242,7 +242,7 @@ public abstract class AbstractKeycloakIdentityProviderTest extends AbstractIdent
      * Test for KEYCLOAK-1053 - verify email action is not performed if email is not provided, login is normal, but action stays in set to be performed later
      */
     @Test
-    public void testSuccessfulAuthenticationWithoutUpdateProfile_emailNotProvided_emailVerifyEnabled() {
+    public void testSuccessfulAuthenticationWithoutUpdateProfile_emailNotProvided_emailVerifyEnabled() throws Exception {
         RealmModel realm = getRealm();
         realm.setVerifyEmail(true);
         setUpdateProfileFirstLogin(realm, IdentityProviderRepresentation.UPFLM_OFF);
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeyCloakServerBrokerBasicTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeyCloakServerBrokerBasicTest.java
index 0552875..58c4ca1 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeyCloakServerBrokerBasicTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeyCloakServerBrokerBasicTest.java
@@ -151,7 +151,7 @@ public class OIDCKeyCloakServerBrokerBasicTest extends AbstractKeycloakIdentityP
     }
 
     @Test
-    public void testSuccessfulAuthenticationWithoutUpdateProfile_emailNotProvided_emailVerifyEnabled() {
+    public void testSuccessfulAuthenticationWithoutUpdateProfile_emailNotProvided_emailVerifyEnabled() throws Exception {
         super.testSuccessfulAuthenticationWithoutUpdateProfile_emailNotProvided_emailVerifyEnabled();
     }
 
diff --git a/testsuite/integration/src/test/resources/broker-test/test-broker-realm-with-kc-oidc.json b/testsuite/integration/src/test/resources/broker-test/test-broker-realm-with-kc-oidc.json
index 767ab44..05cb0e9 100755
--- a/testsuite/integration/src/test/resources/broker-test/test-broker-realm-with-kc-oidc.json
+++ b/testsuite/integration/src/test/resources/broker-test/test-broker-realm-with-kc-oidc.json
@@ -26,8 +26,8 @@
                     "claim.name": "mobile",
                     "Claim JSON Type": "String",
                     "access.token.claim": "true",
-                    "id.token.claim": "true"
-
+                    "id.token.claim": "true",
+                    "userinfo.token.claim": "true"
                 }
             },
             {
@@ -40,8 +40,8 @@
                     "claim.name": "preferred_username",
                     "Claim JSON Type": "String",
                     "access.token.claim": "true",
-                    "id.token.claim": "true"
-
+                    "id.token.claim": "true",
+                    "userinfo.token.claim": "true"
                 }
             },
             {
@@ -54,8 +54,8 @@
                     "claim.name": "email",
                     "Claim JSON Type": "String",
                     "access.token.claim": "true",
-                    "id.token.claim": "true"
-
+                    "id.token.claim": "true",
+                    "userinfo.token.claim": "true"
                 }
             },
             {
@@ -68,8 +68,8 @@
                     "claim.name": "given_name",
                     "Claim JSON Type": "String",
                     "access.token.claim": "true",
-                    "id.token.claim": "true"
-
+                    "id.token.claim": "true",
+                    "userinfo.token.claim": "true"
                 }
             },
             {
@@ -83,7 +83,6 @@
                     "Claim JSON Type": "String",
                     "access.token.claim": "true",
                     "id.token.claim": "true"
-
                 }
             },
             {
@@ -93,8 +92,8 @@
                 "consentRequired": false,
                 "config": {
                     "access.token.claim": "true",
-                    "id.token.claim": "true"
-
+                    "id.token.claim": "true",
+                    "userinfo.token.claim": "true"
                 }
             }
 
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/UserInfoClientUtil.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/UserInfoClientUtil.java
index fc54023..fb39ec5 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/UserInfoClientUtil.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/UserInfoClientUtil.java
@@ -49,7 +49,7 @@ public class UserInfoClientUtil {
         return client.target(userInfoUri);
     }
 
-    public static void testSuccessfulUserInfoResponse(Response response, String expectedUsername, String expectedEmail) {
+    public static UserInfo testSuccessfulUserInfoResponse(Response response, String expectedUsername, String expectedEmail) {
         Assert.assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
         Assert.assertEquals(response.getHeaderString(HttpHeaders.CONTENT_TYPE), MediaType.APPLICATION_JSON);
 
@@ -61,6 +61,7 @@ public class UserInfoClientUtil {
         Assert.assertNotNull(userInfo.getSubject());
         Assert.assertEquals(expectedEmail, userInfo.getEmail());
         Assert.assertEquals(expectedUsername, userInfo.getPreferredUsername());
+        return userInfo;
     }
 
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCPairwiseClientRegistrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCPairwiseClientRegistrationTest.java
index ca4553d..c5bb785 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCPairwiseClientRegistrationTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCPairwiseClientRegistrationTest.java
@@ -27,6 +27,7 @@ import org.keycloak.client.registration.ClientRegistrationException;
 import org.keycloak.client.registration.HttpErrorException;
 import org.keycloak.protocol.oidc.mappers.SHA256PairwiseSubMapper;
 import org.keycloak.representations.AccessToken;
+import org.keycloak.representations.UserInfo;
 import org.keycloak.representations.idm.ClientInitialAccessCreatePresentation;
 import org.keycloak.representations.idm.ClientInitialAccessPresentation;
 import org.keycloak.representations.idm.ProtocolMapperRepresentation;
@@ -38,7 +39,9 @@ import org.keycloak.testsuite.client.resources.TestApplicationResourceUrls;
 import org.keycloak.testsuite.client.resources.TestOIDCEndpointsApplicationResource;
 import org.keycloak.testsuite.util.ClientManager;
 import org.keycloak.testsuite.util.OAuthClient;
+import org.keycloak.testsuite.util.UserInfoClientUtil;
 
+import javax.ws.rs.client.Client;
 import javax.ws.rs.core.Response;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -323,5 +326,17 @@ public class OIDCPairwiseClientRegistrationTest extends AbstractClientRegistrati
         // Assert pairwise client has different subject like userId
         String pairwiseUserId = accessToken.getSubject();
         Assert.assertNotEquals(pairwiseUserId, user.getId());
+
+        // Send request to userInfo endpoint
+        Client jaxrsClient = javax.ws.rs.client.ClientBuilder.newClient();
+        try {
+            // Check that userInfo contains pairwise subjectId as well
+            Response userInfoResponse = UserInfoClientUtil.executeUserInfoRequest_getMethod(jaxrsClient, accessTokenResponse.getAccessToken());
+            UserInfo userInfo = UserInfoClientUtil.testSuccessfulUserInfoResponse(userInfoResponse, "test-user", "test-user@localhost");
+            String userInfoSubId = userInfo.getSubject();
+            Assert.assertEquals(pairwiseUserId, userInfoSubId);
+        } finally {
+            jaxrsClient.close();
+        }
     }
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java
index 13c1ea4..eaeeafd 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java
@@ -674,138 +674,6 @@ public class AccessTokenTest extends AbstractKeycloakTest {
     }
 
     @Test
-    public void testTokenMapping() throws Exception {
-        Client client = javax.ws.rs.client.ClientBuilder.newClient();
-        UriBuilder builder = UriBuilder.fromUri(AUTH_SERVER_ROOT);
-        URI grantUri = OIDCLoginProtocolService.tokenUrl(builder).build("test");
-        WebTarget grantTarget = client.target(grantUri);
-        {
-            UserResource userResource = findUserByUsernameId(adminClient.realm("test"), "test-user@localhost");
-            UserRepresentation user = userResource.toRepresentation();
-
-            user.singleAttribute("street", "5 Yawkey Way");
-            user.singleAttribute("locality", "Boston");
-            user.singleAttribute("region", "MA");
-            user.singleAttribute("postal_code", "02115");
-            user.singleAttribute("country", "USA");
-            user.singleAttribute("phone", "617-777-6666");
-
-            List<String> departments = Arrays.asList("finance", "development");
-            user.getAttributes().put("departments", departments);
-            userResource.update(user);
-
-            ClientResource app = findClientResourceByClientId(adminClient.realm("test"), "test-app");
-
-            ProtocolMapperRepresentation mapper = createAddressMapper(true, true);
-            app.getProtocolMappers().createMapper(mapper);
-
-            ProtocolMapperRepresentation hard = createHardcodedClaim("hard", "hard", "coded", "String", false, null, true, true);
-            app.getProtocolMappers().createMapper(hard);
-            app.getProtocolMappers().createMapper(createHardcodedClaim("hard-nested", "nested.hard", "coded-nested", "String", false, null, true, true));
-            app.getProtocolMappers().createMapper(createClaimMapper("custom phone", "phone", "home_phone", "String", true, "", true, true, false));
-            app.getProtocolMappers().createMapper(createClaimMapper("nested phone", "phone", "home.phone", "String", true, "", true, true, false));
-            app.getProtocolMappers().createMapper(createClaimMapper("departments", "departments", "department", "String", true, "", true, true, true));
-            app.getProtocolMappers().createMapper(createHardcodedRole("hard-realm", "hardcoded"));
-            app.getProtocolMappers().createMapper(createHardcodedRole("hard-app", "app.hardcoded"));
-            app.getProtocolMappers().createMapper(createRoleNameMapper("rename-app-role", "test-app.customer-user", "realm-user"));
-        }
-
-        {
-            Response response = executeGrantAccessTokenRequest(grantTarget);
-            assertEquals(200, response.getStatus());
-
-            org.keycloak.representations.AccessTokenResponse tokenResponse = response.readEntity(org.keycloak.representations.AccessTokenResponse.class);
-            IDToken idToken = getIdToken(tokenResponse);
-            assertNotNull(idToken.getAddress());
-            assertEquals(idToken.getName(), "Tom Brady");
-            assertEquals(idToken.getAddress().getStreetAddress(), "5 Yawkey Way");
-            assertEquals(idToken.getAddress().getLocality(), "Boston");
-            assertEquals(idToken.getAddress().getRegion(), "MA");
-            assertEquals(idToken.getAddress().getPostalCode(), "02115");
-            assertEquals(idToken.getAddress().getCountry(), "USA");
-            assertNotNull(idToken.getOtherClaims().get("home_phone"));
-            assertEquals("617-777-6666", idToken.getOtherClaims().get("home_phone"));
-            assertEquals("coded", idToken.getOtherClaims().get("hard"));
-            Map nested = (Map) idToken.getOtherClaims().get("nested");
-            assertEquals("coded-nested", nested.get("hard"));
-            nested = (Map) idToken.getOtherClaims().get("home");
-            assertEquals("617-777-6666", nested.get("phone"));
-            List<String> departments = (List<String>) idToken.getOtherClaims().get("department");
-            assertEquals(2, departments.size());
-            assertTrue(departments.contains("finance") && departments.contains("development"));
-
-            AccessToken accessToken = getAccessToken(tokenResponse);
-            assertEquals(accessToken.getName(), "Tom Brady");
-            assertNotNull(accessToken.getAddress());
-            assertEquals(accessToken.getAddress().getStreetAddress(), "5 Yawkey Way");
-            assertEquals(accessToken.getAddress().getLocality(), "Boston");
-            assertEquals(accessToken.getAddress().getRegion(), "MA");
-            assertEquals(accessToken.getAddress().getPostalCode(), "02115");
-            assertEquals(accessToken.getAddress().getCountry(), "USA");
-            assertNotNull(accessToken.getOtherClaims().get("home_phone"));
-            assertEquals("617-777-6666", accessToken.getOtherClaims().get("home_phone"));
-            assertEquals("coded", accessToken.getOtherClaims().get("hard"));
-            nested = (Map) accessToken.getOtherClaims().get("nested");
-            assertEquals("coded-nested", nested.get("hard"));
-            nested = (Map) accessToken.getOtherClaims().get("home");
-            assertEquals("617-777-6666", nested.get("phone"));
-            departments = (List<String>) idToken.getOtherClaims().get("department");
-            assertEquals(2, departments.size());
-            assertTrue(departments.contains("finance") && departments.contains("development"));
-            assertTrue(accessToken.getRealmAccess().getRoles().contains("hardcoded"));
-            assertTrue(accessToken.getRealmAccess().getRoles().contains("realm-user"));
-            Assert.assertFalse(accessToken.getResourceAccess("test-app").getRoles().contains("customer-user"));
-            assertTrue(accessToken.getResourceAccess("app").getRoles().contains("hardcoded"));
-
-
-            response.close();
-        }
-
-        // undo mappers
-        {
-            ClientResource app = findClientByClientId(adminClient.realm("test"), "test-app");
-            ClientRepresentation clientRepresentation = app.toRepresentation();
-            for (ProtocolMapperRepresentation model : clientRepresentation.getProtocolMappers()) {
-                if (model.getName().equals("address")
-                        || model.getName().equals("hard")
-                        || model.getName().equals("hard-nested")
-                        || model.getName().equals("custom phone")
-                        || model.getName().equals("departments")
-                        || model.getName().equals("nested phone")
-                        || model.getName().equals("rename-app-role")
-                        || model.getName().equals("hard-realm")
-                        || model.getName().equals("hard-app")
-                        ) {
-                    app.getProtocolMappers().delete(model.getId());
-                }
-            }
-        }
-
-        events.clear();
-
-
-        {
-            Response response = executeGrantAccessTokenRequest(grantTarget);
-            assertEquals(200, response.getStatus());
-            org.keycloak.representations.AccessTokenResponse tokenResponse = response.readEntity(org.keycloak.representations.AccessTokenResponse.class);
-            IDToken idToken = getIdToken(tokenResponse);
-            assertNull(idToken.getAddress());
-            assertNull(idToken.getOtherClaims().get("home_phone"));
-            assertNull(idToken.getOtherClaims().get("hard"));
-            assertNull(idToken.getOtherClaims().get("nested"));
-            assertNull(idToken.getOtherClaims().get("department"));
-
-            response.close();
-        }
-
-
-        events.clear();
-        client.close();
-
-
-    }
-
-    @Test
     public void testClientTemplate() throws Exception {
         RealmResource realm = adminClient.realm("test");
         RoleRepresentation realmRole = new RoleRepresentation();
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OIDCProtocolMappersTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OIDCProtocolMappersTest.java
new file mode 100644
index 0000000..d317af7
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OIDCProtocolMappersTest.java
@@ -0,0 +1,244 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.oauth;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.keycloak.admin.client.resource.ClientResource;
+import org.keycloak.admin.client.resource.ProtocolMappersResource;
+import org.keycloak.admin.client.resource.UserResource;
+import org.keycloak.protocol.oidc.OIDCLoginProtocol;
+import org.keycloak.representations.AccessToken;
+import org.keycloak.representations.IDToken;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.ProtocolMapperRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.testsuite.AbstractKeycloakTest;
+import org.keycloak.testsuite.Assert;
+import org.keycloak.testsuite.AssertEvents;
+import org.keycloak.testsuite.admin.ApiUtil;
+import org.keycloak.testsuite.util.ClientManager;
+import org.keycloak.testsuite.util.OAuthClient;
+import org.keycloak.testsuite.util.ProtocolMapperUtil;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson;
+import static org.keycloak.testsuite.admin.ApiUtil.findClientByClientId;
+import static org.keycloak.testsuite.admin.ApiUtil.findClientResourceByClientId;
+import static org.keycloak.testsuite.admin.ApiUtil.findUserByUsernameId;
+import static org.keycloak.testsuite.util.ProtocolMapperUtil.createAddressMapper;
+import static org.keycloak.testsuite.util.ProtocolMapperUtil.createClaimMapper;
+import static org.keycloak.testsuite.util.ProtocolMapperUtil.createHardcodedClaim;
+import static org.keycloak.testsuite.util.ProtocolMapperUtil.createHardcodedRole;
+import static org.keycloak.testsuite.util.ProtocolMapperUtil.createRoleNameMapper;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class OIDCProtocolMappersTest extends AbstractKeycloakTest {
+
+    @Rule
+    public AssertEvents events = new AssertEvents(this);
+
+
+    @Override
+    public void beforeAbstractKeycloakTest() throws Exception {
+        super.beforeAbstractKeycloakTest();
+    }
+
+    @Before
+    public void clientConfiguration() {
+        ClientManager.realm(adminClient.realm("test")).clientId("test-app").directAccessGrant(true);
+        /*
+         * Configure the default client ID. Seems like OAuthClient is keeping the state of clientID
+         * For example: If some test case configure oauth.clientId("sample-public-client"), other tests
+         * will faile and the clientID will always be "sample-public-client
+         * @see AccessTokenTest#testAuthorizationNegotiateHeaderIgnored()
+         */
+        oauth.clientId("test-app");
+    }
+
+    @Override
+    public void addTestRealms(List<RealmRepresentation> testRealms) {
+        RealmRepresentation realm = loadJson(getClass().getResourceAsStream("/testrealm.json"), RealmRepresentation.class);
+        testRealms.add(realm);
+    }
+
+
+    @Test
+    public void testTokenMapping() throws Exception {
+        {
+            UserResource userResource = findUserByUsernameId(adminClient.realm("test"), "test-user@localhost");
+            UserRepresentation user = userResource.toRepresentation();
+
+            user.singleAttribute("street", "5 Yawkey Way");
+            user.singleAttribute("locality", "Boston");
+            user.singleAttribute("region", "MA");
+            user.singleAttribute("postal_code", "02115");
+            user.singleAttribute("country", "USA");
+            user.singleAttribute("phone", "617-777-6666");
+
+            List<String> departments = Arrays.asList("finance", "development");
+            user.getAttributes().put("departments", departments);
+            userResource.update(user);
+
+            ClientResource app = findClientResourceByClientId(adminClient.realm("test"), "test-app");
+
+            ProtocolMapperRepresentation mapper = createAddressMapper(true, true);
+            app.getProtocolMappers().createMapper(mapper);
+
+            ProtocolMapperRepresentation hard = createHardcodedClaim("hard", "hard", "coded", "String", false, null, true, true);
+            app.getProtocolMappers().createMapper(hard);
+            app.getProtocolMappers().createMapper(createHardcodedClaim("hard-nested", "nested.hard", "coded-nested", "String", false, null, true, true));
+            app.getProtocolMappers().createMapper(createClaimMapper("custom phone", "phone", "home_phone", "String", true, "", true, true, false));
+            app.getProtocolMappers().createMapper(createClaimMapper("nested phone", "phone", "home.phone", "String", true, "", true, true, false));
+            app.getProtocolMappers().createMapper(createClaimMapper("departments", "departments", "department", "String", true, "", true, true, true));
+            app.getProtocolMappers().createMapper(createHardcodedRole("hard-realm", "hardcoded"));
+            app.getProtocolMappers().createMapper(createHardcodedRole("hard-app", "app.hardcoded"));
+            app.getProtocolMappers().createMapper(createRoleNameMapper("rename-app-role", "test-app.customer-user", "realm-user"));
+        }
+
+        {
+            OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("password", "test-user@localhost", "password");
+
+            IDToken idToken = oauth.verifyIDToken(response.getIdToken());
+            assertNotNull(idToken.getAddress());
+            assertEquals(idToken.getName(), "Tom Brady");
+            assertEquals(idToken.getAddress().getStreetAddress(), "5 Yawkey Way");
+            assertEquals(idToken.getAddress().getLocality(), "Boston");
+            assertEquals(idToken.getAddress().getRegion(), "MA");
+            assertEquals(idToken.getAddress().getPostalCode(), "02115");
+            assertEquals(idToken.getAddress().getCountry(), "USA");
+            assertNotNull(idToken.getOtherClaims().get("home_phone"));
+            assertEquals("617-777-6666", idToken.getOtherClaims().get("home_phone"));
+            assertEquals("coded", idToken.getOtherClaims().get("hard"));
+            Map nested = (Map) idToken.getOtherClaims().get("nested");
+            assertEquals("coded-nested", nested.get("hard"));
+            nested = (Map) idToken.getOtherClaims().get("home");
+            assertEquals("617-777-6666", nested.get("phone"));
+            List<String> departments = (List<String>) idToken.getOtherClaims().get("department");
+            assertEquals(2, departments.size());
+            assertTrue(departments.contains("finance") && departments.contains("development"));
+
+            AccessToken accessToken = oauth.verifyToken(response.getAccessToken());
+            assertEquals(accessToken.getName(), "Tom Brady");
+            assertNotNull(accessToken.getAddress());
+            assertEquals(accessToken.getAddress().getStreetAddress(), "5 Yawkey Way");
+            assertEquals(accessToken.getAddress().getLocality(), "Boston");
+            assertEquals(accessToken.getAddress().getRegion(), "MA");
+            assertEquals(accessToken.getAddress().getPostalCode(), "02115");
+            assertEquals(accessToken.getAddress().getCountry(), "USA");
+            assertNotNull(accessToken.getOtherClaims().get("home_phone"));
+            assertEquals("617-777-6666", accessToken.getOtherClaims().get("home_phone"));
+            assertEquals("coded", accessToken.getOtherClaims().get("hard"));
+            nested = (Map) accessToken.getOtherClaims().get("nested");
+            assertEquals("coded-nested", nested.get("hard"));
+            nested = (Map) accessToken.getOtherClaims().get("home");
+            assertEquals("617-777-6666", nested.get("phone"));
+            departments = (List<String>) idToken.getOtherClaims().get("department");
+            assertEquals(2, departments.size());
+            assertTrue(departments.contains("finance") && departments.contains("development"));
+            assertTrue(accessToken.getRealmAccess().getRoles().contains("hardcoded"));
+            assertTrue(accessToken.getRealmAccess().getRoles().contains("realm-user"));
+            Assert.assertFalse(accessToken.getResourceAccess("test-app").getRoles().contains("customer-user"));
+            assertTrue(accessToken.getResourceAccess("app").getRoles().contains("hardcoded"));
+        }
+
+        // undo mappers
+        {
+            ClientResource app = findClientByClientId(adminClient.realm("test"), "test-app");
+            ClientRepresentation clientRepresentation = app.toRepresentation();
+            for (ProtocolMapperRepresentation model : clientRepresentation.getProtocolMappers()) {
+                if (model.getName().equals("address")
+                        || model.getName().equals("hard")
+                        || model.getName().equals("hard-nested")
+                        || model.getName().equals("custom phone")
+                        || model.getName().equals("departments")
+                        || model.getName().equals("nested phone")
+                        || model.getName().equals("rename-app-role")
+                        || model.getName().equals("hard-realm")
+                        || model.getName().equals("hard-app")
+                        ) {
+                    app.getProtocolMappers().delete(model.getId());
+                }
+            }
+        }
+
+        events.clear();
+
+
+        {
+            OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("password", "test-user@localhost", "password");
+            IDToken idToken = oauth.verifyIDToken(response.getIdToken());
+            assertNull(idToken.getAddress());
+            assertNull(idToken.getOtherClaims().get("home_phone"));
+            assertNull(idToken.getOtherClaims().get("hard"));
+            assertNull(idToken.getOtherClaims().get("nested"));
+            assertNull(idToken.getOtherClaims().get("department"));
+        }
+
+
+        events.clear();
+    }
+
+
+    @Test
+    public void testUserRoleToAttributeMappers() throws Exception {
+        // Add mapper for realm roles
+        ProtocolMapperRepresentation realmMapper = ProtocolMapperUtil.createUserRealmRoleMappingMapper("pref.", "Realm roles mapper", "roles-custom.realm", true, true);
+        ProtocolMapperRepresentation clientMapper = ProtocolMapperUtil.createUserClientRoleMappingMapper("test-app", null, "Client roles mapper", "roles-custom.test-app", true, true);
+
+        ProtocolMappersResource protocolMappers = ApiUtil.findClientResourceByClientId(adminClient.realm("test"), "test-app").getProtocolMappers();
+        protocolMappers.createMapper(Arrays.asList(realmMapper, clientMapper));
+
+        // Login user
+        OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("password", "test-user@localhost", "password");
+        IDToken idToken = oauth.verifyIDToken(response.getIdToken());
+
+        // Verify attribute is filled
+        Map<String, Object> roleMappings = (Map<String, Object>)idToken.getOtherClaims().get("roles-custom");
+        Assert.assertEquals(2, roleMappings.size());
+        String realmRoleMappings = (String) roleMappings.get("realm");
+        String testAppMappings = (String) roleMappings.get("test-app");
+        Assert.assertTrue(realmRoleMappings.contains("pref.user"));
+        Assert.assertEquals("[customer-user]", testAppMappings);
+    }
+
+
+    private ProtocolMapperRepresentation makeMapper(String name, String mapperType, Map<String, String> config) {
+        ProtocolMapperRepresentation rep = new ProtocolMapperRepresentation();
+        rep.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
+        rep.setName(name);
+        rep.setProtocolMapper(mapperType);
+        rep.setConfig(config);
+        rep.setConsentRequired(true);
+        rep.setConsentText("Test Consent Text");
+        return rep;
+    }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/ProtocolMapperUtil.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/ProtocolMapperUtil.java
index 62018e1..6bcea06 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/ProtocolMapperUtil.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/ProtocolMapperUtil.java
@@ -6,6 +6,8 @@ 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.protocol.oidc.mappers.UserClientRoleMappingMapper;
+import org.keycloak.protocol.oidc.mappers.UserRealmRoleMappingMapper;
 import org.keycloak.protocol.oidc.mappers.UserSessionNoteMapper;
 import org.keycloak.representations.idm.ProtocolMapperRepresentation;
 
@@ -103,4 +105,22 @@ public class ProtocolMapperUtil {
                 consentRequired, consentText,
                 accessToken, idToken));
     }
+
+
+    public static ProtocolMapperRepresentation createUserRealmRoleMappingMapper(String realmRolePrefix,
+                                                                                String name,
+                                                                                String tokenClaimName,
+                                                                                boolean accessToken, boolean idToken) {
+
+        return ModelToRepresentation.toRepresentation(UserRealmRoleMappingMapper.create(realmRolePrefix, name, tokenClaimName, accessToken, idToken));
+    }
+
+
+    public static ProtocolMapperRepresentation createUserClientRoleMappingMapper(String clientId, String clientRolePrefix,
+                                                                                String name,
+                                                                                String tokenClaimName,
+                                                                                boolean accessToken, boolean idToken) {
+
+        return ModelToRepresentation.toRepresentation(UserClientRoleMappingMapper.create(clientId, clientRolePrefix, name, tokenClaimName, accessToken, idToken));
+    }
 }
diff --git a/themes/src/main/resources/theme/base/admin/resources/js/app.js b/themes/src/main/resources/theme/base/admin/resources/js/app.js
index e1f9891..e892b45 100755
--- a/themes/src/main/resources/theme/base/admin/resources/js/app.js
+++ b/themes/src/main/resources/theme/base/admin/resources/js/app.js
@@ -953,6 +953,9 @@ module.config([ '$routeProvider', function($routeProvider) {
                 },
                 mapper : function(ClientProtocolMapperLoader) {
                     return ClientProtocolMapperLoader();
+                },
+                clients : function(ClientListLoader) {
+                    return ClientListLoader();
                 }
 
             },
@@ -969,6 +972,9 @@ module.config([ '$routeProvider', function($routeProvider) {
                 },
                 client : function(ClientLoader) {
                     return ClientLoader();
+                },
+                clients : function(ClientListLoader) {
+                    return ClientListLoader();
                 }
             },
             controller : 'ClientProtocolMapperCreateCtrl'
@@ -1017,6 +1023,9 @@ module.config([ '$routeProvider', function($routeProvider) {
                 },
                 mapper : function(ClientTemplateProtocolMapperLoader) {
                     return ClientTemplateProtocolMapperLoader();
+                },
+                clients : function(ClientListLoader) {
+                    return ClientListLoader();
                 }
 
             },
@@ -1033,6 +1042,9 @@ module.config([ '$routeProvider', function($routeProvider) {
                 },
                 template : function(ClientTemplateLoader) {
                     return ClientTemplateLoader();
+                },
+                clients : function(ClientListLoader) {
+                    return ClientListLoader();
                 }
             },
             controller : 'ClientTemplateProtocolMapperCreateCtrl'
diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
index 2de4453..3405791 100755
--- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
+++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
@@ -1705,8 +1705,10 @@ module.controller('ClientProtocolMapperListCtrl', function($scope, realm, client
     updateMappers();
 });
 
-module.controller('ClientProtocolMapperCtrl', function($scope, realm, serverInfo, client, mapper, ClientProtocolMapper, Notifications, Dialog, $location) {
+module.controller('ClientProtocolMapperCtrl', function($scope, realm, serverInfo, client, clients, mapper, ClientProtocolMapper, Notifications, Dialog, $location) {
     $scope.realm = realm;
+    $scope.clients = clients;
+
     /*
     $scope.client = client;
     $scope.create = false;
@@ -1786,8 +1788,9 @@ module.controller('ClientProtocolMapperCtrl', function($scope, realm, serverInfo
 
 });
 
-module.controller('ClientProtocolMapperCreateCtrl', function($scope, realm, serverInfo, client, ClientProtocolMapper, Notifications, Dialog, $location) {
+module.controller('ClientProtocolMapperCreateCtrl', function($scope, realm, serverInfo, client, clients, ClientProtocolMapper, Notifications, Dialog, $location) {
     $scope.realm = realm;
+    $scope.clients = clients;
 
     if (client.protocol == null) {
         client.protocol = 'openid-connect';
@@ -2001,8 +2004,10 @@ module.controller('ClientTemplateProtocolMapperListCtrl', function($scope, realm
     updateMappers();
 });
 
-module.controller('ClientTemplateProtocolMapperCtrl', function($scope, realm, serverInfo, template, mapper, ClientTemplateProtocolMapper, Notifications, Dialog, $location) {
+module.controller('ClientTemplateProtocolMapperCtrl', function($scope, realm, serverInfo, template, mapper, clients, ClientTemplateProtocolMapper, Notifications, Dialog, $location) {
     $scope.realm = realm;
+    $scope.clients = clients;
+
     if (template.protocol == null) {
         template.protocol = 'openid-connect';
     }
@@ -2068,8 +2073,10 @@ module.controller('ClientTemplateProtocolMapperCtrl', function($scope, realm, se
 
 });
 
-module.controller('ClientTemplateProtocolMapperCreateCtrl', function($scope, realm, serverInfo, template, ClientTemplateProtocolMapper, Notifications, Dialog, $location) {
+module.controller('ClientTemplateProtocolMapperCreateCtrl', function($scope, realm, serverInfo, template, clients, ClientTemplateProtocolMapper, Notifications, Dialog, $location) {
     $scope.realm = realm;
+    $scope.clients = clients;
+
     if (template.protocol == null) {
         template.protocol = 'openid-connect';
     }
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/protocol-mapper-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/protocol-mapper-detail.html
index fe781c2..e630bf9 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/protocol-mapper-detail.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/protocol-mapper-detail.html
@@ -60,7 +60,7 @@
                 </div>
                 <kc-tooltip>{{model.mapperType.helpText}}</kc-tooltip>
             </div>
-            <kc-provider-config config="model.mapper.config" properties="model.mapperType.properties" realm="model.realm"></kc-provider-config>
+            <kc-provider-config config="model.mapper.config" properties="model.mapperType.properties" realm="model.realm" clients="clients"></kc-provider-config>
         </fieldset>
 
         <div class="form-group">