pangea-sdk 6.1.1__py3-none-any.whl → 6.2.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 +9 -1
- pangea/asyncio/__init__.py +1 -0
- pangea/asyncio/file_uploader.py +4 -2
- pangea/asyncio/request.py +52 -17
- pangea/asyncio/services/__init__.py +2 -0
- pangea/asyncio/services/ai_guard.py +9 -12
- pangea/asyncio/services/audit.py +6 -1
- pangea/asyncio/services/authn.py +15 -4
- pangea/asyncio/services/base.py +4 -0
- pangea/asyncio/services/file_scan.py +7 -1
- pangea/asyncio/services/intel.py +26 -28
- pangea/asyncio/services/redact.py +4 -0
- pangea/asyncio/services/sanitize.py +5 -1
- pangea/asyncio/services/share.py +5 -1
- pangea/asyncio/services/vault.py +4 -0
- pangea/audit_logger.py +3 -1
- pangea/deep_verify.py +13 -13
- pangea/deprecated.py +1 -1
- pangea/dump_audit.py +2 -3
- pangea/exceptions.py +8 -5
- pangea/file_uploader.py +4 -0
- pangea/request.py +63 -47
- pangea/response.py +21 -18
- pangea/services/__init__.py +2 -0
- pangea/services/ai_guard.py +35 -24
- pangea/services/audit/audit.py +10 -7
- pangea/services/audit/models.py +71 -34
- pangea/services/audit/signing.py +1 -1
- pangea/services/audit/util.py +10 -10
- pangea/services/authn/authn.py +15 -4
- pangea/services/authn/models.py +10 -56
- pangea/services/authz.py +4 -0
- pangea/services/base.py +7 -4
- pangea/services/embargo.py +6 -0
- pangea/services/file_scan.py +7 -1
- pangea/services/intel.py +36 -19
- pangea/services/redact.py +4 -0
- pangea/services/sanitize.py +5 -1
- pangea/services/share/share.py +13 -7
- pangea/services/vault/models/asymmetric.py +4 -0
- pangea/services/vault/models/common.py +4 -0
- pangea/services/vault/models/symmetric.py +4 -0
- pangea/services/vault/vault.py +2 -4
- pangea/tools.py +13 -9
- pangea/utils.py +3 -5
- pangea/verify_audit.py +23 -27
- {pangea_sdk-6.1.1.dist-info → pangea_sdk-6.2.0.dist-info}/METADATA +36 -17
- pangea_sdk-6.2.0.dist-info/RECORD +60 -0
- pangea_sdk-6.1.1.dist-info/RECORD +0 -60
- {pangea_sdk-6.1.1.dist-info → pangea_sdk-6.2.0.dist-info}/WHEEL +0 -0
pangea/services/file_scan.py
CHANGED
@@ -1,5 +1,11 @@
|
|
1
1
|
# Copyright 2022 Pangea Cyber Corporation
|
2
2
|
# Author: Pangea Cyber Corporation
|
3
|
+
|
4
|
+
# TODO: Use `list` instead of `List`.
|
5
|
+
# ruff: noqa: UP006, UP035
|
6
|
+
|
7
|
+
from __future__ import annotations
|
8
|
+
|
3
9
|
import io
|
4
10
|
import logging
|
5
11
|
from typing import Dict, List, Optional, Tuple
|
@@ -133,7 +139,7 @@ class FileScan(ServiceBase):
|
|
133
139
|
files: Optional[List[Tuple]] = None
|
134
140
|
if file or file_path:
|
135
141
|
if file_path:
|
136
|
-
file = open(file_path, "rb")
|
142
|
+
file = open(file_path, "rb") # noqa: SIM115
|
137
143
|
if transfer_method == TransferMethod.POST_URL:
|
138
144
|
params = get_file_upload_params(file) # type: ignore[arg-type]
|
139
145
|
crc = params.crc_hex
|
pangea/services/intel.py
CHANGED
@@ -1,10 +1,14 @@
|
|
1
1
|
# Copyright 2022 Pangea Cyber Corporation
|
2
2
|
# Author: Pangea Cyber Corporation
|
3
|
+
|
4
|
+
# TODO: Use `list` instead of `List`.
|
5
|
+
# ruff: noqa: UP006, UP035
|
6
|
+
|
3
7
|
from __future__ import annotations
|
4
8
|
|
5
9
|
import enum
|
6
10
|
import hashlib
|
7
|
-
from typing import Dict, List, Optional
|
11
|
+
from typing import Dict, List, Literal, Optional
|
8
12
|
|
9
13
|
from pangea.exceptions import PangeaException
|
10
14
|
from pangea.response import APIRequestModel, PangeaResponse, PangeaResponseResult
|
@@ -61,8 +65,20 @@ class FileReputationBulkRequest(APIRequestModel):
|
|
61
65
|
hash_type (str): Type of hash, can be "sha256", "sha" or "md5"
|
62
66
|
"""
|
63
67
|
|
64
|
-
hashes:
|
65
|
-
|
68
|
+
hashes: list[str]
|
69
|
+
"""The hash of the file to be looked up"""
|
70
|
+
|
71
|
+
hash_type: Literal["sha256", "sha", "md5"]
|
72
|
+
"""One of "sha256", "sha", "md5"."""
|
73
|
+
|
74
|
+
verbose: Optional[bool] = None
|
75
|
+
"""Echo the API parameters in the response"""
|
76
|
+
|
77
|
+
raw: Optional[bool] = None
|
78
|
+
"""Include raw data from this provider"""
|
79
|
+
|
80
|
+
provider: Optional[Literal["reversinglabs", "crowdstrike"]] = None
|
81
|
+
"""Use reputation data from this provider"""
|
66
82
|
|
67
83
|
|
68
84
|
class FileReputationData(IntelReputationData):
|
@@ -356,7 +372,8 @@ class DomainWhoIsRequest(DomainCommonRequest):
|
|
356
372
|
Domain whois request data
|
357
373
|
"""
|
358
374
|
|
359
|
-
|
375
|
+
domain: str
|
376
|
+
"""The domain to query."""
|
360
377
|
|
361
378
|
|
362
379
|
class DomainWhoIsData(PangeaResponseResult):
|
@@ -530,11 +547,11 @@ class FileIntel(ServiceBase):
|
|
530
547
|
|
531
548
|
def hash_reputation_bulk(
|
532
549
|
self,
|
533
|
-
hashes:
|
534
|
-
hash_type:
|
535
|
-
provider:
|
536
|
-
verbose:
|
537
|
-
raw:
|
550
|
+
hashes: list[str],
|
551
|
+
hash_type: Literal["sha256", "sha", "md5"],
|
552
|
+
provider: Literal["reversinglabs", "crowdstrike"] | None = None,
|
553
|
+
verbose: bool | None = None,
|
554
|
+
raw: bool | None = None,
|
538
555
|
) -> PangeaResponse[FileReputationBulkResult]:
|
539
556
|
"""
|
540
557
|
Reputation check V2
|
@@ -562,7 +579,7 @@ class FileIntel(ServiceBase):
|
|
562
579
|
provider="reversinglabs",
|
563
580
|
)
|
564
581
|
"""
|
565
|
-
input = FileReputationBulkRequest(
|
582
|
+
input = FileReputationBulkRequest(
|
566
583
|
hashes=hashes, hash_type=hash_type, verbose=verbose, raw=raw, provider=provider
|
567
584
|
)
|
568
585
|
return self.request.post("v2/reputation", FileReputationBulkResult, data=input.model_dump(exclude_none=True))
|
@@ -610,10 +627,10 @@ class FileIntel(ServiceBase):
|
|
610
627
|
|
611
628
|
def filepath_reputation_bulk(
|
612
629
|
self,
|
613
|
-
filepaths:
|
614
|
-
provider:
|
615
|
-
verbose:
|
616
|
-
raw:
|
630
|
+
filepaths: list[str],
|
631
|
+
provider: Literal["reversinglabs", "crowdstrike"] | None = None,
|
632
|
+
verbose: bool | None = None,
|
633
|
+
raw: bool | None = None,
|
617
634
|
) -> PangeaResponse[FileReputationBulkResult]:
|
618
635
|
"""
|
619
636
|
Reputation, from filepath V2
|
@@ -624,10 +641,10 @@ class FileIntel(ServiceBase):
|
|
624
641
|
OperationId: file_intel_post_v2_reputation
|
625
642
|
|
626
643
|
Args:
|
627
|
-
filepaths
|
628
|
-
provider
|
629
|
-
verbose
|
630
|
-
raw
|
644
|
+
filepaths: The path list to the files to be looked up
|
645
|
+
provider: Use reputation data from these providers: "reversinglabs" or "crowdstrike"
|
646
|
+
verbose: Echo the API parameters in the response
|
647
|
+
raw: Include raw data from this provider
|
631
648
|
|
632
649
|
Raises:
|
633
650
|
PangeaAPIException: If an API Error happens
|
@@ -777,7 +794,7 @@ class DomainIntel(ServiceBase):
|
|
777
794
|
provider="whoisxml",
|
778
795
|
)
|
779
796
|
"""
|
780
|
-
input = DomainWhoIsRequest(domain=domain, verbose=verbose, provider=provider, raw=raw)
|
797
|
+
input = DomainWhoIsRequest(domain=domain, verbose=verbose, provider=provider, raw=raw)
|
781
798
|
return self.request.post("v1/whois", DomainWhoIsResult, data=input.model_dump(exclude_none=True))
|
782
799
|
|
783
800
|
|
pangea/services/redact.py
CHANGED
pangea/services/sanitize.py
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
# Copyright 2022 Pangea Cyber Corporation
|
2
2
|
# Author: Pangea Cyber Corporation
|
3
|
+
|
4
|
+
# TODO: Use `list` instead of `List`.
|
5
|
+
# ruff: noqa: UP006, UP035
|
6
|
+
|
3
7
|
from __future__ import annotations
|
4
8
|
|
5
9
|
import io
|
@@ -267,7 +271,7 @@ class Sanitize(ServiceBase):
|
|
267
271
|
files: Optional[List[Tuple]] = None
|
268
272
|
if file or file_path:
|
269
273
|
if file_path:
|
270
|
-
file = open(file_path, "rb")
|
274
|
+
file = open(file_path, "rb") # noqa: SIM115
|
271
275
|
if (
|
272
276
|
transfer_method == TransferMethod.POST_URL
|
273
277
|
and file
|
pangea/services/share/share.py
CHANGED
@@ -1,10 +1,16 @@
|
|
1
1
|
# Copyright 2022 Pangea Cyber Corporation
|
2
2
|
# Author: Pangea Cyber Corporation
|
3
|
+
|
4
|
+
# TODO: Use `list` instead of `List`.
|
5
|
+
# ruff: noqa: UP006, UP035
|
6
|
+
|
3
7
|
from __future__ import annotations
|
4
8
|
|
5
9
|
import enum
|
6
10
|
import io
|
7
|
-
from typing import Dict, List,
|
11
|
+
from typing import Dict, List, Optional, Tuple, Union
|
12
|
+
|
13
|
+
from pydantic import RootModel
|
8
14
|
|
9
15
|
from pangea.config import PangeaConfig
|
10
16
|
from pangea.response import APIRequestModel, PangeaResponse, PangeaResponseResult, TransferMethod
|
@@ -12,8 +18,8 @@ from pangea.services.base import ServiceBase
|
|
12
18
|
from pangea.services.share.file_format import FileFormat
|
13
19
|
from pangea.utils import get_file_size, get_file_upload_params
|
14
20
|
|
15
|
-
Metadata =
|
16
|
-
Tags =
|
21
|
+
Metadata = RootModel[dict[str, str]]
|
22
|
+
Tags = RootModel[list[str]]
|
17
23
|
|
18
24
|
|
19
25
|
class ItemOrder(str, enum.Enum):
|
@@ -916,10 +922,10 @@ class Share(ServiceBase):
|
|
916
922
|
|
917
923
|
def get_archive(
|
918
924
|
self,
|
919
|
-
ids:
|
920
|
-
format:
|
921
|
-
transfer_method:
|
922
|
-
bucket_id:
|
925
|
+
ids: list[str],
|
926
|
+
format: ArchiveFormat | None = None,
|
927
|
+
transfer_method: TransferMethod | None = None,
|
928
|
+
bucket_id: str | None = None,
|
923
929
|
) -> PangeaResponse[GetArchiveResult]:
|
924
930
|
"""
|
925
931
|
Get archive
|
pangea/services/vault/vault.py
CHANGED
@@ -2,10 +2,9 @@
|
|
2
2
|
# Author: Pangea Cyber Corporation
|
3
3
|
from __future__ import annotations
|
4
4
|
|
5
|
-
from typing import TYPE_CHECKING,
|
5
|
+
from typing import TYPE_CHECKING, Annotated, Any, Literal, Optional, Union, cast, overload
|
6
6
|
|
7
7
|
from pydantic import Field, TypeAdapter
|
8
|
-
from typing_extensions import Annotated
|
9
8
|
|
10
9
|
from pangea.response import PangeaResponse, PangeaResponseResult
|
11
10
|
from pangea.services.base import ServiceBase
|
@@ -34,7 +33,6 @@ from pangea.services.vault.models.common import (
|
|
34
33
|
EncryptTransformRequest,
|
35
34
|
EncryptTransformResult,
|
36
35
|
ExportEncryptionAlgorithm,
|
37
|
-
ExportEncryptionType,
|
38
36
|
ExportRequest,
|
39
37
|
ExportResult,
|
40
38
|
Folder,
|
@@ -98,7 +96,7 @@ vault_item_adapter: TypeAdapter[VaultItem] = TypeAdapter(VaultItem)
|
|
98
96
|
|
99
97
|
|
100
98
|
class GetBulkResponse(PangeaResponseResult):
|
101
|
-
items:
|
99
|
+
items: list[VaultItem]
|
102
100
|
|
103
101
|
|
104
102
|
class Vault(ServiceBase):
|
pangea/tools.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# Copyright 2022 Pangea Cyber Corporation
|
2
2
|
# Author: Pangea Cyber Corporation
|
3
|
+
from __future__ import annotations
|
3
4
|
|
4
5
|
import enum
|
5
6
|
import io
|
@@ -7,9 +8,10 @@ import json
|
|
7
8
|
import logging
|
8
9
|
import os
|
9
10
|
import sys
|
11
|
+
from collections.abc import Iterator
|
10
12
|
from datetime import datetime, timezone
|
11
13
|
from logging.handlers import TimedRotatingFileHandler
|
12
|
-
from typing import
|
14
|
+
from typing import Optional
|
13
15
|
|
14
16
|
from pangea.config import PangeaConfig
|
15
17
|
from pangea.exceptions import PangeaException
|
@@ -17,6 +19,8 @@ from pangea.services import Audit
|
|
17
19
|
|
18
20
|
|
19
21
|
class TestEnvironment(str, enum.Enum):
|
22
|
+
__test__ = False
|
23
|
+
|
20
24
|
DEVELOP = "DEV"
|
21
25
|
LIVE = "LVE"
|
22
26
|
STAGING = "STG"
|
@@ -28,15 +32,15 @@ class TestEnvironment(str, enum.Enum):
|
|
28
32
|
return str(self.value)
|
29
33
|
|
30
34
|
|
31
|
-
class Root(
|
35
|
+
class Root(dict):
|
32
36
|
size: int
|
33
37
|
tree_name: str
|
34
38
|
|
35
39
|
|
36
|
-
class Event(
|
40
|
+
class Event(dict):
|
37
41
|
membership_proof: str
|
38
42
|
leaf_index: Optional[int]
|
39
|
-
event:
|
43
|
+
event: dict
|
40
44
|
hash: str
|
41
45
|
tree_size: Optional[int]
|
42
46
|
|
@@ -68,7 +72,7 @@ def exit_with_error(message: str):
|
|
68
72
|
sys.exit(1)
|
69
73
|
|
70
74
|
|
71
|
-
def file_events(root_hashes:
|
75
|
+
def file_events(root_hashes: dict[int, str], f: io.TextIOWrapper) -> Iterator[Event]:
|
72
76
|
"""
|
73
77
|
Reads a file containing Events in JSON format with the following fields:
|
74
78
|
- membership_proof: str
|
@@ -111,8 +115,8 @@ def make_aware_datetime(d: datetime) -> datetime:
|
|
111
115
|
return d
|
112
116
|
|
113
117
|
|
114
|
-
def filter_deep_none(data:
|
115
|
-
return {k: v if not isinstance(v,
|
118
|
+
def filter_deep_none(data: dict) -> dict:
|
119
|
+
return {k: v if not isinstance(v, dict) else filter_deep_none(v) for k, v in data.items() if v is not None}
|
116
120
|
|
117
121
|
|
118
122
|
def _load_env_var(env_var_name: str) -> str:
|
@@ -183,7 +187,7 @@ class SequenceFollower:
|
|
183
187
|
self.numbers.remove(min_val)
|
184
188
|
min_val += 1
|
185
189
|
|
186
|
-
def holes(self) ->
|
190
|
+
def holes(self) -> list[int]:
|
187
191
|
if not self.numbers:
|
188
192
|
return []
|
189
193
|
|
@@ -192,7 +196,7 @@ class SequenceFollower:
|
|
192
196
|
return [val for val in range(min_val, max_val) if val not in self.numbers]
|
193
197
|
|
194
198
|
|
195
|
-
loggers:
|
199
|
+
loggers: dict[str, bool] = {}
|
196
200
|
|
197
201
|
|
198
202
|
def logger_set_pangea_config(logger_name: str, level=logging.DEBUG):
|
pangea/utils.py
CHANGED
@@ -60,7 +60,7 @@ def canonicalize(data: dict) -> str:
|
|
60
60
|
return json.dumps(
|
61
61
|
data, ensure_ascii=False, allow_nan=False, separators=(",", ":"), sort_keys=True, default=default_encoder
|
62
62
|
)
|
63
|
-
elif isinstance(data, datetime.datetime
|
63
|
+
elif isinstance(data, (datetime.datetime, datetime.date)):
|
64
64
|
return format_datetime(data)
|
65
65
|
else:
|
66
66
|
return str(data)
|
@@ -151,10 +151,8 @@ def get_crc32c(data: str) -> str:
|
|
151
151
|
|
152
152
|
|
153
153
|
def hash_256_filepath(filepath: str) -> str:
|
154
|
-
|
155
|
-
|
156
|
-
data.close()
|
157
|
-
return hash
|
154
|
+
with open(filepath, "rb") as data:
|
155
|
+
return sha256(data.read()).hexdigest()
|
158
156
|
|
159
157
|
|
160
158
|
def get_prefix(hash: str, len: int = 5):
|
pangea/verify_audit.py
CHANGED
@@ -9,14 +9,16 @@ You can provide a single event (obtained from the PUC) or the result from a sear
|
|
9
9
|
In the latter case, all the events are verified.
|
10
10
|
"""
|
11
11
|
|
12
|
+
from __future__ import annotations
|
13
|
+
|
12
14
|
import argparse
|
13
15
|
import json
|
14
16
|
import logging
|
15
17
|
import os
|
16
18
|
import sys
|
17
|
-
from collections.abc import Set
|
19
|
+
from collections.abc import Iterable, Set
|
18
20
|
from enum import Enum
|
19
|
-
from typing import
|
21
|
+
from typing import Optional, Union
|
20
22
|
|
21
23
|
from pangea.config import PangeaConfig
|
22
24
|
from pangea.exceptions import TreeNotFoundException
|
@@ -36,8 +38,8 @@ from pangea.services.audit.util import (
|
|
36
38
|
)
|
37
39
|
|
38
40
|
logger = logging.getLogger("audit")
|
39
|
-
arweave_roots:
|
40
|
-
pangea_roots:
|
41
|
+
arweave_roots: dict[int, PublishedRoot] = {} # roots fetched from Arweave
|
42
|
+
pangea_roots: dict[int, Root] = {} # roots fetched from Pangea
|
41
43
|
audit: Optional[Audit] = None
|
42
44
|
|
43
45
|
|
@@ -72,10 +74,7 @@ class VerifierLogFormatter(logging.Formatter):
|
|
72
74
|
self.in_section = True
|
73
75
|
return f"{' ' * self.indent}⎾ {record.msg}"
|
74
76
|
else:
|
75
|
-
if self.in_section
|
76
|
-
pre = f"{' ' * (self.indent+4)}⌲ "
|
77
|
-
else:
|
78
|
-
pre = ""
|
77
|
+
pre = f"{' ' * (self.indent + 4)}⌲ " if self.in_section else ""
|
79
78
|
return f"{pre}{record.msg}"
|
80
79
|
|
81
80
|
|
@@ -100,8 +99,8 @@ formatter = VerifierLogFormatter()
|
|
100
99
|
InvalidTokenError = ValueError("Invalid Pangea Token provided")
|
101
100
|
|
102
101
|
|
103
|
-
def get_pangea_roots(tree_name: str, tree_sizes: Iterable[int]) ->
|
104
|
-
ans:
|
102
|
+
def get_pangea_roots(tree_name: str, tree_sizes: Iterable[int]) -> dict[int, Root]:
|
103
|
+
ans: dict[int, Root] = {}
|
105
104
|
if audit is None:
|
106
105
|
return ans
|
107
106
|
|
@@ -125,7 +124,7 @@ def get_pangea_roots(tree_name: str, tree_sizes: Iterable[int]) -> Dict[int, Roo
|
|
125
124
|
return ans
|
126
125
|
|
127
126
|
|
128
|
-
def _verify_hash(data:
|
127
|
+
def _verify_hash(data: dict, data_hash: str) -> Status:
|
129
128
|
log_section("Checking data hash")
|
130
129
|
status = Status.SKIPPED
|
131
130
|
try:
|
@@ -213,7 +212,7 @@ def _fetch_roots(tree_name: str, tree_size: int, leaf_index: Optional[int]) -> S
|
|
213
212
|
logger.debug(f"Fetching root(s) {comma_sep(pending_roots)} from Arweave")
|
214
213
|
arweave_roots |= {int(k): v for k, v in get_arweave_published_roots(tree_name, pending_roots).items()}
|
215
214
|
update_pending_roots()
|
216
|
-
except:
|
215
|
+
except: # noqa: E722
|
217
216
|
pass
|
218
217
|
|
219
218
|
if pending_roots:
|
@@ -227,7 +226,7 @@ def _fetch_roots(tree_name: str, tree_size: int, leaf_index: Optional[int]) -> S
|
|
227
226
|
pangea_roots |= {int(k): v for k, v in get_pangea_roots(tree_name, pending_roots).items()}
|
228
227
|
update_pending_roots()
|
229
228
|
status = Status.SUCCEEDED_PANGEA
|
230
|
-
except:
|
229
|
+
except: # noqa: E722
|
231
230
|
pass
|
232
231
|
|
233
232
|
if pending_roots:
|
@@ -244,7 +243,7 @@ def _fetch_roots(tree_name: str, tree_size: int, leaf_index: Optional[int]) -> S
|
|
244
243
|
|
245
244
|
|
246
245
|
def _verify_membership_proof(tree_size: int, node_hash: str, proof: Optional[str]) -> Status:
|
247
|
-
pub_roots:
|
246
|
+
pub_roots: dict[int, Union[Root, PublishedRoot]] = arweave_roots | pangea_roots
|
248
247
|
|
249
248
|
log_section("Checking membership proof")
|
250
249
|
|
@@ -277,7 +276,7 @@ def _verify_membership_proof(tree_size: int, node_hash: str, proof: Optional[str
|
|
277
276
|
return status
|
278
277
|
|
279
278
|
|
280
|
-
def _consistency_proof_ok(pub_roots:
|
279
|
+
def _consistency_proof_ok(pub_roots: dict[int, Union[Root, PublishedRoot]], leaf_index: int) -> bool:
|
281
280
|
"""returns true if a consistency proof is correct"""
|
282
281
|
|
283
282
|
curr_root = pub_roots[leaf_index + 1]
|
@@ -296,7 +295,7 @@ def _consistency_proof_ok(pub_roots: Dict[int, Union[Root, PublishedRoot]], leaf
|
|
296
295
|
# Due to an (already fixed) bug, some proofs from Arweave may be wrong.
|
297
296
|
# Try the proof from Pangea instead. If the root hash in both Arweave and Pangea is the same,
|
298
297
|
# it doesn't matter where the proof came from.
|
299
|
-
def _fix_consistency_proof(pub_roots:
|
298
|
+
def _fix_consistency_proof(pub_roots: dict[int, Union[Root, PublishedRoot]], tree_name: str, leaf_index: int):
|
300
299
|
logger.debug("Consistency proof from Arweave failed to verify")
|
301
300
|
size = leaf_index + 1
|
302
301
|
logger.debug(f"Fetching root from Pangea for size {size}")
|
@@ -305,13 +304,13 @@ def _fix_consistency_proof(pub_roots: Dict[int, Union[Root, PublishedRoot]], tre
|
|
305
304
|
raise ValueError("Error fetching root from Pangea")
|
306
305
|
pangea_roots[size] = new_roots[size]
|
307
306
|
pub_roots[size] = pangea_roots[size]
|
308
|
-
logger.debug(
|
307
|
+
logger.debug("Comparing Arweave root hash with Pangea root hash")
|
309
308
|
if pangea_roots[size].root_hash != arweave_roots[size].root_hash:
|
310
309
|
raise ValueError("Hash does not match")
|
311
310
|
|
312
311
|
|
313
312
|
def _verify_consistency_proof(tree_name: str, leaf_index: Optional[int]) -> Status:
|
314
|
-
pub_roots:
|
313
|
+
pub_roots: dict[int, Union[Root, PublishedRoot]] = arweave_roots | pangea_roots
|
315
314
|
|
316
315
|
log_section("Checking consistency proof")
|
317
316
|
|
@@ -336,10 +335,7 @@ def _verify_consistency_proof(tree_name: str, leaf_index: Optional[int]) -> Stat
|
|
336
335
|
_fix_consistency_proof(pub_roots, tree_name, leaf_index)
|
337
336
|
|
338
337
|
# check again
|
339
|
-
if _consistency_proof_ok(pub_roots, leaf_index)
|
340
|
-
status = Status.SUCCEEDED
|
341
|
-
else:
|
342
|
-
status = Status.FAILED
|
338
|
+
status = Status.SUCCEEDED if _consistency_proof_ok(pub_roots, leaf_index) else Status.FAILED
|
343
339
|
|
344
340
|
else:
|
345
341
|
logger.debug(
|
@@ -356,11 +352,11 @@ def _verify_consistency_proof(tree_name: str, leaf_index: Optional[int]) -> Stat
|
|
356
352
|
return status
|
357
353
|
|
358
354
|
|
359
|
-
def create_signed_event(event:
|
355
|
+
def create_signed_event(event: dict) -> dict:
|
360
356
|
return {k: v for k, v in event.items() if v is not None}
|
361
357
|
|
362
358
|
|
363
|
-
def _verify_signature(data:
|
359
|
+
def _verify_signature(data: dict) -> Status:
|
364
360
|
log_section("Checking signature")
|
365
361
|
if "signature" not in data:
|
366
362
|
logger.debug("Signature is not present")
|
@@ -383,13 +379,13 @@ def _verify_signature(data: Dict) -> Status:
|
|
383
379
|
return status
|
384
380
|
|
385
381
|
|
386
|
-
def verify_multiple(root:
|
382
|
+
def verify_multiple(root: dict, unpublished_root: dict, events: list[dict]) -> Status:
|
387
383
|
"""
|
388
384
|
Verify a list of events.
|
389
385
|
Returns a status.
|
390
386
|
"""
|
391
387
|
|
392
|
-
statuses:
|
388
|
+
statuses: list[Status] = []
|
393
389
|
for counter, event in enumerate(events):
|
394
390
|
event.update({"root": root, "unpublished_root": unpublished_root})
|
395
391
|
event_status = verify_single(event, counter + 1)
|
@@ -403,7 +399,7 @@ def verify_multiple(root: Dict, unpublished_root: Dict, events: List[Dict]) -> S
|
|
403
399
|
return Status.SUCCEEDED
|
404
400
|
|
405
401
|
|
406
|
-
def verify_single(data:
|
402
|
+
def verify_single(data: dict, counter: Optional[int] = None) -> Status:
|
407
403
|
"""
|
408
404
|
Verify a single event.
|
409
405
|
Returns a status.
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: pangea-sdk
|
3
|
-
Version: 6.
|
3
|
+
Version: 6.2.0
|
4
4
|
Summary: Pangea API SDK
|
5
5
|
License: MIT
|
6
6
|
Keywords: Pangea,SDK,Audit
|
@@ -9,16 +9,16 @@ Author-email: glenn.gallien@pangea.cloud
|
|
9
9
|
Requires-Python: >=3.9.2,<4.0.0
|
10
10
|
Classifier: Topic :: Software Development
|
11
11
|
Classifier: Topic :: Software Development :: Libraries
|
12
|
-
Requires-Dist: aiohttp (>=3.
|
13
|
-
Requires-Dist: cryptography (>=
|
12
|
+
Requires-Dist: aiohttp (>=3.12.13,<4.0.0)
|
13
|
+
Requires-Dist: cryptography (>=45.0.4,<46.0.0)
|
14
14
|
Requires-Dist: deprecated (>=1.2.18,<2.0.0)
|
15
15
|
Requires-Dist: google-crc32c (>=1.7.1,<2.0.0)
|
16
|
-
Requires-Dist: pydantic (>=2.11.
|
16
|
+
Requires-Dist: pydantic (>=2.11.7,<3.0.0)
|
17
17
|
Requires-Dist: python-dateutil (>=2.9.0.post0,<3.0.0)
|
18
|
-
Requires-Dist: requests (>=2.32.
|
18
|
+
Requires-Dist: requests (>=2.32.4,<3.0.0)
|
19
19
|
Requires-Dist: requests-toolbelt (>=1.0.0,<2.0.0)
|
20
|
-
Requires-Dist: typing-extensions (>=4.
|
21
|
-
Requires-Dist: yarl (>=1.20.
|
20
|
+
Requires-Dist: typing-extensions (>=4.14.0,<5.0.0)
|
21
|
+
Requires-Dist: yarl (>=1.20.1,<2.0.0)
|
22
22
|
Description-Content-Type: text/markdown
|
23
23
|
|
24
24
|
<a href="https://pangea.cloud?utm_source=github&utm_medium=python-sdk" target="_blank" rel="noopener noreferrer">
|
@@ -63,13 +63,13 @@ the same compatibility guarantees as stable releases.
|
|
63
63
|
Via pip:
|
64
64
|
|
65
65
|
```bash
|
66
|
-
$ pip3 install pangea-sdk==
|
66
|
+
$ pip3 install pangea-sdk==6.2.0b2
|
67
67
|
```
|
68
68
|
|
69
69
|
Via poetry:
|
70
70
|
|
71
71
|
```bash
|
72
|
-
$ poetry add pangea-sdk==
|
72
|
+
$ poetry add pangea-sdk==6.2.0b2
|
73
73
|
```
|
74
74
|
|
75
75
|
## Usage
|
@@ -102,6 +102,26 @@ audit = Audit(token, config)
|
|
102
102
|
response = audit.log(message="Hello, World!")
|
103
103
|
```
|
104
104
|
|
105
|
+
## Configuration
|
106
|
+
|
107
|
+
The SDK supports the following configuration options via `PangeaConfig`:
|
108
|
+
|
109
|
+
- `base_url_template` — Template for constructing the base URL for API requests.
|
110
|
+
The placeholder `{SERVICE_NAME}` will be replaced with the service name slug.
|
111
|
+
This is a more powerful version of `domain` that allows for setting more than
|
112
|
+
just the host of the API server. Defaults to
|
113
|
+
`https://{SERVICE_NAME}.aws.us.pangea.cloud`.
|
114
|
+
- `domain` — Base domain for API requests. This is a weaker version of
|
115
|
+
`base_url_template` that only allows for setting the host of the API server.
|
116
|
+
Use `base_url_template` for more control over the URL, such as setting
|
117
|
+
service-specific paths. Defaults to `aws.us.pangea.cloud`.
|
118
|
+
- `request_retries` — Number of retries on the initial request.
|
119
|
+
- `request_backoff` — Backoff strategy passed to 'requests'.
|
120
|
+
- `request_timeout` — Timeout used on initial request attempts.
|
121
|
+
- `poll_result_timeout` — Timeout used to poll results after 202 (in secs).
|
122
|
+
- `queued_retry_enabled` — Enable queued request retry support.
|
123
|
+
- `custom_user_agent` — Custom user agent to be used in the request headers.
|
124
|
+
|
105
125
|
## asyncio support
|
106
126
|
|
107
127
|
asyncio support is available through the `pangea.asyncio.services` module. The
|
@@ -156,6 +176,7 @@ options:
|
|
156
176
|
```
|
157
177
|
|
158
178
|
It accepts multiple file formats:
|
179
|
+
|
159
180
|
- a Verification Artifact from the Pangea User Console
|
160
181
|
- a search response from the REST API:
|
161
182
|
|
@@ -163,7 +184,6 @@ It accepts multiple file formats:
|
|
163
184
|
$ curl -H "Authorization: Bearer ${PANGEA_TOKEN}" -X POST -H 'Content-Type: application/json' --data '{"verbose": true}' https://audit.aws.us.pangea.cloud/v1/search
|
164
185
|
```
|
165
186
|
|
166
|
-
|
167
187
|
### Bulk Download Audit Data
|
168
188
|
|
169
189
|
Download all audit logs for a given time range. Start and end date should be provided,
|
@@ -213,15 +233,14 @@ options:
|
|
213
233
|
```
|
214
234
|
|
215
235
|
It accepts multiple file formats:
|
236
|
+
|
216
237
|
- a Verification Artifact from the Pangea User Console
|
217
238
|
- a file generated by the `dump_audit` command
|
218
239
|
- a search response from the REST API (see `verify_audit`)
|
219
240
|
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
[Pangea Console]: https://console.pangea.cloud/
|
226
|
-
[Secure Audit Log]: https://pangea.cloud/docs/audit
|
241
|
+
[Documentation]: https://pangea.cloud/docs/sdk/python/
|
242
|
+
[GA Examples]: https://github.com/pangeacyber/pangea-python/tree/main/examples
|
243
|
+
[Beta Examples]: https://github.com/pangeacyber/pangea-python/tree/beta/examples
|
244
|
+
[Pangea Console]: https://console.pangea.cloud/
|
245
|
+
[Secure Audit Log]: https://pangea.cloud/docs/audit
|
227
246
|
|