Skip to content

Commit

Permalink
Add generic Metadata.read_from_json class method
Browse files Browse the repository at this point in the history
Add generic read from json class method that returns a Metadata
object with a signed field that contains the appropriate Signed
subclass, based on the signed._type field of the read metadata.

Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
  • Loading branch information
lukpueh committed Aug 19, 2020
1 parent 14d455f commit be19a11
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 0 deletions.
34 changes: 34 additions & 0 deletions tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"""

import json
import sys
import logging
import os
Expand All @@ -28,8 +29,10 @@ def setUpModule():
# Since setUpModule is called after imports we need to import conditionally.
if IS_PY_VERSION_SUPPORTED:
from tuf.api.metadata import (
Metadata,
Snapshot,
Timestamp,
Targets
)


Expand Down Expand Up @@ -91,6 +94,37 @@ def tearDownClass(cls):
# threshold = Threshold(1, 5)
# return KeyRing(threshold=threshold, keys=key_list)

def test_generic_read(self):
for metadata, inner_metadata_cls in [
("snapshot", Snapshot),
("timestamp", Timestamp),
("targets", Targets)]:

path = os.path.join(self.repo_dir, 'metadata', metadata + '.json')
metadata_obj = Metadata.read_from_json(path)

# Assert that generic method ...
# ... instantiates the right inner class for each metadata type
self.assertTrue(
isinstance(metadata_obj.signed, inner_metadata_cls))
# ... and reads the same metadata file as the corresponding method
# on the inner class would do (compare their dict representation)
self.assertDictEqual(
metadata_obj.as_dict(),
inner_metadata_cls.read_from_json(path).as_dict())

# Assert that it chokes correctly on an unknown metadata type
bad_metadata_path = "bad-metadata.json"
bad_metadata = {"signed": {"_type": "bad-metadata"}}
with open(bad_metadata_path, "wb") as f:
f.write(json.dumps(bad_metadata).encode('utf-8'))

with self.assertRaises(ValueError):
Metadata.read_from_json(bad_metadata_path)

os.remove(bad_metadata_path)


def test_metadata_base(self):
# Use of Snapshot is arbitrary, we're just testing the base class features
# with real data
Expand Down
44 changes: 44 additions & 0 deletions tuf/api/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,50 @@ def __update_signature(self, signatures, keyid, signature):
# break

# return len(verified_keyids) >= key_ring.threshold.least
@classmethod
def read_from_json(
cls, filename: str,
storage_backend: Optional[StorageBackendInterface] = None
) -> 'Metadata':
"""Loads JSON-formatted TUF metadata from a file storage.
Arguments:
filename: The path to read the file from.
storage_backend: An object that implements
securesystemslib.storage.StorageBackendInterface. Per default
a (local) FilesystemBackend is used.
Raises:
securesystemslib.exceptions.StorageError: The file cannot be read.
securesystemslib.exceptions.Error, ValueError: The metadata cannot
be parsed.
Returns:
A TUF Metadata object.
"""
signable = load_json_file(filename, storage_backend)

# TODO: Should we use constants?
# And/or maybe a dispatch table? (<-- maybe too much magic)
_type = signable['signed']['_type']

if _type == 'targets':
inner_cls = Targets
elif _type == 'snapshot':
inner_cls = Snapshot
elif _type == 'timestamp':
inner_cls = Timestamp
elif _type == 'root':
# TODO: implement Root class
raise NotImplementedError('Root not yet implemented')
else:
raise ValueError(f'unrecognized metadata type "{_type}"')

return Metadata(
signed=inner_cls(**signable['signed']),
signatures=signable['signatures'])


def write_to_json(
self, filename: str,
Expand Down

0 comments on commit be19a11

Please sign in to comment.