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.
Files changed (51) hide show
  1. pangea/__init__.py +2 -1
  2. pangea/asyncio/__init__.py +1 -0
  3. pangea/asyncio/file_uploader.py +39 -0
  4. pangea/asyncio/request.py +46 -23
  5. pangea/asyncio/services/__init__.py +2 -0
  6. pangea/asyncio/services/audit.py +46 -20
  7. pangea/asyncio/services/authn.py +123 -61
  8. pangea/asyncio/services/authz.py +57 -31
  9. pangea/asyncio/services/base.py +21 -2
  10. pangea/asyncio/services/embargo.py +2 -2
  11. pangea/asyncio/services/file_scan.py +24 -9
  12. pangea/asyncio/services/intel.py +104 -30
  13. pangea/asyncio/services/redact.py +52 -3
  14. pangea/asyncio/services/sanitize.py +217 -0
  15. pangea/asyncio/services/share.py +733 -0
  16. pangea/asyncio/services/vault.py +1709 -766
  17. pangea/crypto/rsa.py +135 -0
  18. pangea/deep_verify.py +7 -1
  19. pangea/dump_audit.py +9 -8
  20. pangea/file_uploader.py +35 -0
  21. pangea/request.py +70 -49
  22. pangea/response.py +36 -17
  23. pangea/services/__init__.py +2 -0
  24. pangea/services/audit/audit.py +57 -29
  25. pangea/services/audit/models.py +12 -3
  26. pangea/services/audit/signing.py +6 -5
  27. pangea/services/audit/util.py +3 -3
  28. pangea/services/authn/authn.py +120 -66
  29. pangea/services/authn/models.py +167 -11
  30. pangea/services/authz.py +53 -30
  31. pangea/services/base.py +16 -2
  32. pangea/services/embargo.py +2 -2
  33. pangea/services/file_scan.py +32 -15
  34. pangea/services/intel.py +155 -30
  35. pangea/services/redact.py +132 -3
  36. pangea/services/sanitize.py +388 -0
  37. pangea/services/share/file_format.py +170 -0
  38. pangea/services/share/share.py +1440 -0
  39. pangea/services/vault/models/asymmetric.py +120 -18
  40. pangea/services/vault/models/common.py +439 -141
  41. pangea/services/vault/models/keys.py +94 -0
  42. pangea/services/vault/models/secret.py +27 -3
  43. pangea/services/vault/models/symmetric.py +68 -22
  44. pangea/services/vault/vault.py +1690 -766
  45. pangea/tools.py +6 -7
  46. pangea/utils.py +94 -33
  47. pangea/verify_audit.py +270 -83
  48. {pangea_sdk-3.8.0.dist-info → pangea_sdk-5.3.0.dist-info}/METADATA +21 -29
  49. pangea_sdk-5.3.0.dist-info/RECORD +56 -0
  50. {pangea_sdk-3.8.0.dist-info → pangea_sdk-5.3.0.dist-info}/WHEEL +1 -1
  51. 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 typing import Dict, List, Optional
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
- pub_roots: Dict[int, Dict] = {}
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.succeeded:
59
+ if record.status == Status.SUCCEEDED:
44
60
  point = "🟢"
45
- elif record.succeeded is None:
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, succeeded: Optional[bool]):
65
- if succeeded is True:
82
+ def log_result(msg: str, status: Status):
83
+ if status == Status.SUCCEEDED:
66
84
  msg += " succeeded"
67
- elif succeeded is False:
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 += " could not be performed"
71
- logger.log(logging.INFO, msg, extra={"is_result": True, "succeeded": succeeded})
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) -> Optional[bool]:
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
- succeeded = True
140
+ status = Status.SUCCEEDED
93
141
  except Exception:
94
- succeeded = False
142
+ status = Status.FAILED
95
143
 
96
- log_result("Data hash verification", succeeded)
144
+ log_result("Data hash verification", status)
97
145
  logger.info("")
98
- return succeeded
146
+ return status
99
147
 
100
148
 
101
- def _verify_unpublished_membership_proof(root_hash, node_hash: str, proof: Optional[str]) -> Optional[bool]:
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
- succeeded = None
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
- succeeded = verify_membership_proof(node_hash_dec, root_hash_dec, proof_dec)
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
- succeeded = False
171
+ status = Status.FAILED
123
172
  logger.debug(str(e))
124
173
 
125
- log_result("Unpublished membership proof verification", succeeded)
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 succeeded
243
+ return status
128
244
 
129
245
 
130
- def _verify_membership_proof(tree_name: str, tree_size: int, node_hash: str, proof: Optional[str]) -> Optional[bool]:
131
- global pub_roots
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 proof is None:
136
- succeeded = None
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
- logger.debug("Fetching published roots from Arweave")
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
- succeeded = verify_membership_proof(node_hash_dec, root_hash_dec, proof_dec)
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
- succeeded = False
272
+ status = Status.FAILED
154
273
  logger.debug(str(e))
155
274
 
156
- log_result("Membership proof verification", succeeded)
275
+ log_result("Membership proof verification", status)
157
276
  logger.info("")
158
- return succeeded
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
- def _verify_consistency_proof(tree_name: str, leaf_index: Optional[int]) -> Optional[bool]:
162
- global pub_roots
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
- succeeded = None
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
- succeeded = None
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
- logger.debug("Fetching published roots from Arweave")
175
- pub_roots |= { # type: ignore[operator]
176
- int(k): v for k, v in get_arweave_published_roots(tree_name, [leaf_index + 1, leaf_index]).items()
177
- }
178
- if leaf_index + 1 not in pub_roots or leaf_index not in pub_roots:
179
- raise ValueError("Published roots could not be retrieved")
180
-
181
- curr_root = pub_roots[leaf_index + 1]
182
- prev_root = pub_roots[leaf_index]
183
- curr_root_hash = decode_hash(curr_root.root_hash) # type: ignore[attr-defined]
184
- prev_root_hash = decode_hash(prev_root.root_hash) # type: ignore[attr-defined]
185
- logger.debug("Calculating the proof")
186
- proof = decode_consistency_proof(curr_root.consistency_proof) # type: ignore[attr-defined]
187
- succeeded = verify_consistency_proof(curr_root_hash, prev_root_hash, proof)
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
- succeeded = False
351
+ status = Status.FAILED
191
352
  logger.debug(str(e))
192
353
 
193
- log_result("Consistency proof verification", succeeded)
354
+ log_result("Consistency proof verification", status)
194
355
  logger.info("")
195
- return succeeded
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) -> Optional[bool]:
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
- succeeded = None
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
- succeeded = True
377
+ status = Status.SUCCEEDED
217
378
  except Exception:
218
- succeeded = False
379
+ status = Status.FAILED
219
380
 
220
- log_result("Data signature verification", succeeded)
381
+ log_result("Data signature verification", status)
221
382
  logger.info("")
222
- return succeeded
383
+ return status
223
384
 
224
385
 
225
- def verify_multiple(root: Dict, unpublished_root: Dict, events: List[Dict]) -> Optional[bool]:
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
- succeeded = []
392
+ statuses: List[Status] = []
232
393
  for counter, event in enumerate(events):
233
394
  event.update({"root": root, "unpublished_root": unpublished_root})
234
- event_succeeded = verify_single(event, counter + 1)
235
- succeeded.append(event_succeeded)
236
- return not any(event_succeeded is False for event_succeeded in succeeded)
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) -> Optional[bool]:
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 = True
441
+ ok_consistency = Status.SUCCEEDED
271
442
 
272
443
  all_ok = (
273
- ok_hash is True
274
- and (ok_signature is True or ok_signature is None)
275
- and ok_membership is True
276
- and ok_consistency is True
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 True
461
+ return Status.SUCCEEDED
285
462
  elif any_failed:
286
- return False
463
+ return Status.FAILED
287
464
  else:
288
- return None
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 is True:
507
+ if status == Status.SUCCEEDED:
321
508
  logger.info("🟢 Verification succeeded 🟢")
322
- elif status is False:
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
1
+ Metadata-Version: 2.3
2
2
  Name: pangea-sdk
3
- Version: 3.8.0
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.7.2,<4.0.0
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 (>=3.8.6,<4.0.0)
20
- Requires-Dist: asyncio (>=3.4.3,<4.0.0)
21
- Requires-Dist: cryptography (>=42.0.7,<43.0.0)
22
- Requires-Dist: deprecated (>=1.2.14,<2.0.0)
23
- Requires-Dist: google-crc32c (>=1.5.0,<2.0.0)
24
- Requires-Dist: pydantic (>=1.10.15,<2.0.0)
25
- Requires-Dist: python-dateutil (>=2.9.0,<3.0.0)
26
- Requires-Dist: requests (>=2.31.0,<3.0.0)
27
- Requires-Dist: requests-toolbelt (>=1.0.0,<2.0.0)
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
- [![Slack](https://img.shields.io/badge/Slack-4A154B?style=for-the-badge&logo=slack&logoColor=white)][Slack]
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.7 and
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 beta services and APIs. Per
68
- Semantic Versioning, they are considered unstable and do not carry the same
69
- compatibility guarantees as stable releases. [Beta changelog](https://github.com/pangeacyber/pangea-python/blob/beta/CHANGELOG.md).
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==3.8.0b2
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==3.8.0b2
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
- [Slack]: https://pangea.cloud/join-slack/
226
+ [Discourse]: https://l.pangea.cloud/Jd4wlGs
235
227
  [Secure Audit Log]: https://pangea.cloud/docs/audit
236
228