Skip to content

feat: add Google Domain-Wide Delegation (GOOGLE_DWD) auth support#153

Merged
Avinash-Kamath merged 12 commits into
mainfrom
feat/google-dwd-support
May 12, 2026
Merged

feat: add Google Domain-Wide Delegation (GOOGLE_DWD) auth support#153
Avinash-Kamath merged 12 commits into
mainfrom
feat/google-dwd-support

Conversation

@Avinash-Kamath

@Avinash-Kamath Avinash-Kamath commented May 7, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add GoogleDWDAuth message to proto and regenerate connected_accounts_pb2.py
  • Add google_dwd branch to CreateConnectedAccountRequest.to_proto() and UpdateConnectedAccountRequest.to_proto()
  • Switch ConnectedAccount.from_proto() to use WhichOneof("details") for correct deserialization of all auth types including google_dwd
  • Add 5 unit tests covering request serialization, response deserialization, token_expires_at absence, and no cross-contamination with oauth_token

Test plan

  • python3 -m pytest tests/test_actions.py -k "google_dwd" -v — 5 tests pass
  • Create a GOOGLE_DWD connected account: authorization_details={"google_dwd": {"subject": "user@domain.com"}}
  • GET the connected account and verify authorization_details.google_dwd contains subject, access_token, scopes, token_expires_at

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added Google DWD (Domain-Wide Delegation) authorization support for connected accounts with validation
    • Added environment connection management: list app connections with filtering and pagination, retrieve connection details, create and update environment connections
  • Tests

    • Comprehensive coverage added for Google DWD authorization workflows and environment connection operations

Review Change Stack

- Add GoogleDWDAuth message and regenerate connected_accounts_pb2.py
- Add google_dwd branch to CreateConnectedAccountRequest.to_proto()
- Add google_dwd branch to UpdateConnectedAccountRequest.to_proto()
- Switch ConnectedAccount.from_proto() to use WhichOneof("details")
- Add 5 unit tests for request/response serialization and oauth_token isolation
@coderabbitai

coderabbitai Bot commented May 7, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This PR adds Google Domain-Wide Delegation (DWD) authorization support for connected accounts and extends the ConnectionClient with four new methods for managing environment and app-level connections. Request models validate and serialize google_dwd payloads; response models deserialize the authorization details oneof and convert token expiry timestamps. Tests cover DWD serialization, deserialization, and integration scenarios including HubSpot OAuth and GOOGLE_DWD environment connections. Makefile convenience targets enable running single test files or specific test cases.

Changes

Google DWD Authorization Serialization & Deserialization

Layer / File(s) Summary
Request serialization for google_dwd with subject validation
scalekit/actions/models/requests/create_connected_account_request.py, scalekit/actions/models/requests/update_connected_account_request.py
CreateConnectedAccountRequest.to_proto() and UpdateConnectedAccountRequest.to_proto() import GoogleDWDAuth and add validation branches that require google_dwd to be a dict with non-empty subject, then construct AuthorizationDetails(google_dwd=GoogleDWDAuth(subject=...)).
Response deserialization for google_dwd
scalekit/actions/models/responses/get_connected_account_auth_response.py
ConnectedAccount.from_proto() uses HasField and WhichOneof to detect and deserialize each authorization_details variant (oauth_token, static_auth, google_dwd) into the corresponding output dict, converting token_expires_at to datetime when present.
Unit tests for google_dwd authorization
tests/test_actions.py
Tests verify request proto serialization, response proto deserialization (including token_expires_at present vs. missing), and ensure google_dwd is not populated when oauth_token is used instead.

Connection API Client Methods & Integration Tests

Layer / File(s) Summary
ConnectionClient gRPC methods
scalekit/connection.py
Adds list_app_connections (with pagination and provider filtering), get_environment_connection, create_environment_connection (with optional Flags), and update_environment_connection, each wrapping corresponding ConnectionServiceStub RPCs via core_client.grpc_exec.
Connection integration tests for environment and app-level CRUD
tests/test_connection.py
Tests create environment connections with Flags(is_app=True), list app connections (with/without provider filter, with page_size limit), retrieve by id, update OAuth credentials while preserving URIs, and create/update/get GOOGLE_DWD connections with service account configuration.
Makefile test helper targets
Makefile
Adds test-file target to run unittest discovery for a FILE pattern and test-case target to run a specific CASE, both with required-variable validation.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 A fluffy hop through DWD lands,
With GoogleDWDAuth in steady hands,
Connections list, create, and gleam—
The ConnectionClient's a rabbit's dream!
Test targets swift, from file to case,
Makefile magic sets the pace! 🌟

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 70.83% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the primary change: adding support for Google Domain-Wide Delegation (GOOGLE_DWD) authentication.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/google-dwd-support

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scalekit/actions/models/requests/create_connected_account_request.py`:
- Around line 49-52: The branch that builds GoogleDWDAuth must validate that
google_dwd.subject is provided instead of defaulting to ""; in the block where
you read self.authorization_details["google_dwd"] (referencing
self.authorization_details, dwd_data, GoogleDWDAuth, AuthorizationDetails),
check that dwd_data.get("subject") is present and non-empty and raise a clear
exception (e.g., ValueError) if missing; only construct
GoogleDWDAuth(subject=...) and AuthorizationDetails(google_dwd=...) after the
subject validation succeeds.

In `@scalekit/actions/models/requests/update_connected_account_request.py`:
- Around line 50-53: The branch handling
self.authorization_details["google_dwd"] currently defaults
GoogleDWDAuth(subject="") which allows blank subjects; change this to validate
and fail fast: in the update logic where authorization_details and "google_dwd"
are checked, extract dwd_data and assert that dwd_data.get("subject") exists and
is non-empty (e.g., raise ValueError or a RequestValidationError) before
constructing GoogleDWDAuth(subject=...), and only then build
AuthorizationDetails(google_dwd=google_dwd); reference symbols:
self.authorization_details, "google_dwd", dwd_data, GoogleDWDAuth,
AuthorizationDetails.

In `@sdk.py`:
- Around line 65-73: Update the docblock to remove the stale claim that
GOOGLE_DWD is absent from ConnectorType and instead state that the regenerated
proto includes ConnectorType.GOOGLE_DWD; clarify that callers can rely on
ConnectorType (e.g., ConnectorType.GOOGLE_DWD) rather than the prior workaround,
and reflect the current serializer behavior that create/update requests only
write subject (not access_token/scopes/token_expires_at). Keep the existing
guidance about using WhichOneof("details") in from_proto() where appropriate and
retaining the token_expires_at HasField("token_expires_at") guard before calling
ToDatetime() to avoid AttributeError.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 46a70b52-3497-49b4-bf57-aa21b5c66e1d

📥 Commits

Reviewing files that changed from the base of the PR and between 67e7171 and 1430a7b.

📒 Files selected for processing (8)
  • scalekit/actions/models/requests/create_connected_account_request.py
  • scalekit/actions/models/requests/update_connected_account_request.py
  • scalekit/actions/models/responses/get_connected_account_auth_response.py
  • scalekit/v1/connected_accounts/connected_accounts_pb2.py
  • scalekit/v1/connected_accounts/connected_accounts_pb2.pyi
  • scalekit/v1/connected_accounts/connected_accounts_pb2_grpc.py
  • sdk.py
  • tests/test_actions.py

Comment thread sdk.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scalekit/actions/models/requests/create_connected_account_request.py`:
- Around line 49-55: The code assumes self.authorization_details["google_dwd"]
is a dict; add a type/shape guard for dwd_data before accessing .get so non-dict
values produce a clear validation error: check that self.authorization_details
and its "google_dwd" entry is a mapping (e.g., isinstance(dwd_data, dict)) and
if not raise ValueError("authorization_details.google_dwd must be an object");
then read subject, validate subject presence and raise the existing ValueError
if missing, and proceed to construct GoogleDWDAuth and AuthorizationDetails as
before (references: authorization_details, dwd_data, subject, GoogleDWDAuth,
AuthorizationDetails).

In `@scalekit/actions/models/requests/update_connected_account_request.py`:
- Around line 50-56: The code assumes authorization_details["google_dwd"] is a
dict and calls dwd_data.get(...), which can raise AttributeError for bad input;
in the update serialization branch (the elif handling authorization_details and
"google_dwd") validate that dwd_data is a dict (e.g., if not
isinstance(dwd_data, dict): raise ValueError("authorization_details.google_dwd
must be an object")), then proceed to extract subject and raise the existing
ValueError if missing, before constructing GoogleDWDAuth and
AuthorizationDetails; update the branch that sets google_dwd =
GoogleDWDAuth(subject=subject) / auth_details =
AuthorizationDetails(google_dwd=google_dwd) accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9a96e0c5-555d-4366-b95e-536b353b90ee

📥 Commits

Reviewing files that changed from the base of the PR and between 1430a7b and 206fb14.

📒 Files selected for processing (2)
  • scalekit/actions/models/requests/create_connected_account_request.py
  • scalekit/actions/models/requests/update_connected_account_request.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
tests/test_actions.py (1)

784-860: ⚡ Quick win

Add a negative test for missing google_dwd.subject validation.

The happy-path coverage is good, but Line 784–Line 860 doesn’t assert the new validation behavior when subject is absent. Add one failure-case test so the validation fix can’t regress silently.

Proposed test addition
+    def test_google_dwd_create_request_missing_subject_raises(self):
+        """CreateConnectedAccountRequest rejects google_dwd without subject"""
+        from scalekit.actions.models.requests.create_connected_account_request import CreateConnectedAccountRequest
+
+        req = CreateConnectedAccountRequest(
+            connection_name="GDWD",
+            identifier="admin@example.com",
+            authorization_details={"google_dwd": {}}
+        )
+
+        with self.assertRaises(ValueError) as context:
+            req.to_proto()
+        self.assertIn("subject", str(context.exception).lower())
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_actions.py` around lines 784 - 860, Add a negative test that
ensures missing google_dwd.subject triggers validation: create a test (e.g.
test_google_dwd_create_request_missing_subject_raises) that constructs
CreateConnectedAccountRequest with authorization_details={"google_dwd": {}} and
assert that calling req.to_proto() raises an exception (use
assertRaises(ValueError) or the specific validation exception used in your
codebase). Reference CreateConnectedAccountRequest and to_proto() so the test
fails if the new validation is removed or bypassed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@tests/test_actions.py`:
- Around line 784-860: Add a negative test that ensures missing
google_dwd.subject triggers validation: create a test (e.g.
test_google_dwd_create_request_missing_subject_raises) that constructs
CreateConnectedAccountRequest with authorization_details={"google_dwd": {}} and
assert that calling req.to_proto() raises an exception (use
assertRaises(ValueError) or the specific validation exception used in your
codebase). Reference CreateConnectedAccountRequest and to_proto() so the test
fails if the new validation is removed or bypassed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 06f5fd51-12af-441e-bee6-40e60c2df6c1

📥 Commits

Reviewing files that changed from the base of the PR and between 206fb14 and 7d44800.

📒 Files selected for processing (1)
  • tests/test_actions.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scalekit/actions/actions.py`:
- Around line 857-961: These connection methods assume self._connection_client
exists and will raise an unhelpful AttributeError if not; add a guard at the
start of list_connections, get_connection, create_connection, and
update_connection to check if self._connection_client is truthy and, if not,
raise a clear error (e.g., ValueError or a specific SDK exception) with a
message like "connection client is not configured; initialize ActionClient with
a connection_client" so callers get a descriptive failure instead of an
AttributeError.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ae380591-a805-4f81-bf0c-a9cff6702c96

📥 Commits

Reviewing files that changed from the base of the PR and between 7d44800 and 05a08c7.

📒 Files selected for processing (4)
  • Makefile
  • scalekit/actions/actions.py
  • scalekit/connection.py
  • tests/test_connection.py

Comment thread scalekit/actions/actions.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
tests/test_connection.py (1)

163-184: ⚡ Quick win

Assert the client_secret update too

This test updates client_secret but never verifies it. Add an assertion (or redaction-safe equivalent) so the update path is fully covered.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_connection.py` around lines 163 - 184, The test updates the OAuth
client_secret but doesn't assert it; update the test in tests/test_connection.py
(the block creating UpdateConnection and OAuthConnectionConfig) to also verify
the client_secret change by asserting conn.oauth_config.client_secret is updated
— use a redaction-safe check if values are masked (e.g., compare presence/length
or a masked placeholder) so the update path for UpdateConnection and
OAuthConnectionConfig.client_secret is fully covered.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/test_connection.py`:
- Line 205: The test currently embeds a PEM-looking private key in the
fake_sa_json (variable fake_sa_json), which triggers secret scanners; replace
the PEM-formatted value with a non-PEM placeholder (e.g.,
"private_key":"REDACTED_PRIVATE_KEY" or a random base64 string that does not
include "BEGIN" / "END" headers) and, if the test asserts parsing of a key
format, adjust the assertion/mocks in the test to accept the placeholder value
so behavior remains the same; update only the fake_sa_json string and any
dependent expectations in the test (refer to fake_sa_json) to remove any
real/PEM headers and avoid secret-scanner triggers.
- Around line 95-232: Tests create environment/app connections in methods like
test_create_environment_connection_with_flags,
test_list_app_connections_with_provider_filter, test_get_custom_connection,
test_update_environment_connection, and test_google_dwd_create_update_get but do
not unregister them; update the test class to track created connection IDs
(e.g., add self._created_connection_ids initialized in setUp) and after each
create_environment_connection call append the returned connection.id (from
create_response[0].connection.id) to that list; modify tearDown to iterate over
self._created_connection_ids and call the API to delete each environment
connection (e.g.,
self.scalekit_client.connection.delete_environment_connection(connection_id=...))
swallowing/not failing on 404/errors so teardown is robust, leaving the existing
self.conn_id deletion intact.

---

Nitpick comments:
In `@tests/test_connection.py`:
- Around line 163-184: The test updates the OAuth client_secret but doesn't
assert it; update the test in tests/test_connection.py (the block creating
UpdateConnection and OAuthConnectionConfig) to also verify the client_secret
change by asserting conn.oauth_config.client_secret is updated — use a
redaction-safe check if values are masked (e.g., compare presence/length or a
masked placeholder) so the update path for UpdateConnection and
OAuthConnectionConfig.client_secret is fully covered.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8aa391cb-46f3-4c64-994a-e1f94fadb0dc

📥 Commits

Reviewing files that changed from the base of the PR and between 05a08c7 and 877a33c.

📒 Files selected for processing (3)
  • scalekit/actions/models/requests/create_connected_account_request.py
  • scalekit/actions/models/requests/update_connected_account_request.py
  • tests/test_connection.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • scalekit/actions/models/requests/update_connected_account_request.py
  • scalekit/actions/models/requests/create_connected_account_request.py

Comment thread tests/test_connection.py
Comment thread tests/test_connection.py
@Avinash-Kamath Avinash-Kamath merged commit d0535c4 into main May 12, 2026
2 checks passed
@Avinash-Kamath Avinash-Kamath deleted the feat/google-dwd-support branch May 12, 2026 14:21
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