From 3023fdd7935ac8926266badbfae0750265ac7c9c Mon Sep 17 00:00:00 2001 From: Benedikt Reiser Date: Mon, 3 Aug 2026 10:13:40 +0200 Subject: [PATCH] fix: authenticate the client application version lookup The duplicate check that runs before uploading a new client application version was the only request in this uploader without credentials. Portal only serves the *current* version of a client application to anonymous callers, so once a version has been superseded the unauthenticated lookup reports it as missing: the "Version X already existing! Aborting." guard is skipped, the binary is uploaded, and the run then fails on the backend's uniqueness constraint with a bare 400 instead. Refs Innoactive/Portal-Backend#2280 Co-Authored-By: Claude Opus 5 --- portal_client/client_application_uploader.py | 7 ++++- tests/test_client_application_uploader.py | 31 ++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 tests/test_client_application_uploader.py diff --git a/portal_client/client_application_uploader.py b/portal_client/client_application_uploader.py index 00ba1d9..df0a6b1 100755 --- a/portal_client/client_application_uploader.py +++ b/portal_client/client_application_uploader.py @@ -45,7 +45,12 @@ def create_client_application_version(self, slug, **version_data): backoff.expo, requests.exceptions.ConnectionError, max_time=60 ) def retrieve_client_application_version(self, slug, version): - return requests.get(urljoin(self.base_url, f"{slug}/versions/{version}/")) + # authenticate: reads of superseded versions are not available anonymously, so without credentials this + # would report an already existing version as missing + return requests.get( + urljoin(self.base_url, f"{slug}/versions/{version}/"), + headers={"Authorization": get_authorization_header()}, + ) @backoff.on_exception( backoff.expo, requests.exceptions.ConnectionError, max_time=60 diff --git a/tests/test_client_application_uploader.py b/tests/test_client_application_uploader.py new file mode 100644 index 0000000..3e16572 --- /dev/null +++ b/tests/test_client_application_uploader.py @@ -0,0 +1,31 @@ +from unittest.mock import patch + +import requests_mock + +from portal_client.client_application_uploader import ClientApplicationApiClient + + +def test_version_lookup_is_authenticated(requests_mock: requests_mock.Mocker): + """The duplicate check before an upload has to authenticate: the backend only serves the *current* version of a + client application anonymously, so an unauthenticated lookup reports an already existing - but by now superseded - + version as missing, and the upload then fails later on the uniqueness constraint instead of aborting cleanly. + """ + # Given a backend that serves the details of an existing client application version + api_client = ClientApplicationApiClient(base_url="https://portal.test") + requests_mock.get( + "https://portal.test/api/client-applications/desktop-client/versions/1.2.3/", + json={"version": "1.2.3"}, + ) + + # When looking up that version + with patch( + "portal_client.client_application_uploader.get_authorization_header", + return_value="Bearer token", + ): + response = api_client.retrieve_client_application_version( + "desktop-client", "1.2.3" + ) + + # Then expect the request to have carried the credentials + assert response.status_code == 200 + assert requests_mock.last_request.headers["Authorization"] == "Bearer token"