pangea-sdk 3.8.0b1__py3-none-any.whl → 5.3.0__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- pangea/__init__.py +1 -1
- pangea/asyncio/file_uploader.py +1 -1
- pangea/asyncio/request.py +49 -31
- pangea/asyncio/services/__init__.py +2 -0
- pangea/asyncio/services/audit.py +192 -31
- pangea/asyncio/services/authn.py +187 -109
- pangea/asyncio/services/authz.py +285 -0
- pangea/asyncio/services/base.py +21 -2
- pangea/asyncio/services/embargo.py +2 -2
- pangea/asyncio/services/file_scan.py +24 -9
- pangea/asyncio/services/intel.py +108 -34
- pangea/asyncio/services/redact.py +72 -4
- pangea/asyncio/services/sanitize.py +217 -0
- pangea/asyncio/services/share.py +246 -73
- pangea/asyncio/services/vault.py +1710 -750
- pangea/crypto/rsa.py +135 -0
- pangea/deep_verify.py +7 -1
- pangea/dump_audit.py +9 -8
- pangea/request.py +83 -59
- pangea/response.py +49 -31
- pangea/services/__init__.py +2 -0
- pangea/services/audit/audit.py +205 -42
- pangea/services/audit/models.py +56 -8
- pangea/services/audit/signing.py +6 -5
- pangea/services/audit/util.py +3 -3
- pangea/services/authn/authn.py +140 -70
- pangea/services/authn/models.py +167 -11
- pangea/services/authz.py +400 -0
- pangea/services/base.py +39 -8
- pangea/services/embargo.py +2 -2
- pangea/services/file_scan.py +32 -15
- pangea/services/intel.py +157 -32
- pangea/services/redact.py +152 -4
- pangea/services/sanitize.py +388 -0
- pangea/services/share/share.py +683 -107
- pangea/services/vault/models/asymmetric.py +120 -18
- pangea/services/vault/models/common.py +439 -141
- pangea/services/vault/models/keys.py +94 -0
- pangea/services/vault/models/secret.py +27 -3
- pangea/services/vault/models/symmetric.py +68 -22
- pangea/services/vault/vault.py +1690 -749
- pangea/tools.py +6 -7
- pangea/utils.py +16 -27
- pangea/verify_audit.py +270 -83
- {pangea_sdk-3.8.0b1.dist-info → pangea_sdk-5.3.0.dist-info}/METADATA +43 -35
- pangea_sdk-5.3.0.dist-info/RECORD +56 -0
- {pangea_sdk-3.8.0b1.dist-info → pangea_sdk-5.3.0.dist-info}/WHEEL +1 -1
- pangea_sdk-3.8.0b1.dist-info/RECORD +0 -50
@@ -0,0 +1,285 @@
|
|
1
|
+
# Copyright 2022 Pangea Cyber Corporation
|
2
|
+
# Author: Pangea Cyber Corporation
|
3
|
+
|
4
|
+
from __future__ import annotations
|
5
|
+
|
6
|
+
from typing import Any, Dict, List, Optional
|
7
|
+
|
8
|
+
from pangea.asyncio.services.base import ServiceBaseAsync
|
9
|
+
from pangea.config import PangeaConfig
|
10
|
+
from pangea.response import PangeaResponse
|
11
|
+
from pangea.services.authz import (
|
12
|
+
CheckRequest,
|
13
|
+
CheckResult,
|
14
|
+
ItemOrder,
|
15
|
+
ListResourcesRequest,
|
16
|
+
ListResourcesResult,
|
17
|
+
ListSubjectsRequest,
|
18
|
+
ListSubjectsResult,
|
19
|
+
Resource,
|
20
|
+
Subject,
|
21
|
+
Tuple,
|
22
|
+
TupleCreateRequest,
|
23
|
+
TupleCreateResult,
|
24
|
+
TupleDeleteRequest,
|
25
|
+
TupleDeleteResult,
|
26
|
+
TupleListFilter,
|
27
|
+
TupleListRequest,
|
28
|
+
TupleListResult,
|
29
|
+
TupleOrderBy,
|
30
|
+
)
|
31
|
+
|
32
|
+
|
33
|
+
class AuthZAsync(ServiceBaseAsync):
|
34
|
+
"""AuthZ service client.
|
35
|
+
|
36
|
+
Provides methods to interact with the Pangea AuthZ Service.
|
37
|
+
Documentation for the AuthZ Service API can be found at
|
38
|
+
<https://pangea.cloud/docs/api/authz>.
|
39
|
+
|
40
|
+
Examples:
|
41
|
+
import os
|
42
|
+
from pangea.asyncio.services import AuthZAsync
|
43
|
+
from pangea.config import PangeaConfig
|
44
|
+
|
45
|
+
PANGEA_TOKEN = os.getenv("PANGEA_AUTHZ_TOKEN")
|
46
|
+
|
47
|
+
authz_config = PangeaConfig(domain="aws.us.pangea.cloud")
|
48
|
+
|
49
|
+
# Setup Pangea AuthZ service client
|
50
|
+
authz = AuthZAsync(token=PANGEA_TOKEN, config=authz_config)
|
51
|
+
"""
|
52
|
+
|
53
|
+
service_name = "authz"
|
54
|
+
|
55
|
+
def __init__(
|
56
|
+
self, token: str, config: PangeaConfig | None = None, logger_name: str = "pangea", config_id: str | None = None
|
57
|
+
) -> None:
|
58
|
+
"""
|
59
|
+
AuthZ client
|
60
|
+
|
61
|
+
Initializes a new AuthZ client.
|
62
|
+
|
63
|
+
Args:
|
64
|
+
token: Pangea API token.
|
65
|
+
config: Configuration.
|
66
|
+
logger_name: Logger name.
|
67
|
+
config_id: Configuration ID.
|
68
|
+
|
69
|
+
Examples:
|
70
|
+
config = PangeaConfig(domain="aws.us.pangea.cloud")
|
71
|
+
authz = AuthZAsync(token="pangea_token", config=config)
|
72
|
+
"""
|
73
|
+
|
74
|
+
super().__init__(token, config, logger_name, config_id=config_id)
|
75
|
+
|
76
|
+
async def tuple_create(self, tuples: List[Tuple]) -> PangeaResponse[TupleCreateResult]:
|
77
|
+
"""Create tuples.
|
78
|
+
|
79
|
+
Create tuples in the AuthZ Service. The request will fail if there is no schema
|
80
|
+
or the tuples do not validate against the schema.
|
81
|
+
|
82
|
+
Args:
|
83
|
+
tuples (List[Tuple]): List of tuples to be created.
|
84
|
+
|
85
|
+
Raises:
|
86
|
+
PangeaAPIException: If an API Error happens.
|
87
|
+
|
88
|
+
Returns:
|
89
|
+
Pangea Response with empty result.
|
90
|
+
Available response fields can be found in our
|
91
|
+
[API Documentation](https://pangea.cloud/docs/api/authz#/v1/tuple/create).
|
92
|
+
|
93
|
+
Examples:
|
94
|
+
await authz.tuple_create(
|
95
|
+
tuples=[
|
96
|
+
Tuple(
|
97
|
+
resource=Resource(type="file", id="file_1"),
|
98
|
+
relation="owner",
|
99
|
+
subject=Subject(type="user", id="user_1"),
|
100
|
+
)
|
101
|
+
]
|
102
|
+
)
|
103
|
+
"""
|
104
|
+
|
105
|
+
input_data = TupleCreateRequest(tuples=tuples)
|
106
|
+
return await self.request.post(
|
107
|
+
"v1/tuple/create", TupleCreateResult, data=input_data.model_dump(exclude_none=True)
|
108
|
+
)
|
109
|
+
|
110
|
+
async def tuple_list(
|
111
|
+
self,
|
112
|
+
filter: TupleListFilter,
|
113
|
+
size: Optional[int] = None,
|
114
|
+
last: Optional[str] = None,
|
115
|
+
order: Optional[ItemOrder] = None,
|
116
|
+
order_by: Optional[TupleOrderBy] = None,
|
117
|
+
) -> PangeaResponse[TupleListResult]:
|
118
|
+
"""List tuples.
|
119
|
+
|
120
|
+
Return a paginated list of filtered tuples. The filter is given in terms
|
121
|
+
of a tuple. Fill out the fields that you want to filter. If the filter
|
122
|
+
is empty it will return all the tuples.
|
123
|
+
|
124
|
+
Args:
|
125
|
+
filter (TupleListFilter): The filter for listing tuples.
|
126
|
+
size (Optional[int]): The size of the result set. Default is None.
|
127
|
+
last (Optional[str]): The last token from a previous response. Default is None.
|
128
|
+
order (Optional[ItemOrder]): Order results asc(ending) or desc(ending).
|
129
|
+
order_by (Optional[TupleOrderBy]): Which field to order results by.
|
130
|
+
|
131
|
+
Raises:
|
132
|
+
PangeaAPIException: If an API Error happens.
|
133
|
+
|
134
|
+
Returns:
|
135
|
+
Pangea Response with a list of tuples and the last token.
|
136
|
+
Available response fields can be found in our
|
137
|
+
[API Documentation](https://pangea.cloud/docs/api/authz#/v1/tuple/list).
|
138
|
+
|
139
|
+
Examples:
|
140
|
+
await authz.tuple_list(TupleListFilter(subject_type="user", subject_id="user_1"))
|
141
|
+
"""
|
142
|
+
input_data = TupleListRequest(
|
143
|
+
filter=filter.model_dump(exclude_none=True), size=size, last=last, order=order, order_by=order_by
|
144
|
+
)
|
145
|
+
return await self.request.post("v1/tuple/list", TupleListResult, data=input_data.model_dump(exclude_none=True))
|
146
|
+
|
147
|
+
async def tuple_delete(self, tuples: List[Tuple]) -> PangeaResponse[TupleDeleteResult]:
|
148
|
+
"""Delete tuples.
|
149
|
+
|
150
|
+
Delete tuples in the AuthZ Service.
|
151
|
+
|
152
|
+
Args:
|
153
|
+
tuples (List[Tuple]): List of tuples to be deleted.
|
154
|
+
|
155
|
+
Raises:
|
156
|
+
PangeaAPIException: If an API Error happens.
|
157
|
+
|
158
|
+
Returns:
|
159
|
+
Pangea Response with empty result.
|
160
|
+
Available response fields can be found in our
|
161
|
+
[API Documentation](https://pangea.cloud/docs/api/authz#/v1/tuple/delete).
|
162
|
+
|
163
|
+
Examples:
|
164
|
+
await authz.tuple_delete(
|
165
|
+
tuples=[
|
166
|
+
Tuple(
|
167
|
+
resource=Resource(type="file", id="file_1"),
|
168
|
+
relation="owner",
|
169
|
+
subject=Subject(type="user", id="user_1"),
|
170
|
+
)
|
171
|
+
]
|
172
|
+
)
|
173
|
+
"""
|
174
|
+
|
175
|
+
input_data = TupleDeleteRequest(tuples=tuples)
|
176
|
+
return await self.request.post(
|
177
|
+
"v1/tuple/delete", TupleDeleteResult, data=input_data.model_dump(exclude_none=True)
|
178
|
+
)
|
179
|
+
|
180
|
+
async def check(
|
181
|
+
self,
|
182
|
+
resource: Resource,
|
183
|
+
action: str,
|
184
|
+
subject: Subject,
|
185
|
+
debug: Optional[bool] = None,
|
186
|
+
attributes: Optional[Dict[str, Any]] = None,
|
187
|
+
) -> PangeaResponse[CheckResult]:
|
188
|
+
"""Perform a check request.
|
189
|
+
|
190
|
+
Check if a subject has permission to perform an action on the resource.
|
191
|
+
|
192
|
+
Args:
|
193
|
+
resource (Resource): The resource to check.
|
194
|
+
action (str): The action to check.
|
195
|
+
subject (Subject): The subject to check.
|
196
|
+
debug (Optional[bool]): Setting this value to True will provide a detailed analysis of the check.
|
197
|
+
attributes (Optional[Dict[str, Any]]): Additional attributes for the check.
|
198
|
+
|
199
|
+
Raises:
|
200
|
+
PangeaAPIException: If an API Error happens.
|
201
|
+
|
202
|
+
Returns:
|
203
|
+
Pangea Response with the result of the check.
|
204
|
+
Available response fields can be found in our
|
205
|
+
[API Documentation](https://pangea.cloud/docs/api/authz#/v1/check).
|
206
|
+
|
207
|
+
Examples:
|
208
|
+
await authz.check(
|
209
|
+
resource=Resource(type="file", id="file_1"),
|
210
|
+
action="update",
|
211
|
+
subject=Subject(type="user", id="user_1"),
|
212
|
+
debug=True,
|
213
|
+
)
|
214
|
+
"""
|
215
|
+
|
216
|
+
input_data = CheckRequest(resource=resource, action=action, subject=subject, debug=debug, attributes=attributes)
|
217
|
+
return await self.request.post("v1/check", CheckResult, data=input_data.model_dump(exclude_none=True))
|
218
|
+
|
219
|
+
async def list_resources(
|
220
|
+
self, type: str, action: str, subject: Subject, attributes: Optional[Dict[str, Any]] = None
|
221
|
+
) -> PangeaResponse[ListResourcesResult]:
|
222
|
+
"""List resources.
|
223
|
+
|
224
|
+
Given a type, action, and subject, list all the resources in the
|
225
|
+
type that the subject has access to the action with.
|
226
|
+
|
227
|
+
Args:
|
228
|
+
type (str): The type to filter resources.
|
229
|
+
action (str): The action to filter resources.
|
230
|
+
subject (Subject): The subject to filter resources.
|
231
|
+
attributes (Optional[Dict[str, Any]]): A JSON object of attribute data.
|
232
|
+
|
233
|
+
Raises:
|
234
|
+
PangeaAPIException: If an API Error happens.
|
235
|
+
|
236
|
+
Returns:
|
237
|
+
Pangea Response with a list of resource IDs.
|
238
|
+
Available response fields can be found in our
|
239
|
+
[API Documentation](https://pangea.cloud/docs/api/authz#/v1/list-resources).
|
240
|
+
|
241
|
+
Examples:
|
242
|
+
await authz.list_resources(
|
243
|
+
type="file",
|
244
|
+
action="update",
|
245
|
+
subject=Subject(type="user", id="user_1"),
|
246
|
+
)
|
247
|
+
"""
|
248
|
+
|
249
|
+
input_data = ListResourcesRequest(type=type, action=action, subject=subject, attributes=attributes)
|
250
|
+
return await self.request.post(
|
251
|
+
"v1/list-resources", ListResourcesResult, data=input_data.model_dump(exclude_none=True)
|
252
|
+
)
|
253
|
+
|
254
|
+
async def list_subjects(
|
255
|
+
self, resource: Resource, action: str, attributes: Optional[Dict[str, Any]] = None
|
256
|
+
) -> PangeaResponse[ListSubjectsResult]:
|
257
|
+
"""List subjects.
|
258
|
+
|
259
|
+
Given a resource and an action, return the list of subjects who have
|
260
|
+
access to the action for the given resource.
|
261
|
+
|
262
|
+
Args:
|
263
|
+
resource (Resource): The resource to filter subjects.
|
264
|
+
action (str): The action to filter subjects.
|
265
|
+
attributes (Optional[Dict[str, Any]]): A JSON object of attribute data.
|
266
|
+
|
267
|
+
Raises:
|
268
|
+
PangeaAPIException: If an API Error happens.
|
269
|
+
|
270
|
+
Returns:
|
271
|
+
Pangea Response with a list of subjects.
|
272
|
+
Available response fields can be found in our
|
273
|
+
[API Documentation](https://pangea.cloud/docs/api/authz#/v1/list-subjects).
|
274
|
+
|
275
|
+
Examples:
|
276
|
+
await authz.list_subjects(
|
277
|
+
resource=Resource(type="file", id="file_1"),
|
278
|
+
action="update",
|
279
|
+
)
|
280
|
+
"""
|
281
|
+
|
282
|
+
input_data = ListSubjectsRequest(resource=resource, action=action, attributes=attributes)
|
283
|
+
return await self.request.post(
|
284
|
+
"v1/list-subjects", ListSubjectsResult, data=input_data.model_dump(exclude_none=True)
|
285
|
+
)
|
pangea/asyncio/services/base.py
CHANGED
@@ -1,8 +1,11 @@
|
|
1
1
|
# Copyright 2022 Pangea Cyber Corporation
|
2
2
|
# Author: Pangea Cyber Corporation
|
3
|
+
from __future__ import annotations
|
3
4
|
|
4
5
|
from typing import Dict, Optional, Type, Union
|
5
6
|
|
7
|
+
from typing_extensions import override
|
8
|
+
|
6
9
|
from pangea.asyncio.request import PangeaRequestAsync
|
7
10
|
from pangea.exceptions import AcceptedRequestException
|
8
11
|
from pangea.response import AttachedFile, PangeaResponse, PangeaResponseResult
|
@@ -23,6 +26,7 @@ class ServiceBaseAsync(ServiceBase):
|
|
23
26
|
|
24
27
|
return self._request
|
25
28
|
|
29
|
+
@override
|
26
30
|
async def poll_result( # type: ignore[override]
|
27
31
|
self,
|
28
32
|
exception: Optional[AcceptedRequestException] = None,
|
@@ -36,7 +40,8 @@ class ServiceBaseAsync(ServiceBase):
|
|
36
40
|
Returns request's result that has been accepted by the server
|
37
41
|
|
38
42
|
Args:
|
39
|
-
exception
|
43
|
+
exception: Exception that was previously raised by the SDK on a call
|
44
|
+
that is being processed.
|
40
45
|
|
41
46
|
Returns:
|
42
47
|
PangeaResponse
|
@@ -58,7 +63,21 @@ class ServiceBaseAsync(ServiceBase):
|
|
58
63
|
else:
|
59
64
|
raise AttributeError("Need to set exception, response or request_id")
|
60
65
|
|
61
|
-
|
66
|
+
@override
|
67
|
+
async def download_file(self, url: str, filename: str | None = None) -> AttachedFile: # type: ignore[override]
|
68
|
+
"""
|
69
|
+
Download file
|
70
|
+
|
71
|
+
Download a file from the specified URL and save it with the given
|
72
|
+
filename.
|
73
|
+
|
74
|
+
Args:
|
75
|
+
url: URL of the file to download
|
76
|
+
filename: Name to save the downloaded file as. If not provided, the
|
77
|
+
filename will be determined from the Content-Disposition header or
|
78
|
+
the URL.
|
79
|
+
"""
|
80
|
+
|
62
81
|
return await self.request.download_file(url=url, filename=filename)
|
63
82
|
|
64
83
|
async def close(self):
|
@@ -57,7 +57,7 @@ class EmbargoAsync(ServiceBaseAsync):
|
|
57
57
|
response = embargo.ip_check("190.6.64.94")
|
58
58
|
"""
|
59
59
|
input = e.IPCheckRequest(ip=ip)
|
60
|
-
return await self.request.post("v1/ip/check", e.EmbargoResult, data=input.
|
60
|
+
return await self.request.post("v1/ip/check", e.EmbargoResult, data=input.model_dump())
|
61
61
|
|
62
62
|
async def iso_check(self, iso_code: str) -> PangeaResponse[e.EmbargoResult]:
|
63
63
|
"""
|
@@ -84,4 +84,4 @@ class EmbargoAsync(ServiceBaseAsync):
|
|
84
84
|
response = embargo.iso_check("CU")
|
85
85
|
"""
|
86
86
|
input = e.ISOCheckRequest(iso_code=iso_code)
|
87
|
-
return await self.request.post("v1/iso/check", result_class=e.EmbargoResult, data=input.
|
87
|
+
return await self.request.post("v1/iso/check", result_class=e.EmbargoResult, data=input.model_dump())
|
@@ -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,
|
@@ -109,8 +120,12 @@ class FileScanAsync(ServiceBaseAsync):
|
|
109
120
|
transfer_method=transfer_method,
|
110
121
|
source_url=source_url,
|
111
122
|
)
|
112
|
-
data = input.
|
113
|
-
|
123
|
+
data = input.model_dump(exclude_none=True)
|
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,
|
@@ -131,7 +146,7 @@ class FileScanAsync(ServiceBaseAsync):
|
|
131
146
|
input.sha256 = params.sha256_hex
|
132
147
|
input.size = params.size
|
133
148
|
|
134
|
-
data = input.
|
149
|
+
data = input.model_dump(exclude_none=True)
|
135
150
|
return await self.request.request_presigned_url("v1/scan", m.FileScanResult, data=data)
|
136
151
|
|
137
152
|
|
@@ -154,7 +169,7 @@ class FileUploaderAsync:
|
|
154
169
|
) -> None:
|
155
170
|
if transfer_method == TransferMethod.PUT_URL:
|
156
171
|
files = [("file", ("filename", file, "application/octet-stream"))]
|
157
|
-
await self._request.put_presigned_url(url=url, files=files)
|
172
|
+
await self._request.put_presigned_url(url=url, files=files)
|
158
173
|
elif transfer_method == TransferMethod.POST_URL:
|
159
174
|
files = [("file", ("filename", file, "application/octet-stream"))]
|
160
175
|
await self._request.post_presigned_url(url=url, data=file_details, files=files) # type: ignore[arg-type]
|