@@ -728,6 +728,135 @@ def update(
728
728
self .meta [metadata_fn ]["hashes" ] = hashes
729
729
730
730
731
+ class DelegationRole (Role ):
732
+ """A container with information about particular delegated role.
733
+
734
+ Attributes:
735
+ name: A string giving the name of the delegated role.
736
+ keyids: A set of strings each of which represents a given key.
737
+ threshold: An integer representing the required number of keys for that
738
+ particular role.
739
+ terminating: A boolean indicating whether subsequent delegations
740
+ should be considered.
741
+ paths: An optional list of strings, where each string describes
742
+ a path that the role is trusted to provide.
743
+ path_hash_prefixes: An optional list of HEX_DIGESTs used to succinctly
744
+ describe a set of target paths. Only one of the attributes "paths"
745
+ and "path_hash_prefixes" is allowed to be set.
746
+ unrecognized_fields: Dictionary of all unrecognized fields.
747
+
748
+ """
749
+
750
+ def __init__ (
751
+ self ,
752
+ name : str ,
753
+ keyids : list ,
754
+ threshold : int ,
755
+ terminating : bool ,
756
+ paths : Optional [list ] = None ,
757
+ path_hash_prefixes : Optional [list ] = None ,
758
+ unrecognized_fields : Optional [Mapping [str , Any ]] = None ,
759
+ ) -> None :
760
+ super ().__init__ (keyids , threshold , unrecognized_fields )
761
+ self .name = name
762
+ self .terminating = terminating
763
+ if paths and path_hash_prefixes :
764
+ raise ValueError (
765
+ f"Only one of the attributes 'paths' and"
766
+ f"'path_hash_prefixes' can be set!"
767
+ )
768
+ if paths :
769
+ self .paths = paths
770
+ elif path_hash_prefixes :
771
+ self .path_hash_prefixes = path_hash_prefixes
772
+
773
+ @classmethod
774
+ def from_dict (cls , role_dict : Mapping [str , Any ]) -> "Role" :
775
+ """Creates DelegationRole object from its dict representation."""
776
+ name = role_dict .pop ("name" )
777
+ keyids = role_dict .pop ("keyids" )
778
+ threshold = role_dict .pop ("threshold" )
779
+ terminating = role_dict .pop ("terminating" )
780
+ paths = paths = role_dict .pop ("paths" , None )
781
+ path_hash_prefixes = role_dict .pop ("path_hash_prefixes" , None )
782
+ # All fields left in the role_dict are unrecognized.
783
+ return cls (
784
+ name ,
785
+ keyids ,
786
+ threshold ,
787
+ terminating ,
788
+ paths ,
789
+ path_hash_prefixes ,
790
+ role_dict ,
791
+ )
792
+
793
+ def to_dict (self ) -> Dict [str , Any ]:
794
+ """Returns the dict representation of self. """
795
+ base_role_dict = super ().to_dict ()
796
+ res_dict = {
797
+ "name" : self .name ,
798
+ "terminating" : self .terminating ,
799
+ ** base_role_dict ,
800
+ }
801
+ if self .paths :
802
+ res_dict ["paths" ] = self .paths
803
+ elif self .path_hash_prefixes :
804
+ res_dict ["path_hash_prefixes" ] = self .path_hash_prefixes
805
+ return res_dict
806
+
807
+
808
+ class Delegations :
809
+ """A container object storing information about all delegations.
810
+
811
+ Attributes:
812
+ keys: A dictionary of keyids and key objects containing information
813
+ about the corresponding key.
814
+ roles: A list of DelegationRole instances containing information about
815
+ all delegated roles.
816
+ unrecognized_fields: Dictionary of all unrecognized fields.
817
+
818
+ """
819
+
820
+ def __init__ (
821
+ self ,
822
+ keys : Mapping [str , Key ],
823
+ roles : list ,
824
+ unrecognized_fields : Optional [Mapping [str , Any ]] = None ,
825
+ ) -> None :
826
+ self .keys = keys
827
+ self .roles = roles
828
+ self .unrecognized_fields = unrecognized_fields or {}
829
+
830
+ @classmethod
831
+ def from_dict (cls , delegations_dict : Mapping [str , Any ]) -> "Delegations" :
832
+ """Creates Delegations object from its dict representation."""
833
+ keys = delegations_dict .pop ("keys" )
834
+ for keyid , key_dict in keys .items ():
835
+ keys [keyid ] = Key .from_dict (key_dict .copy ())
836
+ roles = delegations_dict .pop ("roles" )
837
+ roles_res = []
838
+ for role_dict in roles :
839
+ new_role = DelegationRole .from_dict (role_dict .copy ())
840
+ roles_res .append (new_role )
841
+ # All fields left in the delegations_dict are unrecognized.
842
+ return cls (keys , roles_res , delegations_dict )
843
+
844
+ def to_dict (self ) -> Dict [str , Any ]:
845
+ """Returns the dict representation of self. """
846
+ keys = {}
847
+ for keyid , key in self .keys .items ():
848
+ keys [keyid ] = key .to_dict ()
849
+ roles = []
850
+ for role_obj in self .roles :
851
+ roles .append (role_obj .to_dict ())
852
+ res_dict = {
853
+ "keys" : keys ,
854
+ "roles" : roles ,
855
+ ** self .unrecognized_fields ,
856
+ }
857
+ return res_dict
858
+
859
+
731
860
class Targets (Signed ):
732
861
"""A container for the signed part of targets metadata.
733
862
@@ -747,38 +876,9 @@ class Targets(Signed):
747
876
...
748
877
}
749
878
750
- delegations: A dictionary that contains a list of delegated target
879
+ delegations: An optional object containing a list of delegated target
751
880
roles and public key store used to verify their metadata
752
- signatures::
753
-
754
- {
755
- 'keys' : {
756
- '<KEYID>': {
757
- 'keytype': '<KEY TYPE>',
758
- 'scheme': '<KEY SCHEME>',
759
- 'keyid_hash_algorithms': [
760
- '<HASH ALGO 1>',
761
- '<HASH ALGO 2>'
762
- ...
763
- ],
764
- 'keyval': {
765
- 'public': '<PUBLIC KEY HEX REPRESENTATION>'
766
- }
767
- },
768
- ...
769
- },
770
- 'roles': [
771
- {
772
- 'name': '<ROLENAME>',
773
- 'keyids': ['<SIGNING KEY KEYID>', ...],
774
- 'threshold': <SIGNATURE THRESHOLD>,
775
- 'terminating': <TERMINATING BOOLEAN>,
776
- 'path_hash_prefixes': ['<HEX DIGEST>', ... ], // or
777
- 'paths' : ['PATHPATTERN', ... ],
778
- },
779
- ...
780
- ]
781
- }
881
+ signatures.
782
882
783
883
"""
784
884
@@ -793,13 +893,12 @@ def __init__(
793
893
spec_version : str ,
794
894
expires : datetime ,
795
895
targets : Mapping [str , Any ],
796
- delegations : Mapping [ str , Any ] ,
896
+ delegations : Optional [ Delegations ] = None ,
797
897
unrecognized_fields : Optional [Mapping [str , Any ]] = None ,
798
898
) -> None :
799
899
super ().__init__ (
800
900
_type , version , spec_version , expires , unrecognized_fields
801
901
)
802
- # TODO: Add class for meta
803
902
self .targets = targets
804
903
self .delegations = delegations
805
904
@@ -808,17 +907,18 @@ def from_dict(cls, targets_dict: Mapping[str, Any]) -> "Targets":
808
907
"""Creates Targets object from its dict representation."""
809
908
common_args = cls ._common_fields_from_dict (targets_dict )
810
909
targets = targets_dict .pop ("targets" )
811
- delegations = targets_dict .pop ("delegations" )
910
+ delegations_dict = targets_dict .pop ("delegations" )
911
+ delegations_obj = Delegations .from_dict (delegations_dict )
812
912
# All fields left in the targets_dict are unrecognized.
813
- return cls (* common_args , targets , delegations , targets_dict )
913
+ return cls (* common_args , targets , delegations_obj , targets_dict )
814
914
815
915
def to_dict (self ) -> Dict [str , Any ]:
816
916
"""Returns the dict representation of self."""
817
917
targets_dict = self ._common_fields_to_dict ()
818
918
targets_dict .update (
819
919
{
820
920
"targets" : self .targets ,
821
- "delegations" : self .delegations ,
921
+ "delegations" : self .delegations . to_dict () ,
822
922
}
823
923
)
824
924
return targets_dict
0 commit comments