diff --git a/oidc-exchange.py b/oidc-exchange.py index c429c8b..7dea3b0 100644 --- a/oidc-exchange.py +++ b/oidc-exchange.py @@ -109,26 +109,26 @@ a few minutes and try again. def die(msg: str) -> NoReturn: - with _GITHUB_STEP_SUMMARY.open("a", encoding="utf-8") as io: + with _GITHUB_STEP_SUMMARY.open('a', encoding='utf-8') as io: print(_ERROR_SUMMARY_MESSAGE.format(message=msg), file=io) # HACK: GitHub Actions' annotations don't work across multiple lines naively; # translating `\n` into `%0A` (i.e., HTML percent-encoding) is known to work. # See: https://github.com/actions/toolkit/issues/193 - msg = msg.replace("\n", "%0A") - print(f"::error::Trusted publishing exchange failure: {msg}", file=sys.stderr) + msg = msg.replace('\n', '%0A') + print(f'::error::Trusted publishing exchange failure: {msg}', file=sys.stderr) sys.exit(1) def debug(msg: str): - print(f"::debug::{msg.title()}", file=sys.stderr) + print(f'::debug::{msg.title()}', file=sys.stderr) def get_normalized_input(name: str) -> str | None: - name = f"INPUT_{name.upper()}" + name = f'INPUT_{name.upper()}' if val := os.getenv(name): return val - return os.getenv(name.replace("-", "_")) + return os.getenv(name.replace('-', '_')) def assert_successful_audience_call(resp: requests.Response, domain: str): @@ -140,13 +140,13 @@ def assert_successful_audience_call(resp: requests.Response, domain: str): # This index supports OIDC, but forbids the client from using # it (either because it's disabled, ratelimited, etc.) die( - f"audience retrieval failed: repository at {domain} has trusted publishing disabled", + f'audience retrieval failed: repository at {domain} has trusted publishing disabled', ) case HTTPStatus.NOT_FOUND: # This index does not support OIDC. die( - "audience retrieval failed: repository at " - f"{domain} does not indicate trusted publishing support", + 'audience retrieval failed: repository at ' + f'{domain} does not indicate trusted publishing support', ) case other: status = HTTPStatus(other) @@ -154,67 +154,67 @@ def assert_successful_audience_call(resp: requests.Response, domain: str): # something we expect. This can happen if the index is broken, in maintenance mode, # misconfigured, etc. die( - "audience retrieval failed: repository at " - f"{domain} responded with unexpected {other}: {status.phrase}", + 'audience retrieval failed: repository at ' + f'{domain} responded with unexpected {other}: {status.phrase}', ) def render_claims(token: str) -> str: - _, payload, _ = token.split(".", 2) + _, payload, _ = token.split('.', 2) # urlsafe_b64decode needs padding; JWT payloads don't contain any. payload += "=" * (4 - (len(payload) % 4)) claims = json.loads(base64.urlsafe_b64decode(payload)) def _get(name: str) -> str: # noqa: WPS430 - return claims.get(name, "MISSING") + return claims.get(name, 'MISSING') return _RENDERED_CLAIMS.format( - sub=_get("sub"), - repository=_get("repository"), - repository_owner=_get("repository_owner"), - repository_owner_id=_get("repository_owner_id"), - job_workflow_ref=_get("job_workflow_ref"), - ref=_get("ref"), + sub=_get('sub'), + repository=_get('repository'), + repository_owner=_get('repository_owner'), + repository_owner_id=_get('repository_owner_id'), + job_workflow_ref=_get('job_workflow_ref'), + ref=_get('ref'), ) def event_is_third_party_pr() -> bool: # Non-`pull_request` events cannot be from third-party PRs. - if os.getenv("GITHUB_EVENT_NAME") != "pull_request": + if os.getenv('GITHUB_EVENT_NAME') != 'pull_request': return False - event_path = os.getenv("GITHUB_EVENT_PATH") + event_path = os.getenv('GITHUB_EVENT_PATH') if not event_path: # No GITHUB_EVENT_PATH indicates a weird GitHub or runner bug. - debug("unexpected: no GITHUB_EVENT_PATH to check") + debug('unexpected: no GITHUB_EVENT_PATH to check') return False try: event = json.loads(Path(event_path).read_bytes()) except json.JSONDecodeError: - debug("unexpected: GITHUB_EVENT_PATH does not contain valid JSON") + debug('unexpected: GITHUB_EVENT_PATH does not contain valid JSON') return False try: - return event["pull_request"]["head"]["repo"]["fork"] + return event['pull_request']['head']['repo']['fork'] except KeyError: return False -repository_url = get_normalized_input("repository-url") +repository_url = get_normalized_input('repository-url') repository_domain = urlparse(repository_url).netloc -token_exchange_url = f"https://{repository_domain}/_/oidc/mint-token" +token_exchange_url = f'https://{repository_domain}/_/oidc/mint-token' # Indices are expected to support `https://{domain}/_/oidc/audience`, # which tells OIDC exchange clients which audience to use. -audience_url = f"https://{repository_domain}/_/oidc/audience" +audience_url = f'https://{repository_domain}/_/oidc/audience' audience_resp = requests.get(audience_url) assert_successful_audience_call(audience_resp, repository_domain) -oidc_audience = audience_resp.json()["audience"] +oidc_audience = audience_resp.json()['audience'] -debug(f"selected trusted publishing exchange endpoint: {token_exchange_url}") +debug(f'selected trusted publishing exchange endpoint: {token_exchange_url}') try: oidc_token = id.detect_credential(audience=oidc_audience) @@ -229,7 +229,7 @@ except id.IdentityError as identity_error: # Now we can do the actual token exchange. mint_token_resp = requests.post( token_exchange_url, - json={"token": oidc_token}, + json={'token': oidc_token}, ) try: @@ -246,9 +246,9 @@ except requests.JSONDecodeError: # On failure, the JSON response includes the list of errors that # occurred during minting. if not mint_token_resp.ok: - reasons = "\n".join( - f"* `{error['code']}`: {error['description']}" - for error in mint_token_payload["errors"] + reasons = '\n'.join( + f'* `{error['code']}`: {error['description']}' + for error in mint_token_payload['errors'] ) rendered_claims = render_claims(oidc_token) @@ -260,12 +260,12 @@ if not mint_token_resp.ok: ), ) -pypi_token = mint_token_payload.get("token") +pypi_token = mint_token_payload.get('token') if pypi_token is None: die(_SERVER_TOKEN_RESPONSE_MALFORMED_MESSAGE) # Mask the newly minted PyPI token, so that we don't accidentally leak it in logs. -print(f"::add-mask::{pypi_token}", file=sys.stderr) +print(f'::add-mask::{pypi_token}', file=sys.stderr) # This final print will be captured by the subshell in `twine-upload.sh`. print(pypi_token)