Changeset - 25d8e4836bc2
[Not reviewed]
beta
0 4 0
Liad Shani - 14 years ago 2011-11-01 00:31:24
liadff@gmail.com
Improved container-based auth support for middleware
4 files changed with 22 insertions and 19 deletions:
0 comments (0 inline, 0 general)
rhodecode/lib/auth.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
"""
 
    rhodecode.lib.auth
 
    ~~~~~~~~~~~~~~~~~~
 

	
 
    authentication and permission libraries
 

	
 
    :created_on: Apr 4, 2010
 
    :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
 
    :license: GPLv3, see COPYING for more details.
 
"""
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 

	
 
import random
 
import logging
 
import traceback
 
import hashlib
 

	
 
from tempfile import _RandomNameSequence
 
from decorator import decorator
 

	
 
from pylons import config, session, url, request
 
from pylons.controllers.util import abort, redirect
 
from pylons.i18n.translation import _
 

	
 
from rhodecode import __platform__, PLATFORM_WIN, PLATFORM_OTHERS
 

	
 
if __platform__ in PLATFORM_WIN:
 
    from hashlib import sha256
 
if __platform__ in PLATFORM_OTHERS:
 
    import bcrypt
 

	
 
from rhodecode.lib import str2bool, safe_unicode
 
from rhodecode.lib.exceptions import LdapPasswordError, LdapUsernameError
 
from rhodecode.lib.utils import get_repo_slug
 
from rhodecode.lib.auth_ldap import AuthLdap
 

	
 
from rhodecode.model import meta
 
from rhodecode.model.user import UserModel
 
from rhodecode.model.db import Permission, RhodeCodeSettings, User
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
class PasswordGenerator(object):
 
    """This is a simple class for generating password from
 
        different sets of characters
 
        usage:
 
        passwd_gen = PasswordGenerator()
 
        #print 8-letter password containing only big and small letters
 
            of alphabet
 
        print passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
 
    """
 
    ALPHABETS_NUM = r'''1234567890'''
 
    ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
 
    ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
 
    ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
 
    ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
 
        + ALPHABETS_NUM + ALPHABETS_SPECIAL
 
    ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
 
    ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
 
    ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
 
    ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
 

	
 
    def __init__(self, passwd=''):
 
        self.passwd = passwd
 

	
 
    def gen_password(self, len, type):
 
        self.passwd = ''.join([random.choice(type) for _ in xrange(len)])
 
        return self.passwd
 

	
 

	
 
class RhodeCodeCrypto(object):
 

	
 
    @classmethod
 
    def hash_string(cls, str_):
 
        """
 
        Cryptographic function used for password hashing based on pybcrypt
 
        or pycrypto in windows
 

	
 
        :param password: password to hash
 
        """
 
        if __platform__ in PLATFORM_WIN:
 
            return sha256(str_).hexdigest()
 
        elif __platform__ in PLATFORM_OTHERS:
 
            return bcrypt.hashpw(str_, bcrypt.gensalt(10))
 
        else:
 
            raise Exception('Unknown or unsupported platform %s' \
 
                            % __platform__)
 

	
 
    @classmethod
 
    def hash_check(cls, password, hashed):
 
        """
 
        Checks matching password with it's hashed value, runs different
 
        implementation based on platform it runs on
 

	
 
        :param password: password
 
        :param hashed: password in hashed form
 
        """
 

	
 
        if __platform__ in PLATFORM_WIN:
 
            return sha256(password).hexdigest() == hashed
 
        elif __platform__ in PLATFORM_OTHERS:
 
            return bcrypt.hashpw(password, hashed) == hashed
 
        else:
 
            raise Exception('Unknown or unsupported platform %s' \
 
                            % __platform__)
 

	
 

	
 
def get_crypt_password(password):
 
    return RhodeCodeCrypto.hash_string(password)
 

	
 

	
 
def check_password(password, hashed):
 
    return RhodeCodeCrypto.hash_check(password, hashed)
 

	
 
def generate_api_key(str_, salt=None):
 
    """
 
    Generates API KEY from given string
 
    
 
    :param str_:
 
    :param salt:
 
    """
 
    
 
    if salt is None:
 
        salt = _RandomNameSequence().next()
 

	
 
    return hashlib.sha1(str_ + salt).hexdigest()
 

	
 

	
 
def authfunc(environ, username, password):
 
    """
 
    Dummy authentication function used in Mercurial/Git/ and access control,
 

	
 
    :param environ: needed only for using in Basic auth
 
    """
 
    return authenticate(username, password)
 

	
 

	
 
def authenticate(username, password):
 
    """
 
    Authentication function used for access control,
 
    firstly checks for db authentication then if ldap is enabled for ldap
 
    authentication, also creates ldap user if not in database
 

	
 
    :param username: username
 
    :param password: password
 
    """
 

	
 
    user_model = UserModel()
 
    user = User.get_by_username(username)
 

	
 
    log.debug('Authenticating user using RhodeCode account')
 
    if user is not None and not user.ldap_dn:
 
        if user.active:
 
            if user.username == 'default' and user.active:
 
                log.info('user %s authenticated correctly as anonymous user',
 
                         username)
 
                return True
 

	
 
            elif user.username == username and check_password(password,
 
                                                              user.password):
 
                log.info('user %s authenticated correctly', username)
 
                return True
 
        else:
 
            log.warning('user %s is disabled', username)
 

	
 
    else:
 
        log.debug('Regular authentication failed')
 
        user_obj = User.get_by_username(username, case_insensitive=True)
 

	
 
        if user_obj is not None and not user_obj.ldap_dn:
 
            log.debug('this user already exists as non ldap')
 
            return False
 

	
 
        ldap_settings = RhodeCodeSettings.get_ldap_settings()
 
        #======================================================================
 
        # FALLBACK TO LDAP AUTH IF ENABLE
 
        #======================================================================
 
        if str2bool(ldap_settings.get('ldap_active')):
 
            log.debug("Authenticating user using ldap")
 
            kwargs = {
 
                  'server': ldap_settings.get('ldap_host', ''),
 
                  'base_dn': ldap_settings.get('ldap_base_dn', ''),
 
                  'port': ldap_settings.get('ldap_port'),
 
                  'bind_dn': ldap_settings.get('ldap_dn_user'),
 
                  'bind_pass': ldap_settings.get('ldap_dn_pass'),
 
                  'tls_kind': ldap_settings.get('ldap_tls_kind'),
 
                  'tls_reqcert': ldap_settings.get('ldap_tls_reqcert'),
 
                  'ldap_filter': ldap_settings.get('ldap_filter'),
 
                  'search_scope': ldap_settings.get('ldap_search_scope'),
 
                  'attr_login': ldap_settings.get('ldap_attr_login'),
 
                  'ldap_version': 3,
 
                  }
 
            log.debug('Checking for ldap authentication')
 
            try:
 
                aldap = AuthLdap(**kwargs)
 
                (user_dn, ldap_attrs) = aldap.authenticate_ldap(username,
 
                                                                password)
 
                log.debug('Got ldap DN response %s', user_dn)
 

	
 
                get_ldap_attr = lambda k: ldap_attrs.get(ldap_settings\
 
                                                           .get(k), [''])[0]
 

	
 
                user_attrs = {
 
                 'name': safe_unicode(get_ldap_attr('ldap_attr_firstname')),
 
                 'lastname': safe_unicode(get_ldap_attr('ldap_attr_lastname')),
 
                 'email': get_ldap_attr('ldap_attr_email'),
 
                }
 

	
 
                if user_model.create_ldap(username, password, user_dn,
 
                                          user_attrs):
 
                    log.info('created new ldap user %s', username)
 

	
 
                return True
 
            except (LdapUsernameError, LdapPasswordError,):
 
                pass
 
            except (Exception,):
 
                log.error(traceback.format_exc())
 
                pass
 
    return False
 

	
 
def login_container_auth(username):
 
    user = User.get_by_username(username)
 
    if user is None:
 
        user_model = UserModel()
 
        user_attrs = {
 
            'name': username,
 
            'lastname': None,
 
            'email': None,
 
        }
 
        user = user_model.create_for_container_auth(username, user_attrs)
 
        if not user:
 
            return None
 
        log.info('User %s was created by container authentication', username)
 

	
 
    if not user.active:
 
        return None
 

	
 
    user.update_lastlogin()
 
    log.debug('User %s is now logged in by container authentication', 
 
              user.username)
 
    return user
 

	
 
def get_container_username(environ, cfg):
 
def get_container_username(environ, config):
 
    username = None
 

	
 
    if str2bool(config.get('container_auth_enabled', False)):
 
    from paste.httpheaders import REMOTE_USER
 
    from paste.deploy.converters import asbool
 

	
 
    proxy_pass_enabled = asbool(cfg.get('proxypass_auth_enabled', False))
 
    username = REMOTE_USER(environ)
 
    
 
    if not username and proxy_pass_enabled:
 
    if not username and str2bool(config.get('proxypass_auth_enabled', False)):
 
        username = environ.get('HTTP_X_FORWARDED_USER')
 

	
 
    if username and proxy_pass_enabled:
 
    if username:
 
        # Removing realm and domain from username
 
        username = username.partition('@')[0]
 
        username = username.rpartition('\\')[2]
 
        log.debug('Received username %s from container', username)
 

	
 
    return username
 

	
 
class  AuthUser(object):
 
    """
 
    A simple object that handles all attributes of user in RhodeCode
 

	
 
    It does lookup based on API key,given user, or user present in session
 
    Then it fills all required information for such user. It also checks if
 
    anonymous access is enabled and if so, it returns default user as logged
 
    in
 
    """
 

	
 
    def __init__(self, user_id=None, api_key=None, username=None):
 

	
 
        self.user_id = user_id
 
        self.api_key = None
 
        self.username = username
 

	
 
        self.name = ''
 
        self.lastname = ''
 
        self.email = ''
 
        self.is_authenticated = False
 
        self.admin = False
 
        self.permissions = {}
 
        self._api_key = api_key
 
        self.propagate_data()
 

	
 
    def propagate_data(self):
 
        user_model = UserModel()
 
        self.anonymous_user = User.get_by_username('default')
 
        is_user_loaded = False
 
        
 
        # try go get user by api key
 
        if self._api_key and self._api_key != self.anonymous_user.api_key:
 
            log.debug('Auth User lookup by API KEY %s', self._api_key)
 
            is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
 
        # lookup by userid    
 
        elif (self.user_id is not None and 
 
              self.user_id != self.anonymous_user.user_id):
 
            log.debug('Auth User lookup by USER ID %s', self.user_id)
 
            is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
 
        # lookup by username    
 
        elif self.username:
 
            log.debug('Auth User lookup by USER NAME %s', self.username)
 
            dbuser = login_container_auth(self.username)
 
            if dbuser is not None:
 
                for k, v in dbuser.get_dict().items():
 
                    setattr(self, k, v)
 
                self.set_authenticated()
 
                is_user_loaded = True
 

	
 
        if not is_user_loaded:
 
            # if we cannot authenticate user try anonymous
 
            if self.anonymous_user.active is True:
 
                user_model.fill_data(self,user_id=self.anonymous_user.user_id)
 
                # then we set this user is logged in
 
                self.is_authenticated = True
 
            else:
 
                self.user_id = None
 
                self.username = None
 
                self.is_authenticated = False
 

	
 
        if not self.username:
 
            self.username = 'None'
 

	
 
        log.debug('Auth User is now %s', self)
 
        user_model.fill_perms(self)
 

	
 
    @property
 
    def is_admin(self):
 
        return self.admin
 

	
 
    @property
 
    def full_contact(self):
 
        return '%s %s <%s>' % (self.name, self.lastname, self.email)
 

	
 
    def __repr__(self):
 
        return "<AuthUser('id:%s:%s|%s')>" % (self.user_id, self.username,
 
                                              self.is_authenticated)
 

	
 
    def set_authenticated(self, authenticated=True):
 
        if self.user_id != self.anonymous_user.user_id:
 
            self.is_authenticated = authenticated
 

	
 

	
 
def set_available_permissions(config):
 
    """
 
    This function will propagate pylons globals with all available defined
 
    permission given in db. We don't want to check each time from db for new
 
    permissions since adding a new permission also requires application restart
 
    ie. to decorate new views with the newly created permission
 

	
 
    :param config: current pylons config instance
 

	
 
    """
 
    log.info('getting information about all available permissions')
 
    try:
 
        sa = meta.Session()
 
        all_perms = sa.query(Permission).all()
 
    except:
 
        pass
 
    finally:
 
        meta.Session.remove()
 

	
 
    config['available_permissions'] = [x.permission_name for x in all_perms]
 

	
 

	
 
#==============================================================================
 
# CHECK DECORATORS
 
#==============================================================================
 
class LoginRequired(object):
 
    """
 
    Must be logged in to execute this function else
 
    redirect to login page
 

	
 
    :param api_access: if enabled this checks only for valid auth token
 
        and grants access based on valid token
 
    """
 

	
 
    def __init__(self, api_access=False):
 
        self.api_access = api_access
 

	
 
    def __call__(self, func):
 
        return decorator(self.__wrapper, func)
 

	
 
    def __wrapper(self, func, *fargs, **fkwargs):
 
        cls = fargs[0]
 
        user = cls.rhodecode_user
 

	
 
        api_access_ok = False
 
        if self.api_access:
 
            log.debug('Checking API KEY access for %s', cls)
 
            if user.api_key == request.GET.get('api_key'):
 
                api_access_ok = True
 
            else:
 
                log.debug("API KEY token not valid")
 

	
 
        log.debug('Checking if %s is authenticated @ %s', user.username, cls)
 
        if user.is_authenticated or api_access_ok:
 
            log.debug('user %s is authenticated', user.username)
 
            return func(*fargs, **fkwargs)
 
        else:
 
            log.warn('user %s NOT authenticated', user.username)
 
            p = url.current()
 

	
 
            log.debug('redirecting to login page with %s', p)
 
            return redirect(url('login_home', came_from=p))
 

	
 

	
 
class NotAnonymous(object):
 
    """Must be logged in to execute this function else
 
    redirect to login page"""
 

	
 
    def __call__(self, func):
 
        return decorator(self.__wrapper, func)
 

	
 
    def __wrapper(self, func, *fargs, **fkwargs):
 
        cls = fargs[0]
 
        self.user = cls.rhodecode_user
 

	
 
        log.debug('Checking if user is not anonymous @%s', cls)
 

	
 
        anonymous = self.user.username == 'default'
 

	
 
        if anonymous:
 
            p = url.current()
 

	
 
            import rhodecode.lib.helpers as h
 
            h.flash(_('You need to be a registered user to '
 
                      'perform this action'),
 
                    category='warning')
 
            return redirect(url('login_home', came_from=p))
 
        else:
 
            return func(*fargs, **fkwargs)
 

	
 

	
 
class PermsDecorator(object):
 
    """Base class for controller decorators"""
 

	
 
    def __init__(self, *required_perms):
 
        available_perms = config['available_permissions']
 
        for perm in required_perms:
 
            if perm not in available_perms:
 
                raise Exception("'%s' permission is not defined" % perm)
 
        self.required_perms = set(required_perms)
 
        self.user_perms = None
 

	
 
    def __call__(self, func):
 
        return decorator(self.__wrapper, func)
 

	
 
    def __wrapper(self, func, *fargs, **fkwargs):
 
        cls = fargs[0]
 
        self.user = cls.rhodecode_user
 
        self.user_perms = self.user.permissions
 
        log.debug('checking %s permissions %s for %s %s',
 
           self.__class__.__name__, self.required_perms, cls,
 
               self.user)
 

	
 
        if self.check_permissions():
 
            log.debug('Permission granted for %s %s', cls, self.user)
 
            return func(*fargs, **fkwargs)
 

	
 
        else:
 
            log.warning('Permission denied for %s %s', cls, self.user)
 

	
 

	
 
            anonymous = self.user.username == 'default'
 

	
 
            if anonymous:
 
                p = url.current()
 

	
 
                import rhodecode.lib.helpers as h
 
                h.flash(_('You need to be a signed in to '
 
                          'view this page'),
 
                        category='warning')
 
                return redirect(url('login_home', came_from=p))
 

	
 
            else:
 
                # redirect with forbidden ret code
 
                return abort(403)
 

	
 
    def check_permissions(self):
 
        """Dummy function for overriding"""
 
        raise Exception('You have to write this function in child class')
 

	
 

	
 
class HasPermissionAllDecorator(PermsDecorator):
 
    """Checks for access permission for all given predicates. All of them
 
    have to be meet in order to fulfill the request
 
    """
 

	
 
    def check_permissions(self):
 
        if self.required_perms.issubset(self.user_perms.get('global')):
 
            return True
 
        return False
 

	
 

	
 
class HasPermissionAnyDecorator(PermsDecorator):
 
    """Checks for access permission for any of given predicates. In order to
 
    fulfill the request any of predicates must be meet
 
    """
 

	
 
    def check_permissions(self):
 
        if self.required_perms.intersection(self.user_perms.get('global')):
 
            return True
 
        return False
 

	
 

	
 
class HasRepoPermissionAllDecorator(PermsDecorator):
 
    """Checks for access permission for all given predicates for specific
 
    repository. All of them have to be meet in order to fulfill the request
 
    """
 

	
 
    def check_permissions(self):
 
        repo_name = get_repo_slug(request)
 
        try:
 
            user_perms = set([self.user_perms['repositories'][repo_name]])
 
        except KeyError:
 
            return False
 
        if self.required_perms.issubset(user_perms):
 
            return True
 
        return False
 

	
 

	
 
class HasRepoPermissionAnyDecorator(PermsDecorator):
 
    """Checks for access permission for any of given predicates for specific
 
    repository. In order to fulfill the request any of predicates must be meet
 
    """
 

	
 
    def check_permissions(self):
 
        repo_name = get_repo_slug(request)
 

	
 
        try:
 
            user_perms = set([self.user_perms['repositories'][repo_name]])
 
        except KeyError:
 
            return False
 
        if self.required_perms.intersection(user_perms):
 
            return True
 
        return False
 

	
 

	
 
#==============================================================================
 
# CHECK FUNCTIONS
 
#==============================================================================
 
class PermsFunction(object):
 
    """Base function for other check functions"""
 

	
 
    def __init__(self, *perms):
 
        available_perms = config['available_permissions']
 

	
 
        for perm in perms:
 
            if perm not in available_perms:
 
                raise Exception("'%s' permission in not defined" % perm)
 
        self.required_perms = set(perms)
 
        self.user_perms = None
 
        self.granted_for = ''
 
        self.repo_name = None
 

	
 
    def __call__(self, check_Location=''):
 
        user = session.get('rhodecode_user', False)
 
        if not user:
 
            return False
 
        self.user_perms = user.permissions
 
        self.granted_for = user
 
        log.debug('checking %s %s %s', self.__class__.__name__,
 
                  self.required_perms, user)
 

	
 
        if self.check_permissions():
 
            log.debug('Permission granted %s @ %s', self.granted_for,
 
                      check_Location or 'unspecified location')
 
            return True
 

	
 
        else:
 
            log.warning('Permission denied for %s @ %s', self.granted_for,
 
                        check_Location or 'unspecified location')
 
            return False
 

	
 
    def check_permissions(self):
 
        """Dummy function for overriding"""
 
        raise Exception('You have to write this function in child class')
 

	
 

	
 
class HasPermissionAll(PermsFunction):
 
    def check_permissions(self):
 
        if self.required_perms.issubset(self.user_perms.get('global')):
 
            return True
 
        return False
 

	
 

	
 
class HasPermissionAny(PermsFunction):
 
    def check_permissions(self):
 
        if self.required_perms.intersection(self.user_perms.get('global')):
 
            return True
 
        return False
 

	
 

	
 
class HasRepoPermissionAll(PermsFunction):
 

	
 
    def __call__(self, repo_name=None, check_Location=''):
 
        self.repo_name = repo_name
 
        return super(HasRepoPermissionAll, self).__call__(check_Location)
 

	
 
    def check_permissions(self):
 
        if not self.repo_name:
 
            self.repo_name = get_repo_slug(request)
 

	
 
        try:
 
            self.user_perms = set([self.user_perms['reposit'
 
                                                   'ories'][self.repo_name]])
 
        except KeyError:
 
            return False
 
        self.granted_for = self.repo_name
 
        if self.required_perms.issubset(self.user_perms):
 
            return True
 
        return False
 

	
 

	
 
class HasRepoPermissionAny(PermsFunction):
 

	
 
    def __call__(self, repo_name=None, check_Location=''):
 
        self.repo_name = repo_name
 
        return super(HasRepoPermissionAny, self).__call__(check_Location)
 

	
 
    def check_permissions(self):
 
        if not self.repo_name:
 
            self.repo_name = get_repo_slug(request)
 

	
 
        try:
 
            self.user_perms = set([self.user_perms['reposi'
 
                                                   'tories'][self.repo_name]])
 
        except KeyError:
 
            return False
 
        self.granted_for = self.repo_name
 
        if self.required_perms.intersection(self.user_perms):
 
            return True
 
        return False
 

	
 

	
 
#==============================================================================
rhodecode/lib/base.py
Show inline comments
 
"""The base Controller API
 

	
 
Provides the BaseController class for subclassing.
 
"""
 
import logging
 
import time
 
from pylons import config, tmpl_context as c, request, session, url
 
from pylons.controllers import WSGIController
 
from pylons.controllers.util import redirect
 
from pylons.templating import render_mako as render
 
from paste.deploy.converters import asbool
 

	
 
from rhodecode import __version__
 
from rhodecode.lib import str2bool
 
from rhodecode.lib.auth import AuthUser, get_container_username
 
from rhodecode.lib.utils import get_repo_slug
 
from rhodecode.model import meta
 
from rhodecode.model.scm import ScmModel
 
from rhodecode import BACKENDS
 
from rhodecode.model.db import Repository
 

	
 
log = logging.getLogger(__name__)
 

	
 
class BaseController(WSGIController):
 

	
 
    def __before__(self):
 
        c.rhodecode_version = __version__
 
        c.rhodecode_name = config.get('rhodecode_title')
 
        c.use_gravatar = str2bool(config.get('use_gravatar'))
 
        c.ga_code = config.get('rhodecode_ga_code')
 
        c.repo_name = get_repo_slug(request)
 
        c.backends = BACKENDS.keys()
 
        self.cut_off_limit = int(config.get('cut_off_limit'))
 

	
 
        self.sa = meta.Session()
 
        self.scm_model = ScmModel(self.sa)
 

	
 
    def __call__(self, environ, start_response):
 
        """Invoke the Controller"""
 
        # WSGIController.__call__ dispatches to the Controller method
 
        # the request is routed to. This routing information is
 
        # available in environ['pylons.routes_dict']
 
        start = time.time()
 
        try:
 
            # make sure that we update permissions each time we call controller
 
            api_key = request.GET.get('api_key')
 
            user_id = getattr(session.get('rhodecode_user'), 'user_id', None)
 
            if asbool(config.get('container_auth_enabled', False)):
 
                username = get_container_username(environ)
 
            else:
 
                username = None
 
            username = get_container_username(environ, config)
 

	
 
            auth_user = AuthUser(user_id, api_key, username)
 
            self.rhodecode_user = c.rhodecode_user = auth_user
 
            if not self.rhodecode_user.is_authenticated and \
 
                       self.rhodecode_user.user_id is not None:
 
                self.rhodecode_user.set_authenticated(
 
                                        getattr(session.get('rhodecode_user'),
 
                                       'is_authenticated', False))
 
            session['rhodecode_user'] = self.rhodecode_user
 
            session.save()
 
            return WSGIController.__call__(self, environ, start_response)
 
        finally:
 
            log.debug('Request time: %.3fs' % (time.time()-start))
 
            meta.Session.remove()
 

	
 

	
 
class BaseRepoController(BaseController):
 
    """
 
    Base class for controllers responsible for loading all needed data for
 
    repository loaded items are
 

	
 
    c.rhodecode_repo: instance of scm repository
 
    c.rhodecode_db_repo: instance of db
 
    c.repository_followers: number of followers
 
    c.repository_forks: number of forks
 
    """
 

	
 
    def __before__(self):
 
        super(BaseRepoController, self).__before__()
 
        if c.repo_name:
 

	
 
            c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name)
 
            c.rhodecode_repo = c.rhodecode_db_repo.scm_instance
 

	
 
            if c.rhodecode_repo is None:
 
                log.error('%s this repository is present in database but it '
 
                          'cannot be created as an scm instance', c.repo_name)
 

	
 
                redirect(url('home'))
 

	
 
            c.repository_followers = self.scm_model.get_followers(c.repo_name)
 
            c.repository_forks = self.scm_model.get_forks(c.repo_name)
 

	
rhodecode/lib/middleware/simplegit.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
"""
 
    rhodecode.lib.middleware.simplegit
 
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

	
 
    SimpleGit middleware for handling git protocol request (push/clone etc.)
 
    It's implemented with basic auth function
 

	
 
    :created_on: Apr 28, 2010
 
    :author: marcink
 
    :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
 
    :license: GPLv3, see COPYING for more details.
 
"""
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 

	
 
import os
 
import logging
 
import traceback
 

	
 
from dulwich import server as dulserver
 

	
 

	
 
class SimpleGitUploadPackHandler(dulserver.UploadPackHandler):
 

	
 
    def handle(self):
 
        write = lambda x: self.proto.write_sideband(1, x)
 

	
 
        graph_walker = dulserver.ProtocolGraphWalker(self,
 
                                                     self.repo.object_store,
 
                                                     self.repo.get_peeled)
 
        objects_iter = self.repo.fetch_objects(
 
          graph_walker.determine_wants, graph_walker, self.progress,
 
          get_tagged=self.get_tagged)
 

	
 
        # Do they want any objects?
 
        if objects_iter is None or len(objects_iter) == 0:
 
            return
 

	
 
        self.progress("counting objects: %d, done.\n" % len(objects_iter))
 
        dulserver.write_pack_objects(dulserver.ProtocolFile(None, write),
 
                                  objects_iter, len(objects_iter))
 
        messages = []
 
        messages.append('thank you for using rhodecode')
 

	
 
        for msg in messages:
 
            self.progress(msg + "\n")
 
        # we are done
 
        self.proto.write("0000")
 

	
 
dulserver.DEFAULT_HANDLERS = {
 
  'git-upload-pack': SimpleGitUploadPackHandler,
 
  'git-receive-pack': dulserver.ReceivePackHandler,
 
}
 

	
 
from dulwich.repo import Repo
 
from dulwich.web import HTTPGitApplication
 

	
 
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, get_container_username
 
from rhodecode.lib.utils import invalidate_cache, is_valid_repo
 
from rhodecode.model.db import User
 

	
 
from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
def is_git(environ):
 
    """Returns True if request's target is git server.
 
    ``HTTP_USER_AGENT`` would then have git client version given.
 

	
 
    :param environ:
 
    """
 
    http_user_agent = environ.get('HTTP_USER_AGENT')
 
    if http_user_agent and http_user_agent.startswith('git'):
 
        return True
 
    return False
 

	
 

	
 
class SimpleGit(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)
 

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

	
 
        proxy_key = 'HTTP_X_REAL_IP'
 
        def_key = 'REMOTE_ADDR'
 
        ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
 
        username = None
 
        # skip passing error to error controller
 
        environ['pylons.status_code_redirect'] = True
 

	
 
        #======================================================================
 
        # EXTRACT REPOSITORY NAME FROM ENV
 
        #======================================================================
 
        try:
 
            repo_name = self.__get_repository(environ)
 
            log.debug('Extracted repo name is %s' % repo_name)
 
        except:
 
            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)
 

	
 
            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 get_container_username(environ, self.config):
 
                # Attempting to retrieve username from the container
 
                username = get_container_username(environ, self.config)
 

	
 
                # If not authenticated by the container, running basic auth
 
                if not username:
 
                    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)
 
                        username = result
 
                    else:
 
                        return result.wsgi_application(environ, start_response)
 

	
 
                #==============================================================
 
                # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME FROM
 
                # BASIC AUTH
 
                # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
 
                #==============================================================
 

	
 
                if action in ['pull', 'push']:
 
                    username = get_container_username(environ, self.config)
 
                    try:
 
                        user = self.__get_user(username)
 
                        if user is None or not user.active:
 
                            return HTTPForbidden()(environ, start_response)                        
 
                        username = user.username
 
                    except:
 
                        log.error(traceback.format_exc())
 
                        return HTTPInternalServerError()(environ,
 
                                                         start_response)
 

	
 
                    #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}
 

	
 
        #===================================================================
 
        # GIT REQUEST HANDLING
 
        #===================================================================
 

	
 
        repo_path = safe_str(os.path.join(self.basepath, repo_name))
 
        log.debug('Repository path is %s' % repo_path)
 

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

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

	
 
            app = self.__make_app(repo_name, repo_path)
 
            return app(environ, start_response)
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            return HTTPInternalServerError()(environ, start_response)
 

	
 
    def __make_app(self, repo_name, repo_path):
 
        """
 
        Make an wsgi application using dulserver
 
        
 
        :param repo_name: name of the repository
 
        :param repo_path: full path to the repository
 
        """
 

	
 
        _d = {'/' + repo_name: Repo(repo_path)}
 
        backend = dulserver.DictBackend(_d)
 
        gitserve = HTTPGitApplication(backend)
 

	
 
        return gitserve
 

	
 
    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
 
        repo_name = repo_name.split('/')[0]
 
        return repo_name
 

	
 
    def __get_user(self, username):
 
        return User.get_by_username(username)
 

	
 
    def __get_action(self, environ):
 
        """Maps git request commands into a pull or push command.
 

	
 
        :param environ:
 
        """
 
        service = environ['QUERY_STRING'].split('=')
 
        if len(service) > 1:
 
            service_cmd = service[1]
 
            mapping = {'git-receive-pack': 'push',
 
                       'git-upload-pack': 'pull',
 
                       }
 

	
 
            return mapping.get(service_cmd,
 
                               service_cmd if service_cmd else 'other')
 
        else:
 
            return 'other'
 

	
 
    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('get_repo_cached_%s' % repo_name)
 

	
rhodecode/lib/middleware/simplehg.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
"""
 
    rhodecode.lib.middleware.simplehg
 
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

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

	
 
    :created_on: Apr 28, 2010
 
    :author: marcink
 
    :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
 
    :license: GPLv3, see COPYING for more details.
 
"""
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 

	
 
import os
 
import logging
 
import traceback
 

	
 
from mercurial.error import RepoError
 
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, get_container_username
 
from rhodecode.lib.utils import make_ui, invalidate_cache, \
 
    is_valid_repo, ui_sections
 
from rhodecode.model.db import User
 

	
 
from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
 

	
 
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``.
 
    """
 
    http_accept = environ.get('HTTP_ACCEPT')
 
    if http_accept and http_accept.startswith('application/mercurial'):
 
        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'
 

	
 
    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'
 
        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
 

	
 
        #======================================================================
 
        # EXTRACT REPOSITORY NAME FROM ENV
 
        #======================================================================
 
        try:
 
            repo_name = environ['REPO_NAME'] = self.__get_repository(environ)
 
            log.debug('Extracted repo name is %s' % repo_name)
 
        except:
 
            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)
 

	
 
            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 get_container_username(environ, self.config):
 
                # Attempting to retrieve username from the container
 
                username = get_container_username(environ, self.config)
 

	
 
                # If not authenticated by the container, running basic auth
 
                if not username:
 
                    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)
 
                        username = result
 
                    else:
 
                        return result.wsgi_application(environ, start_response)
 

	
 
                #==============================================================
 
                # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME FROM
 
                # BASIC AUTH
 
                # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
 
                #==============================================================
 

	
 
                if action in ['pull', 'push']:
 
                    username = get_container_username(environ, self.config)
 
                    try:
 
                        user = self.__get_user(username)
 
                        if user is None or not user.active:
 
                            return HTTPForbidden()(environ, start_response)
 
                        username = user.username
 
                    except:
 
                        log.error(traceback.format_exc())
 
                        return HTTPInternalServerError()(environ,
 
                                                         start_response)
 

	
 
                    #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
 
        #======================================================================
 

	
 
        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 is_valid_repo(repo_name, self.basepath) is False:
 
            return HTTPNotFound()(environ, start_response)
 

	
 
        try:
 
            #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)
 
        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
 
        """
 
        return hgweb_mod.hgweb(repo_name, name=repo_name, baseui=baseui)
 

	
 

	
 
    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_user(self, username):
 
        return User.get_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',
 
                   'changegroupsubset': 'pull',
 
                   'stream_out': 'pull',
 
                   'listkeys': 'pull',
 
                   'unbundle': 'push',
 
                   'pushkey': 'push', }
 
        for qry in environ['QUERY_STRING'].split('&'):
 
            if qry.startswith('cmd'):
 
                cmd = qry.split('=')[-1]
 
                if cmd in mapping:
 
                    return mapping[cmd]
 
                else:
 
                    return 'pull'
 

	
 
    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('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
 
        """
 

	
 
        hgrc = os.path.join(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():
 
            baseui.setconfig('rhodecode_extras', k, v)
 

	
 
        repoui = make_ui('file', hgrc, False)
 

	
 
        if repoui:
 
            #overwrite our ui instance with the section from hgrc file
 
            for section in ui_sections:
 
                for k, v in repoui.configitems(section):
 
                    baseui.setconfig(section, k, v)
 

	
0 comments (0 inline, 0 general)