keycloak-uncached

Merge pull request #3608 from hmlnarik/KEYCLOAK-4035 KEYCLOAK-4035

12/5/2016 5:44:21 PM

Details

diff --git a/server-spi/src/main/java/org/keycloak/models/utils/RoleUtils.java b/server-spi/src/main/java/org/keycloak/models/utils/RoleUtils.java
index a03a55d..1dd7267 100644
--- a/server-spi/src/main/java/org/keycloak/models/utils/RoleUtils.java
+++ b/server-spi/src/main/java/org/keycloak/models/utils/RoleUtils.java
@@ -20,7 +20,11 @@ package org.keycloak.models.utils;
 import org.keycloak.models.GroupModel;
 import org.keycloak.models.RoleModel;
 
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.HashSet;
 import java.util.Set;
+import java.util.stream.Stream;
 import java.util.stream.StreamSupport;
 
 /**
@@ -98,4 +102,33 @@ public class RoleUtils {
                 .anyMatch(group -> hasRoleFromGroup(group, targetRole, checkParentGroup));
     }
 
+    /**
+     * Recursively expands composite roles into their composite.
+     * @param role
+     * @return Stream of containing all of the composite roles and their components.
+     */
+    public static Stream<RoleModel> expandCompositeRolesStream(RoleModel role) {
+        Stream.Builder<RoleModel> sb = Stream.builder();
+        Set<RoleModel> roles = new HashSet<>();
+
+        Deque<RoleModel> stack = new ArrayDeque<>();
+        stack.add(role);
+
+        while (! stack.isEmpty()) {
+            RoleModel current = stack.pop();
+            sb.add(current);
+
+            if (current.isComposite()) {
+                current.getComposites().stream()
+                  .filter(r -> ! roles.contains(r))
+                  .forEach(r -> {
+                    roles.add(r);
+                    stack.add(r);
+                  });
+            }
+        }
+
+        return sb.build();
+    }
+
 }
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 4de3720..bfedd29 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
@@ -22,10 +22,9 @@ import org.keycloak.models.ProtocolMapperModel;
 import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserSessionModel;
+import org.keycloak.models.utils.RoleUtils;
 import org.keycloak.representations.IDToken;
 
-import java.util.ArrayDeque;
-import java.util.Deque;
 import java.util.Set;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
@@ -54,7 +53,7 @@ abstract class AbstractUserRoleMappingMapper extends AbstractOIDCProtocolMapper 
           user.getGroups().stream()
             .flatMap(g -> groupAndItsParentsStream(g))
             .flatMap(g -> g.getRoleMappings().stream()))
-          .flatMap(role -> expandCompositeRolesStream(role));
+          .flatMap(RoleUtils::expandCompositeRolesStream);
     }
 
     /**
@@ -72,29 +71,6 @@ abstract class AbstractUserRoleMappingMapper extends AbstractOIDCProtocolMapper 
     }
 
     /**
-     * Recursively expands composite roles into their composite.
-     * @param role
-     * @return Stream of containing all of the composite roles and their components.
-     */
-    private static Stream<RoleModel> expandCompositeRolesStream(RoleModel role) {
-        Stream.Builder<RoleModel> sb = Stream.builder();
-
-        Deque<RoleModel> stack = new ArrayDeque<>();
-        stack.add(role);
-
-        while (! stack.isEmpty()) {
-            RoleModel current = stack.pop();
-            sb.add(current);
-
-            if (current.isComposite()) {
-                stack.addAll(current.getComposites());
-            }
-        }
-
-        return sb.build();
-    }
-
-    /**
      * Retrieves all roles of the current user based on direct roles set to the user, its groups and their parent groups.
      * Then it recursively expands all composite roles, and restricts according to the given predicate {@code restriction}.
      * If the current client sessions is restricted (i.e. no client found in active user session has full scope allowed),
diff --git a/services/src/main/java/org/keycloak/protocol/saml/mappers/HardcodedRole.java b/services/src/main/java/org/keycloak/protocol/saml/mappers/HardcodedRole.java
index 9efead2..9b78e60 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/mappers/HardcodedRole.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/mappers/HardcodedRole.java
@@ -35,12 +35,13 @@ import java.util.Map;
 public class HardcodedRole extends AbstractSAMLProtocolMapper {
     public static final String PROVIDER_ID = "saml-hardcode-role-mapper";
     public static final String ATTRIBUTE_VALUE = "attribute.value";
-    private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
+    private static final List<ProviderConfigProperty> configProperties = new ArrayList<>();
+    public static final String ROLE_ATTRIBUTE = "role";
 
     static {
         ProviderConfigProperty property;
         property = new ProviderConfigProperty();
-        property.setName("role");
+        property.setName(ROLE_ATTRIBUTE);
         property.setLabel("Role");
         property.setHelpText("Arbitrary role name you want to hardcode.  This role does not have to exist in current realm and can be just any string you need");
         property.setType(ProviderConfigProperty.ROLE_TYPE);
@@ -79,8 +80,8 @@ public class HardcodedRole extends AbstractSAMLProtocolMapper {
         mapper.setName(name);
         mapper.setProtocolMapper(mapperId);
         mapper.setProtocol(SamlProtocol.LOGIN_PROTOCOL);
-       Map<String, String> config = new HashMap<String, String>();
-        config.put("role", role);
+        Map<String, String> config = new HashMap<>();
+        config.put(ROLE_ATTRIBUTE, role);
         mapper.setConfig(config);
         return mapper;
 
diff --git a/services/src/main/java/org/keycloak/protocol/saml/mappers/RoleListMapper.java b/services/src/main/java/org/keycloak/protocol/saml/mappers/RoleListMapper.java
index dd27472..5650333 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/mappers/RoleListMapper.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/mappers/RoleListMapper.java
@@ -23,8 +23,9 @@ import org.keycloak.models.ClientSessionModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakSessionFactory;
 import org.keycloak.models.ProtocolMapperModel;
-import org.keycloak.models.RoleModel;
+import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserSessionModel;
+import org.keycloak.models.utils.RoleUtils;
 import org.keycloak.protocol.ProtocolMapper;
 import org.keycloak.protocol.saml.SamlProtocol;
 import org.keycloak.provider.ProviderConfigProperty;
@@ -35,7 +36,9 @@ import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -45,7 +48,7 @@ public class RoleListMapper extends AbstractSAMLProtocolMapper implements SAMLRo
     public static final String PROVIDER_ID = "saml-role-list-mapper";
     public static final String SINGLE_ROLE_ATTRIBUTE = "single";
 
-    private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
+    private static final List<ProviderConfigProperty> configProperties = new ArrayList<>();
 
     static {
         ProviderConfigProperty property;
@@ -120,11 +123,13 @@ public class RoleListMapper extends AbstractSAMLProtocolMapper implements SAMLRo
 
             ProtocolMapper mapper = (ProtocolMapper)sessionFactory.getProviderFactory(ProtocolMapper.class, mapping.getProtocolMapper());
             if (mapper == null) continue;
+
             if (mapper instanceof SAMLRoleNameMapper) {
                 roleNameMappers.add(new SamlProtocol.ProtocolMapperProcessor<>((SAMLRoleNameMapper) mapper,mapping));
             }
+
             if (mapper instanceof HardcodedRole) {
-                AttributeType attributeType = null;
+                AttributeType attributeType;
                 if (singleAttribute) {
                     if (singleAttributeType == null) {
                         singleAttributeType = AttributeStatementHelper.createAttributeType(mappingModel);
@@ -135,14 +140,26 @@ public class RoleListMapper extends AbstractSAMLProtocolMapper implements SAMLRo
                     attributeType = AttributeStatementHelper.createAttributeType(mappingModel);
                     roleAttributeStatement.addAttribute(new AttributeStatementType.ASTChoiceType(attributeType));
                 }
-                attributeType.addAttributeValue(mapping.getConfig().get("role"));
+
+                attributeType.addAttributeValue(mapping.getConfig().get(HardcodedRole.ROLE_ATTRIBUTE));
             }
         }
 
-        for (String roleId : clientSession.getRoles()) {
-            // todo need a role mapping
-            RoleModel roleModel = clientSession.getRealm().getRoleById(roleId);
-            AttributeType attributeType = null;
+        RealmModel realm = clientSession.getRealm();
+        List<String> allRoleNames = clientSession.getRoles().stream()
+          // todo need a role mapping
+          .map(realm::getRoleById)
+          .filter(Objects::nonNull)
+          .flatMap(RoleUtils::expandCompositeRolesStream)
+          .map(roleModel -> roleNameMappers.stream()
+            .map(entry -> entry.mapper.mapName(entry.model, roleModel))
+            .filter(Objects::nonNull)
+            .findFirst()
+            .orElse(roleModel.getName())
+          ).collect(Collectors.toList());
+
+        for (String roleName : allRoleNames) {
+            AttributeType attributeType;
             if (singleAttribute) {
                 if (singleAttributeType == null) {
                     singleAttributeType = AttributeStatementHelper.createAttributeType(mappingModel);
@@ -153,14 +170,7 @@ public class RoleListMapper extends AbstractSAMLProtocolMapper implements SAMLRo
                 attributeType = AttributeStatementHelper.createAttributeType(mappingModel);
                 roleAttributeStatement.addAttribute(new AttributeStatementType.ASTChoiceType(attributeType));
             }
-            String roleName = roleModel.getName();
-            for (SamlProtocol.ProtocolMapperProcessor<SAMLRoleNameMapper> entry : roleNameMappers) {
-                String newName = entry.mapper.mapName(entry.model, roleModel);
-                if (newName != null) {
-                    roleName = newName;
-                    break;
-                }
-            }
+
             attributeType.addAttributeValue(roleName);
         }
 
@@ -172,7 +182,7 @@ public class RoleListMapper extends AbstractSAMLProtocolMapper implements SAMLRo
         mapper.setProtocolMapper(PROVIDER_ID);
         mapper.setProtocol(SamlProtocol.LOGIN_PROTOCOL);
         mapper.setConsentRequired(false);
-        Map<String, String> config = new HashMap<String, String>();
+        Map<String, String> config = new HashMap<>();
         config.put(AttributeStatementHelper.SAML_ATTRIBUTE_NAME, samlAttributeName);
         if (friendlyName != null) {
             config.put(AttributeStatementHelper.FRIENDLY_NAME, friendlyName);
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java
index d6d52f2..3f9dabb 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java
@@ -22,6 +22,7 @@ import org.jboss.arquillian.graphene.page.Page;
 import org.jboss.shrinkwrap.api.spec.WebArchive;
 import org.junit.Assert;
 import org.junit.Test;
+
 import org.keycloak.admin.client.resource.ClientResource;
 import org.keycloak.admin.client.resource.ProtocolMappersResource;
 import org.keycloak.admin.client.resource.RoleScopeResource;
@@ -71,6 +72,7 @@ import org.keycloak.testsuite.auth.page.login.SAMLIDPInitiatedLogin;
 import org.keycloak.testsuite.page.AbstractPage;
 import org.keycloak.testsuite.util.IOUtil;
 import org.keycloak.testsuite.util.UserBuilder;
+
 import org.openqa.selenium.By;
 import org.w3c.dom.Document;
 import org.xml.sax.SAXException;
@@ -104,6 +106,7 @@ import static org.junit.Assert.*;
 import static org.keycloak.representations.idm.CredentialRepresentation.PASSWORD;
 import static org.keycloak.testsuite.AbstractAuthTest.createUserRepresentation;
 import static org.keycloak.testsuite.admin.ApiUtil.createUserAndResetPasswordWithAdminClient;
+import static org.keycloak.testsuite.admin.Users.setPasswordFor;
 import static org.keycloak.testsuite.auth.page.AuthRealm.SAMLSERVLETDEMO;
 import static org.keycloak.testsuite.util.IOUtil.loadRealm;
 import static org.keycloak.testsuite.util.IOUtil.loadXML;
@@ -530,6 +533,14 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
     }
 
     @Test
+    public void salesPostTestCompositeRoleForUser() {
+        UserRepresentation topGroupUser = createUserRepresentation("topGroupUser", "top@redhat.com", "", "", true);
+        setPasswordFor(topGroupUser, PASSWORD);
+
+        assertSuccessfulLogin(salesPostServletPage, topGroupUser, testRealmSAMLPostLoginPage, "principal=topgroupuser");
+    }
+
+    @Test
     public void salesPostTest() {
         testSuccessfulAndUnauthorizedLogin(salesPostServletPage, testRealmSAMLPostLoginPage);
     }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/demorealm.json b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/demorealm.json
index b1f70e2..072ca40 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/demorealm.json
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/demorealm.json
@@ -49,6 +49,7 @@
                 { "type" : "password",
                     "value" : "password" }
             ],
+            "realmRoles": [ "realm-composite-role" ],
             "groups": [
                 "/top"
             ]
@@ -75,6 +76,14 @@
             {
                 "name": "admin",
                 "description": "Administrator privileges"
+            },
+            {
+                "name": "realm-composite-role",
+                "description": "Realm composite role containing user role",
+                "composite": true,
+                "composites": {
+                    "realm": ["user"]
+                }
             }
         ]
     },