Skip to content

Commit 6ea5372

Browse files
author
Martin Vrachev
committed
Take order into account for certain cases
After we have dropped OrderedDict in e3b267e 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>
1 parent a17ceda commit 6ea5372

2 files changed

Lines changed: 65 additions & 0 deletions

File tree

tests/test_metadata_eq_.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
import unittest
1313
from typing import Any, ClassVar, Dict
1414

15+
from securesystemslib.signer import Signature
16+
1517
from tests import utils
1618
from tuf.api.metadata import (
1719
TOP_LEVEL_ROLE_NAMES,
@@ -60,6 +62,23 @@ def test_metadata_eq_(self) -> None:
6062
setattr(md_2, attr, value)
6163
self.assertNotEqual(md, md_2, f"Failed case: {attr}")
6264

65+
def test_md_eq_signatures_reversed_order(self) -> None:
66+
# Test comparing objects with same signatures but different order.
67+
68+
# Remove all signatures and create new ones.
69+
md = Metadata.from_bytes(self.metadata["snapshot"])
70+
md.signatures = {"a": Signature("a", "a"), "b": Signature("b", "b")}
71+
md_2 = copy.deepcopy(md)
72+
# Reverse signatures order in md_2.
73+
# In python3.7 we need to cast to a list and then reverse.
74+
md_2.signatures = dict(reversed(list(md_2.signatures.items())))
75+
# Assert that both objects are not the same because of signatures order.
76+
self.assertNotEqual(md, md_2)
77+
78+
# but if we fix the signatures order they will be equal
79+
md_2.signatures = {"a": Signature("a", "a"), "b": Signature("b", "b")}
80+
self.assertEqual(md, md_2)
81+
6382
def test_md_eq_special_signatures_tests(self) -> None:
6483
# Test that metadata objects with different signatures are not equal.
6584
md = Metadata.from_bytes(self.metadata["snapshot"])
@@ -226,6 +245,48 @@ def test_targetfile_eq_(self) -> None:
226245
setattr(targetfile_2, "path", "")
227246
self.assertNotEqual(targetfile, targetfile_2)
228247

248+
def test_delegations_eq_roles_reversed_order(self) -> None:
249+
# Test comparing objects with same delegated roles but different order.
250+
role_one_dict = {
251+
"keyids": ["keyid1"],
252+
"name": "a",
253+
"terminating": False,
254+
"paths": ["fn1"],
255+
"threshold": 1,
256+
}
257+
role_two_dict = {
258+
"keyids": ["keyid2"],
259+
"name": "b",
260+
"terminating": True,
261+
"paths": ["fn2"],
262+
"threshold": 4,
263+
}
264+
265+
delegations_dict = {
266+
"keys": {
267+
"keyid2": {
268+
"keytype": "ed25519",
269+
"scheme": "ed25519",
270+
"keyval": {"public": "bar"},
271+
}
272+
},
273+
"roles": [role_one_dict, role_two_dict],
274+
}
275+
delegations = Delegations.from_dict(copy.deepcopy(delegations_dict))
276+
277+
# Create a second delegations obj with reversed roles order
278+
delegations_2 = copy.deepcopy(delegations)
279+
# In python3.7 we need to cast to a list and then reverse.
280+
delegations_2.roles = dict(reversed(list(delegations.roles.items())))
281+
282+
# Both objects are not the equal because of delegated roles order.
283+
self.assertNotEqual(delegations, delegations_2)
284+
285+
# but if we fix the delegated roles order they will be equal
286+
delegations_2.roles = delegations.roles
287+
288+
self.assertEqual(delegations, delegations_2)
289+
229290
def test_targets_eq_(self) -> None:
230291
md = Metadata.from_bytes(self.metadata["targets"])
231292
signed_copy: Targets = self.copy_and_simple_assert(md.signed)

tuf/api/metadata.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,8 @@ def __eq__(self, other: Any) -> bool:
133133

134134
return (
135135
self.signatures == other.signatures
136+
# Order of the signatures matters (see issue #1788).
137+
and list(self.signatures.items()) == list(other.signatures.items())
136138
and self.signed == other.signed
137139
and self.unrecognized_fields == other.unrecognized_fields
138140
)
@@ -1429,6 +1431,8 @@ def __eq__(self, other: Any) -> bool:
14291431

14301432
return (
14311433
self.keys == other.keys
1434+
# Order of the delegated roles matters (see issue #1788).
1435+
and list(self.roles.items()) == list(other.roles.items())
14321436
and self.roles == other.roles
14331437
and self.unrecognized_fields == other.unrecognized_fields
14341438
)

0 commit comments

Comments
 (0)