Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions server-spi/src/main/java/org/keycloak/models/RoleMapperModel.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,17 @@
* @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);

/**
Expand All @@ -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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -38,39 +39,95 @@
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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ public static void mapClaim(IDToken token, ProtocolMapperModel mappingModel, Obj
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++) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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 {

Expand All @@ -60,6 +63,7 @@ public class UserClientRoleMappingMapper extends AbstractUserRoleMappingMapper {
OIDCAttributeMapperHelper.addAttributeConfig(CONFIG_PROPERTIES, UserClientRoleMappingMapper.class);
}

@Override
public List<ProviderConfigProperty> getConfigProperties() {
return CONFIG_PROPERTIES;
}
Expand All @@ -84,23 +88,51 @@ public String getHelpText() {
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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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 {

Expand All @@ -54,6 +49,7 @@ public class UserRealmRoleMappingMapper extends AbstractUserRoleMappingMapper {
OIDCAttributeMapperHelper.addAttributeConfig(CONFIG_PROPERTIES, UserRealmRoleMappingMapper.class);
}

@Override
public List<ProviderConfigProperty> getConfigProperties() {
return CONFIG_PROPERTIES;
}
Expand All @@ -78,17 +74,12 @@ public String getHelpText() {
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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -817,7 +817,7 @@ public void roleMappings() {

// 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -438,7 +438,7 @@ public void roleMappings() {

// 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
Expand Down
Loading