forked from loafle/openapi-generator-original
[Micronaut] [Server] Add support for security roles with extension (#11995)
* Minor refactor for Micronaut generators * Add support for security roles in micronaut server generator * Micronaut Server Generator refactor the x-roles String variable
This commit is contained in:
@@ -11,7 +11,7 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
public class JavaMicronautClientCodegen extends JavaMicronautAbstractCodegen {
|
||||
|
||||
private final Logger LOGGER = LoggerFactory.getLogger(JavaClientCodegen.class);
|
||||
private final Logger LOGGER = LoggerFactory.getLogger(JavaMicronautClientCodegen.class);
|
||||
|
||||
public static final String OPT_CONFIGURE_AUTH = "configureAuth";
|
||||
|
||||
|
||||
@@ -8,8 +8,10 @@ import org.openapitools.codegen.utils.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import java.io.File;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
||||
public class JavaMicronautServerCodegen extends JavaMicronautAbstractCodegen {
|
||||
@@ -17,7 +19,15 @@ public class JavaMicronautServerCodegen extends JavaMicronautAbstractCodegen {
|
||||
public static final String OPT_GENERATE_CONTROLLER_FROM_EXAMPLES = "generateControllerFromExamples";
|
||||
public static final String OPT_GENERATE_CONTROLLER_AS_ABSTRACT = "generateControllerAsAbstract";
|
||||
|
||||
private final Logger LOGGER = LoggerFactory.getLogger(JavaClientCodegen.class);
|
||||
public static final String EXTENSION_ROLES = "x-roles";
|
||||
public static final String ANONYMOUS_ROLE_KEY = "isAnonymous()";
|
||||
public static final String ANONYMOUS_ROLE = "SecurityRule.IS_ANONYMOUS";
|
||||
public static final String AUTHORIZED_ROLE_KEY = "isAuthorized()";
|
||||
public static final String AUTHORIZED_ROLE = "SecurityRule.IS_AUTHENTICATED";
|
||||
public static final String DENY_ALL_ROLE_KEY = "denyAll()";
|
||||
public static final String DENY_ALL_ROLE = "SecurityRule.DENY_ALL";
|
||||
|
||||
private final Logger LOGGER = LoggerFactory.getLogger(JavaMicronautServerCodegen.class);
|
||||
|
||||
public static final String NAME = "java-micronaut-server";
|
||||
|
||||
@@ -30,7 +40,6 @@ public class JavaMicronautServerCodegen extends JavaMicronautAbstractCodegen {
|
||||
protected String controllerSuffix = "Controller";
|
||||
protected String apiPrefix = "Abstract";
|
||||
protected String apiSuffix = "Controller";
|
||||
private String testOutputDir;
|
||||
|
||||
public JavaMicronautServerCodegen() {
|
||||
super();
|
||||
@@ -144,6 +153,7 @@ public class JavaMicronautServerCodegen extends JavaMicronautAbstractCodegen {
|
||||
|
||||
@Override
|
||||
public String apiTestFileFolder() {
|
||||
// Set it to the whole output dir, so that validation always passes
|
||||
return super.getOutputDir();
|
||||
}
|
||||
|
||||
@@ -154,7 +164,6 @@ public class JavaMicronautServerCodegen extends JavaMicronautAbstractCodegen {
|
||||
String implementationFolder = outputFolder + File.separator +
|
||||
sourceFolder + File.separator +
|
||||
controllerPackage.replace('.', File.separatorChar);
|
||||
testOutputDir = implementationFolder;
|
||||
return (implementationFolder + File.separator +
|
||||
StringUtils.camelize(controllerPrefix + "_" + tag + "_" + controllerSuffix) + ".java"
|
||||
).replace('/', File.separatorChar);
|
||||
@@ -184,6 +193,31 @@ public class JavaMicronautServerCodegen extends JavaMicronautAbstractCodegen {
|
||||
String controllerClassname = StringUtils.camelize(controllerPrefix + "_" + operations.get("pathPrefix") + "_" + controllerSuffix);
|
||||
objs.put("controllerClassname", controllerClassname);
|
||||
|
||||
List<CodegenOperation> allOperations = (List<CodegenOperation>) operations.get("operation");
|
||||
if (useAuth) {
|
||||
for (CodegenOperation operation : allOperations) {
|
||||
if (!operation.vendorExtensions.containsKey(EXTENSION_ROLES)) {
|
||||
String role = operation.hasAuthMethods ? AUTHORIZED_ROLE : ANONYMOUS_ROLE;
|
||||
operation.vendorExtensions.put(EXTENSION_ROLES, Collections.singletonList(role));
|
||||
} else {
|
||||
List<String> roles = (List<String>) operation.vendorExtensions.get(EXTENSION_ROLES);
|
||||
roles = roles.stream().map(role -> {
|
||||
switch (role) {
|
||||
case ANONYMOUS_ROLE_KEY:
|
||||
return ANONYMOUS_ROLE;
|
||||
case AUTHORIZED_ROLE_KEY:
|
||||
return AUTHORIZED_ROLE;
|
||||
case DENY_ALL_ROLE_KEY:
|
||||
return DENY_ALL_ROLE;
|
||||
default:
|
||||
return "\"" + escapeText(role) + "\"";
|
||||
}
|
||||
}).collect(Collectors.toList());
|
||||
operation.vendorExtensions.put(EXTENSION_ROLES, roles);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return objs;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,12 +92,7 @@ public {{#generateControllerAsAbstract}}abstract {{/generateControllerAsAbstract
|
||||
{{/consumes.0}}
|
||||
{{!security annotations}}
|
||||
{{#useAuth}}
|
||||
{{#hasAuthMethods}}
|
||||
@Secured(SecurityRule.IS_AUTHENTICATED)
|
||||
{{/hasAuthMethods}}
|
||||
{{^hasAuthMethods}}
|
||||
@Secured(SecurityRule.IS_ANONYMOUS)
|
||||
{{/hasAuthMethods}}
|
||||
@Secured({{openbrace}}{{#vendorExtensions.x-roles}}{{{.}}}{{^-last}}, {{/-last}}{{/vendorExtensions.x-roles}}{{closebrace}})
|
||||
{{/useAuth}}
|
||||
{{!the method definition}}
|
||||
public {{#returnType}}Mono<{{{returnType}}}>{{/returnType}}{{^returnType}}Mono<Object>{{/returnType}} {{nickname}}{{#generateControllerAsAbstract}}Api{{/generateControllerAsAbstract}}({{#allParams}}
|
||||
|
||||
@@ -6,6 +6,7 @@ import io.swagger.v3.oas.models.servers.Server;
|
||||
import org.openapitools.codegen.CliOption;
|
||||
import org.openapitools.codegen.CodegenConstants;
|
||||
import org.openapitools.codegen.TestUtils;
|
||||
import org.openapitools.codegen.languages.JavaMicronautAbstractCodegen;
|
||||
import org.openapitools.codegen.languages.JavaMicronautServerCodegen;
|
||||
import org.testng.Assert;
|
||||
import org.testng.annotations.Test;
|
||||
@@ -14,6 +15,8 @@ import static java.util.stream.Collectors.groupingBy;
|
||||
import static org.testng.Assert.assertEquals;
|
||||
|
||||
public class MicronautServerCodegenTest extends AbstractMicronautCodegenTest {
|
||||
protected static String ROLES_EXTENSION_TEST_PATH = "src/test/resources/3_0/micronaut/roles-extension-test.yaml";
|
||||
|
||||
@Test
|
||||
public void clientOptsUnicity() {
|
||||
JavaMicronautServerCodegen codegen = new JavaMicronautServerCodegen();
|
||||
@@ -203,4 +206,35 @@ public class MicronautServerCodegenTest extends AbstractMicronautCodegenTest {
|
||||
TestUtils.assertExtraAnnotationFiles(outputPath + "/src/main/java/org/openapitools/model");
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doNotGenerateAuthRolesWithExtensionWhenNotUseAuth() {
|
||||
JavaMicronautServerCodegen codegen = new JavaMicronautServerCodegen();
|
||||
codegen.additionalProperties().put(JavaMicronautServerCodegen.OPT_USE_AUTH, false);
|
||||
String outputPath = generateFiles(codegen, ROLES_EXTENSION_TEST_PATH, CodegenConstants.MODELS, CodegenConstants.APIS);
|
||||
|
||||
String controllerPath = outputPath + "src/main/java/org/openapitools/controller/";
|
||||
assertFileNotContains(controllerPath + "BooksController.java", "@Secured");
|
||||
assertFileNotContains(controllerPath + "UsersController.java", "@Secured");
|
||||
assertFileNotContains(controllerPath + "ReviewsController.java", "@Secured");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generateAuthRolesWithExtension() {
|
||||
JavaMicronautServerCodegen codegen = new JavaMicronautServerCodegen();
|
||||
codegen.additionalProperties().put(JavaMicronautServerCodegen.OPT_USE_AUTH, true);
|
||||
String outputPath = generateFiles(codegen, ROLES_EXTENSION_TEST_PATH, CodegenConstants.MODELS, CodegenConstants.APIS);
|
||||
|
||||
String controllerPath = outputPath + "src/main/java/org/openapitools/controller/";
|
||||
assertFileContainsRegex(controllerPath + "BooksController.java", "IS_ANONYMOUS[^;]{0,100}bookSearchGet");
|
||||
assertFileContainsRegex(controllerPath + "BooksController.java", "@Secured\\(\\{\"admin\"\\}\\)[^;]{0,100}createBook");
|
||||
assertFileContainsRegex(controllerPath + "BooksController.java", "IS_ANONYMOUS[^;]{0,100}getBook");
|
||||
assertFileContainsRegex(controllerPath + "BooksController.java", "IS_AUTHENTICATED[^;]{0,100}reserveBook");
|
||||
|
||||
assertFileContainsRegex(controllerPath + "ReviewsController.java", "IS_AUTHENTICATED[^;]{0,100}bookSendReviewPost");
|
||||
assertFileContainsRegex(controllerPath + "ReviewsController.java", "IS_ANONYMOUS[^;]{0,100}bookViewReviewsGet");
|
||||
|
||||
assertFileContainsRegex(controllerPath + "UsersController.java", "IS_ANONYMOUS[^;]{0,100}getUserProfile");
|
||||
assertFileContainsRegex(controllerPath + "UsersController.java", "IS_AUTHENTICATED[^;]{0,100}updateProfile");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
openapi: 3.0.0
|
||||
info:
|
||||
description: This is a test api description
|
||||
version: 1.0.0
|
||||
title: Library
|
||||
license:
|
||||
name: Apache-2.0
|
||||
url: 'https://www.apache.org/licenses/LICENSE-2.0.html'
|
||||
tags:
|
||||
- {name: books, description: Everything about books}
|
||||
- {name: users, description: Everyting about users}
|
||||
- {name: reviews, description: Everything related to book reviews}
|
||||
paths:
|
||||
/book/{bookName}:
|
||||
get:
|
||||
tags: [books]
|
||||
summary: Get a book by name
|
||||
operationId: getBook
|
||||
parameters:
|
||||
- {name: bookName, in: path, required: true, schema: {type: string}}
|
||||
responses:
|
||||
'200':
|
||||
description: success
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/Book" }
|
||||
x-roles: ["isAnonymous()"]
|
||||
post:
|
||||
tags: [books]
|
||||
summary: Create a new book
|
||||
operationId: createBook
|
||||
parameters:
|
||||
- {name: bookName, in: path, required: true, schema: {type: string}}
|
||||
requestBody:
|
||||
content:
|
||||
application/json: { schema: { $ref: "#/components/schemas/Book" } }
|
||||
responses:
|
||||
'200':
|
||||
description: success
|
||||
x-roles: ["admin"]
|
||||
/book/search:
|
||||
get:
|
||||
tags: [books]
|
||||
summary: Search for a book
|
||||
parameters:
|
||||
- {name: bookName, in: query, required: false, schema: {type: string, example: "Book 2"}}
|
||||
- {name: ISBN, in: query, required: false, schema: {type: string, pattern: "[0-9]{13}", example: "0123456789123"}}
|
||||
- {name: published, in: query, required: false, schema: {type: string, format: date}}
|
||||
- {name: minNumPages, in: query, required: false, schema: {type: integer, format: int32, minimum: 1, maximum: 1000}}
|
||||
- {name: minReadTime, in: query, required: false, schema: {type: number, format: float, minimum: 1, example: 5.7}}
|
||||
- {name: description, in: query, required: false, schema: {type: string, minLength: 4, nullable: true}}
|
||||
- {name: preferences, in: cookie, required: false, schema: {type: string}}
|
||||
- {name: geoLocation, in: header, required: false, schema: {type: string}}
|
||||
responses:
|
||||
'200':
|
||||
description: success
|
||||
content:
|
||||
application/json:
|
||||
{ schema: { type: array, items: { $ref: "#/components/schemas/Book" } } }
|
||||
/book/availability/{bookName}:
|
||||
get:
|
||||
tags: [books]
|
||||
summary: Check book availability
|
||||
operationId: isBookAvailable
|
||||
parameters:
|
||||
- { name: bookName, in: path, required: true, schema: { type: string, example: "Book 1" } }
|
||||
responses:
|
||||
'200':
|
||||
description: success
|
||||
content:
|
||||
text/plain:
|
||||
schema: { $ref: "#/components/schemas/BookAvailability" }
|
||||
/book/reserve/{bookName}:
|
||||
get:
|
||||
tags: [books]
|
||||
summary: Reserve book for self
|
||||
operationId: reserveBook
|
||||
parameters:
|
||||
- { name: bookName, in: path, required: true, schema: { type: string, example: "Book 2" } }
|
||||
responses:
|
||||
'200':
|
||||
description: success
|
||||
x-roles: ["isAuthorized()"]
|
||||
/user/{userName}:
|
||||
get:
|
||||
tags: [users]
|
||||
summary: View user profile
|
||||
operationId: getUserProfile
|
||||
parameters:
|
||||
- {name: userName, in: path, required: true, schema: {type: string, pattern: "[0-9a-zA-Z ]+"}}
|
||||
responses:
|
||||
'200':
|
||||
description: success
|
||||
content:
|
||||
application/json: { schema: { $ref: "#/components/schemas/User" } }
|
||||
/user:
|
||||
post:
|
||||
tags: [users]
|
||||
summary: Update your own profile
|
||||
operationId: updateProfile
|
||||
requestBody:
|
||||
content:
|
||||
'*/*': { schema: { $ref: "#/components/schemas/User"} }
|
||||
responses:
|
||||
'200':
|
||||
description: success
|
||||
x-roles: ["isAuthorized()"]
|
||||
/book/viewReviews:
|
||||
get:
|
||||
tags: [reviews]
|
||||
summary: Get all reviews for a book
|
||||
parameters:
|
||||
- { name: bookName, in: query, required: true, schema: { type: string, nullable: false } }
|
||||
responses:
|
||||
'200':
|
||||
description: success
|
||||
content:
|
||||
application/json: { schema: { type: array, items: { $ref: "#/content/schemas/Review" } } }
|
||||
/book/sendReview:
|
||||
post:
|
||||
tags: [reviews]
|
||||
summary: Send a review to a book
|
||||
parameters:
|
||||
- {name: bookName, in: query, required: true, schema: { type: string, nullable: false } }
|
||||
requestBody:
|
||||
content:
|
||||
application/x-www-form-urlencoded:
|
||||
schema: {$ref: "#/components/schemas/Review"}
|
||||
responses:
|
||||
'200':
|
||||
description: success
|
||||
x-roles: ["isAuthorized()"]
|
||||
components:
|
||||
schemas:
|
||||
Book:
|
||||
title: Book
|
||||
description: book instance
|
||||
type: object
|
||||
properties:
|
||||
name: {type: string}
|
||||
availability: {$ref: "#/components/schemas/BookAvailability"}
|
||||
pages: {type: integer, format: int32, minimum: 1}
|
||||
author: {type: string, pattern: "[a-zA-z ]+"}
|
||||
readTime: {type: number, format: float, minimum: 0, exclusiveMinimum: true}
|
||||
required: ["name", "availability"]
|
||||
default:
|
||||
name: "Bob's Adventures"
|
||||
availability: "available"
|
||||
BookAvailability:
|
||||
type: string
|
||||
enum: ["available", "not available", "reserved"]
|
||||
default: "not available"
|
||||
Review:
|
||||
type: object
|
||||
properties:
|
||||
rating: {type: integer, minimum: 1, maximum: 5, default: 2}
|
||||
description: {type: string, maxLength: 200}
|
||||
required: [rating]
|
||||
User:
|
||||
type: object
|
||||
properties:
|
||||
username: { type: string, minLength: 2, nullable: false }
|
||||
name: { type: string, minLength: 1 }
|
||||
description: { type: string, nullable: true }
|
||||
required: ["username", "name"]
|
||||
Reference in New Issue
Block a user