Skip to content

Commit 30cdb9a

Browse files
csrbarberclaude
andcommitted
Add authorization permissions support
Introduce the authorization module with CRUD operations for permissions including create, list (paginated), get, update (PATCH), and delete. Register the module on both sync and async clients. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 5cf8252 commit 30cdb9a

File tree

10 files changed

+468
-0
lines changed

10 files changed

+468
-0
lines changed

tests/test_authorization.py

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
from typing import Union
2+
3+
import pytest
4+
from tests.utils.fixtures.mock_permission import MockPermission
5+
from tests.utils.list_resource import list_response_of
6+
from tests.utils.syncify import syncify
7+
from workos.authorization import AsyncAuthorization, Authorization
8+
9+
10+
@pytest.mark.sync_and_async(Authorization, AsyncAuthorization)
11+
class TestAuthorization:
12+
@pytest.fixture(autouse=True)
13+
def setup(self, module_instance: Union[Authorization, AsyncAuthorization]):
14+
self.http_client = module_instance._http_client
15+
self.authorization = module_instance
16+
17+
@pytest.fixture
18+
def mock_permission(self):
19+
return MockPermission(id="perm_01ABC").dict()
20+
21+
@pytest.fixture
22+
def mock_permissions(self):
23+
permission_list = [
24+
MockPermission(id=f"perm_{i}", slug=f"perm-{i}").dict() for i in range(5)
25+
]
26+
return {
27+
"data": permission_list,
28+
"list_metadata": {"before": None, "after": None},
29+
"object": "list",
30+
}
31+
32+
@pytest.fixture
33+
def mock_permissions_multiple_data_pages(self):
34+
permission_list = [
35+
MockPermission(id=f"perm_{i}", slug=f"perm-{i}").dict() for i in range(40)
36+
]
37+
return list_response_of(data=permission_list)
38+
39+
def test_create_permission(
40+
self, mock_permission, capture_and_mock_http_client_request
41+
):
42+
request_kwargs = capture_and_mock_http_client_request(
43+
self.http_client, mock_permission, 201
44+
)
45+
46+
permission = syncify(
47+
self.authorization.create_permission(
48+
slug="documents:read", name="Read Documents"
49+
)
50+
)
51+
52+
assert permission.id == "perm_01ABC"
53+
assert permission.slug == "documents:read"
54+
assert request_kwargs["method"] == "post"
55+
assert request_kwargs["url"].endswith("/authorization/permissions")
56+
assert request_kwargs["json"] == {
57+
"slug": "documents:read",
58+
"name": "Read Documents",
59+
}
60+
61+
def test_create_permission_with_description(
62+
self, mock_permission, capture_and_mock_http_client_request
63+
):
64+
request_kwargs = capture_and_mock_http_client_request(
65+
self.http_client, mock_permission, 201
66+
)
67+
68+
syncify(
69+
self.authorization.create_permission(
70+
slug="documents:read",
71+
name="Read Documents",
72+
description="Allows reading documents",
73+
)
74+
)
75+
76+
assert request_kwargs["json"] == {
77+
"slug": "documents:read",
78+
"name": "Read Documents",
79+
"description": "Allows reading documents",
80+
}
81+
82+
def test_list_permissions(
83+
self, mock_permissions, capture_and_mock_http_client_request
84+
):
85+
request_kwargs = capture_and_mock_http_client_request(
86+
self.http_client, mock_permissions, 200
87+
)
88+
89+
permissions_response = syncify(self.authorization.list_permissions())
90+
91+
assert request_kwargs["method"] == "get"
92+
assert request_kwargs["url"].endswith("/authorization/permissions")
93+
assert len(permissions_response.data) == 5
94+
95+
def test_list_permissions_auto_pagination(
96+
self,
97+
mock_permissions_multiple_data_pages,
98+
test_auto_pagination,
99+
):
100+
test_auto_pagination(
101+
http_client=self.http_client,
102+
list_function=self.authorization.list_permissions,
103+
expected_all_page_data=mock_permissions_multiple_data_pages["data"],
104+
)
105+
106+
def test_get_permission(
107+
self, mock_permission, capture_and_mock_http_client_request
108+
):
109+
request_kwargs = capture_and_mock_http_client_request(
110+
self.http_client, mock_permission, 200
111+
)
112+
113+
permission = syncify(self.authorization.get_permission("documents:read"))
114+
115+
assert permission.id == "perm_01ABC"
116+
assert request_kwargs["method"] == "get"
117+
assert request_kwargs["url"].endswith(
118+
"/authorization/permissions/documents:read"
119+
)
120+
121+
def test_update_permission(
122+
self, mock_permission, capture_and_mock_http_client_request
123+
):
124+
request_kwargs = capture_and_mock_http_client_request(
125+
self.http_client, mock_permission, 200
126+
)
127+
128+
permission = syncify(
129+
self.authorization.update_permission("documents:read", name="Updated Name")
130+
)
131+
132+
assert permission.id == "perm_01ABC"
133+
assert request_kwargs["method"] == "patch"
134+
assert request_kwargs["url"].endswith(
135+
"/authorization/permissions/documents:read"
136+
)
137+
assert request_kwargs["json"] == {"name": "Updated Name"}
138+
139+
def test_update_permission_with_description(
140+
self, mock_permission, capture_and_mock_http_client_request
141+
):
142+
request_kwargs = capture_and_mock_http_client_request(
143+
self.http_client, mock_permission, 200
144+
)
145+
146+
syncify(
147+
self.authorization.update_permission(
148+
"documents:read",
149+
name="Updated Name",
150+
description="Updated description",
151+
)
152+
)
153+
154+
assert request_kwargs["json"] == {
155+
"name": "Updated Name",
156+
"description": "Updated description",
157+
}
158+
159+
def test_delete_permission(self, capture_and_mock_http_client_request):
160+
request_kwargs = capture_and_mock_http_client_request(
161+
self.http_client,
162+
202,
163+
headers={"content-type": "text/plain; charset=utf-8"},
164+
)
165+
166+
response = syncify(self.authorization.delete_permission("documents:read"))
167+
168+
assert response is None
169+
assert request_kwargs["method"] == "delete"
170+
assert request_kwargs["url"].endswith(
171+
"/authorization/permissions/documents:read"
172+
)
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import datetime
2+
3+
from workos.types.authorization.permission import Permission
4+
5+
6+
class MockPermission(Permission):
7+
def __init__(self, id: str, slug: str = "documents:read"):
8+
now = datetime.datetime.now().isoformat()
9+
super().__init__(
10+
object="permission",
11+
id=id,
12+
slug=slug,
13+
name="Read Documents",
14+
description="Allows reading documents",
15+
system=False,
16+
created_at=now,
17+
updated_at=now,
18+
)

workos/_base_client.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from workos.utils._base_http_client import DEFAULT_REQUEST_TIMEOUT
88
from workos.utils.http_client import HTTPClient
99
from workos.audit_logs import AuditLogsModule
10+
from workos.authorization import AuthorizationModule
1011
from workos.directory_sync import DirectorySyncModule
1112
from workos.events import EventsModule
1213
from workos.mfa import MFAModule
@@ -65,6 +66,10 @@ def __init__(
6566
else int(os.getenv("WORKOS_REQUEST_TIMEOUT", DEFAULT_REQUEST_TIMEOUT))
6667
)
6768

69+
@property
70+
@abstractmethod
71+
def authorization(self) -> AuthorizationModule: ...
72+
6873
@property
6974
@abstractmethod
7075
def audit_logs(self) -> AuditLogsModule: ...

workos/async_client.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from workos.__about__ import __version__
33
from workos._base_client import BaseClient
44
from workos.audit_logs import AuditLogsModule
5+
from workos.authorization import AsyncAuthorization
56
from workos.directory_sync import AsyncDirectorySync
67
from workos.events import AsyncEvents
78
from workos.fga import FGAModule
@@ -45,6 +46,12 @@ def __init__(
4546
timeout=self.request_timeout,
4647
)
4748

49+
@property
50+
def authorization(self) -> AsyncAuthorization:
51+
if not getattr(self, "_authorization", None):
52+
self._authorization = AsyncAuthorization(self._http_client)
53+
return self._authorization
54+
4855
@property
4956
def sso(self) -> AsyncSSO:
5057
if not getattr(self, "_sso", None):

0 commit comments

Comments
 (0)