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/__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/authn.py
CHANGED
@@ -7,7 +7,7 @@ from typing import Dict, List, Optional, Union
|
|
7
7
|
import pangea.services.authn.models as m
|
8
8
|
from pangea.asyncio.services.base import ServiceBaseAsync
|
9
9
|
from pangea.config import PangeaConfig
|
10
|
-
from pangea.response import PangeaResponse
|
10
|
+
from pangea.response import PangeaResponse, PangeaResponseResult
|
11
11
|
|
12
12
|
SERVICE_NAME = "authn"
|
13
13
|
|
@@ -399,6 +399,25 @@ class AuthNAsync(ServiceBaseAsync):
|
|
399
399
|
"v2/client/password/change", m.ClientPasswordChangeResult, data=input.model_dump(exclude_none=True)
|
400
400
|
)
|
401
401
|
|
402
|
+
async def expire(self, user_id: str) -> PangeaResponse[PangeaResponseResult]:
|
403
|
+
"""
|
404
|
+
Expire a user's password
|
405
|
+
|
406
|
+
Expire a user's password.
|
407
|
+
|
408
|
+
OperationId: authn_post_v2_user_password_expire
|
409
|
+
|
410
|
+
Args:
|
411
|
+
user_id: The identity of a user or a service.
|
412
|
+
|
413
|
+
Returns:
|
414
|
+
A PangeaResponse with an empty object in the response.result field.
|
415
|
+
|
416
|
+
Examples:
|
417
|
+
await authn.client.password.expire("pui_[...]")
|
418
|
+
"""
|
419
|
+
return await self.request.post("v2/user/password/expire", PangeaResponseResult, {"id": user_id})
|
420
|
+
|
402
421
|
class TokenAsync(ServiceBaseAsync):
|
403
422
|
service_name = SERVICE_NAME
|
404
423
|
|
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/authn/authn.py
CHANGED
@@ -6,7 +6,7 @@ from typing import Dict, List, Optional, Union
|
|
6
6
|
|
7
7
|
import pangea.services.authn.models as m
|
8
8
|
from pangea.config import PangeaConfig
|
9
|
-
from pangea.response import PangeaResponse
|
9
|
+
from pangea.response import PangeaResponse, PangeaResponseResult
|
10
10
|
from pangea.services.base import ServiceBase
|
11
11
|
|
12
12
|
SERVICE_NAME = "authn"
|
@@ -363,10 +363,10 @@ class AuthN(ServiceBase):
|
|
363
363
|
|
364
364
|
def __init__(
|
365
365
|
self,
|
366
|
-
token,
|
367
|
-
config=None,
|
368
|
-
logger_name="pangea",
|
369
|
-
):
|
366
|
+
token: str,
|
367
|
+
config: PangeaConfig | None = None,
|
368
|
+
logger_name: str = "pangea",
|
369
|
+
) -> None:
|
370
370
|
super().__init__(token, config, logger_name=logger_name)
|
371
371
|
|
372
372
|
def change(
|
@@ -399,6 +399,25 @@ class AuthN(ServiceBase):
|
|
399
399
|
"v2/client/password/change", m.ClientPasswordChangeResult, data=input.model_dump(exclude_none=True)
|
400
400
|
)
|
401
401
|
|
402
|
+
def expire(self, user_id: str) -> PangeaResponse[PangeaResponseResult]:
|
403
|
+
"""
|
404
|
+
Expire a user's password
|
405
|
+
|
406
|
+
Expire a user's password.
|
407
|
+
|
408
|
+
OperationId: authn_post_v2_user_password_expire
|
409
|
+
|
410
|
+
Args:
|
411
|
+
user_id: The identity of a user or a service.
|
412
|
+
|
413
|
+
Returns:
|
414
|
+
A PangeaResponse with an empty object in the response.result field.
|
415
|
+
|
416
|
+
Examples:
|
417
|
+
authn.client.password.expire("pui_[...]")
|
418
|
+
"""
|
419
|
+
return self.request.post("v2/user/password/expire", PangeaResponseResult, {"id": user_id})
|
420
|
+
|
402
421
|
class Token(ServiceBase):
|
403
422
|
service_name = SERVICE_NAME
|
404
423
|
|