Skip to content

Commit aa261c0

Browse files
committed
pangea-sdk: modernize some AuthZ models
1 parent 39eda6c commit aa261c0

File tree

7 files changed

+2135
-69
lines changed

7 files changed

+2135
-69
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## Unreleased
99

10+
### Changed
11+
12+
- AuthN: modernized models around token check.
13+
- AuthZ: modernized models around tuples.
14+
1015
## 6.2.0 - 2025-06-25
1116

1217
### Changed

packages/pangea-sdk/pangea/asyncio/services/authz.py

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@
33

44
from __future__ import annotations
55

6+
from collections.abc import Sequence
67
from typing import Any
78

89
from pangea.asyncio.services.base import ServiceBaseAsync
910
from pangea.config import PangeaConfig
1011
from pangea.response import PangeaResponse
1112
from pangea.services.authz import (
12-
CheckRequest,
1313
CheckResult,
1414
ItemOrder,
1515
ListResourcesRequest,
@@ -19,7 +19,6 @@
1919
Resource,
2020
Subject,
2121
Tuple,
22-
TupleCreateRequest,
2322
TupleCreateResult,
2423
TupleDeleteRequest,
2524
TupleDeleteResult,
@@ -73,7 +72,7 @@ def __init__(
7372

7473
super().__init__(token, config, logger_name, config_id=config_id)
7574

76-
async def tuple_create(self, tuples: list[Tuple]) -> PangeaResponse[TupleCreateResult]:
75+
async def tuple_create(self, tuples: Sequence[Tuple]) -> PangeaResponse[TupleCreateResult]:
7776
"""Create tuples.
7877
7978
Create tuples in the AuthZ Service. The request will fail if there is no schema
@@ -102,10 +101,7 @@ async def tuple_create(self, tuples: list[Tuple]) -> PangeaResponse[TupleCreateR
102101
)
103102
"""
104103

105-
input_data = TupleCreateRequest(tuples=tuples)
106-
return await self.request.post(
107-
"v1/tuple/create", TupleCreateResult, data=input_data.model_dump(exclude_none=True)
108-
)
104+
return await self.request.post("v1/tuple/create", TupleCreateResult, data={"tuples": tuples})
109105

110106
async def tuple_list(
111107
self,
@@ -190,8 +186,8 @@ async def check(
190186
Check if a subject has permission to perform an action on the resource.
191187
192188
Args:
193-
resource (Resource): The resource to check.
194-
action (str): The action to check.
189+
resource: The resource to check.
190+
action: The action to check.
195191
subject: The subject to check.
196192
debug: Setting this value to True will provide a detailed analysis of the check.
197193
attributes: Additional attributes for the check.
@@ -213,8 +209,11 @@ async def check(
213209
)
214210
"""
215211

216-
input_data = CheckRequest(resource=resource, action=action, subject=subject, debug=debug, attributes=attributes)
217-
return await self.request.post("v1/check", CheckResult, data=input_data.model_dump(exclude_none=True))
212+
return await self.request.post(
213+
"v1/check",
214+
CheckResult,
215+
data={"resource": resource, "action": action, "subject": subject, "debug": debug, "attributes": attributes},
216+
)
218217

219218
async def list_resources(
220219
self, type: str, action: str, subject: Subject, attributes: dict[str, Any] | None = None

packages/pangea-sdk/pangea/services/authz.py

Lines changed: 48 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
# Copyright 2022 Pangea Cyber Corporation
22
# Author: Pangea Cyber Corporation
33

4-
# TODO: Use `list` instead of `List`.
5-
# ruff: noqa: UP006, UP035
6-
74
from __future__ import annotations
85

96
import enum
10-
from typing import Any, Dict, List, Optional, Union
7+
from collections.abc import Mapping, Sequence
8+
from typing import Annotated, Any, Optional, Union
9+
10+
from pydantic import Field
1111

1212
from pangea.config import PangeaConfig
13-
from pangea.response import APIRequestModel, APIResponseModel, PangeaResponse, PangeaResponseResult
13+
from pangea.response import APIRequestModel, APIResponseModel, PangeaDateTime, PangeaResponse, PangeaResponseResult
1414
from pangea.services.base import ServiceBase
1515

1616

@@ -47,20 +47,18 @@ class Resource(PangeaResponseResult):
4747

4848
class Subject(PangeaResponseResult):
4949
type: str
50-
id: Optional[str] = None
51-
action: Optional[str] = None
50+
id: Annotated[str, Field(pattern="^([a-zA-Z0-9_][a-zA-Z0-9/|_.@-]*)$")]
51+
action: Annotated[Optional[str], Field(pattern="^([a-zA-Z0-9_][a-zA-Z0-9/|_]*)$")] = None
5252

5353

5454
class Tuple(PangeaResponseResult):
5555
resource: Resource
56-
relation: str
56+
relation: Annotated[str, Field(pattern="^([a-zA-Z0-9_][a-zA-Z0-9/|_]*)$")]
5757
subject: Subject
58-
expires_at: Optional[str] = None
58+
expires_at: Optional[PangeaDateTime] = None
5959
"""A time in ISO-8601 format"""
60-
61-
62-
class TupleCreateRequest(APIRequestModel):
63-
tuples: List[Tuple]
60+
attributes: Optional[dict[str, Any]] = None
61+
"""A JSON object of attribute data."""
6462

6563

6664
class TupleCreateResult(PangeaResponseResult):
@@ -70,92 +68,82 @@ class TupleCreateResult(PangeaResponseResult):
7068
class TupleListFilter(APIRequestModel):
7169
resource_type: Optional[str] = None
7270
"""Only records where resource type equals this value."""
73-
resource_type__contains: Optional[List[str]] = None
71+
resource_type__contains: Optional[list[str]] = None
7472
"""Only records where resource type includes each substring."""
75-
resource_type__in: Optional[List[str]] = None
73+
resource_type__in: Optional[list[str]] = None
7674
"""Only records where resource type equals one of the provided substrings."""
7775
resource_id: Optional[str] = None
7876
"""Only records where resource id equals this value."""
79-
resource_id__contains: Optional[List[str]] = None
77+
resource_id__contains: Optional[list[str]] = None
8078
"""Only records where resource id includes each substring."""
81-
resource_id__in: Optional[List[str]] = None
79+
resource_id__in: Optional[list[str]] = None
8280
"""Only records where resource id equals one of the provided substrings."""
8381
relation: Optional[str] = None
8482
"""Only records where relation equals this value."""
85-
relation__contains: Optional[List[str]] = None
83+
relation__contains: Optional[list[str]] = None
8684
"""Only records where relation includes each substring."""
87-
relation__in: Optional[List[str]] = None
85+
relation__in: Optional[list[str]] = None
8886
"""Only records where relation equals one of the provided substrings."""
8987
subject_type: Optional[str] = None
9088
"""Only records where subject type equals this value."""
91-
subject_type__contains: Optional[List[str]] = None
89+
subject_type__contains: Optional[list[str]] = None
9290
"""Only records where subject type includes each substring."""
93-
subject_type__in: Optional[List[str]] = None
91+
subject_type__in: Optional[list[str]] = None
9492
"""Only records where subject type equals one of the provided substrings."""
9593
subject_id: Optional[str] = None
9694
"""Only records where subject id equals this value."""
97-
subject_id__contains: Optional[List[str]] = None
95+
subject_id__contains: Optional[list[str]] = None
9896
"""Only records where subject id includes each substring."""
99-
subject_id__in: Optional[List[str]] = None
97+
subject_id__in: Optional[list[str]] = None
10098
"""Only records where subject id equals one of the provided substrings."""
10199
subject_action: Optional[str] = None
102100
"""Only records where subject action equals this value."""
103-
subject_action__contains: Optional[List[str]] = None
101+
subject_action__contains: Optional[list[str]] = None
104102
"""Only records where subject action includes each substring."""
105-
subject_action__in: Optional[List[str]] = None
103+
subject_action__in: Optional[list[str]] = None
106104
"""Only records where subject action equals one of the provided substrings."""
107-
expires_at: Optional[str] = None
105+
expires_at: Optional[PangeaDateTime] = None
108106
"""Only records where expires_at equals this value."""
109-
expires_at__gt: Optional[str] = None
107+
expires_at__gt: Optional[PangeaDateTime] = None
110108
"""Only records where expires_at is greater than this value."""
111-
expires_at__gte: Optional[str] = None
109+
expires_at__gte: Optional[PangeaDateTime] = None
112110
"""Only records where expires_at is greater than or equal to this value."""
113-
expires_at__lt: Optional[str] = None
111+
expires_at__lt: Optional[PangeaDateTime] = None
114112
"""Only records where expires_at is less than this value."""
115-
expires_at__lte: Optional[str] = None
113+
expires_at__lte: Optional[PangeaDateTime] = None
116114
"""Only records where expires_at is less than or equal to this value."""
117115

118116

119117
class TupleListRequest(APIRequestModel):
120-
filter: Optional[Union[Dict, TupleListFilter]] = None
118+
filter: Optional[Union[dict, TupleListFilter]] = None
121119
size: Optional[int] = None
122120
last: Optional[str] = None
123121
order: Optional[ItemOrder] = None
124122
order_by: Optional[TupleOrderBy] = None
125123

126124

127125
class TupleListResult(PangeaResponseResult):
128-
tuples: List[Tuple]
126+
tuples: list[Tuple]
129127
last: str
130128
count: int
131129

132130

133131
class TupleDeleteRequest(APIRequestModel):
134-
tuples: List[Tuple]
132+
tuples: list[Tuple]
135133

136134

137135
class TupleDeleteResult(PangeaResponseResult):
138136
pass
139137

140138

141-
class CheckRequest(APIRequestModel):
142-
resource: Resource
143-
action: str
144-
subject: Subject
145-
debug: Optional[bool] = None
146-
"""In the event of an allowed check, return a path that granted access."""
147-
attributes: Optional[Dict[str, Any]] = None
148-
"""A JSON object of attribute data."""
149-
150-
151139
class DebugPath(APIResponseModel):
152-
type: str
153-
id: str
140+
type: Optional[str] = None
141+
id: Optional[str] = None
154142
action: Optional[str] = None
155143

156144

157145
class Debug(APIResponseModel):
158-
path: List[DebugPath]
146+
path: list[DebugPath]
159147

160148

161149
class CheckResult(PangeaResponseResult):
@@ -170,23 +158,23 @@ class ListResourcesRequest(APIRequestModel):
170158
type: str
171159
action: str
172160
subject: Subject
173-
attributes: Optional[Dict[str, Any]] = None
161+
attributes: Optional[dict[str, Any]] = None
174162

175163

176164
class ListResourcesResult(PangeaResponseResult):
177-
ids: List[str]
165+
ids: list[str]
178166

179167

180168
class ListSubjectsRequest(APIRequestModel):
181169
resource: Resource
182170
action: str
183-
attributes: Optional[Dict[str, Any]] = None
171+
attributes: Optional[dict[str, Any]] = None
184172
debug: Optional[bool] = None
185173
"""Return a path for each found subject"""
186174

187175

188176
class ListSubjectsResult(PangeaResponseResult):
189-
subjects: List[Subject]
177+
subjects: list[Subject]
190178

191179

192180
class AuthZ(ServiceBase):
@@ -232,7 +220,7 @@ def __init__(
232220

233221
super().__init__(token, config, logger_name, config_id=config_id)
234222

235-
def tuple_create(self, tuples: list[Tuple]) -> PangeaResponse[TupleCreateResult]:
223+
def tuple_create(self, tuples: Sequence[Tuple]) -> PangeaResponse[TupleCreateResult]:
236224
"""Create tuples.
237225
238226
Create tuples in the AuthZ Service. The request will fail if there is no schema
@@ -261,8 +249,7 @@ def tuple_create(self, tuples: list[Tuple]) -> PangeaResponse[TupleCreateResult]
261249
)
262250
"""
263251

264-
input_data = TupleCreateRequest(tuples=tuples)
265-
return self.request.post("v1/tuple/create", TupleCreateResult, data=input_data.model_dump(exclude_none=True))
252+
return self.request.post("v1/tuple/create", TupleCreateResult, data={"tuples": tuples})
266253

267254
def tuple_list(
268255
self,
@@ -337,8 +324,9 @@ def check(
337324
resource: Resource,
338325
action: str,
339326
subject: Subject,
327+
*,
340328
debug: bool | None = None,
341-
attributes: dict[str, Any] | None = None,
329+
attributes: Mapping[str, Any] | None = None,
342330
) -> PangeaResponse[CheckResult]:
343331
"""Perform a check request.
344332
@@ -349,7 +337,7 @@ def check(
349337
action: The action to check.
350338
subject: The subject to check.
351339
debug: In the event of an allowed check, return a path that granted access.
352-
attributes: Additional attributes for the check.
340+
attributes: A JSON object of attribute data.
353341
354342
Raises:
355343
PangeaAPIException: If an API Error happens.
@@ -368,8 +356,11 @@ def check(
368356
)
369357
"""
370358

371-
input_data = CheckRequest(resource=resource, action=action, subject=subject, debug=debug, attributes=attributes)
372-
return self.request.post("v1/check", CheckResult, data=input_data.model_dump(exclude_none=True))
359+
return self.request.post(
360+
"v1/check",
361+
CheckResult,
362+
data={"resource": resource, "action": action, "subject": subject, "debug": debug, "attributes": attributes},
363+
)
373364

374365
def list_resources(
375366
self, type: str, action: str, subject: Subject, attributes: dict[str, Any] | None = None

packages/pangea-sdk/scripts/test.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ pnpm dlx start-server-and-test --expect 404 \
1919
4010 \
2020
"poetry run pytest tests/integration2/test_authn.py"
2121

22+
pnpm dlx start-server-and-test --expect 404 \
23+
"pnpm dlx @stoplight/prism-cli mock -d --json-schema-faker-fillProperties=false tests/testdata/specs/authz.openapi.json" \
24+
4010 \
25+
"poetry run pytest tests/integration2/test_authz.py"
26+
2227
pnpm dlx start-server-and-test --expect 404 \
2328
"pnpm dlx @stoplight/prism-cli mock -d --json-schema-faker-fillProperties=false tests/testdata/specs/share.openapi.json" \
2429
4010 \

packages/pangea-sdk/tests/integration/test_authz.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ def test_expires_at(self) -> None:
158158
resource=m.Resource(type=type_folder, id=folder1),
159159
relation=relation_reader,
160160
subject=m.Subject(type=type_user, id=user1),
161-
expires_at="2999-09-21T17:24:33.105Z",
161+
expires_at=datetime.datetime(2099, 9, 21, 17, 24, 33, 105000),
162162
)
163163
]
164164
)

0 commit comments

Comments
 (0)