pangea-sdk 3.8.0__py3-none-any.whl → 3.8.0b2__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 +19 -14
- pangea/asyncio/services/__init__.py +2 -1
- pangea/asyncio/services/audit.py +14 -149
- pangea/asyncio/services/authn.py +48 -64
- pangea/asyncio/services/intel.py +4 -4
- pangea/asyncio/services/redact.py +1 -20
- pangea/asyncio/services/sanitize.py +185 -0
- pangea/asyncio/services/share.py +573 -0
- pangea/asyncio/services/vault.py +4 -21
- pangea/file_uploader.py +35 -0
- pangea/request.py +21 -24
- pangea/response.py +28 -27
- pangea/services/__init__.py +2 -1
- pangea/services/audit/audit.py +16 -151
- pangea/services/audit/models.py +5 -44
- pangea/services/authn/authn.py +4 -20
- pangea/services/base.py +6 -23
- pangea/services/file_scan.py +0 -1
- pangea/services/intel.py +2 -2
- pangea/services/redact.py +1 -20
- pangea/services/sanitize.py +275 -0
- pangea/services/share/file_format.py +170 -0
- pangea/services/share/share.py +877 -0
- pangea/services/vault/vault.py +6 -23
- pangea/utils.py +91 -19
- {pangea_sdk-3.8.0.dist-info → pangea_sdk-3.8.0b2.dist-info}/METADATA +15 -31
- pangea_sdk-3.8.0b2.dist-info/RECORD +52 -0
- pangea/asyncio/services/authz.py +0 -259
- pangea/services/authz.py +0 -377
- pangea_sdk-3.8.0.dist-info/RECORD +0 -46
- {pangea_sdk-3.8.0.dist-info → pangea_sdk-3.8.0b2.dist-info}/WHEEL +0 -0
pangea/request.py
CHANGED
@@ -5,12 +5,12 @@ import copy
|
|
5
5
|
import json
|
6
6
|
import logging
|
7
7
|
import time
|
8
|
-
from typing import
|
8
|
+
from typing import Dict, List, Optional, Tuple, Type, Union
|
9
9
|
|
10
|
+
import aiohttp
|
10
11
|
import requests
|
11
12
|
from requests.adapters import HTTPAdapter, Retry
|
12
13
|
from requests_toolbelt import MultipartDecoder # type: ignore
|
13
|
-
from typing_extensions import TypeVar
|
14
14
|
|
15
15
|
import pangea
|
16
16
|
import pangea.exceptions as pe
|
@@ -18,9 +18,6 @@ from pangea.config import PangeaConfig
|
|
18
18
|
from pangea.response import AttachedFile, PangeaResponse, PangeaResponseResult, ResponseStatus, TransferMethod
|
19
19
|
from pangea.utils import default_encoder
|
20
20
|
|
21
|
-
if TYPE_CHECKING:
|
22
|
-
import aiohttp
|
23
|
-
|
24
21
|
|
25
22
|
class MultipartResponse(object):
|
26
23
|
pangea_json: Dict[str, str]
|
@@ -190,9 +187,6 @@ class PangeaRequestBase(object):
|
|
190
187
|
raise pe.PangeaAPIException(f"{summary} ", response)
|
191
188
|
|
192
189
|
|
193
|
-
TResult = TypeVar("TResult", bound=PangeaResponseResult, default=PangeaResponseResult)
|
194
|
-
|
195
|
-
|
196
190
|
class PangeaRequest(PangeaRequestBase):
|
197
191
|
"""An object that makes direct calls to Pangea Service APIs.
|
198
192
|
|
@@ -208,12 +202,12 @@ class PangeaRequest(PangeaRequestBase):
|
|
208
202
|
def post(
|
209
203
|
self,
|
210
204
|
endpoint: str,
|
211
|
-
result_class: Type[
|
205
|
+
result_class: Type[PangeaResponseResult],
|
212
206
|
data: Union[str, Dict] = {},
|
213
207
|
files: Optional[List[Tuple]] = None,
|
214
208
|
poll_result: bool = True,
|
215
209
|
url: Optional[str] = None,
|
216
|
-
) -> PangeaResponse
|
210
|
+
) -> PangeaResponse:
|
217
211
|
"""Makes the POST call to a Pangea Service endpoint.
|
218
212
|
|
219
213
|
Args:
|
@@ -349,7 +343,7 @@ class PangeaRequest(PangeaRequestBase):
|
|
349
343
|
|
350
344
|
return data, files
|
351
345
|
|
352
|
-
def _handle_queued_result(self, response: PangeaResponse
|
346
|
+
def _handle_queued_result(self, response: PangeaResponse) -> PangeaResponse[Type[PangeaResponseResult]]:
|
353
347
|
if self._queued_retry_enabled and response.http_status == 202:
|
354
348
|
self.logger.debug(
|
355
349
|
json.dumps(
|
@@ -361,7 +355,7 @@ class PangeaRequest(PangeaRequestBase):
|
|
361
355
|
|
362
356
|
return response
|
363
357
|
|
364
|
-
def get(self, path: str, result_class: Type[
|
358
|
+
def get(self, path: str, result_class: Type[PangeaResponseResult], check_response: bool = True) -> PangeaResponse:
|
365
359
|
"""Makes the GET call to a Pangea Service endpoint.
|
366
360
|
|
367
361
|
Args:
|
@@ -433,21 +427,21 @@ class PangeaRequest(PangeaRequestBase):
|
|
433
427
|
raise pe.DownloadFileError(f"Failed to download file. Status: {response.status_code}", response.text)
|
434
428
|
|
435
429
|
def poll_result_by_id(
|
436
|
-
self, request_id: str, result_class: Type[
|
437
|
-
)
|
430
|
+
self, request_id: str, result_class: Union[Type[PangeaResponseResult], Type[dict]], check_response: bool = True
|
431
|
+
):
|
438
432
|
path = self._get_poll_path(request_id)
|
439
433
|
self.logger.debug(json.dumps({"service": self.service, "action": "poll_result_once", "url": path}))
|
440
|
-
return self.get(path, result_class, check_response=check_response)
|
434
|
+
return self.get(path, result_class, check_response=check_response) # type: ignore[arg-type]
|
441
435
|
|
442
436
|
def poll_result_once(
|
443
|
-
self, response: PangeaResponse
|
444
|
-
) -> PangeaResponse[
|
437
|
+
self, response: PangeaResponse, check_response: bool = True
|
438
|
+
) -> PangeaResponse[Type[PangeaResponseResult]]:
|
445
439
|
request_id = response.request_id
|
446
440
|
if not request_id:
|
447
441
|
raise pe.PangeaException("Poll result error: response did not include a 'request_id'")
|
448
442
|
|
449
443
|
if response.status != ResponseStatus.ACCEPTED.value:
|
450
|
-
raise pe.PangeaException("Response already
|
444
|
+
raise pe.PangeaException("Response already proccesed")
|
451
445
|
|
452
446
|
return self.poll_result_by_id(request_id, response.result_class, check_response=check_response)
|
453
447
|
|
@@ -459,10 +453,8 @@ class PangeaRequest(PangeaRequestBase):
|
|
459
453
|
) -> PangeaResponse:
|
460
454
|
# Send request
|
461
455
|
try:
|
462
|
-
# This should return 202 (AcceptedRequestException)
|
463
|
-
|
464
|
-
raise pe.PresignedURLException("Should return 202", resp)
|
465
|
-
|
456
|
+
# This should return 202 (AcceptedRequestException) at least zero size file is sent
|
457
|
+
return self.post(endpoint=endpoint, result_class=result_class, data=data, poll_result=False)
|
466
458
|
except pe.AcceptedRequestException as e:
|
467
459
|
accepted_exception = e
|
468
460
|
except Exception as e:
|
@@ -520,6 +512,9 @@ class PangeaRequest(PangeaRequestBase):
|
|
520
512
|
raise AttributeError("files attribute should have at least 1 file")
|
521
513
|
|
522
514
|
response = self.request_presigned_url(endpoint=endpoint, result_class=result_class, data=data)
|
515
|
+
|
516
|
+
if response.success: # This should only happen when uploading a zero bytes file
|
517
|
+
return response.raw_response
|
523
518
|
if response.accepted_result is None:
|
524
519
|
raise pe.PangeaException("No accepted_result field when requesting presigned url")
|
525
520
|
if response.accepted_result.post_url is None:
|
@@ -531,7 +526,7 @@ class PangeaRequest(PangeaRequestBase):
|
|
531
526
|
self.post_presigned_url(url=presigned_url, data=data_to_presigned, files=files)
|
532
527
|
return response.raw_response
|
533
528
|
|
534
|
-
def _poll_result_retry(self, response: PangeaResponse
|
529
|
+
def _poll_result_retry(self, response: PangeaResponse) -> PangeaResponse[Type[PangeaResponseResult]]:
|
535
530
|
retry_count = 1
|
536
531
|
start = time.time()
|
537
532
|
|
@@ -543,7 +538,9 @@ class PangeaRequest(PangeaRequestBase):
|
|
543
538
|
self.logger.debug(json.dumps({"service": self.service, "action": "poll_result_retry", "step": "exit"}))
|
544
539
|
return self._check_response(response)
|
545
540
|
|
546
|
-
def _poll_presigned_url(
|
541
|
+
def _poll_presigned_url(
|
542
|
+
self, response: PangeaResponse[Type[PangeaResponseResult]]
|
543
|
+
) -> PangeaResponse[Type[PangeaResponseResult]]:
|
547
544
|
if response.http_status != 202:
|
548
545
|
raise AttributeError("Response should be 202")
|
549
546
|
|
pangea/response.py
CHANGED
@@ -3,15 +3,16 @@
|
|
3
3
|
import datetime
|
4
4
|
import enum
|
5
5
|
import os
|
6
|
-
from typing import Any, Dict, Generic, List, Optional, Type, Union
|
6
|
+
from typing import Any, Dict, Generic, List, Optional, Type, TypeVar, Union
|
7
7
|
|
8
8
|
import aiohttp
|
9
9
|
import requests
|
10
10
|
from pydantic import BaseModel
|
11
|
-
from typing_extensions import TypeVar
|
12
11
|
|
13
12
|
from pangea.utils import format_datetime
|
14
13
|
|
14
|
+
T = TypeVar("T")
|
15
|
+
|
15
16
|
|
16
17
|
class AttachedFile(object):
|
17
18
|
filename: str
|
@@ -28,6 +29,7 @@ class AttachedFile(object):
|
|
28
29
|
filename = self.filename if self.filename else "default_save_filename"
|
29
30
|
|
30
31
|
filepath = os.path.join(dest_folder, filename)
|
32
|
+
filepath = self._find_available_file(filepath)
|
31
33
|
directory = os.path.dirname(filepath)
|
32
34
|
if not os.path.exists(directory):
|
33
35
|
os.makedirs(directory)
|
@@ -35,6 +37,17 @@ class AttachedFile(object):
|
|
35
37
|
with open(filepath, "wb") as file:
|
36
38
|
file.write(self.file)
|
37
39
|
|
40
|
+
def _find_available_file(self, file_path):
|
41
|
+
base_name, ext = os.path.splitext(file_path)
|
42
|
+
counter = 1
|
43
|
+
while os.path.exists(file_path):
|
44
|
+
if ext:
|
45
|
+
file_path = f"{base_name}_{counter}{ext}"
|
46
|
+
else:
|
47
|
+
file_path = f"{base_name}_{counter}"
|
48
|
+
counter += 1
|
49
|
+
return file_path
|
50
|
+
|
38
51
|
|
39
52
|
class TransferMethod(str, enum.Enum):
|
40
53
|
MULTIPART = "multipart"
|
@@ -142,34 +155,22 @@ class ResponseStatus(str, enum.Enum):
|
|
142
155
|
|
143
156
|
|
144
157
|
class ResponseHeader(APIResponseModel):
|
145
|
-
"""Pangea response API header."""
|
146
|
-
|
147
|
-
request_id: str
|
148
|
-
"""A unique identifier assigned to each request made to the API."""
|
149
|
-
|
150
|
-
request_time: str
|
151
158
|
"""
|
152
|
-
|
159
|
+
Pangea response API header.
|
160
|
+
|
161
|
+
Arguments:
|
162
|
+
request_id -- The request ID.
|
163
|
+
request_time -- The time the request was issued, ISO8601.
|
164
|
+
response_time -- The time the response was issued, ISO8601.
|
165
|
+
status -- Pangea response status
|
166
|
+
summary -- The summary of the response.
|
153
167
|
"""
|
154
168
|
|
169
|
+
request_id: str
|
170
|
+
request_time: str
|
155
171
|
response_time: str
|
156
|
-
"""
|
157
|
-
Duration it takes for the API to process a request and generate a response.
|
158
|
-
"""
|
159
|
-
|
160
172
|
status: str
|
161
|
-
"""
|
162
|
-
Represents the status or outcome of the API request.
|
163
|
-
"""
|
164
|
-
|
165
173
|
summary: str
|
166
|
-
"""
|
167
|
-
Provides a concise and brief overview of the purpose or primary objective of
|
168
|
-
the API endpoint.
|
169
|
-
"""
|
170
|
-
|
171
|
-
|
172
|
-
T = TypeVar("T", bound=PangeaResponseResult, default=PangeaResponseResult)
|
173
174
|
|
174
175
|
|
175
176
|
class PangeaResponse(Generic[T], ResponseHeader):
|
@@ -178,14 +179,14 @@ class PangeaResponse(Generic[T], ResponseHeader):
|
|
178
179
|
result: Optional[T] = None
|
179
180
|
pangea_error: Optional[PangeaError] = None
|
180
181
|
accepted_result: Optional[AcceptedResult] = None
|
181
|
-
result_class: Type[
|
182
|
+
result_class: Union[Type[PangeaResponseResult], Type[dict]] = PangeaResponseResult
|
182
183
|
_json: Any
|
183
184
|
attached_files: List[AttachedFile] = []
|
184
185
|
|
185
186
|
def __init__(
|
186
187
|
self,
|
187
188
|
response: requests.Response,
|
188
|
-
result_class: Type[
|
189
|
+
result_class: Union[Type[PangeaResponseResult], Type[dict]],
|
189
190
|
json: dict,
|
190
191
|
attached_files: List[AttachedFile] = [],
|
191
192
|
):
|
@@ -197,7 +198,7 @@ class PangeaResponse(Generic[T], ResponseHeader):
|
|
197
198
|
self.attached_files = attached_files
|
198
199
|
|
199
200
|
self.result = (
|
200
|
-
self.result_class(**self.raw_result)
|
201
|
+
self.result_class(**self.raw_result) # type: ignore[assignment]
|
201
202
|
if self.raw_result is not None and issubclass(self.result_class, PangeaResponseResult) and self.success
|
202
203
|
else None
|
203
204
|
)
|
pangea/services/__init__.py
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
from .audit.audit import Audit
|
2
2
|
from .authn.authn import AuthN
|
3
|
-
from .authz import AuthZ
|
4
3
|
from .embargo import Embargo
|
5
4
|
from .file_scan import FileScan
|
6
5
|
from .intel import DomainIntel, FileIntel, IpIntel, UrlIntel, UserIntel
|
7
6
|
from .redact import Redact
|
7
|
+
from .share.share import Share
|
8
|
+
from .sanitize import Sanitize
|
8
9
|
from .vault.vault import Vault
|
pangea/services/audit/audit.py
CHANGED
@@ -1,14 +1,11 @@
|
|
1
1
|
# Copyright 2022 Pangea Cyber Corporation
|
2
2
|
# Author: Pangea Cyber Corporation
|
3
|
-
from __future__ import annotations
|
4
|
-
|
5
3
|
import datetime
|
6
4
|
import json
|
7
|
-
from typing import Any, Dict, List, Optional,
|
5
|
+
from typing import Any, Dict, List, Optional, Set, Tuple, Union
|
8
6
|
|
9
7
|
import pangea.exceptions as pexc
|
10
|
-
from pangea.
|
11
|
-
from pangea.response import PangeaResponse, PangeaResponseResult
|
8
|
+
from pangea.response import PangeaResponse
|
12
9
|
from pangea.services.audit.exceptions import AuditException, EventCorruption
|
13
10
|
from pangea.services.audit.models import (
|
14
11
|
DownloadFormat,
|
@@ -17,7 +14,6 @@ from pangea.services.audit.models import (
|
|
17
14
|
Event,
|
18
15
|
EventEnvelope,
|
19
16
|
EventVerification,
|
20
|
-
ExportRequest,
|
21
17
|
LogBulkRequest,
|
22
18
|
LogBulkResult,
|
23
19
|
LogEvent,
|
@@ -55,8 +51,8 @@ from pangea.utils import canonicalize_nested_json
|
|
55
51
|
|
56
52
|
class AuditBase:
|
57
53
|
def __init__(
|
58
|
-
self, private_key_file: str = "", public_key_info:
|
59
|
-
)
|
54
|
+
self, private_key_file: str = "", public_key_info: Dict[str, str] = {}, tenant_id: Optional[str] = None
|
55
|
+
):
|
60
56
|
self.pub_roots: Dict[int, PublishedRoot] = {}
|
61
57
|
self.buffer_data: Optional[str] = None
|
62
58
|
self.signer: Optional[Signer] = Signer(private_key_file) if private_key_file else None
|
@@ -336,9 +332,7 @@ class AuditBase:
|
|
336
332
|
if audit_envelope and audit_envelope.signature and public_key:
|
337
333
|
v = Verifier()
|
338
334
|
verification = v.verify_signature(
|
339
|
-
audit_envelope.signature,
|
340
|
-
canonicalize_event(Event(**audit_envelope.event)),
|
341
|
-
public_key,
|
335
|
+
audit_envelope.signature, canonicalize_event(audit_envelope.event), public_key # type: ignore[arg-type]
|
342
336
|
)
|
343
337
|
if verification is not None:
|
344
338
|
return EventVerification.PASS if verification else EventVerification.FAIL
|
@@ -380,32 +374,14 @@ class Audit(ServiceBase, AuditBase):
|
|
380
374
|
|
381
375
|
def __init__(
|
382
376
|
self,
|
383
|
-
token
|
384
|
-
config
|
377
|
+
token,
|
378
|
+
config=None,
|
385
379
|
private_key_file: str = "",
|
386
|
-
public_key_info:
|
387
|
-
tenant_id: str
|
388
|
-
logger_name
|
389
|
-
config_id: str
|
390
|
-
)
|
391
|
-
"""
|
392
|
-
Audit client
|
393
|
-
|
394
|
-
Initializes a new Audit client.
|
395
|
-
|
396
|
-
Args:
|
397
|
-
token: Pangea API token.
|
398
|
-
config: Configuration.
|
399
|
-
private_key_file: Private key filepath.
|
400
|
-
public_key_info: Public key information.
|
401
|
-
tenant_id: Tenant ID.
|
402
|
-
logger_name: Logger name.
|
403
|
-
config_id: Configuration ID.
|
404
|
-
|
405
|
-
Examples:
|
406
|
-
config = PangeaConfig(domain="pangea_domain")
|
407
|
-
audit = Audit(token="pangea_token", config=config)
|
408
|
-
"""
|
380
|
+
public_key_info: Dict[str, str] = {},
|
381
|
+
tenant_id: Optional[str] = None,
|
382
|
+
logger_name="pangea",
|
383
|
+
config_id: Optional[str] = None,
|
384
|
+
):
|
409
385
|
# FIXME: Temporary check to deprecate config_id from PangeaConfig.
|
410
386
|
# Delete it when deprecate PangeaConfig.config_id
|
411
387
|
if config_id and config is not None and config.config_id is not None:
|
@@ -622,7 +598,7 @@ class Audit(ServiceBase, AuditBase):
|
|
622
598
|
end: Optional[Union[datetime.datetime, str]] = None,
|
623
599
|
limit: Optional[int] = None,
|
624
600
|
max_results: Optional[int] = None,
|
625
|
-
search_restriction: Optional[
|
601
|
+
search_restriction: Optional[dict] = None,
|
626
602
|
verbose: Optional[bool] = None,
|
627
603
|
verify_consistency: bool = False,
|
628
604
|
verify_events: bool = True,
|
@@ -651,7 +627,7 @@ class Audit(ServiceBase, AuditBase):
|
|
651
627
|
end (datetime, optional): An RFC-3339 formatted timestamp, or relative time adjustment from the current time.
|
652
628
|
limit (int, optional): Optional[int] = None,
|
653
629
|
max_results (int, optional): Maximum number of results to return.
|
654
|
-
search_restriction (
|
630
|
+
search_restriction (dict, optional): A list of keys to restrict the search results to. Useful for partitioning data available to the query string.
|
655
631
|
verbose (bool, optional): If true, response include root and membership and consistency proofs.
|
656
632
|
verify_consistency (bool): True to verify logs consistency
|
657
633
|
verify_events (bool): True to verify hash events and signatures
|
@@ -702,7 +678,6 @@ class Audit(ServiceBase, AuditBase):
|
|
702
678
|
id: str,
|
703
679
|
limit: Optional[int] = 20,
|
704
680
|
offset: Optional[int] = 0,
|
705
|
-
assert_search_restriction: Optional[Dict[str, Sequence[str]]] = None,
|
706
681
|
verify_consistency: bool = False,
|
707
682
|
verify_events: bool = True,
|
708
683
|
) -> PangeaResponse[SearchResultOutput]:
|
@@ -717,7 +692,6 @@ class Audit(ServiceBase, AuditBase):
|
|
717
692
|
id (string): the id of a search action, found in `response.result.id`
|
718
693
|
limit (integer, optional): the maximum number of results to return, default is 20
|
719
694
|
offset (integer, optional): the position of the first result to return, default is 0
|
720
|
-
assert_search_restriction (Dict[str, Sequence[str]], optional): Assert the requested search results were queried with the exact same search restrictions, to ensure the results comply to the expected restrictions.
|
721
695
|
verify_consistency (bool): True to verify logs consistency
|
722
696
|
verify_events (bool): True to verify hash events and signatures
|
723
697
|
Raises:
|
@@ -742,7 +716,6 @@ class Audit(ServiceBase, AuditBase):
|
|
742
716
|
id=id,
|
743
717
|
limit=limit,
|
744
718
|
offset=offset,
|
745
|
-
assert_search_restriction=assert_search_restriction,
|
746
719
|
)
|
747
720
|
response: PangeaResponse[SearchResultOutput] = self.request.post(
|
748
721
|
"v1/results", SearchResultOutput, data=input.dict(exclude_none=True)
|
@@ -751,107 +724,6 @@ class Audit(ServiceBase, AuditBase):
|
|
751
724
|
self.update_published_roots(response.result)
|
752
725
|
return self.handle_results_response(response, verify_consistency, verify_events)
|
753
726
|
|
754
|
-
def export(
|
755
|
-
self,
|
756
|
-
*,
|
757
|
-
format: DownloadFormat = DownloadFormat.CSV,
|
758
|
-
start: Optional[datetime.datetime] = None,
|
759
|
-
end: Optional[datetime.datetime] = None,
|
760
|
-
order: Optional[SearchOrder] = None,
|
761
|
-
order_by: Optional[str] = None,
|
762
|
-
verbose: bool = True,
|
763
|
-
) -> PangeaResponse[PangeaResponseResult]:
|
764
|
-
"""
|
765
|
-
Export from the audit log
|
766
|
-
|
767
|
-
Bulk export of data from the Secure Audit Log, with optional filtering.
|
768
|
-
|
769
|
-
OperationId: audit_post_v1_export
|
770
|
-
|
771
|
-
Args:
|
772
|
-
format: Format for the records.
|
773
|
-
start: The start of the time range to perform the search on.
|
774
|
-
end: The end of the time range to perform the search on. If omitted,
|
775
|
-
then all records up to the latest will be searched.
|
776
|
-
order: Specify the sort order of the response.
|
777
|
-
order_by: Name of column to sort the results by.
|
778
|
-
verbose: Whether or not to include the root hash of the tree and the
|
779
|
-
membership proof for each record.
|
780
|
-
|
781
|
-
Raises:
|
782
|
-
AuditException: If an audit based api exception happens
|
783
|
-
PangeaAPIException: If an API Error happens
|
784
|
-
|
785
|
-
Examples:
|
786
|
-
export_res = audit.export(verbose=False)
|
787
|
-
|
788
|
-
# Export may take several dozens of minutes, so polling for the result
|
789
|
-
# should be done in a loop. That is omitted here for brevity's sake.
|
790
|
-
try:
|
791
|
-
audit.poll_result(request_id=export_res.request_id)
|
792
|
-
except AcceptedRequestException:
|
793
|
-
# Retry later.
|
794
|
-
|
795
|
-
# Download the result when it's ready.
|
796
|
-
download_res = audit.download_results(request_id=export_res.request_id)
|
797
|
-
download_res.result.dest_url
|
798
|
-
# => https://pangea-runtime.s3.amazonaws.com/audit/xxxxx/search_results_[...]
|
799
|
-
"""
|
800
|
-
input = ExportRequest(
|
801
|
-
format=format,
|
802
|
-
start=start,
|
803
|
-
end=end,
|
804
|
-
order=order,
|
805
|
-
order_by=order_by,
|
806
|
-
verbose=verbose,
|
807
|
-
)
|
808
|
-
try:
|
809
|
-
return self.request.post(
|
810
|
-
"v1/export", PangeaResponseResult, data=input.dict(exclude_none=True), poll_result=False
|
811
|
-
)
|
812
|
-
except pexc.AcceptedRequestException as e:
|
813
|
-
return e.response
|
814
|
-
|
815
|
-
def log_stream(self, data: dict) -> PangeaResponse[PangeaResponseResult]:
|
816
|
-
"""
|
817
|
-
Log streaming endpoint
|
818
|
-
|
819
|
-
This API allows 3rd party vendors (like Auth0) to stream events to this
|
820
|
-
endpoint where the structure of the payload varies across different
|
821
|
-
vendors.
|
822
|
-
|
823
|
-
OperationId: audit_post_v1_log_stream
|
824
|
-
|
825
|
-
Args:
|
826
|
-
data: Event data. The exact schema of this will vary by vendor.
|
827
|
-
|
828
|
-
Raises:
|
829
|
-
AuditException: If an audit based api exception happens
|
830
|
-
PangeaAPIException: If an API Error happens
|
831
|
-
|
832
|
-
Examples:
|
833
|
-
data = {
|
834
|
-
"logs": [
|
835
|
-
{
|
836
|
-
"log_id": "some log ID",
|
837
|
-
"data": {
|
838
|
-
"date": "2024-03-29T17:26:50.193Z",
|
839
|
-
"type": "sapi",
|
840
|
-
"description": "Create a log stream",
|
841
|
-
"client_id": "some client ID",
|
842
|
-
"ip": "127.0.0.1",
|
843
|
-
"user_agent": "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0",
|
844
|
-
"user_id": "some user ID",
|
845
|
-
},
|
846
|
-
}
|
847
|
-
# ...
|
848
|
-
]
|
849
|
-
}
|
850
|
-
|
851
|
-
response = audit.log_stream(data)
|
852
|
-
"""
|
853
|
-
return self.request.post("v1/log_stream", PangeaResponseResult, data=data)
|
854
|
-
|
855
727
|
def root(self, tree_size: Optional[int] = None) -> PangeaResponse[RootResult]:
|
856
728
|
"""
|
857
729
|
Tamperproof verification
|
@@ -877,10 +749,7 @@ class Audit(ServiceBase, AuditBase):
|
|
877
749
|
return self.request.post("v1/root", RootResult, data=input.dict(exclude_none=True))
|
878
750
|
|
879
751
|
def download_results(
|
880
|
-
self,
|
881
|
-
result_id: Optional[str] = None,
|
882
|
-
format: DownloadFormat = DownloadFormat.CSV,
|
883
|
-
request_id: Optional[str] = None,
|
752
|
+
self, result_id: str, format: Optional[DownloadFormat] = None
|
884
753
|
) -> PangeaResponse[DownloadResult]:
|
885
754
|
"""
|
886
755
|
Download search results
|
@@ -892,7 +761,6 @@ class Audit(ServiceBase, AuditBase):
|
|
892
761
|
Args:
|
893
762
|
result_id: ID returned by the search API.
|
894
763
|
format: Format for the records.
|
895
|
-
request_id: ID returned by the export API.
|
896
764
|
|
897
765
|
Returns:
|
898
766
|
URL where search results can be downloaded.
|
@@ -908,10 +776,7 @@ class Audit(ServiceBase, AuditBase):
|
|
908
776
|
)
|
909
777
|
"""
|
910
778
|
|
911
|
-
|
912
|
-
raise ValueError("must pass one of `request_id` or `result_id`")
|
913
|
-
|
914
|
-
input = DownloadRequest(request_id=request_id, result_id=result_id, format=format)
|
779
|
+
input = DownloadRequest(result_id=result_id, format=format)
|
915
780
|
return self.request.post("v1/download_results", DownloadResult, data=input.dict(exclude_none=True))
|
916
781
|
|
917
782
|
def update_published_roots(self, result: SearchResultOutput):
|
pangea/services/audit/models.py
CHANGED
@@ -1,10 +1,8 @@
|
|
1
1
|
# Copyright 2022 Pangea Cyber Corporation
|
2
2
|
# Author: Pangea Cyber Corporation
|
3
|
-
from __future__ import annotations
|
4
|
-
|
5
3
|
import datetime
|
6
4
|
import enum
|
7
|
-
from typing import Any, Dict, List, Optional,
|
5
|
+
from typing import Any, Dict, List, Optional, Union
|
8
6
|
|
9
7
|
from pangea.response import APIRequestModel, APIResponseModel, PangeaResponseResult
|
10
8
|
|
@@ -21,14 +19,14 @@ class EventVerification(str, enum.Enum):
|
|
21
19
|
return str(self.value)
|
22
20
|
|
23
21
|
|
24
|
-
class Event(
|
22
|
+
class Event(dict):
|
25
23
|
"""
|
26
24
|
Event to perform an auditable activity
|
27
25
|
|
28
26
|
Auxiliary class to be compatible with older SDKs
|
29
27
|
"""
|
30
28
|
|
31
|
-
def __init__(self, **data)
|
29
|
+
def __init__(self, **data):
|
32
30
|
super().__init__(**data)
|
33
31
|
|
34
32
|
@property
|
@@ -281,7 +279,7 @@ class SearchRequest(APIRequestModel):
|
|
281
279
|
end: Optional[str] = None
|
282
280
|
limit: Optional[int] = None
|
283
281
|
max_results: Optional[int] = None
|
284
|
-
search_restriction: Optional[
|
282
|
+
search_restriction: Optional[dict] = None
|
285
283
|
verbose: Optional[bool] = None
|
286
284
|
|
287
285
|
|
@@ -417,13 +415,11 @@ class SearchResultRequest(APIRequestModel):
|
|
417
415
|
id -- A search results identifier returned by the search call.
|
418
416
|
limit -- Number of audit records to include from the first page of the results.
|
419
417
|
offset -- Offset from the start of the result set to start returning results from.
|
420
|
-
assert_search_restriction -- Assert the requested search results were queried with the exact same search restrictions, to ensure the results comply to the expected restrictions.
|
421
418
|
"""
|
422
419
|
|
423
420
|
id: str
|
424
421
|
limit: Optional[int] = 20
|
425
422
|
offset: Optional[int] = 0
|
426
|
-
assert_search_restriction: Optional[Dict[str, Sequence[str]]] = None
|
427
423
|
|
428
424
|
|
429
425
|
class DownloadFormat(str, enum.Enum):
|
@@ -441,10 +437,7 @@ class DownloadFormat(str, enum.Enum):
|
|
441
437
|
|
442
438
|
|
443
439
|
class DownloadRequest(APIRequestModel):
|
444
|
-
|
445
|
-
"""ID returned by the export API."""
|
446
|
-
|
447
|
-
result_id: Optional[str] = None
|
440
|
+
result_id: str
|
448
441
|
"""ID returned by the search API."""
|
449
442
|
|
450
443
|
format: Optional[str] = None
|
@@ -454,35 +447,3 @@ class DownloadRequest(APIRequestModel):
|
|
454
447
|
class DownloadResult(PangeaResponseResult):
|
455
448
|
dest_url: str
|
456
449
|
"""URL where search results can be downloaded."""
|
457
|
-
|
458
|
-
expires_at: str
|
459
|
-
"""
|
460
|
-
The time when the results will no longer be available to page through via
|
461
|
-
the results API.
|
462
|
-
"""
|
463
|
-
|
464
|
-
|
465
|
-
class ExportRequest(APIRequestModel):
|
466
|
-
format: DownloadFormat = DownloadFormat.CSV
|
467
|
-
"""Format for the records."""
|
468
|
-
|
469
|
-
start: Optional[datetime.datetime] = None
|
470
|
-
"""The start of the time range to perform the search on."""
|
471
|
-
|
472
|
-
end: Optional[datetime.datetime] = None
|
473
|
-
"""
|
474
|
-
The end of the time range to perform the search on. If omitted, then all
|
475
|
-
records up to the latest will be searched.
|
476
|
-
"""
|
477
|
-
|
478
|
-
order_by: Optional[str] = None
|
479
|
-
"""Name of column to sort the results by."""
|
480
|
-
|
481
|
-
order: Optional[SearchOrder] = None
|
482
|
-
"""Specify the sort order of the response."""
|
483
|
-
|
484
|
-
verbose: bool = True
|
485
|
-
"""
|
486
|
-
Whether or not to include the root hash of the tree and the membership proof
|
487
|
-
for each record.
|
488
|
-
"""
|
pangea/services/authn/authn.py
CHANGED
@@ -1,11 +1,9 @@
|
|
1
1
|
# Copyright 2022 Pangea Cyber Corporation
|
2
2
|
# Author: Pangea Cyber Corporation
|
3
|
-
from __future__ import annotations
|
4
3
|
|
5
4
|
from typing import Dict, List, Optional, Union
|
6
5
|
|
7
6
|
import pangea.services.authn.models as m
|
8
|
-
from pangea.config import PangeaConfig
|
9
7
|
from pangea.response import PangeaResponse
|
10
8
|
from pangea.services.base import ServiceBase
|
11
9
|
|
@@ -39,24 +37,10 @@ class AuthN(ServiceBase):
|
|
39
37
|
|
40
38
|
def __init__(
|
41
39
|
self,
|
42
|
-
token
|
43
|
-
config
|
44
|
-
logger_name
|
45
|
-
)
|
46
|
-
"""
|
47
|
-
AuthN client
|
48
|
-
|
49
|
-
Initializes a new AuthN client.
|
50
|
-
|
51
|
-
Args:
|
52
|
-
token: Pangea API token.
|
53
|
-
config: Configuration.
|
54
|
-
logger_name: Logger name.
|
55
|
-
|
56
|
-
Examples:
|
57
|
-
config = PangeaConfig(domain="pangea_domain")
|
58
|
-
authn = AuthN(token="pangea_token", config=config)
|
59
|
-
"""
|
40
|
+
token,
|
41
|
+
config=None,
|
42
|
+
logger_name="pangea",
|
43
|
+
):
|
60
44
|
super().__init__(token, config, logger_name=logger_name)
|
61
45
|
self.user = AuthN.User(token, config, logger_name=logger_name)
|
62
46
|
self.flow = AuthN.Flow(token, config, logger_name=logger_name)
|