Skip to content

feat: SSO identity provider config get/update + signing certificate rotation (REQ-1612) - #994

Open
atlan-owen wants to merge 1 commit into
mainfrom
owen/req-1612-sso-idp-config
Open

feat: SSO identity provider config get/update + signing certificate rotation (REQ-1612)#994
atlan-owen wants to merge 1 commit into
mainfrom
owen/req-1612-sso-idp-config

Conversation

@atlan-owen

Copy link
Copy Markdown

Summary

Adds a supported SDK path to read and update a tenant's SSO identity provider configuration, wrapping the existing heracles GET /api/service/idp and POST /api/service/idp/{alias} endpoints. Four methods on client.sso, in sync + async form, in both pyatlan/ and pyatlan_v9/:

  • get_all_identity_providers() / get_identity_provider(sso_alias)
  • update_identity_provider(provider) - full-object replacement; re-reads after write (the endpoint returns an empty body)
  • update_signing_certificate(sso_alias, certificate) - get-then-merge certificate rotation; accepts PEM or single-line base64 and stores the single-line form the config expects; rejects multi-cert bundles, empty, and non-base64 input (new ErrorCode.INVALID_CERTIFICATE, ATLAN-PYTHON-400-080; IDP_NOT_FOUND_BY_ALIAS, ATLAN-PYTHON-404-031)

Origin: REQ-1612 / ZD-127486 - a customer with an expired SAML signing cert had no supported programmatic way to rotate it (SSOClient only wrapped group mappings).

Design notes

  • The update endpoint treats the payload as a full replacement - omitted fields can be silently reset (SHA-497, BLDX-634). The pydantic SSOProvider uses Extra.allow + an untyped config dict so unknown Keycloak fields round-trip; a unit test pins this, including that __atlan_extra__ never leaks into payloads.
  • The msgspec (v9) model cannot capture unknown top-level fields; mitigated by modeling the fuller Keycloak IdentityProviderRepresentation field set, and pinned as a documented limitation for maintainer input.
  • Credential requirement documented per the heracles authz model: these endpoints need a token with the admin role or workspace-admin subrole (READ_TENANT_IDP / UPDATE_TENANT_IDP); personas are not an axis here (heracles role resolution excludes persona-tagged roles).
  • get_identity_provider deliberately stays list+filter rather than GET /idp/{alias}: probing the dedicated route with an unknown alias returns a generic 400 (code 1000, "Please check your request parameters"), so list+filter keeps clean NotFound semantics; IdP lists are 1-2 entries.

Validation

  • Live on an internal tenant (fs3) with this branch installed: list/get, unchanged full-object round-trip, marker-update-and-revert, PEM-input cert rotation (DER-identical result), NotFoundError on unknown alias; tenant state restored afterwards.
  • Enforce-SSO-ON reachability: with SSO enforcement enabled on the tenant, a pre-provisioned admin API token still received 200 on GET /idp, GET /idp/default, and POST /idp/{alias} - the token path works while interactive logins are gated (enforcement was reverted immediately after).
  • 94 unit tests across both stacks, sync + aio, including regression guards for the dropped-field/full-replacement trap and the certificate normalizer edge cases; existing SSO suites unaffected.
  • qa-checks green: ruff format, ruff check, mypy.
  • Adversarial review on the Linear thread (REQ-1612): approve with suggestions; suggestions adopted (credential-requirement language, wider v9 field set) except the GET /idp/{alias} switch, rejected with the probe evidence above.

Open questions for maintainers

  • Integration test: happy to add an IdP-config case to tests/integration/test_sso_client.py if you want it in the harness (needs a live tenant with a configured IdP).
  • v9 top-level unknown-field capture: raw-dict passthrough vs the widened Struct - current branch takes the Struct approach.

🤖 Generated with Claude Code

…otation (REQ-1612)

Adds SSOClient methods (sync, async, and pyatlan_v9 equivalents) to read
and update a tenant's SSO identity provider configuration, wrapping the
existing heracles GET /api/service/idp and POST /api/service/idp/{alias}
endpoints:

- get_all_identity_providers() / get_identity_provider(sso_alias)
- update_identity_provider(provider) - full-object replacement; re-reads
  after write (endpoint returns an empty body)
- update_signing_certificate(sso_alias, certificate) - get-then-merge
  cert swap; accepts PEM or raw base64 and normalizes to the single-line
  form the config requires

Design notes:
- SSOProvider (pydantic) uses Extra.allow + an untyped config dict so
  unknown Keycloak fields round-trip; the update endpoint treats the
  payload as a full replacement, and omitted fields may be silently
  reset (see SHA-497, BLDX-634). The msgspec (v9) model cannot capture
  unknown top-level fields - limitation pinned in its docstring/tests.
- /idp endpoints require admin workspace permissions
  (READ_TENANT_IDP / UPDATE_TENANT_IDP); documented in docstrings.

Validated live on an internal tenant (fs3): list/get, unchanged
round-trip, marker-update-and-revert, PEM-input cert rotation
(DER-identical result), NotFoundError on unknown alias.

Co-Authored-By: Claude Fable 5 (1M context) <[email protected]>
@linear

linear Bot commented Jul 29, 2026

Copy link
Copy Markdown

REQ-1612

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.

1 participant