Changeset - 3790279d2538
[Not reviewed]
beta
0 1 0
Marcin Kuzminski - 15 years ago 2011-02-09 18:54:20
marcin@python-works.com
#56 added propagation of permission from group
1 file changed with 43 insertions and 17 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) 2010 by marcink.
 
    :license: LICENSE_NAME, see LICENSE_FILE 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; version 2
 
# of the License or (at your opinion) any later version of the license.
 
# 
 
# 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, write to the Free Software
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 
# MA  02110-1301, USA.
 

	
 
import bcrypt
 
import random
 
import logging
 
import traceback
 

	
 
from decorator import decorator
 

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

	
 
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 User, RepoToPerm, Repository, Permission, \
 
    UserToPerm
 
    UserToPerm, UsersGroupToPerm, UsersGroupMember
 

	
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
PERM_WEIGHTS = {'repository.none':0,
 
                'repository.read':1,
 
                'repository.write':3,
 
                'repository.admin':3}
 

	
 

	
 
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'''#[0]
 
    ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''#[1]
 
    ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''#[2]
 
    ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''    #[3]
 
    ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM + ALPHABETS_SPECIAL#[4]
 
    ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM#[5]
 
    ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
 
    ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM#[6]
 
    ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM#[7]
 

	
 
    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
 

	
 

	
 
def get_crypt_password(password):
 
    """Cryptographic function used for password hashing based on sha1
 
    """Cryptographic function used for password hashing based on pybcrypt
 
    
 
    :param password: password to hash
 
    """
 
    return bcrypt.hashpw(password, bcrypt.gensalt(10))
 

	
 
def check_password(password, hashed):
 
    return bcrypt.hashpw(password, hashed) == hashed
 

	
 
def authfunc(environ, username, password):
 
    """
 
    Dummy authentication function used in Mercurial/Git/ and access control,
 
    """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,
 
    """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_model.get_by_username(username, cache=False)
 

	
 
    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_model.get_by_username(username, cache=False,
 
                                            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
 

	
 
        from rhodecode.model.settings import SettingsModel
 
        ldap_settings = SettingsModel().get_ldap_settings()
 

	
 
        #======================================================================
 
        # FALLBACK TO LDAP AUTH IN ENABLE                
 
        # FALLBACK TO LDAP AUTH IF ENABLE                
 
        #======================================================================
 
        if ldap_settings.get('ldap_active', False):
 
            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'),
 
                  'use_ldaps':ldap_settings.get('ldap_ldaps'),
 
                  '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)
 

	
 
                user_attrs = {
 
                    'name'     : ldap_attrs[ldap_settings.get('ldap_attr_firstname')][0],
 
                    'lastname' : ldap_attrs[ldap_settings.get('ldap_attr_lastname')][0],
 
                    'email'    : ldap_attrs[ldap_settings.get('ldap_attr_email')][0],
 
                    }
 

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

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

	
 
class  AuthUser(object):
 
    """A simple object that handles a mercurial username for authentication
 
    """
 
    A simple object that handles a mercurial username for authentication
 
    """
 

	
 
    def __init__(self):
 
        self.username = 'None'
 
        self.name = ''
 
        self.lastname = ''
 
        self.email = ''
 
        self.user_id = None
 
        self.is_authenticated = False
 
        self.is_admin = False
 
        self.permissions = {}
 

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

	
 
def set_available_permissions(config):
 
    """This function will propagate pylons globals with all available defined
 
    permission given in db. We don't wannt to check each time from db for new 
 
    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]
 

	
 
def set_base_path(config):
 
    config['base_path'] = config['pylons.app_globals'].base_path
 

	
 

	
 
def fill_perms(user):
 
    """Fills user permission attribute with permissions taken from database
 
    works for permissions given for repositories, and for permissions that
 
    as part of beeing group member
 
    
 
    :param user:
 
    
 
    :param user: user instance to fill his perms
 
    """
 

	
 
    sa = meta.Session()
 
    user.permissions['repositories'] = {}
 
    user.permissions['global'] = set()
 

	
 
    #===========================================================================
 
    # fetch default permissions
 
    #===========================================================================
 
    default_user = UserModel().get_by_username('default', cache=True)
 

	
 
    default_perms = sa.query(RepoToPerm, Repository, Permission)\
 
        .join((Repository, RepoToPerm.repository_id == Repository.repo_id))\
 
        .join((Permission, RepoToPerm.permission_id == Permission.permission_id))\
 
        .filter(RepoToPerm.user == default_user).all()
 

	
 
    if user.is_admin:
 
        #=======================================================================
 
        # #admin have all default rights set to admin        
 
        #=======================================================================
 
        user.permissions['global'].add('hg.admin')
 

	
 
        for perm in default_perms:
 
            p = 'repository.admin'
 
            user.permissions['repositories'][perm.RepoToPerm.repository.repo_name] = p
 

	
 
    else:
 
        #=======================================================================
 
        # set default permissions
 
        #=======================================================================
 

	
 
        #default global
 
        default_global_perms = sa.query(UserToPerm)\
 
            .filter(UserToPerm.user == sa.query(User)\
 
                   .filter(User.username == 'default').one())
 

	
 
        for perm in default_global_perms:
 
            user.permissions['global'].add(perm.permission.permission_name)
 

	
 
        #default repositories
 
        #default for repositories
 
        for perm in default_perms:
 
            if perm.Repository.private and not perm.Repository.user_id == user.user_id:
 
                #disable defaults for private repos,
 
                p = 'repository.none'
 
            elif perm.Repository.user_id == user.user_id:
 
                #set admin if owner
 
                p = 'repository.admin'
 
            else:
 
                p = perm.Permission.permission_name
 

	
 
            user.permissions['repositories'][perm.RepoToPerm.repository.repo_name] = p
 

	
 
        #=======================================================================
 
        # #overwrite default with user permissions if any
 
        # overwrite default with user permissions if any
 
        #=======================================================================
 
        user_perms = sa.query(RepoToPerm, Permission, Repository)\
 
            .join((Repository, RepoToPerm.repository_id == Repository.repo_id))\
 
            .join((Permission, RepoToPerm.permission_id == Permission.permission_id))\
 
            .filter(RepoToPerm.user_id == user.user_id).all()
 

	
 
        for perm in user_perms:
 
            if perm.Repository.user_id == user.user_id:#set admin if owner
 
                p = 'repository.admin'
 
            else:
 
                p = perm.Permission.permission_name
 
            user.permissions['repositories'][perm.RepoToPerm.repository.repo_name] = p
 

	
 

	
 
        #=======================================================================
 
        # check if user is part of groups for this repository and fill in 
 
        # (or replace with higher) permissions
 
        #=======================================================================
 
        user_perms_from_users_groups = sa.query(UsersGroupToPerm, Permission, Repository,)\
 
            .join((Repository, UsersGroupToPerm.repository_id == Repository.repo_id))\
 
            .join((Permission, UsersGroupToPerm.permission_id == Permission.permission_id))\
 
            .join((UsersGroupMember, UsersGroupToPerm.users_group_id == UsersGroupMember.users_group_id))\
 
            .filter(UsersGroupMember.user_id == user.user_id).all()
 

	
 
        for perm in user_perms_from_users_groups:
 
            p = perm.Permission.permission_name
 
            cur_perm = user.permissions['repositories'][perm.UsersGroupToPerm.repository.repo_name]
 
            #overwrite permission only if it's greater than permission given from other sources
 
            if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]:
 
                user.permissions['repositories'][perm.UsersGroupToPerm.repository.repo_name] = p
 

	
 
    meta.Session.remove()
 
    return user
 

	
 
def get_user(session):
 
    """
 
    Gets user from session, and wraps permissions into user
 
    """Gets user from session, and wraps permissions into user
 
    
 
    :param session:
 
    """
 
    user = session.get('rhodecode_user', AuthUser())
 
    #if the user is not logged in we check for anonymous access
 
    #if user is logged and it's a default user check if we still have anonymous
 
    #access enabled
 
    if user.user_id is None or user.username == 'default':
 
        anonymous_user = UserModel().get_by_username('default', cache=True)
 
        if anonymous_user.active is True:
 
            #then we set this user is logged in
 
            user.is_authenticated = True
 
            user.user_id = anonymous_user.user_id
 
        else:
 
            user.is_authenticated = False
 

	
 
    if user.is_authenticated:
 
        user = UserModel().fill_data(user)
 

	
 
    user = fill_perms(user)
 
    session['rhodecode_user'] = user
 
    session.save()
 
    return user
 

	
 
#===============================================================================
 
# CHECK DECORATORS
 
#===============================================================================
 
class LoginRequired(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):
 
        user = session.get('rhodecode_user', AuthUser())
 
        log.debug('Checking login required for user:%s', user.username)
 
        if user.is_authenticated:
 
            log.debug('user %s is authenticated', user.username)
 
            return func(*fargs, **fkwargs)
 
        else:
 
            log.warn('user %s not authenticated', user.username)
 

	
 
            p = ''
 
            if request.environ.get('SCRIPT_NAME') != '/':
 
                p += request.environ.get('SCRIPT_NAME')
 

	
 
            p += request.environ.get('PATH_INFO')
 
            if request.environ.get('QUERY_STRING'):
0 comments (0 inline, 0 general)