Skip to content

feat: support Snowflake OAuth for Posit Team Native Apps#321

Draft
kfeinauer wants to merge 1 commit into
mainfrom
feat/snowflake-oauth-auth
Draft

feat: support Snowflake OAuth for Posit Team Native Apps#321
kfeinauer wants to merge 1 commit into
mainfrom
feat/snowflake-oauth-auth

Conversation

@kfeinauer
Copy link
Copy Markdown
Contributor

Draft — supports rstudio/partnership-snowflake#3022. Opening for review/visibility; not ready to merge.

Adds the auth plumbing VIP needs to verify Posit Team deployed as a Snowflake Native App, in two prongs.

1. Browser auth — Snowflake IdP strategy

idp.py gains _fill_snowflake_login, registered as idp = "snowflake". Posit Team products delegate OIDC to the deployment's controller, which itself sits behind the Snowpark Container Services ingress — so reaching a product bounces through Snowflake OAuth more than once (product-host ingress, then controller host). The strategy loops, filling each sign-in form in the chain (waiting for each hop to settle) and clicking the optional Allow consent, until the flow leaves Snowflake.

2. HTTP client auth — pluggable seam

Snowflake's SPCS ingress expects a per-host Authorization: Snowflake Token="…" (a key-pair JWT exchange), which a static API key can't express. Rather than teach VIP about Snowflake, this adds a small generic seam:

  • client_auth.py: register_client_auth(idp, factory) / build_client_auth(...).
  • An optional auth: httpx.Auth on BaseClient and the product clients.
  • Client fixtures consult the registry and skip the Connect API-key requirement when a provider is registered.

A downstream extension (in partnership-snowflake) registers a Snowflake httpx.Auth against idp="snowflake"; VIP stays Snowflake-agnostic.

Validated

Live against a Snowflake posit-team deployment: headless double-bounce login succeeds; Workbench API/security/health tests pass via the JWT prong; the full Workbench UI subset passes (run serially — high xdist concurrency contends on session launches, unrelated to auth).

Tests

selftests/test_idp.py, selftests/test_client_auth.py; full selftest suite green, ruff + mypy clean. No new dependencies.

Add Snowflake as a headless-auth IdP and a pluggable HTTP client-auth
seam so VIP can verify Posit Team deployed as a Snowflake Native App.

- idp.py: `_fill_snowflake_login` drives Snowflake's OAuth sign-in form
  and walks the multi-hop chain (product-host SPCS ingress, then the
  controller host that acts as the products' OIDC IdP), filling each
  sign-in form and clicking the optional "Allow" consent until the flow
  leaves Snowflake. Registered as idp "snowflake".
- client_auth.py: a generic registry (`register_client_auth` /
  `build_client_auth`) + an injectable `httpx.Auth` on the product
  clients, so a downstream extension can authenticate the HTTP clients
  with a non-static scheme (e.g. a Snowflake JWT) without VIP depending
  on Snowflake. Client fixtures skip the Connect API-key requirement when
  such a provider is registered.
- cli.py / vip.toml.example: document idp "snowflake".

Tests: selftests/test_idp.py, selftests/test_client_auth.py.
Copilot AI review requested due to automatic review settings May 29, 2026 21:18
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 adds Snowflake Native App support by (1) introducing a Snowflake-specific headless browser IdP automation strategy for Snowflake OAuth redirects, and (2) adding a pluggable httpx.Auth registry so product API clients can use non-static/per-host authorization schemes (while keeping VIP Snowflake-agnostic).

Changes:

  • Add idp="snowflake" Playwright form-fill strategy that can handle multi-hop Snowflake OAuth and optional consent.
  • Introduce vip.client_auth registry and thread an optional httpx.Auth through BaseClient and product clients, with fixtures consulting the registry.
  • Update CLI/config examples and add selftests for the new IdP strategy + auth registry.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
vip.toml.example Documents idp="snowflake" usage for Snowflake Native App deployments.
src/vip/idp.py Adds Snowflake OAuth headless login strategy and registers it under supported IdPs.
src/vip/clients/base.py Adds optional httpx.Auth support to the shared client base.
src/vip/clients/connect.py Threads optional httpx.Auth into Connect client construction.
src/vip/clients/workbench.py Threads optional httpx.Auth into Workbench client construction.
src/vip/clients/packagemanager.py Threads optional httpx.Auth into Package Manager client construction.
src/vip/client_auth.py New module: registry + builder for IdP-keyed httpx.Auth factories.
src/vip/cli.py Updates --idp help text to include "snowflake".
src/vip/auth.py Expands supported IdP messaging and includes "snowflake" in docs/errors.
src/vip_tests/conftest.py Injects registry-provided httpx.Auth into product clients; relaxes Connect API-key requirement when auth is provided.
selftests/test_idp.py Adds tests covering Snowflake IdP strategy dispatch and looping/consent behavior.
selftests/test_client_auth.py Adds tests for registry behavior and BaseClient auth injection.
Comments suppressed due to low confidence (1)

src/vip/clients/base.py:58

  • BaseClient's docstring says auth takes precedence over auth_header_value, but the implementation always sets the Authorization header when auth_header_value is non-empty, even if a custom httpx.Auth is provided. This can result in sending the static API key/token alongside (or instead of) the custom auth scheme, which is especially problematic for per-host schemes like Snowflake ingress auth.
        headers: dict[str, str] = {}
        if auth_header_value:
            headers["Authorization"] = auth_header_value

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

Comment thread vip.toml.example
# Identity provider for --headless-auth: "keycloak", "okta", "snowflake"
# Only required when provider is "oidc", "saml", or "oauth2".
# For Snowflake Native App deployments, use provider = "oauth2" and
# idp = "snowflake".
@github-actions
Copy link
Copy Markdown

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.

2 participants