def test_map_iss_sub_pair_to_user_with_no_prior_DRS_access(db_session): """ Test RASOauth2Client.map_iss_sub_pair_to_user when the username passed in (e.g. eRA username) does not already exist in the Fence database and that user's <iss, sub> combination has not already been mapped through a prior DRS access request. """ # reset users table db_session.query(User).delete() db_session.commit() iss = "https://domain.tld" sub = "123_abc" username = "******" email = "*****@*****.**" oidc = config.get("OPENID_CONNECT", {}) ras_client = RASClient( oidc["ras"], HTTP_PROXY=config.get("HTTP_PROXY"), logger=logger, ) assert not query_for_user(db_session, username) iss_sub_pair_to_user_records = db_session.query(IssSubPairToUser).all() assert len(iss_sub_pair_to_user_records) == 0 username_to_log_in = ras_client.map_iss_sub_pair_to_user( iss, sub, username, email, db_session=db_session) assert username_to_log_in == username iss_sub_pair_to_user = db_session.query(IssSubPairToUser).get((iss, sub)) assert iss_sub_pair_to_user.user.username == username assert iss_sub_pair_to_user.user.email == email iss_sub_pair_to_user_records = db_session.query(IssSubPairToUser).all() assert len(iss_sub_pair_to_user_records) == 1
def test_map_iss_sub_pair_to_user_with_prior_DRS_access_and_arborist_error( db_session, mock_arborist_requests): """ Test that RASOauth2Client.map_iss_sub_pair_to_user raises an internal error when Arborist fails to return a successful response. """ mock_arborist_requests( {"arborist/user/123_abcdomain.tld": { "PATCH": (None, 500) }}) # reset users table db_session.query(User).delete() db_session.commit() iss = "https://domain.tld" sub = "123_abc" username = "******" email = "*****@*****.**" oidc = config.get("OPENID_CONNECT", {}) ras_client = RASClient( oidc["ras"], HTTP_PROXY=config.get("HTTP_PROXY"), logger=logger, ) get_or_create_gen3_user_from_iss_sub(iss, sub, db_session=db_session) with pytest.raises(InternalError): ras_client.map_iss_sub_pair_to_user(iss, sub, username, email, db_session=db_session)
def test_map_iss_sub_pair_to_user_with_prior_login_and_prior_DRS_access( db_session, ): """ Test RASOauth2Client.map_iss_sub_pair_to_user when the username passed in (e.g. eRA username) already exists in the Fence database and that user's <iss, sub> combination has already been mapped to a separate user created during a prior DRS access request. In this case, map_iss_sub_pair_to_user returns the user created from prior DRS/data access, rendering the other user (e.g. the eRA one) inaccessible. """ iss = "https://domain.tld" sub = "123_abc" username = "******" email = "*****@*****.**" oidc = config.get("OPENID_CONNECT", {}) ras_client = RASClient( oidc["ras"], HTTP_PROXY=config.get("HTTP_PROXY"), logger=logger, ) # reset users table db_session.query(User).delete() db_session.commit() user = User(username=username, email=email) db_session.add(user) db_session.commit() get_or_create_gen3_user_from_iss_sub(iss, sub, db_session=db_session) username_to_log_in = ras_client.map_iss_sub_pair_to_user( iss, sub, username, email, db_session=db_session) assert username_to_log_in == "123_abcdomain.tld" iss_sub_pair_to_user = db_session.query(IssSubPairToUser).get((iss, sub)) assert iss_sub_pair_to_user.user.username == "123_abcdomain.tld"
def __init__( self, chunk_size=None, concurrency=None, thread_pool_size=None, buffer_size=None, logger=logger, ): """ args: chunk_size: size of chunk of users we want to take from each iteration concurrency: number of concurrent users going through the visa update flow thread_pool_size: number of Docker container CPU used for jwt verifcation buffer_size: max size of queue """ self.chunk_size = chunk_size or 10 self.concurrency = concurrency or 5 self.thread_pool_size = thread_pool_size or 3 self.buffer_size = buffer_size or 10 self.n_workers = self.thread_pool_size + self.concurrency self.logger = logger self.visa_types = config.get("USERSYNC", {}).get("visa_types", {}) # Initialize visa clients: oidc = config.get("OPENID_CONNECT", {}) if "ras" not in oidc: self.logger.error("RAS client not configured") self.ras_client = None else: self.ras_client = RASClient( oidc["ras"], HTTP_PROXY=config.get("HTTP_PROXY"), logger=logger, )
def test_store_refresh_token(db_session): """ Test to check if store_refresh_token replaces the existing token with a new one in the db """ test_user = add_test_ras_user(db_session) add_refresh_token(db_session, test_user) initial_query = db_session.query(UpstreamRefreshToken).first() assert initial_query.refresh_token new_refresh_token = "newtoken1234567" new_expire = 50000 oidc = config.get("OPENID_CONNECT", {}) ras_client = RASClient( oidc["ras"], HTTP_PROXY=config.get("HTTP_PROXY"), logger=logger, ) ras_client.store_refresh_token(test_user, new_refresh_token, new_expire, db_session=db_session) final_query = db_session.query(UpstreamRefreshToken).first() assert final_query.refresh_token == new_refresh_token assert final_query.expires == new_expire
def _setup_oidc_clients(app): if config["LOGIN_OPTIONS"]: enabled_idp_ids = [option["idp"] for option in config["LOGIN_OPTIONS"]] else: # fall back on "providers" enabled_idp_ids = list( config.get("ENABLED_IDENTITY_PROVIDERS", {}).get("providers", {}).keys()) oidc = config.get("OPENID_CONNECT", {}) # Add OIDC client for Google if configured. if "google" in oidc: app.google_client = GoogleClient( config["OPENID_CONNECT"]["google"], HTTP_PROXY=config.get("HTTP_PROXY"), logger=logger, ) # Add OIDC client for ORCID if configured. if "orcid" in oidc: app.orcid_client = ORCIDClient( config["OPENID_CONNECT"]["orcid"], HTTP_PROXY=config.get("HTTP_PROXY"), logger=logger, ) # Add OIDC client for RAS if configured. if "ras" in oidc: app.ras_client = RASClient( oidc["ras"], HTTP_PROXY=config.get("HTTP_PROXY"), logger=logger, ) # Add OIDC client for Synapse if configured. if "synapse" in oidc: app.synapse_client = SynapseClient(oidc["synapse"], HTTP_PROXY=config.get("HTTP_PROXY"), logger=logger) # Add OIDC client for Microsoft if configured. if "microsoft" in oidc: app.microsoft_client = MicrosoftClient( config["OPENID_CONNECT"]["microsoft"], HTTP_PROXY=config.get("HTTP_PROXY"), logger=logger, ) # Add OIDC client for Amazon Cognito if configured. if "cognito" in oidc: app.cognito_client = CognitoClient(oidc["cognito"], HTTP_PROXY=config.get("HTTP_PROXY"), logger=logger) # Add OIDC client for multi-tenant fence if configured. configured_fence = "fence" in oidc and "fence" in enabled_idp_ids if configured_fence: app.fence_client = OAuthClient(**config["OPENID_CONNECT"]["fence"])
def test_update_visa_empty_passport_returned( mock_discovery, mock_get_token, mock_userinfo, config, db_session, rsa_private_key, rsa_public_key, kid, ): """ Test to handle empty passport sent from RAS """ mock_discovery.return_value = "https://ras/token_endpoint" new_token = "refresh12345abcdefg" token_response = { "access_token": "abcdef12345", "id_token": "id12345abcdef", "refresh_token": new_token, } mock_get_token.return_value = token_response userinfo_response = { "sub": "abcd-asdj-sajpiasj12iojd-asnoin", "name": "", "preferred_username": "******", "UID": "", "UserID": "admin_user", "email": "", "passport_jwt_v11": "", } mock_userinfo.return_value = userinfo_response test_user = add_test_user(db_session) add_visa_manually(db_session, test_user, rsa_private_key, kid) add_refresh_token(db_session, test_user) visa_query = db_session.query(GA4GHVisaV1).filter_by( user=test_user).first() initial_visa = visa_query.ga4gh_visa assert initial_visa oidc = config.get("OPENID_CONNECT", {}) ras_client = RASClient( oidc["ras"], HTTP_PROXY=config.get("HTTP_PROXY"), logger=logger, ) pkey_cache = { "https://stsstg.nih.gov": { kid: rsa_public_key, } } ras_client.update_user_visas(test_user, pkey_cache=pkey_cache) query_visa = db_session.query(GA4GHVisaV1).first() assert query_visa == None
def test_update_visa_empty_visa_returned( mock_discovery, mock_get_token, mock_userinfo, config, db_session, rsa_private_key, kid, kid_2, ): """ Test to check if the db is emptied if the ras userinfo sends back an empty visa """ mock_discovery.return_value = "https://ras/token_endpoint" new_token = "refresh12345abcdefg" token_response = { "access_token": "abcdef12345", "id_token": "id12345abcdef", "refresh_token": new_token, } mock_get_token.return_value = token_response userinfo_response = { "sub": "abcd-asdj-sajpiasj12iojd-asnoin", "name": "", "preferred_username": "******", "UID": "", "UserID": "admin_user", "email": "", } userinfo_response["ga4gh_passport_v1"] = [] mock_userinfo.return_value = userinfo_response test_user = add_test_user(db_session) add_visa_manually(db_session, test_user, rsa_private_key, kid) add_refresh_token(db_session, test_user) visa_query = db_session.query(GA4GHVisaV1).filter_by( user=test_user).first() initial_visa = visa_query.ga4gh_visa assert initial_visa oidc = config.get("OPENID_CONNECT", {}) ras_client = RASClient( oidc["ras"], HTTP_PROXY=config.get("HTTP_PROXY"), logger=logger, ) ras_client.update_user_visas(test_user) query_visa = db_session.query(GA4GHVisaV1).first() assert query_visa == None
def _setup_oidc_clients(app): oidc = config.get("OPENID_CONNECT", {}) # Add OIDC client for Google if configured. if "google" in oidc: app.google_client = GoogleClient( config["OPENID_CONNECT"]["google"], HTTP_PROXY=config.get("HTTP_PROXY"), logger=logger, ) # Add OIDC client for ORCID if configured. if "orcid" in oidc: app.orcid_client = ORCIDClient( config["OPENID_CONNECT"]["orcid"], HTTP_PROXY=config.get("HTTP_PROXY"), logger=logger, ) # Add OIDC client for RAS if configured. if "ras" in oidc: app.ras_client = RASClient( oidc["ras"], HTTP_PROXY=config.get("HTTP_PROXY"), logger=logger, ) # Add OIDC client for Synapse if configured. if "synapse" in oidc: app.synapse_client = SynapseClient(oidc["synapse"], HTTP_PROXY=config.get("HTTP_PROXY"), logger=logger) # Add OIDC client for Microsoft if configured. if "microsoft" in oidc: app.microsoft_client = MicrosoftClient( config["OPENID_CONNECT"]["microsoft"], HTTP_PROXY=config.get("HTTP_PROXY"), logger=logger, ) # Add OIDC client for Amazon Cognito if configured. if "cognito" in oidc: app.cognito_client = CognitoClient(oidc["cognito"], HTTP_PROXY=config.get("HTTP_PROXY"), logger=logger) # Add OIDC client for multi-tenant fence if configured. if "fence" in oidc: app.fence_client = OAuthClient(**config["OPENID_CONNECT"]["fence"])
def test_map_iss_sub_pair_to_user_with_prior_DRS_access( db_session, mock_arborist_requests): """ Test RASOauth2Client.map_iss_sub_pair_to_user when the username passed in (e.g. eRA username) does not already exist in the Fence database but that user's <iss, sub> combination has already been mapped to an existing user created during a prior DRS access request. In this case, that existing user's username is changed from sub+iss to the username passed in. """ mock_arborist_requests( {"arborist/user/123_abcdomain.tld": { "PATCH": (None, 204) }}) # reset users table db_session.query(User).delete() db_session.commit() iss = "https://domain.tld" sub = "123_abc" username = "******" email = "*****@*****.**" oidc = config.get("OPENID_CONNECT", {}) ras_client = RASClient( oidc["ras"], HTTP_PROXY=config.get("HTTP_PROXY"), logger=logger, ) get_or_create_gen3_user_from_iss_sub(iss, sub, db_session=db_session) iss_sub_pair_to_user_records = db_session.query(IssSubPairToUser).all() assert len(iss_sub_pair_to_user_records) == 1 iss_sub_pair_to_user = db_session.query(IssSubPairToUser).get((iss, sub)) assert iss_sub_pair_to_user.user.username == "123_abcdomain.tld" username_to_log_in = ras_client.map_iss_sub_pair_to_user( iss, sub, username, email, db_session=db_session) assert username_to_log_in == username iss_sub_pair_to_user_records = db_session.query(IssSubPairToUser).all() assert len(iss_sub_pair_to_user_records) == 1 iss_sub_pair_to_user = db_session.query(IssSubPairToUser).get((iss, sub)) assert iss_sub_pair_to_user.user.username == username assert iss_sub_pair_to_user.user.email == email
def __init__( self, chunk_size=None, concurrency=None, thread_pool_size=None, buffer_size=None, logger=logger, ): """ args: chunk_size: size of chunk of users we want to take from each iteration concurrency: number of concurrent users going through the visa update flow thread_pool_size: number of Docker container CPU used for jwt verifcation buffer_size: max size of queue """ self.chunk_size = chunk_size or 10 self.concurrency = concurrency or 5 self.thread_pool_size = thread_pool_size or 3 self.buffer_size = buffer_size or 10 self.n_workers = self.thread_pool_size + self.concurrency self.logger = logger # This job runs without an application context, so it cannot use the # current_app.jwt_public_keys cache. # This is a simple dict with the same lifetime as the job. # When there are many visas from many issuers it will make sense to # implement a more persistent cache. self.pkey_cache = {} self.visa_types = config.get("USERSYNC", {}).get("visa_types", {}) # Initialize visa clients: oidc = config.get("OPENID_CONNECT", {}) if "ras" not in oidc: self.logger.error("RAS client not configured") self.ras_client = None else: self.ras_client = RASClient( oidc["ras"], HTTP_PROXY=config.get("HTTP_PROXY"), logger=logger, )
def test_update_visa_token_with_invalid_visa( mock_discovery, mock_get_token, mock_userinfo, config, db_session, rsa_private_key, rsa_public_key, kid, ): """ Test to check the following case: Received visa: [good1, bad2, good3] Processed/stored visa: [good1, good3] """ mock_discovery.return_value = "https://ras/token_endpoint" new_token = "refresh12345abcdefg" token_response = { "access_token": "abcdef12345", "id_token": "id12345abcdef", "refresh_token": new_token, } mock_get_token.return_value = token_response userinfo_response = { "sub": "abcd-asdj-sajpiasj12iojd-asnoin", "name": "", "preferred_username": "******", "UID": "", "UserID": "admin_user", "email": "", } test_user = add_test_user(db_session) add_visa_manually(db_session, test_user, rsa_private_key, kid) add_refresh_token(db_session, test_user) visa_query = db_session.query(GA4GHVisaV1).filter_by( user=test_user).first() initial_visa = visa_query.ga4gh_visa assert initial_visa oidc = config.get("OPENID_CONNECT", {}) ras_client = RASClient( oidc["ras"], HTTP_PROXY=config.get("HTTP_PROXY"), logger=logger, ) new_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", "asserted": int(time.time()), "value": "https://nig/passport/dbgap", "source": "https://ncbi/gap", }, } headers = {"kid": kid} encoded_visa = jwt.encode(new_visa, key=rsa_private_key, headers=headers, algorithm="RS256").decode("utf-8") passport_header = { "type": "JWT", "alg": "RS256", "kid": kid, } new_passport = { "iss": "https://stsstg.nih.gov", "sub": "abcde12345aspdij", "iat": int(time.time()), "scope": "openid ga4gh_passport_v1 email profile", "exp": int(time.time()) + 1000, } new_passport["ga4gh_passport_v1"] = [encoded_visa, [], encoded_visa] encoded_passport = jwt.encode(new_passport, key=rsa_private_key, headers=passport_header, algorithm="RS256").decode("utf-8") userinfo_response["passport_jwt_v11"] = encoded_passport mock_userinfo.return_value = userinfo_response pkey_cache = { "https://stsstg.nih.gov": { kid: rsa_public_key, } } ras_client.update_user_visas(test_user, pkey_cache=pkey_cache) query_visas = db_session.query(GA4GHVisaV1).filter_by(user=test_user).all() assert len(query_visas) == 2 for query_visa in query_visas: assert query_visa.ga4gh_visa assert query_visa.ga4gh_visa == encoded_visa
def test_update_visa_token_with_invalid_visa( mock_discovery, mock_get_token, mock_userinfo, config, db_session, rsa_private_key, rsa_public_key, kid, mock_arborist_requests, no_app_context_no_public_keys, ): """ Test to check the following case: Received visa: [good1, bad2, good3] Processed/stored visa: [good1, good3] """ mock_arborist_requests( {f"arborist/user/{TEST_RAS_USERNAME}": { "PATCH": (None, 204) }}) mock_discovery.return_value = "https://ras/token_endpoint" new_token = "refresh12345abcdefg" token_response = { "access_token": "abcdef12345", "id_token": "id12345abcdef", "refresh_token": new_token, } mock_get_token.return_value = token_response userinfo_response = { "sub": TEST_RAS_SUB, "name": "", "preferred_username": "******", "UID": "", "UserID": TEST_RAS_USERNAME, "email": "", } test_user = add_test_ras_user(db_session) existing_encoded_visa, _ = add_visa_manually(db_session, test_user, rsa_private_key, kid) add_refresh_token(db_session, test_user) visa_query = db_session.query(GA4GHVisaV1).filter_by( user=test_user).first() initial_visa = visa_query.ga4gh_visa assert initial_visa oidc = config.get("OPENID_CONNECT", {}) ras_client = RASClient( oidc["ras"], HTTP_PROXY=config.get("HTTP_PROXY"), logger=logger, ) new_visa = { "iss": "https://stsstg.nih.gov", "sub": TEST_RAS_SUB, "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", "asserted": int(time.time()), "value": "https://stsstg.nih.gov/passport/dbgap/v1.1", "source": "https://ncbi.nlm.nih.gov/gap", }, } headers = {"kid": kid} encoded_visa = jwt.encode(new_visa, key=rsa_private_key, headers=headers, algorithm="RS256").decode("utf-8") passport_header = { "type": "JWT", "alg": "RS256", "kid": kid, } new_passport = { "iss": "https://stsstg.nih.gov", "sub": TEST_RAS_SUB, "iat": int(time.time()), "scope": "openid ga4gh_passport_v1 email profile", "exp": int(time.time()) + 1000, } new_passport["ga4gh_passport_v1"] = [encoded_visa, [], encoded_visa] encoded_passport = jwt.encode(new_passport, key=rsa_private_key, headers=passport_header, algorithm="RS256").decode("utf-8") userinfo_response["passport_jwt_v11"] = encoded_passport mock_userinfo.return_value = userinfo_response pkey_cache = { "https://stsstg.nih.gov": { kid: rsa_public_key, } } ras_client.update_user_authorization( test_user, pkey_cache=pkey_cache, db_session=db_session, ) # at this point we expect the existing visa to stay around (since it hasn't expired) # and 2 new good visas query_visas = [ item.ga4gh_visa for item in db_session.query(GA4GHVisaV1).filter_by(user=test_user) ] assert len(query_visas) == 3 for query_visa in query_visas: assert query_visa == existing_encoded_visa or query_visa == encoded_visa
def test_update_visa_token( mock_discovery, mock_get_token, mock_userinfo, config, db_session, rsa_private_key, kid, kid_2, ): """ Test to check visa table is updated when getting new visa """ mock_discovery.return_value = "https://ras/token_endpoint" new_token = "refresh12345abcdefg" token_response = { "access_token": "abcdef12345", "id_token": "id12345abcdef", "refresh_token": new_token, } mock_get_token.return_value = token_response userinfo_response = { "sub": "abcd-asdj-sajpiasj12iojd-asnoin", "name": "", "preferred_username": "******", "UID": "", "UserID": "admin_user", "email": "", } test_user = add_test_user(db_session) add_visa_manually(db_session, test_user, rsa_private_key, kid) add_refresh_token(db_session, test_user) visa_query = db_session.query(GA4GHVisaV1).filter_by( user=test_user).first() initial_visa = visa_query.ga4gh_visa assert initial_visa oidc = config.get("OPENID_CONNECT", {}) ras_client = RASClient( oidc["ras"], HTTP_PROXY=config.get("HTTP_PROXY"), logger=logger, ) new_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", "asserted": int(time.time()), "value": "https://nig/passport/dbgap", "source": "https://ncbi/gap", }, } headers = {"kid": kid_2} encoded_visa = jwt.encode(new_visa, key=rsa_private_key, headers=headers, algorithm="RS256").decode("utf-8") userinfo_response["ga4gh_passport_v1"] = [encoded_visa] mock_userinfo.return_value = userinfo_response ras_client.update_user_visas(test_user) query_visa = db_session.query(GA4GHVisaV1).first() assert query_visa.ga4gh_visa assert query_visa.ga4gh_visa == encoded_visa
def test_update_visa_fetch_pkey( mock_discovery, mock_get_token, mock_userinfo, mock_httpx_get, db_session, rsa_private_key, kid, ): """ Test that when the RAS client's pkey cache is empty, the client's update_user_visas can fetch and serialize the visa issuer's public keys and validate a visa using the correct key. """ mock_discovery.return_value = "https://ras/token_endpoint" mock_get_token.return_value = { "access_token": "abcdef12345", "id_token": "id12345abcdef", "refresh_token": "refresh12345abcdefg", } # New visa that will be returned by userinfo new_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", "asserted": int(time.time()), "value": "https://nig/passport/dbgap", "source": "https://ncbi/gap", }, } headers = {"kid": kid} encoded_visa = jwt.encode(new_visa, key=rsa_private_key, headers=headers, algorithm="RS256").decode("utf-8") passport_header = { "type": "JWT", "alg": "RS256", "kid": kid, } new_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(new_passport, key=rsa_private_key, headers=passport_header, algorithm="RS256").decode("utf-8") mock_userinfo.return_value = { "passport_jwt_v11": encoded_passport, } # Mock the call to the jwks endpoint so it returns the test app's keypairs, # one of which is rsa_private_key (and its corresponding public key), which # we just used to sign new_visa. keys = [ keypair.public_key_to_jwk() for keypair in flask.current_app.keypairs ] mock_httpx_get.return_value = httpx.Response(200, json={"keys": keys}) oidc = config.get("OPENID_CONNECT", {}) ras_client = RASClient( oidc["ras"], HTTP_PROXY=config.get("HTTP_PROXY"), logger=logger, ) test_user = add_test_user(db_session) # Pass in an empty pkey cache so that the client will have to hit the jwks endpoint. ras_client.update_user_visas(test_user, pkey_cache={}) # Check that the new visa passed validation, indicating a successful pkey fetch query_visa = db_session.query(GA4GHVisaV1).first() assert query_visa.ga4gh_visa == encoded_visa
def test_update_visa_token( mock_discovery, mock_get_token, mock_userinfo, config, db_session, rsa_private_key, rsa_public_key, kid, mock_arborist_requests, no_app_context_no_public_keys, ): """ Test to check visa table is updated when getting new visa """ # ensure we don't actually try to reach out to external sites to refresh public keys def validate_jwt_no_key_refresh(*args, **kwargs): kwargs.update({"attempt_refresh": False}) return validate_jwt(*args, **kwargs) # ensure there is no application context or cached keys temp_stored_public_keys = flask.current_app.jwt_public_keys temp_app_context = flask.has_app_context del flask.current_app.jwt_public_keys def return_false(): return False flask.has_app_context = return_false mock_arborist_requests( {f"arborist/user/{TEST_RAS_USERNAME}": { "PATCH": (None, 204) }}) mock_discovery.return_value = "https://ras/token_endpoint" new_token = "refresh12345abcdefg" token_response = { "access_token": "abcdef12345", "id_token": "id12345abcdef", "refresh_token": new_token, } mock_get_token.return_value = token_response userinfo_response = { "sub": TEST_RAS_SUB, "name": "", "preferred_username": "******", "UID": "", "UserID": TEST_RAS_USERNAME, "email": "", } test_user = add_test_ras_user(db_session) existing_encoded_visa, _ = add_visa_manually(db_session, test_user, rsa_private_key, kid) add_refresh_token(db_session, test_user) visa_query = db_session.query(GA4GHVisaV1).filter_by( user=test_user).first() initial_visa = visa_query.ga4gh_visa assert initial_visa oidc = config.get("OPENID_CONNECT", {}) ras_client = RASClient( oidc["ras"], HTTP_PROXY=config.get("HTTP_PROXY"), logger=logger, ) # use default user and passport subjects_to_passports = get_subjects_to_passports( kid=kid, rsa_private_key=rsa_private_key) userinfo_response["passport_jwt_v11"] = subjects_to_passports[ TEST_RAS_SUB]["encoded_passport"] mock_userinfo.return_value = userinfo_response pkey_cache = { "https://stsstg.nih.gov": { kid: rsa_public_key, } } ras_client.update_user_authorization( test_user, pkey_cache=pkey_cache, db_session=db_session, ) # restore public keys and context flask.current_app.jwt_public_keys = temp_stored_public_keys flask.has_app_context = temp_app_context query_visas = [ item.ga4gh_visa for item in db_session.query(GA4GHVisaV1).filter_by(user=test_user) ] # at this point we expect the existing visa to stay around (since it hasn't expired) # and the new visa should also show up assert len(query_visas) == 2 assert existing_encoded_visa in query_visas for visa in subjects_to_passports[TEST_RAS_SUB]["encoded_visas"]: assert visa in query_visas
def test_update_visa_empty_passport_returned( mock_discovery, mock_get_token, mock_userinfo, config, db_session, rsa_private_key, rsa_public_key, kid, mock_arborist_requests, ): """ Test to handle empty passport sent from RAS """ mock_arborist_requests( {f"arborist/user/{TEST_RAS_USERNAME}": { "PATCH": (None, 204) }}) mock_discovery.return_value = "https://ras/token_endpoint" new_token = "refresh12345abcdefg" token_response = { "access_token": "abcdef12345", "id_token": "id12345abcdef", "refresh_token": new_token, } mock_get_token.return_value = token_response userinfo_response = { "sub": TEST_RAS_SUB, "name": "", "preferred_username": "******", "UID": "", "UserID": TEST_RAS_USERNAME, "email": "", "passport_jwt_v11": "", } mock_userinfo.return_value = userinfo_response test_user = add_test_ras_user(db_session) existing_encoded_visa, _ = add_visa_manually(db_session, test_user, rsa_private_key, kid) add_refresh_token(db_session, test_user) visa_query = db_session.query(GA4GHVisaV1).filter_by( user=test_user).first() initial_visa = visa_query.ga4gh_visa assert initial_visa oidc = config.get("OPENID_CONNECT", {}) ras_client = RASClient( oidc["ras"], HTTP_PROXY=config.get("HTTP_PROXY"), logger=logger, ) pkey_cache = { "https://stsstg.nih.gov": { kid: rsa_public_key, } } ras_client.update_user_authorization( test_user, pkey_cache=pkey_cache, db_session=db_session, ) # at this point we expect the existing visa to stay around (since it hasn't expired) # but no new visas query_visas = [ item.ga4gh_visa for item in db_session.query(GA4GHVisaV1).filter_by(user=test_user) ] assert len(query_visas) == 1 assert existing_encoded_visa in query_visas
def test_update_visa_empty_visa_returned( mock_discovery, mock_get_token, mock_userinfo, config, db_session, rsa_private_key, kid, mock_arborist_requests, ): """ Test to check if the db is emptied if the ras userinfo sends back an empty visa """ mock_arborist_requests( {f"arborist/user/{TEST_RAS_USERNAME}": { "PATCH": (None, 204) }}) mock_discovery.return_value = "https://ras/token_endpoint" new_token = "refresh12345abcdefg" token_response = { "access_token": "abcdef12345", "id_token": "id12345abcdef", "refresh_token": new_token, } mock_get_token.return_value = token_response userinfo_response = { "sub": TEST_RAS_SUB, "name": "", "preferred_username": "******", "UID": "", "UserID": TEST_RAS_USERNAME, "email": "", } passport_header = { "type": "JWT", "alg": "RS256", "kid": kid, } new_passport = { "iss": "https://stsstg.nih.gov", "sub": TEST_RAS_SUB, "iat": int(time.time()), "scope": "openid ga4gh_passport_v1 email profile", "exp": int(time.time()) + 1000, "ga4gh_passport_v1": [], } encoded_passport = jwt.encode(new_passport, key=rsa_private_key, headers=passport_header, algorithm="RS256").decode("utf-8") userinfo_response["passport_jwt_v11"] = encoded_passport mock_userinfo.return_value = userinfo_response test_user = add_test_ras_user(db_session) existing_encoded_visa, _ = add_visa_manually(db_session, test_user, rsa_private_key, kid) add_refresh_token(db_session, test_user) visa_query = db_session.query(GA4GHVisaV1).filter_by( user=test_user).first() initial_visa = visa_query.ga4gh_visa assert initial_visa oidc = config.get("OPENID_CONNECT", {}) ras_client = RASClient( oidc["ras"], HTTP_PROXY=config.get("HTTP_PROXY"), logger=logger, ) ras_client.update_user_authorization(test_user, pkey_cache={}, db_session=db_session) # at this point we expect the existing visa to stay around (since it hasn't expired) # but no new visas query_visas = [ item.ga4gh_visa for item in db_session.query(GA4GHVisaV1).filter_by(user=test_user) ] assert len(query_visas) == 1 assert existing_encoded_visa in query_visas
def test_update_visa_fetch_pkey( mock_discovery, mock_get_token, mock_userinfo, mock_httpx_get, db_session, rsa_private_key, kid, mock_arborist_requests, ): """ Test that when the RAS client's pkey cache is empty, the client's update_user_authorization can fetch and serialize the visa issuer's public keys and validate a visa using the correct key. """ # ensure there is no application context or cached keys temp_stored_public_keys = flask.current_app.jwt_public_keys temp_app_context = flask.has_app_context del flask.current_app.jwt_public_keys def return_false(): return False flask.has_app_context = return_false mock_arborist_requests( {f"arborist/user/{TEST_RAS_USERNAME}": { "PATCH": (None, 204) }}) mock_discovery.return_value = "https://ras/token_endpoint" mock_get_token.return_value = { "access_token": "abcdef12345", "id_token": "id12345abcdef", "refresh_token": "refresh12345abcdefg", } # New visa that will be returned by userinfo new_visa = { "iss": "https://stsstg.nih.gov", "sub": TEST_RAS_SUB, "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", "asserted": int(time.time()), "value": "https://stsstg.nih.gov/passport/dbgap/v1.1", "source": "https://ncbi.nlm.nih.gov/gap", }, } headers = {"kid": kid} encoded_visa = jwt.encode(new_visa, key=rsa_private_key, headers=headers, algorithm="RS256").decode("utf-8") passport_header = { "type": "JWT", "alg": "RS256", "kid": kid, } new_passport = { "iss": "https://stsstg.nih.gov", "sub": TEST_RAS_SUB, "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(new_passport, key=rsa_private_key, headers=passport_header, algorithm="RS256").decode("utf-8") mock_userinfo.return_value = { "passport_jwt_v11": encoded_passport, } # Mock the call to the jwks endpoint so it returns the test app's keypairs, # one of which is rsa_private_key (and its corresponding public key), which # we just used to sign new_visa. keys = [ keypair.public_key_to_jwk() for keypair in flask.current_app.keypairs ] mock_httpx_get.return_value = httpx.Response(200, json={"keys": keys}) oidc = config.get("OPENID_CONNECT", {}) ras_client = RASClient( oidc["ras"], HTTP_PROXY=config.get("HTTP_PROXY"), logger=logger, ) test_user = add_test_ras_user(db_session) # Pass in an empty pkey cache so that the client will have to hit the jwks endpoint. ras_client.update_user_authorization(test_user, pkey_cache={}, db_session=db_session) # restore public keys and context flask.current_app.jwt_public_keys = temp_stored_public_keys flask.has_app_context = temp_app_context # Check that the new visa passed validation, indicating a successful pkey fetch query_visas = [ item.ga4gh_visa for item in db_session.query(GA4GHVisaV1).filter_by(user=test_user) ] for visa in query_visas: assert visa == encoded_visa
def test_update_visa_empty_visa_returned( mock_discovery, mock_get_token, mock_userinfo, config, db_session, rsa_private_key, kid, ): """ Test to check if the db is emptied if the ras userinfo sends back an empty visa """ mock_discovery.return_value = "https://ras/token_endpoint" new_token = "refresh12345abcdefg" token_response = { "access_token": "abcdef12345", "id_token": "id12345abcdef", "refresh_token": new_token, } mock_get_token.return_value = token_response userinfo_response = { "sub": "abcd-asdj-sajpiasj12iojd-asnoin", "name": "", "preferred_username": "******", "UID": "", "UserID": "admin_user", "email": "", } passport_header = { "type": "JWT", "alg": "RS256", "kid": kid, } new_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_passport = jwt.encode(new_passport, key=rsa_private_key, headers=passport_header, algorithm="RS256").decode("utf-8") userinfo_response["passport_jwt_v11"] = encoded_passport mock_userinfo.return_value = userinfo_response test_user = add_test_user(db_session) add_visa_manually(db_session, test_user, rsa_private_key, kid) add_refresh_token(db_session, test_user) visa_query = db_session.query(GA4GHVisaV1).filter_by( user=test_user).first() initial_visa = visa_query.ga4gh_visa assert initial_visa oidc = config.get("OPENID_CONNECT", {}) ras_client = RASClient( oidc["ras"], HTTP_PROXY=config.get("HTTP_PROXY"), logger=logger, ) ras_client.update_user_visas(test_user, pkey_cache={}) query_visa = db_session.query(GA4GHVisaV1).first() assert query_visa == None