Skip to content
This repository was archived by the owner on Oct 23, 2023. It is now read-only.

Commit ff4151b

Browse files
teemukatajablankdots
authored andcommitted
fix controlled datasets, move validation and decoding into function #130
1 parent aab89ea commit ff4151b

1 file changed

Lines changed: 73 additions & 103 deletions

File tree

beacon_api/permissions/ga4gh.py

Lines changed: 73 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
1. Take token (JWT)
3434
2. Decode token
3535
3a. 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
3737
4. Extract `jku` key from the header portion (value is a URL that returns a JWK public key set)
3838
5. Decode the complete token with the received public key
3939
6. 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

Comments
 (0)