Repository navigation
Add PawOAuthSession for interactive OAuth sign-in to PAW - #1494
Draft
bdunleavy22 wants to merge 7 commits into
Draft
bdunleavy22 wants to merge 7 commits into
bdunleavy22 wants to merge 7 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>
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
TM1py.Utils.PawOAuth.PawOAuthSessionsigns a user in to PAW through the browser once (OAuth authorization codewith PKCE), then supplies and refreshes access tokens for
TM1Service.sequenceDiagram participant App participant PawOAuthSession participant Browser participant PAW as PAW /oauth2 participant TM1py App->>PawOAuthSession: login() PawOAuthSession->>PawOAuthSession: bind 127.0.0.1 listener PawOAuthSession->>Browser: open /authorize (PKCE, state) Browser-->>PawOAuthSession: redirect with code PawOAuthSession->>PAW: exchange code → access + refresh token App->>TM1py: TM1Service(access_token=oauth.access_token, keep_authorization_header=True) Note over TM1py: on connect / 401 reconnect TM1py->>PawOAuthSession: access_token() PawOAuthSession->>PAW: refresh if expiredStandard library plus
requests; no new required dependencies.cache="keyring"keeps the refresh token in theOS credential store so later runs refresh silently; the default is
cache="in_memory", and anyTokenCachecan bepassed instead. The keyring entry is keyed on token endpoint host, client ID and scope (never the secret). It needs
the optional
keyringpackage (pip install TM1py[keyring]), which is imported only when that cache is created.Evidence
Offline unit tests (
Tests/PawOAuth_test.py, 36 tests;keyringis faked, so CI doesn't need it), including:KeyringTokenCacheagainst the real Windows Credential Manager backend: without chunking, values over 1280characters fail with
CredWrite: The stub received bad data. With 1000-character chunks, tokens of 100–4000characters round-trip and are fully removed afterwards.
Against real instances with my own user (manual smoke test, not committed):
base_urlthat accepts the tokenhttps://<env>.planning-analytics.cloud.ibm.com/api/v1/tm1/<db>https://<region>.planninganalytics.saas.ibm.com/api/<tenant>/v0/tm1/<db>whoamire_connect()→ new token →whoamikeep_authorization_header=FalseBoth used
scope=v0userContextand a confidential client (secret sent as HTTP Basic). On TM1 11,/tm1/api/<db>rejects the token (
WWW-Authenticate: CAMPassport …): it only accepts CAM, so the docs point to PAW's/api/v1route instead. With
cache="keyring", the first run opened the browser and the second signed in silently.Merge Danger
Door: two-way for behaviour, but adds public API
Nothing existing imports it, so it can be reverted freely before a release. Once released, renaming or removing
PawOAuthSessionwould break users, so the name and location are worth settling in review.Blast Radius: none
New module and a new optional extra only; the module isn't imported by
TM1py/__init__.py. Existing connectionsand installs are unaffected. Opening a browser and binding a local port happen only when a caller explicitly calls
login().🤖 Generated with Claude Code