From 77e26db4573e0f5ece9245ebffb074b0bab538aa Mon Sep 17 00:00:00 2001 From: tobes Date: Sat, 23 Mar 2013 11:03:38 +0000 Subject: [PATCH] Add IAuthenticator --- ckan/controllers/user.py | 11 ++++++++ ckan/lib/base.py | 52 ++++++++++++++++++++++++++++---------- ckan/plugins/interfaces.py | 18 +++++++++++++ 3 files changed, 68 insertions(+), 13 deletions(-) diff --git a/ckan/controllers/user.py b/ckan/controllers/user.py index d79d68a64e8..087f03dbe25 100644 --- a/ckan/controllers/user.py +++ b/ckan/controllers/user.py @@ -16,6 +16,7 @@ import ckan.lib.captcha as captcha import ckan.lib.mailer as mailer import ckan.lib.navl.dictization_functions as dictization_functions +import ckan.plugins as p log = logging.getLogger(__name__) @@ -293,6 +294,11 @@ def login(self, error=None): session.save() return h.redirect_to(locale=str(lang), controller='user', action='login') + + # Do any plugin login stuff + for item in p.PluginImplementations(p.IAuthenticator): + item.login() + if 'error' in request.params: h.flash_error(request.params['error']) @@ -351,6 +357,11 @@ def logout(self): # save our language in the session so we don't lose it session['lang'] = request.environ.get('CKAN_LANG') session.save() + + # Do any plugin logout stuff + for item in p.PluginImplementations(p.IAuthenticator): + item.logout() + h.redirect_to(self._get_repoze_handler('logout_handler_path')) def set_lang(self, lang): diff --git a/ckan/lib/base.py b/ckan/lib/base.py index 7f944a3393b..8f36da8f52f 100644 --- a/ckan/lib/base.py +++ b/ckan/lib/base.py @@ -23,7 +23,7 @@ import lib.render import ckan.lib.helpers as h import ckan.lib.app_globals as app_globals -from ckan.plugins import PluginImplementations, IGenshiStreamFilter +from ckan.plugins import PluginImplementations, IGenshiStreamFilter, IAuthenticator import ckan.model as model from ckan.common import json @@ -38,6 +38,12 @@ def abort(status_code=None, detail='', headers=None, comment=None): + if status_code == 401: + # Allow IAuthenticator plugins to alter the abort + for item in PluginImplementations(IAuthenticator): + result = item.abort(status_code, detail, headers, comment) + (status_code, detail, headers, comment) = result + if detail and status_code != 503: h.flash_error(detail) # #1267 Convert detail to plain text, since WebOb 0.9.7.1 (which comes @@ -208,7 +214,9 @@ def __before__(self, action, **params): c.__timer = time.time() c.__version__ = ckan.__version__ app_globals.app_globals._check_uptodate() + self._identify_user() + i18n.handle_request(request, c) # If the user is logged in add their number of new activities to the @@ -222,11 +230,7 @@ def __before__(self, action, **params): c.new_activities = new_activities_count(context, {}) def _identify_user(self): - ''' - Identifies the user using two methods: - a) If he has logged into the web interface then repoze.who will - set REMOTE_USER. - b) For API calls he may set a header with his API key. + '''Try to identify the user If the user is identified then: c.user = user name (unicode) c.userobj = user object @@ -234,14 +238,41 @@ def _identify_user(self): otherwise: c.user = None c.userobj = None - c.author = user\'s IP address (unicode) - ''' + c.author = user's IP address (unicode)''' # see if it was proxied first c.remote_addr = request.environ.get('HTTP_X_FORWARDED_FOR', '') if not c.remote_addr: c.remote_addr = request.environ.get('REMOTE_ADDR', 'Unknown IP Address') + # Authentication plugins get a chance to run here break as soon as a + # user is identified. + authenticators = PluginImplementations(IAuthenticator) + if authenticators: + for item in authenticators: + item.identify() + if c.user: + break + + # We haven't identified the user so try the default methods + if not c.user: + self._identify_user_default() + + # general settings + if c.user: + c.author = c.user + else: + c.author = c.remote_addr + c.author = unicode(c.author) + + def _identify_user_default(self): + ''' + Identifies the user using two methods: + a) If they logged into the web interface then repoze.who will + set REMOTE_USER. + b) For API calls they may set a header with an API key. + ''' + # environ['REMOTE_USER'] is set by repoze.who if it authenticates # a user's cookie or OpenID. But repoze.who doesn't check the user # (still) exists in our database - we need to do that here. (Another @@ -272,11 +303,6 @@ def _identify_user(self): c.userobj = self._get_user_for_apikey() if c.userobj is not None: c.user = c.userobj.name - if c.user: - c.author = c.user - else: - c.author = c.remote_addr - c.author = unicode(c.author) def __call__(self, environ, start_response): """Invoke the Controller""" diff --git a/ckan/plugins/interfaces.py b/ckan/plugins/interfaces.py index 1dd22b2f88c..f297f50667e 100644 --- a/ckan/plugins/interfaces.py +++ b/ckan/plugins/interfaces.py @@ -19,6 +19,7 @@ 'ITagController', 'ITemplateHelpers', 'IFacets', + 'IAuthenticator', ] from inspect import isclass @@ -875,3 +876,20 @@ def group_facets(self, facets_dict, group_type, package_type): def organization_facets(self, facets_dict, organization_type, package_type): ''' Update the facets_dict and return it. ''' return facets_dict + + +class IAuthenticator(Interface): + '''EXPERIMENTAL''' + + def identify(self): + '''called to identify the user.''' + + def login(self): + '''called at login.''' + + def logout(self): + '''called at logout.''' + + def abort(self, status_code, detail, headers, comment): + '''called on abort.''' + return (status_code, detail, headers, comment)