keycloak-uncached

KEYCLOAK-4205 Allow to return json arrays in Client and Realm

2/13/2017 4:16:45 PM

Details

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 bfedd29..f4ef89d 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
@@ -23,8 +23,10 @@ import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserSessionModel;
 import org.keycloak.models.utils.RoleUtils;
+import org.keycloak.protocol.ProtocolMapperUtils;
 import org.keycloak.representations.IDToken;
 
+import java.util.List;
 import java.util.Set;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
@@ -100,10 +102,17 @@ abstract class AbstractUserRoleMappingMapper extends AbstractOIDCProtocolMapper 
             clientUserRoles = clientUserRoles.filter(clientRoles::contains);
         }
 
-        Set<String> realmRoleNames = clientUserRoles
+        List<String> realmRoleNames = clientUserRoles
           .map(m -> rolePrefix + m.getName())
-          .collect(Collectors.toSet());
+          .collect(Collectors.toList());
 
-        OIDCAttributeMapperHelper.mapClaim(token, mappingModel, realmRoleNames);
+        Object claimValue = realmRoleNames;
+
+        boolean multiValued = "true".equals(mappingModel.getConfig().get(ProtocolMapperUtils.MULTIVALUED));
+        if (!multiValued) {
+            claimValue = realmRoleNames.toString();
+        }
+
+        OIDCAttributeMapperHelper.mapClaim(token, mappingModel, claimValue);
     }
 }
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 5a88c2a..7ce5c35 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
@@ -60,6 +60,14 @@ public class UserClientRoleMappingMapper extends AbstractUserRoleMappingMapper {
         clientRolePrefix.setType(ProviderConfigProperty.STRING_TYPE);
         CONFIG_PROPERTIES.add(clientRolePrefix);
 
+        ProviderConfigProperty multiValued = new ProviderConfigProperty();
+        multiValued.setName(ProtocolMapperUtils.MULTIVALUED);
+        multiValued.setLabel(ProtocolMapperUtils.MULTIVALUED_LABEL);
+        multiValued.setHelpText(ProtocolMapperUtils.MULTIVALUED_HELP_TEXT);
+        multiValued.setType(ProviderConfigProperty.BOOLEAN_TYPE);
+        multiValued.setDefaultValue(false);
+        CONFIG_PROPERTIES.add(multiValued);
+
         OIDCAttributeMapperHelper.addAttributeConfig(CONFIG_PROPERTIES, UserClientRoleMappingMapper.class);
     }
 
@@ -138,15 +146,23 @@ public class UserClientRoleMappingMapper extends AbstractUserRoleMappingMapper {
                                              String name,
                                              String tokenClaimName,
                                              boolean accessToken, boolean idToken) {
+        return create(clientId, clientRolePrefix, name, tokenClaimName, accessToken, idToken, false);
+
+    }
+
+    public static ProtocolMapperModel create(String clientId, String clientRolePrefix,
+                                             String name,
+                                             String tokenClaimName,
+                                             boolean accessToken, boolean idToken, boolean multiValued) {
         ProtocolMapperModel mapper = OIDCAttributeMapperHelper.createClaimMapper(name, "foo",
-                tokenClaimName, "String",
-                true, name,
-                accessToken, idToken,
-                PROVIDER_ID);
+          tokenClaimName, "String",
+          true, name,
+          accessToken, idToken,
+          PROVIDER_ID);
 
+        mapper.getConfig().put(ProtocolMapperUtils.MULTIVALUED, String.valueOf(multiValued));
         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/UserRealmRoleMappingMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserRealmRoleMappingMapper.java
index f978b08..3b7ffe5 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
@@ -46,6 +46,14 @@ public class UserRealmRoleMappingMapper extends AbstractUserRoleMappingMapper {
         realmRolePrefix.setType(ProviderConfigProperty.STRING_TYPE);
         CONFIG_PROPERTIES.add(realmRolePrefix);
 
+        ProviderConfigProperty multiValued = new ProviderConfigProperty();
+        multiValued.setName(ProtocolMapperUtils.MULTIVALUED);
+        multiValued.setLabel(ProtocolMapperUtils.MULTIVALUED_LABEL);
+        multiValued.setHelpText(ProtocolMapperUtils.MULTIVALUED_HELP_TEXT);
+        multiValued.setType(ProviderConfigProperty.BOOLEAN_TYPE);
+        multiValued.setDefaultValue(false);
+        CONFIG_PROPERTIES.add(multiValued);
+
         OIDCAttributeMapperHelper.addAttributeConfig(CONFIG_PROPERTIES, UserRealmRoleMappingMapper.class);
     }
 
@@ -83,14 +91,21 @@ public class UserRealmRoleMappingMapper extends AbstractUserRoleMappingMapper {
     public static ProtocolMapperModel create(String realmRolePrefix,
                                              String name,
                                              String tokenClaimName, boolean accessToken, boolean idToken) {
+
+        return create(realmRolePrefix, name, tokenClaimName, accessToken, idToken, false);
+    }
+
+    public static ProtocolMapperModel create(String realmRolePrefix,
+                                             String name,
+                                             String tokenClaimName, boolean accessToken, boolean idToken, boolean multiValued) {
         ProtocolMapperModel mapper = OIDCAttributeMapperHelper.createClaimMapper(name, "foo",
-                tokenClaimName, "String",
-                true, name,
-                accessToken, idToken,
-                PROVIDER_ID);
+          tokenClaimName, "String",
+          true, name,
+          accessToken, idToken,
+          PROVIDER_ID);
 
+        mapper.getConfig().put(ProtocolMapperUtils.MULTIVALUED, String.valueOf(multiValued));
         mapper.getConfig().put(ProtocolMapperUtils.USER_MODEL_REALM_ROLE_MAPPING_ROLE_PREFIX, realmRolePrefix);
         return mapper;
-
     }
 }
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
index 01d4beb..0aa91f9 100644
--- 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
@@ -17,19 +17,13 @@
 
 package org.keycloak.testsuite.oauth;
 
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-
-
-import org.junit.After;
+import org.hamcrest.CoreMatchers;
 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.testsuite.util.ProtocolMapperUtil;
 import org.keycloak.protocol.oidc.OIDCLoginProtocol;
 import org.keycloak.protocol.oidc.mappers.AddressMapper;
 import org.keycloak.representations.AccessToken;
@@ -47,8 +41,21 @@ import org.keycloak.testsuite.util.ClientManager;
 import org.keycloak.testsuite.util.OAuthClient;
 import org.keycloak.testsuite.util.ProtocolMapperUtil;
 
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import static org.hamcrest.Matchers.anyOf;
+import static org.hamcrest.Matchers.arrayContainingInAnyOrder;
+import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.hamcrest.Matchers.hasItems;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+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;
@@ -249,9 +256,46 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest {
         Assert.assertThat(roleMappings.keySet(), containsInAnyOrder("realm", "test-app"));
         String realmRoleMappings = (String) roleMappings.get("realm");
         String testAppMappings = (String) roleMappings.get("test-app");
+        assertRolesString(realmRoleMappings,
+          "pref.user",                      // from direct assignment in user definition
+          "pref.offline_access"             // from direct assignment in user definition
+        );
+        assertRolesString(testAppMappings,
+          "customer-user"                   // from direct assignment in user definition
+        );
+
+        // Revert
+        deleteMappers(protocolMappers);
+    }
+
+    /**
+     * KEYCLOAK-4205
+     * @throws Exception
+     */
+    @Test
+    public void testUserRoleToAttributeMappersWithMultiValuedRoles() throws Exception {
+        // Add mapper for realm roles
+        ProtocolMapperRepresentation realmMapper = ProtocolMapperUtil.createUserRealmRoleMappingMapper("pref.", "Realm roles mapper", "roles-custom.realm", true, true, true);
+        ProtocolMapperRepresentation clientMapper = ProtocolMapperUtil.createUserClientRoleMappingMapper("test-app", null, "Client roles mapper", "roles-custom.test-app", true, 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.assertThat(roleMappings.keySet(), containsInAnyOrder("realm", "test-app"));
+        Assert.assertThat(roleMappings.get("realm"), CoreMatchers.instanceOf(List.class));
+        Assert.assertThat(roleMappings.get("test-app"), CoreMatchers.instanceOf(List.class));
+
+        List<String> realmRoleMappings = (List<String>) roleMappings.get("realm");
+        List<String> testAppMappings = (List<String>) roleMappings.get("test-app");
         assertRoles(realmRoleMappings,
-                "pref.user",                      // from direct assignment in user definition
-                "pref.offline_access"             // from direct assignment in user definition
+          "pref.user",                      // from direct assignment in user definition
+          "pref.offline_access"             // from direct assignment in user definition
         );
         assertRoles(testAppMappings,
           "customer-user"                   // from direct assignment in user definition
@@ -261,7 +305,6 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest {
         deleteMappers(protocolMappers);
     }
 
-
     @Test
     public void testUserGroupRoleToAttributeMappers() throws Exception {
         // Add mapper for realm roles
@@ -281,14 +324,14 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest {
         Assert.assertThat(roleMappings.keySet(), containsInAnyOrder("realm", clientId));
         String realmRoleMappings = (String) roleMappings.get("realm");
         String testAppMappings = (String) roleMappings.get(clientId);
-        assertRoles(realmRoleMappings,
-                "pref.admin",                     // from direct assignment to /roleRichGroup/level2group
-                "pref.user",                      // from parent group of /roleRichGroup/level2group, i.e. from /roleRichGroup
-                "pref.customer-user-premium",     // from client role customer-admin-composite-role - realm role for test-app
-                "pref.realm-composite-role",      // from parent group of /roleRichGroup/level2group, i.e. from /roleRichGroup
-                "pref.sample-realm-role"          // from realm role realm-composite-role
+        assertRolesString(realmRoleMappings,
+          "pref.admin",                     // from direct assignment to /roleRichGroup/level2group
+          "pref.user",                      // from parent group of /roleRichGroup/level2group, i.e. from /roleRichGroup
+          "pref.customer-user-premium",     // from client role customer-admin-composite-role - realm role for test-app
+          "pref.realm-composite-role",      // from parent group of /roleRichGroup/level2group, i.e. from /roleRichGroup
+          "pref.sample-realm-role"          // from realm role realm-composite-role
         );
-        assertRoles(testAppMappings,
+        assertRolesString(testAppMappings,
           "ta.customer-user",                  // from direct assignment to /roleRichGroup/level2group
           "ta.customer-admin-composite-role",  // from direct assignment to /roleRichGroup/level2group
           "ta.customer-admin",                 // from client role customer-admin-composite-role - client role for test-app
@@ -319,14 +362,14 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest {
         Assert.assertThat(roleMappings.keySet(), containsInAnyOrder("realm", clientId));
         String realmRoleMappings = (String) roleMappings.get("realm");
         String testAppAuthzMappings = (String) roleMappings.get(clientId);
-        assertRoles(realmRoleMappings,
-                "pref.admin",                     // from direct assignment to /roleRichGroup/level2group
-                "pref.user",                      // from parent group of /roleRichGroup/level2group, i.e. from /roleRichGroup
-                "pref.customer-user-premium",     // from client role customer-admin-composite-role - realm role for test-app
-                "pref.realm-composite-role",      // from parent group of /roleRichGroup/level2group, i.e. from /roleRichGroup
-                "pref.sample-realm-role"          // from realm role realm-composite-role
+        assertRolesString(realmRoleMappings,
+          "pref.admin",                     // from direct assignment to /roleRichGroup/level2group
+          "pref.user",                      // from parent group of /roleRichGroup/level2group, i.e. from /roleRichGroup
+          "pref.customer-user-premium",     // from client role customer-admin-composite-role - realm role for test-app
+          "pref.realm-composite-role",      // from parent group of /roleRichGroup/level2group, i.e. from /roleRichGroup
+          "pref.sample-realm-role"          // from realm role realm-composite-role
         );
-        assertRoles(testAppAuthzMappings);  // There is no client role defined for test-app-authz
+        assertRolesString(testAppAuthzMappings);  // There is no client role defined for test-app-authz
 
         // Revert
         deleteMappers(protocolMappers);
@@ -352,11 +395,11 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest {
         Assert.assertThat(roleMappings.keySet(), containsInAnyOrder("realm", clientId));
         String realmRoleMappings = (String) roleMappings.get("realm");
         String testAppScopeMappings = (String) roleMappings.get(clientId);
-        assertRoles(realmRoleMappings,
-                "pref.admin",                     // from direct assignment to /roleRichGroup/level2group
-                "pref.user"                       // from parent group of /roleRichGroup/level2group, i.e. from /roleRichGroup
+        assertRolesString(realmRoleMappings,
+          "pref.admin",                     // from direct assignment to /roleRichGroup/level2group
+          "pref.user"                       // from parent group of /roleRichGroup/level2group, i.e. from /roleRichGroup
         );
-        assertRoles(testAppScopeMappings,
+        assertRolesString(testAppScopeMappings,
           "test-app-allowed-by-scope"       // from direct assignment to roleRichUser, present as scope allows it
         );
 
@@ -384,11 +427,11 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest {
         Assert.assertThat(roleMappings.keySet(), containsInAnyOrder("realm", clientId));
         String realmRoleMappings = (String) roleMappings.get("realm");
         String testAppScopeMappings = (String) roleMappings.get(clientId);
-        assertRoles(realmRoleMappings,
+        assertRolesString(realmRoleMappings,
           "pref.admin",                     // from direct assignment to /roleRichGroup/level2group
           "pref.user"                       // from parent group of /roleRichGroup/level2group, i.e. from /roleRichGroup
         );
-        assertRoles(testAppScopeMappings,
+        assertRolesString(testAppScopeMappings,
           "test-app-allowed-by-scope",      // from direct assignment to roleRichUser, present as scope allows it
           "customer-admin-composite-role"   // from direct assignment to /roleRichGroup/level2group, present as scope allows it
         );
@@ -397,10 +440,14 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest {
         deleteMappers(protocolMappers);
     }
 
-    private void assertRoles(String actualRoleString, String...expectedRoles) {
-        String[] roles;
+    private void assertRoles(List<String> actualRoleList, String ...expectedRoles){
+        Assert.assertNames(actualRoleList, expectedRoles);
+    }
+
+    private void assertRolesString(String actualRoleString, String...expectedRoles) {
+
         Assert.assertThat(actualRoleString.matches("^\\[.*\\]$"), is(true));
-        roles = actualRoleString.substring(1, actualRoleString.length() - 1).split(",\\s*");
+        String[] roles = actualRoleString.substring(1, actualRoleString.length() - 1).split(",\\s*");
 
         if (expectedRoles == null || expectedRoles.length == 0) {
             Assert.assertThat(roles, arrayContainingInAnyOrder(""));
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 e8e9e62..922f1ef 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
@@ -114,16 +114,31 @@ public class ProtocolMapperUtil {
                                                                                 String tokenClaimName,
                                                                                 boolean accessToken, boolean idToken) {
 
-        return ModelToRepresentation.toRepresentation(UserRealmRoleMappingMapper.create(realmRolePrefix, name, tokenClaimName, accessToken, idToken));
+        return createUserRealmRoleMappingMapper(realmRolePrefix, name, tokenClaimName, accessToken, idToken, false);
     }
 
+    public static ProtocolMapperRepresentation createUserRealmRoleMappingMapper(String realmRolePrefix,
+                                                                                String name,
+                                                                                String tokenClaimName,
+                                                                                boolean accessToken, boolean idToken, boolean multiValued) {
+
+        return ModelToRepresentation.toRepresentation(UserRealmRoleMappingMapper.create(realmRolePrefix, name, tokenClaimName, accessToken, idToken, multiValued));
+    }
 
     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));
+        return createUserClientRoleMappingMapper(clientId, clientRolePrefix, name, tokenClaimName, accessToken, idToken, false);
+    }
+
+    public static ProtocolMapperRepresentation createUserClientRoleMappingMapper(String clientId, String clientRolePrefix,
+                                                                                 String name,
+                                                                                 String tokenClaimName,
+                                                                                 boolean accessToken, boolean idToken, boolean multiValued) {
+
+        return ModelToRepresentation.toRepresentation(UserClientRoleMappingMapper.create(clientId, clientRolePrefix, name, tokenClaimName, accessToken, idToken, multiValued));
     }
 
     public static ProtocolMapperRepresentation getMapperByNameAndProtocol(ProtocolMappersResource protocolMappers, String protocol, String name) {