From 505aac68fdcf31d578c09528c1f1623241176b59 Mon Sep 17 00:00:00 2001 From: germangarces Date: Thu, 2 Jul 2026 08:19:55 +0200 Subject: [PATCH 1/4] fix(usage): Send raw licence signature bytes in Control Plane auth token --- api/organisations/usage_reporting/mappers.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/api/organisations/usage_reporting/mappers.py b/api/organisations/usage_reporting/mappers.py index 38292a749d70..eac9826b0961 100644 --- a/api/organisations/usage_reporting/mappers.py +++ b/api/organisations/usage_reporting/mappers.py @@ -18,9 +18,8 @@ def map_signature_to_control_plane_auth_token(signature: str) -> str: - return ( - base64.urlsafe_b64encode(signature.encode("utf-8")).decode("ascii").rstrip("=") - ) + raw_signature = base64.b64decode(signature) + return base64.urlsafe_b64encode(raw_signature).decode("ascii").rstrip("=") def map_usage_data_to_total_api_calls(usage_data: list[UsageData]) -> int: From 6bd2fae9ba385fb273512497d73c93cc043c5be1 Mon Sep 17 00:00:00 2001 From: germangarces Date: Thu, 2 Jul 2026 08:31:33 +0200 Subject: [PATCH 2/4] test(usage): Fix push auth-token expectations for corrected encoding --- .../unit/organisations/usage_reporting/test_services.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/api/tests/unit/organisations/usage_reporting/test_services.py b/api/tests/unit/organisations/usage_reporting/test_services.py index d88a2490eba4..93a424255dba 100644 --- a/api/tests/unit/organisations/usage_reporting/test_services.py +++ b/api/tests/unit/organisations/usage_reporting/test_services.py @@ -91,7 +91,7 @@ def test_push_snapshot__status_code__logs_expected_event( push_snapshot( base_url="https://cp.example.com/", snapshot=snapshot, - signature="sig", + signature="c2ln", ) # Then @@ -105,7 +105,7 @@ def test_push_snapshot__valid_snapshot__sends_bearer_authed_post( # Given mocked_post = mocker.patch("organisations.usage_reporting.services.requests.post") mocked_post.return_value.status_code = 201 - signature = "abc+/=def==" + signature = "++++ABE=" # When push_snapshot( @@ -118,8 +118,8 @@ def test_push_snapshot__valid_snapshot__sends_bearer_authed_post( mocked_post.assert_called_once() (url,), kwargs = mocked_post.call_args assert url == "https://cp.example.com/v1/public/usage" - # The unpadded base64url token the Control Plane receives on the wire - assert kwargs["headers"]["Authorization"] == "Bearer YWJjKy89ZGVmPT0" + # The signature's raw bytes, unpadded base64url-encoded for the wire. + assert kwargs["headers"]["Authorization"] == "Bearer ----ABE" assert kwargs["headers"]["Content-Type"] == "application/json" assert json.loads(kwargs["data"]) == { "timestamp": "2026-06-18T08:00:00Z", From c99614c1c403851f6fc9529d983f1de80aa15547 Mon Sep 17 00:00:00 2001 From: germangarces Date: Thu, 2 Jul 2026 11:34:49 +0200 Subject: [PATCH 3/4] refactor(usage): Name the mapper param signature_b64 to flag its encoding --- api/organisations/usage_reporting/mappers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/organisations/usage_reporting/mappers.py b/api/organisations/usage_reporting/mappers.py index eac9826b0961..cb12d567f6b0 100644 --- a/api/organisations/usage_reporting/mappers.py +++ b/api/organisations/usage_reporting/mappers.py @@ -17,8 +17,8 @@ MAX_PROJECT_USAGE_ROWS = 5_000 -def map_signature_to_control_plane_auth_token(signature: str) -> str: - raw_signature = base64.b64decode(signature) +def map_signature_to_control_plane_auth_token(signature_b64: str) -> str: + raw_signature = base64.b64decode(signature_b64) return base64.urlsafe_b64encode(raw_signature).decode("ascii").rstrip("=") From d32fcb09ea3ac2d908a97c4160e6832437a8f473 Mon Sep 17 00:00:00 2001 From: germangarces Date: Thu, 2 Jul 2026 12:00:34 +0200 Subject: [PATCH 4/4] refactor(usage): Carry signature_b64 name through the push call path --- api/organisations/usage_reporting/services.py | 6 +++--- .../organisations/usage_reporting/test_services.py | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/api/organisations/usage_reporting/services.py b/api/organisations/usage_reporting/services.py index 6474bba2afea..7f5d41369d68 100644 --- a/api/organisations/usage_reporting/services.py +++ b/api/organisations/usage_reporting/services.py @@ -31,12 +31,12 @@ def push_snapshot( *, base_url: str, snapshot: UsageSnapshot, - signature: str, + signature_b64: str, ) -> None: url = f"{base_url.rstrip('/')}{USAGE_ENDPOINT_PATH}" headers = { "Authorization": ( - f"Bearer {map_signature_to_control_plane_auth_token(signature)}" + f"Bearer {map_signature_to_control_plane_auth_token(signature_b64)}" ), "Content-Type": "application/json", } @@ -69,7 +69,7 @@ def push_usage_snapshots() -> None: push_snapshot( base_url=base_url, snapshot=map_organisation_to_usage_snapshot(organisation), - signature=organisation.licence.signature, + signature_b64=organisation.licence.signature, ) except Exception: logger.exception("snapshot.errored") diff --git a/api/tests/unit/organisations/usage_reporting/test_services.py b/api/tests/unit/organisations/usage_reporting/test_services.py index 93a424255dba..f27cbd1e003b 100644 --- a/api/tests/unit/organisations/usage_reporting/test_services.py +++ b/api/tests/unit/organisations/usage_reporting/test_services.py @@ -91,7 +91,7 @@ def test_push_snapshot__status_code__logs_expected_event( push_snapshot( base_url="https://cp.example.com/", snapshot=snapshot, - signature="c2ln", + signature_b64="c2ln", ) # Then @@ -105,13 +105,13 @@ def test_push_snapshot__valid_snapshot__sends_bearer_authed_post( # Given mocked_post = mocker.patch("organisations.usage_reporting.services.requests.post") mocked_post.return_value.status_code = 201 - signature = "++++ABE=" + signature_b64 = "++++ABE=" # When push_snapshot( base_url="https://cp.example.com/", snapshot=snapshot, - signature=signature, + signature_b64=signature_b64, ) # Then @@ -200,10 +200,10 @@ def test_push_usage_snapshots__licensed_organisations__pushes_each( # Then assert mocked_push.call_count == 2 mocked_push.assert_any_call( - base_url="https://cp.example.com", snapshot=snapshot, signature="sig-1" + base_url="https://cp.example.com", snapshot=snapshot, signature_b64="sig-1" ) mocked_push.assert_any_call( - base_url="https://cp.example.com", snapshot=snapshot, signature="sig-2" + base_url="https://cp.example.com", snapshot=snapshot, signature_b64="sig-2" ) @@ -233,5 +233,5 @@ def test_push_usage_snapshots__one_organisation_raises__continues( # Then - first organisation failed (logged) but second still pushed assert log.has("snapshot.errored", level="error") mocked_push.assert_called_once_with( - base_url="https://cp.example.com", snapshot=snapshot, signature="sig-2" + base_url="https://cp.example.com", snapshot=snapshot, signature_b64="sig-2" )