refactored userinfo serializer
parent
1a82060cf5
commit
190caee9a1
|
@ -1,99 +1,20 @@
|
|||
/**
|
||||
*
|
||||
*/
|
||||
package org.mitre.openid.connect.service;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.google.common.collect.HashMultimap;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.SetMultimap;
|
||||
|
||||
/**
|
||||
* Service to map scopes to claims, and claims to Java field names
|
||||
*
|
||||
* @author Amanda Anganes
|
||||
* @author jricher
|
||||
*
|
||||
*/
|
||||
public class ScopeClaimTranslationService {
|
||||
public interface ScopeClaimTranslationService {
|
||||
|
||||
private SetMultimap<String, String> scopesToClaims = HashMultimap.create();
|
||||
private Map<String, String> claimsToFields = Maps.newHashMap();
|
||||
|
||||
/**
|
||||
* Default constructor; initializes scopesToClaims map
|
||||
*/
|
||||
public ScopeClaimTranslationService() {
|
||||
|
||||
scopesToClaims.put("openid", "sub");
|
||||
|
||||
scopesToClaims.put("profile", "name");
|
||||
scopesToClaims.put("profile", "preferred_username");
|
||||
scopesToClaims.put("profile", "given_name");
|
||||
scopesToClaims.put("profile", "family_name");
|
||||
scopesToClaims.put("profile", "middle_name");
|
||||
scopesToClaims.put("profile", "nickname");
|
||||
scopesToClaims.put("profile", "profile");
|
||||
scopesToClaims.put("profile", "picture");
|
||||
scopesToClaims.put("profile", "website");
|
||||
scopesToClaims.put("profile", "gender");
|
||||
scopesToClaims.put("profile", "zone_info");
|
||||
scopesToClaims.put("profile", "locale");
|
||||
scopesToClaims.put("profile", "updated_time");
|
||||
scopesToClaims.put("profile", "birthdate");
|
||||
|
||||
scopesToClaims.put("email", "email");
|
||||
scopesToClaims.put("email", "email_verified");
|
||||
|
||||
scopesToClaims.put("phone", "phone_number");
|
||||
scopesToClaims.put("phone", "phone_number_verified");
|
||||
|
||||
scopesToClaims.put("address", "address");
|
||||
|
||||
claimsToFields.put("sub", "sub");
|
||||
|
||||
claimsToFields.put("name", "name");
|
||||
claimsToFields.put("preferred_username", "preferredUsername");
|
||||
claimsToFields.put("given_name", "givenName");
|
||||
claimsToFields.put("family_name", "familyName");
|
||||
claimsToFields.put("middle_name", "middleName");
|
||||
claimsToFields.put("nickname", "nickname");
|
||||
claimsToFields.put("profile", "profile");
|
||||
claimsToFields.put("picture", "picture");
|
||||
claimsToFields.put("website", "website");
|
||||
claimsToFields.put("gender", "gender");
|
||||
claimsToFields.put("zone_info", "zoneinfo");
|
||||
claimsToFields.put("locale", "locale");
|
||||
claimsToFields.put("updated_time", "updatedTime");
|
||||
claimsToFields.put("birthdate", "birthdate");
|
||||
|
||||
claimsToFields.put("email", "email");
|
||||
claimsToFields.put("email_verified", "emailVerified");
|
||||
|
||||
claimsToFields.put("phone_number", "phoneNumber");
|
||||
claimsToFields.put("phone_number_verified", "phoneNumberVerified");
|
||||
|
||||
claimsToFields.put("address", "address");
|
||||
|
||||
}
|
||||
|
||||
public Set<String> getClaimsForScope(String scope) {
|
||||
if (scopesToClaims.containsKey(scope)) {
|
||||
return scopesToClaims.get(scope);
|
||||
} else {
|
||||
return new HashSet<String>();
|
||||
}
|
||||
}
|
||||
|
||||
public Set<String> getClaimsForScopeSet(Set<String> scopes) {
|
||||
Set<String> result = new HashSet<String>();
|
||||
for (String scope : scopes) {
|
||||
result.addAll(getClaimsForScope(scope));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public String getFieldNameForClaim(String claim) {
|
||||
return claimsToFields.get(claim);
|
||||
}
|
||||
|
||||
}
|
||||
public Set<String> getClaimsForScope(String scope);
|
||||
|
||||
public Set<String> getClaimsForScopeSet(Set<String> scopes);
|
||||
|
||||
public String getFieldNameForClaim(String claim);
|
||||
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
package org.mitre.openid.connect.view;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import org.mitre.openid.connect.model.UserInfo;
|
||||
import org.mitre.openid.connect.service.ScopeClaimTranslationService;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
public class UserInfoSerializer {
|
||||
|
||||
private static ScopeClaimTranslationService translator = new ScopeClaimTranslationService();
|
||||
|
||||
/**
|
||||
* Build a JSON response according to the request object received.
|
||||
*
|
||||
* Claims requested in requestObj.userinfo.claims are added to any
|
||||
* claims corresponding to requested scopes, if any.
|
||||
*
|
||||
* @param ui the UserInfo to filter
|
||||
* @param scope the allowed scopes to filter by
|
||||
* @param authorizedClaims the claims authorized by the client or user
|
||||
* @param requestedClaims the claims requested in the RequestObject
|
||||
* @return the filtered JsonObject result
|
||||
*/
|
||||
public static JsonObject toJsonFromRequestObj(UserInfo ui, Set<String> scope, JsonObject authorizedClaims, JsonObject requestedClaims) {
|
||||
|
||||
// get the base object
|
||||
JsonObject obj = ui.toJson();
|
||||
|
||||
Set<String> allowedByScope = translator.getClaimsForScopeSet(scope);
|
||||
Set<String> authorizedByClaims = new HashSet<String>();
|
||||
Set<String> requestedByClaims = new HashSet<String>();
|
||||
|
||||
if (authorizedClaims != null) {
|
||||
JsonObject userinfoAuthorized = authorizedClaims.getAsJsonObject().get("userinfo").getAsJsonObject();
|
||||
for (Entry<String, JsonElement> entry : userinfoAuthorized.getAsJsonObject().entrySet()) {
|
||||
authorizedByClaims.add(entry.getKey());
|
||||
}
|
||||
}
|
||||
if (requestedClaims != null) {
|
||||
JsonObject userinfoRequested = requestedClaims.getAsJsonObject().get("userinfo").getAsJsonObject();
|
||||
for (Entry<String, JsonElement> entry : userinfoRequested.getAsJsonObject().entrySet()) {
|
||||
requestedByClaims.add(entry.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
// Filter claims by performing a manual intersection of claims that are allowed by the given scope, requested, and authorized.
|
||||
// We cannot use Sets.intersection() or similar because Entry<> objects will evaluate to being unequal if their values are
|
||||
// different, whereas we are only interested in matching the Entry<>'s key values.
|
||||
JsonObject result = new JsonObject();
|
||||
for (Entry<String, JsonElement> entry : obj.entrySet()) {
|
||||
|
||||
if (allowedByScope.contains(entry.getKey())
|
||||
|| authorizedByClaims.contains(entry.getKey())) {
|
||||
// it's allowed either by scope or by the authorized claims (either way is fine with us)
|
||||
|
||||
if (requestedByClaims.isEmpty() || requestedByClaims.contains(entry.getKey())) {
|
||||
// the requested claims are empty (so we allow all), or they're not empty and this claim was specifically asked for
|
||||
result.add(entry.getKey(), entry.getValue());
|
||||
} // otherwise there were specific claims requested and this wasn't one of them
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
package org.mitre.openid.connect.service.impl;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.mitre.openid.connect.service.ScopeClaimTranslationService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.google.common.collect.HashMultimap;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.SetMultimap;
|
||||
|
||||
/**
|
||||
* Service to map scopes to claims, and claims to Java field names
|
||||
*
|
||||
* @author Amanda Anganes
|
||||
*
|
||||
*/
|
||||
@Service("scopeClaimTranslator")
|
||||
public class DefaultScopeClaimTranslationService implements ScopeClaimTranslationService {
|
||||
|
||||
private SetMultimap<String, String> scopesToClaims = HashMultimap.create();
|
||||
private Map<String, String> claimsToFields = Maps.newHashMap();
|
||||
|
||||
/**
|
||||
* Default constructor; initializes scopesToClaims map
|
||||
*/
|
||||
public DefaultScopeClaimTranslationService() {
|
||||
|
||||
scopesToClaims.put("openid", "sub");
|
||||
|
||||
scopesToClaims.put("profile", "name");
|
||||
scopesToClaims.put("profile", "preferred_username");
|
||||
scopesToClaims.put("profile", "given_name");
|
||||
scopesToClaims.put("profile", "family_name");
|
||||
scopesToClaims.put("profile", "middle_name");
|
||||
scopesToClaims.put("profile", "nickname");
|
||||
scopesToClaims.put("profile", "profile");
|
||||
scopesToClaims.put("profile", "picture");
|
||||
scopesToClaims.put("profile", "website");
|
||||
scopesToClaims.put("profile", "gender");
|
||||
scopesToClaims.put("profile", "zone_info");
|
||||
scopesToClaims.put("profile", "locale");
|
||||
scopesToClaims.put("profile", "updated_time");
|
||||
scopesToClaims.put("profile", "birthdate");
|
||||
|
||||
scopesToClaims.put("email", "email");
|
||||
scopesToClaims.put("email", "email_verified");
|
||||
|
||||
scopesToClaims.put("phone", "phone_number");
|
||||
scopesToClaims.put("phone", "phone_number_verified");
|
||||
|
||||
scopesToClaims.put("address", "address");
|
||||
|
||||
claimsToFields.put("sub", "sub");
|
||||
|
||||
claimsToFields.put("name", "name");
|
||||
claimsToFields.put("preferred_username", "preferredUsername");
|
||||
claimsToFields.put("given_name", "givenName");
|
||||
claimsToFields.put("family_name", "familyName");
|
||||
claimsToFields.put("middle_name", "middleName");
|
||||
claimsToFields.put("nickname", "nickname");
|
||||
claimsToFields.put("profile", "profile");
|
||||
claimsToFields.put("picture", "picture");
|
||||
claimsToFields.put("website", "website");
|
||||
claimsToFields.put("gender", "gender");
|
||||
claimsToFields.put("zone_info", "zoneinfo");
|
||||
claimsToFields.put("locale", "locale");
|
||||
claimsToFields.put("updated_time", "updatedTime");
|
||||
claimsToFields.put("birthdate", "birthdate");
|
||||
|
||||
claimsToFields.put("email", "email");
|
||||
claimsToFields.put("email_verified", "emailVerified");
|
||||
|
||||
claimsToFields.put("phone_number", "phoneNumber");
|
||||
claimsToFields.put("phone_number_verified", "phoneNumberVerified");
|
||||
|
||||
claimsToFields.put("address", "address");
|
||||
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.mitre.openid.connect.service.ScopeClaimTranslationService#getClaimsForScope(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public Set<String> getClaimsForScope(String scope) {
|
||||
if (scopesToClaims.containsKey(scope)) {
|
||||
return scopesToClaims.get(scope);
|
||||
} else {
|
||||
return new HashSet<String>();
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.mitre.openid.connect.service.ScopeClaimTranslationService#getClaimsForScopeSet(java.util.Set)
|
||||
*/
|
||||
@Override
|
||||
public Set<String> getClaimsForScopeSet(Set<String> scopes) {
|
||||
Set<String> result = new HashSet<String>();
|
||||
for (String scope : scopes) {
|
||||
result.addAll(getClaimsForScope(scope));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.mitre.openid.connect.service.ScopeClaimTranslationService#getFieldNameForClaim(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public String getFieldNameForClaim(String claim) {
|
||||
return claimsToFields.get(claim);
|
||||
}
|
||||
|
||||
}
|
|
@ -18,15 +18,19 @@ package org.mitre.openid.connect.view;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.mitre.openid.connect.model.UserInfo;
|
||||
import org.mitre.openid.connect.service.ScopeClaimTranslationService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.validation.BeanPropertyBindingResult;
|
||||
import org.springframework.web.servlet.view.AbstractView;
|
||||
|
@ -35,6 +39,7 @@ import com.google.gson.ExclusionStrategy;
|
|||
import com.google.gson.FieldAttributes;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
|
@ -44,6 +49,9 @@ public class UserInfoView extends AbstractView {
|
|||
private static JsonParser jsonParser = new JsonParser();
|
||||
|
||||
private static Logger logger = LoggerFactory.getLogger(UserInfoView.class);
|
||||
|
||||
@Autowired
|
||||
private ScopeClaimTranslationService translator;
|
||||
|
||||
private Gson gson = new GsonBuilder().setExclusionStrategies(new ExclusionStrategy() {
|
||||
|
||||
|
@ -95,7 +103,7 @@ public class UserInfoView extends AbstractView {
|
|||
if (model.get("requestedClaims") != null) {
|
||||
requestedClaims = jsonParser.parse((String) model.get("requestedClaims")).getAsJsonObject();
|
||||
}
|
||||
gson.toJson(UserInfoSerializer.toJsonFromRequestObj(userInfo, scope, authorizedClaims, requestedClaims), out);
|
||||
gson.toJson(toJsonFromRequestObj(userInfo, scope, authorizedClaims, requestedClaims), out);
|
||||
|
||||
} catch (IOException e) {
|
||||
|
||||
|
@ -105,4 +113,57 @@ public class UserInfoView extends AbstractView {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a JSON response according to the request object received.
|
||||
*
|
||||
* Claims requested in requestObj.userinfo.claims are added to any
|
||||
* claims corresponding to requested scopes, if any.
|
||||
*
|
||||
* @param ui the UserInfo to filter
|
||||
* @param scope the allowed scopes to filter by
|
||||
* @param authorizedClaims the claims authorized by the client or user
|
||||
* @param requestedClaims the claims requested in the RequestObject
|
||||
* @return the filtered JsonObject result
|
||||
*/
|
||||
private JsonObject toJsonFromRequestObj(UserInfo ui, Set<String> scope, JsonObject authorizedClaims, JsonObject requestedClaims) {
|
||||
|
||||
// get the base object
|
||||
JsonObject obj = ui.toJson();
|
||||
|
||||
Set<String> allowedByScope = translator.getClaimsForScopeSet(scope);
|
||||
Set<String> authorizedByClaims = new HashSet<String>();
|
||||
Set<String> requestedByClaims = new HashSet<String>();
|
||||
|
||||
if (authorizedClaims != null) {
|
||||
JsonObject userinfoAuthorized = authorizedClaims.getAsJsonObject().get("userinfo").getAsJsonObject();
|
||||
for (Entry<String, JsonElement> entry : userinfoAuthorized.getAsJsonObject().entrySet()) {
|
||||
authorizedByClaims.add(entry.getKey());
|
||||
}
|
||||
}
|
||||
if (requestedClaims != null) {
|
||||
JsonObject userinfoRequested = requestedClaims.getAsJsonObject().get("userinfo").getAsJsonObject();
|
||||
for (Entry<String, JsonElement> entry : userinfoRequested.getAsJsonObject().entrySet()) {
|
||||
requestedByClaims.add(entry.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
// Filter claims by performing a manual intersection of claims that are allowed by the given scope, requested, and authorized.
|
||||
// We cannot use Sets.intersection() or similar because Entry<> objects will evaluate to being unequal if their values are
|
||||
// different, whereas we are only interested in matching the Entry<>'s key values.
|
||||
JsonObject result = new JsonObject();
|
||||
for (Entry<String, JsonElement> entry : obj.entrySet()) {
|
||||
|
||||
if (allowedByScope.contains(entry.getKey())
|
||||
|| authorizedByClaims.contains(entry.getKey())) {
|
||||
// it's allowed either by scope or by the authorized claims (either way is fine with us)
|
||||
|
||||
if (requestedByClaims.isEmpty() || requestedByClaims.contains(entry.getKey())) {
|
||||
// the requested claims are empty (so we allow all), or they're not empty and this claim was specifically asked for
|
||||
result.add(entry.getKey(), entry.getValue());
|
||||
} // otherwise there were specific claims requested and this wasn't one of them
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue