Merge pull request #1345 from MVrachev/implement-adr-8

New metadata API: add support for ADR 0008
This commit is contained in:
Jussi Kukkonen 2021-04-22 21:54:32 +03:00 committed by GitHub
commit feb340f8da
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 64 additions and 9 deletions

View file

@ -349,6 +349,37 @@ def test_metadata_targets(self):
# Verify that data is updated
self.assertEqual(targets.signed.targets[filename], fileinfo)
def setup_dict_with_unrecognized_field(self, file_path, field, value):
json_dict = {}
with open(file_path) as f:
json_dict = json.loads(f.read())
# We are changing the json dict without changing the signature.
# This could be a problem if we want to do verification on this dict.
json_dict["signed"][field] = value
return json_dict
def test_support_for_unrecognized_fields(self):
for metadata in ["root", "timestamp", "snapshot", "targets"]:
path = os.path.join(self.repo_dir, "metadata", metadata + ".json")
dict1 = self.setup_dict_with_unrecognized_field(path, "f", "b")
# Test that the metadata classes store unrecognized fields when
# initializing and passes them when casting the instance to a dict.
temp_copy = copy.deepcopy(dict1)
metadata_obj = Metadata.from_dict(temp_copy)
self.assertEqual(dict1["signed"], metadata_obj.signed.to_dict())
# Test that two instances of the same class could have different
# unrecognized fields.
dict2 = self.setup_dict_with_unrecognized_field(path, "f2", "b2")
temp_copy2 = copy.deepcopy(dict2)
metadata_obj2 = Metadata.from_dict(temp_copy2)
self.assertNotEqual(
metadata_obj.signed.to_dict(), metadata_obj2.signed.to_dict()
)
# Run unit test.
if __name__ == '__main__':
utils.configure_test_logging(sys.argv)

View file

@ -320,6 +320,7 @@ class Signed:
spec_version: The TUF specification version number (semver) the
metadata format adheres to.
expires: The metadata expiration datetime object.
unrecognized_fields: Dictionary of all unrecognized fields.
"""
@ -327,7 +328,12 @@ class Signed:
# we keep it to match spec terminology (I often refer to this as "payload",
# or "inner metadata")
def __init__(
self, _type: str, version: int, spec_version: str, expires: datetime
self,
_type: str,
version: int,
spec_version: str,
expires: datetime,
unrecognized_fields: Optional[Mapping[str, Any]] = None,
) -> None:
self._type = _type
@ -338,6 +344,7 @@ def __init__(
if version < 0:
raise ValueError(f"version must be >= 0, got {version}")
self.version = version
self.unrecognized_fields = unrecognized_fields or {}
@staticmethod
def _common_fields_from_dict(signed_dict: Mapping[str, Any]) -> list:
@ -369,6 +376,7 @@ def _common_fields_to_dict(self) -> Dict[str, Any]:
"version": self.version,
"spec_version": self.spec_version,
"expires": self.expires.isoformat() + "Z",
**self.unrecognized_fields,
}
def is_expired(self, reference_time: datetime = None) -> bool:
@ -445,8 +453,11 @@ def __init__(
consistent_snapshot: bool,
keys: Mapping[str, Any],
roles: Mapping[str, Any],
unrecognized_fields: Optional[Mapping[str, Any]] = None,
) -> None:
super().__init__(_type, version, spec_version, expires)
super().__init__(
_type, version, spec_version, expires, unrecognized_fields
)
# TODO: Add classes for keys and roles
self.consistent_snapshot = consistent_snapshot
self.keys = keys
@ -459,7 +470,8 @@ def from_dict(cls, root_dict: Mapping[str, Any]) -> "Root":
consistent_snapshot = root_dict.pop("consistent_snapshot")
keys = root_dict.pop("keys")
roles = root_dict.pop("roles")
return cls(*common_args, consistent_snapshot, keys, roles)
# All fields left in the root_dict are unrecognized.
return cls(*common_args, consistent_snapshot, keys, roles, root_dict)
def to_dict(self) -> Dict[str, Any]:
"""Returns the dict representation of self. """
@ -521,8 +533,11 @@ def __init__(
spec_version: str,
expires: datetime,
meta: Mapping[str, Any],
unrecognized_fields: Optional[Mapping[str, Any]] = None,
) -> None:
super().__init__(_type, version, spec_version, expires)
super().__init__(
_type, version, spec_version, expires, unrecognized_fields
)
# TODO: Add class for meta
self.meta = meta
@ -531,7 +546,8 @@ def from_dict(cls, timestamp_dict: Mapping[str, Any]) -> "Timestamp":
"""Creates Timestamp object from its dict representation. """
common_args = cls._common_fields_from_dict(timestamp_dict)
meta = timestamp_dict.pop("meta")
return cls(*common_args, meta)
# All fields left in the timestamp_dict are unrecognized.
return cls(*common_args, meta, timestamp_dict)
def to_dict(self) -> Dict[str, Any]:
"""Returns the dict representation of self. """
@ -585,8 +601,11 @@ def __init__(
spec_version: str,
expires: datetime,
meta: Mapping[str, Any],
unrecognized_fields: Optional[Mapping[str, Any]] = None,
) -> None:
super().__init__(_type, version, spec_version, expires)
super().__init__(
_type, version, spec_version, expires, unrecognized_fields
)
# TODO: Add class for meta
self.meta = meta
@ -595,7 +614,8 @@ def from_dict(cls, snapshot_dict: Mapping[str, Any]) -> "Snapshot":
"""Creates Snapshot object from its dict representation. """
common_args = cls._common_fields_from_dict(snapshot_dict)
meta = snapshot_dict.pop("meta")
return cls(*common_args, meta)
# All fields left in the snapshot_dict are unrecognized.
return cls(*common_args, meta, snapshot_dict)
def to_dict(self) -> Dict[str, Any]:
"""Returns the dict representation of self. """
@ -688,8 +708,11 @@ def __init__(
expires: datetime,
targets: Mapping[str, Any],
delegations: Mapping[str, Any],
unrecognized_fields: Optional[Mapping[str, Any]] = None,
) -> None:
super().__init__(_type, version, spec_version, expires)
super().__init__(
_type, version, spec_version, expires, unrecognized_fields
)
# TODO: Add class for meta
self.targets = targets
self.delegations = delegations
@ -700,7 +723,8 @@ def from_dict(cls, targets_dict: Mapping[str, Any]) -> "Targets":
common_args = cls._common_fields_from_dict(targets_dict)
targets = targets_dict.pop("targets")
delegations = targets_dict.pop("delegations")
return cls(*common_args, targets, delegations)
# All fields left in the targets_dict are unrecognized.
return cls(*common_args, targets, delegations, targets_dict)
def to_dict(self) -> Dict[str, Any]:
"""Returns the dict representation of self. """