dominus-sdk-python-staging 2.1.6__py3-none-any.whl
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.
- dominus/__init__.py +47 -0
- dominus/config/__init__.py +24 -0
- dominus/config/endpoints.py +39 -0
- dominus/errors.py +201 -0
- dominus/helpers/__init__.py +2 -0
- dominus/helpers/auth.py +23 -0
- dominus/helpers/cache.py +192 -0
- dominus/helpers/core.py +657 -0
- dominus/helpers/crypto.py +118 -0
- dominus/namespaces/__init__.py +26 -0
- dominus/namespaces/_deprecated_crossover.py +10 -0
- dominus/namespaces/_deprecated_sql.py +1341 -0
- dominus/namespaces/auth.py +1056 -0
- dominus/namespaces/courier.py +251 -0
- dominus/namespaces/db.py +267 -0
- dominus/namespaces/ddl.py +590 -0
- dominus/namespaces/files.py +299 -0
- dominus/namespaces/health.py +59 -0
- dominus/namespaces/logs.py +367 -0
- dominus/namespaces/open.py +72 -0
- dominus/namespaces/portal.py +504 -0
- dominus/namespaces/redis.py +357 -0
- dominus/namespaces/secrets.py +126 -0
- dominus/services/__init__.py +2 -0
- dominus/services/_deprecated_architect.py +323 -0
- dominus/services/_deprecated_sovereign.py +93 -0
- dominus/start.py +1034 -0
- dominus_sdk_python_staging-2.1.6.dist-info/METADATA +381 -0
- dominus_sdk_python_staging-2.1.6.dist-info/RECORD +31 -0
- dominus_sdk_python_staging-2.1.6.dist-info/WHEEL +5 -0
- dominus_sdk_python_staging-2.1.6.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1056 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Auth Namespace - Guardian authentication and authorization operations.
|
|
3
|
+
|
|
4
|
+
Provides management of users, roles, scopes, tenants, clients, pages, and navigation.
|
|
5
|
+
Uses RESTful patterns matching the Guardian backend.
|
|
6
|
+
"""
|
|
7
|
+
from typing import Any, Dict, List, Optional, TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from ..start import Dominus
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AuthNamespace:
|
|
14
|
+
"""
|
|
15
|
+
Authentication and authorization namespace.
|
|
16
|
+
|
|
17
|
+
All auth operations go through /api/guardian/* endpoints.
|
|
18
|
+
Uses RESTful patterns: GET/POST/PUT/DELETE with path params.
|
|
19
|
+
|
|
20
|
+
Usage:
|
|
21
|
+
# User management
|
|
22
|
+
user = await dominus.auth.create_user(username="john", email="j@ex.com", password="secret")
|
|
23
|
+
users = await dominus.auth.list_users(status="active")
|
|
24
|
+
await dominus.auth.update_password(user_id, "new_password")
|
|
25
|
+
|
|
26
|
+
# Role management
|
|
27
|
+
role = await dominus.auth.create_role(name="admin", description="Admin role")
|
|
28
|
+
roles = await dominus.auth.list_roles()
|
|
29
|
+
|
|
30
|
+
# JWT validation
|
|
31
|
+
claims = await dominus.auth.validate_jwt(token)
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(self, client: "Dominus"):
|
|
35
|
+
self._client = client
|
|
36
|
+
self._public_key_cache = None
|
|
37
|
+
|
|
38
|
+
# ========================================
|
|
39
|
+
# USERS
|
|
40
|
+
# ========================================
|
|
41
|
+
|
|
42
|
+
async def create_user(
|
|
43
|
+
self,
|
|
44
|
+
username: str,
|
|
45
|
+
email: str,
|
|
46
|
+
password: str,
|
|
47
|
+
status: str = "active"
|
|
48
|
+
) -> Dict[str, Any]:
|
|
49
|
+
"""
|
|
50
|
+
Create a new user.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
username: Unique username
|
|
54
|
+
email: Email address
|
|
55
|
+
password: Raw password (will be hashed server-side)
|
|
56
|
+
status: User status (default: "active")
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
Created user record (without password_hash)
|
|
60
|
+
"""
|
|
61
|
+
return await self._client._request(
|
|
62
|
+
endpoint="/api/guardian/users",
|
|
63
|
+
body={
|
|
64
|
+
"username": username,
|
|
65
|
+
"email": email,
|
|
66
|
+
"password": password,
|
|
67
|
+
"status": status
|
|
68
|
+
}
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
async def get_user(self, user_id: str) -> Dict[str, Any]:
|
|
72
|
+
"""Get user by ID."""
|
|
73
|
+
return await self._client._request(
|
|
74
|
+
endpoint=f"/api/guardian/users/{user_id}",
|
|
75
|
+
method="GET"
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
async def list_users(
|
|
79
|
+
self,
|
|
80
|
+
status: Optional[str] = None,
|
|
81
|
+
limit: int = 100,
|
|
82
|
+
offset: int = 0,
|
|
83
|
+
order_by: str = "created_at",
|
|
84
|
+
order_desc: bool = True
|
|
85
|
+
) -> Dict[str, Any]:
|
|
86
|
+
"""
|
|
87
|
+
List users with optional filters.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
Dict with "items" list and "total" count
|
|
91
|
+
"""
|
|
92
|
+
params = f"?limit={limit}&offset={offset}&order_by={order_by}&order_desc={str(order_desc).lower()}"
|
|
93
|
+
if status:
|
|
94
|
+
params += f"&status={status}"
|
|
95
|
+
|
|
96
|
+
return await self._client._request(
|
|
97
|
+
endpoint=f"/api/guardian/users{params}",
|
|
98
|
+
method="GET"
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
async def update_user(
|
|
102
|
+
self,
|
|
103
|
+
user_id: str,
|
|
104
|
+
username: Optional[str] = None,
|
|
105
|
+
email: Optional[str] = None,
|
|
106
|
+
status: Optional[str] = None
|
|
107
|
+
) -> Dict[str, Any]:
|
|
108
|
+
"""Update user fields."""
|
|
109
|
+
body = {}
|
|
110
|
+
if username is not None:
|
|
111
|
+
body["username"] = username
|
|
112
|
+
if email is not None:
|
|
113
|
+
body["email"] = email
|
|
114
|
+
if status is not None:
|
|
115
|
+
body["status"] = status
|
|
116
|
+
|
|
117
|
+
return await self._client._request(
|
|
118
|
+
endpoint=f"/api/guardian/users/{user_id}",
|
|
119
|
+
method="PUT",
|
|
120
|
+
body=body
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
async def delete_user(self, user_id: str) -> Dict[str, Any]:
|
|
124
|
+
"""Delete a user."""
|
|
125
|
+
return await self._client._request(
|
|
126
|
+
endpoint=f"/api/guardian/users/{user_id}",
|
|
127
|
+
method="DELETE"
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
async def update_password(self, user_id: str, password: str) -> Dict[str, Any]:
|
|
131
|
+
"""Update user password."""
|
|
132
|
+
return await self._client._request(
|
|
133
|
+
endpoint=f"/api/guardian/users/{user_id}/password",
|
|
134
|
+
method="PUT",
|
|
135
|
+
body={"password": password}
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
async def verify_password(self, user_id: str, password: str) -> Dict[str, Any]:
|
|
139
|
+
"""Verify user password."""
|
|
140
|
+
return await self._client._request(
|
|
141
|
+
endpoint=f"/api/guardian/users/{user_id}/verify-password",
|
|
142
|
+
body={"password": password}
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
# User junction tables
|
|
146
|
+
async def get_user_roles(self, user_id: str) -> List[Dict[str, Any]]:
|
|
147
|
+
"""Get roles assigned to user."""
|
|
148
|
+
return await self._client._request(
|
|
149
|
+
endpoint=f"/api/guardian/users/{user_id}/roles",
|
|
150
|
+
method="GET"
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
async def add_user_roles(self, user_id: str, role_ids: List[str]) -> Dict[str, Any]:
|
|
154
|
+
"""Add roles to user."""
|
|
155
|
+
return await self._client._request(
|
|
156
|
+
endpoint=f"/api/guardian/users/{user_id}/roles",
|
|
157
|
+
body={"role_ids": role_ids}
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
async def remove_user_roles(self, user_id: str, role_ids: List[str]) -> Dict[str, Any]:
|
|
161
|
+
"""Remove roles from user."""
|
|
162
|
+
return await self._client._request(
|
|
163
|
+
endpoint=f"/api/guardian/users/{user_id}/roles",
|
|
164
|
+
method="DELETE",
|
|
165
|
+
body={"role_ids": role_ids}
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
async def get_user_scopes(self, user_id: str) -> List[Dict[str, Any]]:
|
|
169
|
+
"""Get scopes assigned to user."""
|
|
170
|
+
return await self._client._request(
|
|
171
|
+
endpoint=f"/api/guardian/users/{user_id}/scopes",
|
|
172
|
+
method="GET"
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
async def add_user_scopes(self, user_id: str, scope_ids: List[str]) -> Dict[str, Any]:
|
|
176
|
+
"""Add scopes to user."""
|
|
177
|
+
return await self._client._request(
|
|
178
|
+
endpoint=f"/api/guardian/users/{user_id}/scopes",
|
|
179
|
+
body={"scope_ids": scope_ids}
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
async def remove_user_scopes(self, user_id: str, scope_ids: List[str]) -> Dict[str, Any]:
|
|
183
|
+
"""Remove scopes from user."""
|
|
184
|
+
return await self._client._request(
|
|
185
|
+
endpoint=f"/api/guardian/users/{user_id}/scopes",
|
|
186
|
+
method="DELETE",
|
|
187
|
+
body={"scope_ids": scope_ids}
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
async def get_user_tenants(self, user_id: str) -> List[Dict[str, Any]]:
|
|
191
|
+
"""Get tenants assigned to user."""
|
|
192
|
+
return await self._client._request(
|
|
193
|
+
endpoint=f"/api/guardian/users/{user_id}/tenants",
|
|
194
|
+
method="GET"
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
async def add_user_tenants(self, user_id: str, tenant_ids: List[str]) -> Dict[str, Any]:
|
|
198
|
+
"""Add tenants to user."""
|
|
199
|
+
return await self._client._request(
|
|
200
|
+
endpoint=f"/api/guardian/users/{user_id}/tenants",
|
|
201
|
+
body={"tenant_ids": tenant_ids}
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
async def remove_user_tenants(self, user_id: str, tenant_ids: List[str]) -> Dict[str, Any]:
|
|
205
|
+
"""Remove tenants from user."""
|
|
206
|
+
return await self._client._request(
|
|
207
|
+
endpoint=f"/api/guardian/users/{user_id}/tenants",
|
|
208
|
+
method="DELETE",
|
|
209
|
+
body={"tenant_ids": tenant_ids}
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
async def get_user_subtypes(self, user_id: str) -> List[Dict[str, Any]]:
|
|
213
|
+
"""Get subtypes assigned to user."""
|
|
214
|
+
return await self._client._request(
|
|
215
|
+
endpoint=f"/api/guardian/users/{user_id}/subtypes",
|
|
216
|
+
method="GET"
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
async def add_user_subtypes(self, user_id: str, subtype_ids: List[str]) -> Dict[str, Any]:
|
|
220
|
+
"""Add subtypes to user."""
|
|
221
|
+
return await self._client._request(
|
|
222
|
+
endpoint=f"/api/guardian/users/{user_id}/subtypes",
|
|
223
|
+
body={"subtype_ids": subtype_ids}
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
async def remove_user_subtypes(self, user_id: str, subtype_ids: List[str]) -> Dict[str, Any]:
|
|
227
|
+
"""Remove subtypes from user."""
|
|
228
|
+
return await self._client._request(
|
|
229
|
+
endpoint=f"/api/guardian/users/{user_id}/subtypes",
|
|
230
|
+
method="DELETE",
|
|
231
|
+
body={"subtype_ids": subtype_ids}
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
# ========================================
|
|
235
|
+
# ROLES
|
|
236
|
+
# ========================================
|
|
237
|
+
|
|
238
|
+
async def create_role(
|
|
239
|
+
self,
|
|
240
|
+
name: str,
|
|
241
|
+
description: Optional[str] = None
|
|
242
|
+
) -> Dict[str, Any]:
|
|
243
|
+
"""Create a new role."""
|
|
244
|
+
body = {"name": name}
|
|
245
|
+
if description:
|
|
246
|
+
body["description"] = description
|
|
247
|
+
|
|
248
|
+
return await self._client._request(
|
|
249
|
+
endpoint="/api/guardian/roles",
|
|
250
|
+
body=body
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
async def get_role(self, role_id: str) -> Dict[str, Any]:
|
|
254
|
+
"""Get role by ID."""
|
|
255
|
+
return await self._client._request(
|
|
256
|
+
endpoint=f"/api/guardian/roles/{role_id}",
|
|
257
|
+
method="GET"
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
async def list_roles(
|
|
261
|
+
self,
|
|
262
|
+
limit: int = 100,
|
|
263
|
+
offset: int = 0
|
|
264
|
+
) -> Dict[str, Any]:
|
|
265
|
+
"""List all roles."""
|
|
266
|
+
return await self._client._request(
|
|
267
|
+
endpoint=f"/api/guardian/roles?limit={limit}&offset={offset}",
|
|
268
|
+
method="GET"
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
async def update_role(
|
|
272
|
+
self,
|
|
273
|
+
role_id: str,
|
|
274
|
+
name: Optional[str] = None,
|
|
275
|
+
description: Optional[str] = None
|
|
276
|
+
) -> Dict[str, Any]:
|
|
277
|
+
"""Update role."""
|
|
278
|
+
body = {}
|
|
279
|
+
if name is not None:
|
|
280
|
+
body["name"] = name
|
|
281
|
+
if description is not None:
|
|
282
|
+
body["description"] = description
|
|
283
|
+
|
|
284
|
+
return await self._client._request(
|
|
285
|
+
endpoint=f"/api/guardian/roles/{role_id}",
|
|
286
|
+
method="PUT",
|
|
287
|
+
body=body
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
async def delete_role(self, role_id: str) -> Dict[str, Any]:
|
|
291
|
+
"""Delete a role."""
|
|
292
|
+
return await self._client._request(
|
|
293
|
+
endpoint=f"/api/guardian/roles/{role_id}",
|
|
294
|
+
method="DELETE"
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
async def get_role_scopes(self, role_id: str) -> List[Dict[str, Any]]:
|
|
298
|
+
"""Get scopes assigned to role."""
|
|
299
|
+
return await self._client._request(
|
|
300
|
+
endpoint=f"/api/guardian/roles/{role_id}/scopes",
|
|
301
|
+
method="GET"
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
async def add_role_scopes(self, role_id: str, scope_ids: List[str]) -> Dict[str, Any]:
|
|
305
|
+
"""Add scopes to role."""
|
|
306
|
+
return await self._client._request(
|
|
307
|
+
endpoint=f"/api/guardian/roles/{role_id}/scopes",
|
|
308
|
+
body={"scope_ids": scope_ids}
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
async def remove_role_scopes(self, role_id: str, scope_ids: List[str]) -> Dict[str, Any]:
|
|
312
|
+
"""Remove scopes from role."""
|
|
313
|
+
return await self._client._request(
|
|
314
|
+
endpoint=f"/api/guardian/roles/{role_id}/scopes",
|
|
315
|
+
method="DELETE",
|
|
316
|
+
body={"scope_ids": scope_ids}
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
# ========================================
|
|
320
|
+
# SCOPES
|
|
321
|
+
# ========================================
|
|
322
|
+
|
|
323
|
+
async def create_scope(
|
|
324
|
+
self,
|
|
325
|
+
slug: str,
|
|
326
|
+
display_name: str,
|
|
327
|
+
description: Optional[str] = None
|
|
328
|
+
) -> Dict[str, Any]:
|
|
329
|
+
"""Create a new scope."""
|
|
330
|
+
body = {"slug": slug, "display_name": display_name}
|
|
331
|
+
if description:
|
|
332
|
+
body["description"] = description
|
|
333
|
+
|
|
334
|
+
return await self._client._request(
|
|
335
|
+
endpoint="/api/guardian/scopes",
|
|
336
|
+
body=body
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
async def get_scope(self, scope_id: str) -> Dict[str, Any]:
|
|
340
|
+
"""Get scope by ID."""
|
|
341
|
+
return await self._client._request(
|
|
342
|
+
endpoint=f"/api/guardian/scopes/{scope_id}",
|
|
343
|
+
method="GET"
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
async def list_scopes(
|
|
347
|
+
self,
|
|
348
|
+
limit: int = 100,
|
|
349
|
+
offset: int = 0
|
|
350
|
+
) -> Dict[str, Any]:
|
|
351
|
+
"""List all scopes."""
|
|
352
|
+
return await self._client._request(
|
|
353
|
+
endpoint=f"/api/guardian/scopes?limit={limit}&offset={offset}",
|
|
354
|
+
method="GET"
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
async def update_scope(
|
|
358
|
+
self,
|
|
359
|
+
scope_id: str,
|
|
360
|
+
slug: Optional[str] = None,
|
|
361
|
+
display_name: Optional[str] = None,
|
|
362
|
+
description: Optional[str] = None
|
|
363
|
+
) -> Dict[str, Any]:
|
|
364
|
+
"""Update scope."""
|
|
365
|
+
body = {}
|
|
366
|
+
if slug is not None:
|
|
367
|
+
body["slug"] = slug
|
|
368
|
+
if display_name is not None:
|
|
369
|
+
body["display_name"] = display_name
|
|
370
|
+
if description is not None:
|
|
371
|
+
body["description"] = description
|
|
372
|
+
|
|
373
|
+
return await self._client._request(
|
|
374
|
+
endpoint=f"/api/guardian/scopes/{scope_id}",
|
|
375
|
+
method="PUT",
|
|
376
|
+
body=body
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
async def delete_scope(self, scope_id: str) -> Dict[str, Any]:
|
|
380
|
+
"""Delete a scope."""
|
|
381
|
+
return await self._client._request(
|
|
382
|
+
endpoint=f"/api/guardian/scopes/{scope_id}",
|
|
383
|
+
method="DELETE"
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
# ========================================
|
|
387
|
+
# CLIENTS (PSK/Machine Auth)
|
|
388
|
+
# ========================================
|
|
389
|
+
|
|
390
|
+
async def create_client(
|
|
391
|
+
self,
|
|
392
|
+
label: str,
|
|
393
|
+
description: Optional[str] = None
|
|
394
|
+
) -> Dict[str, Any]:
|
|
395
|
+
"""
|
|
396
|
+
Create a new service client with PSK.
|
|
397
|
+
|
|
398
|
+
Returns dict with client info and one-time-visible "psk".
|
|
399
|
+
"""
|
|
400
|
+
body = {"label": label}
|
|
401
|
+
if description:
|
|
402
|
+
body["description"] = description
|
|
403
|
+
|
|
404
|
+
return await self._client._request(
|
|
405
|
+
endpoint="/api/guardian/clients",
|
|
406
|
+
body=body
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
async def get_client(self, client_id: str) -> Dict[str, Any]:
|
|
410
|
+
"""Get client by ID."""
|
|
411
|
+
return await self._client._request(
|
|
412
|
+
endpoint=f"/api/guardian/clients/{client_id}",
|
|
413
|
+
method="GET"
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
async def list_clients(
|
|
417
|
+
self,
|
|
418
|
+
limit: int = 100,
|
|
419
|
+
offset: int = 0
|
|
420
|
+
) -> Dict[str, Any]:
|
|
421
|
+
"""List all clients."""
|
|
422
|
+
return await self._client._request(
|
|
423
|
+
endpoint=f"/api/guardian/clients?limit={limit}&offset={offset}",
|
|
424
|
+
method="GET"
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
async def update_client(
|
|
428
|
+
self,
|
|
429
|
+
client_id: str,
|
|
430
|
+
label: Optional[str] = None,
|
|
431
|
+
description: Optional[str] = None,
|
|
432
|
+
status: Optional[str] = None
|
|
433
|
+
) -> Dict[str, Any]:
|
|
434
|
+
"""Update client."""
|
|
435
|
+
body = {}
|
|
436
|
+
if label is not None:
|
|
437
|
+
body["label"] = label
|
|
438
|
+
if description is not None:
|
|
439
|
+
body["description"] = description
|
|
440
|
+
if status is not None:
|
|
441
|
+
body["status"] = status
|
|
442
|
+
|
|
443
|
+
return await self._client._request(
|
|
444
|
+
endpoint=f"/api/guardian/clients/{client_id}",
|
|
445
|
+
method="PUT",
|
|
446
|
+
body=body
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
async def delete_client(self, client_id: str) -> Dict[str, Any]:
|
|
450
|
+
"""Delete a client."""
|
|
451
|
+
return await self._client._request(
|
|
452
|
+
endpoint=f"/api/guardian/clients/{client_id}",
|
|
453
|
+
method="DELETE"
|
|
454
|
+
)
|
|
455
|
+
|
|
456
|
+
async def regenerate_psk(self, client_id: str) -> Dict[str, Any]:
|
|
457
|
+
"""Regenerate client PSK. Returns new one-time-visible PSK."""
|
|
458
|
+
return await self._client._request(
|
|
459
|
+
endpoint=f"/api/guardian/clients/{client_id}/regenerate-psk",
|
|
460
|
+
body={}
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
async def verify_psk(self, client_id: str, psk: str) -> Dict[str, Any]:
|
|
464
|
+
"""Verify client PSK."""
|
|
465
|
+
return await self._client._request(
|
|
466
|
+
endpoint=f"/api/guardian/clients/{client_id}/verify-psk",
|
|
467
|
+
body={"psk": psk}
|
|
468
|
+
)
|
|
469
|
+
|
|
470
|
+
# Client junction tables
|
|
471
|
+
async def get_client_tenants(self, client_id: str) -> List[Dict[str, Any]]:
|
|
472
|
+
"""Get tenants assigned to client."""
|
|
473
|
+
return await self._client._request(
|
|
474
|
+
endpoint=f"/api/guardian/clients/{client_id}/tenants",
|
|
475
|
+
method="GET"
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
async def add_client_tenants(self, client_id: str, tenant_ids: List[str]) -> Dict[str, Any]:
|
|
479
|
+
"""Add tenants to client."""
|
|
480
|
+
return await self._client._request(
|
|
481
|
+
endpoint=f"/api/guardian/clients/{client_id}/tenants",
|
|
482
|
+
body={"tenant_ids": tenant_ids}
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
async def remove_client_tenants(self, client_id: str, tenant_ids: List[str]) -> Dict[str, Any]:
|
|
486
|
+
"""Remove tenants from client."""
|
|
487
|
+
return await self._client._request(
|
|
488
|
+
endpoint=f"/api/guardian/clients/{client_id}/tenants",
|
|
489
|
+
method="DELETE",
|
|
490
|
+
body={"tenant_ids": tenant_ids}
|
|
491
|
+
)
|
|
492
|
+
|
|
493
|
+
# ========================================
|
|
494
|
+
# TENANTS
|
|
495
|
+
# ========================================
|
|
496
|
+
|
|
497
|
+
async def create_tenant(
|
|
498
|
+
self,
|
|
499
|
+
name: str,
|
|
500
|
+
slug: str,
|
|
501
|
+
category_id: Optional[str] = None,
|
|
502
|
+
display_name: Optional[str] = None,
|
|
503
|
+
description: Optional[str] = None
|
|
504
|
+
) -> Dict[str, Any]:
|
|
505
|
+
"""Create a new tenant."""
|
|
506
|
+
body = {"name": name, "slug": slug}
|
|
507
|
+
if category_id:
|
|
508
|
+
body["category_id"] = category_id
|
|
509
|
+
if display_name:
|
|
510
|
+
body["display_name"] = display_name
|
|
511
|
+
if description:
|
|
512
|
+
body["description"] = description
|
|
513
|
+
|
|
514
|
+
return await self._client._request(
|
|
515
|
+
endpoint="/api/guardian/tenants",
|
|
516
|
+
body=body
|
|
517
|
+
)
|
|
518
|
+
|
|
519
|
+
async def get_tenant(self, tenant_id: str) -> Dict[str, Any]:
|
|
520
|
+
"""Get tenant by ID."""
|
|
521
|
+
return await self._client._request(
|
|
522
|
+
endpoint=f"/api/guardian/tenants/{tenant_id}",
|
|
523
|
+
method="GET"
|
|
524
|
+
)
|
|
525
|
+
|
|
526
|
+
async def list_tenants(
|
|
527
|
+
self,
|
|
528
|
+
status: Optional[str] = None,
|
|
529
|
+
category_id: Optional[str] = None,
|
|
530
|
+
limit: int = 100,
|
|
531
|
+
offset: int = 0
|
|
532
|
+
) -> Dict[str, Any]:
|
|
533
|
+
"""List tenants with optional filters."""
|
|
534
|
+
params = f"?limit={limit}&offset={offset}"
|
|
535
|
+
if status:
|
|
536
|
+
params += f"&status={status}"
|
|
537
|
+
if category_id:
|
|
538
|
+
params += f"&category_id={category_id}"
|
|
539
|
+
|
|
540
|
+
return await self._client._request(
|
|
541
|
+
endpoint=f"/api/guardian/tenants{params}",
|
|
542
|
+
method="GET"
|
|
543
|
+
)
|
|
544
|
+
|
|
545
|
+
async def update_tenant(
|
|
546
|
+
self,
|
|
547
|
+
tenant_id: str,
|
|
548
|
+
name: Optional[str] = None,
|
|
549
|
+
display_name: Optional[str] = None,
|
|
550
|
+
status: Optional[str] = None
|
|
551
|
+
) -> Dict[str, Any]:
|
|
552
|
+
"""Update tenant."""
|
|
553
|
+
body = {}
|
|
554
|
+
if name is not None:
|
|
555
|
+
body["name"] = name
|
|
556
|
+
if display_name is not None:
|
|
557
|
+
body["display_name"] = display_name
|
|
558
|
+
if status is not None:
|
|
559
|
+
body["status"] = status
|
|
560
|
+
|
|
561
|
+
return await self._client._request(
|
|
562
|
+
endpoint=f"/api/guardian/tenants/{tenant_id}",
|
|
563
|
+
method="PUT",
|
|
564
|
+
body=body
|
|
565
|
+
)
|
|
566
|
+
|
|
567
|
+
async def delete_tenant(self, tenant_id: str) -> Dict[str, Any]:
|
|
568
|
+
"""Delete a tenant."""
|
|
569
|
+
return await self._client._request(
|
|
570
|
+
endpoint=f"/api/guardian/tenants/{tenant_id}",
|
|
571
|
+
method="DELETE"
|
|
572
|
+
)
|
|
573
|
+
|
|
574
|
+
# ========================================
|
|
575
|
+
# TENANT CATEGORIES
|
|
576
|
+
# ========================================
|
|
577
|
+
|
|
578
|
+
async def create_tenant_category(
|
|
579
|
+
self,
|
|
580
|
+
name: str,
|
|
581
|
+
slug: str,
|
|
582
|
+
description: Optional[str] = None,
|
|
583
|
+
color: str = "#3B82F6"
|
|
584
|
+
) -> Dict[str, Any]:
|
|
585
|
+
"""Create a new tenant category."""
|
|
586
|
+
body = {"name": name, "slug": slug, "color": color}
|
|
587
|
+
if description:
|
|
588
|
+
body["description"] = description
|
|
589
|
+
|
|
590
|
+
return await self._client._request(
|
|
591
|
+
endpoint="/api/guardian/tenant-categories",
|
|
592
|
+
body=body
|
|
593
|
+
)
|
|
594
|
+
|
|
595
|
+
async def get_tenant_category(self, category_id: str) -> Dict[str, Any]:
|
|
596
|
+
"""Get tenant category by ID."""
|
|
597
|
+
return await self._client._request(
|
|
598
|
+
endpoint=f"/api/guardian/tenant-categories/{category_id}",
|
|
599
|
+
method="GET"
|
|
600
|
+
)
|
|
601
|
+
|
|
602
|
+
async def list_tenant_categories(
|
|
603
|
+
self,
|
|
604
|
+
limit: int = 100,
|
|
605
|
+
offset: int = 0
|
|
606
|
+
) -> Dict[str, Any]:
|
|
607
|
+
"""List all tenant categories."""
|
|
608
|
+
return await self._client._request(
|
|
609
|
+
endpoint=f"/api/guardian/tenant-categories?limit={limit}&offset={offset}",
|
|
610
|
+
method="GET"
|
|
611
|
+
)
|
|
612
|
+
|
|
613
|
+
async def update_tenant_category(
|
|
614
|
+
self,
|
|
615
|
+
category_id: str,
|
|
616
|
+
name: Optional[str] = None,
|
|
617
|
+
description: Optional[str] = None,
|
|
618
|
+
color: Optional[str] = None
|
|
619
|
+
) -> Dict[str, Any]:
|
|
620
|
+
"""Update tenant category."""
|
|
621
|
+
body = {}
|
|
622
|
+
if name is not None:
|
|
623
|
+
body["name"] = name
|
|
624
|
+
if description is not None:
|
|
625
|
+
body["description"] = description
|
|
626
|
+
if color is not None:
|
|
627
|
+
body["color"] = color
|
|
628
|
+
|
|
629
|
+
return await self._client._request(
|
|
630
|
+
endpoint=f"/api/guardian/tenant-categories/{category_id}",
|
|
631
|
+
method="PUT",
|
|
632
|
+
body=body
|
|
633
|
+
)
|
|
634
|
+
|
|
635
|
+
async def delete_tenant_category(self, category_id: str) -> Dict[str, Any]:
|
|
636
|
+
"""Delete a tenant category."""
|
|
637
|
+
return await self._client._request(
|
|
638
|
+
endpoint=f"/api/guardian/tenant-categories/{category_id}",
|
|
639
|
+
method="DELETE"
|
|
640
|
+
)
|
|
641
|
+
|
|
642
|
+
# ========================================
|
|
643
|
+
# SUBTYPES
|
|
644
|
+
# ========================================
|
|
645
|
+
|
|
646
|
+
async def create_subtype(
|
|
647
|
+
self,
|
|
648
|
+
name: str,
|
|
649
|
+
slug: str,
|
|
650
|
+
description: Optional[str] = None
|
|
651
|
+
) -> Dict[str, Any]:
|
|
652
|
+
"""Create a new user subtype."""
|
|
653
|
+
body = {"name": name, "slug": slug}
|
|
654
|
+
if description:
|
|
655
|
+
body["description"] = description
|
|
656
|
+
|
|
657
|
+
return await self._client._request(
|
|
658
|
+
endpoint="/api/guardian/subtypes",
|
|
659
|
+
body=body
|
|
660
|
+
)
|
|
661
|
+
|
|
662
|
+
async def get_subtype(self, subtype_id: str) -> Dict[str, Any]:
|
|
663
|
+
"""Get subtype by ID."""
|
|
664
|
+
return await self._client._request(
|
|
665
|
+
endpoint=f"/api/guardian/subtypes/{subtype_id}",
|
|
666
|
+
method="GET"
|
|
667
|
+
)
|
|
668
|
+
|
|
669
|
+
async def list_subtypes(
|
|
670
|
+
self,
|
|
671
|
+
limit: int = 100,
|
|
672
|
+
offset: int = 0
|
|
673
|
+
) -> Dict[str, Any]:
|
|
674
|
+
"""List all subtypes."""
|
|
675
|
+
return await self._client._request(
|
|
676
|
+
endpoint=f"/api/guardian/subtypes?limit={limit}&offset={offset}",
|
|
677
|
+
method="GET"
|
|
678
|
+
)
|
|
679
|
+
|
|
680
|
+
async def update_subtype(
|
|
681
|
+
self,
|
|
682
|
+
subtype_id: str,
|
|
683
|
+
name: Optional[str] = None,
|
|
684
|
+
description: Optional[str] = None
|
|
685
|
+
) -> Dict[str, Any]:
|
|
686
|
+
"""Update subtype."""
|
|
687
|
+
body = {}
|
|
688
|
+
if name is not None:
|
|
689
|
+
body["name"] = name
|
|
690
|
+
if description is not None:
|
|
691
|
+
body["description"] = description
|
|
692
|
+
|
|
693
|
+
return await self._client._request(
|
|
694
|
+
endpoint=f"/api/guardian/subtypes/{subtype_id}",
|
|
695
|
+
method="PUT",
|
|
696
|
+
body=body
|
|
697
|
+
)
|
|
698
|
+
|
|
699
|
+
async def delete_subtype(self, subtype_id: str) -> Dict[str, Any]:
|
|
700
|
+
"""Delete a subtype."""
|
|
701
|
+
return await self._client._request(
|
|
702
|
+
endpoint=f"/api/guardian/subtypes/{subtype_id}",
|
|
703
|
+
method="DELETE"
|
|
704
|
+
)
|
|
705
|
+
|
|
706
|
+
# ========================================
|
|
707
|
+
# PAGES
|
|
708
|
+
# ========================================
|
|
709
|
+
|
|
710
|
+
async def create_page(
|
|
711
|
+
self,
|
|
712
|
+
path: str,
|
|
713
|
+
name: str,
|
|
714
|
+
description: Optional[str] = None,
|
|
715
|
+
is_active: bool = True
|
|
716
|
+
) -> Dict[str, Any]:
|
|
717
|
+
"""Create a new page."""
|
|
718
|
+
body = {"path": path, "name": name, "is_active": is_active}
|
|
719
|
+
if description:
|
|
720
|
+
body["description"] = description
|
|
721
|
+
|
|
722
|
+
return await self._client._request(
|
|
723
|
+
endpoint="/api/guardian/pages",
|
|
724
|
+
body=body
|
|
725
|
+
)
|
|
726
|
+
|
|
727
|
+
async def get_page(self, page_id: str) -> Dict[str, Any]:
|
|
728
|
+
"""Get page by ID."""
|
|
729
|
+
return await self._client._request(
|
|
730
|
+
endpoint=f"/api/guardian/pages/{page_id}",
|
|
731
|
+
method="GET"
|
|
732
|
+
)
|
|
733
|
+
|
|
734
|
+
async def list_pages(
|
|
735
|
+
self,
|
|
736
|
+
is_active: Optional[bool] = None,
|
|
737
|
+
limit: int = 100,
|
|
738
|
+
offset: int = 0
|
|
739
|
+
) -> Dict[str, Any]:
|
|
740
|
+
"""List pages with optional filters."""
|
|
741
|
+
params = f"?limit={limit}&offset={offset}"
|
|
742
|
+
if is_active is not None:
|
|
743
|
+
params += f"&is_active={str(is_active).lower()}"
|
|
744
|
+
|
|
745
|
+
return await self._client._request(
|
|
746
|
+
endpoint=f"/api/guardian/pages{params}",
|
|
747
|
+
method="GET"
|
|
748
|
+
)
|
|
749
|
+
|
|
750
|
+
async def update_page(
|
|
751
|
+
self,
|
|
752
|
+
page_id: str,
|
|
753
|
+
path: Optional[str] = None,
|
|
754
|
+
name: Optional[str] = None,
|
|
755
|
+
description: Optional[str] = None,
|
|
756
|
+
is_active: Optional[bool] = None
|
|
757
|
+
) -> Dict[str, Any]:
|
|
758
|
+
"""Update page."""
|
|
759
|
+
body = {}
|
|
760
|
+
if path is not None:
|
|
761
|
+
body["path"] = path
|
|
762
|
+
if name is not None:
|
|
763
|
+
body["name"] = name
|
|
764
|
+
if description is not None:
|
|
765
|
+
body["description"] = description
|
|
766
|
+
if is_active is not None:
|
|
767
|
+
body["is_active"] = is_active
|
|
768
|
+
|
|
769
|
+
return await self._client._request(
|
|
770
|
+
endpoint=f"/api/guardian/pages/{page_id}",
|
|
771
|
+
method="PUT",
|
|
772
|
+
body=body
|
|
773
|
+
)
|
|
774
|
+
|
|
775
|
+
async def delete_page(self, page_id: str) -> Dict[str, Any]:
|
|
776
|
+
"""Delete a page."""
|
|
777
|
+
return await self._client._request(
|
|
778
|
+
endpoint=f"/api/guardian/pages/{page_id}",
|
|
779
|
+
method="DELETE"
|
|
780
|
+
)
|
|
781
|
+
|
|
782
|
+
# Page-tenant junction
|
|
783
|
+
async def get_page_tenants(self, page_id: str) -> List[Dict[str, Any]]:
|
|
784
|
+
"""Get tenants assigned to page."""
|
|
785
|
+
return await self._client._request(
|
|
786
|
+
endpoint=f"/api/guardian/pages/{page_id}/tenants",
|
|
787
|
+
method="GET"
|
|
788
|
+
)
|
|
789
|
+
|
|
790
|
+
async def add_page_tenants(self, page_id: str, tenant_ids: List[str]) -> Dict[str, Any]:
|
|
791
|
+
"""Add tenants to page."""
|
|
792
|
+
return await self._client._request(
|
|
793
|
+
endpoint=f"/api/guardian/pages/{page_id}/tenants",
|
|
794
|
+
body={"tenant_ids": tenant_ids}
|
|
795
|
+
)
|
|
796
|
+
|
|
797
|
+
async def remove_page_tenants(self, page_id: str, tenant_ids: List[str]) -> Dict[str, Any]:
|
|
798
|
+
"""Remove tenants from page."""
|
|
799
|
+
return await self._client._request(
|
|
800
|
+
endpoint=f"/api/guardian/pages/{page_id}/tenants",
|
|
801
|
+
method="DELETE",
|
|
802
|
+
body={"tenant_ids": tenant_ids}
|
|
803
|
+
)
|
|
804
|
+
|
|
805
|
+
# Page-scope junction
|
|
806
|
+
async def get_page_scopes(self, page_id: str) -> List[Dict[str, Any]]:
|
|
807
|
+
"""Get scopes assigned to page."""
|
|
808
|
+
return await self._client._request(
|
|
809
|
+
endpoint=f"/api/guardian/pages/{page_id}/scopes",
|
|
810
|
+
method="GET"
|
|
811
|
+
)
|
|
812
|
+
|
|
813
|
+
async def add_page_scopes(self, page_id: str, scope_ids: List[str]) -> Dict[str, Any]:
|
|
814
|
+
"""Add scopes to page."""
|
|
815
|
+
return await self._client._request(
|
|
816
|
+
endpoint=f"/api/guardian/pages/{page_id}/scopes",
|
|
817
|
+
body={"scope_ids": scope_ids}
|
|
818
|
+
)
|
|
819
|
+
|
|
820
|
+
async def remove_page_scopes(self, page_id: str, scope_ids: List[str]) -> Dict[str, Any]:
|
|
821
|
+
"""Remove scopes from page."""
|
|
822
|
+
return await self._client._request(
|
|
823
|
+
endpoint=f"/api/guardian/pages/{page_id}/scopes",
|
|
824
|
+
method="DELETE",
|
|
825
|
+
body={"scope_ids": scope_ids}
|
|
826
|
+
)
|
|
827
|
+
|
|
828
|
+
async def set_page_scopes(self, page_id: str, scope_ids: List[str]) -> List[Dict[str, Any]]:
|
|
829
|
+
"""Set all scopes for page (replaces existing)."""
|
|
830
|
+
return await self._client._request(
|
|
831
|
+
endpoint=f"/api/guardian/pages/{page_id}/scopes",
|
|
832
|
+
method="PUT",
|
|
833
|
+
body={"scope_ids": scope_ids}
|
|
834
|
+
)
|
|
835
|
+
|
|
836
|
+
# ========================================
|
|
837
|
+
# NAVIGATION
|
|
838
|
+
# ========================================
|
|
839
|
+
|
|
840
|
+
async def create_nav_item(
|
|
841
|
+
self,
|
|
842
|
+
title: str,
|
|
843
|
+
icon: Optional[str] = None,
|
|
844
|
+
page_id: Optional[str] = None,
|
|
845
|
+
parent_id: Optional[str] = None,
|
|
846
|
+
sort_order: int = 0
|
|
847
|
+
) -> Dict[str, Any]:
|
|
848
|
+
"""Create a new navigation item."""
|
|
849
|
+
body = {"title": title, "sort_order": sort_order}
|
|
850
|
+
if icon:
|
|
851
|
+
body["icon"] = icon
|
|
852
|
+
if page_id:
|
|
853
|
+
body["page_id"] = page_id
|
|
854
|
+
if parent_id:
|
|
855
|
+
body["parent_id"] = parent_id
|
|
856
|
+
|
|
857
|
+
return await self._client._request(
|
|
858
|
+
endpoint="/api/guardian/nav-items",
|
|
859
|
+
body=body
|
|
860
|
+
)
|
|
861
|
+
|
|
862
|
+
async def get_nav_item(self, nav_id: str) -> Dict[str, Any]:
|
|
863
|
+
"""Get navigation item by ID."""
|
|
864
|
+
return await self._client._request(
|
|
865
|
+
endpoint=f"/api/guardian/nav-items/{nav_id}",
|
|
866
|
+
method="GET"
|
|
867
|
+
)
|
|
868
|
+
|
|
869
|
+
async def list_nav_items(
|
|
870
|
+
self,
|
|
871
|
+
limit: int = 100,
|
|
872
|
+
offset: int = 0
|
|
873
|
+
) -> Dict[str, Any]:
|
|
874
|
+
"""List all navigation items."""
|
|
875
|
+
return await self._client._request(
|
|
876
|
+
endpoint=f"/api/guardian/nav-items?limit={limit}&offset={offset}",
|
|
877
|
+
method="GET"
|
|
878
|
+
)
|
|
879
|
+
|
|
880
|
+
async def update_nav_item(
|
|
881
|
+
self,
|
|
882
|
+
nav_id: str,
|
|
883
|
+
title: Optional[str] = None,
|
|
884
|
+
icon: Optional[str] = None,
|
|
885
|
+
sort_order: Optional[int] = None
|
|
886
|
+
) -> Dict[str, Any]:
|
|
887
|
+
"""Update navigation item."""
|
|
888
|
+
body = {}
|
|
889
|
+
if title is not None:
|
|
890
|
+
body["title"] = title
|
|
891
|
+
if icon is not None:
|
|
892
|
+
body["icon"] = icon
|
|
893
|
+
if sort_order is not None:
|
|
894
|
+
body["sort_order"] = sort_order
|
|
895
|
+
|
|
896
|
+
return await self._client._request(
|
|
897
|
+
endpoint=f"/api/guardian/nav-items/{nav_id}",
|
|
898
|
+
method="PUT",
|
|
899
|
+
body=body
|
|
900
|
+
)
|
|
901
|
+
|
|
902
|
+
async def delete_nav_item(self, nav_id: str) -> Dict[str, Any]:
|
|
903
|
+
"""Delete a navigation item."""
|
|
904
|
+
return await self._client._request(
|
|
905
|
+
endpoint=f"/api/guardian/nav-items/{nav_id}",
|
|
906
|
+
method="DELETE"
|
|
907
|
+
)
|
|
908
|
+
|
|
909
|
+
# Nav-tenant junction
|
|
910
|
+
async def get_nav_tenants(self, nav_id: str) -> List[Dict[str, Any]]:
|
|
911
|
+
"""Get tenants assigned to nav item."""
|
|
912
|
+
return await self._client._request(
|
|
913
|
+
endpoint=f"/api/guardian/nav-items/{nav_id}/tenants",
|
|
914
|
+
method="GET"
|
|
915
|
+
)
|
|
916
|
+
|
|
917
|
+
async def add_nav_tenants(self, nav_id: str, tenant_ids: List[str]) -> Dict[str, Any]:
|
|
918
|
+
"""Add tenants to nav item."""
|
|
919
|
+
return await self._client._request(
|
|
920
|
+
endpoint=f"/api/guardian/nav-items/{nav_id}/tenants",
|
|
921
|
+
body={"tenant_ids": tenant_ids}
|
|
922
|
+
)
|
|
923
|
+
|
|
924
|
+
async def remove_nav_tenants(self, nav_id: str, tenant_ids: List[str]) -> Dict[str, Any]:
|
|
925
|
+
"""Remove tenants from nav item."""
|
|
926
|
+
return await self._client._request(
|
|
927
|
+
endpoint=f"/api/guardian/nav-items/{nav_id}/tenants",
|
|
928
|
+
method="DELETE",
|
|
929
|
+
body={"tenant_ids": tenant_ids}
|
|
930
|
+
)
|
|
931
|
+
|
|
932
|
+
# ========================================
|
|
933
|
+
# SECURE TABLES
|
|
934
|
+
# ========================================
|
|
935
|
+
|
|
936
|
+
async def create_secure_table(
|
|
937
|
+
self,
|
|
938
|
+
table_name: str,
|
|
939
|
+
schema_name: str = "public"
|
|
940
|
+
) -> Dict[str, Any]:
|
|
941
|
+
"""Mark a table as requiring access justification."""
|
|
942
|
+
return await self._client._request(
|
|
943
|
+
endpoint="/api/guardian/secure-tables",
|
|
944
|
+
body={"table_name": table_name, "schema_name": schema_name}
|
|
945
|
+
)
|
|
946
|
+
|
|
947
|
+
async def get_secure_table(self, secure_table_id: str) -> Dict[str, Any]:
|
|
948
|
+
"""Get secure table by ID."""
|
|
949
|
+
return await self._client._request(
|
|
950
|
+
endpoint=f"/api/guardian/secure-tables/{secure_table_id}",
|
|
951
|
+
method="GET"
|
|
952
|
+
)
|
|
953
|
+
|
|
954
|
+
async def list_secure_tables(
|
|
955
|
+
self,
|
|
956
|
+
limit: int = 100,
|
|
957
|
+
offset: int = 0
|
|
958
|
+
) -> Dict[str, Any]:
|
|
959
|
+
"""List all secure tables."""
|
|
960
|
+
return await self._client._request(
|
|
961
|
+
endpoint=f"/api/guardian/secure-tables?limit={limit}&offset={offset}",
|
|
962
|
+
method="GET"
|
|
963
|
+
)
|
|
964
|
+
|
|
965
|
+
async def delete_secure_table(self, secure_table_id: str) -> Dict[str, Any]:
|
|
966
|
+
"""Remove secure table designation."""
|
|
967
|
+
return await self._client._request(
|
|
968
|
+
endpoint=f"/api/guardian/secure-tables/{secure_table_id}",
|
|
969
|
+
method="DELETE"
|
|
970
|
+
)
|
|
971
|
+
|
|
972
|
+
# Secure table scope junction
|
|
973
|
+
async def get_secure_table_scopes(self, secure_table_id: str) -> List[Dict[str, Any]]:
|
|
974
|
+
"""Get scopes required to access secure table."""
|
|
975
|
+
return await self._client._request(
|
|
976
|
+
endpoint=f"/api/guardian/secure-tables/{secure_table_id}/scopes",
|
|
977
|
+
method="GET"
|
|
978
|
+
)
|
|
979
|
+
|
|
980
|
+
async def add_secure_table_scopes(
|
|
981
|
+
self,
|
|
982
|
+
secure_table_id: str,
|
|
983
|
+
scope_ids: List[str]
|
|
984
|
+
) -> Dict[str, Any]:
|
|
985
|
+
"""Add scope requirements to secure table."""
|
|
986
|
+
return await self._client._request(
|
|
987
|
+
endpoint=f"/api/guardian/secure-tables/{secure_table_id}/scopes",
|
|
988
|
+
body={"scope_ids": scope_ids}
|
|
989
|
+
)
|
|
990
|
+
|
|
991
|
+
async def remove_secure_table_scopes(
|
|
992
|
+
self,
|
|
993
|
+
secure_table_id: str,
|
|
994
|
+
scope_ids: List[str]
|
|
995
|
+
) -> Dict[str, Any]:
|
|
996
|
+
"""Remove scope requirements from secure table."""
|
|
997
|
+
return await self._client._request(
|
|
998
|
+
endpoint=f"/api/guardian/secure-tables/{secure_table_id}/scopes",
|
|
999
|
+
method="DELETE",
|
|
1000
|
+
body={"scope_ids": scope_ids}
|
|
1001
|
+
)
|
|
1002
|
+
|
|
1003
|
+
# ========================================
|
|
1004
|
+
# JWT OPERATIONS (via Warden)
|
|
1005
|
+
# ========================================
|
|
1006
|
+
|
|
1007
|
+
async def get_jwks(self) -> Dict[str, Any]:
|
|
1008
|
+
"""Get JWKS for JWT validation (cached)."""
|
|
1009
|
+
if self._public_key_cache:
|
|
1010
|
+
return self._public_key_cache
|
|
1011
|
+
|
|
1012
|
+
result = await self._client._request(
|
|
1013
|
+
endpoint="/api/warden/jwks",
|
|
1014
|
+
method="GET"
|
|
1015
|
+
)
|
|
1016
|
+
self._public_key_cache = result
|
|
1017
|
+
return result
|
|
1018
|
+
|
|
1019
|
+
async def validate_jwt(self, token: str) -> Dict[str, Any]:
|
|
1020
|
+
"""
|
|
1021
|
+
Validate a JWT and return its claims.
|
|
1022
|
+
|
|
1023
|
+
Raises ValueError if token is invalid or expired.
|
|
1024
|
+
"""
|
|
1025
|
+
import jwt as pyjwt
|
|
1026
|
+
from jwt import PyJWK
|
|
1027
|
+
|
|
1028
|
+
jwks = await self.get_jwks()
|
|
1029
|
+
|
|
1030
|
+
try:
|
|
1031
|
+
unverified_header = pyjwt.get_unverified_header(token)
|
|
1032
|
+
kid = unverified_header.get("kid")
|
|
1033
|
+
|
|
1034
|
+
key_data = None
|
|
1035
|
+
for key in jwks.get("keys", []):
|
|
1036
|
+
if key.get("kid") == kid or kid is None:
|
|
1037
|
+
key_data = key
|
|
1038
|
+
break
|
|
1039
|
+
|
|
1040
|
+
if not key_data:
|
|
1041
|
+
raise ValueError(f"No matching key found for kid: {kid}")
|
|
1042
|
+
|
|
1043
|
+
jwk = PyJWK.from_dict(key_data)
|
|
1044
|
+
public_key = jwk.key
|
|
1045
|
+
|
|
1046
|
+
claims = pyjwt.decode(
|
|
1047
|
+
token,
|
|
1048
|
+
public_key,
|
|
1049
|
+
algorithms=["RS256"],
|
|
1050
|
+
options={"verify_exp": True}
|
|
1051
|
+
)
|
|
1052
|
+
return claims
|
|
1053
|
+
except pyjwt.ExpiredSignatureError:
|
|
1054
|
+
raise ValueError("Token has expired")
|
|
1055
|
+
except pyjwt.InvalidTokenError as e:
|
|
1056
|
+
raise ValueError(f"Invalid token: {e}")
|