changeset 910:811fa5d45de8 beta

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
author Marcin Kuzminski <marcin@python-works.com>
date Mon, 03 Jan 2011 00:47:16 +0100
parents 1f0e37c0854d
children 435882d9e3bf
files rhodecode/controllers/summary.py rhodecode/lib/middleware/simplehg.py rhodecode/tests/test_hg_operations.py
diffstat 3 files changed, 128 insertions(+), 63 deletions(-) [+]
line wrap: on
line diff
--- a/rhodecode/controllers/summary.py	Mon Jan 03 00:30:05 2011 +0100
+++ b/rhodecode/controllers/summary.py	Mon Jan 03 00:47:16 2011 +0100
@@ -76,13 +76,16 @@
         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'),
--- a/rhodecode/lib/middleware/simplehg.py	Mon Jan 03 00:30:05 2011 +0100
+++ b/rhodecode/lib/middleware/simplehg.py	Mon Jan 03 00:47:16 2011 +0100
@@ -26,19 +26,23 @@
 # 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__)
 
@@ -59,7 +63,7 @@
         #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
 
@@ -72,64 +76,73 @@
         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
@@ -137,10 +150,10 @@
         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)
@@ -153,15 +166,60 @@
 
         #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')
 
@@ -171,6 +229,7 @@
     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',
--- a/rhodecode/tests/test_hg_operations.py	Mon Jan 03 00:30:05 2011 +0100
+++ b/rhodecode/tests/test_hg_operations.py	Mon Jan 03 00:47:16 2011 +0100
@@ -23,7 +23,7 @@
 USER = 'test_admin'
 PASS = 'test12'
 HOST = '127.0.0.1:5000'
-
+DEBUG = True
 log = logging.getLogger(__name__)
 
 
@@ -38,16 +38,18 @@
 
         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)
 
@@ -215,9 +217,10 @@
 
 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()