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

Commit d43543b

Browse files
committed
unit tests for ga4gh permissions
1 parent 52c51af commit d43543b

2 files changed

Lines changed: 173 additions & 40 deletions

File tree

tests/test_basic.py

Lines changed: 142 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,16 @@
44
from beacon_api.conf.config import init_db_pool
55
from beacon_api.api.query import access_resolution
66
from beacon_api.utils.validate import token_scheme_check, verify_aud_claim
7-
from beacon_api.permissions.ga4gh import get_ga4gh_controlled, get_ga4gh_bona_fide
7+
from beacon_api.permissions.ga4gh import get_ga4gh_controlled, get_ga4gh_bona_fide, validate_passport
8+
from beacon_api.permissions.ga4gh import check_ga4gh_token, decode_passport, get_ga4gh_permissions
9+
from deploy.test.mock_auth import generate_token
810
from .test_app import PARAMS
911
from testfixtures import TempDirectory
1012
from test.support import EnvironmentVarGuard
13+
# Hack to import a function from beyond top-level package
14+
# used for test_decode_passport() to get a real token
15+
import sys
16+
sys.path.append("..")
1117

1218

1319
def mock_token(bona_fide, permissions, auth):
@@ -17,6 +23,21 @@ def mock_token(bona_fide, permissions, auth):
1723
"authenticated": auth}
1824

1925

26+
class MockDecodedPassport:
27+
"""Mock JWT."""
28+
29+
def __init__(self, validated=True):
30+
"""Initialise mock JWT."""
31+
self.validated = validated
32+
33+
def validate(self):
34+
"""Invoke validate."""
35+
if self.validated:
36+
return True
37+
else:
38+
raise Exception
39+
40+
2041
class MockBeaconDB:
2142
"""BeaconDB mock.
2243
@@ -290,31 +311,37 @@ def test_access_resolution_controlled_never_reached2(self):
290311
with self.assertRaises(aiohttp.web_exceptions.HTTPForbidden):
291312
access_resolution(request, token, host, [], [], [8])
292313

293-
async def test_ga4gh_controlled(self):
314+
@asynctest.mock.patch('beacon_api.permissions.ga4gh.validate_passport')
315+
async def test_ga4gh_controlled(self, m_validation):
294316
"""Test ga4gh permissions claim parsing."""
295-
# Good test: claims OK, userinfo OK
296-
token_claim = []
297-
datasets = await get_ga4gh_controlled(token_claim)
298-
self.assertEqual(datasets, set()) # has permissions
299-
# # Bad test: no claims, userinfo OK
300-
# token_claim = []
301-
# token = 'this_is_a_jwt'
302-
# datasets = await get_ga4gh_controlled(token, token_claim)
303-
# self.assertEqual(datasets, set()) # doesn't have permissions
304-
# # Bad test: claims OK, no userinfo
305-
# userinfo.return_value = {}
306-
# token_claim = ["ga4gh.ControlledAccessGrants"]
307-
# token = 'this_is_a_jwt'
308-
# datasets = await get_ga4gh_controlled(token, token_claim)
309-
# self.assertEqual(datasets, set()) # doesn't have permissions
310-
# # Bad test: no claims, no userinfo
311-
# token_claim = []
312-
# token = 'this_is_a_jwt'
313-
# datasets = await get_ga4gh_controlled(token, token_claim)
314-
# self.assertEqual(datasets, set()) # doesn't have permissions
315-
316-
@asynctest.mock.patch('beacon_api.permissions.ga4gh.retrieve_user_data')
317-
async def test_ga4gh_bona_fide(self, userinfo):
317+
# Test: no passports, no permissions
318+
datasets = await get_ga4gh_controlled([])
319+
self.assertEqual(datasets, set())
320+
# Test: 1 passport, 1 unique dataset, 1 permission
321+
passport = {"ga4gh_visa_v1": {"type": "ControlledAccessGrants",
322+
"value": "https://institution.org/EGAD01",
323+
"source": "https://ga4gh.org/duri/no_org",
324+
"by": "self",
325+
"asserted": 1539069213,
326+
"expires": 4694742813}}
327+
m_validation.return_value = passport
328+
dataset = await get_ga4gh_controlled([{}]) # one passport
329+
self.assertEqual(dataset, {'EGAD01'})
330+
# Test: 2 passports, 1 unique dataset, 1 permission (permissions must not be duplicated)
331+
passport = {"ga4gh_visa_v1": {"type": "ControlledAccessGrants",
332+
"value": "https://institution.org/EGAD01",
333+
"source": "https://ga4gh.org/duri/no_org",
334+
"by": "self",
335+
"asserted": 1539069213,
336+
"expires": 4694742813}}
337+
m_validation.return_value = passport
338+
dataset = await get_ga4gh_controlled([{}, {}]) # two passports
339+
self.assertEqual(dataset, {'EGAD01'})
340+
# Test: 2 passports, 2 unique datasets, 2 permissions
341+
# Can't test this case with the current design!
342+
# Would need a way for validate_passport() to mock two different results
343+
344+
async def test_ga4gh_bona_fide(self):
318345
"""Test ga4gh statuses claim parsing."""
319346
passports = [("enc", "header", {
320347
"ga4gh_visa_v1": {"type": "AcceptedTermsAndPolicies",
@@ -331,14 +358,102 @@ async def test_ga4gh_bona_fide(self, userinfo):
331358
"by": "peer",
332359
"asserted": 1539017776,
333360
"expires": 1593165413}})]
334-
# Good test: claims OK, userinfo OK
361+
# Good test: both required passport types contained the correct value
335362
bona_fide_status = await get_ga4gh_bona_fide(passports)
336363
self.assertEqual(bona_fide_status, True) # has bona fide
337-
# Bad test: no claims, userinfo OK
364+
# Bad test: missing passports of required type
338365
passports_empty = []
339366
bona_fide_status = await get_ga4gh_bona_fide(passports_empty)
340367
self.assertEqual(bona_fide_status, False) # doesn't have bona fide
341368

369+
@asynctest.mock.patch('beacon_api.permissions.ga4gh.get_jwk')
370+
@asynctest.mock.patch('beacon_api.permissions.ga4gh.jwt')
371+
async def test_validate_passport(self, m_jwt, m_jwk):
372+
"""Test passport validation."""
373+
m_jwk.return_value = 'jwk'
374+
# Test: validation passed
375+
m_jwt.return_value = MockDecodedPassport()
376+
await validate_passport({})
377+
#
378+
# This test doesn't work
379+
#
380+
# # Test: validation failed
381+
# m_jwt.return_value = MockDecodedPassport(validated=False)
382+
# with self.assertRaises(Exception):
383+
# await validate_passport({})
384+
385+
@asynctest.mock.patch('beacon_api.permissions.ga4gh.get_ga4gh_permissions')
386+
async def test_check_ga4gh_token(self, m_get_perms):
387+
"""Test token scopes."""
388+
# Test: no scope found
389+
decoded_data = {}
390+
dataset_permissions, bona_fide_status = await check_ga4gh_token(decoded_data, {}, False, set())
391+
self.assertEqual(dataset_permissions, set())
392+
self.assertEqual(bona_fide_status, False)
393+
# Test: scope is ok, but no claims
394+
decoded_data = {'scope': ''}
395+
dataset_permissions, bona_fide_status = await check_ga4gh_token(decoded_data, {}, False, set())
396+
self.assertEqual(dataset_permissions, set())
397+
self.assertEqual(bona_fide_status, False)
398+
# Test: scope is ok, claims are ok
399+
m_get_perms.return_value = {'EGAD01'}, True
400+
decoded_data = {'scope': 'openid ga4gh_passport_v1'}
401+
dataset_permissions, bona_fide_status = await check_ga4gh_token(decoded_data, {}, False, set())
402+
self.assertEqual(dataset_permissions, {'EGAD01'})
403+
self.assertEqual(bona_fide_status, True)
404+
405+
async def test_decode_passport(self):
406+
"""Test key-less JWT decoding."""
407+
_, token, _ = generate_token()
408+
header, payload = await decode_passport(token)
409+
self.assertEqual(header.get('alg'), 'RS256')
410+
self.assertEqual(payload.get('iss'), 'http://test.csc.fi')
411+
412+
@asynctest.mock.patch('beacon_api.permissions.ga4gh.get_ga4gh_bona_fide')
413+
@asynctest.mock.patch('beacon_api.permissions.ga4gh.get_ga4gh_controlled')
414+
@asynctest.mock.patch('beacon_api.permissions.ga4gh.decode_passport')
415+
@asynctest.mock.patch('beacon_api.permissions.ga4gh.retrieve_user_data')
416+
async def test_get_ga4gh_permissions(self, m_userinfo, m_decode, m_controlled, m_bonafide):
417+
"""Test GA4GH permissions main function."""
418+
# Test: no data (nothing)
419+
m_userinfo.return_value = [{}]
420+
header = {}
421+
payload = {}
422+
m_decode.return_value = header, payload
423+
m_controlled.return_value = set()
424+
m_bonafide.return_value = False
425+
dataset_permissions, bona_fide_status = await get_ga4gh_permissions('token')
426+
self.assertEqual(dataset_permissions, set())
427+
self.assertEqual(bona_fide_status, False)
428+
# Test: permissions
429+
m_userinfo.return_value = [{}]
430+
header = {}
431+
payload = {
432+
'ga4gh_visa_v1': {
433+
'type': 'ControlledAccessGrants'
434+
}
435+
}
436+
m_decode.return_value = header, payload
437+
m_controlled.return_value = {'EGAD01'}
438+
m_bonafide.return_value = False
439+
dataset_permissions, bona_fide_status = await get_ga4gh_permissions('token')
440+
self.assertEqual(dataset_permissions, {'EGAD01'})
441+
self.assertEqual(bona_fide_status, False)
442+
# Test: bona fide
443+
m_userinfo.return_value = [{}]
444+
header = {}
445+
payload = {
446+
'ga4gh_visa_v1': {
447+
'type': 'ResearcherStatus'
448+
}
449+
}
450+
m_decode.return_value = header, payload
451+
m_controlled.return_value = set()
452+
m_bonafide.return_value = True
453+
dataset_permissions, bona_fide_status = await get_ga4gh_permissions('token')
454+
self.assertEqual(dataset_permissions, set())
455+
self.assertEqual(bona_fide_status, True)
456+
342457

343458
if __name__ == '__main__':
344459
asynctest.main()

tests/test_response.py

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import asynctest
44
from beacon_api.schemas import load_schema
55
from beacon_api.utils.validate import get_key
6-
from beacon_api.permissions.ga4gh import retrieve_user_data
6+
from beacon_api.permissions.ga4gh import retrieve_user_data, get_jwk
77
import jsonschema
88
import json
99
import aiohttp
@@ -110,19 +110,19 @@ async def test_bad_retrieve_user_data(self, m):
110110
with self.assertRaises(aiohttp.web_exceptions.HTTPInternalServerError):
111111
await retrieve_user_data('bad_token')
112112

113-
# @aioresponses()
114-
# async def test_bad_none_retrieve_user_data(self, m):
115-
# """Test a failing userdata call because response didn't have ga4gh format."""
116-
# m.get("http://test.csc.fi/userinfo", payload={"not_ga4gh": [{}]})
117-
# user_data = await retrieve_user_data('good_token')
118-
# self.assertEqual(user_data, None)
113+
@aioresponses()
114+
async def test_bad_none_retrieve_user_data(self, m):
115+
"""Test a failing userdata call because response didn't have ga4gh format."""
116+
m.get("http://test.csc.fi/userinfo", payload={"not_ga4gh": [{}]})
117+
user_data = await retrieve_user_data('good_token')
118+
self.assertEqual(user_data, None)
119119

120-
# @aioresponses()
121-
# async def test_good_retrieve_user_data(self, m):
122-
# """Test a passing call to retrieve user data."""
123-
# m.get("http://test.csc.fi/userinfo", payload={"ga4gh": [{}]})
124-
# user_data = await retrieve_user_data('good_token')
125-
# self.assertEqual(user_data, [{}])
120+
@aioresponses()
121+
async def test_good_retrieve_user_data(self, m):
122+
"""Test a passing call to retrieve user data."""
123+
m.get("http://test.csc.fi/userinfo", payload={"ga4gh_passport_v1": [{}]})
124+
user_data = await retrieve_user_data('good_token')
125+
self.assertEqual(user_data, [{}])
126126

127127
@aioresponses()
128128
async def test_get_key(self, m):
@@ -146,6 +146,24 @@ async def test_get_key(self, m):
146146
self.assertTrue(isinstance(result, dict))
147147
self.assertTrue(result["keys"][0]['alg'], 'RSA256')
148148

149+
@aioresponses()
150+
async def test_get_jwk(self, m):
151+
"""Test get JWK."""
152+
data = {
153+
"keys": [
154+
{
155+
"alg": "RS256",
156+
"kty": "RSA",
157+
"use": "sig",
158+
"n": "public_key",
159+
"e": "AQAB"
160+
}
161+
]}
162+
m.get("http://test.csc.fi/jwk", payload=data)
163+
result = await get_jwk('http://test.csc.fi/jwk')
164+
self.assertTrue(isinstance(result, dict))
165+
self.assertTrue(result["keys"][0]['alg'], 'RSA256')
166+
149167
@asynctest.mock.patch('beacon_api.utils.validate.OAUTH2_CONFIG', return_value={'server': None})
150168
async def test_bad_get_key(self, oauth_none):
151169
"""Test bad test_get_key."""

0 commit comments

Comments
 (0)