Repository navigation
Add use_paw_oauth to TM1Service for one-line PAW sign-in - #1495
Draft
bdunleavy22 wants to merge 8 commits into
Draft
bdunleavy22 wants to merge 8 commits into
bdunleavy22 wants to merge 8 commits into
Conversation
Add option to keep Authorization header on requests
…oken - Parse keep_authorization_header with translate_to_boolean, so a config.ini value of "False" no longer switches it on. - Document keep_authorization_header as an option for token-based gateways (e.g. PAW's OAuth route), and document access_token. - Accept a zero-argument callable for access_token. It is resolved on every connect and reconnect, so a refreshing OAuth client supplies a fresh token after a session timeout instead of the stale one. - Add offline unit tests for the header being kept/removed, the "False" string, and the callable being resolved on each session start. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TM1py.Utils.PawOAuth runs the OAuth 2.0 authorization-code flow with PKCE against Planning Analytics Workspace, then refreshes the access token on demand. Its access_token method is passed to TM1Service as a callable access_token, together with keep_authorization_header=True. It stays outside RestService because the flow opens a browser; the caller decides when an interactive sign-in is acceptable, and TM1py itself stays headless. Standard library plus requests only. - The loopback listener is bound before the browser opens, so an instant redirect is not refused. - Only 127.0.0.1 / [::1] redirect URIs are accepted (RFC 8252 8.3), since "localhost" may resolve to the other address family than the listener. - A busy port raises OAuthError; address reuse is off on Windows, where it would let the listener share a port with another process. - Requests have a read timeout, so a browser's idle speculative connection cannot block the single-threaded listener. - Offline unit tests for the listener and token handling, and a section in the getting-started docs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both were confirmed against real instances (TM1 11.8 on IBM Cloud, TM1 12.5.9 PAaaS): the OAuth token is accepted on PAW's API route, not on TM1 11's /tm1/api/<db> endpoint, which only accepts CAM authentication. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keeps the refresh token in the OS credential store via keyring, so later
processes refresh silently instead of opening the browser. keyring is an
optional extra (pip install TM1py[keyring]) and imported only when the cache
is created, with an ImportError naming the extra if it is missing.
Windows Credential Manager rejects values over 1280 UTF-16 characters
("The stub received bad data"), which refresh tokens can exceed, so the token
is stored in 1000-character chunks plus a count entry written last. Checked
against the real WinVaultKeyring backend with tokens up to 4000 characters.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cache now accepts "in_memory" (the default, also None), "keyring", or a TokenCache instance. "keyring" builds a KeyringTokenCache keyed on the token endpoint host, client ID and scope, with characters outside [A-Za-z0-9._-] replaced and a short hash of the exact values appended, so sign-ins that only differ in replaced characters stay separate. The client secret is left out of the key: key names are visible in the credential store, and a rotated secret should keep the stored token. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
use_paw_oauth=True (or "in_memory", "keyring", or a TokenCache, which also picks where the refresh token is kept) signs in to PAW through the browser before connecting. The OAuth endpoints are derived from base_url's host, paw_client_id and paw_client_secret identify the PAW OAuth client, and paw_redirect_uri defaults to http://127.0.0.1:8000/oauth/paw/callback. The session's access_token callable is supplied as access_token, and keep_authorization_header defaults to True, so the existing ACCESS_TOKEN mode and reconnect handling apply unchanged. paw_client_id is deliberately separate from application_client_id, which is a TM1 12 engine application client for headless service-to-service sign-in. Also adds PawOAuthSession.from_base_url and a default redirect_uri. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
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
Sign in to PAW as a user, through the browser, with one extra kwarg:
paw_client_idis deliberately separate fromapplication_client_id, which is a TM1 12 engine application clientfor headless service-to-service sign-in.
paw_redirect_uridefaults tohttp://127.0.0.1:8000/oauth/paw/callback.use_paw_oauthacceptsTrue/Falseand their config.ini spellings,"in_memory","keyring", or aTokenCache.Evidence
Offline unit tests (
Tests/RestService_paw_oauth_test.py, 10 tests with subtests); the browser sign-in is stubbedand
connect()is patched out:Against real instances with my own user (manual smoke test, not committed), using only
TM1Service(base_url=..., use_paw_oauth="keyring", paw_client_id=..., paw_client_secret=..., **connection_options),with no
access_token, OAuth URLs orkeep_authorization_headerpassed:base_urlmatch the client's real oneshttp://127.0.0.1:8000/oauth/paw/callback)whoamiafter session start (header kept automatically)The interactive path is the same
PawOAuthSession.login()that #1494's smoke tests exercised with a real browsersign-in on both instances.
Merge Danger
Door: two-way for behaviour, but adds public kwargs
Off by default and reverted cleanly. Once released, the
use_paw_oauth/paw_*names are public API, so they'reworth settling in review.
Blast Radius: opt-in
Only
TM1Service/RestServiceconstructions withuse_paw_oauthset are affected; the helper module isn't evenimported otherwise. With it on,
TM1Service(...)blocks until the browser sign-in completes (up to 180 s), unless acached refresh token signs in silently. Token requests don't yet use TM1Service's
verifyorproxiessettings.🤖 Generated with Claude Code