10
10
import id # pylint: disable=redefined-builtin
11
11
import requests
12
12
13
- _GITHUB_STEP_SUMMARY = Path (os .getenv ( 'GITHUB_STEP_SUMMARY' ) )
13
+ _GITHUB_STEP_SUMMARY = Path (os .environ [ 'GITHUB_STEP_SUMMARY' ] )
14
14
15
15
# The top-level error message that gets rendered.
16
16
# This message wraps one of the other templates/messages defined below.
@@ -155,7 +155,7 @@ def warn(msg: str) -> None:
155
155
print (f'::warning::Potential workflow misconfiguration: { msg } ' , file = sys .stderr )
156
156
157
157
158
- def debug (msg : str ):
158
+ def debug (msg : str ) -> None :
159
159
print (f'::debug::{ msg .title ()} ' , file = sys .stderr )
160
160
161
161
@@ -166,7 +166,7 @@ def get_normalized_input(name: str) -> str | None:
166
166
return os .getenv (name .replace ('-' , '_' ))
167
167
168
168
169
- def assert_successful_audience_call (resp : requests .Response , domain : str ):
169
+ def assert_successful_audience_call (resp : requests .Response , domain : str ) -> None :
170
170
if resp .ok :
171
171
return
172
172
@@ -194,17 +194,32 @@ def assert_successful_audience_call(resp: requests.Response, domain: str):
194
194
)
195
195
196
196
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 :
198
209
_ , payload , _ = token .split ('.' , 2 )
199
210
200
211
# urlsafe_b64decode needs padding; JWT payloads don't contain any.
201
212
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
203
218
204
219
205
- def render_claims (claims : dict [ str , object ] ) -> str :
220
+ def render_claims (claims : TrustedPublishingClaims ) -> str :
206
221
def _get (name : str ) -> str : # noqa: WPS430
207
- return claims .get (name , 'MISSING' )
222
+ return str ( claims .get (name , 'MISSING' ) )
208
223
209
224
return _RENDERED_CLAIMS .format (
210
225
sub = _get ('sub' ),
@@ -218,7 +233,7 @@ def _get(name: str) -> str: # noqa: WPS430
218
233
)
219
234
220
235
221
- def warn_on_reusable_workflow (claims : dict [ str , object ] ) -> None :
236
+ def warn_on_reusable_workflow (claims : TrustedPublishingClaims ) -> None :
222
237
# A reusable workflow is identified by having different values
223
238
# for its workflow_ref (the initiating workflow) and job_workflow_ref
224
239
# (the reusable workflow).
@@ -228,7 +243,27 @@ def warn_on_reusable_workflow(claims: dict[str, object]) -> None:
228
243
if workflow_ref == job_workflow_ref :
229
244
return
230
245
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
232
267
233
268
234
269
def event_is_third_party_pr () -> bool :
@@ -243,7 +278,9 @@ def event_is_third_party_pr() -> bool:
243
278
return False
244
279
245
280
try :
246
- event = json .loads (Path (event_path ).read_bytes ())
281
+ event : ThirdPartyPullRequestGitHubEvent = json .loads (
282
+ Path (event_path ).read_bytes (),
283
+ )
247
284
except json .JSONDecodeError :
248
285
debug ('unexpected: GITHUB_EVENT_PATH does not contain valid JSON' )
249
286
return False
@@ -255,7 +292,7 @@ def event_is_third_party_pr() -> bool:
255
292
256
293
257
294
repository_url = get_normalized_input ('repository-url' )
258
- repository_domain = urlparse (repository_url ).netloc
295
+ repository_domain = str ( urlparse (repository_url ).netloc )
259
296
token_exchange_url = f'https://{ repository_domain } /_/oidc/mint-token'
260
297
261
298
# Indices are expected to support `https://{domain}/_/oidc/audience`,
@@ -264,12 +301,22 @@ def event_is_third_party_pr() -> bool:
264
301
audience_resp = requests .get (audience_url , timeout = 5 ) # S113 wants a timeout
265
302
assert_successful_audience_call (audience_resp , repository_domain )
266
303
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' ]
268
311
269
312
debug (f'selected trusted publishing exchange endpoint: { token_exchange_url } ' )
270
313
271
314
try :
272
315
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
+ )
273
320
except id .IdentityError as identity_error :
274
321
cause_msg_tmpl = (
275
322
_TOKEN_RETRIEVAL_FAILED_FORK_PR_MESSAGE
@@ -285,15 +332,30 @@ def event_is_third_party_pr() -> bool:
285
332
oidc_claims = extract_claims (oidc_token )
286
333
warn_on_reusable_workflow (oidc_claims )
287
334
335
+ oidc_token_payload : dict [str , str ] = {'token' : oidc_token }
288
336
# Now we can do the actual token exchange.
289
337
mint_token_resp = requests .post (
290
338
token_exchange_url ,
291
- json = { 'token' : oidc_token } ,
339
+ json = oidc_token_payload ,
292
340
timeout = 5 , # S113 wants a timeout
293
341
)
294
342
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
+
295
357
try :
296
- mint_token_payload = mint_token_resp .json ()
358
+ mint_token_payload : TrustedPublishingToken = mint_token_resp .json ()
297
359
except requests .JSONDecodeError :
298
360
# Token exchange failure normally produces a JSON error response, but
299
361
# we might have hit a server error instead.
0 commit comments