From 190caee9a13d55fdecee10bcc85b0f348edc9649 Mon Sep 17 00:00:00 2001
From: Justin Richer <jricher@mitre.org>
Date: Fri, 27 Sep 2013 13:57:15 -0400
Subject: [PATCH] refactored userinfo serializer

---
 .../service/ScopeClaimTranslationService.java | 103 ++--------------
 .../connect/view/UserInfoSerializer.java      |  70 -----------
 .../DefaultScopeClaimTranslationService.java  | 115 ++++++++++++++++++
 .../openid/connect/view/UserInfoView.java     |  63 +++++++++-
 4 files changed, 189 insertions(+), 162 deletions(-)
 delete mode 100644 openid-connect-common/src/main/java/org/mitre/openid/connect/view/UserInfoSerializer.java
 create mode 100644 openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultScopeClaimTranslationService.java

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<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);
+
+}
\ 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<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;
-	}
-}
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<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);
+		}
+	
+}
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<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;
+	}
 }