pangea-sdk 6.2.0b1__py3-none-any.whl → 6.3.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 +9 -1
- pangea/asyncio/__init__.py +1 -0
- pangea/asyncio/file_uploader.py +4 -2
- pangea/asyncio/request.py +70 -169
- pangea/asyncio/services/__init__.py +2 -1
- pangea/asyncio/services/ai_guard.py +9 -12
- pangea/asyncio/services/audit.py +13 -307
- pangea/asyncio/services/authn.py +40 -32
- pangea/asyncio/services/authz.py +51 -17
- pangea/asyncio/services/base.py +4 -0
- pangea/asyncio/services/file_scan.py +8 -2
- pangea/asyncio/services/intel.py +26 -28
- pangea/asyncio/services/redact.py +11 -268
- pangea/asyncio/services/sanitize.py +5 -1
- pangea/asyncio/services/share.py +5 -1
- pangea/asyncio/services/vault.py +71 -55
- pangea/audit_logger.py +3 -1
- pangea/deep_verify.py +13 -13
- pangea/deprecated.py +1 -1
- pangea/dump_audit.py +2 -3
- pangea/exceptions.py +8 -5
- pangea/file_uploader.py +4 -0
- pangea/request.py +80 -200
- pangea/response.py +21 -18
- pangea/services/__init__.py +2 -1
- pangea/services/ai_guard.py +35 -24
- pangea/services/audit/audit.py +17 -314
- pangea/services/audit/models.py +69 -307
- pangea/services/audit/signing.py +1 -1
- pangea/services/audit/util.py +10 -10
- pangea/services/authn/authn.py +39 -31
- pangea/services/authn/models.py +183 -148
- pangea/services/authz.py +108 -60
- pangea/services/base.py +7 -4
- pangea/services/embargo.py +6 -0
- pangea/services/file_scan.py +8 -2
- pangea/services/intel.py +36 -19
- pangea/services/redact.py +14 -476
- pangea/services/sanitize.py +5 -1
- pangea/services/share/share.py +13 -7
- pangea/services/vault/models/asymmetric.py +4 -0
- pangea/services/vault/models/common.py +15 -12
- pangea/services/vault/models/keys.py +4 -9
- pangea/services/vault/models/secret.py +3 -8
- pangea/services/vault/models/symmetric.py +4 -0
- pangea/services/vault/vault.py +69 -59
- pangea/tools.py +13 -9
- pangea/utils.py +3 -5
- pangea/verify_audit.py +23 -27
- {pangea_sdk-6.2.0b1.dist-info → pangea_sdk-6.3.0.dist-info}/METADATA +36 -17
- pangea_sdk-6.3.0.dist-info/RECORD +60 -0
- {pangea_sdk-6.2.0b1.dist-info → pangea_sdk-6.3.0.dist-info}/WHEEL +1 -1
- pangea/asyncio/services/management.py +0 -576
- pangea/services/management.py +0 -720
- pangea_sdk-6.2.0b1.dist-info/RECORD +0 -62
pangea/request.py
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
# Copyright 2022 Pangea Cyber Corporation
|
2
2
|
# Author: Pangea Cyber Corporation
|
3
|
+
|
4
|
+
# TODO: Modernize.
|
5
|
+
# ruff: noqa: UP006, UP035
|
6
|
+
|
3
7
|
from __future__ import annotations
|
4
8
|
|
5
9
|
import copy
|
@@ -7,14 +11,14 @@ import json
|
|
7
11
|
import logging
|
8
12
|
import time
|
9
13
|
from collections.abc import Iterable, Mapping
|
10
|
-
from typing import TYPE_CHECKING, Any, Dict, List,
|
14
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Type, Union, cast
|
11
15
|
|
12
16
|
import requests
|
13
|
-
from pydantic import BaseModel
|
17
|
+
from pydantic import BaseModel
|
14
18
|
from pydantic_core import to_jsonable_python
|
15
19
|
from requests.adapters import HTTPAdapter, Retry
|
16
20
|
from requests_toolbelt import MultipartDecoder # type: ignore[import-untyped]
|
17
|
-
from typing_extensions import TypeVar
|
21
|
+
from typing_extensions import TypeAlias, TypeVar, override
|
18
22
|
from yarl import URL
|
19
23
|
|
20
24
|
import pangea
|
@@ -27,11 +31,26 @@ if TYPE_CHECKING:
|
|
27
31
|
import aiohttp
|
28
32
|
|
29
33
|
|
34
|
+
_Data: TypeAlias = Union[Iterable[bytes], str, bytes, list[tuple[Any, Any]]]
|
35
|
+
|
36
|
+
_FileName: TypeAlias = Union[str, None]
|
37
|
+
_FileContent: TypeAlias = Union[str, bytes]
|
38
|
+
_FileContentType: TypeAlias = str
|
39
|
+
_FileCustomHeaders: TypeAlias = Mapping[str, str]
|
40
|
+
_FileSpecTuple2: TypeAlias = tuple[_FileName, _FileContent]
|
41
|
+
_FileSpecTuple3: TypeAlias = tuple[_FileName, _FileContent, _FileContentType]
|
42
|
+
_FileSpecTuple4: TypeAlias = tuple[_FileName, _FileContent, _FileContentType, _FileCustomHeaders]
|
43
|
+
_FileSpec: TypeAlias = Union[_FileContent, _FileSpecTuple2, _FileSpecTuple3, _FileSpecTuple4]
|
44
|
+
_Files: TypeAlias = Union[Mapping[str, _FileSpec], Iterable[tuple[str, _FileSpec]]]
|
45
|
+
|
46
|
+
_HeadersUpdateMapping: TypeAlias = Mapping[str, str]
|
47
|
+
|
48
|
+
|
30
49
|
class MultipartResponse:
|
31
50
|
pangea_json: Dict[str, str]
|
32
51
|
attached_files: List = []
|
33
52
|
|
34
|
-
def __init__(self, pangea_json:
|
53
|
+
def __init__(self, pangea_json: dict[str, str], attached_files: list = []): # noqa: B006
|
35
54
|
self.pangea_json = pangea_json
|
36
55
|
self.attached_files = attached_files
|
37
56
|
|
@@ -49,7 +68,7 @@ class PangeaRequestBase:
|
|
49
68
|
self._queued_retry_enabled = config.queued_retry_enabled
|
50
69
|
|
51
70
|
# Custom headers
|
52
|
-
self._extra_headers:
|
71
|
+
self._extra_headers: _HeadersUpdateMapping = {}
|
53
72
|
self._user_agent = ""
|
54
73
|
|
55
74
|
self.set_custom_user_agent(config.custom_user_agent)
|
@@ -64,18 +83,17 @@ class PangeaRequestBase:
|
|
64
83
|
|
65
84
|
return self._session
|
66
85
|
|
67
|
-
def set_extra_headers(self, headers:
|
86
|
+
def set_extra_headers(self, headers: _HeadersUpdateMapping):
|
68
87
|
"""Sets any additional headers in the request.
|
69
88
|
|
70
89
|
Args:
|
71
|
-
headers
|
90
|
+
headers: key-value pairs containing extra headers to set
|
72
91
|
|
73
92
|
Example:
|
74
93
|
set_extra_headers({ "My-Header" : "foobar" })
|
75
94
|
"""
|
76
95
|
|
77
|
-
|
78
|
-
self._extra_headers = headers
|
96
|
+
self._extra_headers = headers
|
79
97
|
|
80
98
|
def set_custom_user_agent(self, user_agent: Optional[str]):
|
81
99
|
self.config.custom_user_agent = user_agent
|
@@ -112,16 +130,14 @@ class PangeaRequestBase:
|
|
112
130
|
url = URL(self.config.base_url_template.format(SERVICE_NAME=self.service))
|
113
131
|
return str(url / path)
|
114
132
|
|
115
|
-
def _headers(self) -> dict:
|
116
|
-
|
117
|
-
|
133
|
+
def _headers(self) -> dict[str, str]:
|
134
|
+
return {
|
135
|
+
**self._extra_headers,
|
118
136
|
"Authorization": f"Bearer {self.token}",
|
137
|
+
"Content-Type": "application/json",
|
138
|
+
"User-Agent": self._user_agent,
|
119
139
|
}
|
120
140
|
|
121
|
-
# We want to ignore previous headers if user tried to set them, so we will overwrite them.
|
122
|
-
self._extra_headers.update(headers)
|
123
|
-
return self._extra_headers
|
124
|
-
|
125
141
|
def _get_filename_from_content_disposition(self, content_disposition: str) -> Optional[str]:
|
126
142
|
filename_parts = content_disposition.split("name=")
|
127
143
|
if len(filename_parts) > 1:
|
@@ -185,6 +201,9 @@ class PangeaRequestBase:
|
|
185
201
|
raise pe.AcceptedRequestException(response)
|
186
202
|
raise pe.PangeaAPIException(f"{summary} ", response)
|
187
203
|
|
204
|
+
def _init_session(self) -> requests.Session | aiohttp.ClientSession:
|
205
|
+
raise NotImplementedError
|
206
|
+
|
188
207
|
|
189
208
|
TResult = TypeVar("TResult", bound=PangeaResponseResult)
|
190
209
|
|
@@ -201,88 +220,26 @@ class PangeaRequest(PangeaRequestBase):
|
|
201
220
|
def __del__(self) -> None:
|
202
221
|
self.session.close()
|
203
222
|
|
204
|
-
def delete(self, endpoint: str) -> None:
|
205
|
-
"""
|
206
|
-
Makes a DELETE call to a Pangea endpoint.
|
207
|
-
|
208
|
-
Args:
|
209
|
-
endpoint: The Pangea API endpoint.
|
210
|
-
"""
|
211
|
-
|
212
|
-
url = self._url(endpoint)
|
213
|
-
|
214
|
-
self.logger.debug(
|
215
|
-
json.dumps({"service": self.service, "action": "delete", "url": url}, default=default_encoder)
|
216
|
-
)
|
217
|
-
|
218
|
-
requests_response = self._http_delete(url, headers=self._headers())
|
219
|
-
self._check_http_errors(requests_response)
|
220
|
-
|
221
|
-
@overload
|
222
223
|
def post(
|
223
224
|
self,
|
224
225
|
endpoint: str,
|
225
226
|
result_class: Type[TResult],
|
226
|
-
data: str | BaseModel |
|
227
|
-
files: Optional[
|
227
|
+
data: str | BaseModel | Mapping[str, Any] | None = None,
|
228
|
+
files: Optional[list[Tuple]] = None,
|
228
229
|
poll_result: bool = True,
|
229
230
|
url: Optional[str] = None,
|
230
|
-
*,
|
231
|
-
pangea_response: Literal[True] = True,
|
232
231
|
) -> PangeaResponse[TResult]:
|
233
|
-
"""
|
234
|
-
Makes the POST call to a Pangea Service endpoint.
|
232
|
+
"""Makes the POST call to a Pangea Service endpoint.
|
235
233
|
|
236
234
|
Args:
|
237
|
-
endpoint: The Pangea Service API endpoint.
|
238
|
-
data: The POST body payload object
|
235
|
+
endpoint(str): The Pangea Service API endpoint.
|
236
|
+
data(dict): The POST body payload object
|
239
237
|
|
240
238
|
Returns:
|
241
239
|
PangeaResponse which contains the response in its entirety and
|
242
240
|
various properties to retrieve individual fields
|
243
241
|
"""
|
244
242
|
|
245
|
-
@overload
|
246
|
-
def post(
|
247
|
-
self,
|
248
|
-
endpoint: str,
|
249
|
-
result_class: Type[TResult],
|
250
|
-
data: str | BaseModel | dict[str, Any] | None = None,
|
251
|
-
files: Optional[List[Tuple]] = None,
|
252
|
-
poll_result: bool = True,
|
253
|
-
url: Optional[str] = None,
|
254
|
-
*,
|
255
|
-
pangea_response: Literal[False],
|
256
|
-
) -> TResult:
|
257
|
-
"""
|
258
|
-
Makes the POST call to a Pangea Service endpoint.
|
259
|
-
|
260
|
-
Args:
|
261
|
-
endpoint: The Pangea Service API endpoint.
|
262
|
-
data: The POST body payload object
|
263
|
-
"""
|
264
|
-
|
265
|
-
def post(
|
266
|
-
self,
|
267
|
-
endpoint: str,
|
268
|
-
result_class: Type[TResult],
|
269
|
-
data: str | BaseModel | dict[str, Any] | None = None,
|
270
|
-
files: Optional[List[Tuple]] = None,
|
271
|
-
poll_result: bool = True,
|
272
|
-
url: Optional[str] = None,
|
273
|
-
*,
|
274
|
-
pangea_response: bool = True,
|
275
|
-
) -> PangeaResponse[TResult] | TResult:
|
276
|
-
"""
|
277
|
-
Makes the POST call to a Pangea Service endpoint.
|
278
|
-
|
279
|
-
Args:
|
280
|
-
endpoint: The Pangea Service API endpoint.
|
281
|
-
data: The POST body payload object
|
282
|
-
pangea_response: Whether or not the response body follows Pangea's
|
283
|
-
standard response schema
|
284
|
-
"""
|
285
|
-
|
286
243
|
if isinstance(data, BaseModel):
|
287
244
|
data = data.model_dump(exclude_none=True)
|
288
245
|
|
@@ -294,7 +251,7 @@ class PangeaRequest(PangeaRequestBase):
|
|
294
251
|
data = {}
|
295
252
|
|
296
253
|
# Normalize.
|
297
|
-
data = cast(dict[str, Any], to_jsonable_python(data))
|
254
|
+
data = cast(dict[str, Any], to_jsonable_python(data, exclude_none=True))
|
298
255
|
|
299
256
|
if url is None:
|
300
257
|
url = self._url(endpoint)
|
@@ -313,19 +270,16 @@ class PangeaRequest(PangeaRequestBase):
|
|
313
270
|
endpoint, result_class=result_class, data=data, files=files
|
314
271
|
)
|
315
272
|
else:
|
316
|
-
|
317
|
-
|
318
|
-
|
273
|
+
headers = self._headers()
|
274
|
+
if transfer_method == TransferMethod.MULTIPART.value:
|
275
|
+
del headers["Content-Type"]
|
276
|
+
requests_response = self._http_post(url, headers=headers, data=data, files=files)
|
319
277
|
|
320
278
|
self._check_http_errors(requests_response)
|
321
279
|
|
322
|
-
if not pangea_response:
|
323
|
-
type_adapter = TypeAdapter(result_class)
|
324
|
-
return type_adapter.validate_python(requests_response.json())
|
325
|
-
|
326
280
|
if "multipart/form-data" in requests_response.headers.get("content-type", ""):
|
327
281
|
multipart_response = self._process_multipart_response(requests_response)
|
328
|
-
|
282
|
+
pangea_response: PangeaResponse = PangeaResponse(
|
329
283
|
requests_response,
|
330
284
|
result_class=result_class,
|
331
285
|
json=multipart_response.pangea_json,
|
@@ -338,14 +292,14 @@ class PangeaRequest(PangeaRequestBase):
|
|
338
292
|
json.dumps({"service": self.service, "action": "post", "url": url, "response": json_resp})
|
339
293
|
)
|
340
294
|
|
341
|
-
|
295
|
+
pangea_response = PangeaResponse(requests_response, result_class=result_class, json=json_resp)
|
342
296
|
except requests.exceptions.JSONDecodeError as e:
|
343
|
-
raise pe.PangeaException(f"Failed to decode json response. {e}. Body: {requests_response.text}")
|
297
|
+
raise pe.PangeaException(f"Failed to decode json response. {e}. Body: {requests_response.text}") from e
|
344
298
|
|
345
299
|
if poll_result:
|
346
|
-
|
300
|
+
pangea_response = self._handle_queued_result(pangea_response)
|
347
301
|
|
348
|
-
return self._check_response(
|
302
|
+
return self._check_response(pangea_response)
|
349
303
|
|
350
304
|
def _get_pangea_json(self, decoder: MultipartDecoder) -> Optional[Dict]:
|
351
305
|
# Iterate through parts
|
@@ -388,20 +342,12 @@ class PangeaRequest(PangeaRequestBase):
|
|
388
342
|
if resp.status_code == 503:
|
389
343
|
raise pe.ServiceTemporarilyUnavailable(resp.json())
|
390
344
|
|
391
|
-
def _http_delete(
|
392
|
-
self,
|
393
|
-
url: str,
|
394
|
-
*,
|
395
|
-
headers: Mapping[str, str | bytes | None] = {},
|
396
|
-
) -> requests.Response:
|
397
|
-
return self.session.delete(url, headers=headers)
|
398
|
-
|
399
345
|
def _http_post(
|
400
346
|
self,
|
401
347
|
url: str,
|
402
|
-
headers:
|
403
|
-
data:
|
404
|
-
files:
|
348
|
+
headers: _HeadersUpdateMapping = {}, # noqa: B006
|
349
|
+
data: str | dict[Any, Any] = {}, # noqa: B006
|
350
|
+
files: _Files | None = None,
|
405
351
|
multipart_post: bool = True,
|
406
352
|
) -> requests.Response:
|
407
353
|
data_send, files = self._http_post_process(data=data, files=files, multipart_post=multipart_post)
|
@@ -409,31 +355,25 @@ class PangeaRequest(PangeaRequestBase):
|
|
409
355
|
|
410
356
|
def _http_post_process(
|
411
357
|
self,
|
412
|
-
data:
|
413
|
-
files:
|
358
|
+
data: str | dict[Any, Any] = {}, # noqa: B006
|
359
|
+
files: _Files | None = None,
|
414
360
|
multipart_post: bool = True,
|
415
|
-
):
|
361
|
+
) -> tuple[_Data | None, _Files | None]:
|
416
362
|
if files:
|
417
363
|
if multipart_post is True:
|
418
364
|
data_send: str = json.dumps(data, default=default_encoder) if isinstance(data, dict) else data
|
419
|
-
multi = [("request", (None, data_send, "application/json"))]
|
420
|
-
multi
|
421
|
-
|
422
|
-
|
423
|
-
#
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
files = { # type: ignore[assignment]
|
429
|
-
"file": files[0][1],
|
430
|
-
}
|
431
|
-
return data_send, files
|
365
|
+
multi: list[tuple[str, _FileSpec]] = [("request", (None, data_send, "application/json")), *files]
|
366
|
+
return None, multi
|
367
|
+
|
368
|
+
# Post to presigned URL as form.
|
369
|
+
# When posting to presigned URL, file key should be 'file'.
|
370
|
+
assert isinstance(data, dict)
|
371
|
+
assert isinstance(files, list)
|
372
|
+
return [(k, v) for k, v in data.items()], {"file": files[0][1]}
|
373
|
+
|
432
374
|
data_send = json.dumps(data, default=default_encoder) if isinstance(data, dict) else data
|
433
375
|
return data_send, None
|
434
376
|
|
435
|
-
return data, files
|
436
|
-
|
437
377
|
def _handle_queued_result(self, response: PangeaResponse[TResult]) -> PangeaResponse[TResult]:
|
438
378
|
if self._queued_retry_enabled and response.http_status == 202:
|
439
379
|
self.logger.debug(
|
@@ -446,98 +386,37 @@ class PangeaRequest(PangeaRequestBase):
|
|
446
386
|
|
447
387
|
return response
|
448
388
|
|
449
|
-
|
450
|
-
|
451
|
-
self,
|
452
|
-
path: str,
|
453
|
-
result_class: Type[TResult],
|
454
|
-
check_response: bool = True,
|
455
|
-
*,
|
456
|
-
params: (
|
457
|
-
Mapping[str | bytes | int | float, str | bytes | int | float | Iterable[str | bytes | int | float] | None]
|
458
|
-
| None
|
459
|
-
) = None,
|
460
|
-
pangea_response: Literal[True] = True,
|
461
|
-
) -> PangeaResponse[TResult]:
|
462
|
-
"""
|
463
|
-
Makes the GET call to a Pangea Service endpoint.
|
389
|
+
def get(self, path: str, result_class: Type[TResult], check_response: bool = True) -> PangeaResponse[TResult]:
|
390
|
+
"""Makes the GET call to a Pangea Service endpoint.
|
464
391
|
|
465
392
|
Args:
|
466
|
-
|
467
|
-
|
393
|
+
endpoint(str): The Pangea Service API endpoint.
|
394
|
+
path(str): Additional URL path
|
468
395
|
|
469
396
|
Returns:
|
470
397
|
PangeaResponse which contains the response in its entirety and
|
471
398
|
various properties to retrieve individual fields
|
472
399
|
"""
|
473
400
|
|
474
|
-
@overload
|
475
|
-
def get(
|
476
|
-
self,
|
477
|
-
path: str,
|
478
|
-
result_class: Type[TResult],
|
479
|
-
check_response: bool = True,
|
480
|
-
*,
|
481
|
-
params: (
|
482
|
-
Mapping[str | bytes | int | float, str | bytes | int | float | Iterable[str | bytes | int | float] | None]
|
483
|
-
| None
|
484
|
-
) = None,
|
485
|
-
pangea_response: Literal[False] = False,
|
486
|
-
) -> TResult:
|
487
|
-
"""
|
488
|
-
Makes the GET call to a Pangea Service endpoint.
|
489
|
-
|
490
|
-
Args:
|
491
|
-
path: Additional URL path
|
492
|
-
params: Dictionary of querystring data to attach to the request
|
493
|
-
"""
|
494
|
-
|
495
|
-
def get(
|
496
|
-
self,
|
497
|
-
path: str,
|
498
|
-
result_class: Type[TResult],
|
499
|
-
check_response: bool = True,
|
500
|
-
*,
|
501
|
-
params: (
|
502
|
-
Mapping[str | bytes | int | float, str | bytes | int | float | Iterable[str | bytes | int | float] | None]
|
503
|
-
| None
|
504
|
-
) = None,
|
505
|
-
pangea_response: bool = True,
|
506
|
-
) -> PangeaResponse[TResult] | TResult:
|
507
|
-
"""
|
508
|
-
Makes the GET call to a Pangea Service endpoint.
|
509
|
-
|
510
|
-
Args:
|
511
|
-
path: Additional URL path
|
512
|
-
params: Dictionary of querystring data to attach to the request
|
513
|
-
pangea_response: Whether or not the response body follows Pangea's
|
514
|
-
standard response schema
|
515
|
-
"""
|
516
|
-
|
517
401
|
url = self._url(path)
|
518
402
|
self.logger.debug(json.dumps({"service": self.service, "action": "get", "url": url}))
|
519
|
-
requests_response = self.session.get(url,
|
403
|
+
requests_response = self.session.get(url, headers=self._headers())
|
520
404
|
self._check_http_errors(requests_response)
|
521
|
-
|
522
|
-
if not pangea_response:
|
523
|
-
type_adapter = TypeAdapter(result_class)
|
524
|
-
return type_adapter.validate_python(requests_response.json())
|
525
|
-
|
526
|
-
pangea_response_obj: PangeaResponse = PangeaResponse(
|
405
|
+
pangea_response: PangeaResponse = PangeaResponse(
|
527
406
|
requests_response, result_class=result_class, json=requests_response.json()
|
528
407
|
)
|
529
408
|
|
530
409
|
self.logger.debug(
|
531
410
|
json.dumps(
|
532
|
-
{"service": self.service, "action": "get", "url": url, "response":
|
411
|
+
{"service": self.service, "action": "get", "url": url, "response": pangea_response.json},
|
533
412
|
default=default_encoder,
|
534
413
|
)
|
535
414
|
)
|
536
415
|
|
537
416
|
if check_response is False:
|
538
|
-
return
|
417
|
+
return pangea_response
|
539
418
|
|
540
|
-
return self._check_response(
|
419
|
+
return self._check_response(pangea_response)
|
541
420
|
|
542
421
|
def download_file(self, url: str, filename: str | None = None) -> AttachedFile:
|
543
422
|
"""
|
@@ -613,7 +492,7 @@ class PangeaRequest(PangeaRequestBase):
|
|
613
492
|
self,
|
614
493
|
endpoint: str,
|
615
494
|
result_class: Type[PangeaResponseResult],
|
616
|
-
data: Union[str,
|
495
|
+
data: Union[str, Mapping[str, Any]] = {},
|
617
496
|
) -> PangeaResponse:
|
618
497
|
# Send request
|
619
498
|
try:
|
@@ -656,8 +535,8 @@ class PangeaRequest(PangeaRequestBase):
|
|
656
535
|
def _http_put(
|
657
536
|
self,
|
658
537
|
url: str,
|
659
|
-
files:
|
660
|
-
headers:
|
538
|
+
files: list[Tuple],
|
539
|
+
headers: Mapping[str, str] = {},
|
661
540
|
) -> requests.Response:
|
662
541
|
self.logger.debug(
|
663
542
|
json.dumps({"service": self.service, "action": "http_put", "url": url}, default=default_encoder)
|
@@ -669,7 +548,7 @@ class PangeaRequest(PangeaRequestBase):
|
|
669
548
|
self,
|
670
549
|
endpoint: str,
|
671
550
|
result_class: Type[PangeaResponseResult],
|
672
|
-
data: Union[str,
|
551
|
+
data: Union[str, Mapping[str, Any]] = {},
|
673
552
|
files: Optional[List[Tuple]] = None,
|
674
553
|
):
|
675
554
|
if files is None or len(files) == 0:
|
@@ -739,7 +618,7 @@ class PangeaRequest(PangeaRequestBase):
|
|
739
618
|
{"service": self.service, "action": "poll_presigned_url", "step": "exit", "cause": {str(e)}}
|
740
619
|
)
|
741
620
|
)
|
742
|
-
raise pe.PresignedURLException("Failed to pull Presigned URL", loop_resp, e)
|
621
|
+
raise pe.PresignedURLException("Failed to pull Presigned URL", loop_resp, e) from e
|
743
622
|
|
744
623
|
self.logger.debug(json.dumps({"service": self.service, "action": "poll_presigned_url", "step": "exit"}))
|
745
624
|
|
@@ -747,6 +626,7 @@ class PangeaRequest(PangeaRequestBase):
|
|
747
626
|
return loop_resp
|
748
627
|
raise loop_exc
|
749
628
|
|
629
|
+
@override
|
750
630
|
def _init_session(self) -> requests.Session:
|
751
631
|
retry_config = Retry(
|
752
632
|
total=self.config.request_retries,
|
pangea/response.py
CHANGED
@@ -1,19 +1,25 @@
|
|
1
1
|
# Copyright 2022 Pangea Cyber Corporation
|
2
2
|
# Author: Pangea Cyber Corporation
|
3
|
+
|
4
|
+
# TODO: Modernize.
|
5
|
+
# ruff: noqa: UP006, UP035
|
6
|
+
|
7
|
+
from __future__ import annotations
|
8
|
+
|
3
9
|
import datetime
|
4
10
|
import enum
|
5
11
|
import os
|
6
|
-
from typing import Any, Dict, Generic, List, Optional, Type, Union
|
12
|
+
from typing import Annotated, Any, Dict, Generic, List, Optional, Type, Union
|
7
13
|
|
8
14
|
import aiohttp
|
9
15
|
import requests
|
10
16
|
from pydantic import BaseModel, ConfigDict, PlainSerializer
|
11
|
-
from typing_extensions import
|
17
|
+
from typing_extensions import TypeVar
|
12
18
|
|
13
19
|
from pangea.utils import format_datetime
|
14
20
|
|
15
21
|
|
16
|
-
class AttachedFile
|
22
|
+
class AttachedFile:
|
17
23
|
filename: str
|
18
24
|
file: bytes
|
19
25
|
content_type: str
|
@@ -40,10 +46,7 @@ class AttachedFile(object):
|
|
40
46
|
base_name, ext = os.path.splitext(file_path)
|
41
47
|
counter = 1
|
42
48
|
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}"
|
49
|
+
file_path = f"{base_name}_{counter}{ext}" if ext else f"{base_name}_{counter}"
|
47
50
|
counter += 1
|
48
51
|
return file_path
|
49
52
|
|
@@ -79,14 +82,12 @@ class TransferMethod(str, enum.Enum):
|
|
79
82
|
PangeaDateTime = Annotated[datetime.datetime, PlainSerializer(format_datetime)]
|
80
83
|
|
81
84
|
|
82
|
-
|
83
|
-
|
84
|
-
model_config = ConfigDict(arbitrary_types_allowed=True, extra="allow")
|
85
|
+
class APIRequestModel(BaseModel):
|
86
|
+
model_config = ConfigDict(extra="forbid")
|
85
87
|
|
86
88
|
|
87
|
-
|
88
|
-
|
89
|
-
model_config = ConfigDict(arbitrary_types_allowed=True, extra="allow")
|
89
|
+
class APIResponseModel(BaseModel):
|
90
|
+
model_config = ConfigDict(extra="allow")
|
90
91
|
|
91
92
|
|
92
93
|
class PangeaResponseResult(APIResponseModel):
|
@@ -192,6 +193,8 @@ T = TypeVar("T", bound=PangeaResponseResult)
|
|
192
193
|
|
193
194
|
|
194
195
|
class PangeaResponse(ResponseHeader, Generic[T]):
|
196
|
+
model_config = ConfigDict(arbitrary_types_allowed=True, extra="forbid")
|
197
|
+
|
195
198
|
raw_result: Optional[Dict[str, Any]] = None
|
196
199
|
raw_response: Optional[Union[requests.Response, aiohttp.ClientResponse]] = None
|
197
200
|
result: Optional[T] = None
|
@@ -199,16 +202,16 @@ class PangeaResponse(ResponseHeader, Generic[T]):
|
|
199
202
|
accepted_result: Optional[AcceptedResult] = None
|
200
203
|
result_class: Type[T] = PangeaResponseResult # type: ignore[assignment]
|
201
204
|
_json: Any
|
202
|
-
attached_files:
|
205
|
+
attached_files: list[AttachedFile] = []
|
203
206
|
|
204
207
|
def __init__(
|
205
208
|
self,
|
206
209
|
response: requests.Response,
|
207
210
|
result_class: Type[T],
|
208
211
|
json: dict,
|
209
|
-
attached_files:
|
212
|
+
attached_files: list[AttachedFile] = [], # noqa: B006
|
210
213
|
):
|
211
|
-
super(
|
214
|
+
super().__init__(**json)
|
212
215
|
self._json = json
|
213
216
|
self.raw_response = response
|
214
217
|
self.raw_result = self._json["result"]
|
@@ -241,10 +244,10 @@ class PangeaResponse(ResponseHeader, Generic[T]):
|
|
241
244
|
@property
|
242
245
|
def http_status(self) -> int: # type: ignore[return]
|
243
246
|
if self.raw_response:
|
244
|
-
if
|
247
|
+
if isinstance(self.raw_response, aiohttp.ClientResponse):
|
245
248
|
return self.raw_response.status
|
246
249
|
else:
|
247
|
-
return self.raw_response.status_code
|
250
|
+
return self.raw_response.status_code
|
248
251
|
|
249
252
|
@property
|
250
253
|
def url(self) -> str:
|
pangea/services/__init__.py
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# ruff: noqa: F401
|
2
|
+
|
1
3
|
from .ai_guard import AIGuard
|
2
4
|
from .audit.audit import Audit
|
3
5
|
from .authn.authn import AuthN
|
@@ -5,7 +7,6 @@ from .authz import AuthZ
|
|
5
7
|
from .embargo import Embargo
|
6
8
|
from .file_scan import FileScan
|
7
9
|
from .intel import DomainIntel, FileIntel, IpIntel, UrlIntel, UserIntel
|
8
|
-
from .management import Management
|
9
10
|
from .prompt_guard import PromptGuard
|
10
11
|
from .redact import Redact
|
11
12
|
from .sanitize import Sanitize
|