Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#9843: Sort out OAuth scopes for individual endpoints #12410

Open
wants to merge 2 commits into
base: 3.0.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -1157,16 +1156,55 @@ private Map<String, SecurityScheme> getAuthMethods(List<SecurityRequirement> sec
}
final Map<String, SecurityScheme> 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<String> 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<String> 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<CodegenArgument> languageArguments = config.getLanguageArguments();
if (languageArguments == null || languageArguments.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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<File> 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();
Expand Down
206 changes: 206 additions & 0 deletions modules/swagger-codegen/src/test/resources/3_0_0/issue-9843.yaml
Original file line number Diff line number Diff line change
@@ -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: [email protected]
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