diff --git a/server-spi/src/main/java/org/keycloak/migration/MigrationModelManager.java b/server-spi/src/main/java/org/keycloak/migration/MigrationModelManager.java index e2b55e139e..a21aa65c7b 100755 --- a/server-spi/src/main/java/org/keycloak/migration/MigrationModelManager.java +++ b/server-spi/src/main/java/org/keycloak/migration/MigrationModelManager.java @@ -30,6 +30,7 @@ import org.keycloak.migration.migrators.MigrateTo1_9_2; import org.keycloak.migration.migrators.MigrateTo2_0_0; import org.keycloak.migration.migrators.MigrateTo2_1_0; import org.keycloak.migration.migrators.MigrateTo2_2_0; +import org.keycloak.migration.migrators.MigrateTo2_3_0; import org.keycloak.migration.migrators.Migration; import org.keycloak.models.KeycloakSession; @@ -53,6 +54,7 @@ public class MigrationModelManager { new MigrateTo2_0_0(), new MigrateTo2_1_0(), new MigrateTo2_2_0(), + new MigrateTo2_3_0(), }; public static void migrate(KeycloakSession session) { diff --git a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo2_3_0.java b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo2_3_0.java new file mode 100644 index 0000000000..80862ca4f2 --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo2_3_0.java @@ -0,0 +1,69 @@ +/* + * 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.migration.migrators; + +import java.util.LinkedList; +import java.util.List; + +import org.keycloak.migration.ModelVersion; +import org.keycloak.models.ClientModel; +import org.keycloak.models.ClientTemplateModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.ProtocolMapperContainerModel; +import org.keycloak.models.ProtocolMapperModel; +import org.keycloak.models.RealmModel; + +/** + * @author Marek Posolda + */ +public class MigrateTo2_3_0 implements Migration { + + public static final ModelVersion VERSION = new ModelVersion("2.3.0"); + + @Override + public void migrate(KeycloakSession session) { + for (RealmModel realm : session.realms().getRealms()) { + for (ClientModel client : realm.getClients()) { + updateProtocolMappers(client); + } + + for (ClientTemplateModel clientTemplate : realm.getClientTemplates()) { + updateProtocolMappers(clientTemplate); + } + } + } + + private void updateProtocolMappers(ProtocolMapperContainerModel client) { + List toUpdate = new LinkedList<>(); + for (ProtocolMapperModel mapper : client.getProtocolMappers()) { + if (!mapper.getConfig().containsKey("userinfo.token.claim") && mapper.getConfig().containsKey("id.token.claim")) { + mapper.getConfig().put("userinfo.token.claim", mapper.getConfig().get("id.token.claim")); + toUpdate.add(mapper); + } + } + + for (ProtocolMapperModel mapper : toUpdate) { + client.updateProtocolMapper(mapper); + } + } + + @Override + public ModelVersion getVersion() { + return VERSION; + } +} 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 index 37434cf57d..de4d0548b3 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractUserRoleMappingMapper.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractUserRoleMappingMapper.java @@ -35,7 +35,7 @@ import java.util.Set; * * @author Thomas Darimont */ -abstract class AbstractUserRoleMappingMapper extends AbstractOIDCProtocolMapper { +abstract class AbstractUserRoleMappingMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper, UserInfoTokenMapper { /** * Returns the role names extracted from the given {@code roleModels} while recursively traversing "Composite Roles". diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/AddressMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AddressMapper.java index f7d30a7871..674c9ff10c 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/AddressMapper.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AddressMapper.java @@ -44,21 +44,7 @@ public class AddressMapper extends AbstractOIDCProtocolMapper implements OIDCAcc private static final List configProperties = new ArrayList(); static { - ProviderConfigProperty property; - property = new ProviderConfigProperty(); - property.setName(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN); - property.setLabel(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN_LABEL); - property.setType(ProviderConfigProperty.BOOLEAN_TYPE); - property.setDefaultValue("true"); - property.setHelpText(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN_HELP_TEXT); - configProperties.add(property); - property = new ProviderConfigProperty(); - property.setName(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN); - property.setLabel(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN_LABEL); - property.setType(ProviderConfigProperty.BOOLEAN_TYPE); - property.setDefaultValue("true"); - property.setHelpText(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN_HELP_TEXT); - configProperties.add(property); + OIDCAttributeMapperHelper.addIncludeInTokensConfig(configProperties, AddressMapper.class); } public static final String PROVIDER_ID = "oidc-address-mapper"; diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/FullNameMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/FullNameMapper.java index 1675f60f36..1e4ad9df09 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/FullNameMapper.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/FullNameMapper.java @@ -43,21 +43,7 @@ public class FullNameMapper extends AbstractOIDCProtocolMapper implements OIDCAc private static final List configProperties = new ArrayList(); static { - ProviderConfigProperty property; - property = new ProviderConfigProperty(); - property.setName(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN); - property.setLabel(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN_LABEL); - property.setType(ProviderConfigProperty.BOOLEAN_TYPE); - property.setDefaultValue("true"); - property.setHelpText(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN_HELP_TEXT); - configProperties.add(property); - property = new ProviderConfigProperty(); - property.setName(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN); - property.setLabel(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN_LABEL); - property.setType(ProviderConfigProperty.BOOLEAN_TYPE); - property.setDefaultValue("true"); - property.setHelpText(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN_HELP_TEXT); - configProperties.add(property); + OIDCAttributeMapperHelper.addIncludeInTokensConfig(configProperties, FullNameMapper.class); } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/GroupMembershipMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/GroupMembershipMapper.java index ee179fb158..41dbb47db9 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/GroupMembershipMapper.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/GroupMembershipMapper.java @@ -45,16 +45,8 @@ public class GroupMembershipMapper extends AbstractOIDCProtocolMapper implements private static final List configProperties = new ArrayList(); static { - ProviderConfigProperty property; - ProviderConfigProperty property1; - property1 = new ProviderConfigProperty(); - property1.setName(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME); - property1.setLabel(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME_LABEL); - property1.setType(ProviderConfigProperty.STRING_TYPE); - property1.setDefaultValue("groups"); - property1.setHelpText(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME_TOOLTIP); - configProperties.add(property1); - property1 = new ProviderConfigProperty(); + OIDCAttributeMapperHelper.addTokenClaimNameConfig(configProperties); + ProviderConfigProperty property1 = new ProviderConfigProperty(); property1.setName("full.path"); property1.setLabel("Full group path"); property1.setType(ProviderConfigProperty.BOOLEAN_TYPE); @@ -62,23 +54,7 @@ public class GroupMembershipMapper extends AbstractOIDCProtocolMapper implements property1.setHelpText("Include full path to group i.e. /top/level1/level2, false will just specify the group name"); configProperties.add(property1); - property1 = new ProviderConfigProperty(); - property1.setName(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN); - property1.setLabel(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN_LABEL); - property1.setType(ProviderConfigProperty.BOOLEAN_TYPE); - property1.setDefaultValue("true"); - property1.setHelpText(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN_HELP_TEXT); - configProperties.add(property1); - property1 = new ProviderConfigProperty(); - property1.setName(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN); - property1.setLabel(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN_LABEL); - property1.setType(ProviderConfigProperty.BOOLEAN_TYPE); - property1.setDefaultValue("true"); - property1.setHelpText(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN_HELP_TEXT); - configProperties.add(property1); - - - + OIDCAttributeMapperHelper.addIncludeInTokensConfig(configProperties, GroupMembershipMapper.class); } public static final String PROVIDER_ID = "oidc-group-membership-mapper"; diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/HardcodedClaim.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/HardcodedClaim.java index 571a6a22ab..40628245dd 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/HardcodedClaim.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/HardcodedClaim.java @@ -44,46 +44,17 @@ public class HardcodedClaim extends AbstractOIDCProtocolMapper implements OIDCAc public static final String CLAIM_VALUE = "claim.value"; static { - ProviderConfigProperty property; - property = new ProviderConfigProperty(); - property.setName(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME); - property.setLabel(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME_LABEL); - property.setType(ProviderConfigProperty.STRING_TYPE); - property.setHelpText("Claim name you want to hard code into the token. This can be a fully qualified name like 'address.street'. In this case, a nested json object will be created."); - configProperties.add(property); - property = new ProviderConfigProperty(); + OIDCAttributeMapperHelper.addTokenClaimNameConfig(configProperties); + + ProviderConfigProperty property = new ProviderConfigProperty(); property.setName(CLAIM_VALUE); property.setLabel("Claim value"); property.setType(ProviderConfigProperty.STRING_TYPE); property.setHelpText("Value of the claim you want to hard code. 'true' and 'false can be used for boolean values."); configProperties.add(property); - property = new ProviderConfigProperty(); - property.setName(OIDCAttributeMapperHelper.JSON_TYPE); - property.setLabel(OIDCAttributeMapperHelper.JSON_TYPE); - List types = new ArrayList(3); - types.add("String"); - types.add("long"); - types.add("int"); - types.add("boolean"); - property.setType(ProviderConfigProperty.LIST_TYPE); - property.setOptions(types); - property.setHelpText("JSON type that should be used for the value of the claim. long, int, boolean, and String are valid values."); - configProperties.add(property); - property = new ProviderConfigProperty(); - property.setName(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN); - property.setLabel(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN_LABEL); - property.setType(ProviderConfigProperty.BOOLEAN_TYPE); - property.setDefaultValue("true"); - property.setHelpText(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN_HELP_TEXT); - configProperties.add(property); - property = new ProviderConfigProperty(); - property.setName(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN); - property.setLabel(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN_LABEL); - property.setType(ProviderConfigProperty.BOOLEAN_TYPE); - property.setDefaultValue("true"); - property.setHelpText(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN_HELP_TEXT); - configProperties.add(property); + OIDCAttributeMapperHelper.addJsonTypeConfig(configProperties); + OIDCAttributeMapperHelper.addIncludeInTokensConfig(configProperties, HardcodedClaim.class); } public static final String PROVIDER_ID = "oidc-hardcoded-claim-mapper"; diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCAttributeMapperHelper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCAttributeMapperHelper.java index af829217b8..99b261074b 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCAttributeMapperHelper.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCAttributeMapperHelper.java @@ -18,6 +18,7 @@ package org.keycloak.protocol.oidc.mappers; import org.keycloak.models.ProtocolMapperModel; +import org.keycloak.protocol.ProtocolMapper; import org.keycloak.protocol.ProtocolMapperUtils; import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.provider.ProviderConfigProperty; @@ -162,20 +163,34 @@ public class OIDCAttributeMapperHelper { } public static boolean includeInUserInfo(ProtocolMapperModel mappingModel){ - return "true".equals(mappingModel.getConfig().get(INCLUDE_IN_USERINFO)); + String includeInUserInfo = mappingModel.getConfig().get(INCLUDE_IN_USERINFO); + + // Backwards compatibility + if (includeInUserInfo == null && includeInIDToken(mappingModel)) { + return true; + } + + return "true".equals(includeInUserInfo); } - public static void addAttributeConfig(List configProperties) { + public static void addAttributeConfig(List configProperties, Class protocolMapperClass) { + addTokenClaimNameConfig(configProperties); + addJsonTypeConfig(configProperties); - ProviderConfigProperty property; - property = new ProviderConfigProperty(); + addIncludeInTokensConfig(configProperties, protocolMapperClass); + } + + public static void addTokenClaimNameConfig(List configProperties) { + ProviderConfigProperty property = new ProviderConfigProperty(); property.setName(TOKEN_CLAIM_NAME); property.setLabel(TOKEN_CLAIM_NAME_LABEL); property.setType(ProviderConfigProperty.STRING_TYPE); property.setHelpText(TOKEN_CLAIM_NAME_TOOLTIP); configProperties.add(property); + } - property = new ProviderConfigProperty(); + public static void addJsonTypeConfig(List configProperties) { + ProviderConfigProperty property = new ProviderConfigProperty(); property.setName(JSON_TYPE); property.setLabel(JSON_TYPE); List types = new ArrayList(3); @@ -187,29 +202,37 @@ public class OIDCAttributeMapperHelper { property.setOptions(types); property.setHelpText(JSON_TYPE_TOOLTIP); configProperties.add(property); + } - property = new ProviderConfigProperty(); - property.setName(INCLUDE_IN_ID_TOKEN); - property.setLabel(INCLUDE_IN_ID_TOKEN_LABEL); - property.setType(ProviderConfigProperty.BOOLEAN_TYPE); - property.setDefaultValue("true"); - property.setHelpText(INCLUDE_IN_ID_TOKEN_HELP_TEXT); - configProperties.add(property); + public static void addIncludeInTokensConfig(List configProperties, Class protocolMapperClass) { + if (OIDCIDTokenMapper.class.isAssignableFrom(protocolMapperClass)) { + ProviderConfigProperty property = new ProviderConfigProperty(); + property.setName(INCLUDE_IN_ID_TOKEN); + property.setLabel(INCLUDE_IN_ID_TOKEN_LABEL); + property.setType(ProviderConfigProperty.BOOLEAN_TYPE); + property.setDefaultValue("true"); + property.setHelpText(INCLUDE_IN_ID_TOKEN_HELP_TEXT); + configProperties.add(property); + } - property = new ProviderConfigProperty(); - property.setName(INCLUDE_IN_ACCESS_TOKEN); - property.setLabel(INCLUDE_IN_ACCESS_TOKEN_LABEL); - property.setType(ProviderConfigProperty.BOOLEAN_TYPE); - property.setDefaultValue("true"); - property.setHelpText(INCLUDE_IN_ACCESS_TOKEN_HELP_TEXT); - configProperties.add(property); + if (OIDCAccessTokenMapper.class.isAssignableFrom(protocolMapperClass)) { + ProviderConfigProperty property = new ProviderConfigProperty(); + property.setName(INCLUDE_IN_ACCESS_TOKEN); + property.setLabel(INCLUDE_IN_ACCESS_TOKEN_LABEL); + property.setType(ProviderConfigProperty.BOOLEAN_TYPE); + property.setDefaultValue("true"); + property.setHelpText(INCLUDE_IN_ACCESS_TOKEN_HELP_TEXT); + configProperties.add(property); + } - property = new ProviderConfigProperty(); - property.setName(INCLUDE_IN_USERINFO); - property.setLabel(INCLUDE_IN_USERINFO_LABEL); - property.setType(ProviderConfigProperty.BOOLEAN_TYPE); - property.setDefaultValue("true"); - property.setHelpText(INCLUDE_IN_USERINFO_HELP_TEXT); - configProperties.add(property); + if (UserInfoTokenMapper.class.isAssignableFrom(protocolMapperClass)) { + ProviderConfigProperty property = new ProviderConfigProperty(); + property.setName(INCLUDE_IN_USERINFO); + property.setLabel(INCLUDE_IN_USERINFO_LABEL); + property.setType(ProviderConfigProperty.BOOLEAN_TYPE); + property.setDefaultValue("true"); + property.setHelpText(INCLUDE_IN_USERINFO_HELP_TEXT); + configProperties.add(property); + } } } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserAttributeMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserAttributeMapper.java index 7020e5f62e..e6d0d209f5 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserAttributeMapper.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserAttributeMapper.java @@ -51,7 +51,7 @@ public class UserAttributeMapper extends AbstractOIDCProtocolMapper implements O property.setHelpText(ProtocolMapperUtils.USER_MODEL_ATTRIBUTE_HELP_TEXT); property.setType(ProviderConfigProperty.STRING_TYPE); configProperties.add(property); - OIDCAttributeMapperHelper.addAttributeConfig(configProperties); + OIDCAttributeMapperHelper.addAttributeConfig(configProperties, UserAttributeMapper.class); property = new ProviderConfigProperty(); property.setName(ProtocolMapperUtils.MULTIVALUED); 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 index dcb55a8fa3..01d47e13d7 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserClientRoleMappingMapper.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserClientRoleMappingMapper.java @@ -47,7 +47,7 @@ public class UserClientRoleMappingMapper extends AbstractUserRoleMappingMapper { 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); + clientId.setType(ProviderConfigProperty.CLIENT_LIST_TYPE); CONFIG_PROPERTIES.add(clientId); ProviderConfigProperty clientRolePrefix = new ProviderConfigProperty(); @@ -57,7 +57,7 @@ public class UserClientRoleMappingMapper extends AbstractUserRoleMappingMapper { clientRolePrefix.setType(ProviderConfigProperty.STRING_TYPE); CONFIG_PROPERTIES.add(clientRolePrefix); - OIDCAttributeMapperHelper.addAttributeConfig(CONFIG_PROPERTIES); + OIDCAttributeMapperHelper.addAttributeConfig(CONFIG_PROPERTIES, UserClientRoleMappingMapper.class); } public List getConfigProperties() { @@ -100,4 +100,21 @@ public class UserClientRoleMappingMapper extends AbstractUserRoleMappingMapper { OIDCAttributeMapperHelper.mapClaim(token, mappingModel, clientRoleNames); } } + + + public static ProtocolMapperModel create(String clientId, String clientRolePrefix, + String name, + String tokenClaimName, + boolean accessToken, boolean idToken) { + ProtocolMapperModel mapper = OIDCAttributeMapperHelper.createClaimMapper(name, "foo", + tokenClaimName, "String", + true, name, + accessToken, idToken, + 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/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserPropertyMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserPropertyMapper.java index 54ba0963d0..6fd649199d 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserPropertyMapper.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserPropertyMapper.java @@ -38,7 +38,7 @@ import java.util.List; * @author Bill Burke * @version $Revision: 1 $ */ -public class UserPropertyMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper { +public class UserPropertyMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper, UserInfoTokenMapper { private static final List configProperties = new ArrayList(); static { @@ -49,7 +49,7 @@ public class UserPropertyMapper extends AbstractOIDCProtocolMapper implements OI property.setType(ProviderConfigProperty.STRING_TYPE); property.setHelpText(ProtocolMapperUtils.USER_MODEL_PROPERTY_HELP_TEXT); configProperties.add(property); - OIDCAttributeMapperHelper.addAttributeConfig(configProperties); + OIDCAttributeMapperHelper.addAttributeConfig(configProperties, UserPropertyMapper.class); } public static final String PROVIDER_ID = "oidc-usermodel-property-mapper"; 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 index 33460708a9..ef9818227b 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserRealmRoleMappingMapper.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserRealmRoleMappingMapper.java @@ -21,11 +21,14 @@ import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.UserModel; import org.keycloak.models.UserSessionModel; import org.keycloak.protocol.ProtocolMapperUtils; +import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.representations.IDToken; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Set; /** @@ -48,7 +51,7 @@ public class UserRealmRoleMappingMapper extends AbstractUserRoleMappingMapper { realmRolePrefix.setType(ProviderConfigProperty.STRING_TYPE); CONFIG_PROPERTIES.add(realmRolePrefix); - OIDCAttributeMapperHelper.addAttributeConfig(CONFIG_PROPERTIES); + OIDCAttributeMapperHelper.addAttributeConfig(CONFIG_PROPERTIES, UserRealmRoleMappingMapper.class); } public List getConfigProperties() { @@ -80,8 +83,23 @@ public class UserRealmRoleMappingMapper extends AbstractUserRoleMappingMapper { UserModel user = userSession.getUser(); String rolePrefix = mappingModel.getConfig().get(ProtocolMapperUtils.USER_MODEL_REALM_ROLE_MAPPING_ROLE_PREFIX); - Set realmRoleNames = flattenRoleModelToRoleNames(user.getRoleMappings(), rolePrefix); + Set realmRoleNames = flattenRoleModelToRoleNames(user.getRealmRoleMappings(), rolePrefix); OIDCAttributeMapperHelper.mapClaim(token, mappingModel, realmRoleNames); } + + + public static ProtocolMapperModel create(String realmRolePrefix, + String name, + String tokenClaimName, boolean accessToken, boolean idToken) { + ProtocolMapperModel mapper = OIDCAttributeMapperHelper.createClaimMapper(name, "foo", + tokenClaimName, "String", + true, name, + accessToken, idToken, + PROVIDER_ID); + + mapper.getConfig().put(ProtocolMapperUtils.USER_MODEL_REALM_ROLE_MAPPING_ROLE_PREFIX, realmRolePrefix); + return mapper; + + } } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserSessionNoteMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserSessionNoteMapper.java index e2615f9c0a..fd6bfe1c68 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserSessionNoteMapper.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserSessionNoteMapper.java @@ -49,7 +49,7 @@ public class UserSessionNoteMapper extends AbstractOIDCProtocolMapper implements property.setHelpText(ProtocolMapperUtils.USER_SESSION_MODEL_NOTE_HELP_TEXT); property.setType(ProviderConfigProperty.STRING_TYPE); configProperties.add(property); - OIDCAttributeMapperHelper.addAttributeConfig(configProperties); + OIDCAttributeMapperHelper.addAttributeConfig(configProperties, UserSessionNoteMapper.class); } public static final String PROVIDER_ID = "oidc-usersessionmodel-note-mapper"; diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/UserInfoClientUtil.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/UserInfoClientUtil.java index fc540231b4..fb39ec5457 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/UserInfoClientUtil.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/UserInfoClientUtil.java @@ -49,7 +49,7 @@ public class UserInfoClientUtil { return client.target(userInfoUri); } - public static void testSuccessfulUserInfoResponse(Response response, String expectedUsername, String expectedEmail) { + public static UserInfo testSuccessfulUserInfoResponse(Response response, String expectedUsername, String expectedEmail) { Assert.assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); Assert.assertEquals(response.getHeaderString(HttpHeaders.CONTENT_TYPE), MediaType.APPLICATION_JSON); @@ -61,6 +61,7 @@ public class UserInfoClientUtil { Assert.assertNotNull(userInfo.getSubject()); Assert.assertEquals(expectedEmail, userInfo.getEmail()); Assert.assertEquals(expectedUsername, userInfo.getPreferredUsername()); + return userInfo; } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCPairwiseClientRegistrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCPairwiseClientRegistrationTest.java index ca4553d69e..c5bb78577b 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCPairwiseClientRegistrationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCPairwiseClientRegistrationTest.java @@ -27,6 +27,7 @@ import org.keycloak.client.registration.ClientRegistrationException; import org.keycloak.client.registration.HttpErrorException; import org.keycloak.protocol.oidc.mappers.SHA256PairwiseSubMapper; import org.keycloak.representations.AccessToken; +import org.keycloak.representations.UserInfo; import org.keycloak.representations.idm.ClientInitialAccessCreatePresentation; import org.keycloak.representations.idm.ClientInitialAccessPresentation; import org.keycloak.representations.idm.ProtocolMapperRepresentation; @@ -38,7 +39,9 @@ import org.keycloak.testsuite.client.resources.TestApplicationResourceUrls; import org.keycloak.testsuite.client.resources.TestOIDCEndpointsApplicationResource; import org.keycloak.testsuite.util.ClientManager; import org.keycloak.testsuite.util.OAuthClient; +import org.keycloak.testsuite.util.UserInfoClientUtil; +import javax.ws.rs.client.Client; import javax.ws.rs.core.Response; import java.util.ArrayList; import java.util.Collections; @@ -323,5 +326,17 @@ public class OIDCPairwiseClientRegistrationTest extends AbstractClientRegistrati // Assert pairwise client has different subject like userId String pairwiseUserId = accessToken.getSubject(); Assert.assertNotEquals(pairwiseUserId, user.getId()); + + // Send request to userInfo endpoint + Client jaxrsClient = javax.ws.rs.client.ClientBuilder.newClient(); + try { + // Check that userInfo contains pairwise subjectId as well + Response userInfoResponse = UserInfoClientUtil.executeUserInfoRequest_getMethod(jaxrsClient, accessTokenResponse.getAccessToken()); + UserInfo userInfo = UserInfoClientUtil.testSuccessfulUserInfoResponse(userInfoResponse, "test-user", "test-user@localhost"); + String userInfoSubId = userInfo.getSubject(); + Assert.assertEquals(pairwiseUserId, userInfoSubId); + } finally { + jaxrsClient.close(); + } } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java index 13c1ea45ad..eaeeafd9c1 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java @@ -673,138 +673,6 @@ public class AccessTokenTest extends AbstractKeycloakTest { } - @Test - public void testTokenMapping() throws Exception { - Client client = javax.ws.rs.client.ClientBuilder.newClient(); - UriBuilder builder = UriBuilder.fromUri(AUTH_SERVER_ROOT); - URI grantUri = OIDCLoginProtocolService.tokenUrl(builder).build("test"); - WebTarget grantTarget = client.target(grantUri); - { - UserResource userResource = findUserByUsernameId(adminClient.realm("test"), "test-user@localhost"); - UserRepresentation user = userResource.toRepresentation(); - - user.singleAttribute("street", "5 Yawkey Way"); - user.singleAttribute("locality", "Boston"); - user.singleAttribute("region", "MA"); - user.singleAttribute("postal_code", "02115"); - user.singleAttribute("country", "USA"); - user.singleAttribute("phone", "617-777-6666"); - - List departments = Arrays.asList("finance", "development"); - user.getAttributes().put("departments", departments); - userResource.update(user); - - ClientResource app = findClientResourceByClientId(adminClient.realm("test"), "test-app"); - - ProtocolMapperRepresentation mapper = createAddressMapper(true, true); - app.getProtocolMappers().createMapper(mapper); - - ProtocolMapperRepresentation hard = createHardcodedClaim("hard", "hard", "coded", "String", false, null, true, true); - app.getProtocolMappers().createMapper(hard); - app.getProtocolMappers().createMapper(createHardcodedClaim("hard-nested", "nested.hard", "coded-nested", "String", false, null, true, true)); - app.getProtocolMappers().createMapper(createClaimMapper("custom phone", "phone", "home_phone", "String", true, "", true, true, false)); - app.getProtocolMappers().createMapper(createClaimMapper("nested phone", "phone", "home.phone", "String", true, "", true, true, false)); - app.getProtocolMappers().createMapper(createClaimMapper("departments", "departments", "department", "String", true, "", true, true, true)); - app.getProtocolMappers().createMapper(createHardcodedRole("hard-realm", "hardcoded")); - app.getProtocolMappers().createMapper(createHardcodedRole("hard-app", "app.hardcoded")); - app.getProtocolMappers().createMapper(createRoleNameMapper("rename-app-role", "test-app.customer-user", "realm-user")); - } - - { - Response response = executeGrantAccessTokenRequest(grantTarget); - assertEquals(200, response.getStatus()); - - org.keycloak.representations.AccessTokenResponse tokenResponse = response.readEntity(org.keycloak.representations.AccessTokenResponse.class); - IDToken idToken = getIdToken(tokenResponse); - assertNotNull(idToken.getAddress()); - assertEquals(idToken.getName(), "Tom Brady"); - assertEquals(idToken.getAddress().getStreetAddress(), "5 Yawkey Way"); - assertEquals(idToken.getAddress().getLocality(), "Boston"); - assertEquals(idToken.getAddress().getRegion(), "MA"); - assertEquals(idToken.getAddress().getPostalCode(), "02115"); - assertEquals(idToken.getAddress().getCountry(), "USA"); - assertNotNull(idToken.getOtherClaims().get("home_phone")); - assertEquals("617-777-6666", idToken.getOtherClaims().get("home_phone")); - assertEquals("coded", idToken.getOtherClaims().get("hard")); - Map nested = (Map) idToken.getOtherClaims().get("nested"); - assertEquals("coded-nested", nested.get("hard")); - nested = (Map) idToken.getOtherClaims().get("home"); - assertEquals("617-777-6666", nested.get("phone")); - List departments = (List) idToken.getOtherClaims().get("department"); - assertEquals(2, departments.size()); - assertTrue(departments.contains("finance") && departments.contains("development")); - - AccessToken accessToken = getAccessToken(tokenResponse); - assertEquals(accessToken.getName(), "Tom Brady"); - assertNotNull(accessToken.getAddress()); - assertEquals(accessToken.getAddress().getStreetAddress(), "5 Yawkey Way"); - assertEquals(accessToken.getAddress().getLocality(), "Boston"); - assertEquals(accessToken.getAddress().getRegion(), "MA"); - assertEquals(accessToken.getAddress().getPostalCode(), "02115"); - assertEquals(accessToken.getAddress().getCountry(), "USA"); - assertNotNull(accessToken.getOtherClaims().get("home_phone")); - assertEquals("617-777-6666", accessToken.getOtherClaims().get("home_phone")); - assertEquals("coded", accessToken.getOtherClaims().get("hard")); - nested = (Map) accessToken.getOtherClaims().get("nested"); - assertEquals("coded-nested", nested.get("hard")); - nested = (Map) accessToken.getOtherClaims().get("home"); - assertEquals("617-777-6666", nested.get("phone")); - departments = (List) idToken.getOtherClaims().get("department"); - assertEquals(2, departments.size()); - assertTrue(departments.contains("finance") && departments.contains("development")); - assertTrue(accessToken.getRealmAccess().getRoles().contains("hardcoded")); - assertTrue(accessToken.getRealmAccess().getRoles().contains("realm-user")); - Assert.assertFalse(accessToken.getResourceAccess("test-app").getRoles().contains("customer-user")); - assertTrue(accessToken.getResourceAccess("app").getRoles().contains("hardcoded")); - - - response.close(); - } - - // undo mappers - { - ClientResource app = findClientByClientId(adminClient.realm("test"), "test-app"); - ClientRepresentation clientRepresentation = app.toRepresentation(); - for (ProtocolMapperRepresentation model : clientRepresentation.getProtocolMappers()) { - if (model.getName().equals("address") - || model.getName().equals("hard") - || model.getName().equals("hard-nested") - || model.getName().equals("custom phone") - || model.getName().equals("departments") - || model.getName().equals("nested phone") - || model.getName().equals("rename-app-role") - || model.getName().equals("hard-realm") - || model.getName().equals("hard-app") - ) { - app.getProtocolMappers().delete(model.getId()); - } - } - } - - events.clear(); - - - { - Response response = executeGrantAccessTokenRequest(grantTarget); - assertEquals(200, response.getStatus()); - org.keycloak.representations.AccessTokenResponse tokenResponse = response.readEntity(org.keycloak.representations.AccessTokenResponse.class); - IDToken idToken = getIdToken(tokenResponse); - assertNull(idToken.getAddress()); - assertNull(idToken.getOtherClaims().get("home_phone")); - assertNull(idToken.getOtherClaims().get("hard")); - assertNull(idToken.getOtherClaims().get("nested")); - assertNull(idToken.getOtherClaims().get("department")); - - response.close(); - } - - - events.clear(); - client.close(); - - - } - @Test public void testClientTemplate() throws Exception { RealmResource realm = adminClient.realm("test"); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OIDCProtocolMappersTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OIDCProtocolMappersTest.java new file mode 100644 index 0000000000..d317af7de1 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OIDCProtocolMappersTest.java @@ -0,0 +1,244 @@ +/* + * 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.testsuite.oauth; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.keycloak.admin.client.resource.ClientResource; +import org.keycloak.admin.client.resource.ProtocolMappersResource; +import org.keycloak.admin.client.resource.UserResource; +import org.keycloak.protocol.oidc.OIDCLoginProtocol; +import org.keycloak.representations.AccessToken; +import org.keycloak.representations.IDToken; +import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.representations.idm.ProtocolMapperRepresentation; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.representations.idm.UserRepresentation; +import org.keycloak.testsuite.AbstractKeycloakTest; +import org.keycloak.testsuite.Assert; +import org.keycloak.testsuite.AssertEvents; +import org.keycloak.testsuite.admin.ApiUtil; +import org.keycloak.testsuite.util.ClientManager; +import org.keycloak.testsuite.util.OAuthClient; +import org.keycloak.testsuite.util.ProtocolMapperUtil; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson; +import static org.keycloak.testsuite.admin.ApiUtil.findClientByClientId; +import static org.keycloak.testsuite.admin.ApiUtil.findClientResourceByClientId; +import static org.keycloak.testsuite.admin.ApiUtil.findUserByUsernameId; +import static org.keycloak.testsuite.util.ProtocolMapperUtil.createAddressMapper; +import static org.keycloak.testsuite.util.ProtocolMapperUtil.createClaimMapper; +import static org.keycloak.testsuite.util.ProtocolMapperUtil.createHardcodedClaim; +import static org.keycloak.testsuite.util.ProtocolMapperUtil.createHardcodedRole; +import static org.keycloak.testsuite.util.ProtocolMapperUtil.createRoleNameMapper; + +/** + * @author Marek Posolda + */ +public class OIDCProtocolMappersTest extends AbstractKeycloakTest { + + @Rule + public AssertEvents events = new AssertEvents(this); + + + @Override + public void beforeAbstractKeycloakTest() throws Exception { + super.beforeAbstractKeycloakTest(); + } + + @Before + public void clientConfiguration() { + ClientManager.realm(adminClient.realm("test")).clientId("test-app").directAccessGrant(true); + /* + * Configure the default client ID. Seems like OAuthClient is keeping the state of clientID + * For example: If some test case configure oauth.clientId("sample-public-client"), other tests + * will faile and the clientID will always be "sample-public-client + * @see AccessTokenTest#testAuthorizationNegotiateHeaderIgnored() + */ + oauth.clientId("test-app"); + } + + @Override + public void addTestRealms(List testRealms) { + RealmRepresentation realm = loadJson(getClass().getResourceAsStream("/testrealm.json"), RealmRepresentation.class); + testRealms.add(realm); + } + + + @Test + public void testTokenMapping() throws Exception { + { + UserResource userResource = findUserByUsernameId(adminClient.realm("test"), "test-user@localhost"); + UserRepresentation user = userResource.toRepresentation(); + + user.singleAttribute("street", "5 Yawkey Way"); + user.singleAttribute("locality", "Boston"); + user.singleAttribute("region", "MA"); + user.singleAttribute("postal_code", "02115"); + user.singleAttribute("country", "USA"); + user.singleAttribute("phone", "617-777-6666"); + + List departments = Arrays.asList("finance", "development"); + user.getAttributes().put("departments", departments); + userResource.update(user); + + ClientResource app = findClientResourceByClientId(adminClient.realm("test"), "test-app"); + + ProtocolMapperRepresentation mapper = createAddressMapper(true, true); + app.getProtocolMappers().createMapper(mapper); + + ProtocolMapperRepresentation hard = createHardcodedClaim("hard", "hard", "coded", "String", false, null, true, true); + app.getProtocolMappers().createMapper(hard); + app.getProtocolMappers().createMapper(createHardcodedClaim("hard-nested", "nested.hard", "coded-nested", "String", false, null, true, true)); + app.getProtocolMappers().createMapper(createClaimMapper("custom phone", "phone", "home_phone", "String", true, "", true, true, false)); + app.getProtocolMappers().createMapper(createClaimMapper("nested phone", "phone", "home.phone", "String", true, "", true, true, false)); + app.getProtocolMappers().createMapper(createClaimMapper("departments", "departments", "department", "String", true, "", true, true, true)); + app.getProtocolMappers().createMapper(createHardcodedRole("hard-realm", "hardcoded")); + app.getProtocolMappers().createMapper(createHardcodedRole("hard-app", "app.hardcoded")); + app.getProtocolMappers().createMapper(createRoleNameMapper("rename-app-role", "test-app.customer-user", "realm-user")); + } + + { + OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("password", "test-user@localhost", "password"); + + IDToken idToken = oauth.verifyIDToken(response.getIdToken()); + assertNotNull(idToken.getAddress()); + assertEquals(idToken.getName(), "Tom Brady"); + assertEquals(idToken.getAddress().getStreetAddress(), "5 Yawkey Way"); + assertEquals(idToken.getAddress().getLocality(), "Boston"); + assertEquals(idToken.getAddress().getRegion(), "MA"); + assertEquals(idToken.getAddress().getPostalCode(), "02115"); + assertEquals(idToken.getAddress().getCountry(), "USA"); + assertNotNull(idToken.getOtherClaims().get("home_phone")); + assertEquals("617-777-6666", idToken.getOtherClaims().get("home_phone")); + assertEquals("coded", idToken.getOtherClaims().get("hard")); + Map nested = (Map) idToken.getOtherClaims().get("nested"); + assertEquals("coded-nested", nested.get("hard")); + nested = (Map) idToken.getOtherClaims().get("home"); + assertEquals("617-777-6666", nested.get("phone")); + List departments = (List) idToken.getOtherClaims().get("department"); + assertEquals(2, departments.size()); + assertTrue(departments.contains("finance") && departments.contains("development")); + + AccessToken accessToken = oauth.verifyToken(response.getAccessToken()); + assertEquals(accessToken.getName(), "Tom Brady"); + assertNotNull(accessToken.getAddress()); + assertEquals(accessToken.getAddress().getStreetAddress(), "5 Yawkey Way"); + assertEquals(accessToken.getAddress().getLocality(), "Boston"); + assertEquals(accessToken.getAddress().getRegion(), "MA"); + assertEquals(accessToken.getAddress().getPostalCode(), "02115"); + assertEquals(accessToken.getAddress().getCountry(), "USA"); + assertNotNull(accessToken.getOtherClaims().get("home_phone")); + assertEquals("617-777-6666", accessToken.getOtherClaims().get("home_phone")); + assertEquals("coded", accessToken.getOtherClaims().get("hard")); + nested = (Map) accessToken.getOtherClaims().get("nested"); + assertEquals("coded-nested", nested.get("hard")); + nested = (Map) accessToken.getOtherClaims().get("home"); + assertEquals("617-777-6666", nested.get("phone")); + departments = (List) idToken.getOtherClaims().get("department"); + assertEquals(2, departments.size()); + assertTrue(departments.contains("finance") && departments.contains("development")); + assertTrue(accessToken.getRealmAccess().getRoles().contains("hardcoded")); + assertTrue(accessToken.getRealmAccess().getRoles().contains("realm-user")); + Assert.assertFalse(accessToken.getResourceAccess("test-app").getRoles().contains("customer-user")); + assertTrue(accessToken.getResourceAccess("app").getRoles().contains("hardcoded")); + } + + // undo mappers + { + ClientResource app = findClientByClientId(adminClient.realm("test"), "test-app"); + ClientRepresentation clientRepresentation = app.toRepresentation(); + for (ProtocolMapperRepresentation model : clientRepresentation.getProtocolMappers()) { + if (model.getName().equals("address") + || model.getName().equals("hard") + || model.getName().equals("hard-nested") + || model.getName().equals("custom phone") + || model.getName().equals("departments") + || model.getName().equals("nested phone") + || model.getName().equals("rename-app-role") + || model.getName().equals("hard-realm") + || model.getName().equals("hard-app") + ) { + app.getProtocolMappers().delete(model.getId()); + } + } + } + + events.clear(); + + + { + OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("password", "test-user@localhost", "password"); + IDToken idToken = oauth.verifyIDToken(response.getIdToken()); + assertNull(idToken.getAddress()); + assertNull(idToken.getOtherClaims().get("home_phone")); + assertNull(idToken.getOtherClaims().get("hard")); + assertNull(idToken.getOtherClaims().get("nested")); + assertNull(idToken.getOtherClaims().get("department")); + } + + + events.clear(); + } + + + @Test + public void testUserRoleToAttributeMappers() throws Exception { + // Add mapper for realm roles + ProtocolMapperRepresentation realmMapper = ProtocolMapperUtil.createUserRealmRoleMappingMapper("pref.", "Realm roles mapper", "roles-custom.realm", true, true); + ProtocolMapperRepresentation clientMapper = ProtocolMapperUtil.createUserClientRoleMappingMapper("test-app", null, "Client roles mapper", "roles-custom.test-app", true, true); + + ProtocolMappersResource protocolMappers = ApiUtil.findClientResourceByClientId(adminClient.realm("test"), "test-app").getProtocolMappers(); + protocolMappers.createMapper(Arrays.asList(realmMapper, clientMapper)); + + // Login user + OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("password", "test-user@localhost", "password"); + IDToken idToken = oauth.verifyIDToken(response.getIdToken()); + + // Verify attribute is filled + Map roleMappings = (Map)idToken.getOtherClaims().get("roles-custom"); + Assert.assertEquals(2, roleMappings.size()); + String realmRoleMappings = (String) roleMappings.get("realm"); + String testAppMappings = (String) roleMappings.get("test-app"); + Assert.assertTrue(realmRoleMappings.contains("pref.user")); + Assert.assertEquals("[customer-user]", testAppMappings); + } + + + private ProtocolMapperRepresentation makeMapper(String name, String mapperType, Map config) { + ProtocolMapperRepresentation rep = new ProtocolMapperRepresentation(); + rep.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); + rep.setName(name); + rep.setProtocolMapper(mapperType); + rep.setConfig(config); + rep.setConsentRequired(true); + rep.setConsentText("Test Consent Text"); + return rep; + } + +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/ProtocolMapperUtil.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/ProtocolMapperUtil.java index 62018e1c0e..6bcea06cd2 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/ProtocolMapperUtil.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/ProtocolMapperUtil.java @@ -6,6 +6,8 @@ import org.keycloak.protocol.oidc.mappers.HardcodedClaim; import org.keycloak.protocol.oidc.mappers.HardcodedRole; import org.keycloak.protocol.oidc.mappers.RoleNameMapper; import org.keycloak.protocol.oidc.mappers.UserAttributeMapper; +import org.keycloak.protocol.oidc.mappers.UserClientRoleMappingMapper; +import org.keycloak.protocol.oidc.mappers.UserRealmRoleMappingMapper; import org.keycloak.protocol.oidc.mappers.UserSessionNoteMapper; import org.keycloak.representations.idm.ProtocolMapperRepresentation; @@ -103,4 +105,22 @@ public class ProtocolMapperUtil { consentRequired, consentText, accessToken, idToken)); } + + + public static ProtocolMapperRepresentation createUserRealmRoleMappingMapper(String realmRolePrefix, + String name, + String tokenClaimName, + boolean accessToken, boolean idToken) { + + return ModelToRepresentation.toRepresentation(UserRealmRoleMappingMapper.create(realmRolePrefix, name, tokenClaimName, accessToken, idToken)); + } + + + public static ProtocolMapperRepresentation createUserClientRoleMappingMapper(String clientId, String clientRolePrefix, + String name, + String tokenClaimName, + boolean accessToken, boolean idToken) { + + return ModelToRepresentation.toRepresentation(UserClientRoleMappingMapper.create(clientId, clientRolePrefix, name, tokenClaimName, accessToken, idToken)); + } } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java index 9cad056222..ed1e757228 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java @@ -242,7 +242,7 @@ public abstract class AbstractKeycloakIdentityProviderTest extends AbstractIdent * Test for KEYCLOAK-1053 - verify email action is not performed if email is not provided, login is normal, but action stays in set to be performed later */ @Test - public void testSuccessfulAuthenticationWithoutUpdateProfile_emailNotProvided_emailVerifyEnabled() { + public void testSuccessfulAuthenticationWithoutUpdateProfile_emailNotProvided_emailVerifyEnabled() throws Exception { RealmModel realm = getRealm(); realm.setVerifyEmail(true); setUpdateProfileFirstLogin(realm, IdentityProviderRepresentation.UPFLM_OFF); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeyCloakServerBrokerBasicTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeyCloakServerBrokerBasicTest.java index 0552875a4e..58c4ca18f3 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeyCloakServerBrokerBasicTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeyCloakServerBrokerBasicTest.java @@ -151,7 +151,7 @@ public class OIDCKeyCloakServerBrokerBasicTest extends AbstractKeycloakIdentityP } @Test - public void testSuccessfulAuthenticationWithoutUpdateProfile_emailNotProvided_emailVerifyEnabled() { + public void testSuccessfulAuthenticationWithoutUpdateProfile_emailNotProvided_emailVerifyEnabled() throws Exception { super.testSuccessfulAuthenticationWithoutUpdateProfile_emailNotProvided_emailVerifyEnabled(); } diff --git a/testsuite/integration/src/test/resources/broker-test/test-broker-realm-with-kc-oidc.json b/testsuite/integration/src/test/resources/broker-test/test-broker-realm-with-kc-oidc.json index 767ab44a3b..05cb0e9340 100755 --- a/testsuite/integration/src/test/resources/broker-test/test-broker-realm-with-kc-oidc.json +++ b/testsuite/integration/src/test/resources/broker-test/test-broker-realm-with-kc-oidc.json @@ -26,8 +26,8 @@ "claim.name": "mobile", "Claim JSON Type": "String", "access.token.claim": "true", - "id.token.claim": "true" - + "id.token.claim": "true", + "userinfo.token.claim": "true" } }, { @@ -40,8 +40,8 @@ "claim.name": "preferred_username", "Claim JSON Type": "String", "access.token.claim": "true", - "id.token.claim": "true" - + "id.token.claim": "true", + "userinfo.token.claim": "true" } }, { @@ -54,8 +54,8 @@ "claim.name": "email", "Claim JSON Type": "String", "access.token.claim": "true", - "id.token.claim": "true" - + "id.token.claim": "true", + "userinfo.token.claim": "true" } }, { @@ -68,8 +68,8 @@ "claim.name": "given_name", "Claim JSON Type": "String", "access.token.claim": "true", - "id.token.claim": "true" - + "id.token.claim": "true", + "userinfo.token.claim": "true" } }, { @@ -83,7 +83,6 @@ "Claim JSON Type": "String", "access.token.claim": "true", "id.token.claim": "true" - } }, { @@ -93,8 +92,8 @@ "consentRequired": false, "config": { "access.token.claim": "true", - "id.token.claim": "true" - + "id.token.claim": "true", + "userinfo.token.claim": "true" } } diff --git a/themes/src/main/resources/theme/base/admin/resources/js/app.js b/themes/src/main/resources/theme/base/admin/resources/js/app.js index e1f9891895..e892b4544b 100755 --- a/themes/src/main/resources/theme/base/admin/resources/js/app.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/app.js @@ -953,6 +953,9 @@ module.config([ '$routeProvider', function($routeProvider) { }, mapper : function(ClientProtocolMapperLoader) { return ClientProtocolMapperLoader(); + }, + clients : function(ClientListLoader) { + return ClientListLoader(); } }, @@ -969,6 +972,9 @@ module.config([ '$routeProvider', function($routeProvider) { }, client : function(ClientLoader) { return ClientLoader(); + }, + clients : function(ClientListLoader) { + return ClientListLoader(); } }, controller : 'ClientProtocolMapperCreateCtrl' @@ -1017,6 +1023,9 @@ module.config([ '$routeProvider', function($routeProvider) { }, mapper : function(ClientTemplateProtocolMapperLoader) { return ClientTemplateProtocolMapperLoader(); + }, + clients : function(ClientListLoader) { + return ClientListLoader(); } }, @@ -1033,6 +1042,9 @@ module.config([ '$routeProvider', function($routeProvider) { }, template : function(ClientTemplateLoader) { return ClientTemplateLoader(); + }, + clients : function(ClientListLoader) { + return ClientListLoader(); } }, controller : 'ClientTemplateProtocolMapperCreateCtrl' diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js index 2de4453e32..3405791c03 100755 --- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js @@ -1705,8 +1705,10 @@ module.controller('ClientProtocolMapperListCtrl', function($scope, realm, client updateMappers(); }); -module.controller('ClientProtocolMapperCtrl', function($scope, realm, serverInfo, client, mapper, ClientProtocolMapper, Notifications, Dialog, $location) { +module.controller('ClientProtocolMapperCtrl', function($scope, realm, serverInfo, client, clients, mapper, ClientProtocolMapper, Notifications, Dialog, $location) { $scope.realm = realm; + $scope.clients = clients; + /* $scope.client = client; $scope.create = false; @@ -1786,8 +1788,9 @@ module.controller('ClientProtocolMapperCtrl', function($scope, realm, serverInfo }); -module.controller('ClientProtocolMapperCreateCtrl', function($scope, realm, serverInfo, client, ClientProtocolMapper, Notifications, Dialog, $location) { +module.controller('ClientProtocolMapperCreateCtrl', function($scope, realm, serverInfo, client, clients, ClientProtocolMapper, Notifications, Dialog, $location) { $scope.realm = realm; + $scope.clients = clients; if (client.protocol == null) { client.protocol = 'openid-connect'; @@ -2001,8 +2004,10 @@ module.controller('ClientTemplateProtocolMapperListCtrl', function($scope, realm updateMappers(); }); -module.controller('ClientTemplateProtocolMapperCtrl', function($scope, realm, serverInfo, template, mapper, ClientTemplateProtocolMapper, Notifications, Dialog, $location) { +module.controller('ClientTemplateProtocolMapperCtrl', function($scope, realm, serverInfo, template, mapper, clients, ClientTemplateProtocolMapper, Notifications, Dialog, $location) { $scope.realm = realm; + $scope.clients = clients; + if (template.protocol == null) { template.protocol = 'openid-connect'; } @@ -2068,8 +2073,10 @@ module.controller('ClientTemplateProtocolMapperCtrl', function($scope, realm, se }); -module.controller('ClientTemplateProtocolMapperCreateCtrl', function($scope, realm, serverInfo, template, ClientTemplateProtocolMapper, Notifications, Dialog, $location) { +module.controller('ClientTemplateProtocolMapperCreateCtrl', function($scope, realm, serverInfo, template, clients, ClientTemplateProtocolMapper, Notifications, Dialog, $location) { $scope.realm = realm; + $scope.clients = clients; + if (template.protocol == null) { template.protocol = 'openid-connect'; } diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/protocol-mapper-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/protocol-mapper-detail.html index fe781c22db..e630bf981a 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/protocol-mapper-detail.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/protocol-mapper-detail.html @@ -60,7 +60,7 @@ {{model.mapperType.helpText}} - +