Skip to content

Commit 55c8a78

Browse files
author
Jussi Kukkonen
authored
Merge pull request #1463 from sechkova/review-preorder-dfs
ngclient: review preorder dfs code
2 parents 98cc149 + bc073b8 commit 55c8a78

File tree

3 files changed

+100
-180
lines changed

3 files changed

+100
-180
lines changed

tests/test_metadata_serialization.py

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -242,10 +242,9 @@ def test_snapshot_serialization(self, test_case_data: str):
242242
"no path attribute":
243243
'{"keyids": ["keyid"], "name": "a", "terminating": false, \
244244
"path_hash_prefixes": ["h1", "h2"], "threshold": 99}',
245-
"no hash or path prefix":
246-
'{"keyids": ["keyid"], "name": "a", "terminating": true, "threshold": 3}',
247245
"unrecognized field":
248-
'{"keyids": ["keyid"], "name": "a", "terminating": true, "threshold": 3, "foo": "bar"}',
246+
'{"keyids": ["keyid"], "name": "a", "paths": ["fn1", "fn2"], \
247+
"terminating": true, "threshold": 3, "foo": "bar"}',
249248
}
250249

251250
@run_sub_tests_with_dataset(valid_delegated_roles)
@@ -255,12 +254,27 @@ def test_delegated_role_serialization(self, test_case_data: str):
255254
self.assertDictEqual(case_dict, deserialized_role.to_dict())
256255

257256

257+
invalid_delegated_roles: DataSet = {
258+
"missing hash prefixes and paths":
259+
'{"name": "a", "keyids": ["keyid"], "threshold": 1, "terminating": false}',
260+
"both hash prefixes and paths":
261+
'{"name": "a", "keyids": ["keyid"], "threshold": 1, "terminating": false, \
262+
"paths": ["fn1", "fn2"], "path_hash_prefixes": ["h1", "h2"]}',
263+
}
264+
265+
@run_sub_tests_with_dataset(invalid_delegated_roles)
266+
def test_invalid_delegated_role_serialization(self, test_case_data: str):
267+
case_dict = json.loads(test_case_data)
268+
with self.assertRaises(ValueError):
269+
DelegatedRole.from_dict(copy.copy(case_dict))
270+
271+
258272
valid_delegations: DataSet = {
259273
"all": '{"keys": {"keyid" : {"keytype": "rsa", "scheme": "rsassa-pss-sha256", "keyval": {"public": "foo"}}}, \
260-
"roles": [ {"keyids": ["keyid"], "name": "a", "terminating": true, "threshold": 3} ]}',
274+
"roles": [ {"keyids": ["keyid"], "name": "a", "paths": ["fn1", "fn2"], "terminating": true, "threshold": 3} ]}',
261275
"unrecognized field":
262276
'{"keys": {"keyid" : {"keytype": "rsa", "scheme": "rsassa-pss-sha256", "keyval": {"public": "foo"}}}, \
263-
"roles": [ {"keyids": ["keyid"], "name": "a", "terminating": true, "threshold": 3} ], \
277+
"roles": [ {"keyids": ["keyid"], "name": "a", "paths": ["fn1", "fn2"], "terminating": true, "threshold": 3} ], \
264278
"foo": "bar"}',
265279
}
266280

@@ -305,13 +319,13 @@ def test_targetfile_serialization(self, test_case_data: str):
305319
"targets": { "file.txt": {"length": 12, "hashes": {"sha256" : "abc"} } }, \
306320
"delegations": {"keys": {"keyid" : {"keytype": "rsa", \
307321
"scheme": "rsassa-pss-sha256", "keyval": {"public": "foo"} }}, \
308-
"roles": [ {"keyids": ["keyid"], "name": "a", "terminating": true, "threshold": 3} ]} \
322+
"roles": [ {"keyids": ["keyid"], "name": "a", "paths": ["fn1", "fn2"], "terminating": true, "threshold": 3} ]} \
309323
}',
310324
"empty targets": '{"_type": "targets", "spec_version": "1.0.0", "version": 1, "expires": "2030-01-01T00:00:00Z", \
311325
"targets": {}, \
312326
"delegations": {"keys": {"keyid" : {"keytype": "rsa", \
313327
"scheme": "rsassa-pss-sha256", "keyval": {"public": "foo"} }}, \
314-
"roles": [ {"keyids": ["keyid"], "name": "a", "terminating": true, "threshold": 3} ]} \
328+
"roles": [ {"keyids": ["keyid"], "name": "a", "paths": ["fn1", "fn2"], "terminating": true, "threshold": 3} ]} \
315329
}',
316330
"no delegations": '{"_type": "targets", "spec_version": "1.0.0", "version": 1, "expires": "2030-01-01T00:00:00Z", \
317331
"targets": { "file.txt": {"length": 12, "hashes": {"sha256" : "abc"} } } \

tuf/api/metadata.py

Lines changed: 39 additions & 7 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
@@ -960,12 +962,12 @@ def update(self, rolename: str, role_info: MetaFile) -> None:
960962
class DelegatedRole(Role):
961963
"""A container with information about a delegated role.
962964
963-
A delegation can happen in three ways:
964-
- paths is None and path_hash_prefixes is None: delegates all targets
965+
A delegation can happen in two ways:
965966
- paths is set: delegates targets matching any path pattern in paths
966967
- path_hash_prefixes is set: delegates targets whose target path hash
967968
starts with any of the prefixes in path_hash_prefixes
968-
paths and path_hash_prefixes are mutually exclusive: both cannot be set.
969+
paths and path_hash_prefixes are mutually exclusive: both cannot be set,
970+
at least one of them must be set.
969971
970972
Attributes:
971973
name: A string giving the name of the delegated role.
@@ -990,10 +992,11 @@ def __init__(
990992
self.name = name
991993
self.terminating = terminating
992994
if paths is not None and path_hash_prefixes is not None:
993-
raise ValueError(
994-
"Only one of the attributes 'paths' and"
995-
"'path_hash_prefixes' can be set!"
996-
)
995+
raise ValueError("Either paths or path_hash_prefixes can be set")
996+
997+
if paths is None and path_hash_prefixes is None:
998+
raise ValueError("One of paths or path_hash_prefixes must be set")
999+
9971000
self.paths = paths
9981001
self.path_hash_prefixes = path_hash_prefixes
9991002

@@ -1031,6 +1034,35 @@ def to_dict(self) -> Dict[str, Any]:
10311034
res_dict["path_hash_prefixes"] = self.path_hash_prefixes
10321035
return res_dict
10331036

1037+
def is_delegated_path(self, target_filepath: str) -> bool:
1038+
"""Determines whether the given 'target_filepath' is in one of
1039+
the paths that DelegatedRole is trusted to provide"""
1040+
1041+
if self.path_hash_prefixes is not None:
1042+
# Calculate the hash of the filepath
1043+
# to determine in which bin to find the target.
1044+
digest_object = sslib_hash.digest(algorithm="sha256")
1045+
digest_object.update(target_filepath.encode("utf-8"))
1046+
target_filepath_hash = digest_object.hexdigest()
1047+
1048+
for path_hash_prefix in self.path_hash_prefixes:
1049+
if target_filepath_hash.startswith(path_hash_prefix):
1050+
return True
1051+
1052+
elif self.paths is not None:
1053+
for pathpattern in self.paths:
1054+
# A delegated role path may be an explicit path or glob
1055+
# pattern (Unix shell-style wildcards). Explicit filepaths
1056+
# are also considered matches. Make sure to strip any leading
1057+
# 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), pathpattern.lstrip(os.sep)
1061+
):
1062+
return True
1063+
1064+
return False
1065+
10341066

10351067
class Delegations:
10361068
"""A container object storing information about all delegations.

0 commit comments

Comments
 (0)