pangea-sdk 4.2.0__py3-none-any.whl → 4.4.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,6 +1,7 @@
1
- __version__ = "4.2.0"
1
+ __version__ = "4.4.0"
2
2
 
3
3
  from pangea.asyncio.request import PangeaRequestAsync
4
4
  from pangea.config import PangeaConfig
5
+ from pangea.file_uploader import FileUploader
5
6
  from pangea.request import PangeaRequest
6
7
  from pangea.response import PangeaResponse
@@ -0,0 +1 @@
1
+ from .file_uploader import FileUploaderAsync
@@ -0,0 +1,39 @@
1
+ # Copyright 2022 Pangea Cyber Corporation
2
+ # Author: Pangea Cyber Corporation
3
+ import io
4
+ import logging
5
+ from typing import Dict, Optional
6
+
7
+ from pangea.asyncio.request import PangeaRequestAsync
8
+ from pangea.request import PangeaConfig
9
+ from pangea.response import TransferMethod
10
+
11
+
12
+ class FileUploaderAsync:
13
+ def __init__(self) -> None:
14
+ self.logger = logging.getLogger("pangea")
15
+ self._request: PangeaRequestAsync = PangeaRequestAsync(
16
+ config=PangeaConfig(),
17
+ token="",
18
+ service="FileUploader",
19
+ logger=self.logger,
20
+ )
21
+
22
+ async def upload_file(
23
+ self,
24
+ url: str,
25
+ file: io.BufferedReader,
26
+ transfer_method: TransferMethod = TransferMethod.PUT_URL,
27
+ file_details: Optional[Dict] = None,
28
+ ) -> None:
29
+ if transfer_method == TransferMethod.PUT_URL:
30
+ files = [("file", ("filename", file, "application/octet-stream"))]
31
+ await self._request.put_presigned_url(url=url, files=files)
32
+ elif transfer_method == TransferMethod.POST_URL:
33
+ files = [("file", ("filename", file, "application/octet-stream"))]
34
+ await self._request.post_presigned_url(url=url, data=file_details, files=files) # type: ignore[arg-type]
35
+ else:
36
+ raise ValueError(f"Transfer method not supported: {transfer_method}")
37
+
38
+ async def close(self) -> None:
39
+ await self._request.session.close()
pangea/asyncio/request.py CHANGED
@@ -1,10 +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 asyncio
5
6
  import json
6
7
  import time
7
- from typing import Dict, List, Optional, Sequence, Tuple, Type, Union
8
+ from typing import Any, Dict, List, Optional, Sequence, Tuple, Type, Union
8
9
 
9
10
  import aiohttp
10
11
  from aiohttp import FormData
@@ -32,7 +33,7 @@ class PangeaRequestAsync(PangeaRequestBase):
32
33
  endpoint: str,
33
34
  result_class: Type[TResult],
34
35
  data: Union[str, Dict] = {},
35
- files: List[Tuple] = [],
36
+ files: Optional[List[Tuple]] = None,
36
37
  poll_result: bool = True,
37
38
  url: Optional[str] = None,
38
39
  ) -> PangeaResponse[TResult]:
@@ -211,13 +212,14 @@ class PangeaRequestAsync(PangeaRequestBase):
211
212
  else:
212
213
  raise pe.DownloadFileError(f"Failed to download file. Status: {response.status}", await response.text())
213
214
 
214
- async def _get_pangea_json(self, reader: aiohttp.MultipartReader) -> Optional[Dict]:
215
+ async def _get_pangea_json(self, reader: aiohttp.multipart.MultipartResponseWrapper) -> Optional[Dict[str, Any]]:
215
216
  # Iterate through parts
216
217
  async for part in reader:
217
- return await part.json()
218
+ if isinstance(part, aiohttp.BodyPartReader):
219
+ return await part.json()
218
220
  return None
219
221
 
220
- async def _get_attached_files(self, reader: aiohttp.MultipartReader) -> List[AttachedFile]:
222
+ async def _get_attached_files(self, reader: aiohttp.multipart.MultipartResponseWrapper) -> List[AttachedFile]:
221
223
  files = []
222
224
  i = 0
223
225
 
@@ -228,7 +230,7 @@ class PangeaRequestAsync(PangeaRequestBase):
228
230
  if name is None:
229
231
  name = f"default_file_name_{i}"
230
232
  i += 1
231
- files.append(AttachedFile(name, await part.read(), content_type))
233
+ files.append(AttachedFile(name, await part.read(), content_type)) # type: ignore[union-attr]
232
234
 
233
235
  return files
234
236
 
@@ -236,13 +238,12 @@ class PangeaRequestAsync(PangeaRequestBase):
236
238
  # Parse the multipart response
237
239
  multipart_reader = aiohttp.MultipartReader.from_response(resp)
238
240
 
239
- pangea_json = await self._get_pangea_json(multipart_reader) # type: ignore[arg-type]
241
+ pangea_json = await self._get_pangea_json(multipart_reader)
240
242
  self.logger.debug(
241
243
  json.dumps({"service": self.service, "action": "multipart response", "response": pangea_json})
242
244
  )
243
245
 
244
- multipart_reader = multipart_reader.__aiter__()
245
- attached_files = await self._get_attached_files(multipart_reader) # type: ignore[arg-type]
246
+ attached_files = await self._get_attached_files(multipart_reader)
246
247
  return MultipartResponse(pangea_json, attached_files) # type: ignore[arg-type]
247
248
 
248
249
  async def _http_post(
@@ -250,7 +251,7 @@ class PangeaRequestAsync(PangeaRequestBase):
250
251
  url: str,
251
252
  headers: Dict = {},
252
253
  data: Union[str, Dict] = {},
253
- files: List[Tuple] = [],
254
+ files: Optional[List[Tuple]] = [],
254
255
  presigned_url_post: bool = False,
255
256
  ) -> aiohttp.ClientResponse:
256
257
  if files:
@@ -5,4 +5,5 @@ from .embargo import EmbargoAsync
5
5
  from .file_scan import FileScanAsync
6
6
  from .intel import DomainIntelAsync, FileIntelAsync, IpIntelAsync, UrlIntelAsync, UserIntelAsync
7
7
  from .redact import RedactAsync
8
+ from .sanitize import SanitizeAsync
8
9
  from .vault import VaultAsync
@@ -1,7 +1,7 @@
1
1
  # Copyright 2022 Pangea Cyber Corporation
2
2
  # Author: Pangea Cyber Corporation
3
3
 
4
- from typing import Dict, List, Optional, Union
4
+ from typing import Any, Dict, List, Optional
5
5
 
6
6
  from pangea.asyncio.services.base import ServiceBaseAsync
7
7
  from pangea.response import PangeaResponse
@@ -162,7 +162,7 @@ class AuthZAsync(ServiceBaseAsync):
162
162
  action: str,
163
163
  subject: Subject,
164
164
  debug: Optional[bool] = None,
165
- attributes: Optional[Dict[str, Union[int, str]]] = None,
165
+ attributes: Optional[Dict[str, Any]] = None,
166
166
  ) -> PangeaResponse[CheckResult]:
167
167
  """Perform a check request.
168
168
 
@@ -173,7 +173,7 @@ class AuthZAsync(ServiceBaseAsync):
173
173
  action (str): The action to check.
174
174
  subject (Subject): The subject to check.
175
175
  debug (Optional[bool]): Setting this value to True will provide a detailed analysis of the check.
176
- attributes (Optional[Dict[str, Union[int, str]]]): Additional attributes for the check.
176
+ attributes (Optional[Dict[str, Any]]): Additional attributes for the check.
177
177
 
178
178
  Raises:
179
179
  PangeaAPIException: If an API Error happens.
@@ -195,7 +195,9 @@ class AuthZAsync(ServiceBaseAsync):
195
195
  input_data = CheckRequest(resource=resource, action=action, subject=subject, debug=debug, attributes=attributes)
196
196
  return await self.request.post("v1/check", CheckResult, data=input_data.model_dump(exclude_none=True))
197
197
 
198
- async def list_resources(self, type: str, action: str, subject: Subject) -> PangeaResponse[ListResourcesResult]:
198
+ async def list_resources(
199
+ self, type: str, action: str, subject: Subject, attributes: Optional[Dict[str, Any]] = None
200
+ ) -> PangeaResponse[ListResourcesResult]:
199
201
  """List resources.
200
202
 
201
203
  Given a type, action, and subject, list all the resources in the
@@ -205,6 +207,7 @@ class AuthZAsync(ServiceBaseAsync):
205
207
  type (str): The type to filter resources.
206
208
  action (str): The action to filter resources.
207
209
  subject (Subject): The subject to filter resources.
210
+ attributes (Optional[Dict[str, Any]]): A JSON object of attribute data.
208
211
 
209
212
  Raises:
210
213
  PangeaAPIException: If an API Error happens.
@@ -222,12 +225,14 @@ class AuthZAsync(ServiceBaseAsync):
222
225
  )
223
226
  """
224
227
 
225
- input_data = ListResourcesRequest(type=type, action=action, subject=subject)
228
+ input_data = ListResourcesRequest(type=type, action=action, subject=subject, attributes=attributes)
226
229
  return await self.request.post(
227
230
  "v1/list-resources", ListResourcesResult, data=input_data.model_dump(exclude_none=True)
228
231
  )
229
232
 
230
- async def list_subjects(self, resource: Resource, action: str) -> PangeaResponse[ListSubjectsResult]:
233
+ async def list_subjects(
234
+ self, resource: Resource, action: str, attributes: Optional[Dict[str, Any]] = None
235
+ ) -> PangeaResponse[ListSubjectsResult]:
231
236
  """List subjects.
232
237
 
233
238
  Given a resource and an action, return the list of subjects who have
@@ -236,6 +241,7 @@ class AuthZAsync(ServiceBaseAsync):
236
241
  Args:
237
242
  resource (Resource): The resource to filter subjects.
238
243
  action (str): The action to filter subjects.
244
+ attributes (Optional[Dict[str, Any]]): A JSON object of attribute data.
239
245
 
240
246
  Raises:
241
247
  PangeaAPIException: If an API Error happens.
@@ -252,7 +258,7 @@ class AuthZAsync(ServiceBaseAsync):
252
258
  )
253
259
  """
254
260
 
255
- input_data = ListSubjectsRequest(resource=resource, action=action)
261
+ input_data = ListSubjectsRequest(resource=resource, action=action, attributes=attributes)
256
262
  return await self.request.post(
257
263
  "v1/list-subjects", ListSubjectsResult, data=input_data.model_dump(exclude_none=True)
258
264
  )
@@ -59,12 +59,14 @@ class FileScanAsync(ServiceBaseAsync):
59
59
  OperationId: file_scan_post_v1_scan
60
60
 
61
61
  Args:
62
- file (io.BufferedReader, optional): file to be scanned (should be opened with read permissions and in binary format)
63
62
  file_path (str, optional): filepath to be opened and scanned
63
+ file (io.BufferedReader, optional): file to be scanned (should be opened with read permissions and in binary format)
64
64
  verbose (bool, optional): Echo the API parameters in the response
65
65
  raw (bool, optional): Include raw data from this provider
66
66
  provider (str, optional): Scan file using this provider
67
67
  sync_call (bool, optional): True to wait until server returns a result, False to return immediately and retrieve result asynchronously
68
+ transfer_method (TransferMethod, optional): Transfer method used to upload the file data.
69
+ source_url (str, optional): A URL where the Pangea APIs can fetch the contents of the input file.
68
70
 
69
71
  Raises:
70
72
  PangeaAPIException: If an API Error happens
@@ -77,7 +79,7 @@ class FileScanAsync(ServiceBaseAsync):
77
79
  Examples:
78
80
  try:
79
81
  with open("./path/to/file.pdf", "rb") as f:
80
- response = client.file_scan(file=f, verbose=True, provider="crowdstrike")
82
+ response = await client.file_scan(file=f, verbose=True, provider="crowdstrike")
81
83
  print(f"Response: {response.result}")
82
84
  except pe.PangeaAPIException as e:
83
85
  print(f"Request Error: {e.response.summary}")
@@ -85,6 +87,15 @@ class FileScanAsync(ServiceBaseAsync):
85
87
  print(f"\\t{err.detail} \\n")
86
88
  """
87
89
 
90
+ if transfer_method == TransferMethod.SOURCE_URL and source_url is None:
91
+ raise ValueError("`source_url` argument is required when using `TransferMethod.SOURCE_URL`.")
92
+
93
+ if source_url is not None and transfer_method != TransferMethod.SOURCE_URL:
94
+ raise ValueError(
95
+ "`transfer_method` should be `TransferMethod.SOURCE_URL` when using the `source_url` argument."
96
+ )
97
+
98
+ files: Optional[List[Tuple]] = None
88
99
  if file or file_path:
89
100
  if file_path:
90
101
  file = open(file_path, "rb")
@@ -95,9 +106,9 @@ class FileScanAsync(ServiceBaseAsync):
95
106
  size = params.size
96
107
  else:
97
108
  crc, sha, size = None, None, None
98
- files: List[Tuple] = [("upload", ("filename", file, "application/octet-stream"))]
99
- else:
100
- raise ValueError("Need to set file_path or file arguments")
109
+ files = [("upload", ("filename", file, "application/octet-stream"))]
110
+ elif source_url is None:
111
+ raise ValueError("Need to set one of `file_path`, `file`, or `source_url` arguments.")
101
112
 
102
113
  input = m.FileScanRequest(
103
114
  verbose=verbose,
@@ -110,7 +121,11 @@ class FileScanAsync(ServiceBaseAsync):
110
121
  source_url=source_url,
111
122
  )
112
123
  data = input.model_dump(exclude_none=True)
113
- return await self.request.post("v1/scan", m.FileScanResult, data=data, files=files, poll_result=sync_call)
124
+ try:
125
+ return await self.request.post("v1/scan", m.FileScanResult, data=data, files=files, poll_result=sync_call)
126
+ finally:
127
+ if file_path and file:
128
+ file.close()
114
129
 
115
130
  async def request_upload_url(
116
131
  self,
@@ -846,6 +846,7 @@ class UserIntelAsync(ServiceBaseAsync):
846
846
  usernames: Optional[List[str]] = None,
847
847
  ips: Optional[List[str]] = None,
848
848
  phone_numbers: Optional[List[str]] = None,
849
+ domains: Optional[List[str]] = None,
849
850
  start: Optional[str] = None,
850
851
  end: Optional[str] = None,
851
852
  verbose: Optional[bool] = None,
@@ -864,6 +865,7 @@ class UserIntelAsync(ServiceBaseAsync):
864
865
  usernames (List[str]): An username's list to search for
865
866
  ips (List[str]): An ip's list to search for
866
867
  phone_numbers (List[str]): A phone number's list to search for. minLength: 7, maxLength: 15.
868
+ domains (List[str]): Search for user under these domains.
867
869
  start (str): Earliest date for search
868
870
  end (str): Latest date for search
869
871
  verbose (bool, optional): Echo the API parameters in the response
@@ -886,6 +888,7 @@ class UserIntelAsync(ServiceBaseAsync):
886
888
  phone_numbers=phone_numbers,
887
889
  usernames=usernames,
888
890
  ips=ips,
891
+ domains=domains,
889
892
  provider=provider,
890
893
  start=start,
891
894
  end=end,
@@ -0,0 +1,193 @@
1
+ # Copyright 2022 Pangea Cyber Corporation
2
+ # Author: Pangea Cyber Corporation
3
+ import io
4
+ from typing import List, Optional, Tuple
5
+
6
+ import pangea.services.sanitize as m
7
+ from pangea.asyncio.services.base import ServiceBaseAsync
8
+ from pangea.response import PangeaResponse, TransferMethod
9
+ from pangea.utils import FileUploadParams, get_file_upload_params
10
+
11
+
12
+ class SanitizeAsync(ServiceBaseAsync):
13
+ """Sanitize service client.
14
+
15
+ Examples:
16
+ import os
17
+
18
+ # Pangea SDK
19
+ from pangea.config import PangeaConfig
20
+ from pangea.asyncio.services import Sanitize
21
+
22
+ PANGEA_SANITIZE_TOKEN = os.getenv("PANGEA_SANITIZE_TOKEN")
23
+ config = PangeaConfig(domain="pangea.cloud")
24
+
25
+ sanitize = Sanitize(token=PANGEA_SANITIZE_TOKEN, config=config)
26
+ """
27
+
28
+ service_name = "sanitize"
29
+
30
+ async def sanitize(
31
+ self,
32
+ transfer_method: TransferMethod = TransferMethod.POST_URL,
33
+ file_path: Optional[str] = None,
34
+ file: Optional[io.BufferedReader] = None,
35
+ source_url: Optional[str] = None,
36
+ share_id: Optional[str] = None,
37
+ file_scan: Optional[m.SanitizeFile] = None,
38
+ content: Optional[m.SanitizeContent] = None,
39
+ share_output: Optional[m.SanitizeShareOutput] = None,
40
+ size: Optional[int] = None,
41
+ crc32c: Optional[str] = None,
42
+ sha256: Optional[str] = None,
43
+ uploaded_file_name: Optional[str] = None,
44
+ sync_call: bool = True,
45
+ ) -> PangeaResponse[m.SanitizeResult]:
46
+ """
47
+ Sanitize
48
+
49
+ Apply file sanitization actions according to specified rules.
50
+
51
+ OperationId: sanitize_post_v1_sanitize
52
+
53
+ Args:
54
+ transfer_method: The transfer method used to upload the file data.
55
+ file_path: Path to file to sanitize.
56
+ file: File to sanitize.
57
+ source_url: A URL where the file to be sanitized can be downloaded.
58
+ share_id: A Pangea Secure Share ID where the file to be sanitized is stored.
59
+ file_scan: Options for File Scan.
60
+ content: Options for how the file should be sanitized.
61
+ share_output: Integration with Secure Share.
62
+ size: The size (in bytes) of the file. If the upload doesn't match, the call will fail.
63
+ crc32c: The CRC32C hash of the file data, which will be verified by the server if provided.
64
+ sha256: The hexadecimal-encoded SHA256 hash of the file data, which will be verified by the server if provided.
65
+ uploaded_file_name: Name of the user-uploaded file, required for `TransferMethod.PUT_URL` and `TransferMethod.POST_URL`.
66
+ sync_call: Whether or not to poll on HTTP/202.
67
+
68
+ Raises:
69
+ PangeaAPIException: If an API error happens.
70
+
71
+ Returns:
72
+ The sanitized file and information on the sanitization that was
73
+ performed.
74
+
75
+ Examples:
76
+ with open("/path/to/file.pdf", "rb") as f:
77
+ response = await sanitize.sanitize(
78
+ file=f,
79
+ transfer_method=TransferMethod.POST_URL,
80
+ uploaded_file_name="uploaded_file",
81
+ )
82
+ """
83
+
84
+ if transfer_method == TransferMethod.SOURCE_URL and source_url is None:
85
+ raise ValueError("`source_url` argument is required when using `TransferMethod.SOURCE_URL`.")
86
+
87
+ if source_url is not None and transfer_method != TransferMethod.SOURCE_URL:
88
+ raise ValueError(
89
+ "`transfer_method` should be `TransferMethod.SOURCE_URL` when using the `source_url` argument."
90
+ )
91
+
92
+ files: Optional[List[Tuple]] = None
93
+ if file or file_path:
94
+ if file_path:
95
+ file = open(file_path, "rb")
96
+ if transfer_method == TransferMethod.POST_URL and (sha256 is None or crc32c is None or size is None):
97
+ params = get_file_upload_params(file) # type: ignore[arg-type]
98
+ crc32c = params.crc_hex if crc32c is None else crc32c
99
+ sha256 = params.sha256_hex if sha256 is None else sha256
100
+ size = params.size if size is None else size
101
+ else:
102
+ crc32c, sha256, size = None, None, None
103
+ files = [("upload", ("filename", file, "application/octet-stream"))]
104
+ elif source_url is None:
105
+ raise ValueError("Need to set one of `file_path`, `file`, or `source_url` arguments.")
106
+
107
+ input = m.SanitizeRequest(
108
+ transfer_method=transfer_method,
109
+ source_url=source_url,
110
+ share_id=share_id,
111
+ file=file_scan,
112
+ content=content,
113
+ share_output=share_output,
114
+ crc32c=crc32c,
115
+ sha256=sha256,
116
+ size=size,
117
+ uploaded_file_name=uploaded_file_name,
118
+ )
119
+ data = input.model_dump(exclude_none=True)
120
+ try:
121
+ return await self.request.post(
122
+ "v1/sanitize", m.SanitizeResult, data=data, files=files, poll_result=sync_call
123
+ )
124
+ finally:
125
+ if file_path and file is not None:
126
+ file.close()
127
+
128
+ async def request_upload_url(
129
+ self,
130
+ transfer_method: TransferMethod = TransferMethod.PUT_URL,
131
+ params: Optional[FileUploadParams] = None,
132
+ file_scan: Optional[m.SanitizeFile] = None,
133
+ content: Optional[m.SanitizeContent] = None,
134
+ share_output: Optional[m.SanitizeShareOutput] = None,
135
+ size: Optional[int] = None,
136
+ crc32c: Optional[str] = None,
137
+ sha256: Optional[str] = None,
138
+ uploaded_file_name: Optional[str] = None,
139
+ ) -> PangeaResponse[m.SanitizeResult]:
140
+ """
141
+ Sanitize via presigned URL
142
+
143
+ Apply file sanitization actions according to specified rules via a
144
+ [presigned URL](https://pangea.cloud/docs/api/transfer-methods).
145
+
146
+ OperationId: sanitize_post_v1_sanitize 2
147
+
148
+ Args:
149
+ transfer_method: The transfer method used to upload the file data.
150
+ params: File upload parameters.
151
+ file_scan: Options for File Scan.
152
+ content: Options for how the file should be sanitized.
153
+ share_output: Integration with Secure Share.
154
+ size: The size (in bytes) of the file. If the upload doesn't match, the call will fail.
155
+ crc32c: The CRC32C hash of the file data, which will be verified by the server if provided.
156
+ sha256: The hexadecimal-encoded SHA256 hash of the file data, which will be verified by the server if provided.
157
+ uploaded_file_name: Name of the user-uploaded file, required for `TransferMethod.PUT_URL` and `TransferMethod.POST_URL`.
158
+
159
+ Raises:
160
+ PangeaAPIException: If an API error happens.
161
+
162
+ Returns:
163
+ A presigned URL.
164
+
165
+ Examples:
166
+ presignedUrl = await sanitize.request_upload_url(
167
+ transfer_method=TransferMethod.PUT_URL,
168
+ uploaded_file_name="uploaded_file",
169
+ )
170
+
171
+ # Upload file to `presignedUrl.accepted_result.put_url`.
172
+
173
+ # Poll for Sanitize's result.
174
+ response: PangeaResponse[SanitizeResult] = await sanitize.poll_result(response=presignedUrl)
175
+ """
176
+
177
+ input = m.SanitizeRequest(
178
+ transfer_method=transfer_method,
179
+ file=file_scan,
180
+ content=content,
181
+ share_output=share_output,
182
+ crc32c=crc32c,
183
+ sha256=sha256,
184
+ size=size,
185
+ uploaded_file_name=uploaded_file_name,
186
+ )
187
+ if params is not None and (transfer_method == TransferMethod.POST_URL):
188
+ input.crc32c = params.crc_hex
189
+ input.sha256 = params.sha256_hex
190
+ input.size = params.size
191
+
192
+ data = input.model_dump(exclude_none=True)
193
+ return await self.request.request_presigned_url("v1/sanitize", m.SanitizeResult, data=data)
@@ -0,0 +1,35 @@
1
+ # Copyright 2022 Pangea Cyber Corporation
2
+ # Author: Pangea Cyber Corporation
3
+ import io
4
+ import logging
5
+ from typing import Dict, Optional
6
+
7
+ from pangea.request import PangeaConfig, PangeaRequest
8
+ from pangea.response import TransferMethod
9
+
10
+
11
+ class FileUploader:
12
+ def __init__(self):
13
+ self.logger = logging.getLogger("pangea")
14
+ self._request = PangeaRequest(
15
+ config=PangeaConfig(),
16
+ token="",
17
+ service="FileUploader",
18
+ logger=self.logger,
19
+ )
20
+
21
+ def upload_file(
22
+ self,
23
+ url: str,
24
+ file: io.BufferedReader,
25
+ transfer_method: TransferMethod = TransferMethod.PUT_URL,
26
+ file_details: Optional[Dict] = None,
27
+ ):
28
+ if transfer_method == TransferMethod.PUT_URL:
29
+ files = [("file", ("filename", file, "application/octet-stream"))]
30
+ self._request.put_presigned_url(url=url, files=files)
31
+ elif transfer_method == TransferMethod.POST_URL:
32
+ files = [("file", ("filename", file, "application/octet-stream"))]
33
+ self._request.post_presigned_url(url=url, data=file_details, files=files)
34
+ else:
35
+ raise ValueError(f"Transfer method not supported: {transfer_method}")
pangea/request.py CHANGED
@@ -182,7 +182,7 @@ class PangeaRequestBase(object):
182
182
  elif status == ResponseStatus.VAULT_ITEM_NOT_FOUND.value:
183
183
  raise pe.VaultItemNotFound(summary, response)
184
184
  elif status == ResponseStatus.NOT_FOUND.value:
185
- raise pe.NotFound(str(response.raw_response.url) if response.raw_response is not None else "", response) # type: ignore[arg-type]
185
+ raise pe.NotFound(str(response.raw_response.url) if response.raw_response is not None else "", response)
186
186
  elif status == ResponseStatus.INTERNAL_SERVER_ERROR.value:
187
187
  raise pe.InternalServerError(response)
188
188
  elif status == ResponseStatus.ACCEPTED.value:
pangea/response.py CHANGED
@@ -37,10 +37,24 @@ class AttachedFile(object):
37
37
 
38
38
 
39
39
  class TransferMethod(str, enum.Enum):
40
+ """Transfer methods for uploading file data."""
41
+
40
42
  MULTIPART = "multipart"
41
43
  POST_URL = "post-url"
42
44
  PUT_URL = "put-url"
43
45
  SOURCE_URL = "source-url"
46
+ """
47
+ A `source-url` is a caller-specified URL where the Pangea APIs can fetch the
48
+ contents of the input file. When calling a Pangea API with a
49
+ `transfer_method` of `source-url`, you must also specify a `source_url`
50
+ input parameter that provides a URL to the input file. The source URL can be
51
+ a presigned URL created by the caller, and it will be used to download the
52
+ content of the input file. The `source-url` transfer method is useful when
53
+ you already have a file in your storage and can provide a URL from which
54
+ Pangea API can fetch the input file—there is no need to transfer it to
55
+ Pangea with a separate POST or PUT request.
56
+ """
57
+
44
58
  DEST_URL = "dest-url"
45
59
 
46
60
  def __str__(self):
@@ -222,4 +236,4 @@ class PangeaResponse(ResponseHeader, Generic[T]):
222
236
 
223
237
  @property
224
238
  def url(self) -> str:
225
- return str(self.raw_response.url) # type: ignore[arg-type,union-attr]
239
+ return str(self.raw_response.url) # type: ignore[union-attr]
@@ -5,4 +5,5 @@ from .embargo import Embargo
5
5
  from .file_scan import FileScan
6
6
  from .intel import DomainIntel, FileIntel, IpIntel, UrlIntel, UserIntel
7
7
  from .redact import Redact
8
+ from .sanitize import Sanitize
8
9
  from .vault.vault import Vault
@@ -96,7 +96,7 @@ class Signer:
96
96
 
97
97
  for func in (serialization.load_pem_private_key, serialization.load_ssh_private_key):
98
98
  try:
99
- return func(private_key, None) # type: ignore[operator]
99
+ return func(private_key, None)
100
100
  except exceptions.UnsupportedAlgorithm as e:
101
101
  raise e
102
102
  except ValueError:
pangea/services/authz.py CHANGED
@@ -132,6 +132,7 @@ class ListResourcesRequest(APIRequestModel):
132
132
  type: str
133
133
  action: str
134
134
  subject: Subject
135
+ attributes: Optional[Dict[str, Any]] = None
135
136
 
136
137
 
137
138
  class ListResourcesResult(PangeaResponseResult):
@@ -141,6 +142,7 @@ class ListResourcesResult(PangeaResponseResult):
141
142
  class ListSubjectsRequest(APIRequestModel):
142
143
  resource: Resource
143
144
  action: str
145
+ attributes: Optional[Dict[str, Any]] = None
144
146
 
145
147
 
146
148
  class ListSubjectsResult(PangeaResponseResult):
@@ -278,7 +280,7 @@ class AuthZ(ServiceBase):
278
280
  action: str,
279
281
  subject: Subject,
280
282
  debug: Optional[bool] = None,
281
- attributes: Optional[Dict[str, Union[int, str]]] = None,
283
+ attributes: Optional[Dict[str, Any]] = None,
282
284
  ) -> PangeaResponse[CheckResult]:
283
285
  """Perform a check request.
284
286
 
@@ -289,7 +291,7 @@ class AuthZ(ServiceBase):
289
291
  action (str): The action to check.
290
292
  subject (Subject): The subject to check.
291
293
  debug (Optional[bool]): Setting this value to True will provide a detailed analysis of the check.
292
- attributes (Optional[Dict[str, Union[int, str]]]): Additional attributes for the check.
294
+ attributes (Optional[Dict[str, Any]]): Additional attributes for the check.
293
295
 
294
296
  Raises:
295
297
  PangeaAPIException: If an API Error happens.
@@ -311,7 +313,9 @@ class AuthZ(ServiceBase):
311
313
  input_data = CheckRequest(resource=resource, action=action, subject=subject, debug=debug, attributes=attributes)
312
314
  return self.request.post("v1/check", CheckResult, data=input_data.model_dump(exclude_none=True))
313
315
 
314
- def list_resources(self, type: str, action: str, subject: Subject) -> PangeaResponse[ListResourcesResult]:
316
+ def list_resources(
317
+ self, type: str, action: str, subject: Subject, attributes: Optional[Dict[str, Any]] = None
318
+ ) -> PangeaResponse[ListResourcesResult]:
315
319
  """List resources.
316
320
 
317
321
  Given a type, action, and subject, list all the resources in the
@@ -321,6 +325,7 @@ class AuthZ(ServiceBase):
321
325
  type (str): The type to filter resources.
322
326
  action (str): The action to filter resources.
323
327
  subject (Subject): The subject to filter resources.
328
+ attributes (Optional[Dict[str, Any]]): A JSON object of attribute data.
324
329
 
325
330
  Raises:
326
331
  PangeaAPIException: If an API Error happens.
@@ -338,12 +343,14 @@ class AuthZ(ServiceBase):
338
343
  )
339
344
  """
340
345
 
341
- input_data = ListResourcesRequest(type=type, action=action, subject=subject)
346
+ input_data = ListResourcesRequest(type=type, action=action, subject=subject, attributes=attributes)
342
347
  return self.request.post(
343
348
  "v1/list-resources", ListResourcesResult, data=input_data.model_dump(exclude_none=True)
344
349
  )
345
350
 
346
- def list_subjects(self, resource: Resource, action: str) -> PangeaResponse[ListSubjectsResult]:
351
+ def list_subjects(
352
+ self, resource: Resource, action: str, attributes: Optional[Dict[str, Any]] = None
353
+ ) -> PangeaResponse[ListSubjectsResult]:
347
354
  """List subjects.
348
355
 
349
356
  Given a resource and an action, return the list of subjects who have
@@ -352,6 +359,7 @@ class AuthZ(ServiceBase):
352
359
  Args:
353
360
  resource (Resource): The resource to filter subjects.
354
361
  action (str): The action to filter subjects.
362
+ attributes (Optional[Dict[str, Any]]): A JSON object of attribute data.
355
363
 
356
364
  Raises:
357
365
  PangeaAPIException: If an API Error happens.
@@ -368,5 +376,5 @@ class AuthZ(ServiceBase):
368
376
  )
369
377
  """
370
378
 
371
- input_data = ListSubjectsRequest(resource=resource, action=action)
379
+ input_data = ListSubjectsRequest(resource=resource, action=action, attributes=attributes)
372
380
  return self.request.post("v1/list-subjects", ListSubjectsResult, data=input_data.model_dump(exclude_none=True))
@@ -11,22 +11,25 @@ from pangea.utils import FileUploadParams, get_file_upload_params
11
11
 
12
12
 
13
13
  class FileScanRequest(APIRequestModel):
14
- """
15
- File Scan request data
16
-
17
- provider (str, optional): Provider of the information. Default provider defined by the configuration.
18
- verbose (bool, optional): Echo back the parameters of the API in the response
19
- raw (bool, optional): Return additional details from the provider.
20
- """
14
+ """File Scan request data."""
21
15
 
22
16
  verbose: Optional[bool] = None
17
+ """Echo back the parameters of the API in the response."""
18
+
23
19
  raw: Optional[bool] = None
20
+ """Return additional details from the provider."""
21
+
24
22
  provider: Optional[str] = None
23
+ """Provider of the information. Default provider defined by the configuration."""
24
+
25
25
  size: Optional[int] = None
26
26
  crc32c: Optional[str] = None
27
27
  sha256: Optional[str] = None
28
28
  source_url: Optional[str] = None
29
+ """A URL where the file to be scanned can be downloaded."""
30
+
29
31
  transfer_method: TransferMethod = TransferMethod.POST_URL
32
+ """The transfer method used to upload the file data."""
30
33
 
31
34
 
32
35
  class FileScanData(PangeaResponseResult):
@@ -71,7 +74,6 @@ class FileScan(ServiceBase):
71
74
  """
72
75
 
73
76
  service_name = "file-scan"
74
- version = "v1"
75
77
 
76
78
  def file_scan(
77
79
  self,
@@ -92,12 +94,14 @@ class FileScan(ServiceBase):
92
94
  OperationId: file_scan_post_v1_scan
93
95
 
94
96
  Args:
95
- file (io.BufferedReader, optional): file to be scanned (should be opened with read permissions and in binary format)
96
97
  file_path (str, optional): filepath to be opened and scanned
98
+ file (io.BufferedReader, optional): file to be scanned (should be opened with read permissions and in binary format)
97
99
  verbose (bool, optional): Echo the API parameters in the response
98
100
  raw (bool, optional): Include raw data from this provider
99
101
  provider (str, optional): Scan file using this provider
100
102
  sync_call (bool, optional): True to wait until server returns a result, False to return immediately and retrieve result asynchronously
103
+ transfer_method (TransferMethod, optional): Transfer method used to upload the file data.
104
+ source_url (str, optional): A URL where the Pangea APIs can fetch the contents of the input file.
101
105
 
102
106
  Raises:
103
107
  PangeaAPIException: If an API Error happens
@@ -118,6 +122,15 @@ class FileScan(ServiceBase):
118
122
  print(f"\\t{err.detail} \\n")
119
123
  """
120
124
 
125
+ if transfer_method == TransferMethod.SOURCE_URL and source_url is None:
126
+ raise ValueError("`source_url` argument is required when using `TransferMethod.SOURCE_URL`.")
127
+
128
+ if source_url is not None and transfer_method != TransferMethod.SOURCE_URL:
129
+ raise ValueError(
130
+ "`transfer_method` should be `TransferMethod.SOURCE_URL` when using the `source_url` argument."
131
+ )
132
+
133
+ files: Optional[List[Tuple]] = None
121
134
  if file or file_path:
122
135
  if file_path:
123
136
  file = open(file_path, "rb")
@@ -128,9 +141,9 @@ class FileScan(ServiceBase):
128
141
  size = params.size
129
142
  else:
130
143
  crc, sha, size = None, None, None
131
- files: List[Tuple] = [("upload", ("filename", file, "application/octet-stream"))]
132
- else:
133
- raise ValueError("Need to set file_path or file arguments")
144
+ files = [("upload", ("filename", file, "application/octet-stream"))]
145
+ elif source_url is None:
146
+ raise ValueError("Need to set one of `file_path`, `file`, or `source_url` arguments.")
134
147
 
135
148
  input = FileScanRequest(
136
149
  verbose=verbose,
@@ -143,7 +156,11 @@ class FileScan(ServiceBase):
143
156
  source_url=source_url,
144
157
  )
145
158
  data = input.model_dump(exclude_none=True)
146
- return self.request.post("v1/scan", FileScanResult, data=data, files=files, poll_result=sync_call)
159
+ try:
160
+ return self.request.post("v1/scan", FileScanResult, data=data, files=files, poll_result=sync_call)
161
+ finally:
162
+ if file_path and file:
163
+ file.close()
147
164
 
148
165
  def request_upload_url(
149
166
  self,
pangea/services/intel.py CHANGED
@@ -1255,6 +1255,7 @@ class UserBreachedBulkRequest(IntelCommonRequest):
1255
1255
  usernames (List[str]): An username' list to search for
1256
1256
  ips (List[str]): An ip's list to search for
1257
1257
  phone_numbers (List[str]): A phone number's list to search for. minLength: 7, maxLength: 15.
1258
+ domains (List[str]): Search for user under these domains.
1258
1259
  start (str): Earliest date for search
1259
1260
  end (str): Latest date for search
1260
1261
  """
@@ -1263,6 +1264,7 @@ class UserBreachedBulkRequest(IntelCommonRequest):
1263
1264
  usernames: Optional[List[str]] = None
1264
1265
  ips: Optional[List[str]] = None
1265
1266
  phone_numbers: Optional[List[str]] = None
1267
+ domains: Optional[List[str]] = None
1266
1268
  start: Optional[str] = None
1267
1269
  end: Optional[str] = None
1268
1270
 
@@ -1439,6 +1441,7 @@ class UserIntel(ServiceBase):
1439
1441
  usernames: Optional[List[str]] = None,
1440
1442
  ips: Optional[List[str]] = None,
1441
1443
  phone_numbers: Optional[List[str]] = None,
1444
+ domains: Optional[List[str]] = None,
1442
1445
  start: Optional[str] = None,
1443
1446
  end: Optional[str] = None,
1444
1447
  verbose: Optional[bool] = None,
@@ -1457,6 +1460,7 @@ class UserIntel(ServiceBase):
1457
1460
  usernames (List[str]): A list of usernames to search for
1458
1461
  ips (List[str]): A list of ips to search for
1459
1462
  phone_numbers (List[str]): A list of phone numbers to search for. minLength: 7, maxLength: 15.
1463
+ domains (List[str]): Search for user under these domains.
1460
1464
  start (str): Earliest date for search
1461
1465
  end (str): Latest date for search
1462
1466
  verbose (bool, optional): Echo the API parameters in the response
@@ -1484,6 +1488,7 @@ class UserIntel(ServiceBase):
1484
1488
  phone_numbers=phone_numbers,
1485
1489
  usernames=usernames,
1486
1490
  ips=ips,
1491
+ domains=domains,
1487
1492
  provider=provider,
1488
1493
  start=start,
1489
1494
  end=end,
@@ -0,0 +1,366 @@
1
+ # Copyright 2022 Pangea Cyber Corporation
2
+ # Author: Pangea Cyber Corporation
3
+ from __future__ import annotations
4
+
5
+ import io
6
+ from typing import Dict, List, Optional, Tuple
7
+
8
+ from pydantic import Field
9
+
10
+ from pangea.response import APIRequestModel, PangeaResponse, PangeaResponseResult, TransferMethod
11
+ from pangea.services.base import ServiceBase
12
+ from pangea.utils import FileUploadParams, get_file_upload_params
13
+
14
+
15
+ class SanitizeFile(APIRequestModel):
16
+ scan_provider: Optional[str] = None
17
+ """Provider to use for File Scan."""
18
+
19
+
20
+ class SanitizeContent(APIRequestModel):
21
+ url_intel: Optional[bool] = None
22
+ """Perform URL Intel lookup."""
23
+
24
+ url_intel_provider: Optional[str] = None
25
+ """Provider to use for URL Intel."""
26
+
27
+ domain_intel: Optional[bool] = None
28
+ """Perform Domain Intel lookup."""
29
+
30
+ domain_intel_provider: Optional[str] = None
31
+ """Provider to use for Domain Intel lookup."""
32
+
33
+ defang: Optional[bool] = None
34
+ """Defang external links."""
35
+
36
+ defang_threshold: Optional[int] = None
37
+ """Defang risk threshold."""
38
+
39
+ redact: Optional[bool] = None
40
+ """Redact sensitive content."""
41
+
42
+ redact_detect_only: Optional[bool] = None
43
+ """
44
+ If redact is enabled, avoids redacting the file and instead returns the PII
45
+ analysis engine results. Only works if redact is enabled.
46
+ """
47
+
48
+ remove_attachments: Optional[bool] = None
49
+ """Remove file attachments (PDF only)."""
50
+
51
+ remove_interactive: Optional[bool] = None
52
+ """Remove interactive content (PDF only)."""
53
+
54
+
55
+ class SanitizeShareOutput(APIRequestModel):
56
+ enabled: Optional[bool] = None
57
+ """Store Sanitized files to Pangea Secure Share."""
58
+
59
+ output_folder: Optional[str] = None
60
+ """
61
+ Store Sanitized files to this Secure Share folder (will be auto-created if
62
+ it does not exist)
63
+ """
64
+
65
+
66
+ class SanitizeRequest(APIRequestModel):
67
+ transfer_method: TransferMethod = TransferMethod.POST_URL
68
+ """The transfer method used to upload the file data."""
69
+
70
+ source_url: Optional[str] = None
71
+ """A URL where the file to be sanitized can be downloaded."""
72
+
73
+ share_id: Optional[str] = None
74
+ """A Pangea Secure Share ID where the file to be Sanitized is stored."""
75
+
76
+ file: Optional[SanitizeFile] = None
77
+ """File."""
78
+
79
+ content: Optional[SanitizeContent] = None
80
+ """Content."""
81
+
82
+ share_output: Optional[SanitizeShareOutput] = None
83
+ """Share output."""
84
+
85
+ size: Optional[int] = None
86
+ """The size (in bytes) of the file. If the upload doesn't match, the call will fail."""
87
+
88
+ crc32c: Optional[str] = None
89
+ """The CRC32C hash of the file data, which will be verified by the server if provided."""
90
+
91
+ sha256: Optional[str] = None
92
+ """The hexadecimal-encoded SHA256 hash of the file data, which will be verified by the server if provided."""
93
+
94
+ uploaded_file_name: Optional[str] = None
95
+ """Name of the user-uploaded file, required for transfer-method 'put-url' and 'post-url'."""
96
+
97
+
98
+ class DefangData(PangeaResponseResult):
99
+ external_urls_count: Optional[int] = None
100
+ """Number of external links found."""
101
+
102
+ external_domains_count: Optional[int] = None
103
+ """Number of external domains found."""
104
+
105
+ defanged_count: Optional[int] = None
106
+ """Number of items defanged per provided rules and detections."""
107
+
108
+ url_intel_summary: Optional[str] = None
109
+ """Processed N URLs: X are malicious, Y are suspicious, Z are unknown."""
110
+
111
+ domain_intel_summary: Optional[str] = None
112
+ """Processed N Domains: X are malicious, Y are suspicious, Z are unknown."""
113
+
114
+
115
+ class RedactRecognizerResult(PangeaResponseResult):
116
+ field_type: str
117
+ """The entity name."""
118
+
119
+ score: float
120
+ """The certainty score that the entity matches this specific snippet."""
121
+
122
+ text: str
123
+ """The text snippet that matched."""
124
+
125
+ start: int
126
+ """The starting index of a snippet."""
127
+
128
+ end: int
129
+ """The ending index of a snippet."""
130
+
131
+ redacted: bool
132
+ """Indicates if this rule was used to anonymize a text snippet."""
133
+
134
+
135
+ class RedactData(PangeaResponseResult):
136
+ redaction_count: int
137
+ """Number of items redacted"""
138
+
139
+ summary_counts: Dict[str, int] = Field(default_factory=dict)
140
+ """Summary counts."""
141
+
142
+ recognizer_results: Optional[List[RedactRecognizerResult]] = None
143
+ """The scoring result of a set of rules."""
144
+
145
+
146
+ class CDR(PangeaResponseResult):
147
+ file_attachments_removed: Optional[int] = None
148
+ """Number of file attachments removed."""
149
+
150
+ interactive_contents_removed: Optional[int] = None
151
+ """Number of interactive content items removed."""
152
+
153
+
154
+ class SanitizeData(PangeaResponseResult):
155
+ defang: Optional[DefangData] = None
156
+ """Defang."""
157
+
158
+ redact: Optional[RedactData] = None
159
+ """Redact."""
160
+
161
+ malicious_file: Optional[bool] = None
162
+ """If the file scanned was malicious."""
163
+
164
+ cdr: Optional[CDR] = None
165
+ """Content Disarm and Reconstruction."""
166
+
167
+
168
+ class SanitizeResult(PangeaResponseResult):
169
+ dest_url: Optional[str] = None
170
+ """A URL where the Sanitized file can be downloaded."""
171
+
172
+ dest_share_id: Optional[str] = None
173
+ """Pangea Secure Share ID of the Sanitized file."""
174
+
175
+ data: SanitizeData
176
+ """Sanitize data."""
177
+
178
+ parameters: Dict = {}
179
+ """The parameters, which were passed in the request, echoed back."""
180
+
181
+
182
+ class Sanitize(ServiceBase):
183
+ """Sanitize service client.
184
+
185
+ Examples:
186
+ import os
187
+
188
+ # Pangea SDK
189
+ from pangea.config import PangeaConfig
190
+ from pangea.services import Sanitize
191
+
192
+ PANGEA_SANITIZE_TOKEN = os.getenv("PANGEA_SANITIZE_TOKEN")
193
+ config = PangeaConfig(domain="pangea.cloud")
194
+
195
+ sanitize = Sanitize(token=PANGEA_SANITIZE_TOKEN, config=config)
196
+ """
197
+
198
+ service_name = "sanitize"
199
+
200
+ def sanitize(
201
+ self,
202
+ transfer_method: TransferMethod = TransferMethod.POST_URL,
203
+ file_path: Optional[str] = None,
204
+ file: Optional[io.BufferedReader] = None,
205
+ source_url: Optional[str] = None,
206
+ share_id: Optional[str] = None,
207
+ file_scan: Optional[SanitizeFile] = None,
208
+ content: Optional[SanitizeContent] = None,
209
+ share_output: Optional[SanitizeShareOutput] = None,
210
+ size: Optional[int] = None,
211
+ crc32c: Optional[str] = None,
212
+ sha256: Optional[str] = None,
213
+ uploaded_file_name: Optional[str] = None,
214
+ sync_call: bool = True,
215
+ ) -> PangeaResponse[SanitizeResult]:
216
+ """
217
+ Sanitize
218
+
219
+ Apply file sanitization actions according to specified rules.
220
+
221
+ OperationId: sanitize_post_v1_sanitize
222
+
223
+ Args:
224
+ transfer_method: The transfer method used to upload the file data.
225
+ file_path: Path to file to sanitize.
226
+ file: File to sanitize.
227
+ source_url: A URL where the file to be sanitized can be downloaded.
228
+ share_id: A Pangea Secure Share ID where the file to be sanitized is stored.
229
+ file_scan: Options for File Scan.
230
+ content: Options for how the file should be sanitized.
231
+ share_output: Integration with Secure Share.
232
+ size: The size (in bytes) of the file. If the upload doesn't match, the call will fail.
233
+ crc32c: The CRC32C hash of the file data, which will be verified by the server if provided.
234
+ sha256: The hexadecimal-encoded SHA256 hash of the file data, which will be verified by the server if provided.
235
+ uploaded_file_name: Name of the user-uploaded file, required for `TransferMethod.PUT_URL` and `TransferMethod.POST_URL`.
236
+ sync_call: Whether or not to poll on HTTP/202.
237
+
238
+ Raises:
239
+ PangeaAPIException: If an API error happens.
240
+
241
+ Returns:
242
+ The sanitized file and information on the sanitization that was
243
+ performed.
244
+
245
+ Examples:
246
+ with open("/path/to/file.pdf", "rb") as f:
247
+ response = sanitize.sanitize(
248
+ file=f,
249
+ transfer_method=TransferMethod.POST_URL,
250
+ uploaded_file_name="uploaded_file",
251
+ )
252
+ """
253
+
254
+ if transfer_method == TransferMethod.SOURCE_URL and source_url is None:
255
+ raise ValueError("`source_url` argument is required when using `TransferMethod.SOURCE_URL`.")
256
+
257
+ if source_url is not None and transfer_method != TransferMethod.SOURCE_URL:
258
+ raise ValueError(
259
+ "`transfer_method` should be `TransferMethod.SOURCE_URL` when using the `source_url` argument."
260
+ )
261
+
262
+ files: Optional[List[Tuple]] = None
263
+ if file or file_path:
264
+ if file_path:
265
+ file = open(file_path, "rb")
266
+ if (
267
+ transfer_method == TransferMethod.POST_URL
268
+ and file
269
+ and (sha256 is None or crc32c is None or size is None)
270
+ ):
271
+ params = get_file_upload_params(file)
272
+ crc32c = params.crc_hex if crc32c is None else crc32c
273
+ sha256 = params.sha256_hex if sha256 is None else sha256
274
+ size = params.size if size is None else size
275
+ else:
276
+ crc32c, sha256, size = None, None, None
277
+ files = [("upload", ("filename", file, "application/octet-stream"))]
278
+ elif source_url is None:
279
+ raise ValueError("Need to set one of `file_path`, `file`, or `source_url` arguments.")
280
+
281
+ input = SanitizeRequest(
282
+ transfer_method=transfer_method,
283
+ source_url=source_url,
284
+ share_id=share_id,
285
+ file=file_scan,
286
+ content=content,
287
+ share_output=share_output,
288
+ crc32c=crc32c,
289
+ sha256=sha256,
290
+ size=size,
291
+ uploaded_file_name=uploaded_file_name,
292
+ )
293
+ data = input.model_dump(exclude_none=True)
294
+ try:
295
+ response = self.request.post("v1/sanitize", SanitizeResult, data=data, files=files, poll_result=sync_call)
296
+ finally:
297
+ if file_path and file is not None:
298
+ file.close()
299
+ return response
300
+
301
+ def request_upload_url(
302
+ self,
303
+ transfer_method: TransferMethod = TransferMethod.PUT_URL,
304
+ params: Optional[FileUploadParams] = None,
305
+ file_scan: Optional[SanitizeFile] = None,
306
+ content: Optional[SanitizeContent] = None,
307
+ share_output: Optional[SanitizeShareOutput] = None,
308
+ size: Optional[int] = None,
309
+ crc32c: Optional[str] = None,
310
+ sha256: Optional[str] = None,
311
+ uploaded_file_name: Optional[str] = None,
312
+ ) -> PangeaResponse[SanitizeResult]:
313
+ """
314
+ Sanitize via presigned URL
315
+
316
+ Apply file sanitization actions according to specified rules via a
317
+ [presigned URL](https://pangea.cloud/docs/api/transfer-methods).
318
+
319
+ OperationId: sanitize_post_v1_sanitize 2
320
+
321
+ Args:
322
+ transfer_method: The transfer method used to upload the file data.
323
+ params: File upload parameters.
324
+ file_scan: Options for File Scan.
325
+ content: Options for how the file should be sanitized.
326
+ share_output: Integration with Secure Share.
327
+ size: The size (in bytes) of the file. If the upload doesn't match, the call will fail.
328
+ crc32c: The CRC32C hash of the file data, which will be verified by the server if provided.
329
+ sha256: The hexadecimal-encoded SHA256 hash of the file data, which will be verified by the server if provided.
330
+ uploaded_file_name: Name of the user-uploaded file, required for `TransferMethod.PUT_URL` and `TransferMethod.POST_URL`.
331
+
332
+ Raises:
333
+ PangeaAPIException: If an API error happens.
334
+
335
+ Returns:
336
+ A presigned URL.
337
+
338
+ Examples:
339
+ presignedUrl = sanitize.request_upload_url(
340
+ transfer_method=TransferMethod.PUT_URL,
341
+ uploaded_file_name="uploaded_file",
342
+ )
343
+
344
+ # Upload file to `presignedUrl.accepted_result.put_url`.
345
+
346
+ # Poll for Sanitize's result.
347
+ response: PangeaResponse[SanitizeResult] = sanitize.poll_result(response=presignedUrl)
348
+ """
349
+
350
+ input = SanitizeRequest(
351
+ transfer_method=transfer_method,
352
+ file=file_scan,
353
+ content=content,
354
+ share_output=share_output,
355
+ crc32c=crc32c,
356
+ sha256=sha256,
357
+ size=size,
358
+ uploaded_file_name=uploaded_file_name,
359
+ )
360
+ if params is not None and (transfer_method == TransferMethod.POST_URL):
361
+ input.crc32c = params.crc_hex
362
+ input.sha256 = params.sha256_hex
363
+ input.size = params.size
364
+
365
+ data = input.model_dump(exclude_none=True)
366
+ return self.request.request_presigned_url("v1/sanitize", SanitizeResult, data=data)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pangea-sdk
3
- Version: 4.2.0
3
+ Version: 4.4.0
4
4
  Summary: Pangea API SDK
5
5
  Home-page: https://pangea.cloud/docs/sdk/python/
6
6
  License: MIT
@@ -15,14 +15,14 @@ Classifier: Programming Language :: Python :: 3.9
15
15
  Classifier: Programming Language :: Python :: 3.10
16
16
  Classifier: Programming Language :: Python :: 3.11
17
17
  Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
18
19
  Classifier: Topic :: Software Development
19
20
  Classifier: Topic :: Software Development :: Libraries
20
- Requires-Dist: aiohttp (>=3.9.3,<4.0.0)
21
- Requires-Dist: asyncio (>=3.4.3,<4.0.0)
22
- Requires-Dist: cryptography (>=42.0.8,<43.0.0)
21
+ Requires-Dist: aiohttp (>=3.10.10,<4.0.0)
22
+ Requires-Dist: cryptography (>=43.0.1,<44.0.0)
23
23
  Requires-Dist: deprecated (>=1.2.14,<2.0.0)
24
24
  Requires-Dist: google-crc32c (>=1.5.0,<2.0.0)
25
- Requires-Dist: pydantic (>=2.8.2,<3.0.0)
25
+ Requires-Dist: pydantic (>=2.9.2,<3.0.0)
26
26
  Requires-Dist: python-dateutil (>=2.9.0,<3.0.0)
27
27
  Requires-Dist: requests (>=2.31.0,<3.0.0)
28
28
  Requires-Dist: requests-toolbelt (>=1.0.0,<2.0.0)
@@ -1,14 +1,17 @@
1
- pangea/__init__.py,sha256=LU-w4iaLZ2cv9Z05TnO50JrxjnAYBIF410gDqtx3Hgw,200
2
- pangea/asyncio/request.py,sha256=ysCT-SB1nkAr64O6JkI64xZt-Za1TQGt8Jp9gfqeLxE,17077
3
- pangea/asyncio/services/__init__.py,sha256=hmySN2LoXYpHzSKLVSdVjMbpGXi5Z5iNx-fJ2Fc8W6I,320
1
+ pangea/__init__.py,sha256=v6ZDmf842vyF60xDR-QFmFgqOrACNjTFpxIBQcx3gFc,246
2
+ pangea/asyncio/__init__.py,sha256=kjEMkqMQ521LlMSu5jn3_WgweyArwVZ2C-s3x7mR6Pk,45
3
+ pangea/asyncio/file_uploader.py,sha256=wI7epib7Rc5jtZw4eJ1L1SlmutDG6CPv59C8N2UPhtY,1436
4
+ pangea/asyncio/request.py,sha256=KMfQYC027EBeyYyMlvnDtjxbpaRj9xubeqAdpot3F3U,17168
5
+ pangea/asyncio/services/__init__.py,sha256=hMeTMnksGimg-fS_XMpRgh02a7BNcLYCt56tnChYeC4,356
4
6
  pangea/asyncio/services/audit.py,sha256=bZ7gdkVWkzqLqUVc1Wnf3oDAaCLg97-zTWhY8UdX0_Y,26549
5
7
  pangea/asyncio/services/authn.py,sha256=rPeLJweL8mYH_t4ebcQn4n_Wglr3kClKNnCXNCimZU4,46622
6
- pangea/asyncio/services/authz.py,sha256=DcPn5FpdWMunim6Qtsk2vFLXyUbFb3lgl1XpX3w0hew,9176
8
+ pangea/asyncio/services/authz.py,sha256=HgW9R8DeW19wS7fpgq0NWOx41wZWcn6NYS4NMbi8p1A,9482
7
9
  pangea/asyncio/services/base.py,sha256=4FtKtlq74NmE9myrgIt9HMA6JDnP4mPZ6krafWr286o,2663
8
10
  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/file_scan.py,sha256=PLG1O-PL4Yk9uY9D6NbMrZ5LHg70Z311s7bFe46UMZA,7108
12
+ pangea/asyncio/services/intel.py,sha256=cCm3VwWxUzEUCNhuPCeejJvr4uOeLXuYDbDwTzNG6Aw,38121
11
13
  pangea/asyncio/services/redact.py,sha256=jRNtXr_DZ_cY7guhut-eZmOEhy2uN_VCXrjGH6bkh74,7265
14
+ pangea/asyncio/services/sanitize.py,sha256=bf98J-s-P51oSKqNBgR0wj5mlHOCBwpjWz7k0NdXCKQ,7899
12
15
  pangea/asyncio/services/vault.py,sha256=gFch7dVFZzjcTryn68AR4Xbj37U4A_LxfCMrX2mhtSk,53271
13
16
  pangea/audit_logger.py,sha256=gRkCfUUT5LDNaycwxkhZUySgY47jDfn1ZeKOul4XCQI,3842
14
17
  pangea/config.py,sha256=mQUu8GX_6weIuv3vjNdG5plppXskXYASmxMWtFQh-hc,1662
@@ -17,23 +20,25 @@ pangea/deep_verify.py,sha256=mocaGbC6XLbMTVWxTpMv4oJtXGPWpT-SbFqT3obpiZs,8443
17
20
  pangea/deprecated.py,sha256=IjFYEVvY1E0ld0SMkEYC1o62MAleX3nnT1If2dFVbHo,608
18
21
  pangea/dump_audit.py,sha256=1Je8D2fXwU4PWcZ-ZD4icfO3DNFvWqJkwsac4qFEhOo,7025
19
22
  pangea/exceptions.py,sha256=OBtzUECpNa6vNp8ySkHC-tm4QjFRCOAHBkMHqzAlOu8,5656
23
+ pangea/file_uploader.py,sha256=4RQ44xt-faApC61nn2PlwHT7XYrJ4GeQA8Ug4tySEAg,1227
20
24
  pangea/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
- pangea/request.py,sha256=ADMbnSlU-vrQQTlDp6w0b352ZDA_t4srZAihjaciQ5E,24285
22
- pangea/response.py,sha256=w-jTsCiNxDVt_qP27DQXbtqxPucSOAAYVlI7CnA4aiU,6597
23
- pangea/services/__init__.py,sha256=iAIa1kk_C0EHBsSn2XP3QT-bOZNGwMBPUcO2JoI-9cE,278
25
+ pangea/request.py,sha256=fKMuf2tuAguK2TnSSMpmdcBdHlgsYFQoYCBVh3mUkBY,24259
26
+ pangea/response.py,sha256=rjQuTAHLSAB0m2uAsdbt11xUVL5cCGT4uTRzQhUz9hE,7321
27
+ pangea/services/__init__.py,sha256=YPXqGGUlAm2oHQNArhDVfr6Y1Gq2o4nhwx_dpdKe74c,309
24
28
  pangea/services/audit/audit.py,sha256=IFv7jANA8S2SypQVS47x94_Cr5Z9zSsL9Dp9eXw9RHk,39593
25
29
  pangea/services/audit/exceptions.py,sha256=bhVuYe4ammacOVxwg98CChxvwZf5FKgR2DcgqILOcwc,471
26
30
  pangea/services/audit/models.py,sha256=1h1B9eSYQMYG3f8WNi1UcDX2-impRrET_ErjJYUnj7M,14678
27
- pangea/services/audit/signing.py,sha256=pOjw60BIYDcg3_5YKDCMWZUaapsEZpCHaFhyFu7TEgc,5567
31
+ pangea/services/audit/signing.py,sha256=EYuZN6pcFOjDJBG6S65jesE_8xOz5SNms6qHZ1qambQ,5541
28
32
  pangea/services/audit/util.py,sha256=Zq1qvfeplYfhCP_ud5YMvntSB0UvnCdsuYbOzZkHbjg,7620
29
33
  pangea/services/authn/authn.py,sha256=cZKl2Ixc6HwHnkRecpSaAGTQUgaZUtxfLa0T3S03HMs,45478
30
34
  pangea/services/authn/models.py,sha256=HH5su6jx3O9AwVGzASXZ99-eIWjgXEP5LhIVdewM13s,22394
31
- pangea/services/authz.py,sha256=kgJ4iWytqm2PqLB2n2AQy1lHR8TI8tdYBuOYAfY0fY0,11818
35
+ pangea/services/authz.py,sha256=HfDnovAokzAHvnjYdOCwceM-1sCmzODnjNEbQBUSfo8,12222
32
36
  pangea/services/base.py,sha256=lwhHoe5Juy28Ir3Mfj2lHdM58gxZRaxa2SRFi4_DBRw,3453
33
37
  pangea/services/embargo.py,sha256=9Wfku4td5ORaIENKmnGmS5jxJJIRfWp6Q51L36Jsy0I,3897
34
- pangea/services/file_scan.py,sha256=lWXiD4lHpagQ7O-nJeqwRQJKyP4BtauUdU19HZ3zcQM,6954
35
- pangea/services/intel.py,sha256=H7mo5r5pUDuYFphG4eWfI86twsOMAM3Qlf6dsAZ8UKM,51793
38
+ pangea/services/file_scan.py,sha256=QiO80uKqB_BnAOiYQKznXfxpa5j40qqETE3-zBRT_QE,7813
39
+ pangea/services/intel.py,sha256=CziBhC5K6O_kBXpD8zgJLpDtLHzBRgATGW4gHHFJT48,52039
36
40
  pangea/services/redact.py,sha256=ZYXkzEoriLJyCqaj5dqmgsC56mIz4T3pPToZ7TcNfhg,11465
41
+ pangea/services/sanitize.py,sha256=XP5D4CcbCZfzgU567X6H5eFBWwZuYSsHdvsdrQAZekY,12767
37
42
  pangea/services/vault/models/asymmetric.py,sha256=xr8oZnjzExMYcbzPJRG3xPXSmhumKDKn7RO90RvdrwU,1526
38
43
  pangea/services/vault/models/common.py,sha256=FOmi2UN5cEgSsrM-aDT1KWLK4TTgrtMukqNczrnWH6w,15491
39
44
  pangea/services/vault/models/secret.py,sha256=cLgEj-_BeGkB4-pmSeTkWVyasFbaJwcEltIEcOyf1U8,481
@@ -42,6 +47,6 @@ pangea/services/vault/vault.py,sha256=pv52dpZM0yicdtNra0Yd4AdkWUZC91Yk4rATthu1bs
42
47
  pangea/tools.py,sha256=sa2pSz-L8tB6GcZg6lghsmm8w0qMQAIkzqcv7dilU6Q,6429
43
48
  pangea/utils.py,sha256=pMSwL8B-owtrjeWYRjxuyaTQN4V-HsCT669KtOLU3Sw,3195
44
49
  pangea/verify_audit.py,sha256=rvni5akz_P2kYLAGAeA1A5gY6XGpXpAQpbIa7V1PoRY,17458
45
- pangea_sdk-4.2.0.dist-info/METADATA,sha256=SwojBSSbANVBCo4FOmE0qWZqYjW46tI6yNIFNrz3HlM,7532
46
- pangea_sdk-4.2.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
47
- pangea_sdk-4.2.0.dist-info/RECORD,,
50
+ pangea_sdk-4.4.0.dist-info/METADATA,sha256=Pdac62iEWB9wChXMZrR2saCrBMXnsLuWJlhIx6ovA44,7545
51
+ pangea_sdk-4.4.0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
52
+ pangea_sdk-4.4.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.9.0
2
+ Generator: poetry-core 1.9.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any