pangea-sdk 6.2.0b1__py3-none-any.whl → 6.2.0b2__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 +51 -21
- pangea/asyncio/services/__init__.py +2 -0
- pangea/asyncio/services/ai_guard.py +91 -2
- pangea/asyncio/services/audit.py +14 -8
- pangea/asyncio/services/authn.py +33 -23
- pangea/asyncio/services/authz.py +6 -6
- pangea/asyncio/services/base.py +4 -0
- pangea/asyncio/services/file_scan.py +8 -2
- pangea/asyncio/services/intel.py +6 -2
- pangea/asyncio/services/prompt_guard.py +112 -2
- pangea/asyncio/services/redact.py +7 -3
- pangea/asyncio/services/sanitize.py +5 -1
- pangea/asyncio/services/share.py +5 -1
- pangea/asyncio/services/vault.py +19 -15
- 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 +58 -41
- pangea/response.py +15 -12
- pangea/services/__init__.py +2 -0
- pangea/services/ai_guard.py +497 -16
- pangea/services/audit/audit.py +15 -13
- pangea/services/audit/models.py +4 -0
- pangea/services/audit/signing.py +1 -1
- pangea/services/audit/util.py +10 -10
- pangea/services/authn/authn.py +33 -23
- pangea/services/authn/models.py +3 -0
- pangea/services/authz.py +10 -6
- pangea/services/base.py +5 -1
- pangea/services/embargo.py +6 -0
- pangea/services/file_scan.py +8 -2
- pangea/services/intel.py +4 -0
- pangea/services/management.py +8 -8
- pangea/services/prompt_guard.py +193 -2
- pangea/services/redact.py +7 -3
- 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 +4 -0
- pangea/services/vault/models/symmetric.py +4 -0
- pangea/services/vault/vault.py +17 -19
- 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.2.0b2.dist-info}/METADATA +6 -6
- pangea_sdk-6.2.0b2.dist-info/RECORD +62 -0
- {pangea_sdk-6.2.0b1.dist-info → pangea_sdk-6.2.0b2.dist-info}/WHEEL +1 -1
- 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, overload
|
11
15
|
|
12
16
|
import requests
|
13
17
|
from pydantic import BaseModel, TypeAdapter
|
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 Literal, TypeAlias, TypeVar, override
|
18
22
|
from yarl import URL
|
19
23
|
|
20
24
|
import pangea
|
@@ -27,11 +31,24 @@ 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
|
+
|
30
47
|
class MultipartResponse:
|
31
48
|
pangea_json: Dict[str, str]
|
32
49
|
attached_files: List = []
|
33
50
|
|
34
|
-
def __init__(self, pangea_json:
|
51
|
+
def __init__(self, pangea_json: dict[str, str], attached_files: list = []): # noqa: B006
|
35
52
|
self.pangea_json = pangea_json
|
36
53
|
self.attached_files = attached_files
|
37
54
|
|
@@ -116,6 +133,7 @@ class PangeaRequestBase:
|
|
116
133
|
headers = {
|
117
134
|
"User-Agent": self._user_agent,
|
118
135
|
"Authorization": f"Bearer {self.token}",
|
136
|
+
"Content-Type": "application/json",
|
119
137
|
}
|
120
138
|
|
121
139
|
# We want to ignore previous headers if user tried to set them, so we will overwrite them.
|
@@ -185,6 +203,9 @@ class PangeaRequestBase:
|
|
185
203
|
raise pe.AcceptedRequestException(response)
|
186
204
|
raise pe.PangeaAPIException(f"{summary} ", response)
|
187
205
|
|
206
|
+
def _init_session(self) -> requests.Session | aiohttp.ClientSession:
|
207
|
+
raise NotImplementedError
|
208
|
+
|
188
209
|
|
189
210
|
TResult = TypeVar("TResult", bound=PangeaResponseResult)
|
190
211
|
|
@@ -223,8 +244,8 @@ class PangeaRequest(PangeaRequestBase):
|
|
223
244
|
self,
|
224
245
|
endpoint: str,
|
225
246
|
result_class: Type[TResult],
|
226
|
-
data: str | BaseModel |
|
227
|
-
files: Optional[
|
247
|
+
data: str | BaseModel | Mapping[str, Any] | None = None,
|
248
|
+
files: Optional[list[Tuple]] = None,
|
228
249
|
poll_result: bool = True,
|
229
250
|
url: Optional[str] = None,
|
230
251
|
*,
|
@@ -247,8 +268,8 @@ class PangeaRequest(PangeaRequestBase):
|
|
247
268
|
self,
|
248
269
|
endpoint: str,
|
249
270
|
result_class: Type[TResult],
|
250
|
-
data: str | BaseModel |
|
251
|
-
files: Optional[
|
271
|
+
data: str | BaseModel | Mapping[str, Any] | None = None,
|
272
|
+
files: Optional[list[Tuple]] = None,
|
252
273
|
poll_result: bool = True,
|
253
274
|
url: Optional[str] = None,
|
254
275
|
*,
|
@@ -266,15 +287,15 @@ class PangeaRequest(PangeaRequestBase):
|
|
266
287
|
self,
|
267
288
|
endpoint: str,
|
268
289
|
result_class: Type[TResult],
|
269
|
-
data: str | BaseModel |
|
270
|
-
files: Optional[
|
290
|
+
data: str | BaseModel | Mapping[str, Any] | None = None,
|
291
|
+
files: Optional[list[Tuple]] = None,
|
271
292
|
poll_result: bool = True,
|
272
293
|
url: Optional[str] = None,
|
273
294
|
*,
|
274
295
|
pangea_response: bool = True,
|
275
296
|
) -> PangeaResponse[TResult] | TResult:
|
276
297
|
"""
|
277
|
-
Makes
|
298
|
+
Makes a POST call to a Pangea Service endpoint.
|
278
299
|
|
279
300
|
Args:
|
280
301
|
endpoint: The Pangea Service API endpoint.
|
@@ -294,7 +315,7 @@ class PangeaRequest(PangeaRequestBase):
|
|
294
315
|
data = {}
|
295
316
|
|
296
317
|
# Normalize.
|
297
|
-
data = cast(dict[str, Any], to_jsonable_python(data))
|
318
|
+
data = cast(dict[str, Any], to_jsonable_python(data, exclude_none=True))
|
298
319
|
|
299
320
|
if url is None:
|
300
321
|
url = self._url(endpoint)
|
@@ -313,9 +334,10 @@ class PangeaRequest(PangeaRequestBase):
|
|
313
334
|
endpoint, result_class=result_class, data=data, files=files
|
314
335
|
)
|
315
336
|
else:
|
316
|
-
|
317
|
-
|
318
|
-
|
337
|
+
headers = self._headers()
|
338
|
+
if transfer_method == TransferMethod.MULTIPART.value:
|
339
|
+
del headers["Content-Type"]
|
340
|
+
requests_response = self._http_post(url, headers=headers, data=data, files=files)
|
319
341
|
|
320
342
|
self._check_http_errors(requests_response)
|
321
343
|
|
@@ -340,7 +362,7 @@ class PangeaRequest(PangeaRequestBase):
|
|
340
362
|
|
341
363
|
pangea_response_obj = PangeaResponse(requests_response, result_class=result_class, json=json_resp)
|
342
364
|
except requests.exceptions.JSONDecodeError as e:
|
343
|
-
raise pe.PangeaException(f"Failed to decode json response. {e}. Body: {requests_response.text}")
|
365
|
+
raise pe.PangeaException(f"Failed to decode json response. {e}. Body: {requests_response.text}") from e
|
344
366
|
|
345
367
|
if poll_result:
|
346
368
|
pangea_response_obj = self._handle_queued_result(pangea_response_obj)
|
@@ -399,9 +421,9 @@ class PangeaRequest(PangeaRequestBase):
|
|
399
421
|
def _http_post(
|
400
422
|
self,
|
401
423
|
url: str,
|
402
|
-
headers: Mapping[str, str
|
403
|
-
data:
|
404
|
-
files:
|
424
|
+
headers: Mapping[str, str] = {},
|
425
|
+
data: str | dict[Any, Any] = {}, # noqa: B006
|
426
|
+
files: _Files | None = None,
|
405
427
|
multipart_post: bool = True,
|
406
428
|
) -> requests.Response:
|
407
429
|
data_send, files = self._http_post_process(data=data, files=files, multipart_post=multipart_post)
|
@@ -409,31 +431,25 @@ class PangeaRequest(PangeaRequestBase):
|
|
409
431
|
|
410
432
|
def _http_post_process(
|
411
433
|
self,
|
412
|
-
data:
|
413
|
-
files:
|
434
|
+
data: str | dict[Any, Any] = {}, # noqa: B006
|
435
|
+
files: _Files | None = None,
|
414
436
|
multipart_post: bool = True,
|
415
|
-
):
|
437
|
+
) -> tuple[_Data | None, _Files | None]:
|
416
438
|
if files:
|
417
439
|
if multipart_post is True:
|
418
440
|
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
|
441
|
+
multi: list[tuple[str, _FileSpec]] = [("request", (None, data_send, "application/json")), *files]
|
442
|
+
return None, multi
|
443
|
+
|
444
|
+
# Post to presigned URL as form.
|
445
|
+
# When posting to presigned URL, file key should be 'file'.
|
446
|
+
assert isinstance(data, dict)
|
447
|
+
assert isinstance(files, list)
|
448
|
+
return [(k, v) for k, v in data.items()], {"file": files[0][1]}
|
449
|
+
|
432
450
|
data_send = json.dumps(data, default=default_encoder) if isinstance(data, dict) else data
|
433
451
|
return data_send, None
|
434
452
|
|
435
|
-
return data, files
|
436
|
-
|
437
453
|
def _handle_queued_result(self, response: PangeaResponse[TResult]) -> PangeaResponse[TResult]:
|
438
454
|
if self._queued_retry_enabled and response.http_status == 202:
|
439
455
|
self.logger.debug(
|
@@ -613,7 +629,7 @@ class PangeaRequest(PangeaRequestBase):
|
|
613
629
|
self,
|
614
630
|
endpoint: str,
|
615
631
|
result_class: Type[PangeaResponseResult],
|
616
|
-
data: Union[str,
|
632
|
+
data: Union[str, Mapping[str, Any]] = {},
|
617
633
|
) -> PangeaResponse:
|
618
634
|
# Send request
|
619
635
|
try:
|
@@ -656,8 +672,8 @@ class PangeaRequest(PangeaRequestBase):
|
|
656
672
|
def _http_put(
|
657
673
|
self,
|
658
674
|
url: str,
|
659
|
-
files:
|
660
|
-
headers:
|
675
|
+
files: list[Tuple],
|
676
|
+
headers: Mapping[str, str] = {},
|
661
677
|
) -> requests.Response:
|
662
678
|
self.logger.debug(
|
663
679
|
json.dumps({"service": self.service, "action": "http_put", "url": url}, default=default_encoder)
|
@@ -669,7 +685,7 @@ class PangeaRequest(PangeaRequestBase):
|
|
669
685
|
self,
|
670
686
|
endpoint: str,
|
671
687
|
result_class: Type[PangeaResponseResult],
|
672
|
-
data: Union[str,
|
688
|
+
data: Union[str, Mapping[str, Any]] = {},
|
673
689
|
files: Optional[List[Tuple]] = None,
|
674
690
|
):
|
675
691
|
if files is None or len(files) == 0:
|
@@ -739,7 +755,7 @@ class PangeaRequest(PangeaRequestBase):
|
|
739
755
|
{"service": self.service, "action": "poll_presigned_url", "step": "exit", "cause": {str(e)}}
|
740
756
|
)
|
741
757
|
)
|
742
|
-
raise pe.PresignedURLException("Failed to pull Presigned URL", loop_resp, e)
|
758
|
+
raise pe.PresignedURLException("Failed to pull Presigned URL", loop_resp, e) from e
|
743
759
|
|
744
760
|
self.logger.debug(json.dumps({"service": self.service, "action": "poll_presigned_url", "step": "exit"}))
|
745
761
|
|
@@ -747,6 +763,7 @@ class PangeaRequest(PangeaRequestBase):
|
|
747
763
|
return loop_resp
|
748
764
|
raise loop_exc
|
749
765
|
|
766
|
+
@override
|
750
767
|
def _init_session(self) -> requests.Session:
|
751
768
|
retry_config = Retry(
|
752
769
|
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
|
|
@@ -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:
|