Skip to content

Commit

Permalink
Support CKAN 2.8
Browse files Browse the repository at this point in the history
  • Loading branch information
aarranz committed Jul 5, 2018
1 parent 81ee0c9 commit 3639445
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 79 deletions.
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
4 changes: 2 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 Down Expand Up @@ -85,9 +86,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
48 changes: 41 additions & 7 deletions ckanext/oauth2/tests/test_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 <http://www.gnu.org/licenses/>.

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'
Expand Down Expand Up @@ -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()
Expand All @@ -67,16 +70,17 @@ 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})))

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'
Expand Down Expand Up @@ -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
Expand All @@ -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)
40 changes: 5 additions & 35 deletions ckanext/oauth2/tests/test_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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'),
Expand All @@ -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)

Expand Down Expand Up @@ -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:
Expand All @@ -176,38 +178,6 @@ def authenticate_side_effect(identity):
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, '/'),
Expand Down

0 comments on commit 3639445

Please sign in to comment.