23
23
import tuf .exceptions
24
24
from tuf .api .metadata import (
25
25
Metadata ,
26
+ Root ,
26
27
Snapshot ,
27
28
Timestamp ,
28
- Targets
29
+ Targets ,
30
+ Key ,
31
+ Role ,
32
+ Delegations ,
33
+ DelegatedRole ,
29
34
)
30
35
31
36
from tuf .api .serialization import (
@@ -96,6 +101,7 @@ def tearDownClass(cls):
96
101
97
102
def test_generic_read (self ):
98
103
for metadata , inner_metadata_cls in [
104
+ ('root' , Root ),
99
105
('snapshot' , Snapshot ),
100
106
('timestamp' , Timestamp ),
101
107
('targets' , Targets )]:
@@ -142,7 +148,7 @@ def test_compact_json(self):
142
148
143
149
144
150
def test_read_write_read_compare (self ):
145
- for metadata in ['snapshot' , 'timestamp' , 'targets' ]:
151
+ for metadata in ['root' , ' snapshot' , 'timestamp' , 'targets' ]:
146
152
path = os .path .join (self .repo_dir , 'metadata' , metadata + '.json' )
147
153
metadata_obj = Metadata .from_file (path )
148
154
@@ -256,6 +262,18 @@ def test_metadata_snapshot(self):
256
262
snapshot .signed .update ('role1' , 2 , 123 , hashes )
257
263
self .assertEqual (snapshot .signed .meta , fileinfo )
258
264
265
+ # Update only version. Length and hashes are optional.
266
+ snapshot .signed .update ('role2' , 3 )
267
+ fileinfo ['role2.json' ] = {'version' : 3 }
268
+ self .assertEqual (snapshot .signed .meta , fileinfo )
269
+
270
+ # Test from_dict and to_dict without hashes and length.
271
+ snapshot_dict = snapshot .to_dict ()
272
+ test_dict = snapshot_dict ['signed' ].copy ()
273
+ del test_dict ['meta' ]['role1.json' ]['length' ]
274
+ del test_dict ['meta' ]['role1.json' ]['hashes' ]
275
+ snapshot = Snapshot .from_dict (test_dict )
276
+ self .assertEqual (snapshot_dict ['signed' ], snapshot .to_dict ())
259
277
260
278
def test_metadata_timestamp (self ):
261
279
timestamp_path = os .path .join (
@@ -291,6 +309,81 @@ def test_metadata_timestamp(self):
291
309
timestamp .signed .update (2 , 520 , hashes )
292
310
self .assertEqual (timestamp .signed .meta ['snapshot.json' ], fileinfo )
293
311
312
+ # Test from_dict and to_dict without hashes and length.
313
+ timestamp_dict = timestamp .to_dict ()
314
+ test_dict = timestamp_dict ['signed' ].copy ()
315
+ del test_dict ['meta' ]['snapshot.json' ]['length' ]
316
+ del test_dict ['meta' ]['snapshot.json' ]['hashes' ]
317
+ timestamp_test = Timestamp .from_dict (test_dict )
318
+ self .assertEqual (timestamp_dict ['signed' ], timestamp_test .to_dict ())
319
+
320
+ # Update only version. Length and hashes are optional.
321
+ timestamp .signed .update (3 )
322
+ fileinfo = {'version' : 3 }
323
+ self .assertEqual (timestamp .signed .meta ['snapshot.json' ], fileinfo )
324
+
325
+ def test_key_class (self ):
326
+ keys = {
327
+ "59a4df8af818e9ed7abe0764c0b47b4240952aa0d179b5b78346c470ac30278d" :{
328
+ "keytype" : "ed25519" ,
329
+ "keyval" : {
330
+ "public" : "edcd0a32a07dce33f7c7873aaffbff36d20ea30787574ead335eefd337e4dacd"
331
+ },
332
+ "scheme" : "ed25519"
333
+ },
334
+ }
335
+ for key_dict in keys .values ():
336
+ # Testing that the workflow of deserializing and serializing
337
+ # a key dictionary doesn't change the content.
338
+ test_key_dict = key_dict .copy ()
339
+ key_obj = Key .from_dict (test_key_dict )
340
+ self .assertEqual (key_dict , key_obj .to_dict ())
341
+ # Test creating an instance without a required attribute.
342
+ for key in key_dict .keys ():
343
+ test_key_dict = key_dict .copy ()
344
+ del test_key_dict [key ]
345
+ with self .assertRaises (KeyError ):
346
+ Key .from_dict (test_key_dict )
347
+ # Test creating a Key instance with wrong keyval format.
348
+ key_dict ["keyval" ] = {}
349
+ with self .assertRaises (ValueError ):
350
+ Key .from_dict (key_dict )
351
+
352
+
353
+ def test_role_class (self ):
354
+ roles = {
355
+ "root" : {
356
+ "keyids" : [
357
+ "4e777de0d275f9d28588dd9a1606cc748e548f9e22b6795b7cb3f63f98035fcb"
358
+ ],
359
+ "threshold" : 1
360
+ },
361
+ "snapshot" : {
362
+ "keyids" : [
363
+ "59a4df8af818e9ed7abe0764c0b47b4240952aa0d179b5b78346c470ac30278d"
364
+ ],
365
+ "threshold" : 1
366
+ },
367
+ }
368
+ for role_dict in roles .values ():
369
+ # Testing that the workflow of deserializing and serializing
370
+ # a role dictionary doesn't change the content.
371
+ test_role_dict = role_dict .copy ()
372
+ role_obj = Role .from_dict (test_role_dict )
373
+ self .assertEqual (role_dict , role_obj .to_dict ())
374
+ # Test creating an instance without a required attribute.
375
+ for role_attr in role_dict .keys ():
376
+ test_role_dict = role_dict .copy ()
377
+ del test_role_dict [role_attr ]
378
+ with self .assertRaises (KeyError ):
379
+ Key .from_dict (test_role_dict )
380
+ # Test creating a Role instance with keyid dublicates.
381
+ # for keyid in role_dict["keyids"]:
382
+ role_dict ["keyids" ].append (role_dict ["keyids" ][0 ])
383
+ test_role_dict = role_dict .copy ()
384
+ with self .assertRaises (ValueError ):
385
+ Role .from_dict (test_role_dict )
386
+
294
387
295
388
def test_metadata_root (self ):
296
389
root_path = os .path .join (
@@ -306,23 +399,100 @@ def test_metadata_root(self):
306
399
root_key2 ['keytype' ], root_key2 ['scheme' ], root_key2 ['keyval' ])
307
400
308
401
# Assert that root does not contain the new key
309
- self .assertNotIn (keyid , root .signed .roles ['root' ][ ' keyids' ] )
402
+ self .assertNotIn (keyid , root .signed .roles ['root' ]. keyids )
310
403
self .assertNotIn (keyid , root .signed .keys )
311
404
312
405
# Add new root key
313
406
root .signed .add_key ('root' , keyid , key_metadata )
314
407
315
408
# Assert that key is added
316
- self .assertIn (keyid , root .signed .roles ['root' ][ ' keyids' ] )
409
+ self .assertIn (keyid , root .signed .roles ['root' ]. keyids )
317
410
self .assertIn (keyid , root .signed .keys )
318
411
412
+ # Try adding the same key again and assert its ignored.
413
+ pre_add_keyid = root .signed .roles ['root' ].keyids .copy ()
414
+ root .signed .add_key ('root' , keyid , key_metadata )
415
+ self .assertEqual (pre_add_keyid , root .signed .roles ['root' ].keyids )
416
+
319
417
# Remove the key
320
418
root .signed .remove_key ('root' , keyid )
321
419
322
420
# Assert that root does not contain the new key anymore
323
- self .assertNotIn (keyid , root .signed .roles ['root' ][ ' keyids' ] )
421
+ self .assertNotIn (keyid , root .signed .roles ['root' ]. keyids )
324
422
self .assertNotIn (keyid , root .signed .keys )
325
423
424
+ with self .assertRaises (KeyError ):
425
+ root .signed .remove_key ('root' , 'nosuchkey' )
426
+
427
+ def test_delegated_role_class (self ):
428
+ roles = [
429
+ {
430
+ "keyids" : [
431
+ "c8022fa1e9b9cb239a6b362bbdffa9649e61ad2cb699d2e4bc4fdf7930a0e64a"
432
+ ],
433
+ "name" : "role1" ,
434
+ "paths" : [
435
+ "file3.txt"
436
+ ],
437
+ "terminating" : False ,
438
+ "threshold" : 1
439
+ }
440
+ ]
441
+ for role in roles :
442
+ # Testing that the workflow of deserializing and serializing
443
+ # a delegation role dictionary doesn't change the content.
444
+ key_obj = DelegatedRole .from_dict (role .copy ())
445
+ self .assertEqual (role , key_obj .to_dict ())
446
+
447
+ # Test creating a DelegatedRole object with both "paths" and
448
+ # "path_hash_prefixes" set.
449
+ role ["path_hash_prefixes" ] = "foo"
450
+ with self .assertRaises (ValueError ):
451
+ DelegatedRole .from_dict (role .copy ())
452
+
453
+ # Test creating DelegatedRole only with "path_hash_prefixes"
454
+ del role ["paths" ]
455
+ DelegatedRole .from_dict (role .copy ())
456
+ role ["paths" ] = "foo"
457
+
458
+ # Test creating DelegatedRole only with "paths"
459
+ del role ["path_hash_prefixes" ]
460
+ DelegatedRole .from_dict (role .copy ())
461
+ role ["path_hash_prefixes" ] = "foo"
462
+
463
+ # Test creating DelegatedRole without "paths" and
464
+ # "path_hash_prefixes" set
465
+ del role ["paths" ]
466
+ del role ["path_hash_prefixes" ]
467
+ DelegatedRole .from_dict (role )
468
+
469
+
470
+ def test_delegation_class (self ):
471
+ roles = [
472
+ {
473
+ "keyids" : [
474
+ "c8022fa1e9b9cb239a6b362bbdffa9649e61ad2cb699d2e4bc4fdf7930a0e64a"
475
+ ],
476
+ "name" : "role1" ,
477
+ "paths" : [
478
+ "file3.txt"
479
+ ],
480
+ "terminating" : False ,
481
+ "threshold" : 1
482
+ }
483
+ ]
484
+ keys = {
485
+ "59a4df8af818e9ed7abe0764c0b47b4240952aa0d179b5b78346c470ac30278d" :{
486
+ "keytype" : "ed25519" ,
487
+ "keyval" : {
488
+ "public" : "edcd0a32a07dce33f7c7873aaffbff36d20ea30787574ead335eefd337e4dacd"
489
+ },
490
+ "scheme" : "ed25519"
491
+ },
492
+ }
493
+ delegations_dict = {"keys" : keys , "roles" : roles }
494
+ delegations = Delegations .from_dict (copy .deepcopy (delegations_dict ))
495
+ self .assertEqual (delegations_dict , delegations .to_dict ())
326
496
327
497
328
498
def test_metadata_targets (self ):
@@ -349,6 +519,13 @@ def test_metadata_targets(self):
349
519
# Verify that data is updated
350
520
self .assertEqual (targets .signed .targets [filename ], fileinfo )
351
521
522
+ # Test from_dict/to_dict Targets without delegations
523
+ targets_dict = targets .to_dict ()
524
+ del targets_dict ["signed" ]["delegations" ]
525
+ tmp_dict = targets_dict ["signed" ].copy ()
526
+ targets_obj = Targets .from_dict (tmp_dict )
527
+ self .assertEqual (targets_dict ["signed" ], targets_obj .to_dict ())
528
+
352
529
def setup_dict_with_unrecognized_field (self , file_path , field , value ):
353
530
json_dict = {}
354
531
with open (file_path ) as f :
@@ -365,6 +542,22 @@ def test_support_for_unrecognized_fields(self):
365
542
# Test that the metadata classes store unrecognized fields when
366
543
# initializing and passes them when casting the instance to a dict.
367
544
545
+ # Add unrecognized fields to all metadata sub (helper) classes.
546
+ if metadata == "root" :
547
+ for keyid in dict1 ["signed" ]["keys" ].keys ():
548
+ dict1 ["signed" ]["keys" ][keyid ]["d" ] = "c"
549
+ for role_str in dict1 ["signed" ]["roles" ].keys ():
550
+ dict1 ["signed" ]["roles" ][role_str ]["e" ] = "g"
551
+ elif metadata == "targets" and dict1 ["signed" ].get ("delegations" ):
552
+ for keyid in dict1 ["signed" ]["delegations" ]["keys" ].keys ():
553
+ dict1 ["signed" ]["delegations" ]["keys" ][keyid ]["d" ] = "c"
554
+ new_roles = []
555
+ for role in dict1 ["signed" ]["delegations" ]["roles" ]:
556
+ role ["e" ] = "g"
557
+ new_roles .append (role )
558
+ dict1 ["signed" ]["delegations" ]["roles" ] = new_roles
559
+ dict1 ["signed" ]["delegations" ]["foo" ] = "bar"
560
+
368
561
temp_copy = copy .deepcopy (dict1 )
369
562
metadata_obj = Metadata .from_dict (temp_copy )
370
563
0 commit comments