pangea-sdk 3.0.0__py3-none-any.whl → 3.2.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/request.py +138 -22
- pangea/asyncio/services/audit.py +82 -3
- pangea/asyncio/services/file_scan.py +6 -2
- pangea/exceptions.py +20 -2
- pangea/request.py +158 -35
- pangea/response.py +26 -4
- pangea/services/audit/audit.py +163 -62
- pangea/services/audit/models.py +32 -0
- pangea/services/authn/models.py +2 -2
- pangea/services/file_scan.py +21 -3
- pangea/services/vault/models/common.py +11 -0
- pangea/utils.py +24 -1
- {pangea_sdk-3.0.0.dist-info → pangea_sdk-3.2.0.dist-info}/METADATA +2 -1
- {pangea_sdk-3.0.0.dist-info → pangea_sdk-3.2.0.dist-info}/RECORD +16 -16
- {pangea_sdk-3.0.0.dist-info → pangea_sdk-3.2.0.dist-info}/WHEEL +0 -0
pangea/response.py
CHANGED
@@ -12,6 +12,17 @@ from pydantic import BaseModel
|
|
12
12
|
T = TypeVar("T")
|
13
13
|
|
14
14
|
|
15
|
+
class TransferMethod(str, enum.Enum):
|
16
|
+
DIRECT = "direct"
|
17
|
+
MULTIPART = "multipart"
|
18
|
+
|
19
|
+
def __str__(self):
|
20
|
+
return str(self.value)
|
21
|
+
|
22
|
+
def __repr__(self):
|
23
|
+
return str(self.value)
|
24
|
+
|
25
|
+
|
15
26
|
# API response should accept arbitrary fields to make them accept possible new parameters
|
16
27
|
class APIResponseModel(BaseModel):
|
17
28
|
class Config:
|
@@ -59,6 +70,15 @@ class ErrorField(APIResponseModel):
|
|
59
70
|
return self.__repr__()
|
60
71
|
|
61
72
|
|
73
|
+
class AcceptedStatus(APIResponseModel):
|
74
|
+
upload_url: str = ""
|
75
|
+
upload_details: Dict[str, Any] = {}
|
76
|
+
|
77
|
+
|
78
|
+
class AcceptedResult(PangeaResponseResult):
|
79
|
+
accepted_status: Optional[AcceptedStatus] = None
|
80
|
+
|
81
|
+
|
62
82
|
class PangeaError(PangeaResponseResult):
|
63
83
|
errors: List[ErrorField] = []
|
64
84
|
|
@@ -109,6 +129,7 @@ class PangeaResponse(Generic[T], ResponseHeader):
|
|
109
129
|
raw_response: Optional[Union[requests.Response, aiohttp.ClientResponse]] = None
|
110
130
|
result: Optional[T] = None
|
111
131
|
pangea_error: Optional[PangeaError] = None
|
132
|
+
accepted_result: Optional[AcceptedResult] = None
|
112
133
|
result_class: Type[PangeaResponseResult] = PangeaResponseResult
|
113
134
|
_json: Any
|
114
135
|
|
@@ -120,13 +141,14 @@ class PangeaResponse(Generic[T], ResponseHeader):
|
|
120
141
|
self.result_class = result_class
|
121
142
|
self.result = (
|
122
143
|
self.result_class(**self.raw_result)
|
123
|
-
if self.raw_result is not None
|
124
|
-
and issubclass(self.result_class, PangeaResponseResult)
|
125
|
-
and self.status == ResponseStatus.SUCCESS.value
|
144
|
+
if self.raw_result is not None and issubclass(self.result_class, PangeaResponseResult) and self.success
|
126
145
|
else None
|
127
146
|
)
|
128
147
|
if not self.success:
|
129
|
-
|
148
|
+
if self.http_status == 202:
|
149
|
+
self.accepted_result = AcceptedResult(**self.raw_result) if self.raw_result is not None else None
|
150
|
+
else:
|
151
|
+
self.pangea_error = PangeaError(**self.raw_result) if self.raw_result is not None else None
|
130
152
|
|
131
153
|
@property
|
132
154
|
def success(self) -> bool:
|
pangea/services/audit/audit.py
CHANGED
@@ -2,14 +2,18 @@
|
|
2
2
|
# Author: Pangea Cyber Corporation
|
3
3
|
import datetime
|
4
4
|
import json
|
5
|
-
from typing import Any, Dict, Optional, Union
|
5
|
+
from typing import Any, Dict, List, Optional, Union
|
6
6
|
|
7
|
+
import pangea.exceptions as pexc
|
7
8
|
from pangea.response import PangeaResponse
|
8
9
|
from pangea.services.audit.exceptions import AuditException, EventCorruption
|
9
10
|
from pangea.services.audit.models import (
|
10
11
|
Event,
|
11
12
|
EventEnvelope,
|
12
13
|
EventVerification,
|
14
|
+
LogBulkRequest,
|
15
|
+
LogBulkResult,
|
16
|
+
LogEvent,
|
13
17
|
LogRequest,
|
14
18
|
LogResult,
|
15
19
|
PublishedRoot,
|
@@ -56,7 +60,29 @@ class AuditBase:
|
|
56
60
|
self.prev_unpublished_root_hash: Optional[str] = None
|
57
61
|
self.tenant_id = tenant_id
|
58
62
|
|
59
|
-
def
|
63
|
+
def _get_log_request(
|
64
|
+
self, events: Union[dict, List[dict]], sign_local: bool, verify: bool, verbose: Optional[bool]
|
65
|
+
) -> Union[LogRequest, LogBulkResult]:
|
66
|
+
if isinstance(events, list):
|
67
|
+
request_events: List[LogEvent] = []
|
68
|
+
for e in events:
|
69
|
+
request_events.append(self._process_log(e, sign_local=sign_local))
|
70
|
+
|
71
|
+
input = LogBulkRequest(events=request_events, verbose=verbose)
|
72
|
+
|
73
|
+
elif isinstance(events, dict):
|
74
|
+
log = self._process_log(events, sign_local=sign_local)
|
75
|
+
input = LogRequest(event=log.event, signature=log.signature, public_key=log.public_key)
|
76
|
+
input.verbose = True if verify else verbose
|
77
|
+
if verify and self.prev_unpublished_root_hash:
|
78
|
+
input.prev_root = self.prev_unpublished_root_hash
|
79
|
+
|
80
|
+
else:
|
81
|
+
raise AttributeError(f"events should be a dict or a list[dict] and it is {type(events)}")
|
82
|
+
|
83
|
+
return input
|
84
|
+
|
85
|
+
def _process_log(self, event: dict, sign_local: bool) -> LogEvent:
|
60
86
|
if event.get("tenant_id", None) is None and self.tenant_id:
|
61
87
|
event["tenant_id"] = self.tenant_id
|
62
88
|
|
@@ -66,70 +92,60 @@ class AuditBase:
|
|
66
92
|
if sign_local is True and self.signer is None:
|
67
93
|
raise AuditException("Error: the `signing` parameter set, but `signer` is not configured")
|
68
94
|
|
69
|
-
|
70
|
-
|
95
|
+
signature = None
|
96
|
+
pki = None
|
71
97
|
if sign_local is True:
|
72
98
|
data2sign = canonicalize_event(event)
|
73
99
|
signature = self.signer.sign(data2sign)
|
74
|
-
if signature is
|
75
|
-
input.signature = signature
|
76
|
-
else:
|
100
|
+
if signature is None:
|
77
101
|
raise AuditException("Error: failure signing message")
|
78
102
|
|
79
103
|
# Add public key value to public key info and serialize
|
80
|
-
self.
|
81
|
-
|
82
|
-
if verify:
|
83
|
-
input.verbose = True
|
84
|
-
if self.prev_unpublished_root_hash:
|
85
|
-
input.prev_root = self.prev_unpublished_root_hash
|
86
|
-
|
87
|
-
return input
|
104
|
+
pki = self._get_public_key_info(self.signer, self.public_key_info)
|
88
105
|
|
89
|
-
|
90
|
-
if not response.success:
|
91
|
-
return response
|
106
|
+
return LogEvent(event=event, signature=signature, public_key=pki)
|
92
107
|
|
93
|
-
|
108
|
+
def _process_log_result(self, result: LogResult, verify: bool):
|
109
|
+
new_unpublished_root_hash = result.unpublished_root
|
94
110
|
|
95
111
|
if verify:
|
96
|
-
if
|
112
|
+
if result.envelope:
|
97
113
|
# verify event hash
|
98
|
-
if
|
114
|
+
if result.hash and not verify_envelope_hash(result.envelope, result.hash):
|
99
115
|
# it's a extreme case, it's OK to raise an exception
|
100
|
-
raise EventCorruption("Error: Event hash failed.",
|
116
|
+
raise EventCorruption("Error: Event hash failed.", result.envelope)
|
101
117
|
|
102
|
-
|
118
|
+
result.signature_verification = self.verify_signature(result.envelope)
|
103
119
|
|
104
120
|
if new_unpublished_root_hash:
|
105
|
-
if
|
121
|
+
if result.membership_proof is not None:
|
106
122
|
# verify membership proofs
|
107
|
-
membership_proof = decode_membership_proof(
|
123
|
+
membership_proof = decode_membership_proof(result.membership_proof)
|
108
124
|
if verify_membership_proof(
|
109
|
-
node_hash=decode_hash(
|
125
|
+
node_hash=decode_hash(result.hash),
|
110
126
|
root_hash=decode_hash(new_unpublished_root_hash),
|
111
127
|
proof=membership_proof,
|
112
128
|
):
|
113
|
-
|
129
|
+
result.membership_verification = EventVerification.PASS
|
114
130
|
else:
|
115
|
-
|
131
|
+
result.membership_verification = EventVerification.FAIL
|
116
132
|
|
117
133
|
# verify consistency proofs (following events)
|
118
|
-
if
|
119
|
-
consistency_proof = decode_consistency_proof(
|
134
|
+
if result.consistency_proof is not None and self.prev_unpublished_root_hash:
|
135
|
+
consistency_proof = decode_consistency_proof(result.consistency_proof)
|
120
136
|
if verify_consistency_proof(
|
121
137
|
new_root=decode_hash(new_unpublished_root_hash),
|
122
138
|
prev_root=decode_hash(self.prev_unpublished_root_hash),
|
123
139
|
proof=consistency_proof,
|
124
140
|
):
|
125
|
-
|
141
|
+
result.consistency_verification = EventVerification.PASS
|
126
142
|
else:
|
127
|
-
|
143
|
+
result.consistency_verification = EventVerification.FAIL
|
128
144
|
|
129
145
|
# Update prev unpublished root
|
130
146
|
if new_unpublished_root_hash:
|
131
147
|
self.prev_unpublished_root_hash = new_unpublished_root_hash
|
132
|
-
return
|
148
|
+
return
|
133
149
|
|
134
150
|
def handle_results_response(
|
135
151
|
self, response: PangeaResponse[SearchResultOutput], verify_consistency: bool = False, verify_events: bool = True
|
@@ -348,12 +364,10 @@ class AuditBase:
|
|
348
364
|
else:
|
349
365
|
return EventVerification.NONE
|
350
366
|
|
351
|
-
def
|
367
|
+
def _get_public_key_info(self, signer: Signer, public_key_info: Dict[str, str]):
|
352
368
|
public_key_info["key"] = signer.get_public_key_PEM()
|
353
369
|
public_key_info["algorithm"] = signer.get_algorithm()
|
354
|
-
|
355
|
-
public_key_info, ensure_ascii=False, allow_nan=False, separators=(",", ":"), sort_keys=True
|
356
|
-
)
|
370
|
+
return json.dumps(public_key_info, ensure_ascii=False, allow_nan=False, separators=(",", ":"), sort_keys=True)
|
357
371
|
|
358
372
|
|
359
373
|
class Audit(ServiceBase, AuditBase):
|
@@ -444,16 +458,13 @@ class Audit(ServiceBase, AuditBase):
|
|
444
458
|
A PangeaResponse where the hash of event data and optional verbose
|
445
459
|
results are returned in the response.result field.
|
446
460
|
Available response fields can be found in our
|
447
|
-
[API documentation](https://pangea.cloud/docs/api/audit
|
461
|
+
[API documentation](https://pangea.cloud/docs/api/audit#/v1/log).
|
448
462
|
|
449
463
|
Examples:
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
print(f"Request Error: {e.response.summary}")
|
455
|
-
for err in e.errors:
|
456
|
-
print(f"\\t{err.detail} \\n")
|
464
|
+
log_response = audit.log(
|
465
|
+
message="hello world",
|
466
|
+
verbose=True,
|
467
|
+
)
|
457
468
|
"""
|
458
469
|
|
459
470
|
event = Event(
|
@@ -481,6 +492,7 @@ class Audit(ServiceBase, AuditBase):
|
|
481
492
|
Log an entry
|
482
493
|
|
483
494
|
Create a log entry in the Secure Audit Log.
|
495
|
+
|
484
496
|
Args:
|
485
497
|
event (dict[str, Any]): event to be logged
|
486
498
|
verify (bool, optional): True to verify logs consistency after response.
|
@@ -493,11 +505,11 @@ class Audit(ServiceBase, AuditBase):
|
|
493
505
|
Returns:
|
494
506
|
A PangeaResponse where the hash of event data and optional verbose
|
495
507
|
results are returned in the response.result field.
|
496
|
-
Available response fields can be found in our [API documentation](https://pangea.cloud/docs/api/audit
|
508
|
+
Available response fields can be found in our [API documentation](https://pangea.cloud/docs/api/audit#/v1/log).
|
497
509
|
|
498
510
|
Examples:
|
499
511
|
try:
|
500
|
-
log_response = audit.log({"message"
|
512
|
+
log_response = audit.log({"message": "hello world"}, verbose=True)
|
501
513
|
print(f"Response. Hash: {log_response.result.hash}")
|
502
514
|
except pe.PangeaAPIException as e:
|
503
515
|
print(f"Request Error: {e.response.summary}")
|
@@ -505,9 +517,98 @@ class Audit(ServiceBase, AuditBase):
|
|
505
517
|
print(f"\\t{err.detail} \\n")
|
506
518
|
"""
|
507
519
|
|
508
|
-
input = self.
|
520
|
+
input = self._get_log_request(event, sign_local=sign_local, verify=verify, verbose=verbose)
|
509
521
|
response = self.request.post("v1/log", LogResult, data=input.dict(exclude_none=True))
|
510
|
-
|
522
|
+
if response.success:
|
523
|
+
self._process_log_result(response.result, verify=verify)
|
524
|
+
return response
|
525
|
+
|
526
|
+
def log_bulk(
|
527
|
+
self,
|
528
|
+
events: List[Dict[str, Any]],
|
529
|
+
sign_local: bool = False,
|
530
|
+
verbose: Optional[bool] = None,
|
531
|
+
) -> PangeaResponse[LogBulkResult]:
|
532
|
+
"""
|
533
|
+
Log multiple entries
|
534
|
+
|
535
|
+
Create multiple log entries in the Secure Audit Log.
|
536
|
+
|
537
|
+
OperationId: audit_post_v2_log
|
538
|
+
|
539
|
+
Args:
|
540
|
+
events (List[dict[str, Any]]): events to be logged
|
541
|
+
sign_local (bool, optional): True to sign event with local key.
|
542
|
+
verbose (bool, optional): True to get a more verbose response.
|
543
|
+
Raises:
|
544
|
+
AuditException: If an audit based api exception happens
|
545
|
+
PangeaAPIException: If an API Error happens
|
546
|
+
|
547
|
+
Returns:
|
548
|
+
A PangeaResponse where the hash of event data and optional verbose
|
549
|
+
results are returned in the response.result field.
|
550
|
+
Available response fields can be found in our [API documentation](https://pangea.cloud/docs/api/audit#/v2/log).
|
551
|
+
|
552
|
+
Examples:
|
553
|
+
log_response = audit.log_bulk(
|
554
|
+
events=[{"message": "hello world"}],
|
555
|
+
verbose=True,
|
556
|
+
)
|
557
|
+
"""
|
558
|
+
|
559
|
+
input = self._get_log_request(events, sign_local=sign_local, verify=False, verbose=verbose)
|
560
|
+
response = self.request.post("v2/log", LogBulkResult, data=input.dict(exclude_none=True))
|
561
|
+
|
562
|
+
if response.success:
|
563
|
+
for result in response.result.results:
|
564
|
+
self._process_log_result(result, verify=True)
|
565
|
+
return response
|
566
|
+
|
567
|
+
def log_bulk_async(
|
568
|
+
self,
|
569
|
+
events: List[Dict[str, Any]],
|
570
|
+
sign_local: bool = False,
|
571
|
+
verbose: Optional[bool] = None,
|
572
|
+
) -> PangeaResponse[LogBulkResult]:
|
573
|
+
"""
|
574
|
+
Log multiple entries asynchronously
|
575
|
+
|
576
|
+
Asynchronously create multiple log entries in the Secure Audit Log.
|
577
|
+
|
578
|
+
Args:
|
579
|
+
events (List[dict[str, Any]]): events to be logged
|
580
|
+
sign_local (bool, optional): True to sign event with local key.
|
581
|
+
verbose (bool, optional): True to get a more verbose response.
|
582
|
+
Raises:
|
583
|
+
AuditException: If an audit based api exception happens
|
584
|
+
PangeaAPIException: If an API Error happens
|
585
|
+
|
586
|
+
Returns:
|
587
|
+
A PangeaResponse where the hash of event data and optional verbose
|
588
|
+
results are returned in the response.result field.
|
589
|
+
Available response fields can be found in our [API documentation](https://pangea.cloud/docs/api/audit#/v2/log_async).
|
590
|
+
|
591
|
+
Examples:
|
592
|
+
log_response = audit.log_bulk_async(
|
593
|
+
events=[{"message": "hello world"}],
|
594
|
+
verbose=True,
|
595
|
+
)
|
596
|
+
"""
|
597
|
+
|
598
|
+
input = self._get_log_request(events, sign_local=sign_local, verify=False, verbose=verbose)
|
599
|
+
|
600
|
+
try:
|
601
|
+
# Calling to v2 methods will return always a 202.
|
602
|
+
response = self.request.post(
|
603
|
+
"v2/log_async", LogBulkResult, data=input.dict(exclude_none=True), poll_result=False
|
604
|
+
)
|
605
|
+
except pexc.AcceptedRequestException as e:
|
606
|
+
return e.response
|
607
|
+
|
608
|
+
if response.success:
|
609
|
+
for result in response.result.results:
|
610
|
+
self._process_log_result(result, verify=True)
|
611
|
+
return response
|
511
612
|
|
512
613
|
def search(
|
513
614
|
self,
|
@@ -558,11 +659,17 @@ class Audit(ServiceBase, AuditBase):
|
|
558
659
|
|
559
660
|
Returns:
|
560
661
|
A PangeaResponse[SearchOutput] where the first page of matched events is returned in the
|
561
|
-
response.result field. Available response fields can be found in our [API documentation](https://pangea.cloud/docs/api/audit
|
562
|
-
Pagination can be found in the [search results endpoint](https://pangea.cloud/docs/api/audit
|
662
|
+
response.result field. Available response fields can be found in our [API documentation](https://pangea.cloud/docs/api/audit#/v1/search).
|
663
|
+
Pagination can be found in the [search results endpoint](https://pangea.cloud/docs/api/audit#/v1/results).
|
563
664
|
|
564
665
|
Examples:
|
565
|
-
response
|
666
|
+
response = audit.search(
|
667
|
+
query="message:test",
|
668
|
+
search_restriction={'source': ["monitor"]},
|
669
|
+
limit=1,
|
670
|
+
verify_consistency=True,
|
671
|
+
verify_events=True,
|
672
|
+
)
|
566
673
|
"""
|
567
674
|
|
568
675
|
if verify_consistency:
|
@@ -609,17 +716,11 @@ class Audit(ServiceBase, AuditBase):
|
|
609
716
|
PangeaAPIException: If an API Error happens
|
610
717
|
|
611
718
|
Examples:
|
612
|
-
|
613
|
-
|
614
|
-
search_restriction={'source': ["monitor"]},
|
615
|
-
limit=100,
|
616
|
-
verify_consistency=True,
|
617
|
-
verify_events=True)
|
618
|
-
|
619
|
-
result_res: PangeaResponse[SearchResultsOutput] = audit.results(
|
620
|
-
id=search_res.result.id,
|
719
|
+
response = audit.results(
|
720
|
+
id="pas_sqilrhruwu54uggihqj3aie24wrctakr",
|
621
721
|
limit=10,
|
622
|
-
offset=0
|
722
|
+
offset=0,
|
723
|
+
)
|
623
724
|
"""
|
624
725
|
|
625
726
|
if limit <= 0:
|
pangea/services/audit/models.py
CHANGED
@@ -146,6 +146,34 @@ class LogRequest(APIRequestModel):
|
|
146
146
|
prev_root: Optional[str] = None
|
147
147
|
|
148
148
|
|
149
|
+
class LogEvent(APIRequestModel):
|
150
|
+
"""
|
151
|
+
Event to perform a log action
|
152
|
+
|
153
|
+
Arguments:
|
154
|
+
event -- A structured event describing an auditable activity.
|
155
|
+
signature -- An optional client-side signature for forgery protection.
|
156
|
+
public_key -- The base64-encoded ed25519 public key used for the signature, if one is provided.
|
157
|
+
"""
|
158
|
+
|
159
|
+
event: Dict[str, Any]
|
160
|
+
signature: Optional[str] = None
|
161
|
+
public_key: Optional[str] = None
|
162
|
+
|
163
|
+
|
164
|
+
class LogBulkRequest(APIRequestModel):
|
165
|
+
"""
|
166
|
+
Request to perform a bulk log action
|
167
|
+
|
168
|
+
Arguments:
|
169
|
+
events -- A list structured events describing an auditable activity.
|
170
|
+
verbose -- If true, be verbose in the response; include membership proof, unpublished root and consistency proof, etc.
|
171
|
+
"""
|
172
|
+
|
173
|
+
events: List[LogEvent]
|
174
|
+
verbose: Optional[bool] = None
|
175
|
+
|
176
|
+
|
149
177
|
class LogResult(PangeaResponseResult):
|
150
178
|
"""
|
151
179
|
Result class after an audit log action
|
@@ -167,6 +195,10 @@ class LogResult(PangeaResponseResult):
|
|
167
195
|
signature_verification: EventVerification = EventVerification.NONE
|
168
196
|
|
169
197
|
|
198
|
+
class LogBulkResult(PangeaResponseResult):
|
199
|
+
results: List[LogResult] = []
|
200
|
+
|
201
|
+
|
170
202
|
class SearchRestriction(APIResponseModel):
|
171
203
|
"""
|
172
204
|
Set of restrictions when perform an audit search action
|
pangea/services/authn/models.py
CHANGED
@@ -152,7 +152,7 @@ class UserListOrderBy(enum.Enum):
|
|
152
152
|
class Authenticator(APIResponseModel):
|
153
153
|
id: str
|
154
154
|
type: str
|
155
|
-
|
155
|
+
enabled: bool
|
156
156
|
provider: Optional[str] = None
|
157
157
|
rpid: Optional[str] = None
|
158
158
|
phase: Optional[str] = None
|
@@ -399,7 +399,7 @@ class UserAuthenticatorsListRequest(APIRequestModel):
|
|
399
399
|
|
400
400
|
|
401
401
|
class UserAuthenticatorsListResult(PangeaResponseResult):
|
402
|
-
authenticators: List[Authenticator]
|
402
|
+
authenticators: List[Authenticator] = []
|
403
403
|
|
404
404
|
|
405
405
|
class FlowCompleteRequest(APIRequestModel):
|
pangea/services/file_scan.py
CHANGED
@@ -3,7 +3,8 @@
|
|
3
3
|
import io
|
4
4
|
from typing import Dict, List, Optional
|
5
5
|
|
6
|
-
from pangea.response import APIRequestModel, PangeaResponse, PangeaResponseResult
|
6
|
+
from pangea.response import APIRequestModel, PangeaResponse, PangeaResponseResult, TransferMethod
|
7
|
+
from pangea.utils import get_presigned_url_upload_params
|
7
8
|
|
8
9
|
from .base import ServiceBase
|
9
10
|
|
@@ -20,6 +21,10 @@ class FileScanRequest(APIRequestModel):
|
|
20
21
|
verbose: Optional[bool] = None
|
21
22
|
raw: Optional[bool] = None
|
22
23
|
provider: Optional[str] = None
|
24
|
+
transfer_size: Optional[int] = None
|
25
|
+
transfer_crc32c: Optional[str] = None
|
26
|
+
transfer_sha256: Optional[str] = None
|
27
|
+
transfer_method: TransferMethod = TransferMethod.DIRECT
|
23
28
|
|
24
29
|
|
25
30
|
class FileScanData(PangeaResponseResult):
|
@@ -74,6 +79,7 @@ class FileScan(ServiceBase):
|
|
74
79
|
raw: Optional[bool] = None,
|
75
80
|
provider: Optional[str] = None,
|
76
81
|
sync_call: bool = True,
|
82
|
+
transfer_method: TransferMethod = TransferMethod.DIRECT,
|
77
83
|
) -> PangeaResponse[FileScanResult]:
|
78
84
|
"""
|
79
85
|
Scan
|
@@ -108,14 +114,26 @@ class FileScan(ServiceBase):
|
|
108
114
|
for err in e.errors:
|
109
115
|
print(f"\\t{err.detail} \\n")
|
110
116
|
"""
|
111
|
-
input = FileScanRequest(verbose=verbose, raw=raw, provider=provider)
|
112
117
|
|
113
118
|
if file or file_path:
|
114
119
|
if file_path:
|
115
120
|
file = open(file_path, "rb")
|
116
|
-
|
121
|
+
if transfer_method == TransferMethod.DIRECT:
|
122
|
+
crc, sha, size, _ = get_presigned_url_upload_params(file)
|
123
|
+
else:
|
124
|
+
crc, sha, size = None, None, None
|
125
|
+
files = [("upload", ("filename", file, "application/octet-stream"))]
|
117
126
|
else:
|
118
127
|
raise ValueError("Need to set file_path or file arguments")
|
119
128
|
|
129
|
+
input = FileScanRequest(
|
130
|
+
verbose=verbose,
|
131
|
+
raw=raw,
|
132
|
+
provider=provider,
|
133
|
+
transfer_crc32c=crc,
|
134
|
+
transfer_sha256=sha,
|
135
|
+
transfer_size=size,
|
136
|
+
transfer_method=transfer_method,
|
137
|
+
)
|
120
138
|
data = input.dict(exclude_none=True)
|
121
139
|
return self.request.post("v1/scan", FileScanResult, data=data, files=files, poll_result=sync_call)
|
@@ -129,6 +129,7 @@ class ItemVersionState(str, enum.Enum):
|
|
129
129
|
SUSPENDED = "suspended"
|
130
130
|
COMPROMISED = "compromised"
|
131
131
|
DESTROYED = "destroyed"
|
132
|
+
INHERITED = "inherited"
|
132
133
|
|
133
134
|
def __str__(self):
|
134
135
|
return str(self.value)
|
@@ -217,9 +218,16 @@ class ItemData(PangeaResponseResult):
|
|
217
218
|
purpose: Optional[str] = None
|
218
219
|
|
219
220
|
|
221
|
+
class InheritedSettings(PangeaResponseResult):
|
222
|
+
rotation_frequency: Optional[str] = None
|
223
|
+
rotation_state: Optional[str] = None
|
224
|
+
rotation_grace_period: Optional[str] = None
|
225
|
+
|
226
|
+
|
220
227
|
class GetResult(ItemData):
|
221
228
|
versions: List[ItemVersionData] = []
|
222
229
|
rotation_grace_period: Optional[str] = None
|
230
|
+
inherited_settings: Optional[InheritedSettings] = None
|
223
231
|
|
224
232
|
|
225
233
|
class ListItemData(ItemData):
|
@@ -360,6 +368,9 @@ class FolderCreateRequest(APIRequestModel):
|
|
360
368
|
folder: str
|
361
369
|
metadata: Optional[Metadata] = None
|
362
370
|
tags: Optional[Tags] = None
|
371
|
+
rotation_frequency: Optional[str] = None
|
372
|
+
rotation_state: Optional[ItemVersionState] = None
|
373
|
+
rotation_grace_period: Optional[str] = None
|
363
374
|
|
364
375
|
|
365
376
|
class FolderCreateResult(PangeaResponseResult):
|
pangea/utils.py
CHANGED
@@ -1,11 +1,14 @@
|
|
1
1
|
import base64
|
2
2
|
import copy
|
3
3
|
import datetime
|
4
|
+
import io
|
4
5
|
import json
|
5
6
|
from binascii import hexlify
|
6
7
|
from collections import OrderedDict
|
7
8
|
from hashlib import new, sha1, sha256, sha512
|
8
9
|
|
10
|
+
from google_crc32c import Checksum as CRC32C
|
11
|
+
|
9
12
|
|
10
13
|
def format_datetime(dt: datetime.datetime) -> str:
|
11
14
|
"""
|
@@ -22,7 +25,6 @@ def default_encoder(obj) -> str:
|
|
22
25
|
if isinstance(obj, datetime.date):
|
23
26
|
return str(obj)
|
24
27
|
if isinstance(obj, dict):
|
25
|
-
print("encoder canonicalize obj")
|
26
28
|
return canonicalize(obj)
|
27
29
|
else:
|
28
30
|
return str(obj)
|
@@ -96,3 +98,24 @@ def hash_ntlm(data: str):
|
|
96
98
|
|
97
99
|
def get_prefix(hash: str, len: int = 5):
|
98
100
|
return hash[0:len]
|
101
|
+
|
102
|
+
|
103
|
+
def get_presigned_url_upload_params(file: io.BufferedReader):
|
104
|
+
if "b" not in file.mode:
|
105
|
+
raise AttributeError("File need to be open in binary mode")
|
106
|
+
|
107
|
+
file.seek(0) # restart reading
|
108
|
+
crc = CRC32C()
|
109
|
+
size = 0
|
110
|
+
sha = sha256()
|
111
|
+
|
112
|
+
while True:
|
113
|
+
chunk = file.read(1024 * 1024)
|
114
|
+
if not chunk:
|
115
|
+
break
|
116
|
+
crc.update(chunk)
|
117
|
+
sha.update(chunk)
|
118
|
+
size += len(chunk)
|
119
|
+
|
120
|
+
file.seek(0) # restart reading
|
121
|
+
return crc.hexdigest().decode("utf-8"), sha.hexdigest(), size, file
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: pangea-sdk
|
3
|
-
Version: 3.
|
3
|
+
Version: 3.2.0
|
4
4
|
Summary: Pangea API SDK
|
5
5
|
License: MIT
|
6
6
|
Keywords: Pangea,SDK,Audit
|
@@ -20,6 +20,7 @@ Requires-Dist: alive-progress (>=2.4.1,<3.0.0)
|
|
20
20
|
Requires-Dist: asyncio (>=3.4.3,<4.0.0)
|
21
21
|
Requires-Dist: cryptography (==41.0.3)
|
22
22
|
Requires-Dist: deprecated (>=1.2.13,<2.0.0)
|
23
|
+
Requires-Dist: google-crc32c (>=1.5.0,<2.0.0)
|
23
24
|
Requires-Dist: pydantic (>=1.10.2,<2.0.0)
|
24
25
|
Requires-Dist: pytest (>=7.2.0,<8.0.0)
|
25
26
|
Requires-Dist: python-dateutil (>=2.8.2,<3.0.0)
|