apache / apache/pulsar

[improve][client] PIP-478: the v5 HTTP authentication capabilities are unreachable for a v5-native plugin

Open
#26,388 0 comments 0 reactions 1 assignee Claimed by @lhotari View on GitHub
Dominant language
Java
Stars
15.3k
Forks
3.8k
Avg merge
1d 14h
Merged PRs (30d)
160

Description

### Motivation

`pulsar-client-api-v5` publishes two HTTP capability interfaces — `HttpAuthHeadersProvider` and
`HttpAuthChallengeHandler` — plus `HttpAuthCallContext`. `package-info`'s capability matrix,
`Authentication`'s class javadoc and `SinglePassAuthentication` all present them as first-class,
independently useful capabilities, and `pip-478.md` states the dispatch rule normatively:

> the driver engages only for a plugin exposing **both** `capability(HttpAuthChallengeHandler.class)` and
> `capability(HttpAuthHeadersProvider.class)`. A plugin exposing neither, or only one, is not driven: its
> request is issued with whatever headers `getHttpHeadersAsync(ctx)` supplies, or none.

The code dispatches on something else entirely. Both HTTP call sites gate on an internal marker
interface, on the **v4** authentication slot:

```java
// HttpClient.computeAuthHeaders / BaseResource.computeAuthHeaders
if (authentication instanceof AsyncHttpAuthenticationProvider provider) { ... }
```

where `authentication` is `conf.getAuthentication()`. `AsyncHttpAuthenticationProvider` is implemented by
exactly one class in the tree, the v4 `AuthenticationSasl`. Consequently:

- `getHttpHeadersAsync` has one production caller, inside `HttpAuthenticationDriver.authenticateAsync`,
which is only entered after `capability(HttpAuthChallengeHandler.class)` returns non-empty. A plugin
exposing only `HttpAuthHeadersProvider` — the single-pass HTTP case the capability is named for, and
half of the shipped `SinglePassAuthentication` — never has it called. `TokenAuthenticationV5`,
`BasicAuthenticationV5`, `OAuth2AuthenticationV5`, `AthenzAuthenticationV5` and
`LegacyV4CredentialAdapter` all implement it and none is ever asked.
- A genuinely v5-native plugin lives in `conf.v5Authentication` (set only by
`PulsarClientBuilderV5.applyAuthentication`) and the HTTP path never consults that slot at all.
- The "only one capability → not driven" sentence is also wrong in the other direction: a plugin exposing
only `HttpAuthChallengeHandler` passes `supportsHttpChallenge()` and then hard-fails with
`UnsupportedAuthenticationException`.

### Why it matters now

The gap is currently masked — the v5 builder rejects `http(s)://` service URLs and `PulsarAdmin` is still
v4-only — so nothing in tree exercises it. But `pulsar-client-api-v5` is public API that 5.0 freezes. A
third-party author who implements the HTTP half against the documented rule gets no dispatch, with no
error and no log line, and the shape is hard to change once the interfaces are released.

Related: `Authentication.capability()` states "the framework never queries capabilities before
`initializeAsync` completes", and `HttpAuthenticationDriver` queries in four places before initialization
— including `supportsHttpChallenge()`, which is the routing gate. A plugin whose HTTP capability is only
knowable after init (needs a keytab, an IdP probe) is classified as unsupported and silently downgraded
to the deprecated v4 hook. The binary transport does honour the contract, so this is an asymmetry rather
than a uniform relaxation.

### Suggested resolution

Either close the gap in code — have `HttpClient`/`BaseResource` ask the resolved v5 `Authentication` for
`capability(HttpAuthHeadersProvider.class)` and use `getHttpHeadersAsync` for the single-pass case,
instead of only the deprecated v4 `getAuthData().getHttpHeaders()` fallback — or reconcile the documents:
state in `pip-478.md` and on `HttpAuthHeadersProvider` / `SinglePassAuthentication` / `package-info` that
HTTP capability dispatch is reachable in 5.0 only through the internal `AsyncHttpAuthenticationProvider`
seam, and that a v5-slot plugin's HTTP capabilities are not yet driven.

Deciding this before 5.0 ships matters more than which way it is decided.

---

Found in a final-state review of the PIP-478 series (#26321).

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.