Skip to content

Commit 520bc20

Browse files
committed
Re-order metadata methods logically and add vspace
Signed-off-by: Lukas Puehringer <[email protected]>
1 parent a7a33ce commit 520bc20

File tree

1 file changed

+132
-105
lines changed

1 file changed

+132
-105
lines changed

tuf/api/metadata.py

Lines changed: 132 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,12 @@
1818
1919
* Add Root metadata class
2020
21+
* Add classes for other complex metadata attributes, see 'signatures' (in
22+
Metadata) 'meta'/'targets' (in Timestamp, Snapshot, Targets), 'delegations'
23+
(in Targets), 'keys'/'roles' (in not yet existent 'Delegation'), ...
24+
2125
"""
2226
# Imports
23-
2427
from datetime import datetime, timedelta
2528
from typing import Any, Dict, Optional
2629

@@ -45,12 +48,10 @@
4548

4649

4750
# Types
48-
4951
JsonDict = Dict[str, Any]
5052

5153

5254
# Classes.
53-
5455
class Metadata():
5556
"""A container for signed TUF metadata.
5657
@@ -77,12 +78,50 @@ def __init__(self, signed: 'Signed', signatures: list) -> None:
7778
self.signed = signed
7879
self.signatures = signatures
7980

80-
def to_dict(self) -> JsonDict:
81-
"""Returns the JSON-serializable dictionary representation of self. """
82-
return {
83-
'signatures': self.signatures,
84-
'signed': self.signed.to_dict()
85-
}
81+
82+
# Deserialization (factories).
83+
@classmethod
84+
def from_dict(cls, metadata: JsonDict) -> 'Metadata':
85+
"""Creates Metadata object from its JSON/dict representation.
86+
87+
Calls 'from_dict' for any complex metadata attribute represented by a
88+
class also that has a 'from_dict' factory method. (Currently this is
89+
only the signed attribute.)
90+
91+
Arguments:
92+
metadata: TUF metadata in JSON/dict representation, as e.g.
93+
returned by 'json.loads'.
94+
95+
Raises:
96+
KeyError: The metadata dict format is invalid.
97+
ValueError: The metadata has an unrecognized signed._type field.
98+
99+
Returns:
100+
A TUF Metadata object.
101+
102+
"""
103+
# Dispatch to contained metadata class on metadata _type field.
104+
_type = metadata['signed']['_type']
105+
106+
if _type == 'targets':
107+
inner_cls = Targets
108+
elif _type == 'snapshot':
109+
inner_cls = Snapshot
110+
elif _type == 'timestamp':
111+
inner_cls = Timestamp
112+
elif _type == 'root':
113+
# TODO: implement Root class
114+
raise NotImplementedError('Root not yet implemented')
115+
else:
116+
raise ValueError(f'unrecognized metadata type "{_type}"')
117+
118+
# NOTE: If Signature becomes a class, we should iterate over
119+
# metadata['signatures'], call Signature.from_dict for each item, and
120+
# pass a list of Signature objects to the Metadata constructor intead.
121+
return cls(
122+
signed=inner_cls.from_dict(metadata['signed']),
123+
signatures=metadata['signatures'])
124+
86125

87126
@classmethod
88127
def from_json(cls, metadata_json: str) -> 'Metadata':
@@ -102,6 +141,40 @@ def from_json(cls, metadata_json: str) -> 'Metadata':
102141
return cls.from_dict(load_json_string(metadata_json))
103142

104143

144+
@classmethod
145+
def from_json_file(
146+
cls, filename: str,
147+
storage_backend: Optional[StorageBackendInterface] = None
148+
) -> 'Metadata':
149+
"""Loads JSON-formatted TUF metadata from file storage.
150+
151+
Arguments:
152+
filename: The path to read the file from.
153+
storage_backend: An object that implements
154+
securesystemslib.storage.StorageBackendInterface. Per default
155+
a (local) FilesystemBackend is used.
156+
157+
Raises:
158+
securesystemslib.exceptions.StorageError: The file cannot be read.
159+
securesystemslib.exceptions.Error, ValueError, KeyError: The
160+
metadata cannot be parsed.
161+
162+
Returns:
163+
A TUF Metadata object.
164+
165+
"""
166+
return cls.from_dict(load_json_file(filename, storage_backend))
167+
168+
169+
# Serialization.
170+
def to_dict(self) -> JsonDict:
171+
"""Returns the JSON-serializable dictionary representation of self. """
172+
return {
173+
'signatures': self.signatures,
174+
'signed': self.signed.to_dict()
175+
}
176+
177+
105178
def to_json(self, compact: bool = False) -> None:
106179
"""Returns the optionally compacted JSON representation of self. """
107180
return json.dumps(
@@ -110,6 +183,30 @@ def to_json(self, compact: bool = False) -> None:
110183
separators=((',', ':') if compact else (',', ': ')),
111184
sort_keys=True)
112185

186+
187+
def to_json_file(
188+
self, filename: str, compact: bool = False,
189+
storage_backend: StorageBackendInterface = None) -> None:
190+
"""Writes the JSON representation of self to file storage.
191+
192+
Arguments:
193+
filename: The path to write the file to.
194+
compact: A boolean indicating if the JSON string should be compact
195+
by excluding whitespace.
196+
storage_backend: An object that implements
197+
securesystemslib.storage.StorageBackendInterface. Per default
198+
a (local) FilesystemBackend is used.
199+
Raises:
200+
securesystemslib.exceptions.StorageError:
201+
The file cannot be written.
202+
203+
"""
204+
with tempfile.TemporaryFile() as f:
205+
f.write(self.to_json(compact).encode('utf-8'))
206+
persist_temp_file(f, filename, storage_backend)
207+
208+
209+
# Signatures.
113210
def sign(self, key: JsonDict, append: bool = False) -> JsonDict:
114211
"""Creates signature over 'signed' and assigns it to 'signatures'.
115212
@@ -137,6 +234,7 @@ def sign(self, key: JsonDict, append: bool = False) -> JsonDict:
137234

138235
return signature
139236

237+
140238
def verify(self, key: JsonDict) -> bool:
141239
"""Verifies 'signatures' over 'signed' that match the passed key by id.
142240
@@ -172,93 +270,6 @@ def verify(self, key: JsonDict) -> bool:
172270

173271
return True
174272

175-
@classmethod
176-
def from_json_file(
177-
cls, filename: str,
178-
storage_backend: Optional[StorageBackendInterface] = None
179-
) -> 'Metadata':
180-
"""Loads JSON-formatted TUF metadata from file storage.
181-
182-
Arguments:
183-
filename: The path to read the file from.
184-
storage_backend: An object that implements
185-
securesystemslib.storage.StorageBackendInterface. Per default
186-
a (local) FilesystemBackend is used.
187-
188-
Raises:
189-
securesystemslib.exceptions.StorageError: The file cannot be read.
190-
securesystemslib.exceptions.Error, ValueError, KeyError: The
191-
metadata cannot be parsed.
192-
193-
Returns:
194-
A TUF Metadata object.
195-
196-
"""
197-
return cls.from_dict(load_json_file(filename, storage_backend))
198-
199-
@classmethod
200-
def from_dict(cls, metadata: JsonDict) -> 'Metadata':
201-
"""Creates Metadata object from its JSON/dict representation.
202-
203-
Calls 'from_dict' for any complex metadata attribute represented by a
204-
class also that has a 'from_dict' factory method. (Currently this is
205-
only the signed attribute.)
206-
207-
Arguments:
208-
metadata: TUF metadata in JSON/dict representation, as e.g.
209-
returned by 'json.loads'.
210-
211-
Raises:
212-
KeyError: The metadata dict format is invalid.
213-
ValueError: The metadata has an unrecognized signed._type field.
214-
215-
Returns:
216-
A TUF Metadata object.
217-
218-
"""
219-
# Dispatch to contained metadata class on metadata _type field.
220-
_type = metadata['signed']['_type']
221-
222-
if _type == 'targets':
223-
inner_cls = Targets
224-
elif _type == 'snapshot':
225-
inner_cls = Snapshot
226-
elif _type == 'timestamp':
227-
inner_cls = Timestamp
228-
elif _type == 'root':
229-
# TODO: implement Root class
230-
raise NotImplementedError('Root not yet implemented')
231-
else:
232-
raise ValueError(f'unrecognized metadata type "{_type}"')
233-
234-
# NOTE: If Signature becomes a class, we should iterate over
235-
# metadata['signatures'], call Signature.from_dict for each item, and
236-
# pass a list of Signature objects to the Metadata constructor intead.
237-
return cls(
238-
signed=inner_cls.from_dict(metadata['signed']),
239-
signatures=metadata['signatures'])
240-
241-
242-
def to_json_file(
243-
self, filename: str, compact: bool = False,
244-
storage_backend: StorageBackendInterface = None) -> None:
245-
"""Writes the JSON representation of self to file storage.
246-
247-
Arguments:
248-
filename: The path to write the file to.
249-
compact: A boolean indicating if the JSON string should be compact
250-
by excluding whitespace.
251-
storage_backend: An object that implements
252-
securesystemslib.storage.StorageBackendInterface. Per default
253-
a (local) FilesystemBackend is used.
254-
Raises:
255-
securesystemslib.exceptions.StorageError:
256-
The file cannot be written.
257-
258-
"""
259-
with tempfile.TemporaryFile() as f:
260-
f.write(self.to_json(compact).encode('utf-8'))
261-
persist_temp_file(f, filename, storage_backend)
262273

263274
class Signed:
264275
"""A base class for the signed part of TUF metadata.
@@ -293,6 +304,8 @@ def __init__(
293304
raise ValueError(f'version must be < 0, got {version}')
294305
self.version = version
295306

307+
308+
# Deserialization (factories).
296309
@classmethod
297310
def from_dict(cls, signed_dict) -> 'Signed':
298311
"""Creates Signed object from its JSON/dict representation. """
@@ -314,17 +327,12 @@ def from_dict(cls, signed_dict) -> 'Signed':
314327
# that subclass (see e.g. Metadata.from_dict).
315328
return cls(**signed_dict)
316329

330+
331+
# Serialization.
317332
def to_canonical_bytes(self) -> bytes:
318333
"""Returns the UTF-8 encoded canonical JSON representation of self. """
319334
return encode_canonical(self.to_dict()).encode('UTF-8')
320335

321-
def bump_expiration(self, delta: timedelta = timedelta(days=1)) -> None:
322-
"""Increments the expires attribute by the passed timedelta. """
323-
self.expires += delta
324-
325-
def bump_version(self) -> None:
326-
"""Increments the metadata version number by 1."""
327-
self.version += 1
328336

329337
def to_dict(self) -> JsonDict:
330338
"""Returns the JSON-serializable dictionary representation of self. """
@@ -335,6 +343,18 @@ def to_dict(self) -> JsonDict:
335343
'expires': self.expires.isoformat() + 'Z'
336344
}
337345

346+
347+
# Modification.
348+
def bump_expiration(self, delta: timedelta = timedelta(days=1)) -> None:
349+
"""Increments the expires attribute by the passed timedelta. """
350+
self.expires += delta
351+
352+
353+
def bump_version(self) -> None:
354+
"""Increments the metadata version number by 1."""
355+
self.version += 1
356+
357+
338358
class Timestamp(Signed):
339359
"""A container for the signed part of timestamp metadata.
340360
@@ -361,6 +381,8 @@ def __init__(
361381
# TODO: Add class for meta
362382
self.meta = meta
363383

384+
385+
# Serialization.
364386
def to_dict(self) -> JsonDict:
365387
"""Returns the JSON-serializable dictionary representation of self. """
366388
json_dict = super().to_dict()
@@ -369,6 +391,8 @@ def to_dict(self) -> JsonDict:
369391
})
370392
return json_dict
371393

394+
395+
# Modification.
372396
def update(self, version: int, length: int, hashes: JsonDict) -> None:
373397
"""Assigns passed info about snapshot metadata to meta dict. """
374398
self.meta['snapshot.json'] = {
@@ -411,6 +435,7 @@ def __init__(
411435
# TODO: Add class for meta
412436
self.meta = meta
413437

438+
# Serialization.
414439
def to_dict(self) -> JsonDict:
415440
"""Returns the JSON-serializable dictionary representation of self. """
416441
json_dict = super().to_dict()
@@ -419,7 +444,8 @@ def to_dict(self) -> JsonDict:
419444
})
420445
return json_dict
421446

422-
# Add or update metadata about the targets metadata.
447+
448+
# Modification.
423449
def update(
424450
self, rolename: str, version: int, length: Optional[int] = None,
425451
hashes: Optional[JsonDict] = None) -> None:
@@ -497,6 +523,7 @@ def __init__(
497523
self.delegations = delegations
498524

499525

526+
# Serialization.
500527
def to_dict(self) -> JsonDict:
501528
"""Returns the JSON-serializable dictionary representation of self. """
502529
json_dict = super().to_dict()
@@ -506,7 +533,7 @@ def to_dict(self) -> JsonDict:
506533
})
507534
return json_dict
508535

509-
# Add or update metadata about the target.
536+
# Modification.
510537
def update(self, filename: str, fileinfo: JsonDict) -> None:
511538
"""Assigns passed target file info to meta dict. """
512539
self.targets[filename] = fileinfo

0 commit comments

Comments
 (0)