##// END OF EJS Templates
implements #60, ldap configuration and authentication....
marcink -
r705:9e9f1b91 beta
parent child Browse files
Show More
@@ -79,6 +79,8 b' def make_map(config):'
79
79
80 #ADMIN PERMISSIONS REST ROUTES
80 #ADMIN PERMISSIONS REST ROUTES
81 map.resource('permission', 'permissions', controller='admin/permissions', path_prefix='/_admin')
81 map.resource('permission', 'permissions', controller='admin/permissions', path_prefix='/_admin')
82 map.connect('permissions_ldap', '/_admin/permissions_ldap', controller='admin/permissions', action='ldap')
83
82
84
83 #ADMIN SETTINGS REST ROUTES
85 #ADMIN SETTINGS REST ROUTES
84 with map.submapper(path_prefix='/_admin', controller='admin/settings') as m:
86 with map.submapper(path_prefix='/_admin', controller='admin/settings') as m:
@@ -29,9 +29,11 b' from pylons.controllers.util import abor'
29 from pylons.i18n.translation import _
29 from pylons.i18n.translation import _
30 from rhodecode.lib import helpers as h
30 from rhodecode.lib import helpers as h
31 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
31 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
32 from rhodecode.lib.auth_ldap import LdapImportError
32 from rhodecode.lib.base import BaseController, render
33 from rhodecode.lib.base import BaseController, render
33 from rhodecode.model.forms import UserForm, DefaultPermissionsForm
34 from rhodecode.model.forms import LdapSettingsForm, DefaultPermissionsForm
34 from rhodecode.model.permission import PermissionModel
35 from rhodecode.model.permission import PermissionModel
36 from rhodecode.model.settings import SettingsModel
35 from rhodecode.model.user import UserModel
37 from rhodecode.model.user import UserModel
36 import formencode
38 import formencode
37 import logging
39 import logging
@@ -99,17 +101,19 b' class PermissionsController(BaseControll'
99 form_result = _form.to_python(dict(request.POST))
101 form_result = _form.to_python(dict(request.POST))
100 form_result.update({'perm_user_name':id})
102 form_result.update({'perm_user_name':id})
101 permission_model.update(form_result)
103 permission_model.update(form_result)
102 h.flash(_('Default permissions updated succesfully'),
104 h.flash(_('Default permissions updated successfully'),
103 category='success')
105 category='success')
104
106
105 except formencode.Invalid, errors:
107 except formencode.Invalid, errors:
106 c.perms_choices = self.perms_choices
108 c.perms_choices = self.perms_choices
107 c.register_choices = self.register_choices
109 c.register_choices = self.register_choices
108 c.create_choices = self.create_choices
110 c.create_choices = self.create_choices
111 defaults = errors.value
112 defaults.update(SettingsModel().get_ldap_settings())
109
113
110 return htmlfill.render(
114 return htmlfill.render(
111 render('admin/permissions/permissions.html'),
115 render('admin/permissions/permissions.html'),
112 defaults=errors.value,
116 defaults=defaults,
113 errors=errors.error_dict or {},
117 errors=errors.error_dict or {},
114 prefix_error=False,
118 prefix_error=False,
115 encoding="UTF-8")
119 encoding="UTF-8")
@@ -146,6 +150,7 b' class PermissionsController(BaseControll'
146 default_user = UserModel().get_by_username('default')
150 default_user = UserModel().get_by_username('default')
147 defaults = {'_method':'put',
151 defaults = {'_method':'put',
148 'anonymous':default_user.active}
152 'anonymous':default_user.active}
153 defaults.update(SettingsModel().get_ldap_settings())
149 for p in default_user.user_perms:
154 for p in default_user.user_perms:
150 if p.permission.permission_name.startswith('repository.'):
155 if p.permission.permission_name.startswith('repository.'):
151 defaults['default_perm'] = p.permission.permission_name
156 defaults['default_perm'] = p.permission.permission_name
@@ -163,3 +168,50 b' class PermissionsController(BaseControll'
163 force_defaults=True,)
168 force_defaults=True,)
164 else:
169 else:
165 return redirect(url('admin_home'))
170 return redirect(url('admin_home'))
171
172
173 def ldap(self, id_user='default'):
174 """
175 POST ldap create and store ldap settings
176 """
177
178 settings_model = SettingsModel()
179 _form = LdapSettingsForm()()
180
181 try:
182 form_result = _form.to_python(dict(request.POST))
183 try:
184
185 for k, v in form_result.items():
186 if k.startswith('ldap_'):
187 setting = settings_model.get(k)
188 setting.app_settings_value = v
189 self.sa.add(setting)
190
191 self.sa.commit()
192 h.flash(_('Ldap settings updated successfully'),
193 category='success')
194 except:
195 raise
196 except LdapImportError:
197 h.flash(_('Unable to activate ldap. The "ldap-python" library '
198 'is missing.'),
199 category='warning')
200
201 except formencode.Invalid, errors:
202 c.perms_choices = self.perms_choices
203 c.register_choices = self.register_choices
204 c.create_choices = self.create_choices
205
206 return htmlfill.render(
207 render('admin/permissions/permissions.html'),
208 defaults=errors.value,
209 errors=errors.error_dict or {},
210 prefix_error=False,
211 encoding="UTF-8")
212 except Exception:
213 log.error(traceback.format_exc())
214 h.flash(_('error occured during update of ldap settings'),
215 category='error')
216
217 return redirect(url('edit_permission', id=id_user))
@@ -31,14 +31,15 b' from rhodecode.lib import helpers as h'
31 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
31 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
32 HasPermissionAnyDecorator
32 HasPermissionAnyDecorator
33 from rhodecode.lib.base import BaseController, render
33 from rhodecode.lib.base import BaseController, render
34 from rhodecode.lib.celerylib import tasks, run_task
34 from rhodecode.lib.utils import repo2db_mapper, invalidate_cache, \
35 from rhodecode.lib.utils import repo2db_mapper, invalidate_cache, \
35 set_rhodecode_config, get_hg_settings, get_hg_ui_settings
36 set_rhodecode_config, get_hg_settings, get_hg_ui_settings
36 from rhodecode.model.db import RhodeCodeSettings, RhodeCodeUi, Repository
37 from rhodecode.model.db import RhodeCodeUi, Repository
37 from rhodecode.model.forms import UserForm, ApplicationSettingsForm, \
38 from rhodecode.model.forms import UserForm, ApplicationSettingsForm, \
38 ApplicationUiSettingsForm
39 ApplicationUiSettingsForm
39 from rhodecode.model.scm import ScmModel
40 from rhodecode.model.scm import ScmModel
41 from rhodecode.model.settings import SettingsModel
40 from rhodecode.model.user import UserModel
42 from rhodecode.model.user import UserModel
41 from rhodecode.lib.celerylib import tasks, run_task
42 from sqlalchemy import func
43 from sqlalchemy import func
43 import formencode
44 import formencode
44 import logging
45 import logging
@@ -118,18 +119,12 b' class SettingsController(BaseController)'
118 application_form = ApplicationSettingsForm()()
119 application_form = ApplicationSettingsForm()()
119 try:
120 try:
120 form_result = application_form.to_python(dict(request.POST))
121 form_result = application_form.to_python(dict(request.POST))
121
122 settings_model = SettingsModel()
122 try:
123 try:
123 hgsettings1 = self.sa.query(RhodeCodeSettings)\
124 hgsettings1 = settings_model.get('title')
124 .filter(RhodeCodeSettings.app_settings_name \
125 == 'title').one()
126
127 hgsettings1.app_settings_value = form_result['rhodecode_title']
125 hgsettings1.app_settings_value = form_result['rhodecode_title']
128
126
129 hgsettings2 = self.sa.query(RhodeCodeSettings)\
127 hgsettings2 = settings_model('realm')
130 .filter(RhodeCodeSettings.app_settings_name \
131 == 'realm').one()
132
133 hgsettings2.app_settings_value = form_result['rhodecode_realm']
128 hgsettings2.app_settings_value = form_result['rhodecode_realm']
134
129
135
130
@@ -25,6 +25,7 b' Created on April 4, 2010'
25 from pylons import config, session, url, request
25 from pylons import config, session, url, request
26 from pylons.controllers.util import abort, redirect
26 from pylons.controllers.util import abort, redirect
27 from rhodecode.lib.utils import get_repo_slug
27 from rhodecode.lib.utils import get_repo_slug
28 from rhodecode.lib.auth_ldap import AuthLdap, UsernameError, PasswordError
28 from rhodecode.model import meta
29 from rhodecode.model import meta
29 from rhodecode.model.user import UserModel
30 from rhodecode.model.user import UserModel
30 from rhodecode.model.caching_query import FromCache
31 from rhodecode.model.caching_query import FromCache
@@ -34,6 +35,7 b' import bcrypt'
34 from decorator import decorator
35 from decorator import decorator
35 import logging
36 import logging
36 import random
37 import random
38 import traceback
37
39
38 log = logging.getLogger(__name__)
40 log = logging.getLogger(__name__)
39
41
@@ -74,17 +76,18 b' def check_password(password, hashed):'
74
76
75 def authfunc(environ, username, password):
77 def authfunc(environ, username, password):
76 """
78 """
77 Authentication function used in Mercurial/Git/ and access controll,
79 Authentication function used in Mercurial/Git/ and access control,
78 firstly checks for db authentication then if ldap is enabled for ldap
80 firstly checks for db authentication then if ldap is enabled for ldap
79 authentication
81 authentication, also creates ldap user if not in database
82
80 :param environ: needed only for using in Basic auth, can be None
83 :param environ: needed only for using in Basic auth, can be None
81 :param username: username
84 :param username: username
82 :param password: password
85 :param password: password
83 """
86 """
87 user_model = UserModel()
88 user = user_model.get_by_username(username, cache=False)
84
89
85 user = UserModel().get_by_username(username, cache=False)
90 if user is not None and user.is_ldap is False:
86
87 if user:
88 if user.active:
91 if user.active:
89
92
90 if user.username == 'default' and user.active:
93 if user.username == 'default' and user.active:
@@ -97,6 +100,40 b' def authfunc(environ, username, password'
97 else:
100 else:
98 log.error('user %s is disabled', username)
101 log.error('user %s is disabled', username)
99
102
103
104 else:
105 from rhodecode.model.settings import SettingsModel
106 ldap_settings = SettingsModel().get_ldap_settings()
107
108 #======================================================================
109 # FALLBACK TO LDAP AUTH IN ENABLE
110 #======================================================================
111 if ldap_settings.get('ldap_active', False):
112 kwargs = {
113 'server':ldap_settings.get('ldap_host', ''),
114 'base_dn':ldap_settings.get('ldap_base_dn', ''),
115 'port':ldap_settings.get('ldap_port'),
116 'bind_dn':ldap_settings.get('ldap_dn_user'),
117 'bind_pass':ldap_settings.get('ldap_dn_pass'),
118 'use_ldaps':ldap_settings.get('ldap_ldaps'),
119 'ldap_version':3,
120 }
121 log.debug('Checking for ldap authentication')
122 try:
123 aldap = AuthLdap(**kwargs)
124 res = aldap.authenticate_ldap(username, password)
125
126 authenticated = res[1]['uid'][0] == username
127
128 if authenticated and user_model.create_ldap(username, password):
129 log.info('created new ldap user')
130
131 return authenticated
132 except (UsernameError, PasswordError):
133 return False
134 except:
135 log.error(traceback.format_exc())
136 return False
100 return False
137 return False
101
138
102 class AuthUser(object):
139 class AuthUser(object):
@@ -1,7 +1,3 b''
1 import logging
2 logging.basicConfig(level=logging.DEBUG)
3 log = logging.getLogger('ldap')
4
5 #==============================================================================
1 #==============================================================================
6 # LDAP
2 # LDAP
7 #Name = Just a description for the auth modes page
3 #Name = Just a description for the auth modes page
@@ -11,76 +7,87 b" log = logging.getLogger('ldap')"
11 #Account = DepartmentName\UserName (or UserName@MyDomain depending on AD server)
7 #Account = DepartmentName\UserName (or UserName@MyDomain depending on AD server)
12 #Password = <password>
8 #Password = <password>
13 #Base DN = DC=DepartmentName,DC=OrganizationName,DC=local
9 #Base DN = DC=DepartmentName,DC=OrganizationName,DC=local
14 #
15 #On-the-fly user creation = yes
16 #Attributes
17 # Login = sAMAccountName
18 # Firstname = givenName
19 # Lastname = sN
20 # Email = mail
21
10
22 #==============================================================================
11 #==============================================================================
23 class UsernameError(Exception):pass
12
24 class PasswordError(Exception):pass
13 from rhodecode.lib.exceptions import LdapImportError, UsernameError, \
14 PasswordError, ConnectionError
15 import logging
16
17 log = logging.getLogger(__name__)
25
18
26 LDAP_USE_LDAPS = False
19 try:
27 ldap_server_type = 'ldap'
20 import ldap
28 LDAP_SERVER_ADDRESS = 'myldap.com'
21 except ImportError:
29 LDAP_SERVER_PORT = '389'
22 pass
30
23
31 #USE FOR READ ONLY BIND TO LDAP SERVER
24 class AuthLdap(object):
32 LDAP_BIND_DN = ''
33 LDAP_BIND_PASS = ''
34
25
35 if LDAP_USE_LDAPS:ldap_server_type = ldap_server_type + 's'
26 def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='',
36 LDAP_SERVER = "%s://%s:%s" % (ldap_server_type,
27 use_ldaps=False, ldap_version=3):
37 LDAP_SERVER_ADDRESS,
28 self.ldap_version = ldap_version
38 LDAP_SERVER_PORT)
29 if use_ldaps:
39
30 port = port or 689
40 BASE_DN = "ou=people,dc=server,dc=com"
31 self.LDAP_USE_LDAPS = use_ldaps
41 AUTH_DN = "uid=%s,%s"
32 self.LDAP_SERVER_ADDRESS = server
33 self.LDAP_SERVER_PORT = port
42
34
43 def authenticate_ldap(username, password):
35 #USE FOR READ ONLY BIND TO LDAP SERVER
44 """Authenticate a user via LDAP and return his/her LDAP properties.
36 self.LDAP_BIND_DN = bind_dn
37 self.LDAP_BIND_PASS = bind_pass
45
38
46 Raises AuthenticationError if the credentials are rejected, or
39 ldap_server_type = 'ldap'
47 EnvironmentError if the LDAP server can't be reached.
40 if self.LDAP_USE_LDAPS:ldap_server_type = ldap_server_type + 's'
48 """
41 self.LDAP_SERVER = "%s://%s:%s" % (ldap_server_type,
49 try:
42 self.LDAP_SERVER_ADDRESS,
50 import ldap
43 self.LDAP_SERVER_PORT)
51 except ImportError:
44
52 raise Exception('Could not import ldap make sure You install python-ldap')
45 self.BASE_DN = base_dn
46 self.AUTH_DN = "uid=%s,%s"
53
47
54 from rhodecode.lib.helpers import chop_at
48 def authenticate_ldap(self, username, password):
55
49 """Authenticate a user via LDAP and return his/her LDAP properties.
56 uid = chop_at(username, "@%s" % LDAP_SERVER_ADDRESS)
50
57 dn = AUTH_DN % (uid, BASE_DN)
51 Raises AuthenticationError if the credentials are rejected, or
58 log.debug("Authenticating %r at %s", dn, LDAP_SERVER)
52 EnvironmentError if the LDAP server can't be reached.
59 if "," in username:
60 raise UsernameError("invalid character in username: ,")
61 try:
62 #ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, '/etc/openldap/cacerts')
63 server = ldap.initialize(LDAP_SERVER)
64 server.protocol = ldap.VERSION3
65
66 if LDAP_BIND_DN and LDAP_BIND_PASS:
67 server.simple_bind_s(AUTH_DN % (LDAP_BIND_DN,
68 LDAP_BIND_PASS),
69 password)
70
53
71 server.simple_bind_s(dn, password)
54 :param username: username
72 properties = server.search_s(dn, ldap.SCOPE_SUBTREE)
55 :param password: password
73 if not properties:
56 """
74 raise ldap.NO_SUCH_OBJECT()
57
75 except ldap.NO_SUCH_OBJECT, e:
58 from rhodecode.lib.helpers import chop_at
76 log.debug("LDAP says no such user '%s' (%s)", uid, username)
59
77 raise UsernameError()
60 uid = chop_at(username, "@%s" % self.LDAP_SERVER_ADDRESS)
78 except ldap.INVALID_CREDENTIALS, e:
61 dn = self.AUTH_DN % (uid, self.BASE_DN)
79 log.debug("LDAP rejected password for user '%s' (%s)", uid, username)
62 log.debug("Authenticating %r at %s", dn, self.LDAP_SERVER)
80 raise PasswordError()
63 if "," in username:
81 except ldap.SERVER_DOWN, e:
64 raise UsernameError("invalid character in username: ,")
82 raise EnvironmentError("can't access authentication server")
65 try:
83 return properties
66 ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, '/etc/openldap/cacerts')
67 ldap.set_option(ldap.OPT_NETWORK_TIMEOUT, 10)
68 server = ldap.initialize(self.LDAP_SERVER)
69 if self.ldap_version == 2:
70 server.protocol = ldap.VERSION2
71 else:
72 server.protocol = ldap.VERSION3
84
73
74 if self.LDAP_BIND_DN and self.LDAP_BIND_PASS:
75 server.simple_bind_s(self.AUTH_DN % (self.LDAP_BIND_DN,
76 self.BASE_DN),
77 self.LDAP_BIND_PASS)
85
78
86 print authenticate_ldap('test', 'test')
79 server.simple_bind_s(dn, password)
80 properties = server.search_s(dn, ldap.SCOPE_SUBTREE)
81 if not properties:
82 raise ldap.NO_SUCH_OBJECT()
83 except ldap.NO_SUCH_OBJECT, e:
84 log.debug("LDAP says no such user '%s' (%s)", uid, username)
85 raise UsernameError()
86 except ldap.INVALID_CREDENTIALS, e:
87 log.debug("LDAP rejected password for user '%s' (%s)", uid, username)
88 raise PasswordError()
89 except ldap.SERVER_DOWN, e:
90 raise ConnectionError("LDAP can't access authentication server")
91
92 return properties[0]
93
@@ -25,6 +25,7 b' from formencode.validators import Unicod'
25 from pylons import session
25 from pylons import session
26 from pylons.i18n.translation import _
26 from pylons.i18n.translation import _
27 from rhodecode.lib.auth import authfunc, get_crypt_password
27 from rhodecode.lib.auth import authfunc, get_crypt_password
28 from rhodecode.lib.exceptions import LdapImportError
28 from rhodecode.model import meta
29 from rhodecode.model import meta
29 from rhodecode.model.user import UserModel
30 from rhodecode.model.user import UserModel
30 from rhodecode.model.repo import RepoModel
31 from rhodecode.model.repo import RepoModel
@@ -82,7 +83,7 b' class ValidAuth(formencode.validators.Fa'
82 messages = {
83 messages = {
83 'invalid_password':_('invalid password'),
84 'invalid_password':_('invalid password'),
84 'invalid_login':_('invalid user name'),
85 'invalid_login':_('invalid user name'),
85 'disabled_account':_('Your acccount is disabled')
86 'disabled_account':_('Your account is disabled')
86
87
87 }
88 }
88 #error mapping
89 #error mapping
@@ -236,6 +237,16 b' class ValidSystemEmail(formencode.valida'
236
237
237 return value
238 return value
238
239
240 class LdapLibValidator(formencode.validators.FancyValidator):
241
242 def to_python(self, value, state):
243
244 try:
245 import ldap
246 except ImportError:
247 raise LdapImportError
248 return value
249
239 #===============================================================================
250 #===============================================================================
240 # FORMS
251 # FORMS
241 #===============================================================================
252 #===============================================================================
@@ -352,10 +363,26 b' def DefaultPermissionsForm(perms_choices'
352 class _DefaultPermissionsForm(formencode.Schema):
363 class _DefaultPermissionsForm(formencode.Schema):
353 allow_extra_fields = True
364 allow_extra_fields = True
354 filter_extra_fields = True
365 filter_extra_fields = True
355 overwrite_default = OneOf(['true', 'false'], if_missing='false')
366 overwrite_default = StringBoolean(if_missing=False)
356 anonymous = OneOf(['True', 'False'], if_missing=False)
367 anonymous = OneOf(['True', 'False'], if_missing=False)
357 default_perm = OneOf(perms_choices)
368 default_perm = OneOf(perms_choices)
358 default_register = OneOf(register_choices)
369 default_register = OneOf(register_choices)
359 default_create = OneOf(create_choices)
370 default_create = OneOf(create_choices)
360
371
361 return _DefaultPermissionsForm
372 return _DefaultPermissionsForm
373
374
375 def LdapSettingsForm():
376 class _LdapSettingsForm(formencode.Schema):
377 allow_extra_fields = True
378 filter_extra_fields = True
379 pre_validators = [LdapLibValidator]
380 ldap_active = StringBoolean(if_missing=False)
381 ldap_host = UnicodeString(strip=True,)
382 ldap_port = Number(strip=True,)
383 ldap_ldaps = StringBoolean(if_missing=False)
384 ldap_dn_user = UnicodeString(strip=True,)
385 ldap_dn_pass = UnicodeString(strip=True,)
386 ldap_base_dn = UnicodeString(strip=True,)
387
388 return _LdapSettingsForm
@@ -96,8 +96,3 b' class PermissionModel(object):'
96 log.error(traceback.format_exc())
96 log.error(traceback.format_exc())
97 self.sa.rollback()
97 self.sa.rollback()
98 raise
98 raise
99
100
101
102
103
@@ -51,6 +51,17 b' class SettingsModel(object):'
51
51
52
52
53 def get_ldap_settings(self):
53 def get_ldap_settings(self):
54 """
55 Returns ldap settings from database
56 :returns:
57 ldap_active
58 ldap_host
59 ldap_port
60 ldap_ldaps
61 ldap_dn_user
62 ldap_dn_pass
63 ldap_base_dn
64 """
54
65
55 r = self.sa.query(RhodeCodeSettings)\
66 r = self.sa.query(RhodeCodeSettings)\
56 .filter(RhodeCodeSettings.app_settings_name\
67 .filter(RhodeCodeSettings.app_settings_name\
@@ -68,6 +68,36 b' class UserModel(object):'
68 self.sa.rollback()
68 self.sa.rollback()
69 raise
69 raise
70
70
71 def create_ldap(self, username, password):
72 """
73 Checks if user is in database, if not creates this user marked
74 as ldap user
75 :param username:
76 :param password:
77 """
78
79 if self.get_by_username(username) is None:
80 try:
81 new_user = User()
82 new_user.username = username
83 new_user.password = password
84 new_user.email = '%s@ldap.server' % username
85 new_user.active = True
86 new_user.is_ldap = True
87 new_user.name = '%s@ldap' % username
88 new_user.lastname = ''
89
90
91 self.sa.add(new_user)
92 self.sa.commit()
93 return True
94 except:
95 log.error(traceback.format_exc())
96 self.sa.rollback()
97 raise
98
99 return False
100
71 def create_registration(self, form_data):
101 def create_registration(self, form_data):
72 from rhodecode.lib.celerylib import tasks, run_task
102 from rhodecode.lib.celerylib import tasks, run_task
73 try:
103 try:
General Comments 0
You need to be logged in to leave comments. Login now