Skip to content

feat: add per-operation auth descriptor with precedence ladder#155

Open
OmarAlJarrah wants to merge 1 commit into
mainfrom
feat/auth-descriptor-precedence
Open

feat: add per-operation auth descriptor with precedence ladder#155
OmarAlJarrah wants to merge 1 commit into
mainfrom
feat/auth-descriptor-precedence

Conversation

@OmarAlJarrah

Copy link
Copy Markdown
Member

Auth requirements are per-operation, and a two-boolean "needs-auth / needs-key" model does not generalise to operations that accept several alternative schemes with different OAuth parameters. This adds a hand-constructable, scheme-agnostic auth descriptor model to sdk-core plus a deterministic resolver.

What this adds (org.dexpace.sdk.core.http.auth)

  • AuthRequirement — one accepted AuthScheme paired with its own OAuth scopes/params. Where AuthMetadata flattens an operations schemes into a single shared OAuth bag, a requirement pairs each scheme with its own parameters. Immutable, private-field copy-in + Builder/newBuilder, of(scheme) convenience.
  • AuthDescriptor — the per-operation ordered list of AuthRequirements in preference order. Immutable, Builder/newBuilder, of(...) / ofSchemes(...) factories, allowsAnonymous(). Records which schemes are acceptable and in what order, never how a scheme is stamped onto the wire.
  • AuthDescriptorTier — the precedence tier an descriptor occupies: PER_CALL > OPERATION > CLIENT.
  • AuthResolution — the resolved outcome: the chosen requirement, the tier it came from, and an isAnonymous flag.
  • AuthResolutionException — tailored failure naming the required and available schemes (e.g. operation requires one of [OAUTH2, API_KEY] but no matching credential is available (have: [BASIC])).
  • AuthDescriptorResolver — the precedence ladder. Two independent orders:
    1. Tier precedence: the most specific present descriptor wins outright (per-call override > operation default > client default). A supplied higher tier does not fall through to a lower one if it cannot be satisfied — an explicit per-call override fails rather than silently degrading.
    2. Requirement precedence: within the chosen descriptor, requirements are tried in declared order and the first satisfiable scheme wins. NO_AUTH is always satisfiable (anonymous access); any other scheme is satisfiable iff it is in the caller-supplied available-schemes set.

Scheme-agnostic by design

Core never inspects a concrete Credential or knows how a scheme is stamped. The caller supplies the set of schemes it can satisfy (derived from configured credentials) and maps the resolved requirement to a credential + auth step itself. Per-cloud / OAuth specifics stay in adapters. There is no code generation — these are the runtime primitives a generator would later target, fully usable by hand today.

Tests

New suites cover requirement/descriptor construction + builders + defensive copies, tier precedence (including the no-fall-through rule), requirement precedence, anonymous (NO_AUTH) handling, OAuth-parameter forwarding, and the tailored failure message.

Gated build (scoped, run locally)

./gradlew :sdk-core:test :sdk-core:ktlintCheck :sdk-core:detekt --no-daemon   # BUILD SUCCESSFUL
./gradlew :sdk-core:apiDump --no-daemon                                       # regenerated, committed
./gradlew :sdk-core:apiCheck --no-daemon                                      # BUILD SUCCESSFUL

Closes #63

Auth requirements are per-operation, and a two-boolean "needs-auth /
needs-key" model does not generalise to operations that accept several
alternative schemes with different OAuth parameters. This adds a
hand-constructable, scheme-agnostic descriptor model to sdk-core plus a
deterministic resolver.

- AuthRequirement: one accepted AuthScheme paired with its own OAuth
  scopes/params (immutable, Builder + newBuilder).
- AuthDescriptor: a per-operation ordered list of AuthRequirements in
  preference order (immutable, Builder + newBuilder, of/ofSchemes
  factories). Records which schemes are acceptable, never how they are
  stamped onto the wire.
- AuthDescriptorTier / AuthResolution: the precedence tier and the
  resolved outcome (requirement + tier + anonymous flag).
- AuthDescriptorResolver: applies two precedence orders — tier
  precedence (per-call override > operation default > client default,
  no fall-through past a supplied higher tier) then requirement
  precedence within the chosen descriptor (first satisfiable scheme
  wins; NO_AUTH is always satisfiable). Throws AuthResolutionException
  naming the required and available schemes when nothing matches.

The resolver stays scheme-agnostic: callers supply the set of schemes
they can satisfy and map the resolved requirement to a concrete
credential and auth step themselves; per-cloud / OAuth specifics stay
in adapters. No code generation — these are the runtime primitives a
generator would later target.

Closes #63
@OmarAlJarrah

Copy link
Copy Markdown
Member Author

Adds a scheme-agnostic per-operation auth descriptor model to sdk-core (AuthRequirement, AuthDescriptor, AuthDescriptorTier, AuthResolution, AuthResolutionException) plus a stateless AuthDescriptorResolver that walks a two-level precedence ladder. Looks good to merge.

I checked the resolution logic against the described contract: tier precedence (PER_CALL > OPERATION > CLIENT) is applied with explicit no-fall-through, and within the chosen descriptor it selects the first satisfiable requirement, with NO_AUTH always satisfiable. The model types are immutable with private constructors, defensive copies of the scopes/params collections, and the usual Builder/factory surface, so there's no shared mutable state to worry about across calls. The resolver being stateless keeps it safe to reuse concurrently. Since these are new types not yet wired into any pipeline there's no API-compat concern, and the unit tests cover the ladder ordering, the no-fall-through behavior, and the NO_AUTH path well.

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.

Generate per-operation auth descriptors with a precedence ladder

1 participant