Merge pull request #3859 from thomasdarimont/issue/KEYCLOAK-4205-role-mapper-value-as-array
KEYCLOAK-4205 Allow to return json arrays in Client and Realm Role Mappers
This commit is contained in:
commit
16d5ca3378
5 changed files with 154 additions and 52 deletions
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
ProtocolMapperModel mapper = OIDCAttributeMapperHelper.createClaimMapper(name, "foo",
|
||||
tokenClaimName, "String",
|
||||
true, name,
|
||||
accessToken, idToken,
|
||||
PROVIDER_ID);
|
||||
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);
|
||||
|
||||
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;
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
ProtocolMapperModel mapper = OIDCAttributeMapperHelper.createClaimMapper(name, "foo",
|
||||
tokenClaimName, "String",
|
||||
true, name,
|
||||
accessToken, idToken,
|
||||
PROVIDER_ID);
|
||||
|
||||
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);
|
||||
|
||||
mapper.getConfig().put(ProtocolMapperUtils.MULTIVALUED, String.valueOf(multiValued));
|
||||
mapper.getConfig().put(ProtocolMapperUtils.USER_MODEL_REALM_ROLE_MAPPING_ROLE_PREFIX, realmRolePrefix);
|
||||
return mapper;
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(""));
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in a new issue