pangea-sdk 3.9.0__py3-none-any.whl → 4.0.0__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.
- pangea/__init__.py +1 -1
- pangea/asyncio/request.py +4 -4
- pangea/asyncio/services/audit.py +30 -11
- pangea/asyncio/services/authn.py +49 -29
- pangea/asyncio/services/authz.py +13 -7
- pangea/asyncio/services/embargo.py +2 -2
- pangea/asyncio/services/file_scan.py +3 -3
- pangea/asyncio/services/intel.py +40 -22
- pangea/asyncio/services/redact.py +5 -3
- pangea/asyncio/services/vault.py +32 -28
- pangea/dump_audit.py +1 -1
- pangea/request.py +8 -5
- pangea/response.py +9 -16
- pangea/services/audit/audit.py +43 -20
- pangea/services/audit/models.py +3 -3
- pangea/services/audit/util.py +3 -3
- pangea/services/authn/authn.py +39 -29
- pangea/services/authn/models.py +9 -4
- pangea/services/authz.py +10 -8
- pangea/services/embargo.py +2 -2
- pangea/services/file_scan.py +2 -2
- pangea/services/intel.py +23 -21
- pangea/services/redact.py +3 -3
- pangea/services/vault/models/common.py +6 -6
- pangea/services/vault/models/symmetric.py +2 -2
- pangea/services/vault/vault.py +28 -28
- pangea/utils.py +1 -18
- pangea/verify_audit.py +267 -83
- {pangea_sdk-3.9.0.dist-info → pangea_sdk-4.0.0.dist-info}/METADATA +10 -9
- pangea_sdk-4.0.0.dist-info/RECORD +46 -0
- {pangea_sdk-3.9.0.dist-info → pangea_sdk-4.0.0.dist-info}/WHEEL +1 -1
- pangea_sdk-3.9.0.dist-info/RECORD +0 -46
pangea/services/audit/audit.py
CHANGED
@@ -4,7 +4,7 @@ from __future__ import annotations
|
|
4
4
|
|
5
5
|
import datetime
|
6
6
|
import json
|
7
|
-
from typing import Any, Dict, List, Optional, Sequence, Set, Tuple, Union
|
7
|
+
from typing import Any, Dict, Iterable, List, Optional, Sequence, Set, Tuple, Union
|
8
8
|
|
9
9
|
import pangea.exceptions as pexc
|
10
10
|
from pangea.config import PangeaConfig
|
@@ -199,7 +199,7 @@ class AuditBase:
|
|
199
199
|
|
200
200
|
# verify consistency proofs
|
201
201
|
if self.can_verify_consistency_proof(search_event):
|
202
|
-
if self.verify_consistency_proof(
|
202
|
+
if search_event.leaf_index is not None and self.verify_consistency_proof(search_event.leaf_index):
|
203
203
|
search_event.consistency_verification = EventVerification.PASS
|
204
204
|
else:
|
205
205
|
search_event.consistency_verification = EventVerification.FAIL
|
@@ -222,7 +222,7 @@ class AuditBase:
|
|
222
222
|
tree_sizes.difference_update(self.pub_roots.keys())
|
223
223
|
|
224
224
|
if tree_sizes:
|
225
|
-
arweave_roots = get_arweave_published_roots(result.root.tree_name,
|
225
|
+
arweave_roots = get_arweave_published_roots(result.root.tree_name, tree_sizes)
|
226
226
|
else:
|
227
227
|
arweave_roots = {}
|
228
228
|
|
@@ -284,7 +284,7 @@ class AuditBase:
|
|
284
284
|
"""
|
285
285
|
return event.published and event.leaf_index is not None and event.leaf_index >= 0 # type: ignore[return-value]
|
286
286
|
|
287
|
-
def verify_consistency_proof(self,
|
287
|
+
def verify_consistency_proof(self, tree_size: int) -> bool:
|
288
288
|
"""
|
289
289
|
Verify consistency proof
|
290
290
|
|
@@ -293,18 +293,17 @@ class AuditBase:
|
|
293
293
|
Read more at: [What is a consistency proof?](https://pangea.cloud/docs/audit/merkle-trees#what-is-a-consistency-proof)
|
294
294
|
|
295
295
|
Args:
|
296
|
-
|
297
|
-
event (SearchEvent): Audit event to be verified.
|
296
|
+
leaf_index (int): The tree size of the root to be verified.
|
298
297
|
|
299
298
|
Returns:
|
300
299
|
bool: True if consistency proof is verified, False otherwise.
|
301
300
|
"""
|
302
301
|
|
303
|
-
if
|
302
|
+
if tree_size == 0:
|
304
303
|
return True
|
305
304
|
|
306
|
-
curr_root = pub_roots.get(
|
307
|
-
prev_root = pub_roots.get(
|
305
|
+
curr_root = self.pub_roots.get(tree_size + 1)
|
306
|
+
prev_root = self.pub_roots.get(tree_size)
|
308
307
|
|
309
308
|
if not curr_root or not prev_root:
|
310
309
|
return False
|
@@ -314,9 +313,12 @@ class AuditBase:
|
|
314
313
|
):
|
315
314
|
return False
|
316
315
|
|
316
|
+
if curr_root.consistency_proof is None:
|
317
|
+
return False
|
318
|
+
|
317
319
|
curr_root_hash = decode_hash(curr_root.root_hash)
|
318
320
|
prev_root_hash = decode_hash(prev_root.root_hash)
|
319
|
-
proof = decode_consistency_proof(curr_root.consistency_proof)
|
321
|
+
proof = decode_consistency_proof(curr_root.consistency_proof)
|
320
322
|
|
321
323
|
return verify_consistency_proof(curr_root_hash, prev_root_hash, proof)
|
322
324
|
|
@@ -519,7 +521,9 @@ class Audit(ServiceBase, AuditBase):
|
|
519
521
|
"""
|
520
522
|
|
521
523
|
input = self._get_log_request(event, sign_local=sign_local, verify=verify, verbose=verbose)
|
522
|
-
response: PangeaResponse[LogResult] = self.request.post(
|
524
|
+
response: PangeaResponse[LogResult] = self.request.post(
|
525
|
+
"v1/log", LogResult, data=input.model_dump(exclude_none=True)
|
526
|
+
)
|
523
527
|
if response.success and response.result is not None:
|
524
528
|
self._process_log_result(response.result, verify=verify)
|
525
529
|
return response
|
@@ -559,7 +563,7 @@ class Audit(ServiceBase, AuditBase):
|
|
559
563
|
|
560
564
|
input = self._get_log_request(events, sign_local=sign_local, verify=False, verbose=verbose)
|
561
565
|
response: PangeaResponse[LogBulkResult] = self.request.post(
|
562
|
-
"v2/log", LogBulkResult, data=input.
|
566
|
+
"v2/log", LogBulkResult, data=input.model_dump(exclude_none=True)
|
563
567
|
)
|
564
568
|
|
565
569
|
if response.success and response.result is not None:
|
@@ -603,7 +607,7 @@ class Audit(ServiceBase, AuditBase):
|
|
603
607
|
try:
|
604
608
|
# Calling to v2 methods will return always a 202.
|
605
609
|
response: PangeaResponse[LogBulkResult] = self.request.post(
|
606
|
-
"v2/log_async", LogBulkResult, data=input.
|
610
|
+
"v2/log_async", LogBulkResult, data=input.model_dump(exclude_none=True), poll_result=False
|
607
611
|
)
|
608
612
|
except pexc.AcceptedRequestException as e:
|
609
613
|
return e.response
|
@@ -694,7 +698,7 @@ class Audit(ServiceBase, AuditBase):
|
|
694
698
|
)
|
695
699
|
|
696
700
|
response: PangeaResponse[SearchOutput] = self.request.post(
|
697
|
-
"v1/search", SearchOutput, data=input.
|
701
|
+
"v1/search", SearchOutput, data=input.model_dump(exclude_none=True)
|
698
702
|
)
|
699
703
|
if verify_consistency and response.result is not None:
|
700
704
|
self.update_published_roots(response.result)
|
@@ -751,7 +755,7 @@ class Audit(ServiceBase, AuditBase):
|
|
751
755
|
return_context=return_context,
|
752
756
|
)
|
753
757
|
response: PangeaResponse[SearchResultOutput] = self.request.post(
|
754
|
-
"v1/results", SearchResultOutput, data=input.
|
758
|
+
"v1/results", SearchResultOutput, data=input.model_dump(exclude_none=True)
|
755
759
|
)
|
756
760
|
if verify_consistency and response.result is not None:
|
757
761
|
self.update_published_roots(response.result)
|
@@ -813,7 +817,7 @@ class Audit(ServiceBase, AuditBase):
|
|
813
817
|
)
|
814
818
|
try:
|
815
819
|
return self.request.post(
|
816
|
-
"v1/export", PangeaResponseResult, data=input.
|
820
|
+
"v1/export", PangeaResponseResult, data=input.model_dump(exclude_none=True), poll_result=False
|
817
821
|
)
|
818
822
|
except pexc.AcceptedRequestException as e:
|
819
823
|
return e.response
|
@@ -880,7 +884,7 @@ class Audit(ServiceBase, AuditBase):
|
|
880
884
|
response = audit.root(tree_size=7)
|
881
885
|
"""
|
882
886
|
input = RootRequest(tree_size=tree_size)
|
883
|
-
return self.request.post("v1/root", RootResult, data=input.
|
887
|
+
return self.request.post("v1/root", RootResult, data=input.model_dump(exclude_none=True))
|
884
888
|
|
885
889
|
def download_results(
|
886
890
|
self,
|
@@ -922,7 +926,7 @@ class Audit(ServiceBase, AuditBase):
|
|
922
926
|
input = DownloadRequest(
|
923
927
|
request_id=request_id, result_id=result_id, format=format, return_context=return_context
|
924
928
|
)
|
925
|
-
return self.request.post("v1/download_results", DownloadResult, data=input.
|
929
|
+
return self.request.post("v1/download_results", DownloadResult, data=input.model_dump(exclude_none=True))
|
926
930
|
|
927
931
|
def update_published_roots(self, result: SearchResultOutput):
|
928
932
|
"""Fetches series of published root hashes from Arweave
|
@@ -947,12 +951,31 @@ class Audit(ServiceBase, AuditBase):
|
|
947
951
|
for tree_size in tree_sizes:
|
948
952
|
pub_root = None
|
949
953
|
if tree_size in arweave_roots:
|
950
|
-
pub_root = PublishedRoot(**arweave_roots[tree_size].
|
954
|
+
pub_root = PublishedRoot(**arweave_roots[tree_size].model_dump(exclude_none=True))
|
951
955
|
pub_root.source = RootSource.ARWEAVE
|
952
956
|
elif self.allow_server_roots:
|
953
957
|
resp = self.root(tree_size=tree_size)
|
954
958
|
if resp.success and resp.result is not None:
|
955
|
-
pub_root = PublishedRoot(**resp.result.data.
|
959
|
+
pub_root = PublishedRoot(**resp.result.data.model_dump(exclude_none=True))
|
956
960
|
pub_root.source = RootSource.PANGEA
|
957
961
|
if pub_root is not None:
|
958
962
|
self.pub_roots[tree_size] = pub_root
|
963
|
+
|
964
|
+
self.fix_consistency_proofs(tree_sizes)
|
965
|
+
|
966
|
+
def fix_consistency_proofs(self, tree_sizes: Iterable[int]):
|
967
|
+
# on very rare occasions, the consistency proof in Arweave may be wrong
|
968
|
+
# override it with the proof from pangea (not the root hash, just the proof)
|
969
|
+
for tree_size in tree_sizes:
|
970
|
+
if tree_size not in self.pub_roots or tree_size - 1 not in self.pub_roots:
|
971
|
+
continue
|
972
|
+
|
973
|
+
if self.pub_roots[tree_size].source == RootSource.PANGEA:
|
974
|
+
continue
|
975
|
+
|
976
|
+
if self.verify_consistency_proof(tree_size):
|
977
|
+
continue
|
978
|
+
|
979
|
+
resp = self.root(tree_size=tree_size)
|
980
|
+
if resp.success and resp.result is not None and resp.result.data is not None:
|
981
|
+
self.pub_roots[tree_size].consistency_proof = resp.result.data.consistency_proof
|
pangea/services/audit/models.py
CHANGED
@@ -6,7 +6,7 @@ import datetime
|
|
6
6
|
import enum
|
7
7
|
from typing import Any, Dict, List, Optional, Sequence, Union
|
8
8
|
|
9
|
-
from pangea.response import APIRequestModel, APIResponseModel, PangeaResponseResult
|
9
|
+
from pangea.response import APIRequestModel, APIResponseModel, PangeaDateTime, PangeaResponseResult
|
10
10
|
|
11
11
|
|
12
12
|
class EventVerification(str, enum.Enum):
|
@@ -126,7 +126,7 @@ class EventEnvelope(APIResponseModel):
|
|
126
126
|
event: Dict[str, Any]
|
127
127
|
signature: Optional[str] = None
|
128
128
|
public_key: Optional[str] = None
|
129
|
-
received_at:
|
129
|
+
received_at: PangeaDateTime
|
130
130
|
|
131
131
|
|
132
132
|
class LogRequest(APIRequestModel):
|
@@ -410,7 +410,7 @@ class SearchOutput(SearchResultOutput):
|
|
410
410
|
"""
|
411
411
|
|
412
412
|
id: str
|
413
|
-
expires_at:
|
413
|
+
expires_at: PangeaDateTime
|
414
414
|
|
415
415
|
|
416
416
|
class SearchResultRequest(APIRequestModel):
|
pangea/services/audit/util.py
CHANGED
@@ -7,7 +7,7 @@ from binascii import hexlify, unhexlify
|
|
7
7
|
from dataclasses import dataclass
|
8
8
|
from datetime import datetime
|
9
9
|
from hashlib import sha256
|
10
|
-
from typing import Dict, List, Optional
|
10
|
+
from typing import Collection, Dict, List, Optional
|
11
11
|
|
12
12
|
import requests
|
13
13
|
|
@@ -61,7 +61,7 @@ def verify_hash(hash1: str, hash2: str) -> bool:
|
|
61
61
|
|
62
62
|
|
63
63
|
def verify_envelope_hash(envelope: EventEnvelope, hash: str):
|
64
|
-
return verify_hash(hash_dict(normalize_log(envelope.
|
64
|
+
return verify_hash(hash_dict(normalize_log(envelope.model_dump(exclude_none=True))), hash)
|
65
65
|
|
66
66
|
|
67
67
|
def canonicalize_event(event: Event) -> bytes:
|
@@ -192,7 +192,7 @@ def arweave_graphql_url():
|
|
192
192
|
return f"{ARWEAVE_BASE_URL}/graphql"
|
193
193
|
|
194
194
|
|
195
|
-
def get_arweave_published_roots(tree_name: str, tree_sizes:
|
195
|
+
def get_arweave_published_roots(tree_name: str, tree_sizes: Collection[int]) -> Dict[int, PublishedRoot]:
|
196
196
|
if len(tree_sizes) == 0:
|
197
197
|
return {}
|
198
198
|
|
pangea/services/authn/authn.py
CHANGED
@@ -96,7 +96,7 @@ class AuthN(ServiceBase):
|
|
96
96
|
"""
|
97
97
|
input = m.SessionInvalidateRequest(session_id=session_id)
|
98
98
|
return self.request.post(
|
99
|
-
"v2/session/invalidate", m.SessionInvalidateResult, data=input.
|
99
|
+
"v2/session/invalidate", m.SessionInvalidateResult, data=input.model_dump(exclude_none=True)
|
100
100
|
)
|
101
101
|
|
102
102
|
def list(
|
@@ -134,7 +134,7 @@ class AuthN(ServiceBase):
|
|
134
134
|
filter = m.SessionListFilter(**filter)
|
135
135
|
|
136
136
|
input = m.SessionListRequest(filter=filter, last=last, order=order, order_by=order_by, size=size)
|
137
|
-
return self.request.post("v2/session/list", m.SessionListResults, data=input.
|
137
|
+
return self.request.post("v2/session/list", m.SessionListResults, data=input.model_dump(exclude_none=True))
|
138
138
|
|
139
139
|
def logout(self, user_id: str) -> PangeaResponse[m.SessionLogoutResult]:
|
140
140
|
"""
|
@@ -156,7 +156,9 @@ class AuthN(ServiceBase):
|
|
156
156
|
)
|
157
157
|
"""
|
158
158
|
input = m.SessionLogoutRequest(user_id=user_id)
|
159
|
-
return self.request.post(
|
159
|
+
return self.request.post(
|
160
|
+
"v2/session/logout", m.SessionLogoutResult, data=input.model_dump(exclude_none=True)
|
161
|
+
)
|
160
162
|
|
161
163
|
class Client(ServiceBase):
|
162
164
|
service_name = SERVICE_NAME
|
@@ -194,7 +196,9 @@ class AuthN(ServiceBase):
|
|
194
196
|
)
|
195
197
|
"""
|
196
198
|
input = m.ClientUserinfoRequest(code=code)
|
197
|
-
return self.request.post(
|
199
|
+
return self.request.post(
|
200
|
+
"v2/client/userinfo", m.ClientUserinfoResult, data=input.model_dump(exclude_none=True)
|
201
|
+
)
|
198
202
|
|
199
203
|
def jwks(
|
200
204
|
self,
|
@@ -250,7 +254,9 @@ class AuthN(ServiceBase):
|
|
250
254
|
"""
|
251
255
|
input = m.ClientSessionInvalidateRequest(token=token, session_id=session_id)
|
252
256
|
return self.request.post(
|
253
|
-
"v2/client/session/invalidate",
|
257
|
+
"v2/client/session/invalidate",
|
258
|
+
m.ClientSessionInvalidateResult,
|
259
|
+
data=input.model_dump(exclude_none=True),
|
254
260
|
)
|
255
261
|
|
256
262
|
def list(
|
@@ -295,7 +301,7 @@ class AuthN(ServiceBase):
|
|
295
301
|
token=token, filter=filter, last=last, order=order, order_by=order_by, size=size
|
296
302
|
)
|
297
303
|
return self.request.post(
|
298
|
-
"v2/client/session/list", m.ClientSessionListResults, data=input.
|
304
|
+
"v2/client/session/list", m.ClientSessionListResults, data=input.model_dump(exclude_none=True)
|
299
305
|
)
|
300
306
|
|
301
307
|
def logout(self, token: str) -> PangeaResponse[m.ClientSessionLogoutResult]:
|
@@ -319,7 +325,7 @@ class AuthN(ServiceBase):
|
|
319
325
|
"""
|
320
326
|
input = m.ClientSessionLogoutRequest(token=token)
|
321
327
|
return self.request.post(
|
322
|
-
"v2/client/session/logout", m.ClientSessionLogoutResult, data=input.
|
328
|
+
"v2/client/session/logout", m.ClientSessionLogoutResult, data=input.model_dump(exclude_none=True)
|
323
329
|
)
|
324
330
|
|
325
331
|
def refresh(
|
@@ -349,7 +355,7 @@ class AuthN(ServiceBase):
|
|
349
355
|
"""
|
350
356
|
input = m.ClientSessionRefreshRequest(refresh_token=refresh_token, user_token=user_token)
|
351
357
|
return self.request.post(
|
352
|
-
"v2/client/session/refresh", m.ClientSessionRefreshResult, data=input.
|
358
|
+
"v2/client/session/refresh", m.ClientSessionRefreshResult, data=input.model_dump(exclude_none=True)
|
353
359
|
)
|
354
360
|
|
355
361
|
class Password(ServiceBase):
|
@@ -390,7 +396,7 @@ class AuthN(ServiceBase):
|
|
390
396
|
"""
|
391
397
|
input = m.ClientPasswordChangeRequest(token=token, old_password=old_password, new_password=new_password)
|
392
398
|
return self.request.post(
|
393
|
-
"v2/client/password/change", m.ClientPasswordChangeResult, data=input.
|
399
|
+
"v2/client/password/change", m.ClientPasswordChangeResult, data=input.model_dump(exclude_none=True)
|
394
400
|
)
|
395
401
|
|
396
402
|
class Token(ServiceBase):
|
@@ -427,7 +433,7 @@ class AuthN(ServiceBase):
|
|
427
433
|
"""
|
428
434
|
input = m.ClientTokenCheckRequest(token=token)
|
429
435
|
return self.request.post(
|
430
|
-
"v2/client/token/check", m.ClientTokenCheckResult, data=input.
|
436
|
+
"v2/client/token/check", m.ClientTokenCheckResult, data=input.model_dump(exclude_none=True)
|
431
437
|
)
|
432
438
|
|
433
439
|
class User(ServiceBase):
|
@@ -482,7 +488,7 @@ class AuthN(ServiceBase):
|
|
482
488
|
profile=profile,
|
483
489
|
username=username,
|
484
490
|
)
|
485
|
-
return self.request.post("v2/user/create", m.UserCreateResult, data=input.
|
491
|
+
return self.request.post("v2/user/create", m.UserCreateResult, data=input.model_dump(exclude_none=True))
|
486
492
|
|
487
493
|
def delete(
|
488
494
|
self, email: str | None = None, id: str | None = None, *, username: str | None = None
|
@@ -506,7 +512,7 @@ class AuthN(ServiceBase):
|
|
506
512
|
authn.user.delete(email="example@example.com")
|
507
513
|
"""
|
508
514
|
input = m.UserDeleteRequest(email=email, id=id, username=username)
|
509
|
-
return self.request.post("v2/user/delete", m.UserDeleteResult, data=input.
|
515
|
+
return self.request.post("v2/user/delete", m.UserDeleteResult, data=input.model_dump(exclude_none=True))
|
510
516
|
|
511
517
|
def invite(
|
512
518
|
self,
|
@@ -547,7 +553,7 @@ class AuthN(ServiceBase):
|
|
547
553
|
callback=callback,
|
548
554
|
state=state,
|
549
555
|
)
|
550
|
-
return self.request.post("v2/user/invite", m.UserInviteResult, data=input.
|
556
|
+
return self.request.post("v2/user/invite", m.UserInviteResult, data=input.model_dump(exclude_none=True))
|
551
557
|
|
552
558
|
def update(
|
553
559
|
self,
|
@@ -592,7 +598,7 @@ class AuthN(ServiceBase):
|
|
592
598
|
username=username,
|
593
599
|
)
|
594
600
|
|
595
|
-
return self.request.post("v2/user/update", m.UserUpdateResult, data=input.
|
601
|
+
return self.request.post("v2/user/update", m.UserUpdateResult, data=input.model_dump(exclude_none=True))
|
596
602
|
|
597
603
|
def list(
|
598
604
|
self,
|
@@ -635,7 +641,7 @@ class AuthN(ServiceBase):
|
|
635
641
|
order_by=order_by,
|
636
642
|
size=size,
|
637
643
|
)
|
638
|
-
return self.request.post("v2/user/list", m.UserListResult, data=input.
|
644
|
+
return self.request.post("v2/user/list", m.UserListResult, data=input.model_dump(exclude_none=True))
|
639
645
|
|
640
646
|
class Invites(ServiceBase):
|
641
647
|
service_name = SERVICE_NAME
|
@@ -682,7 +688,7 @@ class AuthN(ServiceBase):
|
|
682
688
|
|
683
689
|
input = m.UserInviteListRequest(filter=filter, last=last, order=order, order_by=order_by, size=size)
|
684
690
|
return self.request.post(
|
685
|
-
"v2/user/invite/list", m.UserInviteListResult, data=input.
|
691
|
+
"v2/user/invite/list", m.UserInviteListResult, data=input.model_dump(exclude_none=True)
|
686
692
|
)
|
687
693
|
|
688
694
|
def delete(self, id: str) -> PangeaResponse[m.UserInviteDeleteResult]:
|
@@ -706,7 +712,7 @@ class AuthN(ServiceBase):
|
|
706
712
|
"""
|
707
713
|
input = m.UserInviteDeleteRequest(id=id)
|
708
714
|
return self.request.post(
|
709
|
-
"v2/user/invite/delete", m.UserInviteDeleteResult, data=input.
|
715
|
+
"v2/user/invite/delete", m.UserInviteDeleteResult, data=input.model_dump(exclude_none=True)
|
710
716
|
)
|
711
717
|
|
712
718
|
class Authenticators(ServiceBase):
|
@@ -756,7 +762,7 @@ class AuthN(ServiceBase):
|
|
756
762
|
return self.request.post(
|
757
763
|
"v2/user/authenticators/delete",
|
758
764
|
m.UserAuthenticatorsDeleteResult,
|
759
|
-
data=input.
|
765
|
+
data=input.model_dump(exclude_none=True),
|
760
766
|
)
|
761
767
|
|
762
768
|
def list(
|
@@ -786,7 +792,9 @@ class AuthN(ServiceBase):
|
|
786
792
|
"""
|
787
793
|
input = m.UserAuthenticatorsListRequest(email=email, id=id, username=username)
|
788
794
|
return self.request.post(
|
789
|
-
"v2/user/authenticators/list",
|
795
|
+
"v2/user/authenticators/list",
|
796
|
+
m.UserAuthenticatorsListResult,
|
797
|
+
data=input.model_dump(exclude_none=True),
|
790
798
|
)
|
791
799
|
|
792
800
|
class Profile(ServiceBase):
|
@@ -827,7 +835,7 @@ class AuthN(ServiceBase):
|
|
827
835
|
"""
|
828
836
|
input = m.UserProfileGetRequest(id=id, email=email, username=username)
|
829
837
|
return self.request.post(
|
830
|
-
"v2/user/profile/get", m.UserProfileGetResult, data=input.
|
838
|
+
"v2/user/profile/get", m.UserProfileGetResult, data=input.model_dump(exclude_none=True)
|
831
839
|
)
|
832
840
|
|
833
841
|
def update(
|
@@ -871,7 +879,7 @@ class AuthN(ServiceBase):
|
|
871
879
|
username=username,
|
872
880
|
)
|
873
881
|
return self.request.post(
|
874
|
-
"v2/user/profile/update", m.UserProfileUpdateResult, data=input.
|
882
|
+
"v2/user/profile/update", m.UserProfileUpdateResult, data=input.model_dump(exclude_none=True)
|
875
883
|
)
|
876
884
|
|
877
885
|
class Flow(ServiceBase):
|
@@ -907,7 +915,7 @@ class AuthN(ServiceBase):
|
|
907
915
|
)
|
908
916
|
"""
|
909
917
|
input = m.FlowCompleteRequest(flow_id=flow_id)
|
910
|
-
return self.request.post("v2/flow/complete", m.FlowCompleteResult, data=input.
|
918
|
+
return self.request.post("v2/flow/complete", m.FlowCompleteResult, data=input.model_dump(exclude_none=True))
|
911
919
|
|
912
920
|
def restart(
|
913
921
|
self, flow_id: str, choice: m.FlowChoice, data: m.FlowRestartData = {}
|
@@ -939,7 +947,7 @@ class AuthN(ServiceBase):
|
|
939
947
|
"""
|
940
948
|
|
941
949
|
input = m.FlowRestartRequest(flow_id=flow_id, choice=choice, data=data)
|
942
|
-
return self.request.post("v2/flow/restart", m.FlowRestartResult, data=input.
|
950
|
+
return self.request.post("v2/flow/restart", m.FlowRestartResult, data=input.model_dump(exclude_none=True))
|
943
951
|
|
944
952
|
def start(
|
945
953
|
self,
|
@@ -978,7 +986,7 @@ class AuthN(ServiceBase):
|
|
978
986
|
)
|
979
987
|
"""
|
980
988
|
input = m.FlowStartRequest(cb_uri=cb_uri, email=email, flow_types=flow_types, invitation=invitation)
|
981
|
-
return self.request.post("v2/flow/start", m.FlowStartResult, data=input.
|
989
|
+
return self.request.post("v2/flow/start", m.FlowStartResult, data=input.model_dump(exclude_none=True))
|
982
990
|
|
983
991
|
def update(
|
984
992
|
self, flow_id: str, choice: m.FlowChoice, data: m.FlowUpdateData = {}
|
@@ -1012,7 +1020,7 @@ class AuthN(ServiceBase):
|
|
1012
1020
|
"""
|
1013
1021
|
|
1014
1022
|
input = m.FlowUpdateRequest(flow_id=flow_id, choice=choice, data=data)
|
1015
|
-
return self.request.post("v2/flow/update", m.FlowUpdateResult, data=input.
|
1023
|
+
return self.request.post("v2/flow/update", m.FlowUpdateResult, data=input.model_dump(exclude_none=True))
|
1016
1024
|
|
1017
1025
|
class Agreements(ServiceBase):
|
1018
1026
|
service_name = SERVICE_NAME
|
@@ -1056,7 +1064,7 @@ class AuthN(ServiceBase):
|
|
1056
1064
|
|
1057
1065
|
input = m.AgreementCreateRequest(type=type, name=name, text=text, active=active)
|
1058
1066
|
return self.request.post(
|
1059
|
-
"v2/agreements/create", m.AgreementCreateResult, data=input.
|
1067
|
+
"v2/agreements/create", m.AgreementCreateResult, data=input.model_dump(exclude_none=True)
|
1060
1068
|
)
|
1061
1069
|
|
1062
1070
|
def delete(self, type: m.AgreementType, id: str) -> PangeaResponse[m.AgreementDeleteResult]:
|
@@ -1083,7 +1091,7 @@ class AuthN(ServiceBase):
|
|
1083
1091
|
|
1084
1092
|
input = m.AgreementDeleteRequest(type=type, id=id)
|
1085
1093
|
return self.request.post(
|
1086
|
-
"v2/agreements/delete", m.AgreementDeleteResult, data=input.
|
1094
|
+
"v2/agreements/delete", m.AgreementDeleteResult, data=input.model_dump(exclude_none=True)
|
1087
1095
|
)
|
1088
1096
|
|
1089
1097
|
def list(
|
@@ -1121,7 +1129,9 @@ class AuthN(ServiceBase):
|
|
1121
1129
|
filter = m.AgreementListFilter(**filter)
|
1122
1130
|
|
1123
1131
|
input = m.AgreementListRequest(filter=filter, last=last, order=order, order_by=order_by, size=size)
|
1124
|
-
return self.request.post(
|
1132
|
+
return self.request.post(
|
1133
|
+
"v2/agreements/list", m.AgreementListResult, data=input.model_dump(exclude_none=True)
|
1134
|
+
)
|
1125
1135
|
|
1126
1136
|
def update(
|
1127
1137
|
self,
|
@@ -1161,5 +1171,5 @@ class AuthN(ServiceBase):
|
|
1161
1171
|
|
1162
1172
|
input = m.AgreementUpdateRequest(type=type, id=id, name=name, text=text, active=active)
|
1163
1173
|
return self.request.post(
|
1164
|
-
"v2/agreements/update", m.AgreementUpdateResult, data=input.
|
1174
|
+
"v2/agreements/update", m.AgreementUpdateResult, data=input.model_dump(exclude_none=True)
|
1165
1175
|
)
|
pangea/services/authn/models.py
CHANGED
@@ -1,10 +1,13 @@
|
|
1
1
|
# Copyright 2022 Pangea Cyber Corporation
|
2
2
|
# Author: Pangea Cyber Corporation
|
3
|
+
|
3
4
|
from __future__ import annotations
|
4
5
|
|
5
6
|
import enum
|
6
7
|
from typing import Dict, List, NewType, Optional, Union
|
7
8
|
|
9
|
+
from pydantic import BaseModel, ConfigDict
|
10
|
+
|
8
11
|
import pangea.services.intel as im
|
9
12
|
from pangea.response import APIRequestModel, APIResponseModel, PangeaResponseResult
|
10
13
|
from pangea.services.vault.models.common import JWK, JWKec, JWKrsa
|
@@ -12,11 +15,13 @@ from pangea.services.vault.models.common import JWK, JWKec, JWKrsa
|
|
12
15
|
Scopes = NewType("Scopes", List[str])
|
13
16
|
|
14
17
|
|
15
|
-
class Profile(
|
18
|
+
class Profile(BaseModel):
|
16
19
|
first_name: str
|
17
|
-
last_name: str
|
20
|
+
last_name: Optional[str] = None
|
18
21
|
phone: Optional[str] = None
|
19
22
|
|
23
|
+
model_config = ConfigDict(extra="allow")
|
24
|
+
|
20
25
|
|
21
26
|
class ClientPasswordChangeRequest(APIRequestModel):
|
22
27
|
token: str
|
@@ -70,7 +75,7 @@ class LoginToken(SessionToken):
|
|
70
75
|
|
71
76
|
|
72
77
|
class ClientTokenCheckResult(SessionToken):
|
73
|
-
token: Optional[str]
|
78
|
+
token: Optional[str] = None
|
74
79
|
|
75
80
|
|
76
81
|
class IDProvider(str, enum.Enum):
|
@@ -324,7 +329,7 @@ class UserInviterOrderBy(enum.Enum):
|
|
324
329
|
|
325
330
|
|
326
331
|
class UserInviteListFilter(APIRequestModel):
|
327
|
-
callback: Optional[str]
|
332
|
+
callback: Optional[str] = None
|
328
333
|
callback__contains: Optional[List[str]] = None
|
329
334
|
callback__in: Optional[List[str]] = None
|
330
335
|
created_at: Optional[str] = None
|
pangea/services/authz.py
CHANGED
@@ -113,7 +113,7 @@ class CheckRequest(APIRequestModel):
|
|
113
113
|
class DebugPath(APIResponseModel):
|
114
114
|
type: str
|
115
115
|
id: str
|
116
|
-
action: Optional[str]
|
116
|
+
action: Optional[str] = None
|
117
117
|
|
118
118
|
|
119
119
|
class Debug(APIResponseModel):
|
@@ -202,7 +202,7 @@ class AuthZ(ServiceBase):
|
|
202
202
|
"""
|
203
203
|
|
204
204
|
input_data = TupleCreateRequest(tuples=tuples)
|
205
|
-
return self.request.post("v1/tuple/create", TupleCreateResult, data=input_data.
|
205
|
+
return self.request.post("v1/tuple/create", TupleCreateResult, data=input_data.model_dump(exclude_none=True))
|
206
206
|
|
207
207
|
def tuple_list(
|
208
208
|
self,
|
@@ -237,9 +237,9 @@ class AuthZ(ServiceBase):
|
|
237
237
|
authz.tuple_list(TupleListFilter(subject_type="user", subject_id="user_1"))
|
238
238
|
"""
|
239
239
|
input_data = TupleListRequest(
|
240
|
-
filter=filter.
|
240
|
+
filter=filter.model_dump(exclude_none=True), size=size, last=last, order=order, order_by=order_by
|
241
241
|
)
|
242
|
-
return self.request.post("v1/tuple/list", TupleListResult, data=input_data.
|
242
|
+
return self.request.post("v1/tuple/list", TupleListResult, data=input_data.model_dump(exclude_none=True))
|
243
243
|
|
244
244
|
def tuple_delete(self, tuples: List[Tuple]) -> PangeaResponse[TupleDeleteResult]:
|
245
245
|
"""Delete tuples.
|
@@ -270,7 +270,7 @@ class AuthZ(ServiceBase):
|
|
270
270
|
"""
|
271
271
|
|
272
272
|
input_data = TupleDeleteRequest(tuples=tuples)
|
273
|
-
return self.request.post("v1/tuple/delete", TupleDeleteResult, data=input_data.
|
273
|
+
return self.request.post("v1/tuple/delete", TupleDeleteResult, data=input_data.model_dump(exclude_none=True))
|
274
274
|
|
275
275
|
def check(
|
276
276
|
self,
|
@@ -309,7 +309,7 @@ class AuthZ(ServiceBase):
|
|
309
309
|
"""
|
310
310
|
|
311
311
|
input_data = CheckRequest(resource=resource, action=action, subject=subject, debug=debug, attributes=attributes)
|
312
|
-
return self.request.post("v1/check", CheckResult, data=input_data.
|
312
|
+
return self.request.post("v1/check", CheckResult, data=input_data.model_dump(exclude_none=True))
|
313
313
|
|
314
314
|
def list_resources(self, type: str, action: str, subject: Subject) -> PangeaResponse[ListResourcesResult]:
|
315
315
|
"""List resources.
|
@@ -339,7 +339,9 @@ class AuthZ(ServiceBase):
|
|
339
339
|
"""
|
340
340
|
|
341
341
|
input_data = ListResourcesRequest(type=type, action=action, subject=subject)
|
342
|
-
return self.request.post(
|
342
|
+
return self.request.post(
|
343
|
+
"v1/list-resources", ListResourcesResult, data=input_data.model_dump(exclude_none=True)
|
344
|
+
)
|
343
345
|
|
344
346
|
def list_subjects(self, resource: Resource, action: str) -> PangeaResponse[ListSubjectsResult]:
|
345
347
|
"""List subjects.
|
@@ -367,4 +369,4 @@ class AuthZ(ServiceBase):
|
|
367
369
|
"""
|
368
370
|
|
369
371
|
input_data = ListSubjectsRequest(resource=resource, action=action)
|
370
|
-
return self.request.post("v1/list-subjects", ListSubjectsResult, data=input_data.
|
372
|
+
return self.request.post("v1/list-subjects", ListSubjectsResult, data=input_data.model_dump(exclude_none=True))
|
pangea/services/embargo.py
CHANGED
@@ -103,7 +103,7 @@ class Embargo(ServiceBase):
|
|
103
103
|
response = embargo.ip_check("190.6.64.94")
|
104
104
|
"""
|
105
105
|
input = IPCheckRequest(ip=ip)
|
106
|
-
return self.request.post("v1/ip/check", EmbargoResult, data=input.
|
106
|
+
return self.request.post("v1/ip/check", EmbargoResult, data=input.model_dump())
|
107
107
|
|
108
108
|
def iso_check(self, iso_code: str) -> PangeaResponse[EmbargoResult]:
|
109
109
|
"""
|
@@ -130,4 +130,4 @@ class Embargo(ServiceBase):
|
|
130
130
|
response = embargo.iso_check("CU")
|
131
131
|
"""
|
132
132
|
input = ISOCheckRequest(iso_code=iso_code)
|
133
|
-
return self.request.post("v1/iso/check", result_class=EmbargoResult, data=input.
|
133
|
+
return self.request.post("v1/iso/check", result_class=EmbargoResult, data=input.model_dump())
|
pangea/services/file_scan.py
CHANGED
@@ -142,7 +142,7 @@ class FileScan(ServiceBase):
|
|
142
142
|
transfer_method=transfer_method,
|
143
143
|
source_url=source_url,
|
144
144
|
)
|
145
|
-
data = input.
|
145
|
+
data = input.model_dump(exclude_none=True)
|
146
146
|
return self.request.post("v1/scan", FileScanResult, data=data, files=files, poll_result=sync_call)
|
147
147
|
|
148
148
|
def request_upload_url(
|
@@ -164,7 +164,7 @@ class FileScan(ServiceBase):
|
|
164
164
|
input.sha256 = params.sha256_hex
|
165
165
|
input.size = params.size
|
166
166
|
|
167
|
-
data = input.
|
167
|
+
data = input.model_dump(exclude_none=True)
|
168
168
|
return self.request.request_presigned_url("v1/scan", FileScanResult, data=data)
|
169
169
|
|
170
170
|
|