Skip to content

[jaxrs-spec][quarkus] Emit @PermitAll for unauthenticated operations (op/global empty security, anonymous OR alternative, no security defined)#23782

Merged
wing328 merged 1 commit into
OpenAPITools:masterfrom
Ignacio-Vidal:quarkus-permit-all
May 13, 2026
Merged

[jaxrs-spec][quarkus] Emit @PermitAll for unauthenticated operations (op/global empty security, anonymous OR alternative, no security defined)#23782
wing328 merged 1 commit into
OpenAPITools:masterfrom
Ignacio-Vidal:quarkus-permit-all

Conversation

@Ignacio-Vidal
Copy link
Copy Markdown
Contributor

@Ignacio-Vidal Ignacio-Vidal commented May 12, 2026

This is part 4 of #23691 to improve authentication and authorisation support in the jaxrs-spec/quarkus server generator, building on #23680 and #23752.

What this PR does

Extends the useJakartaSecurityAnnotations flag (Quarkus library only, requires useJakartaEe=true) to emit @jakarta.annotation.security.PermitAll on JAX-RS interface methods and implementation stubs for operations whose OpenAPI security requirements mean no authentication is required.

This PR covers the unauthenticated case (@PermitAll). The generator has 3 mutually exclusive emission per operation:

  • `@RolesAllowed({"**}): Any authentication is required
  • `@RolesAllowed({"scope"}): Specific scope is required
  • @PermitAll: No authentication is required

Semantic rules

When @PermitAll is emitted

Condition Result
Op-level security: [] (explicit opt-out) @PermitAll — overrides any global setting
No op-level security AND global security: [] @PermitAll — inherits empty list
No op-level security AND no global security block at all @PermitAll — entire API is unauthenticated
OR list contains an anonymous alternative - {} @PermitAll — least-restrictive alternative wins
Mixed-scope AND group / undefined scheme / other ambiguous case No annotation + WARN log (does not fall through to @PermitAll)

Anonymous OR alternative (- {}) — least restrictive wins

OpenAPI 3 allows an empty SecurityRequirement inside the OR list to indicate anonymous access is acceptable alongside other alternatives. When present, the least-restrictive alternative is "no auth required":

security:
  - oauth2: [admin]
  - {}                 # anonymous allowed → @PermitAll emitted

This was previously deferred (PR #23680 left these operations unannotated). They now emit @PermitAll.

Global vs per-operation security

Op-level security Global security Result
[] (empty) anything @PermitAll
absent absent @PermitAll
absent [] (empty) @PermitAll
absent non-empty inherits global — falls through to @RolesAllowed paths
non-empty anything uses op-level — falls through to @RolesAllowed paths

Why mixed-scope AND groups do not fall through

PR #23752 already returns null and logs a WARN for AND groups with more than one scoped scheme — Jakarta annotations cannot express AND-of-different-scope-sets. Silently emitting @PermitAll in that case would turn an authorisation gap into a security hole, so the processor explicitly evaluates only the unauthenticated cases above and leaves the operation unannotated otherwise.

Implementation notes

New vendor extension. The processor sets x-jakarta-permit-all = true (mutually exclusive with x-jakarta-roles-allowed) on qualifying operations. The shared partial jakartaSecurityAnnotations.mustache (and the equivalent blocks in apiInterface.mustache / apiMethod.mustache) gains a section guarded by the new extension to emit @jakarta.annotation.security.PermitAll.

Processor changes in JakartaSecurityAnnotationProcessor:

  • Added qualifiesForPermitAll(rawOp, openAPI, effectiveRequirements) that inspects the raw op-level and global security fields (not the already-resolved effective list) so it can distinguish explicit op-level opt-out from inherited global. This is necessary because the resolved list collapses op.security == [] and "no op.security, inherits global == []" into the same value.
  • applyTo now has three short-circuit branches: wildcard @RolesAllowed({"**"}) → scoped @RolesAllowed({scopes})@PermitAll. The first match wins; subsequent branches are skipped.
  • Mutual exclusion is enforced by the data shape: at most one of x-jakarta-roles-allowed and x-jakarta-permit-all is ever set on a given operation.

No changes to JavaJAXRSSpecServerCodegen. The existing fromOperation override from #23680 already delegates to the processor.

CI sample fixture bin/configs/jaxrs-spec-quarkus-security.yaml and samples/server/petstore/jaxrs-spec/quarkus-security/ (added in #23752) gain a PublicApi endpoint that exercises the @PermitAll path end-to-end alongside the existing scoped and wildcard endpoints, so the four annotation outcomes (@PermitAll, @RolesAllowed({"**"}), @RolesAllowed({scope}), @RolesAllowed({scope1,scope2})) all render in the committed sample.

Test coverage

The existing quarkusJakartaSecurityCases data provider (introduced in #23680) is extended with 10 new rows covering the four @PermitAll cases across interfaceOnly={true,false} and useJakartaSecurityAnnotations={true,false}:

Scenario Fixture
Op-level security: [] (overrides non-empty global) quarkus-permit-all-op-empty-security.yaml
Op-level security: [] AND global security: [] quarkus-permit-all-op-empty-global-empty.yaml
Op-level security: [] AND non-empty global (per-op opt-out only) quarkus-permit-all-op-empty-global-non-empty.yaml
No security field anywhere in the spec quarkus-permit-all-no-security-defined.yaml

Plus targeted tests:

  • quarkusGlobalEmptySecurityListEmitsPermitAll — global security: [] inheritance.
  • quarkusOrAnonymousAlternativeEmitsPermitAll — OR list containing - {}.
  • quarkusNoSecurityDefinedAnywhereEmitsPermitAll — entire spec unauthenticated.
  • quarkusMixedAndGroupDoesNotFallThroughToPermitAll — guards the security-hole regression: mixed-scope AND groups must emit nothing, not @PermitAll.
  • quarkusPermitAllCoexistsWithMicroProfileAnnotations@PermitAll renders alongside MicroProfile @SecurityRequirement annotations on the same method (quarkus-permit-all-microprofile-coexist.yaml).
  • quarkusMixedSecuritySampleEmitsAllExpectedAnnotations — end-to-end check against the committed CI sample asserting all four annotation outcomes render in PublicApi, AuthenticatedApi, AdminApi, AdminOrUserApi, AnonymousOrAuthenticatedApi.

All test rows from #23680 and #23752 continue to pass unchanged.

Closes #23691

PR checklist

  • Read the contribution guidelines.
  • Pull Request title clearly describes the work in the pull request and Pull Request description provides details about how to validate the work. Missing information here may result in delayed response from the community.
  • Run the following to build the project and update samples:
    ./mvnw clean package || exit
    ./bin/generate-samples.sh ./bin/configs/*.yaml || exit
    ./bin/utils/export_docs_generators.sh || exit
    
    (For Windows users, please run the script in WSL)
    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.
  • File the PR against the correct branch: master (upcoming 7.x.0 minor release - breaking changes with fallbacks), 8.0.x (breaking changes without fallbacks)
  • If your PR solves a reported issue, reference it using GitHub's linking syntax (e.g., having "fixes #123" present in the PR description)
  • If your PR is targeting a particular programming language, @mention the technical committee members, so they are more likely to review the pull request.

Summary by cubic

Generate @PermitAll for unauthenticated endpoints in the jaxrs-spec quarkus generator, and keep Jakarta security annotations mutually exclusive with wildcard or scoped @RolesAllowed. Generated APIs now cleanly mirror OpenAPI security: public, any-authenticated, or role-scoped.

  • New Features
    • Emit @PermitAll when an operation is unauthenticated: op-level security: [], global security: [], no security anywhere, or an OR list with {}.
    • Keep @RolesAllowed({"**"}) for “any authenticated user”; emit scoped @RolesAllowed({"admin","user"}) when all OR alternatives are scoped (unioned/deduped/sorted). For mixed-scope AND groups, log and emit nothing.
    • Enforce mutual exclusion between @PermitAll, wildcard @RolesAllowed, and scoped @RolesAllowed, driven by x-jakarta-permit-all and x-jakarta-roles-allowed. Updated apiInterface.mustache and apiMethod.mustache to render them.
    • Add sample AnonymousOrAuthenticatedApi and new test fixtures to cover global empty security, op-level opt-out, anonymous OR alternatives, “no security defined,” and coexistence with MicroProfile annotations.

Written for commit 27b61d5. Summary will update on new commits.

@Ignacio-Vidal Ignacio-Vidal changed the title [jaxrs-spec][quarkus] Emit @PermitAll for endpoints with no security requirement [jaxrs-spec][quarkus] Emit @PermitAll for unauthenticated operations (op/global empty security, anonymous OR alternative, no security defined) May 12, 2026
@Ignacio-Vidal Ignacio-Vidal marked this pull request as ready for review May 12, 2026 22:45
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 37 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="samples/server/petstore/jaxrs-spec/quarkus-security/src/main/docker/Dockerfile.native">

<violation number="1" location="samples/server/petstore/jaxrs-spec/quarkus-security/src/main/docker/Dockerfile.native:17">
P1: Container runs as root because no non-root `USER` is set. The sibling `Dockerfile.jvm` in the same directory explicitly uses `USER 1001`, but this native Dockerfile is missing the non-root user hardening.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

…e security handling for unauthenticated operations
@Ignacio-Vidal
Copy link
Copy Markdown
Contributor Author

Ignacio-Vidal commented May 13, 2026

@wing328 - This is the final MR for #23691. Whilst technically it doesn't have any functional impact because emits a no-op annotation @PermitAll, it completes the loop to emit jakarta annotations for all security scenarios [no-authentication, any-authentication,role-authorisation]

Tested in a separate project:

  • See the api spec with all cases covered in this MR
  • See the adapter implementing the generated interface
  • See the integration tests using the generated client with microprofile library
image

@wing328 wing328 merged commit a3d7b47 into OpenAPITools:master May 13, 2026
37 checks passed
@wing328
Copy link
Copy Markdown
Member

wing328 commented May 13, 2026

@Ignacio-Vidal just merged. Thanks for all your contributions 👍

@Ignacio-Vidal
Copy link
Copy Markdown
Contributor Author

Perfect, thank you for the quick reviews

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[jaxrs-spec][quarkus] - Emit Authentication & Authorisation annotations (@Authenticated, @RolesAllowed, @PermitAll) from Security Schemes

2 participants