KEYCLOAK-3648

This commit is contained in:
Manuel Palacio 2016-12-01 19:34:33 +01:00
parent bed592460d
commit bfec073457
12 changed files with 262 additions and 122 deletions

View file

@ -161,6 +161,10 @@ public class BrokeredIdentityContext {
getContextData().put(Constants.USER_ATTRIBUTES_PREFIX + attributeName, list);
}
public void setUserAttribute(String attributeName, List<String> attributeValues) {
getContextData().put(Constants.USER_ATTRIBUTES_PREFIX + attributeName, attributeValues);
}
public String getUserAttribute(String attributeName) {
List<String> userAttribute = (List<String>) getContextData().get(Constants.USER_ATTRIBUTES_PREFIX + attributeName);
if (userAttribute == null || userAttribute.isEmpty()) {

View file

@ -76,7 +76,7 @@ public abstract class AbstractClaimMapper extends AbstractIdentityProviderMapper
{
// Search the OIDC UserInfo claim set (if any)
JsonNode profileJsonNode = (JsonNode) context.getContextData().get(OIDCIdentityProvider.USER_INFO);
String value = AbstractJsonUserAttributeMapper.getJsonValue(profileJsonNode, claim);
Object value = AbstractJsonUserAttributeMapper.getJsonValue(profileJsonNode, claim);
if (value != null) return value;
}
return null;

View file

@ -125,9 +125,13 @@ public abstract class AbstractJsonUserAttributeMapper extends AbstractIdentityPr
}
attribute = attribute.trim();
String value = getJsonValue(mapperModel, context);
Object value = getJsonValue(mapperModel, context);
if (value != null) {
context.setUserAttribute(attribute, value);
if (value instanceof List) {
context.setUserAttribute(attribute, (List<String>) value);
} else {
context.setUserAttribute(attribute, value.toString());
}
}
}
@ -136,7 +140,7 @@ public abstract class AbstractJsonUserAttributeMapper extends AbstractIdentityPr
// we do not update user profile from social provider
}
protected static String getJsonValue(IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
protected static Object getJsonValue(IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
String jsonField = mapperModel.getConfig().get(CONF_JSON_FIELD);
if (jsonField == null || jsonField.trim().isEmpty()) {
@ -152,7 +156,7 @@ public abstract class AbstractJsonUserAttributeMapper extends AbstractIdentityPr
JsonNode profileJsonNode = (JsonNode) context.getContextData().get(CONTEXT_JSON_NODE);
String value = getJsonValue(profileJsonNode, jsonField);
Object value = getJsonValue(profileJsonNode, jsonField);
if (value == null) {
logger.debugf("User profile JSON value '%s' is not available.", jsonField);
@ -161,7 +165,7 @@ public abstract class AbstractJsonUserAttributeMapper extends AbstractIdentityPr
return value;
}
public static String getJsonValue(JsonNode baseNode, String fieldPath) {
public static Object getJsonValue(JsonNode baseNode, String fieldPath) {
logger.debug("Going to process JsonNode path " + fieldPath + " on data " + baseNode);
if (baseNode != null) {
@ -206,6 +210,20 @@ public abstract class AbstractJsonUserAttributeMapper extends AbstractIdentityPr
}
if (idx < 0) {
if (currentNode.isArray()) {
List<String> values = new ArrayList<>();
for (JsonNode childNode : currentNode) {
if (childNode.isTextual()) {
values.add(childNode.textValue());
} else {
logger.warn("JsonNode in array is not text value " + childNode);
}
}
if (values.isEmpty()) {
return null;
}
return arrayIndex == idx? values : null;
}
if (!currentNode.isValueNode()) {
logger.debug("JsonNode is not value node for name " + currentFieldName);
return null;

View file

@ -20,14 +20,20 @@ package org.keycloak.broker.oidc.mappers;
import org.keycloak.broker.oidc.KeycloakOIDCIdentityProviderFactory;
import org.keycloak.broker.oidc.OIDCIdentityProviderFactory;
import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.common.util.CollectionUtil;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.saml.common.util.StringUtil;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.stream.Collectors;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -37,9 +43,12 @@ public class UserAttributeMapper extends AbstractClaimMapper {
public static final String[] COMPATIBLE_PROVIDERS = {KeycloakOIDCIdentityProviderFactory.PROVIDER_ID, OIDCIdentityProviderFactory.PROVIDER_ID};
private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
private static final List<ProviderConfigProperty> configProperties = new ArrayList<>();
public static final String USER_ATTRIBUTE = "user.attribute";
public static final String EMAIL = "email";
public static final String FIRST_NAME = "firstName";
public static final String LAST_NAME = "lastName";
static {
ProviderConfigProperty property;
@ -88,37 +97,63 @@ public class UserAttributeMapper extends AbstractClaimMapper {
@Override
public void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
String attribute = mapperModel.getConfig().get(USER_ATTRIBUTE);
Object value = getClaimValue(mapperModel, context);
if (value != null) {
if (attribute.equalsIgnoreCase("email")) {
context.setEmail(value.toString());
} else if (attribute.equalsIgnoreCase("firstName")) {
context.setFirstName(value.toString());
} else if (attribute.equalsIgnoreCase("lastName")) {
context.setLastName(value.toString());
} else {
context.setUserAttribute(attribute, value.toString());
}
if(StringUtil.isNullOrEmpty(attribute)){
return;
}
Object value = getClaimValue(mapperModel, context);
List<String> values = toList(value);
if (EMAIL.equalsIgnoreCase(attribute)) {
setIfNotEmpty(context::setEmail, values);
} else if (FIRST_NAME.equalsIgnoreCase(attribute)) {
setIfNotEmpty(context::setFirstName, values);
} else if (LAST_NAME.equalsIgnoreCase(attribute)) {
setIfNotEmpty(context::setLastName, values);
} else {
List<String> valuesToString = values.stream()
.filter(Objects::nonNull)
.map(Object::toString)
.collect(Collectors.toList());
context.setUserAttribute(attribute, valuesToString);
}
}
private void setIfNotEmpty(Consumer<String> consumer, List<String> values) {
if (values != null && !values.isEmpty()) {
consumer.accept(values.get(0));
}
}
private List<String> toList(Object value) {
List<Object> values = (value instanceof List)
? (List) value
: Collections.singletonList(value);
return values.stream()
.filter(Objects::nonNull)
.map(Object::toString)
.collect(Collectors.toList());
}
@Override
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
String attribute = mapperModel.getConfig().get(USER_ATTRIBUTE);
if(StringUtil.isNullOrEmpty(attribute)){
return;
}
Object value = getClaimValue(mapperModel, context);
String stringValue = null;
if (value != null) stringValue = value.toString();
if (attribute.equalsIgnoreCase("email")) {
user.setEmail(stringValue);
} else if (attribute.equalsIgnoreCase("firstName")) {
user.setFirstName(stringValue);
} else if (attribute.equalsIgnoreCase("lastName")) {
user.setLastName(stringValue);
List<String> values = toList(value);
if (EMAIL.equalsIgnoreCase(attribute)) {
setIfNotEmpty(user::setEmail, values);
} else if (FIRST_NAME.equalsIgnoreCase(attribute)) {
setIfNotEmpty(user::setFirstName, values);
} else if (LAST_NAME.equalsIgnoreCase(attribute)) {
setIfNotEmpty(user::setLastName, values);
} else {
String current = user.getFirstAttribute(attribute);
if (stringValue != null && !stringValue.equals(current)) {
user.setSingleAttribute(attribute, stringValue);
} else if (value == null) {
List<String> current = user.getAttribute(attribute);
if (!CollectionUtil.collectionEquals(values, current)) {
user.setAttribute(attribute, values);
} else if (values.isEmpty()) {
user.removeAttribute(attribute);
}
}

View file

@ -21,17 +21,18 @@ import org.keycloak.broker.provider.AbstractIdentityProviderMapper;
import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.broker.saml.SAMLEndpoint;
import org.keycloak.broker.saml.SAMLIdentityProviderFactory;
import org.keycloak.common.util.CollectionUtil;
import org.keycloak.dom.saml.v2.assertion.AssertionType;
import org.keycloak.dom.saml.v2.assertion.AttributeStatementType;
import org.keycloak.dom.saml.v2.assertion.AttributeType;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.*;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.saml.common.util.StringUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -41,11 +42,14 @@ public class UserAttributeMapper extends AbstractIdentityProviderMapper {
public static final String[] COMPATIBLE_PROVIDERS = {SAMLIdentityProviderFactory.PROVIDER_ID};
private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
private static final List<ProviderConfigProperty> configProperties = new ArrayList<>();
public static final String ATTRIBUTE_NAME = "attribute.name";
public static final String ATTRIBUTE_FRIENDLY_NAME = "attribute.friendly.name";
public static final String USER_ATTRIBUTE = "user.attribute";
private static final String EMAIL = "email";
private static final String FIRST_NAME = "firstName";
private static final String LAST_NAME = "lastName";
static {
ProviderConfigProperty property;
@ -99,61 +103,84 @@ public class UserAttributeMapper extends AbstractIdentityProviderMapper {
@Override
public void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
String attribute = mapperModel.getConfig().get(USER_ATTRIBUTE);
String value = getAttribute(mapperModel, context);
if (value != null) {
if (attribute.equalsIgnoreCase("email")) {
context.setEmail(value);
} else if (attribute.equalsIgnoreCase("firstName")) {
context.setFirstName(value);
} else if (attribute.equalsIgnoreCase("lastName")) {
context.setLastName(value);
if (StringUtil.isNullOrEmpty(attribute)) {
return;
}
String attributeName = getAttributeNameFromMapperModel(mapperModel);
List<String> attributeValuesInContext = findAttributeValuesInContext(attributeName, context);
if (!attributeValuesInContext.isEmpty()) {
if (attribute.equalsIgnoreCase(EMAIL)) {
setIfNotEmpty(context::setEmail, attributeValuesInContext);
} else if (attribute.equalsIgnoreCase(FIRST_NAME)) {
setIfNotEmpty(context::setFirstName, attributeValuesInContext);
} else if (attribute.equalsIgnoreCase(LAST_NAME)) {
setIfNotEmpty(context::setLastName, attributeValuesInContext);
} else {
context.setUserAttribute(attribute, value);
context.setUserAttribute(attribute, attributeValuesInContext);
}
}
}
protected String getAttribute(IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
String name = mapperModel.getConfig().get(ATTRIBUTE_NAME);
if (name != null && name.trim().equals("")) name = null;
String friendly = mapperModel.getConfig().get(ATTRIBUTE_FRIENDLY_NAME);
if (friendly != null && friendly.trim().equals("")) friendly = null;
AssertionType assertion = (AssertionType)context.getContextData().get(SAMLEndpoint.SAML_ASSERTION);
for (AttributeStatementType statement : assertion.getAttributeStatements()) {
for (AttributeStatementType.ASTChoiceType choice : statement.getAttributes()) {
AttributeType attr = choice.getAttribute();
if (name != null && !name.equals(attr.getName())) continue;
if (friendly != null && !friendly.equals(attr.getFriendlyName())) continue;
List<Object> attributeValue = attr.getAttributeValue();
if (attributeValue == null || attributeValue.isEmpty()) return null;
return attributeValue.get(0).toString();
}
private String getAttributeNameFromMapperModel(IdentityProviderMapperModel mapperModel) {
String attributeName = mapperModel.getConfig().get(ATTRIBUTE_NAME);
if (attributeName == null) {
attributeName = mapperModel.getConfig().get(ATTRIBUTE_FRIENDLY_NAME);
}
return null;
return attributeName;
}
private void setIfNotEmpty(Consumer<String> consumer, List<String> values) {
if (values != null && !values.isEmpty()) {
consumer.accept(values.get(0));
}
}
private Predicate<AttributeStatementType.ASTChoiceType> elementWith(String attributeName) {
return attributeType -> {
AttributeType attribute = attributeType.getAttribute();
return Objects.equals(attribute.getName(), attributeName)
|| Objects.equals(attribute.getFriendlyName(), attributeName);
};
}
private List<String> findAttributeValuesInContext(String attributeName, BrokeredIdentityContext context) {
AssertionType assertion = (AssertionType) context.getContextData().get(SAMLEndpoint.SAML_ASSERTION);
return assertion.getAttributeStatements().stream()
.flatMap(statement -> statement.getAttributes().stream())
.filter(elementWith(attributeName))
.flatMap(attributeType -> attributeType.getAttribute().getAttributeValue().stream())
.filter(Objects::nonNull)
.map(Object::toString)
.collect(Collectors.toList());
}
@Override
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
String attribute = mapperModel.getConfig().get(USER_ATTRIBUTE);
String value = getAttribute(mapperModel, context);
if (attribute.equalsIgnoreCase("email")) {
user.setEmail(value);
} else if (attribute.equalsIgnoreCase("firstName")) {
user.setFirstName(value);
} else if (attribute.equalsIgnoreCase("lastName")) {
user.setLastName(value);
if (StringUtil.isNullOrEmpty(attribute)) {
return;
}
String attributeName = getAttributeNameFromMapperModel(mapperModel);
List<String> attributeValuesInContext = findAttributeValuesInContext(attributeName, context);
if (attribute.equalsIgnoreCase(EMAIL)) {
setIfNotEmpty(user::setEmail, attributeValuesInContext);
} else if (attribute.equalsIgnoreCase(FIRST_NAME)) {
setIfNotEmpty(user::setFirstName, attributeValuesInContext);
} else if (attribute.equalsIgnoreCase(LAST_NAME)) {
setIfNotEmpty(user::setLastName, attributeValuesInContext);
} else {
String current = user.getFirstAttribute(attribute);
if (value != null && !value.equals(current)) {
user.setSingleAttribute(attribute, value.toString());
} else if (value == null) {
List<String> currentAttributeValues = user.getAttributes().get(attribute);
if (attributeValuesInContext != null
&& currentAttributeValues != null
&& !CollectionUtil.collectionEquals(attributeValuesInContext, currentAttributeValues)) {
user.setAttribute(attribute, attributeValuesInContext);
} else if (attributeValuesInContext == null) {
user.removeAttribute(attribute);
}
}
}
@Override

View file

@ -25,10 +25,9 @@ import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.representations.IDToken;
import org.keycloak.services.ServicesLogger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -70,29 +69,78 @@ public class OIDCAttributeMapperHelper {
ServicesLogger.LOGGER.multipleValuesForMapper(attributeValue.toString(), mappingModel.getName());
}
attributeValue = valueAsList.get(0);
attributeValue = valueAsList;
}
}
String type = mappingModel.getConfig().get(JSON_TYPE);
Object converted = convertToType(type, attributeValue);
return converted != null ? converted : attributeValue;
}
private static <X, T> List<T> transform(List<X> attributeValue, Function<X, T> mapper) {
return attributeValue.stream()
.filter(Objects::nonNull)
.map(mapper)
.collect(Collectors.toList());
}
private static Object convertToType(String type, Object attributeValue) {
if (type == null) return attributeValue;
if (type.equals("boolean")) {
if (attributeValue instanceof Boolean) return attributeValue;
if (attributeValue instanceof String) return Boolean.valueOf((String)attributeValue);
throw new RuntimeException("cannot map type for token claim");
} else if (type.equals("String")) {
if (attributeValue instanceof String) return attributeValue;
return attributeValue.toString();
} else if (type.equals("long")) {
if (attributeValue instanceof Long) return attributeValue;
if (attributeValue instanceof String) return Long.valueOf((String)attributeValue);
throw new RuntimeException("cannot map type for token claim");
} else if (type.equals("int")) {
if (attributeValue instanceof Integer) return attributeValue;
if (attributeValue instanceof String) return Integer.valueOf((String)attributeValue);
throw new RuntimeException("cannot map type for token claim");
switch (type) {
case "boolean":
Boolean booleanObject = getBoolean(attributeValue);
if (booleanObject != null) return booleanObject;
if (attributeValue instanceof List) {
return transform((List<Boolean>) attributeValue, OIDCAttributeMapperHelper::getBoolean);
}
throw new RuntimeException("cannot map type for token claim");
case "String":
if (attributeValue instanceof String) return attributeValue;
if (attributeValue instanceof List) {
return transform((List<String>) attributeValue, OIDCAttributeMapperHelper::getString);
}
return attributeValue.toString();
case "long":
Long longObject = getLong(attributeValue);
if (longObject != null) return longObject;
if (attributeValue instanceof List) {
return transform((List<Long>) attributeValue, OIDCAttributeMapperHelper::getLong);
}
throw new RuntimeException("cannot map type for token claim");
case "int":
Integer intObject = getInteger(attributeValue);
if (intObject != null) return intObject;
if (attributeValue instanceof List) {
return transform((List<Integer>) attributeValue, OIDCAttributeMapperHelper::getInteger);
}
throw new RuntimeException("cannot map type for token claim");
default:
return null;
}
return attributeValue;
}
private static String getString(Object attributeValue) {
return attributeValue.toString();
}
private static Long getLong(Object attributeValue) {
if (attributeValue instanceof Long) return (Long) attributeValue;
if (attributeValue instanceof String) return Long.valueOf((String) attributeValue);
return null;
}
private static Integer getInteger(Object attributeValue) {
if (attributeValue instanceof Integer) return (Integer) attributeValue;
if (attributeValue instanceof String) return Integer.valueOf((String) attributeValue);
return null;
}
private static Boolean getBoolean(Object attributeValue) {
if (attributeValue instanceof Boolean) return (Boolean) attributeValue;
if (attributeValue instanceof String) return Boolean.valueOf((String) attributeValue);
return null;
}
public static void mapClaim(IDToken token, ProtocolMapperModel mappingModel, Object attributeValue) {

View file

@ -52,6 +52,15 @@ public class AttributeStatementHelper {
attributeStatement.addAttribute(new AttributeStatementType.ASTChoiceType(attribute));
}
public static void addAttributes(AttributeStatementType attributeStatement, ProtocolMapperModel mappingModel,
List<String> attributeValues) {
AttributeType attribute = createAttributeType(mappingModel);
attributeValues.forEach(attribute::addAttributeValue);
attributeStatement.addAttribute(new AttributeStatementType.ASTChoiceType(attribute));
}
public static AttributeType createAttributeType(ProtocolMapperModel mappingModel) {
String attributeName = mappingModel.getConfig().get(SAML_ATTRIBUTE_NAME);
AttributeType attribute = new AttributeType(attributeName);

View file

@ -80,10 +80,9 @@ public class UserAttributeStatementMapper extends AbstractSAMLProtocolMapper imp
public void transformAttributeStatement(AttributeStatementType attributeStatement, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) {
UserModel user = userSession.getUser();
String attributeName = mappingModel.getConfig().get(ProtocolMapperUtils.USER_ATTRIBUTE);
String attributeValue = KeycloakModelUtils.resolveFirstAttribute(user, attributeName);
if (attributeValue == null) return;
AttributeStatementHelper.addAttribute(attributeStatement, mappingModel, attributeValue);
List<String> attributeValues = KeycloakModelUtils.resolveAttribute(user, attributeName);
if (attributeValues.isEmpty()) return;
AttributeStatementHelper.addAttributes(attributeStatement, mappingModel, attributeValues);
}
public static ProtocolMapperModel createAttributeMapper(String name, String userAttribute,

View file

@ -24,6 +24,7 @@ import org.junit.Test;
import org.keycloak.broker.oidc.mappers.AbstractJsonUserAttributeMapper;
import java.io.IOException;
import java.util.Arrays;
/**
* Unit test for {@link org.keycloak.broker.oidc.mappers.AbstractJsonUserAttributeMapper}
@ -95,7 +96,7 @@ public class AbstractJsonUserAttributeMapperTest {
public void getJsonValue_simpleArray() throws JsonProcessingException, IOException {
// array field itself returns null if no index is provided
Assert.assertEquals(null, AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "value_array"));
Assert.assertEquals(Arrays.asList("a1", "a2"), AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "value_array"));
// outside index returns null
Assert.assertEquals(null, AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "value_array[2]"));

View file

@ -32,10 +32,10 @@ import org.keycloak.representations.idm.ProtocolMapperRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.*;
import static org.hamcrest.CoreMatchers.hasItems;
import static org.hamcrest.MatcherAssert.assertThat;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
@ -119,7 +119,7 @@ public class GroupMappersTest extends AbstractGroupTest {
Assert.assertNotNull(groups);
Assert.assertTrue(groups.size() == 1);
Assert.assertEquals("topGroup", groups.get(0));
Assert.assertEquals("true", token.getOtherClaims().get("topAttribute"));
Assert.assertEquals(Collections.singletonList("true"), token.getOtherClaims().get("topAttribute"));
}
{
UserRepresentation user = realm.users().search("level2GroupUser", -1, -1).get(0);
@ -132,8 +132,8 @@ public class GroupMappersTest extends AbstractGroupTest {
Assert.assertNotNull(groups);
Assert.assertTrue(groups.size() == 1);
Assert.assertEquals("level2group", groups.get(0));
Assert.assertEquals("true", token.getOtherClaims().get("topAttribute"));
Assert.assertEquals("true", token.getOtherClaims().get("level2Attribute"));
Assert.assertEquals(Collections.singletonList("true"), token.getOtherClaims().get("topAttribute"));
Assert.assertEquals(Collections.singletonList("true"), token.getOtherClaims().get("level2Attribute"));
}
}
}

View file

@ -238,7 +238,6 @@ public abstract class AbstractUserAttributeMapperTest extends AbstractBaseBroker
}
@Test
@Ignore("Unignore to test KEYCLOAK-3648")
public void testBasicMappingMultipleValues() {
testValueMapping(ImmutableMap.<String, List<String>>builder()
.put(ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("value 1").add("value 2").build())

View file

@ -141,12 +141,12 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest {
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"));
assertThat((List<String>) idToken.getOtherClaims().get("home_phone"), hasItems("617-777-6666"));
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"));
assertThat((List<String>) nested.get("phone"), hasItems("617-777-6666"));
List<String> departments = (List<String>) idToken.getOtherClaims().get("department");
assertEquals(2, departments.size());
assertTrue(departments.contains("finance") && departments.contains("development"));
@ -161,12 +161,12 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest {
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"));
assertThat((List<String>) accessToken.getOtherClaims().get("home_phone"), hasItems("617-777-6666"));
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"));
assertThat((List<String>) nested.get("phone"), hasItems("617-777-6666"));
departments = (List<String>) idToken.getOtherClaims().get("department");
assertEquals(2, departments.size());
assertTrue(departments.contains("finance") && departments.contains("development"));