23
23
import tuf .exceptions
24
24
25
25
26
+
26
27
# Types
27
28
JsonDict = Dict [str , Any ]
28
29
@@ -55,6 +56,49 @@ def __init__(self, signed: 'Signed', signatures: list) -> None:
55
56
self .signatures = signatures
56
57
57
58
59
+ # Deserialization (factories).
60
+ @classmethod
61
+ def from_dict (cls , metadata : JsonDict ) -> 'Metadata' :
62
+ """Creates Metadata object from its JSON/dict representation.
63
+
64
+ Calls 'from_dict' for any complex metadata attribute represented by a
65
+ class also that has a 'from_dict' factory method. (Currently this is
66
+ only the signed attribute.)
67
+
68
+ Arguments:
69
+ metadata: TUF metadata in JSON/dict representation, as e.g.
70
+ returned by 'json.loads'.
71
+
72
+ Raises:
73
+ KeyError: The metadata dict format is invalid.
74
+ ValueError: The metadata has an unrecognized signed._type field.
75
+
76
+ Returns:
77
+ A TUF Metadata object.
78
+
79
+ """
80
+ # Dispatch to contained metadata class on metadata _type field.
81
+ _type = metadata ['signed' ]['_type' ]
82
+
83
+ if _type == 'targets' :
84
+ inner_cls = Targets
85
+ elif _type == 'snapshot' :
86
+ inner_cls = Snapshot
87
+ elif _type == 'timestamp' :
88
+ inner_cls = Timestamp
89
+ elif _type == 'root' :
90
+ inner_cls = Root
91
+ else :
92
+ raise ValueError (f'unrecognized metadata type "{ _type } "' )
93
+
94
+ # NOTE: If Signature becomes a class, we should iterate over
95
+ # metadata['signatures'], call Signature.from_dict for each item, and
96
+ # pass a list of Signature objects to the Metadata constructor intead.
97
+ return cls (
98
+ signed = inner_cls .from_dict (metadata ['signed' ]),
99
+ signatures = metadata ['signatures' ])
100
+
101
+
58
102
@classmethod
59
103
def from_file (
60
104
cls , filename : str , deserializer : MetadataDeserializer = None ,
@@ -95,6 +139,14 @@ def from_file(
95
139
return deserializer .deserialize (raw_data )
96
140
97
141
142
+ # Serialization.
143
+ def to_dict (self ) -> JsonDict :
144
+ """Returns the JSON-serializable dictionary representation of self. """
145
+ return {
146
+ 'signatures' : self .signatures ,
147
+ 'signed' : self .signed .to_dict ()
148
+ }
149
+
98
150
def to_file (self , filename : str , serializer : MetadataSerializer = None ,
99
151
storage_backend : StorageBackendInterface = None ) -> None :
100
152
"""Writes TUF metadata to file storage.
@@ -250,6 +302,39 @@ def __init__(
250
302
self .version = version
251
303
252
304
305
+ # Deserialization (factories).
306
+ @classmethod
307
+ def from_dict (cls , signed_dict : JsonDict ) -> 'Signed' :
308
+ """Creates Signed object from its JSON/dict representation. """
309
+
310
+ # Convert 'expires' TUF metadata string to a datetime object, which is
311
+ # what the constructor expects and what we store. The inverse operation
312
+ # is implemented in 'to_dict'.
313
+ signed_dict ['expires' ] = tuf .formats .expiry_string_to_datetime (
314
+ signed_dict ['expires' ])
315
+ # NOTE: We write the converted 'expires' back into 'signed_dict' above
316
+ # so that we can pass it to the constructor as '**signed_dict' below,
317
+ # along with other fields that belong to Signed subclasses.
318
+ # Any 'from_dict'(-like) conversions of fields that correspond to a
319
+ # subclass should be performed in the 'from_dict' method of that
320
+ # subclass and also be written back into 'signed_dict' before calling
321
+ # super().from_dict.
322
+
323
+ # NOTE: cls might be a subclass of Signed, if 'from_dict' was called on
324
+ # that subclass (see e.g. Metadata.from_dict).
325
+ return cls (** signed_dict )
326
+
327
+
328
+ def to_dict (self ) -> JsonDict :
329
+ """Returns the JSON-serializable dictionary representation of self. """
330
+ return {
331
+ '_type' : self ._type ,
332
+ 'version' : self .version ,
333
+ 'spec_version' : self .spec_version ,
334
+ 'expires' : self .expires .isoformat () + 'Z'
335
+ }
336
+
337
+
253
338
# Modification.
254
339
def bump_expiration (self , delta : timedelta = timedelta (days = 1 )) -> None :
255
340
"""Increments the expires attribute by the passed timedelta. """
@@ -261,7 +346,6 @@ def bump_version(self) -> None:
261
346
self .version += 1
262
347
263
348
264
-
265
349
class Root (Signed ):
266
350
"""A container for the signed part of root metadata.
267
351
@@ -311,6 +395,18 @@ def __init__(
311
395
self .roles = roles
312
396
313
397
398
+ # Serialization.
399
+ def to_dict (self ) -> JsonDict :
400
+ """Returns the JSON-serializable dictionary representation of self. """
401
+ json_dict = super ().to_dict ()
402
+ json_dict .update ({
403
+ 'consistent_snapshot' : self .consistent_snapshot ,
404
+ 'keys' : self .keys ,
405
+ 'roles' : self .roles
406
+ })
407
+ return json_dict
408
+
409
+
314
410
# Update key for a role.
315
411
def add_key (self , role : str , keyid : str , key_metadata : JsonDict ) -> None :
316
412
"""Adds new key for 'role' and updates the key store. """
@@ -332,6 +428,7 @@ def remove_key(self, role: str, keyid: str) -> None:
332
428
333
429
334
430
431
+
335
432
class Timestamp (Signed ):
336
433
"""A container for the signed part of timestamp metadata.
337
434
@@ -359,6 +456,16 @@ def __init__(
359
456
self .meta = meta
360
457
361
458
459
+ # Serialization.
460
+ def to_dict (self ) -> JsonDict :
461
+ """Returns the JSON-serializable dictionary representation of self. """
462
+ json_dict = super ().to_dict ()
463
+ json_dict .update ({
464
+ 'meta' : self .meta
465
+ })
466
+ return json_dict
467
+
468
+
362
469
# Modification.
363
470
def update (self , version : int , length : int , hashes : JsonDict ) -> None :
364
471
"""Assigns passed info about snapshot metadata to meta dict. """
@@ -369,7 +476,6 @@ def update(self, version: int, length: int, hashes: JsonDict) -> None:
369
476
}
370
477
371
478
372
-
373
479
class Snapshot (Signed ):
374
480
"""A container for the signed part of snapshot metadata.
375
481
@@ -403,6 +509,15 @@ def __init__(
403
509
# TODO: Add class for meta
404
510
self .meta = meta
405
511
512
+ # Serialization.
513
+ def to_dict (self ) -> JsonDict :
514
+ """Returns the JSON-serializable dictionary representation of self. """
515
+ json_dict = super ().to_dict ()
516
+ json_dict .update ({
517
+ 'meta' : self .meta
518
+ })
519
+ return json_dict
520
+
406
521
407
522
# Modification.
408
523
def update (
@@ -419,7 +534,6 @@ def update(
419
534
self .meta [metadata_fn ]['hashes' ] = hashes
420
535
421
536
422
-
423
537
class Targets (Signed ):
424
538
"""A container for the signed part of targets metadata.
425
539
@@ -487,6 +601,16 @@ def __init__(
487
601
self .delegations = delegations
488
602
489
603
604
+ # Serialization.
605
+ def to_dict (self ) -> JsonDict :
606
+ """Returns the JSON-serializable dictionary representation of self. """
607
+ json_dict = super ().to_dict ()
608
+ json_dict .update ({
609
+ 'targets' : self .targets ,
610
+ 'delegations' : self .delegations ,
611
+ })
612
+ return json_dict
613
+
490
614
# Modification.
491
615
def update (self , filename : str , fileinfo : JsonDict ) -> None :
492
616
"""Assigns passed target file info to meta dict. """
0 commit comments