From 0ad1a9ef9ee5ac9a162e7bd8721601bc927db460 Mon Sep 17 00:00:00 2001 From: Matthias Piepkorn <mpiepk@gmail.com> Date: Sun, 29 Jan 2017 11:01:01 +0000 Subject: [PATCH] Add more attribute mappers, cleanup existing mappers --- src/main/java/org/keycloak/protocol/cas/mappers/GroupMembershipMapper.java | 30 +-- src/main/java/org/keycloak/protocol/cas/mappers/CASAttributeMapperHelper.java | 28 +++ src/main/java/org/keycloak/protocol/cas/mappers/HardcodedClaim.java | 8 src/main/java/org/keycloak/protocol/cas/mappers/UserRealmRoleMappingMapper.java | 67 +++++++ src/main/java/org/keycloak/protocol/cas/mappers/UserAttributeMapper.java | 26 -- src/main/java/org/keycloak/protocol/cas/mappers/AbstractUserRoleMappingMapper.java | 98 ++++++++++ src/main/java/org/keycloak/protocol/cas/mappers/UserPropertyMapper.java | 25 -- src/main/java/org/keycloak/protocol/cas/mappers/UserSessionNoteMapper.java | 73 ++++++++ src/main/java/org/keycloak/protocol/cas/mappers/AbstractCASProtocolMapper.java | 16 + src/main/java/org/keycloak/protocol/cas/utils/AttributesMapAdapter.java | 5 src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper | 3 src/main/java/org/keycloak/protocol/cas/mappers/UserClientRoleMappingMapper.java | 115 ++++++++++++ src/main/java/org/keycloak/protocol/cas/mappers/FullNameMapper.java | 22 -- 13 files changed, 427 insertions(+), 89 deletions(-) diff --git a/src/main/java/org/keycloak/protocol/cas/mappers/AbstractCASProtocolMapper.java b/src/main/java/org/keycloak/protocol/cas/mappers/AbstractCASProtocolMapper.java index 2c19433..6838f6d 100644 --- a/src/main/java/org/keycloak/protocol/cas/mappers/AbstractCASProtocolMapper.java +++ b/src/main/java/org/keycloak/protocol/cas/mappers/AbstractCASProtocolMapper.java @@ -3,8 +3,12 @@ import org.keycloak.Config; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.models.ProtocolMapperModel; import org.keycloak.protocol.ProtocolMapper; import org.keycloak.protocol.cas.CASLoginProtocol; +import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper; + +import java.util.Map; public abstract class AbstractCASProtocolMapper implements ProtocolMapper, CASAttributeMapper { public static final String TOKEN_MAPPER_CATEGORY = "Token mapper"; @@ -35,4 +39,16 @@ public String getDisplayCategory() { return TOKEN_MAPPER_CATEGORY; } + + protected void setMappedAttribute(Map<String, Object> attributes, ProtocolMapperModel mappingModel, Object attributeValue) { + setPlainAttribute(attributes, mappingModel, OIDCAttributeMapperHelper.mapAttributeValue(mappingModel, attributeValue)); + } + + protected void setPlainAttribute(Map<String, Object> attributes, ProtocolMapperModel mappingModel, Object attributeValue) { + String protocolClaim = mappingModel.getConfig().get(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME); + if (protocolClaim == null || attributeValue == null) { + return; + } + attributes.put(protocolClaim, attributeValue); + } } diff --git a/src/main/java/org/keycloak/protocol/cas/mappers/AbstractUserRoleMappingMapper.java b/src/main/java/org/keycloak/protocol/cas/mappers/AbstractUserRoleMappingMapper.java new file mode 100644 index 0000000..93f59f1 --- /dev/null +++ b/src/main/java/org/keycloak/protocol/cas/mappers/AbstractUserRoleMappingMapper.java @@ -0,0 +1,98 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.protocol.cas.mappers; + +import org.keycloak.models.*; +import org.keycloak.models.utils.RoleUtils; + +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. + * + * @author <a href="mailto:thomas.darimont@gmail.com">Thomas Darimont</a> + */ +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.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); + } + + Set<String> realmRoleNames = clientUserRoles + .map(m -> rolePrefix + m.getName()) + .collect(Collectors.toSet()); + + setPlainAttribute(attributes, mappingModel, realmRoleNames); + } +} diff --git a/src/main/java/org/keycloak/protocol/cas/mappers/CASAttributeMapperHelper.java b/src/main/java/org/keycloak/protocol/cas/mappers/CASAttributeMapperHelper.java new file mode 100644 index 0000000..53ba5d2 --- /dev/null +++ b/src/main/java/org/keycloak/protocol/cas/mappers/CASAttributeMapperHelper.java @@ -0,0 +1,28 @@ +package org.keycloak.protocol.cas.mappers; + +import org.keycloak.models.ProtocolMapperModel; +import org.keycloak.protocol.cas.CASLoginProtocol; +import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper; + +import java.util.HashMap; +import java.util.Map; + +public class CASAttributeMapperHelper { + public static ProtocolMapperModel createClaimMapper(String name, + String tokenClaimName, String claimType, + boolean consentRequired, String consentText, + String mapperId) { + ProtocolMapperModel mapper = new ProtocolMapperModel(); + mapper.setName(name); + mapper.setProtocolMapper(mapperId); + mapper.setProtocol(CASLoginProtocol.LOGIN_PROTOCOL); + mapper.setConsentRequired(consentRequired); + mapper.setConsentText(consentText); + Map<String, String> config = new HashMap<String, String>(); + config.put(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME, tokenClaimName); + config.put(OIDCAttributeMapperHelper.JSON_TYPE, claimType); + mapper.setConfig(config); + return mapper; + } + +} diff --git a/src/main/java/org/keycloak/protocol/cas/mappers/FullNameMapper.java b/src/main/java/org/keycloak/protocol/cas/mappers/FullNameMapper.java index e66da7d..aef4b51 100644 --- a/src/main/java/org/keycloak/protocol/cas/mappers/FullNameMapper.java +++ b/src/main/java/org/keycloak/protocol/cas/mappers/FullNameMapper.java @@ -3,16 +3,12 @@ import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.UserModel; import org.keycloak.models.UserSessionModel; -import org.keycloak.protocol.cas.CASLoginProtocol; import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper; import org.keycloak.provider.ProviderConfigProperty; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map; - -import static org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME; public class FullNameMapper extends AbstractCASProtocolMapper { private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>(); @@ -47,26 +43,14 @@ @Override public void setAttribute(Map<String, Object> attributes, ProtocolMapperModel mappingModel, UserSessionModel userSession) { UserModel user = userSession.getUser(); - String protocolClaim = mappingModel.getConfig().get(TOKEN_CLAIM_NAME); - if (protocolClaim == null) { - return; - } String first = user.getFirstName() == null ? "" : user.getFirstName() + " "; String last = user.getLastName() == null ? "" : user.getLastName(); - attributes.put(protocolClaim, first + last); + setMappedAttribute(attributes, mappingModel, first + last); } public static ProtocolMapperModel create(String name, String tokenClaimName, boolean consentRequired, String consentText) { - ProtocolMapperModel mapper = new ProtocolMapperModel(); - mapper.setName(name); - mapper.setProtocolMapper(PROVIDER_ID); - mapper.setProtocol(CASLoginProtocol.LOGIN_PROTOCOL); - mapper.setConsentRequired(consentRequired); - mapper.setConsentText(consentText); - Map<String, String> config = new HashMap<String, String>(); - config.put(TOKEN_CLAIM_NAME, tokenClaimName); - mapper.setConfig(config); - return mapper; + return CASAttributeMapperHelper.createClaimMapper(name, tokenClaimName, + "String", consentRequired, consentText, PROVIDER_ID); } } diff --git a/src/main/java/org/keycloak/protocol/cas/mappers/GroupMembershipMapper.java b/src/main/java/org/keycloak/protocol/cas/mappers/GroupMembershipMapper.java index 6d9c8ac..a6db974 100644 --- a/src/main/java/org/keycloak/protocol/cas/mappers/GroupMembershipMapper.java +++ b/src/main/java/org/keycloak/protocol/cas/mappers/GroupMembershipMapper.java @@ -4,19 +4,23 @@ import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.UserSessionModel; import org.keycloak.models.utils.ModelToRepresentation; -import org.keycloak.protocol.cas.CASLoginProtocol; import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper; import org.keycloak.provider.ProviderConfigProperty; -import java.util.*; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; public class GroupMembershipMapper extends AbstractCASProtocolMapper { private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>(); + private static final String FULL_PATH = "full.path"; + static { OIDCAttributeMapperHelper.addTokenClaimNameConfig(configProperties); ProviderConfigProperty property1 = new ProviderConfigProperty(); - property1.setName("full.path"); + property1.setName(FULL_PATH); property1.setLabel("Full group path"); property1.setType(ProviderConfigProperty.BOOLEAN_TYPE); property1.setDefaultValue("true"); @@ -58,28 +62,18 @@ membership.add(group.getName()); } } - String protocolClaim = mappingModel.getConfig().get(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME); - - attributes.put(protocolClaim, membership); + setPlainAttribute(attributes, mappingModel, membership); } public static boolean useFullPath(ProtocolMapperModel mappingModel) { - return "true".equals(mappingModel.getConfig().get("full.path")); + return "true".equals(mappingModel.getConfig().get(FULL_PATH)); } public static ProtocolMapperModel create(String name, String tokenClaimName, boolean consentRequired, String consentText, boolean fullPath) { - ProtocolMapperModel mapper = new ProtocolMapperModel(); - mapper.setName(name); - mapper.setProtocolMapper(PROVIDER_ID); - mapper.setProtocol(CASLoginProtocol.LOGIN_PROTOCOL); - mapper.setConsentRequired(consentRequired); - mapper.setConsentText(consentText); - Map<String, String> config = new HashMap<String, String>(); - config.put(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME, tokenClaimName); - config.put("full.path", Boolean.toString(fullPath)); - mapper.setConfig(config); - + ProtocolMapperModel mapper = CASAttributeMapperHelper.createClaimMapper(name, tokenClaimName, + "String", consentRequired, consentText, PROVIDER_ID); + mapper.getConfig().put(FULL_PATH, Boolean.toString(fullPath)); return mapper; } } diff --git a/src/main/java/org/keycloak/protocol/cas/mappers/HardcodedClaim.java b/src/main/java/org/keycloak/protocol/cas/mappers/HardcodedClaim.java index df7000c..42c7535 100644 --- a/src/main/java/org/keycloak/protocol/cas/mappers/HardcodedClaim.java +++ b/src/main/java/org/keycloak/protocol/cas/mappers/HardcodedClaim.java @@ -54,13 +54,7 @@ @Override public void setAttribute(Map<String, Object> attributes, ProtocolMapperModel mappingModel, UserSessionModel userSession) { - String protocolClaim = mappingModel.getConfig().get(TOKEN_CLAIM_NAME); - if (protocolClaim == null) { - return; - } - String attributeValue = mappingModel.getConfig().get(CLAIM_VALUE); - if (attributeValue == null) return; - attributes.put(protocolClaim, OIDCAttributeMapperHelper.mapAttributeValue(mappingModel, attributeValue)); + setMappedAttribute(attributes, mappingModel, mappingModel.getConfig().get(CLAIM_VALUE)); } } diff --git a/src/main/java/org/keycloak/protocol/cas/mappers/UserAttributeMapper.java b/src/main/java/org/keycloak/protocol/cas/mappers/UserAttributeMapper.java index 3637069..19173c2 100644 --- a/src/main/java/org/keycloak/protocol/cas/mappers/UserAttributeMapper.java +++ b/src/main/java/org/keycloak/protocol/cas/mappers/UserAttributeMapper.java @@ -5,17 +5,12 @@ import org.keycloak.models.UserSessionModel; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.protocol.ProtocolMapperUtils; -import org.keycloak.protocol.cas.CASLoginProtocol; import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper; import org.keycloak.provider.ProviderConfigProperty; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map; - -import static org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper.JSON_TYPE; -import static org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME; public class UserAttributeMapper extends AbstractCASProtocolMapper { private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>(); @@ -66,33 +61,20 @@ @Override public void setAttribute(Map<String, Object> attributes, ProtocolMapperModel mappingModel, UserSessionModel userSession) { UserModel user = userSession.getUser(); - String protocolClaim = mappingModel.getConfig().get(TOKEN_CLAIM_NAME); - if (protocolClaim == null) { - return; - } String attributeName = mappingModel.getConfig().get(ProtocolMapperUtils.USER_ATTRIBUTE); List<String> attributeValue = KeycloakModelUtils.resolveAttribute(user, attributeName); - if (attributeValue == null) return; - attributes.put(protocolClaim, OIDCAttributeMapperHelper.mapAttributeValue(mappingModel, attributeValue)); + setMappedAttribute(attributes, mappingModel, attributeValue); } public static ProtocolMapperModel create(String name, String userAttribute, String tokenClaimName, String claimType, boolean consentRequired, String consentText, boolean multivalued) { - ProtocolMapperModel mapper = new ProtocolMapperModel(); - mapper.setName(name); - mapper.setProtocolMapper(PROVIDER_ID); - mapper.setProtocol(CASLoginProtocol.LOGIN_PROTOCOL); - mapper.setConsentRequired(consentRequired); - mapper.setConsentText(consentText); - Map<String, String> config = new HashMap<String, String>(); - config.put(ProtocolMapperUtils.USER_ATTRIBUTE, userAttribute); - config.put(TOKEN_CLAIM_NAME, tokenClaimName); - config.put(JSON_TYPE, claimType); + ProtocolMapperModel mapper = CASAttributeMapperHelper.createClaimMapper(name, tokenClaimName, + claimType, consentRequired, consentText, PROVIDER_ID); + mapper.getConfig().put(ProtocolMapperUtils.USER_ATTRIBUTE, userAttribute); if (multivalued) { mapper.getConfig().put(ProtocolMapperUtils.MULTIVALUED, "true"); } - mapper.setConfig(config); return mapper; } } diff --git a/src/main/java/org/keycloak/protocol/cas/mappers/UserClientRoleMappingMapper.java b/src/main/java/org/keycloak/protocol/cas/mappers/UserClientRoleMappingMapper.java new file mode 100644 index 0000000..15ff8ac --- /dev/null +++ b/src/main/java/org/keycloak/protocol/cas/mappers/UserClientRoleMappingMapper.java @@ -0,0 +1,115 @@ +package org.keycloak.protocol.cas.mappers; + +import org.keycloak.models.*; +import org.keycloak.protocol.ProtocolMapperUtils; +import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper; +import org.keycloak.provider.ProviderConfigProperty; + +import java.util.*; +import java.util.function.Predicate; + +public class UserClientRoleMappingMapper extends AbstractUserRoleMappingMapper { + + public static final String PROVIDER_ID = "cas-usermodel-client-role-mapper"; + + private static final List<ProviderConfigProperty> CONFIG_PROPERTIES = new ArrayList<>(); + + static { + + ProviderConfigProperty clientId = new ProviderConfigProperty(); + clientId.setName(ProtocolMapperUtils.USER_MODEL_CLIENT_ROLE_MAPPING_CLIENT_ID); + clientId.setLabel(ProtocolMapperUtils.USER_MODEL_CLIENT_ROLE_MAPPING_CLIENT_ID_LABEL); + clientId.setHelpText(ProtocolMapperUtils.USER_MODEL_CLIENT_ROLE_MAPPING_CLIENT_ID_HELP_TEXT); + clientId.setType(ProviderConfigProperty.CLIENT_LIST_TYPE); + CONFIG_PROPERTIES.add(clientId); + + ProviderConfigProperty clientRolePrefix = new ProviderConfigProperty(); + clientRolePrefix.setName(ProtocolMapperUtils.USER_MODEL_CLIENT_ROLE_MAPPING_ROLE_PREFIX); + clientRolePrefix.setLabel(ProtocolMapperUtils.USER_MODEL_CLIENT_ROLE_MAPPING_ROLE_PREFIX_LABEL); + clientRolePrefix.setHelpText(ProtocolMapperUtils.USER_MODEL_CLIENT_ROLE_MAPPING_ROLE_PREFIX_HELP_TEXT); + clientRolePrefix.setType(ProviderConfigProperty.STRING_TYPE); + CONFIG_PROPERTIES.add(clientRolePrefix); + + OIDCAttributeMapperHelper.addTokenClaimNameConfig(CONFIG_PROPERTIES); + } + + @Override + public List<ProviderConfigProperty> getConfigProperties() { + return CONFIG_PROPERTIES; + } + + @Override + public String getId() { + return PROVIDER_ID; + } + + @Override + public String getDisplayType() { + return "User Client Role"; + } + + @Override + public String getDisplayCategory() { + return TOKEN_MAPPER_CATEGORY; + } + + @Override + public String getHelpText() { + return "Map a user client role to a token claim."; + } + + @Override + public void setAttribute(Map<String, Object> attributes, 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); + + setAttribute(attributes, 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, + String name, String tokenClaimName) { + ProtocolMapperModel mapper = CASAttributeMapperHelper.createClaimMapper(name, tokenClaimName, + "String", true, name, PROVIDER_ID); + 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; + } +} diff --git a/src/main/java/org/keycloak/protocol/cas/mappers/UserPropertyMapper.java b/src/main/java/org/keycloak/protocol/cas/mappers/UserPropertyMapper.java index 057636d..b299b27 100644 --- a/src/main/java/org/keycloak/protocol/cas/mappers/UserPropertyMapper.java +++ b/src/main/java/org/keycloak/protocol/cas/mappers/UserPropertyMapper.java @@ -4,17 +4,12 @@ import org.keycloak.models.UserModel; import org.keycloak.models.UserSessionModel; import org.keycloak.protocol.ProtocolMapperUtils; -import org.keycloak.protocol.cas.CASLoginProtocol; import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper; import org.keycloak.provider.ProviderConfigProperty; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map; - -import static org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper.JSON_TYPE; -import static org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME; public class UserPropertyMapper extends AbstractCASProtocolMapper { private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>(); @@ -57,29 +52,17 @@ @Override public void setAttribute(Map<String, Object> attributes, ProtocolMapperModel mappingModel, UserSessionModel userSession) { UserModel user = userSession.getUser(); - String protocolClaim = mappingModel.getConfig().get(TOKEN_CLAIM_NAME); - if (protocolClaim == null) { - return; - } String propertyName = mappingModel.getConfig().get(ProtocolMapperUtils.USER_ATTRIBUTE); String propertyValue = ProtocolMapperUtils.getUserModelValue(user, propertyName); - attributes.put(protocolClaim, OIDCAttributeMapperHelper.mapAttributeValue(mappingModel, propertyValue)); + setMappedAttribute(attributes, mappingModel, propertyValue); } public static ProtocolMapperModel create(String name, String userAttribute, String tokenClaimName, String claimType, boolean consentRequired, String consentText) { - ProtocolMapperModel mapper = new ProtocolMapperModel(); - mapper.setName(name); - mapper.setProtocolMapper(PROVIDER_ID); - mapper.setProtocol(CASLoginProtocol.LOGIN_PROTOCOL); - mapper.setConsentRequired(consentRequired); - mapper.setConsentText(consentText); - Map<String, String> config = new HashMap<String, String>(); - config.put(ProtocolMapperUtils.USER_ATTRIBUTE, userAttribute); - config.put(TOKEN_CLAIM_NAME, tokenClaimName); - config.put(JSON_TYPE, claimType); - mapper.setConfig(config); + ProtocolMapperModel mapper = CASAttributeMapperHelper.createClaimMapper(name, tokenClaimName, + claimType, consentRequired, consentText, PROVIDER_ID); + mapper.getConfig().put(ProtocolMapperUtils.USER_ATTRIBUTE, userAttribute); return mapper; } } diff --git a/src/main/java/org/keycloak/protocol/cas/mappers/UserRealmRoleMappingMapper.java b/src/main/java/org/keycloak/protocol/cas/mappers/UserRealmRoleMappingMapper.java new file mode 100644 index 0000000..117264a --- /dev/null +++ b/src/main/java/org/keycloak/protocol/cas/mappers/UserRealmRoleMappingMapper.java @@ -0,0 +1,67 @@ +package org.keycloak.protocol.cas.mappers; + +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 java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class UserRealmRoleMappingMapper extends AbstractUserRoleMappingMapper { + public static final String PROVIDER_ID = "cas-usermodel-realm-role-mapper"; + + private static final List<ProviderConfigProperty> CONFIG_PROPERTIES = new ArrayList<>(); + + static { + + ProviderConfigProperty realmRolePrefix = new ProviderConfigProperty(); + realmRolePrefix.setName(ProtocolMapperUtils.USER_MODEL_REALM_ROLE_MAPPING_ROLE_PREFIX); + realmRolePrefix.setLabel(ProtocolMapperUtils.USER_MODEL_REALM_ROLE_MAPPING_ROLE_PREFIX_LABEL); + realmRolePrefix.setHelpText(ProtocolMapperUtils.USER_MODEL_REALM_ROLE_MAPPING_ROLE_PREFIX_HELP_TEXT); + realmRolePrefix.setType(ProviderConfigProperty.STRING_TYPE); + CONFIG_PROPERTIES.add(realmRolePrefix); + + OIDCAttributeMapperHelper.addTokenClaimNameConfig(CONFIG_PROPERTIES); + } + + @Override + public List<ProviderConfigProperty> getConfigProperties() { + return CONFIG_PROPERTIES; + } + + @Override + public String getId() { + return PROVIDER_ID; + } + + @Override + public String getDisplayType() { + return "User Realm Role"; + } + + @Override + public String getDisplayCategory() { + return TOKEN_MAPPER_CATEGORY; + } + + @Override + public String getHelpText() { + return "Map a user realm role to a token claim."; + } + + @Override + public void setAttribute(Map<String, Object> attributes, ProtocolMapperModel mappingModel, UserSessionModel userSession) { + String rolePrefix = mappingModel.getConfig().get(ProtocolMapperUtils.USER_MODEL_REALM_ROLE_MAPPING_ROLE_PREFIX); + setAttribute(attributes, mappingModel, userSession, role -> ! role.isClientRole(), rolePrefix); + } + + public static ProtocolMapperModel create(String realmRolePrefix, String name, String tokenClaimName) { + ProtocolMapperModel mapper = CASAttributeMapperHelper.createClaimMapper(name, tokenClaimName, + "String", true, name, PROVIDER_ID); + mapper.getConfig().put(ProtocolMapperUtils.USER_MODEL_REALM_ROLE_MAPPING_ROLE_PREFIX, realmRolePrefix); + return mapper; + } +} diff --git a/src/main/java/org/keycloak/protocol/cas/mappers/UserSessionNoteMapper.java b/src/main/java/org/keycloak/protocol/cas/mappers/UserSessionNoteMapper.java new file mode 100644 index 0000000..5c2881a --- /dev/null +++ b/src/main/java/org/keycloak/protocol/cas/mappers/UserSessionNoteMapper.java @@ -0,0 +1,73 @@ +package org.keycloak.protocol.cas.mappers; + +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 java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class UserSessionNoteMapper extends AbstractCASProtocolMapper { + + private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>(); + + static { + ProviderConfigProperty property; + property = new ProviderConfigProperty(); + property.setName(ProtocolMapperUtils.USER_SESSION_NOTE); + property.setLabel(ProtocolMapperUtils.USER_SESSION_MODEL_NOTE_LABEL); + property.setHelpText(ProtocolMapperUtils.USER_SESSION_MODEL_NOTE_HELP_TEXT); + property.setType(ProviderConfigProperty.STRING_TYPE); + configProperties.add(property); + OIDCAttributeMapperHelper.addTokenClaimNameConfig(configProperties); + OIDCAttributeMapperHelper.addJsonTypeConfig(configProperties); + } + + public static final String PROVIDER_ID = "cas-usersessionmodel-note-mapper"; + + + public List<ProviderConfigProperty> getConfigProperties() { + return configProperties; + } + + @Override + public String getId() { + return PROVIDER_ID; + } + + @Override + public String getDisplayType() { + return "User Session Note"; + } + + @Override + public String getDisplayCategory() { + return TOKEN_MAPPER_CATEGORY; + } + + @Override + public String getHelpText() { + return "Map a custom user session note to a token claim."; + } + + @Override + public void setAttribute(Map<String, Object> attributes, ProtocolMapperModel mappingModel, UserSessionModel userSession) { + String noteName = mappingModel.getConfig().get(ProtocolMapperUtils.USER_SESSION_NOTE); + String noteValue = userSession.getNote(noteName); + if (noteValue == null) return; + setMappedAttribute(attributes, mappingModel, noteValue); + } + + public static ProtocolMapperModel create(String name, + String userSessionNote, + String tokenClaimName, String jsonType, + boolean consentRequired, String consentText) { + ProtocolMapperModel mapper = CASAttributeMapperHelper.createClaimMapper(name, tokenClaimName, + jsonType, consentRequired, consentText, PROVIDER_ID); + mapper.getConfig().put(ProtocolMapperUtils.USER_SESSION_NOTE, userSessionNote); + return mapper; + } +} diff --git a/src/main/java/org/keycloak/protocol/cas/utils/AttributesMapAdapter.java b/src/main/java/org/keycloak/protocol/cas/utils/AttributesMapAdapter.java index bf9b148..ca8f7ed 100644 --- a/src/main/java/org/keycloak/protocol/cas/utils/AttributesMapAdapter.java +++ b/src/main/java/org/keycloak/protocol/cas/utils/AttributesMapAdapter.java @@ -10,6 +10,7 @@ import javax.xml.bind.annotation.adapters.XmlAdapter; import javax.xml.namespace.QName; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Map; @@ -37,8 +38,8 @@ AttributeWrapperType(Map<String, Object> attributes) { this.elements = new ArrayList<>(); for (Map.Entry<String, Object> entry : attributes.entrySet()) { - if (entry.getValue() instanceof List) { - for (Object item : ((List) entry.getValue())) { + if (entry.getValue() instanceof Collection) { + for (Object item : ((Collection) entry.getValue())) { addElement(entry.getKey(), item); } } else { diff --git a/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper b/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper index 46409eb..353f1c8 100644 --- a/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper +++ b/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper @@ -19,4 +19,7 @@ org.keycloak.protocol.cas.mappers.GroupMembershipMapper org.keycloak.protocol.cas.mappers.HardcodedClaim org.keycloak.protocol.cas.mappers.UserAttributeMapper +org.keycloak.protocol.cas.mappers.UserClientRoleMappingMapper org.keycloak.protocol.cas.mappers.UserPropertyMapper +org.keycloak.protocol.cas.mappers.UserRealmRoleMappingMapper +org.keycloak.protocol.cas.mappers.UserSessionNoteMapper -- Gitblit v1.9.1