Skip to content

Commit be19a11

Browse files
committed
Add generic Metadata.read_from_json class method
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 <[email protected]>
1 parent 14d455f commit be19a11

File tree

2 files changed

+78
-0
lines changed

2 files changed

+78
-0
lines changed

tests/test_api.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
99
"""
1010

11+
import json
1112
import sys
1213
import logging
1314
import os
@@ -28,8 +29,10 @@ def setUpModule():
2829
# Since setUpModule is called after imports we need to import conditionally.
2930
if IS_PY_VERSION_SUPPORTED:
3031
from tuf.api.metadata import (
32+
Metadata,
3133
Snapshot,
3234
Timestamp,
35+
Targets
3336
)
3437

3538

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

97+
def test_generic_read(self):
98+
for metadata, inner_metadata_cls in [
99+
("snapshot", Snapshot),
100+
("timestamp", Timestamp),
101+
("targets", Targets)]:
102+
103+
path = os.path.join(self.repo_dir, 'metadata', metadata + '.json')
104+
metadata_obj = Metadata.read_from_json(path)
105+
106+
# Assert that generic method ...
107+
# ... instantiates the right inner class for each metadata type
108+
self.assertTrue(
109+
isinstance(metadata_obj.signed, inner_metadata_cls))
110+
# ... and reads the same metadata file as the corresponding method
111+
# on the inner class would do (compare their dict representation)
112+
self.assertDictEqual(
113+
metadata_obj.as_dict(),
114+
inner_metadata_cls.read_from_json(path).as_dict())
115+
116+
# Assert that it chokes correctly on an unknown metadata type
117+
bad_metadata_path = "bad-metadata.json"
118+
bad_metadata = {"signed": {"_type": "bad-metadata"}}
119+
with open(bad_metadata_path, "wb") as f:
120+
f.write(json.dumps(bad_metadata).encode('utf-8'))
121+
122+
with self.assertRaises(ValueError):
123+
Metadata.read_from_json(bad_metadata_path)
124+
125+
os.remove(bad_metadata_path)
126+
127+
94128
def test_metadata_base(self):
95129
# Use of Snapshot is arbitrary, we're just testing the base class features
96130
# with real data

tuf/api/metadata.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,50 @@ def __update_signature(self, signatures, keyid, signature):
116116
# break
117117

118118
# return len(verified_keyids) >= key_ring.threshold.least
119+
@classmethod
120+
def read_from_json(
121+
cls, filename: str,
122+
storage_backend: Optional[StorageBackendInterface] = None
123+
) -> 'Metadata':
124+
"""Loads JSON-formatted TUF metadata from a file storage.
125+
126+
Arguments:
127+
filename: The path to read the file from.
128+
storage_backend: An object that implements
129+
securesystemslib.storage.StorageBackendInterface. Per default
130+
a (local) FilesystemBackend is used.
131+
132+
Raises:
133+
securesystemslib.exceptions.StorageError: The file cannot be read.
134+
securesystemslib.exceptions.Error, ValueError: The metadata cannot
135+
be parsed.
136+
137+
Returns:
138+
A TUF Metadata object.
139+
140+
"""
141+
signable = load_json_file(filename, storage_backend)
142+
143+
# TODO: Should we use constants?
144+
# And/or maybe a dispatch table? (<-- maybe too much magic)
145+
_type = signable['signed']['_type']
146+
147+
if _type == 'targets':
148+
inner_cls = Targets
149+
elif _type == 'snapshot':
150+
inner_cls = Snapshot
151+
elif _type == 'timestamp':
152+
inner_cls = Timestamp
153+
elif _type == 'root':
154+
# TODO: implement Root class
155+
raise NotImplementedError('Root not yet implemented')
156+
else:
157+
raise ValueError(f'unrecognized metadata type "{_type}"')
158+
159+
return Metadata(
160+
signed=inner_cls(**signable['signed']),
161+
signatures=signable['signatures'])
162+
119163

120164
def write_to_json(
121165
self, filename: str,

0 commit comments

Comments
 (0)