Skip to content

Commit

Permalink
New API: Add TargetFile class
Browse files Browse the repository at this point in the history
Add a new TargetFile class to tuf.api.metadata module make Targets
class to use it.
This class will contain information about the "targets" field
from targets.json

Also, update the tests for that change.

Signed-off-by: Martin Vrachev <[email protected]>
  • Loading branch information
MVrachev committed Jan 20, 2021
1 parent 8cc785c commit 6259f31
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 20 deletions.
10 changes: 4 additions & 6 deletions tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ def setUpModule():
Root,
Snapshot,
Timestamp,
Targets
Targets,
TargetInfo
)

from securesystemslib.interface import (
Expand Down Expand Up @@ -324,15 +325,12 @@ def test_metadata_targets(self):
"sha512": "ef5beafa16041bcdd2937140afebd485296cd54f7348ecd5a4d035c09759608de467a7ac0eb58753d0242df873c305e8bffad2454aa48f44480f15efae1cacd0"
},

fileinfo = {
'hashes': hashes,
'length': 28
}
fileinfo = TargetInfo(length=28, hashes=hashes)

# Assert that data is not aleady equal
self.assertNotEqual(targets.signed.targets[filename], fileinfo)
# Update an already existing fileinfo
targets.signed.update(filename, fileinfo)
targets.signed.update(filename, fileinfo.to_dict())
# Verify that data is updated
self.assertEqual(targets.signed.targets[filename], fileinfo)

Expand Down
88 changes: 74 additions & 14 deletions tuf/api/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -570,22 +570,66 @@ def update(
self.meta[metadata_fn] = MetadataInfo(version, length, hashes)


class TargetInfo:
"""A container with information about a particular target file.
Instances of TargetInfo are used as values in a dictionary
called "targets" in Targets.
Attributes:
length: An integer indicating the length of the target file.
hashes: A dictionary containing hash algorithms and the
hashes resulted from applying them over the target file::
'hashes': {
'<HASH ALGO 1>': '<TARGET FILE HASH 1>',
'<HASH ALGO 2>': '<TARGET FILE HASH 2>',
...
}
custom: An optional dictionary which may include version numbers,
dependencies, or any other data that the application wants
to include to describe the target file::
'custom': {
'type': 'metadata',
'file_permissions': '0644',
...
} // optional
"""

def __init__(self, length: int, hashes: JsonDict,
custom: Optional[JsonDict] = None) -> None:
self.length = length
self.hashes = hashes
self.custom = custom


def __eq__(self, other: 'TargetInfo') -> bool:
"""Compare objects by their values instead of by their addresses."""
return (self.length == other.length and
self.hashes == other.hashes and
self.custom == other.custom)


def to_dict(self) -> JsonDict:
"""Returns the JSON-serializable dictionary representation of self. """
json_dict = {'length': self.length, 'hashes': self.hashes}

if self.custom is not None:
json_dict['custom'] = self.custom

return json_dict


class Targets(Signed):
"""A container for the signed part of targets metadata.
Attributes:
targets: A dictionary that contains information about target files::
{
'<TARGET FILE NAME>': {
'length': <TARGET FILE SIZE>,
'hashes': {
'<HASH ALGO 1>': '<TARGET FILE HASH 1>',
'<HASH ALGO 2>': '<TARGETS FILE HASH 2>',
...
},
'custom': <CUSTOM OPAQUE DICT> // optional
},
'<TARGET FILE NAME>': <TargetInfo INSTANCE>,
...
}
Expand Down Expand Up @@ -629,25 +673,41 @@ class Targets(Signed):
# pylint: disable=too-many-arguments
def __init__(
self, _type: str, version: int, spec_version: str,
expires: datetime, targets: JsonDict, delegations: JsonDict
) -> None:
expires: datetime, targets: Dict[str, TargetInfo],
delegations: JsonDict) -> None:
super().__init__(_type, version, spec_version, expires)
# TODO: Add class for meta
self.targets = targets

# TODO: Add Key and Role classes
self.delegations = delegations


@classmethod
def from_dict(cls, signed_dict: JsonDict) -> 'Targets':
"""Creates Targets object from its JSON/dict representation. """
for target_path in signed_dict['targets'].keys():
signed_dict['targets'][target_path] = TargetInfo(
**signed_dict['targets'][target_path])

return super().from_dict(signed_dict)


# Serialization.
def to_dict(self) -> JsonDict:
"""Returns the JSON-serializable dictionary representation of self. """
json_dict = super().to_dict()
target_dict = {}
for target_path, target_file_obj in self.targets.items():
target_dict[target_path] = target_file_obj.to_dict()

json_dict.update({
'targets': self.targets,
'targets': target_dict,
'delegations': self.delegations,
})
return json_dict

# Modification.
def update(self, filename: str, fileinfo: JsonDict) -> None:
"""Assigns passed target file info to meta dict. """
self.targets[filename] = fileinfo
self.targets[filename] = TargetInfo(fileinfo['length'],
fileinfo['hashes'], fileinfo.get('custom'))

0 comments on commit 6259f31

Please sign in to comment.