Skip to content

Commit

Permalink
global: add CERN OpenID contrib
Browse files Browse the repository at this point in the history
  • Loading branch information
zzacharo authored and ntarocco committed Aug 31, 2020
1 parent 273ae36 commit e718cdf
Show file tree
Hide file tree
Showing 14 changed files with 821 additions and 62 deletions.
402 changes: 402 additions & 0 deletions invenio_oauthclient/contrib/cern_openid.py

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion invenio_oauthclient/contrib/github.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@
from invenio_oauthclient.handlers import authorized_signup_handler, \
oauth_error_handler
from invenio_oauthclient.handlers.rest import \
authorized_signup_handler as authorized_signup_rest_handler, \
authorized_signup_handler as authorized_signup_rest_handler
from invenio_oauthclient.handlers.rest import \
oauth_resp_remote_error_handler, response_handler
from invenio_oauthclient.models import RemoteAccount
from invenio_oauthclient.utils import oauth_link_external_id, \
Expand Down
14 changes: 7 additions & 7 deletions invenio_oauthclient/handlers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@

from __future__ import absolute_import, print_function

from .rest import authorized_default_handler as \
authorized_default_handler_rest, \
authorized_signup_handler as authorized_signup_handler_rest, \
disconnect_handler as disconnect_handler_rest, \
signup_handler as signup_handler_rest
from .rest import authorized_default_handler as authorized_default_handler_rest
from .rest import authorized_signup_handler as authorized_signup_handler_rest
from .rest import disconnect_handler as disconnect_handler_rest
from .rest import signup_handler as signup_handler_rest
from .ui import authorized_default_handler, authorized_signup_handler, \
disconnect_handler, oauth2_handle_error, \
oauth_resp_remote_error_handler as oauth_error_handler, signup_handler
disconnect_handler, oauth2_handle_error
from .ui import oauth_resp_remote_error_handler as oauth_error_handler
from .ui import signup_handler
from .utils import authorized_handler, get_session_next_url, make_handler, \
make_token_getter, oauth1_token_setter, oauth2_token_setter, \
oauth_logout_handler, response_token_setter, set_session_next_url, \
Expand Down
10 changes: 5 additions & 5 deletions invenio_oauthclient/handlers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@
from werkzeug.utils import import_string

from ..errors import AlreadyLinkedError, OAuthCERNRejectedAccountError, \
OAuthClientError, OAuthError, OAuthRejectedRequestError, \
OAuthResponseError, OAuthClientAlreadyAuthorized, \
OAuthClientUnAuthorized, OAuthClientTokenNotSet, \
OAuthClientUserNotRegistered, OAuthClientTokenNotFound, \
OAuthClientMustRedirectLogin, OAuthClientMustRedirectSignup
OAuthClientAlreadyAuthorized, OAuthClientError, \
OAuthClientMustRedirectLogin, OAuthClientMustRedirectSignup, \
OAuthClientTokenNotFound, OAuthClientTokenNotSet, \
OAuthClientUnAuthorized, OAuthClientUserNotRegistered, OAuthError, \
OAuthRejectedRequestError, OAuthResponseError
from ..models import RemoteAccount, RemoteToken
from ..proxies import current_oauthclient
from ..signals import account_info_received, account_setup_committed, \
Expand Down
25 changes: 11 additions & 14 deletions invenio_oauthclient/handlers/rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,30 +12,27 @@

from functools import partial, wraps

from flask import abort, current_app, jsonify, make_response, \
redirect, render_template, request, session, url_for
from flask import abort, current_app, jsonify, make_response, redirect, \
render_template, request, session, url_for
from flask_login import current_user
from invenio_db import db
from six.moves.urllib.parse import parse_qs, urlencode, urlsplit, urlunsplit

from ..errors import AlreadyLinkedError, OAuthClientError, OAuthError, \
OAuthRejectedRequestError, OAuthClientAlreadyAuthorized, \
OAuthClientUnAuthorized, OAuthClientTokenNotSet, \
OAuthClientUserNotRegistered, OAuthClientTokenNotFound, \
OAuthClientMustRedirectLogin, OAuthClientMustRedirectSignup
from ..errors import AlreadyLinkedError, OAuthClientAlreadyAuthorized, \
OAuthClientError, OAuthClientMustRedirectLogin, \
OAuthClientMustRedirectSignup, OAuthClientTokenNotFound, \
OAuthClientTokenNotSet, OAuthClientUnAuthorized, \
OAuthClientUserNotRegistered, OAuthError, OAuthRejectedRequestError
from ..models import RemoteAccount
from ..proxies import current_oauthclient
from ..signals import account_info_received, account_setup_committed, \
account_setup_received
from ..utils import oauth_authenticate, oauth_get_user
from .utils import create_csrf_disabled_registrationform, \
get_session_next_url, response_token_setter, token_getter, \
token_session_key, token_setter, fill_form
from .base import (
base_authorized_signup_handler,
base_disconnect_handler,
from .base import base_authorized_signup_handler, base_disconnect_handler, \
base_signup_handler
)
from .utils import create_csrf_disabled_registrationform, fill_form, \
get_session_next_url, response_token_setter, token_getter, \
token_session_key, token_setter


def response_handler_postmessage(remote, url, payload=None):
Expand Down
23 changes: 9 additions & 14 deletions invenio_oauthclient/handlers/ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,28 +20,23 @@
from invenio_db import db
from werkzeug.utils import import_string

from ..errors import AlreadyLinkedError, OAuthClientError, OAuthError, \
OAuthRejectedRequestError, OAuthResponseError, \
OAuthClientAlreadyAuthorized, OAuthClientUnAuthorized, \
OAuthClientTokenNotSet, OAuthClientUserNotRegistered, \
OAuthClientTokenNotFound, OAuthClientMustRedirectLogin, \
OAuthClientMustRedirectSignup
from ..errors import AlreadyLinkedError, OAuthClientAlreadyAuthorized, \
OAuthClientError, OAuthClientMustRedirectLogin, \
OAuthClientMustRedirectSignup, OAuthClientTokenNotFound, \
OAuthClientTokenNotSet, OAuthClientUnAuthorized, \
OAuthClientUserNotRegistered, OAuthError, OAuthRejectedRequestError, \
OAuthResponseError
from ..models import RemoteAccount, RemoteToken
from ..proxies import current_oauthclient
from ..signals import account_info_received, account_setup_committed, \
account_setup_received
from ..utils import create_csrf_disabled_registrationform, \
create_registrationform, fill_form, oauth_authenticate, oauth_get_user, \
oauth_register
create_registrationform, fill_form, oauth_authenticate, oauth_register
from .base import base_authorized_signup_handler, base_disconnect_handler, \
base_signup_handler
from .utils import get_session_next_url, response_token_setter, token_getter, \
token_session_key, token_setter

from .base import (
base_authorized_signup_handler,
base_disconnect_handler,
base_signup_handler
)


def _oauth_error_handler(remote, f, *args, **kwargs):
"""Function to handle exceptions."""
Expand Down
1 change: 1 addition & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@
pep8ignore = docs/conf.py ALL
addopts = --pep8 --doctest-glob="*.rst" --doctest-modules --cov=invenio_oauthclient --cov-report=term-missing
testpaths = docs tests invenio_oauthclient
filterwarnings = ignore::pytest.PytestDeprecationWarning
4 changes: 2 additions & 2 deletions run-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
# -*- coding: utf-8 -*-
#
# This file is part of Invenio.
# Copyright (C) 2015-2018 CERN.
# Copyright (C) 2015-2020 CERN.
#
# Invenio is free software; you can redistribute it and/or modify it
# under the terms of the MIT License; see LICENSE file for more details.


pydocstyle invenio_oauthclient tests && \
isort -rc -c -df **/*.py && \
isort invenio_oauthclient tests --check --diff && \
check-manifest --ignore ".travis-*" && \
sphinx-build -qnNW docs docs/_build/html && \
python setup.py test
8 changes: 1 addition & 7 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,13 @@
history = open('CHANGES.rst').read()

tests_require = [
'pytest-invenio>=1.3.2',
'SQLAlchemy-Continuum>=1.2.1',
'check-manifest>=0.25',
'coverage>=4.0',
'httpretty>=0.8.14',
'invenio-userprofiles>=1.0.0',
'requests-oauthlib>=0.6.2,<1.2.0',
'oauthlib>=1.1.2,<3.0.0',
'isort>=4.3.0',
'mock>=1.3.0',
'pydocstyle>=1.0.0',
'pytest-cov>=2.8.1',
'pytest-pep8>=1.0.6',
'pytest>=4.6.0,<5.0.0',
'simplejson>=3.8',
]

Expand Down
10 changes: 10 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@
from invenio_oauthclient.contrib.cern import REMOTE_APP as CERN_REMOTE_APP
from invenio_oauthclient.contrib.cern import \
REMOTE_REST_APP as CERN_REMOTE_REST_APP
from invenio_oauthclient.contrib.cern_openid import \
REMOTE_APP as CERN_OPENID_REMOTE_APP
from invenio_oauthclient.contrib.cern_openid import \
REMOTE_REST_APP as CERN_OPENID_REMOTE_REST_APP
from invenio_oauthclient.contrib.github import REMOTE_APP as GITHUB_REMOTE_APP
from invenio_oauthclient.contrib.github import \
REMOTE_REST_APP as GITHUB_REMOTE_REST_APP
Expand Down Expand Up @@ -69,12 +73,14 @@ def base_app(request):
CACHE_TYPE='simple',
OAUTHCLIENT_REMOTE_APPS=dict(
cern=CERN_REMOTE_APP,
cern_openid=CERN_OPENID_REMOTE_APP,
orcid=ORCID_REMOTE_APP,
github=GITHUB_REMOTE_APP,
globus=GLOBUS_REMOTE_APP,
),
OAUTHCLIENT_REST_REMOTE_APPS=dict(
cern=CERN_REMOTE_REST_APP,
cern_openid=CERN_OPENID_REMOTE_REST_APP,
orcid=ORCID_REMOTE_REST_APP,
github=GITHUB_REMOTE_REST_APP,
globus=GLOBUS_REMOTE_REST_APP,
Expand All @@ -92,6 +98,10 @@ def base_app(request):
consumer_key='cern_key_changeme',
consumer_secret='cern_secret_changeme',
),
CERN_APP_OPENID_CREDENTIALS=dict(
consumer_key='cern_key_changeme',
consumer_secret='cern_secret_changeme',
),
GLOBUS_APP_CREDENTIALS=dict(
consumer_key='globus_key_changeme',
consumer_secret='globus_secret_changeme',
Expand Down
8 changes: 8 additions & 0 deletions tests/data/cern_openid_response_content.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"email": "john.doe@cern.ch",
"cern_person_id": "234567",
"cern_upn": "222222",
"preferred_username": "jdoe",
"name": "John Doe",
"cern_roles": ["cern_user"]
}
175 changes: 175 additions & 0 deletions tests/test_contrib_cern_openid.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
# -*- coding: utf-8 -*-
#
# This file is part of Invenio.
# Copyright (C) 2016-2018 CERN.
#
# Invenio is free software; you can redistribute it and/or modify it
# under the terms of the MIT License; see LICENSE file for more details.

"""Test case for CERN oauth remote app."""

from __future__ import absolute_import

import os

import pytest
from flask import g, session, url_for
from flask_security import login_user
from helpers import get_state, mock_remote_get, mock_response
from six.moves.urllib_parse import parse_qs, urlparse

from invenio_oauthclient.contrib.cern_openid import account_info, \
disconnect_handler, fetch_extra_data, get_dict_from_response
from invenio_oauthclient.errors import OAuthCERNRejectedAccountError

from flask_oauthlib.client import OAuthResponse # noqa isort:skip


@pytest.fixture()
def example_cern_openid(request):
"""CERN openid example data."""
file_path = os.path.join(os.path.dirname(__file__),
'data/cern_openid_response_content.json')
with open(file_path) as response_file:
json_data = response_file.read()

from jwt import encode
token = encode(dict(name="John Doe"), key="1234")
return OAuthResponse(
resp=None,
content=json_data,
content_type='application/json'
), dict(
access_token=token,
token_type='bearer',
expires_in=1199,
refresh_token='test_refresh_token'
), dict(
user=dict(
email='john.doe@cern.ch',
profile=dict(username='jdoe', full_name='John Doe'),
),
external_id='222222', external_method='cern_openid',
active=True
)


def test_fetch_extra_data(app, example_cern_openid):
"""Test extra data extraction."""
example_response, example_token, _ = example_cern_openid
res = get_dict_from_response(example_response)

# Check that groups were hidden as required
extra_data = fetch_extra_data(res)

assert 'person_id' in extra_data
assert extra_data['person_id'] == "234567"


def test_account_info(app, example_cern_openid):
"""Test account info extraction."""
client = app.test_client()
ioc = app.extensions['oauthlib.client']

# Ensure remote apps have been loaded (due to before first request)
client.get(url_for('invenio_oauthclient.login', remote_app='cern_openid'))

example_response, _, example_account_info = example_cern_openid

mock_remote_get(ioc, 'cern_openid', example_response)

assert account_info(
ioc.remote_apps['cern_openid'], None) == example_account_info


def test_account_setup(app, example_cern_openid, models_fixture):
"""Test account setup after login."""
with app.test_client() as c:
ioc = app.extensions['oauthlib.client']

# Ensure remote apps have been loaded (due to before first request)
resp = c.get(url_for('invenio_oauthclient.login',
remote_app='cern_openid'))
assert resp.status_code == 302

example_response, example_token, example_account_info = \
example_cern_openid

mock_response(app.extensions['oauthlib.client'], 'cern_openid',
example_token)
mock_remote_get(ioc, 'cern_openid', example_response)

resp = c.get(url_for(
'invenio_oauthclient.authorized',
remote_app='cern_openid', code='test',
state=get_state('cern_openid')))
assert resp.status_code == 302
assert resp.location == ('http://localhost/account/settings/'
'linkedaccounts/')
assert len(g.identity.provides) == 3

datastore = app.extensions['invenio-accounts'].datastore
user = datastore.find_user(email='john.doe@cern.ch')
assert user

with app.test_request_context():
resp = disconnect_handler(ioc.remote_apps['cern_openid'])
assert resp.status_code >= 300

login_user(user)
assert len(g.identity.provides) == 3
disconnect_handler(ioc.remote_apps['cern_openid'])


def test_login(app):
"""Test CERN login."""
client = app.test_client()

resp = client.get(
url_for('invenio_oauthclient.login', remote_app='cern_openid',
next='/someurl/')
)
assert resp.status_code == 302

params = parse_qs(urlparse(resp.location).query)
assert params['response_type'], ['code']
assert params['redirect_uri']
assert params['client_id']
assert params['state']


def test_authorized_reject(app):
"""Test a rejected request."""
with app.test_client() as c:
c.get(url_for('invenio_oauthclient.login', remote_app='cern_openid'))
resp = c.get(
url_for('invenio_oauthclient.authorized',
remote_app='cern_openid', error='access_denied',
error_description='User denied access',
state=get_state('cern_openid')))
assert resp.status_code in (301, 302)
assert resp.location == 'http://localhost/'
# Check message flash
assert session['_flashes'][0][0] == 'info'


def test_account_info_not_allowed_account(app, example_cern_openid):
"""Test account info extraction."""
client = app.test_client()

app.config['OAUTHCLIENT_CERN_OPENID_ALLOWED_ROLES'] = [
'another cern role'
]
ioc = app.extensions['oauthlib.client']

# Ensure remote apps have been loaded (due to before first request)
client.get(url_for('invenio_oauthclient.login', remote_app='cern_openid'))

example_response, _, example_account_info = example_cern_openid

mock_remote_get(ioc, 'cern_openid', example_response)

resp = account_info(ioc.remote_apps['cern_openid'], None)
assert resp.status_code == 302
assert session['_flashes'][0][0] == 'danger'
assert session['_flashes'][0][1] == 'CERN account not allowed.'

0 comments on commit e718cdf

Please sign in to comment.