keycloak-uncached
Changes
services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractUserRoleMappingMapper.java 105(+81 -24)
services/src/main/java/org/keycloak/protocol/oidc/mappers/UserClientRoleMappingMapper.java 54(+43 -11)
services/src/main/java/org/keycloak/protocol/oidc/mappers/UserRealmRoleMappingMapper.java 17(+4 -13)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/group/GroupTest.java 2(+1 -1)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserTest.java 2(+1 -1)
Details
diff --git a/server-spi/src/main/java/org/keycloak/models/RoleMapperModel.java b/server-spi/src/main/java/org/keycloak/models/RoleMapperModel.java
index 85f1fd3..46c3198 100755
--- a/server-spi/src/main/java/org/keycloak/models/RoleMapperModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/RoleMapperModel.java
@@ -24,8 +24,17 @@ import java.util.Set;
* @version $Revision: 1 $
*/
public interface RoleMapperModel {
+ /**
+ * Returns set of realm roles that are directly set to this object.
+ * @return see description
+ */
Set<RoleModel> getRealmRoleMappings();
+ /**
+ * Returns set of client roles that are directly set to this object for the given client.
+ * @param app Client to get the roles for
+ * @return see description
+ */
Set<RoleModel> getClientRoleMappings(ClientModel app);
/**
@@ -48,7 +57,15 @@ public interface RoleMapperModel {
*/
void grantRole(RoleModel role);
+ /**
+ * Returns set of all role (both realm all client) that are directly set to this object.
+ * @return
+ */
Set<RoleModel> getRoleMappings();
+ /**
+ * Removes the given role mapping from this object.
+ * @param role Role to remove
+ */
void deleteRoleMapping(RoleModel role);
}
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 de4d054..4de3720 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
@@ -17,18 +17,19 @@
package org.keycloak.protocol.oidc.mappers;
-import org.keycloak.models.ClientSessionModel;
-import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.GroupModel;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
-import org.keycloak.representations.AccessToken;
import org.keycloak.representations.IDToken;
import java.util.ArrayDeque;
import java.util.Deque;
-import java.util.LinkedHashSet;
import java.util.Set;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
/**
* Base class for mapping of user role mappings to an ID and Access Token claim.
@@ -38,39 +39,95 @@ import java.util.Set;
abstract class AbstractUserRoleMappingMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper, UserInfoTokenMapper {
/**
- * Returns the role names extracted from the given {@code roleModels} while recursively traversing "Composite Roles".
- * <p>
- * Optionally prefixes each role name with the given {@code prefix}.
- * </p>
- *
- * @param roleModels
- * @param prefix the prefix to apply, may be {@literal null}
+ * Returns a stream with roles that come from:
+ * <ul>
+ * <li>Direct assignment of the role to the user</li>
+ * <li>Direct assignment of the role to any group of the user or any of its parent group</li>
+ * <li>Composite roles are expanded recursively, the composite role itself is also contained in the returned stream</li>
+ * </ul>
+ * @param user User to enumerate the roles for
+ * @return
+ */
+ public static Stream<RoleModel> getAllUserRolesStream(UserModel user) {
+ return Stream.concat(
+ user.getRoleMappings().stream(),
+ user.getGroups().stream()
+ .flatMap(g -> groupAndItsParentsStream(g))
+ .flatMap(g -> g.getRoleMappings().stream()))
+ .flatMap(role -> expandCompositeRolesStream(role));
+ }
+
+ /**
+ * Returns stream of the given group and its parents (recursively).
+ * @param group
* @return
*/
- protected Set<String> flattenRoleModelToRoleNames(Set<RoleModel> roleModels, String prefix) {
+ private static Stream<GroupModel> groupAndItsParentsStream(GroupModel group) {
+ Stream.Builder<GroupModel> sb = Stream.builder();
+ while (group != null) {
+ sb.add(group);
+ group = group.getParent();
+ }
+ return sb.build();
+ }
- Set<String> roleNames = new LinkedHashSet<>();
+ /**
+ * 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<>(roleModels);
- while (!stack.isEmpty()) {
+ Deque<RoleModel> stack = new ArrayDeque<>();
+ stack.add(role);
+ while (! stack.isEmpty()) {
RoleModel current = stack.pop();
+ sb.add(current);
if (current.isComposite()) {
- for (RoleModel compositeRoleModel : current.getComposites()) {
- stack.push(compositeRoleModel);
- }
+ stack.addAll(current.getComposites());
}
+ }
- String roleName = current.getName();
+ return sb.build();
+ }
- if (prefix != null && !prefix.trim().isEmpty()) {
- roleName = prefix.trim() + roleName;
- }
+ /**
+ * 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),
+ * the final list of roles is also restricted by the client scope. Finally, the list is mapped to the token into
+ * a claim.
+ *
+ * @param token
+ * @param mappingModel
+ * @param userSession
+ * @param restriction
+ * @param prefix
+ */
+ protected static void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession,
+ Predicate<RoleModel> restriction, String prefix) {
+ String rolePrefix = prefix == null ? "" : prefix;
+ UserModel user = userSession.getUser();
+
+ // get a set of all realm roles assigned to the user or its group
+ Stream<RoleModel> clientUserRoles = getAllUserRolesStream(user).filter(restriction);
- roleNames.add(roleName);
+ boolean dontLimitScope = userSession.getClientSessions().stream().anyMatch(cs -> cs.getClient().isFullScopeAllowed());
+ if (! dontLimitScope) {
+ Set<RoleModel> clientRoles = userSession.getClientSessions().stream()
+ .flatMap(cs -> cs.getClient().getScopeMappings().stream())
+ .collect(Collectors.toSet());
+
+ clientUserRoles = clientUserRoles.filter(clientRoles::contains);
}
- return roleNames;
+ Set<String> realmRoleNames = clientUserRoles
+ .map(m -> rolePrefix + m.getName())
+ .collect(Collectors.toSet());
+
+ OIDCAttributeMapperHelper.mapClaim(token, mappingModel, realmRoleNames);
}
}
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 99b2610..8b64aef 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
@@ -100,6 +100,9 @@ public class OIDCAttributeMapperHelper {
if (attributeValue == null) return;
String protocolClaim = mappingModel.getConfig().get(TOKEN_CLAIM_NAME);
+ if (protocolClaim == null) {
+ return;
+ }
String[] split = protocolClaim.split("\\.");
Map<String, Object> jsonObject = token.getOtherClaims();
for (int i = 0; i < split.length; i++) {
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 01d47e1..5a88c2a 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
@@ -18,17 +18,20 @@
package org.keycloak.protocol.oidc.mappers;
import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientTemplateModel;
import org.keycloak.models.ProtocolMapperModel;
+import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
-import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.protocol.ProtocolMapperUtils;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.representations.IDToken;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.function.Predicate;
/**
* Allows mapping of user client role mappings to an ID and Access Token claim.
@@ -39,7 +42,7 @@ public class UserClientRoleMappingMapper extends AbstractUserRoleMappingMapper {
public static final String PROVIDER_ID = "oidc-usermodel-client-role-mapper";
- private static final List<ProviderConfigProperty> CONFIG_PROPERTIES = new ArrayList<ProviderConfigProperty>();
+ private static final List<ProviderConfigProperty> CONFIG_PROPERTIES = new ArrayList<>();
static {
@@ -60,6 +63,7 @@ public class UserClientRoleMappingMapper extends AbstractUserRoleMappingMapper {
OIDCAttributeMapperHelper.addAttributeConfig(CONFIG_PROPERTIES, UserClientRoleMappingMapper.class);
}
+ @Override
public List<ProviderConfigProperty> getConfigProperties() {
return CONFIG_PROPERTIES;
}
@@ -84,23 +88,51 @@ public class UserClientRoleMappingMapper extends AbstractUserRoleMappingMapper {
return "Map a user client role to a token claim.";
}
+ @Override
protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession) {
+ String clientId = mappingModel.getConfig().get(ProtocolMapperUtils.USER_MODEL_CLIENT_ROLE_MAPPING_CLIENT_ID);
+ String rolePrefix = mappingModel.getConfig().get(ProtocolMapperUtils.USER_MODEL_CLIENT_ROLE_MAPPING_ROLE_PREFIX);
+
+ setClaim(token, mappingModel, userSession, getClientRoleFilter(clientId, userSession), rolePrefix);
+ }
- UserModel user = userSession.getUser();
+ private static Predicate<RoleModel> getClientRoleFilter(String clientId, UserSessionModel userSession) {
+ if (clientId == null) {
+ return RoleModel::isClientRole;
+ }
- String clientId = mappingModel.getConfig().get(ProtocolMapperUtils.USER_MODEL_CLIENT_ROLE_MAPPING_CLIENT_ID);
- if (clientId != null) {
+ RealmModel clientRealm = userSession.getRealm();
+ ClientModel client = clientRealm.getClientByClientId(clientId.trim());
- ClientModel clientModel = userSession.getRealm().getClientByClientId(clientId.trim());
- Set<RoleModel> clientRoleMappings = user.getClientRoleMappings(clientModel);
+ if (client == null) {
+ return RoleModel::isClientRole;
+ }
- String rolePrefix = mappingModel.getConfig().get(ProtocolMapperUtils.USER_MODEL_CLIENT_ROLE_MAPPING_ROLE_PREFIX);
- Set<String> clientRoleNames = flattenRoleModelToRoleNames(clientRoleMappings, rolePrefix);
+ ClientTemplateModel template = client.getClientTemplate();
+ boolean useTemplateScope = template != null && client.useTemplateScope();
+ boolean fullScopeAllowed = (useTemplateScope && template.isFullScopeAllowed()) || client.isFullScopeAllowed();
- OIDCAttributeMapperHelper.mapClaim(token, mappingModel, clientRoleNames);
+ Set<RoleModel> clientRoleMappings = client.getRoles();
+ if (fullScopeAllowed) {
+ return clientRoleMappings::contains;
+ }
+
+ Set<RoleModel> scopeMappings = new HashSet<>();
+
+ if (useTemplateScope) {
+ Set<RoleModel> templateScopeMappings = template.getScopeMappings();
+ if (templateScopeMappings != null) {
+ scopeMappings.addAll(templateScopeMappings);
+ }
}
- }
+ Set<RoleModel> clientScopeMappings = client.getScopeMappings();
+ if (clientScopeMappings != null) {
+ scopeMappings.addAll(clientScopeMappings);
+ }
+
+ return role -> clientRoleMappings.contains(role) && scopeMappings.contains(role);
+ }
public static ProtocolMapperModel create(String clientId, String clientRolePrefix,
String name,
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 ef98182..f978b08 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
@@ -18,18 +18,13 @@
package org.keycloak.protocol.oidc.mappers;
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;
/**
* Allows mapping of user realm role mappings to an ID and Access Token claim.
@@ -40,7 +35,7 @@ public class UserRealmRoleMappingMapper extends AbstractUserRoleMappingMapper {
public static final String PROVIDER_ID = "oidc-usermodel-realm-role-mapper";
- private static final List<ProviderConfigProperty> CONFIG_PROPERTIES = new ArrayList<ProviderConfigProperty>();
+ private static final List<ProviderConfigProperty> CONFIG_PROPERTIES = new ArrayList<>();
static {
@@ -54,6 +49,7 @@ public class UserRealmRoleMappingMapper extends AbstractUserRoleMappingMapper {
OIDCAttributeMapperHelper.addAttributeConfig(CONFIG_PROPERTIES, UserRealmRoleMappingMapper.class);
}
+ @Override
public List<ProviderConfigProperty> getConfigProperties() {
return CONFIG_PROPERTIES;
}
@@ -78,17 +74,12 @@ public class UserRealmRoleMappingMapper extends AbstractUserRoleMappingMapper {
return "Map a user realm role to a token claim.";
}
+ @Override
protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession) {
-
- UserModel user = userSession.getUser();
-
String rolePrefix = mappingModel.getConfig().get(ProtocolMapperUtils.USER_MODEL_REALM_ROLE_MAPPING_ROLE_PREFIX);
- Set<String> realmRoleNames = flattenRoleModelToRoleNames(user.getRealmRoleMappings(), rolePrefix);
-
- OIDCAttributeMapperHelper.mapClaim(token, mappingModel, realmRoleNames);
+ AbstractUserRoleMappingMapper.setClaim(token, mappingModel, userSession, role -> ! role.isClientRole(), rolePrefix);
}
-
public static ProtocolMapperModel create(String realmRolePrefix,
String name,
String tokenClaimName, boolean accessToken, boolean idToken) {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/group/GroupTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/group/GroupTest.java
index 7e61f51..1f0274e 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/group/GroupTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/group/GroupTest.java
@@ -438,7 +438,7 @@ public class GroupTest extends AbstractGroupTest {
// List realm roles
assertNames(roles.realmLevel().listAll(), "realm-role", "realm-composite");
- assertNames(roles.realmLevel().listAvailable(), "admin", "offline_access", Constants.AUTHZ_UMA_AUTHORIZATION, "user", "customer-user-premium");
+ assertNames(roles.realmLevel().listAvailable(), "admin", "offline_access", Constants.AUTHZ_UMA_AUTHORIZATION, "user", "customer-user-premium", "realm-composite-role", "sample-realm-role");
assertNames(roles.realmLevel().listEffective(), "realm-role", "realm-composite", "realm-child");
// List client roles
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserTest.java
index 81af8c6..545fbff 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserTest.java
@@ -817,7 +817,7 @@ public class UserTest extends AbstractAdminTest {
// List realm roles
assertNames(roles.realmLevel().listAll(), "realm-role", "realm-composite", "user", "offline_access", Constants.AUTHZ_UMA_AUTHORIZATION);
- assertNames(roles.realmLevel().listAvailable(), "admin", "customer-user-premium");
+ assertNames(roles.realmLevel().listAvailable(), "admin", "customer-user-premium", "realm-composite-role", "sample-realm-role");
assertNames(roles.realmLevel().listEffective(), "realm-role", "realm-composite", "realm-child", "user", "offline_access", Constants.AUTHZ_UMA_AUTHORIZATION);
// List client roles
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 d317af7..ac86d20 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
@@ -43,10 +43,8 @@ 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.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson;
import static org.keycloak.testsuite.admin.ApiUtil.findClientByClientId;
import static org.keycloak.testsuite.admin.ApiUtil.findClientResourceByClientId;
@@ -222,11 +220,152 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest {
// Verify attribute is filled
Map<String, Object> roleMappings = (Map<String, Object>)idToken.getOtherClaims().get("roles-custom");
- Assert.assertEquals(2, roleMappings.size());
+ Assert.assertThat(roleMappings.keySet(), containsInAnyOrder("realm", "test-app"));
String realmRoleMappings = (String) roleMappings.get("realm");
String testAppMappings = (String) roleMappings.get("test-app");
- Assert.assertTrue(realmRoleMappings.contains("pref.user"));
- Assert.assertEquals("[customer-user]", testAppMappings);
+ assertRoles(realmRoleMappings,
+ "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
+ );
+ }
+
+
+ @Test
+ public void testUserGroupRoleToAttributeMappers() throws Exception {
+ // Add mapper for realm roles
+ String clientId = "test-app";
+ ProtocolMapperRepresentation realmMapper = ProtocolMapperUtil.createUserRealmRoleMappingMapper("pref.", "Realm roles mapper", "roles-custom.realm", true, true);
+ ProtocolMapperRepresentation clientMapper = ProtocolMapperUtil.createUserClientRoleMappingMapper(clientId, "ta.", "Client roles mapper", "roles-custom.test-app", true, true);
+
+ ProtocolMappersResource protocolMappers = ApiUtil.findClientResourceByClientId(adminClient.realm("test"), clientId).getProtocolMappers();
+ protocolMappers.createMapper(Arrays.asList(realmMapper, clientMapper));
+
+ // Login user
+ OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("password", "rich.roles@redhat.com", "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", 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
+ );
+ assertRoles(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
+ "ta.sample-client-role" // from realm role realm-composite-role - client role for test-app
+ );
+ }
+
+ @Test
+ public void testUserGroupRoleToAttributeMappersNotScopedOtherApp() throws Exception {
+ String clientId = "test-app-authz";
+ ProtocolMapperRepresentation realmMapper = ProtocolMapperUtil.createUserRealmRoleMappingMapper("pref.", "Realm roles mapper", "roles-custom.realm", true, true);
+ ProtocolMapperRepresentation clientMapper = ProtocolMapperUtil.createUserClientRoleMappingMapper(clientId, null, "Client roles mapper", "roles-custom." + clientId, true, true);
+
+ ProtocolMappersResource protocolMappers = ApiUtil.findClientResourceByClientId(adminClient.realm("test"), clientId).getProtocolMappers();
+ protocolMappers.createMapper(Arrays.asList(realmMapper, clientMapper));
+
+ // Login user
+ ClientManager.realm(adminClient.realm("test")).clientId(clientId).directAccessGrant(true);
+ oauth.clientId(clientId);
+ OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("secret", "rich.roles@redhat.com", "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", 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
+ );
+ assertRoles(testAppAuthzMappings); // There is no client role defined for test-app-authz
+ }
+
+ @Test
+ public void testUserGroupRoleToAttributeMappersScoped() throws Exception {
+ String clientId = "test-app-scope";
+ ProtocolMapperRepresentation realmMapper = ProtocolMapperUtil.createUserRealmRoleMappingMapper("pref.", "Realm roles mapper", "roles-custom.realm", true, true);
+ ProtocolMapperRepresentation clientMapper = ProtocolMapperUtil.createUserClientRoleMappingMapper(clientId, null, "Client roles mapper", "roles-custom.test-app-scope", true, true);
+
+ ProtocolMappersResource protocolMappers = ApiUtil.findClientResourceByClientId(adminClient.realm("test"), clientId).getProtocolMappers();
+ protocolMappers.createMapper(Arrays.asList(realmMapper, clientMapper));
+
+ // Login user
+ ClientManager.realm(adminClient.realm("test")).clientId(clientId).directAccessGrant(true);
+ oauth.clientId(clientId);
+ OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("password", "rich.roles@redhat.com", "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", 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
+ );
+ assertRoles(testAppScopeMappings,
+ "test-app-allowed-by-scope" // from direct assignment to roleRichUser, present as scope allows it
+ );
+ }
+
+ @Test
+ public void testUserGroupRoleToAttributeMappersScopedClientNotSet() throws Exception {
+ String clientId = "test-app-scope";
+ ProtocolMapperRepresentation realmMapper = ProtocolMapperUtil.createUserRealmRoleMappingMapper("pref.", "Realm roles mapper", "roles-custom.realm", true, true);
+ ProtocolMapperRepresentation clientMapper = ProtocolMapperUtil.createUserClientRoleMappingMapper(null, null, "Client roles mapper", "roles-custom.test-app-scope", true, true);
+
+ ProtocolMappersResource protocolMappers = ApiUtil.findClientResourceByClientId(adminClient.realm("test"), clientId).getProtocolMappers();
+ protocolMappers.createMapper(Arrays.asList(realmMapper, clientMapper));
+
+ // Login user
+ ClientManager.realm(adminClient.realm("test")).clientId(clientId).directAccessGrant(true);
+ oauth.clientId(clientId);
+ OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("password", "rich.roles@redhat.com", "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", 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
+ );
+ assertRoles(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
+ );
+ }
+
+ private void assertRoles(String actualRoleString, String...expectedRoles) {
+ String[] roles;
+ Assert.assertThat(actualRoleString.matches("^\\[.*\\]$"), is(true));
+ roles = actualRoleString.substring(1, actualRoleString.length() - 1).split(",\\s*");
+
+ if (expectedRoles == null || expectedRoles.length == 0) {
+ Assert.assertThat(roles, arrayContainingInAnyOrder(""));
+ } else {
+ Assert.assertThat(roles, arrayContainingInAnyOrder(expectedRoles));
+ }
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/testrealm.json b/testsuite/integration-arquillian/tests/base/src/test/resources/testrealm.json
index c0b2b6c..b0e8767 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/testrealm.json
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/testrealm.json
@@ -85,6 +85,21 @@
"groups": [
"/topGroup/level2group"
]
+ },
+ {
+ "username" : "roleRichUser",
+ "enabled": true,
+ "email" : "rich.roles@redhat.com",
+ "credentials" : [
+ { "type" : "password",
+ "value" : "password" }
+ ],
+ "groups": [
+ "/roleRichGroup/level2group"
+ ],
+ "clientRoles": {
+ "test-app-scope": [ "test-app-allowed-by-scope", "test-app-disallowed-by-scope" ]
+ }
}
],
"scopeMappings": [
@@ -95,6 +110,10 @@
{
"client": "test-app",
"roles": ["user"]
+ },
+ {
+ "client": "test-app-scope",
+ "roles": ["user", "admin"]
}
],
"clients": [
@@ -109,6 +128,16 @@
"secret": "password"
},
{
+ "clientId" : "test-app-scope",
+ "enabled": true,
+
+ "redirectUris": [
+ "http://localhost:8180/auth/realms/master/app/*"
+ ],
+ "secret": "password",
+ "fullScopeAllowed": "false"
+ },
+ {
"clientId" : "third-party",
"enabled": true,
"consentRequired": true,
@@ -290,6 +319,22 @@
{
"name": "customer-user-premium",
"description": "Have User Premium privileges"
+ },
+ {
+ "name": "sample-realm-role",
+ "description": "Sample realm role"
+ },
+ {
+ "name": "realm-composite-role",
+ "description": "Realm composite role containing client role",
+ "composite" : true,
+ "composites" : {
+ "realm" : [ "sample-realm-role" ],
+ "client" : {
+ "test-app" : [ "sample-client-role" ],
+ "account" : [ "view-profile" ]
+ }
+ }
}
],
"client" : {
@@ -301,6 +346,31 @@
{
"name": "customer-admin",
"description": "Have Customer Admin privileges"
+ },
+ {
+ "name": "sample-client-role",
+ "description": "Sample client role"
+ },
+ {
+ "name": "customer-admin-composite-role",
+ "description": "Have Customer Admin privileges via composite role",
+ "composite" : true,
+ "composites" : {
+ "realm" : [ "customer-user-premium" ],
+ "client" : {
+ "test-app" : [ "customer-admin" ]
+ }
+ }
+ }
+ ],
+ "test-app-scope" : [
+ {
+ "name": "test-app-allowed-by-scope",
+ "description": "Role allowed by scope in test-app-scope"
+ },
+ {
+ "name": "test-app-disallowed-by-scope",
+ "description": "Role disallowed by scope in test-app-scope"
}
]
}
@@ -328,6 +398,31 @@
}
}
]
+ },
+ {
+ "name": "roleRichGroup",
+ "attributes": {
+ "topAttribute": ["true"]
+
+ },
+ "realmRoles": ["user", "realm-composite-role"],
+ "clientRoles": {
+ "account": ["manage-account"]
+ },
+
+ "subGroups": [
+ {
+ "name": "level2group",
+ "realmRoles": ["admin"],
+ "clientRoles": {
+ "test-app": ["customer-user", "customer-admin-composite-role"]
+ },
+ "attributes": {
+ "level2Attribute": ["true"]
+
+ }
+ }
+ ]
}
],
@@ -337,6 +432,16 @@
{
"client": "third-party",
"roles": ["customer-user"]
+ },
+ {
+ "client": "test-app-scope",
+ "roles": ["customer-admin-composite-role"]
+ }
+ ],
+ "test-app-scope": [
+ {
+ "client": "test-app-scope",
+ "roles": ["test-app-allowed-by-scope"]
}
]
},