Changeset - 34a9b64a5e00
[Not reviewed]
Merge beta
0 7 0
Marcin Kuzminski - 15 years ago 2011-04-26 20:28:46
marcin@python-works.com
Merge with 74685a31cc43600646843697dfa8bb2094eb1ea5 - contributions to LDAP made by Lorenzo M. Catucci
7 files changed with 38 insertions and 21 deletions:
0 comments (0 inline, 0 general)
rhodecode/controllers/admin/ldap_settings.py
Show inline comments
 
@@ -38,74 +38,84 @@ from rhodecode.lib.auth import LoginRequ
 
from rhodecode.lib.auth_ldap import LdapImportError
 
from rhodecode.model.settings import SettingsModel
 
from rhodecode.model.forms import LdapSettingsForm
 
from sqlalchemy.exc import DatabaseError
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
class LdapSettingsController(BaseController):
 

	
 
    search_scope_choices = [('BASE', _('BASE'),),
 
                            ('ONELEVEL', _('ONELEVEL'),),
 
                            ('SUBTREE', _('SUBTREE'),),
 
                            ]
 
    search_scope_default = 'SUBTREE'
 

	
 
    tls_reqcert_choices = [('NEVER', _('NEVER'),),
 
                           ('ALLOW', _('ALLOW'),),
 
                           ('TRY', _('TRY'),),
 
                           ('DEMAND', _('DEMAND'),),
 
                           ('HARD', _('HARD'),),
 
                           ]
 
    tls_reqcert_default = 'DEMAND'
 

	
 
    tls_kind_choices = [('PLAIN', _('No encryption'),),
 
                        ('LDAPS', _('LDAPS connection'),),
 
                        ('START_TLS', _('START_TLS on LDAP connection'),)
 
                        ]
 

	
 
    tls_kind_default = 'PLAIN'
 

	
 
    @LoginRequired()
 
    @HasPermissionAllDecorator('hg.admin')
 
    def __before__(self):
 
        c.admin_user = session.get('admin_user')
 
        c.admin_username = session.get('admin_username')
 
        c.search_scope_choices = self.search_scope_choices
 
        c.tls_reqcert_choices = self.tls_reqcert_choices
 
        c.tls_kind_choices = self.tls_kind_choices
 
        super(LdapSettingsController, self).__before__()
 

	
 
    def index(self):
 
        defaults = SettingsModel().get_ldap_settings()
 
        c.search_scope_cur = defaults.get('ldap_search_scope')
 
        c.tls_reqcert_cur = defaults.get('ldap_tls_reqcert')
 
        c.tls_kind_cur = defaults.get('ldap_tls_kind')
 

	
 
        return htmlfill.render(
 
                    render('admin/ldap/ldap.html'),
 
                    defaults=defaults,
 
                    encoding="UTF-8",
 
                    force_defaults=True,)
 

	
 
    def ldap_settings(self):
 
        """POST ldap create and store ldap settings"""
 

	
 
        settings_model = SettingsModel()
 
        _form = LdapSettingsForm([x[0] for x in self.tls_reqcert_choices],
 
                                 [x[0] for x in self.search_scope_choices])()
 
                                 [x[0] for x in self.search_scope_choices],
 
                                 [x[0] for x in self.tls_kind_choices])()
 

	
 
        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 (DatabaseError,):
 
                raise
 
        except LdapImportError:
 
            h.flash(_('Unable to activate ldap. The "python-ldap" library '
 
                      'is missing.'), category='warning')
 

	
 
        except formencode.Invalid, errors:
 

	
 
            c.search_scope_cur = self.search_scope_default
 
            c.tls_reqcert_cur = self.search_scope_default
rhodecode/lib/auth.py
Show inline comments
 
@@ -169,69 +169,69 @@ def authenticate(username, password):
 

	
 
    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 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'),
 
                  'use_ldaps': str2bool(ldap_settings.get('ldap_ldaps')),
 
                  '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)
 

	
 
                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],
 
                    'name': ldap_attrs.get(ldap_settings\
 
                                       .get('ldap_attr_firstname'), [''])[0],
 
                    'lastname': ldap_attrs.get(ldap_settings\
 
                                           .get('ldap_attr_lastname'),[''])[0],
 
                    'email': ldap_attrs.get(ldap_settings\
 
                                        .get('ldap_attr_email'), [''])[0],
 
                    }
 

	
 
                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
 

	
 

	
 
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
 
    """
rhodecode/lib/auth_ldap.py
Show inline comments
 
@@ -13,116 +13,123 @@
 
# 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/>.
 
"""
 
Created on Nov 17, 2010
 

	
 
@author: marcink
 
"""
 

	
 
from rhodecode.lib.exceptions import *
 
import logging
 

	
 
log = logging.getLogger(__name__)
 

	
 
try:
 
    import ldap
 
except ImportError:
 
    pass
 

	
 
class AuthLdap(object):
 

	
 
    def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='',
 
                 use_ldaps=False, tls_reqcert='DEMAND', ldap_version=3,
 
                 tls_kind = 'PLAIN', tls_reqcert='DEMAND', ldap_version=3,
 
                 ldap_filter='(&(objectClass=user)(!(objectClass=computer)))',
 
                 search_scope='SUBTREE',
 
                 attr_login='uid'):
 
        self.ldap_version = ldap_version
 
        if use_ldaps:
 
        ldap_server_type = 'ldap'
 

	
 
        self.TLS_KIND = tls_kind
 

	
 
        if self.TLS_KIND == 'LDAPS':
 
            port = port or 689
 
        self.LDAP_USE_LDAPS = use_ldaps
 
            ldap_server_type = ldap_server_type + 's'
 

	
 
        self.TLS_REQCERT = ldap.__dict__['OPT_X_TLS_' + tls_reqcert]
 
        self.LDAP_SERVER_ADDRESS = server
 
        self.LDAP_SERVER_PORT = port
 

	
 
        #USE FOR READ ONLY BIND TO LDAP SERVER
 
        self.LDAP_BIND_DN = bind_dn
 
        self.LDAP_BIND_PASS = bind_pass
 

	
 
        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.LDAP_FILTER = ldap_filter
 
        self.SEARCH_SCOPE = ldap.__dict__['SCOPE_' + search_scope]
 
        self.attr_login = attr_login
 

	
 

	
 
    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.
 

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

	
 
        from rhodecode.lib.helpers import chop_at
 

	
 
        uid = chop_at(username, "@%s" % self.LDAP_SERVER_ADDRESS)
 

	
 
        if "," in username:
 
            raise LdapUsernameError("invalid character in username: ,")
 
        try:
 
            ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, '/etc/openldap/cacerts')
 
            ldap.set_option(ldap.OPT_REFERRALS, ldap.OPT_OFF)
 
            ldap.set_option(ldap.OPT_RESTART, ldap.OPT_ON)
 
            ldap.set_option(ldap.OPT_TIMEOUT, 20)
 
            ldap.set_option(ldap.OPT_NETWORK_TIMEOUT, 10)
 
            ldap.set_option(ldap.OPT_TIMELIMIT, 15)
 
            if self.LDAP_USE_LDAPS:
 
            if self.TLS_KIND != 'PLAIN':
 
                ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, self.TLS_REQCERT)
 
            server = ldap.initialize(self.LDAP_SERVER)
 
            if self.ldap_version == 2:
 
                server.protocol = ldap.VERSION2
 
            else:
 
                server.protocol = ldap.VERSION3
 

	
 
            if self.TLS_KIND == 'START_TLS':
 
                server.start_tls_s()
 

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

	
 
            filt = '(&%s(%s=%s))' % (self.LDAP_FILTER, self.attr_login, username)
 
            log.debug("Authenticating %r filt %s at %s", self.BASE_DN,
 
                      filt, self.LDAP_SERVER)
 
            lobjects = server.search_ext_s(self.BASE_DN, self.SEARCH_SCOPE,
 
                                           filt)
 

	
 
            if not lobjects:
 
                raise ldap.NO_SUCH_OBJECT()
 

	
 
            for (dn, attrs) in lobjects:
 
            for (dn, _attrs) in lobjects:
 
                try:
 
                    server.simple_bind_s(dn, password)
 
                    attrs = server.search_ext_s(dn, ldap.SCOPE_BASE, '(objectClass=*)')[0][1]
 
                    break
 

	
 
                except ldap.INVALID_CREDENTIALS, e:
 
                    log.debug("LDAP rejected password for user '%s' (%s): %s",
 
                              uid, username, dn)
 

	
 
            else:
 
                log.debug("No matching LDAP objects for authentication "
 
                          "of '%s' (%s)", uid, username)
 
                raise LdapPasswordError()
 

	
 
        except ldap.NO_SUCH_OBJECT, e:
 
            log.debug("LDAP says no such user '%s' (%s)", uid, username)
 
            raise LdapUsernameError()
 
        except ldap.SERVER_DOWN, e:
 
            raise LdapConnectionError("LDAP can't access authentication server")
 

	
 
        return (dn, attrs)
rhodecode/lib/db_manage.py
Show inline comments
 
@@ -291,49 +291,49 @@ class DbManage(object):
 
        hooks4.ui_value = 'python:rhodecode.lib.hooks.log_pull_action'
 

	
 
        #For mercurial 1.7 set backward comapatibility with format
 
        dotencode_disable = RhodeCodeUi()
 
        dotencode_disable.ui_section = 'format'
 
        dotencode_disable.ui_key = 'dotencode'
 
        dotencode_disable.ui_value = 'false'
 

	
 
        try:
 
            self.sa.add(hooks1)
 
            self.sa.add(hooks2)
 
            self.sa.add(hooks3)
 
            self.sa.add(hooks4)
 
            self.sa.add(dotencode_disable)
 
            self.sa.commit()
 
        except:
 
            self.sa.rollback()
 
            raise
 

	
 
    def create_ldap_options(self):
 
        """Creates ldap settings"""
 

	
 
        try:
 
            for k, v in [('ldap_active', 'false'), ('ldap_host', ''),
 
                        ('ldap_port', '389'), ('ldap_ldaps', 'false'),
 
                        ('ldap_port', '389'), ('ldap_tls_kind', 'PLAIN'),
 
                        ('ldap_tls_reqcert', ''), ('ldap_dn_user', ''),
 
                        ('ldap_dn_pass', ''), ('ldap_base_dn', ''),
 
                        ('ldap_filter', ''), ('ldap_search_scope', ''),
 
                        ('ldap_attr_login', ''), ('ldap_attr_firstname', ''),
 
                        ('ldap_attr_lastname', ''), ('ldap_attr_email', '')]:
 

	
 
                setting = RhodeCodeSettings(k, v)
 
                self.sa.add(setting)
 
            self.sa.commit()
 
        except:
 
            self.sa.rollback()
 
            raise
 

	
 
    def config_prompt(self, test_repo_path='', retries=3):
 
        if retries == 3:
 
            log.info('Setting up repositories config')
 

	
 
        if not self.tests and not test_repo_path:
 
            path = raw_input('Specify valid full path to your repositories'
 
                        ' you can change this later in application settings:')
 
        else:
 
            path = test_repo_path
 
        path_ok = True
 

	
rhodecode/model/forms.py
Show inline comments
 
@@ -535,45 +535,45 @@ def ApplicationUiSettingsForm():
 
        allow_extra_fields = True
 
        filter_extra_fields = False
 
        web_push_ssl = OneOf(['true', 'false'], if_missing='false')
 
        paths_root_path = All(ValidPath(), UnicodeString(strip=True, min=1, not_empty=True))
 
        hooks_changegroup_update = OneOf(['True', 'False'], if_missing=False)
 
        hooks_changegroup_repo_size = OneOf(['True', 'False'], if_missing=False)
 
        hooks_pretxnchangegroup_push_logger = OneOf(['True', 'False'], if_missing=False)
 
        hooks_preoutgoing_pull_logger = OneOf(['True', 'False'], if_missing=False)
 

	
 
    return _ApplicationUiSettingsForm
 

	
 
def DefaultPermissionsForm(perms_choices, register_choices, create_choices):
 
    class _DefaultPermissionsForm(formencode.Schema):
 
        allow_extra_fields = True
 
        filter_extra_fields = True
 
        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(tls_reqcert_choices, search_scope_choices):
 
def LdapSettingsForm(tls_reqcert_choices, search_scope_choices, tls_kind_choices):
 
    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_tls_kind = OneOf(tls_kind_choices)
 
        ldap_tls_reqcert = OneOf(tls_reqcert_choices)
 
        ldap_dn_user = UnicodeString(strip=True,)
 
        ldap_dn_pass = UnicodeString(strip=True,)
 
        ldap_base_dn = UnicodeString(strip=True,)
 
        ldap_filter = UnicodeString(strip=True,)
 
        ldap_search_scope = OneOf(search_scope_choices)
 
        ldap_attr_login = All(AttrLoginValidator, UnicodeString(strip=True,))
 
        ldap_attr_firstname = UnicodeString(strip=True,)
 
        ldap_attr_lastname = UnicodeString(strip=True,)
 
        ldap_attr_email = UnicodeString(strip=True,)
 

	
 
    return _LdapSettingsForm
rhodecode/model/settings.py
Show inline comments
 
@@ -49,49 +49,49 @@ class SettingsModel(BaseModel):
 
        'rhodecode_' prefix, than global pylons config is updated with such
 
        keys
 
        """
 

	
 
        ret = self.sa.query(RhodeCodeSettings)
 

	
 
        if cache:
 
            ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
 

	
 
        if not ret:
 
            raise Exception('Could not get application settings !')
 
        settings = {}
 
        for each in ret:
 
            settings['rhodecode_' + each.app_settings_name] = each.app_settings_value
 

	
 
        return settings
 

	
 
    def get_ldap_settings(self):
 
        """
 
        Returns ldap settings from database
 
        :returns:
 
        ldap_active
 
        ldap_host
 
        ldap_port
 
        ldap_ldaps
 
        ldap_tls_kind
 
        ldap_tls_reqcert
 
        ldap_dn_user
 
        ldap_dn_pass
 
        ldap_base_dn
 
        ldap_filter
 
        ldap_search_scope
 
        ldap_attr_login
 
        ldap_attr_firstname
 
        ldap_attr_lastname
 
        ldap_attr_email
 
        """
 
        # ldap_search_scope
 

	
 
        r = self.sa.query(RhodeCodeSettings)\
 
                .filter(RhodeCodeSettings.app_settings_name\
 
                        .startswith('ldap_'))\
 
                .all()
 

	
 
        fd = {}
 

	
 
        for row in r:
 
            v = row.app_settings_value
 
            if v in ['true', 'yes', 'on', 'y', 't', '1']:
 
                v = True
rhodecode/templates/admin/ldap/ldap.html
Show inline comments
 
@@ -26,50 +26,50 @@
 
        <div class="fields">
 

	
 
	  <h3>${_('Connection settings')}</h3>
 
            <div class="field">
 
                <div class="label label-checkbox"><label for="ldap_active">${_('Enable LDAP')}</label></div>
 
                <div class="checkboxes"><div class="checkbox">${h.checkbox('ldap_active',True,class_='small')}</div></div>
 
            </div>
 
            <div class="field">
 
                <div class="label"><label for="ldap_host">${_('Host')}</label></div>
 
                <div class="input">${h.text('ldap_host',class_='small')}</div>
 
            </div>
 
            <div class="field">
 
                <div class="label"><label for="ldap_port">${_('Port')}</label></div>
 
                <div class="input">${h.text('ldap_port',class_='small')}</div>
 
            </div>
 
            <div class="field">
 
                <div class="label"><label for="ldap_dn_user">${_('Account')}</label></div>
 
                <div class="input">${h.text('ldap_dn_user',class_='small')}</div>
 
            </div>
 
            <div class="field">
 
                <div class="label"><label for="ldap_dn_pass">${_('Password')}</label></div>
 
                <div class="input">${h.password('ldap_dn_pass',class_='small')}</div>
 
            </div>
 
            <div class="field">
 
                <div class="label label-checkbox"><label for="ldap_ldaps">${_('Enable LDAPS')}</label></div>
 
                <div class="checkboxes"><div class="checkbox">${h.checkbox('ldap_ldaps',True,class_='small')}</div></div>
 
                <div class="label"><label for="ldap_tls_kind">${_('Connection security')}</label></div>
 
                <div class="select">${h.select('ldap_tls_kind',c.tls_kind_cur,c.tls_kind_choices,class_='small')}</div>
 
            </div>
 
            <div class="field">
 
                <div class="label"><label for="ldap_tls_reqcert">${_('Certificate Checks')}</label></div>
 
                <div class="select">${h.select('ldap_tls_reqcert',c.tls_reqcert_cur,c.tls_reqcert_choices,class_='small')}</div>
 
            </div>
 
	  <h3>${_('Search settings')}</h3>
 
            <div class="field">
 
                <div class="label"><label for="ldap_base_dn">${_('Base DN')}</label></div>
 
                <div class="input">${h.text('ldap_base_dn',class_='small')}</div>
 
            </div>
 
            <div class="field">
 
                <div class="label"><label for="ldap_filter">${_('LDAP Filter')}</label></div>
 
                <div class="input">${h.text('ldap_filter',class_='small')}</div>
 
            </div>
 
            <div class="field">
 
                <div class="label"><label for="ldap_search_scope">${_('LDAP Search Scope')}</label></div>
 
                <div class="select">${h.select('ldap_search_scope',c.search_scope_cur,c.search_scope_choices,class_='small')}</div>
 
            </div>
 
	  <h3>${_('Attribute mappings')}</h3>
 
            <div class="field">
 
                <div class="label"><label for="ldap_attr_login">${_('Login Attribute')}</label></div>
 
                <div class="input">${h.text('ldap_attr_login',class_='small')}</div>
 
            </div>
 
            <div class="field">
0 comments (0 inline, 0 general)