update for KEYCLOAK-6884 KEYCLOAK-3454 KEYCLOAK-8298
| | |
| | | for (ProtocolMapperModel mapping : mappings) { |
| | | ProtocolMapper mapper = (ProtocolMapper) sessionFactory.getProviderFactory(ProtocolMapper.class, mapping.getProtocolMapper()); |
| | | if (mapper instanceof CASAttributeMapper) { |
| | | ((CASAttributeMapper) mapper).setAttribute(attributes, mapping, userSession); |
| | | ((CASAttributeMapper) mapper).setAttribute(attributes, mapping, userSession, session, clientSessionCtx); |
| | | } |
| | | } |
| | | |
| | |
| | | |
| | | package org.keycloak.protocol.cas.mappers; |
| | | |
| | | import org.keycloak.models.*; |
| | | import org.keycloak.models.utils.RoleUtils; |
| | | import org.keycloak.models.ProtocolMapperModel; |
| | | |
| | | import java.util.Map; |
| | | 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. |
| | |
| | | abstract class AbstractUserRoleMappingMapper extends AbstractCASProtocolMapper { |
| | | |
| | | /** |
| | | * 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 |
| | | */ |
| | | public Stream<RoleModel> getAllUserRolesStream(UserModel user) { |
| | | return Stream.concat( |
| | | user.getRoleMappings().stream(), |
| | | user.getGroups().stream() |
| | | .flatMap(this::groupAndItsParentsStream) |
| | | .flatMap(g -> g.getRoleMappings().stream())) |
| | | .flatMap(RoleUtils::expandCompositeRolesStream); |
| | | } |
| | | |
| | | /** |
| | | * Returns stream of the given group and its parents (recursively). |
| | | * @param group |
| | | * @return |
| | | */ |
| | | private Stream<GroupModel> groupAndItsParentsStream(GroupModel group) { |
| | | Stream.Builder<GroupModel> sb = Stream.builder(); |
| | | while (group != null) { |
| | | sb.add(group); |
| | | group = group.getParent(); |
| | | } |
| | | return sb.build(); |
| | | } |
| | | |
| | | /** |
| | | * 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. |
| | | */ |
| | | protected void setAttribute(Map<String, Object> attributes, 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.getAuthenticatedClientSessions().values().stream().anyMatch(cs -> cs.getClient().isFullScopeAllowed()); |
| | | if (! dontLimitScope) { |
| | | Set<RoleModel> clientRoles = userSession.getAuthenticatedClientSessions().values().stream() |
| | | .flatMap(cs -> cs.getClient().getScopeMappings().stream()) |
| | | protected void setAttribute(Map<String, Object> attributes, ProtocolMapperModel mappingModel, Set<String> rolesToAdd, |
| | | String prefix) { |
| | | Set<String> realmRoleNames; |
| | | if (prefix != null && !prefix.isEmpty()) { |
| | | realmRoleNames = rolesToAdd.stream() |
| | | .map(roleName -> prefix + roleName) |
| | | .collect(Collectors.toSet()); |
| | | |
| | | clientUserRoles = clientUserRoles.filter(clientRoles::contains); |
| | | } else { |
| | | realmRoleNames = rolesToAdd; |
| | | } |
| | | |
| | | Set<String> realmRoleNames = clientUserRoles |
| | | .map(m -> rolePrefix + m.getName()) |
| | | .collect(Collectors.toSet()); |
| | | |
| | | setPlainAttribute(attributes, mappingModel, realmRoleNames); |
| | | } |
| | |
| | | package org.keycloak.protocol.cas.mappers; |
| | | |
| | | import org.keycloak.models.ClientSessionContext; |
| | | import org.keycloak.models.KeycloakSession; |
| | | import org.keycloak.models.ProtocolMapperModel; |
| | | import org.keycloak.models.UserSessionModel; |
| | | |
| | | import java.util.Map; |
| | | |
| | | public interface CASAttributeMapper { |
| | | void setAttribute(Map<String, Object> attributes, ProtocolMapperModel mappingModel, UserSessionModel userSession); |
| | | void setAttribute(Map<String, Object> attributes, ProtocolMapperModel mappingModel, UserSessionModel userSession, |
| | | KeycloakSession session, ClientSessionContext clientSessionCtx); |
| | | } |
| | |
| | | package org.keycloak.protocol.cas.mappers; |
| | | |
| | | import org.keycloak.models.ProtocolMapperModel; |
| | | import org.keycloak.models.UserModel; |
| | | import org.keycloak.models.UserSessionModel; |
| | | import org.keycloak.models.*; |
| | | import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper; |
| | | import org.keycloak.provider.ProviderConfigProperty; |
| | | |
| | |
| | | } |
| | | |
| | | @Override |
| | | public void setAttribute(Map<String, Object> attributes, ProtocolMapperModel mappingModel, UserSessionModel userSession) { |
| | | public void setAttribute(Map<String, Object> attributes, ProtocolMapperModel mappingModel, UserSessionModel userSession, |
| | | KeycloakSession session, ClientSessionContext clientSessionCt) { |
| | | UserModel user = userSession.getUser(); |
| | | String first = user.getFirstName() == null ? "" : user.getFirstName() + " "; |
| | | String last = user.getLastName() == null ? "" : user.getLastName(); |
| | |
| | | package org.keycloak.protocol.cas.mappers; |
| | | |
| | | import org.keycloak.models.GroupModel; |
| | | import org.keycloak.models.ProtocolMapperModel; |
| | | import org.keycloak.models.UserSessionModel; |
| | | import org.keycloak.models.*; |
| | | import org.keycloak.models.utils.ModelToRepresentation; |
| | | import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper; |
| | | import org.keycloak.provider.ProviderConfigProperty; |
| | |
| | | } |
| | | |
| | | @Override |
| | | public void setAttribute(Map<String, Object> attributes, ProtocolMapperModel mappingModel, UserSessionModel userSession) { |
| | | public void setAttribute(Map<String, Object> attributes, ProtocolMapperModel mappingModel, UserSessionModel userSession, |
| | | KeycloakSession session, ClientSessionContext clientSessionCt) { |
| | | List<String> membership = new LinkedList<>(); |
| | | boolean fullPath = useFullPath(mappingModel); |
| | | for (GroupModel group : userSession.getUser().getGroups()) { |
| | |
| | | package org.keycloak.protocol.cas.mappers; |
| | | |
| | | import org.keycloak.models.ClientSessionContext; |
| | | import org.keycloak.models.KeycloakSession; |
| | | import org.keycloak.models.ProtocolMapperModel; |
| | | import org.keycloak.models.UserSessionModel; |
| | | import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper; |
| | |
| | | } |
| | | |
| | | @Override |
| | | public void setAttribute(Map<String, Object> attributes, ProtocolMapperModel mappingModel, UserSessionModel userSession) { |
| | | public void setAttribute(Map<String, Object> attributes, ProtocolMapperModel mappingModel, UserSessionModel userSession, |
| | | KeycloakSession session, ClientSessionContext clientSessionCt) { |
| | | setMappedAttribute(attributes, mappingModel, mappingModel.getConfig().get(CLAIM_VALUE)); |
| | | } |
| | | |
| | |
| | | package org.keycloak.protocol.cas.mappers; |
| | | |
| | | import org.keycloak.models.ProtocolMapperModel; |
| | | import org.keycloak.models.UserModel; |
| | | import org.keycloak.models.UserSessionModel; |
| | | import org.keycloak.models.*; |
| | | import org.keycloak.models.utils.KeycloakModelUtils; |
| | | import org.keycloak.protocol.ProtocolMapperUtils; |
| | | import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper; |
| | |
| | | } |
| | | |
| | | @Override |
| | | public void setAttribute(Map<String, Object> attributes, ProtocolMapperModel mappingModel, UserSessionModel userSession) { |
| | | public void setAttribute(Map<String, Object> attributes, ProtocolMapperModel mappingModel, UserSessionModel userSession, |
| | | KeycloakSession session, ClientSessionContext clientSessionCt) { |
| | | UserModel user = userSession.getUser(); |
| | | String attributeName = mappingModel.getConfig().get(ProtocolMapperUtils.USER_ATTRIBUTE); |
| | | boolean aggregateAttrs = Boolean.valueOf(mappingModel.getConfig().get(ProtocolMapperUtils.AGGREGATE_ATTRS)); |
| | |
| | | |
| | | import org.keycloak.models.*; |
| | | import org.keycloak.protocol.ProtocolMapperUtils; |
| | | import org.keycloak.protocol.oidc.TokenManager; |
| | | import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper; |
| | | import org.keycloak.provider.ProviderConfigProperty; |
| | | import org.keycloak.representations.AccessToken; |
| | | import org.keycloak.utils.RoleResolveUtil; |
| | | |
| | | import java.util.*; |
| | | import java.util.function.Predicate; |
| | | import java.util.stream.Collectors; |
| | | |
| | | public class UserClientRoleMappingMapper extends AbstractUserRoleMappingMapper { |
| | | |
| | |
| | | } |
| | | |
| | | @Override |
| | | public void setAttribute(Map<String, Object> attributes, ProtocolMapperModel mappingModel, UserSessionModel userSession) { |
| | | public void setAttribute(Map<String, Object> attributes, ProtocolMapperModel mappingModel, UserSessionModel userSession, |
| | | KeycloakSession session, ClientSessionContext clientSessionCtx) { |
| | | 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); |
| | | |
| | | setAttribute(attributes, mappingModel, userSession, getClientRoleFilter(clientId, userSession), rolePrefix); |
| | | if (clientId != null && !clientId.isEmpty()) { |
| | | AccessToken.Access access = RoleResolveUtil.getResolvedClientRoles(session, clientSessionCtx, clientId, false); |
| | | if (access == null) { |
| | | return; |
| | | } |
| | | |
| | | private static Predicate<RoleModel> getClientRoleFilter(String clientId, UserSessionModel userSession) { |
| | | if (clientId == null) { |
| | | return RoleModel::isClientRole; |
| | | setAttribute(attributes, mappingModel, access.getRoles(), rolePrefix); |
| | | } else { |
| | | // If clientId is not specified, we consider all clients |
| | | Map<String, AccessToken.Access> allAccess = RoleResolveUtil.getAllResolvedClientRoles(session, clientSessionCtx); |
| | | Set<String> allRoles = allAccess.values().stream().filter(Objects::nonNull) |
| | | .flatMap(access -> access.getRoles().stream()) |
| | | .collect(Collectors.toSet()); |
| | | setAttribute(attributes, mappingModel, allRoles, rolePrefix); |
| | | } |
| | | |
| | | RealmModel clientRealm = userSession.getRealm(); |
| | | ClientModel client = clientRealm.getClientByClientId(clientId.trim()); |
| | | |
| | | if (client == null) { |
| | | return RoleModel::isClientRole; |
| | | } |
| | | |
| | | boolean fullScopeAllowed = client.isFullScopeAllowed(); |
| | | Set<RoleModel> clientRoleMappings = client.getRoles(); |
| | | if (fullScopeAllowed) { |
| | | return clientRoleMappings::contains; |
| | | } |
| | | |
| | | Set<RoleModel> scopeMappings = new HashSet<>(); |
| | | |
| | | // CAS protocol does not support scopes, so pass null scopeParam |
| | | Set<ClientScopeModel> clientScopes = TokenManager.getRequestedClientScopes(null, client); |
| | | for (ClientScopeModel clientScope : clientScopes) { |
| | | scopeMappings.addAll(clientScope.getScopeMappings()); |
| | | } |
| | | |
| | | return role -> clientRoleMappings.contains(role) && scopeMappings.contains(role); |
| | | } |
| | | |
| | | public static ProtocolMapperModel create(String clientId, String clientRolePrefix, |
| | |
| | | package org.keycloak.protocol.cas.mappers; |
| | | |
| | | import org.keycloak.models.ProtocolMapperModel; |
| | | import org.keycloak.models.UserModel; |
| | | import org.keycloak.models.UserSessionModel; |
| | | import org.keycloak.models.*; |
| | | import org.keycloak.protocol.ProtocolMapperUtils; |
| | | import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper; |
| | | import org.keycloak.provider.ProviderConfigProperty; |
| | |
| | | } |
| | | |
| | | @Override |
| | | public void setAttribute(Map<String, Object> attributes, ProtocolMapperModel mappingModel, UserSessionModel userSession) { |
| | | public void setAttribute(Map<String, Object> attributes, ProtocolMapperModel mappingModel, UserSessionModel userSession, |
| | | KeycloakSession session, ClientSessionContext clientSessionCt) { |
| | | UserModel user = userSession.getUser(); |
| | | String propertyName = mappingModel.getConfig().get(ProtocolMapperUtils.USER_ATTRIBUTE); |
| | | String propertyValue = ProtocolMapperUtils.getUserModelValue(user, propertyName); |
| | |
| | | package org.keycloak.protocol.cas.mappers; |
| | | |
| | | import org.keycloak.models.ClientSessionContext; |
| | | import org.keycloak.models.KeycloakSession; |
| | | import org.keycloak.models.ProtocolMapperModel; |
| | | import org.keycloak.models.UserSessionModel; |
| | | import org.keycloak.protocol.ProtocolMapperUtils; |
| | | import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper; |
| | | import org.keycloak.provider.ProviderConfigProperty; |
| | | import org.keycloak.representations.AccessToken; |
| | | import org.keycloak.utils.RoleResolveUtil; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.List; |
| | |
| | | } |
| | | |
| | | @Override |
| | | public void setAttribute(Map<String, Object> attributes, ProtocolMapperModel mappingModel, UserSessionModel userSession) { |
| | | public void setAttribute(Map<String, Object> attributes, ProtocolMapperModel mappingModel, UserSessionModel userSession, |
| | | KeycloakSession session, ClientSessionContext clientSessionCtx) { |
| | | String rolePrefix = mappingModel.getConfig().get(ProtocolMapperUtils.USER_MODEL_REALM_ROLE_MAPPING_ROLE_PREFIX); |
| | | setAttribute(attributes, mappingModel, userSession, role -> ! role.isClientRole(), rolePrefix); |
| | | |
| | | AccessToken.Access access = RoleResolveUtil.getResolvedRealmRoles(session, clientSessionCtx, false); |
| | | if (access == null) { |
| | | return; |
| | | } |
| | | |
| | | setAttribute(attributes, mappingModel, access.getRoles(), rolePrefix); |
| | | } |
| | | |
| | | public static ProtocolMapperModel create(String realmRolePrefix, String name, String tokenClaimName) { |
| | |
| | | package org.keycloak.protocol.cas.mappers; |
| | | |
| | | import org.keycloak.models.ClientSessionContext; |
| | | import org.keycloak.models.KeycloakSession; |
| | | import org.keycloak.models.ProtocolMapperModel; |
| | | import org.keycloak.models.UserSessionModel; |
| | | import org.keycloak.protocol.ProtocolMapperUtils; |
| | |
| | | } |
| | | |
| | | @Override |
| | | public void setAttribute(Map<String, Object> attributes, ProtocolMapperModel mappingModel, UserSessionModel userSession) { |
| | | public void setAttribute(Map<String, Object> attributes, ProtocolMapperModel mappingModel, UserSessionModel userSession, |
| | | KeycloakSession session, ClientSessionContext clientSessionCt) { |
| | | String noteName = mappingModel.getConfig().get(ProtocolMapperUtils.USER_SESSION_NOTE); |
| | | String noteValue = userSession.getNote(noteName); |
| | | if (noteValue == null) return; |