Skip to content

Commit df35943

Browse files
author
Ivana Atanasova
committed
Create constants for top-level rolenames
This is a change in the metadata API to remove hardcoded rolenames and use constants instead. Addresses #164 Signed-off-by: Ivana Atanasova <[email protected]>
1 parent fa7990c commit df35943

File tree

3 files changed

+78
-51
lines changed

3 files changed

+78
-51
lines changed

tuf/api/metadata.py

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import tempfile
3232
from collections import OrderedDict
3333
from datetime import datetime, timedelta
34+
from enum import Enum
3435
from typing import (
3536
IO,
3637
Any,
@@ -61,14 +62,25 @@
6162
SignedSerializer,
6263
)
6364

65+
66+
class RoleNames(Enum):
67+
ROOT: str = "root"
68+
SNAPSHOT: str = "snapshot"
69+
TARGETS: str = "targets"
70+
TIMESTAMP: str = "timestamp"
71+
72+
@staticmethod
73+
def all() -> set:
74+
return set(map(lambda r: r.value, RoleNames))
75+
76+
6477
# pylint: disable=too-many-lines
6578

6679
logger = logging.getLogger(__name__)
6780

6881
# We aim to support SPECIFICATION_VERSION and require the input metadata
6982
# files to have the same major version (the first number) as ours.
7083
SPECIFICATION_VERSION = ["1", "0", "19"]
71-
TOP_LEVEL_ROLE_NAMES = {"root", "timestamp", "snapshot", "targets"}
7284

7385
# T is a Generic type constraint for Metadata.signed
7486
T = TypeVar("T", "Root", "Timestamp", "Snapshot", "Targets")
@@ -130,13 +142,13 @@ def from_dict(cls, metadata: Dict[str, Any]) -> "Metadata[T]":
130142
# Dispatch to contained metadata class on metadata _type field.
131143
_type = metadata["signed"]["_type"]
132144

133-
if _type == "targets":
145+
if _type == RoleNames.TARGETS.value:
134146
inner_cls: Type[Signed] = Targets
135-
elif _type == "snapshot":
147+
elif _type == RoleNames.SNAPSHOT.value:
136148
inner_cls = Snapshot
137-
elif _type == "timestamp":
149+
elif _type == RoleNames.TIMESTAMP.value:
138150
inner_cls = Timestamp
139-
elif _type == "root":
151+
elif _type == RoleNames.ROOT.value:
140152
inner_cls = Root
141153
else:
142154
raise ValueError(f'unrecognized metadata type "{_type}"')
@@ -712,7 +724,7 @@ class Root(Signed):
712724
unrecognized_fields: Dictionary of all unrecognized fields.
713725
"""
714726

715-
_signed_type = "root"
727+
_signed_type = RoleNames.ROOT.value
716728

717729
# TODO: determine an appropriate value for max-args
718730
# pylint: disable=too-many-arguments
@@ -729,7 +741,7 @@ def __init__(
729741
super().__init__(version, spec_version, expires, unrecognized_fields)
730742
self.consistent_snapshot = consistent_snapshot
731743
self.keys = keys
732-
if set(roles) != TOP_LEVEL_ROLE_NAMES:
744+
if set(roles) != RoleNames.all():
733745
raise ValueError("Role names must be the top-level metadata roles")
734746

735747
self.roles = roles
@@ -965,7 +977,7 @@ class Timestamp(Signed):
965977
snapshot_meta: Meta information for snapshot metadata.
966978
"""
967979

968-
_signed_type = "timestamp"
980+
_signed_type = RoleNames.TIMESTAMP.value
969981

970982
def __init__(
971983
self,
@@ -1015,7 +1027,7 @@ class Snapshot(Signed):
10151027
meta: A dictionary of target metadata filenames to MetaFile objects.
10161028
"""
10171029

1018-
_signed_type = "snapshot"
1030+
_signed_type = RoleNames.SNAPSHOT.value
10191031

10201032
def __init__(
10211033
self,
@@ -1402,7 +1414,7 @@ class Targets(Signed):
14021414
unrecognized_fields: Dictionary of all unrecognized fields.
14031415
"""
14041416

1405-
_signed_type = "targets"
1417+
_signed_type = RoleNames.TARGETS.value
14061418

14071419
# TODO: determine an appropriate value for max-args
14081420
# pylint: disable=too-many-arguments
@@ -1423,7 +1435,7 @@ def __init__(
14231435
def from_dict(cls, signed_dict: Dict[str, Any]) -> "Targets":
14241436
"""Creates Targets object from its dict representation."""
14251437
common_args = cls._common_fields_from_dict(signed_dict)
1426-
targets = signed_dict.pop("targets")
1438+
targets = signed_dict.pop(RoleNames.TARGETS.value)
14271439
try:
14281440
delegations_dict = signed_dict.pop("delegations")
14291441
except KeyError:
@@ -1444,7 +1456,7 @@ def to_dict(self) -> Dict[str, Any]:
14441456
targets = {}
14451457
for target_path, target_file_obj in self.targets.items():
14461458
targets[target_path] = target_file_obj.to_dict()
1447-
targets_dict["targets"] = targets
1459+
targets_dict[RoleNames.TARGETS.value] = targets
14481460
if self.delegations is not None:
14491461
targets_dict["delegations"] = self.delegations.to_dict()
14501462
return targets_dict

tuf/ngclient/_internal/trusted_metadata_set.py

Lines changed: 38 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
network IO, which are not handled here.
1111
1212
Loaded metadata can be accessed via index access with rolename as key
13-
(trusted_set["root"]) or, in the case of top-level metadata, using the helper
13+
(trusted_set[RoleNames.ROOT.value]) or, in the case of top-level metadata, using the helper
1414
properties (trusted_set.root).
1515
1616
The rules that TrustedMetadataSet follows for top-level metadata are
@@ -35,7 +35,7 @@
3535
>>> trusted_set = TrustedMetadataSet(f.read())
3636
>>>
3737
>>> # update root from remote until no more are available
38-
>>> with download("root", trusted_set.root.signed.version + 1) as f:
38+
>>> with download(RoleNames.ROOT.value, trusted_set.root.signed.version + 1) as f:
3939
>>> trusted_set.update_root(f.read())
4040
>>>
4141
>>> # load local timestamp, then update from remote
@@ -45,7 +45,7 @@
4545
>>> except (RepositoryError, OSError):
4646
>>> pass # failure to load a local file is ok
4747
>>>
48-
>>> with download("timestamp") as f:
48+
>>> with download(RoleNames.TIMESTAMP.value) as f:
4949
>>> trusted_set.update_timestamp(f.read())
5050
>>>
5151
>>> # load local snapshot, then update from remote if needed
@@ -55,7 +55,7 @@
5555
>>> except (RepositoryError, OSError):
5656
>>> # local snapshot is not valid, load from remote
5757
>>> # (RepositoryErrors here stop the update)
58-
>>> with download("snapshot", version) as f:
58+
>>> with download(RoleNames.SNAPSHOT.value, version) as f:
5959
>>> trusted_set.update_snapshot(f.read())
6060
6161
TODO:
@@ -73,7 +73,14 @@
7373
from typing import Dict, Iterator, Optional
7474

7575
from tuf import exceptions
76-
from tuf.api.metadata import Metadata, Root, Snapshot, Targets, Timestamp
76+
from tuf.api.metadata import (
77+
Metadata,
78+
RoleNames,
79+
Root,
80+
Snapshot,
81+
Targets,
82+
Timestamp,
83+
)
7784
from tuf.api.serialization import DeserializationError
7885

7986
logger = logging.getLogger(__name__)
@@ -123,22 +130,22 @@ def __iter__(self) -> Iterator[Metadata]:
123130
@property
124131
def root(self) -> Metadata[Root]:
125132
"""Current root Metadata"""
126-
return self._trusted_set["root"]
133+
return self._trusted_set[RoleNames.ROOT.value]
127134

128135
@property
129136
def timestamp(self) -> Optional[Metadata[Timestamp]]:
130137
"""Current timestamp Metadata or None"""
131-
return self._trusted_set.get("timestamp")
138+
return self._trusted_set.get(RoleNames.TIMESTAMP.value)
132139

133140
@property
134141
def snapshot(self) -> Optional[Metadata[Snapshot]]:
135142
"""Current snapshot Metadata or None"""
136-
return self._trusted_set.get("snapshot")
143+
return self._trusted_set.get(RoleNames.SNAPSHOT.value)
137144

138145
@property
139146
def targets(self) -> Optional[Metadata[Targets]]:
140147
"""Current targets Metadata or None"""
141-
return self._trusted_set.get("targets")
148+
return self._trusted_set.get(RoleNames.TARGETS.value)
142149

143150
# Methods for updating metadata
144151
def update_root(self, data: bytes) -> None:
@@ -163,23 +170,25 @@ def update_root(self, data: bytes) -> None:
163170
except DeserializationError as e:
164171
raise exceptions.RepositoryError("Failed to load root") from e
165172

166-
if new_root.signed.type != "root":
173+
if new_root.signed.type != RoleNames.ROOT.value:
167174
raise exceptions.RepositoryError(
168175
f"Expected 'root', got '{new_root.signed.type}'"
169176
)
170177

171178
# Verify that new root is signed by trusted root
172-
self.root.verify_delegate("root", new_root)
179+
self.root.verify_delegate(RoleNames.ROOT.value, new_root)
173180

174181
if new_root.signed.version != self.root.signed.version + 1:
175182
raise exceptions.ReplayedMetadataError(
176-
"root", new_root.signed.version, self.root.signed.version
183+
RoleNames.ROOT.value,
184+
new_root.signed.version,
185+
self.root.signed.version,
177186
)
178187

179188
# Verify that new root is signed by itself
180-
new_root.verify_delegate("root", new_root)
189+
new_root.verify_delegate(RoleNames.ROOT.value, new_root)
181190

182-
self._trusted_set["root"] = new_root
191+
self._trusted_set[RoleNames.ROOT.value] = new_root
183192
logger.info("Updated root v%d", new_root.signed.version)
184193

185194
def update_timestamp(self, data: bytes) -> None:
@@ -214,20 +223,20 @@ def update_timestamp(self, data: bytes) -> None:
214223
except DeserializationError as e:
215224
raise exceptions.RepositoryError("Failed to load timestamp") from e
216225

217-
if new_timestamp.signed.type != "timestamp":
226+
if new_timestamp.signed.type != RoleNames.TIMESTAMP.value:
218227
raise exceptions.RepositoryError(
219228
f"Expected 'timestamp', got '{new_timestamp.signed.type}'"
220229
)
221230

222-
self.root.verify_delegate("timestamp", new_timestamp)
231+
self.root.verify_delegate(RoleNames.TIMESTAMP.value, new_timestamp)
223232

224233
# If an existing trusted timestamp is updated,
225234
# check for a rollback attack
226235
if self.timestamp is not None:
227236
# Prevent rolling back timestamp version
228237
if new_timestamp.signed.version < self.timestamp.signed.version:
229238
raise exceptions.ReplayedMetadataError(
230-
"timestamp",
239+
RoleNames.TIMESTAMP.value,
231240
new_timestamp.signed.version,
232241
self.timestamp.signed.version,
233242
)
@@ -237,15 +246,15 @@ def update_timestamp(self, data: bytes) -> None:
237246
< self.timestamp.signed.snapshot_meta.version
238247
):
239248
raise exceptions.ReplayedMetadataError(
240-
"snapshot",
249+
RoleNames.SNAPSHOT.value,
241250
new_timestamp.signed.snapshot_meta.version,
242251
self.timestamp.signed.snapshot_meta.version,
243252
)
244253

245254
# expiry not checked to allow old timestamp to be used for rollback
246255
# protection of new timestamp: expiry is checked in update_snapshot()
247256

248-
self._trusted_set["timestamp"] = new_timestamp
257+
self._trusted_set[RoleNames.TIMESTAMP.value] = new_timestamp
249258
logger.info("Updated timestamp v%d", new_timestamp.signed.version)
250259

251260
# timestamp is loaded: raise if it is not valid _final_ timestamp
@@ -310,12 +319,12 @@ def update_snapshot(
310319
except DeserializationError as e:
311320
raise exceptions.RepositoryError("Failed to load snapshot") from e
312321

313-
if new_snapshot.signed.type != "snapshot":
322+
if new_snapshot.signed.type != RoleNames.SNAPSHOT.value:
314323
raise exceptions.RepositoryError(
315324
f"Expected 'snapshot', got '{new_snapshot.signed.type}'"
316325
)
317326

318-
self.root.verify_delegate("snapshot", new_snapshot)
327+
self.root.verify_delegate(RoleNames.SNAPSHOT.value, new_snapshot)
319328

320329
# version not checked against meta version to allow old snapshot to be
321330
# used in rollback protection: it is checked when targets is updated
@@ -341,7 +350,7 @@ def update_snapshot(
341350
# expiry not checked to allow old snapshot to be used for rollback
342351
# protection of new snapshot: it is checked when targets is updated
343352

344-
self._trusted_set["snapshot"] = new_snapshot
353+
self._trusted_set[RoleNames.SNAPSHOT.value] = new_snapshot
345354
logger.info("Updated snapshot v%d", new_snapshot.signed.version)
346355

347356
# snapshot is loaded, but we raise if it's not valid _final_ snapshot
@@ -371,7 +380,9 @@ def update_targets(self, data: bytes) -> None:
371380
RepositoryError: Metadata failed to load or verify. The actual
372381
error type and content will contain more details.
373382
"""
374-
self.update_delegated_targets(data, "targets", "root")
383+
self.update_delegated_targets(
384+
data, RoleNames.TARGETS.value, RoleNames.ROOT.value
385+
)
375386

376387
def update_delegated_targets(
377388
self, data: bytes, role_name: str, delegator_name: str
@@ -419,7 +430,7 @@ def update_delegated_targets(
419430
except DeserializationError as e:
420431
raise exceptions.RepositoryError("Failed to load snapshot") from e
421432

422-
if new_delegate.signed.type != "targets":
433+
if new_delegate.signed.type != RoleNames.TARGETS.value:
423434
raise exceptions.RepositoryError(
424435
f"Expected 'targets', got '{new_delegate.signed.type}'"
425436
)
@@ -449,12 +460,12 @@ def _load_trusted_root(self, data: bytes) -> None:
449460
except DeserializationError as e:
450461
raise exceptions.RepositoryError("Failed to load root") from e
451462

452-
if new_root.signed.type != "root":
463+
if new_root.signed.type != RoleNames.ROOT.value:
453464
raise exceptions.RepositoryError(
454465
f"Expected 'root', got '{new_root.signed.type}'"
455466
)
456467

457-
new_root.verify_delegate("root", new_root)
468+
new_root.verify_delegate(RoleNames.ROOT.value, new_root)
458469

459-
self._trusted_set["root"] = new_root
470+
self._trusted_set[RoleNames.ROOT.value] = new_root
460471
logger.info("Loaded trusted root v%d", new_root.signed.version)

0 commit comments

Comments
 (0)