##// END OF EJS Templates
ldap: handle more elegantly that python-ldap isn't installed when trying to use ldap...
Mads Kiilerich -
r3632:1ec67ddc beta
parent child Browse files
Show More
@@ -1,1029 +1,1030 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.auth
3 rhodecode.lib.auth
4 ~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~
5
5
6 authentication and permission libraries
6 authentication and permission libraries
7
7
8 :created_on: Apr 4, 2010
8 :created_on: Apr 4, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import random
26 import random
27 import logging
27 import logging
28 import traceback
28 import traceback
29 import hashlib
29 import hashlib
30
30
31 from tempfile import _RandomNameSequence
31 from tempfile import _RandomNameSequence
32 from decorator import decorator
32 from decorator import decorator
33
33
34 from pylons import config, url, request
34 from pylons import config, url, request
35 from pylons.controllers.util import abort, redirect
35 from pylons.controllers.util import abort, redirect
36 from pylons.i18n.translation import _
36 from pylons.i18n.translation import _
37 from sqlalchemy.orm.exc import ObjectDeletedError
37 from sqlalchemy.orm.exc import ObjectDeletedError
38
38
39 from rhodecode import __platform__, is_windows, is_unix
39 from rhodecode import __platform__, is_windows, is_unix
40 from rhodecode.model.meta import Session
40 from rhodecode.model.meta import Session
41
41
42 from rhodecode.lib.utils2 import str2bool, safe_unicode
42 from rhodecode.lib.utils2 import str2bool, safe_unicode
43 from rhodecode.lib.exceptions import LdapPasswordError, LdapUsernameError
43 from rhodecode.lib.exceptions import LdapPasswordError, LdapUsernameError,\
44 LdapImportError
44 from rhodecode.lib.utils import get_repo_slug, get_repos_group_slug
45 from rhodecode.lib.utils import get_repo_slug, get_repos_group_slug
45 from rhodecode.lib.auth_ldap import AuthLdap
46 from rhodecode.lib.auth_ldap import AuthLdap
46
47
47 from rhodecode.model import meta
48 from rhodecode.model import meta
48 from rhodecode.model.user import UserModel
49 from rhodecode.model.user import UserModel
49 from rhodecode.model.db import Permission, RhodeCodeSetting, User, UserIpMap
50 from rhodecode.model.db import Permission, RhodeCodeSetting, User, UserIpMap
50 from rhodecode.lib.caching_query import FromCache
51 from rhodecode.lib.caching_query import FromCache
51
52
52 log = logging.getLogger(__name__)
53 log = logging.getLogger(__name__)
53
54
54
55
55 class PasswordGenerator(object):
56 class PasswordGenerator(object):
56 """
57 """
57 This is a simple class for generating password from different sets of
58 This is a simple class for generating password from different sets of
58 characters
59 characters
59 usage::
60 usage::
60
61
61 passwd_gen = PasswordGenerator()
62 passwd_gen = PasswordGenerator()
62 #print 8-letter password containing only big and small letters
63 #print 8-letter password containing only big and small letters
63 of alphabet
64 of alphabet
64 passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
65 passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
65 """
66 """
66 ALPHABETS_NUM = r'''1234567890'''
67 ALPHABETS_NUM = r'''1234567890'''
67 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
68 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
68 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
69 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
69 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
70 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
70 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
71 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
71 + ALPHABETS_NUM + ALPHABETS_SPECIAL
72 + ALPHABETS_NUM + ALPHABETS_SPECIAL
72 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
73 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
73 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
74 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
74 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
75 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
75 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
76 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
76
77
77 def __init__(self, passwd=''):
78 def __init__(self, passwd=''):
78 self.passwd = passwd
79 self.passwd = passwd
79
80
80 def gen_password(self, length, type_=None):
81 def gen_password(self, length, type_=None):
81 if type_ is None:
82 if type_ is None:
82 type_ = self.ALPHABETS_FULL
83 type_ = self.ALPHABETS_FULL
83 self.passwd = ''.join([random.choice(type_) for _ in xrange(length)])
84 self.passwd = ''.join([random.choice(type_) for _ in xrange(length)])
84 return self.passwd
85 return self.passwd
85
86
86
87
87 class RhodeCodeCrypto(object):
88 class RhodeCodeCrypto(object):
88
89
89 @classmethod
90 @classmethod
90 def hash_string(cls, str_):
91 def hash_string(cls, str_):
91 """
92 """
92 Cryptographic function used for password hashing based on pybcrypt
93 Cryptographic function used for password hashing based on pybcrypt
93 or pycrypto in windows
94 or pycrypto in windows
94
95
95 :param password: password to hash
96 :param password: password to hash
96 """
97 """
97 if is_windows:
98 if is_windows:
98 from hashlib import sha256
99 from hashlib import sha256
99 return sha256(str_).hexdigest()
100 return sha256(str_).hexdigest()
100 elif is_unix:
101 elif is_unix:
101 import bcrypt
102 import bcrypt
102 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
103 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
103 else:
104 else:
104 raise Exception('Unknown or unsupported platform %s' \
105 raise Exception('Unknown or unsupported platform %s' \
105 % __platform__)
106 % __platform__)
106
107
107 @classmethod
108 @classmethod
108 def hash_check(cls, password, hashed):
109 def hash_check(cls, password, hashed):
109 """
110 """
110 Checks matching password with it's hashed value, runs different
111 Checks matching password with it's hashed value, runs different
111 implementation based on platform it runs on
112 implementation based on platform it runs on
112
113
113 :param password: password
114 :param password: password
114 :param hashed: password in hashed form
115 :param hashed: password in hashed form
115 """
116 """
116
117
117 if is_windows:
118 if is_windows:
118 from hashlib import sha256
119 from hashlib import sha256
119 return sha256(password).hexdigest() == hashed
120 return sha256(password).hexdigest() == hashed
120 elif is_unix:
121 elif is_unix:
121 import bcrypt
122 import bcrypt
122 return bcrypt.hashpw(password, hashed) == hashed
123 return bcrypt.hashpw(password, hashed) == hashed
123 else:
124 else:
124 raise Exception('Unknown or unsupported platform %s' \
125 raise Exception('Unknown or unsupported platform %s' \
125 % __platform__)
126 % __platform__)
126
127
127
128
128 def get_crypt_password(password):
129 def get_crypt_password(password):
129 return RhodeCodeCrypto.hash_string(password)
130 return RhodeCodeCrypto.hash_string(password)
130
131
131
132
132 def check_password(password, hashed):
133 def check_password(password, hashed):
133 return RhodeCodeCrypto.hash_check(password, hashed)
134 return RhodeCodeCrypto.hash_check(password, hashed)
134
135
135
136
136 def generate_api_key(str_, salt=None):
137 def generate_api_key(str_, salt=None):
137 """
138 """
138 Generates API KEY from given string
139 Generates API KEY from given string
139
140
140 :param str_:
141 :param str_:
141 :param salt:
142 :param salt:
142 """
143 """
143
144
144 if salt is None:
145 if salt is None:
145 salt = _RandomNameSequence().next()
146 salt = _RandomNameSequence().next()
146
147
147 return hashlib.sha1(str_ + salt).hexdigest()
148 return hashlib.sha1(str_ + salt).hexdigest()
148
149
149
150
150 def authfunc(environ, username, password):
151 def authfunc(environ, username, password):
151 """
152 """
152 Dummy authentication wrapper function used in Mercurial and Git for
153 Dummy authentication wrapper function used in Mercurial and Git for
153 access control.
154 access control.
154
155
155 :param environ: needed only for using in Basic auth
156 :param environ: needed only for using in Basic auth
156 """
157 """
157 return authenticate(username, password)
158 return authenticate(username, password)
158
159
159
160
160 def authenticate(username, password):
161 def authenticate(username, password):
161 """
162 """
162 Authentication function used for access control,
163 Authentication function used for access control,
163 firstly checks for db authentication then if ldap is enabled for ldap
164 firstly checks for db authentication then if ldap is enabled for ldap
164 authentication, also creates ldap user if not in database
165 authentication, also creates ldap user if not in database
165
166
166 :param username: username
167 :param username: username
167 :param password: password
168 :param password: password
168 """
169 """
169
170
170 user_model = UserModel()
171 user_model = UserModel()
171 user = User.get_by_username(username)
172 user = User.get_by_username(username)
172
173
173 log.debug('Authenticating user using RhodeCode account')
174 log.debug('Authenticating user using RhodeCode account')
174 if user is not None and not user.ldap_dn:
175 if user is not None and not user.ldap_dn:
175 if user.active:
176 if user.active:
176 if user.username == 'default' and user.active:
177 if user.username == 'default' and user.active:
177 log.info('user %s authenticated correctly as anonymous user' %
178 log.info('user %s authenticated correctly as anonymous user' %
178 username)
179 username)
179 return True
180 return True
180
181
181 elif user.username == username and check_password(password,
182 elif user.username == username and check_password(password,
182 user.password):
183 user.password):
183 log.info('user %s authenticated correctly' % username)
184 log.info('user %s authenticated correctly' % username)
184 return True
185 return True
185 else:
186 else:
186 log.warning('user %s tried auth but is disabled' % username)
187 log.warning('user %s tried auth but is disabled' % username)
187
188
188 else:
189 else:
189 log.debug('Regular authentication failed')
190 log.debug('Regular authentication failed')
190 user_obj = User.get_by_username(username, case_insensitive=True)
191 user_obj = User.get_by_username(username, case_insensitive=True)
191
192
192 if user_obj is not None and not user_obj.ldap_dn:
193 if user_obj is not None and not user_obj.ldap_dn:
193 log.debug('this user already exists as non ldap')
194 log.debug('this user already exists as non ldap')
194 return False
195 return False
195
196
196 ldap_settings = RhodeCodeSetting.get_ldap_settings()
197 ldap_settings = RhodeCodeSetting.get_ldap_settings()
197 #======================================================================
198 #======================================================================
198 # FALLBACK TO LDAP AUTH IF ENABLE
199 # FALLBACK TO LDAP AUTH IF ENABLE
199 #======================================================================
200 #======================================================================
200 if str2bool(ldap_settings.get('ldap_active')):
201 if str2bool(ldap_settings.get('ldap_active')):
201 log.debug("Authenticating user using ldap")
202 log.debug("Authenticating user using ldap")
202 kwargs = {
203 kwargs = {
203 'server': ldap_settings.get('ldap_host', ''),
204 'server': ldap_settings.get('ldap_host', ''),
204 'base_dn': ldap_settings.get('ldap_base_dn', ''),
205 'base_dn': ldap_settings.get('ldap_base_dn', ''),
205 'port': ldap_settings.get('ldap_port'),
206 'port': ldap_settings.get('ldap_port'),
206 'bind_dn': ldap_settings.get('ldap_dn_user'),
207 'bind_dn': ldap_settings.get('ldap_dn_user'),
207 'bind_pass': ldap_settings.get('ldap_dn_pass'),
208 'bind_pass': ldap_settings.get('ldap_dn_pass'),
208 'tls_kind': ldap_settings.get('ldap_tls_kind'),
209 'tls_kind': ldap_settings.get('ldap_tls_kind'),
209 'tls_reqcert': ldap_settings.get('ldap_tls_reqcert'),
210 'tls_reqcert': ldap_settings.get('ldap_tls_reqcert'),
210 'ldap_filter': ldap_settings.get('ldap_filter'),
211 'ldap_filter': ldap_settings.get('ldap_filter'),
211 'search_scope': ldap_settings.get('ldap_search_scope'),
212 'search_scope': ldap_settings.get('ldap_search_scope'),
212 'attr_login': ldap_settings.get('ldap_attr_login'),
213 'attr_login': ldap_settings.get('ldap_attr_login'),
213 'ldap_version': 3,
214 'ldap_version': 3,
214 }
215 }
215 log.debug('Checking for ldap authentication')
216 log.debug('Checking for ldap authentication')
216 try:
217 try:
217 aldap = AuthLdap(**kwargs)
218 aldap = AuthLdap(**kwargs)
218 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username,
219 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username,
219 password)
220 password)
220 log.debug('Got ldap DN response %s' % user_dn)
221 log.debug('Got ldap DN response %s' % user_dn)
221
222
222 get_ldap_attr = lambda k: ldap_attrs.get(ldap_settings\
223 get_ldap_attr = lambda k: ldap_attrs.get(ldap_settings\
223 .get(k), [''])[0]
224 .get(k), [''])[0]
224
225
225 user_attrs = {
226 user_attrs = {
226 'name': safe_unicode(get_ldap_attr('ldap_attr_firstname')),
227 'name': safe_unicode(get_ldap_attr('ldap_attr_firstname')),
227 'lastname': safe_unicode(get_ldap_attr('ldap_attr_lastname')),
228 'lastname': safe_unicode(get_ldap_attr('ldap_attr_lastname')),
228 'email': get_ldap_attr('ldap_attr_email'),
229 'email': get_ldap_attr('ldap_attr_email'),
229 'active': 'hg.register.auto_activate' in User\
230 'active': 'hg.register.auto_activate' in User\
230 .get_by_username('default').AuthUser.permissions['global']
231 .get_by_username('default').AuthUser.permissions['global']
231 }
232 }
232
233
233 # don't store LDAP password since we don't need it. Override
234 # don't store LDAP password since we don't need it. Override
234 # with some random generated password
235 # with some random generated password
235 _password = PasswordGenerator().gen_password(length=8)
236 _password = PasswordGenerator().gen_password(length=8)
236 # create this user on the fly if it doesn't exist in rhodecode
237 # create this user on the fly if it doesn't exist in rhodecode
237 # database
238 # database
238 if user_model.create_ldap(username, _password, user_dn,
239 if user_model.create_ldap(username, _password, user_dn,
239 user_attrs):
240 user_attrs):
240 log.info('created new ldap user %s' % username)
241 log.info('created new ldap user %s' % username)
241
242
242 Session().commit()
243 Session().commit()
243 return True
244 return True
244 except (LdapUsernameError, LdapPasswordError,):
245 except (LdapUsernameError, LdapPasswordError, LdapImportError):
245 pass
246 pass
246 except (Exception,):
247 except (Exception,):
247 log.error(traceback.format_exc())
248 log.error(traceback.format_exc())
248 pass
249 pass
249 return False
250 return False
250
251
251
252
252 def login_container_auth(username):
253 def login_container_auth(username):
253 user = User.get_by_username(username)
254 user = User.get_by_username(username)
254 if user is None:
255 if user is None:
255 user_attrs = {
256 user_attrs = {
256 'name': username,
257 'name': username,
257 'lastname': None,
258 'lastname': None,
258 'email': None,
259 'email': None,
259 'active': 'hg.register.auto_activate' in User\
260 'active': 'hg.register.auto_activate' in User\
260 .get_by_username('default').AuthUser.permissions['global']
261 .get_by_username('default').AuthUser.permissions['global']
261 }
262 }
262 user = UserModel().create_for_container_auth(username, user_attrs)
263 user = UserModel().create_for_container_auth(username, user_attrs)
263 if not user:
264 if not user:
264 return None
265 return None
265 log.info('User %s was created by container authentication' % username)
266 log.info('User %s was created by container authentication' % username)
266
267
267 if not user.active:
268 if not user.active:
268 return None
269 return None
269
270
270 user.update_lastlogin()
271 user.update_lastlogin()
271 Session().commit()
272 Session().commit()
272
273
273 log.debug('User %s is now logged in by container authentication',
274 log.debug('User %s is now logged in by container authentication',
274 user.username)
275 user.username)
275 return user
276 return user
276
277
277
278
278 def get_container_username(environ, config, clean_username=False):
279 def get_container_username(environ, config, clean_username=False):
279 """
280 """
280 Get's the container_auth username (or email). It tries to get username
281 Get's the container_auth username (or email). It tries to get username
281 from REMOTE_USER if container_auth_enabled is enabled, if that fails
282 from REMOTE_USER if container_auth_enabled is enabled, if that fails
282 it tries to get username from HTTP_X_FORWARDED_USER if proxypass_auth_enabled
283 it tries to get username from HTTP_X_FORWARDED_USER if proxypass_auth_enabled
283 is enabled. clean_username extracts the username from this data if it's
284 is enabled. clean_username extracts the username from this data if it's
284 having @ in it.
285 having @ in it.
285
286
286 :param environ:
287 :param environ:
287 :param config:
288 :param config:
288 :param clean_username:
289 :param clean_username:
289 """
290 """
290 username = None
291 username = None
291
292
292 if str2bool(config.get('container_auth_enabled', False)):
293 if str2bool(config.get('container_auth_enabled', False)):
293 from paste.httpheaders import REMOTE_USER
294 from paste.httpheaders import REMOTE_USER
294 username = REMOTE_USER(environ)
295 username = REMOTE_USER(environ)
295 log.debug('extracted REMOTE_USER:%s' % (username))
296 log.debug('extracted REMOTE_USER:%s' % (username))
296
297
297 if not username and str2bool(config.get('proxypass_auth_enabled', False)):
298 if not username and str2bool(config.get('proxypass_auth_enabled', False)):
298 username = environ.get('HTTP_X_FORWARDED_USER')
299 username = environ.get('HTTP_X_FORWARDED_USER')
299 log.debug('extracted HTTP_X_FORWARDED_USER:%s' % (username))
300 log.debug('extracted HTTP_X_FORWARDED_USER:%s' % (username))
300
301
301 if username and clean_username:
302 if username and clean_username:
302 # Removing realm and domain from username
303 # Removing realm and domain from username
303 username = username.partition('@')[0]
304 username = username.partition('@')[0]
304 username = username.rpartition('\\')[2]
305 username = username.rpartition('\\')[2]
305 log.debug('Received username %s from container' % username)
306 log.debug('Received username %s from container' % username)
306
307
307 return username
308 return username
308
309
309
310
310 class CookieStoreWrapper(object):
311 class CookieStoreWrapper(object):
311
312
312 def __init__(self, cookie_store):
313 def __init__(self, cookie_store):
313 self.cookie_store = cookie_store
314 self.cookie_store = cookie_store
314
315
315 def __repr__(self):
316 def __repr__(self):
316 return 'CookieStore<%s>' % (self.cookie_store)
317 return 'CookieStore<%s>' % (self.cookie_store)
317
318
318 def get(self, key, other=None):
319 def get(self, key, other=None):
319 if isinstance(self.cookie_store, dict):
320 if isinstance(self.cookie_store, dict):
320 return self.cookie_store.get(key, other)
321 return self.cookie_store.get(key, other)
321 elif isinstance(self.cookie_store, AuthUser):
322 elif isinstance(self.cookie_store, AuthUser):
322 return self.cookie_store.__dict__.get(key, other)
323 return self.cookie_store.__dict__.get(key, other)
323
324
324
325
325 class AuthUser(object):
326 class AuthUser(object):
326 """
327 """
327 A simple object that handles all attributes of user in RhodeCode
328 A simple object that handles all attributes of user in RhodeCode
328
329
329 It does lookup based on API key,given user, or user present in session
330 It does lookup based on API key,given user, or user present in session
330 Then it fills all required information for such user. It also checks if
331 Then it fills all required information for such user. It also checks if
331 anonymous access is enabled and if so, it returns default user as logged
332 anonymous access is enabled and if so, it returns default user as logged
332 in
333 in
333 """
334 """
334
335
335 def __init__(self, user_id=None, api_key=None, username=None, ip_addr=None):
336 def __init__(self, user_id=None, api_key=None, username=None, ip_addr=None):
336
337
337 self.user_id = user_id
338 self.user_id = user_id
338 self.api_key = None
339 self.api_key = None
339 self.username = username
340 self.username = username
340 self.ip_addr = ip_addr
341 self.ip_addr = ip_addr
341
342
342 self.name = ''
343 self.name = ''
343 self.lastname = ''
344 self.lastname = ''
344 self.email = ''
345 self.email = ''
345 self.is_authenticated = False
346 self.is_authenticated = False
346 self.admin = False
347 self.admin = False
347 self.inherit_default_permissions = False
348 self.inherit_default_permissions = False
348 self.permissions = {}
349 self.permissions = {}
349 self._api_key = api_key
350 self._api_key = api_key
350 self.propagate_data()
351 self.propagate_data()
351 self._instance = None
352 self._instance = None
352
353
353 def propagate_data(self):
354 def propagate_data(self):
354 user_model = UserModel()
355 user_model = UserModel()
355 self.anonymous_user = User.get_by_username('default', cache=True)
356 self.anonymous_user = User.get_by_username('default', cache=True)
356 is_user_loaded = False
357 is_user_loaded = False
357
358
358 # try go get user by api key
359 # try go get user by api key
359 if self._api_key and self._api_key != self.anonymous_user.api_key:
360 if self._api_key and self._api_key != self.anonymous_user.api_key:
360 log.debug('Auth User lookup by API KEY %s' % self._api_key)
361 log.debug('Auth User lookup by API KEY %s' % self._api_key)
361 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
362 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
362 # lookup by userid
363 # lookup by userid
363 elif (self.user_id is not None and
364 elif (self.user_id is not None and
364 self.user_id != self.anonymous_user.user_id):
365 self.user_id != self.anonymous_user.user_id):
365 log.debug('Auth User lookup by USER ID %s' % self.user_id)
366 log.debug('Auth User lookup by USER ID %s' % self.user_id)
366 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
367 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
367 # lookup by username
368 # lookup by username
368 elif self.username and \
369 elif self.username and \
369 str2bool(config.get('container_auth_enabled', False)):
370 str2bool(config.get('container_auth_enabled', False)):
370
371
371 log.debug('Auth User lookup by USER NAME %s' % self.username)
372 log.debug('Auth User lookup by USER NAME %s' % self.username)
372 dbuser = login_container_auth(self.username)
373 dbuser = login_container_auth(self.username)
373 if dbuser is not None:
374 if dbuser is not None:
374 log.debug('filling all attributes to object')
375 log.debug('filling all attributes to object')
375 for k, v in dbuser.get_dict().items():
376 for k, v in dbuser.get_dict().items():
376 setattr(self, k, v)
377 setattr(self, k, v)
377 self.set_authenticated()
378 self.set_authenticated()
378 is_user_loaded = True
379 is_user_loaded = True
379 else:
380 else:
380 log.debug('No data in %s that could been used to log in' % self)
381 log.debug('No data in %s that could been used to log in' % self)
381
382
382 if not is_user_loaded:
383 if not is_user_loaded:
383 # if we cannot authenticate user try anonymous
384 # if we cannot authenticate user try anonymous
384 if self.anonymous_user.active:
385 if self.anonymous_user.active:
385 user_model.fill_data(self, user_id=self.anonymous_user.user_id)
386 user_model.fill_data(self, user_id=self.anonymous_user.user_id)
386 # then we set this user is logged in
387 # then we set this user is logged in
387 self.is_authenticated = True
388 self.is_authenticated = True
388 else:
389 else:
389 self.user_id = None
390 self.user_id = None
390 self.username = None
391 self.username = None
391 self.is_authenticated = False
392 self.is_authenticated = False
392
393
393 if not self.username:
394 if not self.username:
394 self.username = 'None'
395 self.username = 'None'
395
396
396 log.debug('Auth User is now %s' % self)
397 log.debug('Auth User is now %s' % self)
397 user_model.fill_perms(self)
398 user_model.fill_perms(self)
398
399
399 @property
400 @property
400 def is_admin(self):
401 def is_admin(self):
401 return self.admin
402 return self.admin
402
403
403 @property
404 @property
404 def repos_admin(self):
405 def repos_admin(self):
405 """
406 """
406 Returns list of repositories you're an admin of
407 Returns list of repositories you're an admin of
407 """
408 """
408 return [x[0] for x in self.permissions['repositories'].iteritems()
409 return [x[0] for x in self.permissions['repositories'].iteritems()
409 if x[1] == 'repository.admin']
410 if x[1] == 'repository.admin']
410
411
411 @property
412 @property
412 def groups_admin(self):
413 def groups_admin(self):
413 """
414 """
414 Returns list of repository groups you're an admin of
415 Returns list of repository groups you're an admin of
415 """
416 """
416 return [x[0] for x in self.permissions['repositories_groups'].iteritems()
417 return [x[0] for x in self.permissions['repositories_groups'].iteritems()
417 if x[1] == 'group.admin']
418 if x[1] == 'group.admin']
418
419
419 @property
420 @property
420 def ip_allowed(self):
421 def ip_allowed(self):
421 """
422 """
422 Checks if ip_addr used in constructor is allowed from defined list of
423 Checks if ip_addr used in constructor is allowed from defined list of
423 allowed ip_addresses for user
424 allowed ip_addresses for user
424
425
425 :returns: boolean, True if ip is in allowed ip range
426 :returns: boolean, True if ip is in allowed ip range
426 """
427 """
427 #check IP
428 #check IP
428 allowed_ips = AuthUser.get_allowed_ips(self.user_id, cache=True)
429 allowed_ips = AuthUser.get_allowed_ips(self.user_id, cache=True)
429 if check_ip_access(source_ip=self.ip_addr, allowed_ips=allowed_ips):
430 if check_ip_access(source_ip=self.ip_addr, allowed_ips=allowed_ips):
430 log.debug('IP:%s is in range of %s' % (self.ip_addr, allowed_ips))
431 log.debug('IP:%s is in range of %s' % (self.ip_addr, allowed_ips))
431 return True
432 return True
432 else:
433 else:
433 log.info('Access for IP:%s forbidden, '
434 log.info('Access for IP:%s forbidden, '
434 'not in %s' % (self.ip_addr, allowed_ips))
435 'not in %s' % (self.ip_addr, allowed_ips))
435 return False
436 return False
436
437
437 def __repr__(self):
438 def __repr__(self):
438 return "<AuthUser('id:%s:%s|%s')>" % (self.user_id, self.username,
439 return "<AuthUser('id:%s:%s|%s')>" % (self.user_id, self.username,
439 self.is_authenticated)
440 self.is_authenticated)
440
441
441 def set_authenticated(self, authenticated=True):
442 def set_authenticated(self, authenticated=True):
442 if self.user_id != self.anonymous_user.user_id:
443 if self.user_id != self.anonymous_user.user_id:
443 self.is_authenticated = authenticated
444 self.is_authenticated = authenticated
444
445
445 def get_cookie_store(self):
446 def get_cookie_store(self):
446 return {'username': self.username,
447 return {'username': self.username,
447 'user_id': self.user_id,
448 'user_id': self.user_id,
448 'is_authenticated': self.is_authenticated}
449 'is_authenticated': self.is_authenticated}
449
450
450 @classmethod
451 @classmethod
451 def from_cookie_store(cls, cookie_store):
452 def from_cookie_store(cls, cookie_store):
452 """
453 """
453 Creates AuthUser from a cookie store
454 Creates AuthUser from a cookie store
454
455
455 :param cls:
456 :param cls:
456 :param cookie_store:
457 :param cookie_store:
457 """
458 """
458 user_id = cookie_store.get('user_id')
459 user_id = cookie_store.get('user_id')
459 username = cookie_store.get('username')
460 username = cookie_store.get('username')
460 api_key = cookie_store.get('api_key')
461 api_key = cookie_store.get('api_key')
461 return AuthUser(user_id, api_key, username)
462 return AuthUser(user_id, api_key, username)
462
463
463 @classmethod
464 @classmethod
464 def get_allowed_ips(cls, user_id, cache=False):
465 def get_allowed_ips(cls, user_id, cache=False):
465 _set = set()
466 _set = set()
466 user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id)
467 user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id)
467 if cache:
468 if cache:
468 user_ips = user_ips.options(FromCache("sql_cache_short",
469 user_ips = user_ips.options(FromCache("sql_cache_short",
469 "get_user_ips_%s" % user_id))
470 "get_user_ips_%s" % user_id))
470 for ip in user_ips:
471 for ip in user_ips:
471 try:
472 try:
472 _set.add(ip.ip_addr)
473 _set.add(ip.ip_addr)
473 except ObjectDeletedError:
474 except ObjectDeletedError:
474 # since we use heavy caching sometimes it happens that we get
475 # since we use heavy caching sometimes it happens that we get
475 # deleted objects here, we just skip them
476 # deleted objects here, we just skip them
476 pass
477 pass
477 return _set or set(['0.0.0.0/0', '::/0'])
478 return _set or set(['0.0.0.0/0', '::/0'])
478
479
479
480
480 def set_available_permissions(config):
481 def set_available_permissions(config):
481 """
482 """
482 This function will propagate pylons globals with all available defined
483 This function will propagate pylons globals with all available defined
483 permission given in db. We don't want to check each time from db for new
484 permission given in db. We don't want to check each time from db for new
484 permissions since adding a new permission also requires application restart
485 permissions since adding a new permission also requires application restart
485 ie. to decorate new views with the newly created permission
486 ie. to decorate new views with the newly created permission
486
487
487 :param config: current pylons config instance
488 :param config: current pylons config instance
488
489
489 """
490 """
490 log.info('getting information about all available permissions')
491 log.info('getting information about all available permissions')
491 try:
492 try:
492 sa = meta.Session
493 sa = meta.Session
493 all_perms = sa.query(Permission).all()
494 all_perms = sa.query(Permission).all()
494 except Exception:
495 except Exception:
495 pass
496 pass
496 finally:
497 finally:
497 meta.Session.remove()
498 meta.Session.remove()
498
499
499 config['available_permissions'] = [x.permission_name for x in all_perms]
500 config['available_permissions'] = [x.permission_name for x in all_perms]
500
501
501
502
502 #==============================================================================
503 #==============================================================================
503 # CHECK DECORATORS
504 # CHECK DECORATORS
504 #==============================================================================
505 #==============================================================================
505 class LoginRequired(object):
506 class LoginRequired(object):
506 """
507 """
507 Must be logged in to execute this function else
508 Must be logged in to execute this function else
508 redirect to login page
509 redirect to login page
509
510
510 :param api_access: if enabled this checks only for valid auth token
511 :param api_access: if enabled this checks only for valid auth token
511 and grants access based on valid token
512 and grants access based on valid token
512 """
513 """
513
514
514 def __init__(self, api_access=False):
515 def __init__(self, api_access=False):
515 self.api_access = api_access
516 self.api_access = api_access
516
517
517 def __call__(self, func):
518 def __call__(self, func):
518 return decorator(self.__wrapper, func)
519 return decorator(self.__wrapper, func)
519
520
520 def __wrapper(self, func, *fargs, **fkwargs):
521 def __wrapper(self, func, *fargs, **fkwargs):
521 cls = fargs[0]
522 cls = fargs[0]
522 user = cls.rhodecode_user
523 user = cls.rhodecode_user
523 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
524 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
524
525
525 #check IP
526 #check IP
526 ip_access_ok = True
527 ip_access_ok = True
527 if not user.ip_allowed:
528 if not user.ip_allowed:
528 from rhodecode.lib import helpers as h
529 from rhodecode.lib import helpers as h
529 h.flash(h.literal(_('IP %s not allowed' % (user.ip_addr))),
530 h.flash(h.literal(_('IP %s not allowed' % (user.ip_addr))),
530 category='warning')
531 category='warning')
531 ip_access_ok = False
532 ip_access_ok = False
532
533
533 api_access_ok = False
534 api_access_ok = False
534 if self.api_access:
535 if self.api_access:
535 log.debug('Checking API KEY access for %s' % cls)
536 log.debug('Checking API KEY access for %s' % cls)
536 if user.api_key == request.GET.get('api_key'):
537 if user.api_key == request.GET.get('api_key'):
537 api_access_ok = True
538 api_access_ok = True
538 else:
539 else:
539 log.debug("API KEY token not valid")
540 log.debug("API KEY token not valid")
540
541
541 log.debug('Checking if %s is authenticated @ %s' % (user.username, loc))
542 log.debug('Checking if %s is authenticated @ %s' % (user.username, loc))
542 if (user.is_authenticated or api_access_ok) and ip_access_ok:
543 if (user.is_authenticated or api_access_ok) and ip_access_ok:
543 reason = 'RegularAuth' if user.is_authenticated else 'APIAuth'
544 reason = 'RegularAuth' if user.is_authenticated else 'APIAuth'
544 log.info('user %s is authenticated and granted access to %s '
545 log.info('user %s is authenticated and granted access to %s '
545 'using %s' % (user.username, loc, reason)
546 'using %s' % (user.username, loc, reason)
546 )
547 )
547 return func(*fargs, **fkwargs)
548 return func(*fargs, **fkwargs)
548 else:
549 else:
549 log.warn('user %s NOT authenticated on func: %s' % (
550 log.warn('user %s NOT authenticated on func: %s' % (
550 user, loc)
551 user, loc)
551 )
552 )
552 p = url.current()
553 p = url.current()
553
554
554 log.debug('redirecting to login page with %s' % p)
555 log.debug('redirecting to login page with %s' % p)
555 return redirect(url('login_home', came_from=p))
556 return redirect(url('login_home', came_from=p))
556
557
557
558
558 class NotAnonymous(object):
559 class NotAnonymous(object):
559 """
560 """
560 Must be logged in to execute this function else
561 Must be logged in to execute this function else
561 redirect to login page"""
562 redirect to login page"""
562
563
563 def __call__(self, func):
564 def __call__(self, func):
564 return decorator(self.__wrapper, func)
565 return decorator(self.__wrapper, func)
565
566
566 def __wrapper(self, func, *fargs, **fkwargs):
567 def __wrapper(self, func, *fargs, **fkwargs):
567 cls = fargs[0]
568 cls = fargs[0]
568 self.user = cls.rhodecode_user
569 self.user = cls.rhodecode_user
569
570
570 log.debug('Checking if user is not anonymous @%s' % cls)
571 log.debug('Checking if user is not anonymous @%s' % cls)
571
572
572 anonymous = self.user.username == 'default'
573 anonymous = self.user.username == 'default'
573
574
574 if anonymous:
575 if anonymous:
575 p = url.current()
576 p = url.current()
576
577
577 import rhodecode.lib.helpers as h
578 import rhodecode.lib.helpers as h
578 h.flash(_('You need to be a registered user to '
579 h.flash(_('You need to be a registered user to '
579 'perform this action'),
580 'perform this action'),
580 category='warning')
581 category='warning')
581 return redirect(url('login_home', came_from=p))
582 return redirect(url('login_home', came_from=p))
582 else:
583 else:
583 return func(*fargs, **fkwargs)
584 return func(*fargs, **fkwargs)
584
585
585
586
586 class PermsDecorator(object):
587 class PermsDecorator(object):
587 """Base class for controller decorators"""
588 """Base class for controller decorators"""
588
589
589 def __init__(self, *required_perms):
590 def __init__(self, *required_perms):
590 available_perms = config['available_permissions']
591 available_perms = config['available_permissions']
591 for perm in required_perms:
592 for perm in required_perms:
592 if perm not in available_perms:
593 if perm not in available_perms:
593 raise Exception("'%s' permission is not defined" % perm)
594 raise Exception("'%s' permission is not defined" % perm)
594 self.required_perms = set(required_perms)
595 self.required_perms = set(required_perms)
595 self.user_perms = None
596 self.user_perms = None
596
597
597 def __call__(self, func):
598 def __call__(self, func):
598 return decorator(self.__wrapper, func)
599 return decorator(self.__wrapper, func)
599
600
600 def __wrapper(self, func, *fargs, **fkwargs):
601 def __wrapper(self, func, *fargs, **fkwargs):
601 cls = fargs[0]
602 cls = fargs[0]
602 self.user = cls.rhodecode_user
603 self.user = cls.rhodecode_user
603 self.user_perms = self.user.permissions
604 self.user_perms = self.user.permissions
604 log.debug('checking %s permissions %s for %s %s',
605 log.debug('checking %s permissions %s for %s %s',
605 self.__class__.__name__, self.required_perms, cls, self.user)
606 self.__class__.__name__, self.required_perms, cls, self.user)
606
607
607 if self.check_permissions():
608 if self.check_permissions():
608 log.debug('Permission granted for %s %s' % (cls, self.user))
609 log.debug('Permission granted for %s %s' % (cls, self.user))
609 return func(*fargs, **fkwargs)
610 return func(*fargs, **fkwargs)
610
611
611 else:
612 else:
612 log.debug('Permission denied for %s %s' % (cls, self.user))
613 log.debug('Permission denied for %s %s' % (cls, self.user))
613 anonymous = self.user.username == 'default'
614 anonymous = self.user.username == 'default'
614
615
615 if anonymous:
616 if anonymous:
616 p = url.current()
617 p = url.current()
617
618
618 import rhodecode.lib.helpers as h
619 import rhodecode.lib.helpers as h
619 h.flash(_('You need to be a signed in to '
620 h.flash(_('You need to be a signed in to '
620 'view this page'),
621 'view this page'),
621 category='warning')
622 category='warning')
622 return redirect(url('login_home', came_from=p))
623 return redirect(url('login_home', came_from=p))
623
624
624 else:
625 else:
625 # redirect with forbidden ret code
626 # redirect with forbidden ret code
626 return abort(403)
627 return abort(403)
627
628
628 def check_permissions(self):
629 def check_permissions(self):
629 """Dummy function for overriding"""
630 """Dummy function for overriding"""
630 raise Exception('You have to write this function in child class')
631 raise Exception('You have to write this function in child class')
631
632
632
633
633 class HasPermissionAllDecorator(PermsDecorator):
634 class HasPermissionAllDecorator(PermsDecorator):
634 """
635 """
635 Checks for access permission for all given predicates. All of them
636 Checks for access permission for all given predicates. All of them
636 have to be meet in order to fulfill the request
637 have to be meet in order to fulfill the request
637 """
638 """
638
639
639 def check_permissions(self):
640 def check_permissions(self):
640 if self.required_perms.issubset(self.user_perms.get('global')):
641 if self.required_perms.issubset(self.user_perms.get('global')):
641 return True
642 return True
642 return False
643 return False
643
644
644
645
645 class HasPermissionAnyDecorator(PermsDecorator):
646 class HasPermissionAnyDecorator(PermsDecorator):
646 """
647 """
647 Checks for access permission for any of given predicates. In order to
648 Checks for access permission for any of given predicates. In order to
648 fulfill the request any of predicates must be meet
649 fulfill the request any of predicates must be meet
649 """
650 """
650
651
651 def check_permissions(self):
652 def check_permissions(self):
652 if self.required_perms.intersection(self.user_perms.get('global')):
653 if self.required_perms.intersection(self.user_perms.get('global')):
653 return True
654 return True
654 return False
655 return False
655
656
656
657
657 class HasRepoPermissionAllDecorator(PermsDecorator):
658 class HasRepoPermissionAllDecorator(PermsDecorator):
658 """
659 """
659 Checks for access permission for all given predicates for specific
660 Checks for access permission for all given predicates for specific
660 repository. All of them have to be meet in order to fulfill the request
661 repository. All of them have to be meet in order to fulfill the request
661 """
662 """
662
663
663 def check_permissions(self):
664 def check_permissions(self):
664 repo_name = get_repo_slug(request)
665 repo_name = get_repo_slug(request)
665 try:
666 try:
666 user_perms = set([self.user_perms['repositories'][repo_name]])
667 user_perms = set([self.user_perms['repositories'][repo_name]])
667 except KeyError:
668 except KeyError:
668 return False
669 return False
669 if self.required_perms.issubset(user_perms):
670 if self.required_perms.issubset(user_perms):
670 return True
671 return True
671 return False
672 return False
672
673
673
674
674 class HasRepoPermissionAnyDecorator(PermsDecorator):
675 class HasRepoPermissionAnyDecorator(PermsDecorator):
675 """
676 """
676 Checks for access permission for any of given predicates for specific
677 Checks for access permission for any of given predicates for specific
677 repository. In order to fulfill the request any of predicates must be meet
678 repository. In order to fulfill the request any of predicates must be meet
678 """
679 """
679
680
680 def check_permissions(self):
681 def check_permissions(self):
681 repo_name = get_repo_slug(request)
682 repo_name = get_repo_slug(request)
682 try:
683 try:
683 user_perms = set([self.user_perms['repositories'][repo_name]])
684 user_perms = set([self.user_perms['repositories'][repo_name]])
684 except KeyError:
685 except KeyError:
685 return False
686 return False
686
687
687 if self.required_perms.intersection(user_perms):
688 if self.required_perms.intersection(user_perms):
688 return True
689 return True
689 return False
690 return False
690
691
691
692
692 class HasReposGroupPermissionAllDecorator(PermsDecorator):
693 class HasReposGroupPermissionAllDecorator(PermsDecorator):
693 """
694 """
694 Checks for access permission for all given predicates for specific
695 Checks for access permission for all given predicates for specific
695 repository. All of them have to be meet in order to fulfill the request
696 repository. All of them have to be meet in order to fulfill the request
696 """
697 """
697
698
698 def check_permissions(self):
699 def check_permissions(self):
699 group_name = get_repos_group_slug(request)
700 group_name = get_repos_group_slug(request)
700 try:
701 try:
701 user_perms = set([self.user_perms['repositories_groups'][group_name]])
702 user_perms = set([self.user_perms['repositories_groups'][group_name]])
702 except KeyError:
703 except KeyError:
703 return False
704 return False
704
705
705 if self.required_perms.issubset(user_perms):
706 if self.required_perms.issubset(user_perms):
706 return True
707 return True
707 return False
708 return False
708
709
709
710
710 class HasReposGroupPermissionAnyDecorator(PermsDecorator):
711 class HasReposGroupPermissionAnyDecorator(PermsDecorator):
711 """
712 """
712 Checks for access permission for any of given predicates for specific
713 Checks for access permission for any of given predicates for specific
713 repository. In order to fulfill the request any of predicates must be meet
714 repository. In order to fulfill the request any of predicates must be meet
714 """
715 """
715
716
716 def check_permissions(self):
717 def check_permissions(self):
717 group_name = get_repos_group_slug(request)
718 group_name = get_repos_group_slug(request)
718 try:
719 try:
719 user_perms = set([self.user_perms['repositories_groups'][group_name]])
720 user_perms = set([self.user_perms['repositories_groups'][group_name]])
720 except KeyError:
721 except KeyError:
721 return False
722 return False
722
723
723 if self.required_perms.intersection(user_perms):
724 if self.required_perms.intersection(user_perms):
724 return True
725 return True
725 return False
726 return False
726
727
727
728
728 #==============================================================================
729 #==============================================================================
729 # CHECK FUNCTIONS
730 # CHECK FUNCTIONS
730 #==============================================================================
731 #==============================================================================
731 class PermsFunction(object):
732 class PermsFunction(object):
732 """Base function for other check functions"""
733 """Base function for other check functions"""
733
734
734 def __init__(self, *perms):
735 def __init__(self, *perms):
735 available_perms = config['available_permissions']
736 available_perms = config['available_permissions']
736
737
737 for perm in perms:
738 for perm in perms:
738 if perm not in available_perms:
739 if perm not in available_perms:
739 raise Exception("'%s' permission is not defined" % perm)
740 raise Exception("'%s' permission is not defined" % perm)
740 self.required_perms = set(perms)
741 self.required_perms = set(perms)
741 self.user_perms = None
742 self.user_perms = None
742 self.repo_name = None
743 self.repo_name = None
743 self.group_name = None
744 self.group_name = None
744
745
745 def __call__(self, check_location=''):
746 def __call__(self, check_location=''):
746 #TODO: put user as attribute here
747 #TODO: put user as attribute here
747 user = request.user
748 user = request.user
748 cls_name = self.__class__.__name__
749 cls_name = self.__class__.__name__
749 check_scope = {
750 check_scope = {
750 'HasPermissionAll': '',
751 'HasPermissionAll': '',
751 'HasPermissionAny': '',
752 'HasPermissionAny': '',
752 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
753 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
753 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
754 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
754 'HasReposGroupPermissionAll': 'group:%s' % self.group_name,
755 'HasReposGroupPermissionAll': 'group:%s' % self.group_name,
755 'HasReposGroupPermissionAny': 'group:%s' % self.group_name,
756 'HasReposGroupPermissionAny': 'group:%s' % self.group_name,
756 }.get(cls_name, '?')
757 }.get(cls_name, '?')
757 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
758 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
758 self.required_perms, user, check_scope,
759 self.required_perms, user, check_scope,
759 check_location or 'unspecified location')
760 check_location or 'unspecified location')
760 if not user:
761 if not user:
761 log.debug('Empty request user')
762 log.debug('Empty request user')
762 return False
763 return False
763 self.user_perms = user.permissions
764 self.user_perms = user.permissions
764 if self.check_permissions():
765 if self.check_permissions():
765 log.debug('Permission to %s granted for user: %s @ %s', self.repo_name, user,
766 log.debug('Permission to %s granted for user: %s @ %s', self.repo_name, user,
766 check_location or 'unspecified location')
767 check_location or 'unspecified location')
767 return True
768 return True
768
769
769 else:
770 else:
770 log.debug('Permission to %s denied for user: %s @ %s', self.repo_name, user,
771 log.debug('Permission to %s denied for user: %s @ %s', self.repo_name, user,
771 check_location or 'unspecified location')
772 check_location or 'unspecified location')
772 return False
773 return False
773
774
774 def check_permissions(self):
775 def check_permissions(self):
775 """Dummy function for overriding"""
776 """Dummy function for overriding"""
776 raise Exception('You have to write this function in child class')
777 raise Exception('You have to write this function in child class')
777
778
778
779
779 class HasPermissionAll(PermsFunction):
780 class HasPermissionAll(PermsFunction):
780 def check_permissions(self):
781 def check_permissions(self):
781 if self.required_perms.issubset(self.user_perms.get('global')):
782 if self.required_perms.issubset(self.user_perms.get('global')):
782 return True
783 return True
783 return False
784 return False
784
785
785
786
786 class HasPermissionAny(PermsFunction):
787 class HasPermissionAny(PermsFunction):
787 def check_permissions(self):
788 def check_permissions(self):
788 if self.required_perms.intersection(self.user_perms.get('global')):
789 if self.required_perms.intersection(self.user_perms.get('global')):
789 return True
790 return True
790 return False
791 return False
791
792
792
793
793 class HasRepoPermissionAll(PermsFunction):
794 class HasRepoPermissionAll(PermsFunction):
794 def __call__(self, repo_name=None, check_location=''):
795 def __call__(self, repo_name=None, check_location=''):
795 self.repo_name = repo_name
796 self.repo_name = repo_name
796 return super(HasRepoPermissionAll, self).__call__(check_location)
797 return super(HasRepoPermissionAll, self).__call__(check_location)
797
798
798 def check_permissions(self):
799 def check_permissions(self):
799 if not self.repo_name:
800 if not self.repo_name:
800 self.repo_name = get_repo_slug(request)
801 self.repo_name = get_repo_slug(request)
801
802
802 try:
803 try:
803 self._user_perms = set(
804 self._user_perms = set(
804 [self.user_perms['repositories'][self.repo_name]]
805 [self.user_perms['repositories'][self.repo_name]]
805 )
806 )
806 except KeyError:
807 except KeyError:
807 return False
808 return False
808 if self.required_perms.issubset(self._user_perms):
809 if self.required_perms.issubset(self._user_perms):
809 return True
810 return True
810 return False
811 return False
811
812
812
813
813 class HasRepoPermissionAny(PermsFunction):
814 class HasRepoPermissionAny(PermsFunction):
814 def __call__(self, repo_name=None, check_location=''):
815 def __call__(self, repo_name=None, check_location=''):
815 self.repo_name = repo_name
816 self.repo_name = repo_name
816 return super(HasRepoPermissionAny, self).__call__(check_location)
817 return super(HasRepoPermissionAny, self).__call__(check_location)
817
818
818 def check_permissions(self):
819 def check_permissions(self):
819 if not self.repo_name:
820 if not self.repo_name:
820 self.repo_name = get_repo_slug(request)
821 self.repo_name = get_repo_slug(request)
821
822
822 try:
823 try:
823 self._user_perms = set(
824 self._user_perms = set(
824 [self.user_perms['repositories'][self.repo_name]]
825 [self.user_perms['repositories'][self.repo_name]]
825 )
826 )
826 except KeyError:
827 except KeyError:
827 return False
828 return False
828 if self.required_perms.intersection(self._user_perms):
829 if self.required_perms.intersection(self._user_perms):
829 return True
830 return True
830 return False
831 return False
831
832
832
833
833 class HasReposGroupPermissionAny(PermsFunction):
834 class HasReposGroupPermissionAny(PermsFunction):
834 def __call__(self, group_name=None, check_location=''):
835 def __call__(self, group_name=None, check_location=''):
835 self.group_name = group_name
836 self.group_name = group_name
836 return super(HasReposGroupPermissionAny, self).__call__(check_location)
837 return super(HasReposGroupPermissionAny, self).__call__(check_location)
837
838
838 def check_permissions(self):
839 def check_permissions(self):
839 try:
840 try:
840 self._user_perms = set(
841 self._user_perms = set(
841 [self.user_perms['repositories_groups'][self.group_name]]
842 [self.user_perms['repositories_groups'][self.group_name]]
842 )
843 )
843 except KeyError:
844 except KeyError:
844 return False
845 return False
845 if self.required_perms.intersection(self._user_perms):
846 if self.required_perms.intersection(self._user_perms):
846 return True
847 return True
847 return False
848 return False
848
849
849
850
850 class HasReposGroupPermissionAll(PermsFunction):
851 class HasReposGroupPermissionAll(PermsFunction):
851 def __call__(self, group_name=None, check_location=''):
852 def __call__(self, group_name=None, check_location=''):
852 self.group_name = group_name
853 self.group_name = group_name
853 return super(HasReposGroupPermissionAll, self).__call__(check_location)
854 return super(HasReposGroupPermissionAll, self).__call__(check_location)
854
855
855 def check_permissions(self):
856 def check_permissions(self):
856 try:
857 try:
857 self._user_perms = set(
858 self._user_perms = set(
858 [self.user_perms['repositories_groups'][self.group_name]]
859 [self.user_perms['repositories_groups'][self.group_name]]
859 )
860 )
860 except KeyError:
861 except KeyError:
861 return False
862 return False
862 if self.required_perms.issubset(self._user_perms):
863 if self.required_perms.issubset(self._user_perms):
863 return True
864 return True
864 return False
865 return False
865
866
866
867
867 #==============================================================================
868 #==============================================================================
868 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
869 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
869 #==============================================================================
870 #==============================================================================
870 class HasPermissionAnyMiddleware(object):
871 class HasPermissionAnyMiddleware(object):
871 def __init__(self, *perms):
872 def __init__(self, *perms):
872 self.required_perms = set(perms)
873 self.required_perms = set(perms)
873
874
874 def __call__(self, user, repo_name):
875 def __call__(self, user, repo_name):
875 # repo_name MUST be unicode, since we handle keys in permission
876 # repo_name MUST be unicode, since we handle keys in permission
876 # dict by unicode
877 # dict by unicode
877 repo_name = safe_unicode(repo_name)
878 repo_name = safe_unicode(repo_name)
878 usr = AuthUser(user.user_id)
879 usr = AuthUser(user.user_id)
879 try:
880 try:
880 self.user_perms = set([usr.permissions['repositories'][repo_name]])
881 self.user_perms = set([usr.permissions['repositories'][repo_name]])
881 except Exception:
882 except Exception:
882 log.error('Exception while accessing permissions %s' %
883 log.error('Exception while accessing permissions %s' %
883 traceback.format_exc())
884 traceback.format_exc())
884 self.user_perms = set()
885 self.user_perms = set()
885 self.username = user.username
886 self.username = user.username
886 self.repo_name = repo_name
887 self.repo_name = repo_name
887 return self.check_permissions()
888 return self.check_permissions()
888
889
889 def check_permissions(self):
890 def check_permissions(self):
890 log.debug('checking VCS protocol '
891 log.debug('checking VCS protocol '
891 'permissions %s for user:%s repository:%s', self.user_perms,
892 'permissions %s for user:%s repository:%s', self.user_perms,
892 self.username, self.repo_name)
893 self.username, self.repo_name)
893 if self.required_perms.intersection(self.user_perms):
894 if self.required_perms.intersection(self.user_perms):
894 log.debug('permission granted for user:%s on repo:%s' % (
895 log.debug('permission granted for user:%s on repo:%s' % (
895 self.username, self.repo_name
896 self.username, self.repo_name
896 )
897 )
897 )
898 )
898 return True
899 return True
899 log.debug('permission denied for user:%s on repo:%s' % (
900 log.debug('permission denied for user:%s on repo:%s' % (
900 self.username, self.repo_name
901 self.username, self.repo_name
901 )
902 )
902 )
903 )
903 return False
904 return False
904
905
905
906
906 #==============================================================================
907 #==============================================================================
907 # SPECIAL VERSION TO HANDLE API AUTH
908 # SPECIAL VERSION TO HANDLE API AUTH
908 #==============================================================================
909 #==============================================================================
909 class _BaseApiPerm(object):
910 class _BaseApiPerm(object):
910 def __init__(self, *perms):
911 def __init__(self, *perms):
911 self.required_perms = set(perms)
912 self.required_perms = set(perms)
912
913
913 def __call__(self, check_location='unspecified', user=None, repo_name=None):
914 def __call__(self, check_location='unspecified', user=None, repo_name=None):
914 cls_name = self.__class__.__name__
915 cls_name = self.__class__.__name__
915 check_scope = 'user:%s, repo:%s' % (user, repo_name)
916 check_scope = 'user:%s, repo:%s' % (user, repo_name)
916 log.debug('checking cls:%s %s %s @ %s', cls_name,
917 log.debug('checking cls:%s %s %s @ %s', cls_name,
917 self.required_perms, check_scope, check_location)
918 self.required_perms, check_scope, check_location)
918 if not user:
919 if not user:
919 log.debug('Empty User passed into arguments')
920 log.debug('Empty User passed into arguments')
920 return False
921 return False
921
922
922 ## process user
923 ## process user
923 if not isinstance(user, AuthUser):
924 if not isinstance(user, AuthUser):
924 user = AuthUser(user.user_id)
925 user = AuthUser(user.user_id)
925
926
926 if self.check_permissions(user.permissions, repo_name):
927 if self.check_permissions(user.permissions, repo_name):
927 log.debug('Permission to %s granted for user: %s @ %s', repo_name,
928 log.debug('Permission to %s granted for user: %s @ %s', repo_name,
928 user, check_location)
929 user, check_location)
929 return True
930 return True
930
931
931 else:
932 else:
932 log.debug('Permission to %s denied for user: %s @ %s', repo_name,
933 log.debug('Permission to %s denied for user: %s @ %s', repo_name,
933 user, check_location)
934 user, check_location)
934 return False
935 return False
935
936
936 def check_permissions(self, perm_defs, repo_name):
937 def check_permissions(self, perm_defs, repo_name):
937 """
938 """
938 implement in child class should return True if permissions are ok,
939 implement in child class should return True if permissions are ok,
939 False otherwise
940 False otherwise
940
941
941 :param perm_defs: dict with permission definitions
942 :param perm_defs: dict with permission definitions
942 :param repo_name: repo name
943 :param repo_name: repo name
943 """
944 """
944 raise NotImplementedError()
945 raise NotImplementedError()
945
946
946
947
947 class HasPermissionAllApi(_BaseApiPerm):
948 class HasPermissionAllApi(_BaseApiPerm):
948 def __call__(self, user, check_location=''):
949 def __call__(self, user, check_location=''):
949 return super(HasPermissionAllApi, self)\
950 return super(HasPermissionAllApi, self)\
950 .__call__(check_location=check_location, user=user)
951 .__call__(check_location=check_location, user=user)
951
952
952 def check_permissions(self, perm_defs, repo):
953 def check_permissions(self, perm_defs, repo):
953 if self.required_perms.issubset(perm_defs.get('global')):
954 if self.required_perms.issubset(perm_defs.get('global')):
954 return True
955 return True
955 return False
956 return False
956
957
957
958
958 class HasPermissionAnyApi(_BaseApiPerm):
959 class HasPermissionAnyApi(_BaseApiPerm):
959 def __call__(self, user, check_location=''):
960 def __call__(self, user, check_location=''):
960 return super(HasPermissionAnyApi, self)\
961 return super(HasPermissionAnyApi, self)\
961 .__call__(check_location=check_location, user=user)
962 .__call__(check_location=check_location, user=user)
962
963
963 def check_permissions(self, perm_defs, repo):
964 def check_permissions(self, perm_defs, repo):
964 if self.required_perms.intersection(perm_defs.get('global')):
965 if self.required_perms.intersection(perm_defs.get('global')):
965 return True
966 return True
966 return False
967 return False
967
968
968
969
969 class HasRepoPermissionAllApi(_BaseApiPerm):
970 class HasRepoPermissionAllApi(_BaseApiPerm):
970 def __call__(self, user, repo_name, check_location=''):
971 def __call__(self, user, repo_name, check_location=''):
971 return super(HasRepoPermissionAllApi, self)\
972 return super(HasRepoPermissionAllApi, self)\
972 .__call__(check_location=check_location, user=user,
973 .__call__(check_location=check_location, user=user,
973 repo_name=repo_name)
974 repo_name=repo_name)
974
975
975 def check_permissions(self, perm_defs, repo_name):
976 def check_permissions(self, perm_defs, repo_name):
976
977
977 try:
978 try:
978 self._user_perms = set(
979 self._user_perms = set(
979 [perm_defs['repositories'][repo_name]]
980 [perm_defs['repositories'][repo_name]]
980 )
981 )
981 except KeyError:
982 except KeyError:
982 log.warning(traceback.format_exc())
983 log.warning(traceback.format_exc())
983 return False
984 return False
984 if self.required_perms.issubset(self._user_perms):
985 if self.required_perms.issubset(self._user_perms):
985 return True
986 return True
986 return False
987 return False
987
988
988
989
989 class HasRepoPermissionAnyApi(_BaseApiPerm):
990 class HasRepoPermissionAnyApi(_BaseApiPerm):
990 def __call__(self, user, repo_name, check_location=''):
991 def __call__(self, user, repo_name, check_location=''):
991 return super(HasRepoPermissionAnyApi, self)\
992 return super(HasRepoPermissionAnyApi, self)\
992 .__call__(check_location=check_location, user=user,
993 .__call__(check_location=check_location, user=user,
993 repo_name=repo_name)
994 repo_name=repo_name)
994
995
995 def check_permissions(self, perm_defs, repo_name):
996 def check_permissions(self, perm_defs, repo_name):
996
997
997 try:
998 try:
998 _user_perms = set(
999 _user_perms = set(
999 [perm_defs['repositories'][repo_name]]
1000 [perm_defs['repositories'][repo_name]]
1000 )
1001 )
1001 except KeyError:
1002 except KeyError:
1002 log.warning(traceback.format_exc())
1003 log.warning(traceback.format_exc())
1003 return False
1004 return False
1004 if self.required_perms.intersection(_user_perms):
1005 if self.required_perms.intersection(_user_perms):
1005 return True
1006 return True
1006 return False
1007 return False
1007
1008
1008
1009
1009 def check_ip_access(source_ip, allowed_ips=None):
1010 def check_ip_access(source_ip, allowed_ips=None):
1010 """
1011 """
1011 Checks if source_ip is a subnet of any of allowed_ips.
1012 Checks if source_ip is a subnet of any of allowed_ips.
1012
1013
1013 :param source_ip:
1014 :param source_ip:
1014 :param allowed_ips: list of allowed ips together with mask
1015 :param allowed_ips: list of allowed ips together with mask
1015 """
1016 """
1016 from rhodecode.lib import ipaddr
1017 from rhodecode.lib import ipaddr
1017 log.debug('checking if ip:%s is subnet of %s' % (source_ip, allowed_ips))
1018 log.debug('checking if ip:%s is subnet of %s' % (source_ip, allowed_ips))
1018 if isinstance(allowed_ips, (tuple, list, set)):
1019 if isinstance(allowed_ips, (tuple, list, set)):
1019 for ip in allowed_ips:
1020 for ip in allowed_ips:
1020 try:
1021 try:
1021 if ipaddr.IPAddress(source_ip) in ipaddr.IPNetwork(ip):
1022 if ipaddr.IPAddress(source_ip) in ipaddr.IPNetwork(ip):
1022 return True
1023 return True
1023 # for any case we cannot determine the IP, don't crash just
1024 # for any case we cannot determine the IP, don't crash just
1024 # skip it and log as error, we want to say forbidden still when
1025 # skip it and log as error, we want to say forbidden still when
1025 # sending bad IP
1026 # sending bad IP
1026 except Exception:
1027 except Exception:
1027 log.error(traceback.format_exc())
1028 log.error(traceback.format_exc())
1028 continue
1029 continue
1029 return False
1030 return False
@@ -1,164 +1,167 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.changelog
3 rhodecode.controllers.changelog
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 RhodeCode authentication library for LDAP
6 RhodeCode authentication library for LDAP
7
7
8 :created_on: Created on Nov 17, 2010
8 :created_on: Created on Nov 17, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import logging
26 import logging
27
27
28 from rhodecode.lib.exceptions import LdapConnectionError, LdapUsernameError, \
28 from rhodecode.lib.exceptions import LdapConnectionError, LdapUsernameError, \
29 LdapPasswordError
29 LdapPasswordError, LdapImportError
30 from rhodecode.lib.utils2 import safe_str
30 from rhodecode.lib.utils2 import safe_str
31
31
32 log = logging.getLogger(__name__)
32 log = logging.getLogger(__name__)
33
33
34
34
35 try:
35 try:
36 import ldap
36 import ldap
37 except ImportError:
37 except ImportError:
38 # means that python-ldap is not installed
38 # means that python-ldap is not installed
39 pass
39 ldap = None
40
40
41
41
42 class AuthLdap(object):
42 class AuthLdap(object):
43
43
44 def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='',
44 def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='',
45 tls_kind='PLAIN', tls_reqcert='DEMAND', ldap_version=3,
45 tls_kind='PLAIN', tls_reqcert='DEMAND', ldap_version=3,
46 ldap_filter='(&(objectClass=user)(!(objectClass=computer)))',
46 ldap_filter='(&(objectClass=user)(!(objectClass=computer)))',
47 search_scope='SUBTREE', attr_login='uid'):
47 search_scope='SUBTREE', attr_login='uid'):
48 if ldap is None:
49 raise LdapImportError
50
48 self.ldap_version = ldap_version
51 self.ldap_version = ldap_version
49 ldap_server_type = 'ldap'
52 ldap_server_type = 'ldap'
50
53
51 self.TLS_KIND = tls_kind
54 self.TLS_KIND = tls_kind
52
55
53 if self.TLS_KIND == 'LDAPS':
56 if self.TLS_KIND == 'LDAPS':
54 port = port or 689
57 port = port or 689
55 ldap_server_type = ldap_server_type + 's'
58 ldap_server_type = ldap_server_type + 's'
56
59
57 OPT_X_TLS_DEMAND = 2
60 OPT_X_TLS_DEMAND = 2
58 self.TLS_REQCERT = getattr(ldap, 'OPT_X_TLS_%s' % tls_reqcert,
61 self.TLS_REQCERT = getattr(ldap, 'OPT_X_TLS_%s' % tls_reqcert,
59 OPT_X_TLS_DEMAND)
62 OPT_X_TLS_DEMAND)
60 # split server into list
63 # split server into list
61 self.LDAP_SERVER_ADDRESS = server.split(',')
64 self.LDAP_SERVER_ADDRESS = server.split(',')
62 self.LDAP_SERVER_PORT = port
65 self.LDAP_SERVER_PORT = port
63
66
64 # USE FOR READ ONLY BIND TO LDAP SERVER
67 # USE FOR READ ONLY BIND TO LDAP SERVER
65 self.LDAP_BIND_DN = safe_str(bind_dn)
68 self.LDAP_BIND_DN = safe_str(bind_dn)
66 self.LDAP_BIND_PASS = safe_str(bind_pass)
69 self.LDAP_BIND_PASS = safe_str(bind_pass)
67 _LDAP_SERVERS = []
70 _LDAP_SERVERS = []
68 for host in self.LDAP_SERVER_ADDRESS:
71 for host in self.LDAP_SERVER_ADDRESS:
69 _LDAP_SERVERS.append("%s://%s:%s" % (ldap_server_type,
72 _LDAP_SERVERS.append("%s://%s:%s" % (ldap_server_type,
70 host.replace(' ', ''),
73 host.replace(' ', ''),
71 self.LDAP_SERVER_PORT))
74 self.LDAP_SERVER_PORT))
72 self.LDAP_SERVER = str(', '.join(s for s in _LDAP_SERVERS))
75 self.LDAP_SERVER = str(', '.join(s for s in _LDAP_SERVERS))
73 self.BASE_DN = safe_str(base_dn)
76 self.BASE_DN = safe_str(base_dn)
74 self.LDAP_FILTER = safe_str(ldap_filter)
77 self.LDAP_FILTER = safe_str(ldap_filter)
75 self.SEARCH_SCOPE = getattr(ldap, 'SCOPE_%s' % search_scope)
78 self.SEARCH_SCOPE = getattr(ldap, 'SCOPE_%s' % search_scope)
76 self.attr_login = attr_login
79 self.attr_login = attr_login
77
80
78 def authenticate_ldap(self, username, password):
81 def authenticate_ldap(self, username, password):
79 """
82 """
80 Authenticate a user via LDAP and return his/her LDAP properties.
83 Authenticate a user via LDAP and return his/her LDAP properties.
81
84
82 Raises AuthenticationError if the credentials are rejected, or
85 Raises AuthenticationError if the credentials are rejected, or
83 EnvironmentError if the LDAP server can't be reached.
86 EnvironmentError if the LDAP server can't be reached.
84
87
85 :param username: username
88 :param username: username
86 :param password: password
89 :param password: password
87 """
90 """
88
91
89 from rhodecode.lib.helpers import chop_at
92 from rhodecode.lib.helpers import chop_at
90
93
91 uid = chop_at(username, "@%s" % self.LDAP_SERVER_ADDRESS)
94 uid = chop_at(username, "@%s" % self.LDAP_SERVER_ADDRESS)
92
95
93 if not password:
96 if not password:
94 log.debug("Attempt to authenticate LDAP user "
97 log.debug("Attempt to authenticate LDAP user "
95 "with blank password rejected.")
98 "with blank password rejected.")
96 raise LdapPasswordError()
99 raise LdapPasswordError()
97 if "," in username:
100 if "," in username:
98 raise LdapUsernameError("invalid character in username: ,")
101 raise LdapUsernameError("invalid character in username: ,")
99 try:
102 try:
100 if hasattr(ldap, 'OPT_X_TLS_CACERTDIR'):
103 if hasattr(ldap, 'OPT_X_TLS_CACERTDIR'):
101 ldap.set_option(ldap.OPT_X_TLS_CACERTDIR,
104 ldap.set_option(ldap.OPT_X_TLS_CACERTDIR,
102 '/etc/openldap/cacerts')
105 '/etc/openldap/cacerts')
103 ldap.set_option(ldap.OPT_REFERRALS, ldap.OPT_OFF)
106 ldap.set_option(ldap.OPT_REFERRALS, ldap.OPT_OFF)
104 ldap.set_option(ldap.OPT_RESTART, ldap.OPT_ON)
107 ldap.set_option(ldap.OPT_RESTART, ldap.OPT_ON)
105 ldap.set_option(ldap.OPT_TIMEOUT, 20)
108 ldap.set_option(ldap.OPT_TIMEOUT, 20)
106 ldap.set_option(ldap.OPT_NETWORK_TIMEOUT, 10)
109 ldap.set_option(ldap.OPT_NETWORK_TIMEOUT, 10)
107 ldap.set_option(ldap.OPT_TIMELIMIT, 15)
110 ldap.set_option(ldap.OPT_TIMELIMIT, 15)
108 if self.TLS_KIND != 'PLAIN':
111 if self.TLS_KIND != 'PLAIN':
109 ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, self.TLS_REQCERT)
112 ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, self.TLS_REQCERT)
110 server = ldap.initialize(self.LDAP_SERVER)
113 server = ldap.initialize(self.LDAP_SERVER)
111 if self.ldap_version == 2:
114 if self.ldap_version == 2:
112 server.protocol = ldap.VERSION2
115 server.protocol = ldap.VERSION2
113 else:
116 else:
114 server.protocol = ldap.VERSION3
117 server.protocol = ldap.VERSION3
115
118
116 if self.TLS_KIND == 'START_TLS':
119 if self.TLS_KIND == 'START_TLS':
117 server.start_tls_s()
120 server.start_tls_s()
118
121
119 if self.LDAP_BIND_DN and self.LDAP_BIND_PASS:
122 if self.LDAP_BIND_DN and self.LDAP_BIND_PASS:
120 log.debug('Trying simple_bind with password and given DN: %s'
123 log.debug('Trying simple_bind with password and given DN: %s'
121 % self.LDAP_BIND_DN)
124 % self.LDAP_BIND_DN)
122 server.simple_bind_s(self.LDAP_BIND_DN, self.LDAP_BIND_PASS)
125 server.simple_bind_s(self.LDAP_BIND_DN, self.LDAP_BIND_PASS)
123
126
124 filter_ = '(&%s(%s=%s))' % (self.LDAP_FILTER, self.attr_login,
127 filter_ = '(&%s(%s=%s))' % (self.LDAP_FILTER, self.attr_login,
125 username)
128 username)
126 log.debug("Authenticating %r filter %s at %s", self.BASE_DN,
129 log.debug("Authenticating %r filter %s at %s", self.BASE_DN,
127 filter_, self.LDAP_SERVER)
130 filter_, self.LDAP_SERVER)
128 lobjects = server.search_ext_s(self.BASE_DN, self.SEARCH_SCOPE,
131 lobjects = server.search_ext_s(self.BASE_DN, self.SEARCH_SCOPE,
129 filter_)
132 filter_)
130
133
131 if not lobjects:
134 if not lobjects:
132 raise ldap.NO_SUCH_OBJECT()
135 raise ldap.NO_SUCH_OBJECT()
133
136
134 for (dn, _attrs) in lobjects:
137 for (dn, _attrs) in lobjects:
135 if dn is None:
138 if dn is None:
136 continue
139 continue
137
140
138 try:
141 try:
139 log.debug('Trying simple bind with %s' % dn)
142 log.debug('Trying simple bind with %s' % dn)
140 server.simple_bind_s(dn, password)
143 server.simple_bind_s(dn, password)
141 attrs = server.search_ext_s(dn, ldap.SCOPE_BASE,
144 attrs = server.search_ext_s(dn, ldap.SCOPE_BASE,
142 '(objectClass=*)')[0][1]
145 '(objectClass=*)')[0][1]
143 break
146 break
144
147
145 except ldap.INVALID_CREDENTIALS:
148 except ldap.INVALID_CREDENTIALS:
146 log.debug(
149 log.debug(
147 "LDAP rejected password for user '%s' (%s): %s" % (
150 "LDAP rejected password for user '%s' (%s): %s" % (
148 uid, username, dn
151 uid, username, dn
149 )
152 )
150 )
153 )
151
154
152 else:
155 else:
153 log.debug("No matching LDAP objects for authentication "
156 log.debug("No matching LDAP objects for authentication "
154 "of '%s' (%s)", uid, username)
157 "of '%s' (%s)", uid, username)
155 raise LdapPasswordError()
158 raise LdapPasswordError()
156
159
157 except ldap.NO_SUCH_OBJECT:
160 except ldap.NO_SUCH_OBJECT:
158 log.debug("LDAP says no such user '%s' (%s)" % (uid, username))
161 log.debug("LDAP says no such user '%s' (%s)" % (uid, username))
159 raise LdapUsernameError()
162 raise LdapUsernameError()
160 except ldap.SERVER_DOWN:
163 except ldap.SERVER_DOWN:
161 raise LdapConnectionError("LDAP can't access "
164 raise LdapConnectionError("LDAP can't access "
162 "authentication server")
165 "authentication server")
163
166
164 return (dn, attrs)
167 return (dn, attrs)
General Comments 0
You need to be logged in to leave comments. Login now