From d590a754eeb3551f7617433b32596b73ebce9bdc Mon Sep 17 00:00:00 2001 From: per1234 Date: Tue, 23 Jan 2024 18:14:31 -0800 Subject: [PATCH] Don't include HTTP headers from GitHub API request in redirected artifact download request In the use case where the "arduino/report-size-deltas" action is ran from a GitHub Actions workflow triggered by a `schedule` event, it downloads the sketches report file from a workflow artifact. The GitHub REST API is used to perform this artifact download. The artifact download process is: 1. Action sends request to `/repos/{owner}/{repo}/actions/artifacts/{artifact_id}/{archive_format}` endpoint 2. API responds with HTTP 302 status 3. Action sends request to temporary file download URL provided by the API response 4. Artifact file is downloaded The API request at step (1) must be authenticated using a GitHub access token. This token is passed via the `Authorization` HTTP header in the request. No authentication is required for the download request at step (3). The `urllib.request` Python module is used to perform the HTTP requests. By default, this module passes the headers from the original request to the redirect request. Although these headers were superfluous, they didn't affect the download request when the target artifact was of the v1 format generated by version 3.x and earlier of the "actions/upload-artifact" action. A new v2 artifact format was introduced in the 4.0.0 release of the "actions/upload-artifact" action. Previously, the request at step (3) of the artifact download procedure would fail when the target artifact had the v2 format: ``` urllib.error.HTTPError: HTTP Error 400: Authentication information is not given in the correct format. Check the value of Authorization header. Error: HTTPError: HTTP Error 400: Authentication information is not given in the correct format. Check the value of Authorization header. InvalidAuthenticationInfoAuthentication information is not given in the correct format. Check the value of Authorization header. RequestId:1f13170a-001e-0076-5f5d-4e8d15000000 Time:2024-01-24T00:35:22.8264229Z ``` The cause of the failure was the inclusion of the `Authorization` HTTP header in the download request. The `urllib.request` Python module can be configured to pass a header in the original request but not in the redirected request by defining the header via the `Request.add_unredirected_header` method instead of in the `Request` instantiation. This provides compatibility for using the action with v2 format artifacts. --- reportsizedeltas/reportsizedeltas.py | 13 +++++------ .../tests/test_reportsizedeltas.py | 22 +++++++++++++------ 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/reportsizedeltas/reportsizedeltas.py b/reportsizedeltas/reportsizedeltas.py index 8c6e448..b9be40c 100644 --- a/reportsizedeltas/reportsizedeltas.py +++ b/reportsizedeltas/reportsizedeltas.py @@ -625,14 +625,11 @@ def raw_http_request(self, url: str, data: bytes | None = None): logger.info("Opening URL: " + url) - headers = { - "Accept": "application/vnd.github+json", - "Authorization": "Bearer " + self.token, - # GitHub recommends using user name as User-Agent (https://developer.github.com/v3/#user-agent-required) - "User-Agent": self.repository_name.split("/")[0], - "X-GitHub-Api-Version": "2022-11-28", - } - request = urllib.request.Request(url=url, headers=headers, data=data) + request = urllib.request.Request(url=url, data=data) + request.add_unredirected_header(key="Accept", val="application/vnd.github+json") + request.add_unredirected_header(key="Authorization", val="Bearer " + self.token) + request.add_unredirected_header(key="User-Agent", val=self.repository_name.split("/")[0]) + request.add_unredirected_header(key="X-GitHub-Api-Version", val="2022-11-28") retry_count = 0 while True: diff --git a/reportsizedeltas/tests/test_reportsizedeltas.py b/reportsizedeltas/tests/test_reportsizedeltas.py index 454f7a3..4fe96cd 100644 --- a/reportsizedeltas/tests/test_reportsizedeltas.py +++ b/reportsizedeltas/tests/test_reportsizedeltas.py @@ -876,7 +876,13 @@ def test_raw_http_request(mocker): token = "test_token" url = "https://api.github.com/repo/foo/bar" data = "test_data" - request = "test_request" + + class Request: + def add_unredirected_header(self): + pass # pragma: no cover + + mocker.patch.object(Request, "add_unredirected_header") + request = Request() urlopen_return = unittest.mock.sentinel.urlopen_return report_size_deltas = get_reportsizedeltas_object(repository_name=user_name + "/FooRepositoryName", token=token) @@ -889,14 +895,16 @@ def test_raw_http_request(mocker): urllib.request.Request.assert_called_once_with( url=url, - headers={ - "Accept": "application/vnd.github+json", - "Authorization": "Bearer " + token, - "User-Agent": user_name, - "X-GitHub-Api-Version": "2022-11-28", - }, data=data, ) + request.add_unredirected_header.assert_has_calls( + calls=[ + unittest.mock.call(key="Accept", val="application/vnd.github+json"), + unittest.mock.call(key="Authorization", val="Bearer " + token), + unittest.mock.call(key="User-Agent", val=user_name), + unittest.mock.call(key="X-GitHub-Api-Version", val="2022-11-28"), + ] + ) # URL is subject to GitHub API rate limiting report_size_deltas.handle_rate_limiting.assert_called_once()