keycloak-aplcache

KEYCLOAK-1959 Role offline_access was effective only when

10/15/2015 6:18:02 AM

Details

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 8882da4..f4de665 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
@@ -44,6 +44,8 @@ import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -325,6 +327,21 @@ public class TokenManager {
                     }
                 }
             }
+
+            // Add all roles specified in scope parameter directly into requestedRoles, even if they are available just through composite role
+            List<RoleModel> scopeRoles = new LinkedList<>();
+            for (String scopeParamPart : scopeParamRoles) {
+                RoleModel scopeParamRole = getRoleFromScopeParam(client.getRealm(), scopeParamPart);
+                if (scopeParamRole != null) {
+                    for (RoleModel role : roles) {
+                        if (role.hasRole(scopeParamRole)) {
+                            scopeRoles.add(scopeParamRole);
+                        }
+                    }
+                }
+            }
+
+            roles.addAll(scopeRoles);
             requestedRoles = roles;
         }
 
@@ -341,6 +358,17 @@ public class TokenManager {
         }
     }
 
+    // For now, just use "roleName" for realm roles and "clientId/roleName" for client roles
+    private static RoleModel getRoleFromScopeParam(RealmModel realm, String scopeParamRole) {
+        String[] parts = scopeParamRole.split("/");
+        if (parts.length == 1) {
+            return realm.getRole(parts[0]);
+        } else {
+            ClientModel roleClient = realm.getClientByClientId(parts[0]);
+            return roleClient!=null ? roleClient.getRole(parts[1]) : null;
+        }
+    }
+
     public void verifyAccess(AccessToken token, AccessToken newToken) throws OAuthErrorException {
         if (token.getRealmAccess() != null) {
             if (newToken.getRealmAccess() == null) throw new OAuthErrorException(OAuthErrorException.INVALID_SCOPE, "User no long has permission for realm roles");
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OfflineTokenTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OfflineTokenTest.java
index 370e7fd..b7596a0 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OfflineTokenTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OfflineTokenTest.java
@@ -30,6 +30,7 @@ import org.keycloak.models.Constants;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserModel;
+import org.keycloak.protocol.oidc.TokenManager;
 import org.keycloak.representations.AccessToken;
 import org.keycloak.representations.RefreshToken;
 import org.keycloak.services.managers.ClientManager;
@@ -285,7 +286,6 @@ public class OfflineTokenTest {
 
         Assert.assertEquals(userId, refreshedToken.getSubject());
 
-        Assert.assertEquals(2, refreshedToken.getRealmAccess().getRoles().size());
         Assert.assertTrue(refreshedToken.getRealmAccess().isUserInRole("user"));
         Assert.assertTrue(refreshedToken.getRealmAccess().isUserInRole(Constants.OFFLINE_ACCESS_ROLE));
 
@@ -385,6 +385,54 @@ public class OfflineTokenTest {
     }
 
     @Test
+    public void offlineTokenAllowedWithCompositeRole() throws Exception {
+        keycloakRule.update(new KeycloakRule.KeycloakSetup() {
+
+            @Override
+            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                ClientModel offlineClient = appRealm.getClientByClientId("offline-client");
+                UserModel testUser = session.users().getUserByUsername("test-user@localhost", appRealm);
+                RoleModel offlineAccess = appRealm.getRole(Constants.OFFLINE_ACCESS_ROLE);
+
+                // Test access
+                Assert.assertFalse(TokenManager.getAccess(null, true, offlineClient, testUser).contains(offlineAccess));
+                Assert.assertTrue(TokenManager.getAccess(OAuth2Constants.OFFLINE_ACCESS, true, offlineClient, testUser).contains(offlineAccess));
+
+                // Grant offline_access role indirectly through composite role
+                RoleModel composite = appRealm.addRole("composite");
+                composite.addCompositeRole(offlineAccess);
+
+                testUser.deleteRoleMapping(offlineAccess);
+                testUser.grantRole(composite);
+
+                // Test access
+                Assert.assertFalse(TokenManager.getAccess(null, true, offlineClient, testUser).contains(offlineAccess));
+                Assert.assertTrue(TokenManager.getAccess(OAuth2Constants.OFFLINE_ACCESS, true, offlineClient, testUser).contains(offlineAccess));
+            }
+
+        });
+
+        // Integration test
+        offlineTokenDirectGrantFlow();
+
+        // Revert changes
+        keycloakRule.update(new KeycloakRule.KeycloakSetup() {
+
+            @Override
+            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                RoleModel composite = appRealm.getRole("composite");
+                RoleModel offlineAccess = appRealm.getRole(Constants.OFFLINE_ACCESS_ROLE);
+                UserModel testUser = session.users().getUserByUsername("test-user@localhost", appRealm);
+
+                testUser.deleteRoleMapping(composite);
+                appRealm.removeRole(composite);
+                testUser.grantRole(offlineAccess);
+            }
+
+        });
+    }
+
+    @Test
     public void testServlet() {
         OfflineTokenServlet.tokenInfo = null;