Skip to content

Commit 167441c

Browse files
committed
Move _visit_child_role to DelegatedRole
Make _visit_child_role a public method of DelegatedRole class. Reduce debug logging. Signed-off-by: Teodora Sechkova <[email protected]>
1 parent 01e2308 commit 167441c

2 files changed

Lines changed: 62 additions & 115 deletions

File tree

tuf/api/metadata.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@
1616
1717
"""
1818
import abc
19+
import fnmatch
1920
import io
2021
import logging
22+
import os
2123
import tempfile
2224
from collections import OrderedDict
2325
from datetime import datetime, timedelta
@@ -1031,6 +1033,64 @@ def to_dict(self) -> Dict[str, Any]:
10311033
res_dict["path_hash_prefixes"] = self.path_hash_prefixes
10321034
return res_dict
10331035

1036+
def visit_child_role(self, target_filepath: str) -> str:
1037+
"""Determines whether the given 'target_filepath' is an
1038+
allowed path of DelegatedRole"""
1039+
1040+
if self.path_hash_prefixes is not None:
1041+
target_filepath_hash = _get_filepath_hash(target_filepath)
1042+
for path_hash_prefix in self.path_hash_prefixes:
1043+
if not target_filepath_hash.startswith(path_hash_prefix):
1044+
continue
1045+
1046+
return self.name
1047+
1048+
elif self.paths is not None:
1049+
for path in self.paths:
1050+
# A child role path may be an explicit path or glob pattern (Unix
1051+
# shell-style wildcards). The child role 'child_role_name' is
1052+
# returned if 'target_filepath' is equal to or matches
1053+
# 'child_role_path'. Explicit filepaths are also considered
1054+
# matches. A repo maintainer might delegate a glob pattern with a
1055+
# leading path separator, while the client requests a matching
1056+
# target without a leading path separator - make sure to strip any
1057+
# leading path separators so that a match is made.
1058+
# Example: "foo.tgz" should match with "/*.tgz".
1059+
if fnmatch.fnmatch(
1060+
target_filepath.lstrip(os.sep), path.lstrip(os.sep)
1061+
):
1062+
1063+
return self.name
1064+
1065+
continue
1066+
1067+
else:
1068+
# 'role_name' should have been validated when it was downloaded.
1069+
# The 'paths' or 'path_hash_prefixes' fields should not be missing,
1070+
# so we raise a format error here in case they are both missing.
1071+
raise exceptions.FormatError(
1072+
repr(self.name) + " "
1073+
'has neither a "paths" nor "path_hash_prefixes". At least'
1074+
" one of these attributes must be present."
1075+
)
1076+
1077+
return None
1078+
1079+
1080+
def _get_filepath_hash(target_filepath, hash_function="sha256"):
1081+
"""
1082+
Calculate the hash of the filepath to determine which bin to find the
1083+
target.
1084+
"""
1085+
# The client currently assumes the repository (i.e., repository
1086+
# tool) uses 'hash_function' to generate hashes and UTF-8.
1087+
digest_object = sslib_hash.digest(hash_function)
1088+
encoded_target_filepath = target_filepath.encode("utf-8")
1089+
digest_object.update(encoded_target_filepath)
1090+
target_filepath_hash = digest_object.hexdigest()
1091+
1092+
return target_filepath_hash
1093+
10341094

10351095
class Delegations:
10361096
"""A container object storing information about all delegations.

tuf/ngclient/updater.py

Lines changed: 2 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,11 @@
5858
updater.download_target(targetinfo, "~/tufclient/downloads/")
5959
"""
6060

61-
import fnmatch
6261
import logging
6362
import os
6463
from typing import Any, Dict, List, Optional
6564
from urllib import parse
6665

67-
from securesystemslib import hash as sslib_hash
6866
from securesystemslib import util as sslib_util
6967

7068
from tuf import exceptions
@@ -414,8 +412,8 @@ def _preorder_depth_first_walk(self, target_filepath) -> Dict:
414412
# NOTE: This may be a slow operation if there are many
415413
# delegated roles.
416414
for child_role in child_roles:
417-
child_role_name = _visit_child_role(
418-
child_role, target_filepath
415+
child_role_name = child_role.visit_child_role(
416+
target_filepath
419417
)
420418

421419
if child_role.terminating and child_role_name is not None:
@@ -463,117 +461,6 @@ def _preorder_depth_first_walk(self, target_filepath) -> Dict:
463461
return {"filepath": target_filepath, "fileinfo": target}
464462

465463

466-
def _visit_child_role(child_role: Dict, target_filepath: str) -> str:
467-
"""
468-
<Purpose>
469-
Non-public method that determines whether the given 'target_filepath'
470-
is an allowed path of 'child_role'.
471-
472-
Ensure that we explore only delegated roles trusted with the target. The
473-
metadata for 'child_role' should have been refreshed prior to this point,
474-
however, the paths/targets that 'child_role' signs for have not been
475-
verified (as intended). The paths/targets that 'child_role' is allowed
476-
to specify in its metadata depends on the delegating role, and thus is
477-
left to the caller to verify. We verify here that 'target_filepath'
478-
is an allowed path according to the delegated 'child_role'.
479-
480-
TODO: Should the TUF spec restrict the repository to one particular
481-
algorithm? Should we allow the repository to specify in the role
482-
dictionary the algorithm used for these generated hashed paths?
483-
484-
<Arguments>
485-
child_role:
486-
The delegation targets role object of 'child_role', containing its
487-
paths, path_hash_prefixes, keys, and so on.
488-
489-
target_filepath:
490-
The path to the target file on the repository. This will be relative to
491-
the 'targets' (or equivalent) directory on a given mirror.
492-
493-
<Exceptions>
494-
None.
495-
496-
<Side Effects>
497-
None.
498-
499-
<Returns>
500-
If 'child_role' has been delegated the target with the name
501-
'target_filepath', then we return the role name of 'child_role'.
502-
503-
Otherwise, we return None.
504-
"""
505-
506-
child_role_name = child_role.name
507-
child_role_paths = child_role.paths
508-
child_role_path_hash_prefixes = child_role.path_hash_prefixes
509-
510-
if child_role_path_hash_prefixes is not None:
511-
target_filepath_hash = _get_filepath_hash(target_filepath)
512-
for child_role_path_hash_prefix in child_role_path_hash_prefixes:
513-
if not target_filepath_hash.startswith(child_role_path_hash_prefix):
514-
continue
515-
516-
return child_role_name
517-
518-
elif child_role_paths is not None:
519-
# Is 'child_role_name' allowed to sign for 'target_filepath'?
520-
for child_role_path in child_role_paths:
521-
# A child role path may be an explicit path or glob pattern (Unix
522-
# shell-style wildcards). The child role 'child_role_name' is
523-
# returned if 'target_filepath' is equal to or matches
524-
# 'child_role_path'. Explicit filepaths are also considered
525-
# matches. A repo maintainer might delegate a glob pattern with a
526-
# leading path separator, while the client requests a matching
527-
# target without a leading path separator - make sure to strip any
528-
# leading path separators so that a match is made.
529-
# Example: "foo.tgz" should match with "/*.tgz".
530-
if fnmatch.fnmatch(
531-
target_filepath.lstrip(os.sep), child_role_path.lstrip(os.sep)
532-
):
533-
logger.debug(
534-
"Child role %s is allowed to sign for %s",
535-
repr(child_role_name),
536-
repr(target_filepath),
537-
)
538-
539-
return child_role_name
540-
541-
logger.debug(
542-
"The given target path %s "
543-
"does not match the trusted path or glob pattern: %s",
544-
repr(target_filepath),
545-
repr(child_role_path),
546-
)
547-
continue
548-
549-
else:
550-
# 'role_name' should have been validated when it was downloaded.
551-
# The 'paths' or 'path_hash_prefixes' fields should not be missing,
552-
# so we raise a format error here in case they are both missing.
553-
raise exceptions.FormatError(
554-
repr(child_role_name) + " "
555-
'has neither a "paths" nor "path_hash_prefixes". At least'
556-
" one of these attributes must be present."
557-
)
558-
559-
return None
560-
561-
562-
def _get_filepath_hash(target_filepath, hash_function="sha256"):
563-
"""
564-
Calculate the hash of the filepath to determine which bin to find the
565-
target.
566-
"""
567-
# The client currently assumes the repository (i.e., repository
568-
# tool) uses 'hash_function' to generate hashes and UTF-8.
569-
digest_object = sslib_hash.digest(hash_function)
570-
encoded_target_filepath = target_filepath.encode("utf-8")
571-
digest_object.update(encoded_target_filepath)
572-
target_filepath_hash = digest_object.hexdigest()
573-
574-
return target_filepath_hash
575-
576-
577464
def _ensure_trailing_slash(url: str):
578465
"""Return url guaranteed to end in a slash"""
579466
return url if url.endswith("/") else f"{url}/"

0 commit comments

Comments
 (0)