From 1787276d84d99d2af6ba6b6161fb4596dd7e794a Mon Sep 17 00:00:00 2001 From: Ionel Sirbu Date: Thu, 6 Jun 2024 17:43:15 +0100 Subject: [PATCH 1/4] #9843: Properly apply the OAuth scopes for an individual endpoint as a subset of the scopes defined in the `securitySchemes`. --- .../swagger/codegen/v3/DefaultGenerator.java | 50 ++++- .../v3/service/GeneratorServiceTest.java | 42 ++++ .../src/test/resources/3_0_0/issue-9843.yaml | 206 ++++++++++++++++++ 3 files changed, 292 insertions(+), 6 deletions(-) create mode 100644 modules/swagger-codegen/src/test/resources/3_0_0/issue-9843.yaml diff --git a/modules/swagger-codegen/src/main/java/io/swagger/codegen/v3/DefaultGenerator.java b/modules/swagger-codegen/src/main/java/io/swagger/codegen/v3/DefaultGenerator.java index 282a28c79b1..51247f75d0c 100644 --- a/modules/swagger-codegen/src/main/java/io/swagger/codegen/v3/DefaultGenerator.java +++ b/modules/swagger-codegen/src/main/java/io/swagger/codegen/v3/DefaultGenerator.java @@ -17,8 +17,7 @@ import io.swagger.v3.oas.models.info.License; import io.swagger.v3.oas.models.media.Schema; import io.swagger.v3.oas.models.parameters.Parameter; -import io.swagger.v3.oas.models.security.SecurityRequirement; -import io.swagger.v3.oas.models.security.SecurityScheme; +import io.swagger.v3.oas.models.security.*; import io.swagger.v3.oas.models.tags.Tag; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.ObjectUtils; @@ -1157,16 +1156,55 @@ private Map getAuthMethods(List sec } final Map authMethods = new HashMap<>(); for (SecurityRequirement requirement : securities) { - for (String key : requirement.keySet()) { - SecurityScheme securityScheme = securitySchemes.get(key); + requirement.forEach((securitySchemeName, endpointScopes) -> { + SecurityScheme securityScheme = securitySchemes.get(securitySchemeName); if (securityScheme != null) { - authMethods.put(key, securityScheme); + SecurityScheme endpointSecurityScheme = selectScopeSubsetForScheme(securityScheme, endpointScopes); + authMethods.put(securitySchemeName, endpointSecurityScheme); } - } + }); } return authMethods; } + private static SecurityScheme selectScopeSubsetForScheme(SecurityScheme originalScheme, List scopesToSelect) { + OAuthFlows originalFlows = originalScheme.getFlows(); + if (originalFlows == null) { + return originalScheme; + } + OAuthFlows flowsWithScopeSubset = new OAuthFlows() + .authorizationCode(selectScopeSubsetForFlow(originalFlows.getAuthorizationCode(), scopesToSelect)) + .clientCredentials(selectScopeSubsetForFlow(originalFlows.getClientCredentials(), scopesToSelect)) + .implicit(selectScopeSubsetForFlow(originalFlows.getImplicit(), scopesToSelect)) + .password(selectScopeSubsetForFlow(originalFlows.getPassword(), scopesToSelect)) + .extensions(originalFlows.getExtensions()); + return new SecurityScheme() + .type(originalScheme.getType()) + .$ref(originalScheme.get$ref()) + .name(originalScheme.getName()) + .description(originalScheme.getDescription()) + .scheme(originalScheme.getScheme()) + .in(originalScheme.getIn()) + .flows(flowsWithScopeSubset) + .bearerFormat(originalScheme.getBearerFormat()) + .openIdConnectUrl(originalScheme.getOpenIdConnectUrl()) + .extensions(originalScheme.getExtensions()); + } + + private static OAuthFlow selectScopeSubsetForFlow(OAuthFlow originalFlow, List scopesToSelect) { + if (originalFlow != null) { + Scopes scopeSubset = new Scopes(); + originalFlow.getScopes().entrySet().stream().filter(e -> scopesToSelect.contains(e.getKey())).forEach(e -> scopeSubset.put(e.getKey(), e.getValue())); + return new OAuthFlow() + .authorizationUrl(originalFlow.getAuthorizationUrl()) + .tokenUrl(originalFlow.getTokenUrl()) + .refreshUrl(originalFlow.getRefreshUrl()) + .scopes(scopeSubset) + .extensions(originalFlow.getExtensions()); + } + return null; + } + private Boolean getCustomOptionBooleanValue(String option) { List languageArguments = config.getLanguageArguments(); if (languageArguments == null || languageArguments.isEmpty()) { diff --git a/modules/swagger-codegen/src/test/java/io/swagger/codegen/v3/service/GeneratorServiceTest.java b/modules/swagger-codegen/src/test/java/io/swagger/codegen/v3/service/GeneratorServiceTest.java index 33d611e3444..6e1f48223a9 100644 --- a/modules/swagger-codegen/src/test/java/io/swagger/codegen/v3/service/GeneratorServiceTest.java +++ b/modules/swagger-codegen/src/test/java/io/swagger/codegen/v3/service/GeneratorServiceTest.java @@ -14,6 +14,7 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.nio.charset.Charset; import java.nio.file.Files; import java.util.List; @@ -1022,6 +1023,47 @@ public void testIssue613_605_612_non_resteasy() throws IOException { Assert.assertFalse(files.isEmpty()); System.out.println("Generated server in:\n" + path); } + + @Test + public void testSecurityScopesAreProperlyApplied() throws IOException { + + String path = getTmpFolder().getAbsolutePath(); + GenerationRequest request = new GenerationRequest(); + request + .codegenVersion(GenerationRequest.CodegenVersion.V3) + .type(GenerationRequest.Type.SERVER) + .lang("spring") + .spec(loadSpecAsNode("3_0_0/issue-9843.yaml", true, false)) + .options( + new Options() + .outputDir(path) + ); + + List files = new GeneratorService().generationRequest(request).generate(); + boolean petFound = false, userFound = false; + Assert.assertFalse(files.isEmpty()); + for (File f: files) { + String relPath = f.getAbsolutePath().substring(path.length()).replace("\\", "/"); + if ("/src/main/java/io/swagger/api/PetApi_.java".equals(relPath)) { + petFound = true; + String actualContents = FileUtils.readFileToString(f, Charset.defaultCharset()); + Assert.assertTrue(actualContents.contains("@SecurityRequirement(name = \"petstore_auth\", scopes = {")); + Assert.assertTrue(actualContents.contains("\"read:pets\"")); + Assert.assertFalse(actualContents.contains("\"write:pets\"")); + Assert.assertFalse(actualContents.contains(":users\"")); + } else if ("/src/main/java/io/swagger/api/UserApi.java".equals(relPath)) { + userFound = true; + String actualContents = FileUtils.readFileToString(f, Charset.defaultCharset()); + Assert.assertTrue(actualContents.contains("@SecurityRequirement(name = \"petstore_auth\", scopes = {")); + Assert.assertTrue(actualContents.contains("\"write:users\"")); + Assert.assertFalse(actualContents.contains(":pets\"")); + } + } + final String fileExpectedFormat = "%s expected to be generated"; + Assert.assertTrue(petFound, String.format(fileExpectedFormat, "PetApi.java")); + Assert.assertTrue(userFound, String.format(fileExpectedFormat, "UserApi.java")); + } + protected static File getTmpFolder() { try { File outputFolder = Files.createTempFile("codegentest-", "-tmp").toFile(); diff --git a/modules/swagger-codegen/src/test/resources/3_0_0/issue-9843.yaml b/modules/swagger-codegen/src/test/resources/3_0_0/issue-9843.yaml new file mode 100644 index 00000000000..d44a097a57f --- /dev/null +++ b/modules/swagger-codegen/src/test/resources/3_0_0/issue-9843.yaml @@ -0,0 +1,206 @@ +openapi: 3.0.1 +servers: [] +info: + description: | + This is a sample Petstore server. You can find + out more about Swagger at + [http://swagger.io](http://swagger.io) or on + [irc.freenode.net, #swagger](http://swagger.io/irc/). + version: "1.0.0" + title: Swagger Petstore + termsOfService: 'http://swagger.io/terms/' + contact: + email: apiteam@swagger.io + license: + name: Apache 2.0 + url: 'http://www.apache.org/licenses/LICENSE-2.0.html' +tags: + - name: pet + description: Everything about your Pets + externalDocs: + description: Find out more + url: 'http://swagger.io' + - name: user + description: Operations about user + externalDocs: + description: Find out more about our store + url: 'http://swagger.io' +paths: + /pet/findByStatus: + get: + tags: + - pet + summary: Finds Pets by status + description: Multiple status values can be provided with comma separated strings + operationId: findPetsByStatus + parameters: + - name: status + in: query + description: Status values that need to be considered for filter + required: true + explode: true + schema: + type: array + items: + type: string + enum: + - available + - pending + - sold + default: available + responses: + '200': + description: successful operation + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid status value + security: + - petstore_auth: + - 'read:pets' + /user: + post: + tags: + - user + summary: Create user + description: This can only be done by the logged in user. + operationId: createUser + responses: + default: + description: successful operation + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/User' + description: Created user object + required: true + security: + - petstore_auth: + - 'write:users' +externalDocs: + description: Find out more about Swagger + url: 'http://swagger.io' +components: + schemas: + Category: + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + xml: + name: Category + User: + type: object + properties: + id: + type: integer + format: int64 + username: + type: string + firstName: + type: string + lastName: + type: string + email: + type: string + password: + type: string + phone: + type: string + userStatus: + type: integer + format: int32 + description: User Status + xml: + name: User + Tag: + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + xml: + name: Tag + Pet: + type: object + required: + - name + - photoUrls + properties: + id: + type: integer + format: int64 + category: + $ref: '#/components/schemas/Category' + name: + type: string + example: doggie + photoUrls: + type: array + xml: + name: photoUrl + wrapped: true + items: + type: string + tags: + type: array + xml: + name: tag + wrapped: true + items: + $ref: '#/components/schemas/Tag' + status: + type: string + description: pet status in the store + enum: + - available + - pending + - sold + xml: + name: Pet + + requestBodies: + Pet: + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + description: Pet object that needs to be added to the store + required: true + UserArray: + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/User' + description: List of user object + required: true + securitySchemes: + petstore_auth: + type: oauth2 + flows: + implicit: + authorizationUrl: 'http://petstore.swagger.io/oauth/dialog' + scopes: + 'write:pets': modify pets in your account + 'read:pets': read your pets + 'write:users': modify users From 5d5900632b8462b2c29ccbb0432322a48b09c06f Mon Sep 17 00:00:00 2001 From: Ionel Sirbu Date: Mon, 16 Feb 2026 18:31:41 +0000 Subject: [PATCH 2/4] #9843: Minor fix for unit test - use the correct name for one of the files expected to be generated. --- .../codegen/v3/service/GeneratorServiceTest.java | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/modules/swagger-codegen/src/test/java/io/swagger/codegen/v3/service/GeneratorServiceTest.java b/modules/swagger-codegen/src/test/java/io/swagger/codegen/v3/service/GeneratorServiceTest.java index 6e1f48223a9..c0de766c710 100644 --- a/modules/swagger-codegen/src/test/java/io/swagger/codegen/v3/service/GeneratorServiceTest.java +++ b/modules/swagger-codegen/src/test/java/io/swagger/codegen/v3/service/GeneratorServiceTest.java @@ -1028,23 +1028,19 @@ public void testIssue613_605_612_non_resteasy() throws IOException { public void testSecurityScopesAreProperlyApplied() throws IOException { String path = getTmpFolder().getAbsolutePath(); - GenerationRequest request = new GenerationRequest(); - request + GenerationRequest request = new GenerationRequest() .codegenVersion(GenerationRequest.CodegenVersion.V3) .type(GenerationRequest.Type.SERVER) .lang("spring") .spec(loadSpecAsNode("3_0_0/issue-9843.yaml", true, false)) - .options( - new Options() - .outputDir(path) - ); + .options(new Options().outputDir(path)); List files = new GeneratorService().generationRequest(request).generate(); boolean petFound = false, userFound = false; Assert.assertFalse(files.isEmpty()); for (File f: files) { String relPath = f.getAbsolutePath().substring(path.length()).replace("\\", "/"); - if ("/src/main/java/io/swagger/api/PetApi_.java".equals(relPath)) { + if ("/src/main/java/io/swagger/api/PetApi.java".equals(relPath)) { petFound = true; String actualContents = FileUtils.readFileToString(f, Charset.defaultCharset()); Assert.assertTrue(actualContents.contains("@SecurityRequirement(name = \"petstore_auth\", scopes = {")); From 79dcc304ef3f2e393d5ecd85975e3139e1c4658e Mon Sep 17 00:00:00 2001 From: Ionel Sirbu Date: Wed, 18 Feb 2026 17:50:15 +0000 Subject: [PATCH 3/4] #9843: Minor refinement - rename methods, add comment, log a warning when data doesn't match. --- .../swagger/codegen/v3/DefaultGenerator.java | 67 +++++++++++-------- 1 file changed, 38 insertions(+), 29 deletions(-) diff --git a/modules/swagger-codegen/src/main/java/io/swagger/codegen/v3/DefaultGenerator.java b/modules/swagger-codegen/src/main/java/io/swagger/codegen/v3/DefaultGenerator.java index b9ce9c8e9ea..2ac2c4c8611 100644 --- a/modules/swagger-codegen/src/main/java/io/swagger/codegen/v3/DefaultGenerator.java +++ b/modules/swagger-codegen/src/main/java/io/swagger/codegen/v3/DefaultGenerator.java @@ -1219,7 +1219,7 @@ private Map getAuthMethods(List sec requirement.forEach((securitySchemeName, endpointScopes) -> { SecurityScheme securityScheme = securitySchemes.get(securitySchemeName); if (securityScheme != null) { - SecurityScheme endpointSecurityScheme = selectScopeSubsetForScheme(securityScheme, endpointScopes); + SecurityScheme endpointSecurityScheme = selectScopeSubsetForSecurityScheme(securityScheme, endpointScopes); authMethods.put(securitySchemeName, endpointSecurityScheme); } }); @@ -1227,40 +1227,49 @@ private Map getAuthMethods(List sec return authMethods; } - private static SecurityScheme selectScopeSubsetForScheme(SecurityScheme originalScheme, List scopesToSelect) { - OAuthFlows originalFlows = originalScheme.getFlows(); - if (originalFlows == null) { - return originalScheme; - } - OAuthFlows flowsWithScopeSubset = new OAuthFlows() - .authorizationCode(selectScopeSubsetForFlow(originalFlows.getAuthorizationCode(), scopesToSelect)) - .clientCredentials(selectScopeSubsetForFlow(originalFlows.getClientCredentials(), scopesToSelect)) - .implicit(selectScopeSubsetForFlow(originalFlows.getImplicit(), scopesToSelect)) - .password(selectScopeSubsetForFlow(originalFlows.getPassword(), scopesToSelect)) - .extensions(originalFlows.getExtensions()); + private SecurityScheme selectScopeSubsetForSecurityScheme(SecurityScheme globalSecurityScheme, List scopesToSelect) { + OAuthFlows globalOAuthFlows = globalSecurityScheme.getFlows(); + // the OAuth flows should only be set when security scheme's getType() is either OAUTH2 or OPENIDCONNECT + if (globalOAuthFlows == null) { + return globalSecurityScheme; + } + OAuthFlows oauthFlowsWithScopeSubset = new OAuthFlows() + .authorizationCode(selectScopeSubsetForOAuthFlow(globalOAuthFlows.getAuthorizationCode(), scopesToSelect)) + .clientCredentials(selectScopeSubsetForOAuthFlow(globalOAuthFlows.getClientCredentials(), scopesToSelect)) + .implicit(selectScopeSubsetForOAuthFlow(globalOAuthFlows.getImplicit(), scopesToSelect)) + .password(selectScopeSubsetForOAuthFlow(globalOAuthFlows.getPassword(), scopesToSelect)) + .extensions(globalOAuthFlows.getExtensions()); return new SecurityScheme() - .type(originalScheme.getType()) - .$ref(originalScheme.get$ref()) - .name(originalScheme.getName()) - .description(originalScheme.getDescription()) - .scheme(originalScheme.getScheme()) - .in(originalScheme.getIn()) - .flows(flowsWithScopeSubset) - .bearerFormat(originalScheme.getBearerFormat()) - .openIdConnectUrl(originalScheme.getOpenIdConnectUrl()) - .extensions(originalScheme.getExtensions()); + .type(globalSecurityScheme.getType()) + .$ref(globalSecurityScheme.get$ref()) + .name(globalSecurityScheme.getName()) + .description(globalSecurityScheme.getDescription()) + .scheme(globalSecurityScheme.getScheme()) + .in(globalSecurityScheme.getIn()) + .flows(oauthFlowsWithScopeSubset) + .bearerFormat(globalSecurityScheme.getBearerFormat()) + .openIdConnectUrl(globalSecurityScheme.getOpenIdConnectUrl()) + .extensions(globalSecurityScheme.getExtensions()); } - private static OAuthFlow selectScopeSubsetForFlow(OAuthFlow originalFlow, List scopesToSelect) { - if (originalFlow != null) { + private OAuthFlow selectScopeSubsetForOAuthFlow(OAuthFlow globalOAuthFlow, List scopesToSelect) { + if (globalOAuthFlow != null) { + Scopes globalScopes = globalOAuthFlow.getScopes(); Scopes scopeSubset = new Scopes(); - originalFlow.getScopes().entrySet().stream().filter(e -> scopesToSelect.contains(e.getKey())).forEach(e -> scopeSubset.put(e.getKey(), e.getValue())); + for (String scope : scopesToSelect) { + String selectedScope = globalScopes.get(scope); + if (selectedScope != null) { + scopeSubset.addString(scope, selectedScope); + } else { + LOGGER.warn("Scope {} is not defined in the global security schemes!", scope); + } + } return new OAuthFlow() - .authorizationUrl(originalFlow.getAuthorizationUrl()) - .tokenUrl(originalFlow.getTokenUrl()) - .refreshUrl(originalFlow.getRefreshUrl()) + .authorizationUrl(globalOAuthFlow.getAuthorizationUrl()) + .tokenUrl(globalOAuthFlow.getTokenUrl()) + .refreshUrl(globalOAuthFlow.getRefreshUrl()) .scopes(scopeSubset) - .extensions(originalFlow.getExtensions()); + .extensions(globalOAuthFlow.getExtensions()); } return null; } From 179374aee1b9d737be997fecac510bd2fc711f39 Mon Sep 17 00:00:00 2001 From: Ionel Sirbu Date: Wed, 18 Feb 2026 17:52:29 +0000 Subject: [PATCH 4/4] #9843: Code cleanup - correct ``s in maven submodules so that they include the actual parent `pom.xml`. --- modules/swagger-codegen-cli/pom.xml | 2 +- modules/swagger-codegen-maven-plugin/pom.xml | 2 +- modules/swagger-codegen/pom.xml | 2 +- modules/swagger-generator/pom.xml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/swagger-codegen-cli/pom.xml b/modules/swagger-codegen-cli/pom.xml index 117f696e396..db1eff141fb 100644 --- a/modules/swagger-codegen-cli/pom.xml +++ b/modules/swagger-codegen-cli/pom.xml @@ -4,7 +4,7 @@ io.swagger.codegen.v3 swagger-codegen-project 3.0.78-SNAPSHOT - ../.. + ../../pom.xml 4.0.0 diff --git a/modules/swagger-codegen-maven-plugin/pom.xml b/modules/swagger-codegen-maven-plugin/pom.xml index 0b97e185298..f471fff0189 100644 --- a/modules/swagger-codegen-maven-plugin/pom.xml +++ b/modules/swagger-codegen-maven-plugin/pom.xml @@ -7,7 +7,7 @@ io.swagger.codegen.v3 swagger-codegen-project 3.0.78-SNAPSHOT - ../.. + ../../pom.xml swagger-codegen-maven-plugin swagger-codegen (maven-plugin) diff --git a/modules/swagger-codegen/pom.xml b/modules/swagger-codegen/pom.xml index 44cbd6a4f47..591600fa8a4 100644 --- a/modules/swagger-codegen/pom.xml +++ b/modules/swagger-codegen/pom.xml @@ -4,7 +4,7 @@ io.swagger.codegen.v3 swagger-codegen-project 3.0.78-SNAPSHOT - ../.. + ../../pom.xml 4.0.0 swagger-codegen diff --git a/modules/swagger-generator/pom.xml b/modules/swagger-generator/pom.xml index edf6282105a..c1d38c1401e 100644 --- a/modules/swagger-generator/pom.xml +++ b/modules/swagger-generator/pom.xml @@ -5,7 +5,7 @@ io.swagger.codegen.v3 swagger-codegen-project 3.0.78-SNAPSHOT - ../.. + ../../pom.xml swagger-generator war