1010import id # pylint: disable=redefined-builtin
1111import requests
1212
13- _GITHUB_STEP_SUMMARY = Path (os .getenv ( 'GITHUB_STEP_SUMMARY' ) )
13+ _GITHUB_STEP_SUMMARY = Path (os .environ [ 'GITHUB_STEP_SUMMARY' ] )
1414
1515# The top-level error message that gets rendered.
1616# This message wraps one of the other templates/messages defined below.
@@ -155,7 +155,7 @@ def warn(msg: str) -> None:
155155 print (f'::warning::Potential workflow misconfiguration: { msg } ' , file = sys .stderr )
156156
157157
158- def debug (msg : str ):
158+ def debug (msg : str ) -> None :
159159 print (f'::debug::{ msg .title ()} ' , file = sys .stderr )
160160
161161
@@ -166,7 +166,7 @@ def get_normalized_input(name: str) -> str | None:
166166 return os .getenv (name .replace ('-' , '_' ))
167167
168168
169- def assert_successful_audience_call (resp : requests .Response , domain : str ):
169+ def assert_successful_audience_call (resp : requests .Response , domain : str ) -> None :
170170 if resp .ok :
171171 return
172172
@@ -194,17 +194,32 @@ def assert_successful_audience_call(resp: requests.Response, domain: str):
194194 )
195195
196196
197- def extract_claims (token : str ) -> dict [str , object ]:
197+ class TrustedPublishingClaims (t .TypedDict ):
198+ sub : str
199+ repository : str
200+ repository_owner : str
201+ repository_owner_id : str
202+ workflow_ref : str
203+ job_workflow_ref : str
204+ ref : str
205+ environment : str
206+
207+
208+ def extract_claims (token : str ) -> TrustedPublishingClaims :
198209 _ , payload , _ = token .split ('.' , 2 )
199210
200211 # urlsafe_b64decode needs padding; JWT payloads don't contain any.
201212 payload += '=' * (4 - (len (payload ) % 4 ))
202- return json .loads (base64 .urlsafe_b64decode (payload ))
213+
214+ oidc_claims : TrustedPublishingClaims = json .loads (
215+ base64 .urlsafe_b64decode (payload ),
216+ )
217+ return oidc_claims
203218
204219
205- def render_claims (claims : dict [ str , object ] ) -> str :
220+ def render_claims (claims : TrustedPublishingClaims ) -> str :
206221 def _get (name : str ) -> str : # noqa: WPS430
207- return claims .get (name , 'MISSING' )
222+ return str ( claims .get (name , 'MISSING' ) )
208223
209224 return _RENDERED_CLAIMS .format (
210225 sub = _get ('sub' ),
@@ -218,7 +233,7 @@ def _get(name: str) -> str: # noqa: WPS430
218233 )
219234
220235
221- def warn_on_reusable_workflow (claims : dict [ str , object ] ) -> None :
236+ def warn_on_reusable_workflow (claims : TrustedPublishingClaims ) -> None :
222237 # A reusable workflow is identified by having different values
223238 # for its workflow_ref (the initiating workflow) and job_workflow_ref
224239 # (the reusable workflow).
@@ -228,7 +243,27 @@ def warn_on_reusable_workflow(claims: dict[str, object]) -> None:
228243 if workflow_ref == job_workflow_ref :
229244 return
230245
231- warn (_REUSABLE_WORKFLOW_WARNING .format_map (locals ()))
246+ warn (
247+ _REUSABLE_WORKFLOW_WARNING .format (
248+ workflow_ref = workflow_ref , job_workflow_ref = job_workflow_ref ,
249+ ),
250+ )
251+
252+
253+ class PullRequestRepoGitHubEventObject (t .TypedDict ):
254+ fork : bool
255+
256+
257+ class PullRequestHeadGitHubEventObject (t .TypedDict ):
258+ repo : PullRequestRepoGitHubEventObject
259+
260+
261+ class PullRequestGitHubEventObject (t .TypedDict ):
262+ head : PullRequestHeadGitHubEventObject
263+
264+
265+ class ThirdPartyPullRequestGitHubEvent (t .TypedDict ):
266+ pull_request : PullRequestGitHubEventObject
232267
233268
234269def event_is_third_party_pr () -> bool :
@@ -243,7 +278,9 @@ def event_is_third_party_pr() -> bool:
243278 return False
244279
245280 try :
246- event = json .loads (Path (event_path ).read_bytes ())
281+ event : ThirdPartyPullRequestGitHubEvent = json .loads (
282+ Path (event_path ).read_bytes (),
283+ )
247284 except json .JSONDecodeError :
248285 debug ('unexpected: GITHUB_EVENT_PATH does not contain valid JSON' )
249286 return False
@@ -255,7 +292,7 @@ def event_is_third_party_pr() -> bool:
255292
256293
257294repository_url = get_normalized_input ('repository-url' )
258- repository_domain = urlparse (repository_url ).netloc
295+ repository_domain = str ( urlparse (repository_url ).netloc )
259296token_exchange_url = f'https://{ repository_domain } /_/oidc/mint-token'
260297
261298# Indices are expected to support `https://{domain}/_/oidc/audience`,
@@ -264,12 +301,22 @@ def event_is_third_party_pr() -> bool:
264301audience_resp = requests .get (audience_url , timeout = 5 ) # S113 wants a timeout
265302assert_successful_audience_call (audience_resp , repository_domain )
266303
267- oidc_audience = audience_resp .json ()['audience' ]
304+
305+ class TrustedPublishingAudience (t .TypedDict ):
306+ audience : str
307+
308+
309+ oidc_audience_resp : TrustedPublishingAudience = audience_resp .json ()
310+ oidc_audience = oidc_audience_resp ['audience' ]
268311
269312debug (f'selected trusted publishing exchange endpoint: { token_exchange_url } ' )
270313
271314try :
272315 oidc_token = id .detect_credential (audience = oidc_audience )
316+ if oidc_token is None :
317+ raise id .IdentityError (
318+ 'Attempted to discover OIDC in broken environment' ,
319+ )
273320except id .IdentityError as identity_error :
274321 cause_msg_tmpl = (
275322 _TOKEN_RETRIEVAL_FAILED_FORK_PR_MESSAGE
@@ -285,15 +332,30 @@ def event_is_third_party_pr() -> bool:
285332oidc_claims = extract_claims (oidc_token )
286333warn_on_reusable_workflow (oidc_claims )
287334
335+ oidc_token_payload : dict [str , str ] = {'token' : oidc_token }
288336# Now we can do the actual token exchange.
289337mint_token_resp = requests .post (
290338 token_exchange_url ,
291- json = { 'token' : oidc_token } ,
339+ json = oidc_token_payload ,
292340 timeout = 5 , # S113 wants a timeout
293341)
294342
343+
344+ class TrustedPublishingTokenRetrievalError (t .TypedDict ):
345+ code : str
346+ description : str
347+
348+
349+ class TrustedPublishingToken (t .TypedDict ):
350+ message : str
351+ errors : list [TrustedPublishingTokenRetrievalError ]
352+ token : str
353+ success : bool
354+ expires : int
355+
356+
295357try :
296- mint_token_payload = mint_token_resp .json ()
358+ mint_token_payload : TrustedPublishingToken = mint_token_resp .json ()
297359except requests .JSONDecodeError :
298360 # Token exchange failure normally produces a JSON error response, but
299361 # we might have hit a server error instead.
0 commit comments