pangea-sdk 4.1.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/authn.py +20 -1
- 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/authn/authn.py +24 -5
- pangea/services/authn/models.py +85 -13
- pangea/services/authz.py +16 -8
- pangea/services/file_scan.py +30 -13
- pangea/services/sanitize.py +335 -0
- {pangea_sdk-4.1.0.dist-info → pangea_sdk-4.3.0.dist-info}/METADATA +4 -5
- {pangea_sdk-4.1.0.dist-info → pangea_sdk-4.3.0.dist-info}/RECORD +22 -17
- {pangea_sdk-4.1.0.dist-info → pangea_sdk-4.3.0.dist-info}/WHEEL +0 -0
pangea/services/authn/models.py
CHANGED
@@ -5,22 +5,62 @@ from __future__ import annotations
|
|
5
5
|
|
6
6
|
import enum
|
7
7
|
from typing import Dict, List, NewType, Optional, Union
|
8
|
+
from warnings import warn
|
8
9
|
|
9
|
-
from
|
10
|
+
from typing_extensions import deprecated
|
10
11
|
|
11
12
|
import pangea.services.intel as im
|
13
|
+
from pangea.deprecated import pangea_deprecated
|
12
14
|
from pangea.response import APIRequestModel, APIResponseModel, PangeaResponseResult
|
13
15
|
from pangea.services.vault.models.common import JWK, JWKec, JWKrsa
|
14
16
|
|
15
17
|
Scopes = NewType("Scopes", List[str])
|
16
18
|
|
17
19
|
|
18
|
-
class Profile(
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
20
|
+
class Profile(Dict[str, str]):
|
21
|
+
@property
|
22
|
+
def first_name(self) -> str:
|
23
|
+
warn(
|
24
|
+
'`Profile.first_name` is deprecated. Use `Profile["first_name"]` instead.', DeprecationWarning, stacklevel=2
|
25
|
+
)
|
26
|
+
return self["first_name"]
|
27
|
+
|
28
|
+
@first_name.setter
|
29
|
+
def first_name(self, value: str) -> None:
|
30
|
+
warn(
|
31
|
+
'`Profile.first_name` is deprecated. Use `Profile["first_name"]` instead.', DeprecationWarning, stacklevel=2
|
32
|
+
)
|
33
|
+
self["first_name"] = value
|
34
|
+
|
35
|
+
@property
|
36
|
+
def last_name(self) -> str:
|
37
|
+
warn('`Profile.last_name` is deprecated. Use `Profile["last_name"]` instead.', DeprecationWarning, stacklevel=2)
|
38
|
+
return self["last_name"]
|
39
|
+
|
40
|
+
@last_name.setter
|
41
|
+
def last_name(self, value: str) -> None:
|
42
|
+
warn('`Profile.last_name` is deprecated. Use `Profile["last_name"]` instead.', DeprecationWarning, stacklevel=2)
|
43
|
+
self["last_name"] = value
|
44
|
+
|
45
|
+
@property
|
46
|
+
def phone(self) -> str:
|
47
|
+
warn('`Profile.phone` is deprecated. Use `Profile["phone"]` instead.', DeprecationWarning, stacklevel=2)
|
48
|
+
return self["phone"]
|
49
|
+
|
50
|
+
@phone.setter
|
51
|
+
def phone(self, value: str) -> None:
|
52
|
+
warn('`Profile.phone` is deprecated. Use `Profile["phone"]` instead.', DeprecationWarning, stacklevel=2)
|
53
|
+
self["phone"] = value
|
54
|
+
|
55
|
+
@deprecated("`Profile.model_dump()` is deprecated. `Profile` is already a `dict[str, str]`.")
|
56
|
+
@pangea_deprecated(reason="`Profile` is already a `dict[str, str]`.")
|
57
|
+
def model_dump(self, *, exclude_none: bool = False) -> dict[str, str]:
|
58
|
+
warn(
|
59
|
+
"`Profile.model_dump()` is deprecated. `Profile` is already a `dict[str, str]`.",
|
60
|
+
DeprecationWarning,
|
61
|
+
stacklevel=2,
|
62
|
+
)
|
63
|
+
return self
|
24
64
|
|
25
65
|
|
26
66
|
class ClientPasswordChangeRequest(APIRequestModel):
|
@@ -65,7 +105,7 @@ class SessionToken(PangeaResponseResult):
|
|
65
105
|
identity: str
|
66
106
|
email: str
|
67
107
|
scopes: Optional[Scopes] = None
|
68
|
-
profile: Profile
|
108
|
+
profile: Union[Profile, Dict[str, str]]
|
69
109
|
created_at: str
|
70
110
|
intelligence: Optional[Intelligence] = None
|
71
111
|
|
@@ -156,12 +196,43 @@ class UserListOrderBy(enum.Enum):
|
|
156
196
|
|
157
197
|
|
158
198
|
class Authenticator(APIResponseModel):
|
199
|
+
"""Authenticator."""
|
200
|
+
|
159
201
|
id: str
|
202
|
+
"""An ID for an authenticator."""
|
203
|
+
|
160
204
|
type: str
|
205
|
+
"""An authentication mechanism."""
|
206
|
+
|
161
207
|
enabled: bool
|
208
|
+
"""Enabled."""
|
209
|
+
|
162
210
|
provider: Optional[str] = None
|
211
|
+
"""Provider."""
|
212
|
+
|
213
|
+
provider_name: Optional[str] = None
|
214
|
+
"""Provider name."""
|
215
|
+
|
163
216
|
rpid: Optional[str] = None
|
217
|
+
"""RPID."""
|
218
|
+
|
164
219
|
phase: Optional[str] = None
|
220
|
+
"""Phase."""
|
221
|
+
|
222
|
+
enrolling_browser: Optional[str] = None
|
223
|
+
"""Enrolling browser."""
|
224
|
+
|
225
|
+
enrolling_ip: Optional[str] = None
|
226
|
+
"""Enrolling IP."""
|
227
|
+
|
228
|
+
created_at: str
|
229
|
+
"""A time in ISO-8601 format."""
|
230
|
+
|
231
|
+
updated_at: str
|
232
|
+
"""A time in ISO-8601 format."""
|
233
|
+
|
234
|
+
state: Optional[str] = None
|
235
|
+
"""State."""
|
165
236
|
|
166
237
|
|
167
238
|
class User(PangeaResponseResult):
|
@@ -174,7 +245,7 @@ class User(PangeaResponseResult):
|
|
174
245
|
username: str
|
175
246
|
"""A username."""
|
176
247
|
|
177
|
-
profile: Profile
|
248
|
+
profile: Union[Profile, Dict[str, str]]
|
178
249
|
"""A user profile as a collection of string properties."""
|
179
250
|
|
180
251
|
verified: bool
|
@@ -207,7 +278,7 @@ class UserCreateRequest(APIRequestModel):
|
|
207
278
|
email: str
|
208
279
|
"""An email address."""
|
209
280
|
|
210
|
-
profile: Profile
|
281
|
+
profile: Union[Profile, Dict[str, str]]
|
211
282
|
"""A user profile as a collection of string properties."""
|
212
283
|
|
213
284
|
username: Optional[str] = None
|
@@ -397,7 +468,7 @@ class UserProfileGetResult(User):
|
|
397
468
|
|
398
469
|
|
399
470
|
class UserProfileUpdateRequest(APIRequestModel):
|
400
|
-
profile: Profile
|
471
|
+
profile: Union[Profile, Dict[str, str]]
|
401
472
|
"""Updates to a user profile."""
|
402
473
|
|
403
474
|
id: Optional[str] = None
|
@@ -485,6 +556,7 @@ class UserAuthenticatorsListRequest(APIRequestModel):
|
|
485
556
|
|
486
557
|
class UserAuthenticatorsListResult(PangeaResponseResult):
|
487
558
|
authenticators: List[Authenticator] = []
|
559
|
+
"""A list of authenticators."""
|
488
560
|
|
489
561
|
|
490
562
|
class FlowCompleteRequest(APIRequestModel):
|
@@ -583,7 +655,7 @@ class FlowUpdateDataPassword(APIRequestModel):
|
|
583
655
|
|
584
656
|
|
585
657
|
class FlowUpdateDataProfile(APIRequestModel):
|
586
|
-
profile: Profile
|
658
|
+
profile: Union[Profile, Dict[str, str]]
|
587
659
|
|
588
660
|
|
589
661
|
class FlowUpdateDataProvisionalEnrollment(APIRequestModel):
|
@@ -705,7 +777,7 @@ class SessionItem(APIResponseModel):
|
|
705
777
|
expire: str
|
706
778
|
email: str
|
707
779
|
scopes: Optional[Scopes] = None
|
708
|
-
profile: Profile
|
780
|
+
profile: Union[Profile, Dict[str, str]]
|
709
781
|
created_at: str
|
710
782
|
active_token: Optional[SessionToken] = None
|
711
783
|
|
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)
|