Changeset - 811fa5d45de8
[Not reviewed]
beta
0 3 0
Marcin Kuzminski - 15 years ago 2011-01-03 00:47:16
marcin@python-works.com
Rewrite simehg for enabling cloning with raw url for anonymous access + some optimizations for making less queries when authenticating users.
added debug to test hg operations
3 files changed with 128 insertions and 63 deletions:
0 comments (0 inline, 0 general)
rhodecode/controllers/summary.py
Show inline comments
 
@@ -73,19 +73,22 @@ class SummaryController(BaseController):
 
        c.repo_changesets = Page(c.repo_info, page=1, items_per_page=10,
 
                                 url=url_generator)
 

	
 
        e = request.environ
 

	
 
        if self.rhodecode_user.username == 'default':
 
            password = ':default'
 
            #for default(anonymous) user we don't need to pass credentials
 
            username = ''
 
            password = ''
 
        else:
 
            username = str(c.rhodecode_user.username)
 
            password = ''
 

	
 
        uri = u'%(protocol)s://%(user)s%(password)s@%(host)s%(prefix)s/%(repo_name)s' % {
 
                                        'protocol': e.get('wsgi.url_scheme'),
 
                                        'user':str(c.rhodecode_user.username),
 
                                        'user':username,
 
                                        'password':password,
 
                                        'host':e.get('HTTP_HOST'),
 
                                        'prefix':e.get('SCRIPT_NAME'),
 
                                        'repo_name':c.repo_name, }
 
        c.clone_repo_url = uri
 
        c.repo_tags = OrderedDict()
rhodecode/lib/middleware/simplehg.py
Show inline comments
 
@@ -23,25 +23,29 @@
 
# 
 
# You should have received a copy of the GNU General Public License
 
# along with this program; if not, write to the Free Software
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 
# MA  02110-1301, USA.
 

	
 
import os
 
import logging
 
import traceback
 

	
 
from mercurial.error import RepoError
 
from mercurial.hgweb import hgweb
 
from mercurial.hgweb.request import wsgiapplication
 

	
 
from paste.auth.basic import AuthBasicAuthenticator
 
from paste.httpheaders import REMOTE_USER, AUTH_TYPE
 

	
 
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
 

	
 
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
 
    ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
 
@@ -56,124 +60,179 @@ class SimpleHg(object):
 
    def __init__(self, application, config):
 
        self.application = application
 
        self.config = config
 
        #authenticate this mercurial request using authfunc
 
        self.authenticate = AuthBasicAuthenticator('', authfunc)
 
        self.ipaddr = '0.0.0.0'
 
        self.repository = None
 
        self.repo_name = None
 
        self.username = None
 
        self.action = None
 

	
 
    def __call__(self, environ, start_response):
 
        if not is_mercurial(environ):
 
            return self.application(environ, start_response)
 

	
 
        proxy_key = 'HTTP_X_REAL_IP'
 
        def_key = 'REMOTE_ADDR'
 
        self.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 = 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
 
        #=======================================================================
 
        #======================================================================
 
        # GET ACTION PULL or PUSH
 
        #======================================================================
 
        self.action = self.__get_action(environ)
 
        try:
 
            repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
 
            if repo_name.endswith('/'):
 
                repo_name = repo_name.rstrip('/')
 
            self.repository = repo_name
 
            #==================================================================
 
            # GET REPOSITORY NAME
 
            #==================================================================            
 
            self.repo_name = self.__get_repository(environ)
 
        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)
 
        #======================================================================
 
        # CHECK ANONYMOUS PERMISSION
 
        #======================================================================
 
        if self.action in ['pull', 'push']:
 
            anonymous_user = self.__get_user('default')
 
            self.username = anonymous_user.username
 
            anonymous_perm = self.__check_permission(self.action, anonymous_user ,
 
                                           self.repo_name)
 

	
 
            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 AUTHENTICATED USER PERMISSIONS
 
                #==================================================================
 

	
 
            #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 not REMOTE_USER(environ):
 
                    self.authenticate.realm = 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)
 

	
 

	
 
            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)
 
                #==================================================================
 
                # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME FROM
 
                # BASIC AUTH
 
                #==================================================================
 

	
 
                if self.action in ['pull', 'push']:
 
                    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)
 

	
 
                    #check permissions for this repository
 
                    perm = self.__check_permission(self.action, user, self.repo_name)
 
                    if perm is not True:
 
                        return HTTPForbidden()(environ, start_response)
 

	
 
        self.extras = {'ip':self.ipaddr,
 
                       'username':self.username,
 
                       'action':self.action,
 
                       'repository':self.repository}
 
                       'repository':self.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)
 
        self.repo_path = os.path.join(self.basepath, self.repo_name)
 

	
 
        #quick check if that dir exists...
 
        if check_repo_fast(repo_name, self.basepath):
 
        if check_repo_fast(self.repo_name, self.basepath):
 
            return HTTPNotFound()(environ, start_response)
 
        try:
 
            app = wsgiapplication(self.__make_app)
 
        except RepoError, e:
 
            if str(e).find('not found') != -1:
 
                return HTTPNotFound()(environ, start_response)
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            return HTTPInternalServerError()(environ, start_response)
 

	
 
        #invalidate cache on push
 
        if self.action == 'push':
 
            self.__invalidate_cache(repo_name)
 
            self.__invalidate_cache(self.repo_name)
 

	
 
        return app(environ, start_response)
 

	
 

	
 
    def __make_app(self):
 
        """Make an wsgi application using hgweb, and my generated baseui
 
        instance
 
        """
 

	
 
        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
 

	
 
        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_environ_user(self, environ):
 
        return environ.get('REMOTE_USER')
 

	
 
    def __get_user(self, username):
 
        return UserModel().get_by_username(username, cache=True)
 

	
 
    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',
 
                   'changegroupsubset': 'pull',
 
                   'stream_out': 'pull',
 
                   'listkeys': 'pull',
rhodecode/tests/test_hg_operations.py
Show inline comments
 
@@ -20,13 +20,13 @@ from os.path import join as jn
 

	
 
from rhodecode.tests import TESTS_TMP_PATH, NEW_HG_REPO, HG_REPO
 

	
 
USER = 'test_admin'
 
PASS = 'test12'
 
HOST = '127.0.0.1:5000'
 

	
 
DEBUG = True
 
log = logging.getLogger(__name__)
 

	
 

	
 
class Command(object):
 

	
 
    def __init__(self, cwd):
 
@@ -35,22 +35,24 @@ class Command(object):
 
    def execute(self, cmd, *args):
 
        """Runs command on the system with given ``args``.
 
        """
 

	
 
        command = cmd + ' ' + ' '.join(args)
 
        log.debug('Executing %s' % command)
 
        print command
 
        if DEBUG:
 
            print command
 
        p = Popen(command, shell=True, stdout=PIPE, stderr=PIPE, cwd=self.cwd)
 
        stdout, stderr = p.communicate()
 
        print stdout, stderr
 
        if DEBUG:
 
            print stdout, stderr
 
        return stdout, stderr
 

	
 

	
 
#===============================================================================
 
#==============================================================================
 
# TESTS
 
#===============================================================================
 
#==============================================================================
 
def test_clone():
 
    cwd = path = jn(TESTS_TMP_PATH, HG_REPO)
 

	
 
    try:
 
        shutil.rmtree(path, ignore_errors=True)
 
        os.makedirs(path)
 
@@ -212,14 +214,15 @@ def test_push_wrong_path():
 
    stdout, stderr = Command(cwd).execute('hg push %s' % clone_url)
 
    assert """abort: HTTP Error 403: Forbidden"""  in stderr
 

	
 

	
 
if __name__ == '__main__':
 
    test_clone()
 
    test_clone_wrong_credentials()
 

	
 
    #test_clone_wrong_credentials()
 
    ##test_clone_anonymous_ok()
 

	
 
    test_pull()
 
    #test_push_new_file()
 
    #test_push_wrong_path()
 
    #test_push_wrong_credentials()
 

	
 

	
0 comments (0 inline, 0 general)