From d0a96d463dba38a7f57e1e0157063f69b3b530b1 Mon Sep 17 00:00:00 2001 From: mposolda Date: Wed, 30 Nov 2016 13:04:31 +0100 Subject: [PATCH] KEYCLOAK-3831 Improve AddressMapper configurability. Support for 'formatted' subclaim --- .../protocol/oidc/mappers/AddressMapper.java | 71 +++++++++++++------ .../oauth/OIDCProtocolMappersTest.java | 15 +++- .../messages/admin-messages_en.properties | 14 +++- 3 files changed, 74 insertions(+), 26 deletions(-) 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 674c9ff10c..2f40c21829 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 @@ -17,14 +17,11 @@ 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.UserModel; import org.keycloak.models.UserSessionModel; import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.provider.ProviderConfigProperty; -import org.keycloak.representations.AccessToken; import org.keycloak.representations.AddressClaimSet; import org.keycloak.representations.IDToken; @@ -34,7 +31,6 @@ import java.util.List; import java.util.Map; /** - * Set the 'name' claim to be first + last name. * * @author Bill Burke * @version $Revision: 1 $ @@ -43,26 +39,39 @@ public class AddressMapper extends AbstractOIDCProtocolMapper implements OIDCAcc private static final List configProperties = new ArrayList(); + public static final String STREET = "street"; + static { OIDCAttributeMapperHelper.addIncludeInTokensConfig(configProperties, AddressMapper.class); + + configProperties.add(createConfigProperty(STREET)); + configProperties.add(createConfigProperty(AddressClaimSet.LOCALITY)); + configProperties.add(createConfigProperty(AddressClaimSet.REGION)); + configProperties.add(createConfigProperty(AddressClaimSet.POSTAL_CODE)); + configProperties.add(createConfigProperty(AddressClaimSet.COUNTRY)); + configProperties.add(createConfigProperty(AddressClaimSet.FORMATTED)); + } + + protected static ProviderConfigProperty createConfigProperty(String claimName) { + ProviderConfigProperty property = new ProviderConfigProperty(); + property.setName(getModelPropertyName(claimName)); + property.setLabel("addressClaim." + claimName + ".label"); + property.setHelpText("addressClaim." + claimName + ".tooltip"); + property.setType(ProviderConfigProperty.STRING_TYPE); + property.setDefaultValue(claimName); + return property; + } + + public static String getModelPropertyName(String claimName) { + return "user.attribute." + claimName; } public static final String PROVIDER_ID = "oidc-address-mapper"; public static ProtocolMapperModel createAddressMapper() { - Map config; - ProtocolMapperModel address = new ProtocolMapperModel(); - address.setName("address"); - address.setProtocolMapper(PROVIDER_ID); - address.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); - address.setConsentRequired(true); - address.setConsentText("${address}"); - config = new HashMap(); - config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true"); - config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "true"); - address.setConfig(config); - return address; + return createAddressMapper(true, true); } + public static ProtocolMapperModel createAddressMapper(boolean idToken, boolean accessToken) { Map config; ProtocolMapperModel address = new ProtocolMapperModel(); @@ -74,6 +83,14 @@ public class AddressMapper extends AbstractOIDCProtocolMapper implements OIDCAcc config = new HashMap(); config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, Boolean.toString(idToken)); config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, Boolean.toString(accessToken)); + + config.put(getModelPropertyName(STREET), STREET); + config.put(getModelPropertyName(AddressClaimSet.LOCALITY), AddressClaimSet.LOCALITY); + config.put(getModelPropertyName(AddressClaimSet.REGION), AddressClaimSet.REGION); + config.put(getModelPropertyName(AddressClaimSet.POSTAL_CODE), AddressClaimSet.POSTAL_CODE); + config.put(getModelPropertyName(AddressClaimSet.COUNTRY), AddressClaimSet.COUNTRY); + config.put(getModelPropertyName(AddressClaimSet.FORMATTED), AddressClaimSet.FORMATTED); + address.setConfig(config); return address; } @@ -107,12 +124,24 @@ public class AddressMapper extends AbstractOIDCProtocolMapper implements OIDCAcc protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession) { UserModel user = userSession.getUser(); AddressClaimSet addressSet = new AddressClaimSet(); - addressSet.setStreetAddress(user.getFirstAttribute("street")); - addressSet.setLocality(user.getFirstAttribute("locality")); - addressSet.setRegion(user.getFirstAttribute("region")); - addressSet.setPostalCode(user.getFirstAttribute("postal_code")); - addressSet.setCountry(user.getFirstAttribute("country")); + addressSet.setStreetAddress(getUserModelAttributeValue(user, mappingModel, STREET)); + addressSet.setLocality(getUserModelAttributeValue(user, mappingModel, AddressClaimSet.LOCALITY)); + addressSet.setRegion(getUserModelAttributeValue(user, mappingModel, AddressClaimSet.REGION)); + addressSet.setPostalCode(getUserModelAttributeValue(user, mappingModel, AddressClaimSet.POSTAL_CODE)); + addressSet.setCountry(getUserModelAttributeValue(user, mappingModel, AddressClaimSet.COUNTRY)); + addressSet.setFormattedAddress(getUserModelAttributeValue(user, mappingModel, AddressClaimSet.FORMATTED)); token.getOtherClaims().put("address", addressSet); } + private String getUserModelAttributeValue(UserModel user, ProtocolMapperModel mappingModel, String claim) { + String modelPropertyName = getModelPropertyName(claim); + String userAttrName = mappingModel.getConfig().get(modelPropertyName); + + if (userAttrName == null) { + userAttrName = claim; + } + + return user.getFirstAttribute(userAttrName); + } + } 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 index ac86d2023a..7ffcfdcad7 100644 --- 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 @@ -29,7 +29,9 @@ 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.protocol.oidc.mappers.AddressMapper; import org.keycloak.representations.AccessToken; +import org.keycloak.representations.AddressClaimSet; import org.keycloak.representations.IDToken; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ProtocolMapperRepresentation; @@ -96,11 +98,13 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest { user.singleAttribute("street", "5 Yawkey Way"); user.singleAttribute("locality", "Boston"); - user.singleAttribute("region", "MA"); + user.singleAttribute("region_some", "MA"); // Custom name for userAttribute name, which will be mapped to region user.singleAttribute("postal_code", "02115"); user.singleAttribute("country", "USA"); + user.singleAttribute("formatted", "6 Foo Street"); user.singleAttribute("phone", "617-777-6666"); + List departments = Arrays.asList("finance", "development"); user.getAttributes().put("departments", departments); userResource.update(user); @@ -108,6 +112,9 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest { ClientResource app = findClientResourceByClientId(adminClient.realm("test"), "test-app"); ProtocolMapperRepresentation mapper = createAddressMapper(true, true); + mapper.getConfig().put(AddressMapper.getModelPropertyName(AddressClaimSet.REGION), "region_some"); + mapper.getConfig().put(AddressMapper.getModelPropertyName(AddressClaimSet.COUNTRY), "country_some"); + mapper.getConfig().remove(AddressMapper.getModelPropertyName(AddressClaimSet.POSTAL_CODE)); // Even if we remove protocolMapper config property, it should still default to postal_code app.getProtocolMappers().createMapper(mapper); ProtocolMapperRepresentation hard = createHardcodedClaim("hard", "hard", "coded", "String", false, null, true, true); @@ -131,7 +138,8 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest { assertEquals(idToken.getAddress().getLocality(), "Boston"); assertEquals(idToken.getAddress().getRegion(), "MA"); assertEquals(idToken.getAddress().getPostalCode(), "02115"); - assertEquals(idToken.getAddress().getCountry(), "USA"); + assertNull(idToken.getAddress().getCountry()); // Null because we changed userAttribute name to "country_some", but user contains "country" + assertEquals(idToken.getAddress().getFormattedAddress(), "6 Foo Street"); assertNotNull(idToken.getOtherClaims().get("home_phone")); assertEquals("617-777-6666", idToken.getOtherClaims().get("home_phone")); assertEquals("coded", idToken.getOtherClaims().get("hard")); @@ -150,7 +158,8 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest { assertEquals(accessToken.getAddress().getLocality(), "Boston"); assertEquals(accessToken.getAddress().getRegion(), "MA"); assertEquals(accessToken.getAddress().getPostalCode(), "02115"); - assertEquals(accessToken.getAddress().getCountry(), "USA"); + assertNull(idToken.getAddress().getCountry()); // Null because we changed userAttribute name to "country_some", but user contains "country" + assertEquals(idToken.getAddress().getFormattedAddress(), "6 Foo Street"); assertNotNull(accessToken.getOtherClaims().get("home_phone")); assertEquals("617-777-6666", accessToken.getOtherClaims().get("home_phone")); assertEquals("coded", accessToken.getOtherClaims().get("hard")); 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 29f1465944..8cf09f3b7a 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 @@ -170,8 +170,18 @@ sectorIdentifierUri.label=Sector Identifier URI sectorIdentifierUri.tooltip=Providers that use pairwise sub values and support Dynamic Client Registration SHOULD use the sector_identifier_uri parameter. It provides a way for a group of websites under common administrative control to have consistent pairwise sub values independent of the individual domain names. It also provides a way for Clients to change redirect_uri domains without having to reregister all of their users. pairwiseSubAlgorithmSalt.label=Salt pairwiseSubAlgorithmSalt.tooltip=Salt used when calculating the pairwise subject identifier. If left blank, a salt will be generated. - - +addressClaim.street.label=User Attribute Name for Street +addressClaim.street.tooltip=Name of User Attribute, which will be used to map to 'street_address' subclaim inside 'address' token claim. Defaults to 'street' . +addressClaim.locality.label=User Attribute Name for Locality +addressClaim.locality.tooltip=Name of User Attribute, which will be used to map to 'locality' subclaim inside 'address' token claim. Defaults to 'locality' . +addressClaim.region.label=User Attribute Name for Region +addressClaim.region.tooltip=Name of User Attribute, which will be used to map to 'region' subclaim inside 'address' token claim. Defaults to 'region' . +addressClaim.postal_code.label=User Attribute Name for Postal Code +addressClaim.postal_code.tooltip=Name of User Attribute, which will be used to map to 'postal_code' subclaim inside 'address' token claim. Defaults to 'postal_code' . +addressClaim.country.label=User Attribute Name for Country +addressClaim.country.tooltip=Name of User Attribute, which will be used to map to 'country' subclaim inside 'address' token claim. Defaults to 'country' . +addressClaim.formatted.label=User Attribute Name for Formatted Address +addressClaim.formatted.tooltip=Name of User Attribute, which will be used to map to 'formatted' subclaim inside 'address' token claim. Defaults to 'formatted' . # 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.