pangea-sdk 3.8.0b1__py3-none-any.whl → 5.4.0b1__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 +1 -1
- pangea/asyncio/file_uploader.py +1 -1
- pangea/asyncio/request.py +56 -34
- pangea/asyncio/services/__init__.py +4 -0
- pangea/asyncio/services/ai_guard.py +75 -0
- pangea/asyncio/services/audit.py +192 -31
- pangea/asyncio/services/authn.py +187 -109
- pangea/asyncio/services/authz.py +285 -0
- pangea/asyncio/services/base.py +21 -2
- pangea/asyncio/services/embargo.py +2 -2
- pangea/asyncio/services/file_scan.py +24 -9
- pangea/asyncio/services/intel.py +108 -34
- pangea/asyncio/services/prompt_guard.py +73 -0
- pangea/asyncio/services/redact.py +72 -4
- pangea/asyncio/services/sanitize.py +217 -0
- pangea/asyncio/services/share.py +246 -73
- pangea/asyncio/services/vault.py +1710 -750
- pangea/crypto/rsa.py +135 -0
- pangea/deep_verify.py +7 -1
- pangea/dump_audit.py +9 -8
- pangea/request.py +87 -59
- pangea/response.py +49 -31
- pangea/services/__init__.py +4 -0
- pangea/services/ai_guard.py +128 -0
- pangea/services/audit/audit.py +205 -42
- pangea/services/audit/models.py +56 -8
- pangea/services/audit/signing.py +6 -5
- pangea/services/audit/util.py +3 -3
- pangea/services/authn/authn.py +140 -70
- pangea/services/authn/models.py +167 -11
- pangea/services/authz.py +400 -0
- pangea/services/base.py +39 -8
- pangea/services/embargo.py +2 -2
- pangea/services/file_scan.py +32 -15
- pangea/services/intel.py +157 -32
- pangea/services/prompt_guard.py +83 -0
- pangea/services/redact.py +152 -4
- pangea/services/sanitize.py +371 -0
- pangea/services/share/share.py +683 -107
- pangea/services/vault/models/asymmetric.py +120 -18
- pangea/services/vault/models/common.py +439 -141
- pangea/services/vault/models/keys.py +94 -0
- pangea/services/vault/models/secret.py +27 -3
- pangea/services/vault/models/symmetric.py +68 -22
- pangea/services/vault/vault.py +1690 -749
- pangea/tools.py +6 -7
- pangea/utils.py +16 -27
- pangea/verify_audit.py +270 -83
- {pangea_sdk-3.8.0b1.dist-info → pangea_sdk-5.4.0b1.dist-info}/METADATA +43 -35
- pangea_sdk-5.4.0b1.dist-info/RECORD +60 -0
- {pangea_sdk-3.8.0b1.dist-info → pangea_sdk-5.4.0b1.dist-info}/WHEEL +1 -1
- pangea_sdk-3.8.0b1.dist-info/RECORD +0 -50
pangea/__init__.py
CHANGED
pangea/asyncio/file_uploader.py
CHANGED
@@ -28,7 +28,7 @@ class FileUploaderAsync:
|
|
28
28
|
) -> None:
|
29
29
|
if transfer_method == TransferMethod.PUT_URL:
|
30
30
|
files = [("file", ("filename", file, "application/octet-stream"))]
|
31
|
-
await self._request.put_presigned_url(url=url, files=files)
|
31
|
+
await self._request.put_presigned_url(url=url, files=files)
|
32
32
|
elif transfer_method == TransferMethod.POST_URL:
|
33
33
|
files = [("file", ("filename", file, "application/octet-stream"))]
|
34
34
|
await self._request.post_presigned_url(url=url, data=file_details, files=files) # type: ignore[arg-type]
|
pangea/asyncio/request.py
CHANGED
@@ -1,20 +1,25 @@
|
|
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
|
-
import os
|
7
7
|
import time
|
8
|
-
from typing import Dict, List, Optional, Tuple, Type, Union
|
8
|
+
from typing import Dict, List, Optional, Sequence, Tuple, Type, Union, cast
|
9
9
|
|
10
10
|
import aiohttp
|
11
11
|
from aiohttp import FormData
|
12
|
+
from pydantic import BaseModel
|
13
|
+
from pydantic_core import to_jsonable_python
|
14
|
+
from typing_extensions import Any, TypeVar
|
12
15
|
|
13
16
|
import pangea.exceptions as pe
|
14
17
|
from pangea.request import MultipartResponse, PangeaRequestBase
|
15
18
|
from pangea.response import AttachedFile, PangeaResponse, PangeaResponseResult, ResponseStatus, TransferMethod
|
16
19
|
from pangea.utils import default_encoder
|
17
20
|
|
21
|
+
TResult = TypeVar("TResult", bound=PangeaResponseResult)
|
22
|
+
|
18
23
|
|
19
24
|
class PangeaRequestAsync(PangeaRequestBase):
|
20
25
|
"""An object that makes direct calls to Pangea Service APIs.
|
@@ -28,12 +33,12 @@ class PangeaRequestAsync(PangeaRequestBase):
|
|
28
33
|
async def post(
|
29
34
|
self,
|
30
35
|
endpoint: str,
|
31
|
-
result_class: Type[
|
32
|
-
data:
|
33
|
-
files: List[Tuple] =
|
36
|
+
result_class: Type[TResult],
|
37
|
+
data: str | BaseModel | dict[str, Any] | None = None,
|
38
|
+
files: Optional[List[Tuple]] = None,
|
34
39
|
poll_result: bool = True,
|
35
40
|
url: Optional[str] = None,
|
36
|
-
) -> PangeaResponse:
|
41
|
+
) -> PangeaResponse[TResult]:
|
37
42
|
"""Makes the POST call to a Pangea Service endpoint.
|
38
43
|
|
39
44
|
Args:
|
@@ -44,17 +49,27 @@ class PangeaRequestAsync(PangeaRequestBase):
|
|
44
49
|
PangeaResponse which contains the response in its entirety and
|
45
50
|
various properties to retrieve individual fields
|
46
51
|
"""
|
52
|
+
|
53
|
+
if isinstance(data, BaseModel):
|
54
|
+
data = data.model_dump(exclude_none=True)
|
55
|
+
|
56
|
+
if data is None:
|
57
|
+
data = {}
|
58
|
+
|
59
|
+
# Normalize.
|
60
|
+
data = cast(dict[str, Any], to_jsonable_python(data))
|
61
|
+
|
47
62
|
if url is None:
|
48
63
|
url = self._url(endpoint)
|
49
64
|
|
50
65
|
# Set config ID if available
|
51
|
-
if self.config_id and data.get("config_id", None) is None:
|
52
|
-
data["config_id"] = self.config_id
|
66
|
+
if self.config_id and data.get("config_id", None) is None:
|
67
|
+
data["config_id"] = self.config_id
|
53
68
|
|
54
69
|
self.logger.debug(
|
55
70
|
json.dumps({"service": self.service, "action": "post", "url": url, "data": data}, default=default_encoder)
|
56
71
|
)
|
57
|
-
transfer_method = data.get("transfer_method", None)
|
72
|
+
transfer_method = data.get("transfer_method", None)
|
58
73
|
|
59
74
|
if files and type(data) is dict and (transfer_method == TransferMethod.POST_URL.value):
|
60
75
|
requests_response = await self._full_post_presigned_url(
|
@@ -91,9 +106,7 @@ class PangeaRequestAsync(PangeaRequestBase):
|
|
91
106
|
|
92
107
|
return self._check_response(pangea_response)
|
93
108
|
|
94
|
-
async def get(
|
95
|
-
self, path: str, result_class: Type[PangeaResponseResult], check_response: bool = True
|
96
|
-
) -> PangeaResponse[Type[PangeaResponseResult]]:
|
109
|
+
async def get(self, path: str, result_class: Type[TResult], check_response: bool = True) -> PangeaResponse[TResult]:
|
97
110
|
"""Makes the GET call to a Pangea Service endpoint.
|
98
111
|
|
99
112
|
Args:
|
@@ -110,7 +123,7 @@ class PangeaRequestAsync(PangeaRequestBase):
|
|
110
123
|
|
111
124
|
async with self.session.get(url, headers=self._headers()) as requests_response:
|
112
125
|
await self._check_http_errors(requests_response)
|
113
|
-
pangea_response = PangeaResponse(
|
126
|
+
pangea_response = PangeaResponse(
|
114
127
|
requests_response, result_class=result_class, json=await requests_response.json()
|
115
128
|
)
|
116
129
|
|
@@ -131,11 +144,11 @@ class PangeaRequestAsync(PangeaRequestBase):
|
|
131
144
|
raise pe.ServiceTemporarilyUnavailable(await resp.json())
|
132
145
|
|
133
146
|
async def poll_result_by_id(
|
134
|
-
self, request_id: str, result_class:
|
135
|
-
):
|
147
|
+
self, request_id: str, result_class: Type[TResult], check_response: bool = True
|
148
|
+
) -> PangeaResponse[TResult]:
|
136
149
|
path = self._get_poll_path(request_id)
|
137
150
|
self.logger.debug(json.dumps({"service": self.service, "action": "poll_result_once", "url": path}))
|
138
|
-
return await self.get(path, result_class, check_response=check_response)
|
151
|
+
return await self.get(path, result_class, check_response=check_response)
|
139
152
|
|
140
153
|
async def poll_result_once(self, response: PangeaResponse, check_response: bool = True):
|
141
154
|
request_id = response.request_id
|
@@ -160,7 +173,7 @@ class PangeaRequestAsync(PangeaRequestBase):
|
|
160
173
|
if resp.status < 200 or resp.status >= 300:
|
161
174
|
raise pe.PresignedUploadError(f"presigned POST failure: {resp.status}", await resp.text())
|
162
175
|
|
163
|
-
async def put_presigned_url(self, url: str, files:
|
176
|
+
async def put_presigned_url(self, url: str, files: Sequence[Tuple]):
|
164
177
|
# Send put request with file as body
|
165
178
|
resp = await self._http_put(url=url, files=files)
|
166
179
|
self.logger.debug(
|
@@ -173,7 +186,20 @@ class PangeaRequestAsync(PangeaRequestBase):
|
|
173
186
|
if resp.status < 200 or resp.status >= 300:
|
174
187
|
raise pe.PresignedUploadError(f"presigned PUT failure: {resp.status}", await resp.text())
|
175
188
|
|
176
|
-
async def download_file(self, url: str, filename:
|
189
|
+
async def download_file(self, url: str, filename: str | None = None) -> AttachedFile:
|
190
|
+
"""
|
191
|
+
Download file
|
192
|
+
|
193
|
+
Download a file from the specified URL and save it with the given
|
194
|
+
filename.
|
195
|
+
|
196
|
+
Args:
|
197
|
+
url: URL of the file to download
|
198
|
+
filename: Name to save the downloaded file as. If not provided, the
|
199
|
+
filename will be determined from the Content-Disposition header or
|
200
|
+
the URL.
|
201
|
+
"""
|
202
|
+
|
177
203
|
self.logger.debug(
|
178
204
|
json.dumps(
|
179
205
|
{
|
@@ -209,16 +235,16 @@ class PangeaRequestAsync(PangeaRequestBase):
|
|
209
235
|
)
|
210
236
|
|
211
237
|
return AttachedFile(filename=filename, file=await response.read(), content_type=content_type)
|
212
|
-
|
213
|
-
raise pe.DownloadFileError(f"Failed to download file. Status: {response.status}", await response.text())
|
238
|
+
raise pe.DownloadFileError(f"Failed to download file. Status: {response.status}", await response.text())
|
214
239
|
|
215
|
-
async def _get_pangea_json(self, reader: aiohttp.
|
240
|
+
async def _get_pangea_json(self, reader: aiohttp.multipart.MultipartResponseWrapper) -> Optional[Dict[str, Any]]:
|
216
241
|
# Iterate through parts
|
217
242
|
async for part in reader:
|
218
|
-
|
243
|
+
if isinstance(part, aiohttp.BodyPartReader):
|
244
|
+
return await part.json()
|
219
245
|
return None
|
220
246
|
|
221
|
-
async def _get_attached_files(self, reader: aiohttp.
|
247
|
+
async def _get_attached_files(self, reader: aiohttp.multipart.MultipartResponseWrapper) -> List[AttachedFile]:
|
222
248
|
files = []
|
223
249
|
i = 0
|
224
250
|
|
@@ -229,7 +255,7 @@ class PangeaRequestAsync(PangeaRequestBase):
|
|
229
255
|
if name is None:
|
230
256
|
name = f"default_file_name_{i}"
|
231
257
|
i += 1
|
232
|
-
files.append(AttachedFile(name, await part.read(), content_type))
|
258
|
+
files.append(AttachedFile(name, await part.read(), content_type)) # type: ignore[union-attr]
|
233
259
|
|
234
260
|
return files
|
235
261
|
|
@@ -237,13 +263,12 @@ class PangeaRequestAsync(PangeaRequestBase):
|
|
237
263
|
# Parse the multipart response
|
238
264
|
multipart_reader = aiohttp.MultipartReader.from_response(resp)
|
239
265
|
|
240
|
-
pangea_json = await self._get_pangea_json(multipart_reader)
|
266
|
+
pangea_json = await self._get_pangea_json(multipart_reader)
|
241
267
|
self.logger.debug(
|
242
268
|
json.dumps({"service": self.service, "action": "multipart response", "response": pangea_json})
|
243
269
|
)
|
244
270
|
|
245
|
-
|
246
|
-
attached_files = await self._get_attached_files(multipart_reader) # type: ignore[arg-type]
|
271
|
+
attached_files = await self._get_attached_files(multipart_reader)
|
247
272
|
return MultipartResponse(pangea_json, attached_files) # type: ignore[arg-type]
|
248
273
|
|
249
274
|
async def _http_post(
|
@@ -251,7 +276,7 @@ class PangeaRequestAsync(PangeaRequestBase):
|
|
251
276
|
url: str,
|
252
277
|
headers: Dict = {},
|
253
278
|
data: Union[str, Dict] = {},
|
254
|
-
files: List[Tuple] = [],
|
279
|
+
files: Optional[List[Tuple]] = [],
|
255
280
|
presigned_url_post: bool = False,
|
256
281
|
) -> aiohttp.ClientResponse:
|
257
282
|
if files:
|
@@ -276,7 +301,7 @@ class PangeaRequestAsync(PangeaRequestBase):
|
|
276
301
|
async def _http_put(
|
277
302
|
self,
|
278
303
|
url: str,
|
279
|
-
files:
|
304
|
+
files: Sequence[Tuple],
|
280
305
|
headers: Dict = {},
|
281
306
|
) -> aiohttp.ClientResponse:
|
282
307
|
self.logger.debug(
|
@@ -328,9 +353,7 @@ class PangeaRequestAsync(PangeaRequestBase):
|
|
328
353
|
# Receive 202
|
329
354
|
return await self._poll_presigned_url(accepted_exception.response)
|
330
355
|
|
331
|
-
async def _poll_presigned_url(
|
332
|
-
self, response: PangeaResponse[Type[PangeaResponseResult]]
|
333
|
-
) -> PangeaResponse[Type[PangeaResponseResult]]:
|
356
|
+
async def _poll_presigned_url(self, response: PangeaResponse[TResult]) -> PangeaResponse[TResult]:
|
334
357
|
if response.http_status != 202:
|
335
358
|
raise AttributeError("Response should be 202")
|
336
359
|
|
@@ -373,8 +396,7 @@ class PangeaRequestAsync(PangeaRequestBase):
|
|
373
396
|
|
374
397
|
if loop_resp.accepted_result is not None and not loop_resp.accepted_result.has_upload_url:
|
375
398
|
return loop_resp
|
376
|
-
|
377
|
-
raise loop_exc
|
399
|
+
raise loop_exc
|
378
400
|
|
379
401
|
async def _handle_queued_result(self, response: PangeaResponse) -> PangeaResponse:
|
380
402
|
if self._queued_retry_enabled and response.http_status == 202:
|
@@ -1,8 +1,12 @@
|
|
1
|
+
from .ai_guard import AIGuardAsync
|
1
2
|
from .audit import AuditAsync
|
2
3
|
from .authn import AuthNAsync
|
4
|
+
from .authz import AuthZAsync
|
3
5
|
from .embargo import EmbargoAsync
|
4
6
|
from .file_scan import FileScanAsync
|
5
7
|
from .intel import DomainIntelAsync, FileIntelAsync, IpIntelAsync, UrlIntelAsync, UserIntelAsync
|
8
|
+
from .prompt_guard import PromptGuardAsync
|
6
9
|
from .redact import RedactAsync
|
10
|
+
from .sanitize import SanitizeAsync
|
7
11
|
from .share import ShareAsync
|
8
12
|
from .vault import VaultAsync
|
@@ -0,0 +1,75 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from pangea.asyncio.services.base import ServiceBaseAsync
|
4
|
+
from pangea.config import PangeaConfig
|
5
|
+
from pangea.response import PangeaResponse
|
6
|
+
from pangea.services.ai_guard import TextGuardResult
|
7
|
+
|
8
|
+
|
9
|
+
class AIGuardAsync(ServiceBaseAsync):
|
10
|
+
"""AI Guard service client.
|
11
|
+
|
12
|
+
Provides methods to interact with Pangea's AI Guard service.
|
13
|
+
|
14
|
+
Examples:
|
15
|
+
from pangea import PangeaConfig
|
16
|
+
from pangea.asyncio.services import AIGuardAsync
|
17
|
+
|
18
|
+
config = PangeaConfig(domain="aws.us.pangea.cloud")
|
19
|
+
ai_guard = AIGuardAsync(token="pangea_token", config=config)
|
20
|
+
"""
|
21
|
+
|
22
|
+
service_name = "ai-guard"
|
23
|
+
|
24
|
+
def __init__(
|
25
|
+
self, token: str, config: PangeaConfig | None = None, logger_name: str = "pangea", config_id: str | None = None
|
26
|
+
) -> None:
|
27
|
+
"""
|
28
|
+
AI Guard service client.
|
29
|
+
|
30
|
+
Initializes a new AI Guard client.
|
31
|
+
|
32
|
+
Args:
|
33
|
+
token: Pangea API token.
|
34
|
+
config: Pangea service configuration.
|
35
|
+
logger_name: Logger name.
|
36
|
+
config_id: Configuration ID.
|
37
|
+
|
38
|
+
Examples:
|
39
|
+
from pangea import PangeaConfig
|
40
|
+
from pangea.asyncio.services import AIGuardAsync
|
41
|
+
|
42
|
+
config = PangeaConfig(domain="aws.us.pangea.cloud")
|
43
|
+
ai_guard = AIGuardAsync(token="pangea_token", config=config)
|
44
|
+
"""
|
45
|
+
|
46
|
+
super().__init__(token, config, logger_name, config_id)
|
47
|
+
|
48
|
+
async def guard_text(
|
49
|
+
self,
|
50
|
+
text: str,
|
51
|
+
*,
|
52
|
+
recipe: str = "pangea_prompt_guard",
|
53
|
+
debug: bool = False,
|
54
|
+
) -> PangeaResponse[TextGuardResult]:
|
55
|
+
"""
|
56
|
+
Text guard (Beta)
|
57
|
+
|
58
|
+
Guard text.
|
59
|
+
|
60
|
+
How to install a [Beta release](https://pangea.cloud/docs/sdk/python/#beta-releases).
|
61
|
+
|
62
|
+
OperationId: ai_guard_post_v1beta_text_guard
|
63
|
+
|
64
|
+
Args:
|
65
|
+
text: Text.
|
66
|
+
recipe: Recipe.
|
67
|
+
debug: Debug.
|
68
|
+
|
69
|
+
Examples:
|
70
|
+
response = await ai_guard.guard_text("text")
|
71
|
+
"""
|
72
|
+
|
73
|
+
return await self.request.post(
|
74
|
+
"v1beta/text/guard", TextGuardResult, data={"text": text, "recipe": recipe, "debug": debug}
|
75
|
+
)
|