/* * ****************************************************************************** * Cloud Foundry Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. * * This product is licensed to you under the Apache License, Version 2.0 (the "License"). * You may not use this product except in compliance with the License. * * This product includes a number of subcomponents with * separate copyright notices and license terms. Your use of these * subcomponents is subject to the terms and conditions of the * subcomponent's license, as noted in the LICENSE file. * ****************************************************************************** */ package org.cloudfoundry.identity.uaa.account; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import java.io.IOException; import java.util.Arrays; import java.util.List; import java.util.Map; import static java.util.Optional.ofNullable; import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.EMAIL; import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.FAMILY_NAME; import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.GIVEN_NAME; import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.NAME; import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.PHONE_NUMBER; import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.PREVIOUS_LOGON_TIME; import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.ROLES; import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.SUB; import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.USER_ATTRIBUTES; import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.USER_ID; import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.USER_NAME; @JsonDeserialize(using = UserInfoResponse.UserInfoResponsDeserializer.class) @JsonSerialize(using = UserInfoResponse.UserInfoResponseSerializer.class) public class UserInfoResponse { private static final MultiValueMap<String, Object> EMPTY_MAP = new LinkedMultiValueMap<>(); MultiValueMap<String, Object> attributes = new LinkedMultiValueMap<>(); public String getUserId() { return (String)getAttributeValue(USER_ID); } public void setUserId(String userId) { setAttributeValue(USER_ID, userId); } public String getUsername() { return (String)getAttributeValue(USER_NAME); } public void setUsername(String username) { setAttributeValue(USER_NAME, username); } public String getGivenName() { return (String)getAttributeValue(GIVEN_NAME); } public void setGivenName(String givenName) { setAttributeValue(GIVEN_NAME, givenName); } public String getFamilyName() { return (String)getAttributeValue(FAMILY_NAME); } public void setFamilyName(String familyName) { setAttributeValue(FAMILY_NAME, familyName); } @JsonProperty(NAME) public String getFullName() { return (getGivenName() != null ? getGivenName() : "") + (getFamilyName() != null ? " " + getFamilyName() : ""); } public String getEmail() { return (String)getAttributeValue(EMAIL); } public void setEmail(String email) { setAttributeValue(EMAIL, email); } public String getPhoneNumber() { return (String)getAttributeValue(PHONE_NUMBER); } public void setPhoneNumber(String phoneNumber) { setAttributeValue(PHONE_NUMBER, phoneNumber); } public String getSub() { return getUserId(); } public void setSub(String sub) { setUserId(sub); } public MultiValueMap<String, Object> getAttributes() { return attributes; } public void setAttributeValue(String name, Object value) { setAttributeValues(name, Arrays.asList(value)); } public void setAttributeValues(String name, List<Object> value) { attributes.put(name, value); } public List<Object> getAttributeValues(String name) { return attributes.get(name); } public Object getAttributeValue(String name) { return attributes.getFirst(name); } public void addAttributes(MultiValueMap<String,Object> attr) { ofNullable(attr).orElse(EMPTY_MAP).entrySet().stream().forEach( e -> setAttributeValues(e.getKey(), e.getValue()) ); } public Long getPreviousLogonSuccess() { return (Long) getAttributeValue(PREVIOUS_LOGON_TIME); } public void setPreviousLogonSuccess(Long previousLogonSuccess) { setAttributeValue(PREVIOUS_LOGON_TIME, previousLogonSuccess); } public static class UserInfoResponseSerializer extends JsonSerializer<UserInfoResponse> { @Override public void serialize(UserInfoResponse object, JsonGenerator gen, SerializerProvider serializers) throws IOException { gen.writeStartObject(); for (Map.Entry<String, List<Object>> entry : object.getAttributes().entrySet()) { String key = entry.getKey(); List<Object> value = entry.getValue(); switch (key) { //single value fields case USER_ID: //integration tests expect both user_id and sub to be present gen.writeFieldName(USER_ID); gen.writeObject(object.getUserId()); key = SUB; //use proper claim name case USER_NAME: case GIVEN_NAME: case FAMILY_NAME: case PHONE_NUMBER: case USER_ATTRIBUTES: case ROLES: case EMAIL: { gen.writeFieldName(key); if (value == null || value.size() == 0) { gen.writeNull(); } else { //ensure that type error happens early gen.writeObject(value.get(0)); } break; } case PREVIOUS_LOGON_TIME: gen.writeFieldName(key); gen.writeObject(value.get(0)); break; //multi value fields default: gen.writeFieldName(key); gen.writeObject(value); break; } } gen.writeFieldName(NAME); gen.writeObject(object.getFullName()); gen.writeEndObject(); } } public static class UserInfoResponsDeserializer extends JsonDeserializer<UserInfoResponse> { @Override public UserInfoResponse deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { JsonNode node = JsonUtils.readTree(p); Map<String, Object> map = JsonUtils.getNodeAsMap(node); UserInfoResponse response = new UserInfoResponse(); for (Map.Entry<String, Object> entry : map.entrySet()) { String key = entry.getKey(); Object value = entry.getValue(); switch (key) { case NAME: break; //we don't store this one //single value fields case SUB: key = USER_ID; //use proper attribute name case USER_NAME: case GIVEN_NAME: case FAMILY_NAME: case PHONE_NUMBER: case EMAIL: { response.setAttributeValue(key, value); break; } case PREVIOUS_LOGON_TIME: if (value!=null) { Long longValue = value.getClass() == Long.class ? (Long) value : (Long) ((Integer) value).longValue(); response.setAttributeValue(key, longValue); } break; //multi value fields default: if (value instanceof List) { response.setAttributeValues(key, (List) value); } else { response.setAttributeValue(key, value); } break; } } return response; } } }