Skip to content

Commit 062c9f6

Browse files
committed
Move _visit_child_role to DelegatedRole
Make _visit_child_role a public method of DelegatedRole class. Remove debug logging at this point since logger is not yet used in metadata.py. Signed-off-by: Teodora Sechkova <tsechkova@vmware.com>
1 parent 24fa112 commit 062c9f6

File tree

2 files changed

+62
-116
lines changed

2 files changed

+62
-116
lines changed

tuf/api/metadata.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
1717
"""
1818
import abc
19+
import fnmatch
1920
import io
21+
import os
2022
import tempfile
2123
from collections import OrderedDict
2224
from datetime import datetime, timedelta
@@ -953,6 +955,64 @@ def to_dict(self) -> Dict[str, Any]:
953955
res_dict["path_hash_prefixes"] = self.path_hash_prefixes
954956
return res_dict
955957

958+
def visit_child_role(self, target_filepath: str) -> str:
959+
"""Determines whether the given 'target_filepath' is an
960+
allowed path of DelegatedRole"""
961+
962+
if self.path_hash_prefixes is not None:
963+
target_filepath_hash = _get_filepath_hash(target_filepath)
964+
for path_hash_prefix in self.path_hash_prefixes:
965+
if not target_filepath_hash.startswith(path_hash_prefix):
966+
continue
967+
968+
return self.name
969+
970+
elif self.paths is not None:
971+
for path in self.paths:
972+
# A child role path may be an explicit path or glob pattern (Unix
973+
# shell-style wildcards). The child role 'child_role_name' is
974+
# returned if 'target_filepath' is equal to or matches
975+
# 'child_role_path'. Explicit filepaths are also considered
976+
# matches. A repo maintainer might delegate a glob pattern with a
977+
# leading path separator, while the client requests a matching
978+
# target without a leading path separator - make sure to strip any
979+
# leading path separators so that a match is made.
980+
# Example: "foo.tgz" should match with "/*.tgz".
981+
if fnmatch.fnmatch(
982+
target_filepath.lstrip(os.sep), path.lstrip(os.sep)
983+
):
984+
985+
return self.name
986+
987+
continue
988+
989+
else:
990+
# 'role_name' should have been validated when it was downloaded.
991+
# The 'paths' or 'path_hash_prefixes' fields should not be missing,
992+
# so we raise a format error here in case they are both missing.
993+
raise exceptions.FormatError(
994+
repr(self.name) + " "
995+
'has neither a "paths" nor "path_hash_prefixes". At least'
996+
" one of these attributes must be present."
997+
)
998+
999+
return None
1000+
1001+
1002+
def _get_filepath_hash(target_filepath, hash_function="sha256"):
1003+
"""
1004+
Calculate the hash of the filepath to determine which bin to find the
1005+
target.
1006+
"""
1007+
# The client currently assumes the repository (i.e., repository
1008+
# tool) uses 'hash_function' to generate hashes and UTF-8.
1009+
digest_object = sslib_hash.digest(hash_function)
1010+
encoded_target_filepath = target_filepath.encode("utf-8")
1011+
digest_object.update(encoded_target_filepath)
1012+
target_filepath_hash = digest_object.hexdigest()
1013+
1014+
return target_filepath_hash
1015+
9561016

9571017
class Delegations:
9581018
"""A container object storing information about all delegations.

tuf/ngclient/updater.py

Lines changed: 2 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,11 @@
44
"""TUF client workflow implementation.
55
"""
66

7-
import fnmatch
87
import logging
98
import os
109
from typing import Any, Dict, List, Optional
1110
from urllib import parse
1211

13-
from securesystemslib import hash as sslib_hash
1412
from securesystemslib import util as sslib_util
1513

1614
from tuf import exceptions
@@ -361,8 +359,8 @@ def _preorder_depth_first_walk(self, target_filepath) -> Dict:
361359
# NOTE: This may be a slow operation if there are many
362360
# delegated roles.
363361
for child_role in child_roles:
364-
child_role_name = _visit_child_role(
365-
child_role, target_filepath
362+
child_role_name = child_role.visit_child_role(
363+
target_filepath
366364
)
367365

368366
if child_role.terminating and child_role_name is not None:
@@ -412,118 +410,6 @@ def _preorder_depth_first_walk(self, target_filepath) -> Dict:
412410
return {"filepath": target_filepath, "fileinfo": target}
413411

414412

415-
def _visit_child_role(child_role: Dict, target_filepath: str) -> str:
416-
"""
417-
<Purpose>
418-
Non-public method that determines whether the given 'target_filepath'
419-
is an allowed path of 'child_role'.
420-
421-
Ensure that we explore only delegated roles trusted with the target. The
422-
metadata for 'child_role' should have been refreshed prior to this point,
423-
however, the paths/targets that 'child_role' signs for have not been
424-
verified (as intended). The paths/targets that 'child_role' is allowed
425-
to specify in its metadata depends on the delegating role, and thus is
426-
left to the caller to verify. We verify here that 'target_filepath'
427-
is an allowed path according to the delegated 'child_role'.
428-
429-
TODO: Should the TUF spec restrict the repository to one particular
430-
algorithm? Should we allow the repository to specify in the role
431-
dictionary the algorithm used for these generated hashed paths?
432-
433-
<Arguments>
434-
child_role:
435-
The delegation targets role object of 'child_role', containing its
436-
paths, path_hash_prefixes, keys, and so on.
437-
438-
target_filepath:
439-
The path to the target file on the repository. This will be relative to
440-
the 'targets' (or equivalent) directory on a given mirror.
441-
442-
<Exceptions>
443-
None.
444-
445-
<Side Effects>
446-
None.
447-
448-
<Returns>
449-
If 'child_role' has been delegated the target with the name
450-
'target_filepath', then we return the role name of 'child_role'.
451-
452-
Otherwise, we return None.
453-
"""
454-
455-
child_role_name = child_role.name
456-
child_role_paths = child_role.paths
457-
child_role_path_hash_prefixes = child_role.path_hash_prefixes
458-
459-
if child_role_path_hash_prefixes is not None:
460-
target_filepath_hash = _get_filepath_hash(target_filepath)
461-
for child_role_path_hash_prefix in child_role_path_hash_prefixes:
462-
if not target_filepath_hash.startswith(child_role_path_hash_prefix):
463-
continue
464-
465-
return child_role_name
466-
467-
elif child_role_paths is not None:
468-
# Is 'child_role_name' allowed to sign for 'target_filepath'?
469-
for child_role_path in child_role_paths:
470-
# A child role path may be an explicit path or glob pattern (Unix
471-
# shell-style wildcards). The child role 'child_role_name' is
472-
# returned if 'target_filepath' is equal to or matches
473-
# 'child_role_path'. Explicit filepaths are also considered
474-
# matches. A repo maintainer might delegate a glob pattern with a
475-
# leading path separator, while the client requests a matching
476-
# target without a leading path separator - make sure to strip any
477-
# leading path separators so that a match is made.
478-
# Example: "foo.tgz" should match with "/*.tgz".
479-
if fnmatch.fnmatch(
480-
target_filepath.lstrip(os.sep), child_role_path.lstrip(os.sep)
481-
):
482-
logger.debug(
483-
"Child role "
484-
+ repr(child_role_name)
485-
+ " is allowed to sign for "
486-
+ repr(target_filepath)
487-
)
488-
489-
return child_role_name
490-
491-
logger.debug(
492-
"The given target path "
493-
+ repr(target_filepath)
494-
+ " does not match the trusted path or glob pattern: "
495-
+ repr(child_role_path)
496-
)
497-
continue
498-
499-
else:
500-
# 'role_name' should have been validated when it was downloaded.
501-
# The 'paths' or 'path_hash_prefixes' fields should not be missing,
502-
# so we raise a format error here in case they are both missing.
503-
raise exceptions.FormatError(
504-
repr(child_role_name) + " "
505-
'has neither a "paths" nor "path_hash_prefixes". At least'
506-
" one of these attributes must be present."
507-
)
508-
509-
return None
510-
511-
512-
def _get_filepath_hash(target_filepath, hash_function="sha256"):
513-
"""
514-
Calculate the hash of the filepath to determine which bin to find the
515-
target.
516-
"""
517-
# The client currently assumes the repository (i.e., repository
518-
# tool) uses 'hash_function' to generate hashes and UTF-8.
519-
digest_object = sslib_hash.digest(hash_function)
520-
encoded_target_filepath = target_filepath.encode("utf-8")
521-
digest_object.update(encoded_target_filepath)
522-
target_filepath_hash = digest_object.hexdigest()
523-
524-
return target_filepath_hash
525-
526-
527413
def _ensure_trailing_slash(url: str):
528414
"""Return url guaranteed to end in a slash"""
529415
return url if url.endswith("/") else f"{url}/"

0 commit comments

Comments
 (0)