pangea-sdk 3.8.0__py3-none-any.whl → 5.3.0__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- pangea/__init__.py +2 -1
- pangea/asyncio/__init__.py +1 -0
- pangea/asyncio/file_uploader.py +39 -0
- pangea/asyncio/request.py +46 -23
- pangea/asyncio/services/__init__.py +2 -0
- pangea/asyncio/services/audit.py +46 -20
- pangea/asyncio/services/authn.py +123 -61
- pangea/asyncio/services/authz.py +57 -31
- pangea/asyncio/services/base.py +21 -2
- pangea/asyncio/services/embargo.py +2 -2
- pangea/asyncio/services/file_scan.py +24 -9
- pangea/asyncio/services/intel.py +104 -30
- pangea/asyncio/services/redact.py +52 -3
- pangea/asyncio/services/sanitize.py +217 -0
- pangea/asyncio/services/share.py +733 -0
- pangea/asyncio/services/vault.py +1709 -766
- pangea/crypto/rsa.py +135 -0
- pangea/deep_verify.py +7 -1
- pangea/dump_audit.py +9 -8
- pangea/file_uploader.py +35 -0
- pangea/request.py +70 -49
- pangea/response.py +36 -17
- pangea/services/__init__.py +2 -0
- pangea/services/audit/audit.py +57 -29
- pangea/services/audit/models.py +12 -3
- pangea/services/audit/signing.py +6 -5
- pangea/services/audit/util.py +3 -3
- pangea/services/authn/authn.py +120 -66
- pangea/services/authn/models.py +167 -11
- pangea/services/authz.py +53 -30
- pangea/services/base.py +16 -2
- pangea/services/embargo.py +2 -2
- pangea/services/file_scan.py +32 -15
- pangea/services/intel.py +155 -30
- pangea/services/redact.py +132 -3
- pangea/services/sanitize.py +388 -0
- pangea/services/share/file_format.py +170 -0
- pangea/services/share/share.py +1440 -0
- pangea/services/vault/models/asymmetric.py +120 -18
- pangea/services/vault/models/common.py +439 -141
- pangea/services/vault/models/keys.py +94 -0
- pangea/services/vault/models/secret.py +27 -3
- pangea/services/vault/models/symmetric.py +68 -22
- pangea/services/vault/vault.py +1690 -766
- pangea/tools.py +6 -7
- pangea/utils.py +94 -33
- pangea/verify_audit.py +270 -83
- {pangea_sdk-3.8.0.dist-info → pangea_sdk-5.3.0.dist-info}/METADATA +21 -29
- pangea_sdk-5.3.0.dist-info/RECORD +56 -0
- {pangea_sdk-3.8.0.dist-info → pangea_sdk-5.3.0.dist-info}/WHEEL +1 -1
- pangea_sdk-3.8.0.dist-info/RECORD +0 -46
pangea/verify_audit.py
CHANGED
@@ -12,9 +12,16 @@ In the latter case, all the events are verified.
|
|
12
12
|
import argparse
|
13
13
|
import json
|
14
14
|
import logging
|
15
|
+
import os
|
15
16
|
import sys
|
16
|
-
from
|
17
|
-
|
17
|
+
from collections.abc import Set
|
18
|
+
from enum import Enum
|
19
|
+
from typing import Dict, Iterable, List, Optional, Union
|
20
|
+
|
21
|
+
from pangea.config import PangeaConfig
|
22
|
+
from pangea.exceptions import TreeNotFoundException
|
23
|
+
from pangea.services import Audit
|
24
|
+
from pangea.services.audit.models import PublishedRoot, Root
|
18
25
|
from pangea.services.audit.signing import Verifier
|
19
26
|
from pangea.services.audit.util import (
|
20
27
|
canonicalize_json,
|
@@ -29,7 +36,16 @@ from pangea.services.audit.util import (
|
|
29
36
|
)
|
30
37
|
|
31
38
|
logger = logging.getLogger("audit")
|
32
|
-
|
39
|
+
arweave_roots: Dict[int, PublishedRoot] = {} # roots fetched from Arweave
|
40
|
+
pangea_roots: Dict[int, Root] = {} # roots fetched from Pangea
|
41
|
+
audit: Optional[Audit] = None
|
42
|
+
|
43
|
+
|
44
|
+
class Status(Enum):
|
45
|
+
SUCCEEDED = "succeeded"
|
46
|
+
SUCCEEDED_PANGEA = "succeeded_pangea" # succeeded with data fetched from Pangea instead of Arweave
|
47
|
+
FAILED = "failed"
|
48
|
+
SKIPPED = "skipped"
|
33
49
|
|
34
50
|
|
35
51
|
class VerifierLogFormatter(logging.Formatter):
|
@@ -40,9 +56,11 @@ class VerifierLogFormatter(logging.Formatter):
|
|
40
56
|
|
41
57
|
def format(self, record):
|
42
58
|
if hasattr(record, "is_result"):
|
43
|
-
if record.
|
59
|
+
if record.status == Status.SUCCEEDED:
|
44
60
|
point = "🟢"
|
45
|
-
elif record.
|
61
|
+
elif record.status == Status.SUCCEEDED_PANGEA:
|
62
|
+
point = "🟡"
|
63
|
+
elif record.status == Status.SKIPPED:
|
46
64
|
point = "⚪️"
|
47
65
|
else:
|
48
66
|
point = "🔴"
|
@@ -61,14 +79,16 @@ class VerifierLogFormatter(logging.Formatter):
|
|
61
79
|
return f"{pre}{record.msg}"
|
62
80
|
|
63
81
|
|
64
|
-
def log_result(msg: str,
|
65
|
-
if
|
82
|
+
def log_result(msg: str, status: Status):
|
83
|
+
if status == Status.SUCCEEDED:
|
66
84
|
msg += " succeeded"
|
67
|
-
elif
|
85
|
+
elif status == Status.SUCCEEDED_PANGEA:
|
86
|
+
msg += " succeeded (with data fetched from Pangea)"
|
87
|
+
elif status == Status.FAILED:
|
68
88
|
msg += " failed"
|
69
89
|
else:
|
70
|
-
msg += "
|
71
|
-
logger.log(logging.INFO, msg, extra={"is_result": True, "
|
90
|
+
msg += " skipped"
|
91
|
+
logger.log(logging.INFO, msg, extra={"is_result": True, "status": status})
|
72
92
|
|
73
93
|
|
74
94
|
def log_section(msg: str):
|
@@ -77,9 +97,37 @@ def log_section(msg: str):
|
|
77
97
|
|
78
98
|
formatter = VerifierLogFormatter()
|
79
99
|
|
100
|
+
InvalidTokenError = ValueError("Invalid Pangea Token provided")
|
101
|
+
|
102
|
+
|
103
|
+
def get_pangea_roots(tree_name: str, tree_sizes: Iterable[int]) -> Dict[int, Root]:
|
104
|
+
ans: Dict[int, Root] = {}
|
105
|
+
if audit is None:
|
106
|
+
return ans
|
107
|
+
|
108
|
+
for size in tree_sizes:
|
109
|
+
try:
|
110
|
+
resp = audit.root(size)
|
111
|
+
|
112
|
+
if resp.status != "Success":
|
113
|
+
raise ValueError(resp.status)
|
114
|
+
elif resp.result is None or resp.result.data is None:
|
115
|
+
raise ValueError("No result")
|
116
|
+
elif resp.result.data.tree_name != tree_name:
|
117
|
+
raise InvalidTokenError
|
118
|
+
|
119
|
+
ans[int(size)] = resp.result.data
|
120
|
+
except TreeNotFoundException as e:
|
121
|
+
ex = InvalidTokenError
|
122
|
+
logger.error(f"Error fetching root from Pangea for size {size}: {str(ex)}")
|
123
|
+
except Exception as e:
|
124
|
+
logger.error(f"Error fetching root from Pangea for size {size}: {str(e)}")
|
125
|
+
return ans
|
126
|
+
|
80
127
|
|
81
|
-
def _verify_hash(data: Dict, data_hash: str) ->
|
128
|
+
def _verify_hash(data: Dict, data_hash: str) -> Status:
|
82
129
|
log_section("Checking data hash")
|
130
|
+
status = Status.SKIPPED
|
83
131
|
try:
|
84
132
|
logger.debug("Canonicalizing data")
|
85
133
|
data_canon = canonicalize_json(data)
|
@@ -89,22 +137,20 @@ def _verify_hash(data: Dict, data_hash: str) -> Optional[bool]:
|
|
89
137
|
logger.debug("Comparing calculated hash with server hash")
|
90
138
|
if computed_hash_dec != data_hash_dec:
|
91
139
|
raise ValueError("Hash does not match")
|
92
|
-
|
140
|
+
status = Status.SUCCEEDED
|
93
141
|
except Exception:
|
94
|
-
|
142
|
+
status = Status.FAILED
|
95
143
|
|
96
|
-
log_result("Data hash verification",
|
144
|
+
log_result("Data hash verification", status)
|
97
145
|
logger.info("")
|
98
|
-
return
|
146
|
+
return status
|
99
147
|
|
100
148
|
|
101
|
-
def _verify_unpublished_membership_proof(root_hash, node_hash: str, proof: Optional[str]) ->
|
102
|
-
global pub_roots
|
103
|
-
|
149
|
+
def _verify_unpublished_membership_proof(root_hash, node_hash: str, proof: Optional[str]) -> Status:
|
104
150
|
log_section("Checking unpublished membership proof")
|
105
151
|
|
106
152
|
if proof is None:
|
107
|
-
|
153
|
+
status = Status.SKIPPED
|
108
154
|
logger.debug("Proof not found")
|
109
155
|
else:
|
110
156
|
try:
|
@@ -116,94 +162,209 @@ def _verify_unpublished_membership_proof(root_hash, node_hash: str, proof: Optio
|
|
116
162
|
proof_dec = decode_membership_proof(proof)
|
117
163
|
|
118
164
|
logger.debug("Comparing the unpublished root hash with the proof hash")
|
119
|
-
|
165
|
+
if verify_membership_proof(node_hash_dec, root_hash_dec, proof_dec):
|
166
|
+
status = Status.SUCCEEDED
|
167
|
+
else:
|
168
|
+
status = Status.FAILED
|
120
169
|
|
121
170
|
except Exception as e:
|
122
|
-
|
171
|
+
status = Status.FAILED
|
123
172
|
logger.debug(str(e))
|
124
173
|
|
125
|
-
log_result("Unpublished membership proof verification",
|
174
|
+
log_result("Unpublished membership proof verification", status)
|
175
|
+
logger.info("")
|
176
|
+
return status
|
177
|
+
|
178
|
+
|
179
|
+
def _fetch_roots(tree_name: str, tree_size: int, leaf_index: Optional[int]) -> Status:
|
180
|
+
global arweave_roots, pangea_roots
|
181
|
+
|
182
|
+
log_section("Fetching published roots")
|
183
|
+
|
184
|
+
needed_roots = {tree_size}
|
185
|
+
if leaf_index:
|
186
|
+
needed_roots |= {leaf_index, leaf_index + 1}
|
187
|
+
|
188
|
+
pending_roots = set()
|
189
|
+
|
190
|
+
def update_pending_roots():
|
191
|
+
nonlocal pending_roots
|
192
|
+
pending_roots = needed_roots - set(arweave_roots.keys()) - set(pangea_roots.keys())
|
193
|
+
|
194
|
+
def comma_sep(s: Set):
|
195
|
+
return ",".join(map(str, s))
|
196
|
+
|
197
|
+
status = Status.SUCCEEDED
|
198
|
+
update_pending_roots()
|
199
|
+
|
200
|
+
# print message for roots already fetched
|
201
|
+
arweave_fetched_roots = needed_roots & set(arweave_roots.keys())
|
202
|
+
if arweave_fetched_roots:
|
203
|
+
logger.debug(f"Roots {comma_sep(arweave_fetched_roots)} already fetched from Arweave")
|
204
|
+
|
205
|
+
pangea_fetched_roots = needed_roots & set(pangea_roots.keys())
|
206
|
+
if pangea_fetched_roots:
|
207
|
+
logger.debug(f"Roots {comma_sep(pangea_fetched_roots)} already fetched from Pangea")
|
208
|
+
status = Status.SUCCEEDED_PANGEA
|
209
|
+
|
210
|
+
if pending_roots:
|
211
|
+
# try Arweave first
|
212
|
+
try:
|
213
|
+
logger.debug(f"Fetching root(s) {comma_sep(pending_roots)} from Arweave")
|
214
|
+
arweave_roots |= {int(k): v for k, v in get_arweave_published_roots(tree_name, pending_roots).items()}
|
215
|
+
update_pending_roots()
|
216
|
+
except:
|
217
|
+
pass
|
218
|
+
|
219
|
+
if pending_roots:
|
220
|
+
logger.debug(f"Published root(s) {comma_sep(pending_roots)} could not be fetched from Arweave")
|
221
|
+
|
222
|
+
if pending_roots:
|
223
|
+
if audit:
|
224
|
+
# and then Pangea (if we've set an audit client)
|
225
|
+
try:
|
226
|
+
logger.debug(f"Fetching root(s) {comma_sep(pending_roots)} from Pangea")
|
227
|
+
pangea_roots |= {int(k): v for k, v in get_pangea_roots(tree_name, pending_roots).items()}
|
228
|
+
update_pending_roots()
|
229
|
+
status = Status.SUCCEEDED_PANGEA
|
230
|
+
except:
|
231
|
+
pass
|
232
|
+
|
233
|
+
if pending_roots:
|
234
|
+
logger.debug(f"Roots {comma_sep(pending_roots)} could not be fetched")
|
235
|
+
else:
|
236
|
+
logger.debug("Set Pangea token and domain (from envvars or script parameters) to fetch roots from Pangea")
|
237
|
+
|
238
|
+
if pending_roots:
|
239
|
+
status = Status.FAILED
|
240
|
+
|
241
|
+
log_result("Fetching published roots", status)
|
126
242
|
logger.info("")
|
127
|
-
return
|
243
|
+
return status
|
128
244
|
|
129
245
|
|
130
|
-
def _verify_membership_proof(
|
131
|
-
|
246
|
+
def _verify_membership_proof(tree_size: int, node_hash: str, proof: Optional[str]) -> Status:
|
247
|
+
pub_roots: Dict[int, Union[Root, PublishedRoot]] = arweave_roots | pangea_roots
|
132
248
|
|
133
249
|
log_section("Checking membership proof")
|
134
250
|
|
135
|
-
if
|
136
|
-
|
251
|
+
if tree_size not in pub_roots:
|
252
|
+
status = Status.SKIPPED
|
253
|
+
logger.debug("Published root not found")
|
254
|
+
elif proof is None:
|
255
|
+
status = Status.SKIPPED
|
137
256
|
logger.debug("Proof not found (event not published yet)")
|
138
257
|
else:
|
139
258
|
try:
|
140
|
-
|
141
|
-
if tree_size not in pub_roots:
|
142
|
-
pub_roots |= {int(k): v for k, v in get_arweave_published_roots(tree_name, [tree_size]).items()} # type: ignore[operator]
|
143
|
-
if tree_size not in pub_roots:
|
144
|
-
raise ValueError("Published root could was not found")
|
145
|
-
|
146
|
-
root_hash_dec = decode_hash(pub_roots[tree_size].root_hash) # type: ignore[attr-defined]
|
259
|
+
root_hash_dec = decode_hash(pub_roots[tree_size].root_hash)
|
147
260
|
node_hash_dec = decode_hash(node_hash)
|
148
261
|
logger.debug("Calculating the proof")
|
262
|
+
if proof is None:
|
263
|
+
logger.debug("Consistency proof is missing")
|
264
|
+
return False
|
149
265
|
proof_dec = decode_membership_proof(proof)
|
150
266
|
logger.debug("Comparing the root hash with the proof hash")
|
151
|
-
|
267
|
+
if verify_membership_proof(node_hash_dec, root_hash_dec, proof_dec):
|
268
|
+
status = Status.SUCCEEDED
|
269
|
+
else:
|
270
|
+
status = Status.FAILED
|
152
271
|
except Exception as e:
|
153
|
-
|
272
|
+
status = Status.FAILED
|
154
273
|
logger.debug(str(e))
|
155
274
|
|
156
|
-
log_result("Membership proof verification",
|
275
|
+
log_result("Membership proof verification", status)
|
157
276
|
logger.info("")
|
158
|
-
return
|
277
|
+
return status
|
278
|
+
|
279
|
+
|
280
|
+
def _consistency_proof_ok(pub_roots: Dict[int, Union[Root, PublishedRoot]], leaf_index: int) -> bool:
|
281
|
+
"""returns true if a consistency proof is correct"""
|
282
|
+
|
283
|
+
curr_root = pub_roots[leaf_index + 1]
|
284
|
+
prev_root = pub_roots[leaf_index]
|
285
|
+
curr_root_hash = decode_hash(curr_root.root_hash)
|
286
|
+
prev_root_hash = decode_hash(prev_root.root_hash)
|
287
|
+
if curr_root.consistency_proof is None:
|
288
|
+
logger.debug("Consistency proof is missing")
|
289
|
+
return False
|
290
|
+
|
291
|
+
logger.debug("Calculating the proof")
|
292
|
+
proof = decode_consistency_proof(curr_root.consistency_proof)
|
293
|
+
return verify_consistency_proof(curr_root_hash, prev_root_hash, proof)
|
159
294
|
|
160
295
|
|
161
|
-
|
162
|
-
|
296
|
+
# Due to an (already fixed) bug, some proofs from Arweave may be wrong.
|
297
|
+
# Try the proof from Pangea instead. If the root hash in both Arweave and Pangea is the same,
|
298
|
+
# it doesn't matter where the proof came from.
|
299
|
+
def _fix_consistency_proof(pub_roots: Dict[int, Union[Root, PublishedRoot]], tree_name: str, leaf_index: int):
|
300
|
+
logger.debug("Consistency proof from Arweave failed to verify")
|
301
|
+
size = leaf_index + 1
|
302
|
+
logger.debug(f"Fetching root from Pangea for size {size}")
|
303
|
+
new_roots = get_pangea_roots(tree_name, [size])
|
304
|
+
if size not in new_roots:
|
305
|
+
raise ValueError("Error fetching root from Pangea")
|
306
|
+
pangea_roots[size] = new_roots[size]
|
307
|
+
pub_roots[size] = pangea_roots[size]
|
308
|
+
logger.debug(f"Comparing Arweave root hash with Pangea root hash")
|
309
|
+
if pangea_roots[size].root_hash != arweave_roots[size].root_hash:
|
310
|
+
raise ValueError("Hash does not match")
|
311
|
+
|
312
|
+
|
313
|
+
def _verify_consistency_proof(tree_name: str, leaf_index: Optional[int]) -> Status:
|
314
|
+
pub_roots: Dict[int, Union[Root, PublishedRoot]] = arweave_roots | pangea_roots
|
315
|
+
|
163
316
|
log_section("Checking consistency proof")
|
164
317
|
|
165
318
|
if leaf_index is None:
|
166
|
-
|
319
|
+
status = Status.SKIPPED
|
167
320
|
logger.debug("Proof not found (event was not published yet)")
|
168
321
|
|
169
322
|
elif leaf_index == 0:
|
170
|
-
|
323
|
+
status = Status.SKIPPED
|
171
324
|
logger.debug("Proof not found (event was published in the first leaf)")
|
325
|
+
|
326
|
+
elif leaf_index not in pub_roots:
|
327
|
+
status = Status.SKIPPED
|
328
|
+
logger.debug("Published root not found")
|
329
|
+
|
172
330
|
else:
|
173
331
|
try:
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
332
|
+
if _consistency_proof_ok(pub_roots, leaf_index):
|
333
|
+
status = Status.SUCCEEDED
|
334
|
+
|
335
|
+
elif audit:
|
336
|
+
_fix_consistency_proof(pub_roots, tree_name, leaf_index)
|
337
|
+
|
338
|
+
# check again
|
339
|
+
if _consistency_proof_ok(pub_roots, leaf_index):
|
340
|
+
status = Status.SUCCEEDED
|
341
|
+
else:
|
342
|
+
status = Status.FAILED
|
343
|
+
|
344
|
+
else:
|
345
|
+
logger.debug(
|
346
|
+
"Set Pangea token and domain (from envvars or script parameters) to fetch roots from Pangea"
|
347
|
+
)
|
348
|
+
status = Status.FAILED
|
188
349
|
|
189
350
|
except Exception as e:
|
190
|
-
|
351
|
+
status = Status.FAILED
|
191
352
|
logger.debug(str(e))
|
192
353
|
|
193
|
-
log_result("Consistency proof verification",
|
354
|
+
log_result("Consistency proof verification", status)
|
194
355
|
logger.info("")
|
195
|
-
return
|
356
|
+
return status
|
196
357
|
|
197
358
|
|
198
359
|
def create_signed_event(event: Dict) -> Dict:
|
199
360
|
return {k: v for k, v in event.items() if v is not None}
|
200
361
|
|
201
362
|
|
202
|
-
def _verify_signature(data: Dict) ->
|
363
|
+
def _verify_signature(data: Dict) -> Status:
|
203
364
|
log_section("Checking signature")
|
204
365
|
if "signature" not in data:
|
205
366
|
logger.debug("Signature is not present")
|
206
|
-
|
367
|
+
status = Status.SKIPPED
|
207
368
|
else:
|
208
369
|
try:
|
209
370
|
logger.debug("Obtaining signature and public key from the event")
|
@@ -213,30 +374,36 @@ def _verify_signature(data: Dict) -> Optional[bool]:
|
|
213
374
|
logger.debug("Checking the signature")
|
214
375
|
if not sign_verifier.verify_signature(data["signature"], canonicalize_json(sign_event), public_key):
|
215
376
|
raise ValueError("Signature is invalid")
|
216
|
-
|
377
|
+
status = Status.SUCCEEDED
|
217
378
|
except Exception:
|
218
|
-
|
379
|
+
status = Status.FAILED
|
219
380
|
|
220
|
-
log_result("Data signature verification",
|
381
|
+
log_result("Data signature verification", status)
|
221
382
|
logger.info("")
|
222
|
-
return
|
383
|
+
return status
|
223
384
|
|
224
385
|
|
225
|
-
def verify_multiple(root: Dict, unpublished_root: Dict, events: List[Dict]) ->
|
386
|
+
def verify_multiple(root: Dict, unpublished_root: Dict, events: List[Dict]) -> Status:
|
226
387
|
"""
|
227
388
|
Verify a list of events.
|
228
389
|
Returns a status.
|
229
390
|
"""
|
230
391
|
|
231
|
-
|
392
|
+
statuses: List[Status] = []
|
232
393
|
for counter, event in enumerate(events):
|
233
394
|
event.update({"root": root, "unpublished_root": unpublished_root})
|
234
|
-
|
235
|
-
|
236
|
-
|
395
|
+
event_status = verify_single(event, counter + 1)
|
396
|
+
statuses.append(event_status)
|
397
|
+
|
398
|
+
for event_status in statuses:
|
399
|
+
if event_status == Status.FAILED:
|
400
|
+
return Status.FAILED
|
401
|
+
elif event_status == Status.SKIPPED:
|
402
|
+
return Status.SKIPPED
|
403
|
+
return Status.SUCCEEDED
|
237
404
|
|
238
405
|
|
239
|
-
def verify_single(data: Dict, counter: Optional[int] = None) ->
|
406
|
+
def verify_single(data: Dict, counter: Optional[int] = None) -> Status:
|
240
407
|
"""
|
241
408
|
Verify a single event.
|
242
409
|
Returns a status.
|
@@ -248,11 +415,15 @@ def verify_single(data: Dict, counter: Optional[int] = None) -> Optional[bool]:
|
|
248
415
|
ok_hash = _verify_hash(data["envelope"], data["hash"])
|
249
416
|
ok_signature = _verify_signature(data["envelope"])
|
250
417
|
|
418
|
+
if data.get("root"):
|
419
|
+
ok_roots = _fetch_roots(data["root"]["tree_name"], data["root"]["size"], data.get("leaf_index"))
|
420
|
+
else:
|
421
|
+
ok_roots = Status.SKIPPED
|
422
|
+
|
251
423
|
if data["published"]:
|
252
424
|
if not data.get("root"):
|
253
425
|
raise ValueError("Missing 'root' element")
|
254
426
|
ok_membership = _verify_membership_proof(
|
255
|
-
data["root"]["tree_name"],
|
256
427
|
data["root"]["size"],
|
257
428
|
data["hash"],
|
258
429
|
data.get("membership_proof"),
|
@@ -267,25 +438,31 @@ def verify_single(data: Dict, counter: Optional[int] = None) -> Optional[bool]:
|
|
267
438
|
if data["published"]:
|
268
439
|
ok_consistency = _verify_consistency_proof(data["root"]["tree_name"], data.get("leaf_index"))
|
269
440
|
else:
|
270
|
-
ok_consistency =
|
441
|
+
ok_consistency = Status.SUCCEEDED
|
271
442
|
|
272
443
|
all_ok = (
|
273
|
-
ok_hash
|
274
|
-
and (ok_signature
|
275
|
-
and
|
276
|
-
and
|
444
|
+
ok_hash == Status.SUCCEEDED
|
445
|
+
and (ok_signature in (Status.SUCCEEDED, Status.SKIPPED))
|
446
|
+
and (ok_roots in (Status.SKIPPED, Status.SUCCEEDED, Status.SUCCEEDED_PANGEA))
|
447
|
+
and ok_membership == Status.SUCCEEDED
|
448
|
+
and (ok_consistency in (Status.SUCCEEDED, Status.SKIPPED))
|
449
|
+
)
|
450
|
+
any_failed = (
|
451
|
+
ok_hash == Status.FAILED
|
452
|
+
or (ok_signature == Status.FAILED)
|
453
|
+
or (ok_membership == Status.FAILED)
|
454
|
+
or (ok_consistency == Status.FAILED)
|
277
455
|
)
|
278
|
-
any_failed = ok_hash is False or ok_signature is False or ok_membership is False or ok_consistency is False
|
279
456
|
|
280
457
|
if counter:
|
281
458
|
formatter.indent = 0
|
282
459
|
|
283
460
|
if all_ok:
|
284
|
-
return
|
461
|
+
return Status.SUCCEEDED
|
285
462
|
elif any_failed:
|
286
|
-
return
|
463
|
+
return Status.FAILED
|
287
464
|
else:
|
288
|
-
return
|
465
|
+
return Status.SKIPPED
|
289
466
|
|
290
467
|
|
291
468
|
def main():
|
@@ -303,6 +480,12 @@ def main():
|
|
303
480
|
metavar="PATH",
|
304
481
|
help="Input file (default: standard input).",
|
305
482
|
)
|
483
|
+
parser.add_argument(
|
484
|
+
"--token", "-t", default=os.getenv("PANGEA_TOKEN"), help="Pangea token (default: env PANGEA_TOKEN)"
|
485
|
+
)
|
486
|
+
parser.add_argument(
|
487
|
+
"--domain", "-d", default=os.getenv("PANGEA_DOMAIN"), help="Pangea domain (default: env PANGEA_DOMAIN)"
|
488
|
+
)
|
306
489
|
args = parser.parse_args()
|
307
490
|
|
308
491
|
data = json.load(args.file)
|
@@ -311,15 +494,19 @@ def main():
|
|
311
494
|
logger.info("Pangea Audit - Verification Tool")
|
312
495
|
logger.info("")
|
313
496
|
|
497
|
+
if args.token and args.domain:
|
498
|
+
global audit
|
499
|
+
audit = Audit(token=args.token, config=PangeaConfig(domain=args.domain))
|
500
|
+
|
314
501
|
if events:
|
315
502
|
status = verify_multiple(data["result"].get("root"), data["result"].get("unpublished_root"), events)
|
316
503
|
else:
|
317
504
|
status = verify_single(data)
|
318
505
|
|
319
506
|
logger.info("")
|
320
|
-
if status
|
507
|
+
if status == Status.SUCCEEDED:
|
321
508
|
logger.info("🟢 Verification succeeded 🟢")
|
322
|
-
elif status
|
509
|
+
elif status == Status.FAILED:
|
323
510
|
logger.info("🔴 Verification failed 🔴")
|
324
511
|
else:
|
325
512
|
logger.info("⚪️ Verification could not be finished ⚪️")
|
@@ -1,32 +1,23 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.3
|
2
2
|
Name: pangea-sdk
|
3
|
-
Version: 3.
|
3
|
+
Version: 5.3.0
|
4
4
|
Summary: Pangea API SDK
|
5
|
-
Home-page: https://pangea.cloud/docs/sdk/python/
|
6
5
|
License: MIT
|
7
6
|
Keywords: Pangea,SDK,Audit
|
8
7
|
Author: Glenn Gallien
|
9
8
|
Author-email: glenn.gallien@pangea.cloud
|
10
|
-
Requires-Python: >=3.
|
11
|
-
Classifier: License :: OSI Approved :: MIT License
|
12
|
-
Classifier: Programming Language :: Python :: 3
|
13
|
-
Classifier: Programming Language :: Python :: 3.8
|
14
|
-
Classifier: Programming Language :: Python :: 3.9
|
15
|
-
Classifier: Programming Language :: Python :: 3.10
|
16
|
-
Classifier: Programming Language :: Python :: 3.11
|
9
|
+
Requires-Python: >=3.9
|
17
10
|
Classifier: Topic :: Software Development
|
18
11
|
Classifier: Topic :: Software Development :: Libraries
|
19
|
-
Requires-Dist: aiohttp
|
20
|
-
Requires-Dist:
|
21
|
-
Requires-Dist:
|
22
|
-
Requires-Dist:
|
23
|
-
Requires-Dist:
|
24
|
-
Requires-Dist:
|
25
|
-
Requires-Dist:
|
26
|
-
Requires-Dist: requests
|
27
|
-
Requires-Dist:
|
28
|
-
Requires-Dist: typing-extensions (>=4.7.1,<5.0.0)
|
29
|
-
Project-URL: Repository, https://github.com/pangeacyber/pangea-python/tree/main/packages/pangea-sdk
|
12
|
+
Requires-Dist: aiohttp
|
13
|
+
Requires-Dist: cryptography
|
14
|
+
Requires-Dist: deprecated
|
15
|
+
Requires-Dist: google-crc32c
|
16
|
+
Requires-Dist: pydantic
|
17
|
+
Requires-Dist: python-dateutil
|
18
|
+
Requires-Dist: requests
|
19
|
+
Requires-Dist: requests-toolbelt
|
20
|
+
Requires-Dist: typing-extensions
|
30
21
|
Description-Content-Type: text/markdown
|
31
22
|
|
32
23
|
<a href="https://pangea.cloud?utm_source=github&utm_medium=python-sdk" target="_blank" rel="noopener noreferrer">
|
@@ -36,11 +27,11 @@ Description-Content-Type: text/markdown
|
|
36
27
|
<br />
|
37
28
|
|
38
29
|
[![documentation](https://img.shields.io/badge/documentation-pangea-blue?style=for-the-badge&labelColor=551B76)][Documentation]
|
39
|
-
[![
|
30
|
+
[![Discourse](https://img.shields.io/badge/Discourse-4A154B?style=for-the-badge&logo=discourse&logoColor=white)][Discourse]
|
40
31
|
|
41
32
|
# Pangea Python SDK
|
42
33
|
|
43
|
-
A Python SDK for integrating with Pangea services. Supports Python v3.
|
34
|
+
A Python SDK for integrating with Pangea services. Supports Python v3.9 and
|
44
35
|
above.
|
45
36
|
|
46
37
|
## Installation
|
@@ -64,20 +55,21 @@ $ poetry add pangea-sdk
|
|
64
55
|
#### Beta releases
|
65
56
|
|
66
57
|
Pre-release versions may be available with the `b` (beta) denotation in the
|
67
|
-
version number. These releases serve to preview
|
68
|
-
Semantic Versioning, they are considered unstable and do not carry
|
69
|
-
compatibility guarantees as stable releases.
|
58
|
+
version number. These releases serve to preview Beta and Early Access services
|
59
|
+
and APIs. Per Semantic Versioning, they are considered unstable and do not carry
|
60
|
+
the same compatibility guarantees as stable releases.
|
61
|
+
[Beta changelog](https://github.com/pangeacyber/pangea-python/blob/beta/CHANGELOG.md).
|
70
62
|
|
71
63
|
Via pip:
|
72
64
|
|
73
65
|
```bash
|
74
|
-
$ pip3 install pangea-sdk==
|
66
|
+
$ pip3 install pangea-sdk==5.2.0b2
|
75
67
|
```
|
76
68
|
|
77
69
|
Via poetry:
|
78
70
|
|
79
71
|
```bash
|
80
|
-
$ poetry add pangea-sdk==
|
72
|
+
$ poetry add pangea-sdk==5.2.0b2
|
81
73
|
```
|
82
74
|
|
83
75
|
## Usage
|
@@ -231,6 +223,6 @@ It accepts multiple file formats:
|
|
231
223
|
[GA Examples]: https://github.com/pangeacyber/pangea-python/tree/main/examples
|
232
224
|
[Beta Examples]: https://github.com/pangeacyber/pangea-python/tree/beta/examples
|
233
225
|
[Pangea Console]: https://console.pangea.cloud/
|
234
|
-
[
|
226
|
+
[Discourse]: https://l.pangea.cloud/Jd4wlGs
|
235
227
|
[Secure Audit Log]: https://pangea.cloud/docs/audit
|
236
228
|
|