Skip to content

Commit

Permalink
Implement glob-like pattern matching
Browse files Browse the repository at this point in the history
According to the recently updated version of the specification the shell
style wildcard matching is glob-like (see theupdateframework/specification#174),
and therefore a path separator in a path should not be matched by a
wildcard in the PATHPATTERN.

That's not what happens with `fnmatch.fnmatch()` which doesn't
see "/" separator as a special symbol.
For example: fnmatch.fnmatch("targets/foo.tgz", "*.tgz") will return
True which is not what glob-like implementation will do.

We should make sure that target_path and the pathpattern contain the
same number of directories and because each part of the pathpattern
could include a glob pattern we should check that fnmatch.fnmatch() is
true on each target and pathpattern directory fragment separated by "/".

Signed-off-by: Martin Vrachev <[email protected]>
  • Loading branch information
MVrachev committed Aug 26, 2021
1 parent 8482f2c commit b18176d
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 3 deletions.
33 changes: 31 additions & 2 deletions tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
import shutil
import tempfile
import unittest
import copy

from datetime import datetime, timedelta
from dateutil.relativedelta import relativedelta
Expand All @@ -40,7 +39,6 @@

from tuf.api.serialization.json import (
JSONSerializer,
JSONDeserializer,
CanonicalJSONSerializer
)

Expand Down Expand Up @@ -465,6 +463,37 @@ def test_metadata_root(self):
with self.assertRaises(KeyError):
root.signed.remove_key('root', 'nosuchkey')

def test_is_target_in_pathpattern(self):
supported_use_cases = [
("foo.tgz", "foo.tgz"),
("foo.tgz", "*"),
("foo.tgz", "*.tgz"),
("foo-version-a.tgz", "foo-version-?.tgz"),
("targets/foo.tgz", "targets/*.tgz"),
("foo/bar/zoo/k.tgz", "foo/bar/zoo/*"),
("foo/bar/zoo/k.tgz", "foo/*/zoo/*"),
("foo/bar/zoo/k.tgz", "*/*/*/*"),
("foo/bar", "f?o/bar"),
("foo/bar", "*o/bar"),
]
for targetpath, pathpattern in supported_use_cases:
self.assertTrue(
DelegatedRole._is_target_in_pathpattern(targetpath, pathpattern)
)

invalid_use_cases = [
("targets/foo.tgz", "*.tgz"),
("/foo.tgz", "*.tgz",),
("targets/foo.tgz", "*"),
("foo-version-alpha.tgz", "foo-version-?.tgz"),
("foo//bar", "*/bar"),
("foo/bar", "f?/bar")
]
for targetpath, pathpattern in invalid_use_cases:
self.assertFalse(
DelegatedRole._is_target_in_pathpattern(targetpath, pathpattern)
)


def test_delegation_class(self):
# empty keys and roles
Expand Down
20 changes: 19 additions & 1 deletion tuf/api/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -1057,6 +1057,24 @@ def to_dict(self) -> Dict[str, Any]:
res_dict["path_hash_prefixes"] = self.path_hash_prefixes
return res_dict

@staticmethod
def _is_target_in_pathpattern(targetpath: str, pathpattern: str) -> bool:
"""Determines whether "targetname" matches the "pathpattern"."""
# We need to make sure that targetname and pathpattern are pointing to
# the same directory as fnmatch doesn't threat "/" as a special symbol.
target_parts = targetpath.split("/")
pattern_parts = pathpattern.split("/")
if len(target_parts) != len(pattern_parts):
return False

# Every part in the pathpattern could include a glob pattern, that's why
# each of the target and pathpattern parts should match.
for target_dir, pattern_dir in zip(target_parts, pattern_parts):
if not fnmatch.fnmatch(target_dir, pattern_dir):
return False

return True

def is_delegated_path(self, target_filepath: str) -> bool:
"""Determines whether the given 'target_filepath' is in one of
the paths that DelegatedRole is trusted to provide"""
Expand All @@ -1079,7 +1097,7 @@ def is_delegated_path(self, target_filepath: str) -> bool:
# are also considered matches. Make sure to strip any leading
# path separators so that a match is made.
# Example: "foo.tgz" should match with "/*.tgz".
if fnmatch.fnmatch(
if self._is_target_in_pathpattern(
target_filepath.lstrip(os.sep), pathpattern.lstrip(os.sep)
):
return True
Expand Down

0 comments on commit b18176d

Please sign in to comment.