33331. Take token (JWT)
34342. Decode token
35353a. Extract `type` key from the payload portion and check if the token type is of interest
36- 3b. If the token is of the desired type, continue, if not, discard this token and move to the next one
36+ 3b. If the token is of the desired type, add it to list and continue, if not, discard this token and move to the next one
37374. Extract `jku` key from the header portion (value is a URL that returns a JWK public key set)
38385. Decode the complete token with the received public key
39396. Validate the token claims
@@ -171,7 +171,7 @@ async def retrieve_user_data(token):
171171 async with session .get (OAUTH2_CONFIG .userinfo ) as r :
172172 json_body = await r .json ()
173173 LOG .info ('Retrieve GA4GH user data from ELIXIR AAI.' )
174- return json_body .get ("ga4gh_visa_v1 " , None )
174+ return json_body .get ("ga4gh_passport_v1 " , None )
175175 except Exception :
176176 raise BeaconServerError ("Could not retrieve GA4GH user data from ELIXIR AAI." )
177177
@@ -191,11 +191,17 @@ async def get_jwk(url):
191191 pass
192192
193193
194- async def get_ga4gh_controlled (passports ):
195- """Retrieve dataset permissions from GA4GH passport visas."""
196- # We only want to get datasets once, thus the set which prevents duplicates
197- LOG .info ("Parsing GA4GH dataset permissions." )
198- datasets = set ()
194+ async def validate_passport (passport ):
195+ """Decode a passport and validate its contents."""
196+ LOG .debug ('Validating passport.' )
197+
198+ # Passports from `get_ga4gh_controlled()` will be of form
199+ # passport[0] -> encoded passport (JWT)
200+ # passport[1] -> unverified decoded header (contains `jku`)
201+ # Passports from `get_bona_fide_status()` will be of form
202+ # passport[0] -> encoded passport (JWT)
203+ # passport[1] -> unverified decoded header (contains `jku`)
204+ # passport[2] -> unverified decoded payload
199205
200206 # JWT decoding and validation settings
201207 # The `aud` claim will be ignored, because Beacon has no prior knowledge
@@ -207,42 +213,51 @@ async def get_ga4gh_controlled(passports):
207213 }
208214 }
209215
216+ # Attempt to decode the token and validate its contents
217+ # None of the exceptions are fatal, and will not raise an exception
218+ # Because even if the validation of one passport fails, the query
219+ # Should still continue in case other passports are valid
220+ try :
221+ # Get JWK for this passport from a third party provider
222+ # The JWK will be requested from a URL that is given in the `jku` claim in the header
223+ passport_key = await get_jwk (passport [1 ].get ('jku' ))
224+ # Decode the JWT using public key
225+ decoded_passport = jwt .decode (passport [0 ], passport_key , claims_options = claims_options )
226+ # Validate the JWT signature
227+ decoded_passport .validate ()
228+ # Return decoded and validated payload contents
229+ return decoded_passport
230+ except MissingClaimError as e :
231+ LOG .error (f'Missing claim(s): { e } ' )
232+ pass
233+ except ExpiredTokenError as e :
234+ LOG .error (f'Expired signature: { e } ' )
235+ pass
236+ except InvalidClaimError as e :
237+ LOG .error (f'Token info not corresponding with claim: { e } ' )
238+ pass
239+ except InvalidTokenError as e :
240+ LOG .error (f'Invalid authorization token: { e } ' )
241+ pass
242+
243+ return None
244+
245+
246+ async def get_ga4gh_controlled (passports ):
247+ """Retrieve dataset permissions from GA4GH passport visas."""
248+ # We only want to get datasets once, thus the set which prevents duplicates
249+ LOG .info ("Parsing GA4GH dataset permissions." )
250+ datasets = set ()
251+
210252 for passport in passports :
211- # passport[0] -> token
212- # passport[1] -> decoded unverified header
213- # Attempt to decode the token and validate its contents
214- # None of the exceptions are fatal, and will not raise an exception
215- # Because even if the validation of one passport fails, the query
216- # Should still continue in case other passports are valid
217- try :
218- # Get JWK for this passport from a third party provider
219- # The JWK will be requested from a URL that is given in the `jku` claim in the header
220- passport_key = await get_jwk (passport [1 ].get ('jku' ))
221- # Decode the JWT using public key
222- decoded_passport = jwt .decode (passport [0 ], passport_key , claims_options = claims_options )
223- # Validate the JWT signature
224- decoded_passport .validate ()
225- # Extract dataset id from validated passport
226- # The dataset value will be of form `https://institution.org/urn:dataset:1000`
227- # the extracted dataset will always be the last list element when split with `/`
228- dataset = decoded_passport .get ('ga4gh_visa_v1' , {}).get ('value' ).split ('/' )[- 1 ]
229- # Add dataset to set
230- datasets .update (dataset )
231- except MissingClaimError as e :
232- LOG .error (f'Missing claim(s): { e } ' )
233- pass
234- except ExpiredTokenError as e :
235- LOG .error (f'Expired signature: { e } ' )
236- pass
237- except InvalidClaimError as e :
238- LOG .error (f'Token info not corresponding with claim: { e } ' )
239- pass
240- except InvalidTokenError as e :
241- LOG .error (f'Invalid authorization token: { e } ' )
242- pass
243- except AttributeError as e :
244- LOG .error (f'GA4GH Visa did not contain a value: { e } ' )
245- pass
253+ # Decode passport and validate its contents
254+ validated_passport = await validate_passport (passport )
255+ # Extract dataset id from validated passport
256+ # The dataset value will be of form `https://institution.org/urn:dataset:1000`
257+ # the extracted dataset will always be the last list element when split with `/`
258+ dataset = validated_passport .get ('ga4gh_visa_v1' , {}).get ('value' ).split ('/' )[- 1 ]
259+ # Add dataset to set
260+ datasets .add (dataset )
246261
247262 return datasets
248263
@@ -255,81 +270,36 @@ async def get_ga4gh_bona_fide(passports):
255270 terms = False
256271 status = False
257272
258- # JWT decoding and validation settings
259- # The `aud` claim will be ignored, because Beacon has no prior knowledge
260- # as to where the token has originated from, and is therefore unable to
261- # verify the intended audience. Other claims will be validated as per usual.
262- claims_options = {
263- "aud" : {
264- "essential" : False
265- }
266- }
267-
268273 for passport in passports :
269- # passport[0] -> token
270- # passport[1] -> decoded unverified header
271- # passport[2] -> decoded unverified payload
272- # Attempt to decode the token and validate its contents
273- # None of the exceptions are fatal, and will not raise an exception
274- # Because even if the validation of one passport fails, the query
275- # Should still continue in case other passports are valid
276274 # Check for the `type` of visa to determine if to look for `terms` or `status`
277275 #
278276 # CHECK FOR TERMS
279277 if passport [2 ].get ('ga4gh_visa_v1' , {}).get ('type' ) == 'AcceptedTermsAndPolicies' :
280278 # Check if the visa contains a bona fide value
281279 if passport [2 ].get ('ga4gh_visa_v1' , {}).get ('value' ) == OAUTH2_CONFIG .bona_fide_value :
282280 # This passport has the correct type and value, next step is to validate it
283- try :
284- # Get JWK for this passport from a third party provider
285- # The JWK will be requested from a URL that is given in the `jku` claim in the header
286- passport_key = await get_jwk (passport [1 ].get ('jku' ))
287- # Decode the JWT using public key
288- decoded_passport = jwt .decode (passport [0 ], passport_key , claims_options = claims_options )
289- # Validate the JWT signature
290- decoded_passport .validate ()
291- # The token is validated, therefore the terms are accepted
292- terms = True
293- except MissingClaimError as e :
294- LOG .error (f'Missing claim(s): { e } ' )
295- pass
296- except ExpiredTokenError as e :
297- LOG .error (f'Expired signature: { e } ' )
298- pass
299- except InvalidClaimError as e :
300- LOG .error (f'Token info not corresponding with claim: { e } ' )
301- pass
302- except InvalidTokenError as e :
303- LOG .error (f'Invalid authorization token: { e } ' )
304- pass
281+ #
282+ # Decode passport and validate its contents
283+ # If the validation passes, terms will be set to True
284+ # If the validation fails, an exception will be raised
285+ # (and ignored since it's not fatal), and terms will remain False
286+ await validate_passport (passport )
287+ # The token is validated, therefore the terms are accepted
288+ terms = True
305289 #
306290 # CHECK FOR STATUS
307291 if passport [2 ].get ('ga4gh_visa_v1' , {}).get ('type' ) == 'ResearcherStatus' :
308292 # Check if the visa contains a bona fide value
309293 if passport [2 ].get ('ga4gh_visa_v1' , {}).get ('value' ) == OAUTH2_CONFIG .bona_fide_value :
310294 # This passport has the correct type and value, next step is to validate it
311- try :
312- # Get JWK for this passport from a third party provider
313- # The JWK will be requested from a URL that is given in the `jku` claim in the header
314- passport_key = await get_jwk (passport [1 ].get ('jku' ))
315- # Decode the JWT using public key
316- decoded_passport = jwt .decode (passport [0 ], passport_key , claims_options = claims_options )
317- # Validate the JWT signature
318- decoded_passport .validate ()
319- # The token is validated, therefore the status is accepted
320- status = True
321- except MissingClaimError as e :
322- LOG .error (f'Missing claim(s): { e } ' )
323- pass
324- except ExpiredTokenError as e :
325- LOG .error (f'Expired signature: { e } ' )
326- pass
327- except InvalidClaimError as e :
328- LOG .error (f'Token info not corresponding with claim: { e } ' )
329- pass
330- except InvalidTokenError as e :
331- LOG .error (f'Invalid authorization token: { e } ' )
332- pass
295+ #
296+ # Decode passport and validate its contents
297+ # If the validation passes, status will be set to True
298+ # If the validation fails, an exception will be raised
299+ # (and ignored since it's not fatal), and status will remain False
300+ await validate_passport (passport )
301+ # The token is validated, therefore the status is accepted
302+ status = True
333303
334304 if terms and status :
335305 # User has agreed to terms and has been recognized by a peer, return True for Bona Fide status
0 commit comments