workos 5.23.0__py3-none-any.whl → 5.26.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- workos/__about__.py +1 -1
- workos/_base_client.py +5 -0
- workos/async_client.py +16 -0
- workos/client.py +16 -0
- workos/organization_domains.py +179 -0
- workos/types/events/event.py +16 -1
- workos/types/events/event_model.py +1 -1
- workos/types/events/event_type.py +3 -0
- workos/types/events/organization_domain_verification_failed_payload.py +1 -1
- workos/types/list_resource.py +2 -0
- workos/types/organization_domains/__init__.py +1 -0
- workos/types/{organizations → organization_domains}/organization_domain.py +3 -0
- workos/types/organizations/__init__.py +3 -1
- workos/types/organizations/organization.py +1 -1
- workos/types/organizations/organization_common.py +1 -1
- workos/types/vault/__init__.py +2 -0
- workos/types/vault/key.py +25 -0
- workos/types/vault/object.py +38 -0
- workos/types/webhooks/webhook.py +1 -1
- workos/utils/crypto_provider.py +39 -0
- workos/vault.py +515 -0
- {workos-5.23.0.dist-info → workos-5.26.0.dist-info}/METADATA +1 -1
- {workos-5.23.0.dist-info → workos-5.26.0.dist-info}/RECORD +26 -19
- {workos-5.23.0.dist-info → workos-5.26.0.dist-info}/LICENSE +0 -0
- {workos-5.23.0.dist-info → workos-5.26.0.dist-info}/WHEEL +0 -0
- {workos-5.23.0.dist-info → workos-5.26.0.dist-info}/top_level.txt +0 -0
workos/__about__.py
CHANGED
workos/_base_client.py
CHANGED
|
@@ -11,6 +11,7 @@ from workos.directory_sync import DirectorySyncModule
|
|
|
11
11
|
from workos.events import EventsModule
|
|
12
12
|
from workos.mfa import MFAModule
|
|
13
13
|
from workos.organizations import OrganizationsModule
|
|
14
|
+
from workos.organization_domains import OrganizationDomainsModule
|
|
14
15
|
from workos.passwordless import PasswordlessModule
|
|
15
16
|
from workos.portal import PortalModule
|
|
16
17
|
from workos.sso import SSOModule
|
|
@@ -88,6 +89,10 @@ class BaseClient(ClientConfiguration):
|
|
|
88
89
|
@abstractmethod
|
|
89
90
|
def organizations(self) -> OrganizationsModule: ...
|
|
90
91
|
|
|
92
|
+
@property
|
|
93
|
+
@abstractmethod
|
|
94
|
+
def organization_domains(self) -> OrganizationDomainsModule: ...
|
|
95
|
+
|
|
91
96
|
@property
|
|
92
97
|
@abstractmethod
|
|
93
98
|
def passwordless(self) -> PasswordlessModule: ...
|
workos/async_client.py
CHANGED
|
@@ -7,6 +7,7 @@ from workos.events import AsyncEvents
|
|
|
7
7
|
from workos.fga import FGAModule
|
|
8
8
|
from workos.mfa import MFAModule
|
|
9
9
|
from workos.organizations import AsyncOrganizations
|
|
10
|
+
from workos.organization_domains import AsyncOrganizationDomains
|
|
10
11
|
from workos.passwordless import PasswordlessModule
|
|
11
12
|
from workos.portal import PortalModule
|
|
12
13
|
from workos.sso import AsyncSSO
|
|
@@ -14,6 +15,7 @@ from workos.user_management import AsyncUserManagement
|
|
|
14
15
|
from workos.utils.http_client import AsyncHTTPClient
|
|
15
16
|
from workos.webhooks import WebhooksModule
|
|
16
17
|
from workos.widgets import WidgetsModule
|
|
18
|
+
from workos.vault import VaultModule
|
|
17
19
|
|
|
18
20
|
|
|
19
21
|
class AsyncClient(BaseClient):
|
|
@@ -79,6 +81,14 @@ class AsyncClient(BaseClient):
|
|
|
79
81
|
self._organizations = AsyncOrganizations(self._http_client)
|
|
80
82
|
return self._organizations
|
|
81
83
|
|
|
84
|
+
@property
|
|
85
|
+
def organization_domains(self) -> AsyncOrganizationDomains:
|
|
86
|
+
if not getattr(self, "_organization_domains", None):
|
|
87
|
+
self._organization_domains = AsyncOrganizationDomains(
|
|
88
|
+
http_client=self._http_client, client_configuration=self
|
|
89
|
+
)
|
|
90
|
+
return self._organization_domains
|
|
91
|
+
|
|
82
92
|
@property
|
|
83
93
|
def passwordless(self) -> PasswordlessModule:
|
|
84
94
|
raise NotImplementedError(
|
|
@@ -112,3 +122,9 @@ class AsyncClient(BaseClient):
|
|
|
112
122
|
raise NotImplementedError(
|
|
113
123
|
"Widgets APIs are not yet supported in the async client."
|
|
114
124
|
)
|
|
125
|
+
|
|
126
|
+
@property
|
|
127
|
+
def vault(self) -> VaultModule:
|
|
128
|
+
raise NotImplementedError(
|
|
129
|
+
"Vault APIs are not yet supported in the async client."
|
|
130
|
+
)
|
workos/client.py
CHANGED
|
@@ -5,6 +5,7 @@ from workos.audit_logs import AuditLogs
|
|
|
5
5
|
from workos.directory_sync import DirectorySync
|
|
6
6
|
from workos.fga import FGA
|
|
7
7
|
from workos.organizations import Organizations
|
|
8
|
+
from workos.organization_domains import OrganizationDomains
|
|
8
9
|
from workos.passwordless import Passwordless
|
|
9
10
|
from workos.portal import Portal
|
|
10
11
|
from workos.sso import SSO
|
|
@@ -14,6 +15,7 @@ from workos.events import Events
|
|
|
14
15
|
from workos.user_management import UserManagement
|
|
15
16
|
from workos.utils.http_client import SyncHTTPClient
|
|
16
17
|
from workos.widgets import Widgets
|
|
18
|
+
from workos.vault import Vault
|
|
17
19
|
|
|
18
20
|
|
|
19
21
|
class SyncClient(BaseClient):
|
|
@@ -79,6 +81,14 @@ class SyncClient(BaseClient):
|
|
|
79
81
|
self._organizations = Organizations(self._http_client)
|
|
80
82
|
return self._organizations
|
|
81
83
|
|
|
84
|
+
@property
|
|
85
|
+
def organization_domains(self) -> OrganizationDomains:
|
|
86
|
+
if not getattr(self, "_organization_domains", None):
|
|
87
|
+
self._organization_domains = OrganizationDomains(
|
|
88
|
+
http_client=self._http_client, client_configuration=self
|
|
89
|
+
)
|
|
90
|
+
return self._organization_domains
|
|
91
|
+
|
|
82
92
|
@property
|
|
83
93
|
def passwordless(self) -> Passwordless:
|
|
84
94
|
if not getattr(self, "_passwordless", None):
|
|
@@ -116,3 +126,9 @@ class SyncClient(BaseClient):
|
|
|
116
126
|
if not getattr(self, "_widgets", None):
|
|
117
127
|
self._widgets = Widgets(http_client=self._http_client)
|
|
118
128
|
return self._widgets
|
|
129
|
+
|
|
130
|
+
@property
|
|
131
|
+
def vault(self) -> Vault:
|
|
132
|
+
if not getattr(self, "_vault", None):
|
|
133
|
+
self._vault = Vault(http_client=self._http_client)
|
|
134
|
+
return self._vault
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
from typing import Protocol
|
|
2
|
+
from workos._client_configuration import ClientConfiguration
|
|
3
|
+
from workos.types.organization_domains import OrganizationDomain
|
|
4
|
+
from workos.typing.sync_or_async import SyncOrAsync
|
|
5
|
+
from workos.utils.http_client import AsyncHTTPClient, SyncHTTPClient
|
|
6
|
+
from workos.utils.request_helper import (
|
|
7
|
+
REQUEST_METHOD_DELETE,
|
|
8
|
+
REQUEST_METHOD_GET,
|
|
9
|
+
REQUEST_METHOD_POST,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class OrganizationDomainsModule(Protocol):
|
|
14
|
+
"""Offers methods for managing organization domains."""
|
|
15
|
+
|
|
16
|
+
_client_configuration: ClientConfiguration
|
|
17
|
+
|
|
18
|
+
def get_organization_domain(
|
|
19
|
+
self, organization_domain_id: str
|
|
20
|
+
) -> SyncOrAsync[OrganizationDomain]:
|
|
21
|
+
"""Gets a single Organization Domain
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
organization_domain_id (str): Organization Domain unique identifier
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
OrganizationDomain: Organization Domain response from WorkOS
|
|
28
|
+
"""
|
|
29
|
+
...
|
|
30
|
+
|
|
31
|
+
def create_organization_domain(
|
|
32
|
+
self,
|
|
33
|
+
organization_id: str,
|
|
34
|
+
domain: str,
|
|
35
|
+
) -> SyncOrAsync[OrganizationDomain]:
|
|
36
|
+
"""Creates an Organization Domain
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
organization_id (str): Organization unique identifier
|
|
40
|
+
domain (str): Domain to be added to the organization
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
OrganizationDomain: Organization Domain response from WorkOS
|
|
44
|
+
"""
|
|
45
|
+
...
|
|
46
|
+
|
|
47
|
+
def verify_organization_domain(
|
|
48
|
+
self, organization_domain_id: str
|
|
49
|
+
) -> SyncOrAsync[OrganizationDomain]:
|
|
50
|
+
"""Verifies an Organization Domain
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
organization_domain_id (str): Organization Domain unique identifier
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
OrganizationDomain: Organization Domain response from WorkOS
|
|
57
|
+
"""
|
|
58
|
+
...
|
|
59
|
+
|
|
60
|
+
def delete_organization_domain(
|
|
61
|
+
self, organization_domain_id: str
|
|
62
|
+
) -> SyncOrAsync[None]:
|
|
63
|
+
"""Deletes a single Organization Domain
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
organization_domain_id (str): Organization Domain unique identifier
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
None
|
|
70
|
+
"""
|
|
71
|
+
...
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class OrganizationDomains:
|
|
75
|
+
"""Offers methods for managing organization domains."""
|
|
76
|
+
|
|
77
|
+
_http_client: SyncHTTPClient
|
|
78
|
+
_client_configuration: ClientConfiguration
|
|
79
|
+
|
|
80
|
+
def __init__(
|
|
81
|
+
self,
|
|
82
|
+
http_client: SyncHTTPClient,
|
|
83
|
+
client_configuration: ClientConfiguration,
|
|
84
|
+
):
|
|
85
|
+
self._http_client = http_client
|
|
86
|
+
self._client_configuration = client_configuration
|
|
87
|
+
|
|
88
|
+
def get_organization_domain(
|
|
89
|
+
self, organization_domain_id: str
|
|
90
|
+
) -> OrganizationDomain:
|
|
91
|
+
response = self._http_client.request(
|
|
92
|
+
f"organization_domains/{organization_domain_id}",
|
|
93
|
+
method=REQUEST_METHOD_GET,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
return OrganizationDomain.model_validate(response)
|
|
97
|
+
|
|
98
|
+
def create_organization_domain(
|
|
99
|
+
self,
|
|
100
|
+
organization_id: str,
|
|
101
|
+
domain: str,
|
|
102
|
+
) -> OrganizationDomain:
|
|
103
|
+
response = self._http_client.request(
|
|
104
|
+
"organization_domains",
|
|
105
|
+
method=REQUEST_METHOD_POST,
|
|
106
|
+
json={"organization_id": organization_id, "domain": domain},
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
return OrganizationDomain.model_validate(response)
|
|
110
|
+
|
|
111
|
+
def verify_organization_domain(
|
|
112
|
+
self, organization_domain_id: str
|
|
113
|
+
) -> OrganizationDomain:
|
|
114
|
+
response = self._http_client.request(
|
|
115
|
+
f"organization_domains/{organization_domain_id}/verify",
|
|
116
|
+
method=REQUEST_METHOD_POST,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
return OrganizationDomain.model_validate(response)
|
|
120
|
+
|
|
121
|
+
def delete_organization_domain(self, organization_domain_id: str) -> None:
|
|
122
|
+
self._http_client.request(
|
|
123
|
+
f"organization_domains/{organization_domain_id}",
|
|
124
|
+
method=REQUEST_METHOD_DELETE,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class AsyncOrganizationDomains:
|
|
129
|
+
"""Offers async methods for managing organization domains."""
|
|
130
|
+
|
|
131
|
+
_http_client: AsyncHTTPClient
|
|
132
|
+
_client_configuration: ClientConfiguration
|
|
133
|
+
|
|
134
|
+
def __init__(
|
|
135
|
+
self,
|
|
136
|
+
http_client: AsyncHTTPClient,
|
|
137
|
+
client_configuration: ClientConfiguration,
|
|
138
|
+
):
|
|
139
|
+
self._http_client = http_client
|
|
140
|
+
self._client_configuration = client_configuration
|
|
141
|
+
|
|
142
|
+
async def get_organization_domain(
|
|
143
|
+
self, organization_domain_id: str
|
|
144
|
+
) -> OrganizationDomain:
|
|
145
|
+
response = await self._http_client.request(
|
|
146
|
+
f"organization_domains/{organization_domain_id}",
|
|
147
|
+
method=REQUEST_METHOD_GET,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
return OrganizationDomain.model_validate(response)
|
|
151
|
+
|
|
152
|
+
async def create_organization_domain(
|
|
153
|
+
self,
|
|
154
|
+
organization_id: str,
|
|
155
|
+
domain: str,
|
|
156
|
+
) -> OrganizationDomain:
|
|
157
|
+
response = await self._http_client.request(
|
|
158
|
+
"organization_domains",
|
|
159
|
+
method=REQUEST_METHOD_POST,
|
|
160
|
+
json={"organization_id": organization_id, "domain": domain},
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
return OrganizationDomain.model_validate(response)
|
|
164
|
+
|
|
165
|
+
async def verify_organization_domain(
|
|
166
|
+
self, organization_domain_id: str
|
|
167
|
+
) -> OrganizationDomain:
|
|
168
|
+
response = await self._http_client.request(
|
|
169
|
+
f"organization_domains/{organization_domain_id}/verify",
|
|
170
|
+
method=REQUEST_METHOD_POST,
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
return OrganizationDomain.model_validate(response)
|
|
174
|
+
|
|
175
|
+
async def delete_organization_domain(self, organization_domain_id: str) -> None:
|
|
176
|
+
await self._http_client.request(
|
|
177
|
+
f"organization_domains/{organization_domain_id}",
|
|
178
|
+
method=REQUEST_METHOD_DELETE,
|
|
179
|
+
)
|
workos/types/events/event.py
CHANGED
|
@@ -38,7 +38,7 @@ from workos.types.events.organization_domain_verification_failed_payload import
|
|
|
38
38
|
)
|
|
39
39
|
from workos.types.events.session_created_payload import SessionCreatedPayload
|
|
40
40
|
from workos.types.organizations.organization_common import OrganizationCommon
|
|
41
|
-
from workos.types.
|
|
41
|
+
from workos.types.organization_domains import OrganizationDomain
|
|
42
42
|
from workos.types.roles.role import EventRole
|
|
43
43
|
from workos.types.sso.connection import Connection
|
|
44
44
|
from workos.types.user_management.email_verification import (
|
|
@@ -193,6 +193,18 @@ class OrganizationDomainVerifiedEvent(EventModel[OrganizationDomain]):
|
|
|
193
193
|
event: Literal["organization_domain.verified"]
|
|
194
194
|
|
|
195
195
|
|
|
196
|
+
class OrganizationDomainCreatedEvent(EventModel[OrganizationDomain]):
|
|
197
|
+
event: Literal["organization_domain.created"]
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
class OrganizationDomainUpdatedEvent(EventModel[OrganizationDomain]):
|
|
201
|
+
event: Literal["organization_domain.updated"]
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
class OrganizationDomainDeletedEvent(EventModel[OrganizationDomain]):
|
|
205
|
+
event: Literal["organization_domain.deleted"]
|
|
206
|
+
|
|
207
|
+
|
|
196
208
|
class OrganizationMembershipCreatedEvent(EventModel[OrganizationMembership]):
|
|
197
209
|
event: Literal["organization_membership.created"]
|
|
198
210
|
|
|
@@ -272,6 +284,9 @@ Event = Annotated[
|
|
|
272
284
|
OrganizationCreatedEvent,
|
|
273
285
|
OrganizationDeletedEvent,
|
|
274
286
|
OrganizationUpdatedEvent,
|
|
287
|
+
OrganizationDomainCreatedEvent,
|
|
288
|
+
OrganizationDomainDeletedEvent,
|
|
289
|
+
OrganizationDomainUpdatedEvent,
|
|
275
290
|
OrganizationDomainVerificationFailedEvent,
|
|
276
291
|
OrganizationDomainVerifiedEvent,
|
|
277
292
|
OrganizationMembershipCreatedEvent,
|
|
@@ -37,7 +37,7 @@ from workos.types.events.organization_domain_verification_failed_payload import
|
|
|
37
37
|
)
|
|
38
38
|
from workos.types.events.session_created_payload import SessionCreatedPayload
|
|
39
39
|
from workos.types.organizations.organization_common import OrganizationCommon
|
|
40
|
-
from workos.types.
|
|
40
|
+
from workos.types.organization_domains import OrganizationDomain
|
|
41
41
|
from workos.types.roles.role import EventRole
|
|
42
42
|
from workos.types.sso.connection import Connection
|
|
43
43
|
from workos.types.user_management.email_verification import (
|
|
@@ -36,6 +36,9 @@ EventType = Literal[
|
|
|
36
36
|
"organization.updated",
|
|
37
37
|
"organization_domain.verification_failed",
|
|
38
38
|
"organization_domain.verified",
|
|
39
|
+
"organization_domain.created",
|
|
40
|
+
"organization_domain.deleted",
|
|
41
|
+
"organization_domain.updated",
|
|
39
42
|
"organization_membership.created",
|
|
40
43
|
"organization_membership.deleted",
|
|
41
44
|
"organization_membership.updated",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from typing import Literal
|
|
2
2
|
from workos.types.workos_model import WorkOSModel
|
|
3
|
-
from workos.types.
|
|
3
|
+
from workos.types.organization_domains import OrganizationDomain
|
|
4
4
|
from workos.typing.literals import LiteralOrUntyped
|
|
5
5
|
|
|
6
6
|
|
workos/types/list_resource.py
CHANGED
|
@@ -33,6 +33,7 @@ from workos.types.mfa import AuthenticationFactor
|
|
|
33
33
|
from workos.types.organizations import Organization
|
|
34
34
|
from workos.types.sso import ConnectionWithDomains
|
|
35
35
|
from workos.types.user_management import Invitation, OrganizationMembership, User
|
|
36
|
+
from workos.types.vault import ObjectDigest
|
|
36
37
|
from workos.types.workos_model import WorkOSModel
|
|
37
38
|
from workos.utils.request_helper import DEFAULT_LIST_RESPONSE_LIMIT
|
|
38
39
|
|
|
@@ -51,6 +52,7 @@ ListableResource = TypeVar(
|
|
|
51
52
|
AuthorizationResource,
|
|
52
53
|
AuthorizationResourceType,
|
|
53
54
|
User,
|
|
55
|
+
ObjectDigest,
|
|
54
56
|
Warrant,
|
|
55
57
|
WarrantQueryResult,
|
|
56
58
|
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .organization_domain import *
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
from .domain_data_input import *
|
|
2
2
|
from .organization_common import *
|
|
3
|
-
from .organization_domain import *
|
|
4
3
|
from .organization import *
|
|
4
|
+
|
|
5
|
+
# re-exported for backwards compatibility, can be removed after version 6.0.0
|
|
6
|
+
from workos.types.organization_domains import OrganizationDomain
|
|
@@ -2,7 +2,7 @@ from dataclasses import field
|
|
|
2
2
|
from typing import Optional, Sequence
|
|
3
3
|
from workos.types.metadata import Metadata
|
|
4
4
|
from workos.types.organizations.organization_common import OrganizationCommon
|
|
5
|
-
from workos.types.
|
|
5
|
+
from workos.types.organization_domains import OrganizationDomain
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class Organization(OrganizationCommon):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from typing import Literal, Sequence
|
|
2
2
|
from workos.types.workos_model import WorkOSModel
|
|
3
|
-
from workos.types.
|
|
3
|
+
from workos.types.organization_domains import OrganizationDomain
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class OrganizationCommon(WorkOSModel):
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from typing import Dict
|
|
2
|
+
from pydantic import BaseModel, RootModel
|
|
3
|
+
from workos.types.workos_model import WorkOSModel
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class KeyContext(RootModel[Dict[str, str]]):
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class DataKey(WorkOSModel):
|
|
11
|
+
id: str
|
|
12
|
+
key: str
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class DataKeyPair(WorkOSModel):
|
|
16
|
+
context: KeyContext
|
|
17
|
+
data_key: DataKey
|
|
18
|
+
encrypted_keys: str
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class DecodedKeys(BaseModel):
|
|
22
|
+
iv: bytes
|
|
23
|
+
tag: bytes
|
|
24
|
+
keys: str # Base64-encoded string
|
|
25
|
+
ciphertext: bytes
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from workos.types.workos_model import WorkOSModel
|
|
4
|
+
from workos.types.vault import KeyContext
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ObjectDigest(WorkOSModel):
|
|
8
|
+
id: str
|
|
9
|
+
name: str
|
|
10
|
+
updated_at: str
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ObjectUpdateBy(WorkOSModel):
|
|
14
|
+
id: str
|
|
15
|
+
name: str
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ObjectMetadata(WorkOSModel):
|
|
19
|
+
context: KeyContext
|
|
20
|
+
environment_id: str
|
|
21
|
+
id: str
|
|
22
|
+
key_id: str
|
|
23
|
+
updated_at: str
|
|
24
|
+
updated_by: ObjectUpdateBy
|
|
25
|
+
version_id: str
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class VaultObject(WorkOSModel):
|
|
29
|
+
id: str
|
|
30
|
+
metadata: ObjectMetadata
|
|
31
|
+
name: str
|
|
32
|
+
value: Optional[str] = None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ObjectVersion(WorkOSModel):
|
|
36
|
+
created_at: str
|
|
37
|
+
current_version: bool
|
|
38
|
+
id: str
|
workos/types/webhooks/webhook.py
CHANGED
|
@@ -38,7 +38,7 @@ from workos.types.events.organization_domain_verification_failed_payload import
|
|
|
38
38
|
)
|
|
39
39
|
from workos.types.events.session_created_payload import SessionCreatedPayload
|
|
40
40
|
from workos.types.organizations.organization_common import OrganizationCommon
|
|
41
|
-
from workos.types.
|
|
41
|
+
from workos.types.organization_domains import OrganizationDomain
|
|
42
42
|
from workos.types.roles.role import EventRole
|
|
43
43
|
from workos.types.sso.connection import Connection
|
|
44
44
|
from workos.types.user_management.email_verification import (
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from typing import Optional, Dict
|
|
3
|
+
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
|
4
|
+
from cryptography.hazmat.backends import default_backend
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class CryptoProvider:
|
|
8
|
+
def encrypt(
|
|
9
|
+
self, plaintext: bytes, key: bytes, iv: bytes, aad: Optional[bytes]
|
|
10
|
+
) -> Dict[str, bytes]:
|
|
11
|
+
encryptor = Cipher(
|
|
12
|
+
algorithms.AES(key), modes.GCM(iv), backend=default_backend()
|
|
13
|
+
).encryptor()
|
|
14
|
+
|
|
15
|
+
if aad:
|
|
16
|
+
encryptor.authenticate_additional_data(aad)
|
|
17
|
+
|
|
18
|
+
ciphertext = encryptor.update(plaintext) + encryptor.finalize()
|
|
19
|
+
return {"ciphertext": ciphertext, "iv": iv, "tag": encryptor.tag}
|
|
20
|
+
|
|
21
|
+
def decrypt(
|
|
22
|
+
self,
|
|
23
|
+
ciphertext: bytes,
|
|
24
|
+
key: bytes,
|
|
25
|
+
iv: bytes,
|
|
26
|
+
tag: bytes,
|
|
27
|
+
aad: Optional[bytes] = None,
|
|
28
|
+
) -> bytes:
|
|
29
|
+
decryptor = Cipher(
|
|
30
|
+
algorithms.AES(key), modes.GCM(iv, tag), backend=default_backend()
|
|
31
|
+
).decryptor()
|
|
32
|
+
|
|
33
|
+
if aad:
|
|
34
|
+
decryptor.authenticate_additional_data(aad)
|
|
35
|
+
|
|
36
|
+
return decryptor.update(ciphertext) + decryptor.finalize()
|
|
37
|
+
|
|
38
|
+
def random_bytes(self, n: int) -> bytes:
|
|
39
|
+
return os.urandom(n)
|
workos/vault.py
ADDED
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
from typing import Optional, Protocol, Sequence, Tuple
|
|
3
|
+
from workos.types.vault import VaultObject, ObjectVersion, ObjectDigest, ObjectMetadata
|
|
4
|
+
from workos.types.vault.key import DataKey, DataKeyPair, KeyContext, DecodedKeys
|
|
5
|
+
from workos.types.list_resource import (
|
|
6
|
+
ListArgs,
|
|
7
|
+
ListMetadata,
|
|
8
|
+
ListPage,
|
|
9
|
+
WorkOSListResource,
|
|
10
|
+
)
|
|
11
|
+
from workos.utils.http_client import SyncHTTPClient
|
|
12
|
+
from workos.utils.pagination_order import PaginationOrder
|
|
13
|
+
from workos.utils.request_helper import (
|
|
14
|
+
DEFAULT_LIST_RESPONSE_LIMIT,
|
|
15
|
+
REQUEST_METHOD_DELETE,
|
|
16
|
+
REQUEST_METHOD_GET,
|
|
17
|
+
REQUEST_METHOD_POST,
|
|
18
|
+
REQUEST_METHOD_PUT,
|
|
19
|
+
RequestHelper,
|
|
20
|
+
)
|
|
21
|
+
from workos.utils.crypto_provider import CryptoProvider
|
|
22
|
+
|
|
23
|
+
DEFAULT_RESPONSE_LIMIT = DEFAULT_LIST_RESPONSE_LIMIT
|
|
24
|
+
|
|
25
|
+
VaultObjectList = WorkOSListResource[ObjectDigest, ListArgs, ListMetadata]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class VaultModule(Protocol):
|
|
29
|
+
def read_object(self, *, object_id: str) -> VaultObject:
|
|
30
|
+
"""
|
|
31
|
+
Get a Vault object with the value decrypted.
|
|
32
|
+
|
|
33
|
+
Kwargs:
|
|
34
|
+
object_id (str): The unique identifier for the object.
|
|
35
|
+
Returns:
|
|
36
|
+
VaultObject: A vault object with metadata, name and decrypted value.
|
|
37
|
+
"""
|
|
38
|
+
...
|
|
39
|
+
|
|
40
|
+
def list_objects(
|
|
41
|
+
self,
|
|
42
|
+
*,
|
|
43
|
+
limit: int = DEFAULT_RESPONSE_LIMIT,
|
|
44
|
+
before: Optional[str] = None,
|
|
45
|
+
after: Optional[str] = None,
|
|
46
|
+
) -> VaultObjectList:
|
|
47
|
+
"""
|
|
48
|
+
Gets a list of encrypted Vault objects.
|
|
49
|
+
|
|
50
|
+
Kwargs:
|
|
51
|
+
limit (int): The maximum number of objects to return. (Optional)
|
|
52
|
+
before (str): A cursor to return resources before. (Optional)
|
|
53
|
+
after (str): A cursor to return resources after. (Optional)
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
VaultObjectList: A list of vault objects with built-in pagination iterator.
|
|
57
|
+
"""
|
|
58
|
+
...
|
|
59
|
+
|
|
60
|
+
def list_object_versions(
|
|
61
|
+
self,
|
|
62
|
+
*,
|
|
63
|
+
object_id: str,
|
|
64
|
+
) -> Sequence[ObjectVersion]:
|
|
65
|
+
"""
|
|
66
|
+
Gets a list of versions for a specific Vault object.
|
|
67
|
+
|
|
68
|
+
Kwargs:
|
|
69
|
+
object_id (str): The unique identifier for the object.
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
Sequence[ObjectVersion]: A list of object versions.
|
|
73
|
+
"""
|
|
74
|
+
...
|
|
75
|
+
|
|
76
|
+
def create_object(
|
|
77
|
+
self,
|
|
78
|
+
*,
|
|
79
|
+
name: str,
|
|
80
|
+
value: str,
|
|
81
|
+
key_context: KeyContext,
|
|
82
|
+
) -> ObjectMetadata:
|
|
83
|
+
"""
|
|
84
|
+
Create a new Vault encrypted object.
|
|
85
|
+
|
|
86
|
+
Kwargs:
|
|
87
|
+
name (str): The name of the object.
|
|
88
|
+
value (str): The value to encrypt and store.
|
|
89
|
+
key_context (KeyContext): A set of key-value dictionary pairs that determines which root keys to use when encrypting data.
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
VaultObject: The created vault object.
|
|
93
|
+
"""
|
|
94
|
+
...
|
|
95
|
+
|
|
96
|
+
def update_object(
|
|
97
|
+
self,
|
|
98
|
+
*,
|
|
99
|
+
object_id: str,
|
|
100
|
+
value: str,
|
|
101
|
+
version_check: Optional[str] = None,
|
|
102
|
+
) -> VaultObject:
|
|
103
|
+
"""
|
|
104
|
+
Update an existing Vault object.
|
|
105
|
+
|
|
106
|
+
Kwargs:
|
|
107
|
+
object_id (str): The unique identifier for the object.
|
|
108
|
+
value (str): The new value to encrypt and store.
|
|
109
|
+
version_check (str): A version of the object to prevent clobbering of data during concurrent updates. (Optional)
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
VaultObject: The updated vault object.
|
|
113
|
+
"""
|
|
114
|
+
...
|
|
115
|
+
|
|
116
|
+
def delete_object(
|
|
117
|
+
self,
|
|
118
|
+
*,
|
|
119
|
+
object_id: str,
|
|
120
|
+
) -> None:
|
|
121
|
+
"""
|
|
122
|
+
Permanently delete a Vault encrypted object. Warning: this cannont be undone.
|
|
123
|
+
|
|
124
|
+
Kwargs:
|
|
125
|
+
object_id (str): The unique identifier for the object.
|
|
126
|
+
"""
|
|
127
|
+
...
|
|
128
|
+
|
|
129
|
+
def create_data_key(self, *, key_context: KeyContext) -> DataKeyPair:
|
|
130
|
+
"""
|
|
131
|
+
Generate a data key for local encryption based on the provided key context.
|
|
132
|
+
The encrypted data key MUST be stored by the application, as it cannot be retrieved after generation.
|
|
133
|
+
|
|
134
|
+
Kwargs:
|
|
135
|
+
key_context (KeyContext): A set of key-value dictionary pairs that determines which root keys to use when encrypting data.
|
|
136
|
+
"""
|
|
137
|
+
...
|
|
138
|
+
|
|
139
|
+
def decrypt_data_key(
|
|
140
|
+
self,
|
|
141
|
+
*,
|
|
142
|
+
keys: str,
|
|
143
|
+
) -> DataKey:
|
|
144
|
+
"""
|
|
145
|
+
Decrypt encrypted data keys that were previously generated by create_data_key.
|
|
146
|
+
|
|
147
|
+
This method takes the encrypted data key blob and uses the WorkOS Vault service
|
|
148
|
+
to decrypt it, returning the plaintext data key that can be used for local
|
|
149
|
+
encryption/decryption operations.
|
|
150
|
+
|
|
151
|
+
Kwargs:
|
|
152
|
+
keys (str): The base64-encoded encrypted data key blob returned by create_data_key.
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
DataKey: The decrypted data key containing the key ID and the plaintext key material.
|
|
156
|
+
"""
|
|
157
|
+
...
|
|
158
|
+
|
|
159
|
+
def encrypt(
|
|
160
|
+
self,
|
|
161
|
+
*,
|
|
162
|
+
data: str,
|
|
163
|
+
key_context: KeyContext,
|
|
164
|
+
associated_data: Optional[str] = None,
|
|
165
|
+
) -> str:
|
|
166
|
+
"""
|
|
167
|
+
Encrypt data locally using AES-GCM with a data key derived from the provided context.
|
|
168
|
+
|
|
169
|
+
This method generates a new data key for each encryption operation, ensuring that
|
|
170
|
+
the same plaintext will produce different ciphertext each time it's encrypted.
|
|
171
|
+
The encrypted data key is embedded in the result so it can be decrypted later.
|
|
172
|
+
|
|
173
|
+
Kwargs:
|
|
174
|
+
data (str): The plaintext data to encrypt.
|
|
175
|
+
key_context (KeyContext): A set of key-value dictionary pairs that determines which root keys to use when encrypting data.
|
|
176
|
+
associated_data (str): Additional authenticated data (AAD) that will be authenticated but not encrypted. (Optional)
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
str: Base64-encoded encrypted data containing the IV, authentication tag, encrypted data key, and ciphertext.
|
|
180
|
+
"""
|
|
181
|
+
...
|
|
182
|
+
|
|
183
|
+
def decrypt(
|
|
184
|
+
self, *, encrypted_data: str, associated_data: Optional[str] = None
|
|
185
|
+
) -> str:
|
|
186
|
+
"""
|
|
187
|
+
Decrypt data that was previously encrypted using the encrypt method.
|
|
188
|
+
|
|
189
|
+
This method extracts the encrypted data key from the encrypted payload,
|
|
190
|
+
decrypts it using the WorkOS Vault service, and then uses the resulting
|
|
191
|
+
data key to decrypt the actual data using AES-GCM.
|
|
192
|
+
|
|
193
|
+
Kwargs:
|
|
194
|
+
encrypted_data (str): The base64-encoded encrypted data returned by the encrypt method.
|
|
195
|
+
associated_data (str): The same additional authenticated data (AAD) that was used during encryption, if any. (Optional)
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
str: The original plaintext data.
|
|
199
|
+
|
|
200
|
+
Raises:
|
|
201
|
+
ValueError: If the encrypted_data format is invalid or if associated_data doesn't match what was used during encryption.
|
|
202
|
+
cryptography.exceptions.InvalidTag: If the authentication tag verification fails (data has been tampered with).
|
|
203
|
+
"""
|
|
204
|
+
...
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
class Vault(VaultModule):
|
|
208
|
+
_http_client: SyncHTTPClient
|
|
209
|
+
_crypto_provider: CryptoProvider
|
|
210
|
+
|
|
211
|
+
def __init__(self, http_client: SyncHTTPClient):
|
|
212
|
+
self._http_client = http_client
|
|
213
|
+
self._crypto_provider = CryptoProvider()
|
|
214
|
+
|
|
215
|
+
def read_object(
|
|
216
|
+
self,
|
|
217
|
+
*,
|
|
218
|
+
object_id: str,
|
|
219
|
+
) -> VaultObject:
|
|
220
|
+
if not object_id:
|
|
221
|
+
raise ValueError("Incomplete arguments: 'object_id' is a required argument")
|
|
222
|
+
|
|
223
|
+
response = self._http_client.request(
|
|
224
|
+
RequestHelper.build_parameterized_url(
|
|
225
|
+
"vault/v1/kv/{object_id}",
|
|
226
|
+
object_id=object_id,
|
|
227
|
+
),
|
|
228
|
+
method=REQUEST_METHOD_GET,
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
return VaultObject.model_validate(response)
|
|
232
|
+
|
|
233
|
+
def list_objects(
|
|
234
|
+
self,
|
|
235
|
+
*,
|
|
236
|
+
limit: int = DEFAULT_RESPONSE_LIMIT,
|
|
237
|
+
before: Optional[str] = None,
|
|
238
|
+
after: Optional[str] = None,
|
|
239
|
+
) -> VaultObjectList:
|
|
240
|
+
list_params: ListArgs = {
|
|
241
|
+
"limit": limit,
|
|
242
|
+
"before": before,
|
|
243
|
+
"after": after,
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
response = self._http_client.request(
|
|
247
|
+
"vault/v1/kv",
|
|
248
|
+
method=REQUEST_METHOD_GET,
|
|
249
|
+
params=list_params,
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
# Ensure object field is present
|
|
253
|
+
response_dict = dict(response)
|
|
254
|
+
if "object" not in response_dict:
|
|
255
|
+
response_dict["object"] = "list"
|
|
256
|
+
|
|
257
|
+
return VaultObjectList(
|
|
258
|
+
list_method=self.list_objects,
|
|
259
|
+
list_args=list_params,
|
|
260
|
+
**ListPage[ObjectDigest](**response_dict).model_dump(),
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
def list_object_versions(
|
|
264
|
+
self,
|
|
265
|
+
*,
|
|
266
|
+
object_id: str,
|
|
267
|
+
) -> Sequence[ObjectVersion]:
|
|
268
|
+
response = self._http_client.request(
|
|
269
|
+
RequestHelper.build_parameterized_url(
|
|
270
|
+
"vault/v1/kv/{object_id}/versions",
|
|
271
|
+
object_id=object_id,
|
|
272
|
+
),
|
|
273
|
+
method=REQUEST_METHOD_GET,
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
return [
|
|
277
|
+
ObjectVersion.model_validate(version)
|
|
278
|
+
for version in response.get("data", [])
|
|
279
|
+
]
|
|
280
|
+
|
|
281
|
+
def create_object(
|
|
282
|
+
self,
|
|
283
|
+
*,
|
|
284
|
+
name: str,
|
|
285
|
+
value: str,
|
|
286
|
+
key_context: KeyContext,
|
|
287
|
+
) -> ObjectMetadata:
|
|
288
|
+
if not name or not value:
|
|
289
|
+
raise ValueError(
|
|
290
|
+
"Incomplete arguments: 'name' and 'value' are required arguments"
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
request_data = {
|
|
294
|
+
"name": name,
|
|
295
|
+
"value": value,
|
|
296
|
+
"key_context": key_context,
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
response = self._http_client.request(
|
|
300
|
+
"vault/v1/kv",
|
|
301
|
+
method=REQUEST_METHOD_POST,
|
|
302
|
+
json=request_data,
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
return ObjectMetadata.model_validate(response)
|
|
306
|
+
|
|
307
|
+
def update_object(
|
|
308
|
+
self,
|
|
309
|
+
*,
|
|
310
|
+
object_id: str,
|
|
311
|
+
value: str,
|
|
312
|
+
version_check: Optional[str] = None,
|
|
313
|
+
) -> VaultObject:
|
|
314
|
+
if not object_id:
|
|
315
|
+
raise ValueError("Incomplete arguments: 'object_id' is a required argument")
|
|
316
|
+
|
|
317
|
+
request_data = {
|
|
318
|
+
"value": value,
|
|
319
|
+
}
|
|
320
|
+
if version_check is not None:
|
|
321
|
+
request_data["version_check"] = version_check
|
|
322
|
+
|
|
323
|
+
response = self._http_client.request(
|
|
324
|
+
RequestHelper.build_parameterized_url(
|
|
325
|
+
"vault/v1/kv/{object_id}",
|
|
326
|
+
object_id=object_id,
|
|
327
|
+
),
|
|
328
|
+
method=REQUEST_METHOD_PUT,
|
|
329
|
+
json=request_data,
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
return VaultObject.model_validate(response)
|
|
333
|
+
|
|
334
|
+
def delete_object(
|
|
335
|
+
self,
|
|
336
|
+
*,
|
|
337
|
+
object_id: str,
|
|
338
|
+
) -> None:
|
|
339
|
+
if not object_id:
|
|
340
|
+
raise ValueError("Incomplete arguments: 'object_id' is a required argument")
|
|
341
|
+
|
|
342
|
+
self._http_client.request(
|
|
343
|
+
RequestHelper.build_parameterized_url(
|
|
344
|
+
"vault/v1/kv/{object_id}",
|
|
345
|
+
object_id=object_id,
|
|
346
|
+
),
|
|
347
|
+
method=REQUEST_METHOD_DELETE,
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
def create_data_key(self, *, key_context: KeyContext) -> DataKeyPair:
|
|
351
|
+
request_data = {
|
|
352
|
+
"context": key_context,
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
response = self._http_client.request(
|
|
356
|
+
"vault/v1/keys/data-key",
|
|
357
|
+
method=REQUEST_METHOD_POST,
|
|
358
|
+
json=request_data,
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
return DataKeyPair.model_validate(
|
|
362
|
+
{
|
|
363
|
+
"context": response["context"],
|
|
364
|
+
"data_key": {"id": response["id"], "key": response["data_key"]},
|
|
365
|
+
"encrypted_keys": response["encrypted_keys"],
|
|
366
|
+
}
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
def decrypt_data_key(
|
|
370
|
+
self,
|
|
371
|
+
*,
|
|
372
|
+
keys: str,
|
|
373
|
+
) -> DataKey:
|
|
374
|
+
request_data = {
|
|
375
|
+
"keys": keys,
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
response = self._http_client.request(
|
|
379
|
+
"vault/v1/keys/decrypt",
|
|
380
|
+
method=REQUEST_METHOD_POST,
|
|
381
|
+
json=request_data,
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
return DataKey.model_validate(
|
|
385
|
+
{"id": response["id"], "key": response["data_key"]}
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
def encrypt(
|
|
389
|
+
self,
|
|
390
|
+
*,
|
|
391
|
+
data: str,
|
|
392
|
+
key_context: KeyContext,
|
|
393
|
+
associated_data: Optional[str] = None,
|
|
394
|
+
) -> str:
|
|
395
|
+
key_pair = self.create_data_key(key_context=key_context)
|
|
396
|
+
|
|
397
|
+
key = self._base64_to_bytes(key_pair.data_key.key)
|
|
398
|
+
key_blob = self._base64_to_bytes(key_pair.encrypted_keys)
|
|
399
|
+
prefix_len_buffer = self._encode_u32(len(key_blob))
|
|
400
|
+
aad_buffer = associated_data.encode("utf-8") if associated_data else None
|
|
401
|
+
iv = self._crypto_provider.random_bytes(12)
|
|
402
|
+
|
|
403
|
+
result = self._crypto_provider.encrypt(
|
|
404
|
+
data.encode("utf-8"), key, iv, aad_buffer
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
combined = (
|
|
408
|
+
result["iv"]
|
|
409
|
+
+ result["tag"]
|
|
410
|
+
+ prefix_len_buffer
|
|
411
|
+
+ key_blob
|
|
412
|
+
+ result["ciphertext"]
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
return self._bytes_to_base64(combined)
|
|
416
|
+
|
|
417
|
+
def decrypt(
|
|
418
|
+
self, *, encrypted_data: str, associated_data: Optional[str] = None
|
|
419
|
+
) -> str:
|
|
420
|
+
decoded = self._decode(encrypted_data)
|
|
421
|
+
data_key = self.decrypt_data_key(keys=decoded.keys)
|
|
422
|
+
|
|
423
|
+
key = self._base64_to_bytes(data_key.key)
|
|
424
|
+
aad_buffer = associated_data.encode("utf-8") if associated_data else None
|
|
425
|
+
|
|
426
|
+
decrypted_bytes = self._crypto_provider.decrypt(
|
|
427
|
+
ciphertext=decoded.ciphertext,
|
|
428
|
+
key=key,
|
|
429
|
+
iv=decoded.iv,
|
|
430
|
+
tag=decoded.tag,
|
|
431
|
+
aad=aad_buffer,
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
return decrypted_bytes.decode("utf-8")
|
|
435
|
+
|
|
436
|
+
def _base64_to_bytes(self, data: str) -> bytes:
|
|
437
|
+
return base64.b64decode(data)
|
|
438
|
+
|
|
439
|
+
def _bytes_to_base64(self, data: bytes) -> str:
|
|
440
|
+
return base64.b64encode(data).decode("utf-8")
|
|
441
|
+
|
|
442
|
+
def _encode_u32(self, value: int) -> bytes:
|
|
443
|
+
"""
|
|
444
|
+
Encode a 32-bit unsigned integer as LEB128.
|
|
445
|
+
|
|
446
|
+
Returns:
|
|
447
|
+
bytes: LEB128-encoded representation of the input value.
|
|
448
|
+
"""
|
|
449
|
+
if value < 0 or value > 0xFFFFFFFF:
|
|
450
|
+
raise ValueError("Value must be a 32-bit unsigned integer")
|
|
451
|
+
|
|
452
|
+
encoded = bytearray()
|
|
453
|
+
while True:
|
|
454
|
+
byte = value & 0x7F
|
|
455
|
+
value >>= 7
|
|
456
|
+
if value != 0:
|
|
457
|
+
byte |= 0x80 # Set continuation bit
|
|
458
|
+
encoded.append(byte)
|
|
459
|
+
if value == 0:
|
|
460
|
+
break
|
|
461
|
+
|
|
462
|
+
return bytes(encoded)
|
|
463
|
+
|
|
464
|
+
def _decode(self, encrypted_data_b64: str) -> DecodedKeys:
|
|
465
|
+
"""
|
|
466
|
+
This function extracts IV, tag, keyBlobLength, keyBlob, and ciphertext
|
|
467
|
+
from a base64-encoded payload.
|
|
468
|
+
Encoding format: [IV][TAG][4B Length][keyBlob][ciphertext]
|
|
469
|
+
"""
|
|
470
|
+
try:
|
|
471
|
+
payload = base64.b64decode(encrypted_data_b64)
|
|
472
|
+
except Exception as e:
|
|
473
|
+
raise ValueError("Base64 decoding failed") from e
|
|
474
|
+
|
|
475
|
+
iv = payload[0:12]
|
|
476
|
+
tag = payload[12:28]
|
|
477
|
+
|
|
478
|
+
try:
|
|
479
|
+
key_len, leb_len = self._decode_u32(payload[28:])
|
|
480
|
+
except Exception as e:
|
|
481
|
+
raise ValueError("Failed to decode key length") from e
|
|
482
|
+
|
|
483
|
+
keys_index = 28 + leb_len
|
|
484
|
+
keys_end = keys_index + key_len
|
|
485
|
+
keys_slice = payload[keys_index:keys_end]
|
|
486
|
+
keys = base64.b64encode(keys_slice).decode("utf-8")
|
|
487
|
+
ciphertext = payload[keys_end:]
|
|
488
|
+
|
|
489
|
+
return DecodedKeys(iv=iv, tag=tag, keys=keys, ciphertext=ciphertext)
|
|
490
|
+
|
|
491
|
+
def _decode_u32(self, buf: bytes) -> Tuple[int, int]:
|
|
492
|
+
"""
|
|
493
|
+
Decode an unsigned LEB128-encoded 32-bit integer from bytes.
|
|
494
|
+
|
|
495
|
+
Returns:
|
|
496
|
+
(value, length_consumed)
|
|
497
|
+
|
|
498
|
+
Raises:
|
|
499
|
+
ValueError if decoding fails or overflows.
|
|
500
|
+
"""
|
|
501
|
+
res = 0
|
|
502
|
+
bit = 0
|
|
503
|
+
|
|
504
|
+
for i, b in enumerate(buf):
|
|
505
|
+
if i > 4:
|
|
506
|
+
raise ValueError("LEB128 integer overflow (was more than 4 bytes)")
|
|
507
|
+
|
|
508
|
+
res |= (b & 0x7F) << (7 * bit)
|
|
509
|
+
|
|
510
|
+
if (b & 0x80) == 0:
|
|
511
|
+
return res, i + 1
|
|
512
|
+
|
|
513
|
+
bit += 1
|
|
514
|
+
|
|
515
|
+
raise ValueError("LEB128 integer not found")
|
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
workos/__about__.py,sha256=
|
|
1
|
+
workos/__about__.py,sha256=XjdHlLqPEJ0a5evP3CCUorp0ckGip4qzzY2V8gM6xXg,406
|
|
2
2
|
workos/__init__.py,sha256=hOdbO_MJCvpLx8EbRjQg-fvFAB-glJmrmxUZK8kWG0k,167
|
|
3
|
-
workos/_base_client.py,sha256=
|
|
3
|
+
workos/_base_client.py,sha256=YWLXBpd0YeID3WKYZkKa4l9ZuB9e6HgdLmPKZIzXsME,3599
|
|
4
4
|
workos/_client_configuration.py,sha256=g3eXhtrEMN6CW0hZ5uHb2PmLurXjyBkWZeQYMPeJD6s,222
|
|
5
|
-
workos/async_client.py,sha256=
|
|
5
|
+
workos/async_client.py,sha256=c_lP7y49k3FEx0S6We8PrEw3PhTkD0VHyHJaKgc51HU,4358
|
|
6
6
|
workos/audit_logs.py,sha256=bYoAoNO4FRSaT34UxiVkgTXCVH8givcS2YGhH_9O3NA,3983
|
|
7
|
-
workos/client.py,sha256=
|
|
7
|
+
workos/client.py,sha256=hIm87iNtKfwQ93H5aRXnhTqUJIOzUEG1GX0jdLBLAYc,4345
|
|
8
8
|
workos/directory_sync.py,sha256=6Z1gHz1LWNy56EtkXwNm6jhRRcvsJ7ASeDLy_Q1oKM0,14601
|
|
9
9
|
workos/events.py,sha256=b4JIzMbd5LlVtpOMKVojC70RCHAgmLN3nJ62_2U0GwI,3892
|
|
10
10
|
workos/exceptions.py,sha256=eoy-T4We98HKZn0UZu33fPzhm4DwafzwLeg3juhC6FE,1732
|
|
11
11
|
workos/fga.py,sha256=qjZrdkXKwJDLVTMMrOADxyXRDkswto4kGIdtTjtS3hw,21008
|
|
12
12
|
workos/mfa.py,sha256=J8eOr4ZEmK0TPFKD7pabSalgCFCyg3XJY1stu28_8Vw,6862
|
|
13
|
+
workos/organization_domains.py,sha256=um-dm54SIs2Kd2FxqxVjIlynlenAG9NamQmNVUGOQ0k,5464
|
|
13
14
|
workos/organizations.py,sha256=ugPRhwN8cN2O6qOCf7wj8Wus-oeRKXlNe2642PSl_n4,12266
|
|
14
15
|
workos/passwordless.py,sha256=NGXDoxomBkrIml8-VHXH1HvCFMqotQ-YhRobUQXpVZs,3203
|
|
15
16
|
workos/portal.py,sha256=lzf3fnOor4AyVdHCHMuJzVSpAC9LWWdC5sZIRtCsb0c,2443
|
|
@@ -17,10 +18,11 @@ workos/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
17
18
|
workos/session.py,sha256=CVada-nKRFv9m2-mcghR5VBOguGU76iDCDgQlksOOrQ,11912
|
|
18
19
|
workos/sso.py,sha256=ZBC3y-IRmxG0jPd0BOj7s7XQkXJoTLUg1fx-h3Gfy4g,13541
|
|
19
20
|
workos/user_management.py,sha256=qy21HxYVO40xo7TvCGVxvGWdYIFHZTVnUJTpCQIN2ko,77113
|
|
21
|
+
workos/vault.py,sha256=SJXr3nJ03qJFuf30FjevMD6LLlDNX3MGaKlYGgICRRE,15657
|
|
20
22
|
workos/webhooks.py,sha256=CuwBxh6va9VZFVSXOknveGt6CCGDF3em07a-J12DbXI,4790
|
|
21
23
|
workos/widgets.py,sha256=bfbR0hQOHZabbgGL2ekD5sY1sjiUoWBTdrBd_a6WmBc,1721
|
|
22
24
|
workos/types/__init__.py,sha256=aYScTXq5jOyp0AYvdWffaj5-zdDuNtCCJtbzt5GM19k,267
|
|
23
|
-
workos/types/list_resource.py,sha256=
|
|
25
|
+
workos/types/list_resource.py,sha256=6N9OoaFsWPCXEX9plVONODxed-Yx6RaX1ga1b15ENIA,6570
|
|
24
26
|
workos/types/metadata.py,sha256=uUqDkGJGyFY3H4JZObSiCfn4jKBue5CBhOqv79TI1n0,52
|
|
25
27
|
workos/types/workos_model.py,sha256=bV_p3baadcUJOU_7f6ysZ6KXhpt3E_93spuZnfJs9vc,735
|
|
26
28
|
workos/types/audit_logs/__init__.py,sha256=daPn8wAVEnlM1fCpOsy3dVZV_0YEp8bA1T_nhk5-pU8,211
|
|
@@ -45,11 +47,11 @@ workos/types/events/directory_group_with_previous_attributes.py,sha256=13VLNhdJZ
|
|
|
45
47
|
workos/types/events/directory_payload.py,sha256=tRo3f9g8VoYertSUPAR25iGDyGLr2Dtb2mTkl73PAeA,503
|
|
46
48
|
workos/types/events/directory_payload_with_legacy_fields.py,sha256=jk9nLmRqgllVkBG4EU3uTgcDOhCNptHgCh93U7aBAYE,1005
|
|
47
49
|
workos/types/events/directory_user_with_previous_attributes.py,sha256=PhnO3WakBxAvnlOGf0UB0bvoppUYlwLyU-g9X_pPdko,244
|
|
48
|
-
workos/types/events/event.py,sha256=
|
|
49
|
-
workos/types/events/event_model.py,sha256=
|
|
50
|
-
workos/types/events/event_type.py,sha256=
|
|
50
|
+
workos/types/events/event.py,sha256=4E-71v38J1Udt0qFz9k17V6HmNj9Lgz-mmS-524ysIs,9817
|
|
51
|
+
workos/types/events/event_model.py,sha256=TuCRgjgTWnTvwL3NTR9FBGuF7WO34OhsbtMGKss5QKA,3722
|
|
52
|
+
workos/types/events/event_type.py,sha256=NIVHMahETiKeFJfZGi-ptHQH7czmRY4tChnuMU0OxWw,1690
|
|
51
53
|
workos/types/events/list_filters.py,sha256=P04zmRynx9VPqNX_MBXA-3KA6flPZJogtIUqTI7w9Eg,305
|
|
52
|
-
workos/types/events/organization_domain_verification_failed_payload.py,sha256=
|
|
54
|
+
workos/types/events/organization_domain_verification_failed_payload.py,sha256=b4wX8HVbL9Nx6fCjOXkZg9eLc-Bv6YqAjMap1f7UvFc,470
|
|
53
55
|
workos/types/events/previous_attributes.py,sha256=DxolwLwzcnG8r_W6rh5BT29iDfSVsIELvRYJ0NCrNn0,72
|
|
54
56
|
workos/types/events/session_created_payload.py,sha256=F4eKjmetgyRIluNBDUmB2OXq8hDWEVjALJ4rrT2IHJs,462
|
|
55
57
|
workos/types/fga/__init__.py,sha256=mVb3gvhvK93PAosSgO1RlNmxYX4zIxVDcZZQ8Jyejuw,151
|
|
@@ -65,12 +67,13 @@ workos/types/mfa/authentication_challenge_verification_response.py,sha256=5HxsMJ
|
|
|
65
67
|
workos/types/mfa/authentication_factor.py,sha256=92Cq9MLwBUXfowzCv2u9ZnoH9tVgDm16jjmelN7BOGs,2021
|
|
66
68
|
workos/types/mfa/authentication_factor_totp_and_challenge_response.py,sha256=Ix_DqF7eQ-x6Up8C_0wzvAXXyDY876nobRuiXXL8nbc,541
|
|
67
69
|
workos/types/mfa/enroll_authentication_factor_type.py,sha256=l_w4co5BdmfglII5YAQiyL-kdAw8KiiOsYaDhn8lGWE,227
|
|
68
|
-
workos/types/
|
|
70
|
+
workos/types/organization_domains/__init__.py,sha256=fpNRKfzZ0p1FybwQG4L-YY12nFPTCJjD4j60OJI5fXE,35
|
|
71
|
+
workos/types/organization_domains/organization_domain.py,sha256=PkmdVVQi6h94xHWLJt0SFGgiGEVoyxwiPD-WwQ1naWs,614
|
|
72
|
+
workos/types/organizations/__init__.py,sha256=uE2qD2MOebPs-_BerqenTqmu4I4DI5-CKIY_M8BGwDY,240
|
|
69
73
|
workos/types/organizations/domain_data_input.py,sha256=BW3iYswF-b2SXip7jwPkmlZhgLReS_VyzUlU8PD4xdw,161
|
|
70
74
|
workos/types/organizations/list_filters.py,sha256=u-TsEEMVI8IvPvxn_9--k6iYDs4T64RHTY_HY0Fvwlw,179
|
|
71
|
-
workos/types/organizations/organization.py,sha256=
|
|
72
|
-
workos/types/organizations/organization_common.py,sha256=
|
|
73
|
-
workos/types/organizations/organization_domain.py,sha256=gFumod8dBtTLxmAlD83UuF_96B8lSjwsgwr_OOpvxOY,528
|
|
75
|
+
workos/types/organizations/organization.py,sha256=oVGg-C0blizg7aGVeRTr3vvTRvrn_WPzwaV-LvbQxRo,520
|
|
76
|
+
workos/types/organizations/organization_common.py,sha256=lelq-Id9AGSmosFprFAET_QyXmYR0wHX9-XMsOTEr7s,337
|
|
74
77
|
workos/types/passwordless/__init__.py,sha256=ZP_XIcGUKXsATb--bfesnsFFbbvg3WYzgqRC_qvW0rw,77
|
|
75
78
|
workos/types/passwordless/passwordless_session.py,sha256=izmTI5F7qvBdoVodaXP7fnYo_XodG-ygyYTPrCEObTM,293
|
|
76
79
|
workos/types/passwordless/passwordless_session_type.py,sha256=ltZAV8KFiYDVcxpVt5Hcdc089tB5VETYaa9X6E7Mvoo,75
|
|
@@ -101,8 +104,11 @@ workos/types/user_management/screen_hint.py,sha256=DnPgvjRK-20i82v3YPzggna1rc6yO
|
|
|
101
104
|
workos/types/user_management/session.py,sha256=pyINOo_a53rxtZ-gGYOZziNHoZzc7s56evKgVAPIgMo,1390
|
|
102
105
|
workos/types/user_management/user.py,sha256=aXRHsLXnQbXWBPRTdAP9Ym_-D9hjmrp_E4PTPi1gF1s,603
|
|
103
106
|
workos/types/user_management/user_management_provider_type.py,sha256=UEjtcs9oeDvL9248bFy8nRfzutA6aBfhVMuMByG0qsM,145
|
|
107
|
+
workos/types/vault/__init__.py,sha256=krBuIl8luysrtDf9-b8KTkXOHDOaSsOR-Aao6Wlil0Q,41
|
|
108
|
+
workos/types/vault/key.py,sha256=x30XBplSj9AviDDAB8MdpcULbZvvo2sUzi8RCmZQKxU,453
|
|
109
|
+
workos/types/vault/object.py,sha256=-rk4KovS3eT8T8L3JltYUS0cd2Rg1JKcAX9SOaZO3D8,664
|
|
104
110
|
workos/types/webhooks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
105
|
-
workos/types/webhooks/webhook.py,sha256=
|
|
111
|
+
workos/types/webhooks/webhook.py,sha256=v0Z7Z1eMnjCMR8iWm2KNECv-8nwPdTB8gTZCVfFP0O0,9510
|
|
106
112
|
workos/types/webhooks/webhook_model.py,sha256=v7Hgtzt0nW_5RaYoB_QGVfElhdjySuG3F1BFjoid36w,404
|
|
107
113
|
workos/types/webhooks/webhook_payload.py,sha256=GXt31KtyBM-ji5K5p4dBnu46Gh8adQWTq0ye5USB_6g,68
|
|
108
114
|
workos/types/widgets/__init__.py,sha256=z2Tdlj_bJsRZeJRh4SOFX58PvJdf0LjKnYhrQX1fpME,65
|
|
@@ -115,11 +121,12 @@ workos/typing/untyped_literal.py,sha256=wf48_6kZJ-AN3-V2gLC_y5k2tnUvgGnhn89HsfOK
|
|
|
115
121
|
workos/typing/webhooks.py,sha256=8GhUnrlGrgQbknh32tVtHxeR8FsXsJesW94CtZiB-_4,534
|
|
116
122
|
workos/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
117
123
|
workos/utils/_base_http_client.py,sha256=KqRHUaDJUfXGxhu6UHLfZVkIBikcvrUZYhm9GBSByB4,7410
|
|
124
|
+
workos/utils/crypto_provider.py,sha256=QeQSR4t9xLlb90kEfl8onVUsf1yCkYq0EjFTxK0mUlk,1182
|
|
118
125
|
workos/utils/http_client.py,sha256=TM5yMFFExmAE8D2Z43-5O301tRbnylLG0aXO0isGorE,6197
|
|
119
126
|
workos/utils/pagination_order.py,sha256=_-et1DDJLG0czarTU7op4W6RA0V1f85GNsUgtyRU55Q,70
|
|
120
127
|
workos/utils/request_helper.py,sha256=NaO16qPPbSNnCeE0fiNKYb8gM-dK_okYVJbLGrEGXz8,793
|
|
121
|
-
workos-5.
|
|
122
|
-
workos-5.
|
|
123
|
-
workos-5.
|
|
124
|
-
workos-5.
|
|
125
|
-
workos-5.
|
|
128
|
+
workos-5.26.0.dist-info/LICENSE,sha256=mU--WL1JzelH2tXpKVoOlpud4cpqKSRTtdArCvYZmb4,1063
|
|
129
|
+
workos-5.26.0.dist-info/METADATA,sha256=MohK6HHQVDsilcCLtEwsSRF_ufiHMGCe1puhiVakL60,4187
|
|
130
|
+
workos-5.26.0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
|
131
|
+
workos-5.26.0.dist-info/top_level.txt,sha256=ZFskIfue1Tw-JwjyIXRvdsAl9i0DX9VbqmgICXV84Sk,7
|
|
132
|
+
workos-5.26.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|