diff --git a/docs/setup.rst b/docs/setup.rst --- a/docs/setup.rst +++ b/docs/setup.rst @@ -143,14 +143,14 @@ Setting up LDAP support ----------------------- RhodeCode starting from version 1.1 supports ldap authentication. In order -to use LDAP, you have to install the python-ldap_ package. This package is available -via pypi, so you can install it by running +to use LDAP, you have to install the python-ldap_ package. This package is +available via pypi, so you can install it by running -:: +using easy_install:: easy_install python-ldap -:: +using pip:: pip install python-ldap @@ -168,7 +168,7 @@ Here's a typical ldap setup:: Port = 389 Account = Password = - Enable LDAPS = checked + Connection Security = LDAPS connection Certificate Checks = DEMAND Search settings @@ -212,11 +212,19 @@ Password : optional .. _Enable LDAPS: -Enable LDAPS : optional - Check this if SSL encryption is necessary for communication with the - LDAP server - it will likely require `Port`_ to be set to a different - value (standard LDAPS port is 636). When LDAPS is enabled then - `Certificate Checks`_ is required. +Connection Security : required + Defines the connection to LDAP server + + No encryption + Plain non encrypted connection + + LDAPS connection + Enable ldaps connection. It will likely require `Port`_ to be set to + a different value (standard LDAPS port is 636). When LDAPS is enabled + then `Certificate Checks`_ is required. + + START_TLS on LDAP connection + START TLS connection .. _Certificate Checks: diff --git a/rhodecode/controllers/admin/ldap_settings.py b/rhodecode/controllers/admin/ldap_settings.py --- a/rhodecode/controllers/admin/ldap_settings.py +++ b/rhodecode/controllers/admin/ldap_settings.py @@ -32,13 +32,14 @@ from pylons import request, response, se from pylons.controllers.util import abort, redirect from pylons.i18n.translation import _ +from sqlalchemy.exc import DatabaseError + from rhodecode.lib.base import BaseController, render from rhodecode.lib import helpers as h from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator -from rhodecode.lib.auth_ldap import LdapImportError -from rhodecode.model.settings import SettingsModel +from rhodecode.lib.exceptions import LdapImportError from rhodecode.model.forms import LdapSettingsForm -from sqlalchemy.exc import DatabaseError +from rhodecode.model.db import RhodeCodeSettings log = logging.getLogger(__name__) @@ -74,10 +75,15 @@ class LdapSettingsController(BaseControl c.search_scope_choices = self.search_scope_choices c.tls_reqcert_choices = self.tls_reqcert_choices c.tls_kind_choices = self.tls_kind_choices + + c.search_scope_cur = self.search_scope_default + c.tls_reqcert_cur = self.tls_reqcert_default + c.tls_kind_cur = self.tls_kind_default + super(LdapSettingsController, self).__before__() def index(self): - defaults = SettingsModel().get_ldap_settings() + defaults = RhodeCodeSettings.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') @@ -91,7 +97,6 @@ class LdapSettingsController(BaseControl 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.tls_kind_choices])() @@ -102,7 +107,7 @@ class LdapSettingsController(BaseControl for k, v in form_result.items(): if k.startswith('ldap_'): - setting = settings_model.get(k) + setting = RhodeCodeSettings.get_by_name(k) setting.app_settings_value = v self.sa.add(setting) @@ -116,14 +121,13 @@ class LdapSettingsController(BaseControl 'is missing.'), category='warning') except formencode.Invalid, errors: + e = errors.error_dict or {} - c.search_scope_cur = self.search_scope_default - c.tls_reqcert_cur = self.search_scope_default return htmlfill.render( render('admin/ldap/ldap.html'), defaults=errors.value, - errors=errors.error_dict or {}, + errors=e, prefix_error=False, encoding="UTF-8") except Exception: diff --git a/rhodecode/controllers/admin/settings.py b/rhodecode/controllers/admin/settings.py --- a/rhodecode/controllers/admin/settings.py +++ b/rhodecode/controllers/admin/settings.py @@ -40,11 +40,11 @@ from rhodecode.lib.base import BaseContr from rhodecode.lib.celerylib import tasks, run_task from rhodecode.lib.utils import repo2db_mapper, invalidate_cache, \ set_rhodecode_config, repo_name_slug -from rhodecode.model.db import RhodeCodeUi, Repository, Group +from rhodecode.model.db import RhodeCodeUi, Repository, Group, \ + RhodeCodeSettings 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 log = logging.getLogger(__name__) @@ -68,7 +68,7 @@ class SettingsController(BaseController) """GET /admin/settings: All items in the collection""" # url('admin_settings') - defaults = SettingsModel().get_app_settings() + defaults = RhodeCodeSettings.get_app_settings() defaults.update(self.get_hg_ui_settings()) return htmlfill.render( render('admin/settings/settings.html'), @@ -121,18 +121,17 @@ class SettingsController(BaseController) application_form = ApplicationSettingsForm()() try: form_result = application_form.to_python(dict(request.POST)) - settings_model = SettingsModel() try: - hgsettings1 = settings_model.get('title') + hgsettings1 = RhodeCodeSettings.get_by_name('title') hgsettings1.app_settings_value = \ form_result['rhodecode_title'] - hgsettings2 = settings_model.get('realm') + hgsettings2 = RhodeCodeSettings.get_by_name('realm') hgsettings2.app_settings_value = \ form_result['rhodecode_realm'] - hgsettings3 = settings_model.get('ga_code') + hgsettings3 = RhodeCodeSettings.get_by_name('ga_code') hgsettings3.app_settings_value = \ form_result['rhodecode_ga_code'] diff --git a/rhodecode/lib/auth.py b/rhodecode/lib/auth.py --- a/rhodecode/lib/auth.py +++ b/rhodecode/lib/auth.py @@ -48,7 +48,7 @@ from rhodecode.lib.auth_ldap import Auth from rhodecode.model import meta from rhodecode.model.user import UserModel -from rhodecode.model.db import Permission +from rhodecode.model.db import Permission, RhodeCodeSettings log = logging.getLogger(__name__) @@ -149,6 +149,7 @@ def authenticate(username, password): :param username: username :param password: password """ + user_model = UserModel() user = user_model.get_by_username(username, cache=False) @@ -176,9 +177,7 @@ def authenticate(username, password): log.debug('this user already exists as non ldap') return False - from rhodecode.model.settings import SettingsModel - ldap_settings = SettingsModel().get_ldap_settings() - + ldap_settings = RhodeCodeSettings.get_ldap_settings() #====================================================================== # FALLBACK TO LDAP AUTH IF ENABLE #====================================================================== @@ -204,13 +203,13 @@ def authenticate(username, password): password) log.debug('Got ldap DN response %s', user_dn) + get_ldap_attr = lambda k:ldap_attrs.get(ldap_settings\ + .get(k), [''])[0] + user_attrs = { - 'name': 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], + 'name': get_ldap_attr('ldap_attr_firstname'), + 'lastname': get_ldap_attr('ldap_attr_lastname'), + 'email': get_ldap_attr('ldap_attr_email'), } if user_model.create_ldap(username, password, user_dn, diff --git a/rhodecode/lib/auth_ldap.py b/rhodecode/lib/auth_ldap.py --- a/rhodecode/lib/auth_ldap.py +++ b/rhodecode/lib/auth_ldap.py @@ -1,8 +1,15 @@ -#!/usr/bin/env python -# encoding: utf-8 -# ldap authentication lib -# Copyright (C) 2009-2011 Marcin Kuzminski -# +# -*- coding: utf-8 -*- +""" + rhodecode.controllers.changelog + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + RhodeCode authentication library for LDAP + + :created_on: Created on Nov 17, 2010 + :author: marcink + :copyright: (C) 2009-2011 Marcin Kuzminski + :license: GPLv3, see COPYING for more details. +""" # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or @@ -15,26 +22,26 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . -""" -Created on Nov 17, 2010 -@author: marcink -""" - -from rhodecode.lib.exceptions import * import logging +from rhodecode.lib.exceptions import LdapConnectionError, LdapUsernameError, \ + LdapPasswordError + log = logging.getLogger(__name__) + try: import ldap except ImportError: + # means that python-ldap is not installed pass + class AuthLdap(object): def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='', - tls_kind = 'PLAIN', 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'): @@ -64,7 +71,6 @@ class AuthLdap(object): 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. @@ -102,7 +108,8 @@ class AuthLdap(object): 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) + 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, @@ -114,7 +121,8 @@ class AuthLdap(object): for (dn, _attrs) in lobjects: try: server.simple_bind_s(dn, password) - attrs = server.search_ext_s(dn, ldap.SCOPE_BASE, '(objectClass=*)')[0][1] + attrs = server.search_ext_s(dn, ldap.SCOPE_BASE, + '(objectClass=*)')[0][1] break except ldap.INVALID_CREDENTIALS, e: @@ -130,6 +138,7 @@ class AuthLdap(object): 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") + raise LdapConnectionError("LDAP can't access " + "authentication server") return (dn, attrs) diff --git a/rhodecode/lib/utils.py b/rhodecode/lib/utils.py --- a/rhodecode/lib/utils.py +++ b/rhodecode/lib/utils.py @@ -44,7 +44,8 @@ from vcs.utils.lazy import LazyProperty from rhodecode.model import meta from rhodecode.model.caching_query import FromCache -from rhodecode.model.db import Repository, User, RhodeCodeUi, UserLog, Group +from rhodecode.model.db import Repository, User, RhodeCodeUi, UserLog, Group, \ + RhodeCodeSettings from rhodecode.model.repo import RepoModel from rhodecode.model.user import UserModel @@ -287,8 +288,7 @@ def set_rhodecode_config(config): :param config: """ - from rhodecode.model.settings import SettingsModel - hgsettings = SettingsModel().get_app_settings() + hgsettings = RhodeCodeSettings.get_app_settings() for k, v in hgsettings.items(): config[k] = v diff --git a/rhodecode/model/db.py b/rhodecode/model/db.py --- a/rhodecode/model/db.py +++ b/rhodecode/model/db.py @@ -65,6 +65,11 @@ class RhodeCodeSettings(Base): @classmethod + def get_by_name(cls, ldap_key): + return Session.query(cls)\ + .filter(cls.app_settings_name == ldap_key).scalar() + + @classmethod def get_app_settings(cls, cache=False): ret = Session.query(cls) @@ -88,7 +93,7 @@ class RhodeCodeSettings(Base): .all() fd = {} for row in ret: - fd.update({row.app_settings_name:str2bool(row.app_settings_value)}) + fd.update({row.app_settings_name:row.app_settings_value}) return fd diff --git a/rhodecode/model/settings.py b/rhodecode/model/settings.py deleted file mode 100644 --- a/rhodecode/model/settings.py +++ /dev/null @@ -1,103 +0,0 @@ -# -*- coding: utf-8 -*- -""" - rhodecode.model.settings - ~~~~~~~~~~~~~~~~~~~~~~~~ - - Settings model for RhodeCode - - :created on Nov 17, 2010 - :author: marcink - :copyright: (C) 2009-2011 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import logging - -from rhodecode.model import BaseModel -from rhodecode.model.caching_query import FromCache -from rhodecode.model.db import RhodeCodeSettings - -log = logging.getLogger(__name__) - -class SettingsModel(BaseModel): - """ - Settings model - """ - - def get(self, settings_key, cache=False): - r = self.sa.query(RhodeCodeSettings)\ - .filter(RhodeCodeSettings.app_settings_name == settings_key).scalar() - if cache: - r = r.options(FromCache("sql_cache_short", - "get_setting_%s" % settings_key)) - return r - - def get_app_settings(self, cache=False): - """Get's config from database, each config key is prefixed with - '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_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 - elif v in ['false', 'no', 'off', 'n', 'f', '0']: - v = False - - fd.update({row.app_settings_name:v}) - - return fd diff --git a/rhodecode/tests/functional/test_admin_ldap_settings.py b/rhodecode/tests/functional/test_admin_ldap_settings.py --- a/rhodecode/tests/functional/test_admin_ldap_settings.py +++ b/rhodecode/tests/functional/test_admin_ldap_settings.py @@ -3,5 +3,19 @@ from rhodecode.tests import * class TestLdapSettingsController(TestController): def test_index(self): - response = self.app.get(url(controller='admin/ldap_settings', action='index')) + self.log_user() + response = self.app.get(url(controller='admin/ldap_settings', + action='index')) # Test response... + + def test_ldap_save_settings(self): + pass + + def test_ldap_error_form(self): + pass + + def test_ldap_login(self): + pass + + def test_ldap_login_incorrect(self): + pass diff --git a/rhodecode/tests/functional/test_admin_settings.py b/rhodecode/tests/functional/test_admin_settings.py --- a/rhodecode/tests/functional/test_admin_settings.py +++ b/rhodecode/tests/functional/test_admin_settings.py @@ -1,7 +1,6 @@ from rhodecode.lib.auth import get_crypt_password, check_password -from rhodecode.model.db import User +from rhodecode.model.db import User, RhodeCodeSettings from rhodecode.tests import * -from rhodecode.model.settings import SettingsModel class TestAdminSettingsController(TestController): @@ -60,7 +59,7 @@ class TestAdminSettingsController(TestCo )) assert 'Updated application settings' in response.session['flash'][0][1], 'no flash message about success of change' - assert SettingsModel(self.sa).get_app_settings()['rhodecode_ga_code'] == new_ga_code, 'change not in database' + assert RhodeCodeSettings.get_app_settings()['rhodecode_ga_code'] == new_ga_code, 'change not in database' response = response.follow() assert """_gaq.push(['_setAccount', '%s']);""" % new_ga_code in response.body @@ -79,7 +78,7 @@ class TestAdminSettingsController(TestCo )) assert 'Updated application settings' in response.session['flash'][0][1], 'no flash message about success of change' - assert SettingsModel(self.sa).get_app_settings()['rhodecode_ga_code'] == new_ga_code, 'change not in database' + assert RhodeCodeSettings.get_app_settings()['rhodecode_ga_code'] == new_ga_code, 'change not in database' response = response.follow() assert """_gaq.push(['_setAccount', '%s']);""" % new_ga_code not in response.body @@ -100,7 +99,7 @@ class TestAdminSettingsController(TestCo assert 'Updated application settings' in response.session['flash'][0][1], 'no flash message about success of change' - assert SettingsModel(self.sa).get_app_settings()['rhodecode_title'] == new_title, 'change not in database' + assert RhodeCodeSettings.get_app_settings()['rhodecode_title'] == new_title, 'change not in database' response = response.follow() assert """

%s

""" % new_title in response.body