Changeset - 9e9f1b919c0c
[Not reviewed]
beta
0 9 0
Marcin Kuzminski - 15 years ago 2010-11-17 22:00:36
marcin@python-works.com
implements #60, ldap configuration and authentication.
fixes settings to use settings Model
9 files changed with 248 insertions and 92 deletions:
0 comments (0 inline, 0 general)
rhodecode/config/routing.py
Show inline comments
 
@@ -79,6 +79,8 @@ def make_map(config):
 

	
 
    #ADMIN PERMISSIONS REST ROUTES
 
    map.resource('permission', 'permissions', controller='admin/permissions', path_prefix='/_admin')
 
    map.connect('permissions_ldap', '/_admin/permissions_ldap', controller='admin/permissions', action='ldap')
 

	
 

	
 
    #ADMIN SETTINGS REST ROUTES
 
    with map.submapper(path_prefix='/_admin', controller='admin/settings') as m:
rhodecode/controllers/admin/permissions.py
Show inline comments
 
@@ -29,9 +29,11 @@ from pylons.controllers.util import abor
 
from pylons.i18n.translation import _
 
from rhodecode.lib import helpers as h
 
from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
 
from rhodecode.lib.auth_ldap import LdapImportError
 
from rhodecode.lib.base import BaseController, render
 
from rhodecode.model.forms import UserForm, DefaultPermissionsForm
 
from rhodecode.model.forms import LdapSettingsForm, DefaultPermissionsForm
 
from rhodecode.model.permission import PermissionModel
 
from rhodecode.model.settings import SettingsModel
 
from rhodecode.model.user import UserModel
 
import formencode
 
import logging
 
@@ -99,17 +101,19 @@ class PermissionsController(BaseControll
 
            form_result = _form.to_python(dict(request.POST))
 
            form_result.update({'perm_user_name':id})
 
            permission_model.update(form_result)
 
            h.flash(_('Default permissions updated succesfully'),
 
            h.flash(_('Default permissions updated successfully'),
 
                    category='success')
 

	
 
        except formencode.Invalid, errors:
 
            c.perms_choices = self.perms_choices
 
            c.register_choices = self.register_choices
 
            c.create_choices = self.create_choices
 
            defaults = errors.value
 
            defaults.update(SettingsModel().get_ldap_settings())
 

	
 
            return htmlfill.render(
 
                render('admin/permissions/permissions.html'),
 
                defaults=errors.value,
 
                defaults=defaults,
 
                errors=errors.error_dict or {},
 
                prefix_error=False,
 
                encoding="UTF-8")
 
@@ -146,6 +150,7 @@ class PermissionsController(BaseControll
 
            default_user = UserModel().get_by_username('default')
 
            defaults = {'_method':'put',
 
                        'anonymous':default_user.active}
 
            defaults.update(SettingsModel().get_ldap_settings())
 
            for p in default_user.user_perms:
 
                if p.permission.permission_name.startswith('repository.'):
 
                    defaults['default_perm'] = p.permission.permission_name
 
@@ -163,3 +168,50 @@ class PermissionsController(BaseControll
 
                        force_defaults=True,)
 
        else:
 
            return redirect(url('admin_home'))
 

	
 

	
 
    def ldap(self, id_user='default'):
 
        """
 
        POST ldap create and store ldap settings
 
        """
 

	
 
        settings_model = SettingsModel()
 
        _form = LdapSettingsForm()()
 

	
 
        try:
 
            form_result = _form.to_python(dict(request.POST))
 
            try:
 

	
 
                for k, v in form_result.items():
 
                    if k.startswith('ldap_'):
 
                        setting = settings_model.get(k)
 
                        setting.app_settings_value = v
 
                        self.sa.add(setting)
 

	
 
                self.sa.commit()
 
                h.flash(_('Ldap settings updated successfully'),
 
                    category='success')
 
            except:
 
                raise
 
        except LdapImportError:
 
            h.flash(_('Unable to activate ldap. The "ldap-python" library '
 
                      'is missing.'),
 
                    category='warning')
 

	
 
        except formencode.Invalid, errors:
 
            c.perms_choices = self.perms_choices
 
            c.register_choices = self.register_choices
 
            c.create_choices = self.create_choices
 

	
 
            return htmlfill.render(
 
                render('admin/permissions/permissions.html'),
 
                defaults=errors.value,
 
                errors=errors.error_dict or {},
 
                prefix_error=False,
 
                encoding="UTF-8")
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            h.flash(_('error occured during update of ldap settings'),
 
                    category='error')
 

	
 
        return redirect(url('edit_permission', id=id_user))
rhodecode/controllers/admin/settings.py
Show inline comments
 
@@ -31,14 +31,15 @@ from rhodecode.lib import helpers as h
 
from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
 
    HasPermissionAnyDecorator
 
from rhodecode.lib.base import BaseController, render
 
from rhodecode.lib.celerylib import tasks, run_task
 
from rhodecode.lib.utils import repo2db_mapper, invalidate_cache, \
 
    set_rhodecode_config, get_hg_settings, get_hg_ui_settings
 
from rhodecode.model.db import RhodeCodeSettings, RhodeCodeUi, Repository
 
from rhodecode.model.db import RhodeCodeUi, Repository
 
from rhodecode.model.forms import UserForm, ApplicationSettingsForm, \
 
    ApplicationUiSettingsForm
 
from rhodecode.model.scm import ScmModel
 
from rhodecode.model.settings import SettingsModel
 
from rhodecode.model.user import UserModel
 
from rhodecode.lib.celerylib import tasks, run_task
 
from sqlalchemy import func
 
import formencode
 
import logging
 
@@ -118,18 +119,12 @@ class SettingsController(BaseController)
 
            application_form = ApplicationSettingsForm()()
 
            try:
 
                form_result = application_form.to_python(dict(request.POST))
 

	
 
                settings_model = SettingsModel()
 
                try:
 
                    hgsettings1 = self.sa.query(RhodeCodeSettings)\
 
                        .filter(RhodeCodeSettings.app_settings_name \
 
                                == 'title').one()
 

	
 
                    hgsettings1 = settings_model.get('title')
 
                    hgsettings1.app_settings_value = form_result['rhodecode_title']
 

	
 
                    hgsettings2 = self.sa.query(RhodeCodeSettings)\
 
                        .filter(RhodeCodeSettings.app_settings_name \
 
                                == 'realm').one()
 

	
 
                    hgsettings2 = settings_model('realm')
 
                    hgsettings2.app_settings_value = form_result['rhodecode_realm']
 

	
 

	
rhodecode/lib/auth.py
Show inline comments
 
@@ -25,6 +25,7 @@ Created on April 4, 2010
 
from pylons import config, session, url, request
 
from pylons.controllers.util import abort, redirect
 
from rhodecode.lib.utils import get_repo_slug
 
from rhodecode.lib.auth_ldap import AuthLdap, UsernameError, PasswordError
 
from rhodecode.model import meta
 
from rhodecode.model.user import UserModel
 
from rhodecode.model.caching_query import FromCache
 
@@ -34,6 +35,7 @@ import bcrypt
 
from decorator import decorator
 
import logging
 
import random
 
import traceback
 

	
 
log = logging.getLogger(__name__)
 

	
 
@@ -74,17 +76,18 @@ def check_password(password, hashed):
 

	
 
def authfunc(environ, username, password):
 
    """
 
    Authentication function used in Mercurial/Git/ and access controll,
 
    Authentication function used in Mercurial/Git/ and access control,
 
    firstly checks for db authentication then if ldap is enabled for ldap
 
    authentication
 
    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)
 

	
 
    user = UserModel().get_by_username(username, cache=False)
 

	
 
    if user:
 
    if user is not None and user.is_ldap is False:
 
        if user.active:
 

	
 
            if user.username == 'default' and user.active:
 
@@ -97,6 +100,40 @@ def authfunc(environ, username, password
 
        else:
 
            log.error('user %s is disabled', username)
 

	
 

	
 
    else:
 
        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):
 
            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)
 

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

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

	
 
                return authenticated
 
            except (UsernameError, PasswordError):
 
                return False
 
            except:
 
                log.error(traceback.format_exc())
 
                return False
 
    return False
 

	
 
class  AuthUser(object):
rhodecode/lib/auth_ldap.py
Show inline comments
 
import logging
 
logging.basicConfig(level=logging.DEBUG)
 
log = logging.getLogger('ldap')
 

	
 
#==============================================================================
 
# LDAP
 
#Name     = Just a description for the auth modes page
 
@@ -11,76 +7,87 @@ log = logging.getLogger('ldap')
 
#Account  = DepartmentName\UserName (or UserName@MyDomain depending on AD server)
 
#Password = <password>
 
#Base DN  = DC=DepartmentName,DC=OrganizationName,DC=local
 
#
 
#On-the-fly user creation = yes
 
#Attributes
 
#  Login     = sAMAccountName
 
#  Firstname = givenName
 
#  Lastname  = sN
 
#  Email     = mail
 

	
 
#==============================================================================
 
class UsernameError(Exception):pass
 
class PasswordError(Exception):pass
 

	
 
from rhodecode.lib.exceptions import LdapImportError, UsernameError, \
 
    PasswordError, ConnectionError
 
import logging
 

	
 
log = logging.getLogger(__name__)
 

	
 
LDAP_USE_LDAPS = False
 
ldap_server_type = 'ldap'
 
LDAP_SERVER_ADDRESS = 'myldap.com'
 
LDAP_SERVER_PORT = '389'
 
try:
 
    import ldap
 
except ImportError:
 
    pass
 

	
 
#USE FOR READ ONLY BIND TO LDAP SERVER
 
LDAP_BIND_DN = ''
 
LDAP_BIND_PASS = ''
 
class AuthLdap(object):
 

	
 
if LDAP_USE_LDAPS:ldap_server_type = ldap_server_type + 's'
 
LDAP_SERVER = "%s://%s:%s" % (ldap_server_type,
 
                                       LDAP_SERVER_ADDRESS,
 
                                       LDAP_SERVER_PORT)
 

	
 
BASE_DN = "ou=people,dc=server,dc=com"
 
AUTH_DN = "uid=%s,%s"
 
    def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='',
 
                 use_ldaps=False, ldap_version=3):
 
        self.ldap_version = ldap_version
 
        if use_ldaps:
 
            port = port or 689
 
        self.LDAP_USE_LDAPS = use_ldaps
 
        self.LDAP_SERVER_ADDRESS = server
 
        self.LDAP_SERVER_PORT = port
 

	
 
def authenticate_ldap(username, password):
 
    """Authenticate a user via LDAP and return his/her LDAP properties.
 
        #USE FOR READ ONLY BIND TO LDAP SERVER
 
        self.LDAP_BIND_DN = bind_dn
 
        self.LDAP_BIND_PASS = bind_pass
 

	
 
    Raises AuthenticationError if the credentials are rejected, or
 
    EnvironmentError if the LDAP server can't be reached.
 
    """
 
    try:
 
        import ldap
 
    except ImportError:
 
        raise Exception('Could not import ldap make sure You install python-ldap')
 
        ldap_server_type = 'ldap'
 
        if self.LDAP_USE_LDAPS:ldap_server_type = ldap_server_type + 's'
 
        self.LDAP_SERVER = "%s://%s:%s" % (ldap_server_type,
 
                                               self.LDAP_SERVER_ADDRESS,
 
                                               self.LDAP_SERVER_PORT)
 

	
 
        self.BASE_DN = base_dn
 
        self.AUTH_DN = "uid=%s,%s"
 

	
 
    from rhodecode.lib.helpers import chop_at
 

	
 
    uid = chop_at(username, "@%s" % LDAP_SERVER_ADDRESS)
 
    dn = AUTH_DN % (uid, BASE_DN)
 
    log.debug("Authenticating %r at %s", dn, LDAP_SERVER)
 
    if "," in username:
 
        raise UsernameError("invalid character in username: ,")
 
    try:
 
        #ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, '/etc/openldap/cacerts')
 
        server = ldap.initialize(LDAP_SERVER)
 
        server.protocol = ldap.VERSION3
 
        
 
        if LDAP_BIND_DN and LDAP_BIND_PASS:
 
            server.simple_bind_s(AUTH_DN % (LDAP_BIND_DN,
 
                                            LDAP_BIND_PASS),
 
                                            password)
 
    def authenticate_ldap(self, username, password):
 
        """Authenticate a user via LDAP and return his/her LDAP properties.
 
    
 
        Raises AuthenticationError if the credentials are rejected, or
 
        EnvironmentError if the LDAP server can't be reached.
 
        
 
        server.simple_bind_s(dn, password)
 
        properties = server.search_s(dn, ldap.SCOPE_SUBTREE)
 
        if not properties:
 
            raise ldap.NO_SUCH_OBJECT()
 
    except ldap.NO_SUCH_OBJECT, e:
 
        log.debug("LDAP says no such user '%s' (%s)", uid, username)
 
        raise UsernameError()
 
    except ldap.INVALID_CREDENTIALS, e:
 
        log.debug("LDAP rejected password for user '%s' (%s)", uid, username)
 
        raise PasswordError()
 
    except ldap.SERVER_DOWN, e:
 
        raise EnvironmentError("can't access authentication server")
 
    return properties
 
        :param username: username
 
        :param password: password
 
        """
 

	
 
        from rhodecode.lib.helpers import chop_at
 

	
 
        uid = chop_at(username, "@%s" % self.LDAP_SERVER_ADDRESS)
 
        dn = self.AUTH_DN % (uid, self.BASE_DN)
 
        log.debug("Authenticating %r at %s", dn, self.LDAP_SERVER)
 
        if "," in username:
 
            raise UsernameError("invalid character in username: ,")
 
        try:
 
            ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, '/etc/openldap/cacerts')
 
            ldap.set_option(ldap.OPT_NETWORK_TIMEOUT, 10)
 
            server = ldap.initialize(self.LDAP_SERVER)
 
            if self.ldap_version == 2:
 
                server.protocol = ldap.VERSION2
 
            else:
 
                server.protocol = ldap.VERSION3
 

	
 
            if self.LDAP_BIND_DN and self.LDAP_BIND_PASS:
 
                server.simple_bind_s(self.AUTH_DN % (self.LDAP_BIND_DN,
 
                                                self.BASE_DN),
 
                                                self.LDAP_BIND_PASS)
 

	
 
print authenticate_ldap('test', 'test')
 
            server.simple_bind_s(dn, password)
 
            properties = server.search_s(dn, ldap.SCOPE_SUBTREE)
 
            if not properties:
 
                raise ldap.NO_SUCH_OBJECT()
 
        except ldap.NO_SUCH_OBJECT, e:
 
            log.debug("LDAP says no such user '%s' (%s)", uid, username)
 
            raise UsernameError()
 
        except ldap.INVALID_CREDENTIALS, e:
 
            log.debug("LDAP rejected password for user '%s' (%s)", uid, username)
 
            raise PasswordError()
 
        except ldap.SERVER_DOWN, e:
 
            raise ConnectionError("LDAP can't access authentication server")
 

	
 
        return properties[0]
 

	
rhodecode/model/forms.py
Show inline comments
 
@@ -25,6 +25,7 @@ from formencode.validators import Unicod
 
from pylons import session
 
from pylons.i18n.translation import _
 
from rhodecode.lib.auth import authfunc, 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
 
@@ -82,7 +83,7 @@ class ValidAuth(formencode.validators.Fa
 
    messages = {
 
            'invalid_password':_('invalid password'),
 
            'invalid_login':_('invalid user name'),
 
            'disabled_account':_('Your acccount is disabled')
 
            'disabled_account':_('Your account is disabled')
 

	
 
            }
 
    #error mapping
 
@@ -236,6 +237,16 @@ class ValidSystemEmail(formencode.valida
 

	
 
        return value
 

	
 
class LdapLibValidator(formencode.validators.FancyValidator):
 

	
 
    def to_python(self, value, state):
 

	
 
        try:
 
            import ldap
 
        except ImportError:
 
            raise LdapImportError
 
        return value
 

	
 
#===============================================================================
 
# FORMS        
 
#===============================================================================
 
@@ -352,10 +363,26 @@ def DefaultPermissionsForm(perms_choices
 
    class _DefaultPermissionsForm(formencode.Schema):
 
        allow_extra_fields = True
 
        filter_extra_fields = True
 
        overwrite_default = OneOf(['true', 'false'], if_missing='false')
 
        overwrite_default = StringBoolean(if_missing=False)
 
        anonymous = OneOf(['True', 'False'], if_missing=False)
 
        default_perm = OneOf(perms_choices)
 
        default_register = OneOf(register_choices)
 
        default_create = OneOf(create_choices)
 

	
 
    return _DefaultPermissionsForm
 

	
 

	
 
def LdapSettingsForm():
 
    class _LdapSettingsForm(formencode.Schema):
 
        allow_extra_fields = True
 
        filter_extra_fields = True
 
        pre_validators = [LdapLibValidator]
 
        ldap_active = StringBoolean(if_missing=False)
 
        ldap_host = UnicodeString(strip=True,)
 
        ldap_port = Number(strip=True,)
 
        ldap_ldaps = StringBoolean(if_missing=False)
 
        ldap_dn_user = UnicodeString(strip=True,)
 
        ldap_dn_pass = UnicodeString(strip=True,)
 
        ldap_base_dn = UnicodeString(strip=True,)
 

	
 
    return _LdapSettingsForm
rhodecode/model/permission.py
Show inline comments
 
@@ -96,8 +96,3 @@ class PermissionModel(object):
 
            log.error(traceback.format_exc())
 
            self.sa.rollback()
 
            raise
 

	
 

	
 

	
 

	
 

	
rhodecode/model/settings.py
Show inline comments
 
@@ -51,6 +51,17 @@ class SettingsModel(object):
 

	
 

	
 
    def get_ldap_settings(self):
 
        """
 
        Returns ldap settings from database
 
        :returns:
 
        ldap_active
 
        ldap_host
 
        ldap_port 
 
        ldap_ldaps
 
        ldap_dn_user 
 
        ldap_dn_pass 
 
        ldap_base_dn
 
        """
 

	
 
        r = self.sa.query(RhodeCodeSettings)\
 
                .filter(RhodeCodeSettings.app_settings_name\
rhodecode/model/user.py
Show inline comments
 
@@ -68,6 +68,36 @@ class UserModel(object):
 
            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:
 
        """
 

	
 
        if self.get_by_username(username) is None:
 
            try:
 
                new_user = User()
 
                new_user.username = username
 
                new_user.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:
 
                log.error(traceback.format_exc())
 
                self.sa.rollback()
 
                raise
 

	
 
        return False
 

	
 
    def create_registration(self, form_data):
 
        from rhodecode.lib.celerylib import tasks, run_task
 
        try:
0 comments (0 inline, 0 general)