pangea-sdk 5.0.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/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
  {
@@ -467,10 +480,8 @@ class PangeaRequest(PangeaRequestBase):
467
480
  ) -> PangeaResponse:
468
481
  # Send request
469
482
  try:
470
- # This should return 202 (AcceptedRequestException)
471
- resp = self.post(endpoint=endpoint, result_class=result_class, data=data, poll_result=False)
472
- raise pe.PresignedURLException("Should return 202", resp)
473
-
483
+ # This should return 202 (AcceptedRequestException) at least zero size file is sent
484
+ return self.post(endpoint=endpoint, result_class=result_class, data=data, poll_result=False)
474
485
  except pe.AcceptedRequestException as e:
475
486
  accepted_exception = e
476
487
  except Exception as e:
@@ -528,6 +539,9 @@ class PangeaRequest(PangeaRequestBase):
528
539
  raise AttributeError("files attribute should have at least 1 file")
529
540
 
530
541
  response = self.request_presigned_url(endpoint=endpoint, result_class=result_class, data=data)
542
+
543
+ if response.success: # This should only happen when uploading a zero bytes file
544
+ return response.raw_response
531
545
  if response.accepted_result is None:
532
546
  raise pe.PangeaException("No accepted_result field when requesting presigned url")
533
547
  if response.accepted_result.post_url is None:
pangea/response.py CHANGED
@@ -28,6 +28,7 @@ class AttachedFile(object):
28
28
  filename = self.filename if self.filename else "default_save_filename"
29
29
 
30
30
  filepath = os.path.join(dest_folder, filename)
31
+ filepath = self._find_available_file(filepath)
31
32
  directory = os.path.dirname(filepath)
32
33
  if not os.path.exists(directory):
33
34
  os.makedirs(directory)
@@ -35,6 +36,17 @@ class AttachedFile(object):
35
36
  with open(filepath, "wb") as file:
36
37
  file.write(self.file)
37
38
 
39
+ def _find_available_file(self, file_path):
40
+ base_name, ext = os.path.splitext(file_path)
41
+ counter = 1
42
+ while os.path.exists(file_path):
43
+ if ext:
44
+ file_path = f"{base_name}_{counter}{ext}"
45
+ else:
46
+ file_path = f"{base_name}_{counter}"
47
+ counter += 1
48
+ return file_path
49
+
38
50
 
39
51
  class TransferMethod(str, enum.Enum):
40
52
  """Transfer methods for uploading file data."""
@@ -6,4 +6,5 @@ from .file_scan import FileScan
6
6
  from .intel import DomainIntel, FileIntel, IpIntel, UrlIntel, UserIntel
7
7
  from .redact import Redact
8
8
  from .sanitize import Sanitize
9
+ from .share.share import Share
9
10
  from .vault.vault import Vault
@@ -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,
@@ -0,0 +1,170 @@
1
+ import enum
2
+
3
+
4
+ class FileFormat(str, enum.Enum):
5
+ F3G2 = "3G2"
6
+ F3GP = "3GP"
7
+ F3MF = "3MF"
8
+ F7Z = "7Z"
9
+ A = "A"
10
+ AAC = "AAC"
11
+ ACCDB = "ACCDB"
12
+ AIFF = "AIFF"
13
+ AMF = "AMF"
14
+ AMR = "AMR"
15
+ APE = "APE"
16
+ ASF = "ASF"
17
+ ATOM = "ATOM"
18
+ AU = "AU"
19
+ AVI = "AVI"
20
+ AVIF = "AVIF"
21
+ BIN = "BIN"
22
+ BMP = "BMP"
23
+ BPG = "BPG"
24
+ BZ2 = "BZ2"
25
+ CAB = "CAB"
26
+ CLASS = "CLASS"
27
+ CPIO = "CPIO"
28
+ CRX = "CRX"
29
+ CSV = "CSV"
30
+ DAE = "DAE"
31
+ DBF = "DBF"
32
+ DCM = "DCM"
33
+ DEB = "DEB"
34
+ DJVU = "DJVU"
35
+ DLL = "DLL"
36
+ DOC = "DOC"
37
+ DOCX = "DOCX"
38
+ DWG = "DWG"
39
+ EOT = "EOT"
40
+ EPUB = "EPUB"
41
+ EXE = "EXE"
42
+ FDF = "FDF"
43
+ FITS = "FITS"
44
+ FLAC = "FLAC"
45
+ FLV = "FLV"
46
+ GBR = "GBR"
47
+ GEOJSON = "GEOJSON"
48
+ GIF = "GIF"
49
+ GLB = "GLB"
50
+ GML = "GML"
51
+ GPX = "GPX"
52
+ GZ = "GZ"
53
+ HAR = "HAR"
54
+ HDR = "HDR"
55
+ HEIC = "HEIC"
56
+ HEIF = "HEIF"
57
+ HTML = "HTML"
58
+ ICNS = "ICNS"
59
+ ICO = "ICO"
60
+ ICS = "ICS"
61
+ ISO = "ISO"
62
+ JAR = "JAR"
63
+ JP2 = "JP2"
64
+ JPF = "JPF"
65
+ JPG = "JPG"
66
+ JPM = "JPM"
67
+ JS = "JS"
68
+ JSON = "JSON"
69
+ JXL = "JXL"
70
+ JXR = "JXR"
71
+ KML = "KML"
72
+ LIT = "LIT"
73
+ LNK = "LNK"
74
+ LUA = "LUA"
75
+ LZ = "LZ"
76
+ M3U = "M3U"
77
+ M4A = "M4A"
78
+ MACHO = "MACHO"
79
+ MDB = "MDB"
80
+ MIDI = "MIDI"
81
+ MKV = "MKV"
82
+ MOBI = "MOBI"
83
+ MOV = "MOV"
84
+ MP3 = "MP3"
85
+ MP4 = "MP4"
86
+ MPC = "MPC"
87
+ MPEG = "MPEG"
88
+ MQV = "MQV"
89
+ MRC = "MRC"
90
+ MSG = "MSG"
91
+ MSI = "MSI"
92
+ NDJSON = "NDJSON"
93
+ NES = "NES"
94
+ ODC = "ODC"
95
+ ODF = "ODF"
96
+ ODG = "ODG"
97
+ ODP = "ODP"
98
+ ODS = "ODS"
99
+ ODT = "ODT"
100
+ OGA = "OGA"
101
+ OGV = "OGV"
102
+ OTF = "OTF"
103
+ OTG = "OTG"
104
+ OTP = "OTP"
105
+ OTS = "OTS"
106
+ OTT = "OTT"
107
+ OWL = "OWL"
108
+ P7S = "P7S"
109
+ PAT = "PAT"
110
+ PDF = "PDF"
111
+ PHP = "PHP"
112
+ PL = "PL"
113
+ PNG = "PNG"
114
+ PPT = "PPT"
115
+ PPTX = "PPTX"
116
+ PS = "PS"
117
+ PSD = "PSD"
118
+ PUB = "PUB"
119
+ PY = "PY"
120
+ QCP = "QCP"
121
+ RAR = "RAR"
122
+ RMVB = "RMVB"
123
+ RPM = "RPM"
124
+ RSS = "RSS"
125
+ RTF = "RTF"
126
+ SHP = "SHP"
127
+ SHX = "SHX"
128
+ SO = "SO"
129
+ SQLITE = "SQLITE"
130
+ SRT = "SRT"
131
+ SVG = "SVG"
132
+ SWF = "SWF"
133
+ SXC = "SXC"
134
+ TAR = "TAR"
135
+ TCL = "TCL"
136
+ TCX = "TCX"
137
+ TIFF = "TIFF"
138
+ TORRENT = "TORRENT"
139
+ TSV = "TSV"
140
+ TTC = "TTC"
141
+ TTF = "TTF"
142
+ TXT = "TXT"
143
+ VCF = "VCF"
144
+ VOC = "VOC"
145
+ VTT = "VTT"
146
+ WARC = "WARC"
147
+ WASM = "WASM"
148
+ WAV = "WAV"
149
+ WEBM = "WEBM"
150
+ WEBP = "WEBP"
151
+ WOFF = "WOFF"
152
+ WOFF2 = "WOFF2"
153
+ X3D = "X3D"
154
+ XAR = "XAR"
155
+ XCF = "XCF"
156
+ XFDF = "XFDF"
157
+ XLF = "XLF"
158
+ XLS = "XLS"
159
+ XLSX = "XLSX"
160
+ XML = "XML"
161
+ XPM = "XPM"
162
+ XZ = "XZ"
163
+ ZIP = "ZIP"
164
+ ZST = "ZST"
165
+
166
+ def __str__(self):
167
+ return str(self.value)
168
+
169
+ def __repr__(self):
170
+ return str(self.value)