From bddd7b5298e2724114278b309333fcc5c0378cd8 Mon Sep 17 00:00:00 2001 From: amercader Date: Tue, 23 Aug 2016 12:02:53 +0100 Subject: [PATCH] [#3196] Move user identify logic to a separate module So it can be reused by Flask requests handlers and Pylons base controllers --- ckan/controllers/api.py | 4 +- ckan/lib/base.py | 105 +--------------------- ckan/tests/controllers/test_package.py | 2 +- ckan/views/__init__.py | 120 +++++++++++++++++++++++++ 4 files changed, 127 insertions(+), 104 deletions(-) diff --git a/ckan/controllers/api.py b/ckan/controllers/api.py index dcda810d806..924952bfd0f 100644 --- a/ckan/controllers/api.py +++ b/ckan/controllers/api.py @@ -19,6 +19,8 @@ import ckan.lib.jsonp as jsonp import ckan.lib.munge as munge +from ckan.views import identify_user + from ckan.common import _, c, request, response @@ -52,7 +54,7 @@ def __call__(self, environ, start_response): api_version = api_version[1:] routes_dict['ver'] = int(api_version) - self._identify_user() + identify_user() try: context = {'model': model, 'user': c.user, 'auth_user_obj': c.userobj} diff --git a/ckan/lib/base.py b/ckan/lib/base.py index 2442a69b9cd..9b955dfe790 100644 --- a/ckan/lib/base.py +++ b/ckan/lib/base.py @@ -26,6 +26,8 @@ import ckan.plugins as p import ckan.model as model +from ckan.views import identify_user + # These imports are for legacy usages and will be removed soon these should # be imported directly from ckan.common for internal ckan code and via the # plugins.toolkit for extensions. @@ -194,89 +196,10 @@ def __before__(self, action, **params): c.__timer = time.time() app_globals.app_globals._check_uptodate() - self._identify_user() + identify_user() i18n.handle_request(request, c) - def _identify_user(self): - '''Try to identify the user - If the user is identified then: - c.user = user name (unicode) - c.userobj = user object - c.author = user name - otherwise: - c.user = None - c.userobj = None - 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 = p.PluginImplementations(p.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() - - # If we have a user but not the userobj let's get the userobj. This - # means that IAuthenticator extensions do not need to access the user - # model directly. - if c.user and not c.userobj: - c.userobj = model.User.by_name(c.user) - - # 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. But repoze.who doesn't check the user (still) exists - # in our database - we need to do that here. (Another way would be - # with an userid_checker, but that would mean another db access. - # See: http://docs.repoze.org/who/1.0/narr.html#module-repoze.who\ - # .plugins.sql ) - c.user = request.environ.get('REMOTE_USER', '') - if c.user: - c.user = c.user.decode('utf8') - c.userobj = model.User.by_name(c.user) - if c.userobj is None or not c.userobj.is_active(): - - # This occurs when a user that was still logged in is deleted, - # or when you are logged in, clean db and then restart (or - # when you change your username) There is no user object, so - # even though repoze thinks you are logged in and your cookie - # has ckan_display_name, we need to force user to logout and - # login again to get the User object. - - ev = request.environ - if 'repoze.who.plugins' in ev: - pth = getattr(ev['repoze.who.plugins']['friendlyform'], - 'logout_handler_path') - h.redirect_to(pth) - else: - c.userobj = self._get_user_for_apikey() - if c.userobj is not None: - c.user = c.userobj.name - def __call__(self, environ, start_response): """Invoke the Controller""" # WSGIController.__call__ dispatches to the Controller method @@ -345,28 +268,6 @@ def _set_cors(self): response.headers['Access-Control-Allow-Headers'] = \ "X-CKAN-API-KEY, Authorization, Content-Type" - def _get_user_for_apikey(self): - apikey_header_name = config.get(APIKEY_HEADER_NAME_KEY, - APIKEY_HEADER_NAME_DEFAULT) - apikey = request.headers.get(apikey_header_name, '') - if not apikey: - apikey = request.environ.get(apikey_header_name, '') - if not apikey: - # For misunderstanding old documentation (now fixed). - apikey = request.environ.get('HTTP_AUTHORIZATION', '') - if not apikey: - apikey = request.environ.get('Authorization', '') - # Forget HTTP Auth credentials (they have spaces). - if ' ' in apikey: - apikey = '' - if not apikey: - return None - self.log.debug("Received API Key: %s" % apikey) - apikey = unicode(apikey) - query = model.Session.query(model.User) - user = query.filter_by(apikey=apikey).first() - return user - # Include the '_' function in the public names __all__ = [__name for __name in locals().keys() if not __name.startswith('_') diff --git a/ckan/tests/controllers/test_package.py b/ckan/tests/controllers/test_package.py index 0f30518efe0..d4cc8b379ed 100644 --- a/ckan/tests/controllers/test_package.py +++ b/ckan/tests/controllers/test_package.py @@ -411,7 +411,7 @@ def test_unauthed_user_creating_dataset(self): app = self._get_test_app() # provide REMOTE_ADDR to idenfity as remote user, see - # BaseController._identify_user() for details + # ckan.views.identify_user() for details response = app.post(url=url_for(controller='package', action='new'), extra_environ={'REMOTE_ADDR': '127.0.0.1'}, status=403) diff --git a/ckan/views/__init__.py b/ckan/views/__init__.py index e69de29bb2d..3f2cc271925 100644 --- a/ckan/views/__init__.py +++ b/ckan/views/__init__.py @@ -0,0 +1,120 @@ +# encoding: utf-8 + +import ckan.model as model +from ckan.common import g, request, config +from ckan.lib.helpers import redirect_to as redirect +import ckan.plugins as p + +import logging +log = logging.getLogger(__name__) + +APIKEY_HEADER_NAME_KEY = u'apikey_header_name' +APIKEY_HEADER_NAME_DEFAULT = u'X-CKAN-API-Key' + + +def identify_user(): + u'''Try to identify the user + If the user is identified then: + g.user = user name (unicode) + g.userobj = user object + g.author = user name + otherwise: + g.user = None + g.userobj = None + g.author = user's IP address (unicode) + + Note: Remember, when running under Pylons, `g` is the Pylons `c` object + ''' + # see if it was proxied first + g.remote_addr = request.environ.get(u'HTTP_X_FORWARDED_FOR', u'') + if not g.remote_addr: + g.remote_addr = request.environ.get(u'REMOTE_ADDR', + u'Unknown IP Address') + + # Authentication plugins get a chance to run here break as soon as a user + # is identified. + authenticators = p.PluginImplementations(p.IAuthenticator) + if authenticators: + for item in authenticators: + item.identify() + if g.user: + break + + # We haven't identified the user so try the default methods + if not getattr(g, u'user', None): + _identify_user_default() + + # If we have a user but not the userobj let's get the userobj. This means + # that IAuthenticator extensions do not need to access the user model + # directly. + if g.user and not getattr(g, u'userobj', None): + g.userobj = model.User.by_name(g.user) + + # general settings + if g.user: + g.author = g.user + else: + g.author = g.remote_addr + g.author = unicode(g.author) + + +def _identify_user_default(): + u''' + 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. But repoze.who doesn't check the user (still) exists + # in our database - we need to do that here. (Another way would be + # with an userid_checker, but that would mean another db access. + # See: http://docs.repoze.org/who/1.0/narr.html#module-repoze.who\ + # .plugins.sql ) + g.user = request.environ.get(u'REMOTE_USER', u'') + if g.user: + g.user = g.user.decode(u'utf8') + g.userobj = model.User.by_name(g.user) + + if g.userobj is None or not g.userobj.is_active(): + + # This occurs when a user that was still logged in is deleted, or + # when you are logged in, clean db and then restart (or when you + # change your username). There is no user object, so even though + # repoze thinks you are logged in and your cookie has + # ckan_display_name, we need to force user to logout and login + # again to get the User object. + + ev = request.environ + if u'repoze.who.plugins' in ev: + pth = getattr(ev[u'repoze.who.plugins'][u'friendlyform'], + u'logout_handler_path') + redirect(pth) + else: + g.userobj = _get_user_for_apikey() + if g.userobj is not None: + g.user = g.userobj.name + + +def _get_user_for_apikey(): + apikey_header_name = config.get(APIKEY_HEADER_NAME_KEY, + APIKEY_HEADER_NAME_DEFAULT) + apikey = request.headers.get(apikey_header_name, u'') + if not apikey: + apikey = request.environ.get(apikey_header_name, u'') + if not apikey: + # For misunderstanding old documentation (now fixed). + apikey = request.environ.get(u'HTTP_AUTHORIZATION', u'') + if not apikey: + apikey = request.environ.get(u'Authorization', u'') + # Forget HTTP Auth credentials (they have spaces). + if u' ' in apikey: + apikey = u'' + if not apikey: + return None + log.debug(u'Received API Key: %s' % apikey) + apikey = unicode(apikey) + query = model.Session.query(model.User) + user = query.filter_by(apikey=apikey).first() + return user