diff --git a/.travis.yml b/.travis.yml
index ab272e5..493b877 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,11 +3,12 @@ language: python
python:
- "2.7"
env:
- - CKANVERSION=2.6.3 POSTGISVERSION=2
+ - CKANVERSION=2.6.3 POSTGISVERSION=2 INTEGRATION_TEST=true
- CKANVERSION=2.7.2 POSTGISVERSION=2
- CKANVERSION=2.7.3 POSTGISVERSION=2 INTEGRATION_TEST=true
- - CKANVERSION=2.8.0 POSTGISVERSION=2
+ - CKANVERSION=2.8.0 POSTGISVERSION=2 INTEGRATION_TEST=true
services:
+ - docker
- redis-server
- postgresql
addons:
@@ -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
diff --git a/README.md b/README.md
index 3702911..ab463a9 100644
--- a/README.md
+++ b/README.md
@@ -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
diff --git a/bin/travis-build.bash b/bin/travis-build.bash
index 5e77e12..b6d5dda 100755
--- a/bin/travis-build.bash
+++ b/bin/travis-build.bash
@@ -44,4 +44,21 @@ cd -
echo "Installing ckanext-oauth2 and its requirements..."
python setup.py develop
-echo "travis-build.bash is done."
\ No newline at end of file
+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."
diff --git a/ci/idm-config.js b/ci/idm-config.js
new file mode 100644
index 0000000..7291c53
--- /dev/null
+++ b/ci/idm-config.js
@@ -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;
diff --git a/ckanext/oauth2/controller.py b/ckanext/oauth2/controller.py
index 4c1fab8..acd965b 100644
--- a/ckanext/oauth2/controller.py
+++ b/ckanext/oauth2/controller.py
@@ -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.
@@ -17,15 +18,18 @@
# You should have received a copy of the GNU Affero General Public License
# along with OAuth2 CKAN Extension. If not, see .
+from __future__ import unicode_literals
+
import logging
import constants
-import oauth2
+from ckan.common import session
import ckan.lib.helpers as helpers
import ckan.lib.base as base
+import ckan.plugins.toolkit as toolkit
+import oauth2
-from ckan.common import session
-from ckanext.oauth2.plugin import toolkit
+from ckanext.oauth2.plugin import _get_previous_page
log = logging.getLogger(__name__)
@@ -36,6 +40,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()
diff --git a/ckanext/oauth2/oauth2.py b/ckanext/oauth2/oauth2.py
index cf0bf91..81ae95c 100644
--- a/ckanext/oauth2/oauth2.py
+++ b/ckanext/oauth2/oauth2.py
@@ -23,7 +23,6 @@
import base64
import ckan.model as model
-import constants
import db
import json
import logging
@@ -37,6 +36,9 @@
from requests_oauthlib import OAuth2Session
import six
+import constants
+
+
log = logging.getLogger(__name__)
@@ -48,11 +50,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()
@@ -61,7 +68,7 @@ def __init__(self):
self.client_id = six.text_type(os.environ.get('CKAN_OAUTH2_CLIENT_ID', toolkit.config.get('ckan.oauth2.client_id', ''))).strip()
self.client_secret = six.text_type(os.environ.get('CKAN_OAUTH2_CLIENT_SECRET', toolkit.config.get('ckan.oauth2.client_secret', ''))).strip()
self.scope = six.text_type(os.environ.get('CKAN_OAUTH2_SCOPE', toolkit.config.get('ckan.oauth2.scope', ''))).strip()
- self.rememberer_name = six.text_type(os.environ.get('CKAN_OAUTH2_REMEMBER_NAME', toolkit.config.get('ckan.oauth2.rememberer_name', ''))).strip()
+ self.rememberer_name = six.text_type(os.environ.get('CKAN_OAUTH2_REMEMBER_NAME', toolkit.config.get('ckan.oauth2.rememberer_name', 'auth_tkt'))).strip()
self.profile_api_user_field = six.text_type(os.environ.get('CKAN_OAUTH2_PROFILE_API_USER_FIELD', toolkit.config.get('ckan.oauth2.profile_api_user_field', ''))).strip()
self.profile_api_fullname_field = six.text_type(os.environ.get('CKAN_OAUTH2_PROFILE_API_FULLNAME_FIELD', toolkit.config.get('ckan.oauth2.profile_api_fullname_field', ''))).strip()
self.profile_api_mail_field = six.text_type(os.environ.get('CKAN_OAUTH2_PROFILE_API_MAIL_FIELD', toolkit.config.get('ckan.oauth2.profile_api_mail_field', ''))).strip()
@@ -85,9 +92,9 @@ 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))
+ # CKAN 2.6 only supports bytes
+ return toolkit.redirect_to(auth_url.encode('utf-8'))
def get_token(self):
oauth = OAuth2Session(self.client_id, redirect_uri=self.redirect_uri, scope=self.scope)
diff --git a/ckanext/oauth2/plugin.py b/ckanext/oauth2/plugin.py
index 3c7fa32..3d2e1db 100644
--- a/ckanext/oauth2/plugin.py
+++ b/ckanext/oauth2/plugin.py
@@ -20,13 +20,13 @@
from __future__ import unicode_literals
-import constants
import logging
import oauth2
import os
from functools import partial
from ckan import plugins
+from ckan.common import g
from ckan.plugins import toolkit
from urlparse import urlparse
@@ -62,6 +62,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)
@@ -78,6 +99,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',
@@ -125,70 +150,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')
-
- # If the user is authenticated, but they cannot access a protected resource, the system
- # should redirect them to the previous page. If the user is not redirected, the system
- # will try to reauthenticate the user generating a redirect loop:
- # (authenticate -> user not allowed -> auto log out -> authenticate -> ...)
- # If the user is not authenticated, the system should start the authentication process
-
- 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('/')
-
- # Init headers and set Location
- if headers is None:
- headers = {}
- headers['Location'] = came_from_url
-
- # 302 -> Found
- return 302, detail, headers, comment
- else: # USER IS NOT AUTHENTICATED
- # By not modifying the received parameters, the authentication process will start
- return status_code, detail, headers, comment
-
def get_auth_functions(self):
# we need to prevent some actions being authorized.
return {
diff --git a/ckanext/oauth2/tests/test_controller.py b/ckanext/oauth2/tests/test_controller.py
index 689268c..b4c7b8a 100644
--- a/ckanext/oauth2/tests/test_controller.py
+++ b/ckanext/oauth2/tests/test_controller.py
@@ -18,14 +18,16 @@
# You should have received a copy of the GNU Affero General Public License
# along with OAuth2 CKAN Extension. If not, see .
+from base64 import b64decode, b64encode
import unittest
-import ckanext.oauth2.controller as controller
import json
-from base64 import b64decode, b64encode
from mock import MagicMock
from parameterized import parameterized
+from ckanext.oauth2 import controller, plugin
+
+
RETURNED_STATUS = 302
EXAMPLE_FLASH = 'This is a test'
EXCEPTION_MSG = 'Invalid'
@@ -55,8 +57,9 @@ def setUp(self):
self._oauth2 = controller.oauth2
controller.oauth2 = MagicMock()
- self._toolkit = controller.toolkit
- controller.toolkit = MagicMock()
+ self._toolkit_controller = controller.toolkit
+ self._toolkit_plugin = plugin.toolkit
+ plugin.toolkit = controller.toolkit = MagicMock()
self.__session = controller.session
controller.session = MagicMock()
@@ -67,8 +70,9 @@ def tearDown(self):
# Unmock the function
controller.helpers = self._helpers
controller.oauth2 = self._oauth2
- controller.toolkit = self._toolkit
+ controller.toolkit = self._toolkit_controller
controller.session = self.__session
+ plugin.toolkit = self._toolkit_plugin
def generate_state(self, url):
return b64encode(bytes(json.dumps({CAME_FROM_FIELD: url})))
@@ -76,7 +80,7 @@ def generate_state(self, url):
def get_came_from(self, state):
return json.loads(b64decode(state)).get(CAME_FROM_FIELD, '/')
- def test_controller_no_errors(self):
+ def test_callback_no_errors(self):
oauth2Helper = controller.oauth2.OAuth2Helper.return_value
token = 'TOKEN'
@@ -104,7 +108,7 @@ def test_controller_no_errors(self):
('/', VoidException(), None, type(VoidException()).__name__),
('/about', Exception(EXCEPTION_MSG), EXAMPLE_FLASH, EXAMPLE_FLASH)
])
- def test_controller_errors(self, came_from=None, exception=Exception(EXCEPTION_MSG),
+ def test_callback_errors(self, came_from=None, exception=Exception(EXCEPTION_MSG),
error_description=None, expected_flash=EXCEPTION_MSG):
# Recover function
@@ -127,3 +131,33 @@ def test_controller_errors(self, came_from=None, exception=Exception(EXCEPTION_M
self.assertEquals(RETURNED_STATUS, controller.toolkit.response.status_int)
self.assertEquals(came_from, controller.toolkit.response.location)
controller.helpers.flash_error.assert_called_once_with(expected_flash)
+
+ @parameterized.expand([
+ (),
+ (None, None, '/dashboard'),
+ ('/about', None, '/about'),
+ ('/about', '/ckan-admin', '/ckan-admin'),
+ (None, '/ckan-admin', '/ckan-admin'),
+ ('/', None, '/dashboard'),
+ ('/user/logged_out_redirect', None, '/dashboard'),
+ ('/', '/ckan-admin', '/ckan-admin'),
+ ('/user/logged_out_redirect', '/ckan-admin', '/ckan-admin'),
+ ('http://google.es', None, '/dashboard'),
+ ('http://google.es', None, '/dashboard')
+ ])
+ def test_login(self, referer=None, came_from=None, expected_referer='/dashboard'):
+
+ # The login function will check these variables
+ controller.toolkit.request.headers = {}
+ controller.toolkit.request.params = {}
+
+ if referer:
+ controller.toolkit.request.headers['Referer'] = referer
+
+ if came_from:
+ controller.toolkit.request.params['came_from'] = came_from
+
+ # Call the function
+ self.controller.login()
+
+ self.controller.oauth2helper.challenge.assert_called_once_with(expected_referer)
diff --git a/ckanext/oauth2/tests/test_oauth2.py b/ckanext/oauth2/tests/test_oauth2.py
index 0f5c896..931f69e 100644
--- a/ckanext/oauth2/tests/test_oauth2.py
+++ b/ckanext/oauth2/tests/test_oauth2.py
@@ -262,7 +262,6 @@ def test_challenge(self):
came_from = '/came_from_example'
oauth2.toolkit.request = request
- oauth2.toolkit.response = MagicMock()
# Call the method
helper.challenge(came_from)
@@ -271,8 +270,7 @@ def test_challenge(self):
state = urlencode({'state': b64encode(bytes(json.dumps({'came_from': came_from})))})
expected_url = 'https://test/oauth2/authorize/?response_type=code&client_id=client-id&' + \
'redirect_uri=http%3A%2F%2Flocalhost%3A5000%2Foauth2%2Fcallback&' + state
- self.assertEquals(302, oauth2.toolkit.response.status)
- self.assertEquals(expected_url, oauth2.toolkit.response.location)
+ oauth2.toolkit.redirect_to.assert_called_once_with(expected_url)
@parameterized.expand([
('test_user', 'Test User Full Name', 'test@test.com'),
@@ -492,7 +490,7 @@ def test_update_token(self, user_exists):
(True,),
(False,)
])
- @patch.dict(os.environ, {'OAUTHLIB_INSECURE_TRANSPORT': ''})
+ @patch.dict(os.environ, {'OAUTHLIB_INSECURE_TRANSPORT': '', 'REQUESTS_CA_BUNDLE': ''})
def test_refresh_token(self, user_exists):
username = 'user'
helper = self.helper = self._helper()
diff --git a/ckanext/oauth2/tests/test_plugin.py b/ckanext/oauth2/tests/test_plugin.py
index fb4ab7b..6a6abd1 100644
--- a/ckanext/oauth2/tests/test_plugin.py
+++ b/ckanext/oauth2/tests/test_plugin.py
@@ -21,7 +21,7 @@
import unittest
import ckanext.oauth2.plugin as plugin
-from mock import MagicMock
+from mock import MagicMock, patch
from parameterized import parameterized
AUTHORIZATION_HEADER = 'custom_header'
@@ -110,7 +110,7 @@ def test_auth_functions(self):
self.assertEquals(False, function_result['success'])
@parameterized.expand([
- (),
+ ({}, None, None, None),
({}, None, 'test', 'test'),
({AUTHORIZATION_HEADER: 'api_key'}, 'test', None, 'test'),
({AUTHORIZATION_HEADER: 'api_key'}, 'test', 'test2', 'test'),
@@ -119,7 +119,8 @@ def test_auth_functions(self):
({'invalid_header': 'api_key'}, 'test', None, None),
({'invalid_header': 'api_key'}, 'test', 'test2', 'test2'),
])
- def test_identify(self, headers={}, authenticate_result=None, identity=None, expected_user=None):
+ @patch("ckanext.oauth2.plugin.g")
+ def test_identify(self, headers, authenticate_result, identity, expected_user, g_mock):
self._set_identity(identity)
@@ -163,6 +164,7 @@ def authenticate_side_effect(identity):
else:
self.assertEquals(0, self._plugin.oauth2helper.identify.call_count)
+ self.assertEquals(expected_user, g_mock.user)
self.assertEquals(expected_user, plugin.toolkit.c.user)
if expected_user is None:
@@ -175,92 +177,3 @@ def authenticate_side_effect(identity):
plugin.toolkit.c.usertoken_refresh()
self._plugin.oauth2helper.refresh_token.assert_called_once_with(expected_user)
self.assertEquals(newtoken, plugin.toolkit.c.usertoken)
-
- @parameterized.expand([
- (),
- (None, None, '/dashboard'),
- ('/about', None, '/about'),
- ('/about', '/ckan-admin', '/ckan-admin'),
- (None, '/ckan-admin', '/ckan-admin'),
- ('/', None, '/dashboard'),
- ('/user/logged_out_redirect', None, '/dashboard'),
- ('/', '/ckan-admin', '/ckan-admin'),
- ('/user/logged_out_redirect', '/ckan-admin', '/ckan-admin'),
- ('http://google.es', None, '/dashboard'),
- ('http://google.es', None, '/dashboard')
- ])
- def test_login(self, referer=None, came_from=None, expected_referer='/dashboard'):
-
- # The login function will check these variables
- plugin.toolkit.request.headers = {}
- plugin.toolkit.request.params = {}
-
- self._plugin.oauth2helper.challenge = MagicMock()
-
- if referer:
- plugin.toolkit.request.headers['Referer'] = referer
-
- if came_from:
- plugin.toolkit.request.params['came_from'] = came_from
-
- # Call the function
- self._plugin.login()
-
- self._plugin.oauth2helper.challenge.assert_called_once_with(expected_referer)
-
- @parameterized.expand([
- (),
- ('user', None, None, None, '/'),
- ('user', None, None, {'Param1': 'value1', 'paRam2': 'value2'}, '/'),
- ('user', '/about', None, None, '/about'),
- ('user', '/about', '/ckan-admin', None, '/ckan-admin'),
- ('user', None, '/ckan-admin', None, '/ckan-admin'),
- ('user', '/', None, None, '/'),
- ('user', '/user/logged_out_redirect', None, None, '/'),
- ('user', '/', '/ckan-admin', None, '/ckan-admin'),
- ('user', '/user/logged_out_redirect', '/ckan-admin', None, '/ckan-admin'),
- ('user', 'http://google.es', None, None, '/'),
- ('user', 'http://google.es', None, None, '/'),
- ('user', 'http://' + HOST + '/about', None, None, 'http://' + HOST + '/about'),
- ('user', 'http://' + HOST + '/about', '/other_url', None, '/other_url'),
- (None, '/about', '/other', None, None),
- ])
- def test_abort(self, user='user', referer=None, came_from=None, headers=None, expected_location='/'):
-
- # The abort function will check these variables
- plugin.toolkit.c.user = user
- plugin.toolkit.request.host = HOST
- plugin.toolkit.request.headers = {}
- plugin.toolkit.request.params = {}
-
- if referer:
- plugin.toolkit.request.headers['Referer'] = referer
-
- if came_from:
- plugin.toolkit.request.params['came_from'] = came_from
-
- # Call the function
- initial_status_code = 401
- initial_detail = 'DETAIL'
- initial_headers = None if not headers else headers.copy()
- initial_comment = 'COMMENT'
-
- # headers will be modified inside the function, but we should retain a copy (initial_headers)
- status_code, detail, new_headers, comment = self._plugin.abort(initial_status_code, initial_detail, headers, initial_comment)
-
- # Verifications
- self.assertEquals(initial_detail, detail)
- self.assertEquals(initial_comment, comment)
-
- if user:
- self.assertEquals(302, status_code)
- self.assertEquals(new_headers['Location'], expected_location)
- else:
- self.assertEquals(initial_status_code, status_code)
- self.assertEquals(initial_headers, new_headers)
-
- # Check previous headers if they were not None
- if initial_headers:
- for header in initial_headers:
- self.assertIn(header, new_headers)
- self.assertEquals(initial_headers[header], new_headers[header])
diff --git a/ckanext/oauth2/tests/test_selenium.py b/ckanext/oauth2/tests/test_selenium.py
index 2aa80e6..0614de3 100644
--- a/ckanext/oauth2/tests/test_selenium.py
+++ b/ckanext/oauth2/tests/test_selenium.py
@@ -18,6 +18,8 @@
# You should have received a copy of the GNU Affero General Public License
# along with OAuth2 CKAN Extension. If not, see .
+from __future__ import print_function
+
import unittest
import os
from subprocess import Popen
@@ -25,16 +27,20 @@
from urlparse import urljoin
from parameterized import parameterized
+import requests
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
-IDM_URL = "https://account.lab.fiware.org"
-FILAB2_MAIL = "filab2@mailinator.com"
-FILAB3_MAIL = "filab3@mailinator.com"
-FILAB_PASSWORD = "filab1234"
+
+IDM_URL = "https://localhost"
+FILAB2_MAIL = "admin@test.com"
+FILAB_PASSWORD = "1234"
PASS_INTEGRATION_TESTS = os.environ.get("INTEGRATION_TEST", "").strip().lower() in ('1', 'true', 'on')
+AUTH_TOKEN_ENDPOINT = "v1/auth/tokens"
+APPLICATION_ENDPOINT = "v1/applications"
+
@unittest.skipUnless(PASS_INTEGRATION_TESTS, "set INTEGRATION_TEST environment variable (e.g. INTEGRATION_TEST=true) for running the integration tests")
class IntegrationTest(unittest.TestCase):
@@ -45,23 +51,50 @@ def setUpClass(cls):
if not PASS_INTEGRATION_TESTS:
return
+ # Get an admin token
+ body = {
+ "name": "admin@test.com",
+ "password": "1234"
+ }
+ url = urljoin(IDM_URL, AUTH_TOKEN_ENDPOINT)
+ response = requests.post(url, json=body)
+
+ token = response.headers["X-Subject-Token"]
+
+ # Create the OAuth2 application
+ headers = {
+ "X-Auth-Token": token
+ }
+
+ body = {
+ "application": {
+ "name": "Travis Selenium Tests",
+ "description": "Travis Selenium Tests",
+ "redirect_uri": "http://localhost:5000/oauth2/callback",
+ "url": "http://localhost:5000",
+ "grant_type": [
+ "authorization_code"
+ ]
+ }
+ }
+
+ url = urljoin(IDM_URL, APPLICATION_ENDPOINT)
+ response = requests.post(url, json=body, headers=headers)
+ app = response.json()
+
+ # Run CKAN
env = os.environ.copy()
env['DEBUG'] = 'True'
- env['OAUTHLIB_INSECURE_TRANSPORT'] = 'True'
+ env['OAUTHLIB_INSECURE_TRANSPORT'] = 'False'
+ env['CKAN_OAUTH2_CLIENT_ID'] = app['application']['id']
+ env['CKAN_OAUTH2_CLIENT_SECRET'] = app['application']['secret']
cls._process = Popen(['paster', 'serve', 'test-fiware.ini'], env=env)
+ # Init Selenium
cls.driver = webdriver.Firefox()
cls.base_url = 'http://localhost:5000/'
cls.driver.set_window_size(1024, 768)
- cls.driver.get(IDM_URL)
- cls._introduce_log_in_parameters()
- cls.driver.get(urljoin(IDM_URL, '/idm/myApplications/361020fd7cf64456890dd98da88e64f3/edit/'))
- id_callbackurl = WebDriverWait(cls.driver, 10).until(EC.presence_of_element_located((By.ID, "id_callbackurl")))
- id_callbackurl.clear()
- id_callbackurl.send_keys(urljoin(cls.base_url, '/oauth2/callback'))
- cls.driver.find_element_by_xpath("//button[@type='submit']").click()
-
@classmethod
def tearDownClass(cls):
# nose calls this method also if they are going to be skiped
@@ -74,7 +107,7 @@ def tearDownClass(cls):
@classmethod
def _introduce_log_in_parameters(cls, username=FILAB2_MAIL, password=FILAB_PASSWORD):
driver = cls.driver
- id_username = WebDriverWait(cls.driver, 10).until(EC.presence_of_element_located((By.ID, "id_username")))
+ id_username = WebDriverWait(cls.driver, 10).until(EC.presence_of_element_located((By.ID, "id_email")))
id_username.clear()
id_username.send_keys(username)
driver.find_element_by_id("id_password").clear()
@@ -110,47 +143,45 @@ def test_basic_login(self):
driver = self.driver
self._log_in(self.base_url)
WebDriverWait(driver, 20).until(lambda driver: (self.base_url + 'dashboard') == driver.current_url)
- self.assertEqual("filab2 Example User", driver.find_element_by_link_text("filab2 Example User").text)
+ self.assertEqual("admin", driver.find_element_by_link_text("admin").text)
driver.find_element_by_link_text("About").click()
WebDriverWait(driver, 20).until(lambda driver: (self.base_url + 'about') == driver.current_url)
- self.assertEqual("filab2 Example User", driver.find_element_by_css_selector("span.username").text)
+ self.assertEqual("admin", driver.find_element_by_css_selector("span.username").text)
driver.find_element_by_css_selector("a[title=\"Edit settings\"]").click()
time.sleep(3) # Wait the OAuth2 Server to return the page
- assert driver.current_url.startswith(IDM_URL + "/settings")
+ self.assertTrue(driver.current_url.startswith(IDM_URL + "/idm/settings"), "%s does not starts with %s" % (driver.current_url, IDM_URL + "/idm/settings"))
def test_basic_login_different_referer(self):
driver = self.driver
self._log_in(self.base_url + "about")
WebDriverWait(driver, 20).until(lambda driver: (self.base_url + 'about') == driver.current_url)
- self.assertEqual("filab2 Example User", driver.find_element_by_css_selector("span.username").text)
+ self.assertEqual("admin", driver.find_element_by_css_selector("span.username").text)
driver.find_element_by_link_text("Datasets").click()
WebDriverWait(driver, 20).until(lambda driver: (self.base_url + 'dataset') == driver.current_url)
- self.assertEqual("filab2 Example User", driver.find_element_by_css_selector("span.username").text)
+ self.assertEqual("admin", driver.find_element_by_css_selector("span.username").text)
def test_user_access_unauthorized_page(self):
driver = self.driver
self._log_in(self.base_url)
driver.get(self.base_url + "ckan-admin")
- # Check that the user has been redirected to the main page
- WebDriverWait(driver, 10).until(lambda driver: driver.current_url == self.base_url)
# Check that an error message is shown
- assert driver.find_element_by_xpath("//div/div/div/div").text.startswith("Need to be system administrator to administer")
+ self.assertIn("Need to be system administrator to administer", self.driver.find_element_by_tag_name('body').text)
def test_register_btn(self):
driver = self.driver
driver.get(self.base_url)
WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.LINK_TEXT, "Register"))).click()
- WebDriverWait(driver, 10).until(lambda driver: driver.current_url == (IDM_URL + "/sign_up/"))
+ WebDriverWait(driver, 10).until(lambda driver: driver.current_url == (IDM_URL + "/sign_up"))
@parameterized.expand([
- ("user/register", IDM_URL + "/sign_up/"),
- ("user/reset", IDM_URL + "/password/request/")
+ ("user/register", IDM_URL + "/sign_up"),
+ ("user/reset", IDM_URL + "/password/request")
])
def test_register(self, action, expected_url):
driver = self.driver
driver.get(self.base_url + action)
- WebDriverWait(driver, 10).until(lambda driver: driver.current_url == expected_url)
+ WebDriverWait(driver, 10).until(lambda driver: print(driver.current_url) or driver.current_url == expected_url)
if __name__ == "__main__":
diff --git a/test-fiware.ini b/test-fiware.ini
index 3507fbc..3755ef7 100644
--- a/test-fiware.ini
+++ b/test-fiware.ini
@@ -33,21 +33,21 @@ ckan.plugins = oauth2
## OAuth2 configuration
ckan.oauth2.logout_url = /user/logged_out
-ckan.oauth2.register_url = https://account.lab.fiware.org/sign_up
-ckan.oauth2.reset_url = https://account.lab.fiware.org/password/request
-ckan.oauth2.edit_url = https://account.lab.fiware.org/settings
-ckan.oauth2.authorization_endpoint = https://account.lab.fiware.org/oauth2/authorize
-ckan.oauth2.token_endpoint = https://account.lab.fiware.org/oauth2/token
-ckan.oauth2.profile_api_url = https://account.lab.fiware.org/user
-ckan.oauth2.client_id = 361020fd7cf64456890dd98da88e64f3
-ckan.oauth2.client_secret = edf713bf8a2344139f46a757fadae24f
+ckan.oauth2.register_url = https://localhost/sign_up
+ckan.oauth2.reset_url = https://localhost/password/request
+ckan.oauth2.edit_url = https://localhost/idm/settings
+ckan.oauth2.authorization_endpoint = https://localhost/oauth2/authorize
+ckan.oauth2.token_endpoint = https://localhost/oauth2/token
+ckan.oauth2.profile_api_url = https://localhost/user
+# The following parameters are configured through environment variables
+#ckan.oauth2.client_id = 361020fd7cf64456890dd98da88e64f3
+#ckan.oauth2.client_secret = edf713bf8a2344139f46a757fadae24f
ckan.oauth2.scope = all_info
ckan.oauth2.rememberer_name = auth_tkt
ckan.oauth2.profile_api_user_field = id
ckan.oauth2.profile_api_fullname_field = displayName
ckan.oauth2.profile_api_mail_field = email
ckan.oauth2.authorization_header = X-Auth-Token
-ckan.oauth2.legacy_idm = True
#who.config_file = %(here)s/who-fiware.ini