@@ -413,7 +413,7 @@ def verify_delegate(
413413 """
414414
415415 # Find the keys and role in delegator metadata
416- role = None
416+ role : Optional [ Role ] = None
417417 if isinstance (self .signed , Root ):
418418 keys = self .signed .keys
419419 role = self .signed .roles .get (delegated_role )
@@ -422,7 +422,13 @@ def verify_delegate(
422422 raise ValueError (f"No delegation found for { delegated_role } " )
423423
424424 keys = self .signed .delegations .keys
425- role = self .signed .delegations .roles .get (delegated_role )
425+ if self .signed .delegations .roles is not None :
426+ role = self .signed .delegations .roles .get (delegated_role )
427+ elif self .signed .delegations .succinct_roles is not None :
428+ if self .signed .delegations .succinct_roles .is_delegated_role (
429+ delegated_role
430+ ):
431+ role = self .signed .delegations .succinct_roles
426432 else :
427433 raise TypeError ("Call is valid only on delegator metadata" )
428434
@@ -1592,28 +1598,42 @@ class Delegations:
15921598 defines which keys are required to sign the metadata for a specific
15931599 role. The roles order also defines the order that role delegations
15941600 are considered during target searches.
1601+ succinct_roles: Contains succinct information about hash bin
1602+ delegations. Note that succinct roles is not a TUF specification
1603+ feature yet and setting `succinct_roles` to a value makes the
1604+ resulting metadata non-compliant. The metadata will not be accepted
1605+ as valid by specification compliant clients such as those built with
1606+ python-tuf <= 1.1.0. For more information see: https://github.com/theupdateframework/taps/blob/master/tap15.md
15951607 unrecognized_fields: Dictionary of all attributes that are not managed
15961608 by TUF Metadata API
15971609
1610+ Exactly one of ``roles`` and ``succinct_roles`` must be set.
1611+
15981612 Raises:
15991613 ValueError: Invalid arguments.
16001614 """
16011615
16021616 def __init__ (
16031617 self ,
16041618 keys : Dict [str , Key ],
1605- roles : Dict [str , DelegatedRole ],
1619+ roles : Optional [Dict [str , DelegatedRole ]] = None ,
1620+ succinct_roles : Optional [SuccinctRoles ] = None ,
16061621 unrecognized_fields : Optional [Dict [str , Any ]] = None ,
16071622 ):
16081623 self .keys = keys
1609-
1610- for role in roles :
1611- if not role or role in TOP_LEVEL_ROLE_NAMES :
1612- raise ValueError (
1613- "Delegated roles cannot be empty or use top-level role names"
1614- )
1624+ if sum (1 for v in [roles , succinct_roles ] if v is not None ) != 1 :
1625+ raise ValueError ("One of roles and succinct_roles must be set" )
1626+
1627+ if roles is not None :
1628+ for role in roles :
1629+ if not role or role in TOP_LEVEL_ROLE_NAMES :
1630+ raise ValueError (
1631+ "Delegated roles cannot be empty or use top-level "
1632+ "role names"
1633+ )
16151634
16161635 self .roles = roles
1636+ self .succinct_roles = succinct_roles
16171637 if unrecognized_fields is None :
16181638 unrecognized_fields = {}
16191639
@@ -1623,14 +1643,22 @@ def __eq__(self, other: Any) -> bool:
16231643 if not isinstance (other , Delegations ):
16241644 return False
16251645
1626- return (
1646+ all_attributes_check = (
16271647 self .keys == other .keys
1628- # Order of the delegated roles matters (see issue #1788).
1629- and list (self .roles .items ()) == list (other .roles .items ())
16301648 and self .roles == other .roles
1649+ and self .succinct_roles == other .succinct_roles
16311650 and self .unrecognized_fields == other .unrecognized_fields
16321651 )
16331652
1653+ if self .roles is not None and other .roles is not None :
1654+ all_attributes_check = (
1655+ all_attributes_check
1656+ # Order of the delegated roles matters (see issue #1788).
1657+ and list (self .roles .items ()) == list (other .roles .items ())
1658+ )
1659+
1660+ return all_attributes_check
1661+
16341662 @classmethod
16351663 def from_dict (cls , delegations_dict : Dict [str , Any ]) -> "Delegations" :
16361664 """Creates ``Delegations`` object from its json/dict representation.
@@ -1642,25 +1670,59 @@ def from_dict(cls, delegations_dict: Dict[str, Any]) -> "Delegations":
16421670 keys_res = {}
16431671 for keyid , key_dict in keys .items ():
16441672 keys_res [keyid ] = Key .from_dict (keyid , key_dict )
1645- roles = delegations_dict .pop ("roles" )
1646- roles_res : Dict [str , DelegatedRole ] = {}
1647- for role_dict in roles :
1648- new_role = DelegatedRole .from_dict (role_dict )
1649- if new_role .name in roles_res :
1650- raise ValueError (f"Duplicate role { new_role .name } " )
1651- roles_res [new_role .name ] = new_role
1673+ roles = delegations_dict .pop ("roles" , None )
1674+ roles_res : Optional [Dict [str , DelegatedRole ]] = None
1675+
1676+ if roles is not None :
1677+ roles_res = {}
1678+ for role_dict in roles :
1679+ new_role = DelegatedRole .from_dict (role_dict )
1680+ if new_role .name in roles_res :
1681+ raise ValueError (f"Duplicate role { new_role .name } " )
1682+ roles_res [new_role .name ] = new_role
1683+
1684+ succinct_roles_dict = delegations_dict .pop ("succinct_roles" , None )
1685+ succinct_roles_info = None
1686+ if succinct_roles_dict is not None :
1687+ succinct_roles_info = SuccinctRoles .from_dict (succinct_roles_dict )
1688+
16521689 # All fields left in the delegations_dict are unrecognized.
1653- return cls (keys_res , roles_res , delegations_dict )
1690+ return cls (keys_res , roles_res , succinct_roles_info , delegations_dict )
16541691
16551692 def to_dict (self ) -> Dict [str , Any ]:
16561693 """Returns the dict representation of self."""
16571694 keys = {keyid : key .to_dict () for keyid , key in self .keys .items ()}
1658- roles = [role_obj .to_dict () for role_obj in self .roles .values ()]
1659- return {
1695+ res_dict : Dict [str , Any ] = {
16601696 "keys" : keys ,
1661- "roles" : roles ,
16621697 ** self .unrecognized_fields ,
16631698 }
1699+ if self .roles is not None :
1700+ roles = [role_obj .to_dict () for role_obj in self .roles .values ()]
1701+ res_dict ["roles" ] = roles
1702+ elif self .succinct_roles is not None :
1703+ res_dict ["succinct_roles" ] = self .succinct_roles .to_dict ()
1704+
1705+ return res_dict
1706+
1707+ def get_roles_for_target (
1708+ self , target_filepath : str
1709+ ) -> Iterator [Tuple [str , bool ]]:
1710+ """Given ``target_filepath`` get names and terminating status of all
1711+ delegated roles who are responsible for it.
1712+
1713+ Args:
1714+ target_filepath: URL path to a target file, relative to a base
1715+ targets URL.
1716+ """
1717+ if self .roles is not None :
1718+ for role in self .roles .values ():
1719+ if role .is_delegated_path (target_filepath ):
1720+ yield role .name , role .terminating
1721+
1722+ elif self .succinct_roles is not None :
1723+ # We consider all succinct_roles as terminating.
1724+ # For more information read TAP 15.
1725+ yield self .succinct_roles .get_role_for_target (target_filepath ), True
16641726
16651727
16661728class TargetFile (BaseFile ):
@@ -1921,11 +1983,15 @@ def add_key(self, role: str, key: Key) -> None:
19211983 ValueError: If there are no delegated roles or if ``role`` is not
19221984 delegated by this Target.
19231985 """
1924- if self .delegations is None or role not in self . delegations . roles :
1986+ if self .delegations is None :
19251987 raise ValueError (f"Delegated role { role } doesn't exist" )
1926- if key .keyid not in self .delegations .roles [role ].keyids :
1927- self .delegations .roles [role ].keyids .append (key .keyid )
1928- self .delegations .keys [key .keyid ] = key
1988+
1989+ if self .delegations .roles is not None :
1990+ if role not in self .delegations .roles :
1991+ raise ValueError (f"Delegated role { role } doesn't exist" )
1992+ if key .keyid not in self .delegations .roles [role ].keyids :
1993+ self .delegations .roles [role ].keyids .append (key .keyid )
1994+ self .delegations .keys [key .keyid ] = key
19291995
19301996 def remove_key (self , role : str , keyid : str ) -> None :
19311997 """Removes key from delegated role ``role`` and updates the delegations
@@ -1939,13 +2005,18 @@ def remove_key(self, role: str, keyid: str) -> None:
19392005 ValueError: If there are no delegated roles or if ``role`` is not
19402006 delegated by this ``Target`` or if key is not used by ``role``.
19412007 """
1942- if self .delegations is None or role not in self . delegations . roles :
2008+ if self .delegations is None :
19432009 raise ValueError (f"Delegated role { role } doesn't exist" )
1944- if keyid not in self .delegations .roles [role ].keyids :
1945- raise ValueError (f"Key with id { keyid } is not used by { role } " )
1946- self .delegations .roles [role ].keyids .remove (keyid )
1947- for keyinfo in self .delegations .roles .values ():
1948- if keyid in keyinfo .keyids :
1949- return
19502010
1951- del self .delegations .keys [keyid ]
2011+ if self .delegations .roles is not None :
2012+ if role not in self .delegations .roles :
2013+ raise ValueError (f"Delegated role { role } doesn't exist" )
2014+ if keyid not in self .delegations .roles [role ].keyids :
2015+ raise ValueError (f"Key with id { keyid } is not used by { role } " )
2016+
2017+ self .delegations .roles [role ].keyids .remove (keyid )
2018+ for keyinfo in self .delegations .roles .values ():
2019+ if keyid in keyinfo .keyids :
2020+ return
2021+
2022+ del self .delegations .keys [keyid ]
0 commit comments