# HG changeset patch # User Søren Løvborg # Date 1483567308 -3600 # Node ID b5551ad26fa30b548a366e957da4932472d7cc5d # Parent 701f0a3f9616ebf7ba97a86d354433e6501187d3 vcs: dedup auth code between Hg and Git middleware There's a lot of verbatim duplicated code in the Hg and Git VCS middleware. This attempts to deduplicate a bit of it. The _authorize function is a bit awkward, but for now the goal is simply to remove duplicated code, not improving program structure and design. As such, the code in _authorize is almost a verbatim copy of the code removed in the two controllers. diff -r 701f0a3f9616 -r b5551ad26fa3 kallithea/lib/base.py --- a/kallithea/lib/base.py Mon Jan 02 23:16:32 2017 +0100 +++ b/kallithea/lib/base.py Wed Jan 04 23:01:48 2017 +0100 @@ -179,6 +179,9 @@ class BaseVCSController(object): + """Base controller for handling Mercurial/Git protocol requests + (coming from a VCS client, and not a browser). + """ def __init__(self, application, config): self.application = application @@ -189,6 +192,78 @@ self.authenticate = BasicAuth('', auth_modules.authenticate, config.get('auth_ret_code')) + def _authorize(self, environ, start_response, action, repo_name, ip_addr): + """Authenticate and authorize user. + + Since we're dealing with a VCS client and not a browser, we only + support HTTP basic authentication, either directly via raw header + inspection, or by using container authentication to delegate the + authentication to the web server. + + Returns (user, None) on successful authentication and authorization. + Returns (None, wsgi_app) to send the wsgi_app response to the client. + """ + anonymous_user = User.get_default_user(cache=True) + user = anonymous_user + if anonymous_user.active: + # ONLY check permissions if the user is activated + anonymous_perm = self._check_permission(action, anonymous_user, + repo_name, ip_addr) + else: + anonymous_perm = False + + if not anonymous_user.active or not anonymous_perm: + if not anonymous_user.active: + log.debug('Anonymous access is disabled, running ' + 'authentication') + + if not anonymous_perm: + log.debug('Not enough credentials to access this ' + 'repository as anonymous user') + + username = None + #============================================================== + # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE + # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS + #============================================================== + + # try to auth based on environ, container auth methods + log.debug('Running PRE-AUTH for container based authentication') + pre_auth = auth_modules.authenticate('', '', environ) + if pre_auth is not None and pre_auth.get('username'): + username = pre_auth['username'] + log.debug('PRE-AUTH got %s as username', username) + + # If not authenticated by the container, running basic auth + if not username: + self.authenticate.realm = \ + safe_str(self.config['realm']) + result = self.authenticate(environ) + if isinstance(result, str): + paste.httpheaders.AUTH_TYPE.update(environ, 'basic') + paste.httpheaders.REMOTE_USER.update(environ, result) + username = result + else: + return None, result.wsgi_application + + #============================================================== + # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME + #============================================================== + try: + user = User.get_by_username_or_email(username) + if user is None or not user.active: + return None, webob.exc.HTTPForbidden() + except Exception: + log.error(traceback.format_exc()) + return None, webob.exc.HTTPInternalServerError() + + #check permissions for this repository + perm = self._check_permission(action, user, repo_name, ip_addr) + if not perm: + return None, webob.exc.HTTPForbidden() + + return user, None + def _handle_request(self, environ, start_response): raise NotImplementedError() diff -r 701f0a3f9616 -r b5551ad26fa3 kallithea/lib/middleware/simplegit.py --- a/kallithea/lib/middleware/simplegit.py Mon Jan 02 23:16:32 2017 +0100 +++ b/kallithea/lib/middleware/simplegit.py Wed Jan 04 23:01:48 2017 +0100 @@ -95,64 +95,9 @@ #====================================================================== # CHECK PERMISSIONS #====================================================================== - anonymous_user = User.get_default_user(cache=True) - user = anonymous_user - if anonymous_user.active: - # ONLY check permissions if the user is activated - anonymous_perm = self._check_permission(action, anonymous_user, - repo_name, ip_addr) - else: - anonymous_perm = False - - if not anonymous_user.active or not anonymous_perm: - if not anonymous_user.active: - log.debug('Anonymous access is disabled, running ' - 'authentication') - - if not anonymous_perm: - log.debug('Not enough credentials to access this ' - 'repository as anonymous user') - - username = None - #============================================================== - # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE - # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS - #============================================================== - - # try to auth based on environ, container auth methods - log.debug('Running PRE-AUTH for container based authentication') - pre_auth = auth_modules.authenticate('', '', environ) - if pre_auth is not None and pre_auth.get('username'): - username = pre_auth['username'] - log.debug('PRE-AUTH got %s as username', username) - - # If not authenticated by the container, running basic auth - if not username: - self.authenticate.realm = \ - safe_str(self.config['realm']) - result = self.authenticate(environ) - if isinstance(result, str): - AUTH_TYPE.update(environ, 'basic') - REMOTE_USER.update(environ, result) - username = result - else: - return result.wsgi_application(environ, start_response) - - #============================================================== - # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME - #============================================================== - try: - user = User.get_by_username_or_email(username) - if user is None or not user.active: - return HTTPForbidden()(environ, start_response) - except Exception: - log.error(traceback.format_exc()) - return HTTPInternalServerError()(environ, start_response) - - #check permissions for this repository - perm = self._check_permission(action, user, repo_name, ip_addr) - if not perm: - return HTTPForbidden()(environ, start_response) + user, response_app = self._authorize(environ, start_response, action, repo_name, ip_addr) + if response_app is not None: + return response_app(environ, start_response) # extras are injected into UI object and later available # in hooks executed by kallithea diff -r 701f0a3f9616 -r b5551ad26fa3 kallithea/lib/middleware/simplehg.py --- a/kallithea/lib/middleware/simplehg.py Mon Jan 02 23:16:32 2017 +0100 +++ b/kallithea/lib/middleware/simplehg.py Wed Jan 04 23:01:48 2017 +0100 @@ -32,7 +32,6 @@ import logging import traceback -from paste.httpheaders import REMOTE_USER, AUTH_TYPE from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError, \ HTTPNotAcceptable, HTTPBadRequest from kallithea.model.db import User @@ -43,7 +42,6 @@ from kallithea.lib.utils import make_ui, is_valid_repo, ui_sections from kallithea.lib.vcs.utils.hgcompat import RepoError, hgweb_mod from kallithea.lib.exceptions import HTTPLockedRC -from kallithea.lib import auth_modules log = logging.getLogger(__name__) @@ -103,64 +101,9 @@ #====================================================================== # CHECK PERMISSIONS #====================================================================== - anonymous_user = User.get_default_user(cache=True) - user = anonymous_user - if anonymous_user.active: - # ONLY check permissions if the user is activated - anonymous_perm = self._check_permission(action, anonymous_user, - repo_name, ip_addr) - else: - anonymous_perm = False - - if not anonymous_user.active or not anonymous_perm: - if not anonymous_user.active: - log.debug('Anonymous access is disabled, running ' - 'authentication') - - if not anonymous_perm: - log.debug('Not enough credentials to access this ' - 'repository as anonymous user') - - username = None - #============================================================== - # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE - # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS - #============================================================== - - # try to auth based on environ, container auth methods - log.debug('Running PRE-AUTH for container based authentication') - pre_auth = auth_modules.authenticate('', '', environ) - if pre_auth is not None and pre_auth.get('username'): - username = pre_auth['username'] - log.debug('PRE-AUTH got %s as username', username) - - # If not authenticated by the container, running basic auth - if not username: - self.authenticate.realm = \ - safe_str(self.config['realm']) - result = self.authenticate(environ) - if isinstance(result, str): - AUTH_TYPE.update(environ, 'basic') - REMOTE_USER.update(environ, result) - username = result - else: - return result.wsgi_application(environ, start_response) - - #============================================================== - # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME - #============================================================== - try: - user = User.get_by_username_or_email(username) - if user is None or not user.active: - return HTTPForbidden()(environ, start_response) - except Exception: - log.error(traceback.format_exc()) - return HTTPInternalServerError()(environ, start_response) - - #check permissions for this repository - perm = self._check_permission(action, user, repo_name, ip_addr) - if not perm: - return HTTPForbidden()(environ, start_response) + user, response_app = self._authorize(environ, start_response, action, repo_name, ip_addr) + if response_app is not None: + return response_app(environ, start_response) # extras are injected into mercurial UI object and later available # in hg hooks executed by kallithea