Skip to content

Commit da598c8

Browse files
fga pt3
1 parent a5d3ce2 commit da598c8

3 files changed

Lines changed: 215 additions & 26 deletions

File tree

src/workos/authorization.py

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from pydantic import TypeAdapter
44
from typing_extensions import TypedDict
55

6+
from workos.types.authorization.access_evaluation import AccessEvaluation
67
from workos.types.authorization.environment_role import (
78
EnvironmentRole,
89
EnvironmentRoleList,
@@ -44,6 +45,18 @@ class ParentResourceByExternalId(TypedDict):
4445

4546
ParentResource = Union[ParentResourceById, ParentResourceByExternalId]
4647

48+
49+
class CheckResourceById(TypedDict):
50+
resource_id: str
51+
52+
53+
class CheckResourceByExternalId(TypedDict):
54+
resource_type: str
55+
external_id: str
56+
57+
58+
CheckResource = Union[CheckResourceById, CheckResourceByExternalId]
59+
4760
_role_adapter: TypeAdapter[Role] = TypeAdapter(Role)
4861

4962

@@ -206,6 +219,16 @@ def delete_resource(
206219
cascade_delete: Optional[bool] = None,
207220
) -> SyncOrAsync[None]: ...
208221

222+
# Access Evaluation
223+
224+
def check(
225+
self,
226+
organization_membership_id: str,
227+
*,
228+
resource: CheckResource,
229+
relation: str,
230+
) -> SyncOrAsync[AccessEvaluation]: ...
231+
209232

210233
class Authorization(AuthorizationModule):
211234
_http_client: SyncHTTPClient
@@ -522,8 +545,8 @@ def create_resource(
522545

523546
def update_resource(
524547
self,
525-
*,
526548
resource_id: str,
549+
*,
527550
name: Optional[str] = None,
528551
description: Optional[str] = None,
529552
) -> Resource:
@@ -543,8 +566,8 @@ def update_resource(
543566

544567
def delete_resource(
545568
self,
546-
*,
547569
resource_id: str,
570+
*,
548571
cascade_delete: Optional[bool] = None,
549572
) -> None:
550573
if cascade_delete is not None:
@@ -558,6 +581,28 @@ def delete_resource(
558581
method=REQUEST_METHOD_DELETE,
559582
)
560583

584+
# Access Evaluation
585+
586+
def check(
587+
self,
588+
organization_membership_id: str,
589+
*,
590+
resource: CheckResource,
591+
relation: str,
592+
) -> AccessEvaluation:
593+
json: Dict[str, Any] = {
594+
"resource": resource,
595+
"relation": relation,
596+
}
597+
598+
response = self._http_client.request(
599+
f"authorization/organization_memberships/{organization_membership_id}/check",
600+
method=REQUEST_METHOD_POST,
601+
json=json,
602+
)
603+
604+
return AccessEvaluation.model_validate(response)
605+
561606

562607
class AsyncAuthorization(AuthorizationModule):
563608
_http_client: AsyncHTTPClient
@@ -909,3 +954,25 @@ async def delete_resource(
909954
f"{AUTHORIZATION_RESOURCES_PATH}/{resource_id}",
910955
method=REQUEST_METHOD_DELETE,
911956
)
957+
958+
# Access Evaluation
959+
960+
async def check(
961+
self,
962+
organization_membership_id: str,
963+
*,
964+
resource: CheckResource,
965+
relation: str,
966+
) -> AccessEvaluation:
967+
json: Dict[str, Any] = {
968+
"resource": resource,
969+
"relation": relation,
970+
}
971+
972+
response = await self._http_client.request(
973+
f"authorization/organization_memberships/{organization_membership_id}/check",
974+
method=REQUEST_METHOD_POST,
975+
json=json,
976+
)
977+
978+
return AccessEvaluation.model_validate(response)

tests/test_authorization_check.py

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
from typing import Union
2+
3+
import pytest
4+
from tests.utils.syncify import syncify
5+
from workos.authorization import AsyncAuthorization, Authorization
6+
7+
8+
@pytest.mark.sync_and_async(Authorization, AsyncAuthorization)
9+
class TestAuthorizationCheck:
10+
@pytest.fixture(autouse=True)
11+
def setup(self, module_instance: Union[Authorization, AsyncAuthorization]):
12+
self.http_client = module_instance._http_client
13+
self.authorization = module_instance
14+
15+
# --- check: authorized result ---
16+
17+
def test_check_authorized(self, capture_and_mock_http_client_request):
18+
request_kwargs = capture_and_mock_http_client_request(
19+
self.http_client, {"authorized": True}, 200
20+
)
21+
22+
result = syncify(
23+
self.authorization.check(
24+
"om_01ABC123",
25+
resource={"resource_id": "res_01ABC"},
26+
relation="viewer",
27+
)
28+
)
29+
30+
assert result.authorized is True
31+
assert request_kwargs["method"] == "post"
32+
33+
# --- check: unauthorized result ---
34+
35+
def test_check_unauthorized(self, capture_and_mock_http_client_request):
36+
request_kwargs = capture_and_mock_http_client_request(
37+
self.http_client, {"authorized": False}, 200
38+
)
39+
40+
result = syncify(
41+
self.authorization.check(
42+
"om_01ABC123",
43+
resource={"resource_id": "res_01ABC"},
44+
relation="editor",
45+
)
46+
)
47+
48+
assert result.authorized is False
49+
assert request_kwargs["method"] == "post"
50+
51+
# --- check: resource by internal ID ---
52+
53+
def test_check_resource_by_internal_id(self, capture_and_mock_http_client_request):
54+
request_kwargs = capture_and_mock_http_client_request(
55+
self.http_client, {"authorized": True}, 200
56+
)
57+
58+
syncify(
59+
self.authorization.check(
60+
"om_01ABC123",
61+
resource={"resource_id": "res_01ABC"},
62+
relation="viewer",
63+
)
64+
)
65+
66+
assert request_kwargs["json"] == {
67+
"resource": {"resource_id": "res_01ABC"},
68+
"relation": "viewer",
69+
}
70+
71+
# --- check: resource by external ID ---
72+
73+
def test_check_resource_by_external_id(self, capture_and_mock_http_client_request):
74+
request_kwargs = capture_and_mock_http_client_request(
75+
self.http_client, {"authorized": True}, 200
76+
)
77+
78+
syncify(
79+
self.authorization.check(
80+
"om_01ABC123",
81+
resource={"resource_type": "document", "external_id": "my-doc-456"},
82+
relation="editor",
83+
)
84+
)
85+
86+
assert request_kwargs["json"] == {
87+
"resource": {"resource_type": "document", "external_id": "my-doc-456"},
88+
"relation": "editor",
89+
}
90+
91+
# --- check: URL construction ---
92+
93+
def test_check_url_contains_org_membership_id(
94+
self, capture_and_mock_http_client_request
95+
):
96+
request_kwargs = capture_and_mock_http_client_request(
97+
self.http_client, {"authorized": True}, 200
98+
)
99+
100+
syncify(
101+
self.authorization.check(
102+
"om_01MEMBERSHIP",
103+
resource={"resource_id": "res_01ABC"},
104+
relation="viewer",
105+
)
106+
)
107+
108+
assert request_kwargs["url"].endswith(
109+
"/authorization/organization_memberships/om_01MEMBERSHIP/check"
110+
)

tests/test_authorization_resource_crud.py

Lines changed: 36 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -42,17 +42,23 @@ def test_create_resource_required_fields_only(
4242

4343
resource = syncify(
4444
self.authorization.create_resource(
45-
resource_type="document",
45+
resource_type_slug="document",
4646
organization_id="org_01EHT88Z8J8795GZNQ4ZP1J81T",
47+
external_id="ext_123",
48+
name="Test Resource",
49+
parent={"parent_resource_id": "res_01PARENT"},
4750
)
4851
)
4952

5053
assert resource.id == "res_01ABC"
5154
assert request_kwargs["method"] == "post"
5255
assert request_kwargs["url"].endswith("/authorization/resources")
5356
assert request_kwargs["json"] == {
54-
"resource_type": "document",
57+
"resource_type_slug": "document",
5558
"organization_id": "org_01EHT88Z8J8795GZNQ4ZP1J81T",
59+
"external_id": "ext_123",
60+
"name": "Test Resource",
61+
"parent_resource_id": "res_01PARENT",
5662
}
5763

5864
def test_create_resource_with_all_optional_fields(
@@ -64,20 +70,22 @@ def test_create_resource_with_all_optional_fields(
6470

6571
syncify(
6672
self.authorization.create_resource(
67-
resource_type="document",
73+
resource_type_slug="document",
6874
organization_id="org_01EHT88Z8J8795GZNQ4ZP1J81T",
6975
external_id="ext_123",
70-
meta={"key": "value"},
71-
parent={"resource_id": "res_01PARENT"},
76+
name="Test Resource",
77+
parent={"parent_resource_id": "res_01PARENT"},
78+
description="A test resource",
7279
)
7380
)
7481

7582
assert request_kwargs["json"] == {
76-
"resource_type": "document",
83+
"resource_type_slug": "document",
7784
"organization_id": "org_01EHT88Z8J8795GZNQ4ZP1J81T",
7885
"external_id": "ext_123",
79-
"meta": {"key": "value"},
80-
"parent": {"resource_id": "res_01PARENT"},
86+
"name": "Test Resource",
87+
"parent_resource_id": "res_01PARENT",
88+
"description": "A test resource",
8189
}
8290

8391
def test_create_resource_with_parent_by_id(
@@ -89,13 +97,15 @@ def test_create_resource_with_parent_by_id(
8997

9098
syncify(
9199
self.authorization.create_resource(
92-
resource_type="document",
100+
resource_type_slug="document",
93101
organization_id="org_01EHT88Z8J8795GZNQ4ZP1J81T",
94-
parent={"resource_id": "res_01PARENT"},
102+
external_id="ext_123",
103+
name="Test Resource",
104+
parent={"parent_resource_id": "res_01PARENT"},
95105
)
96106
)
97107

98-
assert request_kwargs["json"]["parent"] == {"resource_id": "res_01PARENT"}
108+
assert request_kwargs["json"]["parent_resource_id"] == "res_01PARENT"
99109

100110
def test_create_resource_with_parent_by_external_id(
101111
self, mock_resource, capture_and_mock_http_client_request
@@ -106,25 +116,23 @@ def test_create_resource_with_parent_by_external_id(
106116

107117
syncify(
108118
self.authorization.create_resource(
109-
resource_type="document",
119+
resource_type_slug="document",
110120
organization_id="org_01EHT88Z8J8795GZNQ4ZP1J81T",
121+
external_id="ext_123",
122+
name="Test Resource",
111123
parent={
112-
"organization_id": "org_01EHT88Z8J8795GZNQ4ZP1J81T",
113-
"resource_type": "folder",
114-
"external_id": "ext_parent_456",
124+
"parent_resource_external_id": "ext_parent_456",
125+
"parent_resource_type_slug": "folder",
115126
},
116127
)
117128
)
118129

119-
assert request_kwargs["json"]["parent"] == {
120-
"organization_id": "org_01EHT88Z8J8795GZNQ4ZP1J81T",
121-
"resource_type": "folder",
122-
"external_id": "ext_parent_456",
123-
}
130+
assert request_kwargs["json"]["parent_resource_external_id"] == "ext_parent_456"
131+
assert request_kwargs["json"]["parent_resource_type_slug"] == "folder"
124132

125133
# --- update_resource ---
126134

127-
def test_update_resource_with_meta(
135+
def test_update_resource_with_name_and_description(
128136
self, mock_resource, capture_and_mock_http_client_request
129137
):
130138
request_kwargs = capture_and_mock_http_client_request(
@@ -134,16 +142,20 @@ def test_update_resource_with_meta(
134142
resource = syncify(
135143
self.authorization.update_resource(
136144
"res_01ABC",
137-
meta={"updated_key": "updated_value"},
145+
name="Updated Name",
146+
description="Updated description",
138147
)
139148
)
140149

141150
assert resource.id == "res_01ABC"
142151
assert request_kwargs["method"] == "patch"
143152
assert request_kwargs["url"].endswith("/authorization/resources/res_01ABC")
144-
assert request_kwargs["json"] == {"meta": {"updated_key": "updated_value"}}
153+
assert request_kwargs["json"] == {
154+
"name": "Updated Name",
155+
"description": "Updated description",
156+
}
145157

146-
def test_update_resource_without_meta(
158+
def test_update_resource_without_optional_fields(
147159
self, mock_resource, capture_and_mock_http_client_request
148160
):
149161
request_kwargs = capture_and_mock_http_client_request(

0 commit comments

Comments
 (0)