Skip to content

Commit

Permalink
contrib: removal of dependency on Invenio-Groups
Browse files Browse the repository at this point in the history
* Removes the dependency on Invenio-Groups reducing the number of fields
  parsed inside CERN or ORCID modules and sending the response to a
  subscribable signal. (closes #18)

Signed-off-by: Javier Delgado <javier.delgado.fernandez@cern.ch>
  • Loading branch information
JavierDelgadoFernandez committed Dec 3, 2015
1 parent 176295c commit 8466596
Show file tree
Hide file tree
Showing 7 changed files with 209 additions and 28 deletions.
167 changes: 167 additions & 0 deletions examples/cern_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
# -*- coding: utf-8 -*-
#
# This file is part of Invenio.
# Copyright (C) 2015 CERN.
#
# Invenio is free software; you can redistribute it
# and/or modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 2 of the
# License, or (at your option) any later version.
#
# Invenio is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Invenio; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
# MA 02111-1307, USA.
#
# In applying this license, CERN does not
# waive the privileges and immunities granted to it by virtue of its status
# as an Intergovernmental Organization or submit itself to any jurisdiction.

r"""Minimal Flask application example for development with CERN handler.
Usage:
1. Register a CERN application in
`https://sso-management.web.cern.ch/OAuth/RegisterOAuthClient.aspx` with
`redirect_uri` as and filling all the fields:
`https://localhost:5000/oauth/authorized/cern/`
2. Ensure you have ``gunicorn`` package installed:
.. code-block:: console
cdvirtualenv src/invenio-oauthclient
pip install -e gunicorn
3. Ensure you have ``openssl`` installed in your system (Most of the Linux
distributions has it by default.).
3. Grab the *client_id* and *secret_uri* after registering the application
and add them to your instance configuration as `consumer_key` and
`consumer_secret`.
.. code-block:: console
$ export CERN_APP_CREDENTIALS_KEY=my_cern_client_id
$ export CERN_APP_CREDENTIALS_SECRET=my_cern_secret_uri
4. Create database and tables:
.. code-block:: console
$ cd examples
$ flask -a cern_app.py db init
$ flask -a cern_app.py db create
You can find the database in `examples/cern_app.db`.
5. Create the key and the certificate in order to run a HTTPS server:
.. code-block:: console
$ openssl genrsa 1024 > ssl.key
$ openssl req -new -x509 -nodes -sha1 -key ssl.key > ssl.crt
6. Run gunicorn server:
.. code-block:: console
$ gunicorn -b :5000 --certfile=ssl.crt --keyfile=ssl.key cern_app:app
7. Open in a browser the page `https://localhost:5000/`.
You will be redirected to CERN to authorize the application.
Click on `Grant` and you will be redirected back to
`https://localhost:5000/oauth/authorized/cern/`
Now, you will be again in homepage but this time it say:
`hello youremail@cern.ch`.
You have completed the user authorization.
"""

from __future__ import absolute_import, print_function

import copy
import os

from invenio_oauthclient.signals import account_setup_received

from flask import Flask, redirect, url_for
from flask_babelex import Babel
from flask_cli import FlaskCLI
from flask_login import current_user
from flask_menu import Menu as FlaskMenu
from flask_oauthlib.client import OAuth as FlaskOAuth
from invenio_accounts import InvenioAccounts
from invenio_accounts.views import blueprint as blueprint_user
from invenio_db import InvenioDB

from invenio_oauthclient import InvenioOAuthClient
from invenio_oauthclient.contrib import cern
from invenio_oauthclient.views.client import blueprint as blueprint_client
from invenio_oauthclient.views.settings import blueprint as blueprint_settings


# [ Configure application credentials ]
CERN_APP_CREDENTIALS = dict(
consumer_key=os.environ.get('CERN_APP_CREDENTIALS_KEY'),
consumer_secret=os.environ.get('CERN_APP_CREDENTIALS_SECRET'),
)

# Create Flask application
app = Flask(__name__)

CERN_REMOTE_APP = copy.deepcopy(cern.REMOTE_APP)
CERN_REMOTE_APP["params"].update(dict(request_token_params={
"scope": "Name Email Bio Groups",
}))

app.config.update(
SQLALCHEMY_DATABASE_URI=os.environ.get(
'SQLALCHEMY_DATABASE_URI', 'sqlite:///cern_app.db'
),
OAUTHCLIENT_REMOTE_APPS=dict(
cern=CERN_REMOTE_APP
),
CERN_APP_CREDENTIALS=CERN_APP_CREDENTIALS,
DEBUG=True,
SECRET_KEY='TEST',
SECURITY_PASSWORD_SALT='security-password-salt',
)

FlaskCLI(app)
Babel(app)
FlaskMenu(app)
InvenioDB(app)
InvenioAccounts(app)
FlaskOAuth(app)
InvenioOAuthClient(app)

app.register_blueprint(blueprint_user)
app.register_blueprint(blueprint_client)
app.register_blueprint(blueprint_settings)


@account_setup_received.connect
def oauth_response(remote, response=None, account_setup=None):
"""Prints the user information when the login in CERN OAuth is done."""
me = remote.get('https://oauthresource.web.cern.ch/api/Me')
print(me.data)


@app.route('/')
def index():
"""Home page: try to print user email or redirect to login with cern."""
if not current_user.is_authenticated():
return redirect(url_for("invenio_oauthclient.login",
remote_app='cern'))
return "hello {}".format(current_user.email)
24 changes: 6 additions & 18 deletions invenio_oauthclient/contrib/cern.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
import re

import requests
from blinker import signal
from flask import current_app
from flask_login import current_user

Expand Down Expand Up @@ -159,6 +160,9 @@
REMOTE_APP_RESOURCE_SCHEMA = "http://schemas.xmlsoap.org/claims/"


oauth_cern_response_received = signal('oauth-cern-response-received')


def fetch_groups(groups):
"""Prepare list of allowed group names."""
hidden_groups = current_app.config.get(
Expand Down Expand Up @@ -201,22 +205,6 @@ def account_info(remote, resp):
return dict(email=email.lower(), nickname=common_name)


def account_setup(remote, token):
def account_setup(remote, token, resp):
"""Perform additional setup after user have been logged in."""
from invenio_db import db

response = remote.get(REMOTE_APP_RESOURCE_API_URL)
user = token.remote_account.user

if response.status == requests.codes.ok:
res = get_dict_from_response(response)
current_user.info['group'] = fetch_groups(res['Group'])
current_user.modified = True
current_user.save()

if user and not any([user.family_name, user.given_names]):
user.family_name = res['Lastname'][0]
user.given_names = res['Firstname'][0]

db.session.add(user)
current_user.reload()
return None
2 changes: 1 addition & 1 deletion invenio_oauthclient/contrib/github.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,6 @@ def account_info(remote, resp):
external_method='github')


def account_setup(remote, token):
def account_setup(remote, token, resp):
"""Perform additional setup after user have been logged in."""
pass
7 changes: 7 additions & 0 deletions invenio_oauthclient/contrib/orcid.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@

import copy

from blinker import signal
from flask import current_app, redirect, url_for
from flask_login import current_user
from invenio_db import db
Expand Down Expand Up @@ -119,9 +120,13 @@
))


oauth_orcid_response_received = signal('oauth-orcid-response-received')


def account_info(remote, resp):
"""Retrieve remote account information used to find local user."""
orcid = resp.get("orcid")
oauth_orcid_response_received.send(resp)
return dict(
external_id=orcid,
external_method="orcid",
Expand Down Expand Up @@ -166,6 +171,8 @@ def account_setup(remote, token, resp):
# Create user <-> external id link.
oauth_link_external_id(user, dict(id=orcid, method="orcid"))

oauth_orcid_response_received.send(resp)

# FIXME put these data in user profile!
# Fill user full name if not already set
# if user and not any([user.given_names, user.family_name]):
Expand Down
15 changes: 9 additions & 6 deletions invenio_oauthclient/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@

from __future__ import absolute_import

import warnings
from functools import partial, wraps

import six
Expand All @@ -36,7 +35,7 @@
from .forms import EmailSignUpForm
from .models import RemoteAccount, RemoteToken
from .proxies import current_oauthclient
from .signals import account_info_received
from .signals import account_info_received, account_setup_received
from .utils import oauth_authenticate, oauth_get_user, oauth_register


Expand Down Expand Up @@ -260,8 +259,10 @@ def authorized_signup_handler(resp, remote, *args, **kwargs):
# Setup account
# -------------
if not token.remote_account.extra_data:
handlers['setup'](token, resp)
# TODO add signal for account setup
account_setup = handlers['setup'](token, resp)
account_setup_received.send(
remote, response=resp, account_setup=account_setup
)

# Redirect to next
next_url = get_session_next_url(remote.name)
Expand Down Expand Up @@ -341,8 +342,10 @@ def signup_handler(remote, *args, **kwargs):
raise OAuthError("Could not create token for user.", remote)

if not token.remote_account.extra_data:
handlers['setup'](token, response)
# TODO add signal for account setup
account_setup = handlers['setup'](token, response)
account_setup_received.send(
remote, response=response, account_setup=account_setup
)

# Remove account info from session
session.pop(session_prefix + '_account_info', None)
Expand Down
21 changes: 18 additions & 3 deletions invenio_oauthclient/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,26 @@
from invenio_oauthclient.signals import account_info_received
# During overlay initialization.
@account_info_received.connect_via(
sender=app.extensions['invenio-oauthclient'].handlers['orcid']
)
@account_info_received.connect
def load_extra_information(remote, response=None, account_info=None):
response = remote.get('https://example.org/api/resource')
# process response
"""

account_setup_received = _signals.signal('oauthclient-account-setup-received')
"""Signal is sent after account info handler response.
Example subscriber:
.. code-block:: python
from invenio_oauthclient.signals import account_setup_received
# During overlay initialization.
@account_setup_received.connect
def load_extra_information(remote, response=None, account_setup=None):
response = remote.get('https://example.org/api/resource')
# process response
"""
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
]

install_requires = [
'blinker>=1.4',
'cryptography>=0.6', # sqlalchemy-utils dependency
'Flask>=0.10.1',
'Flask-BabelEx>=0.9.2',
Expand Down

0 comments on commit 8466596

Please sign in to comment.