workos 5.22.0__tar.gz → 5.24.0__tar.gz
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-5.22.0 → workos-5.24.0}/PKG-INFO +1 -1
- {workos-5.22.0 → workos-5.24.0}/tests/test_organizations.py +13 -9
- {workos-5.22.0 → workos-5.24.0}/tests/test_user_management.py +23 -0
- workos-5.24.0/tests/test_vault.py +460 -0
- {workos-5.22.0 → workos-5.24.0}/workos/__about__.py +1 -1
- {workos-5.22.0 → workos-5.24.0}/workos/async_client.py +7 -0
- {workos-5.22.0 → workos-5.24.0}/workos/client.py +7 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/events/event.py +15 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/events/event_type.py +3 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/list_resource.py +2 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/organizations/organization_domain.py +3 -0
- workos-5.24.0/workos/types/vault/__init__.py +2 -0
- workos-5.24.0/workos/types/vault/key.py +25 -0
- workos-5.24.0/workos/types/vault/object.py +38 -0
- {workos-5.22.0 → workos-5.24.0}/workos/user_management.py +4 -0
- workos-5.24.0/workos/utils/crypto_provider.py +39 -0
- workos-5.24.0/workos/vault.py +515 -0
- {workos-5.22.0 → workos-5.24.0}/workos.egg-info/PKG-INFO +1 -1
- {workos-5.22.0 → workos-5.24.0}/workos.egg-info/SOURCES.txt +6 -0
- {workos-5.22.0 → workos-5.24.0}/LICENSE +0 -0
- {workos-5.22.0 → workos-5.24.0}/README.md +0 -0
- {workos-5.22.0 → workos-5.24.0}/setup.cfg +0 -0
- {workos-5.22.0 → workos-5.24.0}/setup.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/tests/test_async_http_client.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/tests/test_audit_logs.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/tests/test_client.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/tests/test_directory_sync.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/tests/test_events.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/tests/test_fga.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/tests/test_mfa.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/tests/test_passwordless.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/tests/test_portal.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/tests/test_session.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/tests/test_sso.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/tests/test_sync_http_client.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/tests/test_webhooks.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/tests/test_widgets.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/__init__.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/_base_client.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/_client_configuration.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/audit_logs.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/directory_sync.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/events.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/exceptions.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/fga.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/mfa.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/organizations.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/passwordless.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/portal.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/py.typed +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/session.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/sso.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/__init__.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/audit_logs/__init__.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/audit_logs/audit_log_event.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/audit_logs/audit_log_event_actor.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/audit_logs/audit_log_event_context.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/audit_logs/audit_log_event_target.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/audit_logs/audit_log_export.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/audit_logs/audit_log_metadata.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/directory_sync/__init__.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/directory_sync/directory.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/directory_sync/directory_group.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/directory_sync/directory_state.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/directory_sync/directory_type.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/directory_sync/directory_user.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/directory_sync/list_filters.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/events/__init__.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/events/authentication_payload.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/events/connection_payload_with_legacy_fields.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/events/directory_group_membership_payload.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/events/directory_group_with_previous_attributes.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/events/directory_payload.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/events/directory_payload_with_legacy_fields.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/events/directory_user_with_previous_attributes.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/events/event_model.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/events/list_filters.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/events/organization_domain_verification_failed_payload.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/events/previous_attributes.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/events/session_created_payload.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/fga/__init__.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/fga/authorization_resource_types.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/fga/authorization_resources.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/fga/check.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/fga/list_filters.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/fga/warnings.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/fga/warrant.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/metadata.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/mfa/__init__.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/mfa/authentication_challenge.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/mfa/authentication_challenge_verification_response.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/mfa/authentication_factor.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/mfa/authentication_factor_totp_and_challenge_response.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/mfa/enroll_authentication_factor_type.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/organizations/__init__.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/organizations/domain_data_input.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/organizations/list_filters.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/organizations/organization.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/organizations/organization_common.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/passwordless/__init__.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/passwordless/passwordless_session.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/passwordless/passwordless_session_type.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/portal/__init__.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/portal/portal_link.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/portal/portal_link_intent.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/portal/portal_link_intent_options.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/roles/__init__.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/roles/role.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/sso/__init__.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/sso/connection.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/sso/connection_domain.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/sso/profile.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/sso/sso_provider_type.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/user_management/__init__.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/user_management/authenticate_with_common.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/user_management/authentication_response.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/user_management/email_verification.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/user_management/impersonator.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/user_management/invitation.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/user_management/list_filters.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/user_management/magic_auth.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/user_management/oauth_tokens.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/user_management/organization_membership.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/user_management/password_hash_type.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/user_management/password_reset.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/user_management/screen_hint.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/user_management/session.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/user_management/user.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/user_management/user_management_provider_type.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/webhooks/__init__.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/webhooks/webhook.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/webhooks/webhook_model.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/webhooks/webhook_payload.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/widgets/__init__.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/widgets/widget_scope.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/widgets/widget_token_response.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/types/workos_model.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/typing/__init__.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/typing/literals.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/typing/sync_or_async.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/typing/untyped_literal.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/typing/webhooks.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/utils/__init__.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/utils/_base_http_client.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/utils/http_client.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/utils/pagination_order.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/utils/request_helper.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/webhooks.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos/widgets.py +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos.egg-info/dependency_links.txt +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos.egg-info/not-zip-safe +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos.egg-info/requires.txt +0 -0
- {workos-5.22.0 → workos-5.24.0}/workos.egg-info/top_level.txt +0 -0
|
@@ -38,6 +38,8 @@ class TestOrganizations:
|
|
|
38
38
|
"organization_id": "org_01EHT88Z8J8795GZNQ4ZP1J81T",
|
|
39
39
|
"verification_strategy": "dns",
|
|
40
40
|
"verification_token": "token",
|
|
41
|
+
"created_at": datetime.datetime.now().isoformat(),
|
|
42
|
+
"updated_at": datetime.datetime.now().isoformat(),
|
|
41
43
|
}
|
|
42
44
|
],
|
|
43
45
|
}
|
|
@@ -189,15 +191,17 @@ class TestOrganizations:
|
|
|
189
191
|
}
|
|
190
192
|
assert updated_organization.id == "org_01EHT88Z8J8795GZNQ4ZP1J81T"
|
|
191
193
|
assert updated_organization.name == "Example Organization"
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
194
|
+
domain = updated_organization.domains[0]
|
|
195
|
+
assert domain.domain == "example.io"
|
|
196
|
+
assert domain.object == "organization_domain"
|
|
197
|
+
assert domain.id == "org_domain_01EHT88Z8WZEFWYPM6EC9BX2R8"
|
|
198
|
+
assert domain.state == "verified"
|
|
199
|
+
assert domain.organization_id == "org_01EHT88Z8J8795GZNQ4ZP1J81T"
|
|
200
|
+
assert domain.verification_strategy == "dns"
|
|
201
|
+
assert domain.verification_token == "token"
|
|
202
|
+
assert domain.verification_prefix is None
|
|
203
|
+
assert isinstance(domain.created_at, str)
|
|
204
|
+
assert isinstance(domain.updated_at, str)
|
|
201
205
|
|
|
202
206
|
def test_delete_organization(self, capture_and_mock_http_client_request):
|
|
203
207
|
request_kwargs = capture_and_mock_http_client_request(
|
|
@@ -344,6 +344,29 @@ class TestUserManagementBase(UserManagementFixtures):
|
|
|
344
344
|
"provider": "authkit",
|
|
345
345
|
}
|
|
346
346
|
|
|
347
|
+
def test_authorization_url_has_expected_query_params_with_provider_scopes(self):
|
|
348
|
+
provider = "GoogleOAuth"
|
|
349
|
+
provider_scopes = [
|
|
350
|
+
"https://www.googleapis.com/auth/calendar",
|
|
351
|
+
"https://www.googleapis.com/auth/admin.directory.group",
|
|
352
|
+
]
|
|
353
|
+
redirect_uri = "https://localhost/auth/callback"
|
|
354
|
+
authorization_url = self.user_management.get_authorization_url(
|
|
355
|
+
provider=provider,
|
|
356
|
+
provider_scopes=provider_scopes,
|
|
357
|
+
redirect_uri=redirect_uri,
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
parsed_url = urlparse(authorization_url)
|
|
361
|
+
assert parsed_url.path == "/user_management/authorize"
|
|
362
|
+
assert dict(parse_qsl(str(parsed_url.query))) == {
|
|
363
|
+
"provider": provider,
|
|
364
|
+
"provider_scopes": ",".join(provider_scopes),
|
|
365
|
+
"client_id": self.http_client.client_id,
|
|
366
|
+
"redirect_uri": redirect_uri,
|
|
367
|
+
"response_type": RESPONSE_TYPE_CODE,
|
|
368
|
+
}
|
|
369
|
+
|
|
347
370
|
def test_get_jwks_url(self):
|
|
348
371
|
expected = "%ssso/jwks/%s" % (
|
|
349
372
|
self.http_client.base_url,
|
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from tests.utils.fixtures.mock_vault_object import (
|
|
3
|
+
MockVaultObject,
|
|
4
|
+
MockObjectVersion,
|
|
5
|
+
MockObjectDigest,
|
|
6
|
+
MockObjectMetadata,
|
|
7
|
+
)
|
|
8
|
+
from tests.utils.list_resource import list_response_of
|
|
9
|
+
from tests.utils.syncify import syncify
|
|
10
|
+
from workos.vault import Vault
|
|
11
|
+
from workos.types.vault.key import KeyContext
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TestVault:
|
|
15
|
+
@pytest.fixture(autouse=True)
|
|
16
|
+
def setup(self, sync_http_client_for_test):
|
|
17
|
+
self.http_client = sync_http_client_for_test
|
|
18
|
+
self.vault = Vault(http_client=self.http_client)
|
|
19
|
+
|
|
20
|
+
@pytest.fixture
|
|
21
|
+
def mock_vault_object(self):
|
|
22
|
+
return MockVaultObject(
|
|
23
|
+
"vault_01234567890abcdef", "test-secret", "secret-value"
|
|
24
|
+
).dict()
|
|
25
|
+
|
|
26
|
+
@pytest.fixture
|
|
27
|
+
def mock_object_digest(self):
|
|
28
|
+
return MockObjectDigest("vault_01234567890abcdef", "test-secret").dict()
|
|
29
|
+
|
|
30
|
+
@pytest.fixture
|
|
31
|
+
def mock_object_metadata(self):
|
|
32
|
+
return MockObjectMetadata("vault_01234567890abcdef").dict()
|
|
33
|
+
|
|
34
|
+
@pytest.fixture
|
|
35
|
+
def mock_vault_object_no_value(self):
|
|
36
|
+
mock_obj = MockVaultObject("vault_01234567890abcdef", "test-secret")
|
|
37
|
+
mock_obj.value = None
|
|
38
|
+
return mock_obj.dict()
|
|
39
|
+
|
|
40
|
+
@pytest.fixture
|
|
41
|
+
def mock_vault_objects_list(self):
|
|
42
|
+
vault_objects = [
|
|
43
|
+
MockObjectDigest(f"vault_{i}", f"secret-{i}").dict() for i in range(5)
|
|
44
|
+
]
|
|
45
|
+
return {
|
|
46
|
+
"data": vault_objects,
|
|
47
|
+
"list_metadata": {"before": None, "after": None},
|
|
48
|
+
"object": "list",
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@pytest.fixture
|
|
52
|
+
def mock_vault_objects_multiple_pages(self):
|
|
53
|
+
vault_objects = [
|
|
54
|
+
MockObjectDigest(f"vault_{i}", f"secret-{i}").dict() for i in range(25)
|
|
55
|
+
]
|
|
56
|
+
return list_response_of(data=vault_objects)
|
|
57
|
+
|
|
58
|
+
@pytest.fixture
|
|
59
|
+
def mock_object_versions(self):
|
|
60
|
+
versions = [
|
|
61
|
+
MockObjectVersion(f"version_{i}", current_version=(i == 0)).dict()
|
|
62
|
+
for i in range(3)
|
|
63
|
+
]
|
|
64
|
+
return {"data": versions}
|
|
65
|
+
|
|
66
|
+
@pytest.fixture
|
|
67
|
+
def mock_data_key(self):
|
|
68
|
+
return {
|
|
69
|
+
"id": "key_01234567890abcdef",
|
|
70
|
+
"data_key": "MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=",
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
@pytest.fixture
|
|
74
|
+
def mock_data_key_pair(self):
|
|
75
|
+
return {
|
|
76
|
+
"context": {"key": "test-key"},
|
|
77
|
+
"id": "key_01234567890abcdef",
|
|
78
|
+
"data_key": "MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=",
|
|
79
|
+
"encrypted_keys": "ZW5jcnlwdGVkX2tleXNfZGF0YQ==",
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
def test_read_object_success(
|
|
83
|
+
self, mock_vault_object, capture_and_mock_http_client_request
|
|
84
|
+
):
|
|
85
|
+
request_kwargs = capture_and_mock_http_client_request(
|
|
86
|
+
self.http_client, mock_vault_object, 200
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
vault_object = self.vault.read_object(object_id="vault_01234567890abcdef")
|
|
90
|
+
|
|
91
|
+
assert request_kwargs["method"] == "get"
|
|
92
|
+
assert request_kwargs["url"].endswith("/vault/v1/kv/vault_01234567890abcdef")
|
|
93
|
+
assert vault_object.id == "vault_01234567890abcdef"
|
|
94
|
+
assert vault_object.name == "test-secret"
|
|
95
|
+
assert vault_object.value == "secret-value"
|
|
96
|
+
assert vault_object.metadata.environment_id == "env_01234567890abcdef"
|
|
97
|
+
|
|
98
|
+
def test_read_object_missing_object_id(self):
|
|
99
|
+
with pytest.raises(
|
|
100
|
+
ValueError, match="Incomplete arguments: 'object_id' is a required argument"
|
|
101
|
+
):
|
|
102
|
+
self.vault.read_object(object_id="")
|
|
103
|
+
|
|
104
|
+
def test_read_object_none_object_id(self):
|
|
105
|
+
with pytest.raises(
|
|
106
|
+
ValueError, match="Incomplete arguments: 'object_id' is a required argument"
|
|
107
|
+
):
|
|
108
|
+
self.vault.read_object(object_id=None)
|
|
109
|
+
|
|
110
|
+
def test_list_objects_default_params(
|
|
111
|
+
self, mock_vault_objects_list, capture_and_mock_http_client_request
|
|
112
|
+
):
|
|
113
|
+
request_kwargs = capture_and_mock_http_client_request(
|
|
114
|
+
self.http_client, mock_vault_objects_list, 200
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
vault_objects = self.vault.list_objects()
|
|
118
|
+
|
|
119
|
+
assert request_kwargs["method"] == "get"
|
|
120
|
+
assert request_kwargs["url"].endswith("/vault/v1/kv")
|
|
121
|
+
assert request_kwargs["params"]["limit"] == 10
|
|
122
|
+
assert "before" not in request_kwargs["params"]
|
|
123
|
+
assert "after" not in request_kwargs["params"]
|
|
124
|
+
assert len(vault_objects.data) == 5
|
|
125
|
+
assert vault_objects.data[0].id == "vault_0"
|
|
126
|
+
assert vault_objects.data[0].name == "secret-0"
|
|
127
|
+
|
|
128
|
+
def test_list_objects_with_params(
|
|
129
|
+
self, mock_vault_objects_list, capture_and_mock_http_client_request
|
|
130
|
+
):
|
|
131
|
+
request_kwargs = capture_and_mock_http_client_request(
|
|
132
|
+
self.http_client, mock_vault_objects_list, 200
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
vault_objects = self.vault.list_objects(
|
|
136
|
+
limit=5, before="vault_before", after="vault_after"
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
assert request_kwargs["method"] == "get"
|
|
140
|
+
assert request_kwargs["url"].endswith("/vault/v1/kv")
|
|
141
|
+
assert request_kwargs["params"]["limit"] == 5
|
|
142
|
+
assert request_kwargs["params"]["before"] == "vault_before"
|
|
143
|
+
assert request_kwargs["params"]["after"] == "vault_after"
|
|
144
|
+
|
|
145
|
+
def test_list_objects_auto_pagination(
|
|
146
|
+
self, mock_vault_objects_multiple_pages, test_auto_pagination
|
|
147
|
+
):
|
|
148
|
+
test_auto_pagination(
|
|
149
|
+
http_client=self.http_client,
|
|
150
|
+
list_function=self.vault.list_objects,
|
|
151
|
+
expected_all_page_data=mock_vault_objects_multiple_pages["data"],
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
def test_list_object_versions_success(
|
|
155
|
+
self, mock_object_versions, capture_and_mock_http_client_request
|
|
156
|
+
):
|
|
157
|
+
request_kwargs = capture_and_mock_http_client_request(
|
|
158
|
+
self.http_client, mock_object_versions, 200
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
versions = self.vault.list_object_versions(object_id="vault_01234567890abcdef")
|
|
162
|
+
|
|
163
|
+
assert request_kwargs["method"] == "get"
|
|
164
|
+
assert request_kwargs["url"].endswith(
|
|
165
|
+
"/vault/v1/kv/vault_01234567890abcdef/versions"
|
|
166
|
+
)
|
|
167
|
+
assert len(versions) == 3
|
|
168
|
+
assert versions[0].id == "version_0"
|
|
169
|
+
assert versions[0].current_version is True
|
|
170
|
+
assert versions[1].current_version is False
|
|
171
|
+
|
|
172
|
+
def test_list_object_versions_empty_data(
|
|
173
|
+
self, capture_and_mock_http_client_request
|
|
174
|
+
):
|
|
175
|
+
request_kwargs = capture_and_mock_http_client_request(
|
|
176
|
+
self.http_client, {"data": []}, 200
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
versions = self.vault.list_object_versions(object_id="vault_01234567890abcdef")
|
|
180
|
+
|
|
181
|
+
assert request_kwargs["method"] == "get"
|
|
182
|
+
assert len(versions) == 0
|
|
183
|
+
|
|
184
|
+
def test_create_object_success(
|
|
185
|
+
self, mock_object_metadata, capture_and_mock_http_client_request
|
|
186
|
+
):
|
|
187
|
+
request_kwargs = capture_and_mock_http_client_request(
|
|
188
|
+
self.http_client, mock_object_metadata, 200
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
object_metadata = self.vault.create_object(
|
|
192
|
+
name="test-secret",
|
|
193
|
+
value="secret-value",
|
|
194
|
+
key_context=KeyContext({"key": "test-key"}),
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
assert request_kwargs["method"] == "post"
|
|
198
|
+
assert request_kwargs["url"].endswith("/vault/v1/kv")
|
|
199
|
+
assert request_kwargs["json"]["name"] == "test-secret"
|
|
200
|
+
assert request_kwargs["json"]["value"] == "secret-value"
|
|
201
|
+
assert request_kwargs["json"]["key_context"] == KeyContext({"key": "test-key"})
|
|
202
|
+
assert object_metadata.id == "vault_01234567890abcdef"
|
|
203
|
+
|
|
204
|
+
def test_create_object_missing_name(self):
|
|
205
|
+
with pytest.raises(
|
|
206
|
+
ValueError,
|
|
207
|
+
match="Incomplete arguments: 'name' and 'value' are required arguments",
|
|
208
|
+
):
|
|
209
|
+
self.vault.create_object(
|
|
210
|
+
name="",
|
|
211
|
+
value="secret-value",
|
|
212
|
+
key_context=KeyContext({"key": "test-key"}),
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
def test_create_object_missing_value(self):
|
|
216
|
+
with pytest.raises(
|
|
217
|
+
ValueError,
|
|
218
|
+
match="Incomplete arguments: 'name' and 'value' are required arguments",
|
|
219
|
+
):
|
|
220
|
+
self.vault.create_object(
|
|
221
|
+
name="test-secret",
|
|
222
|
+
value="",
|
|
223
|
+
key_context=KeyContext({"key": "test-key"}),
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
def test_create_object_missing_both(self):
|
|
227
|
+
with pytest.raises(
|
|
228
|
+
ValueError,
|
|
229
|
+
match="Incomplete arguments: 'name' and 'value' are required arguments",
|
|
230
|
+
):
|
|
231
|
+
self.vault.create_object(
|
|
232
|
+
name="", value="", key_context=KeyContext({"key": "test-key"})
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
def test_update_object_with_value(
|
|
236
|
+
self, mock_vault_object, capture_and_mock_http_client_request
|
|
237
|
+
):
|
|
238
|
+
request_kwargs = capture_and_mock_http_client_request(
|
|
239
|
+
self.http_client, mock_vault_object, 200
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
vault_object = self.vault.update_object(
|
|
243
|
+
object_id="vault_01234567890abcdef",
|
|
244
|
+
value="updated-value",
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
assert request_kwargs["method"] == "put"
|
|
248
|
+
assert request_kwargs["url"].endswith("/vault/v1/kv/vault_01234567890abcdef")
|
|
249
|
+
assert request_kwargs["json"]["value"] == "updated-value"
|
|
250
|
+
assert "version_check" not in request_kwargs["json"]
|
|
251
|
+
assert vault_object.id == "vault_01234567890abcdef"
|
|
252
|
+
|
|
253
|
+
def test_update_object_with_version_check(
|
|
254
|
+
self, mock_vault_object, capture_and_mock_http_client_request
|
|
255
|
+
):
|
|
256
|
+
request_kwargs = capture_and_mock_http_client_request(
|
|
257
|
+
self.http_client, mock_vault_object, 200
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
vault_object = self.vault.update_object(
|
|
261
|
+
object_id="vault_01234567890abcdef",
|
|
262
|
+
value="updated-value",
|
|
263
|
+
version_check="version_123",
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
assert request_kwargs["method"] == "put"
|
|
267
|
+
assert request_kwargs["json"]["value"] == "updated-value"
|
|
268
|
+
assert request_kwargs["json"]["version_check"] == "version_123"
|
|
269
|
+
|
|
270
|
+
def test_update_object_missing_value(self):
|
|
271
|
+
with pytest.raises(
|
|
272
|
+
TypeError, match="missing 1 required keyword-only argument: 'value'"
|
|
273
|
+
):
|
|
274
|
+
self.vault.update_object(object_id="vault_01234567890abcdef")
|
|
275
|
+
|
|
276
|
+
def test_update_object_missing_object_id(self):
|
|
277
|
+
with pytest.raises(
|
|
278
|
+
ValueError, match="Incomplete arguments: 'object_id' is a required argument"
|
|
279
|
+
):
|
|
280
|
+
self.vault.update_object(object_id="", value="test-value")
|
|
281
|
+
|
|
282
|
+
def test_update_object_none_object_id(self):
|
|
283
|
+
with pytest.raises(
|
|
284
|
+
ValueError,
|
|
285
|
+
match="Incomplete arguments: 'object_id' is a required argument",
|
|
286
|
+
):
|
|
287
|
+
self.vault.update_object(object_id=None, value="updated-value")
|
|
288
|
+
|
|
289
|
+
def test_delete_object_success(self, capture_and_mock_http_client_request):
|
|
290
|
+
request_kwargs = capture_and_mock_http_client_request(self.http_client, {}, 204)
|
|
291
|
+
|
|
292
|
+
result = self.vault.delete_object(object_id="vault_01234567890abcdef")
|
|
293
|
+
|
|
294
|
+
assert request_kwargs["method"] == "delete"
|
|
295
|
+
assert request_kwargs["url"].endswith("/vault/v1/kv/vault_01234567890abcdef")
|
|
296
|
+
assert result is None
|
|
297
|
+
|
|
298
|
+
def test_delete_object_missing_object_id(self):
|
|
299
|
+
with pytest.raises(
|
|
300
|
+
ValueError, match="Incomplete arguments: 'object_id' is a required argument"
|
|
301
|
+
):
|
|
302
|
+
self.vault.delete_object(object_id="")
|
|
303
|
+
|
|
304
|
+
def test_delete_object_none_object_id(self):
|
|
305
|
+
with pytest.raises(
|
|
306
|
+
ValueError, match="Incomplete arguments: 'object_id' is a required argument"
|
|
307
|
+
):
|
|
308
|
+
self.vault.delete_object(object_id=None)
|
|
309
|
+
|
|
310
|
+
def test_create_data_key_success(
|
|
311
|
+
self, mock_data_key_pair, capture_and_mock_http_client_request
|
|
312
|
+
):
|
|
313
|
+
request_kwargs = capture_and_mock_http_client_request(
|
|
314
|
+
self.http_client, mock_data_key_pair, 200
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
data_key_pair = self.vault.create_data_key(
|
|
318
|
+
key_context=KeyContext({"key": "test-key"})
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
assert request_kwargs["method"] == "post"
|
|
322
|
+
assert request_kwargs["url"].endswith("/vault/v1/keys/data-key")
|
|
323
|
+
assert request_kwargs["json"]["context"] == KeyContext({"key": "test-key"})
|
|
324
|
+
assert data_key_pair.data_key.id == "key_01234567890abcdef"
|
|
325
|
+
assert data_key_pair.encrypted_keys == "ZW5jcnlwdGVkX2tleXNfZGF0YQ=="
|
|
326
|
+
|
|
327
|
+
def test_decrypt_data_key_success(
|
|
328
|
+
self, mock_data_key, capture_and_mock_http_client_request
|
|
329
|
+
):
|
|
330
|
+
request_kwargs = capture_and_mock_http_client_request(
|
|
331
|
+
self.http_client, mock_data_key, 200
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
data_key = self.vault.decrypt_data_key(keys="ZW5jcnlwdGVkX2tleXNfZGF0YQ==")
|
|
335
|
+
|
|
336
|
+
assert request_kwargs["method"] == "post"
|
|
337
|
+
assert request_kwargs["url"].endswith("/vault/v1/keys/decrypt")
|
|
338
|
+
assert request_kwargs["json"]["keys"] == "ZW5jcnlwdGVkX2tleXNfZGF0YQ=="
|
|
339
|
+
assert data_key.id == "key_01234567890abcdef"
|
|
340
|
+
assert data_key.key == "MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY="
|
|
341
|
+
|
|
342
|
+
def test_encrypt_success(
|
|
343
|
+
self, mock_data_key_pair, capture_and_mock_http_client_request
|
|
344
|
+
):
|
|
345
|
+
# Mock the create_data_key call
|
|
346
|
+
request_kwargs = capture_and_mock_http_client_request(
|
|
347
|
+
self.http_client, mock_data_key_pair, 200
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
plaintext = "Hello, World!"
|
|
351
|
+
context = KeyContext({"key": "test-key"})
|
|
352
|
+
|
|
353
|
+
encrypted_data = self.vault.encrypt(data=plaintext, key_context=context)
|
|
354
|
+
|
|
355
|
+
# Verify create_data_key was called
|
|
356
|
+
assert request_kwargs["method"] == "post"
|
|
357
|
+
assert request_kwargs["url"].endswith("/vault/v1/keys/data-key")
|
|
358
|
+
assert request_kwargs["json"]["context"] == KeyContext({"key": "test-key"})
|
|
359
|
+
|
|
360
|
+
# Verify we got encrypted data back
|
|
361
|
+
assert isinstance(encrypted_data, str)
|
|
362
|
+
assert len(encrypted_data) > 0
|
|
363
|
+
|
|
364
|
+
def test_encrypt_with_associated_data(
|
|
365
|
+
self, mock_data_key_pair, capture_and_mock_http_client_request
|
|
366
|
+
):
|
|
367
|
+
# Mock the create_data_key call
|
|
368
|
+
capture_and_mock_http_client_request(self.http_client, mock_data_key_pair, 200)
|
|
369
|
+
|
|
370
|
+
plaintext = "Hello, World!"
|
|
371
|
+
context = KeyContext({"key": "test-key"})
|
|
372
|
+
associated_data = "additional-context"
|
|
373
|
+
|
|
374
|
+
encrypted_data = self.vault.encrypt(
|
|
375
|
+
data=plaintext, key_context=context, associated_data=associated_data
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
# Verify we got encrypted data back
|
|
379
|
+
assert isinstance(encrypted_data, str)
|
|
380
|
+
assert len(encrypted_data) > 0
|
|
381
|
+
|
|
382
|
+
def test_decrypt_success(self, mock_data_key, capture_and_mock_http_client_request):
|
|
383
|
+
# First encrypt some data to get a valid encrypted payload
|
|
384
|
+
mock_data_key_pair = {
|
|
385
|
+
"context": {"key": "test-key"},
|
|
386
|
+
"id": "key_01234567890abcdef",
|
|
387
|
+
"data_key": "MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=",
|
|
388
|
+
"encrypted_keys": "ZW5jcnlwdGVkX2tleXNfZGF0YQ==",
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
# Mock create_data_key for encryption
|
|
392
|
+
capture_and_mock_http_client_request(self.http_client, mock_data_key_pair, 200)
|
|
393
|
+
|
|
394
|
+
plaintext = "Hello, World!"
|
|
395
|
+
context = KeyContext({"key": "test-key"})
|
|
396
|
+
encrypted_data = self.vault.encrypt(data=plaintext, key_context=context)
|
|
397
|
+
|
|
398
|
+
# Now mock decrypt_data_key for decryption
|
|
399
|
+
capture_and_mock_http_client_request(self.http_client, mock_data_key, 200)
|
|
400
|
+
|
|
401
|
+
# Decrypt the data
|
|
402
|
+
decrypted_text = self.vault.decrypt(encrypted_data=encrypted_data)
|
|
403
|
+
|
|
404
|
+
# Verify decryption worked
|
|
405
|
+
assert decrypted_text == plaintext
|
|
406
|
+
|
|
407
|
+
def test_decrypt_with_associated_data(
|
|
408
|
+
self, mock_data_key, capture_and_mock_http_client_request
|
|
409
|
+
):
|
|
410
|
+
# First encrypt some data with associated data
|
|
411
|
+
mock_data_key_pair = {
|
|
412
|
+
"context": {"key": "test-key"},
|
|
413
|
+
"id": "key_01234567890abcdef",
|
|
414
|
+
"data_key": "MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=",
|
|
415
|
+
"encrypted_keys": "ZW5jcnlwdGVkX2tleXNfZGF0YQ==",
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
# Mock create_data_key for encryption
|
|
419
|
+
capture_and_mock_http_client_request(self.http_client, mock_data_key_pair, 200)
|
|
420
|
+
|
|
421
|
+
plaintext = "Hello, World!"
|
|
422
|
+
context = KeyContext({"key": "test-key"})
|
|
423
|
+
associated_data = "additional-context"
|
|
424
|
+
encrypted_data = self.vault.encrypt(
|
|
425
|
+
data=plaintext, key_context=context, associated_data=associated_data
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
# Now mock decrypt_data_key for decryption
|
|
429
|
+
capture_and_mock_http_client_request(self.http_client, mock_data_key, 200)
|
|
430
|
+
|
|
431
|
+
# Decrypt the data with the same associated data
|
|
432
|
+
decrypted_text = self.vault.decrypt(
|
|
433
|
+
encrypted_data=encrypted_data, associated_data=associated_data
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
# Verify decryption worked
|
|
437
|
+
assert decrypted_text == plaintext
|
|
438
|
+
|
|
439
|
+
def test_encrypt_decrypt_roundtrip(
|
|
440
|
+
self, mock_data_key_pair, mock_data_key, capture_and_mock_http_client_request
|
|
441
|
+
):
|
|
442
|
+
"""Test that encrypt/decrypt works correctly together"""
|
|
443
|
+
|
|
444
|
+
# Mock create_data_key for encryption
|
|
445
|
+
capture_and_mock_http_client_request(self.http_client, mock_data_key_pair, 200)
|
|
446
|
+
|
|
447
|
+
plaintext = "This is a test message for encryption!"
|
|
448
|
+
context = KeyContext({"env": "test", "service": "vault"})
|
|
449
|
+
|
|
450
|
+
# Encrypt the data
|
|
451
|
+
encrypted_data = self.vault.encrypt(data=plaintext, key_context=context)
|
|
452
|
+
|
|
453
|
+
# Mock decrypt_data_key for decryption
|
|
454
|
+
capture_and_mock_http_client_request(self.http_client, mock_data_key, 200)
|
|
455
|
+
|
|
456
|
+
# Decrypt the data
|
|
457
|
+
decrypted_text = self.vault.decrypt(encrypted_data=encrypted_data)
|
|
458
|
+
|
|
459
|
+
# Verify roundtrip worked
|
|
460
|
+
assert decrypted_text == plaintext
|
|
@@ -14,6 +14,7 @@ from workos.user_management import AsyncUserManagement
|
|
|
14
14
|
from workos.utils.http_client import AsyncHTTPClient
|
|
15
15
|
from workos.webhooks import WebhooksModule
|
|
16
16
|
from workos.widgets import WidgetsModule
|
|
17
|
+
from workos.vault import VaultModule
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
class AsyncClient(BaseClient):
|
|
@@ -112,3 +113,9 @@ class AsyncClient(BaseClient):
|
|
|
112
113
|
raise NotImplementedError(
|
|
113
114
|
"Widgets APIs are not yet supported in the async client."
|
|
114
115
|
)
|
|
116
|
+
|
|
117
|
+
@property
|
|
118
|
+
def vault(self) -> VaultModule:
|
|
119
|
+
raise NotImplementedError(
|
|
120
|
+
"Vault APIs are not yet supported in the async client."
|
|
121
|
+
)
|
|
@@ -14,6 +14,7 @@ from workos.events import Events
|
|
|
14
14
|
from workos.user_management import UserManagement
|
|
15
15
|
from workos.utils.http_client import SyncHTTPClient
|
|
16
16
|
from workos.widgets import Widgets
|
|
17
|
+
from workos.vault import Vault
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
class SyncClient(BaseClient):
|
|
@@ -116,3 +117,9 @@ class SyncClient(BaseClient):
|
|
|
116
117
|
if not getattr(self, "_widgets", None):
|
|
117
118
|
self._widgets = Widgets(http_client=self._http_client)
|
|
118
119
|
return self._widgets
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def vault(self) -> Vault:
|
|
123
|
+
if not getattr(self, "_vault", None):
|
|
124
|
+
self._vault = Vault(http_client=self._http_client)
|
|
125
|
+
return self._vault
|
|
@@ -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,
|
|
@@ -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",
|
|
@@ -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,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
|