Skip to content

Commit

Permalink
Merge e70e356 into 81ee0c9
Browse files Browse the repository at this point in the history
  • Loading branch information
aarranz committed Jul 5, 2018
2 parents 81ee0c9 + e70e356 commit 3bab8ed
Show file tree
Hide file tree
Showing 12 changed files with 275 additions and 118 deletions.
3 changes: 2 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ env:
- CKANVERSION=2.7.3 POSTGISVERSION=2 INTEGRATION_TEST=true
- CKANVERSION=2.8.0 POSTGISVERSION=2
services:
- docker
- redis-server
- postgresql
addons:
Expand All @@ -18,7 +19,7 @@ before_install:
- tar -xzf geckodriver-v0.20.1-linux64.tar.gz -C geckodriver
- export PATH=$PATH:$PWD/geckodriver
install:
- bash bin/travis-build.bash
- . bin/travis-build.bash
script:
- bash bin/travis-run.sh
after_success: coveralls
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ OAuth2 CKAN extension

The OAuth2 extension allows site visitors to login through an OAuth2 server.

**Note**: This extension is being tested in CKAN 2.6 and 2.7. These are therefore considered as the supported versions
**Note**: This extension is being tested in CKAN 2.6, 2.7 and 2.8. These are therefore considered as the supported versions


## Links
Expand Down
19 changes: 18 additions & 1 deletion bin/travis-build.bash
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,21 @@ cd -
echo "Installing ckanext-oauth2 and its requirements..."
python setup.py develop

echo "travis-build.bash is done."
if [ "$INTEGRATION_TEST" = "true" ]; then
sudo sh -c 'echo "\n[ SAN ]\nsubjectAltName=DNS:localhost" >> /etc/ssl/openssl.cnf'
sudo openssl req -new -newkey rsa:2048 -days 3650 -nodes -x509 \
-subj '/O=API Umbrella/CN=localhost' \
-keyout /etc/ssl/self_signed.key -out /usr/local/share/ca-certificates/self_signed.crt \
-reqexts SAN -extensions SAN

sudo update-ca-certificates
export REQUESTS_CA_BUNDLE="/etc/ssl/certs/ca-certificates.crt"
docker network create main
docker run -d --network main -e MYSQL_ROOT_PASSWORD=idm -e MYSQL_ROOT_HOST=% --name mysql mysql/mysql-server:5.7.21
docker run -d -p 443:443 --network main -e DATABASE_HOST=mysql -v "${TRAVIS_BUILD_DIR}/ci/idm-config.js:/opt/fiware-idm/config.js:ro" -v /etc/ssl/self_signed.key:/opt/fiware-idm/certs/self_signed.key:ro -v /usr/local/share/ca-certificates/self_signed.crt:/opt/fiware-idm/certs/self_signed.crt:ro --name idm fiware/idm

# Wait until idm is ready
sleep 30
fi

echo "travis-build.bash is done."
89 changes: 89 additions & 0 deletions ci/idm-config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
var config = {};

config.host = 'https://localhost';
config.port = 3000

// HTTPS enable
config.https = {
enabled: true,
cert_file: 'certs/self_signed.crt',
key_file: 'certs/self_signed.key',
port: 443
};

// Config email list type to use domain filtering
config.email_list_type = null // whitelist or blacklist

// Secret for user sessions in web
config.session = {
secret: 'nodejs_idm', // Must be changed
expires: 60 * 60 * 1000 // 1 hour
}

// Key to encrypt user passwords
config.password_encryption = {
key: 'nodejs_idm' // Must be changed
}

// Config oauth2 parameters
config.oauth2 = {
authorization_code_lifetime: 5 * 60, // Five minutes
access_token_lifetime: 60 * 60, // One hour
refresh_token_lifetime: 60 * 60 * 24 * 14 // Two weeks
}

// Config api parameters
config.api = {
token_lifetime: 60 * 60 // One hour
}

// Enable authzforce
config.authzforce = {
enabled: false,
host: '',
port: 8080
}

var database_host = (process.env.DATABASE_HOST) ? process.env.DATABASE_HOST : 'localhost'

// Database info
config.database = {
host: database_host, // default: 'localhost'
password: 'idm', // default: 'idm'
username: 'root', // default: 'root'
database: 'idm', // default: 'idm'
dialect: 'mysql', // default: 'mysql'
port: undefined // default: undefined (which means that the port
// is the default for each dialect)
};

// External user authentication
config.external_auth = {
enabled: false,
authentication_driver: 'custom_authentication_driver',
database: {
host: 'localhost',
database: 'db_name',
username: 'db_user',
password: 'db_pass',
user_table: 'user',
dialect: 'mysql',
port: undefined
}
}

// Email configuration
config.mail = {
host: 'localhost',
port: 25,
from: 'noreply@localhost'
}


// Config themes
config.site = {
title: 'Identity Manager',
theme: 'default'
};

module.exports = config;
16 changes: 15 additions & 1 deletion ckanext/oauth2/controller.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-

# Copyright (c) 2014 CoNWeT Lab., Universidad Politécnica de Madrid
# Copyright (c) 2018 Future Internet Consulting and Development Solutions S.L.

# This file is part of OAuth2 CKAN Extension.

Expand All @@ -25,7 +26,7 @@
import ckan.lib.base as base

from ckan.common import session
from ckanext.oauth2.plugin import toolkit
from ckanext.oauth2.plugin import toolkit, _get_previous_page


log = logging.getLogger(__name__)
Expand All @@ -36,6 +37,19 @@ class OAuth2Controller(base.BaseController):
def __init__(self):
self.oauth2helper = oauth2.OAuth2Helper()

def login(self):
log.debug('login')

# Log in attemps are fired when the user is not logged in and they click
# on the log in button

# Get the page where the user was when the loggin attemp was fired
# When the user is not logged in, he/she should be redirected to the dashboard when
# the system cannot get the previous page
came_from_url = _get_previous_page(constants.INITIAL_PAGE)

self.oauth2helper.challenge(came_from_url)

def callback(self):
try:
token = self.oauth2helper.get_token()
Expand Down
9 changes: 7 additions & 2 deletions ckanext/oauth2/oauth2.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import os

from base64 import b64encode, b64decode
from ckan.lib import helpers as h
from ckan.plugins import toolkit
from oauthlib.oauth2 import InsecureTransportError
import requests
Expand All @@ -48,11 +49,16 @@ def get_came_from(state):
return json.loads(b64decode(state)).get(constants.CAME_FROM_FIELD, '/')


REQUIRED_CONF = ("authorization_endpoint", "token_endpoint", "client_id", "client_secret", "profile_api_url", "profile_api_user_field", "profile_api_mail_field")


class OAuth2Helper(object):

def __init__(self):

self.verify_https = os.environ.get('OAUTHLIB_INSECURE_TRANSPORT', '') == ""
if self.verify_https and os.environ.get("REQUESTS_CA_BUNDLE", "").strip() != "":
self.verify_https = os.environ["REQUESTS_CA_BUNDLE"].strip()

self.legacy_idm = six.text_type(os.environ.get('CKAN_OAUTH2_LEGACY_IDM', toolkit.config.get('ckan.oauth2.legacy_idm', ''))).strip().lower() in ("true", "1", "on")
self.authorization_endpoint = six.text_type(os.environ.get('CKAN_OAUTH2_AUTHORIZATION_ENDPOINT', toolkit.config.get('ckan.oauth2.authorization_endpoint', ''))).strip()
Expand Down Expand Up @@ -85,9 +91,8 @@ def challenge(self, came_from_url):
state = generate_state(came_from_url)
oauth = OAuth2Session(self.client_id, redirect_uri=self.redirect_uri, scope=self.scope, state=state)
auth_url, _ = oauth.authorization_url(self.authorization_endpoint)
toolkit.response.status = 302
toolkit.response.location = auth_url
log.debug('Challenge: Redirecting challenge to page {0}'.format(auth_url))
return h.redirect_to(auth_url)

def get_token(self):
oauth = OAuth2Session(self.client_id, redirect_uri=self.redirect_uri, scope=self.scope)
Expand Down
63 changes: 29 additions & 34 deletions ckanext/oauth2/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

from functools import partial
from ckan import plugins
from ckan.common import g
from ckan.plugins import toolkit
from urlparse import urlparse

Expand Down Expand Up @@ -62,6 +63,27 @@ def request_reset(context, data_dict):
return _no_permissions(context, msg)


def _get_previous_page(default_page):
if 'came_from' not in toolkit.request.params:
came_from_url = toolkit.request.headers.get('Referer', default_page)
else:
came_from_url = toolkit.request.params.get('came_from', default_page)

came_from_url_parsed = urlparse(came_from_url)

# Avoid redirecting users to external hosts
if came_from_url_parsed.netloc != '' and came_from_url_parsed.netloc != toolkit.request.host:
came_from_url = default_page

# When a user is being logged and REFERER == HOME or LOGOUT_PAGE
# he/she must be redirected to the dashboard
pages = ['/', '/user/logged_out_redirect']
if came_from_url_parsed.path in pages:
came_from_url = default_page

return came_from_url


class OAuth2Plugin(plugins.SingletonPlugin):

plugins.implements(plugins.IAuthenticator, inherit=True)
Expand All @@ -78,6 +100,10 @@ def __init__(self, name=None):
def before_map(self, m):
log.debug('Setting up the redirections to the OAuth2 service')

m.connect('/user/login',
controller='ckanext.oauth2.controller:OAuth2Controller',
action='login')

# We need to handle petitions received to the Callback URL
# since some error can arise and we need to process them
m.connect('/oauth2/callback',
Expand Down Expand Up @@ -125,45 +151,14 @@ def _refresh_and_save_token(user_name):

# If we have been able to log in the user (via API or Session)
if user_name:
g.user = user_name
toolkit.c.user = user_name
toolkit.c.usertoken = self.oauth2helper.get_stored_token(user_name)
toolkit.c.usertoken_refresh = partial(_refresh_and_save_token, user_name)
else:
g.user = None
log.warn('The user is not currently logged...')

def _get_previous_page(self, default_page):
if 'came_from' not in toolkit.request.params:
came_from_url = toolkit.request.headers.get('Referer', default_page)
else:
came_from_url = toolkit.request.params.get('came_from', default_page)

came_from_url_parsed = urlparse(came_from_url)

# Avoid redirecting users to external hosts
if came_from_url_parsed.netloc != '' and came_from_url_parsed.netloc != toolkit.request.host:
came_from_url = default_page

# When a user is being logged and REFERER == HOME or LOGOUT_PAGE
# he/she must be redirected to the dashboard
pages = ['/', '/user/logged_out_redirect']
if came_from_url_parsed.path in pages:
came_from_url = default_page

return came_from_url

def login(self):
log.debug('login')

# Log in attemps are fired when the user is not logged in and they click
# on the log in button

# Get the page where the user was when the loggin attemp was fired
# When the user is not logged in, he/she should be redirected to the dashboard when
# the system cannot get the previous page
came_from_url = self._get_previous_page(constants.INITIAL_PAGE)

self.oauth2helper.challenge(came_from_url)

def abort(self, status_code, detail, headers, comment):
log.debug('abort')

Expand All @@ -176,7 +171,7 @@ def abort(self, status_code, detail, headers, comment):
if toolkit.c.user: # USER IS AUTHENTICATED
# When the user is logged in, he/she should be redirected to the main page when
# the system cannot get the previous page
came_from_url = self._get_previous_page('/')
came_from_url = _get_previous_page('/')

# Init headers and set Location
if headers is None:
Expand Down

0 comments on commit 3bab8ed

Please sign in to comment.