diff --git a/services/src/main/java/org/keycloak/protocol/ProtocolMapperUtils.java b/services/src/main/java/org/keycloak/protocol/ProtocolMapperUtils.java index 32d2272ca4..da73139e9f 100755 --- a/services/src/main/java/org/keycloak/protocol/ProtocolMapperUtils.java +++ b/services/src/main/java/org/keycloak/protocol/ProtocolMapperUtils.java @@ -22,6 +22,7 @@ import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.UserModel; import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory; +import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.provider.ProviderFactory; import java.lang.reflect.Method; @@ -31,6 +32,8 @@ import java.lang.reflect.Method; * @version $Revision: 1 $ */ public class ProtocolMapperUtils { + + public static final String USER_ROLE = "user.role"; public static final String USER_ATTRIBUTE = "user.attribute"; public static final String USER_SESSION_NOTE = "user.session.note"; public static final String MULTIVALUED = "multivalued"; @@ -38,6 +41,19 @@ public class ProtocolMapperUtils { public static final String USER_MODEL_PROPERTY_HELP_TEXT = "usermodel.prop.tooltip"; public static final String USER_MODEL_ATTRIBUTE_LABEL = "usermodel.attr.label"; public static final String USER_MODEL_ATTRIBUTE_HELP_TEXT = "usermodel.attr.tooltip"; + + public static final String USER_MODEL_CLIENT_ROLE_MAPPING_CLIENT_ID = "usermodel.clientRoleMapping.clientId"; + public static final String USER_MODEL_CLIENT_ROLE_MAPPING_CLIENT_ID_LABEL = "usermodel.clientRoleMapping.clientId.label"; + public static final String USER_MODEL_CLIENT_ROLE_MAPPING_CLIENT_ID_HELP_TEXT = "usermodel.clientRoleMapping.clientId.tooltip"; + + public static final String USER_MODEL_CLIENT_ROLE_MAPPING_ROLE_PREFIX = "usermodel.clientRoleMapping.rolePrefix"; + public static final String USER_MODEL_CLIENT_ROLE_MAPPING_ROLE_PREFIX_LABEL = "usermodel.clientRoleMapping.rolePrefix.label"; + public static final String USER_MODEL_CLIENT_ROLE_MAPPING_ROLE_PREFIX_HELP_TEXT = "usermodel.clientRoleMapping.rolePrefix.tooltip"; + + public static final String USER_MODEL_REALM_ROLE_MAPPING_ROLE_PREFIX = "usermodel.realmRoleMapping.rolePrefix"; + public static final String USER_MODEL_REALM_ROLE_MAPPING_ROLE_PREFIX_LABEL = "usermodel.realmRoleMapping.rolePrefix.label"; + public static final String USER_MODEL_REALM_ROLE_MAPPING_ROLE_PREFIX_HELP_TEXT = "usermodel.realmRoleMapping.rolePrefix.tooltip"; + public static final String USER_SESSION_MODEL_NOTE_LABEL = "userSession.modelNote.label"; public static final String USER_SESSION_MODEL_NOTE_HELP_TEXT = "userSession.modelNote.tooltip"; public static final String MULTIVALUED_LABEL = "multivalued.label"; diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractUserRoleMappingMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractUserRoleMappingMapper.java new file mode 100755 index 0000000000..3e639aab5b --- /dev/null +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractUserRoleMappingMapper.java @@ -0,0 +1,102 @@ +/* + * 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.oidc.mappers; + +import org.keycloak.models.ClientSessionModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.ProtocolMapperModel; +import org.keycloak.models.RoleModel; +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; + +/** + * Base class for mapping of user role mappings to an ID and Access Token claim. + * + * @author Thomas Darimont + */ +abstract class AbstractUserRoleMappingMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper { + + @Override + public AccessToken transformAccessToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session, + UserSessionModel userSession, ClientSessionModel clientSession) { + + if (!OIDCAttributeMapperHelper.includeInAccessToken(mappingModel)) { + return token; + } + + setClaim(token, mappingModel, userSession); + return token; + } + + @Override + public IDToken transformIDToken(IDToken token, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) { + + if (!OIDCAttributeMapperHelper.includeInIDToken(mappingModel)) { + return token; + } + + setClaim(token, mappingModel, userSession); + return token; + } + + + protected abstract void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession); + + /** + * Returns the role names extracted from the given {@code roleModels} while recursively traversing "Composite Roles". + *

+ * Optionally prefixes each role name with the given {@code prefix}. + *

+ * + * @param roleModels + * @param prefix the prefix to apply, may be {@literal null} + * @return + */ + protected Set flattenRoleModelToRoleNames(Set roleModels, String prefix) { + + Set roleNames = new LinkedHashSet<>(); + + Deque stack = new ArrayDeque<>(roleModels); + while (!stack.isEmpty()) { + + RoleModel current = stack.pop(); + + if (current.isComposite()) { + for (RoleModel compositeRoleModel : current.getComposites()) { + stack.push(compositeRoleModel); + } + } + + String roleName = current.getName(); + + if (prefix != null && !prefix.trim().isEmpty()) { + roleName = prefix.trim() + roleName; + } + + roleNames.add(roleName); + } + + return roleNames; + } +} diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserClientRoleMappingMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserClientRoleMappingMapper.java new file mode 100755 index 0000000000..dcb55a8fa3 --- /dev/null +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserClientRoleMappingMapper.java @@ -0,0 +1,103 @@ +/* + * 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.oidc.mappers; + +import org.keycloak.models.ClientModel; +import org.keycloak.models.ProtocolMapperModel; +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.List; +import java.util.Set; + +/** + * Allows mapping of user client role mappings to an ID and Access Token claim. + * + * @author Thomas Darimont + */ +public class UserClientRoleMappingMapper extends AbstractUserRoleMappingMapper { + + public static final String PROVIDER_ID = "oidc-usermodel-client-role-mapper"; + + private static final List 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.STRING_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.addAttributeConfig(CONFIG_PROPERTIES); + } + + public List 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."; + } + + protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession) { + + UserModel user = userSession.getUser(); + + String clientId = mappingModel.getConfig().get(ProtocolMapperUtils.USER_MODEL_CLIENT_ROLE_MAPPING_CLIENT_ID); + if (clientId != null) { + + ClientModel clientModel = userSession.getRealm().getClientByClientId(clientId.trim()); + Set clientRoleMappings = user.getClientRoleMappings(clientModel); + + String rolePrefix = mappingModel.getConfig().get(ProtocolMapperUtils.USER_MODEL_CLIENT_ROLE_MAPPING_ROLE_PREFIX); + Set clientRoleNames = flattenRoleModelToRoleNames(clientRoleMappings, rolePrefix); + + OIDCAttributeMapperHelper.mapClaim(token, mappingModel, clientRoleNames); + } + } +} diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserRealmRoleMappingMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserRealmRoleMappingMapper.java new file mode 100755 index 0000000000..33460708a9 --- /dev/null +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserRealmRoleMappingMapper.java @@ -0,0 +1,87 @@ +/* + * 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.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.provider.ProviderConfigProperty; +import org.keycloak.representations.IDToken; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +/** + * Allows mapping of user realm role mappings to an ID and Access Token claim. + * + * @author Thomas Darimont + */ +public class UserRealmRoleMappingMapper extends AbstractUserRoleMappingMapper { + + public static final String PROVIDER_ID = "oidc-usermodel-realm-role-mapper"; + + private static final List 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.addAttributeConfig(CONFIG_PROPERTIES); + } + + public List 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."; + } + + 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 realmRoleNames = flattenRoleModelToRoleNames(user.getRoleMappings(), rolePrefix); + + OIDCAttributeMapperHelper.mapClaim(token, mappingModel, realmRoleNames); + } +} diff --git a/services/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper b/services/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper index 1fc31d78a1..9d1e10e927 100755 --- a/services/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper +++ b/services/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper @@ -32,6 +32,7 @@ org.keycloak.protocol.saml.mappers.UserAttributeStatementMapper org.keycloak.protocol.saml.mappers.UserPropertyAttributeStatementMapper org.keycloak.protocol.saml.mappers.UserSessionNoteStatementMapper org.keycloak.protocol.saml.mappers.GroupMembershipMapper - +org.keycloak.protocol.oidc.mappers.UserClientRoleMappingMapper +org.keycloak.protocol.oidc.mappers.UserRealmRoleMappingMapper diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties index 6658b51a3c..3d3efee986 100644 --- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties +++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties @@ -154,7 +154,12 @@ includeInIdToken.label=Add to ID token includeInIdToken.tooltip=Should the claim be added to the ID token? includeInAccessToken.label=Add to access token includeInAccessToken.tooltip=Should the claim be added to the access token? - +usermodel.clientRoleMapping.clientId.label=Client ID +usermodel.clientRoleMapping.clientId.tooltip=Client ID for role mappings +usermodel.clientRoleMapping.rolePrefix.label=Client Role prefix +usermodel.clientRoleMapping.rolePrefix.tooltip=A prefix for each client role (optional). +usermodel.realmRoleMapping.rolePrefix.label=Realm Role prefix +usermodel.realmRoleMapping.rolePrefix.tooltip=A prefix for each Realm Role (optional). # client details clients.tooltip=Clients are trusted browser apps and web services in a realm. These clients can request a login. You can also define client specific roles.