diff --git a/openid-connect-common/src/main/java/org/mitre/openid/connect/service/ScopeClaimTranslationService.java b/openid-connect-common/src/main/java/org/mitre/openid/connect/service/ScopeClaimTranslationService.java index 2af749bfe..36946bff7 100644 --- a/openid-connect-common/src/main/java/org/mitre/openid/connect/service/ScopeClaimTranslationService.java +++ b/openid-connect-common/src/main/java/org/mitre/openid/connect/service/ScopeClaimTranslationService.java @@ -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 scopesToClaims = HashMultimap.create(); - private Map 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 getClaimsForScope(String scope) { - if (scopesToClaims.containsKey(scope)) { - return scopesToClaims.get(scope); - } else { - return new HashSet(); - } - } - - public Set getClaimsForScopeSet(Set scopes) { - Set result = new HashSet(); - for (String scope : scopes) { - result.addAll(getClaimsForScope(scope)); - } - return result; - } - - public String getFieldNameForClaim(String claim) { - return claimsToFields.get(claim); - } - -} + public Set getClaimsForScope(String scope); + + public Set getClaimsForScopeSet(Set scopes); + + public String getFieldNameForClaim(String claim); + +} \ No newline at end of file diff --git a/openid-connect-common/src/main/java/org/mitre/openid/connect/view/UserInfoSerializer.java b/openid-connect-common/src/main/java/org/mitre/openid/connect/view/UserInfoSerializer.java deleted file mode 100644 index 375eb3628..000000000 --- a/openid-connect-common/src/main/java/org/mitre/openid/connect/view/UserInfoSerializer.java +++ /dev/null @@ -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 scope, JsonObject authorizedClaims, JsonObject requestedClaims) { - - // get the base object - JsonObject obj = ui.toJson(); - - Set allowedByScope = translator.getClaimsForScopeSet(scope); - Set authorizedByClaims = new HashSet(); - Set requestedByClaims = new HashSet(); - - if (authorizedClaims != null) { - JsonObject userinfoAuthorized = authorizedClaims.getAsJsonObject().get("userinfo").getAsJsonObject(); - for (Entry entry : userinfoAuthorized.getAsJsonObject().entrySet()) { - authorizedByClaims.add(entry.getKey()); - } - } - if (requestedClaims != null) { - JsonObject userinfoRequested = requestedClaims.getAsJsonObject().get("userinfo").getAsJsonObject(); - for (Entry 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 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; - } -} diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultScopeClaimTranslationService.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultScopeClaimTranslationService.java new file mode 100644 index 000000000..259d46406 --- /dev/null +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultScopeClaimTranslationService.java @@ -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 scopesToClaims = HashMultimap.create(); + private Map 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 getClaimsForScope(String scope) { + if (scopesToClaims.containsKey(scope)) { + return scopesToClaims.get(scope); + } else { + return new HashSet(); + } + } + + /* (non-Javadoc) + * @see org.mitre.openid.connect.service.ScopeClaimTranslationService#getClaimsForScopeSet(java.util.Set) + */ + @Override + public Set getClaimsForScopeSet(Set scopes) { + Set result = new HashSet(); + 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); + } + +} diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/view/UserInfoView.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/view/UserInfoView.java index 2857d6635..e914765e0 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/view/UserInfoView.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/view/UserInfoView.java @@ -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 scope, JsonObject authorizedClaims, JsonObject requestedClaims) { + + // get the base object + JsonObject obj = ui.toJson(); + + Set allowedByScope = translator.getClaimsForScopeSet(scope); + Set authorizedByClaims = new HashSet(); + Set requestedByClaims = new HashSet(); + + if (authorizedClaims != null) { + JsonObject userinfoAuthorized = authorizedClaims.getAsJsonObject().get("userinfo").getAsJsonObject(); + for (Entry entry : userinfoAuthorized.getAsJsonObject().entrySet()) { + authorizedByClaims.add(entry.getKey()); + } + } + if (requestedClaims != null) { + JsonObject userinfoRequested = requestedClaims.getAsJsonObject().get("userinfo").getAsJsonObject(); + for (Entry 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 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; + } }