From 7ecf522698ae38e1eb26ba33de945981c2248665 Mon Sep 17 00:00:00 2001 From: Sebastien Awwad Date: Fri, 29 Mar 2019 15:19:23 -0400 Subject: [PATCH] Automatically fill _type and spec_version in build_dict_c... in tuf.formats.build_dict_conforming_to_schema Populate _type with the expected value for the given schema, and populate spec_version with tuf.SPECIFICATION_VERSION. Do this only when the values are not provided, and support overriding them. Also adds testing for the above and takes advantage of the above in repository_lib's _generate metadata functions. Signed-off-by: Sebastien Awwad --- tests/test_formats.py | 26 +++++++++++++++- tuf/formats.py | 72 ++++++++++++++++++++++++++++++++++++++----- tuf/repository_lib.py | 10 ------ 3 files changed, 89 insertions(+), 19 deletions(-) diff --git a/tests/test_formats.py b/tests/test_formats.py index a36868ee65..42a354c71b 100755 --- a/tests/test_formats.py +++ b/tests/test_formats.py @@ -300,7 +300,10 @@ def test_build_dict_conforming_to_schema(self): expires = '1985-10-21T13:20:00Z' filedict = {'snapshot.json': {'length': length, 'hashes': hashes}} - self.assertTrue(tuf.formats.TIMESTAMP_SCHEMA.matches( + + # Try with and without _type and spec_version, both of which are + # automatically populated if they are not included. + self.assertTrue(tuf.formats.TIMESTAMP_SCHEMA.matches( # both tuf.formats.build_dict_conforming_to_schema( tuf.formats.TIMESTAMP_SCHEMA, _type='timestamp', @@ -308,6 +311,27 @@ def test_build_dict_conforming_to_schema(self): version=version, expires=expires, meta=filedict))) + self.assertTrue(tuf.formats.TIMESTAMP_SCHEMA.matches( # neither + tuf.formats.build_dict_conforming_to_schema( + tuf.formats.TIMESTAMP_SCHEMA, + version=version, + expires=expires, + meta=filedict))) + self.assertTrue(tuf.formats.TIMESTAMP_SCHEMA.matches( # one + tuf.formats.build_dict_conforming_to_schema( + tuf.formats.TIMESTAMP_SCHEMA, + spec_version=spec_version, + version=version, + expires=expires, + meta=filedict))) + self.assertTrue(tuf.formats.TIMESTAMP_SCHEMA.matches( # the other + tuf.formats.build_dict_conforming_to_schema( + tuf.formats.TIMESTAMP_SCHEMA, + _type='timestamp', + version=version, + expires=expires, + meta=filedict))) + # Try test arguments for invalid Timestamp creation. bad_spec_version = 123 diff --git a/tuf/formats.py b/tuf/formats.py index 9890ec0cca..bc6fa0d3f5 100755 --- a/tuf/formats.py +++ b/tuf/formats.py @@ -69,11 +69,11 @@ import time import copy -import tuf - import securesystemslib.formats import securesystemslib.schema as SCHEMA +import tuf + import six @@ -98,6 +98,8 @@ # Role object in {'keyids': [keydids..], 'name': 'ABC', 'threshold': 1, # 'paths':[filepaths..]} format. +# TODO: This is not a role. In further #660-related PRs, fix it, similar to +# the way I did in Uptane's TUF fork. ROLE_SCHEMA = SCHEMA.Object( object_name = 'ROLE_SCHEMA', name = SCHEMA.Optional(securesystemslib.formats.ROLENAME_SCHEMA), @@ -470,14 +472,27 @@ def make_signable(role_schema): def build_dict_conforming_to_schema(schema, **kwargs): """ - Given a schema object (for example, TIMESTAMP_SCHEMA from this module) and - a set of keyword arguments, create a dictionary that conforms to the given - schema, using the keyword arguments to define the elements of the new dict. + + Given a schema object (for example, TIMESTAMP_SCHEMA from this module) and + a set of keyword arguments, create a dictionary that conforms to the given + schema, using the keyword arguments to define the elements of the new dict. + + Checks the result to make sure that it conforms to the given schema, raising + an error if not. - Checks the result to make sure that it conforms to the given schema, raising - an error if not. + + A dictionary conforming to the given schema. Adds certain required fields + if they are missing and can be deduced from the schema. The data returned + is a deep copy. + + + securesystemslib.exceptions.FormatError + if the provided data does not match the schema when assembled. + + + None. In particular, the provided values are not modified, and the + returned dictionary does not include references to them. - Returns the new dict conforming to the schema if there are no problems. """ # Check that schema supports a check_match call. @@ -501,6 +516,47 @@ def build_dict_conforming_to_schema(schema, **kwargs): + + + + + # Automatically provide certain schema properties if they are not already + # provided and are required in objects of class . + # This includes: + # _type: + # spec_version: SPECIFICATION_VERSION_SCHEMA + # + # (Please note that _required is slightly misleading, as it includes both + # required and optional elements. It should probably be called _components.) + # + for schema_element in schema._required: + key = schema_element[0] + element_type = schema_element[1] + + if key in dictionary: + # If the field has been provided, proceed normally. + continue + + elif isinstance(element_type, SCHEMA.Optional): + # If the field has NOT been provided but IS optional, proceed without it. + continue + + else: + # If the field has not been provided and is required, check to see if + # the field is one of the one of the fields we automatically fill. + + # Currently, the list is limited to ['_type', 'spec_version']. + + if key == '_type' and isinstance(element_type, SCHEMA.String): + # A SCHEMA.String stores its expected value in _string, so use that. + dictionary[key] = element_type._string + + elif (key == 'spec_version' and + element_type == SPECIFICATION_VERSION_SCHEMA): + # If not provided, use the specification version in tuf/__init__.py + dictionary[key] = tuf.SPECIFICATION_VERSION + + # If what we produce does not match the provided schema, raise a FormatError. schema.check_match(dictionary) diff --git a/tuf/repository_lib.py b/tuf/repository_lib.py index 2e3346b8d9..10a1444b98 100755 --- a/tuf/repository_lib.py +++ b/tuf/repository_lib.py @@ -1330,8 +1330,6 @@ def generate_root_metadata(version, expiration_date, consistent_snapshot, # There are very few things that really need to be done differently. return tuf.formats.build_dict_conforming_to_schema( tuf.formats.ROOT_SCHEMA, - _type='root', - spec_version=tuf.SPECIFICATION_VERSION, version=version, expires=expiration_date, keys=keydict, @@ -1469,8 +1467,6 @@ def generate_targets_metadata(targets_directory, target_files, version, if delegations is not None: return tuf.formats.build_dict_conforming_to_schema( tuf.formats.TARGETS_SCHEMA, - _type='targets', - spec_version=tuf.SPECIFICATION_VERSION, version=version, expires=expiration_date, targets=filedict, @@ -1478,8 +1474,6 @@ def generate_targets_metadata(targets_directory, target_files, version, else: return tuf.formats.build_dict_conforming_to_schema( tuf.formats.TARGETS_SCHEMA, - _type='targets', - spec_version=tuf.SPECIFICATION_VERSION, version=version, expires=expiration_date, targets=filedict) @@ -1613,8 +1607,6 @@ def generate_snapshot_metadata(metadata_directory, version, expiration_date, # There are very few things that really need to be done differently. return tuf.formats.build_dict_conforming_to_schema( tuf.formats.SNAPSHOT_SCHEMA, - _type='snapshot', - spec_version=tuf.SPECIFICATION_VERSION, version=version, expires=expiration_date, meta=fileinfodict) @@ -1692,8 +1684,6 @@ def generate_timestamp_metadata(snapshot_filename, version, expiration_date, # There are very few things that really need to be done differently. return tuf.formats.build_dict_conforming_to_schema( tuf.formats.TIMESTAMP_SCHEMA, - spec_version=tuf.SPECIFICATION_VERSION, - _type='timestamp', version=version, expires=expiration_date, meta=snapshot_fileinfo)