From 3063e44223fb7735ed3e77cbe499d119dc637d90 Mon Sep 17 00:00:00 2001 From: HiranyaKavishani Date: Thu, 18 Apr 2024 15:00:25 +0530 Subject: [PATCH] Fixing ScopeNotFound issue when API defaultSecurityScheme has other oauthFlows (#6453) * Fixing ScopeNotFound issue when API default securityScheme has other oauthFlows * Adding unit tests * Fixing formatting issues --- .../wso2/carbon/apimgt/api/APIDefinition.java | 9 ++ .../impl/definitions/AsyncApiParser.java | 5 + .../apimgt/impl/definitions/OAS2Parser.java | 7 ++ .../apimgt/impl/definitions/OAS3Parser.java | 107 ++++++++++++++++++ .../impl/definitions/OASParserUtil.java | 3 + .../impl/definitions/OASParserUtilTest.java | 30 ++++- .../default_multiple_oauth_flows_scopes.yaml | 55 +++++++++ ..._multiple_oauth_flows_scopes_response.json | 82 ++++++++++++++ .../default_password_oauth_flow_scopes.yaml | 36 ++++++ ...t_password_oauth_flow_scopes_response.json | 62 ++++++++++ 10 files changed, 395 insertions(+), 1 deletion(-) create mode 100644 components/apimgt/org.wso2.carbon.apimgt.impl/src/test/resources/definitions/oas3/default_multiple_oauth_flows_scopes.yaml create mode 100644 components/apimgt/org.wso2.carbon.apimgt.impl/src/test/resources/definitions/oas3/default_multiple_oauth_flows_scopes_response.json create mode 100644 components/apimgt/org.wso2.carbon.apimgt.impl/src/test/resources/definitions/oas3/default_password_oauth_flow_scopes.yaml create mode 100644 components/apimgt/org.wso2.carbon.apimgt.impl/src/test/resources/definitions/oas3/default_password_oauth_flow_scopes_response.json diff --git a/components/apimgt/org.wso2.carbon.apimgt.api/src/main/java/org/wso2/carbon/apimgt/api/APIDefinition.java b/components/apimgt/org.wso2.carbon.apimgt.api/src/main/java/org/wso2/carbon/apimgt/api/APIDefinition.java index bc39d9285f62..adc400cb6fe1 100644 --- a/components/apimgt/org.wso2.carbon.apimgt.api/src/main/java/org/wso2/carbon/apimgt/api/APIDefinition.java +++ b/components/apimgt/org.wso2.carbon.apimgt.api/src/main/java/org/wso2/carbon/apimgt/api/APIDefinition.java @@ -207,6 +207,15 @@ public abstract String getOASDefinitionWithTierContentAwareProperty(String oasDe public abstract String processOtherSchemeScopes(String resourceConfigsJSON) throws APIManagementException; + /** + * This method returns OAS definition of default security scheme which support multiple oauth flows. + * @param resourceConfigsJSON + * @return + * @throws APIManagementException + */ + public abstract String processDefaultSchemeScopesOfMultipleOauthFlows(String resourceConfigsJSON) + throws APIManagementException; + /** * This method returns OAS definition which replaced X-WSO2-throttling-tier extension comes from * mgw with X-throttling-tier extensions in OAS file diff --git a/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/definitions/AsyncApiParser.java b/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/definitions/AsyncApiParser.java index 9a3058e2cde9..057b2294571e 100644 --- a/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/definitions/AsyncApiParser.java +++ b/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/definitions/AsyncApiParser.java @@ -1904,6 +1904,11 @@ public String processOtherSchemeScopes(String resourceConfigsJSON) throws APIMan return null; } + @Override + public String processDefaultSchemeScopesOfMultipleOauthFlows(String resourceConfigsJSON) throws APIManagementException { + return null; + } + @Override public API setExtensionsToAPI(String swaggerContent, API api) throws APIManagementException { return null; diff --git a/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/definitions/OAS2Parser.java b/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/definitions/OAS2Parser.java index 7cd47ebb653a..5cbbc211afa4 100644 --- a/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/definitions/OAS2Parser.java +++ b/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/definitions/OAS2Parser.java @@ -1502,6 +1502,13 @@ public String processOtherSchemeScopes(String swaggerContent) throws APIManageme return swaggerContent; } + @Override + public String processDefaultSchemeScopesOfMultipleOauthFlows(String swaggerContent) throws APIManagementException { + // This method is used to inject scopes of multiple oauth flows to the default scheme, + // But OAS2 does not support multiple oauth flows. + return null; + } + /** * This method returns swagger definition which replaced X-WSO2-throttling-tier extension comes from * mgw with X-throttling-tier extensions in swagger file(Swagger version 2) diff --git a/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/definitions/OAS3Parser.java b/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/definitions/OAS3Parser.java index da40508ea7d1..e440c34bb293 100644 --- a/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/definitions/OAS3Parser.java +++ b/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/definitions/OAS3Parser.java @@ -72,6 +72,7 @@ import org.wso2.carbon.apimgt.api.model.URITemplate; import org.wso2.carbon.apimgt.impl.APIConstants; import org.wso2.carbon.apimgt.impl.utils.APIUtil; + import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -1072,6 +1073,60 @@ private Set getScopesFromExtensions(OpenAPI openAPI) throws APIManagement return scopeList; } + private Set getScopesFromDefaultOAuthFlows(OpenAPI openAPI) { + HashSet scopeSet = new HashSet<>(); + + Map securitySchemes; + SecurityScheme defaultScheme; + OAuthFlows oAuthFlows; + + if (openAPI.getComponents() != null && (securitySchemes = openAPI.getComponents() + .getSecuritySchemes()) != null && (defaultScheme = securitySchemes.get( + OPENAPI_SECURITY_SCHEMA_KEY)) != null && (oAuthFlows = defaultScheme.getFlows()) != null) { + + OAuthFlow oAuthFlow = oAuthFlows.getImplicit(); + OAuthFlow authorizationCodeFlow = oAuthFlows.getAuthorizationCode(); + OAuthFlow passwordFlow = oAuthFlows.getPassword(); + OAuthFlow clientCredentialsFlow = oAuthFlows.getClientCredentials(); + + if (oAuthFlow != null && oAuthFlow.getScopes() != null) { + extractScopesFromOauthFlow(scopeSet, oAuthFlow); + } + + if (authorizationCodeFlow != null && authorizationCodeFlow.getScopes() != null) { + extractScopesFromOauthFlow(scopeSet, authorizationCodeFlow); + } + + if (passwordFlow != null) { + extractScopesFromOauthFlow(scopeSet, passwordFlow); + } + + if (clientCredentialsFlow != null) { + extractScopesFromOauthFlow(scopeSet, clientCredentialsFlow); + } + + } + return scopeSet; + } + + private void extractScopesFromOauthFlow(HashSet scopeSet, OAuthFlow oAuthFlow) { + for (Map.Entry entry : oAuthFlow.getScopes().entrySet()) { + Scope scope = new Scope(); + scope.setKey(entry.getKey()); + scope.setName(entry.getKey()); + scope.setDescription(entry.getValue()); + Map scopeBindings; + if (oAuthFlow.getExtensions() != null && (scopeBindings = (Map) oAuthFlow.getExtensions() + .get(APIConstants.SWAGGER_X_SCOPES_BINDINGS)) != null) { + if (scopeBindings.get(scope.getKey()) != null) { + scope.setRoles(scopeBindings.get(scope.getKey())); + } + } + scopeSet.add(scope); + } + } + + /** * Include Scope details to the definition * @@ -1709,6 +1764,58 @@ public String processOtherSchemeScopes(String swaggerContent) throws APIManageme return swaggerContent; } + /** + * This method will inject scopes of multiple oauth flows to default security schemes in the swagger definition + * @param swaggerContent + * @return String + */ + @Override + public String processDefaultSchemeScopesOfMultipleOauthFlows(String swaggerContent) throws APIManagementException { + OpenAPI openAPI = getOpenAPI(swaggerContent); + Set scopesFromDefaultOauthFlows = getScopesFromDefaultOAuthFlows(openAPI); + + if (!scopesFromDefaultOauthFlows.isEmpty()) { + SecurityScheme defaultScheme = openAPI.getComponents().getSecuritySchemes() + .get(OPENAPI_SECURITY_SCHEMA_KEY); + OAuthFlow oAuthFlow = defaultScheme.getFlows().getImplicit(); + if (oAuthFlow == null) { + oAuthFlow = new OAuthFlow(); + defaultScheme.getFlows().setImplicit(oAuthFlow); + } + if (oAuthFlow.getAuthorizationUrl() == null) { + oAuthFlow.setAuthorizationUrl(OPENAPI_DEFAULT_AUTHORIZATION_URL); + } + + Scopes oas3Scopes = oAuthFlow.getScopes() != null ? oAuthFlow.getScopes() : new Scopes(); + + Map scopeBindings = new HashMap<>(); + + if (oAuthFlow.getExtensions() != null) { + scopeBindings = oAuthFlow.getExtensions().get(APIConstants.SWAGGER_X_SCOPES_BINDINGS) != null ? + (Map) oAuthFlow.getExtensions().get(APIConstants.SWAGGER_X_SCOPES_BINDINGS) : + new HashMap<>(); + } + + for (Scope scope : scopesFromDefaultOauthFlows) { + if (!oas3Scopes.containsKey(scope.getKey())) { + String description = scope.getDescription() != null ? scope.getDescription() : ""; + oas3Scopes.put(scope.getKey(), description); + if (scope.getRoles() != null) { + String roles = (StringUtils.isNotBlank(scope.getRoles()) && scope.getRoles().trim() + .split(",").length > 0) ? scope.getRoles() : StringUtils.EMPTY; + scopeBindings.put(scope.getKey(), roles); + } + } + } + + if (!scopeBindings.isEmpty()) { + oAuthFlow.addExtension(APIConstants.SWAGGER_X_SCOPES_BINDINGS, scopeBindings); + } + oAuthFlow.setScopes(oas3Scopes); + } + return Json.pretty(openAPI); + } + /** * This method returns openAPI definition which replaced X-WSO2-throttling-tier extension comes from * mgw with X-throttling-tier extensions in openAPI file(openAPI version 3) diff --git a/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/definitions/OASParserUtil.java b/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/definitions/OASParserUtil.java index dd9b7a4bbcf9..33a454ecaffd 100644 --- a/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/definitions/OASParserUtil.java +++ b/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/definitions/OASParserUtil.java @@ -1555,6 +1555,9 @@ public static String preProcess(String swaggerContent) throws APIManagementExcep swaggerContent = apiDefinition.injectMgwThrottlingExtensionsToDefault(swaggerContent); //Process mgw disable security extension swaggerContent = apiDefinition.processDisableSecurityExtension(swaggerContent); + + swaggerContent = apiDefinition.processDefaultSchemeScopesOfMultipleOauthFlows(swaggerContent); + return apiDefinition.processOtherSchemeScopes(swaggerContent); } diff --git a/components/apimgt/org.wso2.carbon.apimgt.impl/src/test/java/org/wso2/carbon/apimgt/impl/definitions/OASParserUtilTest.java b/components/apimgt/org.wso2.carbon.apimgt.impl/src/test/java/org/wso2/carbon/apimgt/impl/definitions/OASParserUtilTest.java index 70788726c4d2..bba034df84ca 100644 --- a/components/apimgt/org.wso2.carbon.apimgt.impl/src/test/java/org/wso2/carbon/apimgt/impl/definitions/OASParserUtilTest.java +++ b/components/apimgt/org.wso2.carbon.apimgt.impl/src/test/java/org/wso2/carbon/apimgt/impl/definitions/OASParserUtilTest.java @@ -527,7 +527,7 @@ public void testGetOASDefinitionWithTierContentAwareProperty() throws Exception " }\n" + " }\n" + "}"; - + // Content aware tiers TierX and Tier2. // Test 1: API level content-aware tier: TierX contentAwareTiersList = new ArrayList(); @@ -581,4 +581,32 @@ public void testGetOASDefinitionWithTierContentAwareProperty() throws Exception pathsObj.has(APIConstants.SWAGGER_X_THROTTLING_BANDWIDTH)); } + + @Test + public void testSwaggerWithScopesInDefaultSecuritySchemeWithPasswordFlow() + throws IOException, APIManagementException { + String swaggerPath = "definitions" + File.separator + "oas3" + File.separator + "default_password_oauth_flow_scopes.yaml"; + String swagger = IOUtils.toString(getClass().getClassLoader().getResourceAsStream(swaggerPath), "UTF-8"); + + String responsePath = "definitions" + File.separator + "oas3" + File.separator + "default_password_oauth_flow_scopes_response.json"; + String expectedSwaggerResponse = IOUtils.toString(getClass().getClassLoader().getResourceAsStream(responsePath), + "UTF-8"); + + String actualSwaggerResponse = OASParserUtil.preProcess(swagger); + Assert.assertEquals(expectedSwaggerResponse.trim(), actualSwaggerResponse); + } + + @Test + public void testSwaggerWithScopesInDefaultSecuritySchemeWithMultipleFlows() + throws IOException, APIManagementException { + String swaggerPath = "definitions" + File.separator + "oas3" + File.separator + "default_multiple_oauth_flows_scopes.yaml"; + String swagger = IOUtils.toString(getClass().getClassLoader().getResourceAsStream(swaggerPath), "UTF-8"); + + String responsePath = "definitions" + File.separator + "oas3" + File.separator + "default_multiple_oauth_flows_scopes_response.json"; + String expectedSwaggerResponse = IOUtils.toString(getClass().getClassLoader().getResourceAsStream(responsePath), + "UTF-8"); + + String actualSwaggerResponse = OASParserUtil.preProcess(swagger); + Assert.assertEquals(expectedSwaggerResponse.trim(), actualSwaggerResponse); + } } diff --git a/components/apimgt/org.wso2.carbon.apimgt.impl/src/test/resources/definitions/oas3/default_multiple_oauth_flows_scopes.yaml b/components/apimgt/org.wso2.carbon.apimgt.impl/src/test/resources/definitions/oas3/default_multiple_oauth_flows_scopes.yaml new file mode 100644 index 000000000000..61e0f516fe53 --- /dev/null +++ b/components/apimgt/org.wso2.carbon.apimgt.impl/src/test/resources/definitions/oas3/default_multiple_oauth_flows_scopes.yaml @@ -0,0 +1,55 @@ +openapi: 3.0.1 +info: + title: TEST_API + version: '1.0.0' +servers: + - url: 'https://localhost:8080/test_backend' +security: + - default: [] +paths: + /testpath: + get: + parameters: [] + responses: + '200': + description: ok + security: + - OAuth2Security: + - TEST_SCOPE1 + x-auth-type: Application & Application User + x-throttling-tier: 10KPerMin + x-wso2-application-security: + security-types: + - oauth2 + optional: false +components: + securitySchemes: + default: + type: oauth2 + flows: + implicit: + authorizationUrl: 'https://test.com' + scopes: + TEST_SCOPE1: 'HI TEST_SCOPE1' + TEST_SCOPE3: '' + x-scopes-bindings: + TEST_SCOPE1: admin + TEST_SCOPE3: 'internal/publisher' + password: + tokenUrl: 'https://localhost:9443/oauth2/token' + scopes: + TEST_SCOPE1: 'TEST_SCOPE1' + TEST_SCOPE2: 'Hi TEST_SCOPE2' + x-scopes-bindings: + TEST_SCOPE1: 'admin' + clientCredentials: + tokenUrl: https://api.gettyimages.com/oauth2/token/ + scopes: {} + authorizationCode: + authorizationUrl: https://slack.com/oauth/authorize + tokenUrl: https://slack.com/api/oauth.access + scopes: + TEST_SCOPE4: 'Hi TEST_SCOPE4' + x-scopes-bindings: + TEST_SCOPE4: 'admin' +x-wso2-basePath: /test_context/0.1 diff --git a/components/apimgt/org.wso2.carbon.apimgt.impl/src/test/resources/definitions/oas3/default_multiple_oauth_flows_scopes_response.json b/components/apimgt/org.wso2.carbon.apimgt.impl/src/test/resources/definitions/oas3/default_multiple_oauth_flows_scopes_response.json new file mode 100644 index 000000000000..ed8ffb72b1ff --- /dev/null +++ b/components/apimgt/org.wso2.carbon.apimgt.impl/src/test/resources/definitions/oas3/default_multiple_oauth_flows_scopes_response.json @@ -0,0 +1,82 @@ +{ + "openapi" : "3.0.1", + "info" : { + "title" : "TEST_API", + "version" : "1.0.0" + }, + "servers" : [ { + "url" : "https://localhost:8080/test_backend" + } ], + "security" : [ { + "default" : [ ] + } ], + "paths" : { + "/testpath" : { + "get" : { + "parameters" : [ ], + "responses" : { + "200" : { + "description" : "ok" + } + }, + "security" : [ { + "OAuth2Security" : [ "TEST_SCOPE1" ] + } ], + "x-auth-type" : "Application & Application User", + "x-throttling-tier" : "10KPerMin", + "x-wso2-application-security" : { + "security-types" : [ "oauth2" ], + "optional" : false + } + } + } + }, + "components" : { + "securitySchemes" : { + "default" : { + "type" : "oauth2", + "flows" : { + "implicit" : { + "authorizationUrl" : "https://test.com", + "scopes" : { + "TEST_SCOPE1" : "HI TEST_SCOPE1", + "TEST_SCOPE3" : "", + "TEST_SCOPE2" : "Hi TEST_SCOPE2", + "TEST_SCOPE4" : "Hi TEST_SCOPE4" + }, + "x-scopes-bindings" : { + "TEST_SCOPE1" : "admin", + "TEST_SCOPE3" : "internal/publisher", + "TEST_SCOPE4" : "admin" + } + }, + "password" : { + "tokenUrl" : "https://localhost:9443/oauth2/token", + "scopes" : { + "TEST_SCOPE1" : "TEST_SCOPE1", + "TEST_SCOPE2" : "Hi TEST_SCOPE2" + }, + "x-scopes-bindings" : { + "TEST_SCOPE1" : "admin" + } + }, + "clientCredentials" : { + "tokenUrl" : "https://api.gettyimages.com/oauth2/token/", + "scopes" : { } + }, + "authorizationCode" : { + "authorizationUrl" : "https://slack.com/oauth/authorize", + "tokenUrl" : "https://slack.com/api/oauth.access", + "scopes" : { + "TEST_SCOPE4" : "Hi TEST_SCOPE4" + }, + "x-scopes-bindings" : { + "TEST_SCOPE4" : "admin" + } + } + } + } + } + }, + "x-wso2-basePath" : "/test_context/0.1" +} diff --git a/components/apimgt/org.wso2.carbon.apimgt.impl/src/test/resources/definitions/oas3/default_password_oauth_flow_scopes.yaml b/components/apimgt/org.wso2.carbon.apimgt.impl/src/test/resources/definitions/oas3/default_password_oauth_flow_scopes.yaml new file mode 100644 index 000000000000..dc2c9cb5ddb0 --- /dev/null +++ b/components/apimgt/org.wso2.carbon.apimgt.impl/src/test/resources/definitions/oas3/default_password_oauth_flow_scopes.yaml @@ -0,0 +1,36 @@ +openapi: 3.0.1 +info: + title: TEST_API + version: '1.0.0' +servers: + - url: 'https://localhost:8080/test_backend' +security: + - default: [] +paths: + /testpath: + get: + parameters: [] + responses: + '200': + description: ok + security: + - OAuth2Security: + - TEST_SCOPE + x-auth-type: Application & Application User + x-throttling-tier: 10KPerMin + x-wso2-application-security: + security-types: + - oauth2 + optional: false +components: + securitySchemes: + default: + type: oauth2 + flows: + password: + tokenUrl: 'https://localhost:9443/oauth2/token' + scopes: + TEST_SCOPE: 'TEST_SCOPE' + x-scopes-bindings: + TEST_SCOPE: 'admin' +x-wso2-basePath: /test_context/0.1 diff --git a/components/apimgt/org.wso2.carbon.apimgt.impl/src/test/resources/definitions/oas3/default_password_oauth_flow_scopes_response.json b/components/apimgt/org.wso2.carbon.apimgt.impl/src/test/resources/definitions/oas3/default_password_oauth_flow_scopes_response.json new file mode 100644 index 000000000000..4839abf76bdc --- /dev/null +++ b/components/apimgt/org.wso2.carbon.apimgt.impl/src/test/resources/definitions/oas3/default_password_oauth_flow_scopes_response.json @@ -0,0 +1,62 @@ +{ + "openapi" : "3.0.1", + "info" : { + "title" : "TEST_API", + "version" : "1.0.0" + }, + "servers" : [ { + "url" : "https://localhost:8080/test_backend" + } ], + "security" : [ { + "default" : [ ] + } ], + "paths" : { + "/testpath" : { + "get" : { + "parameters" : [ ], + "responses" : { + "200" : { + "description" : "ok" + } + }, + "security" : [ { + "OAuth2Security" : [ "TEST_SCOPE" ] + } ], + "x-auth-type" : "Application & Application User", + "x-throttling-tier" : "10KPerMin", + "x-wso2-application-security" : { + "security-types" : [ "oauth2" ], + "optional" : false + } + } + } + }, + "components" : { + "securitySchemes" : { + "default" : { + "type" : "oauth2", + "flows" : { + "implicit" : { + "authorizationUrl" : "https://test.com", + "scopes" : { + "TEST_SCOPE" : "TEST_SCOPE" + }, + "x-scopes-bindings" : { + "TEST_SCOPE" : "admin" + } + }, + "password" : { + "tokenUrl" : "https://localhost:9443/oauth2/token", + "scopes" : { + "TEST_SCOPE" : "TEST_SCOPE" + }, + "x-scopes-bindings" : { + "TEST_SCOPE" : "admin" + } + } + } + } + } + }, + "x-wso2-basePath" : "/test_context/0.1" +}