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/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()} # type: ignore[operator]
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()} # type: ignore[operator]
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 # type: ignore[operator]
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
159
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)
294
+
295
+
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 # type: ignore[operator]
160
315
 
161
- def _verify_consistency_proof(tree_name: str, leaf_index: Optional[int]) -> Optional[bool]:
162
- global pub_roots
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)
237
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
238
404
 
239
- def verify_single(data: Dict, counter: Optional[int] = None) -> Optional[bool]:
405
+
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,12 @@ 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
+ ok_roots = _fetch_roots(data["root"]["tree_name"], data["root"]["size"], data.get("leaf_index"))
419
+
251
420
  if data["published"]:
252
421
  if not data.get("root"):
253
422
  raise ValueError("Missing 'root' element")
254
423
  ok_membership = _verify_membership_proof(
255
- data["root"]["tree_name"],
256
424
  data["root"]["size"],
257
425
  data["hash"],
258
426
  data.get("membership_proof"),
@@ -267,25 +435,31 @@ def verify_single(data: Dict, counter: Optional[int] = None) -> Optional[bool]:
267
435
  if data["published"]:
268
436
  ok_consistency = _verify_consistency_proof(data["root"]["tree_name"], data.get("leaf_index"))
269
437
  else:
270
- ok_consistency = True
438
+ ok_consistency = Status.SUCCEEDED
271
439
 
272
440
  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
441
+ ok_hash == Status.SUCCEEDED
442
+ and (ok_signature in (Status.SUCCEEDED, Status.SKIPPED))
443
+ and (ok_roots in (Status.SUCCEEDED, Status.SUCCEEDED_PANGEA))
444
+ and ok_membership == Status.SUCCEEDED
445
+ and (ok_consistency in (Status.SUCCEEDED, Status.SKIPPED))
446
+ )
447
+ any_failed = (
448
+ ok_hash == Status.FAILED
449
+ or (ok_signature == Status.FAILED)
450
+ or (ok_membership == Status.FAILED)
451
+ or (ok_consistency == Status.FAILED)
277
452
  )
278
- any_failed = ok_hash is False or ok_signature is False or ok_membership is False or ok_consistency is False
279
453
 
280
454
  if counter:
281
455
  formatter.indent = 0
282
456
 
283
457
  if all_ok:
284
- return True
458
+ return Status.SUCCEEDED
285
459
  elif any_failed:
286
- return False
460
+ return Status.FAILED
287
461
  else:
288
- return None
462
+ return Status.SKIPPED
289
463
 
290
464
 
291
465
  def main():
@@ -303,6 +477,12 @@ def main():
303
477
  metavar="PATH",
304
478
  help="Input file (default: standard input).",
305
479
  )
480
+ parser.add_argument(
481
+ "--token", "-t", default=os.getenv("PANGEA_TOKEN"), help="Pangea token (default: env PANGEA_TOKEN)"
482
+ )
483
+ parser.add_argument(
484
+ "--domain", "-d", default=os.getenv("PANGEA_DOMAIN"), help="Pangea domain (default: env PANGEA_DOMAIN)"
485
+ )
306
486
  args = parser.parse_args()
307
487
 
308
488
  data = json.load(args.file)
@@ -311,15 +491,19 @@ def main():
311
491
  logger.info("Pangea Audit - Verification Tool")
312
492
  logger.info("")
313
493
 
494
+ if args.token and args.domain:
495
+ global audit
496
+ audit = Audit(token=args.token, config=PangeaConfig(domain=args.domain))
497
+
314
498
  if events:
315
499
  status = verify_multiple(data["result"].get("root"), data["result"].get("unpublished_root"), events)
316
500
  else:
317
501
  status = verify_single(data)
318
502
 
319
503
  logger.info("")
320
- if status is True:
504
+ if status == Status.SUCCEEDED:
321
505
  logger.info("🟢 Verification succeeded 🟢")
322
- elif status is False:
506
+ elif status == Status.FAILED:
323
507
  logger.info("🔴 Verification failed 🔴")
324
508
  else:
325
509
  logger.info("⚪️ Verification could not be finished ⚪️")
@@ -1,31 +1,32 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pangea-sdk
3
- Version: 3.9.0
3
+ Version: 4.0.0
4
4
  Summary: Pangea API SDK
5
5
  Home-page: https://pangea.cloud/docs/sdk/python/
6
6
  License: MIT
7
7
  Keywords: Pangea,SDK,Audit
8
8
  Author: Glenn Gallien
9
9
  Author-email: glenn.gallien@pangea.cloud
10
- Requires-Python: >=3.7.2,<4.0.0
10
+ Requires-Python: >=3.8.0,<4.0.0
11
11
  Classifier: License :: OSI Approved :: MIT License
12
12
  Classifier: Programming Language :: Python :: 3
13
13
  Classifier: Programming Language :: Python :: 3.8
14
14
  Classifier: Programming Language :: Python :: 3.9
15
15
  Classifier: Programming Language :: Python :: 3.10
16
16
  Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
17
18
  Classifier: Topic :: Software Development
18
19
  Classifier: Topic :: Software Development :: Libraries
19
- Requires-Dist: aiohttp (>=3.8.6,<4.0.0)
20
+ Requires-Dist: aiohttp (>=3.9.3,<4.0.0)
20
21
  Requires-Dist: asyncio (>=3.4.3,<4.0.0)
21
- Requires-Dist: cryptography (>=42.0.7,<43.0.0)
22
+ Requires-Dist: cryptography (>=42.0.8,<43.0.0)
22
23
  Requires-Dist: deprecated (>=1.2.14,<2.0.0)
23
24
  Requires-Dist: google-crc32c (>=1.5.0,<2.0.0)
24
- Requires-Dist: pydantic (>=1.10.15,<2.0.0)
25
+ Requires-Dist: pydantic (>=2.7.4,<3.0.0)
25
26
  Requires-Dist: python-dateutil (>=2.9.0,<3.0.0)
26
27
  Requires-Dist: requests (>=2.31.0,<3.0.0)
27
28
  Requires-Dist: requests-toolbelt (>=1.0.0,<2.0.0)
28
- Requires-Dist: typing-extensions (>=4.7.1,<5.0.0)
29
+ Requires-Dist: typing-extensions (>=4.12.2,<5.0.0)
29
30
  Project-URL: Repository, https://github.com/pangeacyber/pangea-python/tree/main/packages/pangea-sdk
30
31
  Description-Content-Type: text/markdown
31
32
 
@@ -36,11 +37,11 @@ Description-Content-Type: text/markdown
36
37
  <br />
37
38
 
38
39
  [![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]
40
+ [![Discourse](https://img.shields.io/badge/Discourse-4A154B?style=for-the-badge&logo=discourse&logoColor=white)][Discourse]
40
41
 
41
42
  # Pangea Python SDK
42
43
 
43
- A Python SDK for integrating with Pangea services. Supports Python v3.7 and
44
+ A Python SDK for integrating with Pangea services. Supports Python v3.8 and
44
45
  above.
45
46
 
46
47
  ## Installation
@@ -231,6 +232,6 @@ It accepts multiple file formats:
231
232
  [GA Examples]: https://github.com/pangeacyber/pangea-python/tree/main/examples
232
233
  [Beta Examples]: https://github.com/pangeacyber/pangea-python/tree/beta/examples
233
234
  [Pangea Console]: https://console.pangea.cloud/
234
- [Slack]: https://pangea.cloud/join-slack/
235
+ [Discourse]: https://l.pangea.cloud/Jd4wlGs
235
236
  [Secure Audit Log]: https://pangea.cloud/docs/audit
236
237
 
@@ -0,0 +1,46 @@
1
+ pangea/__init__.py,sha256=xX-TnwsJ5recSAa_KWNsLdPa7CEaOzFbap_GzsZ2Gv4,200
2
+ pangea/asyncio/request.py,sha256=ysCT-SB1nkAr64O6JkI64xZt-Za1TQGt8Jp9gfqeLxE,17077
3
+ pangea/asyncio/services/__init__.py,sha256=hmySN2LoXYpHzSKLVSdVjMbpGXi5Z5iNx-fJ2Fc8W6I,320
4
+ pangea/asyncio/services/audit.py,sha256=bZ7gdkVWkzqLqUVc1Wnf3oDAaCLg97-zTWhY8UdX0_Y,26549
5
+ pangea/asyncio/services/authn.py,sha256=3NUiw1I47fWzjc99pr_boQiHY-sMdkJ-wdOoNWgH4rY,45909
6
+ pangea/asyncio/services/authz.py,sha256=DcPn5FpdWMunim6Qtsk2vFLXyUbFb3lgl1XpX3w0hew,9176
7
+ pangea/asyncio/services/base.py,sha256=4FtKtlq74NmE9myrgIt9HMA6JDnP4mPZ6krafWr286o,2663
8
+ pangea/asyncio/services/embargo.py,sha256=ctzj3kip6xos-Eu3JuOskrCGYC8T3JlsgAopZHiPSXM,3068
9
+ pangea/asyncio/services/file_scan.py,sha256=MSNyRdyEfNiYglfxvEohOYO0kyo-FuSJRVxvgeYQ4l4,6287
10
+ pangea/asyncio/services/intel.py,sha256=ph-Kor2CyKWtVaYVx3OD7pRjb4MGCHrZnrgqMkfMu4I,37977
11
+ pangea/asyncio/services/redact.py,sha256=jRNtXr_DZ_cY7guhut-eZmOEhy2uN_VCXrjGH6bkh74,7265
12
+ pangea/asyncio/services/vault.py,sha256=6gmWgFKjZklTlqTxMqepyeUkvHxxwE4qolSwF2p9Vt8,51045
13
+ pangea/audit_logger.py,sha256=gRkCfUUT5LDNaycwxkhZUySgY47jDfn1ZeKOul4XCQI,3842
14
+ pangea/config.py,sha256=mQUu8GX_6weIuv3vjNdG5plppXskXYASmxMWtFQh-hc,1662
15
+ pangea/deep_verify.py,sha256=mocaGbC6XLbMTVWxTpMv4oJtXGPWpT-SbFqT3obpiZs,8443
16
+ pangea/deprecated.py,sha256=IjFYEVvY1E0ld0SMkEYC1o62MAleX3nnT1If2dFVbHo,608
17
+ pangea/dump_audit.py,sha256=1Je8D2fXwU4PWcZ-ZD4icfO3DNFvWqJkwsac4qFEhOo,7025
18
+ pangea/exceptions.py,sha256=OBtzUECpNa6vNp8ySkHC-tm4QjFRCOAHBkMHqzAlOu8,5656
19
+ pangea/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
+ pangea/request.py,sha256=ADMbnSlU-vrQQTlDp6w0b352ZDA_t4srZAihjaciQ5E,24285
21
+ pangea/response.py,sha256=w-jTsCiNxDVt_qP27DQXbtqxPucSOAAYVlI7CnA4aiU,6597
22
+ pangea/services/__init__.py,sha256=iAIa1kk_C0EHBsSn2XP3QT-bOZNGwMBPUcO2JoI-9cE,278
23
+ pangea/services/audit/audit.py,sha256=IFv7jANA8S2SypQVS47x94_Cr5Z9zSsL9Dp9eXw9RHk,39593
24
+ pangea/services/audit/exceptions.py,sha256=bhVuYe4ammacOVxwg98CChxvwZf5FKgR2DcgqILOcwc,471
25
+ pangea/services/audit/models.py,sha256=1h1B9eSYQMYG3f8WNi1UcDX2-impRrET_ErjJYUnj7M,14678
26
+ pangea/services/audit/signing.py,sha256=pOjw60BIYDcg3_5YKDCMWZUaapsEZpCHaFhyFu7TEgc,5567
27
+ pangea/services/audit/util.py,sha256=Zq1qvfeplYfhCP_ud5YMvntSB0UvnCdsuYbOzZkHbjg,7620
28
+ pangea/services/authn/authn.py,sha256=H0mzbAuZlcQUINktLGrLTD2aygRfrkQqzTN7oFo2o8o,44740
29
+ pangea/services/authn/models.py,sha256=y4N-rbJzIUCga_xvqCdIUkfIheRy9gVRd6BtMYn60Lg,19995
30
+ pangea/services/authz.py,sha256=kgJ4iWytqm2PqLB2n2AQy1lHR8TI8tdYBuOYAfY0fY0,11818
31
+ pangea/services/base.py,sha256=lwhHoe5Juy28Ir3Mfj2lHdM58gxZRaxa2SRFi4_DBRw,3453
32
+ pangea/services/embargo.py,sha256=9Wfku4td5ORaIENKmnGmS5jxJJIRfWp6Q51L36Jsy0I,3897
33
+ pangea/services/file_scan.py,sha256=lWXiD4lHpagQ7O-nJeqwRQJKyP4BtauUdU19HZ3zcQM,6954
34
+ pangea/services/intel.py,sha256=H7mo5r5pUDuYFphG4eWfI86twsOMAM3Qlf6dsAZ8UKM,51793
35
+ pangea/services/redact.py,sha256=ZYXkzEoriLJyCqaj5dqmgsC56mIz4T3pPToZ7TcNfhg,11465
36
+ pangea/services/vault/models/asymmetric.py,sha256=ac2Exc66elXxO-HxBqtvLPQWNI7y_00kb6SVqBPKecA,1450
37
+ pangea/services/vault/models/common.py,sha256=CL3DCNi9pM_ROFDxrXFkTe94RFZB0M1EEqi_Rs_Ptj0,13987
38
+ pangea/services/vault/models/secret.py,sha256=cLgEj-_BeGkB4-pmSeTkWVyasFbaJwcEltIEcOyf1U8,481
39
+ pangea/services/vault/models/symmetric.py,sha256=HEdBIihLvlCepdWRD5HPsyXSYcItCsqPSywR6YHLA0s,1344
40
+ pangea/services/vault/vault.py,sha256=r7eUrnNQCtan1oU-H9kldFDOMldMFYEDhaPTJRb7Tnw,50748
41
+ pangea/tools.py,sha256=sa2pSz-L8tB6GcZg6lghsmm8w0qMQAIkzqcv7dilU6Q,6429
42
+ pangea/utils.py,sha256=KaKHWM9Os6IDwyRW1LUij2JLtZ0AQPn6-M5J_Psz6JI,3042
43
+ pangea/verify_audit.py,sha256=Pu4jW-hgGUciMNs_42hAaPUpEr79xM54Rf0JLjELQWw,17369
44
+ pangea_sdk-4.0.0.dist-info/METADATA,sha256=xRnXCNSbbnF0sWqfVdxV-lFIrO637f4scM10tNtGNpg,7532
45
+ pangea_sdk-4.0.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
46
+ pangea_sdk-4.0.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.6.1
2
+ Generator: poetry-core 1.9.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any