[jaxrs-spec][quarkus] Emit @RolesAllowed({scope}) for OAuth2 and OpenID Connect operations with explicit scopes#23752
Draft
Ignacio-Vidal wants to merge 19 commits intoOpenAPITools:masterfrom
Draft
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This is part 3 of #23691 to improve authentication and authorisation support in the
jaxrs-spec/quarkusserver generator, building on #23680.What this PR does
Extends the
useJakartaSecurityAnnotationsflag (introduced in #23680) to also emit@jakarta.annotation.security.RolesAllowed({"scope1","scope2"})on JAX-RS interface methods and implementation stubs for operations whose OAuth2 or OpenID Connect security requirements carry explicit scopes.PR #23680 covered the any-authenticated-user case (
@RolesAllowed({"**"})). This PR covers the scoped-role case: when every OR alternative has at least one scope, the generator emits a single@RolesAllowedwhose value is the deduplicated, alphabetically sorted union of all scopes across those alternatives.The wildcard and scoped emissions are mutually exclusive per operation: if any OR alternative qualifies as "any authenticated user", the wildcard wins and no scoped annotation is emitted on that operation.
Semantic rules
When
@RolesAllowed({scope})is emitted@RolesAllowed({union_of_all_scopes})@RolesAllowed({"**"})wins — PR #23680 path- {})@PermitAll(next PR)WARNlogOR scope union
When all OR alternatives are scoped, their scope lists are unioned, deduplicated, and sorted alphabetically:
AND group — single scoped scheme
A single
SecurityRequirementmap with multiple keys is an AND group. The generator emits scoped roles only when at most one scheme in the AND group carries scopes. Unscoped co-required schemes (HTTP, API key, mTLS) contribute nothing to the scope list:If two or more schemes in the same AND group carry competing scope sets, Quarkus cannot enforce AND-of-different-scopes with a single annotation, so no annotation is emitted and a
WARNis logged.Global vs per-operation security
Operations with no
securityfield inheritopenapi.security. Operations with an explicitsecurity: []override produce no annotation regardless of the global block. A globalsecurity: []block (top-level empty list) also suppresses emission on inheriting operations.Why scoped and wildcard are mutually exclusive
Emitting both
@RolesAllowed({"**"})and@RolesAllowed({"admin"})on the same method would cause Quarkus to apply both interceptors with AND semantics — net effect is the stricter annotation alone, which contradicts the OR intent of the spec. The processor short-circuits in the wildcard branch and never evaluates the scoped path for the same operation.Implementation notes
Single vendor extension. The processor sets a single
x-jakarta-roles-allowedvendor extension whose value is aList<String>, and collapses what was previously two mutually-exclusive flags (x-jakarta-any-rolesboolean andx-jakarta-roles-allowedlist) into one["**"]for the wildcard case (any-authenticated-user).Processor changes in
JakartaSecurityAnnotationProcessor:x-jakarta-roles-allowed = ["**"]instead of the separatex-jakarta-any-rolesboolean.collectRolesAllowedScopes(returns the deduplicated sorted union as aList<String>, ornullif the operation does not qualify).collectAndGroupScopes(extracts the scope list from a single AND group, enforcing the single-scoped-scheme rule).resolveSchemeshelper so both code paths share theopenAPI.getComponents()...getSecuritySchemes()lookup.applyToshort-circuits after setting the wildcard extension, then falls through to the scoped path. Mutual exclusion is enforced by the data shape (one extension), not by template logic.No changes to
JavaJAXRSSpecServerCodegen. The existingfromOperationoverride from #23680 already delegates to the processor.Template change in
jakartaSecurityAnnotations.mustache:.0(the list's first element being truthy) to emit the annotation exactly once.{{openbrace}}/{{closebrace}}(globally defined inDefaultCodegen) avoid Mustache parser ambiguity when{immediately follows a tag close.apiInterface.mustacheandapiMethod.mustache.New CI sample
bin/configs/jaxrs-spec-quarkus-security.yamlexercises all four annotation paths (security: [], empty-scope, single-scope, OR-union-scope) from a single spec fixture. The generated sample is committed undersamples/server/petstore/jaxrs-spec/quarkus-security/.Test coverage
A new parameterised test (
quarkusEmitsExpectedScopedRolesAllowedCount) drives a 16-row@DataProvidercovering:quarkus-oauth2-single-scope.yamlquarkus-oauth2-multiple-scopes.yamlquarkus-oauth2-or-different-scopes.yamlquarkus-oauth2-with-scopes.yamlquarkus-openidconnect-with-scopes.yamlquarkus-global-with-scopes.yamlquarkus-global-scopes-op-override.yamlquarkus-and-with-apikey-and-scoped-oauth2.yamlquarkus-and-multi-scoped-warns.yamlquarkus-oauth2-or-empty-and-scoped.yamlquarkus-or-empty-anonymous-with-scopes.yamlThe data provider exercises both
interfaceOnly=true(apiInterface.mustache) andinterfaceOnly=false(apiMethod.mustache) for the most representative fixtures, so regressions in either template path are caught.Additional targeted tests verify scope content and ordering (
quarkusEmitsAlphabeticallySortedDeduplicatedScopes,quarkusOrAlternativesProduceUnionedScopes,quarkusGlobalScopesAreInheritedByOperationsWithoutOverride,quarkusOperationOverrideShadowsGlobalScopedSecurity), MicroProfile coexistence (quarkusScopedJakartaCoexistsWithMicroProfileAnnotations), mutual exclusion (quarkusNeverEmitsBothWildcardAndScopedRolesAllowedOnSameOperation), global empty-security inheritance (quarkusGlobalEmptySecurityListEmitsNothing), and end-to-end validation of the CI sample's four endpoints (quarkusMixedSecuritySampleEmitsAllExpectedAnnotations). All 32 existing PR #23680 test rows continue to pass unchanged.Closes #23691 (partial —
@PermitAllfor anonymous/unannotated operations remains for the next PR).@wing328 @jmini @MelleD
PR checklist
Commit all changed files.
This is important, as CI jobs will verify all generator outputs of your HEAD commit as it would merge with master.
These must match the expectations made by your contribution.
You may regenerate an individual generator by passing the relevant config(s) as an argument to the script, for example
./bin/generate-samples.sh bin/configs/java*.IMPORTANT: Do NOT purge/delete any folders/files (e.g. tests) when regenerating the samples as manually written tests may be removed.
master(upcoming7.x.0minor release - breaking changes with fallbacks),8.0.x(breaking changes without fallbacks)"fixes #123"present in the PR description)Summary by cubic
Adds Jakarta security annotations to Quarkus JAX‑RS server stubs, emitting @jakarta.annotation.security.RolesAllowed({"**"}) when an operation requires "any authenticated user." Introduces
useJakartaSecurityAnnotations(requiresuseJakartaEe=true) to align runtime security with OpenAPI.New Features
useJakartaSecurityAnnotationsoption forquarkuslibrary; validatesuseJakartaEe=true.@jakarta.annotation.security.RolesAllowed({"**"})on methods when:http(basic/bearer),apiKey, oroauth2/openIdConnectwith empty scopes.{}) alternative.security.useMicroProfileOpenAPIAnnotations.x-jakarta-any-rolesvendor extension and a dedicated processor to keep template logic simple.Migration
useQuarkusSecurityAnnotationswithuseJakartaSecurityAnnotations.useJakartaEe=true; only supported whenlibrary=quarkus.--additional-properties=useJakartaEe=true,useJakartaSecurityAnnotations=true,library=quarkus.Written for commit 1b06135. Summary will update on new commits.