From 88f7185ff1bd3ea8233c7626fa1167809bf9f7be Mon Sep 17 00:00:00 2001 From: Elia Migliore Date: Mon, 12 Feb 2024 20:46:55 +0100 Subject: [PATCH] fix: harden the order of keys that needs to be serialized --- src/karapace/key_format.py | 32 ++++++++++++++++++++++------ src/karapace/schema_registry_apis.py | 2 +- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/karapace/key_format.py b/src/karapace/key_format.py index e39d4c49e..64f0e525a 100644 --- a/src/karapace/key_format.py +++ b/src/karapace/key_format.py @@ -5,16 +5,31 @@ See LICENSE for details """ +from collections import OrderedDict from enum import Enum from karapace.typing import ArgJsonObject from karapace.utils import json_encode -from typing import Optional +from types import MappingProxyType +from typing import Final, Optional -SCHEMA_KEY_ORDER = ["keytype", "subject", "version", "magic"] -CONFIG_KEY_ORDER = ["keytype", "subject", "magic"] -NOOP_KEY_ORDER = ["keytype", "magic"] +# used by the OrderedDict for the relative order of keys. +SCHEMA_KEY_ORDER: Final[tuple[str, str, str, str]] = ("keytype", "subject", "version", "magic") +CONFIG_KEY_ORDER: Final[tuple[str, str, str]] = ("keytype", "subject", "magic") +NOOP_KEY_ORDER: Final[tuple[str, str]] = ("keytype", "magic") -CANONICAL_KEY_ORDERS = [SCHEMA_KEY_ORDER, CONFIG_KEY_ORDER, NOOP_KEY_ORDER] +KEY_ORDER = MappingProxyType( + { + tuple(sorted(SCHEMA_KEY_ORDER)): SCHEMA_KEY_ORDER, + tuple(sorted(CONFIG_KEY_ORDER)): CONFIG_KEY_ORDER, + tuple(sorted(NOOP_KEY_ORDER)): NOOP_KEY_ORDER, + } +) + +CANONICAL_KEY_ORDERS: tuple[tuple[str, str, str, str], tuple[str, str, str], tuple[str, str]] = ( + SCHEMA_KEY_ORDER, + CONFIG_KEY_ORDER, + NOOP_KEY_ORDER, +) class KeyMode(Enum): @@ -72,8 +87,11 @@ def format_key( corrected_key["version"] = key["version"] # Magic is the last element corrected_key["magic"] = key["magic"] - return json_encode(corrected_key, binary=True, sort_keys=False, compact=True) + + fixed_order = KEY_ORDER[tuple(sorted(corrected_key.keys()))] + fixed_order_dict = OrderedDict(list(sorted(corrected_key.items(), key=lambda t: fixed_order.index(t[0])))) + return json_encode(fixed_order_dict, binary=True, sort_keys=False, compact=True) def is_key_in_canonical_format(key: ArgJsonObject) -> bool: - return list(key.keys()) in CANONICAL_KEY_ORDERS + return tuple(key.keys()) in CANONICAL_KEY_ORDERS diff --git a/src/karapace/schema_registry_apis.py b/src/karapace/schema_registry_apis.py index fbb8f5a0c..ef778b008 100644 --- a/src/karapace/schema_registry_apis.py +++ b/src/karapace/schema_registry_apis.py @@ -1307,7 +1307,7 @@ async def _forward_request_remote( if auth_header is not None: headers["Authorization"] = auth_header - with async_timeout.timeout(timeout): + async with async_timeout.timeout(timeout): async with func(url, headers=headers, json=body) as response: if response.headers.get("content-type", "").startswith(JSON_CONTENT_TYPE): resp_content = await response.json()