pangea-sdk 3.8.0b1__py3-none-any.whl → 5.4.0b1__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. pangea/__init__.py +1 -1
  2. pangea/asyncio/file_uploader.py +1 -1
  3. pangea/asyncio/request.py +56 -34
  4. pangea/asyncio/services/__init__.py +4 -0
  5. pangea/asyncio/services/ai_guard.py +75 -0
  6. pangea/asyncio/services/audit.py +192 -31
  7. pangea/asyncio/services/authn.py +187 -109
  8. pangea/asyncio/services/authz.py +285 -0
  9. pangea/asyncio/services/base.py +21 -2
  10. pangea/asyncio/services/embargo.py +2 -2
  11. pangea/asyncio/services/file_scan.py +24 -9
  12. pangea/asyncio/services/intel.py +108 -34
  13. pangea/asyncio/services/prompt_guard.py +73 -0
  14. pangea/asyncio/services/redact.py +72 -4
  15. pangea/asyncio/services/sanitize.py +217 -0
  16. pangea/asyncio/services/share.py +246 -73
  17. pangea/asyncio/services/vault.py +1710 -750
  18. pangea/crypto/rsa.py +135 -0
  19. pangea/deep_verify.py +7 -1
  20. pangea/dump_audit.py +9 -8
  21. pangea/request.py +87 -59
  22. pangea/response.py +49 -31
  23. pangea/services/__init__.py +4 -0
  24. pangea/services/ai_guard.py +128 -0
  25. pangea/services/audit/audit.py +205 -42
  26. pangea/services/audit/models.py +56 -8
  27. pangea/services/audit/signing.py +6 -5
  28. pangea/services/audit/util.py +3 -3
  29. pangea/services/authn/authn.py +140 -70
  30. pangea/services/authn/models.py +167 -11
  31. pangea/services/authz.py +400 -0
  32. pangea/services/base.py +39 -8
  33. pangea/services/embargo.py +2 -2
  34. pangea/services/file_scan.py +32 -15
  35. pangea/services/intel.py +157 -32
  36. pangea/services/prompt_guard.py +83 -0
  37. pangea/services/redact.py +152 -4
  38. pangea/services/sanitize.py +371 -0
  39. pangea/services/share/share.py +683 -107
  40. pangea/services/vault/models/asymmetric.py +120 -18
  41. pangea/services/vault/models/common.py +439 -141
  42. pangea/services/vault/models/keys.py +94 -0
  43. pangea/services/vault/models/secret.py +27 -3
  44. pangea/services/vault/models/symmetric.py +68 -22
  45. pangea/services/vault/vault.py +1690 -749
  46. pangea/tools.py +6 -7
  47. pangea/utils.py +16 -27
  48. pangea/verify_audit.py +270 -83
  49. {pangea_sdk-3.8.0b1.dist-info → pangea_sdk-5.4.0b1.dist-info}/METADATA +43 -35
  50. pangea_sdk-5.4.0b1.dist-info/RECORD +60 -0
  51. {pangea_sdk-3.8.0b1.dist-info → pangea_sdk-5.4.0b1.dist-info}/WHEEL +1 -1
  52. pangea_sdk-3.8.0b1.dist-info/RECORD +0 -50
pangea/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
- __version__ = "3.8.0beta1"
1
+ __version__ = "5.4.0beta1"
2
2
 
3
3
  from pangea.asyncio.request import PangeaRequestAsync
4
4
  from pangea.config import PangeaConfig
@@ -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) # type: ignore[arg-type]
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[PangeaResponseResult],
32
- data: Union[str, Dict] = {},
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: # type: ignore[union-attr]
52
- data["config_id"] = self.config_id # type: ignore[index]
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) # type: ignore[union-attr]
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( # type: ignore[var-annotated]
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: Union[Type[PangeaResponseResult], Type[dict]], check_response: bool = True
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) # type: ignore[arg-type]
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: List[Tuple]):
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: Optional[str] = None) -> AttachedFile:
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
- else:
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.MultipartReader) -> Optional[Dict]:
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
- return await part.json()
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.MultipartReader) -> List[AttachedFile]:
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) # type: ignore[arg-type]
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
- multipart_reader = multipart_reader.__aiter__()
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: List[Tuple],
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
- else:
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
+ )