def test_get_oauth_access_token(app_req_ctx): """Test the acquisition of OAuth access token.""" with app_req_ctx("/oauth/token", method="POST", data=dict(grant_type="client_credentials", client_id="CLIENT_ID", client_secret="CLIENT_SECRET")) as ctx: rv = ctx.app.full_dispatch_request() assert rv.status_code == 200 data = json.loads(rv.data) client = Client.get(client_id="CLIENT_ID") token = Token.get(client=client) assert data["access_token"] == token.access_token assert data["expires_in"] == ctx.app.config[ "OAUTH2_PROVIDER_TOKEN_EXPIRES_IN"] assert data["token_type"] == token.token_type # prevously created access token should be removed assert not Token.select().where(Token.access_token == "TEST").exists() with app_req_ctx("/oauth/token", method="POST", data=dict(grant_type="client_credentials", client_id="NOT-EXISTING-CLIENT_ID", client_secret="CLIENT_SECRET")) as ctx: # login_user(user, remember=True) rv = ctx.app.full_dispatch_request() assert rv.status_code == 401 data = json.loads(rv.data) assert data["error"] == "invalid_client" with app_req_ctx("/oauth/token", method="POST", data=dict(grant_type="client_credentials", client_id="CLIENT_ID", client_secret="INCORRECT")) as ctx: # login_user(user, remember=True) rv = ctx.app.full_dispatch_request() assert rv.status_code == 401 data = json.loads(rv.data) assert data["error"] == "invalid_client" with app_req_ctx("/oauth/token", method="POST", data=dict(grant_type="INCORRECT", client_id="NOT-EXISTING-CLIENT_ID", client_secret="CLIENT_SECRET")) as ctx: # login_user(user, remember=True) rv = ctx.app.full_dispatch_request() assert rv.status_code == 400 data = json.loads(rv.data) assert data["error"] == "unsupported_grant_type"
def test_users_api(client): """Test user API.""" c = Client.get(client_id="TEST0-ID") resp = client.post( "/oauth/token", content_type="application/x-www-form-urlencoded", data= f"grant_type=client_credentials&client_id={c.client_id}&client_secret={c.client_secret}" ) data = json.loads(resp.data) access_token = data["access_token"] resp = client.get("/api/v1.0/users?page=1&page_size=2000", headers=dict(authorization=f"Bearer {access_token}"), content_type="application/json") data = json.loads(resp.data) assert list(data[0].keys()) == [ "confirmed", "email", "eppn", "name", "orcid", "updated-at" ] assert not any(u["email"] == "*****@*****.**" for u in data) resp = client.get( "/api/v1.0/users?page=1&page_size=2000&from_date=2000-12-01&to_date=1999-01-01", headers=dict(authorization=f"Bearer {access_token}"), content_type="application/json") data = json.loads(resp.data) assert len(data) == 0 resp = client.get("/api/v1.0/users/[email protected]", headers=dict(authorization=f"Bearer {access_token}"), content_type="application/json") assert resp.status_code == 404 data = json.loads(resp.data) resp = client.get("/api/v1.0/users/[email protected]", headers=dict(authorization=f"Bearer {access_token}"), content_type="application/json") assert resp.status_code == 200 data = json.loads(resp.data) assert data["email"] == "*****@*****.**"
def test_get_oauth_access_token(client): """Test the acquisition of OAuth access token.""" resp = client.post("/oauth/token", data=dict(grant_type="client_credentials", client_id="CLIENT_ID", client_secret="CLIENT_SECRET")) assert resp.status_code == 200 c = Client.get(client_id="CLIENT_ID") token = Token.get(client=c) assert resp.json["access_token"] == token.access_token assert resp.json["expires_in"] == client.application.config[ "OAUTH2_PROVIDER_TOKEN_EXPIRES_IN"] assert resp.json["token_type"] == token.token_type # prevously created access token should be removed assert not Token.select().where(Token.access_token == "TEST").exists() resp = client.post("/oauth/token", data=dict(grant_type="client_credentials", client_id="NOT-EXISTING-CLIENT_ID", client_secret="CLIENT_SECRET")) # login_user(user, remember=True) assert resp.status_code == 401 assert resp.json["error"] == "invalid_client" resp = client.post("/oauth/token", data=dict(grant_type="client_credentials", client_id="CLIENT_ID", client_secret="INCORRECT")) # login_user(user, remember=True) assert resp.status_code == 401 assert resp.json["error"] == "invalid_client" resp = client.post("/oauth/token", data=dict(grant_type="INCORRECT", client_id="NOT-EXISTING-CLIENT_ID", client_secret="CLIENT_SECRET")) # login_user(user, remember=True) assert resp.status_code == 400 assert resp.json["error"] == "unsupported_grant_type"
def test_webhook_registration(client): """Test webhook registration.""" test_client = client user = User.get(email="*****@*****.**") test_client.login(user) org = user.organisation orcid_id = "0000-0000-0000-00X3" client = Client.get(org=org) resp = test_client.post( "/oauth/token", data=dict( grant_type="client_credentials", client_id=client.client_id, client_secret=client.client_secret, scope="/webhook", ), ) assert resp.status_code == 200 data = json.loads(resp.data) client = Client.get(client_id="CLIENT_ID") token = Token.select().where(Token.user == user, Token._scopes == "/webhook").first() assert data["access_token"] == token.access_token assert data["expires_in"] == test_client.application.config["OAUTH2_PROVIDER_TOKEN_EXPIRES_IN"] assert data["token_type"] == token.token_type # prevously created access token should be removed resp = test_client.put( "/api/v1/INCORRECT/webhook/http%3A%2F%2FCALL-BACK", headers=dict(authorization=f"Bearer {token.access_token}"), ) assert resp.status_code == 415 assert json.loads(resp.data)["error"] == "Missing or invalid ORCID iD." resp = test_client.put( "/api/v1/0000-0001-8228-7153/webhook/http%3A%2F%2FCALL-BACK", headers=dict(authorization=f"Bearer {token.access_token}"), ) assert resp.status_code == 404 assert json.loads(resp.data)["error"] == "Invalid ORCID iD." resp = test_client.put( f"/api/v1/{orcid_id}/webhook/INCORRECT-WEBHOOK-URL", headers=dict(authorization=f"Bearer {token.access_token}"), ) assert resp.status_code == 415 assert json.loads(resp.data) == { "error": "Invalid call-back URL", "message": "Invalid call-back URL: INCORRECT-WEBHOOK-URL", } with patch("orcid_hub.utils.requests.post") as mockpost, patch( "orcid_hub.utils.requests.put" ) as mockput: # Access toke request resp: mockresp = MagicMock(status_code=201) mockresp.json.return_value = { "access_token": "ACCESS-TOKEN-123", "token_type": "bearer", "refresh_token": "REFRESH-TOKEN-123", "expires_in": 99999, "scope": "/webhook", "orcid": None, } mockpost.return_value = mockresp # Webhook registration response: mockresp = MagicMock(status_code=201, data=b"") mockresp.headers = { "Server": "TEST123", "Connection": "keep-alive", "Pragma": "no-cache", "Expires": "0", } mockput.return_value = mockresp resp = test_client.put( f"/api/v1/{orcid_id}/webhook/http%3A%2F%2FCALL-BACK", headers=dict(authorization=f"Bearer {token.access_token}"), ) assert resp.status_code == 201 args, kwargs = mockpost.call_args assert args[0] == "https://sandbox.orcid.org/oauth/token" assert kwargs["data"] == { "client_id": "APP-12345678", "client_secret": "CLIENT-SECRET", "scope": "/webhook", "grant_type": "client_credentials", } assert kwargs["headers"] == {"Accept": "application/json"} args, kwargs = mockput.call_args assert ( args[0] == "https://api.sandbox.orcid.org/0000-0000-0000-00X3/webhook/http%3A%2F%2FCALL-BACK" ) assert kwargs["headers"] == { "Accept": "application/json", "Authorization": "Bearer ACCESS-TOKEN-123", "Content-Length": "0", } q = OrcidToken.select().where(OrcidToken.org == org, OrcidToken.scopes == "/webhook") assert q.count() == 1 orcid_token = q.first() assert orcid_token.access_token == "ACCESS-TOKEN-123" assert orcid_token.refresh_token == "REFRESH-TOKEN-123" assert orcid_token.expires_in == 99999 assert orcid_token.scopes == "/webhook" with patch("orcid_hub.utils.requests.delete") as mockdelete: # Webhook deletion response: mockresp = MagicMock(status_code=204, data=b"") mockresp.headers = { "Seresper": "TEST123", "Connection": "keep-alive", "Location": "TEST-LOCATION", "Pragma": "no-cache", "Expires": "0", } mockdelete.return_value = mockresp resp = test_client.delete( f"/api/v1/{orcid_id}/webhook/http%3A%2F%2FCALL-BACK", headers=dict(authorization=f"Bearer {token.access_token}"), ) assert resp.status_code == 204 assert urlparse(resp.location).path == f"/api/v1/{orcid_id}/webhook/http://TEST-LOCATION" args, kwargs = mockput.call_args assert ( args[0] == "https://api.sandbox.orcid.org/0000-0000-0000-00X3/webhook/http%3A%2F%2FCALL-BACK" ) assert kwargs["headers"] == { "Accept": "application/json", "Authorization": "Bearer ACCESS-TOKEN-123", "Content-Length": "0", } q = OrcidToken.select().where(OrcidToken.org == org, OrcidToken.scopes == "/webhook") assert q.count() == 1 token = q.first() assert token.access_token == "ACCESS-TOKEN-123" assert token.refresh_token == "REFRESH-TOKEN-123" assert token.expires_in == 99999 assert token.scopes == "/webhook"
def test_org_webhook_api(client, mocker): """Test Organisation webhooks.""" mocker.patch.object( utils.requests, "post", lambda *args, **kwargs: Mock( status_code=201, json=lambda: dict( access_token="ABC123", refresh_token="REFRESH_ME", expires_in=123456789 ), ), ) mockput = mocker.patch.object(utils.requests, "put") mockdelete = mocker.patch.object(utils.requests, "delete") org = client.data["org"] admin = org.tech_contact send_email = mocker.patch("orcid_hub.utils.send_email") api_client = Client.get(org=org) resp = client.post( "/oauth/token", data=dict( grant_type="client_credentials", client_id=api_client.client_id, client_secret=api_client.client_secret, scope="/webhook", ), ) assert resp.status_code == 200 data = json.loads(resp.data) api_client = Client.get(client_id="CLIENT_ID") token = Token.select().where(Token.user == admin, Token._scopes == "/webhook").first() assert data["access_token"] == token.access_token assert data["expires_in"] == client.application.config["OAUTH2_PROVIDER_TOKEN_EXPIRES_IN"] assert data["token_type"] == token.token_type client.access_token = token.access_token resp = client.put("/api/v1/webhook/INCORRECT-WEBHOOK-URL") assert resp.status_code == 415 assert json.loads(resp.data) == { "error": "Invalid call-back URL", "message": "Invalid call-back URL: INCORRECT-WEBHOOK-URL", } # Webhook registration response: mockresp = MagicMock(status_code=201, data=b"") mockresp.headers = { "Server": "TEST123", "Connection": "keep-alive", "Pragma": "no-cache", "Expires": "0", } mockput.return_value = mockresp # Webhook deletion response: mockresp = MagicMock(status_code=204, data=b"") mockresp.headers = { "Seresper": "TEST123", "Connection": "keep-alive", "Location": "TEST-LOCATION", "Pragma": "no-cache", "Expires": "0", } mockdelete.return_value = mockresp resp = client.put("/api/v1/webhook/http%3A%2F%2FCALL-BACK") assert resp.status_code == 200 resp = client.put( "/api/v1/webhook/http%3A%2F%2FCALL-BACK", data=dict(enabled=True, url="https://CALL-BACK.edu/callback"), ) assert resp.status_code == 201 server_name = client.application.config["SERVER_NAME"] mockput.assert_has_calls( [ call( "https://api.sandbox.orcid.org/1001-0001-0001-0001/webhook/" f"https%3A%2F%2F{server_name}%2Fservices%2F21%2Fupdated", headers={ "Accept": "application/json", "Authorization": "Bearer ABC123", "Content-Length": "0", }, ), call( "https://api.sandbox.orcid.org/0000-0000-0000-00X3/webhook/" f"https%3A%2F%2F{server_name}%2Fservices%2F22%2Fupdated", headers={ "Accept": "application/json", "Authorization": "Bearer ABC123", "Content-Length": "0", }, ), call( "https://api.sandbox.orcid.org/0000-0000-0000-11X2/webhook/" f"https%3A%2F%2F{server_name}%2Fservices%2F30%2Fupdated", headers={ "Accept": "application/json", "Authorization": "Bearer ABC123", "Content-Length": "0", }, ), ] ) q = OrcidToken.select().where(OrcidToken.org == org, OrcidToken.scopes == "/webhook") assert q.exists() assert q.count() == 1 orcid_token = q.first() assert orcid_token.access_token == "ABC123" assert orcid_token.refresh_token == "REFRESH_ME" assert orcid_token.expires_in == 123456789 assert orcid_token.scopes == "/webhook" # deactivate: resp = client.delete("/api/v1/webhook/http%3A%2F%2FCALL-BACK") assert resp.status_code == 200 assert "job-id" in resp.json # activate with all options: mockput.reset_mock() resp = client.put( "/api/v1/webhook", data={ "enabled": True, "append-orcid": True, "apikey": "APIKEY123", "email-notifications-enabled": True, "notification-email": "*****@*****.**", }, ) server_name = client.application.config["SERVER_NAME"] mockput.assert_has_calls( [ call( "https://api.sandbox.orcid.org/1001-0001-0001-0001/webhook/" f"https%3A%2F%2F{server_name}%2Fservices%2F21%2Fupdated", headers={ "Accept": "application/json", "Authorization": "Bearer ABC123", "Content-Length": "0", }, ), call( "https://api.sandbox.orcid.org/0000-0000-0000-00X3/webhook/" f"https%3A%2F%2F{server_name}%2Fservices%2F22%2Fupdated", headers={ "Accept": "application/json", "Authorization": "Bearer ABC123", "Content-Length": "0", }, ), call( "https://api.sandbox.orcid.org/0000-0000-0000-11X2/webhook/" f"https%3A%2F%2F{server_name}%2Fservices%2F30%2Fupdated", headers={ "Accept": "application/json", "Authorization": "Bearer ABC123", "Content-Length": "0", }, ), ] ) # Link other org to the users org2 = Organisation.select().where(Organisation.id != org.id).first() UserOrg.insert_many([dict(user_id=u.id, org_id=org2.id) for u in org.users]).execute() org2.webhook_enabled = True org2.save() resp = client.delete("/api/v1/webhook") mockput.reset_mock() resp = client.put( "/api/v1/webhook", data={ "enabled": False, "url": "https://CALL-BACK.edu/callback", "append-orcid": False, "email-notifications-enabled": True, "notification-email": "*****@*****.**", }, ) mockput.assert_not_called() # Test update summary: User.update( orcid_updated_at=datetime.date.today().replace(day=1) - datetime.timedelta(days=15) ).execute() send_email.reset_mock() utils.send_orcid_update_summary() send_email.assert_called_once() client.logout()
def test_webhook_registration(app_req_ctx): """Test webhook registration.""" user = User.get(email="*****@*****.**") org = user.organisation orcid_id = "0000-0000-0000-00X3" client = Client.get(org=org) with app_req_ctx("/oauth/token", method="POST", data=dict(grant_type="client_credentials", client_id=client.client_id, client_secret=client.client_secret, scope="/webhook")) as ctx: login_user(user) rv = ctx.app.full_dispatch_request() assert rv.status_code == 200 data = json.loads(rv.data) token = Token.get(user=user, _scopes="/webhook") client = Client.get(client_id="CLIENT_ID") token = Token.get(client=client) assert data["access_token"] == token.access_token assert data["expires_in"] == ctx.app.config[ "OAUTH2_PROVIDER_TOKEN_EXPIRES_IN"] assert data["token_type"] == token.token_type # prevously created access token should be removed with app_req_ctx( f"/api/v1.0/{orcid_id}/webhook/http%3A%2F%2FCALL-BACK", method="PUT", headers=dict(authorization=f"Bearer {token.access_token}") ) as ctx, patch("orcid_hub.utils.requests.post") as mockpost, patch( "orcid_hub.utils.requests.put") as mockput: # Access toke request resp: mockresp = MagicMock(status_code=201) mockresp.json.return_value = { "access_token": "ACCESS-TOKEN-123", "token_type": "bearer", "refresh_token": "REFRESH-TOKEN-123", "expires_in": 99999, "scope": "/webhook", "orcid": None } mockpost.return_value = mockresp # Webhook registration response: mockresp = MagicMock(status_code=201, data=b'') # mockresp.raw.stream = lambda *args, **kwargs: iter([b"""{"data": "TEST"}"""]) mockresp.raw.headers = { "Server": "TEST123", "Connection": "keep-alive", "Location": "LOCATION", "Pragma": "no-cache", "Expires": "0", } mockput.return_value = mockresp resp = ctx.app.full_dispatch_request() assert resp.status_code == 201 args, kwargs = mockpost.call_args assert args[0] == "https://sandbox.orcid.org/oauth/token" assert kwargs["data"] == { "client_id": "APP-12345678", "client_secret": "CLIENT-SECRET", "scope": "/webhook", "grant_type": "client_credentials" } assert kwargs["headers"] == {"Accept": "application/json"} args, kwargs = mockput.call_args assert args[ 0] == "https://api.sandbox.orcid.org/0000-0000-0000-00X3/webhook/http%3A%2F%2FCALL-BACK" assert kwargs["headers"] == { "Accept": "application/json", "Authorization": "Bearer ACCESS-TOKEN-123", "Content-Length": "0" } q = OrcidToken.select().where(OrcidToken.org == org, OrcidToken.scope == "/webhook") assert q.count() == 1 orcid_token = q.first() assert orcid_token.access_token == "ACCESS-TOKEN-123" assert orcid_token.refresh_token == "REFRESH-TOKEN-123" assert orcid_token.expires_in == 99999 assert orcid_token.scope == "/webhook" with app_req_ctx( f"/api/v1.0/{orcid_id}/webhook/http%3A%2F%2FCALL-BACK", method="DELETE", headers=dict(authorization=f"Bearer {token.access_token}") ) as ctx, patch("orcid_hub.utils.requests.delete") as mockdelete: # Webhook deletion response: mockresp = MagicMock(status_code=204, data=b'') # mockresp.raw.stream = lambda *args, **kwargs: iter([b"""{"data": "TEST"}"""]) mockresp.raw.headers = { "Server": "TEST123", "Connection": "keep-alive", "Location": "LOCATION", "Pragma": "no-cache", "Expires": "0", } mockdelete.return_value = mockresp resp = ctx.app.full_dispatch_request() assert resp.status_code == 204 args, kwargs = mockput.call_args assert args[ 0] == "https://api.sandbox.orcid.org/0000-0000-0000-00X3/webhook/http%3A%2F%2FCALL-BACK" assert kwargs["headers"] == { "Accept": "application/json", "Authorization": "Bearer ACCESS-TOKEN-123", "Content-Length": "0" } q = OrcidToken.select().where(OrcidToken.org == org, OrcidToken.scope == "/webhook") assert q.count() == 1 token = q.first() assert token.access_token == "ACCESS-TOKEN-123" assert token.refresh_token == "REFRESH-TOKEN-123" assert token.expires_in == 99999 assert token.scope == "/webhook"