Skip to content

Commit d0535c4

Browse files
feat: add Google Domain-Wide Delegation (GOOGLE_DWD) auth support (#153)
* feat: add Google Domain-Wide Delegation (GOOGLE_DWD) auth support - 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 * chore: remove implementation plan file * fix: validate google_dwd subject before proto serialization * add create and upadte connection * add make tests for quick testing * remove some unused funcs * add google DWD test * add refrence * increase sleep time to 1 sec
1 parent 89d7567 commit d0535c4

9 files changed

Lines changed: 655 additions & 8 deletions

File tree

Makefile

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,3 +116,13 @@ lint: create-venv
116116
test: create-venv
117117
@echo "Running tests..."
118118
$(VENV_PYTHON) -m unittest discover -s tests -p "test_*.py" -v
119+
120+
test-file: create-venv
121+
@if [ -z "$(FILE)" ]; then echo "Usage: make test-file FILE=test_actions.py" && exit 1; fi
122+
@echo "Running $(FILE)..."
123+
$(VENV_PYTHON) -m unittest discover -s tests -p "$(FILE)" -v
124+
125+
test-case: create-venv
126+
@if [ -z "$(CASE)" ]; then echo "Usage: make test-case CASE=test_connection.TestConnection.test_create_environment_connection" && exit 1; fi
127+
@echo "Running $(CASE)..."
128+
cd tests && $(abspath $(VENV_PYTHON)) -m unittest $(CASE) -v

REFERENCE.md

Lines changed: 321 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1398,6 +1398,327 @@ scalekit_client.connection.delete_connection('org_123456', 'conn_123456')
13981398
</dl>
13991399

14001400

1401+
</dd>
1402+
</dl>
1403+
</details>
1404+
1405+
<details><summary><code>client.connection.<a href="https://github.com/scalekit-inc/scalekit-sdk-python/blob/main/scalekit/connection.py">list_app_connections</a>(page_size?, page_token?, provider?) -> ListAppConnectionsResponse</code></summary>
1406+
<dl>
1407+
<dd>
1408+
1409+
#### 📝 Description
1410+
1411+
<dl>
1412+
<dd>
1413+
1414+
<dl>
1415+
<dd>
1416+
1417+
Lists all environment-level app connections. These are connections configured at the environment level (not tied to a specific organization), such as OAuth integrations (e.g., HubSpot) or Google Domain-Wide Delegation connections.
1418+
</dd>
1419+
</dl>
1420+
</dd>
1421+
</dl>
1422+
1423+
#### 🔌 Usage
1424+
1425+
<dl>
1426+
<dd>
1427+
1428+
<dl>
1429+
<dd>
1430+
1431+
```python
1432+
# List all app connections
1433+
response = scalekit_client.connection.list_app_connections()
1434+
1435+
for conn in response[0].connections:
1436+
print(f"Connection: {conn.id}, Provider: {conn.provider_key}")
1437+
1438+
# Filter by provider
1439+
response = scalekit_client.connection.list_app_connections(provider='HUBSPOT', page_size=10)
1440+
```
1441+
</dd>
1442+
</dl>
1443+
</dd>
1444+
</dl>
1445+
1446+
#### ⚙️ Parameters
1447+
1448+
<dl>
1449+
<dd>
1450+
1451+
<dl>
1452+
<dd>
1453+
1454+
**page_size:** `Optional[int]` - Maximum number of connections to return (max 30)
1455+
1456+
</dd>
1457+
</dl>
1458+
1459+
<dl>
1460+
<dd>
1461+
1462+
**page_token:** `Optional[str]` - Pagination token from a previous response
1463+
1464+
</dd>
1465+
</dl>
1466+
1467+
<dl>
1468+
<dd>
1469+
1470+
**provider:** `Optional[str]` - Filter by provider key (e.g., `'HUBSPOT'`, `'GOOGLEDWD'`)
1471+
1472+
</dd>
1473+
</dl>
1474+
</dd>
1475+
</dl>
1476+
1477+
1478+
</dd>
1479+
</dl>
1480+
</details>
1481+
1482+
<details><summary><code>client.connection.<a href="https://github.com/scalekit-inc/scalekit-sdk-python/blob/main/scalekit/connection.py">get_environment_connection</a>(connection_id) -> GetConnectionResponse</code></summary>
1483+
<dl>
1484+
<dd>
1485+
1486+
#### 📝 Description
1487+
1488+
<dl>
1489+
<dd>
1490+
1491+
<dl>
1492+
<dd>
1493+
1494+
Retrieves an environment-level connection by its ID.
1495+
</dd>
1496+
</dl>
1497+
</dd>
1498+
</dl>
1499+
1500+
#### 🔌 Usage
1501+
1502+
<dl>
1503+
<dd>
1504+
1505+
<dl>
1506+
<dd>
1507+
1508+
```python
1509+
response = scalekit_client.connection.get_environment_connection('conn_123456')
1510+
conn = response[0].connection
1511+
print(f"Provider: {conn.provider_key}, Type: {conn.type}")
1512+
```
1513+
</dd>
1514+
</dl>
1515+
</dd>
1516+
</dl>
1517+
1518+
#### ⚙️ Parameters
1519+
1520+
<dl>
1521+
<dd>
1522+
1523+
<dl>
1524+
<dd>
1525+
1526+
**connection_id:** `str` - The ID of the environment-level connection to retrieve
1527+
1528+
</dd>
1529+
</dl>
1530+
</dd>
1531+
</dl>
1532+
1533+
1534+
</dd>
1535+
</dl>
1536+
</details>
1537+
1538+
<details><summary><code>client.connection.<a href="https://github.com/scalekit-inc/scalekit-sdk-python/blob/main/scalekit/connection.py">create_environment_connection</a>(connection, flags?) -> CreateConnectionResponse</code></summary>
1539+
<dl>
1540+
<dd>
1541+
1542+
#### 📝 Description
1543+
1544+
<dl>
1545+
<dd>
1546+
1547+
<dl>
1548+
<dd>
1549+
1550+
Creates a new environment-level connection. Supports OAuth connections (e.g., HubSpot) and Google Domain-Wide Delegation (GOOGLE_DWD) connections. Pass `Flags(is_app=True)` to mark the connection as an app-level integration.
1551+
</dd>
1552+
</dl>
1553+
</dd>
1554+
</dl>
1555+
1556+
#### 🔌 Usage
1557+
1558+
<dl>
1559+
<dd>
1560+
1561+
<dl>
1562+
<dd>
1563+
1564+
```python
1565+
from scalekit.v1.connections.connections_pb2 import (
1566+
CreateConnection, ConnectionType, Flags
1567+
)
1568+
1569+
# Create an OAuth app connection (e.g., HubSpot)
1570+
response = scalekit_client.connection.create_environment_connection(
1571+
connection=CreateConnection(
1572+
provider_key='HUBSPOT',
1573+
type=ConnectionType.OAUTH
1574+
),
1575+
flags=Flags(is_app=True)
1576+
)
1577+
conn = response[0].connection
1578+
print(f"Created: {conn.id}, Key: {conn.key_id}")
1579+
1580+
# Create a Google Domain-Wide Delegation connection
1581+
response = scalekit_client.connection.create_environment_connection(
1582+
connection=CreateConnection(
1583+
provider_key='GOOGLEDWD',
1584+
type=ConnectionType.GOOGLE_DWD,
1585+
key_id='my-gdwd-connection'
1586+
),
1587+
flags=Flags(is_app=True)
1588+
)
1589+
```
1590+
</dd>
1591+
</dl>
1592+
</dd>
1593+
</dl>
1594+
1595+
#### ⚙️ Parameters
1596+
1597+
<dl>
1598+
<dd>
1599+
1600+
<dl>
1601+
<dd>
1602+
1603+
**connection:** `CreateConnection` - Connection configuration object
1604+
- `provider_key: str` - Provider identifier (e.g., `'HUBSPOT'`, `'GOOGLEDWD'`)
1605+
- `type: ConnectionType` - Connection type (`ConnectionType.OAUTH` or `ConnectionType.GOOGLE_DWD`)
1606+
- `key_id: Optional[str]` - Unique key identifier for the connection (required for GOOGLE_DWD)
1607+
1608+
</dd>
1609+
</dl>
1610+
1611+
<dl>
1612+
<dd>
1613+
1614+
**flags:** `Optional[Flags]` - Connection flags
1615+
- `is_app: bool` - Set to `True` to create an environment-level app connection
1616+
1617+
</dd>
1618+
</dl>
1619+
</dd>
1620+
</dl>
1621+
1622+
1623+
</dd>
1624+
</dl>
1625+
</details>
1626+
1627+
<details><summary><code>client.connection.<a href="https://github.com/scalekit-inc/scalekit-sdk-python/blob/main/scalekit/connection.py">update_environment_connection</a>(connection_id, connection) -> UpdateConnectionResponse</code></summary>
1628+
<dl>
1629+
<dd>
1630+
1631+
#### 📝 Description
1632+
1633+
<dl>
1634+
<dd>
1635+
1636+
<dl>
1637+
<dd>
1638+
1639+
Updates an environment-level connection. This is a PATCH operation — only fields provided in the `UpdateConnection` object are changed; unspecified fields remain unchanged. Always include the `type` field to avoid server-side errors.
1640+
</dd>
1641+
</dl>
1642+
</dd>
1643+
</dl>
1644+
1645+
#### 🔌 Usage
1646+
1647+
<dl>
1648+
<dd>
1649+
1650+
<dl>
1651+
<dd>
1652+
1653+
```python
1654+
from scalekit.v1.connections.connections_pb2 import (
1655+
UpdateConnection, ConnectionType, OAuthConnectionConfig, GoogleDWDConfig
1656+
)
1657+
1658+
# Update OAuth connection credentials
1659+
response = scalekit_client.connection.update_environment_connection(
1660+
connection_id='conn_123456',
1661+
connection=UpdateConnection(
1662+
provider_key='HUBSPOT',
1663+
key_id='hubspot-key',
1664+
type=ConnectionType.OAUTH,
1665+
oauth_config=OAuthConnectionConfig(
1666+
client_id={'value': 'my-client-id'},
1667+
client_secret={'value': 'my-client-secret'}
1668+
)
1669+
)
1670+
)
1671+
1672+
# Update Google DWD connection
1673+
response = scalekit_client.connection.update_environment_connection(
1674+
connection_id='conn_123456',
1675+
connection=UpdateConnection(
1676+
provider_key='GOOGLEDWD',
1677+
key_id='my-gdwd-connection',
1678+
type=ConnectionType.GOOGLE_DWD,
1679+
google_dwd_config=GoogleDWDConfig(
1680+
service_account_json={'value': '{"type":"service_account",...}'},
1681+
scopes=['https://www.googleapis.com/auth/admin.directory.user.readonly'],
1682+
token_uri={'value': 'https://oauth2.googleapis.com/token'}
1683+
)
1684+
)
1685+
)
1686+
conn = response[0].connection
1687+
```
1688+
</dd>
1689+
</dl>
1690+
</dd>
1691+
</dl>
1692+
1693+
#### ⚙️ Parameters
1694+
1695+
<dl>
1696+
<dd>
1697+
1698+
<dl>
1699+
<dd>
1700+
1701+
**connection_id:** `str` - The ID of the environment-level connection to update
1702+
1703+
</dd>
1704+
</dl>
1705+
1706+
<dl>
1707+
<dd>
1708+
1709+
**connection:** `UpdateConnection` - Fields to update on the connection
1710+
- `provider_key: str` - Provider identifier (e.g., `'HUBSPOT'`, `'GOOGLEDWD'`)
1711+
- `key_id: str` - Key identifier of the connection
1712+
- `type: ConnectionType` - Connection type — must always be provided (`ConnectionType.OAUTH` or `ConnectionType.GOOGLE_DWD`)
1713+
- `oauth_config: Optional[OAuthConnectionConfig]` - OAuth credentials to update (`client_id`, `client_secret`)
1714+
- `google_dwd_config: Optional[GoogleDWDConfig]` - Google DWD config to update (`service_account_json`, `scopes`, `token_uri`)
1715+
1716+
</dd>
1717+
</dl>
1718+
</dd>
1719+
</dl>
1720+
1721+
14011722
</dd>
14021723
</dl>
14031724
</details>

scalekit/actions/models/requests/create_connected_account_request.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
CreateConnectedAccount,
55
AuthorizationDetails,
66
OauthToken,
7-
StaticAuth
7+
StaticAuth,
8+
GoogleDWDAuth
89
)
910
from google.protobuf import struct_pb2
1011

@@ -44,6 +45,16 @@ def to_proto(self) -> CreateConnectedAccount:
4445
struct_details.update(static_data)
4546
static_auth = StaticAuth(details=struct_details)
4647
auth_details = AuthorizationDetails(static_auth=static_auth)
48+
49+
elif self.authorization_details and "google_dwd" in self.authorization_details:
50+
dwd_data = self.authorization_details["google_dwd"]
51+
if not isinstance(dwd_data, dict):
52+
raise ValueError("authorization_details.google_dwd must be an object")
53+
subject = dwd_data.get("subject")
54+
if not subject:
55+
raise ValueError("authorization_details.google_dwd.subject is required")
56+
google_dwd = GoogleDWDAuth(subject=subject)
57+
auth_details = AuthorizationDetails(google_dwd=google_dwd)
4758

4859
# Handle api_config if provided
4960
api_config_struct = None
@@ -58,4 +69,4 @@ def to_proto(self) -> CreateConnectedAccount:
5869

5970
class Config:
6071
"""Pydantic configuration"""
61-
validate_assignment = True
72+
validate_assignment = True

0 commit comments

Comments
 (0)