mirror of https://github.com/jacekkow/keycloak-protocol-cas

Matthias Piepkorn
2017-01-29 0ad1a9ef9ee5ac9a162e7bd8721601bc927db460
Add more attribute mappers, cleanup existing mappers
5 files added
8 files modified
516 ■■■■ changed files
src/main/java/org/keycloak/protocol/cas/mappers/AbstractCASProtocolMapper.java 16 ●●●●● patch | view | raw | blame | history
src/main/java/org/keycloak/protocol/cas/mappers/AbstractUserRoleMappingMapper.java 98 ●●●●● patch | view | raw | blame | history
src/main/java/org/keycloak/protocol/cas/mappers/CASAttributeMapperHelper.java 28 ●●●●● patch | view | raw | blame | history
src/main/java/org/keycloak/protocol/cas/mappers/FullNameMapper.java 22 ●●●● patch | view | raw | blame | history
src/main/java/org/keycloak/protocol/cas/mappers/GroupMembershipMapper.java 30 ●●●●● patch | view | raw | blame | history
src/main/java/org/keycloak/protocol/cas/mappers/HardcodedClaim.java 8 ●●●● patch | view | raw | blame | history
src/main/java/org/keycloak/protocol/cas/mappers/UserAttributeMapper.java 26 ●●●● patch | view | raw | blame | history
src/main/java/org/keycloak/protocol/cas/mappers/UserClientRoleMappingMapper.java 115 ●●●●● patch | view | raw | blame | history
src/main/java/org/keycloak/protocol/cas/mappers/UserPropertyMapper.java 25 ●●●● patch | view | raw | blame | history
src/main/java/org/keycloak/protocol/cas/mappers/UserRealmRoleMappingMapper.java 67 ●●●●● patch | view | raw | blame | history
src/main/java/org/keycloak/protocol/cas/mappers/UserSessionNoteMapper.java 73 ●●●●● patch | view | raw | blame | history
src/main/java/org/keycloak/protocol/cas/utils/AttributesMapAdapter.java 5 ●●●●● patch | view | raw | blame | history
src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper 3 ●●●●● patch | view | raw | blame | history
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);
    }
}
src/main/java/org/keycloak/protocol/cas/mappers/AbstractUserRoleMappingMapper.java
New file
@@ -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);
    }
}
src/main/java/org/keycloak/protocol/cas/mappers/CASAttributeMapperHelper.java
New file
@@ -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;
    }
}
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);
    }
}
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;
    }
}
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));
    }
}
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;
    }
}
src/main/java/org/keycloak/protocol/cas/mappers/UserClientRoleMappingMapper.java
New file
@@ -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;
    }
}
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;
    }
}
src/main/java/org/keycloak/protocol/cas/mappers/UserRealmRoleMappingMapper.java
New file
@@ -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;
    }
}
src/main/java/org/keycloak/protocol/cas/mappers/UserSessionNoteMapper.java
New file
@@ -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;
    }
}
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 {
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