@@ -738,6 +738,129 @@ def update(
738738 self .meta [metadata_fn ]["hashes" ] = hashes
739739
740740
741+ class DelegatedRole (Role ):
742+ """A container with information about particular delegated role.
743+
744+ Attributes:
745+ name: A string giving the name of the delegated role.
746+ keyids: A set of strings each of which represents a given key.
747+ threshold: An integer representing the required number of keys for that
748+ particular role.
749+ terminating: A boolean indicating whether subsequent delegations
750+ should be considered.
751+ paths: An optional list of strings, where each string describes
752+ a path that the role is trusted to provide.
753+ path_hash_prefixes: An optional list of HEX_DIGESTs used to succinctly
754+ describe a set of target paths. Only one of the attributes "paths"
755+ and "path_hash_prefixes" is allowed to be set.
756+ unrecognized_fields: Dictionary of all unrecognized fields.
757+
758+ """
759+
760+ def __init__ (
761+ self ,
762+ name : str ,
763+ keyids : List [str ],
764+ threshold : int ,
765+ terminating : bool ,
766+ paths : Optional [List [str ]] = None ,
767+ path_hash_prefixes : Optional [List [str ]] = None ,
768+ unrecognized_fields : Optional [Mapping [str , Any ]] = None ,
769+ ) -> None :
770+ super ().__init__ (keyids , threshold , unrecognized_fields )
771+ self .name = name
772+ self .terminating = terminating
773+ if paths and path_hash_prefixes :
774+ raise ValueError (
775+ "Only one of the attributes 'paths' and"
776+ "'path_hash_prefixes' can be set!"
777+ )
778+ self .paths = paths
779+ self .path_hash_prefixes = path_hash_prefixes
780+
781+ @classmethod
782+ def from_dict (cls , role_dict : Mapping [str , Any ]) -> "Role" :
783+ """Creates DelegatedRole object from its dict representation."""
784+ name = role_dict .pop ("name" )
785+ keyids = role_dict .pop ("keyids" )
786+ threshold = role_dict .pop ("threshold" )
787+ terminating = role_dict .pop ("terminating" )
788+ paths = role_dict .pop ("paths" , None )
789+ path_hash_prefixes = role_dict .pop ("path_hash_prefixes" , None )
790+ # All fields left in the role_dict are unrecognized.
791+ return cls (
792+ name ,
793+ keyids ,
794+ threshold ,
795+ terminating ,
796+ paths ,
797+ path_hash_prefixes ,
798+ role_dict ,
799+ )
800+
801+ def to_dict (self ) -> Dict [str , Any ]:
802+ """Returns the dict representation of self."""
803+ base_role_dict = super ().to_dict ()
804+ res_dict = {
805+ "name" : self .name ,
806+ "terminating" : self .terminating ,
807+ ** base_role_dict ,
808+ }
809+ if self .paths :
810+ res_dict ["paths" ] = self .paths
811+ elif self .path_hash_prefixes :
812+ res_dict ["path_hash_prefixes" ] = self .path_hash_prefixes
813+ return res_dict
814+
815+
816+ class Delegations :
817+ """A container object storing information about all delegations.
818+
819+ Attributes:
820+ keys: A dictionary of keyids and key objects containing information
821+ about the corresponding key.
822+ roles: A list of DelegatedRole instances containing information about
823+ all delegated roles.
824+ unrecognized_fields: Dictionary of all unrecognized fields.
825+
826+ """
827+
828+ def __init__ (
829+ self ,
830+ keys : Mapping [str , Key ],
831+ roles : List [DelegatedRole ],
832+ unrecognized_fields : Optional [Mapping [str , Any ]] = None ,
833+ ) -> None :
834+ self .keys = keys
835+ self .roles = roles
836+ self .unrecognized_fields = unrecognized_fields or {}
837+
838+ @classmethod
839+ def from_dict (cls , delegations_dict : Dict [str , Any ]) -> "Delegations" :
840+ """Creates Delegations object from its dict representation."""
841+ keys = delegations_dict .pop ("keys" )
842+ keys_res = {}
843+ for keyid , key_dict in keys .items ():
844+ keys_res [keyid ] = Key .from_dict (key_dict )
845+ roles = delegations_dict .pop ("roles" )
846+ roles_res = []
847+ for role_dict in roles :
848+ new_role = DelegatedRole .from_dict (role_dict )
849+ roles_res .append (new_role )
850+ # All fields left in the delegations_dict are unrecognized.
851+ return cls (keys_res , roles_res , delegations_dict )
852+
853+ def to_dict (self ) -> Dict [str , Any ]:
854+ """Returns the dict representation of self."""
855+ keys = {keyid : key .to_dict () for keyid , key in self .keys .items ()}
856+ roles = [role_obj .to_dict () for role_obj in self .roles ]
857+ return {
858+ "keys" : keys ,
859+ "roles" : roles ,
860+ ** self .unrecognized_fields ,
861+ }
862+
863+
741864class Targets (Signed ):
742865 """A container for the signed part of targets metadata.
743866
@@ -757,38 +880,9 @@ class Targets(Signed):
757880 ...
758881 }
759882
760- delegations: A dictionary that contains a list of delegated target
883+ delegations: An optional object containing a list of delegated target
761884 roles and public key store used to verify their metadata
762- signatures::
763-
764- {
765- 'keys' : {
766- '<KEYID>': {
767- 'keytype': '<KEY TYPE>',
768- 'scheme': '<KEY SCHEME>',
769- 'keyid_hash_algorithms': [
770- '<HASH ALGO 1>',
771- '<HASH ALGO 2>'
772- ...
773- ],
774- 'keyval': {
775- 'public': '<PUBLIC KEY HEX REPRESENTATION>'
776- }
777- },
778- ...
779- },
780- 'roles': [
781- {
782- 'name': '<ROLENAME>',
783- 'keyids': ['<SIGNING KEY KEYID>', ...],
784- 'threshold': <SIGNATURE THRESHOLD>,
785- 'terminating': <TERMINATING BOOLEAN>,
786- 'path_hash_prefixes': ['<HEX DIGEST>', ... ], // or
787- 'paths' : ['PATHPATTERN', ... ],
788- },
789- ...
790- ]
791- }
885+ signatures.
792886
793887 """
794888
@@ -804,7 +898,7 @@ def __init__(
804898 spec_version : str ,
805899 expires : datetime ,
806900 targets : Dict [str , Any ],
807- delegations : Optional [Dict [ str , Any ] ] = None ,
901+ delegations : Optional [Delegations ] = None ,
808902 unrecognized_fields : Optional [Mapping [str , Any ]] = None ,
809903 ) -> None :
810904 super ().__init__ (version , spec_version , expires , unrecognized_fields )
@@ -818,6 +912,8 @@ def from_dict(cls, targets_dict: Dict[str, Any]) -> "Targets":
818912 common_args = cls ._common_fields_from_dict (targets_dict )
819913 targets = targets_dict .pop ("targets" )
820914 delegations = targets_dict .pop ("delegations" , None )
915+ if delegations :
916+ delegations = Delegations .from_dict (delegations )
821917 # All fields left in the targets_dict are unrecognized.
822918 return cls (* common_args , targets , delegations , targets_dict )
823919
@@ -826,7 +922,7 @@ def to_dict(self) -> Dict[str, Any]:
826922 targets_dict = self ._common_fields_to_dict ()
827923 targets_dict ["targets" ] = self .targets
828924 if self .delegations :
829- targets_dict ["delegations" ] = self .delegations
925+ targets_dict ["delegations" ] = self .delegations . to_dict ()
830926 return targets_dict
831927
832928 # Modification.
0 commit comments