KEYCLOAK-3469 Make role mappers account for user groups

This commit is contained in:
Hynek Mlnarik 2016-11-07 15:10:00 +01:00
parent 6c64494620
commit 750e942267
9 changed files with 407 additions and 63 deletions

View file

@ -24,8 +24,17 @@ import java.util.Set;
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public interface RoleMapperModel { public interface RoleMapperModel {
/**
* Returns set of realm roles that are directly set to this object.
* @return see description
*/
Set<RoleModel> getRealmRoleMappings(); 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); Set<RoleModel> getClientRoleMappings(ClientModel app);
/** /**
@ -48,7 +57,15 @@ public interface RoleMapperModel {
*/ */
void grantRole(RoleModel role); void grantRole(RoleModel role);
/**
* Returns set of all role (both realm all client) that are directly set to this object.
* @return
*/
Set<RoleModel> getRoleMappings(); Set<RoleModel> getRoleMappings();
/**
* Removes the given role mapping from this object.
* @param role Role to remove
*/
void deleteRoleMapping(RoleModel role); void deleteRoleMapping(RoleModel role);
} }

View file

@ -17,18 +17,19 @@
package org.keycloak.protocol.oidc.mappers; package org.keycloak.protocol.oidc.mappers;
import org.keycloak.models.ClientSessionModel; import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RoleModel; import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel; import org.keycloak.models.UserSessionModel;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.IDToken; import org.keycloak.representations.IDToken;
import java.util.ArrayDeque; import java.util.ArrayDeque;
import java.util.Deque; import java.util.Deque;
import java.util.LinkedHashSet;
import java.util.Set; 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. * 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 { abstract class AbstractUserRoleMappingMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper, UserInfoTokenMapper {
/** /**
* Returns the role names extracted from the given {@code roleModels} while recursively traversing "Composite Roles". * Returns a stream with roles that come from:
* <p> * <ul>
* Optionally prefixes each role name with the given {@code prefix}. * <li>Direct assignment of the role to the user</li>
* </p> * <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>
* @param roleModels * </ul>
* @param prefix the prefix to apply, may be {@literal null} * @param user User to enumerate the roles for
* @return * @return
*/ */
protected Set<String> flattenRoleModelToRoleNames(Set<RoleModel> roleModels, String prefix) { 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));
}
Set<String> roleNames = new LinkedHashSet<>(); /**
* Returns stream of the given group and its parents (recursively).
* @param group
* @return
*/
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();
}
Deque<RoleModel> stack = new ArrayDeque<>(roleModels); /**
while (!stack.isEmpty()) { * 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(); RoleModel current = stack.pop();
sb.add(current);
if (current.isComposite()) { if (current.isComposite()) {
for (RoleModel compositeRoleModel : current.getComposites()) { stack.addAll(current.getComposites());
stack.push(compositeRoleModel);
} }
} }
String roleName = current.getName(); return sb.build();
if (prefix != null && !prefix.trim().isEmpty()) {
roleName = prefix.trim() + roleName;
} }
roleNames.add(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);
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);
} }
} }

View file

@ -100,6 +100,9 @@ public class OIDCAttributeMapperHelper {
if (attributeValue == null) return; if (attributeValue == null) return;
String protocolClaim = mappingModel.getConfig().get(TOKEN_CLAIM_NAME); String protocolClaim = mappingModel.getConfig().get(TOKEN_CLAIM_NAME);
if (protocolClaim == null) {
return;
}
String[] split = protocolClaim.split("\\."); String[] split = protocolClaim.split("\\.");
Map<String, Object> jsonObject = token.getOtherClaims(); Map<String, Object> jsonObject = token.getOtherClaims();
for (int i = 0; i < split.length; i++) { for (int i = 0; i < split.length; i++) {

View file

@ -18,17 +18,20 @@
package org.keycloak.protocol.oidc.mappers; package org.keycloak.protocol.oidc.mappers;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientTemplateModel;
import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel; import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel; import org.keycloak.models.UserSessionModel;
import org.keycloak.protocol.ProtocolMapperUtils; import org.keycloak.protocol.ProtocolMapperUtils;
import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.representations.IDToken; import org.keycloak.representations.IDToken;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.function.Predicate;
/** /**
* Allows mapping of user client role mappings to an ID and Access Token claim. * 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"; 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 { static {
@ -60,6 +63,7 @@ public class UserClientRoleMappingMapper extends AbstractUserRoleMappingMapper {
OIDCAttributeMapperHelper.addAttributeConfig(CONFIG_PROPERTIES, UserClientRoleMappingMapper.class); OIDCAttributeMapperHelper.addAttributeConfig(CONFIG_PROPERTIES, UserClientRoleMappingMapper.class);
} }
@Override
public List<ProviderConfigProperty> getConfigProperties() { public List<ProviderConfigProperty> getConfigProperties() {
return CONFIG_PROPERTIES; return CONFIG_PROPERTIES;
} }
@ -84,23 +88,51 @@ public class UserClientRoleMappingMapper extends AbstractUserRoleMappingMapper {
return "Map a user client role to a token claim."; return "Map a user client role to a token claim.";
} }
@Override
protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession) { protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession) {
UserModel user = userSession.getUser();
String clientId = mappingModel.getConfig().get(ProtocolMapperUtils.USER_MODEL_CLIENT_ROLE_MAPPING_CLIENT_ID); String clientId = mappingModel.getConfig().get(ProtocolMapperUtils.USER_MODEL_CLIENT_ROLE_MAPPING_CLIENT_ID);
if (clientId != null) {
ClientModel clientModel = userSession.getRealm().getClientByClientId(clientId.trim());
Set<RoleModel> clientRoleMappings = user.getClientRoleMappings(clientModel);
String rolePrefix = mappingModel.getConfig().get(ProtocolMapperUtils.USER_MODEL_CLIENT_ROLE_MAPPING_ROLE_PREFIX); String rolePrefix = mappingModel.getConfig().get(ProtocolMapperUtils.USER_MODEL_CLIENT_ROLE_MAPPING_ROLE_PREFIX);
Set<String> clientRoleNames = flattenRoleModelToRoleNames(clientRoleMappings, rolePrefix);
OIDCAttributeMapperHelper.mapClaim(token, mappingModel, clientRoleNames); setClaim(token, mappingModel, userSession, getClientRoleFilter(clientId, userSession), rolePrefix);
}
private static Predicate<RoleModel> getClientRoleFilter(String clientId, UserSessionModel userSession) {
if (clientId == null) {
return RoleModel::isClientRole;
}
RealmModel clientRealm = userSession.getRealm();
ClientModel client = clientRealm.getClientByClientId(clientId.trim());
if (client == null) {
return RoleModel::isClientRole;
}
ClientTemplateModel template = client.getClientTemplate();
boolean useTemplateScope = template != null && client.useTemplateScope();
boolean fullScopeAllowed = (useTemplateScope && template.isFullScopeAllowed()) || client.isFullScopeAllowed();
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, public static ProtocolMapperModel create(String clientId, String clientRolePrefix,
String name, String name,

View file

@ -18,18 +18,13 @@
package org.keycloak.protocol.oidc.mappers; package org.keycloak.protocol.oidc.mappers;
import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel; import org.keycloak.models.UserSessionModel;
import org.keycloak.protocol.ProtocolMapperUtils; import org.keycloak.protocol.ProtocolMapperUtils;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.representations.IDToken; import org.keycloak.representations.IDToken;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; 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. * 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"; 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 { static {
@ -54,6 +49,7 @@ public class UserRealmRoleMappingMapper extends AbstractUserRoleMappingMapper {
OIDCAttributeMapperHelper.addAttributeConfig(CONFIG_PROPERTIES, UserRealmRoleMappingMapper.class); OIDCAttributeMapperHelper.addAttributeConfig(CONFIG_PROPERTIES, UserRealmRoleMappingMapper.class);
} }
@Override
public List<ProviderConfigProperty> getConfigProperties() { public List<ProviderConfigProperty> getConfigProperties() {
return CONFIG_PROPERTIES; return CONFIG_PROPERTIES;
} }
@ -78,17 +74,12 @@ public class UserRealmRoleMappingMapper extends AbstractUserRoleMappingMapper {
return "Map a user realm role to a token claim."; return "Map a user realm role to a token claim.";
} }
@Override
protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession) { 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); String rolePrefix = mappingModel.getConfig().get(ProtocolMapperUtils.USER_MODEL_REALM_ROLE_MAPPING_ROLE_PREFIX);
Set<String> realmRoleNames = flattenRoleModelToRoleNames(user.getRealmRoleMappings(), rolePrefix); AbstractUserRoleMappingMapper.setClaim(token, mappingModel, userSession, role -> ! role.isClientRole(), rolePrefix);
OIDCAttributeMapperHelper.mapClaim(token, mappingModel, realmRoleNames);
} }
public static ProtocolMapperModel create(String realmRolePrefix, public static ProtocolMapperModel create(String realmRolePrefix,
String name, String name,
String tokenClaimName, boolean accessToken, boolean idToken) { String tokenClaimName, boolean accessToken, boolean idToken) {

View file

@ -817,7 +817,7 @@ public class UserTest extends AbstractAdminTest {
// List realm roles // List realm roles
assertNames(roles.realmLevel().listAll(), "realm-role", "realm-composite", "user", "offline_access", Constants.AUTHZ_UMA_AUTHORIZATION); 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); assertNames(roles.realmLevel().listEffective(), "realm-role", "realm-composite", "realm-child", "user", "offline_access", Constants.AUTHZ_UMA_AUTHORIZATION);
// List client roles // List client roles

View file

@ -438,7 +438,7 @@ public class GroupTest extends AbstractGroupTest {
// List realm roles // List realm roles
assertNames(roles.realmLevel().listAll(), "realm-role", "realm-composite"); 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"); assertNames(roles.realmLevel().listEffective(), "realm-role", "realm-composite", "realm-child");
// List client roles // List client roles

View file

@ -43,10 +43,8 @@ import org.keycloak.testsuite.util.ClientManager;
import org.keycloak.testsuite.util.OAuthClient; import org.keycloak.testsuite.util.OAuthClient;
import org.keycloak.testsuite.util.ProtocolMapperUtil; import org.keycloak.testsuite.util.ProtocolMapperUtil;
import static org.junit.Assert.assertEquals; import static org.hamcrest.Matchers.*;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.*;
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.AbstractAdminTest.loadJson;
import static org.keycloak.testsuite.admin.ApiUtil.findClientByClientId; import static org.keycloak.testsuite.admin.ApiUtil.findClientByClientId;
import static org.keycloak.testsuite.admin.ApiUtil.findClientResourceByClientId; import static org.keycloak.testsuite.admin.ApiUtil.findClientResourceByClientId;
@ -222,11 +220,152 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest {
// Verify attribute is filled // Verify attribute is filled
Map<String, Object> roleMappings = (Map<String, Object>)idToken.getOtherClaims().get("roles-custom"); 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 realmRoleMappings = (String) roleMappings.get("realm");
String testAppMappings = (String) roleMappings.get("test-app"); String testAppMappings = (String) roleMappings.get("test-app");
Assert.assertTrue(realmRoleMappings.contains("pref.user")); assertRoles(realmRoleMappings,
Assert.assertEquals("[customer-user]", testAppMappings); "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));
}
} }

View file

@ -85,6 +85,21 @@
"groups": [ "groups": [
"/topGroup/level2group" "/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": [ "scopeMappings": [
@ -95,6 +110,10 @@
{ {
"client": "test-app", "client": "test-app",
"roles": ["user"] "roles": ["user"]
},
{
"client": "test-app-scope",
"roles": ["user", "admin"]
} }
], ],
"clients": [ "clients": [
@ -108,6 +127,16 @@
"adminUrl": "http://localhost:8180/auth/realms/master/app/admin", "adminUrl": "http://localhost:8180/auth/realms/master/app/admin",
"secret": "password" "secret": "password"
}, },
{
"clientId" : "test-app-scope",
"enabled": true,
"redirectUris": [
"http://localhost:8180/auth/realms/master/app/*"
],
"secret": "password",
"fullScopeAllowed": "false"
},
{ {
"clientId" : "third-party", "clientId" : "third-party",
"enabled": true, "enabled": true,
@ -290,6 +319,22 @@
{ {
"name": "customer-user-premium", "name": "customer-user-premium",
"description": "Have User Premium privileges" "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" : { "client" : {
@ -301,6 +346,31 @@
{ {
"name": "customer-admin", "name": "customer-admin",
"description": "Have Customer Admin privileges" "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"
} }
] ]
} }
@ -325,6 +395,31 @@
"attributes": { "attributes": {
"level2Attribute": ["true"] "level2Attribute": ["true"]
}
}
]
},
{
"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", "client": "third-party",
"roles": ["customer-user"] "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"]
} }
] ]
}, },