Skip to content

Commit

Permalink
handle @PreAuthorize generation on auth specification
Browse files Browse the repository at this point in the history
  • Loading branch information
nhomble committed May 10, 2020
1 parent a093a9d commit 4c58a2c
Show file tree
Hide file tree
Showing 86 changed files with 9,810 additions and 10 deletions.
5 changes: 4 additions & 1 deletion bin/spring-all-petstore.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,7 @@
./bin/springboot-petstore-server-beanvalidation.sh
./bin/springboot-petstore-server-implicitHeaders.sh
./bin/springboot-petstore-server-useOptional.sh
./bin/springboot-virtualan-petstore-server.sh
./bin/springboot-virtualan-petstore-server.sh

# spring security server
./bin/spring-security-petstore-server.sh
32 changes: 32 additions & 0 deletions bin/spring-security-petstore-server.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#!/bin/sh

SCRIPT="$0"
echo "# START SCRIPT: $SCRIPT"

while [ -h "$SCRIPT" ] ; do
ls=`ls -ld "$SCRIPT"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
SCRIPT="$link"
else
SCRIPT=`dirname "$SCRIPT"`/"$link"
fi
done

if [ ! -d "${APP_DIR}" ]; then
APP_DIR=`dirname "$SCRIPT"`/..
APP_DIR=`cd "${APP_DIR}"; pwd`
fi

executable="./modules/openapi-generator-cli/target/openapi-generator-cli.jar"

if [ ! -f "$executable" ]
then
mvn -B clean package
fi

# if you've executed sbt assembly previously it will use that instead.
export JAVA_OPTS="${JAVA_OPTS} -Xmx1024M -DloggerPath=conf/log4j.properties"
ags="generate -t modules/openapi-generator/src/main/resources/JavaSpring -i modules/openapi-generator/src/test/resources/2_0/petstore-with-fake-endpoints-models-for-testing.yaml -g spring -o samples/server/petstore/spring-security --additional-properties hideGenerationTimestamp=true,java8=false --additional-properties serverPort=8002 --additional-properties booleanGetterPrefix=get --additional-properties useSpringSecurity=true $@"

java $JAVA_OPTS -jar $executable $ags
1 change: 1 addition & 0 deletions docs/generators/spring.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ sidebar_label: spring
|unhandledException|Declare operation methods to throw a generic exception and allow unhandled exceptions (useful for Spring `@ControllerAdvice` directives).| |false|
|useBeanValidation|Use BeanValidation API annotations| |true|
|useOptional|Use Optional container for optional parameters| |false|
|useSpringSecurity|Use spring-security's @PreAuthorize annotation to invoke authorization rules| |false|
|useTags|use tags for creating interface and controller classnames| |false|
|virtualService|Generates the virtual service. For more details refer - https://github.com/elan-venture/virtualan/wiki| |false|
|withXml|whether to include support for application/xml content type and include XML annotations in the model (works with libraries that provide support for JSON and XML)| |false|
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1419,7 +1419,7 @@ private List<CodegenSecurity> filterAuthMethods(List<CodegenSecurity> authMethod

for (CodegenSecurity security : authMethods) {
boolean filtered = false;
if (security != null && security.scopes != null) {
if (security != null && security.scopes != null && SecurityScheme.Type.OAUTH2.toString().equals(security.type)) {
for (SecurityRequirement requirement : securities) {
List<String> opScopes = requirement.get(security.name);
if (opScopes != null) {
Expand All @@ -1435,6 +1435,28 @@ private List<CodegenSecurity> filterAuthMethods(List<CodegenSecurity> authMethod
}
}
}
// This is separate than the oauth2 case since bearerAuths aren't declared in the scheme definition. They
// only exist at the api definition level
if (security != null && SecurityScheme.Type.HTTP.toString().equals(security.type)) {
for (SecurityRequirement requirement : securities) {
List<String> opScopes = requirement.get(security.name);
if (opScopes != null) {
CodegenSecurity opSecurity = security.filterByScopeNames(Collections.emptyList());
Iterator<String> it = opScopes.iterator();
opSecurity.scopes = new ArrayList<>();
while(it.hasNext()){
Map<String, Object> scope = new HashMap<>();
scope.put("scope", it.next());
scope.put("hasMore", it.hasNext()? "true": null);
opSecurity.scopes.add(scope);
}
opSecurity.hasMore = security.hasMore;
result.add(opSecurity);
filtered = true;
break;
}
}
}

// If we didn't get a filtered version, then we can keep the original auth method.
if (!filtered) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ public class SpringCodegen extends AbstractJavaCodegen
public static final String HATEOAS = "hateoas";
public static final String RETURN_SUCCESS_CODE = "returnSuccessCode";
public static final String UNHANDLED_EXCEPTION_HANDLING = "unhandledException";
public static final String USE_SPRING_SECURITY = "useSpringSecurity";

public static final String OPEN_BRACE = "{";
public static final String CLOSE_BRACE = "}";
Expand Down Expand Up @@ -98,6 +99,7 @@ public class SpringCodegen extends AbstractJavaCodegen
protected boolean hateoas = false;
protected boolean returnSuccessCode = false;
protected boolean unhandledException = false;
protected boolean useSpringSecurity = false;

public SpringCodegen() {
super();
Expand Down Expand Up @@ -171,6 +173,7 @@ public SpringCodegen() {
cliOptions.add(CliOption.newBoolean(HATEOAS, "Use Spring HATEOAS library to allow adding HATEOAS links", hateoas));
cliOptions.add(CliOption.newBoolean(RETURN_SUCCESS_CODE, "Generated server returns 2xx code", returnSuccessCode));
cliOptions.add(CliOption.newBoolean(UNHANDLED_EXCEPTION_HANDLING, "Declare operation methods to throw a generic exception and allow unhandled exceptions (useful for Spring `@ControllerAdvice` directives).", unhandledException));
cliOptions.add(CliOption.newBoolean(USE_SPRING_SECURITY, "Use spring-security's @PreAuthorize annotation to invoke authorization rules"));

supportedLibraries.put(SPRING_BOOT, "Spring-boot Server application using the SpringFox integration.");
supportedLibraries.put(SPRING_MVC_LIBRARY, "Spring-MVC Server application using the SpringFox integration.");
Expand Down Expand Up @@ -336,6 +339,11 @@ public void processOpts() {
}
additionalProperties.put(UNHANDLED_EXCEPTION_HANDLING, this.isUnhandledException());

if (additionalProperties.containsKey(USE_SPRING_SECURITY)) {
this.setUseSpringSecurity(Boolean.valueOf(additionalProperties.get(USE_SPRING_SECURITY).toString()));
}
writePropertyBack(USE_SPRING_SECURITY, this.useSpringSecurity);

typeMapping.put("file", "Resource");
importMapping.put("Resource", "org.springframework.core.io.Resource");

Expand Down Expand Up @@ -820,6 +828,10 @@ public void setUnhandledException(boolean unhandledException) {
this.unhandledException = unhandledException;
}

public void setUseSpringSecurity(boolean useSpringSecurity) {
this.useSpringSecurity = useSpringSecurity;
}

@Override
public void postProcessModelProperty(CodegenModel model, CodegenProperty property) {
super.postProcessModelProperty(model, property);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
{{/jdk8-no-delegate}}
import org.springframework.http.ResponseEntity;
{{#useSpringSecurity}}
import org.springframework.security.access.prepost.PreAuthorize;
{{/useSpringSecurity}}
{{#useBeanValidation}}
import org.springframework.validation.annotation.Validated;
{{/useBeanValidation}}
Expand Down Expand Up @@ -123,6 +126,11 @@ public interface {{classname}} {
{{/headerParams}}
})
{{/implicitHeaders}}
{{#hasAuthMethods}}
{{#useSpringSecurity}}
@PreAuthorize("{{#authMethods}}{{#isOAuth}}({{#scopes}}hasAuthority('{{scope}}'){{#hasMore}} and {{/hasMore}}{{/scopes}}{{/isOAuth}}{{#isBasicBearer}}{{#scopes}}hasAuthority('{{scope}}'){{#hasMore}} and {{/hasMore}}{{/scopes}}{{^scopes}}){{/scopes}}{{/isBasicBearer}}{{#hasMore}} or {{/hasMore}}{{/authMethods}}")
{{/useSpringSecurity}}
{{/hasAuthMethods}}
@RequestMapping(value = "{{{path}}}",{{#singleContentTypes}}{{#hasProduces}}
produces = "{{{vendorExtensions.x-accepts}}}", {{/hasProduces}}{{#hasConsumes}}
consumes = "{{{vendorExtensions.x-contentType}}}",{{/hasConsumes}}{{/singleContentTypes}}{{^singleContentTypes}}{{#hasProduces}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
{{#useSpringfox}}
<springfox-version>2.8.0</springfox-version>
{{/useSpringfox}}
{{#useSpringSecurity}}
<spring-security-version>5.3.0.RELEASE</spring-security-version>
{{/useSpringSecurity}}
</properties>
{{#parentOverridden}}
<parent>
Expand Down Expand Up @@ -122,6 +125,13 @@
<artifactId>jackson-dataformat-yaml</artifactId>
</dependency>
{{/useSpringfox}}
{{#useSpringSecurity}}
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>${spring-security-version}</version>
</dependency>
{{/useSpringSecurity}}
{{#withXml}}
<!-- XML processing: Jackson -->
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -571,4 +571,163 @@ public void doAnnotateDatesOnModelParameters() throws IOException {
"@org.springframework.format.annotation.DateTimeFormat(iso = org.springframework.format.annotation.DateTimeFormat.ISO.DATE)",
"@org.springframework.format.annotation.DateTimeFormat(iso = org.springframework.format.annotation.DateTimeFormat.ISO.DATE_TIME)");
}

@Test
public void testPreAuthorizeOnOauth() throws IOException {
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
output.deleteOnExit();
String outputPath = output.getAbsolutePath().replace('\\', '/');

OpenAPI openAPI = new OpenAPIParser()
.readLocation("src/test/resources/3_0/security-oauth.yaml", null, new ParseOptions()).getOpenAPI();

SpringCodegen codegen = new SpringCodegen();
codegen.setOutputDir(output.getAbsolutePath());
codegen.additionalProperties().put(CXFServerFeatures.LOAD_TEST_DATA_FROM_FILE, "true");
codegen.additionalProperties().put(SpringCodegen.USE_SPRING_SECURITY, "true");

ClientOptInput input = new ClientOptInput();
input.setOpenAPI(openAPI);
input.setConfig(codegen);

MockDefaultGenerator generator = new MockDefaultGenerator();
generator.opts(input).generate();

String path = outputPath + "/src/main/java/org/openapitools/api/PetsApi.java";
checkFileContains(generator, path,
"@PreAuthorize(\"(hasAuthority('write_pets') and hasAuthority('rw_pets') or (hasAuthority('read_pets')\")");
}

@Test
public void testPreAuthorizeOnGlobalOauth() throws IOException {
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
output.deleteOnExit();
String outputPath = output.getAbsolutePath().replace('\\', '/');

OpenAPI openAPI = new OpenAPIParser()
.readLocation("src/test/resources/3_0/security-oauth.yaml", null, new ParseOptions()).getOpenAPI();

SpringCodegen codegen = new SpringCodegen();
codegen.setOutputDir(output.getAbsolutePath());
codegen.additionalProperties().put(CXFServerFeatures.LOAD_TEST_DATA_FROM_FILE, "true");
codegen.additionalProperties().put(SpringCodegen.USE_SPRING_SECURITY, "true");

ClientOptInput input = new ClientOptInput();
input.setOpenAPI(openAPI);
input.setConfig(codegen);

MockDefaultGenerator generator = new MockDefaultGenerator();
generator.opts(input).generate();

String path = outputPath + "/src/main/java/org/openapitools/api/PetsApi.java";
checkFileContains(generator, path,
"@PreAuthorize(\"(hasAuthority('read_pets')\")");
}

@Test
public void testDisableSpringSecurityForOauth() throws IOException {
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
output.deleteOnExit();
String outputPath = output.getAbsolutePath().replace('\\', '/');

OpenAPI openAPI = new OpenAPIParser()
.readLocation("src/test/resources/3_0/security-oauth.yaml", null, new ParseOptions()).getOpenAPI();

SpringCodegen codegen = new SpringCodegen();
codegen.setOutputDir(output.getAbsolutePath());
codegen.additionalProperties().put(CXFServerFeatures.LOAD_TEST_DATA_FROM_FILE, "true");
codegen.additionalProperties().put(SpringCodegen.USE_SPRING_SECURITY, "false");

ClientOptInput input = new ClientOptInput();
input.setOpenAPI(openAPI);
input.setConfig(codegen);

MockDefaultGenerator generator = new MockDefaultGenerator();
generator.opts(input).generate();

String path = outputPath + "/src/main/java/org/openapitools/api/PetsApi.java";
checkFileNotContains(generator, path,
"hasAuthority('write_pets'");
checkFileNotContains(generator, path, "hasAuthority('read_pets");
checkFileNotContains(generator, path, "@PreAuthorize(\"");
checkFileNotContains(generator, path, "import org.springframework.security.access.prepost.PreAuthorize;\n");
}

@Test
public void testPreAuthorizeOnJwtAuth() throws IOException {
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
output.deleteOnExit();
String outputPath = output.getAbsolutePath().replace('\\', '/');

OpenAPI openAPI = new OpenAPIParser()
.readLocation("src/test/resources/3_0/security-oauth-bearer.yaml", null, new ParseOptions()).getOpenAPI();

SpringCodegen codegen = new SpringCodegen();
codegen.setOutputDir(output.getAbsolutePath());
codegen.additionalProperties().put(CXFServerFeatures.LOAD_TEST_DATA_FROM_FILE, "true");
codegen.additionalProperties().put(SpringCodegen.USE_SPRING_SECURITY, "true");

ClientOptInput input = new ClientOptInput();
input.setOpenAPI(openAPI);
input.setConfig(codegen);

MockDefaultGenerator generator = new MockDefaultGenerator();
generator.opts(input).generate();

String path = outputPath + "/src/main/java/org/openapitools/api/PetsApi.java";
checkFileContains(generator, path,
"@PreAuthorize(\"hasAuthority('scope:another') or hasAuthority('scope:specific')\")");
}

@Test
public void testDisableSpringSecurityForJwtAuth() throws IOException {
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
output.deleteOnExit();
String outputPath = output.getAbsolutePath().replace('\\', '/');

OpenAPI openAPI = new OpenAPIParser()
.readLocation("src/test/resources/3_0/security-oauth-bearer.yaml", null, new ParseOptions()).getOpenAPI();

SpringCodegen codegen = new SpringCodegen();
codegen.setOutputDir(output.getAbsolutePath());
codegen.additionalProperties().put(CXFServerFeatures.LOAD_TEST_DATA_FROM_FILE, "true");
codegen.additionalProperties().put(SpringCodegen.USE_SPRING_SECURITY, "false");

ClientOptInput input = new ClientOptInput();
input.setOpenAPI(openAPI);
input.setConfig(codegen);

MockDefaultGenerator generator = new MockDefaultGenerator();
generator.opts(input).generate();

String path = outputPath + "/src/main/java/org/openapitools/api/PetsApi.java";
checkFileNotContains(generator, path,
"@PreAuthorize(\"hasAuthority('scope:another') or hasAuthority('scope:specific')\")");
}

@Test
public void testPreAuthorizeOnGlobalJwtAuth() throws IOException {
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
output.deleteOnExit();
String outputPath = output.getAbsolutePath().replace('\\', '/');

OpenAPI openAPI = new OpenAPIParser()
.readLocation("src/test/resources/3_0/security-oauth-bearer.yaml", null, new ParseOptions()).getOpenAPI();

SpringCodegen codegen = new SpringCodegen();
codegen.setOutputDir(output.getAbsolutePath());
codegen.additionalProperties().put(CXFServerFeatures.LOAD_TEST_DATA_FROM_FILE, "true");
codegen.additionalProperties().put(SpringCodegen.USE_SPRING_SECURITY, "true");

ClientOptInput input = new ClientOptInput();
input.setOpenAPI(openAPI);
input.setConfig(codegen);

MockDefaultGenerator generator = new MockDefaultGenerator();
generator.opts(input).generate();

String path = outputPath + "/src/main/java/org/openapitools/api/PetsApi.java";
checkFileContains(generator, path,
"@PreAuthorize(\"hasAuthority('scope:global')\")");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
openapi: 3.0.2 # This should be 3.1, but the parser does not accept that version yet.
info:
version: 1.0.0
title: security-oauth
license:
name: MIT
servers:
- url: http://secured.api.example.xyz/v1

components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
anotherAuth:
type: http
scheme: bearer
bearerFormat: JWT

security:
- bearerAuth:
- "scope:global"

paths:
/pets:
get:
security:
- bearerAuth:
- "scope:specific"
- anotherAuth:
- "scope:another"
post:
summary: make the pet
Loading

0 comments on commit 4c58a2c

Please sign in to comment.