Repository navigation
@W-24145098 - Add Workload Identity Federation (WIF) admin REST API - #1894
Merged
sushmasreedharan-tableau merged 10 commits intoOct 21, 2026
Conversation
Expose the Tableau Server WIF admin surface (REST API 3.30+ / Tableau Server release 2026.3) on the TSC server object as `server.auth_federation`. Site admins can now manage the whole WIF lifecycle from the client, complementing the per-connection WIF attributes delivered in PR tableau#1887. New endpoint methods on `server.auth_federation`: Admin config (`/sites/{siteLuid}/auth-federation/admin`) - get() -> AuthFederationItem - enable(config) -> AuthFederationItem (idempotent PUT; requires enabled=True) - update(config) -> AuthFederationItem (merge-patch; only `token_lifetime_seconds` is mutable - `issuer_url`, `active_key_id`, and `connectors` are read-only and never sent on the wire) - delete() (deletes config + keys + connectors) Connectors (`.../admin/connectors`) - create_connector(connector) -> AuthFederationConnectorItem - list_connectors() -> list[AuthFederationConnectorItem] - get_connector(id) (accepts luid string or item) - delete_connector(id) (accepts luid string or item; idempotent) Keys (`.../admin/keys`) - create_key() -> AuthFederationKeyItem (inactive by default; private key is never returned) - activate_key(id) (atomic; deactivates whatever key was active) - deactivate_key(id) - delete_key(id) (400 if the key is still active) Three new public models exported from both `tableauserverclient.models` and the TSC top-level: `AuthFederationItem`, `AuthFederationConnectorItem`, `AuthFederationKeyItem`. All methods version-gated by @api(version="3.30"). Includes 22 unit tests under `test/test_auth_federation.py` covering the happy path for every method, request-body shape (via defusedxml parsing of `request_history[0].text`) for PUT/PATCH/POST verbs, the read-only-field hygiene of `update()`, the enabled=True guard on `enable()`, the required-field guards on `create_connector`, the list-connectors empty case, both the "pass a str" and "pass an item" forms of every id-taking method, and the EndpointUnavailableError raised when running against a server older than 3.30. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Server-side AuthFederationAdminAppService.updateConfig rejects any non-null `enabled` on PATCH with HTTP 400 and tells callers to use enable() / delete() to change state. The client's update_req already only sends `tokenLifetimeSeconds`, but the docstring listed only issuer_url, active_key_id, and connectors as "silently ignored", leaving `enabled` as a hidden footgun. Add it to the list and point callers at the correct methods. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three docstring claims didn't match what the server actually does per AuthFederationService.java and RestApiAuthFederationKeysAppService.java: * enable(): did not mention that AuthFederationService.enableWif auto-generates one active signing key on first enable (AuthFederationService.java:160-164). A caller doesn't need to immediately create_key + activate_key — the returned item's active_key_id is already populated. * list_connectors(): claimed empty-list on "WIF not enabled". The server's requireWifEnabled (AuthFederationService.java:226-233) throws WIF_NOT_ENABLED_FOR_SITE / FAILED_PRECONDITION, which the client surfaces as ServerResponseError. Only an empty configured site returns []. * create_key(): claimed 409 for the max-keys-exceeded case. The server throws BadRequestRestException (HTTP 400), not 409 (RestApiAuthFederationKeysAppService.java:44-48). Docstring-only; no code or test changes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
RestApiAuthFederationAdminAppService.listConnectors intercepts TableauStatusCode.FAILED_PRECONDITION from the service-layer requireWifEnabled check and re-throws configNotFound (HTTP 404) with AUTH_FEDERATION_NOT_FOUND -- see RestApiAuthFederationAdminAppService.java:157-162. The service-layer behavior cited in the previous commit does not reach the wire; callers see 404. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Reword PAT/OAuth-scope guidance: the four key methods are bound to OAuthScopes.NO_ACCESS server-side, so every OAuth-scoped session (including every PAT) is rejected unconditionally. Authenticate with a username/password session instead. Reflected in CHANGELOG and the class docstring. - Drop the misleading "Snowflake" case-sensitivity counter-example in create_connector; just state that class names are case-sensitive and must be the exact lowercase name. - Note in CHANGELOG that delete_key fails with 400 on an active key; deactivate first. - Replace vague "server rejects the request" in the class docstring with the specific HTTP 404 the server returns when WIF has never been enabled. - Make the _coerce_uuid wording in CHANGELOG explicit: None, empty strings, and non-UUID-shaped targets are rejected client-side. - Add a comment in test_auth_federation_update_empty_patch noting that server-side binding of an empty <authFederationAdmin/> must be covered by an integration test, not this unit test. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Review follow-up on PR tableau#1894. - 403/404 docstring and CHANGELOG rewritten per server behavior: authorizationCheck() runs requireWifFeatureEnabled first, so every method (incl. get()) returns 403 when the per-site WIF feature flag is off — even for a site admin. 404 holds only for update() and the four connector methods; delete() on a never-enabled site is a no-op; create_key() has no enable() gate and leaves an orphan key against the per-site key limit; activate/deactivate/delete_key 404 only on unknown kid. - PAT wording corrected: NO_ACCESS rejects only OAuth-scope-restricted sessions (e.g. connected-app JWT) with 401. PAT and password sessions do not carry scopes and are accepted — this is what the repo's own e2e harness relies on. - delete_key rotation guidance: deactivating the active key with no replacement leaves the site unable to mint tokens. Safe order is create_key() -> activate_key(new) -> delete_key(old). - New test_e2e/test_auth_federation.py: PAT-signed-in round trip across every method. Primarily a wire-shape regression test for the three bare-body routes (PUT/PATCH /admin, POST /admin/connectors) that mocked unit tests cannot catch. Guards against clobbering a live WIF config and tears down to a clean site on exit. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three review nits from Wayne Xie's second pass on PR tableau#1894: - Realism in the "never-enabled" GET fixture: use the real server defaults — token lifetime 7200 (AuthFederationService.java:86) and the actual issuer URL shape %s/api/%s/sites/<luid>/auth-federation (:748-766), not the fabricated /sites/mySite/... URL and 3600 lifetime. - UUID-guard matrix: add None to the bad-target list and parametrize over {raw, item} so the item-wrapped None path (connector.luid=None / key.kid=None) is also covered. Widens bad_target to object. - update() token_lifetime_seconds is now required: there is no other mutable field on the config, so a no-arg call is not a meaningful operation. Tightens the matching request_factory signature and drops the test that exercised the empty-patch wire shape. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
The end-to-end test suppresses deletion failures, allowing a passing result despite unsuccessful cleanup.
2 open issues
What changed in this PR
Adds Workload Identity Federation administration to the Tableau Server Client through server.auth_federation, targeting REST API 3.30+.
Changes:
- Adds configuration, connector, and signing-key management with UUID validation and version gating.
- Exports three response models and implements the admin controller’s bare-XML request format.
- Adds mocked unit tests, response fixtures, an end-to-end round-trip test, and usage documentation.
| File | Description |
|---|---|
test/test_auth_federation.py |
Tests endpoint behavior, request formats, UUID validation, and version gates. |
test/assets/auth_federation_update.xml |
Updated configuration response fixture. |
test/assets/auth_federation_key_create.xml |
Public signing-key response fixture. |
test/assets/auth_federation_get.xml |
Configuration response with registered connectors. |
test/assets/auth_federation_enable.xml |
Enabled configuration response fixture. |
test/assets/auth_federation_connectors_list.xml |
Populated connector-list fixture. |
test/assets/auth_federation_connectors_list_empty.xml |
Empty connector-list fixture. |
test/assets/auth_federation_connector_get.xml |
Single-connector response fixture. |
test/assets/auth_federation_connector_create.xml |
Created-connector response fixture. |
test_e2e/test_auth_federation.py |
Adds a guarded live-site lifecycle test. |
tableauserverclient/server/server.py |
Registers server.auth_federation. |
tableauserverclient/server/request_factory.py |
Serializes bare XML for configuration and connector requests. |
tableauserverclient/server/endpoint/auth_federation_endpoint.py |
Implements WIF administration methods and validation. |
tableauserverclient/server/endpoint/__init__.py |
Exports the new endpoint class. |
tableauserverclient/models/auth_federation_item.py |
Adds configuration, connector, and key response models. |
tableauserverclient/models/__init__.py |
Exports the new models. |
tableauserverclient/__init__.py |
Exposes the models through the public package API. |
CHANGELOG.md |
Documents WIF administration and clarifies the release name. |
Review effort: Balanced
Contributor
|
The copilot suggestions are pretty small but they look like good things to do. Is that server-side TsRequest change actually planned soon? |
Contributor
Author
Yea I'll create a GUS work item and talk to my team to see when we can pick up the server side changes. I can fix the co-pilot issues |
- E2E teardown: let `ServerResponseError` from `auth_federation.delete()` propagate instead of swallowing it. A failed teardown left WIF enabled on the site, so the next run would hit the fixture's `enabled=True` guard and silently skip — the destructive paths stopped being exercised without anyone noticing. - Key-rotation guidance (endpoint docstring, CHANGELOG, and docs): the previous `create_key() -> activate_key(new) -> delete_key(old)` sequence omitted that the old key must stay in the JWKS until every JWT it signed has expired. Deleting it immediately removes it from the key set, and verifiers that fetch JWKS afterwards reject the still-valid tokens signed with it. New guidance keeps the old key inactive (but published) for up to `token_lifetime_seconds` before deleting. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
sushmasreedharan-tableau
added a commit
to sushmasreedharan-tableau/server-client-python
that referenced
this pull request
Oct 21, 2026
The code PR's docstring + CHANGELOG now clarify that the old key must be retained in the site's JWKS until every JWT it signed has expired, and only then deleted. The previous `create_key() -> activate_key(new) -> delete_key(old)` sequence skipped that retain step; deleting the old key immediately removes it from the JWKS and verifiers that fetch the key set afterwards reject the still-valid tokens signed with it. Updated the `auth_federation.delete_key` section here to match. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The behavior is self-evident from a bare `delete()` with no try/except; the rationale belongs in the PR / commit history, not the test body. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
jacalata
approved these changes
Oct 21, 2026
- deactivate_key: absorb the "deactivate on the active key with no replacement leaves the site unable to mint" warning, which belongs on deactivate_key itself (not delete_key). - delete_key: drop the create_key / activate_key chain from the rotation guidance. Reframe the docstring around what distinguishes delete from deactivate: both stop new signing with the key, but delete also pulls it from the JWKS, so still-unexpired tokens signed with it stop verifying. Rotation guidance now reduces to "keep the old key deactivated-but-published until its tokens expire, then delete". Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
sushmasreedharan-tableau
added a commit
to sushmasreedharan-tableau/server-client-python
that referenced
this pull request
Oct 21, 2026
Code PR dropped the activate_key chain from `delete_key` and refocused the docstring on the delete-vs-deactivate distinction (both stop new signing with the key, but delete also pulls it from the JWKS, breaking verification for still-unexpired tokens). Mirror that framing here. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
sushmasreedharan-tableau
merged commit Oct 21, 2026
83b8c14
into
tableau:development
31 checks passed
sushmasreedharan-tableau
added a commit
that referenced
this pull request
Oct 23, 2026
* @W-24145098 - docs: document WIF admin REST API endpoints Documents the new server.auth_federation endpoint added in PR #1894 on the tableau/server-client-python main branch. - New top-level section 'Auth Federation' in docs/api-ref.md, between 'Authentication' and 'Connections'. Documents: - AuthFederationItem, AuthFederationConnectorItem, AuthFederationKeyItem class attribute tables. - 12 endpoint methods: get, enable, update, delete; create_connector, list_connectors, get_connector, delete_connector; create_key, activate_key, deactivate_key, delete_key. - Signature, parameters, return type, and example per method. - Server behavior notes callers can't derive from the signature: per-method 403/404 semantics, PAT/scope acceptance, orphan-key risk when create_key runs before enable, safe-rotation order for keys, and client-side UUID validation that raises ValueError on malformed ids before any HTTP call. - _includes/docs_menu.html: adds an 'Auth Federation' entry in the API Reference sidebar between 'Authentication' and 'Connections'. Requires REST API 3.30+ (Tableau Server release 26.3) and the WIF feature enabled on the site; the server returns 403 otherwise, including on get(). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Match updated key-rotation guidance from PR #1894 The code PR's docstring + CHANGELOG now clarify that the old key must be retained in the site's JWKS until every JWT it signed has expired, and only then deleted. The previous `create_key() -> activate_key(new) -> delete_key(old)` sequence skipped that retain step; deleting the old key immediately removes it from the JWKS and verifiers that fetch the key set afterwards reject the still-valid tokens signed with it. Updated the `auth_federation.delete_key` section here to match. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Match delete_key docstring reframe from PR #1894 Code PR dropped the activate_key chain from `delete_key` and refocused the docstring on the delete-vs-deactivate distinction (both stop new signing with the key, but delete also pulls it from the JWKS, breaking verification for still-unexpired tokens). Mirror that framing here. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Summary
Client-side wrapper for the Tableau Server 2026.3 Workload Identity Federation (WIF) admin REST API 3.30 at
/sites/{siteLuid}/auth-federation/admin[/connectors|/keys].Adds one new endpoint accessor —
server.auth_federation— exposing:get(),enable(token_lifetime_seconds=None),update(token_lifetime_seconds=None),delete()create_connector(connector),list_connectors(),get_connector(id),delete_connector(id)create_key(),activate_key(id),deactivate_key(id),delete_key(id)Three new exports:
AuthFederationItem,AuthFederationConnectorItem,AuthFederationKeyItem.Wire-shape note
The admin controller (
RestApiAuthFederationAdminController) is the only TSC REST controller that binds@RequestBody <SpecificType>instead of@RequestBody TsRequest. For parity,enable(),update(), andcreate_connector()emit the bare<authFederationAdmin>/<authFederationConnector>element with no<tsRequest>envelope. Every other method follows the usual envelope convention. See the comment at the top ofAuthFederationRequestinrequest_factory.py.Security
All id-taking methods (
get_connector,delete_connector,activate_key,deactivate_key,delete_key) validate the id is a well-formed UUID client-side before any HTTP call —None, empty strings, and non-UUID-shaped targets raiseValueError, preventing path injection. 25 parametrized tests cover this.Requirements
WorkloadIdentityFederationfeature flag enabled for the site (403 otherwise, including onget())Scope semantics: the four key-management methods are bound to
OAuthScopes.NO_ACCESSserver-side, which rejects OAuth-scope-restricted sessions (connected-app JWT) with 401. Password and PAT sessions do not carry scopes and are accepted.Test plan
pytest test/test_auth_federation.py)black --checkcleanmypyclean on modified filestest_e2e/test_auth_federation.py— PAT-signed-in, covers every method, guards against clobbering an already-enabled site. Not run by maintainer — requires a 2026.3 site with the WIF feature flag enabled; reviewer should run or provide site creds.Deferred follow-ups (not in this PR)
RestApiAuthFederationAdminControllerto@RequestBody TsRequestfor parity with every other TSC REST controller. Would let us drop the bare-body branch inrequest_factory.AuthFederation.kidis forgotten client-side.@W-24145098