From f09efec0318575e44d2d4fc9ba81d22a3d58415f Mon Sep 17 00:00:00 2001
From: Andrea Ceccanti <andrea.ceccanti@gmail.com>
Date: Thu, 12 Apr 2018 15:24:52 +0200
Subject: [PATCH] dynreg: filter requested grant types

This commit introduces filtering on requested grant types for
dynamically registered clients.

Since extension on the library could support additional grant types,
here we want to be strict about known grant types that cannot be
requested at dynamic client registration (or update) time, but at the
same time we want to preserve grant types that could have been granted
to a client by an administrator.

So at client registration time the list of requested grant types is
filtered to only allow grant types currently enabled for dynamically
registered clients.

OTOH, at client update time the same filtering is implemented while at
the same time preserving grant types assigned the client in other ways.
---
 .../DynamicClientRegistrationEndpoint.java    | 109 ++++++++++--------
 1 file changed, 61 insertions(+), 48 deletions(-)

diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/DynamicClientRegistrationEndpoint.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/web/DynamicClientRegistrationEndpoint.java
index 89e0418ad..07da7b986 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/DynamicClientRegistrationEndpoint.java
+++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/web/DynamicClientRegistrationEndpoint.java
@@ -17,6 +17,45 @@
  *******************************************************************************/
 package org.mitre.openid.connect.web;
 
+import static org.mitre.oauth2.model.RegisteredClientFields.APPLICATION_TYPE;
+import static org.mitre.oauth2.model.RegisteredClientFields.CLAIMS_REDIRECT_URIS;
+import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_ID;
+import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_ID_ISSUED_AT;
+import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_NAME;
+import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_SECRET;
+import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_SECRET_EXPIRES_AT;
+import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_URI;
+import static org.mitre.oauth2.model.RegisteredClientFields.CONTACTS;
+import static org.mitre.oauth2.model.RegisteredClientFields.DEFAULT_ACR_VALUES;
+import static org.mitre.oauth2.model.RegisteredClientFields.DEFAULT_MAX_AGE;
+import static org.mitre.oauth2.model.RegisteredClientFields.GRANT_TYPES;
+import static org.mitre.oauth2.model.RegisteredClientFields.ID_TOKEN_ENCRYPTED_RESPONSE_ALG;
+import static org.mitre.oauth2.model.RegisteredClientFields.ID_TOKEN_ENCRYPTED_RESPONSE_ENC;
+import static org.mitre.oauth2.model.RegisteredClientFields.ID_TOKEN_SIGNED_RESPONSE_ALG;
+import static org.mitre.oauth2.model.RegisteredClientFields.INITIATE_LOGIN_URI;
+import static org.mitre.oauth2.model.RegisteredClientFields.JWKS;
+import static org.mitre.oauth2.model.RegisteredClientFields.JWKS_URI;
+import static org.mitre.oauth2.model.RegisteredClientFields.LOGO_URI;
+import static org.mitre.oauth2.model.RegisteredClientFields.POLICY_URI;
+import static org.mitre.oauth2.model.RegisteredClientFields.POST_LOGOUT_REDIRECT_URIS;
+import static org.mitre.oauth2.model.RegisteredClientFields.REDIRECT_URIS;
+import static org.mitre.oauth2.model.RegisteredClientFields.REGISTRATION_ACCESS_TOKEN;
+import static org.mitre.oauth2.model.RegisteredClientFields.REGISTRATION_CLIENT_URI;
+import static org.mitre.oauth2.model.RegisteredClientFields.REQUEST_OBJECT_SIGNING_ALG;
+import static org.mitre.oauth2.model.RegisteredClientFields.REQUEST_URIS;
+import static org.mitre.oauth2.model.RegisteredClientFields.REQUIRE_AUTH_TIME;
+import static org.mitre.oauth2.model.RegisteredClientFields.RESPONSE_TYPES;
+import static org.mitre.oauth2.model.RegisteredClientFields.SCOPE;
+import static org.mitre.oauth2.model.RegisteredClientFields.SECTOR_IDENTIFIER_URI;
+import static org.mitre.oauth2.model.RegisteredClientFields.SOFTWARE_STATEMENT;
+import static org.mitre.oauth2.model.RegisteredClientFields.SUBJECT_TYPE;
+import static org.mitre.oauth2.model.RegisteredClientFields.TOKEN_ENDPOINT_AUTH_METHOD;
+import static org.mitre.oauth2.model.RegisteredClientFields.TOKEN_ENDPOINT_AUTH_SIGNING_ALG;
+import static org.mitre.oauth2.model.RegisteredClientFields.TOS_URI;
+import static org.mitre.oauth2.model.RegisteredClientFields.USERINFO_ENCRYPTED_RESPONSE_ALG;
+import static org.mitre.oauth2.model.RegisteredClientFields.USERINFO_ENCRYPTED_RESPONSE_ENC;
+import static org.mitre.oauth2.model.RegisteredClientFields.USERINFO_SIGNED_RESPONSE_ALG;
+
 import java.io.UnsupportedEncodingException;
 import java.text.ParseException;
 import java.util.Date;
@@ -71,45 +110,6 @@ import com.nimbusds.jose.JWSAlgorithm;
 import com.nimbusds.jose.jwk.JWKSet;
 import com.nimbusds.jwt.JWTClaimsSet;
 
-import static org.mitre.oauth2.model.RegisteredClientFields.APPLICATION_TYPE;
-import static org.mitre.oauth2.model.RegisteredClientFields.CLAIMS_REDIRECT_URIS;
-import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_ID;
-import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_ID_ISSUED_AT;
-import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_NAME;
-import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_SECRET;
-import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_SECRET_EXPIRES_AT;
-import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_URI;
-import static org.mitre.oauth2.model.RegisteredClientFields.CONTACTS;
-import static org.mitre.oauth2.model.RegisteredClientFields.DEFAULT_ACR_VALUES;
-import static org.mitre.oauth2.model.RegisteredClientFields.DEFAULT_MAX_AGE;
-import static org.mitre.oauth2.model.RegisteredClientFields.GRANT_TYPES;
-import static org.mitre.oauth2.model.RegisteredClientFields.ID_TOKEN_ENCRYPTED_RESPONSE_ALG;
-import static org.mitre.oauth2.model.RegisteredClientFields.ID_TOKEN_ENCRYPTED_RESPONSE_ENC;
-import static org.mitre.oauth2.model.RegisteredClientFields.ID_TOKEN_SIGNED_RESPONSE_ALG;
-import static org.mitre.oauth2.model.RegisteredClientFields.INITIATE_LOGIN_URI;
-import static org.mitre.oauth2.model.RegisteredClientFields.JWKS;
-import static org.mitre.oauth2.model.RegisteredClientFields.JWKS_URI;
-import static org.mitre.oauth2.model.RegisteredClientFields.LOGO_URI;
-import static org.mitre.oauth2.model.RegisteredClientFields.POLICY_URI;
-import static org.mitre.oauth2.model.RegisteredClientFields.POST_LOGOUT_REDIRECT_URIS;
-import static org.mitre.oauth2.model.RegisteredClientFields.REDIRECT_URIS;
-import static org.mitre.oauth2.model.RegisteredClientFields.REGISTRATION_ACCESS_TOKEN;
-import static org.mitre.oauth2.model.RegisteredClientFields.REGISTRATION_CLIENT_URI;
-import static org.mitre.oauth2.model.RegisteredClientFields.REQUEST_OBJECT_SIGNING_ALG;
-import static org.mitre.oauth2.model.RegisteredClientFields.REQUEST_URIS;
-import static org.mitre.oauth2.model.RegisteredClientFields.REQUIRE_AUTH_TIME;
-import static org.mitre.oauth2.model.RegisteredClientFields.RESPONSE_TYPES;
-import static org.mitre.oauth2.model.RegisteredClientFields.SCOPE;
-import static org.mitre.oauth2.model.RegisteredClientFields.SECTOR_IDENTIFIER_URI;
-import static org.mitre.oauth2.model.RegisteredClientFields.SOFTWARE_STATEMENT;
-import static org.mitre.oauth2.model.RegisteredClientFields.SUBJECT_TYPE;
-import static org.mitre.oauth2.model.RegisteredClientFields.TOKEN_ENDPOINT_AUTH_METHOD;
-import static org.mitre.oauth2.model.RegisteredClientFields.TOKEN_ENDPOINT_AUTH_SIGNING_ALG;
-import static org.mitre.oauth2.model.RegisteredClientFields.TOS_URI;
-import static org.mitre.oauth2.model.RegisteredClientFields.USERINFO_ENCRYPTED_RESPONSE_ALG;
-import static org.mitre.oauth2.model.RegisteredClientFields.USERINFO_ENCRYPTED_RESPONSE_ENC;
-import static org.mitre.oauth2.model.RegisteredClientFields.USERINFO_SIGNED_RESPONSE_ALG;
-
 @Controller
 @RequestMapping(value = DynamicClientRegistrationEndpoint.URL)
 public class DynamicClientRegistrationEndpoint {
@@ -143,6 +143,10 @@ public class DynamicClientRegistrationEndpoint {
 	 */
 	private static final Logger logger = LoggerFactory.getLogger(DynamicClientRegistrationEndpoint.class);
 
+	public static final ImmutableSet<String> ALLOWED_GRANT_TYPES = ImmutableSet.of(
+	    "authorization_code", "implicit", "client_credentials", "refresh_token", 
+	    "urn:ietf:params:oauth:grant_type:redelegate", 
+	    "urn:ietf:params:oauth:grant-type:device_code");
 	/**
 	 * Create a new Client, issue a client ID, and create a registration access token.
 	 * @param jsonString
@@ -175,6 +179,10 @@ public class DynamicClientRegistrationEndpoint {
 			newClient.setClientId(null);
 			newClient.setClientSecret(null);
 
+			Set<String> requestedGrantTypes = newClient.getGrantTypes();
+            requestedGrantTypes.retainAll(ALLOWED_GRANT_TYPES);
+            newClient.setGrantTypes(requestedGrantTypes);
+            
 			// do validation on the fields
 			try {
 				newClient = validateSoftwareStatement(newClient); // need to handle the software statement first because it might override requested values
@@ -353,6 +361,13 @@ public class DynamicClientRegistrationEndpoint {
 			newClient.setCreatedAt(oldClient.getCreatedAt());
 			newClient.setReuseRefreshToken(oldClient.isReuseRefreshToken());
 
+			Set<String> requestedGrantTypes = newClient.getGrantTypes();
+			requestedGrantTypes.retainAll(ALLOWED_GRANT_TYPES);
+			newClient.setGrantTypes(requestedGrantTypes);
+			
+			Set<String> oldClientGrantedGrantTypes = oldClient.getGrantTypes();
+			oldClientGrantedGrantTypes.removeAll(ALLOWED_GRANT_TYPES);
+			
 			// do validation on the fields
 			try {
 				newClient = validateSoftwareStatement(newClient); // need to handle the software statement first because it might override requested values
@@ -370,6 +385,11 @@ public class DynamicClientRegistrationEndpoint {
 			}
 
 			try {
+			  
+			    if (!oldClientGrantedGrantTypes.isEmpty()) {
+			      newClient.getGrantTypes().addAll(oldClientGrantedGrantTypes);
+			    }
+			    
 				// save the client
 				ClientDetailsEntity savedClient = clientService.updateClient(oldClient, newClient);
 
@@ -459,7 +479,8 @@ public class DynamicClientRegistrationEndpoint {
 		return newClient;
 	}
 
-	private ClientDetailsEntity validateGrantTypes(ClientDetailsEntity newClient) throws ValidationException {
+	private ClientDetailsEntity validateGrantTypes(ClientDetailsEntity newClient) 
+	    throws ValidationException {
 		// set default grant types if needed
 		if (newClient.getGrantTypes() == null || newClient.getGrantTypes().isEmpty()) {
 			if (newClient.getScope().contains("offline_access")) { // client asked for offline access
@@ -474,14 +495,6 @@ public class DynamicClientRegistrationEndpoint {
 			}
 		}
 
-		// filter out unknown grant types
-		// TODO: make this a pluggable service
-		Set<String> requestedGrantTypes = new HashSet<>(newClient.getGrantTypes());
-		requestedGrantTypes.retainAll(
-				ImmutableSet.of("authorization_code", "implicit",
-						"password", "client_credentials", "refresh_token",
-						"urn:ietf:params:oauth:grant_type:redelegate"));
-
 		// don't allow "password" grant type for dynamic registration
 		if (newClient.getGrantTypes().contains("password")) {
 			// return an error, you can't dynamically register for the password grant