Skip to content

Commit

Permalink
Merge branch 'feat/passport' of github.com:uc-cdis/fence into feat/cr…
Browse files Browse the repository at this point in the history
…eate_presigned_url_passports
  • Loading branch information
BinamB committed Nov 23, 2021
2 parents 2339756 + 60c014c commit 9f0d3cb
Show file tree
Hide file tree
Showing 4 changed files with 177 additions and 1 deletion.
8 changes: 7 additions & 1 deletion fence/blueprints/data/indexd.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ def get_signed_url_for_file(
if no_force_sign_param and no_force_sign_param.lower() == "true":
force_signed_url = False

if ga4gh_passports and not config["GA4GH_PASSPORTS_TO_DRS_ENABLED"]:
raise NotSupported(
"Using GA4GH Passports as a means of authentication and authorization "
"is not supported by this instance of Gen3."
)

user_ids_from_passports = None
if ga4gh_passports:
# TODO change this to usernames
Expand Down Expand Up @@ -532,7 +538,7 @@ def check_authz(self, action, user_ids_from_passports=None):
)
# if any passport provides access, user is authorized
if authorized:
# for google proxy groups we need to know which user_id gave access
# for google proxy groups we need to know which user_id gave access
return user_id
return authorized
else:
Expand Down
6 changes: 6 additions & 0 deletions fence/blueprints/ga4gh.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ def get_ga4gh_signed_url(object_id, access_id):
config["GA4GH_DRS_POSTED_PASSPORT_FIELD"]
)

if ga4gh_passports and flask.request.headers.get("Authorization"):
raise UserError(
"You cannot supply both GA4GH passports and a token "
"in the Authorization header of a request."
)

result = get_signed_url_for_file(
"download",
object_id,
Expand Down
2 changes: 2 additions & 0 deletions fence/config-default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -871,6 +871,8 @@ SERVICE_ACCOUNT_LIMIT: 6
# //////////////////////////////////////////////////////////////////////////////////////
# GA4GH SUPPORT: DATA ACCESS AND AUTHORIZATION SYNCING
# //////////////////////////////////////////////////////////////////////////////////////
# whether or not to accept GA4GH Passports as a means of AuthN/Z to the DRS data access endpoint
GA4GH_PASSPORTS_TO_DRS_ENABLED: false

# RAS refresh_tokens expire in 15 days
RAS_REFRESH_EXPIRATION: 1296000
Expand Down
162 changes: 162 additions & 0 deletions tests/test_drs.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

from gen3authz.client.arborist.client import ArboristClient

from fence.config import config


def get_doc(has_version=True, urls=list(), drs_list=0):
doc = {
Expand Down Expand Up @@ -238,6 +240,165 @@ def test_get_presigned_url_with_query_params(
assert res.status_code == 200


@responses.activate
@pytest.mark.parametrize("indexd_client", ["s3", "gs"], indirect=True)
@patch("httpx.get")
@patch("fence.resources.google.utils._create_proxy_group")
@patch("fence.resources.ga4gh.passports.ArboristClient")
def test_passport_use_disabled(
mock_arborist,
mock_google_proxy_group,
mock_httpx_get,
client,
indexd_client,
kid,
rsa_private_key,
rsa_public_key,
indexd_client_accepting_record,
mock_arborist_requests,
google_proxy_group,
primary_google_service_account,
cloud_manager,
google_signed_url,
):
config["GA4GH_PASSPORTS_TO_DRS_ENABLED"] = False
indexd_record_with_non_public_authz_and_public_acl_populated = {
"did": "1",
"baseid": "",
"rev": "",
"size": 10,
"file_name": "file1",
"urls": ["s3://bucket1/key", "gs://bucket1/key"],
"hashes": {},
"metadata": {},
"authz": ["/orgA/programs/phs000991.c1"],
"acl": ["*"],
"form": "",
"created_date": "",
"updated_date": "",
}
indexd_client_accepting_record(
indexd_record_with_non_public_authz_and_public_acl_populated
)
mock_arborist_requests({"arborist/auth/request": {"POST": ({"auth": True}, 200)}})
mock_arborist.return_value = MagicMock(ArboristClient)
mock_google_proxy_group.return_value = google_proxy_group

# Prepare Passport/Visa
headers = {"kid": kid}
decoded_visa = {
"iss": "https://stsstg.nih.gov",
"sub": "abcde12345aspdij",
"iat": int(time.time()),
"exp": int(time.time()) + 1000,
"scope": "openid ga4gh_passport_v1 email profile",
"jti": "jtiajoidasndokmasdl",
"txn": "sapidjspa.asipidja",
"name": "",
"ga4gh_visa_v1": {
"type": "https://ras.nih.gov/visas/v1.1",
"asserted": int(time.time()),
"value": "https://stsstg.nih.gov/passport/dbgap/v1.1",
"source": "https://ncbi.nlm.nih.gov/gap",
},
"ras_dbgap_permissions": [
{
"consent_name": "Health/Medical/Biomedical",
"phs_id": "phs000991",
"version": "v1",
"participant_set": "p1",
"consent_group": "c1",
"role": "designated user",
"expiration": int(time.time()) + 1001,
},
{
"consent_name": "General Research Use (IRB, PUB)",
"phs_id": "phs000961",
"version": "v1",
"participant_set": "p1",
"consent_group": "c1",
"role": "designated user",
"expiration": int(time.time()) + 1001,
},
{
"consent_name": "Disease-Specific (Cardiovascular Disease)",
"phs_id": "phs000279",
"version": "v2",
"participant_set": "p1",
"consent_group": "c1",
"role": "designated user",
"expiration": int(time.time()) + 1001,
},
{
"consent_name": "Health/Medical/Biomedical (IRB)",
"phs_id": "phs000286",
"version": "v6",
"participant_set": "p2",
"consent_group": "c3",
"role": "designated user",
"expiration": int(time.time()) + 1001,
},
{
"consent_name": "Disease-Specific (Focused Disease Only, IRB, NPU)",
"phs_id": "phs000289",
"version": "v6",
"participant_set": "p2",
"consent_group": "c2",
"role": "designated user",
"expiration": int(time.time()) + 1001,
},
{
"consent_name": "Disease-Specific (Autism Spectrum Disorder)",
"phs_id": "phs000298",
"version": "v4",
"participant_set": "p3",
"consent_group": "c1",
"role": "designated user",
"expiration": int(time.time()) + 1001,
},
],
}
encoded_visa = jwt.encode(
decoded_visa, key=rsa_private_key, headers=headers, algorithm="RS256"
).decode("utf-8")

passport_header = {
"type": "JWT",
"alg": "RS256",
"kid": kid,
}
passport = {
"iss": "https://stsstg.nih.gov",
"sub": "abcde12345aspdij",
"iat": int(time.time()),
"scope": "openid ga4gh_passport_v1 email profile",
"exp": int(time.time()) + 1000,
"ga4gh_passport_v1": [encoded_visa],
}
encoded_passport = jwt.encode(
passport, key=rsa_private_key, headers=passport_header, algorithm="RS256"
).decode("utf-8")

access_id = indexd_client["indexed_file_location"]
test_guid = "1"

passports = [encoded_passport]

data = {"passports": passports}

keys = [keypair.public_key_to_jwk() for keypair in flask.current_app.keypairs]
mock_httpx_get.return_value = httpx.Response(200, json={"keys": keys})

res = client.post(
"/ga4gh/drs/v1/objects/" + test_guid + "/access/" + access_id,
headers={
"Content-Type": "application/json",
},
data=json.dumps(data),
)
assert res.status_code == 400


@responses.activate
@pytest.mark.parametrize("indexd_client", ["s3", "gs"], indirect=True)
@patch("httpx.get")
Expand All @@ -259,6 +420,7 @@ def test_get_presigned_url_for_non_public_data_with_passport(
cloud_manager,
google_signed_url,
):
config["GA4GH_PASSPORTS_TO_DRS_ENABLED"] = True
indexd_record_with_non_public_authz_and_public_acl_populated = {
"did": "1",
"baseid": "",
Expand Down

0 comments on commit 9f0d3cb

Please sign in to comment.