Changeset - 56c2850a5b5f
[Not reviewed]
beta
0 5 0
Marcin Kuzminski - 15 years ago 2010-11-25 22:58:28
marcin@python-works.com
ldap auth rewrite, moved split authfunc into two functions,
made ldap case insensitive for uids
added some extra debug messages for ldap, and auth function
added some docs for utils
5 files changed with 70 insertions and 49 deletions:
0 comments (0 inline, 0 general)
rhodecode/controllers/login.py
Show inline comments
 
#!/usr/bin/env python
 
# encoding: utf-8
 
# login controller for pylons
 
# Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
 
# 
 
# 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.
 

	
 
"""
 
Created on April 22, 2010
 
login controller for pylons
 
@author: marcink
 
"""
 
from formencode import htmlfill
 
from pylons import request, response, session, tmpl_context as c, url
 
from pylons.controllers.util import abort, redirect
 
from rhodecode.lib.auth import AuthUser, HasPermissionAnyDecorator
 
from rhodecode.lib.base import BaseController, render
 
import rhodecode.lib.helpers as h
 
from pylons.i18n.translation import _
 
from rhodecode.model.forms import LoginForm, RegisterForm, PasswordResetForm
 
from rhodecode.model.user import UserModel
 
import formencode
 
import logging
 

	
 
log = logging.getLogger(__name__)
 

	
 
class LoginController(BaseController):
 

	
 
    def __before__(self):
 
        super(LoginController, self).__before__()
 

	
 
    def index(self):
 
        #redirect if already logged in
 
        c.came_from = request.GET.get('came_from', None)
 

	
 
        if c.rhodecode_user.is_authenticated \
 
                            and c.rhodecode_user.username != 'default':
 

	
 
            return redirect(url('home'))
 

	
 
        if request.POST:
 
            #import Login Form validator class
 
            login_form = LoginForm()
 
            try:
 
                c.form_result = login_form.to_python(dict(request.POST))
 
                username = c.form_result['username']
 
                user = UserModel().get_by_username(username)
 
                user = UserModel().get_by_username(username, case_insensitive=True)
 
                auth_user = AuthUser()
 
                auth_user.username = user.username
 
                auth_user.is_authenticated = True
 
                auth_user.is_admin = user.admin
 
                auth_user.user_id = user.user_id
 
                auth_user.name = user.name
 
                auth_user.lastname = user.lastname
 
                session['rhodecode_user'] = auth_user
 
                session.save()
 
                log.info('user %s is now authenticated', username)
 

	
 
                user.update_lastlogin()
 

	
 
                if c.came_from:
 
                    return redirect(c.came_from)
 
                else:
 
                    return redirect(url('home'))
 

	
 
            except formencode.Invalid, errors:
 
                return htmlfill.render(
 
                    render('/login.html'),
 
                    defaults=errors.value,
 
                    errors=errors.error_dict or {},
 
                    prefix_error=False,
 
                    encoding="UTF-8")
 

	
 
        return render('/login.html')
 

	
 
    @HasPermissionAnyDecorator('hg.admin', 'hg.register.auto_activate',
 
                               'hg.register.manual_activate')
 
    def register(self):
 
        user_model = UserModel()
 
        c.auto_active = False
 
        for perm in user_model.get_by_username('default', cache=False).user_perms:
 
            if perm.permission.permission_name == 'hg.register.auto_activate':
 
                c.auto_active = True
 
                break
 

	
 
        if request.POST:
 

	
 
            register_form = RegisterForm()()
 
            try:
 
                form_result = register_form.to_python(dict(request.POST))
 
                form_result['active'] = c.auto_active
 
                user_model.create_registration(form_result)
 
                h.flash(_('You have successfully registered into rhodecode'),
 
                            category='success')
 
                return redirect(url('login_home'))
 

	
 
            except formencode.Invalid, errors:
 
                return htmlfill.render(
 
                    render('/register.html'),
 
                    defaults=errors.value,
 
                    errors=errors.error_dict or {},
 
                    prefix_error=False,
 
                    encoding="UTF-8")
 

	
 
        return render('/register.html')
 

	
 
    def password_reset(self):
 
        user_model = UserModel()
 
        if request.POST:
 

	
 
            password_reset_form = PasswordResetForm()()
 
            try:
 
                form_result = password_reset_form.to_python(dict(request.POST))
 
                user_model.reset_password(form_result)
 
                h.flash(_('Your new password was sent'),
 
                            category='success')
 
                return redirect(url('login_home'))
 

	
 
            except formencode.Invalid, errors:
 
                return htmlfill.render(
 
                    render('/password_reset.html'),
 
                    defaults=errors.value,
 
                    errors=errors.error_dict or {},
 
                    prefix_error=False,
 
                    encoding="UTF-8")
 

	
 
        return render('/password_reset.html')
 

	
 
    def logout(self):
 
        session['rhodecode_user'] = AuthUser()
 
        session.save()
 
        log.info('Logging out and setting user as Empty')
 
        redirect(url('home'))
rhodecode/lib/auth.py
Show inline comments
 
#!/usr/bin/env python
 
# encoding: utf-8
 
# authentication and permission libraries
 
# Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
 
#
 
# 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.
 
"""
 
Created on April 4, 2010
 

	
 
@author: marcink
 
"""
 
from pylons import config, session, url, request
 
from pylons.controllers.util import abort, redirect
 
from rhodecode.lib.exceptions import *
 
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.caching_query import FromCache
 
from rhodecode.model.db import User, RepoToPerm, Repository, Permission, \
 
    UserToPerm
 
import bcrypt
 
from decorator import decorator
 
import logging
 
import random
 
import traceback
 

	
 
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'''#[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
 
    :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):
 
    """
 
    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,
 
    firstly checks for db authentication then if ldap is enabled for ldap
 
    authentication, also creates ldap user if not in database
 
    
 
    :param environ: needed only for using in Basic auth, can be None
 
    :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 user.is_ldap is False:
 
        if user.active:
 

	
 
            if user.username == 'default' and user.active:
 
                log.info('user %s authenticated correctly', username)
 
                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.error('user %s is disabled', username)
 

	
 
            log.warning('user %s is disabled', username)
 

	
 
    else:
 

	
 
        #since ldap is searching in case insensitive check if this user is still
 
        #not in our system
 
        username = username.lower()
 
        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 user_obj.is_ldap is False:
 
            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                
 
        #======================================================================
 
        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'),
 
                  'ldap_version':3,
 
                  }
 
            log.debug('Checking for ldap authentication')
 
            try:
 
                aldap = AuthLdap(**kwargs)
 
                res = aldap.authenticate_ldap(username, password)
 
                log.debug('Got ldap response %s', res)
 

	
 
                authenticated = res[1]['uid'][0] == username
 

	
 
                if authenticated and user_model.create_ldap(username, password):
 
                if user_model.create_ldap(username, password):
 
                    log.info('created new ldap user')
 

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

	
 
class  AuthUser(object):
 
    """
 
    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 
 
    permissions since adding a new permission also requires application restart
 
    ie. to decorate new views with the newly created permission
 
    :param config:
 
    """
 
    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
 
    :param user:
 
    """
 

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

	
rhodecode/lib/utils.py
Show inline comments
 
@@ -35,192 +35,199 @@ from vcs.backends.base import BaseChange
 
from paste.script import command
 
import ConfigParser
 
from vcs.utils.lazy import LazyProperty
 
import traceback
 
import datetime
 
import logging
 
import os
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
def get_repo_slug(request):
 
    return request.environ['pylons.routes_dict'].get('repo_name')
 

	
 
def action_logger(user, action, repo, ipaddr='', sa=None):
 
    """
 
    Action logger for various actions made by users
 
    
 
    :param user: user that made this action, can be a unique username string or
 
        object containing user_id attribute
 
    :param action: action to log, should be on of predefined unique actions for
 
        easy translations
 
    :param repo: string name of repository or object containing repo_id,
 
        that action was made on
 
    :param ipaddr: optional ip address from what the action was made
 
    :param sa: optional sqlalchemy session
 
    
 
    """
 

	
 
    if not sa:
 
        sa = meta.Session()
 

	
 
    try:
 
        um = UserModel()
 
        if hasattr(user, 'user_id'):
 
            user_obj = user
 
        elif isinstance(user, basestring):
 
            user_obj = um.get_by_username(user, cache=False)
 
        else:
 
            raise Exception('You have to provide user object or username')
 

	
 

	
 
        rm = RepoModel()
 
        if hasattr(repo, 'repo_id'):
 
            repo_obj = rm.get(repo.repo_id, cache=False)
 
            repo_name = repo_obj.repo_name
 
        elif  isinstance(repo, basestring):
 
            repo_name = repo.lstrip('/')
 
            repo_obj = rm.get_by_repo_name(repo_name, cache=False)
 
        else:
 
            raise Exception('You have to provide repository to action logger')
 

	
 

	
 
        user_log = UserLog()
 
        user_log.user_id = user_obj.user_id
 
        user_log.action = action
 

	
 
        user_log.repository_id = repo_obj.repo_id
 
        user_log.repository_name = repo_name
 

	
 
        user_log.action_date = datetime.datetime.now()
 
        user_log.user_ip = ipaddr
 
        sa.add(user_log)
 
        sa.commit()
 

	
 
        log.info('Adding user %s, action %s on %s', user_obj, action, repo)
 
    except:
 
        log.error(traceback.format_exc())
 
        sa.rollback()
 

	
 
def get_repos(path, recursive=False, initial=False):
 
    """
 
    Scans given path for repos and return (name,(type,path)) tuple 
 
    :param prefix:
 
    :param path:
 
    :param recursive:
 
    :param initial:
 
    """
 
    from vcs.utils.helpers import get_scm
 
    from vcs.exceptions import VCSError
 

	
 
    try:
 
        scm = get_scm(path)
 
    except:
 
        pass
 
    else:
 
        raise Exception('The given path %s should not be a repository got %s',
 
                        path, scm)
 

	
 
    for dirpath in os.listdir(path):
 
        try:
 
            yield dirpath, get_scm(os.path.join(path, dirpath))
 
        except VCSError:
 
            pass
 

	
 
def check_repo_fast(repo_name, base_path):
 
    """
 
    Check given path for existance of directory
 
    :param repo_name:
 
    :param base_path:
 
    
 
    :return False: if this directory is present
 
    """
 
    if os.path.isdir(os.path.join(base_path, repo_name)):return False
 
    return True
 

	
 
def check_repo(repo_name, base_path, verify=True):
 

	
 
    repo_path = os.path.join(base_path, repo_name)
 

	
 
    try:
 
        if not check_repo_fast(repo_name, base_path):
 
            return False
 
        r = hg.repository(ui.ui(), repo_path)
 
        if verify:
 
            hg.verify(r)
 
        #here we hnow that repo exists it was verified
 
        log.info('%s repo is already created', repo_name)
 
        return False
 
    except RepoError:
 
        #it means that there is no valid repo there...
 
        log.info('%s repo is free for creation', repo_name)
 
        return True
 

	
 
def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
 
    while True:
 
        ok = raw_input(prompt)
 
        if ok in ('y', 'ye', 'yes'): return True
 
        if ok in ('n', 'no', 'nop', 'nope'): return False
 
        retries = retries - 1
 
        if retries < 0: raise IOError
 
        print complaint
 

	
 
#propagated from mercurial documentation
 
ui_sections = ['alias', 'auth',
 
                'decode/encode', 'defaults',
 
                'diff', 'email',
 
                'extensions', 'format',
 
                'merge-patterns', 'merge-tools',
 
                'hooks', 'http_proxy',
 
                'smtp', 'patch',
 
                'paths', 'profiling',
 
                'server', 'trusted',
 
                'ui', 'web', ]
 

	
 
def make_ui(read_from='file', path=None, checkpaths=True):
 
    """
 
    A function that will read python rc files or database
 
    and make an mercurial ui object from read options
 
    
 
    :param path: path to mercurial config file
 
    :param checkpaths: check the path
 
    :param read_from: read from 'file' or 'db'
 
    """
 

	
 
    baseui = ui.ui()
 

	
 
    #clean the baseui object
 
    baseui._ocfg = config.config()
 
    baseui._ucfg = config.config()
 
    baseui._tcfg = config.config()
 

	
 
    if read_from == 'file':
 
        if not os.path.isfile(path):
 
            log.warning('Unable to read config file %s' % path)
 
            return False
 
        log.debug('reading hgrc from %s', path)
 
        cfg = config.config()
 
        cfg.read(path)
 
        for section in ui_sections:
 
            for k, v in cfg.items(section):
 
                log.debug('settings ui from file[%s]%s:%s', section, k, v)
 
                baseui.setconfig(section, k, v)
 

	
 

	
 
    elif read_from == 'db':
 
        sa = meta.Session()
 
        ret = sa.query(RhodeCodeUi)\
 
            .options(FromCache("sql_cache_short",
 
                               "get_hg_ui_settings")).all()
 
        meta.Session.remove()
 
        hg_ui = ret
 
        for ui_ in hg_ui:
 
            if ui_.ui_active:
 
                log.debug('settings ui from db[%s]%s:%s', ui_.ui_section, ui_.ui_key, ui_.ui_value)
 
                baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
 
    return baseui
 

	
 

	
 
def set_rhodecode_config(config):
 
    """
 
    Updates pylons config with new settings from database
 
    :param config:
 
    """
 
    from rhodecode.model.settings import SettingsModel
 
    hgsettings = SettingsModel().get_app_settings()
 

	
 
    for k, v in hgsettings.items():
 
        config[k] = v
rhodecode/model/forms.py
Show inline comments
 
""" this is forms validation classes
 
http://formencode.org/module-formencode.validators.html
 
for list off all availible validators
 

	
 
we can create our own validators
 

	
 
The table below outlines the options which can be used in a schema in addition to the validators themselves
 
pre_validators          []     These validators will be applied before the schema
 
chained_validators      []     These validators will be applied after the schema
 
allow_extra_fields      False     If True, then it is not an error when keys that aren't associated with a validator are present
 
filter_extra_fields     False     If True, then keys that aren't associated with a validator are removed
 
if_key_missing          NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
 
ignore_key_missing      False     If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already    
 
  
 
  
 
<name> = formencode.validators.<name of validator>
 
<name> must equal form name
 
list=[1,2,3,4,5]
 
for SELECT use formencode.All(OneOf(list), Int())
 
    
 
"""
 
import os
 
import re
 
import logging
 

	
 
import formencode
 
from formencode import All
 
from formencode.validators import UnicodeString, OneOf, Int, Number, Regex, \
 
    Email, Bool, StringBoolean
 
from pylons import session
 

	
 
from pylons.i18n.translation import _
 
from rhodecode.lib.auth import authfunc, get_crypt_password
 

	
 
import rhodecode.lib.helpers as h
 
from rhodecode.lib.auth import authenticate, get_crypt_password
 
from rhodecode.lib.exceptions import LdapImportError
 
from rhodecode.model import meta
 
from rhodecode.model.user import UserModel
 
from rhodecode.model.repo import RepoModel
 
from rhodecode.model.db import User
 
from rhodecode import BACKENDS
 

	
 
from webhelpers.pylonslib.secure_form import authentication_token
 
from rhodecode import BACKENDS
 
import formencode
 
import logging
 
import os
 
import re
 
import rhodecode.lib.helpers as h
 

	
 
log = logging.getLogger(__name__)
 

	
 
#this is needed to translate the messages using _() in validators
 
class State_obj(object):
 
    _ = staticmethod(_)
 

	
 
#===============================================================================
 
# VALIDATORS
 
#===============================================================================
 
class ValidAuthToken(formencode.validators.FancyValidator):
 
    messages = {'invalid_token':_('Token mismatch')}
 

	
 
    def validate_python(self, value, state):
 

	
 
        if value != authentication_token():
 
            raise formencode.Invalid(self.message('invalid_token', state,
 
                                            search_number=value), value, state)
 

	
 
def ValidUsername(edit, old_data):
 
    class _ValidUsername(formencode.validators.FancyValidator):
 

	
 
        def validate_python(self, value, state):
 
            if value in ['default', 'new_user']:
 
                raise formencode.Invalid(_('Invalid username'), value, state)
 
            #check if user is unique
 
            old_un = None
 
            if edit:
 
                old_un = UserModel().get(old_data.get('user_id')).username
 

	
 
            if old_un != value or not edit:
 
                if UserModel().get_by_username(value, cache=False,
 
                                               case_insensitive=True):
 
                    raise formencode.Invalid(_('This username already exists') ,
 
                                             value, state)
 

	
 

	
 
            if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_]+$', value) is None:
 
                raise formencode.Invalid(_('Username may only contain '
 
                                           'alphanumeric characters underscores '
 
                                           'or dashes and must begin with '
 
                                           'alphanumeric character'),
 
                                      value, state)
 

	
 

	
 

	
 
    return _ValidUsername
 

	
 
class ValidPassword(formencode.validators.FancyValidator):
 

	
 
    def to_python(self, value, state):
 

	
 
        if value:
 

	
 
            if value.get('password'):
 
                try:
 
                    value['password'] = get_crypt_password(value['password'])
 
                except UnicodeEncodeError:
 
                    e_dict = {'password':_('Invalid characters in password')}
 
                    raise formencode.Invalid('', value, state, error_dict=e_dict)
 

	
 
            if value.get('password_confirmation'):
 
                try:
 
                    value['password_confirmation'] = \
 
                        get_crypt_password(value['password_confirmation'])
 
                except UnicodeEncodeError:
 
                    e_dict = {'password_confirmation':_('Invalid characters in password')}
 
                    raise formencode.Invalid('', value, state, error_dict=e_dict)
 

	
 
            if value.get('new_password'):
 
                try:
 
                    value['new_password'] = \
 
                        get_crypt_password(value['new_password'])
 
                except UnicodeEncodeError:
 
                    e_dict = {'new_password':_('Invalid characters in password')}
 
                    raise formencode.Invalid('', value, state, error_dict=e_dict)
 

	
 
            return value
 

	
 
class ValidPasswordsMatch(formencode.validators.FancyValidator):
 

	
 
    def validate_python(self, value, state):
 

	
 
        if value['password'] != value['password_confirmation']:
 
            e_dict = {'password_confirmation':
 
                   _('Password do not match')}
 
            raise formencode.Invalid('', value, state, error_dict=e_dict)
 

	
 
class ValidAuth(formencode.validators.FancyValidator):
 
    messages = {
 
            'invalid_password':_('invalid password'),
 
            'invalid_login':_('invalid user name'),
 
            'disabled_account':_('Your account is disabled')
 

	
 
            }
 
    #error mapping
 
    e_dict = {'username':messages['invalid_login'],
 
              'password':messages['invalid_password']}
 
    e_dict_disable = {'username':messages['disabled_account']}
 

	
 
    def validate_python(self, value, state):
 
        password = value['password']
 
        username = value['username']
 
        user = UserModel().get_by_username(username)
 

	
 
        if authfunc(None, username, password):
 
        if authenticate(username, password):
 
            return value
 
        else:
 
            if user and user.active is False:
 
                log.warning('user %s is disabled', username)
 
                raise formencode.Invalid(self.message('disabled_account',
 
                                         state=State_obj),
 
                                         value, state,
 
                                         error_dict=self.e_dict_disable)
 
            else:
 
                log.warning('user %s not authenticated', username)
 
                raise formencode.Invalid(self.message('invalid_password',
 
                                         state=State_obj), value, state,
 
                                         error_dict=self.e_dict)
 

	
 
class ValidRepoUser(formencode.validators.FancyValidator):
 

	
 
    def to_python(self, value, state):
 
        sa = meta.Session()
 
        try:
 
            self.user_db = sa.query(User)\
 
                .filter(User.active == True)\
 
                .filter(User.username == value).one()
 
        except Exception:
 
            raise formencode.Invalid(_('This username is not valid'),
 
                                     value, state)
 
        finally:
 
            meta.Session.remove()
 

	
 
        return self.user_db.user_id
 

	
 
def ValidRepoName(edit, old_data):
 
    class _ValidRepoName(formencode.validators.FancyValidator):
 

	
 
        def to_python(self, value, state):
 
            slug = h.repo_name_slug(value)
 
            if slug in ['_admin']:
 
                raise formencode.Invalid(_('This repository name is disallowed'),
 
                                         value, state)
 
            if old_data.get('repo_name') != value or not edit:
 
                if RepoModel().get_by_repo_name(slug, cache=False):
 
                    raise formencode.Invalid(_('This repository already exists') ,
 
                                             value, state)
 
            return slug
 

	
 

	
 
    return _ValidRepoName
 

	
 
def ValidForkType(old_data):
 
    class _ValidForkType(formencode.validators.FancyValidator):
 

	
 
        def to_python(self, value, state):
 
            if old_data['repo_type'] != value:
 
                raise formencode.Invalid(_('Fork have to be the same type as original'),
 
                                         value, state)
 
            return value
 
    return _ValidForkType
 

	
 
class ValidPerms(formencode.validators.FancyValidator):
 
    messages = {'perm_new_user_name':_('This username is not valid')}
 

	
 
    def to_python(self, value, state):
 
        perms_update = []
 
        perms_new = []
 
        #build a list of permission to update and new permission to create
 
        for k, v in value.items():
 
            if k.startswith('perm_'):
 
                if  k.startswith('perm_new_user'):
 
                    new_perm = value.get('perm_new_user', False)
 
                    new_user = value.get('perm_new_user_name', False)
 
                    if new_user and new_perm:
 
                        if (new_user, new_perm) not in perms_new:
 
                            perms_new.append((new_user, new_perm))
 
                else:
 
                    usr = k[5:]
 
                    if usr == 'default':
 
                        if value['private']:
 
                            #set none for default when updating to private repo
 
                            v = 'repository.none'
 
                    perms_update.append((usr, v))
 
        value['perms_updates'] = perms_update
 
        value['perms_new'] = perms_new
 
        sa = meta.Session
 
        for k, v in perms_new:
 
            try:
 
                self.user_db = sa.query(User)\
 
                    .filter(User.active == True)\
 
                    .filter(User.username == k).one()
 
            except Exception:
 
                msg = self.message('perm_new_user_name',
 
                                     state=State_obj)
 
                raise formencode.Invalid(msg, value, state,
 
                                         error_dict={'perm_new_user_name':msg})
 
        return value
 

	
 
class ValidSettings(formencode.validators.FancyValidator):
 

	
rhodecode/model/user.py
Show inline comments
 
#!/usr/bin/env python
 
# encoding: utf-8
 
# Model for users
 
# Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
 
# 
 
# -*- coding: utf-8 -*-
 
"""
 
    package.rhodecode.model.user
 
    ~~~~~~~~~~~~~~
 

	
 
    users model for RhodeCode
 
    :created_on: Apr 9, 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; 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.
 
"""
 
Created on April 9, 2010
 
Model for users
 
:author: marcink
 
"""
 

	
 
from pylons.i18n.translation import _
 
from rhodecode.model import BaseModel
 
from rhodecode.model.caching_query import FromCache
 
from rhodecode.model.db import User
 
from rhodecode.lib.exceptions import *
 

	
 
import logging
 
import traceback
 

	
 
log = logging.getLogger(__name__)
 
from pylons.i18n.translation import _
 

	
 
from rhodecode.model import BaseModel
 
from rhodecode.model.caching_query import FromCache
 
from rhodecode.model.db import User
 

	
 
from rhodecode.lib.exceptions import DefaultUserException, UserOwnsReposException
 

	
 
from sqlalchemy.exc import DatabaseError
 

	
 
log = logging.getLogger(__name__)
 

	
 
class UserModel(BaseModel):
 

	
 
    def get(self, user_id, cache=False):
 
        user = self.sa.query(User)
 
        if cache:
 
            user = user.options(FromCache("sql_cache_short",
 
                                          "get_user_%s" % user_id))
 
        return user.get(user_id)
 

	
 

	
 
    def get_by_username(self, username, cache=False, case_insensitive=False):
 

	
 
        if case_insensitive:
 
            user = self.sa.query(User).filter(User.username.ilike(username))
 
        else:
 
            user = self.sa.query(User)\
 
                .filter(User.username == username)
 
        if cache:
 
            user = user.options(FromCache("sql_cache_short",
 
                                          "get_user_%s" % username))
 
        return user.scalar()
 

	
 
    def create(self, form_data):
 
        try:
 
            new_user = User()
 
            for k, v in form_data.items():
 
                setattr(new_user, k, v)
 

	
 
            self.sa.add(new_user)
 
            self.sa.commit()
 
        except:
 
            log.error(traceback.format_exc())
 
            self.sa.rollback()
 
            raise
 

	
 
    def create_ldap(self, username, password):
 
        """
 
        Checks if user is in database, if not creates this user marked
 
        as ldap user
 
        :param username:
 
        :param password:
 
        """
 
        from rhodecode.lib.auth import get_crypt_password
 
        if self.get_by_username(username) is None:
 
        log.debug('Checking for such ldap account in RhodeCode database')
 
        if self.get_by_username(username, case_insensitive=True) is None:
 
            try:
 
                new_user = User()
 
                new_user.username = username
 
                new_user.username = username.lower()#add ldap account always lowercase
 
                new_user.password = get_crypt_password(password)
 
                new_user.email = '%s@ldap.server' % username
 
                new_user.active = True
 
                new_user.is_ldap = True
 
                new_user.name = '%s@ldap' % username
 
                new_user.lastname = ''
 

	
 

	
 
                self.sa.add(new_user)
 
                self.sa.commit()
 
                return True
 
            except:
 
            except (DatabaseError,):
 
                log.error(traceback.format_exc())
 
                self.sa.rollback()
 
                raise
 

	
 
        log.debug('this %s user exists skipping creation of ldap account',
 
                  username)
 
        return False
 

	
 
    def create_registration(self, form_data):
 
        from rhodecode.lib.celerylib import tasks, run_task
 
        try:
 
            new_user = User()
 
            for k, v in form_data.items():
 
                if k != 'admin':
 
                    setattr(new_user, k, v)
 

	
 
            self.sa.add(new_user)
 
            self.sa.commit()
 
            body = ('New user registration\n'
 
                    'username: %s\n'
 
                    'email: %s\n')
 
            body = body % (form_data['username'], form_data['email'])
 

	
 
            run_task(tasks.send_email, None,
 
                     _('[RhodeCode] New User registration'),
 
                     body)
 
        except:
 
            log.error(traceback.format_exc())
 
            self.sa.rollback()
 
            raise
 

	
 
    def update(self, user_id, form_data):
 
        try:
 
            new_user = self.get(user_id, cache=False)
 
            if new_user.username == 'default':
 
                raise DefaultUserException(
 
                                _("You can't Edit this user since it's"
 
                                  " crucial for entire application"))
 

	
 
            for k, v in form_data.items():
 
                if k == 'new_password' and v != '':
 
                    new_user.password = v
 
                else:
 
                    setattr(new_user, k, v)
 

	
 
            self.sa.add(new_user)
 
            self.sa.commit()
 
        except:
 
            log.error(traceback.format_exc())
 
            self.sa.rollback()
 
            raise
 

	
 
    def update_my_account(self, user_id, form_data):
 
        try:
 
            new_user = self.get(user_id, cache=False)
 
            if new_user.username == 'default':
 
                raise DefaultUserException(
 
                                _("You can't Edit this user since it's"
 
                                  " crucial for entire application"))
 
            for k, v in form_data.items():
 
                if k == 'new_password' and v != '':
 
                    new_user.password = v
 
                else:
 
                    if k not in ['admin', 'active']:
 
                        setattr(new_user, k, v)
 

	
 
            self.sa.add(new_user)
 
            self.sa.commit()
 
        except:
 
            log.error(traceback.format_exc())
 
            self.sa.rollback()
 
            raise
 

	
 
    def delete(self, user_id):
 
        try:
 
            user = self.get(user_id, cache=False)
 
            if user.username == 'default':
 
                raise DefaultUserException(
 
                                _("You can't remove this user since it's"
 
                                  " crucial for entire application"))
 
            if user.repositories:
 
                raise UserOwnsReposException(_('This user still owns %s '
 
                                               'repositories and cannot be '
 
                                               'removed. Switch owners or '
 
                                               'remove those repositories') \
 
                                               % user.repositories)
 
            self.sa.delete(user)
 
            self.sa.commit()
 
        except:
 
            log.error(traceback.format_exc())
 
            self.sa.rollback()
 
            raise
 

	
 
    def reset_password(self, data):
 
        from rhodecode.lib.celerylib import tasks, run_task
 
        run_task(tasks.reset_user_password, data['email'])
 

	
 

	
 
    def fill_data(self, user):
 
        """
 
        Fills user data with those from database and log out user if not 
 
        present in database
0 comments (0 inline, 0 general)