39
39
import shutil
40
40
import json
41
41
42
+ from collections import deque
43
+
42
44
import tuf
43
45
import tuf .formats
44
46
import tuf .roledb
@@ -293,7 +295,7 @@ def writeall(self, consistent_snapshot=False, use_existing_fileinfo=False):
293
295
for dirty_rolename in dirty_rolenames :
294
296
295
297
# Ignore top-level roles, they will be generated later in this method.
296
- if dirty_rolename in [ 'root' , 'targets' , 'snapshot' , 'timestamp' ] :
298
+ if dirty_rolename in tuf . roledb . TOP_LEVEL_ROLES :
297
299
continue
298
300
299
301
dirty_filename = os .path .join (self ._metadata_directory ,
@@ -2393,7 +2395,7 @@ def delegate(self, rolename, public_keys, paths, threshold=1,
2393
2395
self ._parent_targets_object .add_delegated_role (rolename ,
2394
2396
new_targets_object )
2395
2397
2396
- # Add 'new_targets_object' to the 'targets' role object (this object).
2398
+ # Add 'new_targets_object' to the delegating role object (this object).
2397
2399
self .add_delegated_role (rolename , new_targets_object )
2398
2400
2399
2401
# Update the 'delegations' field of the current role.
@@ -3088,7 +3090,7 @@ def load_repository(repository_directory, repository_name='default',
3088
3090
repository = Repository (repository_directory , metadata_directory ,
3089
3091
targets_directory , storage_backend , repository_name )
3090
3092
3091
- filenames = repo_lib .get_metadata_filenames (metadata_directory )
3093
+ filenames = repo_lib .get_top_level_metadata_filenames (metadata_directory )
3092
3094
3093
3095
# The Root file is always available without a version number (a consistent
3094
3096
# snapshot) attached to the filename. Store the 'consistent_snapshot' value
@@ -3100,50 +3102,49 @@ def load_repository(repository_directory, repository_name='default',
3100
3102
repository , consistent_snapshot = repo_lib ._load_top_level_metadata (repository ,
3101
3103
filenames , repository_name )
3102
3104
3103
- # Load the delegated targets metadata and generate their fileinfo. The
3104
- # extracted fileinfo is stored in the 'meta' field of the snapshot metadata
3105
- # object.
3106
- targets_objects = {}
3107
- loaded_metadata = []
3108
- targets_objects ['targets' ] = repository .targets
3109
-
3110
- metadata_files = sorted (storage_backend .list_folder (metadata_directory ),
3111
- reverse = True )
3112
- for metadata_role in metadata_files :
3113
-
3114
- metadata_path = os .path .join (metadata_directory , metadata_role )
3115
- metadata_name = \
3116
- metadata_path [len (metadata_directory ):].lstrip (os .path .sep )
3117
-
3118
- # Strip the version number if 'consistent_snapshot' is True,
3119
- # or if 'metadata_role' is Root.
3120
- # Example: '10.django.json' --> 'django.json'
3121
- consistent_snapshot = \
3122
- metadata_role .endswith ('root.json' ) or consistent_snapshot == True
3123
- metadata_name , junk = repo_lib ._strip_version_number (metadata_name ,
3124
- consistent_snapshot )
3125
-
3126
- if metadata_name .endswith (METADATA_EXTENSION ):
3127
- extension_length = len (METADATA_EXTENSION )
3128
- metadata_name = metadata_name [:- extension_length ]
3129
-
3130
- else :
3131
- logger .debug ('Skipping file with unsupported metadata'
3132
- ' extension: ' + repr (metadata_path ))
3105
+ delegated_roles_filenames = repo_lib .get_delegated_roles_metadata_filenames (
3106
+ metadata_directory , consistent_snapshot , storage_backend )
3107
+
3108
+ # Load the delegated targets metadata and their fileinfo.
3109
+ # The delegated targets roles form a tree/graph which is traversed in a
3110
+ # breadth-first-search manner starting from 'targets' in order to correctly
3111
+ # load the delegations hierarchy.
3112
+ parent_targets_object = repository .targets
3113
+
3114
+ # Keep the next delegations to be loaded in a deque structure which
3115
+ # has the properties of a list but is designed to have fast appends
3116
+ # and pops from both ends
3117
+ delegations = deque ()
3118
+ # A set used to keep the already loaded delegations and avoid an infinite
3119
+ # loop in case of cycles in the delegations graph
3120
+ loaded_delegations = set ()
3121
+
3122
+ # Top-level roles are already loaded, fetch targets and get its delegations.
3123
+ # Store the delegations in the form of delegated-delegating role tuples,
3124
+ # starting from the top-level targets:
3125
+ # [('role1', 'targets'), ('role2', 'targets'), ... ]
3126
+ roleinfo = tuf .roledb .get_roleinfo ('targets' , repository_name )
3127
+ for role in roleinfo ['delegations' ]['roles' ]:
3128
+ delegations .append ((role ['name' ], 'targets' ))
3129
+
3130
+ # Traverse the graph by appending the next delegation to the deque and
3131
+ # 'pop'-ing and loading the left-most element.
3132
+ while delegations :
3133
+ rolename , delegating_role = delegations .popleft ()
3134
+ if (rolename , delegating_role ) in loaded_delegations :
3135
+ logger .warning ('Detected cycle in the delegation graph: ' +
3136
+ repr (delegating_role ) + ' -> ' +
3137
+ repr (rolename ) +
3138
+ ' is reached more than once.' )
3133
3139
continue
3134
3140
3135
- # Skip top-level roles, only interested in delegated roles now that the
3136
- # top-level roles have already been loaded.
3137
- if metadata_name in ['root' , 'snapshot' , 'targets' , 'timestamp' ]:
3138
- continue
3139
-
3140
- # Keep a store of metadata previously loaded metadata to prevent re-loading
3141
- # duplicate versions. Duplicate versions may occur with
3142
- # 'consistent_snapshot', where the same metadata may be available in
3143
- # multiples files (the different hash is included in each filename.
3144
- if metadata_name in loaded_metadata :
3145
- continue
3141
+ # Instead of adding only rolename to the set, store the already loaded
3142
+ # delegated-delegating role tuples. This way a delegated role is added
3143
+ # to each of its delegating roles but when the role is reached twice
3144
+ # from the same delegating role an infinite loop is avoided.
3145
+ loaded_delegations .add ((rolename , delegating_role ))
3146
3146
3147
+ metadata_path = delegated_roles_filenames [rolename ]
3147
3148
signable = None
3148
3149
3149
3150
try :
@@ -3156,9 +3157,9 @@ def load_repository(repository_directory, repository_name='default',
3156
3157
3157
3158
metadata_object = signable ['signed' ]
3158
3159
3159
- # Extract the metadata attributes of 'metadata_name ' and update its
3160
+ # Extract the metadata attributes of 'metadata_object ' and update its
3160
3161
# corresponding roleinfo.
3161
- roleinfo = {'name' : metadata_name ,
3162
+ roleinfo = {'name' : rolename ,
3162
3163
'signing_keyids' : [],
3163
3164
'signatures' : [],
3164
3165
'partial_loaded' : False
@@ -3170,18 +3171,23 @@ def load_repository(repository_directory, repository_name='default',
3170
3171
roleinfo ['paths' ] = metadata_object ['targets' ]
3171
3172
roleinfo ['delegations' ] = metadata_object ['delegations' ]
3172
3173
3173
- tuf .roledb .add_role (metadata_name , roleinfo , repository_name )
3174
- loaded_metadata .append (metadata_name )
3175
-
3176
- # Generate the Targets objects of the delegated roles of 'metadata_name'
3177
- # and add it to the top-level 'targets' object.
3178
- new_targets_object = Targets (targets_directory , metadata_name , roleinfo ,
3179
- repository_name = repository_name )
3180
- targets_object = targets_objects ['targets' ]
3181
- targets_objects [metadata_name ] = new_targets_object
3174
+ # Generate the Targets object of the delegated role,
3175
+ # add it to the top-level 'targets' object and to its
3176
+ # direct delegating role object.
3177
+ new_targets_object = Targets (targets_directory , rolename ,
3178
+ roleinfo , parent_targets_object = parent_targets_object ,
3179
+ repository_name = repository_name )
3180
+
3181
+ parent_targets_object .add_delegated_role (rolename ,
3182
+ new_targets_object )
3183
+ if delegating_role != 'targets' :
3184
+ parent_targets_object (delegating_role ).add_delegated_role (rolename ,
3185
+ new_targets_object )
3182
3186
3183
- targets_object ._delegated_roles [(os .path .basename (metadata_name ))] = \
3184
- new_targets_object
3187
+ # Append the next level delegations to the deque:
3188
+ # the 'delegated' role becomes the 'delegating'
3189
+ for delegation in metadata_object ['delegations' ]['roles' ]:
3190
+ delegations .append ((delegation ['name' ], rolename ))
3185
3191
3186
3192
# Extract the keys specified in the delegations field of the Targets
3187
3193
# role. Add 'key_object' to the list of recognized keys. Keys may be
@@ -3196,8 +3202,10 @@ def load_repository(repository_directory, repository_name='default',
3196
3202
# that doesn't match the client's set of hash algorithms. Make sure
3197
3203
# to only used the repo's selected hashing algorithms.
3198
3204
hash_algorithms = securesystemslib .settings .HASH_ALGORITHMS
3199
- securesystemslib .settings .HASH_ALGORITHMS = key_metadata ['keyid_hash_algorithms' ]
3200
- key_object , keyids = securesystemslib .keys .format_metadata_to_key (key_metadata )
3205
+ securesystemslib .settings .HASH_ALGORITHMS = \
3206
+ key_metadata ['keyid_hash_algorithms' ]
3207
+ key_object , keyids = \
3208
+ securesystemslib .keys .format_metadata_to_key (key_metadata )
3201
3209
securesystemslib .settings .HASH_ALGORITHMS = hash_algorithms
3202
3210
try :
3203
3211
for keyid in keyids : # pragma: no branch
0 commit comments