pangea-sdk 5.1.0__py3-none-any.whl → 5.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 CHANGED
@@ -1,4 +1,4 @@
1
- __version__ = "5.1.0"
1
+ __version__ = "5.2.0"
2
2
 
3
3
  from pangea.asyncio.request import PangeaRequestAsync
4
4
  from pangea.config import PangeaConfig
pangea/asyncio/request.py CHANGED
@@ -4,7 +4,6 @@ from __future__ import annotations
4
4
 
5
5
  import asyncio
6
6
  import json
7
- import os
8
7
  import time
9
8
  from typing import Any, Dict, List, Optional, Sequence, Tuple, Type, Union
10
9
 
@@ -183,7 +182,20 @@ class PangeaRequestAsync(PangeaRequestBase):
183
182
  if resp.status < 200 or resp.status >= 300:
184
183
  raise pe.PresignedUploadError(f"presigned PUT failure: {resp.status}", await resp.text())
185
184
 
186
- async def download_file(self, url: str, filename: Optional[str] = None) -> AttachedFile:
185
+ async def download_file(self, url: str, filename: str | None = None) -> AttachedFile:
186
+ """
187
+ Download file
188
+
189
+ Download a file from the specified URL and save it with the given
190
+ filename.
191
+
192
+ Args:
193
+ url: URL of the file to download
194
+ filename: Name to save the downloaded file as. If not provided, the
195
+ filename will be determined from the Content-Disposition header or
196
+ the URL.
197
+ """
198
+
187
199
  self.logger.debug(
188
200
  json.dumps(
189
201
  {
@@ -174,14 +174,16 @@ class AuditAsync(ServiceBaseAsync, AuditBase):
174
174
  verbose: Optional[bool] = None,
175
175
  ) -> PangeaResponse[LogResult]:
176
176
  """
177
- Log an entry
177
+ Log an event
178
178
 
179
179
  Create a log entry in the Secure Audit Log.
180
+
180
181
  Args:
181
182
  event (dict[str, Any]): event to be logged
182
183
  verify (bool, optional): True to verify logs consistency after response.
183
184
  sign_local (bool, optional): True to sign event with local key.
184
185
  verbose (bool, optional): True to get a more verbose response.
186
+
185
187
  Raises:
186
188
  AuditException: If an audit based api exception happens
187
189
  PangeaAPIException: If an API Error happens
@@ -192,13 +194,7 @@ class AuditAsync(ServiceBaseAsync, AuditBase):
192
194
  Available response fields can be found in our [API documentation](https://pangea.cloud/docs/api/audit#log-an-entry).
193
195
 
194
196
  Examples:
195
- try:
196
- log_response = audit.log({"message"="Hello world"}, verbose=False)
197
- print(f"Response. Hash: {log_response.result.hash}")
198
- except pe.PangeaAPIException as e:
199
- print(f"Request Error: {e.response.summary}")
200
- for err in e.errors:
201
- print(f"\\t{err.detail} \\n")
197
+ response = await audit.log_event({"message": "hello world"}, verbose=True)
202
198
  """
203
199
 
204
200
  input = self._get_log_request(event, sign_local=sign_local, verify=verify, verbose=verbose)
@@ -632,9 +628,9 @@ class AuditAsync(ServiceBaseAsync, AuditBase):
632
628
  if pub_root is not None:
633
629
  self.pub_roots[tree_size] = pub_root
634
630
 
635
- await self.fix_consistency_proofs(tree_sizes)
631
+ await self._fix_consistency_proofs(tree_sizes)
636
632
 
637
- async def fix_consistency_proofs(self, tree_sizes: Iterable[int]):
633
+ async def _fix_consistency_proofs(self, tree_sizes: Iterable[int]) -> None:
638
634
  # on very rare occasions, the consistency proof in Arweave may be wrong
639
635
  # override it with the proof from pangea (not the root hash, just the proof)
640
636
  for tree_size in tree_sizes:
@@ -1,9 +1,12 @@
1
1
  # Copyright 2022 Pangea Cyber Corporation
2
2
  # Author: Pangea Cyber Corporation
3
3
 
4
+ from __future__ import annotations
5
+
4
6
  from typing import Any, Dict, List, Optional
5
7
 
6
8
  from pangea.asyncio.services.base import ServiceBaseAsync
9
+ from pangea.config import PangeaConfig
7
10
  from pangea.response import PangeaResponse
8
11
  from pangea.services.authz import (
9
12
  CheckRequest,
@@ -36,8 +39,8 @@ class AuthZAsync(ServiceBaseAsync):
36
39
 
37
40
  Examples:
38
41
  import os
42
+ from pangea.asyncio.services import AuthZAsync
39
43
  from pangea.config import PangeaConfig
40
- from pangea.services import AuthZ
41
44
 
42
45
  PANGEA_TOKEN = os.getenv("PANGEA_AUTHZ_TOKEN")
43
46
 
@@ -49,7 +52,25 @@ class AuthZAsync(ServiceBaseAsync):
49
52
 
50
53
  service_name = "authz"
51
54
 
52
- def __init__(self, token: str, config=None, logger_name="pangea", config_id: Optional[str] = None):
55
+ def __init__(
56
+ self, token: str, config: PangeaConfig | None = None, logger_name: str = "pangea", config_id: str | None = None
57
+ ) -> None:
58
+ """
59
+ AuthZ client
60
+
61
+ Initializes a new AuthZ client.
62
+
63
+ Args:
64
+ token: Pangea API token.
65
+ config: Configuration.
66
+ logger_name: Logger name.
67
+ config_id: Configuration ID.
68
+
69
+ Examples:
70
+ config = PangeaConfig(domain="aws.us.pangea.cloud")
71
+ authz = AuthZAsync(token="pangea_token", config=config)
72
+ """
73
+
53
74
  super().__init__(token, config, logger_name, config_id=config_id)
54
75
 
55
76
  async def tuple_create(self, tuples: List[Tuple]) -> PangeaResponse[TupleCreateResult]:
@@ -1,8 +1,11 @@
1
1
  # Copyright 2022 Pangea Cyber Corporation
2
2
  # Author: Pangea Cyber Corporation
3
+ from __future__ import annotations
3
4
 
4
5
  from typing import Dict, Optional, Type, Union
5
6
 
7
+ from typing_extensions import override
8
+
6
9
  from pangea.asyncio.request import PangeaRequestAsync
7
10
  from pangea.exceptions import AcceptedRequestException
8
11
  from pangea.response import AttachedFile, PangeaResponse, PangeaResponseResult
@@ -23,6 +26,7 @@ class ServiceBaseAsync(ServiceBase):
23
26
 
24
27
  return self._request
25
28
 
29
+ @override
26
30
  async def poll_result( # type: ignore[override]
27
31
  self,
28
32
  exception: Optional[AcceptedRequestException] = None,
@@ -36,7 +40,8 @@ class ServiceBaseAsync(ServiceBase):
36
40
  Returns request's result that has been accepted by the server
37
41
 
38
42
  Args:
39
- exception (AcceptedRequestException): Exception raise by SDK on the call that is been processed.
43
+ exception: Exception that was previously raised by the SDK on a call
44
+ that is being processed.
40
45
 
41
46
  Returns:
42
47
  PangeaResponse
@@ -58,7 +63,21 @@ class ServiceBaseAsync(ServiceBase):
58
63
  else:
59
64
  raise AttributeError("Need to set exception, response or request_id")
60
65
 
61
- async def download_file(self, url: str, filename: Optional[str] = None) -> AttachedFile: # type: ignore[override]
66
+ @override
67
+ async def download_file(self, url: str, filename: str | None = None) -> AttachedFile: # type: ignore[override]
68
+ """
69
+ Download file
70
+
71
+ Download a file from the specified URL and save it with the given
72
+ filename.
73
+
74
+ Args:
75
+ url: URL of the file to download
76
+ filename: Name to save the downloaded file as. If not provided, the
77
+ filename will be determined from the Content-Disposition header or
78
+ the URL.
79
+ """
80
+
62
81
  return await self.request.download_file(url=url, filename=filename)
63
82
 
64
83
  async def close(self):
@@ -790,6 +790,7 @@ class UserIntelAsync(ServiceBaseAsync):
790
790
  verbose: Optional[bool] = None,
791
791
  raw: Optional[bool] = None,
792
792
  provider: Optional[str] = None,
793
+ cursor: Optional[str] = None,
793
794
  ) -> PangeaResponse[m.UserBreachedResult]:
794
795
  """
795
796
  Look up breached users
@@ -808,6 +809,7 @@ class UserIntelAsync(ServiceBaseAsync):
808
809
  verbose (bool, optional): Echo the API parameters in the response
809
810
  raw (bool, optional): Include raw data from this provider
810
811
  provider (str, optional): Use reputation data from this provider: "crowdstrike"
812
+ cursor (str, optional): A token given in the raw response from SpyCloud. Post this back to paginate results
811
813
 
812
814
  Raises:
813
815
  PangeaAPIException: If an API Error happens
@@ -835,6 +837,7 @@ class UserIntelAsync(ServiceBaseAsync):
835
837
  end=end,
836
838
  verbose=verbose,
837
839
  raw=raw,
840
+ cursor=cursor,
838
841
  )
839
842
  return await self.request.post(
840
843
  "v1/user/breached", m.UserBreachedResult, data=input.model_dump(exclude_none=True)
@@ -1,10 +1,13 @@
1
1
  # Copyright 2022 Pangea Cyber Corporation
2
2
  # Author: Pangea Cyber Corporation
3
+ from __future__ import annotations
4
+
3
5
  import io
4
6
  from typing import List, Optional, Tuple
5
7
 
6
8
  import pangea.services.sanitize as m
7
9
  from pangea.asyncio.services.base import ServiceBaseAsync
10
+ from pangea.config import PangeaConfig
8
11
  from pangea.response import PangeaResponse, TransferMethod
9
12
  from pangea.utils import FileUploadParams, get_file_upload_params
10
13
 
@@ -16,17 +19,38 @@ class SanitizeAsync(ServiceBaseAsync):
16
19
  import os
17
20
 
18
21
  # Pangea SDK
22
+ from pangea.asyncio.services import SanitizeAsync
19
23
  from pangea.config import PangeaConfig
20
- from pangea.asyncio.services import Sanitize
21
24
 
22
25
  PANGEA_SANITIZE_TOKEN = os.getenv("PANGEA_SANITIZE_TOKEN")
23
26
  config = PangeaConfig(domain="pangea.cloud")
24
27
 
25
- sanitize = Sanitize(token=PANGEA_SANITIZE_TOKEN, config=config)
28
+ sanitize = SanitizeAsync(token=PANGEA_SANITIZE_TOKEN, config=config)
26
29
  """
27
30
 
28
31
  service_name = "sanitize"
29
32
 
33
+ def __init__(
34
+ self, token: str, config: PangeaConfig | None = None, logger_name: str = "pangea", config_id: str | None = None
35
+ ) -> None:
36
+ """
37
+ Sanitize client
38
+
39
+ Initializes a new Sanitize client.
40
+
41
+ Args:
42
+ token: Pangea API token.
43
+ config: Configuration.
44
+ logger_name: Logger name.
45
+ config_id: Configuration ID.
46
+
47
+ Examples:
48
+ config = PangeaConfig(domain="aws.us.pangea.cloud")
49
+ authz = SanitizeAsync(token="pangea_token", config=config)
50
+ """
51
+
52
+ super().__init__(token, config, logger_name, config_id=config_id)
53
+
30
54
  async def sanitize(
31
55
  self,
32
56
  transfer_method: TransferMethod = TransferMethod.POST_URL,
@@ -7,16 +7,38 @@ from typing import Dict, List, Optional, Tuple, Union
7
7
 
8
8
  import pangea.services.share.share as m
9
9
  from pangea.asyncio.services.base import ServiceBaseAsync
10
+ from pangea.config import PangeaConfig
10
11
  from pangea.response import PangeaResponse, TransferMethod
11
12
  from pangea.services.share.file_format import FileFormat
12
13
  from pangea.utils import get_file_size, get_file_upload_params
13
14
 
14
15
 
15
16
  class ShareAsync(ServiceBaseAsync):
16
- """Share service client."""
17
+ """Secure Share service client."""
17
18
 
18
19
  service_name = "share"
19
20
 
21
+ def __init__(
22
+ self, token: str, config: PangeaConfig | None = None, logger_name: str = "pangea", config_id: str | None = None
23
+ ) -> None:
24
+ """
25
+ Secure Share client
26
+
27
+ Initializes a new Secure Share client.
28
+
29
+ Args:
30
+ token: Pangea API token.
31
+ config: Configuration.
32
+ logger_name: Logger name.
33
+ config_id: Configuration ID.
34
+
35
+ Examples:
36
+ config = PangeaConfig(domain="aws.us.pangea.cloud")
37
+ authz = ShareAsync(token="pangea_token", config=config)
38
+ """
39
+
40
+ super().__init__(token, config, logger_name, config_id=config_id)
41
+
20
42
  async def buckets(self) -> PangeaResponse[m.BucketsResult]:
21
43
  """
22
44
  Buckets
pangea/deep_verify.py CHANGED
@@ -263,8 +263,14 @@ def main():
263
263
  audit = init_audit(args.token, args.domain)
264
264
  errors = deep_verify(audit, args.file)
265
265
 
266
- print("\n\nTotal errors:")
266
+ print("\n\nWarnings:")
267
+ val = errors["not_persisted"]
268
+ print(f"\tnot_persisted: {val}")
269
+
270
+ print("\nTotal errors:")
267
271
  for key, val in errors.items():
272
+ if key == "not_persisted":
273
+ continue
268
274
  print(f"\t{key.title()}: {val}")
269
275
  print()
270
276
 
pangea/dump_audit.py CHANGED
@@ -63,11 +63,12 @@ def dump_before(audit: Audit, output: io.TextIOWrapper, start: datetime) -> int:
63
63
  cnt = 0
64
64
  if search_res.result and search_res.result.count > 0:
65
65
  leaf_index = search_res.result.events[0].leaf_index
66
- for row in reversed(search_res.result.events):
67
- if row.leaf_index != leaf_index:
68
- break
69
- dump_event(output, row, search_res)
70
- cnt += 1
66
+ if leaf_index is not None:
67
+ for row in reversed(search_res.result.events):
68
+ if row.leaf_index != leaf_index:
69
+ break
70
+ dump_event(output, row, search_res)
71
+ cnt += 1
71
72
  print(f"Dumping before... {cnt} events")
72
73
  return cnt
73
74
 
@@ -89,7 +90,7 @@ def dump_after(audit: Audit, output: io.TextIOWrapper, start: datetime, last_eve
89
90
  cnt = 0
90
91
  if search_res.result and search_res.result.count > 0:
91
92
  leaf_index = search_res.result.events[0].leaf_index
92
- if leaf_index == last_leaf_index:
93
+ if leaf_index is not None and leaf_index == last_leaf_index:
93
94
  start_idx: int = 1 if last_event_hash == search_res.result.events[0].hash else 0
94
95
  for row in search_res.result.events[start_idx:]:
95
96
  if row.leaf_index != leaf_index:
@@ -124,7 +125,7 @@ def dump_page(
124
125
  msg = f"Dumping... {search_res.result.count} events"
125
126
 
126
127
  if search_res.result.count <= 1:
127
- return end, 0 # type: ignore[return-value]
128
+ return end, 0, True, "", 0
128
129
 
129
130
  offset = 0
130
131
  result_id = search_res.result.id
pangea/request.py CHANGED
@@ -11,7 +11,7 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Sequence, Tuple, Ty
11
11
  import requests
12
12
  from pydantic import BaseModel
13
13
  from requests.adapters import HTTPAdapter, Retry
14
- from requests_toolbelt import MultipartDecoder # type: ignore
14
+ from requests_toolbelt import MultipartDecoder # type: ignore[import-untyped]
15
15
  from typing_extensions import TypeVar
16
16
 
17
17
  import pangea
@@ -402,7 +402,20 @@ class PangeaRequest(PangeaRequestBase):
402
402
 
403
403
  return self._check_response(pangea_response)
404
404
 
405
- def download_file(self, url: str, filename: Optional[str] = None) -> AttachedFile:
405
+ def download_file(self, url: str, filename: str | None = None) -> AttachedFile:
406
+ """
407
+ Download file
408
+
409
+ Download a file from the specified URL and save it with the given
410
+ filename.
411
+
412
+ Args:
413
+ url: URL of the file to download
414
+ filename: Name to save the downloaded file as. If not provided, the
415
+ filename will be determined from the Content-Disposition header or
416
+ the URL.
417
+ """
418
+
406
419
  self.logger.debug(
407
420
  json.dumps(
408
421
  {
@@ -492,7 +492,7 @@ class Audit(ServiceBase, AuditBase):
492
492
  verbose: Optional[bool] = None,
493
493
  ) -> PangeaResponse[LogResult]:
494
494
  """
495
- Log an entry
495
+ Log an event
496
496
 
497
497
  Create a log entry in the Secure Audit Log.
498
498
 
@@ -501,6 +501,7 @@ class Audit(ServiceBase, AuditBase):
501
501
  verify (bool, optional): True to verify logs consistency after response.
502
502
  sign_local (bool, optional): True to sign event with local key.
503
503
  verbose (bool, optional): True to get a more verbose response.
504
+
504
505
  Raises:
505
506
  AuditException: If an audit based api exception happens
506
507
  PangeaAPIException: If an API Error happens
@@ -511,13 +512,7 @@ class Audit(ServiceBase, AuditBase):
511
512
  Available response fields can be found in our [API documentation](https://pangea.cloud/docs/api/audit#/v1/log).
512
513
 
513
514
  Examples:
514
- try:
515
- log_response = audit.log({"message": "hello world"}, verbose=True)
516
- print(f"Response. Hash: {log_response.result.hash}")
517
- except pe.PangeaAPIException as e:
518
- print(f"Request Error: {e.response.summary}")
519
- for err in e.errors:
520
- print(f"\\t{err.detail} \\n")
515
+ response = audit.log_event({"message": "hello world"}, verbose=True)
521
516
  """
522
517
 
523
518
  input = self._get_log_request(event, sign_local=sign_local, verify=verify, verbose=verbose)
@@ -961,9 +956,9 @@ class Audit(ServiceBase, AuditBase):
961
956
  if pub_root is not None:
962
957
  self.pub_roots[tree_size] = pub_root
963
958
 
964
- self.fix_consistency_proofs(tree_sizes)
959
+ self._fix_consistency_proofs(tree_sizes)
965
960
 
966
- def fix_consistency_proofs(self, tree_sizes: Iterable[int]):
961
+ def _fix_consistency_proofs(self, tree_sizes: Iterable[int]) -> None:
967
962
  # on very rare occasions, the consistency proof in Arweave may be wrong
968
963
  # override it with the proof from pangea (not the root hash, just the proof)
969
964
  for tree_size in tree_sizes:
pangea/services/authz.py CHANGED
@@ -1,9 +1,11 @@
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
  from typing import Any, Dict, List, Optional, Union
6
7
 
8
+ from pangea.config import PangeaConfig
7
9
  from pangea.response import APIRequestModel, APIResponseModel, PangeaResponse, PangeaResponseResult
8
10
  from pangea.services.base import ServiceBase
9
11
 
@@ -171,7 +173,25 @@ class AuthZ(ServiceBase):
171
173
 
172
174
  service_name = "authz"
173
175
 
174
- def __init__(self, token: str, config=None, logger_name="pangea", config_id: Optional[str] = None):
176
+ def __init__(
177
+ self, token: str, config: PangeaConfig | None = None, logger_name: str = "pangea", config_id: str | None = None
178
+ ) -> None:
179
+ """
180
+ AuthZ client
181
+
182
+ Initializes a new AuthZ client.
183
+
184
+ Args:
185
+ token: Pangea API token.
186
+ config: Configuration.
187
+ logger_name: Logger name.
188
+ config_id: Configuration ID.
189
+
190
+ Examples:
191
+ config = PangeaConfig(domain="aws.us.pangea.cloud")
192
+ authz = AuthZ(token="pangea_token", config=config)
193
+ """
194
+
175
195
  super().__init__(token, config, logger_name, config_id=config_id)
176
196
 
177
197
  def tuple_create(self, tuples: List[Tuple]) -> PangeaResponse[TupleCreateResult]:
pangea/services/base.py CHANGED
@@ -80,7 +80,8 @@ class ServiceBase(object):
80
80
  Returns request's result that has been accepted by the server
81
81
 
82
82
  Args:
83
- exception (AcceptedRequestException): Exception raise by SDK on the call that is been processed.
83
+ exception: Exception that was previously raised by the SDK on a call
84
+ that is being processed.
84
85
 
85
86
  Returns:
86
87
  PangeaResponse
@@ -100,5 +101,18 @@ class ServiceBase(object):
100
101
  else:
101
102
  raise AttributeError("Need to set exception, response or request_id")
102
103
 
103
- def download_file(self, url: str, filename: Optional[str] = None) -> AttachedFile:
104
+ def download_file(self, url: str, filename: str | None = None) -> AttachedFile:
105
+ """
106
+ Download file
107
+
108
+ Download a file from the specified URL and save it with the given
109
+ filename.
110
+
111
+ Args:
112
+ url: URL of the file to download
113
+ filename: Name to save the downloaded file as. If not provided, the
114
+ filename will be determined from the Content-Disposition header or
115
+ the URL.
116
+ """
117
+
104
118
  return self.request.download_file(url=url, filename=filename)
pangea/services/intel.py CHANGED
@@ -1,5 +1,7 @@
1
1
  # Copyright 2022 Pangea Cyber Corporation
2
2
  # Author: Pangea Cyber Corporation
3
+ from __future__ import annotations
4
+
3
5
  import enum
4
6
  import hashlib
5
7
  from typing import Dict, List, Optional
@@ -1237,6 +1239,7 @@ class UserBreachedRequest(IntelCommonRequest):
1237
1239
  phone_number (str): A phone number to search for. minLength: 7, maxLength: 15.
1238
1240
  start (str): Earliest date for search
1239
1241
  end (str): Latest date for search
1242
+ cursor (str, optional): A token given in the raw response from SpyCloud. Post this back to paginate results
1240
1243
  """
1241
1244
 
1242
1245
  email: Optional[str] = None
@@ -1245,6 +1248,7 @@ class UserBreachedRequest(IntelCommonRequest):
1245
1248
  phone_number: Optional[str] = None
1246
1249
  start: Optional[str] = None
1247
1250
  end: Optional[str] = None
1251
+ cursor: Optional[str] = None
1248
1252
 
1249
1253
 
1250
1254
  class UserBreachedBulkRequest(IntelCommonRequest):
@@ -1387,6 +1391,7 @@ class UserIntel(ServiceBase):
1387
1391
  verbose: Optional[bool] = None,
1388
1392
  raw: Optional[bool] = None,
1389
1393
  provider: Optional[str] = None,
1394
+ cursor: Optional[str] = None,
1390
1395
  ) -> PangeaResponse[UserBreachedResult]:
1391
1396
  """
1392
1397
  Look up breached users
@@ -1405,6 +1410,7 @@ class UserIntel(ServiceBase):
1405
1410
  verbose (bool, optional): Echo the API parameters in the response
1406
1411
  raw (bool, optional): Include raw data from this provider
1407
1412
  provider (str, optional): Use reputation data from this provider: "spycloud"
1413
+ cursor (str, optional): A token given in the raw response from SpyCloud. Post this back to paginate results
1408
1414
 
1409
1415
  Raises:
1410
1416
  PangeaAPIException: If an API Error happens
@@ -1432,6 +1438,7 @@ class UserIntel(ServiceBase):
1432
1438
  end=end,
1433
1439
  verbose=verbose,
1434
1440
  raw=raw,
1441
+ cursor=cursor,
1435
1442
  )
1436
1443
  return self.request.post("v1/user/breached", UserBreachedResult, data=input.model_dump(exclude_none=True))
1437
1444
 
@@ -1592,6 +1599,17 @@ class UserIntel(ServiceBase):
1592
1599
 
1593
1600
  @staticmethod
1594
1601
  def is_password_breached(response: PangeaResponse[UserBreachedResult], hash: str) -> PasswordStatus:
1602
+ """
1603
+ Check if a password was breached
1604
+
1605
+ Helper function that simplifies searching the response's raw data for
1606
+ the full hash.
1607
+
1608
+ Args:
1609
+ response: API response from an earlier request
1610
+ hash: Password hash
1611
+ """
1612
+
1595
1613
  if response.result.raw_data is None: # type: ignore[union-attr]
1596
1614
  raise PangeaException("Need raw data to check if hash is breached. Send request with raw=true")
1597
1615
 
pangea/services/redact.py CHANGED
@@ -66,6 +66,18 @@ class RedactRequest(APIRequestModel):
66
66
  rulesets: Optional[List[str]] = None
67
67
  return_result: Optional[bool] = None
68
68
  redaction_method_overrides: Optional[RedactionMethodOverrides] = None
69
+ vault_parameters: Optional[VaultParameters] = None
70
+
71
+ llm_request: Optional[bool] = None
72
+ """Is this redact call going to be used in an LLM request?"""
73
+
74
+
75
+ class VaultParameters(APIRequestModel):
76
+ fpe_key_id: Optional[str] = None
77
+ """A vault key ID of an exportable key used to redact with FPE instead of using the service config default."""
78
+
79
+ salt_secret_id: Optional[str] = None
80
+ """A vault secret ID of a secret used to salt a hash instead of using the service config default."""
69
81
 
70
82
 
71
83
  class RecognizerResult(APIResponseModel):
@@ -134,6 +146,10 @@ class StructuredRequest(APIRequestModel):
134
146
  rulesets: Optional[List[str]] = None
135
147
  return_result: Optional[bool] = None
136
148
  redaction_method_overrides: Optional[RedactionMethodOverrides] = None
149
+ vault_parameters: Optional[VaultParameters] = None
150
+
151
+ llm_request: Optional[bool] = None
152
+ """Is this redact call going to be used in an LLM request?"""
137
153
 
138
154
 
139
155
  class StructuredResult(PangeaResponseResult):
@@ -7,6 +7,7 @@ from typing import Dict, List, Optional, Tuple
7
7
 
8
8
  from pydantic import Field
9
9
 
10
+ from pangea.config import PangeaConfig
10
11
  from pangea.response import APIRequestModel, PangeaResponse, PangeaResponseResult, TransferMethod
11
12
  from pangea.services.base import ServiceBase
12
13
  from pangea.utils import FileUploadParams, get_file_upload_params
@@ -197,6 +198,27 @@ class Sanitize(ServiceBase):
197
198
 
198
199
  service_name = "sanitize"
199
200
 
201
+ def __init__(
202
+ self, token: str, config: PangeaConfig | None = None, logger_name: str = "pangea", config_id: str | None = None
203
+ ) -> None:
204
+ """
205
+ Sanitize client
206
+
207
+ Initializes a new Sanitize client.
208
+
209
+ Args:
210
+ token: Pangea API token.
211
+ config: Configuration.
212
+ logger_name: Logger name.
213
+ config_id: Configuration ID.
214
+
215
+ Examples:
216
+ config = PangeaConfig(domain="aws.us.pangea.cloud")
217
+ authz = Sanitize(token="pangea_token", config=config)
218
+ """
219
+
220
+ super().__init__(token, config, logger_name, config_id=config_id)
221
+
200
222
  def sanitize(
201
223
  self,
202
224
  transfer_method: TransferMethod = TransferMethod.POST_URL,
@@ -6,6 +6,7 @@ import enum
6
6
  import io
7
7
  from typing import Dict, List, NewType, Optional, Tuple, Union
8
8
 
9
+ from pangea.config import PangeaConfig
9
10
  from pangea.response import APIRequestModel, PangeaResponse, PangeaResponseResult, TransferMethod
10
11
  from pangea.services.base import ServiceBase
11
12
  from pangea.services.share.file_format import FileFormat
@@ -669,10 +670,31 @@ class BucketsResult(PangeaResponseResult):
669
670
 
670
671
 
671
672
  class Share(ServiceBase):
672
- """Share service client."""
673
+ """Secure Share service client."""
673
674
 
674
675
  service_name = "share"
675
676
 
677
+ def __init__(
678
+ self, token: str, config: PangeaConfig | None = None, logger_name: str = "pangea", config_id: str | None = None
679
+ ) -> None:
680
+ """
681
+ Secure Share client
682
+
683
+ Initializes a new Secure Share client.
684
+
685
+ Args:
686
+ token: Pangea API token.
687
+ config: Configuration.
688
+ logger_name: Logger name.
689
+ config_id: Configuration ID.
690
+
691
+ Examples:
692
+ config = PangeaConfig(domain="aws.us.pangea.cloud")
693
+ authz = Share(token="pangea_token", config=config)
694
+ """
695
+
696
+ super().__init__(token, config, logger_name, config_id=config_id)
697
+
676
698
  def buckets(self) -> PangeaResponse[BucketsResult]:
677
699
  """
678
700
  Buckets
pangea/utils.py CHANGED
@@ -6,7 +6,6 @@ import datetime
6
6
  import io
7
7
  import json
8
8
  from hashlib import md5, new, sha1, sha256, sha512
9
- from typing import Union
10
9
 
11
10
  from google_crc32c import Checksum as CRC32C
12
11
  from pydantic import BaseModel
@@ -67,7 +66,7 @@ def canonicalize(data: dict) -> str:
67
66
  return str(data)
68
67
 
69
68
 
70
- def hash_sha256(input: Union[str, io.BufferedReader]) -> str:
69
+ def hash_sha256(input: str | io.BufferedReader) -> str:
71
70
  # Return SHA256 hash in hex format
72
71
  hash = sha256()
73
72
  if isinstance(input, io.BufferedReader):
@@ -80,12 +79,12 @@ def hash_sha256(input: Union[str, io.BufferedReader]) -> str:
80
79
 
81
80
  input.seek(0) # restart reading
82
81
  else:
83
- hash.update(input) # type: ignore
82
+ hash.update(input.encode("utf-8"))
84
83
 
85
84
  return hash.hexdigest()
86
85
 
87
86
 
88
- def hash_sha1(input: Union[str, io.BufferedReader]) -> str:
87
+ def hash_sha1(input: str | io.BufferedReader) -> str:
89
88
  # Return SHA1 hash in hex format
90
89
  hash = sha1()
91
90
  if isinstance(input, io.BufferedReader):
@@ -98,12 +97,12 @@ def hash_sha1(input: Union[str, io.BufferedReader]) -> str:
98
97
 
99
98
  input.seek(0) # restart reading
100
99
  else:
101
- hash.update(input) # type: ignore
100
+ hash.update(input.encode("utf-8"))
102
101
 
103
102
  return hash.hexdigest()
104
103
 
105
104
 
106
- def hash_sha512(input: Union[str, io.BufferedReader]) -> str:
105
+ def hash_sha512(input: str | io.BufferedReader) -> str:
107
106
  # Return SHA512 hash in hex format
108
107
  hash = sha512()
109
108
  if isinstance(input, io.BufferedReader):
@@ -116,7 +115,7 @@ def hash_sha512(input: Union[str, io.BufferedReader]) -> str:
116
115
 
117
116
  input.seek(0) # restart reading
118
117
  else:
119
- hash.update(input) # type: ignore
118
+ hash.update(input.encode("utf-8"))
120
119
 
121
120
  return hash.hexdigest()
122
121
 
@@ -126,7 +125,7 @@ def hash_ntlm(data: str) -> str:
126
125
  return new("md4", data.encode("utf-16le")).hexdigest()
127
126
 
128
127
 
129
- def hash_md5(input: Union[str, io.BufferedReader]) -> str:
128
+ def hash_md5(input: str | io.BufferedReader) -> str:
130
129
  # Return MD5 hash in hex format
131
130
  hash = md5()
132
131
  if isinstance(input, io.BufferedReader):
@@ -140,7 +139,7 @@ def hash_md5(input: Union[str, io.BufferedReader]) -> str:
140
139
 
141
140
  input.seek(0) # restart reading
142
141
  else:
143
- hash.update(input) # type: ignore
142
+ hash.update(input.encode("utf-8"))
144
143
 
145
144
  return hash.hexdigest()
146
145
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pangea-sdk
3
- Version: 5.1.0
3
+ Version: 5.2.0
4
4
  Summary: Pangea API SDK
5
5
  Home-page: https://pangea.cloud/docs/sdk/python/
6
6
  License: MIT
@@ -17,11 +17,11 @@ Classifier: Programming Language :: Python :: 3.12
17
17
  Classifier: Programming Language :: Python :: 3.13
18
18
  Classifier: Topic :: Software Development
19
19
  Classifier: Topic :: Software Development :: Libraries
20
- Requires-Dist: aiohttp (>=3.10.10,<4.0.0)
21
- Requires-Dist: cryptography (>=43.0.1,<44.0.0)
22
- Requires-Dist: deprecated (>=1.2.14,<2.0.0)
20
+ Requires-Dist: aiohttp (>=3.11.10,<4.0.0)
21
+ Requires-Dist: cryptography (>=43.0.3,<44.0.0)
22
+ Requires-Dist: deprecated (>=1.2.15,<2.0.0)
23
23
  Requires-Dist: google-crc32c (>=1.5.0,<2.0.0)
24
- Requires-Dist: pydantic (>=2.9.2,<3.0.0)
24
+ Requires-Dist: pydantic (>=2.10.3,<3.0.0)
25
25
  Requires-Dist: python-dateutil (>=2.9.0,<3.0.0)
26
26
  Requires-Dist: requests (>=2.31.0,<3.0.0)
27
27
  Requires-Dist: requests-toolbelt (>=1.0.0,<2.0.0)
@@ -64,20 +64,21 @@ $ poetry add pangea-sdk
64
64
  #### Beta releases
65
65
 
66
66
  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).
67
+ version number. These releases serve to preview Beta and Early Access services
68
+ and APIs. Per Semantic Versioning, they are considered unstable and do not carry
69
+ the same compatibility guarantees as stable releases.
70
+ [Beta changelog](https://github.com/pangeacyber/pangea-python/blob/beta/CHANGELOG.md).
70
71
 
71
72
  Via pip:
72
73
 
73
74
  ```bash
74
- $ pip3 install pangea-sdk==3.8.0b2
75
+ $ pip3 install pangea-sdk==5.2.0b1
75
76
  ```
76
77
 
77
78
  Via poetry:
78
79
 
79
80
  ```bash
80
- $ poetry add pangea-sdk==3.8.0b2
81
+ $ poetry add pangea-sdk==5.2.0b1
81
82
  ```
82
83
 
83
84
  ## Usage
@@ -1,47 +1,47 @@
1
- pangea/__init__.py,sha256=ie7DdC7-YE5szWaPCHAlk2umsSOUBPoE_0HSaSazcqE,246
1
+ pangea/__init__.py,sha256=9eKOhXARZe6eGTwrLyOBGfDQV02cZF1aQ4GVGjL1yyc,246
2
2
  pangea/asyncio/__init__.py,sha256=kjEMkqMQ521LlMSu5jn3_WgweyArwVZ2C-s3x7mR6Pk,45
3
3
  pangea/asyncio/file_uploader.py,sha256=wI7epib7Rc5jtZw4eJ1L1SlmutDG6CPv59C8N2UPhtY,1436
4
- pangea/asyncio/request.py,sha256=b9GNZl-gOk-NhJtOU0Ds7RCF88c_mHpNo0H-3H-hK9I,17478
4
+ pangea/asyncio/request.py,sha256=BREsLY8_MCxGSplHbdKFZTzr4TX5ya0-BsXLffZhsco,17849
5
5
  pangea/asyncio/services/__init__.py,sha256=3IkiTqY_RtFndI7aoDTrb1yLv8xos_cKhmGS1TULcmw,386
6
- pangea/asyncio/services/audit.py,sha256=bZ7gdkVWkzqLqUVc1Wnf3oDAaCLg97-zTWhY8UdX0_Y,26549
6
+ pangea/asyncio/services/audit.py,sha256=rPaCx4cMzj-g9WFMRIysFCJAz6Btp6YrhcKe_exky8k,26283
7
7
  pangea/asyncio/services/authn.py,sha256=rPeLJweL8mYH_t4ebcQn4n_Wglr3kClKNnCXNCimZU4,46622
8
- pangea/asyncio/services/authz.py,sha256=HgW9R8DeW19wS7fpgq0NWOx41wZWcn6NYS4NMbi8p1A,9482
9
- pangea/asyncio/services/base.py,sha256=4FtKtlq74NmE9myrgIt9HMA6JDnP4mPZ6krafWr286o,2663
8
+ pangea/asyncio/services/authz.py,sha256=B_0_nhDMJcjNpjpCx3Vi2LDRhlmfV9325GKbUZ8reos,10025
9
+ pangea/asyncio/services/base.py,sha256=vRFVcO_uEAGJte3OUUBLD43RoiiFB1vC7SPyN6yEMoA,3158
10
10
  pangea/asyncio/services/embargo.py,sha256=ctzj3kip6xos-Eu3JuOskrCGYC8T3JlsgAopZHiPSXM,3068
11
11
  pangea/asyncio/services/file_scan.py,sha256=PLG1O-PL4Yk9uY9D6NbMrZ5LHg70Z311s7bFe46UMZA,7108
12
- pangea/asyncio/services/intel.py,sha256=cCm3VwWxUzEUCNhuPCeejJvr4uOeLXuYDbDwTzNG6Aw,38121
12
+ pangea/asyncio/services/intel.py,sha256=Iwz_DleAPtmd1taekT4W8lVo65uHjIvA4TQ7WUQGrRk,38306
13
13
  pangea/asyncio/services/redact.py,sha256=jRNtXr_DZ_cY7guhut-eZmOEhy2uN_VCXrjGH6bkh74,7265
14
- pangea/asyncio/services/sanitize.py,sha256=bf98J-s-P51oSKqNBgR0wj5mlHOCBwpjWz7k0NdXCKQ,7899
15
- pangea/asyncio/services/share.py,sha256=UYJeUKA3NLSFA8R0X7B6yBi2U1g4q04O4ftrp9SMCzA,26097
14
+ pangea/asyncio/services/sanitize.py,sha256=4pRWBH595kFUXBkmu8euyk6H7F1M_-xXB2Qxnz6St6c,8627
15
+ pangea/asyncio/services/share.py,sha256=YPJm_Gc4tfcx2cX6P_vLWIsHOR6M3RYy9LdU2UzEZbk,26791
16
16
  pangea/asyncio/services/vault.py,sha256=VqrJGSEdq6MlZRI6cJpkthhIsqLClSQdgVxwYCbIwEk,77079
17
17
  pangea/audit_logger.py,sha256=gRkCfUUT5LDNaycwxkhZUySgY47jDfn1ZeKOul4XCQI,3842
18
18
  pangea/config.py,sha256=mQUu8GX_6weIuv3vjNdG5plppXskXYASmxMWtFQh-hc,1662
19
19
  pangea/crypto/rsa.py,sha256=mwSiNy571KAGr3F6oEM0CXWkl9D023ch8ldbZZeLj_4,4747
20
- pangea/deep_verify.py,sha256=mocaGbC6XLbMTVWxTpMv4oJtXGPWpT-SbFqT3obpiZs,8443
20
+ pangea/deep_verify.py,sha256=ZGraaL7TCxwRBIDqjBFR0clKlhAC-Yce6kD-1LClhG8,8616
21
21
  pangea/deprecated.py,sha256=IjFYEVvY1E0ld0SMkEYC1o62MAleX3nnT1If2dFVbHo,608
22
- pangea/dump_audit.py,sha256=1Je8D2fXwU4PWcZ-ZD4icfO3DNFvWqJkwsac4qFEhOo,7025
22
+ pangea/dump_audit.py,sha256=IevqaUUh7GDepdIW7slSxeZbkPrWIVbcX3sr4DgpJXI,7090
23
23
  pangea/exceptions.py,sha256=OBtzUECpNa6vNp8ySkHC-tm4QjFRCOAHBkMHqzAlOu8,5656
24
24
  pangea/file_uploader.py,sha256=4RQ44xt-faApC61nn2PlwHT7XYrJ4GeQA8Ug4tySEAg,1227
25
25
  pangea/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
- pangea/request.py,sha256=-XC1azZtymldOAQLbrSRzNX-h0B_lK-bQcinXyK0QKs,24410
26
+ pangea/request.py,sha256=zxFvqbeGYtxN29nS8SPPZKlcGCqdUtenxaq84pQ4cxs,24807
27
27
  pangea/response.py,sha256=lPAcYsF9Xg166CiyhCofVmQA-W4jevh0MQXxUa8Re68,7737
28
28
  pangea/services/__init__.py,sha256=-QsZxRzRq_V1x1lmS_mu4310MNm0DkM4r6g6rfVGnOc,340
29
- pangea/services/audit/audit.py,sha256=IFv7jANA8S2SypQVS47x94_Cr5Z9zSsL9Dp9eXw9RHk,39593
29
+ pangea/services/audit/audit.py,sha256=7-c9l7jyGtpG7SqRUMpqsAzcUDhMZ5izgPalxHXsUvM,39320
30
30
  pangea/services/audit/exceptions.py,sha256=bhVuYe4ammacOVxwg98CChxvwZf5FKgR2DcgqILOcwc,471
31
31
  pangea/services/audit/models.py,sha256=1h1B9eSYQMYG3f8WNi1UcDX2-impRrET_ErjJYUnj7M,14678
32
32
  pangea/services/audit/signing.py,sha256=5A4hvPtpfP2kMz8bsiiKUACriXbh5dv9gb_rbqiUtuI,5583
33
33
  pangea/services/audit/util.py,sha256=Zq1qvfeplYfhCP_ud5YMvntSB0UvnCdsuYbOzZkHbjg,7620
34
34
  pangea/services/authn/authn.py,sha256=cZKl2Ixc6HwHnkRecpSaAGTQUgaZUtxfLa0T3S03HMs,45478
35
35
  pangea/services/authn/models.py,sha256=HH5su6jx3O9AwVGzASXZ99-eIWjgXEP5LhIVdewM13s,22394
36
- pangea/services/authz.py,sha256=HfDnovAokzAHvnjYdOCwceM-1sCmzODnjNEbQBUSfo8,12222
37
- pangea/services/base.py,sha256=lwhHoe5Juy28Ir3Mfj2lHdM58gxZRaxa2SRFi4_DBRw,3453
36
+ pangea/services/authz.py,sha256=bB0ZEUuXLT7Xjs5kZef1hZK6Du6VUusHe5aekNaxamw,12746
37
+ pangea/services/base.py,sha256=43pWQcR9CeT4sGzgctF3Sy4M_h7DaUzkuZD2Z7CcDUU,3845
38
38
  pangea/services/embargo.py,sha256=9Wfku4td5ORaIENKmnGmS5jxJJIRfWp6Q51L36Jsy0I,3897
39
39
  pangea/services/file_scan.py,sha256=QiO80uKqB_BnAOiYQKznXfxpa5j40qqETE3-zBRT_QE,7813
40
- pangea/services/intel.py,sha256=CziBhC5K6O_kBXpD8zgJLpDtLHzBRgATGW4gHHFJT48,52039
41
- pangea/services/redact.py,sha256=ZYXkzEoriLJyCqaj5dqmgsC56mIz4T3pPToZ7TcNfhg,11465
42
- pangea/services/sanitize.py,sha256=XP5D4CcbCZfzgU567X6H5eFBWwZuYSsHdvsdrQAZekY,12767
40
+ pangea/services/intel.py,sha256=flVdK4WhllPutCkWh7H-MuBxMMz0f4Bl-fz2f-hPuWM,52679
41
+ pangea/services/redact.py,sha256=nst-mfxI7ewkDSSVVoY9cznMgb7EY9TGCjD6ZpPGvY4,12128
42
+ pangea/services/sanitize.py,sha256=D_R_XIe9FvRCX40b8b_3gouhAGduDdxI9bT2w-kiVHU,13444
43
43
  pangea/services/share/file_format.py,sha256=1svO1ee_aenA9zoO_AaU-Rk5Ulp7kcPOc_KwNoluyQE,2797
44
- pangea/services/share/share.py,sha256=iyP32UNWoT2F9C_65FiXWrVoNoO7dBjf0tVX1mF2Fz0,45644
44
+ pangea/services/share/share.py,sha256=8N4zXQJjkVDf2iR62pz6xpmxlbXRPNfHgYfOuEdNaZ8,46333
45
45
  pangea/services/vault/models/asymmetric.py,sha256=vspijmEvHm5WXri_fjOWfQc4maYyZfhDkLuaTM8-PZo,4991
46
46
  pangea/services/vault/models/common.py,sha256=PSZRFqHTUtEMJJGwywEFM2AU3aV8S-sbcoo3LLQ6uTc,17981
47
47
  pangea/services/vault/models/keys.py,sha256=duAuTiOby_D7MloRvN4gNj0P-b-jx9sdtplAWFxsShw,2786
@@ -49,8 +49,8 @@ pangea/services/vault/models/secret.py,sha256=ItGdkulM-SEySfcm4a5yGxMvo_omjC7kCh
49
49
  pangea/services/vault/models/symmetric.py,sha256=t8xCM1wGGKDBpOqTggFueO4-4-2IFmyxqcs7_PDr7U0,2562
50
50
  pangea/services/vault/vault.py,sha256=ow-Zm7PYzfWIfUcA4UNnpeL2DHfZM4C7inRDmNR3zQU,76196
51
51
  pangea/tools.py,sha256=2-Y4SAHWFv6Ocj42J_bWrVy27M5G3wi7a8LJn0dabHc,6427
52
- pangea/utils.py,sha256=KNkWK8o9j4iRDLUfAiobL-Hy5fcAxjl0DOFWXAqLK3E,5032
52
+ pangea/utils.py,sha256=dZ6MwFVEWXUgXvvDg-k6JnvVfsgslvtaBd7ez7afrqk,4983
53
53
  pangea/verify_audit.py,sha256=nSP17OzoSPdvezRExwfcf45H8ZPZnxZu-CbEp3qFJO0,17354
54
- pangea_sdk-5.1.0.dist-info/METADATA,sha256=5vdMrWyqNEhM-sU0GbglC7Y8VfLkTVDe5_Z32-TNT8E,7495
55
- pangea_sdk-5.1.0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
56
- pangea_sdk-5.1.0.dist-info/RECORD,,
54
+ pangea_sdk-5.2.0.dist-info/METADATA,sha256=0_T3gGYHKgvjYYIsf9UgTtm5B5tWAqLx8DtPqm4ny0E,7513
55
+ pangea_sdk-5.2.0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
56
+ pangea_sdk-5.2.0.dist-info/RECORD,,