Skip to content
Draft
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 @@ -360,6 +360,13 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List<Mo
additionalProperties.put("hasResponseStatusAnnotations", true);
}
}
if (QUARKUS_LIBRARY.equals(getLibrary())) {
for (CodegenOperation op : objs.getOperations().getOperation()) {
if (shouldAddAuthenticatedAnnotation(op)){
op.vendorExtensions.put("x-quarkus-authenticated", true);
}
}
}
return objs;
}

Expand All @@ -384,4 +391,17 @@ public Map<String, ModelsMap> postProcessAllModels(Map<String, ModelsMap> objs)
}
return result;
}

protected boolean shouldAddAuthenticatedAnnotation(CodegenOperation op) {
if (!op.hasAuthMethods) {
return false;
}
return op.authMethods.stream().anyMatch(m ->
(Boolean.TRUE.equals(m.isOAuth) && (m.scopes == null || m.scopes.isEmpty())) ||
(Boolean.TRUE.equals(m.isOpenId) && (m.scopes == null || m.scopes.isEmpty())) ||
Boolean.TRUE.equals(m.isBasicBasic) ||
Boolean.TRUE.equals(m.isBasicBearer) ||
Boolean.TRUE.equals(m.isApiKey)
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,7 @@
{{#vendorExtensions.x-java-success-response-code}}
@ResponseStatus({{{vendorExtensions.x-java-success-response-code}}})
{{/vendorExtensions.x-java-success-response-code}}
{{#vendorExtensions.x-quarkus-authenticated}}
@io.quarkus.security.Authenticated
{{/vendorExtensions.x-quarkus-authenticated}}
{{#supportAsync}}{{>returnAsyncTypeInterface}}{{/supportAsync}}{{^supportAsync}}{{#returnJBossResponse}}{{>returnResponseTypeInterface}}{{/returnJBossResponse}}{{^returnJBossResponse}}{{#returnResponse}}Response{{/returnResponse}}{{^returnResponse}}{{>returnTypeInterface}}{{/returnResponse}}{{/returnJBossResponse}}{{/supportAsync}} {{nickname}}({{#allParams}}{{>queryParams}}{{>pathParams}}{{>cookieParams}}{{>headerParams}}{{>bodyParams}}{{>formParams}}{{^-last}},{{/-last}}{{/allParams}});
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@
{{^vendorExtensions.x-java-is-response-void}}@org.eclipse.microprofile.openapi.annotations.media.Content(schema = @org.eclipse.microprofile.openapi.annotations.media.Schema(implementation = {{{baseType}}}.class{{#vendorExtensions.x-microprofile-open-api-return-schema-container}}, type = {{{.}}} {{/vendorExtensions.x-microprofile-open-api-return-schema-container}}{{#vendorExtensions.x-microprofile-open-api-return-unique-items}}, uniqueItems = true {{/vendorExtensions.x-microprofile-open-api-return-unique-items}})){{/vendorExtensions.x-java-is-response-void}}
}){{^-last}},{{/-last}}{{/responses}}
}){{/hasProduces}}{{/useMicroProfileOpenAPIAnnotations}}
{{#vendorExtensions.x-quarkus-authenticated}}
@io.quarkus.security.Authenticated
{{/vendorExtensions.x-quarkus-authenticated}}
public {{#supportAsync}}{{#useMutiny}}Uni{{/useMutiny}}{{^useMutiny}}CompletionStage{{/useMutiny}}<{{/supportAsync}}{{#returnJBossResponse}}{{>returnResponseTypeInterface}}{{/returnJBossResponse}}{{^returnJBossResponse}}Response{{/returnJBossResponse}}{{#supportAsync}}>{{/supportAsync}} {{nickname}}({{#allParams}}{{>queryParams}}{{>pathParams}}{{>cookieParams}}{{>headerParams}}{{>bodyParams}}{{>formParams}}{{^-last}},{{/-last}}{{/allParams}}) {
return {{#supportAsync}}{{#useMutiny}}Uni.createFrom().item({{/useMutiny}}{{^useMutiny}}CompletableFuture.supplyAsync(() -> {{/useMutiny}}{{/supportAsync}}Response.ok().entity("magic!").build(){{#supportAsync}}){{/supportAsync}};
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import org.openapitools.codegen.testutils.ConfigAssert;
import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

import java.io.File;
Expand Down Expand Up @@ -1545,4 +1546,198 @@ public void generateQuarkusConcreteClassDoesNotAddResponseStatusAnnotation() thr
"@ResponseStatus",
"import org.jboss.resteasy.reactive.ResponseStatus");
}

/**
* Parameterized test covering all @Authenticated annotation scenarios for Quarkus + OAuth2.
* Each row: (spec path, interfaceOnly, expected count of @io.quarkus.security.Authenticated).
*/
@DataProvider(name = "quarkusOAuth2AuthenticatedCases")
public Object[][] quarkusOAuth2AuthenticatedCases() {
return new Object[][] {
// single OAuth2 flow, no scopes → @Authenticated
{"src/test/resources/3_0/jaxrs-spec/quarkus-oauth2-no-scopes.yaml", true, 1},
{"src/test/resources/3_0/jaxrs-spec/quarkus-oauth2-no-scopes.yaml", false, 1},
// single OAuth2 flow, non-empty scopes → no @Authenticated
{"src/test/resources/3_0/jaxrs-spec/quarkus-oauth2-with-scopes.yaml", true, 0},
{"src/test/resources/3_0/jaxrs-spec/quarkus-oauth2-with-scopes.yaml", false, 0},
// multiple OAuth2 flows, all no scopes → @Authenticated exactly once (no per-flow duplication)
{"src/test/resources/3_0/jaxrs-spec/quarkus-oauth2-multi-flow-no-scopes.yaml", true, 1},
{"src/test/resources/3_0/jaxrs-spec/quarkus-oauth2-multi-flow-no-scopes.yaml", false, 1},
// OR: one scheme no-scope + one scheme scoped → one op gets @Authenticated, scoped-only op gets none
{"src/test/resources/3_0/jaxrs-spec/quarkus-oauth2-or-empty-and-scoped.yaml", true, 1},
{"src/test/resources/3_0/jaxrs-spec/quarkus-oauth2-or-empty-and-scoped.yaml", false, 1},
};
}

@Test(dataProvider = "quarkusOAuth2AuthenticatedCases")
public void quarkusEmitsAuthenticatedAnnotationForOAuth2(String specPath, boolean interfaceOnly, int expectedCount) throws Exception {
final File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
output.deleteOnExit();

final OpenAPI openAPI = new OpenAPIParser()
.readLocation(specPath, null, new ParseOptions()).getOpenAPI();

codegen.setOutputDir(output.getAbsolutePath());
codegen.setLibrary(QUARKUS_LIBRARY);
codegen.additionalProperties().put(INTERFACE_ONLY, interfaceOnly);
codegen.additionalProperties().put(USE_JAKARTA_EE, true);

final ClientOptInput input = new ClientOptInput()
.openAPI(openAPI)
.config(codegen);

final DefaultGenerator generator = new DefaultGenerator();
final List<File> files = generator.opts(input).generate();

validateJavaSourceFiles(files);

TestUtils.ensureContainsFile(files, output, "src/gen/java/org/openapitools/api/ItemsApi.java");
final String content = Files.readString(output.toPath().resolve("src/gen/java/org/openapitools/api/ItemsApi.java"));
Assert.assertEquals(TestUtils.countOccurrences(content, "@io\\.quarkus\\.security\\.Authenticated"), expectedCount);
}

/**
* Parameterized test covering all @Authenticated annotation scenarios for Quarkus + httpBasic.
* Each row: (interfaceOnly).
*/
@DataProvider(name = "quarkusHttpBasicCases")
public Object[][] quarkusHttpBasicCases() {
return new Object[][] {
{true}, // interface-only
{false}, // concrete stub
};
}

@Test(dataProvider = "quarkusHttpBasicCases")
public void quarkusEmitsAuthenticatedAnnotationForHttpBasic(boolean interfaceOnly) throws Exception {
final File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
output.deleteOnExit();

final OpenAPI openAPI = new OpenAPIParser()
.readLocation("src/test/resources/3_0/jaxrs-spec/quarkus-http-basic.yaml", null, new ParseOptions()).getOpenAPI();

codegen.setOutputDir(output.getAbsolutePath());
codegen.setLibrary(QUARKUS_LIBRARY);
codegen.additionalProperties().put(INTERFACE_ONLY, interfaceOnly);
codegen.additionalProperties().put(USE_JAKARTA_EE, true);

final DefaultGenerator generator = new DefaultGenerator();
final List<File> files = generator.opts(new ClientOptInput().openAPI(openAPI).config(codegen)).generate();

validateJavaSourceFiles(files);

TestUtils.ensureContainsFile(files, output, "src/gen/java/org/openapitools/api/ItemsApi.java");
final String content = Files.readString(output.toPath().resolve("src/gen/java/org/openapitools/api/ItemsApi.java"));
Assert.assertEquals(TestUtils.countOccurrences(content, "@io\\.quarkus\\.security\\.Authenticated"), 1);
}

/**
* Parameterized test covering all @Authenticated annotation scenarios for Quarkus + http bearer.
* Each row: (interfaceOnly).
*/
@DataProvider(name = "quarkusHttpBearerCases")
public Object[][] quarkusHttpBearerCases() {
return new Object[][] {
{true},
{false},
};
}

@Test(dataProvider = "quarkusHttpBearerCases")
public void quarkusEmitsAuthenticatedAnnotationForHttpBearer(boolean interfaceOnly) throws Exception {
final File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
output.deleteOnExit();

final OpenAPI openAPI = new OpenAPIParser()
.readLocation("src/test/resources/3_0/jaxrs-spec/quarkus-http-bearer.yaml", null, new ParseOptions()).getOpenAPI();

codegen.setOutputDir(output.getAbsolutePath());
codegen.setLibrary(QUARKUS_LIBRARY);
codegen.additionalProperties().put(INTERFACE_ONLY, interfaceOnly);
codegen.additionalProperties().put(USE_JAKARTA_EE, true);

final DefaultGenerator generator = new DefaultGenerator();
final List<File> files = generator.opts(new ClientOptInput().openAPI(openAPI).config(codegen)).generate();

validateJavaSourceFiles(files);

TestUtils.ensureContainsFile(files, output, "src/gen/java/org/openapitools/api/ItemsApi.java");
final String content = Files.readString(output.toPath().resolve("src/gen/java/org/openapitools/api/ItemsApi.java"));
Assert.assertEquals(TestUtils.countOccurrences(content, "@io\\.quarkus\\.security\\.Authenticated"), 1);
}

/**
* Parameterized test covering all @Authenticated annotation scenarios for Quarkus + api key.
* Each row: (interfaceOnly).
*/
@DataProvider(name = "quarkusApiKeyCases")
public Object[][] quarkusApiKeyCases() {
return new Object[][] {
{true},
{false},
};
}

@Test(dataProvider = "quarkusApiKeyCases")
public void quarkusEmitsAuthenticatedAnnotationForApiKey(boolean interfaceOnly) throws Exception {
final File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
output.deleteOnExit();

final OpenAPI openAPI = new OpenAPIParser()
.readLocation("src/test/resources/3_0/jaxrs-spec/quarkus-api-key.yaml", null, new ParseOptions()).getOpenAPI();

codegen.setOutputDir(output.getAbsolutePath());
codegen.setLibrary(QUARKUS_LIBRARY);
codegen.additionalProperties().put(INTERFACE_ONLY, interfaceOnly);
codegen.additionalProperties().put(USE_JAKARTA_EE, true);

final DefaultGenerator generator = new DefaultGenerator();
final List<File> files = generator.opts(new ClientOptInput().openAPI(openAPI).config(codegen)).generate();

validateJavaSourceFiles(files);

TestUtils.ensureContainsFile(files, output, "src/gen/java/org/openapitools/api/ItemsApi.java");
final String content = Files.readString(output.toPath().resolve("src/gen/java/org/openapitools/api/ItemsApi.java"));
Assert.assertEquals(TestUtils.countOccurrences(content, "@io\\.quarkus\\.security\\.Authenticated"), 1);
}

/**
* Parameterized test for OpenID Connect: empty scopes → @Authenticated; explicit scopes → absent
* (explicit scopes will be handled by @RolesAllowed in a future PR).
* Each row: (spec path, interfaceOnly, expected occurrence count).
*/
@DataProvider(name = "quarkusOpenIdConnectCases")
public Object[][] quarkusOpenIdConnectCases() {
return new Object[][] {
// no scopes → @Authenticated
{"src/test/resources/3_0/jaxrs-spec/quarkus-openidconnect-no-scopes.yaml", true, 1},
{"src/test/resources/3_0/jaxrs-spec/quarkus-openidconnect-no-scopes.yaml", false, 1},
// explicit scopes → no @Authenticated
{"src/test/resources/3_0/jaxrs-spec/quarkus-openidconnect-with-scopes.yaml", true, 0},
{"src/test/resources/3_0/jaxrs-spec/quarkus-openidconnect-with-scopes.yaml", false, 0},
};
}

@Test(dataProvider = "quarkusOpenIdConnectCases")
public void quarkusEmitsAuthenticatedAnnotationForOpenIdConnect(String specPath, boolean interfaceOnly, int expectedCount) throws Exception {
final File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
output.deleteOnExit();

final OpenAPI openAPI = new OpenAPIParser()
.readLocation(specPath, null, new ParseOptions()).getOpenAPI();

codegen.setOutputDir(output.getAbsolutePath());
codegen.setLibrary(QUARKUS_LIBRARY);
codegen.additionalProperties().put(INTERFACE_ONLY, interfaceOnly);
codegen.additionalProperties().put(USE_JAKARTA_EE, true);

final DefaultGenerator generator = new DefaultGenerator();
final List<File> files = generator.opts(new ClientOptInput().openAPI(openAPI).config(codegen)).generate();

validateJavaSourceFiles(files);

TestUtils.ensureContainsFile(files, output, "src/gen/java/org/openapitools/api/ItemsApi.java");
final String content = Files.readString(output.toPath().resolve("src/gen/java/org/openapitools/api/ItemsApi.java"));
Assert.assertEquals(TestUtils.countOccurrences(content, "@io\\.quarkus\\.security\\.Authenticated"), expectedCount);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
openapi: 3.0.1
info:
title: Quarkus API Key auth test
version: '1.0'
servers:
- url: 'http://localhost:8080/'
paths:
/items:
get:
operationId: getItems
summary: Get items
security:
- api_key: []
responses:
'200':
description: OK
post:
operationId: createItem
summary: Create item
responses:
'201':
description: Created
components:
securitySchemes:
api_key:
type: apiKey
in: header
name: X-API-Key
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
openapi: 3.0.1
info:
title: Quarkus HTTP Basic auth test
version: '1.0'
servers:
- url: 'http://localhost:8080/'
paths:
/items:
get:
operationId: getItems
summary: Get items
security:
- basic_auth: []
responses:
'200':
description: OK
post:
operationId: createItem
summary: Create item
responses:
'201':
description: Created
components:
securitySchemes:
basic_auth:
type: http
scheme: basic
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
openapi: 3.0.1
info:
title: Quarkus HTTP Bearer auth test
version: '1.0'
servers:
- url: 'http://localhost:8080/'
paths:
/items:
get:
operationId: getItems
summary: Get items
security:
- bearer_auth: []
responses:
'200':
description: OK
post:
operationId: createItem
summary: Create item
responses:
'201':
description: Created
components:
securitySchemes:
bearer_auth:
type: http
scheme: bearer
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
openapi: 3.0.1
info:
title: Quarkus OAuth2 multi-flow no scopes test
version: '1.0'
servers:
- url: 'http://localhost:8080/'
paths:
/items:
get:
operationId: getItems
summary: Get items
security:
- oauth2_scheme: []
responses:
'200':
description: OK
post:
operationId: createItem
summary: Create item
responses:
'201':
description: Created
components:
securitySchemes:
oauth2_scheme:
type: oauth2
flows:
authorizationCode:
authorizationUrl: https://example.com/oauth/authorize
tokenUrl: https://example.com/oauth/token
scopes: {}
implicit:
authorizationUrl: https://example.com/api/oauth/dialog
scopes: {}
Loading
Loading