Skip to content

CAMEL-24411: camel-oauth - stop the route when the processors do not authenticate the request - #25567

Merged
oscerd merged 1 commit into
apache:mainfrom
oscerd:fix/CAMEL-24411
Aug 21, 2026
Merged

CAMEL-24411: camel-oauth - stop the route when the processors do not authenticate the request#25567
oscerd merged 1 commit into
apache:mainfrom
oscerd:fix/CAMEL-24411

Conversation

@oscerd

@oscerd oscerd commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Fixes CAMEL-24411.

Problem

The camel-oauth processors return normally from process() on the paths where they do not authenticate the caller, so the remaining steps of the route still run — and overwrite the response the processor had just prepared. The component's own test routes have exactly that shape:

from("platform-http:/bearer")
    .process(new OAuthBearerTokenProcessor())
    .setBody(simple("${body} - OAuthBearerToken"));

Three call sites are affected:

Processor Path Before
OAuthBearerTokenProcessor no Authorization header, or one that does not parse as Bearer <token> 400 + return
OAuthCodeFlowProcessor no authenticated session ⇒ redirect to the IdP 302 + return
OAuthCodeFlowCallback callback without the code parameter 400 + return

There is no setRouteStop, CamelAuthorizationException or RoutePolicy anywhere in camel-oauth src/main, so nothing halts the exchange on any of them.

Note the enforcement was also inverted in the bearer processor: a present-but-invalid token fails closed (the exception from OAuth.authenticate() propagates), while an absent credential did not.

Change

Adds reject() and rejectUnauthorized() to AbstractOAuthProcessor and applies them at the three denial points. The bearer processor now answers 401 with a WWW-Authenticate: Bearer challenge (RFC 6750) rather than 400.

Deliberately left alone: sendRedirect() itself, and OAuthLogoutProcessor. Making sendRedirect stop the route would have been the tidier change, but the shipped logout route

from("servlet:/logout")
    .process(new OAuthLogoutProcessor())
    .process(exc -> exc.getContext().getGlobalOptions().put("OAuthLogout", "ok"));

relies on the step after the redirect running. Only the denial paths stop; authenticated requests continue through the rest of the route exactly as before.

Testing

New OAuthProcessorFailClosedTest covers the three paths that return before any identity provider is contacted, so it needs no Keycloak container. Verified it catches the regression: all 3 tests fail against the pre-fix code, all 3 pass after.

mvn test -Dtest=OAuthProcessorFailClosedTest    # 3 passed
mvn clean install -DskipTests                   # full reactor, BUILD SUCCESS

Adds a test-scoped assertj-core to camel-oauth, which the module was missing (project convention is AssertJ for new test code).

Backport

Intended for camel-4.22.x, camel-4.18.x and camel-4.14.x. It is a behaviour change, but it is the behaviour of an authentication gate, and camel-oauth is a Preview-support component. The upgrade-guide entry stays on main per the project's guide policy.


_Claude Code on behalf of

@oscerd
oscerd requested review from gnodet and luigidemasi August 21, 2026 18:14
@github-actions

Copy link
Copy Markdown
Contributor

🌟 Thank you for your contribution to the Apache Camel project! 🌟
🤖 CI automation will test this PR automatically.

🐫 Apache Camel Committers, please review the following items:

  • First-time contributors require MANUAL approval for the GitHub Actions to run
  • You can use the command /component-test (camel-)component-name1 (camel-)component-name2.. to request a test from the test bot although they are normally detected and executed by CI.
  • You can label PRs using skip-tests and test-dependents to fine-tune the checks executed by this PR.
  • Build and test logs are available in the summary page. Only Apache Camel committers have access to the summary.

⚠️ Be careful when sharing logs. Review their contents before sharing them publicly.

@github-actions

github-actions Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

🧪 CI tested the following changed modules:

  • components/camel-oauth
  • docs

🔬 Scalpel shadow comparison — Scalpel: 10 tested, 28 compile-only — current: 9 all tested

Maveniverse Scalpel detected 38 affected modules (current approach: 9).

⚠️ Modules only in Scalpel (29)
  • apache-camel
  • camel-allcomponents
  • camel-catalog
  • camel-catalog-console
  • camel-catalog-lucene
  • camel-catalog-maven
  • camel-catalog-suggest
  • camel-componentdsl
  • camel-csimple-maven-plugin
  • camel-endpointdsl
  • camel-endpointdsl-support
  • camel-itest
  • camel-jbang-core
  • camel-jbang-it
  • camel-jbang-main
  • camel-jbang-plugin-edit
  • camel-jbang-plugin-generate
  • camel-jbang-plugin-kubernetes
  • camel-jbang-plugin-test
  • camel-kamelet-main
  • camel-launcher
  • camel-report-maven-plugin
  • camel-route-parser
  • camel-yaml-dsl
  • camel-yaml-dsl-deserializers
  • camel-yaml-dsl-maven-plugin
  • coverage
  • docs
  • dummy-component

Skip-tests mode would test 10 modules (2 direct + 8 downstream), skip tests for 28 (generated code, meta-modules)

Modules Scalpel would test (10)
  • camel-jbang-mcp
  • camel-jbang-plugin-mcp
  • camel-jbang-plugin-route-parser
  • camel-jbang-plugin-tui
  • camel-jbang-plugin-validate
  • camel-launcher-container
  • camel-oauth
  • camel-yaml-dsl-validator
  • camel-yaml-dsl-validator-maven-plugin
  • docs
Modules with tests skipped (28)
  • apache-camel
  • camel-allcomponents
  • camel-catalog
  • camel-catalog-console
  • camel-catalog-lucene
  • camel-catalog-maven
  • camel-catalog-suggest
  • camel-componentdsl
  • camel-csimple-maven-plugin
  • camel-endpointdsl
  • camel-endpointdsl-support
  • camel-itest
  • camel-jbang-core
  • camel-jbang-it
  • camel-jbang-main
  • camel-jbang-plugin-edit
  • camel-jbang-plugin-generate
  • camel-jbang-plugin-kubernetes
  • camel-jbang-plugin-test
  • camel-kamelet-main
  • camel-launcher
  • camel-report-maven-plugin
  • camel-route-parser
  • camel-yaml-dsl
  • camel-yaml-dsl-deserializers
  • camel-yaml-dsl-maven-plugin
  • coverage
  • dummy-component

ℹ️ Shadow mode — Scalpel observes but does not affect test execution. Learn more

All tested modules (38 modules)
  • Camel :: All Components Sync point
  • Camel :: Assembly
  • Camel :: Catalog :: CSimple Maven Plugin (deprecated)
  • Camel :: Catalog :: Camel Catalog
  • Camel :: Catalog :: Camel Report Maven Plugin
  • Camel :: Catalog :: Camel Route Parser
  • Camel :: Catalog :: Console
  • Camel :: Catalog :: Dummy Component
  • Camel :: Catalog :: Lucene (deprecated)
  • Camel :: Catalog :: Maven
  • Camel :: Catalog :: Suggest
  • Camel :: Component DSL
  • Camel :: Coverage
  • Camel :: Docs
  • Camel :: Endpoint DSL
  • Camel :: Endpoint DSL :: Support
  • Camel :: Integration Tests
  • Camel :: JBang :: Core
  • Camel :: JBang :: Integration tests
  • Camel :: JBang :: MCP
  • Camel :: JBang :: Main
  • Camel :: JBang :: Plugin :: Edit
  • Camel :: JBang :: Plugin :: Generate
  • Camel :: JBang :: Plugin :: Kubernetes
  • Camel :: JBang :: Plugin :: MCP
  • Camel :: JBang :: Plugin :: Route Parser
  • Camel :: JBang :: Plugin :: TUI
  • Camel :: JBang :: Plugin :: Testing
  • Camel :: JBang :: Plugin :: Validate
  • Camel :: Kamelet Main
  • Camel :: Launcher
  • Camel :: Launcher :: Container
  • Camel :: OAuth
  • Camel :: YAML DSL
  • Camel :: YAML DSL :: Deserializers
  • Camel :: YAML DSL :: Maven Plugins
  • Camel :: YAML DSL :: Validator
  • Camel :: YAML DSL :: Validator Maven Plugin

⚙️ View full build and test results

@gnodet gnodet left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Well-scoped security fix by the original camel-oauth component author. The three OAuth processor denial paths that previously returned without stopping the route now correctly call exchange.setRouteStop(true), preventing subsequent route steps from overwriting authentication responses.

Strengths:

  • The 400-to-401 status code change for Bearer token auth is correct per RFC 6750
  • The removal of the authHeader value from the error log is a good security practice — avoids logging sensitive credential material
  • The deliberate choice not to modify sendRedirect() or OAuthLogoutProcessor is well-reasoned and documented in the PR description
  • Thorough upgrade guide entry targeting the correct 4.23 release
  • Test covers both OAuthBearerTokenProcessor (401 on invalid auth header + route stop) and OAuthCodeFlowCallback (403 on missing session + route stop)
  • The new assertj-core test dependency inherits its version from the parent POM as expected

Minor observations (non-blocking):

  • Pre-existing: OAuthBearerTokenProcessor redeclares private final Logger log which shadows the parent's protected final Logger log. Same pattern in OAuthCodeFlowCallback and OAuthCodeFlowProcessor. Worth a follow-up cleanup.
  • The OAuthCodeFlowProcessor redirect-to-IdP path (which also now stops the route) is not covered by a unit test since it requires an OAuth factory. Consider a follow-up with a mock OAuthFactory if the module gains a mocking dependency.

This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.

Claude Code on behalf of @gnodet

@oscerd oscerd added the bug Something isn't working label Aug 21, 2026
@oscerd oscerd self-assigned this Aug 21, 2026
@oscerd oscerd added this to the 4.23.0 milestone Aug 21, 2026
…authenticate the request

OAuthBearerTokenProcessor and OAuthCodeFlowProcessor both returned normally
from process() on the paths where they do not authenticate the caller, so the
remaining steps of the route still ran and overwrote the response the processor
had just prepared. The component's own test routes have the shape

    .process(new OAuthBearerTokenProcessor()).setBody(...)

where that following step executes.

The same shape is present in OAuthCodeFlowCallback, which answered 400 for a
callback without the code parameter and then let the route continue.

There was no setRouteStop, CamelAuthorizationException or RoutePolicy anywhere
in camel-oauth, so nothing halted the exchange on any of these paths.

This adds reject() and rejectUnauthorized() helpers to AbstractOAuthProcessor
and uses them at the three denial points:

* OAuthBearerTokenProcessor - a missing Authorization header, or one that does
  not parse as "Bearer <token>", now answers 401 with a WWW-Authenticate: Bearer
  challenge (RFC 6750) instead of 400, and stops the route. A present but
  invalid token keeps failing by propagating the exception from
  OAuth.authenticate(), as before.
* OAuthCodeFlowProcessor - stops the route after redirecting an unauthenticated
  caller to the identity provider; the 302 is the whole response.
* OAuthCodeFlowCallback - keeps answering 400 for a missing authorization code
  and now stops the route too.

sendRedirect() itself is deliberately left alone, and OAuthLogoutProcessor is
unchanged: the shipped logout route relies on the step after the redirect
running, so stopping the route inside sendRedirect would break an intended flow.
Only the denial paths stop; authenticated requests continue through the rest of
the route exactly as before.

Adds OAuthProcessorFailClosedTest covering the three paths that return before an
identity provider is contacted, a test-scoped assertj-core the module was
missing, and a 4.23 upgrade-guide entry for the status-code and route-stop
change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@oscerd
oscerd merged commit a3a98e4 into apache:main Aug 21, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working components docs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants