Skip to content

Commit

Permalink
Merge bc073b8 into 98cc149
Browse files Browse the repository at this point in the history
  • Loading branch information
sechkova committed Aug 10, 2021
2 parents 98cc149 + bc073b8 commit 2af95a9
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 180 deletions.
28 changes: 21 additions & 7 deletions tests/test_metadata_serialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,10 +242,9 @@ def test_snapshot_serialization(self, test_case_data: str):
"no path attribute":
'{"keyids": ["keyid"], "name": "a", "terminating": false, \
"path_hash_prefixes": ["h1", "h2"], "threshold": 99}',
"no hash or path prefix":
'{"keyids": ["keyid"], "name": "a", "terminating": true, "threshold": 3}',
"unrecognized field":
'{"keyids": ["keyid"], "name": "a", "terminating": true, "threshold": 3, "foo": "bar"}',
'{"keyids": ["keyid"], "name": "a", "paths": ["fn1", "fn2"], \
"terminating": true, "threshold": 3, "foo": "bar"}',
}

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


invalid_delegated_roles: DataSet = {
"missing hash prefixes and paths":
'{"name": "a", "keyids": ["keyid"], "threshold": 1, "terminating": false}',
"both hash prefixes and paths":
'{"name": "a", "keyids": ["keyid"], "threshold": 1, "terminating": false, \
"paths": ["fn1", "fn2"], "path_hash_prefixes": ["h1", "h2"]}',
}

@run_sub_tests_with_dataset(invalid_delegated_roles)
def test_invalid_delegated_role_serialization(self, test_case_data: str):
case_dict = json.loads(test_case_data)
with self.assertRaises(ValueError):
DelegatedRole.from_dict(copy.copy(case_dict))


valid_delegations: DataSet = {
"all": '{"keys": {"keyid" : {"keytype": "rsa", "scheme": "rsassa-pss-sha256", "keyval": {"public": "foo"}}}, \
"roles": [ {"keyids": ["keyid"], "name": "a", "terminating": true, "threshold": 3} ]}',
"roles": [ {"keyids": ["keyid"], "name": "a", "paths": ["fn1", "fn2"], "terminating": true, "threshold": 3} ]}',
"unrecognized field":
'{"keys": {"keyid" : {"keytype": "rsa", "scheme": "rsassa-pss-sha256", "keyval": {"public": "foo"}}}, \
"roles": [ {"keyids": ["keyid"], "name": "a", "terminating": true, "threshold": 3} ], \
"roles": [ {"keyids": ["keyid"], "name": "a", "paths": ["fn1", "fn2"], "terminating": true, "threshold": 3} ], \
"foo": "bar"}',
}

Expand Down Expand Up @@ -305,13 +319,13 @@ def test_targetfile_serialization(self, test_case_data: str):
"targets": { "file.txt": {"length": 12, "hashes": {"sha256" : "abc"} } }, \
"delegations": {"keys": {"keyid" : {"keytype": "rsa", \
"scheme": "rsassa-pss-sha256", "keyval": {"public": "foo"} }}, \
"roles": [ {"keyids": ["keyid"], "name": "a", "terminating": true, "threshold": 3} ]} \
"roles": [ {"keyids": ["keyid"], "name": "a", "paths": ["fn1", "fn2"], "terminating": true, "threshold": 3} ]} \
}',
"empty targets": '{"_type": "targets", "spec_version": "1.0.0", "version": 1, "expires": "2030-01-01T00:00:00Z", \
"targets": {}, \
"delegations": {"keys": {"keyid" : {"keytype": "rsa", \
"scheme": "rsassa-pss-sha256", "keyval": {"public": "foo"} }}, \
"roles": [ {"keyids": ["keyid"], "name": "a", "terminating": true, "threshold": 3} ]} \
"roles": [ {"keyids": ["keyid"], "name": "a", "paths": ["fn1", "fn2"], "terminating": true, "threshold": 3} ]} \
}',
"no delegations": '{"_type": "targets", "spec_version": "1.0.0", "version": 1, "expires": "2030-01-01T00:00:00Z", \
"targets": { "file.txt": {"length": 12, "hashes": {"sha256" : "abc"} } } \
Expand Down
46 changes: 39 additions & 7 deletions tuf/api/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@
"""
import abc
import fnmatch
import io
import logging
import os
import tempfile
from collections import OrderedDict
from datetime import datetime, timedelta
Expand Down Expand Up @@ -960,12 +962,12 @@ def update(self, rolename: str, role_info: MetaFile) -> None:
class DelegatedRole(Role):
"""A container with information about a delegated role.
A delegation can happen in three ways:
- paths is None and path_hash_prefixes is None: delegates all targets
A delegation can happen in two ways:
- paths is set: delegates targets matching any path pattern in paths
- path_hash_prefixes is set: delegates targets whose target path hash
starts with any of the prefixes in path_hash_prefixes
paths and path_hash_prefixes are mutually exclusive: both cannot be set.
paths and path_hash_prefixes are mutually exclusive: both cannot be set,
at least one of them must be set.
Attributes:
name: A string giving the name of the delegated role.
Expand All @@ -990,10 +992,11 @@ def __init__(
self.name = name
self.terminating = terminating
if paths is not None and path_hash_prefixes is not None:
raise ValueError(
"Only one of the attributes 'paths' and"
"'path_hash_prefixes' can be set!"
)
raise ValueError("Either paths or path_hash_prefixes can be set")

if paths is None and path_hash_prefixes is None:
raise ValueError("One of paths or path_hash_prefixes must be set")

self.paths = paths
self.path_hash_prefixes = path_hash_prefixes

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

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"""

if self.path_hash_prefixes is not None:
# Calculate the hash of the filepath
# to determine in which bin to find the target.
digest_object = sslib_hash.digest(algorithm="sha256")
digest_object.update(target_filepath.encode("utf-8"))
target_filepath_hash = digest_object.hexdigest()

for path_hash_prefix in self.path_hash_prefixes:
if target_filepath_hash.startswith(path_hash_prefix):
return True

elif self.paths is not None:
for pathpattern in self.paths:
# A delegated role path may be an explicit path or glob
# pattern (Unix shell-style wildcards). Explicit filepaths
# 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(
target_filepath.lstrip(os.sep), pathpattern.lstrip(os.sep)
):
return True

return False


class Delegations:
"""A container object storing information about all delegations.
Expand Down
Loading

0 comments on commit 2af95a9

Please sign in to comment.