Repository: gravitee-io/graviteeio-access-management
Pull request #557 (2019-04-09 13:16:43)

Check unsupported subject type on oidc dcr feature
This is related to issue https://github.com/gravitee-io/issues/issues/2135


Patch:
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/clientregistration/impl/DynamicClientRegistrationServiceImpl.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/clientregistration/impl/DynamicClientRegistrationServiceImpl.java
index d104fbb30b..0b92ec92a6 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/clientregistration/impl/DynamicClientRegistrationServiceImpl.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/clientregistration/impl/DynamicClientRegistrationServiceImpl.java
@@ -31,6 +31,7 @@ import io.gravitee.am.service.exception.InvalidClientMetadataException;
 import io.gravitee.am.service.exception.InvalidRedirectUriException;
 import io.gravitee.am.service.utils.GrantTypeUtils;
 import io.gravitee.am.service.utils.ResponseTypeUtils;
+import io.gravitee.am.service.utils.SubjectTypeUtils;
 import io.gravitee.am.service.utils.UriBuilder;
 import io.reactivex.Flowable;
 import io.reactivex.Maybe;
@@ -158,6 +159,7 @@ public class DynamicClientRegistrationServiceImpl implements DynamicClientRegist
         return this.validateRedirectUri(request)
                 .flatMap(this::validateResponseType)
                 .flatMap(this::validateGrantType)
+                .flatMap(this::validateSubjectType)
                 .flatMap(this::validateRequestUri)
                 .flatMap(this::validateSectorIdentifierUri)
                 .flatMap(this::validateJKWs)
@@ -174,6 +176,7 @@ public class DynamicClientRegistrationServiceImpl implements DynamicClientRegist
 
         return this.validateResponseType(request)
                 .flatMap(this::validateGrantType)
+                .flatMap(this::validateSubjectType)
                 .flatMap(this::validateRequestUri)
                 .flatMap(this::validateSectorIdentifierUri)
                 .flatMap(this::validateJKWs);
@@ -207,6 +210,16 @@ public class DynamicClientRegistrationServiceImpl implements DynamicClientRegist
         return Single.just(request);
     }
 
+    private Single<DynamicClientRegistrationRequest> validateSubjectType(DynamicClientRegistrationRequest request) {
+        //if subject_type is provided, they must be valid.
+        if(request.getSubjectType()!=null) {
+            if(!SubjectTypeUtils.isValidSubjectType(request.getSubjectType().get())) {
+                return Single.error(new InvalidClientMetadataException("Unsupported subject type"));
+            }
+        }
+        return Single.just(request);
+    }
+
     private Single<DynamicClientRegistrationRequest> validateRequestUri(DynamicClientRegistrationRequest request) {
         //Check request_uri well formated
         if(request.getRequestUris()!=null && request.getRequestUris().isPresent()) {
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/discovery/impl/OpenIDDiscoveryServiceImpl.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/discovery/impl/OpenIDDiscoveryServiceImpl.java
index 8f64253ffe..10155039d4 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/discovery/impl/OpenIDDiscoveryServiceImpl.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/discovery/impl/OpenIDDiscoveryServiceImpl.java
@@ -25,6 +25,7 @@ import io.gravitee.am.common.oidc.SubjectType;
 import io.gravitee.am.gateway.handler.oidc.discovery.OpenIDDiscoveryService;
 import io.gravitee.am.gateway.handler.oidc.discovery.OpenIDProviderMetadata;
 import io.gravitee.am.model.Domain;
+import io.gravitee.am.service.utils.SubjectTypeUtils;
 import io.jsonwebtoken.SignatureAlgorithm;
 import org.springframework.beans.factory.annotation.Autowired;
 
@@ -79,7 +80,7 @@ public class OpenIDDiscoveryServiceImpl implements OpenIDDiscoveryService {
         openIDProviderMetadata.setClaimsSupported(Stream.of(Scope.values()).map(Scope::getClaims).flatMap(Collection::stream).distinct().collect(Collectors.toList()));
         openIDProviderMetadata.setCodeChallengeMethodsSupported(Arrays.asList(CodeChallengeMethod.PLAIN, CodeChallengeMethod.S256));
         openIDProviderMetadata.setClaimsParameterSupported(true);
-        openIDProviderMetadata.setSubjectTypesSupported(Arrays.asList(SubjectType.PUBLIC));
+        openIDProviderMetadata.setSubjectTypesSupported(SubjectTypeUtils.getSupportedSubjectTypes());
 
         return openIDProviderMetadata;
     }
diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/utils/GrantTypeUtils.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/utils/GrantTypeUtils.java
index d19de129dc..4b48c2d286 100644
--- a/gravitee-am-service/src/main/java/io/gravitee/am/service/utils/GrantTypeUtils.java
+++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/utils/GrantTypeUtils.java
@@ -54,7 +54,7 @@ public class GrantTypeUtils {
     }
 
     /**
-     * Throw InvalidClientMetadataException if null or contains unknown grant types.
+     * Check if grant type is known/supported.
      * @param grantType String grant_type to validate.
      */
     public static boolean isValidGrantType(String grantType) {
diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/utils/SubjectTypeUtils.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/utils/SubjectTypeUtils.java
new file mode 100644
index 0000000000..0df18d158b
--- /dev/null
+++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/utils/SubjectTypeUtils.java
@@ -0,0 +1,52 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.gravitee.am.service.utils;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static io.gravitee.am.common.oidc.SubjectType.*;
+
+/**
+ * @author Alexandre FARIA (contact at alexandrefaria.net)
+ * @author GraviteeSource Team
+ */
+public class SubjectTypeUtils {
+
+    private static final Set<String> SUPPORTED_GRANT_TYPES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
+            PUBLIC
+    )));
+
+    /**
+     * Return the supported list of subject types.
+     * @return
+     */
+    public static List<String> getSupportedSubjectTypes() {
+        return Collections.unmodifiableList(SUPPORTED_GRANT_TYPES.stream().collect(Collectors.toList()));
+    }
+
+    /**
+     * Throw InvalidClientMetadataException if null or contains unsupported subject type.
+     * @param subjectType String grant_type to validate.
+     */
+    public static boolean isValidSubjectType(String subjectType) {
+        return SUPPORTED_GRANT_TYPES.contains(subjectType);
+    }
+}
diff --git a/postman/graviteeam-collection.json b/postman/graviteeam-collection.json
index 94bc0c219f..28622db376 100644
--- a/postman/graviteeam-collection.json
+++ b/postman/graviteeam-collection.json
@@ -1,6 +1,6 @@
 {
 	"info": {
-		"_postman_id": "2c384dba-a62c-4505-b7fb-a2c1b78e4e93",
+		"_postman_id": "4388b7d9-1598-4ebc-910d-337c701d5cce",
 		"name": "Gravitee.io AM",
 		"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
 	},
@@ -16153,6 +16153,61 @@
 									},
 									"response": []
 								},
+								{
+									"name": "Invalid subject type",
+									"event": [
+										{
+											"listen": "test",
+											"script": {
+												"id": "d83b2401-3844-4147-bbb3-abe08b43a342",
+												"exec": [
+													"pm.test(\"Status code is 400\", function () {",
+													"    pm.response.to.have.status(400);",
+													"});",
+													"",
+													"pm.test(\"Invalid grant type\", function () {",
+													"    pm.response.to.be.header('Content-Type', 'application/json');",
+													"    var body = pm.response.json();",
+													"    pm.expect(body.error).to.eql('invalid_client_metadata');",
+													"    pm.expect(body.error_description).to.eql('Unsupported subject type');",
+													"});"
+												],
+												"type": "text/javascript"
+											}
+										}
+									],
+									"request": {
+										"method": "POST",
+										"header": [
+											{
+												"key": "Content-Type",
+												"type": "text",
+												"value": "application/json"
+											},
+											{
+												"key": "Authorization",
+												"type": "text",
+												"value": "Bearer {{access_token}}"
+											}
+										],
+										"body": {
+											"mode": "raw",
+											"raw": "{\n   \"application_type\": \"web\",\n   \"redirect_uris\":\n     [\"https://client.example.org/callback\",\n      \"https://client.example.org/callback2\"],\n   \"logo_uri\": \"https://client.example.org/logo.png\",\n   \"subject_type\": \"pairwise\",\n   \"token_endpoint_auth_method\": \"client_secret_basic\",\n   \"userinfo_encrypted_response_alg\": \"RSA1_5\",\n   \"userinfo_encrypted_response_enc\": \"A128CBC-HS256\",\n   \"contacts\": [\"ve7jtb@example.org\", \"mary@example.org\"],\n   \"request_uris\":\n     [\"https://client.example.org/rf.txt#qpXaRLh_n93TTR9F252ValdatUQvQiJi5BDub2BeznA\"]\n  }"
+										},
+										"url": {
+											"raw": "{{gateway_url}}/test/oidc/register",
+											"host": [
+												"{{gateway_url}}"
+											],
+											"path": [
+												"test",
+												"oidc",
+												"register"
+											]
+										}
+									},
+									"response": []
+								},
 								{
 									"name": "Invalid request uri",
 									"event": [
@@ -16331,7 +16386,7 @@
 										],
 										"body": {
 											"mode": "raw",
-											"raw": "{\n   \"application_type\": \"web\",\n   \"redirect_uris\":\n     [\"https://client.example.org/callback\",\n      \"https://client.example.org/callback2\"],\n   \"logo_uri\": \"https://client.example.org/logo.png\",\n   \"subject_type\": \"pairwise\",\n   \"token_endpoint_auth_method\": \"client_secret_basic\",\n   \"userinfo_encrypted_response_alg\": \"RSA1_5\",\n   \"userinfo_encrypted_response_enc\": \"A128CBC-HS256\",\n   \"contacts\": [\"ve7jtb@example.org\", \"mary@example.org\"],\n   \"request_uris\":\n     [\"https://client.example.org/rf.txt#qpXaRLh_n93TTR9F252ValdatUQvQiJi5BDub2BeznA\"]\n  }"
+											"raw": "{\n   \"application_type\": \"web\",\n   \"redirect_uris\":\n     [\"https://client.example.org/callback\",\n      \"https://client.example.org/callback2\"],\n   \"logo_uri\": \"https://client.example.org/logo.png\",\n   \"token_endpoint_auth_method\": \"client_secret_basic\",\n   \"userinfo_encrypted_response_alg\": \"RSA1_5\",\n   \"userinfo_encrypted_response_enc\": \"A128CBC-HS256\",\n   \"contacts\": [\"ve7jtb@example.org\", \"mary@example.org\"],\n   \"request_uris\":\n     [\"https://client.example.org/rf.txt#qpXaRLh_n93TTR9F252ValdatUQvQiJi5BDub2BeznA\"]\n  }"
 										},
 										"url": {
 											"raw": "{{registrationEndpoint}}",
@@ -16692,7 +16747,7 @@
 										],
 										"body": {
 											"mode": "raw",
-											"raw": "{\n   \"application_type\": \"web\",\n   \"redirect_uris\":\n     [\"https://client.example.org/callback\",\n      \"https://client.example.org/callback2\"],\n   \"logo_uri\": \"https://client.example.org/logo.png\",\n   \"subject_type\": \"pairwise\",\n   \"token_endpoint_auth_method\": \"client_secret_basic\",\n   \"userinfo_encrypted_response_alg\": \"RSA1_5\",\n   \"userinfo_encrypted_response_enc\": \"A128CBC-HS256\",\n   \"contacts\": [\"ve7jtb@example.org\", \"mary@example.org\"],\n   \"request_uris\":\n     [\"https://client.example.org/rf.txt#qpXaRLh_n93TTR9F252ValdatUQvQiJi5BDub2BeznA\"]\n  }"
+											"raw": "{\n   \"application_type\": \"web\",\n   \"redirect_uris\":\n     [\"https://client.example.org/callback\",\n      \"https://client.example.org/callback2\"],\n   \"logo_uri\": \"https://client.example.org/logo.png\",\n   \"subject_type\": \"public\",\n   \"token_endpoint_auth_method\": \"client_secret_basic\",\n   \"userinfo_encrypted_response_alg\": \"RSA1_5\",\n   \"userinfo_encrypted_response_enc\": \"A128CBC-HS256\",\n   \"contacts\": [\"ve7jtb@example.org\", \"mary@example.org\"],\n   \"request_uris\":\n     [\"https://client.example.org/rf.txt#qpXaRLh_n93TTR9F252ValdatUQvQiJi5BDub2BeznA\"]\n  }"
 										},
 										"url": {
 											"raw": "{{registrationEndpoint}}",
@@ -16925,7 +16980,7 @@
 										],
 										"body": {
 											"mode": "raw",
-											"raw": "{\n    \"redirect_uris\": [\n        \"https://client.example.org/callback\",\n        \"https://client.example.org/callback2\"\n    ],\n    \"response_types\": [\n        \"code\"\n    ],\n    \"grant_types\": [\n        \"authorization_code\"\n    ],\n    \"application_type\": \"web\",\n    \"contacts\": [\n        \"marie@example.org\",\n        \"jeanne@example.org\"\n    ],\n\t\"client_name\": \"Client name updated via DCR\",\n    \"logo_uri\": \"https://client.example.org/logo.png\",\n    \"subject_type\": \"pairwise\",\n    \"userinfo_encrypted_response_alg\": \"RSA1_5\",\n    \"userinfo_encrypted_response_enc\": \"A128CBC-HS256\",\n    \"token_endpoint_auth_method\": \"client_secret_basic\",\n    \"require_auth_time\": false,\n    \"request_uris\": [\n        \"https://client.example.org/rf.txt#qpXaRLh_n93TTR9F252ValdatUQvQiJi5BDub2BeznA\"\n    ],\n    \"scope\": \"openid\"\n}"
+											"raw": "{\n    \"redirect_uris\": [\n        \"https://client.example.org/callback\",\n        \"https://client.example.org/callback2\"\n    ],\n    \"response_types\": [\n        \"code\"\n    ],\n    \"grant_types\": [\n        \"authorization_code\"\n    ],\n    \"application_type\": \"web\",\n    \"contacts\": [\n        \"marie@example.org\",\n        \"jeanne@example.org\"\n    ],\n\t\"client_name\": \"Client name updated via DCR\",\n    \"logo_uri\": \"https://client.example.org/logo.png\",\n    \"subject_type\": \"public\",\n    \"userinfo_encrypted_response_alg\": \"RSA1_5\",\n    \"userinfo_encrypted_response_enc\": \"A128CBC-HS256\",\n    \"token_endpoint_auth_method\": \"client_secret_basic\",\n    \"require_auth_time\": false,\n    \"request_uris\": [\n        \"https://client.example.org/rf.txt#qpXaRLh_n93TTR9F252ValdatUQvQiJi5BDub2BeznA\"\n    ],\n    \"scope\": \"openid\"\n}"
 										},
 										"url": {
 											"raw": "{{registrationClientUri}}",
@@ -22562,4 +22617,4 @@
 			]
 		}
 	]
-}
+}
\ No newline at end of file


Test patch:
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/test/java/io/gravitee/am/gateway/handler/oidc/clientregistration/DynamicClientRegistrationServiceTest.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/test/java/io/gravitee/am/gateway/handler/oidc/clientregistration/DynamicClientRegistrationServiceTest.java
index b3a8b846b2..02504cceec 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/test/java/io/gravitee/am/gateway/handler/oidc/clientregistration/DynamicClientRegistrationServiceTest.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/test/java/io/gravitee/am/gateway/handler/oidc/clientregistration/DynamicClientRegistrationServiceTest.java
@@ -209,6 +209,17 @@ public class DynamicClientRegistrationServiceTest {
         testObserver.assertNotComplete();
     }
 
+    @Test
+    public void validateClientRegistrationRequest_unsupportedSubjectTypePayload() {
+        DynamicClientRegistrationRequest request = new DynamicClientRegistrationRequest();
+        request.setRedirectUris(Optional.of(Arrays.asList("https://graviee.io/callback")));
+        request.setSubjectType(Optional.of("unknownSubjectType"));
+
+        TestObserver testObserver = dcrService.validateClientRegistrationRequest(request).test();
+        testObserver.assertError(InvalidClientMetadataException.class);
+        testObserver.assertNotComplete();
+    }
+
     @Test
     public void validateClientRegistrationRequest_unvalidRequestUris() {
         DynamicClientRegistrationRequest request = new DynamicClientRegistrationRequest();
diff --git a/gravitee-am-service/src/test/java/io/gravitee/am/service/utils/SubjectTypeUtilsTest.java b/gravitee-am-service/src/test/java/io/gravitee/am/service/utils/SubjectTypeUtilsTest.java
new file mode 100644
index 0000000000..80fd45ff39
--- /dev/null
+++ b/gravitee-am-service/src/test/java/io/gravitee/am/service/utils/SubjectTypeUtilsTest.java
@@ -0,0 +1,47 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.gravitee.am.service.utils;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+
+/**
+ * @author Alexandre FARIA (contact at alexandrefaria.net)
+ * @author GraviteeSource Team
+ */
+@RunWith(JUnit4.class)
+public class SubjectTypeUtilsTest {
+
+    @Test
+    public void isSupportedSubjectType_nok() {
+        assertFalse("should not be supported", SubjectTypeUtils.isValidSubjectType("unknown"));
+    }
+
+    @Test
+    public void isSupportedSubjectType_ok() {
+        assertTrue("should be supported", SubjectTypeUtils.isValidSubjectType("public"));
+    }
+
+    @Test
+    public void supportedSubjectTypeList() {
+        assertTrue("should have at least public", SubjectTypeUtils.getSupportedSubjectTypes().contains("public"));
+    }
+}