pangea-sdk 3.8.0__py3-none-any.whl → 5.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 +46 -23
 - pangea/asyncio/services/__init__.py +2 -0
 - pangea/asyncio/services/audit.py +46 -20
 - pangea/asyncio/services/authn.py +123 -61
 - pangea/asyncio/services/authz.py +57 -31
 - 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 +104 -30
 - pangea/asyncio/services/redact.py +52 -3
 - pangea/asyncio/services/sanitize.py +217 -0
 - pangea/asyncio/services/share.py +733 -0
 - pangea/asyncio/services/vault.py +1709 -766
 - pangea/crypto/rsa.py +135 -0
 - pangea/deep_verify.py +7 -1
 - pangea/dump_audit.py +9 -8
 - pangea/file_uploader.py +35 -0
 - pangea/request.py +70 -49
 - pangea/response.py +36 -17
 - pangea/services/__init__.py +2 -0
 - pangea/services/audit/audit.py +57 -29
 - pangea/services/audit/models.py +12 -3
 - pangea/services/audit/signing.py +6 -5
 - pangea/services/audit/util.py +3 -3
 - pangea/services/authn/authn.py +120 -66
 - pangea/services/authn/models.py +167 -11
 - pangea/services/authz.py +53 -30
 - pangea/services/base.py +16 -2
 - pangea/services/embargo.py +2 -2
 - pangea/services/file_scan.py +32 -15
 - pangea/services/intel.py +155 -30
 - pangea/services/redact.py +132 -3
 - pangea/services/sanitize.py +388 -0
 - pangea/services/share/file_format.py +170 -0
 - pangea/services/share/share.py +1440 -0
 - 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 -766
 - pangea/tools.py +6 -7
 - pangea/utils.py +94 -33
 - pangea/verify_audit.py +270 -83
 - {pangea_sdk-3.8.0.dist-info → pangea_sdk-5.3.0.dist-info}/METADATA +21 -29
 - pangea_sdk-5.3.0.dist-info/RECORD +56 -0
 - {pangea_sdk-3.8.0.dist-info → pangea_sdk-5.3.0.dist-info}/WHEEL +1 -1
 - pangea_sdk-3.8.0.dist-info/RECORD +0 -46
 
    
        pangea/services/audit/audit.py
    CHANGED
    
    | 
         @@ -4,7 +4,7 @@ from __future__ import annotations 
     | 
|
| 
       4 
4 
     | 
    
         | 
| 
       5 
5 
     | 
    
         
             
            import datetime
         
     | 
| 
       6 
6 
     | 
    
         
             
            import json
         
     | 
| 
       7 
     | 
    
         
            -
            from typing import Any, Dict, List, Optional, Sequence, Set, Tuple, Union
         
     | 
| 
      
 7 
     | 
    
         
            +
            from typing import Any, Dict, Iterable, List, Optional, Sequence, Set, Tuple, Union
         
     | 
| 
       8 
8 
     | 
    
         | 
| 
       9 
9 
     | 
    
         
             
            import pangea.exceptions as pexc
         
     | 
| 
       10 
10 
     | 
    
         
             
            from pangea.config import PangeaConfig
         
     | 
| 
         @@ -199,7 +199,7 @@ class AuditBase: 
     | 
|
| 
       199 
199 
     | 
    
         | 
| 
       200 
200 
     | 
    
         
             
                            # verify consistency proofs
         
     | 
| 
       201 
201 
     | 
    
         
             
                            if self.can_verify_consistency_proof(search_event):
         
     | 
| 
       202 
     | 
    
         
            -
                                if self.verify_consistency_proof( 
     | 
| 
      
 202 
     | 
    
         
            +
                                if search_event.leaf_index is not None and self.verify_consistency_proof(search_event.leaf_index):
         
     | 
| 
       203 
203 
     | 
    
         
             
                                    search_event.consistency_verification = EventVerification.PASS
         
     | 
| 
       204 
204 
     | 
    
         
             
                                else:
         
     | 
| 
       205 
205 
     | 
    
         
             
                                    search_event.consistency_verification = EventVerification.FAIL
         
     | 
| 
         @@ -222,7 +222,7 @@ class AuditBase: 
     | 
|
| 
       222 
222 
     | 
    
         
             
                    tree_sizes.difference_update(self.pub_roots.keys())
         
     | 
| 
       223 
223 
     | 
    
         | 
| 
       224 
224 
     | 
    
         
             
                    if tree_sizes:
         
     | 
| 
       225 
     | 
    
         
            -
                        arweave_roots = get_arweave_published_roots(result.root.tree_name,  
     | 
| 
      
 225 
     | 
    
         
            +
                        arweave_roots = get_arweave_published_roots(result.root.tree_name, tree_sizes)
         
     | 
| 
       226 
226 
     | 
    
         
             
                    else:
         
     | 
| 
       227 
227 
     | 
    
         
             
                        arweave_roots = {}
         
     | 
| 
       228 
228 
     | 
    
         | 
| 
         @@ -284,7 +284,7 @@ class AuditBase: 
     | 
|
| 
       284 
284 
     | 
    
         
             
                    """
         
     | 
| 
       285 
285 
     | 
    
         
             
                    return event.published and event.leaf_index is not None and event.leaf_index >= 0  # type: ignore[return-value]
         
     | 
| 
       286 
286 
     | 
    
         | 
| 
       287 
     | 
    
         
            -
                def verify_consistency_proof(self,  
     | 
| 
      
 287 
     | 
    
         
            +
                def verify_consistency_proof(self, tree_size: int) -> bool:
         
     | 
| 
       288 
288 
     | 
    
         
             
                    """
         
     | 
| 
       289 
289 
     | 
    
         
             
                    Verify consistency proof
         
     | 
| 
       290 
290 
     | 
    
         | 
| 
         @@ -293,18 +293,17 @@ class AuditBase: 
     | 
|
| 
       293 
293 
     | 
    
         
             
                    Read more at: [What is a consistency proof?](https://pangea.cloud/docs/audit/merkle-trees#what-is-a-consistency-proof)
         
     | 
| 
       294 
294 
     | 
    
         | 
| 
       295 
295 
     | 
    
         
             
                    Args:
         
     | 
| 
       296 
     | 
    
         
            -
                         
     | 
| 
       297 
     | 
    
         
            -
                        event (SearchEvent): Audit event to be verified.
         
     | 
| 
      
 296 
     | 
    
         
            +
                        leaf_index (int): The tree size of the root to be verified.
         
     | 
| 
       298 
297 
     | 
    
         | 
| 
       299 
298 
     | 
    
         
             
                    Returns:
         
     | 
| 
       300 
299 
     | 
    
         
             
                        bool: True if consistency proof is verified, False otherwise.
         
     | 
| 
       301 
300 
     | 
    
         
             
                    """
         
     | 
| 
       302 
301 
     | 
    
         | 
| 
       303 
     | 
    
         
            -
                    if  
     | 
| 
      
 302 
     | 
    
         
            +
                    if tree_size == 0:
         
     | 
| 
       304 
303 
     | 
    
         
             
                        return True
         
     | 
| 
       305 
304 
     | 
    
         | 
| 
       306 
     | 
    
         
            -
                    curr_root = pub_roots.get( 
     | 
| 
       307 
     | 
    
         
            -
                    prev_root = pub_roots.get( 
     | 
| 
      
 305 
     | 
    
         
            +
                    curr_root = self.pub_roots.get(tree_size + 1)
         
     | 
| 
      
 306 
     | 
    
         
            +
                    prev_root = self.pub_roots.get(tree_size)
         
     | 
| 
       308 
307 
     | 
    
         | 
| 
       309 
308 
     | 
    
         
             
                    if not curr_root or not prev_root:
         
     | 
| 
       310 
309 
     | 
    
         
             
                        return False
         
     | 
| 
         @@ -314,9 +313,12 @@ class AuditBase: 
     | 
|
| 
       314 
313 
     | 
    
         
             
                    ):
         
     | 
| 
       315 
314 
     | 
    
         
             
                        return False
         
     | 
| 
       316 
315 
     | 
    
         | 
| 
      
 316 
     | 
    
         
            +
                    if curr_root.consistency_proof is None:
         
     | 
| 
      
 317 
     | 
    
         
            +
                        return False
         
     | 
| 
      
 318 
     | 
    
         
            +
             
     | 
| 
       317 
319 
     | 
    
         
             
                    curr_root_hash = decode_hash(curr_root.root_hash)
         
     | 
| 
       318 
320 
     | 
    
         
             
                    prev_root_hash = decode_hash(prev_root.root_hash)
         
     | 
| 
       319 
     | 
    
         
            -
                    proof = decode_consistency_proof(curr_root.consistency_proof) 
     | 
| 
      
 321 
     | 
    
         
            +
                    proof = decode_consistency_proof(curr_root.consistency_proof)
         
     | 
| 
       320 
322 
     | 
    
         | 
| 
       321 
323 
     | 
    
         
             
                    return verify_consistency_proof(curr_root_hash, prev_root_hash, proof)
         
     | 
| 
       322 
324 
     | 
    
         | 
| 
         @@ -490,7 +492,7 @@ class Audit(ServiceBase, AuditBase): 
     | 
|
| 
       490 
492 
     | 
    
         
             
                    verbose: Optional[bool] = None,
         
     | 
| 
       491 
493 
     | 
    
         
             
                ) -> PangeaResponse[LogResult]:
         
     | 
| 
       492 
494 
     | 
    
         
             
                    """
         
     | 
| 
       493 
     | 
    
         
            -
                    Log an  
     | 
| 
      
 495 
     | 
    
         
            +
                    Log an event
         
     | 
| 
       494 
496 
     | 
    
         | 
| 
       495 
497 
     | 
    
         
             
                    Create a log entry in the Secure Audit Log.
         
     | 
| 
       496 
498 
     | 
    
         | 
| 
         @@ -499,6 +501,7 @@ class Audit(ServiceBase, AuditBase): 
     | 
|
| 
       499 
501 
     | 
    
         
             
                        verify (bool, optional): True to verify logs consistency after response.
         
     | 
| 
       500 
502 
     | 
    
         
             
                        sign_local (bool, optional): True to sign event with local key.
         
     | 
| 
       501 
503 
     | 
    
         
             
                        verbose (bool, optional): True to get a more verbose response.
         
     | 
| 
      
 504 
     | 
    
         
            +
             
     | 
| 
       502 
505 
     | 
    
         
             
                    Raises:
         
     | 
| 
       503 
506 
     | 
    
         
             
                        AuditException: If an audit based api exception happens
         
     | 
| 
       504 
507 
     | 
    
         
             
                        PangeaAPIException: If an API Error happens
         
     | 
| 
         @@ -509,17 +512,13 @@ class Audit(ServiceBase, AuditBase): 
     | 
|
| 
       509 
512 
     | 
    
         
             
                            Available response fields can be found in our [API documentation](https://pangea.cloud/docs/api/audit#/v1/log).
         
     | 
| 
       510 
513 
     | 
    
         | 
| 
       511 
514 
     | 
    
         
             
                    Examples:
         
     | 
| 
       512 
     | 
    
         
            -
                         
     | 
| 
       513 
     | 
    
         
            -
                            log_response = audit.log({"message": "hello world"}, verbose=True)
         
     | 
| 
       514 
     | 
    
         
            -
                            print(f"Response. Hash: {log_response.result.hash}")
         
     | 
| 
       515 
     | 
    
         
            -
                        except pe.PangeaAPIException as e:
         
     | 
| 
       516 
     | 
    
         
            -
                            print(f"Request Error: {e.response.summary}")
         
     | 
| 
       517 
     | 
    
         
            -
                            for err in e.errors:
         
     | 
| 
       518 
     | 
    
         
            -
                                print(f"\\t{err.detail} \\n")
         
     | 
| 
      
 515 
     | 
    
         
            +
                        response = audit.log_event({"message": "hello world"}, verbose=True)
         
     | 
| 
       519 
516 
     | 
    
         
             
                    """
         
     | 
| 
       520 
517 
     | 
    
         | 
| 
       521 
518 
     | 
    
         
             
                    input = self._get_log_request(event, sign_local=sign_local, verify=verify, verbose=verbose)
         
     | 
| 
       522 
     | 
    
         
            -
                    response: PangeaResponse[LogResult] = self.request.post( 
     | 
| 
      
 519 
     | 
    
         
            +
                    response: PangeaResponse[LogResult] = self.request.post(
         
     | 
| 
      
 520 
     | 
    
         
            +
                        "v1/log", LogResult, data=input.model_dump(exclude_none=True)
         
     | 
| 
      
 521 
     | 
    
         
            +
                    )
         
     | 
| 
       523 
522 
     | 
    
         
             
                    if response.success and response.result is not None:
         
     | 
| 
       524 
523 
     | 
    
         
             
                        self._process_log_result(response.result, verify=verify)
         
     | 
| 
       525 
524 
     | 
    
         
             
                    return response
         
     | 
| 
         @@ -559,7 +558,7 @@ class Audit(ServiceBase, AuditBase): 
     | 
|
| 
       559 
558 
     | 
    
         | 
| 
       560 
559 
     | 
    
         
             
                    input = self._get_log_request(events, sign_local=sign_local, verify=False, verbose=verbose)
         
     | 
| 
       561 
560 
     | 
    
         
             
                    response: PangeaResponse[LogBulkResult] = self.request.post(
         
     | 
| 
       562 
     | 
    
         
            -
                        "v2/log", LogBulkResult, data=input. 
     | 
| 
      
 561 
     | 
    
         
            +
                        "v2/log", LogBulkResult, data=input.model_dump(exclude_none=True)
         
     | 
| 
       563 
562 
     | 
    
         
             
                    )
         
     | 
| 
       564 
563 
     | 
    
         | 
| 
       565 
564 
     | 
    
         
             
                    if response.success and response.result is not None:
         
     | 
| 
         @@ -603,7 +602,7 @@ class Audit(ServiceBase, AuditBase): 
     | 
|
| 
       603 
602 
     | 
    
         
             
                    try:
         
     | 
| 
       604 
603 
     | 
    
         
             
                        # Calling to v2 methods will return always a 202.
         
     | 
| 
       605 
604 
     | 
    
         
             
                        response: PangeaResponse[LogBulkResult] = self.request.post(
         
     | 
| 
       606 
     | 
    
         
            -
                            "v2/log_async", LogBulkResult, data=input. 
     | 
| 
      
 605 
     | 
    
         
            +
                            "v2/log_async", LogBulkResult, data=input.model_dump(exclude_none=True), poll_result=False
         
     | 
| 
       607 
606 
     | 
    
         
             
                        )
         
     | 
| 
       608 
607 
     | 
    
         
             
                    except pexc.AcceptedRequestException as e:
         
     | 
| 
       609 
608 
     | 
    
         
             
                        return e.response
         
     | 
| 
         @@ -626,6 +625,7 @@ class Audit(ServiceBase, AuditBase): 
     | 
|
| 
       626 
625 
     | 
    
         
             
                    verbose: Optional[bool] = None,
         
     | 
| 
       627 
626 
     | 
    
         
             
                    verify_consistency: bool = False,
         
     | 
| 
       628 
627 
     | 
    
         
             
                    verify_events: bool = True,
         
     | 
| 
      
 628 
     | 
    
         
            +
                    return_context: Optional[bool] = None,
         
     | 
| 
       629 
629 
     | 
    
         
             
                ) -> PangeaResponse[SearchOutput]:
         
     | 
| 
       630 
630 
     | 
    
         
             
                    """
         
     | 
| 
       631 
631 
     | 
    
         
             
                    Search the log
         
     | 
| 
         @@ -655,6 +655,7 @@ class Audit(ServiceBase, AuditBase): 
     | 
|
| 
       655 
655 
     | 
    
         
             
                        verbose (bool, optional): If true, response include root and membership and consistency proofs.
         
     | 
| 
       656 
656 
     | 
    
         
             
                        verify_consistency (bool): True to verify logs consistency
         
     | 
| 
       657 
657 
     | 
    
         
             
                        verify_events (bool): True to verify hash events and signatures
         
     | 
| 
      
 658 
     | 
    
         
            +
                        return_context (bool): Return the context data needed to decrypt secure audit events that have been redacted with format preserving encryption.
         
     | 
| 
       658 
659 
     | 
    
         | 
| 
       659 
660 
     | 
    
         
             
                    Raises:
         
     | 
| 
       660 
661 
     | 
    
         
             
                        AuditException: If an audit based api exception happens
         
     | 
| 
         @@ -688,10 +689,11 @@ class Audit(ServiceBase, AuditBase): 
     | 
|
| 
       688 
689 
     | 
    
         
             
                        max_results=max_results,
         
     | 
| 
       689 
690 
     | 
    
         
             
                        search_restriction=search_restriction,
         
     | 
| 
       690 
691 
     | 
    
         
             
                        verbose=verbose,
         
     | 
| 
      
 692 
     | 
    
         
            +
                        return_context=return_context,
         
     | 
| 
       691 
693 
     | 
    
         
             
                    )
         
     | 
| 
       692 
694 
     | 
    
         | 
| 
       693 
695 
     | 
    
         
             
                    response: PangeaResponse[SearchOutput] = self.request.post(
         
     | 
| 
       694 
     | 
    
         
            -
                        "v1/search", SearchOutput, data=input. 
     | 
| 
      
 696 
     | 
    
         
            +
                        "v1/search", SearchOutput, data=input.model_dump(exclude_none=True)
         
     | 
| 
       695 
697 
     | 
    
         
             
                    )
         
     | 
| 
       696 
698 
     | 
    
         
             
                    if verify_consistency and response.result is not None:
         
     | 
| 
       697 
699 
     | 
    
         
             
                        self.update_published_roots(response.result)
         
     | 
| 
         @@ -705,6 +707,7 @@ class Audit(ServiceBase, AuditBase): 
     | 
|
| 
       705 
707 
     | 
    
         
             
                    assert_search_restriction: Optional[Dict[str, Sequence[str]]] = None,
         
     | 
| 
       706 
708 
     | 
    
         
             
                    verify_consistency: bool = False,
         
     | 
| 
       707 
709 
     | 
    
         
             
                    verify_events: bool = True,
         
     | 
| 
      
 710 
     | 
    
         
            +
                    return_context: Optional[bool] = None,
         
     | 
| 
       708 
711 
     | 
    
         
             
                ) -> PangeaResponse[SearchResultOutput]:
         
     | 
| 
       709 
712 
     | 
    
         
             
                    """
         
     | 
| 
       710 
713 
     | 
    
         
             
                    Results of a search
         
     | 
| 
         @@ -720,6 +723,7 @@ class Audit(ServiceBase, AuditBase): 
     | 
|
| 
       720 
723 
     | 
    
         
             
                        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 
724 
     | 
    
         
             
                        verify_consistency (bool): True to verify logs consistency
         
     | 
| 
       722 
725 
     | 
    
         
             
                        verify_events (bool): True to verify hash events and signatures
         
     | 
| 
      
 726 
     | 
    
         
            +
                        return_context (bool): Return the context data needed to decrypt secure audit events that have been redacted with format preserving encryption.
         
     | 
| 
       723 
727 
     | 
    
         
             
                    Raises:
         
     | 
| 
       724 
728 
     | 
    
         
             
                        AuditException: If an audit based api exception happens
         
     | 
| 
       725 
729 
     | 
    
         
             
                        PangeaAPIException: If an API Error happens
         
     | 
| 
         @@ -743,9 +747,10 @@ class Audit(ServiceBase, AuditBase): 
     | 
|
| 
       743 
747 
     | 
    
         
             
                        limit=limit,
         
     | 
| 
       744 
748 
     | 
    
         
             
                        offset=offset,
         
     | 
| 
       745 
749 
     | 
    
         
             
                        assert_search_restriction=assert_search_restriction,
         
     | 
| 
      
 750 
     | 
    
         
            +
                        return_context=return_context,
         
     | 
| 
       746 
751 
     | 
    
         
             
                    )
         
     | 
| 
       747 
752 
     | 
    
         
             
                    response: PangeaResponse[SearchResultOutput] = self.request.post(
         
     | 
| 
       748 
     | 
    
         
            -
                        "v1/results", SearchResultOutput, data=input. 
     | 
| 
      
 753 
     | 
    
         
            +
                        "v1/results", SearchResultOutput, data=input.model_dump(exclude_none=True)
         
     | 
| 
       749 
754 
     | 
    
         
             
                    )
         
     | 
| 
       750 
755 
     | 
    
         
             
                    if verify_consistency and response.result is not None:
         
     | 
| 
       751 
756 
     | 
    
         
             
                        self.update_published_roots(response.result)
         
     | 
| 
         @@ -807,7 +812,7 @@ class Audit(ServiceBase, AuditBase): 
     | 
|
| 
       807 
812 
     | 
    
         
             
                    )
         
     | 
| 
       808 
813 
     | 
    
         
             
                    try:
         
     | 
| 
       809 
814 
     | 
    
         
             
                        return self.request.post(
         
     | 
| 
       810 
     | 
    
         
            -
                            "v1/export", PangeaResponseResult, data=input. 
     | 
| 
      
 815 
     | 
    
         
            +
                            "v1/export", PangeaResponseResult, data=input.model_dump(exclude_none=True), poll_result=False
         
     | 
| 
       811 
816 
     | 
    
         
             
                        )
         
     | 
| 
       812 
817 
     | 
    
         
             
                    except pexc.AcceptedRequestException as e:
         
     | 
| 
       813 
818 
     | 
    
         
             
                        return e.response
         
     | 
| 
         @@ -874,13 +879,14 @@ class Audit(ServiceBase, AuditBase): 
     | 
|
| 
       874 
879 
     | 
    
         
             
                        response = audit.root(tree_size=7)
         
     | 
| 
       875 
880 
     | 
    
         
             
                    """
         
     | 
| 
       876 
881 
     | 
    
         
             
                    input = RootRequest(tree_size=tree_size)
         
     | 
| 
       877 
     | 
    
         
            -
                    return self.request.post("v1/root", RootResult, data=input. 
     | 
| 
      
 882 
     | 
    
         
            +
                    return self.request.post("v1/root", RootResult, data=input.model_dump(exclude_none=True))
         
     | 
| 
       878 
883 
     | 
    
         | 
| 
       879 
884 
     | 
    
         
             
                def download_results(
         
     | 
| 
       880 
885 
     | 
    
         
             
                    self,
         
     | 
| 
       881 
886 
     | 
    
         
             
                    result_id: Optional[str] = None,
         
     | 
| 
       882 
887 
     | 
    
         
             
                    format: DownloadFormat = DownloadFormat.CSV,
         
     | 
| 
       883 
888 
     | 
    
         
             
                    request_id: Optional[str] = None,
         
     | 
| 
      
 889 
     | 
    
         
            +
                    return_context: Optional[bool] = None,
         
     | 
| 
       884 
890 
     | 
    
         
             
                ) -> PangeaResponse[DownloadResult]:
         
     | 
| 
       885 
891 
     | 
    
         
             
                    """
         
     | 
| 
       886 
892 
     | 
    
         
             
                    Download search results
         
     | 
| 
         @@ -893,6 +899,7 @@ class Audit(ServiceBase, AuditBase): 
     | 
|
| 
       893 
899 
     | 
    
         
             
                        result_id: ID returned by the search API.
         
     | 
| 
       894 
900 
     | 
    
         
             
                        format: Format for the records.
         
     | 
| 
       895 
901 
     | 
    
         
             
                        request_id: ID returned by the export API.
         
     | 
| 
      
 902 
     | 
    
         
            +
                        return_context (bool): Return the context data needed to decrypt secure audit events that have been redacted with format preserving encryption.
         
     | 
| 
       896 
903 
     | 
    
         | 
| 
       897 
904 
     | 
    
         
             
                    Returns:
         
     | 
| 
       898 
905 
     | 
    
         
             
                        URL where search results can be downloaded.
         
     | 
| 
         @@ -911,8 +918,10 @@ class Audit(ServiceBase, AuditBase): 
     | 
|
| 
       911 
918 
     | 
    
         
             
                    if request_id is None and result_id is None:
         
     | 
| 
       912 
919 
     | 
    
         
             
                        raise ValueError("must pass one of `request_id` or `result_id`")
         
     | 
| 
       913 
920 
     | 
    
         | 
| 
       914 
     | 
    
         
            -
                    input = DownloadRequest( 
     | 
| 
       915 
     | 
    
         
            -
             
     | 
| 
      
 921 
     | 
    
         
            +
                    input = DownloadRequest(
         
     | 
| 
      
 922 
     | 
    
         
            +
                        request_id=request_id, result_id=result_id, format=format, return_context=return_context
         
     | 
| 
      
 923 
     | 
    
         
            +
                    )
         
     | 
| 
      
 924 
     | 
    
         
            +
                    return self.request.post("v1/download_results", DownloadResult, data=input.model_dump(exclude_none=True))
         
     | 
| 
       916 
925 
     | 
    
         | 
| 
       917 
926 
     | 
    
         
             
                def update_published_roots(self, result: SearchResultOutput):
         
     | 
| 
       918 
927 
     | 
    
         
             
                    """Fetches series of published root hashes from Arweave
         
     | 
| 
         @@ -937,12 +946,31 @@ class Audit(ServiceBase, AuditBase): 
     | 
|
| 
       937 
946 
     | 
    
         
             
                    for tree_size in tree_sizes:
         
     | 
| 
       938 
947 
     | 
    
         
             
                        pub_root = None
         
     | 
| 
       939 
948 
     | 
    
         
             
                        if tree_size in arweave_roots:
         
     | 
| 
       940 
     | 
    
         
            -
                            pub_root = PublishedRoot(**arweave_roots[tree_size]. 
     | 
| 
      
 949 
     | 
    
         
            +
                            pub_root = PublishedRoot(**arweave_roots[tree_size].model_dump(exclude_none=True))
         
     | 
| 
       941 
950 
     | 
    
         
             
                            pub_root.source = RootSource.ARWEAVE
         
     | 
| 
       942 
951 
     | 
    
         
             
                        elif self.allow_server_roots:
         
     | 
| 
       943 
952 
     | 
    
         
             
                            resp = self.root(tree_size=tree_size)
         
     | 
| 
       944 
953 
     | 
    
         
             
                            if resp.success and resp.result is not None:
         
     | 
| 
       945 
     | 
    
         
            -
                                pub_root = PublishedRoot(**resp.result.data. 
     | 
| 
      
 954 
     | 
    
         
            +
                                pub_root = PublishedRoot(**resp.result.data.model_dump(exclude_none=True))
         
     | 
| 
       946 
955 
     | 
    
         
             
                                pub_root.source = RootSource.PANGEA
         
     | 
| 
       947 
956 
     | 
    
         
             
                        if pub_root is not None:
         
     | 
| 
       948 
957 
     | 
    
         
             
                            self.pub_roots[tree_size] = pub_root
         
     | 
| 
      
 958 
     | 
    
         
            +
             
     | 
| 
      
 959 
     | 
    
         
            +
                    self._fix_consistency_proofs(tree_sizes)
         
     | 
| 
      
 960 
     | 
    
         
            +
             
     | 
| 
      
 961 
     | 
    
         
            +
                def _fix_consistency_proofs(self, tree_sizes: Iterable[int]) -> None:
         
     | 
| 
      
 962 
     | 
    
         
            +
                    # on very rare occasions, the consistency proof in Arweave may be wrong
         
     | 
| 
      
 963 
     | 
    
         
            +
                    # override it with the proof from pangea (not the root hash, just the proof)
         
     | 
| 
      
 964 
     | 
    
         
            +
                    for tree_size in tree_sizes:
         
     | 
| 
      
 965 
     | 
    
         
            +
                        if tree_size not in self.pub_roots or tree_size - 1 not in self.pub_roots:
         
     | 
| 
      
 966 
     | 
    
         
            +
                            continue
         
     | 
| 
      
 967 
     | 
    
         
            +
             
     | 
| 
      
 968 
     | 
    
         
            +
                        if self.pub_roots[tree_size].source == RootSource.PANGEA:
         
     | 
| 
      
 969 
     | 
    
         
            +
                            continue
         
     | 
| 
      
 970 
     | 
    
         
            +
             
     | 
| 
      
 971 
     | 
    
         
            +
                        if self.verify_consistency_proof(tree_size):
         
     | 
| 
      
 972 
     | 
    
         
            +
                            continue
         
     | 
| 
      
 973 
     | 
    
         
            +
             
     | 
| 
      
 974 
     | 
    
         
            +
                        resp = self.root(tree_size=tree_size)
         
     | 
| 
      
 975 
     | 
    
         
            +
                        if resp.success and resp.result is not None and resp.result.data is not None:
         
     | 
| 
      
 976 
     | 
    
         
            +
                            self.pub_roots[tree_size].consistency_proof = resp.result.data.consistency_proof
         
     | 
    
        pangea/services/audit/models.py
    CHANGED
    
    | 
         @@ -6,7 +6,7 @@ import datetime 
     | 
|
| 
       6 
6 
     | 
    
         
             
            import enum
         
     | 
| 
       7 
7 
     | 
    
         
             
            from typing import Any, Dict, List, Optional, Sequence, Union
         
     | 
| 
       8 
8 
     | 
    
         | 
| 
       9 
     | 
    
         
            -
            from pangea.response import APIRequestModel, APIResponseModel, PangeaResponseResult
         
     | 
| 
      
 9 
     | 
    
         
            +
            from pangea.response import APIRequestModel, APIResponseModel, PangeaDateTime, PangeaResponseResult
         
     | 
| 
       10 
10 
     | 
    
         | 
| 
       11 
11 
     | 
    
         | 
| 
       12 
12 
     | 
    
         
             
            class EventVerification(str, enum.Enum):
         
     | 
| 
         @@ -126,7 +126,7 @@ class EventEnvelope(APIResponseModel): 
     | 
|
| 
       126 
126 
     | 
    
         
             
                event: Dict[str, Any]
         
     | 
| 
       127 
127 
     | 
    
         
             
                signature: Optional[str] = None
         
     | 
| 
       128 
128 
     | 
    
         
             
                public_key: Optional[str] = None
         
     | 
| 
       129 
     | 
    
         
            -
                received_at:  
     | 
| 
      
 129 
     | 
    
         
            +
                received_at: PangeaDateTime
         
     | 
| 
       130 
130 
     | 
    
         | 
| 
       131 
131 
     | 
    
         | 
| 
       132 
132 
     | 
    
         
             
            class LogRequest(APIRequestModel):
         
     | 
| 
         @@ -271,6 +271,7 @@ class SearchRequest(APIRequestModel): 
     | 
|
| 
       271 
271 
     | 
    
         
             
                max_results -- Maximum number of results to return.
         
     | 
| 
       272 
272 
     | 
    
         
             
                search_restriction -- A list of keys to restrict the search results to. Useful for partitioning data available to the query string.
         
     | 
| 
       273 
273 
     | 
    
         
             
                verbose -- If true, include root, membership and consistency proofs in response.
         
     | 
| 
      
 274 
     | 
    
         
            +
                return_context -- Return the context data needed to decrypt secure audit events that have been redacted with format preserving encryption.
         
     | 
| 
       274 
275 
     | 
    
         
             
                """
         
     | 
| 
       275 
276 
     | 
    
         | 
| 
       276 
277 
     | 
    
         
             
                query: str
         
     | 
| 
         @@ -283,6 +284,7 @@ class SearchRequest(APIRequestModel): 
     | 
|
| 
       283 
284 
     | 
    
         
             
                max_results: Optional[int] = None
         
     | 
| 
       284 
285 
     | 
    
         
             
                search_restriction: Optional[Dict[str, Sequence[str]]] = None
         
     | 
| 
       285 
286 
     | 
    
         
             
                verbose: Optional[bool] = None
         
     | 
| 
      
 287 
     | 
    
         
            +
                return_context: Optional[bool] = None
         
     | 
| 
       286 
288 
     | 
    
         | 
| 
       287 
289 
     | 
    
         | 
| 
       288 
290 
     | 
    
         
             
            class RootRequest(APIRequestModel):
         
     | 
| 
         @@ -363,6 +365,7 @@ class SearchEvent(APIResponseModel): 
     | 
|
| 
       363 
365 
     | 
    
         
             
                consistency_verification -- Consistency verification calculated if required.
         
     | 
| 
       364 
366 
     | 
    
         
             
                membership_verification -- Membership verification calculated if required.
         
     | 
| 
       365 
367 
     | 
    
         
             
                signature_verification -- Signature verification calculated if required.
         
     | 
| 
      
 368 
     | 
    
         
            +
                fpe_context -- The context data needed to decrypt secure audit events that have been redacted with format preserving encryption.
         
     | 
| 
       366 
369 
     | 
    
         
             
                """
         
     | 
| 
       367 
370 
     | 
    
         | 
| 
       368 
371 
     | 
    
         
             
                envelope: EventEnvelope
         
     | 
| 
         @@ -373,6 +376,7 @@ class SearchEvent(APIResponseModel): 
     | 
|
| 
       373 
376 
     | 
    
         
             
                consistency_verification: EventVerification = EventVerification.NONE
         
     | 
| 
       374 
377 
     | 
    
         
             
                membership_verification: EventVerification = EventVerification.NONE
         
     | 
| 
       375 
378 
     | 
    
         
             
                signature_verification: EventVerification = EventVerification.NONE
         
     | 
| 
      
 379 
     | 
    
         
            +
                fpe_context: Optional[str] = None
         
     | 
| 
       376 
380 
     | 
    
         | 
| 
       377 
381 
     | 
    
         | 
| 
       378 
382 
     | 
    
         
             
            class SearchResultOutput(PangeaResponseResult):
         
     | 
| 
         @@ -406,7 +410,7 @@ class SearchOutput(SearchResultOutput): 
     | 
|
| 
       406 
410 
     | 
    
         
             
                """
         
     | 
| 
       407 
411 
     | 
    
         | 
| 
       408 
412 
     | 
    
         
             
                id: str
         
     | 
| 
       409 
     | 
    
         
            -
                expires_at:  
     | 
| 
      
 413 
     | 
    
         
            +
                expires_at: PangeaDateTime
         
     | 
| 
       410 
414 
     | 
    
         | 
| 
       411 
415 
     | 
    
         | 
| 
       412 
416 
     | 
    
         
             
            class SearchResultRequest(APIRequestModel):
         
     | 
| 
         @@ -418,12 +422,14 @@ class SearchResultRequest(APIRequestModel): 
     | 
|
| 
       418 
422 
     | 
    
         
             
                limit -- Number of audit records to include from the first page of the results.
         
     | 
| 
       419 
423 
     | 
    
         
             
                offset -- Offset from the start of the result set to start returning results from.
         
     | 
| 
       420 
424 
     | 
    
         
             
                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.
         
     | 
| 
      
 425 
     | 
    
         
            +
                return_context -- Return the context data needed to decrypt secure audit events that have been redacted with format preserving encryption.
         
     | 
| 
       421 
426 
     | 
    
         
             
                """
         
     | 
| 
       422 
427 
     | 
    
         | 
| 
       423 
428 
     | 
    
         
             
                id: str
         
     | 
| 
       424 
429 
     | 
    
         
             
                limit: Optional[int] = 20
         
     | 
| 
       425 
430 
     | 
    
         
             
                offset: Optional[int] = 0
         
     | 
| 
       426 
431 
     | 
    
         
             
                assert_search_restriction: Optional[Dict[str, Sequence[str]]] = None
         
     | 
| 
      
 432 
     | 
    
         
            +
                return_context: Optional[bool] = None
         
     | 
| 
       427 
433 
     | 
    
         | 
| 
       428 
434 
     | 
    
         | 
| 
       429 
435 
     | 
    
         
             
            class DownloadFormat(str, enum.Enum):
         
     | 
| 
         @@ -450,6 +456,9 @@ class DownloadRequest(APIRequestModel): 
     | 
|
| 
       450 
456 
     | 
    
         
             
                format: Optional[str] = None
         
     | 
| 
       451 
457 
     | 
    
         
             
                """Format for the records."""
         
     | 
| 
       452 
458 
     | 
    
         | 
| 
      
 459 
     | 
    
         
            +
                return_context: Optional[bool] = None
         
     | 
| 
      
 460 
     | 
    
         
            +
                """Return the context data needed to decrypt secure audit events that have been redacted with format preserving encryption."""
         
     | 
| 
      
 461 
     | 
    
         
            +
             
     | 
| 
       453 
462 
     | 
    
         | 
| 
       454 
463 
     | 
    
         
             
            class DownloadResult(PangeaResponseResult):
         
     | 
| 
       455 
464 
     | 
    
         
             
                dest_url: str
         
     | 
    
        pangea/services/audit/signing.py
    CHANGED
    
    | 
         @@ -1,5 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            # Copyright 2022 Pangea Cyber Corporation
         
     | 
| 
       2 
2 
     | 
    
         
             
            # Author: Pangea Cyber Corporation
         
     | 
| 
      
 3 
     | 
    
         
            +
            from __future__ import annotations
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
       3 
5 
     | 
    
         
             
            from abc import ABC, abstractmethod
         
     | 
| 
       4 
6 
     | 
    
         
             
            from typing import Optional
         
     | 
| 
       5 
7 
     | 
    
         | 
| 
         @@ -10,7 +12,7 @@ from cryptography.hazmat.primitives.asymmetric.types import PrivateKeyTypes, Pub 
     | 
|
| 
       10 
12 
     | 
    
         | 
| 
       11 
13 
     | 
    
         
             
            from pangea.exceptions import PangeaException
         
     | 
| 
       12 
14 
     | 
    
         
             
            from pangea.services.audit.util import b64decode, b64decode_ascii, b64encode_ascii
         
     | 
| 
       13 
     | 
    
         
            -
            from pangea.services.vault.models. 
     | 
| 
      
 15 
     | 
    
         
            +
            from pangea.services.vault.models.asymmetric import AsymmetricKeySigningAlgorithm
         
     | 
| 
       14 
16 
     | 
    
         | 
| 
       15 
17 
     | 
    
         | 
| 
       16 
18 
     | 
    
         
             
            class AlgorithmSigner(ABC):
         
     | 
| 
         @@ -43,7 +45,7 @@ class ED25519Signer(AlgorithmSigner): 
     | 
|
| 
       43 
45 
     | 
    
         
             
                    )
         
     | 
| 
       44 
46 
     | 
    
         | 
| 
       45 
47 
     | 
    
         
             
                def get_algorithm(self) -> str:
         
     | 
| 
       46 
     | 
    
         
            -
                    return  
     | 
| 
      
 48 
     | 
    
         
            +
                    return AsymmetricKeySigningAlgorithm.ED25519.value
         
     | 
| 
       47 
49 
     | 
    
         | 
| 
       48 
50 
     | 
    
         | 
| 
       49 
51 
     | 
    
         
             
            signers = {
         
     | 
| 
         @@ -96,7 +98,7 @@ class Signer: 
     | 
|
| 
       96 
98 
     | 
    
         | 
| 
       97 
99 
     | 
    
         
             
                    for func in (serialization.load_pem_private_key, serialization.load_ssh_private_key):
         
     | 
| 
       98 
100 
     | 
    
         
             
                        try:
         
     | 
| 
       99 
     | 
    
         
            -
                            return func(private_key, None) 
     | 
| 
      
 101 
     | 
    
         
            +
                            return func(private_key, None)
         
     | 
| 
       100 
102 
     | 
    
         
             
                        except exceptions.UnsupportedAlgorithm as e:
         
     | 
| 
       101 
103 
     | 
    
         
             
                            raise e
         
     | 
| 
       102 
104 
     | 
    
         
             
                        except ValueError:
         
     | 
| 
         @@ -144,8 +146,7 @@ class Verifier: 
     | 
|
| 
       144 
146 
     | 
    
         
             
                    for cls, verifier in verifiers.items():
         
     | 
| 
       145 
147 
     | 
    
         
             
                        if isinstance(pubkey, cls):
         
     | 
| 
       146 
148 
     | 
    
         
             
                            return verifier(pubkey).verify(message_bytes, signature_bytes)
         
     | 
| 
       147 
     | 
    
         
            -
                     
     | 
| 
       148 
     | 
    
         
            -
                        raise PangeaException(f"Not supported public key type: {type(pubkey)}")
         
     | 
| 
      
 149 
     | 
    
         
            +
                    raise PangeaException(f"Not supported public key type: {type(pubkey)}")
         
     | 
| 
       149 
150 
     | 
    
         | 
| 
       150 
151 
     | 
    
         
             
                def _decode_public_key(self, public_key: bytes):
         
     | 
| 
       151 
152 
     | 
    
         
             
                    """Parse a public key in PEM or ssh format"""
         
     | 
    
        pangea/services/audit/util.py
    CHANGED
    
    | 
         @@ -7,7 +7,7 @@ from binascii import hexlify, unhexlify 
     | 
|
| 
       7 
7 
     | 
    
         
             
            from dataclasses import dataclass
         
     | 
| 
       8 
8 
     | 
    
         
             
            from datetime import datetime
         
     | 
| 
       9 
9 
     | 
    
         
             
            from hashlib import sha256
         
     | 
| 
       10 
     | 
    
         
            -
            from typing import Dict, List, Optional
         
     | 
| 
      
 10 
     | 
    
         
            +
            from typing import Collection, Dict, List, Optional
         
     | 
| 
       11 
11 
     | 
    
         | 
| 
       12 
12 
     | 
    
         
             
            import requests
         
     | 
| 
       13 
13 
     | 
    
         | 
| 
         @@ -61,7 +61,7 @@ def verify_hash(hash1: str, hash2: str) -> bool: 
     | 
|
| 
       61 
61 
     | 
    
         | 
| 
       62 
62 
     | 
    
         | 
| 
       63 
63 
     | 
    
         
             
            def verify_envelope_hash(envelope: EventEnvelope, hash: str):
         
     | 
| 
       64 
     | 
    
         
            -
                return verify_hash(hash_dict(normalize_log(envelope. 
     | 
| 
      
 64 
     | 
    
         
            +
                return verify_hash(hash_dict(normalize_log(envelope.model_dump(exclude_none=True))), hash)
         
     | 
| 
       65 
65 
     | 
    
         | 
| 
       66 
66 
     | 
    
         | 
| 
       67 
67 
     | 
    
         
             
            def canonicalize_event(event: Event) -> bytes:
         
     | 
| 
         @@ -192,7 +192,7 @@ def arweave_graphql_url(): 
     | 
|
| 
       192 
192 
     | 
    
         
             
                return f"{ARWEAVE_BASE_URL}/graphql"
         
     | 
| 
       193 
193 
     | 
    
         | 
| 
       194 
194 
     | 
    
         | 
| 
       195 
     | 
    
         
            -
            def get_arweave_published_roots(tree_name: str, tree_sizes:  
     | 
| 
      
 195 
     | 
    
         
            +
            def get_arweave_published_roots(tree_name: str, tree_sizes: Collection[int]) -> Dict[int, PublishedRoot]:
         
     | 
| 
       196 
196 
     | 
    
         
             
                if len(tree_sizes) == 0:
         
     | 
| 
       197 
197 
     | 
    
         
             
                    return {}
         
     | 
| 
       198 
198 
     | 
    
         |