Mercurial > kallithea
diff rhodecode/lib/middleware/simplehg.py @ 1512:bf263968da47
merge beta in stable branch
author | Marcin Kuzminski <marcin@python-works.com> |
---|---|
date | Fri, 07 Oct 2011 01:08:50 +0200 |
parents | 9f6560667743 7d687ed11929 |
children | 752b0a7b7679 |
line wrap: on
line diff
--- a/rhodecode/lib/middleware/simplehg.py Thu May 12 19:50:48 2011 +0200 +++ b/rhodecode/lib/middleware/simplehg.py Fri Oct 07 01:08:50 2011 +0200 @@ -29,24 +29,24 @@ import traceback from mercurial.error import RepoError -from mercurial.hgweb import hgweb -from mercurial.hgweb.request import wsgiapplication +from mercurial.hgweb import hgweb_mod + from paste.auth.basic import AuthBasicAuthenticator from paste.httpheaders import REMOTE_USER, AUTH_TYPE + +from rhodecode.lib import safe_str from rhodecode.lib.auth import authfunc, HasPermissionAnyMiddleware from rhodecode.lib.utils import make_ui, invalidate_cache, \ - check_repo_fast, ui_sections -from rhodecode.model.user import UserModel + is_valid_repo, ui_sections +from rhodecode.model.db import User + from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError -import logging -import os -import traceback log = logging.getLogger(__name__) + def is_mercurial(environ): - """ - Returns True if request's target is mercurial server - header + """Returns True if request's target is mercurial server - header ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``. """ http_accept = environ.get('HTTP_ACCEPT') @@ -54,17 +54,17 @@ return True return False + class SimpleHg(object): def __init__(self, application, config): self.application = application self.config = config + # base path of repo locations + self.basepath = self.config['base_path'] #authenticate this mercurial request using authfunc self.authenticate = AuthBasicAuthenticator('', authfunc) self.ipaddr = '0.0.0.0' - self.repository = None - self.username = None - self.action = None def __call__(self, environ, start_response): if not is_mercurial(environ): @@ -72,82 +72,105 @@ proxy_key = 'HTTP_X_REAL_IP' def_key = 'REMOTE_ADDR' - self.ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0')) + ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0')) + # skip passing error to error controller environ['pylons.status_code_redirect'] = True - - #=================================================================== - # AUTHENTICATE THIS MERCURIAL REQUEST - #=================================================================== - username = REMOTE_USER(environ) - - if not username: - self.authenticate.realm = str(self.config['rhodecode_realm']) - result = self.authenticate(environ) - if isinstance(result, str): - AUTH_TYPE.update(environ, 'basic') - REMOTE_USER.update(environ, result) - else: - return result.wsgi_application(environ, start_response) - - #======================================================================= - # GET REPOSITORY - #======================================================================= + + #====================================================================== + # EXTRACT REPOSITORY NAME FROM ENV + #====================================================================== try: - repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:]) - if repo_name.endswith('/'): - repo_name = repo_name.rstrip('/') - self.repository = repo_name + repo_name = environ['REPO_NAME'] = self.__get_repository(environ) + log.debug('Extracted repo name is %s' % repo_name) except: - log.error(traceback.format_exc()) return HTTPInternalServerError()(environ, start_response) - #=================================================================== - # CHECK PERMISSIONS FOR THIS REQUEST - #=================================================================== - self.action = self.__get_action(environ) - if self.action: - username = self.__get_environ_user(environ) - try: - user = self.__get_user(username) - self.username = user.username - except: - log.error(traceback.format_exc()) - return HTTPInternalServerError()(environ, start_response) + #====================================================================== + # GET ACTION PULL or PUSH + #====================================================================== + action = self.__get_action(environ) + + #====================================================================== + # CHECK ANONYMOUS PERMISSION + #====================================================================== + if action in ['pull', 'push']: + anonymous_user = self.__get_user('default') + username = anonymous_user.username + anonymous_perm = self.__check_permission(action, + anonymous_user, + repo_name) - #check permissions for this repository - if self.action == 'push': - if not HasPermissionAnyMiddleware('repository.write', - 'repository.admin')\ - (user, repo_name): - return HTTPForbidden()(environ, start_response) + if anonymous_perm is not True or anonymous_user.active is False: + if anonymous_perm is not True: + log.debug('Not enough credentials to access this ' + 'repository as anonymous user') + if anonymous_user.active is False: + log.debug('Anonymous access is disabled, running ' + 'authentication') + #============================================================== + # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE + # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS + #============================================================== + + if not REMOTE_USER(environ): + self.authenticate.realm = \ + safe_str(self.config['rhodecode_realm']) + result = self.authenticate(environ) + if isinstance(result, str): + AUTH_TYPE.update(environ, 'basic') + REMOTE_USER.update(environ, result) + else: + return result.wsgi_application(environ, start_response) + + #============================================================== + # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME FROM + # BASIC AUTH + #============================================================== - else: - #any other action need at least read permission - if not HasPermissionAnyMiddleware('repository.read', - 'repository.write', - 'repository.admin')\ - (user, repo_name): - return HTTPForbidden()(environ, start_response) + if action in ['pull', 'push']: + username = REMOTE_USER(environ) + try: + user = self.__get_user(username) + username = user.username + except: + log.error(traceback.format_exc()) + return HTTPInternalServerError()(environ, + start_response) - self.extras = {'ip':self.ipaddr, - 'username':self.username, - 'action':self.action, - 'repository':self.repository} + #check permissions for this repository + perm = self.__check_permission(action, user, + repo_name) + if perm is not True: + return HTTPForbidden()(environ, start_response) + + extras = {'ip': ipaddr, + 'username': username, + 'action': action, + 'repository': repo_name} - #=================================================================== + #====================================================================== # MERCURIAL REQUEST HANDLING - #=================================================================== - environ['PATH_INFO'] = '/'#since we wrap into hgweb, reset the path - self.baseui = make_ui('db') - self.basepath = self.config['base_path'] - self.repo_path = os.path.join(self.basepath, repo_name) + #====================================================================== + + repo_path = safe_str(os.path.join(self.basepath, repo_name)) + log.debug('Repository path is %s' % repo_path) + + baseui = make_ui('db') + self.__inject_extras(repo_path, baseui, extras) + - #quick check if that dir exists... - if check_repo_fast(repo_name, self.basepath): + # quick check if that dir exists... + if is_valid_repo(repo_name, self.basepath) is False: return HTTPNotFound()(environ, start_response) + try: - app = wsgiapplication(self.__make_app) + #invalidate cache on push + if action == 'push': + self.__invalidate_cache(repo_name) + + app = self.__make_app(repo_path, baseui, extras) + return app(environ, start_response) except RepoError, e: if str(e).find('not found') != -1: return HTTPNotFound()(environ, start_response) @@ -155,27 +178,63 @@ log.error(traceback.format_exc()) return HTTPInternalServerError()(environ, start_response) - #invalidate cache on push - if self.action == 'push': - self.__invalidate_cache(repo_name) - - return app(environ, start_response) + def __make_app(self, repo_name, baseui, extras): + """ + Make an wsgi application using hgweb, and inject generated baseui + instance, additionally inject some extras into ui object + """ + return hgweb_mod.hgweb(repo_name, name=repo_name, baseui=baseui) - def __make_app(self): - hgserve = hgweb(str(self.repo_path), baseui=self.baseui) - return self.__load_web_settings(hgserve, self.extras) + def __check_permission(self, action, user, repo_name): + """ + Checks permissions using action (push/pull) user and repository + name + + :param action: push or pull action + :param user: user instance + :param repo_name: repository name + """ + if action == 'push': + if not HasPermissionAnyMiddleware('repository.write', + 'repository.admin')(user, + repo_name): + return False - def __get_environ_user(self, environ): - return environ.get('REMOTE_USER') + else: + #any other action need at least read permission + if not HasPermissionAnyMiddleware('repository.read', + 'repository.write', + 'repository.admin')(user, + repo_name): + return False + + return True + + def __get_repository(self, environ): + """ + Get's repository name out of PATH_INFO header + + :param environ: environ where PATH_INFO is stored + """ + try: + repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:]) + if repo_name.endswith('/'): + repo_name = repo_name.rstrip('/') + except: + log.error(traceback.format_exc()) + raise + + return repo_name def __get_user(self, username): - return UserModel().get_by_username(username, cache=True) + return User.by_username(username) def __get_action(self, environ): """ Maps mercurial request commands into a clone,pull or push command. This should always return a valid command string + :param environ: """ mapping = {'changegroup': 'pull', @@ -187,10 +246,10 @@ for qry in environ['QUERY_STRING'].split('&'): if qry.startswith('cmd'): cmd = qry.split('=')[-1] - if mapping.has_key(cmd): + if cmd in mapping: return mapping[cmd] else: - return cmd + return 'pull' def __invalidate_cache(self, repo_name): """we know that some change was made to repositories and we should @@ -198,17 +257,25 @@ push requests""" invalidate_cache('get_repo_cached_%s' % repo_name) + def __inject_extras(self,repo_path, baseui, extras={}): + """ + Injects some extra params into baseui instance + + also overwrites global settings with those takes from local hgrc file + + :param baseui: baseui instance + :param extras: dict with extra params to put into baseui + """ - def __load_web_settings(self, hgserve, extras={}): - #set the global ui for hgserve instance passed - hgserve.repo.ui = self.baseui + hgrc = os.path.join(repo_path, '.hg', 'hgrc') - hgrc = os.path.join(self.repo_path, '.hg', 'hgrc') + # make our hgweb quiet so it doesn't print output + baseui.setconfig('ui', 'quiet', 'true') #inject some additional parameters that will be available in ui #for hooks for k, v in extras.items(): - hgserve.repo.ui.setconfig('rhodecode_extras', k, v) + baseui.setconfig('rhodecode_extras', k, v) repoui = make_ui('file', hgrc, False) @@ -216,20 +283,5 @@ #overwrite our ui instance with the section from hgrc file for section in ui_sections: for k, v in repoui.configitems(section): - hgserve.repo.ui.setconfig(section, k, v) - - return hgserve - - - - - + baseui.setconfig(section, k, v) - - - - - - - -