Changeset - da59b7e07e3c
[Not reviewed]
default
0 3 0
Marcin Kuzminski - 15 years ago 2010-05-22 19:29:50
marcin@python-works.com
Changed import to base. Removed action logging from auth to simplehg.
Splited loggin and invalidation action to private methods inside simplehg.
3 files changed with 63 insertions and 33 deletions:
0 comments (0 inline, 0 general)
pylons_app/lib/auth.py
Show inline comments
 
from datetime import datetime
 
from decorator import decorator
 
from functools import wraps
 
from pylons import session, url
 
from pylons.controllers.util import abort, redirect
 
from pylons_app.model import meta
 
from pylons_app.model.db import Users, UserLogs
 
from pylons_app.model.db import Users
 
from sqlalchemy.exc import OperationalError
 
from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound
 
import crypt
 
import logging
 
log = logging.getLogger(__name__)
 

	
 
def get_crypt_password(password):
 
    """
 
    Cryptographic function used for password hashing
 
    @param password: password to hash
 
    """
 
    return crypt.crypt(password, '6a')
 

	
 
def authfunc(environ, username, password):
 
    sa = meta.Session
 
    password_crypt = get_crypt_password(password)
 
    try:
 
        user = sa.query(Users).filter(Users.username == username).one()
 
    except (NoResultFound, MultipleResultsFound, OperationalError) as e:
 
        log.error(e)
 
        user = None
 
        
 
    if user:
 
        if user.active:
 
            if user.username == username and user.password == password_crypt:
 
                log.info('user %s authenticated correctly', username)
 
                if environ:
 
                    http_accept = environ.get('HTTP_ACCEPT')
 
            
 
                    if http_accept.startswith('application/mercurial') or \
 
                        environ['PATH_INFO'].find('raw-file') != -1:
 
                        repo = environ['PATH_INFO']
 
                        for qry in environ['QUERY_STRING'].split('&'):
 
                            if qry.startswith('cmd'):
 
                                
 
                                try:
 
                                    user_log = UserLogs()
 
                                    user_log.user_id = user.user_id
 
                                    user_log.action = qry
 
                                    user_log.repository = repo
 
                                    user_log.action_date = datetime.now()
 
                                    sa.add(user_log)
 
                                    sa.commit()
 
                                    log.info('Adding user %s, action %s', username, qry)
 
                                except Exception as e:
 
                                    sa.rollback()
 
                                    log.error(e)
 
                                  
 
                return True
 
        else:
 
            log.error('user %s is disabled', username)
 
            
 
    return False
 

	
 
class  AuthUser(object):
 
    """
 
    A simple object that handles a mercurial username for authentication
 
    """
 
    username = 'Empty'
 
    is_authenticated = False
 
    is_admin = False
 
    permissions = set()
 
    group = set()
 
    
 
    def __init__(self):
 
        pass
 
    
 
#===============================================================================
 
# DECORATORS
 
#===============================================================================
 
class LoginRequired(object):
 
    """
pylons_app/lib/simplehg.py
Show inline comments
 
#!/usr/bin/env python
 
# encoding: utf-8
 
#
 
# Copyright (c) 2010 marcink.  All rights reserved.
 
#
 
"""
 
Created on 2010-04-28
 

	
 
@author: marcink
 
SimpleHG middleware for handling mercurial protocol request (push/clone etc.)
 
It's implemented with basic auth function
 
"""
 

	
 
from datetime import datetime
 
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 pylons_app.lib.utils import is_mercurial
 
from pylons_app.lib.auth import authfunc
 
from pylons_app.lib.utils import make_ui, invalidate_cache
 
from pylons_app.lib.utils import is_mercurial, make_ui, invalidate_cache
 
from pylons_app.model import meta
 
from pylons_app.model.db import UserLogs, Users
 
from webob.exc import HTTPNotFound
 
import logging
 
import os
 
log = logging.getLogger(__name__)
 

	
 
class SimpleHg(object):
 

	
 
    def __init__(self, application, config):
 
        self.application = application
 
        self.config = config
 
        #authenticate this mercurial request using 
 
        realm = '%s %s' % (config['hg_app_name'], 'mercurial repository')
 
        self.authenticate = AuthBasicAuthenticator(realm, authfunc)
 
        
 
    def __call__(self, environ, start_response):
 
        if not is_mercurial(environ):
 
            return self.application(environ, start_response)
 
        else:
 
            #===================================================================
 
            # AUTHENTICATE THIS MERCURIAL REQUEST
 
            #===================================================================
 
            username = REMOTE_USER(environ)
 
            if not username:
 
                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)
 
            
 
            try:
 
                repo_name = environ['PATH_INFO'].split('/')[1]
 
            except:
 
                return HTTPNotFound()(environ, start_response)
 
            
 
            #since we wrap into hgweb, just reset the path
 
            environ['PATH_INFO'] = '/'
 
            self.baseui = make_ui()
 
            self.basepath = self.baseui.configitems('paths')[0][1]\
 
                                                            .replace('*', '')
 
            self.repo_path = os.path.join(self.basepath, repo_name)
 
            try:
 
                app = wsgiapplication(self._make_app)
 
                app = wsgiapplication(self.__make_app)
 
            except Exception as e:
 
                return HTTPNotFound()(environ, start_response)
 
            
 
            """we know that some change was made to repositories and we should
 
            invalidate the cache to see the changes right away"""
 
            invalidate_cache('full_changelog', repo_name)
 
            invalidate_cache('cached_repo_list')
 
            action = self.__get_action(environ)            
 
            #invalidate cache on push
 
            if action == 'push':
 
                self.__invalidate_cache(repo_name)
 
            
 
            if action:
 
                username = self.__get_environ_user(environ)
 
                self.__log_user_action(username, action, repo_name)            
 
            return app(environ, start_response)            
 

	
 
    def _make_app(self):
 
    def __make_app(self):
 
        hgserve = hgweb(self.repo_path)
 
        return  self.load_web_settings(hgserve)
 
        
 
    def __get_environ_user(self, environ):
 
        return environ.get('REMOTE_USER')
 
        
 
    def __get_action(self, environ):
 
        """
 
        Maps mercurial request commands into a pull or push command.
 
        @param environ:
 
        """
 
        mapping = {
 
            'changegroup': 'pull',
 
            'changegroupsubset': 'pull',
 
            'unbundle': 'push',
 
            'stream_out': 'pull',
 
        }                    
 
        for qry in environ['QUERY_STRING'].split('&'):
 
            if qry.startswith('cmd'):
 
                cmd = qry.split('=')[-1]
 
                if mapping.has_key(cmd):
 
                    return mapping[cmd]
 
    
 
    def __log_user_action(self, username, action, repo):
 
        sa = meta.Session
 
        try:
 
            user = sa.query(Users)\
 
                    .filter(Users.username == username).one()
 
            user_log = UserLogs()
 
            user_log.user_id = user.user_id
 
            user_log.action = action
 
            user_log.repository = repo.replace('/', '')
 
            user_log.action_date = datetime.now()
 
            sa.add(user_log)
 
            sa.commit()
 
            log.info('Adding user %s, action %s on %s',
 
                                            username, action, repo)
 
        except Exception as e:
 
            sa.rollback()
 
            log.error('could not log user action:%s', str(e))
 
    
 
    def __invalidate_cache(self, repo_name):
 
        """we know that some change was made to repositories and we should
 
        invalidate the cache to see the changes right away but only for
 
        push requests"""
 
        invalidate_cache('cached_repo_list')
 
        invalidate_cache('full_changelog', repo_name)
 
           
 
                
 
    def load_web_settings(self, hgserve):
 
        repoui = make_ui(os.path.join(self.repo_path, '.hg', 'hgrc'), False)
 
        #set the global ui for hgserve
 
        hgserve.repo.ui = self.baseui
 
        
 
        if repoui:
 
            #set the repository based config
 
            hgserve.repo.ui = repoui
 
            
 
        return hgserve
 

	
 

	
pylons_app/lib/utils.py
Show inline comments
 
@@ -87,41 +87,41 @@ def make_ui(path='hgwebdir.config', chec
 
    for section in sections:
 
        for k, v in cfg.items(section):
 
            baseui.setconfig(section, k, v)
 
    
 
    return baseui
 

	
 
def invalidate_cache(name, *args):
 
    """Invalidates given name cache"""
 
    
 
    from beaker.cache import region_invalidate
 
    log.info('INVALIDATING CACHE FOR %s', name)
 
    
 
    """propagate our arguments to make sure invalidation works. First
 
    argument has to be the name of cached func name give to cache decorator
 
    without that the invalidation would not work"""
 
    tmp = [name]
 
    tmp.extend(args)
 
    args = tuple(tmp)
 
    
 
    if name == 'cached_repo_list':
 
        from pylons_app.lib.base import _get_repos_cached
 
        region_invalidate(_get_repos_cached, None, *args)
 
        
 
    if name == 'full_changelog':
 
        from pylons_app.controllers.changelog import _full_changelog_cached
 
        from pylons_app.lib.base import _full_changelog_cached
 
        region_invalidate(_full_changelog_cached, None, *args)
 
        
 
from vcs.backends.base import BaseChangeset
 
from vcs.utils.lazy import LazyProperty
 
class EmptyChangeset(BaseChangeset):
 
    
 
    revision = -1
 

	
 
    @LazyProperty
 
    def raw_id(self):
 
        """
 
        Returns raw string identifing this changeset, useful for web
 
        representation.
 
        """
 
        return '0' * 12
 

	
0 comments (0 inline, 0 general)