Skip to content

refactor(no-ticket): pass resolved credentials through credential-helper plumbing - #336

Merged
cloudsmith-iduffy merged 3 commits into
masterfrom
credential-plumbing
Aug 4, 2026
Merged

refactor(no-ticket): pass resolved credentials through credential-helper plumbing#336
cloudsmith-iduffy merged 3 commits into
masterfrom
credential-plumbing

Conversation

@cloudsmith-iduffy

@cloudsmith-iduffycloudsmith-iduffy commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Description

The credential-helper plumbing flattened opts.credential into loose
api_key/auth_type strings, threaded the pair through every signature, then
rebuilt a CredentialResult (with a fabricated source_name) just to hand it to
initialise_api. This passes the resolved CredentialResult itself instead, so
the object carries its own auth scheme (X-Api-Key vs Authorization: Bearer)
end to end. A bearer credential's custom-domain lookup now authenticates as a
bearer token rather than having the scheme re-derived at the bottom of the stack.

Three auth defects surfaced while reviewing the refactor and are fixed here:

  • Stale X-Api-Key leaked into bearer requests.initialise_api() resets
    config.headers on every call but never clears config.api_key, which
    Configuration.set_default() makes sticky. Re-initialising with a bearer
    credential left a previously configured X-Api-Key in place, so subsequent
    requests carried both auth headers. This is reachable via SSO login: the
    initialise_api decorator sets X-Api-Key from credentials.ini, then
    refresh_api_config_after_auth() re-initialises with the bearer token — the
    API could authenticate as the pre-login identity while the CLI reported a
    successful login.

  • Blank-credential guard.CredentialResult is a plain dataclass, so
    if not credential and if org and credential are true for a credential
    carrying an empty api_key; the if not api_key guards they replaced were not
    re-established. Both now check credential.api_key, matching the convention
    the generic helper already uses, so a blank credential can no longer reach an
    unauthenticated custom-domains lookup.

  • install --dry-run was not read-only. Auto-discovery ran before the
    dry_run short-circuit, so a preview issued a live API call and overwrote the
    on-disk domain cache. Discovery is now skipped under dry_run and reported as
    such in the planned actions.

Testing

New coverage: a bearer request asserted clean of a deliberately seeded stale
X-Api-Key; blank-credential and dry_run autodiscovery scenarios asserting the
domains API is never called; and the without-credential domain check given the
httpretty(allow_net_connect=False) and config-path isolation its neighbours
use, so it can no longer pass by falling through to a live 401.

Full suite: 572 passed, 40 skipped.

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update
  • Refactoring
  • Other (please describe)

@cloudsmith-iduffycloudsmith-iduffy changed the title credential plumbingrefactor(no-ticket): pass resolved credentials through credential-helper plumbingAug 1, 2026
…per plumbing
The credential-helper plumbing flattened opts.credential into loose
api_key/auth_type strings, threaded the pair through every signature,
then rebuilt a CredentialResult (with a fabricated source_name) just to
hand it to initialise_api. The getattr fallback guarding auth_type could
never fire: it is a declared dataclass field with a default.
Pass the CredentialResult itself instead. The object carries its own
auth scheme end to end, so a bearer credential's custom-domain lookup
now goes out as Authorization: Bearer rather than being re-derived at
the bottom of the stack.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
cloudsmith-iduffyand others added 2 commits August 4, 2026 16:01
…ential
initialise_api() resets config.headers on every call but never clears
config.api_key, which Configuration.set_default() makes sticky across
calls. Re-initialising with a bearer credential therefore left a
previously configured X-Api-Key in place, so subsequent requests carried
both auth headers.
This is reachable via the SSO login path: the initialise_api decorator
sets X-Api-Key from credentials.ini, then refresh_api_config_after_auth()
re-initialises with the bearer token. The API could then authenticate as
the pre-login identity while the CLI reported a successful login.
The bearer test previously called unset_api_key() to work around this; it
now seeds a stale key and asserts it is cleared.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…-run
Two defects in the Docker credential-helper install path:
CredentialResult is a plain dataclass, so `if not credential` and
`if org and credential` are true for a credential carrying an empty
api_key — the guards they replaced (`if not api_key`) were not
re-established. Both now check credential.api_key, so a blank credential
no longer reaches an unauthenticated custom-domains lookup.
Auto-discovery also ran before the dry_run short-circuit, so
`install docker --dry-run` issued a live API call and overwrote the
on-disk domain cache despite promising to make no changes. Discovery is
now skipped under dry_run and reported as such in the planned actions.
Also hoists the function-local is_cloudsmith_domain imports to module
level and gives the without-credential test the httpretty and config-path
isolation its neighbours use, so it can no longer pass by falling through
to a live 401.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@cloudsmith-iduffy
cloudsmith-iduffy marked this pull request as ready for review August 4, 2026 15:17
@cloudsmith-iduffy
cloudsmith-iduffy requested a review from a team as a code ownerAugust 4, 2026 15:17
CopilotAI lite review requested due to automatic review settings August 4, 2026 15:17

CopilotAI 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.

Pull request overview

This PR refactors the credential-helper call chain to pass a resolved CredentialResult end-to-end (instead of flattening to api_key/auth_type), ensuring the authentication scheme is preserved throughout custom-domain lookups and API initialization.

Changes:

  • Thread CredentialResult through credential-helper plumbing (install, is_cloudsmith_domain, custom-domain discovery helpers) to avoid re-deriving auth scheme at lower layers.
  • Fix bearer re-initialization behavior so a previously configured X-Api-Key does not leak into bearer-authenticated requests.
  • Make install --dry-run fully read-only by skipping custom-domain auto-discovery (and avoiding cache mutation), with test coverage for the new behaviors.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated no comments.

Show a summary per file
FileDescription
cloudsmith_cli/credential_helpers/docker/runtime.pyPasses the resolved credential object into domain checking for Docker helper runtime.
cloudsmith_cli/credential_helpers/docker/installer.pyRefactors installer autodiscovery to use CredentialResult and makes dry-run skip discovery/cache writes.
cloudsmith_cli/credential_helpers/custom_domains.pyUpdates custom-domain lookup APIs to accept CredentialResult directly and initialize auth accordingly.
cloudsmith_cli/credential_helpers/common.pyUpdates domain checking to require a usable credential (credential.api_key) for custom-domain API lookups.
cloudsmith_cli/core/api/init.pyClears sticky X-Api-Key when switching to bearer auth to prevent mixed-auth headers.
cloudsmith_cli/cli/tests/commands/test_credential_helper.pyAdds/updates tests for credential plumbing, bearer header behavior, and blank-credential guards.
cloudsmith_cli/cli/tests/commands/test_credential_helper_install.pyExtends installer tests for missing/blank credentials and dry-run autodiscovery behavior; asserts credential passthrough.
cloudsmith_cli/cli/commands/credential_helper/manage.pyPasses the resolved credential object to installers instead of flattening fields.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@cloudsmith-iduffy
cloudsmith-iduffy merged commit 665d848 into masterAug 4, 2026
27 checks passed
@cloudsmith-iduffy
cloudsmith-iduffy deleted the credential-plumbing branch August 4, 2026 15:37
cloudsmith-iduffy added a commit that referenced this pull request Aug 4, 2026
Both sides reworked the same credential-helper call sites. Master's
versions are supersets in every conflict — the api_key guard on
is_cloudsmith_domain, the dry-run skip for auto-discovery, and the
stale-key test that seeds a key rather than clearing it — so they win;
the branch's strict-mode custom-domain tests are kept alongside.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@cloudsmith-iduffy@BartoszBlizniak