Skip to content

Commit 26982f7

Browse files
committed
Add a Role class and integrate it into Root
In the top level metadata classes, there are complex attributes such as "meta" in Targets and Snapshot, "key" and "roles" in Root etc. We want to represent those complex attributes with a class to allow easier verification and support for metadata with unrecognized fields. For more context read ADR 0004 and ADR 0008 in the docs/adr folder. Signed-off-by: Martin Vrachev <[email protected]>
1 parent f73ac31 commit 26982f7

File tree

2 files changed

+59
-15
lines changed

2 files changed

+59
-15
lines changed

tests/test_api.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -306,21 +306,21 @@ def test_metadata_root(self):
306306
root_key2['keytype'], root_key2['scheme'], root_key2['keyval'])
307307

308308
# Assert that root does not contain the new key
309-
self.assertNotIn(keyid, root.signed.roles['root']['keyids'])
309+
self.assertNotIn(keyid, root.signed.roles['root'].keyids)
310310
self.assertNotIn(keyid, root.signed.keys)
311311

312312
# Add new root key
313313
root.signed.add_key('root', keyid, key_metadata)
314314

315315
# Assert that key is added
316-
self.assertIn(keyid, root.signed.roles['root']['keyids'])
316+
self.assertIn(keyid, root.signed.roles['root'].keyids)
317317
self.assertIn(keyid, root.signed.keys)
318318

319319
# Remove the key
320320
root.signed.remove_key('root', keyid)
321321

322322
# Assert that root does not contain the new key anymore
323-
self.assertNotIn(keyid, root.signed.roles['root']['keyids'])
323+
self.assertNotIn(keyid, root.signed.roles['root'].keyids)
324324
self.assertNotIn(keyid, root.signed.keys)
325325

326326

@@ -369,6 +369,9 @@ def test_support_for_unrecognized_fields(self):
369369
if metadata == "root":
370370
for keyid in dict1["signed"]["keys"].keys():
371371
dict1["signed"]["keys"][keyid]["d"] = "c"
372+
for role_str in dict1["signed"]["roles"].keys():
373+
dict1["signed"]["roles"][role_str]["e"] = "g"
374+
372375
temp_copy = copy.deepcopy(dict1)
373376
metadata_obj = Metadata.from_dict(temp_copy)
374377

tuf/api/metadata.py

Lines changed: 53 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,41 @@ def get_public_val(self):
450450
return self.keyval["public"]
451451

452452

453+
class Role:
454+
"""A container class containing the set of keyids and threshold associated
455+
with a particular role.
456+
457+
Attributes:
458+
keyids: A set of strings each of which represents a given key.
459+
threshold: An integer representing the required number of keys for that
460+
particular role.
461+
unrecognized_fields: Dictionary of all unrecognized fields.
462+
463+
"""
464+
465+
def __init__(
466+
self,
467+
keyids: set,
468+
threshold: int,
469+
unrecognized_fields: Optional[Mapping[str, Any]] = None,
470+
) -> None:
471+
self.keyids = keyids
472+
self.threshold = threshold
473+
if unrecognized_fields is None:
474+
unrecognized_fields = {}
475+
self.unrecognized_fields = unrecognized_fields
476+
477+
def to_dict(self) -> Dict:
478+
"""Returns the dictionary representation of self."""
479+
res_dict = {
480+
"keyids": self.keyids,
481+
"threshold": self.threshold,
482+
**self.unrecognized_fields,
483+
}
484+
485+
return res_dict
486+
487+
453488
class Root(Signed):
454489
"""A container for the signed part of root metadata.
455490
@@ -465,10 +500,7 @@ class Root(Signed):
465500
roles: A dictionary that contains a list of signing keyids and
466501
a signature threshold for each top level role::
467502
{
468-
'<ROLE>': {
469-
'keyids': ['<SIGNING KEY KEYID>', ...],
470-
'threshold': <SIGNATURE THRESHOLD>,
471-
},
503+
'<ROLE>': <Role istance>,
472504
...
473505
}
474506
@@ -486,13 +518,12 @@ def __init__(
486518
expires: datetime,
487519
consistent_snapshot: bool,
488520
keys: Mapping[str, Key],
489-
roles: Mapping[str, Any],
521+
roles: Mapping[str, Role],
490522
unrecognized_fields: Optional[Mapping[str, Any]] = None,
491523
) -> None:
492524
super().__init__(
493525
_type, version, spec_version, expires, unrecognized_fields
494526
)
495-
# TODO: Add a class for roles
496527
self.consistent_snapshot = consistent_snapshot
497528
self.keys = keys
498529
self.roles = roles
@@ -513,6 +544,13 @@ def from_dict(cls, root_dict: Mapping[str, Any]) -> "Root":
513544
unrecognized_key_fields = key
514545
keys[keyid] = Key(keytype, scheme, keyval, unrecognized_key_fields)
515546

547+
for role_str, role_dict in roles.items():
548+
keyids = role_dict.pop("keyids")
549+
threshold = role_dict.pop("threshold")
550+
# All fields left in the role_dict are unrecognized.
551+
unrecognized_role_fields = role_dict
552+
roles[role_str] = Role(keyids, threshold, unrecognized_role_fields)
553+
516554
# All fields left in the root_dict are unrecognized.
517555
unrecognized_fields = root_dict
518556
return cls(
@@ -525,12 +563,15 @@ def to_dict(self) -> Dict[str, Any]:
525563
keys = {}
526564
for keyid, key in self.keys.items():
527565
keys[keyid] = key.to_dict()
566+
roles = {}
567+
for role_str, role in self.roles.items():
568+
roles[role_str] = role.to_dict()
528569

529570
root_dict.update(
530571
{
531572
"consistent_snapshot": self.consistent_snapshot,
532573
"keys": keys,
533-
"roles": self.roles,
574+
"roles": roles,
534575
}
535576
)
536577
return root_dict
@@ -540,17 +581,17 @@ def add_key(
540581
self, role: str, keyid: str, key_metadata: Mapping[str, Any]
541582
) -> None:
542583
"""Adds new key for 'role' and updates the key store. """
543-
if keyid not in self.roles[role]["keyids"]:
544-
self.roles[role]["keyids"].append(keyid)
584+
if keyid not in self.roles[role].keyids:
585+
self.roles[role].keyids.append(keyid)
545586
self.keys[keyid] = key_metadata
546587

547588
# Remove key for a role.
548589
def remove_key(self, role: str, keyid: str) -> None:
549590
"""Removes key for 'role' and updates the key store. """
550-
if keyid in self.roles[role]["keyids"]:
551-
self.roles[role]["keyids"].remove(keyid)
591+
if keyid in self.roles[role].keyids:
592+
self.roles[role].keyids.remove(keyid)
552593
for keyinfo in self.roles.values():
553-
if keyid in keyinfo["keyids"]:
594+
if keyid in keyinfo.keyids:
554595
return
555596

556597
del self.keys[keyid]

0 commit comments

Comments
 (0)