Changeset - 8eda7bc543cd
[Not reviewed]
default
0 3 0
Mads Kiilerich - 9 years ago 2017-04-07 04:21:38
mads@kiilerich.com
lib: move special Mercurial HTTP listkey exception out of _check_locking_state

Prepare for use with SSH.
3 files changed with 10 insertions and 16 deletions:
0 comments (0 inline, 0 general)
kallithea/lib/base.py
Show inline comments
 
@@ -279,116 +279,111 @@ class BaseVCSController(object):
 
            if by_id_match:
 
                data[1] = safe_str(by_id_match)
 

	
 
        return '/'.join(data)
 

	
 
    def _invalidate_cache(self, repo_name):
 
        """
 
        Sets cache for this repository for invalidation on next access
 

	
 
        :param repo_name: full repo name, also a cache key
 
        """
 
        ScmModel().mark_for_invalidation(repo_name)
 

	
 
    def _check_permission(self, action, user, repo_name, ip_addr=None):
 
        """
 
        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
 
        """
 
        # check IP
 
        ip_allowed = AuthUser.check_ip_allowed(user, ip_addr)
 
        if ip_allowed:
 
            log.info('Access for IP:%s allowed', ip_addr)
 
        else:
 
            return False
 

	
 
        if action == 'push':
 
            if not HasPermissionAnyMiddleware('repository.write',
 
                                              'repository.admin')(user,
 
                                                                  repo_name):
 
                return False
 

	
 
        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_ip_addr(self, environ):
 
        return _get_ip_addr(environ)
 

	
 
    def _check_locking_state(self, environ, action, repo, user_id):
 
    def _check_locking_state(self, action, repo, user_id):
 
        """
 
        Checks locking on this repository, if locking is enabled, and if lock
 
        is present. Returns a tuple of make_lock, locked, locked_by. make_lock
 
        can have 3 states: None (do nothing), True (make lock), and False
 
        (release lock). This value is later propagated to hooks, telling them
 
        what to do.
 
        """
 
        locked = False  # defines that locked error should be thrown to user
 
        make_lock = None
 
        repo = Repository.get_by_repo_name(repo)
 
        user = User.get(user_id)
 

	
 
        # this is kind of hacky, but due to how mercurial handles client-server
 
        # server see all operation on changeset; bookmarks, phases and
 
        # obsolescence marker in different transaction, we don't want to check
 
        # locking on those
 
        obsolete_call = environ['QUERY_STRING'] in ['cmd=listkeys',]
 
        locked_by = repo.locked
 
        if repo and repo.enable_locking and not obsolete_call:
 
        if repo and repo.enable_locking:
 
            if action == 'push':
 
                # Check if repo already is locked !, if it is compare users
 
                user_id, _date = locked_by
 
                if user.user_id == user_id:
 
                    log.debug('Got push from user %s, now unlocking', user)
 
                    # Unlock if we have push from the user who locked
 
                    make_lock = False
 
                else:
 
                    # Another used tried to push - deny access with something like 423 Locked!
 
                    locked = True
 
            if action == 'pull':
 
                if repo.locked[0] and repo.locked[1]:
 
                    locked = True
 
                else:
 
                    log.debug('Setting lock on repo %s by %s', repo, user)
 
                    make_lock = True
 

	
 
        else:
 
            log.debug('Repository %s does not have locking enabled', repo)
 
        log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
 
                  make_lock, locked, locked_by)
 
        return make_lock, locked, locked_by
 

	
 
    def __call__(self, environ, start_response):
 
        start = time.time()
 
        try:
 
            return self._handle_request(environ, start_response)
 
        finally:
 
            log = logging.getLogger('kallithea.' + self.__class__.__name__)
 
            log.debug('Request time: %.3fs', time.time() - start)
 
            meta.Session.remove()
 

	
 

	
 
class BaseController(TGController):
 

	
 
    def _before(self, *args, **kwargs):
 
        """
 
        _before is called before controller methods and after __call__
 
        """
 
        c.kallithea_version = __version__
 
        rc_config = Setting.get_app_settings()
 

	
 
        # Visual options
 
        c.visual = AttributeDict({})
 

	
 
        ## DB stored
 
        c.visual.show_public_icon = str2bool(rc_config.get('show_public_icon'))
 
        c.visual.show_private_icon = str2bool(rc_config.get('show_private_icon'))
kallithea/lib/middleware/simplegit.py
Show inline comments
 
@@ -81,100 +81,97 @@ class SimpleGit(BaseVCSController):
 
            log.debug('Extracted repo name is %s', repo_name)
 
        except Exception as e:
 
            log.error('error extracting repo_name: %r', e)
 
            return HTTPInternalServerError()(environ, start_response)
 

	
 
        # quick check if that dir exists...
 
        if not is_valid_repo(repo_name, self.basepath, 'git'):
 
            return HTTPNotFound()(environ, start_response)
 

	
 
        #======================================================================
 
        # GET ACTION PULL or PUSH
 
        #======================================================================
 
        action = self.__get_action(environ)
 

	
 
        #======================================================================
 
        # CHECK PERMISSIONS
 
        #======================================================================
 
        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
 
        from kallithea import CONFIG
 
        server_url = get_server_url(environ)
 
        extras = {
 
            'ip': ip_addr,
 
            'username': user.username,
 
            'action': action,
 
            'repository': repo_name,
 
            'scm': 'git',
 
            'config': CONFIG['__file__'],
 
            'server_url': server_url,
 
            'make_lock': None,
 
            'locked_by': [None, None]
 
        }
 

	
 
        #===================================================================
 
        # GIT REQUEST HANDLING
 
        #===================================================================
 
        repo_path = os.path.join(safe_str(self.basepath),str_repo_name)
 
        log.debug('Repository path is %s', repo_path)
 

	
 
        # CHECK LOCKING only if it's not ANONYMOUS USER
 
        if not user.is_default_user:
 
            log.debug('Checking locking on repository')
 
            (make_lock,
 
             locked,
 
             locked_by) = self._check_locking_state(
 
                            environ=environ, action=action,
 
                            repo=repo_name, user_id=user.user_id
 
                       )
 
             locked_by) = self._check_locking_state(action, repo_name, user.user_id)
 
            # store the make_lock for later evaluation in hooks
 
            extras.update({'make_lock': make_lock,
 
                           'locked_by': locked_by})
 

	
 
        fix_PATH()
 
        log.debug('HOOKS extras is %s', extras)
 
        baseui = make_ui('db')
 
        self.__inject_extras(repo_path, baseui, extras)
 

	
 
        try:
 
            self._handle_githooks(repo_name, action, baseui, environ)
 
            log.info('%s action on Git repo "%s" by "%s" from %s',
 
                     action, str_repo_name, safe_str(user.username), ip_addr)
 
            app = self.__make_app(repo_name, repo_path, extras)
 
            result = app(environ, start_response)
 
            if action == 'push':
 
                result = WSGIResultCloseCallback(result,
 
                    lambda: self._invalidate_cache(repo_name))
 
            return result
 
        except HTTPLockedRC as e:
 
            log.debug('Locked, response %s: %s', e.code, e.title)
 
            return e(environ, start_response)
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            return HTTPInternalServerError()(environ, start_response)
 

	
 
    def __make_app(self, repo_name, repo_path, extras):
 
        """
 
        Make an wsgi application using dulserver
 

	
 
        :param repo_name: name of the repository
 
        :param repo_path: full path to the repository
 
        """
 

	
 
        from kallithea.lib.middleware.pygrack import make_wsgi_app
 
        app = make_wsgi_app(
 
            repo_root=safe_str(self.basepath),
 
            repo_name=safe_unicode(repo_name),
 
            extras=extras,
 
        )
 
        return app
 

	
 
    def __get_repository(self, environ):
 
        """
 
        Gets repository name out of PATH_INFO header
 

	
 
        :param environ: environ where PATH_INFO is stored
 
        """
kallithea/lib/middleware/simplehg.py
Show inline comments
 
@@ -81,105 +81,107 @@ class SimpleHg(BaseVCSController):
 
            assert isinstance(str_repo_name, str), str_repo_name
 
            repo_name = safe_unicode(str_repo_name)
 
            assert safe_str(repo_name) == str_repo_name, (str_repo_name, repo_name)
 
            log.debug('Extracted repo name is %s', repo_name)
 
        except Exception as e:
 
            log.error('error extracting repo_name: %r', e)
 
            return HTTPInternalServerError()(environ, start_response)
 

	
 
        # quick check if that dir exists...
 
        if not is_valid_repo(repo_name, self.basepath, 'hg'):
 
            return HTTPNotFound()(environ, start_response)
 

	
 
        #======================================================================
 
        # GET ACTION PULL or PUSH
 
        #======================================================================
 
        try:
 
            action = self.__get_action(environ)
 
        except HTTPBadRequest as e:
 
            return e(environ, start_response)
 

	
 
        #======================================================================
 
        # CHECK PERMISSIONS
 
        #======================================================================
 
        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
 
        from kallithea import CONFIG
 
        server_url = get_server_url(environ)
 
        extras = {
 
            'ip': ip_addr,
 
            'username': user.username,
 
            'action': action,
 
            'repository': repo_name,
 
            'scm': 'hg',
 
            'config': CONFIG['__file__'],
 
            'server_url': server_url,
 
            'make_lock': None,
 
            'locked_by': [None, None]
 
        }
 
        #======================================================================
 
        # MERCURIAL REQUEST HANDLING
 
        #======================================================================
 
        repo_path = os.path.join(safe_str(self.basepath), str_repo_name)
 
        log.debug('Repository path is %s', repo_path)
 

	
 
        # A Mercurial HTTP server will see listkeys operations (bookmarks,
 
        # phases and obsolescence marker) in a different request - we don't
 
        # want to check locking on those
 
        if environ['QUERY_STRING'] == 'cmd=listkeys':
 
            pass
 
        # CHECK LOCKING only if it's not ANONYMOUS USER
 
        if not user.is_default_user:
 
        elif not user.is_default_user:
 
            log.debug('Checking locking on repository')
 
            (make_lock,
 
             locked,
 
             locked_by) = self._check_locking_state(
 
                            environ=environ, action=action,
 
                            repo=repo_name, user_id=user.user_id
 
                       )
 
             locked_by) = self._check_locking_state(action, repo_name, user.user_id)
 
            # store the make_lock for later evaluation in hooks
 
            extras.update({'make_lock': make_lock,
 
                           'locked_by': locked_by})
 

	
 
        fix_PATH()
 
        log.debug('HOOKS extras is %s', extras)
 
        baseui = make_ui('db')
 
        self.__inject_extras(repo_path, baseui, extras)
 

	
 
        try:
 
            log.info('%s action on Mercurial repo "%s" by "%s" from %s',
 
                     action, str_repo_name, safe_str(user.username), ip_addr)
 
            app = self.__make_app(repo_path, baseui, extras)
 
            result = app(environ, start_response)
 
            if action == 'push':
 
                result = WSGIResultCloseCallback(result,
 
                    lambda: self._invalidate_cache(repo_name))
 
            return result
 
        except RepoError as e:
 
            if str(e).find('not found') != -1:
 
                return HTTPNotFound()(environ, start_response)
 
        except HTTPLockedRC as e:
 
            # Before Mercurial 3.6, lock exceptions were caught here
 
            log.debug('Locked, response %s: %s', e.code, e.title)
 
            return e(environ, start_response)
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            return HTTPInternalServerError()(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
 
        """
 
        class HgWebWrapper(hgweb_mod.hgweb):
 
            # Work-around for Mercurial 3.6+ causing lock exceptions to be
 
            # thrown late
 
            def _runwsgi(self, req, repo):
 
                try:
 
                    return super(HgWebWrapper, self)._runwsgi(req, repo)
 
                except HTTPLockedRC as e:
 
                    log.debug('Locked, response %s: %s', e.code, e.title)
 
                    req.respond(e.status, 'text/plain')
 
                    return ''
 

	
 
        return HgWebWrapper(repo_name, name=repo_name, baseui=baseui)
 

	
 
    def __get_repository(self, environ):
0 comments (0 inline, 0 general)