Skip to content

Commit a50f5d5

Browse files
FGA_3: check() (#568)
1 parent fe59018 commit a50f5d5

File tree

4 files changed

+193
-0
lines changed

4 files changed

+193
-0
lines changed

src/workos/authorization.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@
44
from pydantic import TypeAdapter
55
from typing_extensions import TypedDict
66

7+
from workos.types.authorization.access_evaluation import AccessEvaluation
78
from workos.types.authorization.environment_role import (
89
EnvironmentRole,
910
EnvironmentRoleList,
1011
)
1112
from workos.types.authorization.organization_role import OrganizationRole
1213
from workos.types.authorization.permission import Permission
14+
from workos.types.authorization.resource_identifier import ResourceIdentifier
1315
from workos.types.authorization.resource import Resource
1416
from workos.types.authorization.role import Role, RoleList
1517
from workos.types.list_resource import (
@@ -268,6 +270,14 @@ def delete_resource_by_external_id(
268270
cascade_delete: Optional[bool] = None,
269271
) -> SyncOrAsync[None]: ...
270272

273+
def check(
274+
self,
275+
organization_membership_id: str,
276+
*,
277+
permission_slug: str,
278+
resource: ResourceIdentifier,
279+
) -> SyncOrAsync[AccessEvaluation]: ...
280+
271281

272282
class Authorization(AuthorizationModule):
273283
_http_client: SyncHTTPClient
@@ -722,6 +732,24 @@ def delete_resource_by_external_id(
722732
params=params if params else None,
723733
)
724734

735+
def check(
736+
self,
737+
organization_membership_id: str,
738+
*,
739+
permission_slug: str,
740+
resource: ResourceIdentifier,
741+
) -> AccessEvaluation:
742+
json: Dict[str, Any] = {"permission_slug": permission_slug}
743+
json.update(resource)
744+
745+
response = self._http_client.request(
746+
f"authorization/organization_memberships/{organization_membership_id}/check",
747+
method=REQUEST_METHOD_POST,
748+
json=json,
749+
)
750+
751+
return AccessEvaluation.model_validate(response)
752+
725753

726754
class AsyncAuthorization(AuthorizationModule):
727755
_http_client: AsyncHTTPClient
@@ -1175,3 +1203,21 @@ async def delete_resource_by_external_id(
11751203
method=REQUEST_METHOD_DELETE,
11761204
params=params if params else None,
11771205
)
1206+
1207+
async def check(
1208+
self,
1209+
organization_membership_id: str,
1210+
*,
1211+
permission_slug: str,
1212+
resource: ResourceIdentifier,
1213+
) -> AccessEvaluation:
1214+
json: Dict[str, Any] = {"permission_slug": permission_slug}
1215+
json.update(resource)
1216+
1217+
response = await self._http_client.request(
1218+
f"authorization/organization_memberships/{organization_membership_id}/check",
1219+
method=REQUEST_METHOD_POST,
1220+
json=json,
1221+
)
1222+
1223+
return AccessEvaluation.model_validate(response)

src/workos/types/authorization/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@
1313
)
1414
from workos.types.authorization.permission import Permission
1515
from workos.types.authorization.resource import Resource
16+
from workos.types.authorization.resource_identifier import (
17+
ResourceIdentifier,
18+
ResourceIdentifierByExternalId,
19+
ResourceIdentifierById,
20+
)
1621
from workos.types.authorization.role import (
1722
Role,
1823
RoleList,
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from typing import Union
2+
3+
from typing_extensions import TypedDict
4+
5+
6+
class ResourceIdentifierById(TypedDict):
7+
resource_id: str
8+
9+
10+
class ResourceIdentifierByExternalId(TypedDict):
11+
resource_external_id: str
12+
resource_type_slug: str
13+
14+
15+
ResourceIdentifier = Union[ResourceIdentifierById, ResourceIdentifierByExternalId]

tests/test_authorization_check.py

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
from typing import Union
2+
3+
import pytest
4+
from tests.utils.syncify import syncify
5+
from workos.authorization import AsyncAuthorization, Authorization
6+
from workos.types.authorization.resource_identifier import (
7+
ResourceIdentifierByExternalId,
8+
ResourceIdentifierById,
9+
)
10+
11+
12+
@pytest.mark.sync_and_async(Authorization, AsyncAuthorization)
13+
class TestAuthorizationCheck:
14+
@pytest.fixture(autouse=True)
15+
def setup(self, module_instance: Union[Authorization, AsyncAuthorization]):
16+
self.http_client = module_instance._http_client
17+
self.authorization = module_instance
18+
19+
@pytest.fixture
20+
def mock_check_authorized(self):
21+
return {"authorized": True}
22+
23+
@pytest.fixture
24+
def mock_check_unauthorized(self):
25+
return {"authorized": False}
26+
27+
def test_check_authorized(
28+
self, mock_check_authorized, capture_and_mock_http_client_request
29+
):
30+
request_kwargs = capture_and_mock_http_client_request(
31+
self.http_client, mock_check_authorized, 200
32+
)
33+
34+
result = syncify(
35+
self.authorization.check(
36+
"om_01ABC",
37+
permission_slug="documents:read",
38+
resource=ResourceIdentifierById(resource_id="res_01ABC"),
39+
)
40+
)
41+
42+
assert result.authorized is True
43+
assert request_kwargs["method"] == "post"
44+
assert request_kwargs["url"].endswith(
45+
"/authorization/organization_memberships/om_01ABC/check"
46+
)
47+
48+
def test_check_unauthorized(
49+
self, mock_check_unauthorized, capture_and_mock_http_client_request
50+
):
51+
request_kwargs = capture_and_mock_http_client_request(
52+
self.http_client, mock_check_unauthorized, 200
53+
)
54+
55+
result = syncify(
56+
self.authorization.check(
57+
"om_01ABC",
58+
permission_slug="documents:write",
59+
resource=ResourceIdentifierById(resource_id="res_01ABC"),
60+
)
61+
)
62+
63+
assert result.authorized is False
64+
assert request_kwargs["method"] == "post"
65+
66+
def test_check_with_resource_id(
67+
self, mock_check_authorized, capture_and_mock_http_client_request
68+
):
69+
request_kwargs = capture_and_mock_http_client_request(
70+
self.http_client, mock_check_authorized, 200
71+
)
72+
73+
syncify(
74+
self.authorization.check(
75+
"om_01ABC",
76+
permission_slug="documents:read",
77+
resource=ResourceIdentifierById(resource_id="res_01XYZ"),
78+
)
79+
)
80+
81+
assert request_kwargs["json"] == {
82+
"permission_slug": "documents:read",
83+
"resource_id": "res_01XYZ",
84+
}
85+
86+
def test_check_with_resource_external_id(
87+
self, mock_check_authorized, capture_and_mock_http_client_request
88+
):
89+
request_kwargs = capture_and_mock_http_client_request(
90+
self.http_client, mock_check_authorized, 200
91+
)
92+
93+
syncify(
94+
self.authorization.check(
95+
"om_01ABC",
96+
permission_slug="documents:read",
97+
resource=ResourceIdentifierByExternalId(
98+
resource_external_id="ext_doc_123",
99+
resource_type_slug="document",
100+
),
101+
)
102+
)
103+
104+
assert request_kwargs["json"] == {
105+
"permission_slug": "documents:read",
106+
"resource_external_id": "ext_doc_123",
107+
"resource_type_slug": "document",
108+
}
109+
110+
def test_check_url_construction(
111+
self, mock_check_authorized, capture_and_mock_http_client_request
112+
):
113+
request_kwargs = capture_and_mock_http_client_request(
114+
self.http_client, mock_check_authorized, 200
115+
)
116+
117+
syncify(
118+
self.authorization.check(
119+
"om_01MEMBERSHIP",
120+
permission_slug="admin:access",
121+
resource=ResourceIdentifierById(resource_id="res_01ABC"),
122+
)
123+
)
124+
125+
assert request_kwargs["url"].endswith(
126+
"/authorization/organization_memberships/om_01MEMBERSHIP/check"
127+
)

0 commit comments

Comments
 (0)