diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/HardcodedRole.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/HardcodedRole.java
index d2bbd073f3..10762d497d 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/HardcodedRole.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/HardcodedRole.java
@@ -26,6 +26,7 @@ import org.keycloak.protocol.ProtocolMapperUtils;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.representations.AccessToken;
+import org.keycloak.representations.IDToken;
import org.keycloak.utils.RoleResolveUtil;
import java.util.ArrayList;
@@ -39,7 +40,7 @@ import java.util.Map;
* @author Bill Burke
* @version $Revision: 1 $
*/
-public class HardcodedRole extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper {
+public class HardcodedRole extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, UserInfoTokenMapper {
private static final List configProperties = new ArrayList<>();
@@ -87,9 +88,25 @@ public class HardcodedRole extends AbstractOIDCProtocolMapper implements OIDCAcc
return ProtocolMapperUtils.PRIORITY_HARDCODED_ROLE_MAPPER;
}
+ @Override
+ public AccessToken transformUserInfoToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
+ UserSessionModel userSession, ClientSessionContext clientSessionCtx) {
+ // the mapper is always executed and then other role mappers decide if the claims are really set to the token
+ setClaim(token, mappingModel, userSession, session, clientSessionCtx);
+ return token;
+ }
+
@Override
public AccessToken transformAccessToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
UserSessionModel userSession, ClientSessionContext clientSessionCtx) {
+ // the mapper is always executed and then other role mappers decide if the claims are really set to the token
+ setClaim(token, mappingModel, userSession, session, clientSessionCtx);
+ return token;
+ }
+
+ @Override
+ protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession, KeycloakSession session,
+ ClientSessionContext clientSessionCtx) {
String role = mappingModel.getConfig().get(ROLE_CONFIG);
String[] scopedRole = KeycloakModelUtils.parseRole(role);
@@ -102,8 +119,6 @@ public class HardcodedRole extends AbstractOIDCProtocolMapper implements OIDCAcc
AccessToken.Access access = RoleResolveUtil.getResolvedRealmRoles(session, clientSessionCtx, true);
access.addRole(role);
}
-
- return token;
}
public static ProtocolMapperModel create(String name,
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/RoleNameMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/RoleNameMapper.java
index 3792c22171..4316d0ae32 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/RoleNameMapper.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/RoleNameMapper.java
@@ -26,6 +26,7 @@ import org.keycloak.protocol.ProtocolMapperUtils;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.representations.AccessToken;
+import org.keycloak.representations.IDToken;
import org.keycloak.utils.RoleResolveUtil;
import java.util.ArrayList;
@@ -39,7 +40,7 @@ import java.util.Map;
* @author Bill Burke
* @version $Revision: 1 $
*/
-public class RoleNameMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper {
+public class RoleNameMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, UserInfoTokenMapper {
private static final List configProperties = new ArrayList<>();
@@ -94,9 +95,25 @@ public class RoleNameMapper extends AbstractOIDCProtocolMapper implements OIDCAc
return ProtocolMapperUtils.PRIORITY_ROLE_NAMES_MAPPER;
}
+ @Override
+ public AccessToken transformUserInfoToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
+ UserSessionModel userSession, ClientSessionContext clientSessionCtx) {
+ // the mapper is always executed and then other role mappers decide if the claims are really set to the token
+ setClaim(token, mappingModel, userSession, session, clientSessionCtx);
+ return token;
+ }
+
@Override
public AccessToken transformAccessToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
UserSessionModel userSession, ClientSessionContext clientSessionCtx) {
+ // the mapper is always executed and then other role mappers decide if the claims are really set to the token
+ setClaim(token, mappingModel, userSession, session, clientSessionCtx);
+ return token;
+ }
+
+ @Override
+ protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession, KeycloakSession session,
+ ClientSessionContext clientSessionCtx) {
String role = mappingModel.getConfig().get(ROLE_CONFIG);
String newName = mappingModel.getConfig().get(NEW_ROLE_NAME);
@@ -106,12 +123,12 @@ public class RoleNameMapper extends AbstractOIDCProtocolMapper implements OIDCAc
String roleName = scopedRole[1];
if (appName != null) {
AccessToken.Access access = RoleResolveUtil.getResolvedClientRoles(session, clientSessionCtx, appName, false);
- if (access == null) return token;
- if (!access.getRoles().contains(roleName)) return token;
+ if (access == null) return;
+ if (!access.getRoles().contains(roleName)) return;
access.getRoles().remove(roleName);
} else {
AccessToken.Access access = RoleResolveUtil.getResolvedRealmRoles(session, clientSessionCtx, false);
- if (access == null || !access.getRoles().contains(roleName)) return token;
+ if (access == null || !access.getRoles().contains(roleName)) return;
access.getRoles().remove(roleName);
}
@@ -125,7 +142,6 @@ public class RoleNameMapper extends AbstractOIDCProtocolMapper implements OIDCAc
}
access.addRole(newRoleName);
- return token;
}
public static ProtocolMapperModel create(String name,
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/admin/ApiUtil.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/admin/ApiUtil.java
index 246d77dc11..5d31082280 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/admin/ApiUtil.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/admin/ApiUtil.java
@@ -115,6 +115,15 @@ public class ApiUtil {
return null;
}
+ public static ProtocolMapperRepresentation findProtocolMapperByName(ClientScopeResource scope, String name) {
+ for (ProtocolMapperRepresentation p : scope.getProtocolMappers().getMappers()) {
+ if (p.getName().equals(name)) {
+ return p;
+ }
+ }
+ return null;
+ }
+
public static ClientScopeResource findClientScopeByName(RealmResource realm, String clientScopeName) {
for (ClientScopeRepresentation clientScope : realm.clientScopes().findAll()) {
if (clientScopeName.equals(clientScope.getName())) {
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 ca4a0f852e..c99f266abb 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
@@ -73,10 +73,12 @@ import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.anyOf;
import static org.hamcrest.Matchers.arrayContainingInAnyOrder;
import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.hasItems;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.isEmptyOrNullString;
+import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
@@ -1475,6 +1477,157 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest {
}
}
+ private void checkRealmAccessInOtherClaims(Map otherClaims, String shouldExistRole, String shouldNotExistRole) {
+ assertThat(otherClaims.get("realm_access"), CoreMatchers.instanceOf(Map.class));
+ Map, ?> access = (Map, ?>) otherClaims.get("realm_access");
+ assertThat(access.get("roles"), CoreMatchers.instanceOf(Collection.class));
+ Collection roles = (Collection) access.get("roles");
+ if (shouldExistRole != null) {
+ assertThat(roles, hasItem(shouldExistRole));
+ }
+ if (shouldNotExistRole != null) {
+ assertThat(roles, not(hasItem(shouldNotExistRole)));
+ }
+ }
+
+ private void checkClientAccessInOtherClaims(Map otherClaims, String app, String shouldExistRole, String shouldNotExistRole) {
+ assertThat(otherClaims.get("resource_access"), CoreMatchers.instanceOf(Map.class));
+ Map, ?> access = (Map, ?>) otherClaims.get("resource_access");
+ assertThat(access.get(app), CoreMatchers.instanceOf(Map.class));
+ access = (Map, ?>) access.get(app);
+ assertThat(access.get("roles"), CoreMatchers.instanceOf(Collection.class));
+ Collection roles = (Collection) access.get("roles");
+ if (shouldExistRole != null) {
+ assertThat(roles, hasItem(shouldExistRole));
+ }
+ if (shouldNotExistRole != null) {
+ assertThat(roles, not(hasItem(shouldNotExistRole)));
+ }
+ }
+
+ private Map modifyScopeRolesMapperToBeIncludedInAll(ClientScopeResource rolesScope, ProtocolMapperRepresentation mapper) {
+ Map config = new HashMap<>(mapper.getConfig());
+ mapper.getConfig().put(OIDCAttributeMapperHelper.INCLUDE_IN_USERINFO, "true");
+ mapper.getConfig().put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true");
+ mapper.getConfig().put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "true");
+ rolesScope.getProtocolMappers().update(mapper.getId(), mapper);
+ return config;
+ }
+
+ @Test
+ public void testHardcodeRoleAll() throws Exception {
+ RealmResource testRealm = adminClient.realm("test");
+ ClientResource app = findClientResourceByClientId(testRealm, "test-app");
+ // create two hardcoded realm mappers for realm and client
+ String hardcodedRoleRealmMapperId, hardcodedRoleClientMapperId;
+ try (Response resp = app.getProtocolMappers().createMapper(createHardcodedRole("hardcoded-realm", "hardcoded"))) {
+ hardcodedRoleRealmMapperId = ApiUtil.getCreatedId(resp);
+ }
+ try (Response resp = app.getProtocolMappers().createMapper(createHardcodedRole("hardcoded-app", "test-app.hardcoded"))) {
+ hardcodedRoleClientMapperId = ApiUtil.getCreatedId(resp);
+ }
+ // modify the default role mappers to be included in access, ID and user-info
+ ClientScopeResource rolesScope = ApiUtil.findClientScopeByName(testRealm, OIDCLoginProtocolFactory.ROLES_SCOPE);
+ ProtocolMapperRepresentation realmRolesMapper = ApiUtil.findProtocolMapperByName(rolesScope, OIDCLoginProtocolFactory.REALM_ROLES);
+ Map configRealmRoles = modifyScopeRolesMapperToBeIncludedInAll(rolesScope, realmRolesMapper);
+ ProtocolMapperRepresentation clientRolesMapper = ApiUtil.findProtocolMapperByName(rolesScope, OIDCLoginProtocolFactory.CLIENT_ROLES);
+ Map configClientRoles = modifyScopeRolesMapperToBeIncludedInAll(rolesScope, clientRolesMapper);
+
+ // check that the hardcoded mappers are in the three responses
+ try {
+ OAuthClient.AccessTokenResponse response = browserLogin("password", "test-user@localhost", "password");
+
+ // check hardcoded roles in access token
+ AccessToken accessToken = oauth.verifyToken(response.getAccessToken());
+ assertThat(accessToken.getRealmAccess().getRoles(), hasItem("hardcoded"));
+ assertNotNull(accessToken.getResourceAccess("test-app"));
+ assertThat(accessToken.getResourceAccess("test-app").getRoles(), hasItem("hardcoded"));
+
+ // in ID token
+ IDToken idToken = oauth.verifyIDToken(response.getIdToken());
+ checkRealmAccessInOtherClaims(idToken.getOtherClaims(), "hardcoded", null);
+ checkClientAccessInOtherClaims(idToken.getOtherClaims(), "test-app", "hardcoded", null);
+
+ // in the user info
+ Client client = AdminClientUtil.createResteasyClient();
+ try {
+ Response userInfoResponse = UserInfoClientUtil.executeUserInfoRequest_getMethod(client, response.getAccessToken());
+ UserInfo userInfo = userInfoResponse.readEntity(UserInfo.class);
+ assertEquals("test-user@localhost", userInfo.getPreferredUsername());
+ checkRealmAccessInOtherClaims(userInfo.getOtherClaims(), "hardcoded", null);
+ checkClientAccessInOtherClaims(userInfo.getOtherClaims(), "test-app", "hardcoded", null);
+ } finally {
+ client.close();
+ }
+ } finally {
+ // reset the roles client scopes
+ app.getProtocolMappers().delete(hardcodedRoleRealmMapperId);
+ app.getProtocolMappers().delete(hardcodedRoleClientMapperId);
+ realmRolesMapper.setConfig(configRealmRoles);
+ rolesScope.getProtocolMappers().update(realmRolesMapper.getId(), realmRolesMapper);
+ clientRolesMapper.setConfig(configClientRoles);
+ rolesScope.getProtocolMappers().update(clientRolesMapper.getId(), clientRolesMapper);
+ }
+ }
+
+ @Test
+ public void testRoleNameMapperAll() throws Exception {
+ RealmResource testRealm = adminClient.realm("test");
+ ClientResource app = findClientResourceByClientId(testRealm, "test-app");
+ // create two role name mappers for realm and client
+ String realmRoleNameMapperId, clientRoleNameMapperId;
+ try (Response resp = app.getProtocolMappers().createMapper(createRoleNameMapper("rename-realm-role", "user", "realm-user"))) {
+ realmRoleNameMapperId = ApiUtil.getCreatedId(resp);
+ }
+ try (Response resp = app.getProtocolMappers().createMapper(createRoleNameMapper("rename-app-role", "test-app.customer-user", "test-app.test-app-user"))) {
+ clientRoleNameMapperId = ApiUtil.getCreatedId(resp);
+ }
+ // modify the default role mappers to be included in access, ID and user-info
+ ClientScopeResource rolesScope = ApiUtil.findClientScopeByName(testRealm, OIDCLoginProtocolFactory.ROLES_SCOPE);
+ ProtocolMapperRepresentation realmRolesMapper = ApiUtil.findProtocolMapperByName(rolesScope, OIDCLoginProtocolFactory.REALM_ROLES);
+ Map configRealmRoles = modifyScopeRolesMapperToBeIncludedInAll(rolesScope, realmRolesMapper);
+ ProtocolMapperRepresentation clientRolesMapper = ApiUtil.findProtocolMapperByName(rolesScope, OIDCLoginProtocolFactory.CLIENT_ROLES);
+ Map configClientRoles = modifyScopeRolesMapperToBeIncludedInAll(rolesScope, clientRolesMapper);
+
+ // check that the role mappers are executed in the three responses
+ try {
+ OAuthClient.AccessTokenResponse response = browserLogin("password", "test-user@localhost", "password");
+
+ // check mapped roles are in access token and not the original ones
+ AccessToken accessToken = oauth.verifyToken(response.getAccessToken());
+ assertThat(accessToken.getRealmAccess().getRoles(), hasItem("realm-user"));
+ assertThat(accessToken.getRealmAccess().getRoles(), not(hasItem("user")));
+ assertNotNull(accessToken.getResourceAccess("test-app"));
+ assertThat(accessToken.getResourceAccess("test-app").getRoles(), hasItem("test-app-user"));
+ assertThat(accessToken.getResourceAccess("test-app").getRoles(), not(hasItem("customer-user")));
+
+ // same in ID token
+ IDToken idToken = oauth.verifyIDToken(response.getIdToken());
+ checkRealmAccessInOtherClaims(idToken.getOtherClaims(), "realm-user", "user");
+ checkClientAccessInOtherClaims(idToken.getOtherClaims(), "test-app", "test-app-user", "customer-user");
+
+ // same in user info
+ Client client = AdminClientUtil.createResteasyClient();
+ try {
+ Response userInfoResponse = UserInfoClientUtil.executeUserInfoRequest_getMethod(client, response.getAccessToken());
+ UserInfo userInfo = userInfoResponse.readEntity(UserInfo.class);
+ assertEquals("test-user@localhost", userInfo.getPreferredUsername());
+ checkRealmAccessInOtherClaims(userInfo.getOtherClaims(), "realm-user", "user");
+ checkClientAccessInOtherClaims(userInfo.getOtherClaims(), "test-app", "test-app-user", "customer-user");
+ } finally {
+ client.close();
+ }
+ } finally {
+ // reset the roles client scopes
+ app.getProtocolMappers().delete(realmRoleNameMapperId);
+ app.getProtocolMappers().delete(clientRoleNameMapperId);
+ realmRolesMapper.setConfig(configRealmRoles);
+ rolesScope.getProtocolMappers().update(realmRolesMapper.getId(), realmRolesMapper);
+ clientRolesMapper.setConfig(configClientRoles);
+ rolesScope.getProtocolMappers().update(clientRolesMapper.getId(), clientRolesMapper);
+ }
+ }
+
@Test
@EnableFeature(value = Profile.Feature.DYNAMIC_SCOPES, skipRestart = true)
public void executeTokenMappersOnDynamicScopes() {