diff --git a/docs/generators/jaxrs-cxf-cdi.md b/docs/generators/jaxrs-cxf-cdi.md
index 607c3b00d25d..54e88fd00a53 100644
--- a/docs/generators/jaxrs-cxf-cdi.md
+++ b/docs/generators/jaxrs-cxf-cdi.md
@@ -82,10 +82,10 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|title|a title describing the application| |OpenAPI Server|
|useBeanValidation|Use BeanValidation API annotations| |true|
|useJakartaEe|whether to use Jakarta EE namespace instead of javax| |false|
+|useJakartaSecurityAnnotations|Whether to generate Jakarta security annotations (@RolesAllowed, @PermitAll). Requires useJakartaEe=true. Currently only supported when library is set to quarkus.| |false|
|useMicroProfileOpenAPIAnnotations|Whether to generate Microprofile OpenAPI annotations. Only valid when library is set to quarkus.| |false|
|useMutiny|Whether to use Smallrye Mutiny instead of CompletionStage for asynchronous computation. Only valid when library is set to quarkus.| |false|
|useOneOfInterfaces|whether to use a java interface to describe a set of oneOf options, where each option is a class that implements the interface| |false|
-|useQuarkusSecurityAnnotations|Whether to generate Quarkus security annotations (@Authenticated, @RolesAllowed, @PermitAll). Only valid when library is set to quarkus.| |false|
|useSwaggerAnnotations|Whether to generate Swagger annotations.| |true|
|useSwaggerV3Annotations|Whether to generate Swagger v3 (OpenAPI v3) annotations.| |false|
|useTags|use tags for creating interface and controller classnames| |false|
diff --git a/docs/generators/jaxrs-spec.md b/docs/generators/jaxrs-spec.md
index 1a8568aa23cf..b5225bffd43e 100644
--- a/docs/generators/jaxrs-spec.md
+++ b/docs/generators/jaxrs-spec.md
@@ -83,10 +83,10 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|title|a title describing the application| |OpenAPI Server|
|useBeanValidation|Use BeanValidation API annotations| |true|
|useJakartaEe|whether to use Jakarta EE namespace instead of javax| |false|
+|useJakartaSecurityAnnotations|Whether to generate Jakarta security annotations (@RolesAllowed, @PermitAll). Requires useJakartaEe=true. Currently only supported when library is set to quarkus.| |false|
|useMicroProfileOpenAPIAnnotations|Whether to generate Microprofile OpenAPI annotations. Only valid when library is set to quarkus.| |false|
|useMutiny|Whether to use Smallrye Mutiny instead of CompletionStage for asynchronous computation. Only valid when library is set to quarkus.| |false|
|useOneOfInterfaces|whether to use a java interface to describe a set of oneOf options, where each option is a class that implements the interface| |false|
-|useQuarkusSecurityAnnotations|Whether to generate Quarkus security annotations (@Authenticated, @RolesAllowed, @PermitAll). Only valid when library is set to quarkus.| |false|
|useSwaggerAnnotations|Whether to generate Swagger annotations.| |true|
|useSwaggerV3Annotations|Whether to generate Swagger v3 (OpenAPI v3) annotations.| |false|
|useTags|use tags for creating interface and controller classnames| |false|
diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JakartaSecurityAnnotationProcessor.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JakartaSecurityAnnotationProcessor.java
new file mode 100644
index 000000000000..fe0780c303e4
--- /dev/null
+++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JakartaSecurityAnnotationProcessor.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech)
+ * Copyright 2018 SmartBear Software
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openapitools.codegen.languages;
+
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.Operation;
+import io.swagger.v3.oas.models.security.SecurityRequirement;
+import io.swagger.v3.oas.models.security.SecurityScheme;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import org.openapitools.codegen.CodegenOperation;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Translates an OpenAPI operation's security requirements into Jakarta security
+ * vendor extensions on a {@link CodegenOperation} for downstream Mustache templates.
+ *
+ *
The OpenAPI {@code security} array uses OR semantics (any one alternative
+ * satisfies the request); the Jakarta annotations are AND-stacked. The two cannot
+ * always be reconciled, so this class emits the least restrictive annotation that
+ * is still correct for the OR group.
+ *
+ *
A single vendor extension {@code x-jakarta-roles-allowed} carries the value to
+ * emit. For the any-authenticated-user case it is set to the singleton list
+ * {@code ["**"]}, producing {@code @RolesAllowed({"**"})}. Future PRs will reuse
+ * the same extension to emit scoped roles (e.g. {@code ["admin"]}) without needing
+ * a second flag or template branch.
+ */
+final class JakartaSecurityAnnotationProcessor {
+
+ static final String VENDOR_X_JAKARTA_ROLES_ALLOWED = "x-jakarta-roles-allowed";
+
+ private static final List ANY_AUTHENTICATED_ROLE = Collections.singletonList("**");
+
+ private final Logger LOGGER = LoggerFactory.getLogger(JakartaSecurityAnnotationProcessor.class);
+
+ /**
+ * Inspects {@code rawOp}'s security requirements (falling back to the global
+ * {@code openAPI.security} when the operation does not override) and sets
+ * {@code x-jakarta-roles-allowed} on {@code op} when the operation qualifies
+ * for {@code @RolesAllowed} emission.
+ */
+ void applyTo(CodegenOperation op, Operation rawOp, OpenAPI openAPI) {
+ // Use the raw Operation here rather than op.authMethods: by the time postProcessOperationsWithModels
+ // runs, DefaultGenerator.filterAuthMethods has flattened all SecurityRequirements into a plain list,
+ // losing the AND-group structure needed to evaluate mixed-scope combinations correctly.
+ List requirements = rawOp.getSecurity();
+ if (requirements == null) {
+ // Fall back to the global security block when the operation does not override it.
+ requirements = openAPI.getSecurity();
+ }
+ Map schemes = resolveSchemes(openAPI);
+
+ if (qualifiesForAnyRoles(requirements, schemes)) {
+ op.vendorExtensions.put(VENDOR_X_JAKARTA_ROLES_ALLOWED, ANY_AUTHENTICATED_ROLE);
+ }
+ }
+
+ /**
+ * Returns true when at least one OR alternative fully qualifies for
+ * {@code @RolesAllowed({"**"})} and no alternative is anonymous ({@code - {}}).
+ *
+ * An empty {@link SecurityRequirement} ({@code - {}}) inside the OR list means
+ * the operation may also be called unauthenticated. When that is present, the
+ * least-restrictive alternative is "no auth required", so emitting
+ * {@code @RolesAllowed({"**"})} would force authentication and contradict the
+ * spec -- we return false instead and let the future {@code @PermitAll} branch
+ * handle that case.
+ */
+ private boolean qualifiesForAnyRoles(List requirements,
+ Map schemes) {
+ if (requirements == null || requirements.isEmpty()) {
+ return false;
+ }
+ boolean anyQualifies = false;
+ for (SecurityRequirement requirement : requirements) {
+ if (requirement.isEmpty()) {
+ // Anonymous OR alternative -- least restrictive wins; do not emit @RolesAllowed.
+ return false;
+ }
+ if (andGroupQualifies(requirement, schemes)) {
+ anyQualifies = true;
+ }
+ }
+ return anyQualifies;
+ }
+
+ /**
+ * A single {@link SecurityRequirement} is an AND group: all schemes must be
+ * satisfied simultaneously. If any scheme in the group has explicit scopes
+ * (e.g. {@code oauth2: [admin:write]}), the combined requirement is more
+ * restrictive than "any authenticated user" and does not qualify.
+ */
+ private boolean andGroupQualifies(SecurityRequirement requirement, Map schemes) {
+ for (Map.Entry> entry : requirement.entrySet()) {
+ SecurityScheme scheme = schemes.get(entry.getKey());
+ if (scheme == null) {
+ LOGGER.warn("Security requirement references undefined scheme '{}' -- skipping Jakarta security annotation for this AND group.",
+ entry.getKey());
+ return false;
+ }
+ if (!schemeQualifies(scheme, entry.getValue())) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private boolean schemeQualifies(SecurityScheme scheme, List scopes) {
+ if (scheme.getType() == null) {
+ LOGGER.warn("Security scheme is missing 'type' -- skipping Jakarta security annotation.");
+ return false;
+ }
+ switch (scheme.getType()) {
+ case OAUTH2:
+ case OPENIDCONNECT:
+ // Empty scope list means the operation requires authentication but no specific role,
+ // so @RolesAllowed({"**"}) is correct. Non-empty scopes belong to a future @RolesAllowed({scope}) PR.
+ return scopes == null || scopes.isEmpty();
+ case HTTP:
+ case APIKEY:
+ case MUTUALTLS:
+ // These schemes have no scope concept; any valid credential satisfies them.
+ return true;
+ default:
+ LOGGER.warn("Unrecognised security scheme type '{}' -- skipping Jakarta security annotation.",
+ scheme.getType());
+ return false;
+ }
+ }
+
+ private static Map resolveSchemes(OpenAPI openAPI) {
+ if (openAPI.getComponents() != null && openAPI.getComponents().getSecuritySchemes() != null) {
+ return openAPI.getComponents().getSecuritySchemes();
+ }
+ return Collections.emptyMap();
+ }
+}
diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavaJAXRSSpecServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavaJAXRSSpecServerCodegen.java
index dd147b5bc7a5..c48002c8ba3c 100644
--- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavaJAXRSSpecServerCodegen.java
+++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavaJAXRSSpecServerCodegen.java
@@ -17,7 +17,9 @@
package org.openapitools.codegen.languages;
+import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.media.Schema;
+import io.swagger.v3.oas.models.servers.Server;
import java.util.Locale;
import lombok.Getter;
import lombok.Setter;
@@ -53,7 +55,7 @@ public class JavaJAXRSSpecServerCodegen extends AbstractJavaJAXRSServerCodegen {
public static final String USE_MUTINY = "useMutiny";
public static final String OPEN_API_SPEC_FILE_LOCATION = "openApiSpecFileLocation";
public static final String GENERATE_JSON_CREATOR = "generateJsonCreator";
- public static final String USE_QUARKUS_SECURITY_ANNOTATIONS = "useQuarkusSecurityAnnotations";
+ public static final String USE_JAKARTA_SECURITY_ANNOTATIONS = "useJakartaSecurityAnnotations";
public static final String QUARKUS_LIBRARY = "quarkus";
public static final String THORNTAIL_LIBRARY = "thorntail";
@@ -69,7 +71,9 @@ public class JavaJAXRSSpecServerCodegen extends AbstractJavaJAXRSServerCodegen {
private boolean useSwaggerV3Annotations = false;
private boolean useMicroProfileOpenAPIAnnotations = false;
private boolean useMutiny = false;
- private boolean useQuarkusSecurityAnnotations = false;
+ private boolean useJakartaSecurityAnnotations = false;
+
+ private final JakartaSecurityAnnotationProcessor jakartaSecurityAnnotationProcessor = new JakartaSecurityAnnotationProcessor();
@Getter @Setter
protected boolean generateJsonCreator = true;
@@ -149,7 +153,7 @@ public JavaJAXRSSpecServerCodegen() {
cliOptions.add(CliOption.newString(OPEN_API_SPEC_FILE_LOCATION, "Location where the file containing the spec will be generated in the output folder. No file generated when set to null or empty string."));
cliOptions.add(CliOption.newBoolean(SUPPORT_ASYNC, "Wrap responses in CompletionStage type, allowing asynchronous computation (requires JAX-RS 2.1).", supportAsync));
cliOptions.add(CliOption.newBoolean(USE_MUTINY, "Whether to use Smallrye Mutiny instead of CompletionStage for asynchronous computation. Only valid when library is set to quarkus.", useMutiny));
- cliOptions.add(CliOption.newBoolean(USE_QUARKUS_SECURITY_ANNOTATIONS, "Whether to generate Quarkus security annotations (@Authenticated, @RolesAllowed, @PermitAll). Only valid when library is set to quarkus.", useQuarkusSecurityAnnotations));
+ cliOptions.add(CliOption.newBoolean(USE_JAKARTA_SECURITY_ANNOTATIONS, "Whether to generate Jakarta security annotations (@RolesAllowed, @PermitAll). Requires useJakartaEe=true. Currently only supported when library is set to quarkus.", useJakartaSecurityAnnotations));
cliOptions.add(CliOption.newBoolean(GENERATE_JSON_CREATOR, "Whether to generate @JsonCreator constructor for required properties.", generateJsonCreator));
}
@@ -192,10 +196,6 @@ public void processOpts() {
convertPropertyToBooleanAndWriteBack(USE_MUTINY, value -> useMutiny = value);
}
- if (QUARKUS_LIBRARY.equals(library)) {
- convertPropertyToBooleanAndWriteBack(USE_QUARKUS_SECURITY_ANNOTATIONS, value -> useQuarkusSecurityAnnotations = value);
- }
-
convertPropertyToBooleanAndWriteBack(GENERATE_JSON_CREATOR, this::setGenerateJsonCreator);
if (additionalProperties.containsKey(OPEN_API_SPEC_FILE_LOCATION)) {
@@ -215,6 +215,18 @@ public void processOpts() {
super.processOpts();
+ // We need to call super.processOpts() before evaluating the `library`, otherwise `library` is null when set via `configOptions` instead of via `library.set("quarkus")` in Gradle
+ if (QUARKUS_LIBRARY.equals(library)) {
+ convertPropertyToBooleanAndWriteBack(USE_JAKARTA_SECURITY_ANNOTATIONS, value -> useJakartaSecurityAnnotations = value);
+ }
+
+ if (useJakartaSecurityAnnotations && !useJakartaEe) {
+ throw new IllegalArgumentException(
+ "Flag '" + USE_JAKARTA_SECURITY_ANNOTATIONS + "' requires '" + USE_JAKARTA_EE
+ + "=true'. The generated annotation '@jakarta.annotation.security.RolesAllowed' "
+ + "is incompatible with the javax.* namespace.");
+ }
+
// expose flags to templates
additionalProperties.put(USE_SWAGGER_ANNOTATIONS, useSwaggerAnnotations);
additionalProperties.put(USE_SWAGGER_V3_ANNOTATIONS, useSwaggerV3Annotations);
@@ -391,4 +403,13 @@ public Map postProcessAllModels(Map objs)
}
return result;
}
+
+ @Override
+ public CodegenOperation fromOperation(String path, String httpMethod, Operation operation, List servers) {
+ CodegenOperation op = super.fromOperation(path, httpMethod, operation, servers);
+ if (QUARKUS_LIBRARY.equals(getLibrary()) && useJakartaSecurityAnnotations) {
+ jakartaSecurityAnnotationProcessor.applyTo(op, operation, openAPI);
+ }
+ return op;
+ }
}
diff --git a/modules/openapi-generator/src/main/resources/JavaJaxRS/spec/libraries/quarkus/apiInterface.mustache b/modules/openapi-generator/src/main/resources/JavaJaxRS/spec/libraries/quarkus/apiInterface.mustache
index b79a56fa34e0..ff40618b737f 100644
--- a/modules/openapi-generator/src/main/resources/JavaJaxRS/spec/libraries/quarkus/apiInterface.mustache
+++ b/modules/openapi-generator/src/main/resources/JavaJaxRS/spec/libraries/quarkus/apiInterface.mustache
@@ -53,4 +53,4 @@
{{#vendorExtensions.x-java-success-response-code}}
@ResponseStatus({{{vendorExtensions.x-java-success-response-code}}})
{{/vendorExtensions.x-java-success-response-code}}
- {{#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}});
+ {{>jakartaSecurityAnnotations}}{{#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}});
diff --git a/modules/openapi-generator/src/main/resources/JavaJaxRS/spec/libraries/quarkus/apiMethod.mustache b/modules/openapi-generator/src/main/resources/JavaJaxRS/spec/libraries/quarkus/apiMethod.mustache
index b97d3447cc32..c22fc17b0312 100644
--- a/modules/openapi-generator/src/main/resources/JavaJaxRS/spec/libraries/quarkus/apiMethod.mustache
+++ b/modules/openapi-generator/src/main/resources/JavaJaxRS/spec/libraries/quarkus/apiMethod.mustache
@@ -47,6 +47,6 @@
{{^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}}
- 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}}) {
+ {{>jakartaSecurityAnnotations}}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}};
}
diff --git a/modules/openapi-generator/src/main/resources/JavaJaxRS/spec/libraries/quarkus/jakartaSecurityAnnotations.mustache b/modules/openapi-generator/src/main/resources/JavaJaxRS/spec/libraries/quarkus/jakartaSecurityAnnotations.mustache
new file mode 100644
index 000000000000..f1797283eaae
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/JavaJaxRS/spec/libraries/quarkus/jakartaSecurityAnnotations.mustache
@@ -0,0 +1,2 @@
+{{#vendorExtensions.x-jakarta-roles-allowed.0}}@jakarta.annotation.security.RolesAllowed({{openbrace}}{{#vendorExtensions.x-jakarta-roles-allowed}}"{{.}}"{{^-last}},{{/-last}}{{/vendorExtensions.x-jakarta-roles-allowed}}{{closebrace}})
+ {{/vendorExtensions.x-jakarta-roles-allowed.0}}
\ No newline at end of file
diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/jaxrs/JavaJAXRSSpecServerCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/jaxrs/JavaJAXRSSpecServerCodegenTest.java
index 69da95430376..d633a448ea29 100644
--- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/jaxrs/JavaJAXRSSpecServerCodegenTest.java
+++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/jaxrs/JavaJAXRSSpecServerCodegenTest.java
@@ -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;
@@ -40,6 +41,12 @@
*/
public class JavaJAXRSSpecServerCodegenTest extends JavaJaxrsBaseTest {
+ // Regex matching @jakarta.annotation.security.RolesAllowed({"**"}).
+ // Built with char concatenation so the embedded double-quote is never inside a string literal,
+ // which prevents formatters from stripping the escape and breaking compilation.
+ private static final String ROLES_ALLOWED_WILDCARD_PATTERN =
+ "@jakarta\\.annotation\\.security\\.RolesAllowed\\(\\{" + '"' + "\\*\\*" + '"' + "\\}\\)";
+
@BeforeMethod
public void before() {
codegen = new JavaJAXRSSpecServerCodegen();
@@ -1547,43 +1554,176 @@ public void generateQuarkusConcreteClassDoesNotAddResponseStatusAnnotation() thr
}
@Test
- public void useQuarkusSecurityAnnotationsIsRegisteredWithDefaultFalse() {
+ public void useJakartaSecurityIsRegisteredWithDefaultFalse() {
final JavaJAXRSSpecServerCodegen codegen = new JavaJAXRSSpecServerCodegen();
Assert.assertTrue(
codegen.cliOptions().stream()
- .anyMatch(opt -> USE_QUARKUS_SECURITY_ANNOTATIONS.equals(opt.getOpt()) && "false".equals(opt.getDefault())),
- "useQuarkusSecurityAnnotations should be a registered CLI option defaulting to false"
+ .anyMatch(opt -> USE_JAKARTA_SECURITY_ANNOTATIONS.equals(opt.getOpt()) && "false".equals(opt.getDefault())),
+ "useJakartaSecurityAnnotations should be a registered CLI option defaulting to false"
);
}
@Test
- public void useQuarkusSecurityAnnotationsDefaultsToFalseForQuarkusLibrary() {
+ public void useJakartaSecurityDefaultsToFalseForQuarkusLibrary() {
codegen.setLibrary(QUARKUS_LIBRARY);
+ codegen.additionalProperties().put(USE_JAKARTA_EE, true);
codegen.processOpts();
Assert.assertFalse(
- (boolean) codegen.additionalProperties().getOrDefault(USE_QUARKUS_SECURITY_ANNOTATIONS, false)
+ (boolean) codegen.additionalProperties().getOrDefault(USE_JAKARTA_SECURITY_ANNOTATIONS, false)
);
}
@Test
- public void useQuarkusSecurityAnnotationsCanBeEnabledForQuarkusLibrary() {
+ public void useJakartaSecurityCanBeEnabledForQuarkusLibrary() {
codegen.setLibrary(QUARKUS_LIBRARY);
- codegen.additionalProperties().put(USE_QUARKUS_SECURITY_ANNOTATIONS, true);
+ codegen.additionalProperties().put(USE_JAKARTA_EE, true);
+ codegen.additionalProperties().put(USE_JAKARTA_SECURITY_ANNOTATIONS, true);
codegen.processOpts();
new ConfigAssert(codegen.additionalProperties())
- .assertValue(USE_QUARKUS_SECURITY_ANNOTATIONS, true);
+ .assertValue(USE_JAKARTA_SECURITY_ANNOTATIONS, true);
}
@Test
- public void useQuarkusSecurityAnnotationsNotProcessedForNonQuarkusLibrary() {
+ public void useJakartaSecurityNotProcessedForNonQuarkusLibrary() {
// flag is only consumed when library=quarkus; for other libraries the block is skipped
- codegen.additionalProperties().put(USE_QUARKUS_SECURITY_ANNOTATIONS, true);
+ codegen.additionalProperties().put(USE_JAKARTA_EE, true);
+ codegen.additionalProperties().put(USE_JAKARTA_SECURITY_ANNOTATIONS, true);
codegen.processOpts();
// convertPropertyToBooleanAndWriteBack was never called, so the value was never
// written back as a boolean — the key holds the raw Object we put in, not false
- Assert.assertNotEquals(false, codegen.additionalProperties().get(USE_QUARKUS_SECURITY_ANNOTATIONS));
+ Assert.assertNotEquals(false, codegen.additionalProperties().get(USE_JAKARTA_SECURITY_ANNOTATIONS));
+ }
+
+ /**
+ * Regression for the latent bug where flags resolved before super.processOpts() were
+ * silently dropped when library was supplied via additionalProperties (e.g. Gradle
+ * configOptions) rather than via setLibrary(). Verifies the flag is honoured even
+ * when the library field is not set on the codegen instance directly.
+ */
+ @Test
+ public void useJakartaSecurityIsHonouredWhenLibrarySuppliedViaAdditionalProperties() {
+ codegen.additionalProperties().put(CodegenConstants.LIBRARY, QUARKUS_LIBRARY);
+ codegen.additionalProperties().put(USE_JAKARTA_EE, true);
+ codegen.additionalProperties().put(USE_JAKARTA_SECURITY_ANNOTATIONS, true);
+ codegen.processOpts();
+
+ new ConfigAssert(codegen.additionalProperties())
+ .assertValue(USE_JAKARTA_SECURITY_ANNOTATIONS, true);
+ }
+
+ @Test(expectedExceptions = IllegalArgumentException.class,
+ expectedExceptionsMessageRegExp = ".*useJakartaSecurityAnnotations.*useJakartaEe.*")
+ public void useJakartaSecurityRequiresUseJakartaEe() {
+ codegen.setLibrary(QUARKUS_LIBRARY);
+ codegen.additionalProperties().put(USE_JAKARTA_EE, false);
+ codegen.additionalProperties().put(USE_JAKARTA_SECURITY_ANNOTATIONS, true);
+ codegen.processOpts();
+ }
+
+ /**
+ * Single parameterised test exercising every spec fixture that drives @RolesAllowed({"**"}) emission.
+ * Each row: (spec path, interfaceOnly, useJakartaSecurityAnnotations, expected occurrences in generated API file).
+ *
+ * Consolidates per-scheme test methods (HttpBasic, HttpBearer, ApiKey, OpenIdConnect, OAuth2, global, mixed-OR, AND-group)
+ * since they only differ by spec path and expected count.
+ */
+ @DataProvider(name = "quarkusJakartaSecurityCases")
+ public Object[][] quarkusJakartaSecurityCases() {
+ return new Object[][] {
+ // single OAuth2 flow, no scopes — flag on → @RolesAllowed({"**"}); flag off → absent
+ {"src/test/resources/3_0/jaxrs-spec/quarkus-oauth2-no-scopes.yaml", true, true, 1},
+ {"src/test/resources/3_0/jaxrs-spec/quarkus-oauth2-no-scopes.yaml", true, false, 0},
+ {"src/test/resources/3_0/jaxrs-spec/quarkus-oauth2-no-scopes.yaml", false, true, 1},
+ {"src/test/resources/3_0/jaxrs-spec/quarkus-oauth2-no-scopes.yaml", false, false, 0},
+ // single OAuth2 flow, non-empty scopes → no @RolesAllowed({"**"}) regardless of flag
+ {"src/test/resources/3_0/jaxrs-spec/quarkus-oauth2-with-scopes.yaml", true, true, 0},
+ {"src/test/resources/3_0/jaxrs-spec/quarkus-oauth2-with-scopes.yaml", false, true, 0},
+ // multiple OAuth2 flows, all no scopes — flag on → exactly once (no per-flow duplication)
+ {"src/test/resources/3_0/jaxrs-spec/quarkus-oauth2-multi-flow-no-scopes.yaml", true, true, 1},
+ {"src/test/resources/3_0/jaxrs-spec/quarkus-oauth2-multi-flow-no-scopes.yaml", false, true, 1},
+ // OR: one scheme no-scope + one scheme scoped — flag on → one op gets @RolesAllowed({"**"})
+ {"src/test/resources/3_0/jaxrs-spec/quarkus-oauth2-or-empty-and-scoped.yaml", true, true, 1},
+ {"src/test/resources/3_0/jaxrs-spec/quarkus-oauth2-or-empty-and-scoped.yaml", false, true, 1},
+ // HTTP Basic — always qualifies (no scope concept)
+ {"src/test/resources/3_0/jaxrs-spec/quarkus-http-basic.yaml", true, true, 1},
+ {"src/test/resources/3_0/jaxrs-spec/quarkus-http-basic.yaml", true, false, 0},
+ {"src/test/resources/3_0/jaxrs-spec/quarkus-http-basic.yaml", false, true, 1},
+ // HTTP Bearer — always qualifies
+ {"src/test/resources/3_0/jaxrs-spec/quarkus-http-bearer.yaml", true, true, 1},
+ {"src/test/resources/3_0/jaxrs-spec/quarkus-http-bearer.yaml", false, true, 1},
+ // API Key — always qualifies
+ {"src/test/resources/3_0/jaxrs-spec/quarkus-api-key.yaml", true, true, 1},
+ {"src/test/resources/3_0/jaxrs-spec/quarkus-api-key.yaml", false, true, 1},
+ // OpenID Connect — empty scopes qualifies
+ {"src/test/resources/3_0/jaxrs-spec/quarkus-openidconnect-no-scopes.yaml", true, true, 1},
+ {"src/test/resources/3_0/jaxrs-spec/quarkus-openidconnect-no-scopes.yaml", false, true, 1},
+ // OpenID Connect — explicit scopes does not qualify
+ {"src/test/resources/3_0/jaxrs-spec/quarkus-openidconnect-with-scopes.yaml", true, true, 0},
+ {"src/test/resources/3_0/jaxrs-spec/quarkus-openidconnect-with-scopes.yaml", false, true, 0},
+ // global HTTP Basic + Bearer; GET inherits (→ @RolesAllowed({"**"})), POST has security:[] (→ none)
+ {"src/test/resources/3_0/jaxrs-spec/quarkus-global-security-one-op-disabled.yaml", true, true, 1},
+ {"src/test/resources/3_0/jaxrs-spec/quarkus-global-security-one-op-disabled.yaml", false, true, 1},
+ // global OR: unscoped OAuth2 + scoped OAuth2; both ops inherit → both get @RolesAllowed({"**"})
+ {"src/test/resources/3_0/jaxrs-spec/quarkus-global-oauth2-or-scoped-and-unscoped.yaml", true, true, 2},
+ {"src/test/resources/3_0/jaxrs-spec/quarkus-global-oauth2-or-scoped-and-unscoped.yaml", false, true, 2},
+ // cross-type OR: scoped OAuth2 OR API Key — API Key qualifies even though OAuth2 alone would not
+ {"src/test/resources/3_0/jaxrs-spec/quarkus-oauth2-scoped-or-api-key.yaml", true, true, 1},
+ {"src/test/resources/3_0/jaxrs-spec/quarkus-oauth2-scoped-or-api-key.yaml", false, true, 1},
+ // AND group: oauth2 empty scopes AND openIdConnect admin:create → never emits
+ {"src/test/resources/3_0/jaxrs-spec/quarkus-and-group-mixed-scopes.yaml", true, true, 0},
+ {"src/test/resources/3_0/jaxrs-spec/quarkus-and-group-mixed-scopes.yaml", false, true, 0},
+ // OR list with anonymous alternative ({}) — least-restrictive wins, no @RolesAllowed
+ {"src/test/resources/3_0/jaxrs-spec/quarkus-or-with-anonymous.yaml", true, true, 0},
+ {"src/test/resources/3_0/jaxrs-spec/quarkus-or-with-anonymous.yaml", false, true, 0},
+ };
+ }
+
+ @Test(dataProvider = "quarkusJakartaSecurityCases")
+ public void quarkusEmitsExpectedRolesAllowedWildcardCount(String specPath, boolean interfaceOnly, boolean useFlag, int expectedCount) throws Exception {
+ final String content = generateQuarkusItemsApi(specPath, interfaceOnly, useFlag, false);
+ Assert.assertEquals(TestUtils.countOccurrences(content, ROLES_ALLOWED_WILDCARD_PATTERN), expectedCount);
+ }
+
+ /**
+ * Asserts @RolesAllowed coexists with MicroProfile @SecurityRequirement annotations on the same method.
+ * The two come from independent template branches; this guards against template ordering or duplication regressions.
+ */
+ @Test
+ public void quarkusJakartaSecurityCoexistsWithMicroProfileAnnotations() throws Exception {
+ final String content = generateQuarkusItemsApi(
+ "src/test/resources/3_0/jaxrs-spec/quarkus-microprofile-coexist.yaml",
+ true, true, true);
+ Assert.assertEquals(TestUtils.countOccurrences(content, ROLES_ALLOWED_WILDCARD_PATTERN), 1,
+ "Expected exactly one @RolesAllowed({\"**\"}) annotation");
+ Assert.assertTrue(content.contains("SecurityRequirement"),
+ "Expected MicroProfile @SecurityRequirement annotation alongside @RolesAllowed");
+ }
+
+ private String generateQuarkusItemsApi(String specPath, boolean interfaceOnly, boolean useJakartaSecurity, boolean useMicroProfile) 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);
+ codegen.additionalProperties().put(USE_JAKARTA_SECURITY_ANNOTATIONS, useJakartaSecurity);
+ if (useMicroProfile) {
+ codegen.additionalProperties().put(USE_MICROPROFILE_OPENAPI_ANNOTATIONS, true);
+ }
+
+ final DefaultGenerator generator = new DefaultGenerator();
+ final List files = generator.opts(new ClientOptInput().openAPI(openAPI).config(codegen)).generate();
+
+ validateJavaSourceFiles(files);
+
+ TestUtils.ensureContainsFile(files, output, "src/gen/java/org/openapitools/api/ItemsApi.java");
+ return Files.readString(output.toPath().resolve("src/gen/java/org/openapitools/api/ItemsApi.java"));
}
}
diff --git a/modules/openapi-generator/src/test/resources/3_0/jaxrs-spec/quarkus-and-group-mixed-scopes.yaml b/modules/openapi-generator/src/test/resources/3_0/jaxrs-spec/quarkus-and-group-mixed-scopes.yaml
new file mode 100644
index 000000000000..c7a5a2971b77
--- /dev/null
+++ b/modules/openapi-generator/src/test/resources/3_0/jaxrs-spec/quarkus-and-group-mixed-scopes.yaml
@@ -0,0 +1,29 @@
+openapi: 3.0.1
+info:
+ title: Quarkus AND group mixed scopes test
+ version: '1.0'
+servers:
+ - url: 'http://localhost:8080/'
+paths:
+ /items:
+ get:
+ operationId: getItems
+ summary: AND group — oauth2 empty scopes AND openIdConnect with admin:create — should NOT emit
+ security:
+ - oauth2Scheme: []
+ openIdScheme:
+ - admin:create
+ responses:
+ '200':
+ description: OK
+components:
+ securitySchemes:
+ oauth2Scheme:
+ type: oauth2
+ flows:
+ clientCredentials:
+ tokenUrl: https://example.com/oauth/token
+ scopes: {}
+ openIdScheme:
+ type: openIdConnect
+ openIdConnectUrl: https://example.com/.well-known/openid-configuration
diff --git a/modules/openapi-generator/src/test/resources/3_0/jaxrs-spec/quarkus-api-key.yaml b/modules/openapi-generator/src/test/resources/3_0/jaxrs-spec/quarkus-api-key.yaml
new file mode 100644
index 000000000000..1d6bf8aff300
--- /dev/null
+++ b/modules/openapi-generator/src/test/resources/3_0/jaxrs-spec/quarkus-api-key.yaml
@@ -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
diff --git a/modules/openapi-generator/src/test/resources/3_0/jaxrs-spec/quarkus-global-oauth2-or-scoped-and-unscoped.yaml b/modules/openapi-generator/src/test/resources/3_0/jaxrs-spec/quarkus-global-oauth2-or-scoped-and-unscoped.yaml
new file mode 100644
index 000000000000..439036c191ae
--- /dev/null
+++ b/modules/openapi-generator/src/test/resources/3_0/jaxrs-spec/quarkus-global-oauth2-or-scoped-and-unscoped.yaml
@@ -0,0 +1,39 @@
+openapi: 3.0.1
+info:
+ title: Quarkus global OAuth2 OR — unscoped and scoped entries
+ version: '1.0'
+servers:
+ - url: 'http://localhost:8080/'
+security:
+ - oauth2_no_scope: []
+ - oauth2_with_scope:
+ - admin
+paths:
+ /items:
+ get:
+ operationId: getItems
+ summary: Inherits global security — unscoped entry in OR list makes it least-restrictive
+ responses:
+ '200':
+ description: OK
+ post:
+ operationId: createItem
+ summary: Also inherits global security — same OR reasoning applies
+ responses:
+ '201':
+ description: Created
+components:
+ securitySchemes:
+ oauth2_no_scope:
+ type: oauth2
+ flows:
+ clientCredentials:
+ tokenUrl: https://example.com/oauth/token
+ scopes: {}
+ oauth2_with_scope:
+ type: oauth2
+ flows:
+ clientCredentials:
+ tokenUrl: https://example.com/oauth/token
+ scopes:
+ admin: Admin access
diff --git a/modules/openapi-generator/src/test/resources/3_0/jaxrs-spec/quarkus-global-security-one-op-disabled.yaml b/modules/openapi-generator/src/test/resources/3_0/jaxrs-spec/quarkus-global-security-one-op-disabled.yaml
new file mode 100644
index 000000000000..3b9ef8072894
--- /dev/null
+++ b/modules/openapi-generator/src/test/resources/3_0/jaxrs-spec/quarkus-global-security-one-op-disabled.yaml
@@ -0,0 +1,32 @@
+openapi: 3.0.1
+info:
+ title: Quarkus global security with one operation explicitly disabled
+ version: '1.0'
+servers:
+ - url: 'http://localhost:8080/'
+security:
+ - basic_auth: []
+ - bearer_auth: []
+paths:
+ /items:
+ get:
+ operationId: getItems
+ summary: Inherits global security (HTTP Basic OR Bearer) — should get @Authenticated
+ responses:
+ '200':
+ description: OK
+ post:
+ operationId: createItem
+ summary: Explicitly disables security with security:[] — should NOT get @Authenticated
+ security: []
+ responses:
+ '201':
+ description: Created
+components:
+ securitySchemes:
+ basic_auth:
+ type: http
+ scheme: basic
+ bearer_auth:
+ type: http
+ scheme: bearer
diff --git a/modules/openapi-generator/src/test/resources/3_0/jaxrs-spec/quarkus-http-basic.yaml b/modules/openapi-generator/src/test/resources/3_0/jaxrs-spec/quarkus-http-basic.yaml
new file mode 100644
index 000000000000..d7fa295d765b
--- /dev/null
+++ b/modules/openapi-generator/src/test/resources/3_0/jaxrs-spec/quarkus-http-basic.yaml
@@ -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
diff --git a/modules/openapi-generator/src/test/resources/3_0/jaxrs-spec/quarkus-http-bearer.yaml b/modules/openapi-generator/src/test/resources/3_0/jaxrs-spec/quarkus-http-bearer.yaml
new file mode 100644
index 000000000000..b006c4a3064e
--- /dev/null
+++ b/modules/openapi-generator/src/test/resources/3_0/jaxrs-spec/quarkus-http-bearer.yaml
@@ -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
diff --git a/modules/openapi-generator/src/test/resources/3_0/jaxrs-spec/quarkus-microprofile-coexist.yaml b/modules/openapi-generator/src/test/resources/3_0/jaxrs-spec/quarkus-microprofile-coexist.yaml
new file mode 100644
index 000000000000..28a45434484f
--- /dev/null
+++ b/modules/openapi-generator/src/test/resources/3_0/jaxrs-spec/quarkus-microprofile-coexist.yaml
@@ -0,0 +1,24 @@
+openapi: 3.0.1
+info:
+ title: Quarkus security with MicroProfile OpenAPI annotations enabled
+ version: '1.0'
+servers:
+ - url: 'http://localhost:8080/'
+paths:
+ /items:
+ get:
+ operationId: getItems
+ summary: Both @SecurityRequirement (MicroProfile) and @RolesAllowed (Jakarta) should be emitted
+ security:
+ - oauth2_no_scope: []
+ responses:
+ '200':
+ description: OK
+components:
+ securitySchemes:
+ oauth2_no_scope:
+ type: oauth2
+ flows:
+ clientCredentials:
+ tokenUrl: https://example.com/oauth/token
+ scopes: {}
diff --git a/modules/openapi-generator/src/test/resources/3_0/jaxrs-spec/quarkus-oauth2-multi-flow-no-scopes.yaml b/modules/openapi-generator/src/test/resources/3_0/jaxrs-spec/quarkus-oauth2-multi-flow-no-scopes.yaml
new file mode 100644
index 000000000000..85002b3d6134
--- /dev/null
+++ b/modules/openapi-generator/src/test/resources/3_0/jaxrs-spec/quarkus-oauth2-multi-flow-no-scopes.yaml
@@ -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: {}
diff --git a/modules/openapi-generator/src/test/resources/3_0/jaxrs-spec/quarkus-oauth2-no-scopes.yaml b/modules/openapi-generator/src/test/resources/3_0/jaxrs-spec/quarkus-oauth2-no-scopes.yaml
new file mode 100644
index 000000000000..5b24d75a8e76
--- /dev/null
+++ b/modules/openapi-generator/src/test/resources/3_0/jaxrs-spec/quarkus-oauth2-no-scopes.yaml
@@ -0,0 +1,30 @@
+openapi: 3.0.1
+info:
+ title: Quarkus OAuth2 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:
+ clientCredentials:
+ tokenUrl: https://example.com/oauth/token
+ scopes: {}
diff --git a/modules/openapi-generator/src/test/resources/3_0/jaxrs-spec/quarkus-oauth2-or-empty-and-scoped.yaml b/modules/openapi-generator/src/test/resources/3_0/jaxrs-spec/quarkus-oauth2-or-empty-and-scoped.yaml
new file mode 100644
index 000000000000..64b4c7905082
--- /dev/null
+++ b/modules/openapi-generator/src/test/resources/3_0/jaxrs-spec/quarkus-oauth2-or-empty-and-scoped.yaml
@@ -0,0 +1,42 @@
+openapi: 3.0.1
+info:
+ title: Quarkus OAuth2 OR empty-and-scoped test
+ version: '1.0'
+servers:
+ - url: 'http://localhost:8080/'
+paths:
+ /items:
+ get:
+ operationId: getItems
+ summary: Get items — OR any-authenticated OR scoped
+ security:
+ - oauth2_no_scope: []
+ - oauth2_with_scope:
+ - admin
+ responses:
+ '200':
+ description: OK
+ post:
+ operationId: createItem
+ summary: Create item — scoped only
+ security:
+ - oauth2_with_scope:
+ - admin
+ responses:
+ '201':
+ description: Created
+components:
+ securitySchemes:
+ oauth2_no_scope:
+ type: oauth2
+ flows:
+ clientCredentials:
+ tokenUrl: https://example.com/oauth/token
+ scopes: {}
+ oauth2_with_scope:
+ type: oauth2
+ flows:
+ clientCredentials:
+ tokenUrl: https://example.com/oauth/token
+ scopes:
+ admin: Admin access
diff --git a/modules/openapi-generator/src/test/resources/3_0/jaxrs-spec/quarkus-oauth2-scoped-or-api-key.yaml b/modules/openapi-generator/src/test/resources/3_0/jaxrs-spec/quarkus-oauth2-scoped-or-api-key.yaml
new file mode 100644
index 000000000000..8b7ab02dc842
--- /dev/null
+++ b/modules/openapi-generator/src/test/resources/3_0/jaxrs-spec/quarkus-oauth2-scoped-or-api-key.yaml
@@ -0,0 +1,40 @@
+openapi: 3.0.1
+info:
+ title: Quarkus OAuth2 scoped OR API Key — API Key qualifies for @Authenticated
+ version: '1.0'
+servers:
+ - url: 'http://localhost:8080/'
+paths:
+ /items:
+ get:
+ operationId: getItems
+ summary: Scoped OAuth2 alone would not qualify, but API Key in the OR list does
+ security:
+ - oauth2_with_scope:
+ - admin
+ - api_key: []
+ responses:
+ '200':
+ description: OK
+ post:
+ operationId: createItem
+ summary: Scoped OAuth2 only — no qualifying scheme
+ security:
+ - oauth2_with_scope:
+ - admin
+ responses:
+ '201':
+ description: Created
+components:
+ securitySchemes:
+ oauth2_with_scope:
+ type: oauth2
+ flows:
+ clientCredentials:
+ tokenUrl: https://example.com/oauth/token
+ scopes:
+ admin: Admin access
+ api_key:
+ type: apiKey
+ in: header
+ name: X-API-Key
diff --git a/modules/openapi-generator/src/test/resources/3_0/jaxrs-spec/quarkus-oauth2-with-scopes.yaml b/modules/openapi-generator/src/test/resources/3_0/jaxrs-spec/quarkus-oauth2-with-scopes.yaml
new file mode 100644
index 000000000000..0d9becab40cb
--- /dev/null
+++ b/modules/openapi-generator/src/test/resources/3_0/jaxrs-spec/quarkus-oauth2-with-scopes.yaml
@@ -0,0 +1,36 @@
+openapi: 3.0.1
+info:
+ title: Quarkus OAuth2 with scopes test
+ version: '1.0'
+servers:
+ - url: 'http://localhost:8080/'
+paths:
+ /items:
+ get:
+ operationId: getItems
+ summary: Get items
+ security:
+ - oauth2_scheme:
+ - read:items
+ responses:
+ '200':
+ description: OK
+ post:
+ operationId: createItem
+ summary: Create item
+ security:
+ - oauth2_scheme:
+ - write:items
+ responses:
+ '201':
+ description: Created
+components:
+ securitySchemes:
+ oauth2_scheme:
+ type: oauth2
+ flows:
+ clientCredentials:
+ tokenUrl: https://example.com/oauth/token
+ scopes:
+ read:items: Read items
+ write:items: Write items
diff --git a/modules/openapi-generator/src/test/resources/3_0/jaxrs-spec/quarkus-openidconnect-no-scopes.yaml b/modules/openapi-generator/src/test/resources/3_0/jaxrs-spec/quarkus-openidconnect-no-scopes.yaml
new file mode 100644
index 000000000000..d12a96679b68
--- /dev/null
+++ b/modules/openapi-generator/src/test/resources/3_0/jaxrs-spec/quarkus-openidconnect-no-scopes.yaml
@@ -0,0 +1,27 @@
+openapi: 3.0.1
+info:
+ title: Quarkus OpenID Connect no scopes test
+ version: '1.0'
+servers:
+ - url: 'http://localhost:8080/'
+paths:
+ /items:
+ get:
+ operationId: getItems
+ summary: Get items
+ security:
+ - oidc_scheme: []
+ responses:
+ '200':
+ description: OK
+ post:
+ operationId: createItem
+ summary: Create item
+ responses:
+ '201':
+ description: Created
+components:
+ securitySchemes:
+ oidc_scheme:
+ type: openIdConnect
+ openIdConnectUrl: https://example.com/.well-known/openid-configuration
diff --git a/modules/openapi-generator/src/test/resources/3_0/jaxrs-spec/quarkus-openidconnect-with-scopes.yaml b/modules/openapi-generator/src/test/resources/3_0/jaxrs-spec/quarkus-openidconnect-with-scopes.yaml
new file mode 100644
index 000000000000..2e708cf412a1
--- /dev/null
+++ b/modules/openapi-generator/src/test/resources/3_0/jaxrs-spec/quarkus-openidconnect-with-scopes.yaml
@@ -0,0 +1,31 @@
+openapi: 3.0.1
+info:
+ title: Quarkus OpenID Connect with scopes test
+ version: '1.0'
+servers:
+ - url: 'http://localhost:8080/'
+paths:
+ /items:
+ get:
+ operationId: getItems
+ summary: Get items
+ security:
+ - oidc_scheme:
+ - read:items
+ responses:
+ '200':
+ description: OK
+ post:
+ operationId: createItem
+ summary: Create item
+ security:
+ - oidc_scheme:
+ - write:items
+ responses:
+ '201':
+ description: Created
+components:
+ securitySchemes:
+ oidc_scheme:
+ type: openIdConnect
+ openIdConnectUrl: https://example.com/.well-known/openid-configuration
diff --git a/modules/openapi-generator/src/test/resources/3_0/jaxrs-spec/quarkus-or-with-anonymous.yaml b/modules/openapi-generator/src/test/resources/3_0/jaxrs-spec/quarkus-or-with-anonymous.yaml
new file mode 100644
index 000000000000..b077e4e18d07
--- /dev/null
+++ b/modules/openapi-generator/src/test/resources/3_0/jaxrs-spec/quarkus-or-with-anonymous.yaml
@@ -0,0 +1,25 @@
+openapi: 3.0.1
+info:
+ title: Quarkus OR list with anonymous alternative
+ version: '1.0'
+servers:
+ - url: 'http://localhost:8080/'
+paths:
+ /items:
+ get:
+ operationId: getItems
+ summary: OR list — oauth2 OR anonymous; least restrictive wins, no @RolesAllowed
+ security:
+ - oauth2_no_scope: []
+ - {}
+ responses:
+ '200':
+ description: OK
+components:
+ securitySchemes:
+ oauth2_no_scope:
+ type: oauth2
+ flows:
+ clientCredentials:
+ tokenUrl: https://example.com/oauth/token
+ scopes: {}