pangea-sdk 4.2.0__py3-none-any.whl → 4.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 +2 -1
- pangea/asyncio/__init__.py +1 -0
- pangea/asyncio/file_uploader.py +39 -0
- pangea/asyncio/request.py +2 -2
- pangea/asyncio/services/__init__.py +1 -0
- pangea/asyncio/services/authz.py +15 -9
- pangea/asyncio/services/file_scan.py +21 -6
- pangea/asyncio/services/sanitize.py +193 -0
- pangea/file_uploader.py +35 -0
- pangea/request.py +1 -1
- pangea/response.py +15 -1
- pangea/services/__init__.py +1 -0
- pangea/services/audit/signing.py +1 -1
- pangea/services/authz.py +16 -8
- pangea/services/file_scan.py +30 -13
- pangea/services/sanitize.py +335 -0
- {pangea_sdk-4.2.0.dist-info → pangea_sdk-4.3.0.dist-info}/METADATA +4 -5
- {pangea_sdk-4.2.0.dist-info → pangea_sdk-4.3.0.dist-info}/RECORD +19 -14
- {pangea_sdk-4.2.0.dist-info → pangea_sdk-4.3.0.dist-info}/WHEEL +0 -0
pangea/__init__.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
|
-
__version__ = "4.
|
1
|
+
__version__ = "4.3.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
@@ -32,7 +32,7 @@ class PangeaRequestAsync(PangeaRequestBase):
|
|
32
32
|
endpoint: str,
|
33
33
|
result_class: Type[TResult],
|
34
34
|
data: Union[str, Dict] = {},
|
35
|
-
files: List[Tuple] =
|
35
|
+
files: Optional[List[Tuple]] = None,
|
36
36
|
poll_result: bool = True,
|
37
37
|
url: Optional[str] = None,
|
38
38
|
) -> PangeaResponse[TResult]:
|
@@ -250,7 +250,7 @@ class PangeaRequestAsync(PangeaRequestBase):
|
|
250
250
|
url: str,
|
251
251
|
headers: Dict = {},
|
252
252
|
data: Union[str, Dict] = {},
|
253
|
-
files: List[Tuple] = [],
|
253
|
+
files: Optional[List[Tuple]] = [],
|
254
254
|
presigned_url_post: bool = False,
|
255
255
|
) -> aiohttp.ClientResponse:
|
256
256
|
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
|
pangea/asyncio/services/authz.py
CHANGED
@@ -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
|
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,
|
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,
|
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,8 +195,10 @@ 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(
|
199
|
-
|
198
|
+
async def list_resources(
|
199
|
+
self, type: str, action: str, subject: Subject, attributes: Optional[Dict[str, Any]] = None
|
200
|
+
) -> PangeaResponse[ListResourcesResult]:
|
201
|
+
"""List resources. (Beta)
|
200
202
|
|
201
203
|
Given a type, action, and subject, list all the resources in the
|
202
204
|
type that the subject has access to the action with.
|
@@ -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,13 +225,15 @@ 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(
|
231
|
-
|
233
|
+
async def list_subjects(
|
234
|
+
self, resource: Resource, action: str, attributes: Optional[Dict[str, Any]] = None
|
235
|
+
) -> PangeaResponse[ListSubjectsResult]:
|
236
|
+
"""List subjects. (Beta)
|
232
237
|
|
233
238
|
Given a resource and an action, return the list of subjects who have
|
234
239
|
access to the action for the given resource.
|
@@ -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
|
99
|
-
|
100
|
-
raise ValueError("Need to set file_path or
|
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
|
-
|
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,
|
@@ -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)
|
pangea/file_uploader.py
ADDED
@@ -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)
|
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[
|
239
|
+
return str(self.raw_response.url) # type: ignore[union-attr]
|
pangea/services/__init__.py
CHANGED
pangea/services/audit/signing.py
CHANGED
@@ -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)
|
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,
|
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,
|
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,8 +313,10 @@ 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(
|
315
|
-
|
316
|
+
def list_resources(
|
317
|
+
self, type: str, action: str, subject: Subject, attributes: Optional[Dict[str, Any]] = None
|
318
|
+
) -> PangeaResponse[ListResourcesResult]:
|
319
|
+
"""List resources. (Beta)
|
316
320
|
|
317
321
|
Given a type, action, and subject, list all the resources in the
|
318
322
|
type that the subject has access to the action with.
|
@@ -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,13 +343,15 @@ 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(
|
347
|
-
|
351
|
+
def list_subjects(
|
352
|
+
self, resource: Resource, action: str, attributes: Optional[Dict[str, Any]] = None
|
353
|
+
) -> PangeaResponse[ListSubjectsResult]:
|
354
|
+
"""List subjects. (Beta)
|
348
355
|
|
349
356
|
Given a resource and an action, return the list of subjects who have
|
350
357
|
access to the action for the given resource.
|
@@ -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))
|
pangea/services/file_scan.py
CHANGED
@@ -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
|
132
|
-
|
133
|
-
raise ValueError("Need to set file_path or
|
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
|
-
|
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,
|
@@ -0,0 +1,335 @@
|
|
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 pangea.response import APIRequestModel, PangeaResponse, PangeaResponseResult, TransferMethod
|
9
|
+
from pangea.services.base import ServiceBase
|
10
|
+
from pangea.utils import FileUploadParams, get_file_upload_params
|
11
|
+
|
12
|
+
|
13
|
+
class SanitizeFile(APIRequestModel):
|
14
|
+
scan_provider: Optional[str] = None
|
15
|
+
"""Provider to use for File Scan."""
|
16
|
+
|
17
|
+
|
18
|
+
class SanitizeContent(APIRequestModel):
|
19
|
+
url_intel: Optional[bool] = None
|
20
|
+
"""Perform URL Intel lookup."""
|
21
|
+
|
22
|
+
url_intel_provider: Optional[str] = None
|
23
|
+
"""Provider to use for URL Intel."""
|
24
|
+
|
25
|
+
domain_intel: Optional[bool] = None
|
26
|
+
"""Perform Domain Intel lookup."""
|
27
|
+
|
28
|
+
domain_intel_provider: Optional[str] = None
|
29
|
+
"""Provider to use for Domain Intel lookup."""
|
30
|
+
|
31
|
+
defang: Optional[bool] = None
|
32
|
+
"""Defang external links."""
|
33
|
+
|
34
|
+
defang_threshold: Optional[int] = None
|
35
|
+
"""Defang risk threshold."""
|
36
|
+
|
37
|
+
redact: Optional[bool] = None
|
38
|
+
"""Redact sensitive content."""
|
39
|
+
|
40
|
+
remove_attachments: Optional[bool] = None
|
41
|
+
"""Remove file attachments (PDF only)."""
|
42
|
+
|
43
|
+
remove_interactive: Optional[bool] = None
|
44
|
+
"""Remove interactive content (PDF only)."""
|
45
|
+
|
46
|
+
|
47
|
+
class SanitizeShareOutput(APIRequestModel):
|
48
|
+
enabled: Optional[bool] = None
|
49
|
+
"""Store Sanitized files to Pangea Secure Share."""
|
50
|
+
|
51
|
+
output_folder: Optional[str] = None
|
52
|
+
"""
|
53
|
+
Store Sanitized files to this Secure Share folder (will be auto-created if
|
54
|
+
it does not exist)
|
55
|
+
"""
|
56
|
+
|
57
|
+
|
58
|
+
class SanitizeRequest(APIRequestModel):
|
59
|
+
transfer_method: TransferMethod = TransferMethod.POST_URL
|
60
|
+
"""The transfer method used to upload the file data."""
|
61
|
+
|
62
|
+
source_url: Optional[str] = None
|
63
|
+
"""A URL where the file to be sanitized can be downloaded."""
|
64
|
+
|
65
|
+
share_id: Optional[str] = None
|
66
|
+
"""A Pangea Secure Share ID where the file to be Sanitized is stored."""
|
67
|
+
|
68
|
+
file: Optional[SanitizeFile] = None
|
69
|
+
"""File."""
|
70
|
+
|
71
|
+
content: Optional[SanitizeContent] = None
|
72
|
+
"""Content."""
|
73
|
+
|
74
|
+
share_output: Optional[SanitizeShareOutput] = None
|
75
|
+
"""Share output."""
|
76
|
+
|
77
|
+
size: Optional[int] = None
|
78
|
+
"""The size (in bytes) of the file. If the upload doesn't match, the call will fail."""
|
79
|
+
|
80
|
+
crc32c: Optional[str] = None
|
81
|
+
"""The CRC32C hash of the file data, which will be verified by the server if provided."""
|
82
|
+
|
83
|
+
sha256: Optional[str] = None
|
84
|
+
"""The hexadecimal-encoded SHA256 hash of the file data, which will be verified by the server if provided."""
|
85
|
+
|
86
|
+
uploaded_file_name: Optional[str] = None
|
87
|
+
"""Name of the user-uploaded file, required for transfer-method 'put-url' and 'post-url'."""
|
88
|
+
|
89
|
+
|
90
|
+
class DefangData(PangeaResponseResult):
|
91
|
+
external_urls_count: Optional[int] = None
|
92
|
+
"""Number of external links found."""
|
93
|
+
|
94
|
+
external_domains_count: Optional[int] = None
|
95
|
+
"""Number of external domains found."""
|
96
|
+
|
97
|
+
defanged_count: Optional[int] = None
|
98
|
+
"""Number of items defanged per provided rules and detections."""
|
99
|
+
|
100
|
+
url_intel_summary: Optional[str] = None
|
101
|
+
"""Processed N URLs: X are malicious, Y are suspicious, Z are unknown."""
|
102
|
+
|
103
|
+
domain_intel_summary: Optional[str] = None
|
104
|
+
"""Processed N Domains: X are malicious, Y are suspicious, Z are unknown."""
|
105
|
+
|
106
|
+
|
107
|
+
class RedactData(PangeaResponseResult):
|
108
|
+
redaction_count: Optional[int] = None
|
109
|
+
"""Number of items redacted"""
|
110
|
+
|
111
|
+
summary_counts: Dict = {}
|
112
|
+
"""Summary counts."""
|
113
|
+
|
114
|
+
|
115
|
+
class CDR(PangeaResponseResult):
|
116
|
+
file_attachments_removed: Optional[int] = None
|
117
|
+
"""Number of file attachments removed."""
|
118
|
+
|
119
|
+
interactive_contents_removed: Optional[int] = None
|
120
|
+
"""Number of interactive content items removed."""
|
121
|
+
|
122
|
+
|
123
|
+
class SanitizeData(PangeaResponseResult):
|
124
|
+
defang: Optional[DefangData] = None
|
125
|
+
"""Defang."""
|
126
|
+
|
127
|
+
redact: Optional[RedactData] = None
|
128
|
+
"""Redact."""
|
129
|
+
|
130
|
+
malicious_file: Optional[bool] = None
|
131
|
+
"""If the file scanned was malicious."""
|
132
|
+
|
133
|
+
cdr: Optional[CDR] = None
|
134
|
+
"""Content Disarm and Reconstruction."""
|
135
|
+
|
136
|
+
|
137
|
+
class SanitizeResult(PangeaResponseResult):
|
138
|
+
dest_url: Optional[str] = None
|
139
|
+
"""A URL where the Sanitized file can be downloaded."""
|
140
|
+
|
141
|
+
dest_share_id: Optional[str] = None
|
142
|
+
"""Pangea Secure Share ID of the Sanitized file."""
|
143
|
+
|
144
|
+
data: SanitizeData
|
145
|
+
"""Sanitize data."""
|
146
|
+
|
147
|
+
parameters: Dict = {}
|
148
|
+
"""The parameters, which were passed in the request, echoed back."""
|
149
|
+
|
150
|
+
|
151
|
+
class Sanitize(ServiceBase):
|
152
|
+
"""Sanitize service client.
|
153
|
+
|
154
|
+
Examples:
|
155
|
+
import os
|
156
|
+
|
157
|
+
# Pangea SDK
|
158
|
+
from pangea.config import PangeaConfig
|
159
|
+
from pangea.services import Sanitize
|
160
|
+
|
161
|
+
PANGEA_SANITIZE_TOKEN = os.getenv("PANGEA_SANITIZE_TOKEN")
|
162
|
+
config = PangeaConfig(domain="pangea.cloud")
|
163
|
+
|
164
|
+
sanitize = Sanitize(token=PANGEA_SANITIZE_TOKEN, config=config)
|
165
|
+
"""
|
166
|
+
|
167
|
+
service_name = "sanitize"
|
168
|
+
|
169
|
+
def sanitize(
|
170
|
+
self,
|
171
|
+
transfer_method: TransferMethod = TransferMethod.POST_URL,
|
172
|
+
file_path: Optional[str] = None,
|
173
|
+
file: Optional[io.BufferedReader] = None,
|
174
|
+
source_url: Optional[str] = None,
|
175
|
+
share_id: Optional[str] = None,
|
176
|
+
file_scan: Optional[SanitizeFile] = None,
|
177
|
+
content: Optional[SanitizeContent] = None,
|
178
|
+
share_output: Optional[SanitizeShareOutput] = None,
|
179
|
+
size: Optional[int] = None,
|
180
|
+
crc32c: Optional[str] = None,
|
181
|
+
sha256: Optional[str] = None,
|
182
|
+
uploaded_file_name: Optional[str] = None,
|
183
|
+
sync_call: bool = True,
|
184
|
+
) -> PangeaResponse[SanitizeResult]:
|
185
|
+
"""
|
186
|
+
Sanitize
|
187
|
+
|
188
|
+
Apply file sanitization actions according to specified rules.
|
189
|
+
|
190
|
+
OperationId: sanitize_post_v1_sanitize
|
191
|
+
|
192
|
+
Args:
|
193
|
+
transfer_method: The transfer method used to upload the file data.
|
194
|
+
file_path: Path to file to sanitize.
|
195
|
+
file: File to sanitize.
|
196
|
+
source_url: A URL where the file to be sanitized can be downloaded.
|
197
|
+
share_id: A Pangea Secure Share ID where the file to be sanitized is stored.
|
198
|
+
file_scan: Options for File Scan.
|
199
|
+
content: Options for how the file should be sanitized.
|
200
|
+
share_output: Integration with Secure Share.
|
201
|
+
size: The size (in bytes) of the file. If the upload doesn't match, the call will fail.
|
202
|
+
crc32c: The CRC32C hash of the file data, which will be verified by the server if provided.
|
203
|
+
sha256: The hexadecimal-encoded SHA256 hash of the file data, which will be verified by the server if provided.
|
204
|
+
uploaded_file_name: Name of the user-uploaded file, required for `TransferMethod.PUT_URL` and `TransferMethod.POST_URL`.
|
205
|
+
sync_call: Whether or not to poll on HTTP/202.
|
206
|
+
|
207
|
+
Raises:
|
208
|
+
PangeaAPIException: If an API error happens.
|
209
|
+
|
210
|
+
Returns:
|
211
|
+
The sanitized file and information on the sanitization that was
|
212
|
+
performed.
|
213
|
+
|
214
|
+
Examples:
|
215
|
+
with open("/path/to/file.pdf", "rb") as f:
|
216
|
+
response = sanitize.sanitize(
|
217
|
+
file=f,
|
218
|
+
transfer_method=TransferMethod.POST_URL,
|
219
|
+
uploaded_file_name="uploaded_file",
|
220
|
+
)
|
221
|
+
"""
|
222
|
+
|
223
|
+
if transfer_method == TransferMethod.SOURCE_URL and source_url is None:
|
224
|
+
raise ValueError("`source_url` argument is required when using `TransferMethod.SOURCE_URL`.")
|
225
|
+
|
226
|
+
if source_url is not None and transfer_method != TransferMethod.SOURCE_URL:
|
227
|
+
raise ValueError(
|
228
|
+
"`transfer_method` should be `TransferMethod.SOURCE_URL` when using the `source_url` argument."
|
229
|
+
)
|
230
|
+
|
231
|
+
files: Optional[List[Tuple]] = None
|
232
|
+
if file or file_path:
|
233
|
+
if file_path:
|
234
|
+
file = open(file_path, "rb")
|
235
|
+
if (
|
236
|
+
transfer_method == TransferMethod.POST_URL
|
237
|
+
and file
|
238
|
+
and (sha256 is None or crc32c is None or size is None)
|
239
|
+
):
|
240
|
+
params = get_file_upload_params(file)
|
241
|
+
crc32c = params.crc_hex if crc32c is None else crc32c
|
242
|
+
sha256 = params.sha256_hex if sha256 is None else sha256
|
243
|
+
size = params.size if size is None else size
|
244
|
+
else:
|
245
|
+
crc32c, sha256, size = None, None, None
|
246
|
+
files = [("upload", ("filename", file, "application/octet-stream"))]
|
247
|
+
elif source_url is None:
|
248
|
+
raise ValueError("Need to set one of `file_path`, `file`, or `source_url` arguments.")
|
249
|
+
|
250
|
+
input = SanitizeRequest(
|
251
|
+
transfer_method=transfer_method,
|
252
|
+
source_url=source_url,
|
253
|
+
share_id=share_id,
|
254
|
+
file=file_scan,
|
255
|
+
content=content,
|
256
|
+
share_output=share_output,
|
257
|
+
crc32c=crc32c,
|
258
|
+
sha256=sha256,
|
259
|
+
size=size,
|
260
|
+
uploaded_file_name=uploaded_file_name,
|
261
|
+
)
|
262
|
+
data = input.model_dump(exclude_none=True)
|
263
|
+
try:
|
264
|
+
response = self.request.post("v1/sanitize", SanitizeResult, data=data, files=files, poll_result=sync_call)
|
265
|
+
finally:
|
266
|
+
if file_path and file is not None:
|
267
|
+
file.close()
|
268
|
+
return response
|
269
|
+
|
270
|
+
def request_upload_url(
|
271
|
+
self,
|
272
|
+
transfer_method: TransferMethod = TransferMethod.PUT_URL,
|
273
|
+
params: Optional[FileUploadParams] = None,
|
274
|
+
file_scan: Optional[SanitizeFile] = None,
|
275
|
+
content: Optional[SanitizeContent] = None,
|
276
|
+
share_output: Optional[SanitizeShareOutput] = None,
|
277
|
+
size: Optional[int] = None,
|
278
|
+
crc32c: Optional[str] = None,
|
279
|
+
sha256: Optional[str] = None,
|
280
|
+
uploaded_file_name: Optional[str] = None,
|
281
|
+
) -> PangeaResponse[SanitizeResult]:
|
282
|
+
"""
|
283
|
+
Sanitize via presigned URL
|
284
|
+
|
285
|
+
Apply file sanitization actions according to specified rules via a
|
286
|
+
[presigned URL](https://pangea.cloud/docs/api/transfer-methods).
|
287
|
+
|
288
|
+
OperationId: sanitize_post_v1_sanitize 2
|
289
|
+
|
290
|
+
Args:
|
291
|
+
transfer_method: The transfer method used to upload the file data.
|
292
|
+
params: File upload parameters.
|
293
|
+
file_scan: Options for File Scan.
|
294
|
+
content: Options for how the file should be sanitized.
|
295
|
+
share_output: Integration with Secure Share.
|
296
|
+
size: The size (in bytes) of the file. If the upload doesn't match, the call will fail.
|
297
|
+
crc32c: The CRC32C hash of the file data, which will be verified by the server if provided.
|
298
|
+
sha256: The hexadecimal-encoded SHA256 hash of the file data, which will be verified by the server if provided.
|
299
|
+
uploaded_file_name: Name of the user-uploaded file, required for `TransferMethod.PUT_URL` and `TransferMethod.POST_URL`.
|
300
|
+
|
301
|
+
Raises:
|
302
|
+
PangeaAPIException: If an API error happens.
|
303
|
+
|
304
|
+
Returns:
|
305
|
+
A presigned URL.
|
306
|
+
|
307
|
+
Examples:
|
308
|
+
presignedUrl = sanitize.request_upload_url(
|
309
|
+
transfer_method=TransferMethod.PUT_URL,
|
310
|
+
uploaded_file_name="uploaded_file",
|
311
|
+
)
|
312
|
+
|
313
|
+
# Upload file to `presignedUrl.accepted_result.put_url`.
|
314
|
+
|
315
|
+
# Poll for Sanitize's result.
|
316
|
+
response: PangeaResponse[SanitizeResult] = sanitize.poll_result(response=presignedUrl)
|
317
|
+
"""
|
318
|
+
|
319
|
+
input = SanitizeRequest(
|
320
|
+
transfer_method=transfer_method,
|
321
|
+
file=file_scan,
|
322
|
+
content=content,
|
323
|
+
share_output=share_output,
|
324
|
+
crc32c=crc32c,
|
325
|
+
sha256=sha256,
|
326
|
+
size=size,
|
327
|
+
uploaded_file_name=uploaded_file_name,
|
328
|
+
)
|
329
|
+
if params is not None and (transfer_method == TransferMethod.POST_URL):
|
330
|
+
input.crc32c = params.crc_hex
|
331
|
+
input.sha256 = params.sha256_hex
|
332
|
+
input.size = params.size
|
333
|
+
|
334
|
+
data = input.model_dump(exclude_none=True)
|
335
|
+
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.
|
3
|
+
Version: 4.3.0
|
4
4
|
Summary: Pangea API SDK
|
5
5
|
Home-page: https://pangea.cloud/docs/sdk/python/
|
6
6
|
License: MIT
|
@@ -17,12 +17,11 @@ Classifier: Programming Language :: Python :: 3.11
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.12
|
18
18
|
Classifier: Topic :: Software Development
|
19
19
|
Classifier: Topic :: Software Development :: Libraries
|
20
|
-
Requires-Dist: aiohttp (>=3.
|
21
|
-
Requires-Dist:
|
22
|
-
Requires-Dist: cryptography (>=42.0.8,<43.0.0)
|
20
|
+
Requires-Dist: aiohttp (>=3.10.3,<4.0.0)
|
21
|
+
Requires-Dist: cryptography (>=43.0.1,<44.0.0)
|
23
22
|
Requires-Dist: deprecated (>=1.2.14,<2.0.0)
|
24
23
|
Requires-Dist: google-crc32c (>=1.5.0,<2.0.0)
|
25
|
-
Requires-Dist: pydantic (>=2.
|
24
|
+
Requires-Dist: pydantic (>=2.9.2,<3.0.0)
|
26
25
|
Requires-Dist: python-dateutil (>=2.9.0,<3.0.0)
|
27
26
|
Requires-Dist: requests (>=2.31.0,<3.0.0)
|
28
27
|
Requires-Dist: requests-toolbelt (>=1.0.0,<2.0.0)
|
@@ -1,14 +1,17 @@
|
|
1
|
-
pangea/__init__.py,sha256=
|
2
|
-
pangea/asyncio/
|
3
|
-
pangea/asyncio/
|
1
|
+
pangea/__init__.py,sha256=jRQKKZlq7PuMhIGECYLfchmOlGy0e03sZhjL-MrXrO0,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=PkyVJQdE9kEKNQsOzuDZWIDbDzmj4oXebr6mgcV5S_o,17099
|
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=
|
8
|
+
pangea/asyncio/services/authz.py,sha256=gBdzlNwckDx5ozYPRagOymZJxX1B614fxyQwySW2MCU,9496
|
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=
|
11
|
+
pangea/asyncio/services/file_scan.py,sha256=PLG1O-PL4Yk9uY9D6NbMrZ5LHg70Z311s7bFe46UMZA,7108
|
10
12
|
pangea/asyncio/services/intel.py,sha256=ph-Kor2CyKWtVaYVx3OD7pRjb4MGCHrZnrgqMkfMu4I,37977
|
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=
|
22
|
-
pangea/response.py,sha256=
|
23
|
-
pangea/services/__init__.py,sha256=
|
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=
|
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=
|
35
|
+
pangea/services/authz.py,sha256=lZVf2CE5kQkxWBqribQKM86lnfJbnvh5xaFRopGvmWc,12236
|
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=
|
38
|
+
pangea/services/file_scan.py,sha256=QiO80uKqB_BnAOiYQKznXfxpa5j40qqETE3-zBRT_QE,7813
|
35
39
|
pangea/services/intel.py,sha256=H7mo5r5pUDuYFphG4eWfI86twsOMAM3Qlf6dsAZ8UKM,51793
|
36
40
|
pangea/services/redact.py,sha256=ZYXkzEoriLJyCqaj5dqmgsC56mIz4T3pPToZ7TcNfhg,11465
|
41
|
+
pangea/services/sanitize.py,sha256=FEMSjulya6Z6TqPVlNE80DMU7nCs-_3lgGVB2YZXc90,11940
|
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.
|
46
|
-
pangea_sdk-4.
|
47
|
-
pangea_sdk-4.
|
50
|
+
pangea_sdk-4.3.0.dist-info/METADATA,sha256=Bv7469TC0tPNL2Knf-YMxANUIimaDiTvSn3U1hdpvcA,7493
|
51
|
+
pangea_sdk-4.3.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
52
|
+
pangea_sdk-4.3.0.dist-info/RECORD,,
|
File without changes
|