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: {}