2022-02-05 17:32:20 +00:00
|
|
|
# Copyright New York University and the TUF contributors
|
|
|
|
|
# SPDX-License-Identifier: MIT OR Apache-2.0
|
|
|
|
|
|
|
|
|
|
"""Test __eq__ implementations of classes inside tuf/api/metadata.py."""
|
|
|
|
|
|
2024-11-29 10:29:32 +00:00
|
|
|
from __future__ import annotations
|
|
|
|
|
|
2022-02-05 17:32:20 +00:00
|
|
|
import copy
|
|
|
|
|
import os
|
|
|
|
|
import sys
|
|
|
|
|
import unittest
|
2024-11-04 04:21:23 +00:00
|
|
|
from typing import Any, ClassVar
|
2022-02-05 17:32:20 +00:00
|
|
|
|
2022-12-01 09:08:05 +00:00
|
|
|
from securesystemslib.signer import SSlibKey
|
Take order into account for certain cases
After we have dropped OrderedDict in https://github.com/theupdateframework/python-tuf/pull/1783/commits/e3b267e2e0799673ac99ccfccd3631628013201c
we are relying on python3.7+ default behavior to preserve the insertion
order, but there is one caveat.
When comparing dictionaries the order is still irrelevant compared to
OrderedDict. For example:
>>> OrderedDict([(1,1), (2,2)]) == OrderedDict([(2,2), (1,1)])
False
>>> dict([(1,1), (2,2)]) == dict([(2,2), (1,1)])
True
There are two special attributes, defined in the specification, where
the order makes a difference when comparing two objects:
- Metadata.signatures
- Targets.delegations.roles.
We want to make sure that the order in those two cases makes a
difference when comparing two objects and that's why those changes
are required inside two __eq__ implementations.
Signed-off-by: Martin Vrachev <mvrachev@vmware.com>
2022-02-04 17:49:44 +00:00
|
|
|
|
2022-02-05 17:32:20 +00:00
|
|
|
from tests import utils
|
|
|
|
|
from tuf.api.metadata import (
|
|
|
|
|
TOP_LEVEL_ROLE_NAMES,
|
|
|
|
|
DelegatedRole,
|
|
|
|
|
Delegations,
|
|
|
|
|
Metadata,
|
|
|
|
|
MetaFile,
|
|
|
|
|
Role,
|
2022-12-01 09:08:05 +00:00
|
|
|
Signature,
|
2022-05-17 16:27:58 +00:00
|
|
|
SuccinctRoles,
|
2022-02-05 17:32:20 +00:00
|
|
|
TargetFile,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2025-03-10 20:48:43 +00:00
|
|
|
class TestMetadataComparisons(unittest.TestCase):
|
2022-02-05 17:32:20 +00:00
|
|
|
"""Test __eq__ for all classes inside tuf/api/metadata.py."""
|
|
|
|
|
|
2024-11-04 04:21:23 +00:00
|
|
|
metadata: ClassVar[dict[str, bytes]]
|
2022-02-05 17:32:20 +00:00
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def setUpClass(cls) -> None:
|
|
|
|
|
cls.repo_dir = os.path.join(
|
|
|
|
|
utils.TESTS_DIR, "repository_data", "repository", "metadata"
|
|
|
|
|
)
|
2022-06-06 20:24:57 +00:00
|
|
|
|
|
|
|
|
# Store class instances in this dict instead of creating them inside the
|
|
|
|
|
# test function in order to escape the need for reinitialization of the
|
|
|
|
|
# instances on each run of the test function.
|
|
|
|
|
cls.objects = {}
|
2022-02-05 17:32:20 +00:00
|
|
|
for md in TOP_LEVEL_ROLE_NAMES:
|
|
|
|
|
with open(os.path.join(cls.repo_dir, f"{md}.json"), "rb") as f:
|
2022-06-06 20:24:57 +00:00
|
|
|
data = f.read()
|
|
|
|
|
cls.objects[md.capitalize()] = Metadata.from_bytes(data).signed
|
|
|
|
|
|
|
|
|
|
cls.objects["Metadata"] = Metadata(cls.objects["Timestamp"], {})
|
|
|
|
|
cls.objects["Signed"] = cls.objects["Timestamp"]
|
2022-12-01 09:08:05 +00:00
|
|
|
cls.objects["Key"] = SSlibKey(
|
2022-06-06 20:24:57 +00:00
|
|
|
"id", "rsa", "rsassa-pss-sha256", {"public": "foo"}
|
|
|
|
|
)
|
|
|
|
|
cls.objects["Role"] = Role(["keyid1", "keyid2"], 3)
|
|
|
|
|
cls.objects["MetaFile"] = MetaFile(1, 12, {"sha256": "abc"})
|
|
|
|
|
cls.objects["DelegatedRole"] = DelegatedRole("a", [], 1, False, ["d"])
|
2022-05-17 16:27:58 +00:00
|
|
|
cls.objects["SuccinctRoles"] = SuccinctRoles(["keyid"], 1, 8, "foo")
|
2022-06-06 20:24:57 +00:00
|
|
|
cls.objects["Delegations"] = Delegations(
|
|
|
|
|
{"keyid": cls.objects["Key"]}, {"a": cls.objects["DelegatedRole"]}
|
|
|
|
|
)
|
|
|
|
|
cls.objects["TargetFile"] = TargetFile(
|
|
|
|
|
1, {"sha256": "abc"}, "file1.txt"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Keys are class names.
|
|
|
|
|
# Values are dictionaries containing attribute names and their new values.
|
2024-11-29 10:29:32 +00:00
|
|
|
classes_attributes_modifications = {
|
2022-06-06 20:24:57 +00:00
|
|
|
"Metadata": {"signed": None, "signatures": None},
|
|
|
|
|
"Signed": {"version": -1, "spec_version": "0.0.0"},
|
|
|
|
|
"Key": {"keyid": "a", "keytype": "foo", "scheme": "b", "keyval": "b"},
|
|
|
|
|
"Role": {"keyids": [], "threshold": 10},
|
|
|
|
|
"Root": {"consistent_snapshot": None, "keys": {}},
|
|
|
|
|
"MetaFile": {"version": None, "length": None, "hashes": {}},
|
|
|
|
|
"Timestamp": {"snapshot_meta": None},
|
|
|
|
|
"Snapshot": {"meta": None},
|
|
|
|
|
"DelegatedRole": {
|
|
|
|
|
"name": "",
|
|
|
|
|
"terminating": None,
|
|
|
|
|
"paths": [""],
|
|
|
|
|
"path_hash_prefixes": [""],
|
|
|
|
|
},
|
2022-05-17 16:27:58 +00:00
|
|
|
"SuccinctRoles": {"bit_length": 0, "name_prefix": ""},
|
2022-06-06 20:24:57 +00:00
|
|
|
"Delegations": {"keys": {}, "roles": {}},
|
|
|
|
|
"TargetFile": {"length": 0, "hashes": {}, "path": ""},
|
|
|
|
|
"Targets": {"targets": {}, "delegations": []},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@utils.run_sub_tests_with_dataset(classes_attributes_modifications)
|
2024-11-04 04:21:23 +00:00
|
|
|
def test_classes_eq_(self, test_case_data: dict[str, Any]) -> None:
|
2024-02-21 03:57:53 +00:00
|
|
|
obj = self.objects[self.case_name]
|
2022-02-05 17:32:20 +00:00
|
|
|
|
|
|
|
|
# Assert that obj is not equal to an object from another type
|
|
|
|
|
self.assertNotEqual(obj, "")
|
2022-06-06 20:24:57 +00:00
|
|
|
obj_2 = copy.deepcopy(obj)
|
2022-02-05 17:32:20 +00:00
|
|
|
# Assert that __eq__ works for equal objects.
|
2022-06-06 20:24:57 +00:00
|
|
|
self.assertEqual(obj, obj_2)
|
2022-02-05 17:32:20 +00:00
|
|
|
|
2022-06-06 20:24:57 +00:00
|
|
|
for attr, value in test_case_data.items():
|
|
|
|
|
original_value = getattr(obj_2, attr)
|
|
|
|
|
setattr(obj_2, attr, value)
|
|
|
|
|
# Assert that the original object != modified one.
|
|
|
|
|
self.assertNotEqual(obj, obj_2, f"Failed case: {attr}")
|
2022-04-13 13:19:14 +00:00
|
|
|
# Restore the old value of the attribute.
|
2022-06-06 20:24:57 +00:00
|
|
|
setattr(obj_2, attr, original_value)
|
2022-02-05 17:32:20 +00:00
|
|
|
|
Take order into account for certain cases
After we have dropped OrderedDict in https://github.com/theupdateframework/python-tuf/pull/1783/commits/e3b267e2e0799673ac99ccfccd3631628013201c
we are relying on python3.7+ default behavior to preserve the insertion
order, but there is one caveat.
When comparing dictionaries the order is still irrelevant compared to
OrderedDict. For example:
>>> OrderedDict([(1,1), (2,2)]) == OrderedDict([(2,2), (1,1)])
False
>>> dict([(1,1), (2,2)]) == dict([(2,2), (1,1)])
True
There are two special attributes, defined in the specification, where
the order makes a difference when comparing two objects:
- Metadata.signatures
- Targets.delegations.roles.
We want to make sure that the order in those two cases makes a
difference when comparing two objects and that's why those changes
are required inside two __eq__ implementations.
Signed-off-by: Martin Vrachev <mvrachev@vmware.com>
2022-02-04 17:49:44 +00:00
|
|
|
def test_md_eq_signatures_reversed_order(self) -> None:
|
|
|
|
|
# Test comparing objects with same signatures but different order.
|
|
|
|
|
|
|
|
|
|
# Remove all signatures and create new ones.
|
2022-06-06 20:24:57 +00:00
|
|
|
md: Metadata = self.objects["Metadata"]
|
Take order into account for certain cases
After we have dropped OrderedDict in https://github.com/theupdateframework/python-tuf/pull/1783/commits/e3b267e2e0799673ac99ccfccd3631628013201c
we are relying on python3.7+ default behavior to preserve the insertion
order, but there is one caveat.
When comparing dictionaries the order is still irrelevant compared to
OrderedDict. For example:
>>> OrderedDict([(1,1), (2,2)]) == OrderedDict([(2,2), (1,1)])
False
>>> dict([(1,1), (2,2)]) == dict([(2,2), (1,1)])
True
There are two special attributes, defined in the specification, where
the order makes a difference when comparing two objects:
- Metadata.signatures
- Targets.delegations.roles.
We want to make sure that the order in those two cases makes a
difference when comparing two objects and that's why those changes
are required inside two __eq__ implementations.
Signed-off-by: Martin Vrachev <mvrachev@vmware.com>
2022-02-04 17:49:44 +00:00
|
|
|
md.signatures = {"a": Signature("a", "a"), "b": Signature("b", "b")}
|
|
|
|
|
md_2 = copy.deepcopy(md)
|
|
|
|
|
# Reverse signatures order in md_2.
|
2023-09-07 12:42:47 +00:00
|
|
|
md_2.signatures = dict(reversed(md_2.signatures.items()))
|
Take order into account for certain cases
After we have dropped OrderedDict in https://github.com/theupdateframework/python-tuf/pull/1783/commits/e3b267e2e0799673ac99ccfccd3631628013201c
we are relying on python3.7+ default behavior to preserve the insertion
order, but there is one caveat.
When comparing dictionaries the order is still irrelevant compared to
OrderedDict. For example:
>>> OrderedDict([(1,1), (2,2)]) == OrderedDict([(2,2), (1,1)])
False
>>> dict([(1,1), (2,2)]) == dict([(2,2), (1,1)])
True
There are two special attributes, defined in the specification, where
the order makes a difference when comparing two objects:
- Metadata.signatures
- Targets.delegations.roles.
We want to make sure that the order in those two cases makes a
difference when comparing two objects and that's why those changes
are required inside two __eq__ implementations.
Signed-off-by: Martin Vrachev <mvrachev@vmware.com>
2022-02-04 17:49:44 +00:00
|
|
|
# Assert that both objects are not the same because of signatures order.
|
|
|
|
|
self.assertNotEqual(md, md_2)
|
|
|
|
|
|
|
|
|
|
# but if we fix the signatures order they will be equal
|
|
|
|
|
md_2.signatures = {"a": Signature("a", "a"), "b": Signature("b", "b")}
|
|
|
|
|
self.assertEqual(md, md_2)
|
|
|
|
|
|
2022-02-05 17:32:20 +00:00
|
|
|
def test_md_eq_special_signatures_tests(self) -> None:
|
|
|
|
|
# Test that metadata objects with different signatures are not equal.
|
2022-06-06 20:24:57 +00:00
|
|
|
md: Metadata = self.objects["Metadata"]
|
2022-02-05 17:32:20 +00:00
|
|
|
md_2 = copy.deepcopy(md)
|
|
|
|
|
md_2.signatures = {}
|
|
|
|
|
self.assertNotEqual(md, md_2)
|
|
|
|
|
|
|
|
|
|
# Test that metadata objects with empty signatures are equal
|
|
|
|
|
md.signatures = {}
|
|
|
|
|
self.assertEqual(md, md_2)
|
|
|
|
|
|
|
|
|
|
# Metadata objects with different signatures types are not equal.
|
2024-04-03 11:43:10 +00:00
|
|
|
md_2.signatures = "" # type: ignore[assignment]
|
2022-02-05 17:32:20 +00:00
|
|
|
self.assertNotEqual(md, md_2)
|
|
|
|
|
|
Take order into account for certain cases
After we have dropped OrderedDict in https://github.com/theupdateframework/python-tuf/pull/1783/commits/e3b267e2e0799673ac99ccfccd3631628013201c
we are relying on python3.7+ default behavior to preserve the insertion
order, but there is one caveat.
When comparing dictionaries the order is still irrelevant compared to
OrderedDict. For example:
>>> OrderedDict([(1,1), (2,2)]) == OrderedDict([(2,2), (1,1)])
False
>>> dict([(1,1), (2,2)]) == dict([(2,2), (1,1)])
True
There are two special attributes, defined in the specification, where
the order makes a difference when comparing two objects:
- Metadata.signatures
- Targets.delegations.roles.
We want to make sure that the order in those two cases makes a
difference when comparing two objects and that's why those changes
are required inside two __eq__ implementations.
Signed-off-by: Martin Vrachev <mvrachev@vmware.com>
2022-02-04 17:49:44 +00:00
|
|
|
def test_delegations_eq_roles_reversed_order(self) -> None:
|
|
|
|
|
# Test comparing objects with same delegated roles but different order.
|
|
|
|
|
role_one_dict = {
|
|
|
|
|
"keyids": ["keyid1"],
|
|
|
|
|
"name": "a",
|
|
|
|
|
"terminating": False,
|
|
|
|
|
"paths": ["fn1"],
|
|
|
|
|
"threshold": 1,
|
|
|
|
|
}
|
|
|
|
|
role_two_dict = {
|
|
|
|
|
"keyids": ["keyid2"],
|
|
|
|
|
"name": "b",
|
|
|
|
|
"terminating": True,
|
|
|
|
|
"paths": ["fn2"],
|
|
|
|
|
"threshold": 4,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
delegations_dict = {
|
|
|
|
|
"keys": {
|
|
|
|
|
"keyid2": {
|
|
|
|
|
"keytype": "ed25519",
|
|
|
|
|
"scheme": "ed25519",
|
|
|
|
|
"keyval": {"public": "bar"},
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
"roles": [role_one_dict, role_two_dict],
|
|
|
|
|
}
|
|
|
|
|
delegations = Delegations.from_dict(copy.deepcopy(delegations_dict))
|
|
|
|
|
|
|
|
|
|
# Create a second delegations obj with reversed roles order
|
|
|
|
|
delegations_2 = copy.deepcopy(delegations)
|
2022-06-02 17:24:57 +00:00
|
|
|
assert isinstance(delegations.roles, dict)
|
2023-09-07 12:42:47 +00:00
|
|
|
delegations_2.roles = dict(reversed(delegations.roles.items()))
|
Take order into account for certain cases
After we have dropped OrderedDict in https://github.com/theupdateframework/python-tuf/pull/1783/commits/e3b267e2e0799673ac99ccfccd3631628013201c
we are relying on python3.7+ default behavior to preserve the insertion
order, but there is one caveat.
When comparing dictionaries the order is still irrelevant compared to
OrderedDict. For example:
>>> OrderedDict([(1,1), (2,2)]) == OrderedDict([(2,2), (1,1)])
False
>>> dict([(1,1), (2,2)]) == dict([(2,2), (1,1)])
True
There are two special attributes, defined in the specification, where
the order makes a difference when comparing two objects:
- Metadata.signatures
- Targets.delegations.roles.
We want to make sure that the order in those two cases makes a
difference when comparing two objects and that's why those changes
are required inside two __eq__ implementations.
Signed-off-by: Martin Vrachev <mvrachev@vmware.com>
2022-02-04 17:49:44 +00:00
|
|
|
|
|
|
|
|
# Both objects are not the equal because of delegated roles order.
|
|
|
|
|
self.assertNotEqual(delegations, delegations_2)
|
|
|
|
|
|
|
|
|
|
# but if we fix the delegated roles order they will be equal
|
|
|
|
|
delegations_2.roles = delegations.roles
|
|
|
|
|
|
|
|
|
|
self.assertEqual(delegations, delegations_2)
|
|
|
|
|
|
2022-02-05 17:32:20 +00:00
|
|
|
|
|
|
|
|
# Run unit test.
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
utils.configure_test_logging(sys.argv)
|
|
|
|
|
unittest.main()
|