Skip to content

Commit feb340f

Browse files
author
Jussi Kukkonen
authored
Merge pull request #1345 from MVrachev/implement-adr-8
New metadata API: add support for ADR 0008
2 parents 41f7e80 + 79391f1 commit feb340f

2 files changed

Lines changed: 64 additions & 9 deletions

File tree

tests/test_api.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,37 @@ def test_metadata_targets(self):
349349
# Verify that data is updated
350350
self.assertEqual(targets.signed.targets[filename], fileinfo)
351351

352+
def setup_dict_with_unrecognized_field(self, file_path, field, value):
353+
json_dict = {}
354+
with open(file_path) as f:
355+
json_dict = json.loads(f.read())
356+
# We are changing the json dict without changing the signature.
357+
# This could be a problem if we want to do verification on this dict.
358+
json_dict["signed"][field] = value
359+
return json_dict
360+
361+
def test_support_for_unrecognized_fields(self):
362+
for metadata in ["root", "timestamp", "snapshot", "targets"]:
363+
path = os.path.join(self.repo_dir, "metadata", metadata + ".json")
364+
dict1 = self.setup_dict_with_unrecognized_field(path, "f", "b")
365+
# Test that the metadata classes store unrecognized fields when
366+
# initializing and passes them when casting the instance to a dict.
367+
368+
temp_copy = copy.deepcopy(dict1)
369+
metadata_obj = Metadata.from_dict(temp_copy)
370+
371+
self.assertEqual(dict1["signed"], metadata_obj.signed.to_dict())
372+
373+
# Test that two instances of the same class could have different
374+
# unrecognized fields.
375+
dict2 = self.setup_dict_with_unrecognized_field(path, "f2", "b2")
376+
temp_copy2 = copy.deepcopy(dict2)
377+
metadata_obj2 = Metadata.from_dict(temp_copy2)
378+
self.assertNotEqual(
379+
metadata_obj.signed.to_dict(), metadata_obj2.signed.to_dict()
380+
)
381+
382+
352383
# Run unit test.
353384
if __name__ == '__main__':
354385
utils.configure_test_logging(sys.argv)

tuf/api/metadata.py

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -320,14 +320,20 @@ class Signed:
320320
spec_version: The TUF specification version number (semver) the
321321
metadata format adheres to.
322322
expires: The metadata expiration datetime object.
323+
unrecognized_fields: Dictionary of all unrecognized fields.
323324
324325
"""
325326

326327
# NOTE: Signed is a stupid name, because this might not be signed yet, but
327328
# we keep it to match spec terminology (I often refer to this as "payload",
328329
# or "inner metadata")
329330
def __init__(
330-
self, _type: str, version: int, spec_version: str, expires: datetime
331+
self,
332+
_type: str,
333+
version: int,
334+
spec_version: str,
335+
expires: datetime,
336+
unrecognized_fields: Optional[Mapping[str, Any]] = None,
331337
) -> None:
332338

333339
self._type = _type
@@ -338,6 +344,7 @@ def __init__(
338344
if version < 0:
339345
raise ValueError(f"version must be >= 0, got {version}")
340346
self.version = version
347+
self.unrecognized_fields = unrecognized_fields or {}
341348

342349
@staticmethod
343350
def _common_fields_from_dict(signed_dict: Mapping[str, Any]) -> list:
@@ -369,6 +376,7 @@ def _common_fields_to_dict(self) -> Dict[str, Any]:
369376
"version": self.version,
370377
"spec_version": self.spec_version,
371378
"expires": self.expires.isoformat() + "Z",
379+
**self.unrecognized_fields,
372380
}
373381

374382
def is_expired(self, reference_time: datetime = None) -> bool:
@@ -445,8 +453,11 @@ def __init__(
445453
consistent_snapshot: bool,
446454
keys: Mapping[str, Any],
447455
roles: Mapping[str, Any],
456+
unrecognized_fields: Optional[Mapping[str, Any]] = None,
448457
) -> None:
449-
super().__init__(_type, version, spec_version, expires)
458+
super().__init__(
459+
_type, version, spec_version, expires, unrecognized_fields
460+
)
450461
# TODO: Add classes for keys and roles
451462
self.consistent_snapshot = consistent_snapshot
452463
self.keys = keys
@@ -459,7 +470,8 @@ def from_dict(cls, root_dict: Mapping[str, Any]) -> "Root":
459470
consistent_snapshot = root_dict.pop("consistent_snapshot")
460471
keys = root_dict.pop("keys")
461472
roles = root_dict.pop("roles")
462-
return cls(*common_args, consistent_snapshot, keys, roles)
473+
# All fields left in the root_dict are unrecognized.
474+
return cls(*common_args, consistent_snapshot, keys, roles, root_dict)
463475

464476
def to_dict(self) -> Dict[str, Any]:
465477
"""Returns the dict representation of self. """
@@ -521,8 +533,11 @@ def __init__(
521533
spec_version: str,
522534
expires: datetime,
523535
meta: Mapping[str, Any],
536+
unrecognized_fields: Optional[Mapping[str, Any]] = None,
524537
) -> None:
525-
super().__init__(_type, version, spec_version, expires)
538+
super().__init__(
539+
_type, version, spec_version, expires, unrecognized_fields
540+
)
526541
# TODO: Add class for meta
527542
self.meta = meta
528543

@@ -531,7 +546,8 @@ def from_dict(cls, timestamp_dict: Mapping[str, Any]) -> "Timestamp":
531546
"""Creates Timestamp object from its dict representation. """
532547
common_args = cls._common_fields_from_dict(timestamp_dict)
533548
meta = timestamp_dict.pop("meta")
534-
return cls(*common_args, meta)
549+
# All fields left in the timestamp_dict are unrecognized.
550+
return cls(*common_args, meta, timestamp_dict)
535551

536552
def to_dict(self) -> Dict[str, Any]:
537553
"""Returns the dict representation of self. """
@@ -585,8 +601,11 @@ def __init__(
585601
spec_version: str,
586602
expires: datetime,
587603
meta: Mapping[str, Any],
604+
unrecognized_fields: Optional[Mapping[str, Any]] = None,
588605
) -> None:
589-
super().__init__(_type, version, spec_version, expires)
606+
super().__init__(
607+
_type, version, spec_version, expires, unrecognized_fields
608+
)
590609
# TODO: Add class for meta
591610
self.meta = meta
592611

@@ -595,7 +614,8 @@ def from_dict(cls, snapshot_dict: Mapping[str, Any]) -> "Snapshot":
595614
"""Creates Snapshot object from its dict representation. """
596615
common_args = cls._common_fields_from_dict(snapshot_dict)
597616
meta = snapshot_dict.pop("meta")
598-
return cls(*common_args, meta)
617+
# All fields left in the snapshot_dict are unrecognized.
618+
return cls(*common_args, meta, snapshot_dict)
599619

600620
def to_dict(self) -> Dict[str, Any]:
601621
"""Returns the dict representation of self. """
@@ -688,8 +708,11 @@ def __init__(
688708
expires: datetime,
689709
targets: Mapping[str, Any],
690710
delegations: Mapping[str, Any],
711+
unrecognized_fields: Optional[Mapping[str, Any]] = None,
691712
) -> None:
692-
super().__init__(_type, version, spec_version, expires)
713+
super().__init__(
714+
_type, version, spec_version, expires, unrecognized_fields
715+
)
693716
# TODO: Add class for meta
694717
self.targets = targets
695718
self.delegations = delegations
@@ -700,7 +723,8 @@ def from_dict(cls, targets_dict: Mapping[str, Any]) -> "Targets":
700723
common_args = cls._common_fields_from_dict(targets_dict)
701724
targets = targets_dict.pop("targets")
702725
delegations = targets_dict.pop("delegations")
703-
return cls(*common_args, targets, delegations)
726+
# All fields left in the targets_dict are unrecognized.
727+
return cls(*common_args, targets, delegations, targets_dict)
704728

705729
def to_dict(self) -> Dict[str, Any]:
706730
"""Returns the dict representation of self. """

0 commit comments

Comments
 (0)