##// END OF EJS Templates
Enable start_tls connection encryption.
"Lorenzo M. Catucci" -
r1290:74685a31 beta
parent child Browse files
Show More
@@ -1,124 +1,134
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.admin.ldap_settings
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 ldap controller for RhodeCode
7 7
8 8 :created_on: Nov 26, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25 import logging
26 26 import formencode
27 27 import traceback
28 28
29 29 from formencode import htmlfill
30 30
31 31 from pylons import request, response, session, tmpl_context as c, url
32 32 from pylons.controllers.util import abort, redirect
33 33 from pylons.i18n.translation import _
34 34
35 35 from rhodecode.lib.base import BaseController, render
36 36 from rhodecode.lib import helpers as h
37 37 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
38 38 from rhodecode.lib.auth_ldap import LdapImportError
39 39 from rhodecode.model.settings import SettingsModel
40 40 from rhodecode.model.forms import LdapSettingsForm
41 41 from sqlalchemy.exc import DatabaseError
42 42
43 43 log = logging.getLogger(__name__)
44 44
45 45
46 46 class LdapSettingsController(BaseController):
47 47
48 48 search_scope_choices = [('BASE', _('BASE'),),
49 49 ('ONELEVEL', _('ONELEVEL'),),
50 50 ('SUBTREE', _('SUBTREE'),),
51 51 ]
52 52 search_scope_default = 'SUBTREE'
53 53
54 54 tls_reqcert_choices = [('NEVER', _('NEVER'),),
55 55 ('ALLOW', _('ALLOW'),),
56 56 ('TRY', _('TRY'),),
57 57 ('DEMAND', _('DEMAND'),),
58 58 ('HARD', _('HARD'),),
59 59 ]
60 60 tls_reqcert_default = 'DEMAND'
61 61
62 tls_kind_choices = [('PLAIN', _('No encryption'),),
63 ('LDAPS', _('LDAPS connection'),),
64 ('START_TLS', _('START_TLS on LDAP connection'),)
65 ]
66
67 tls_kind_default = 'PLAIN'
68
62 69 @LoginRequired()
63 70 @HasPermissionAllDecorator('hg.admin')
64 71 def __before__(self):
65 72 c.admin_user = session.get('admin_user')
66 73 c.admin_username = session.get('admin_username')
67 74 c.search_scope_choices = self.search_scope_choices
68 75 c.tls_reqcert_choices = self.tls_reqcert_choices
76 c.tls_kind_choices = self.tls_kind_choices
69 77 super(LdapSettingsController, self).__before__()
70 78
71 79 def index(self):
72 80 defaults = SettingsModel().get_ldap_settings()
73 81 c.search_scope_cur = defaults.get('ldap_search_scope')
74 82 c.tls_reqcert_cur = defaults.get('ldap_tls_reqcert')
83 c.tls_kind_cur = defaults.get('ldap_tls_kind')
75 84
76 85 return htmlfill.render(
77 86 render('admin/ldap/ldap.html'),
78 87 defaults=defaults,
79 88 encoding="UTF-8",
80 89 force_defaults=True,)
81 90
82 91 def ldap_settings(self):
83 92 """POST ldap create and store ldap settings"""
84 93
85 94 settings_model = SettingsModel()
86 95 _form = LdapSettingsForm([x[0] for x in self.tls_reqcert_choices],
87 [x[0] for x in self.search_scope_choices])()
96 [x[0] for x in self.search_scope_choices],
97 [x[0] for x in self.tls_kind_choices])()
88 98
89 99 try:
90 100 form_result = _form.to_python(dict(request.POST))
91 101 try:
92 102
93 103 for k, v in form_result.items():
94 104 if k.startswith('ldap_'):
95 105 setting = settings_model.get(k)
96 106 setting.app_settings_value = v
97 107 self.sa.add(setting)
98 108
99 109 self.sa.commit()
100 110 h.flash(_('Ldap settings updated successfully'),
101 111 category='success')
102 112 except (DatabaseError,):
103 113 raise
104 114 except LdapImportError:
105 115 h.flash(_('Unable to activate ldap. The "python-ldap" library '
106 116 'is missing.'), category='warning')
107 117
108 118 except formencode.Invalid, errors:
109 119
110 120 c.search_scope_cur = self.search_scope_default
111 121 c.tls_reqcert_cur = self.search_scope_default
112 122
113 123 return htmlfill.render(
114 124 render('admin/ldap/ldap.html'),
115 125 defaults=errors.value,
116 126 errors=errors.error_dict or {},
117 127 prefix_error=False,
118 128 encoding="UTF-8")
119 129 except Exception:
120 130 log.error(traceback.format_exc())
121 131 h.flash(_('error occurred during update of ldap settings'),
122 132 category='error')
123 133
124 134 return redirect(url('ldap_home'))
@@ -1,602 +1,602
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.auth
4 4 ~~~~~~~~~~~~~~~~~~
5 5
6 6 authentication and permission libraries
7 7
8 8 :created_on: Apr 4, 2010
9 9 :copyright: (c) 2010 by marcink.
10 10 :license: LICENSE_NAME, see LICENSE_FILE for more details.
11 11 """
12 12 # This program is free software: you can redistribute it and/or modify
13 13 # it under the terms of the GNU General Public License as published by
14 14 # the Free Software Foundation, either version 3 of the License, or
15 15 # (at your option) any later version.
16 16 #
17 17 # This program is distributed in the hope that it will be useful,
18 18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 20 # GNU General Public License for more details.
21 21 #
22 22 # You should have received a copy of the GNU General Public License
23 23 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 24
25 25 import random
26 26 import logging
27 27 import traceback
28 28 import hashlib
29 29
30 30 from tempfile import _RandomNameSequence
31 31 from decorator import decorator
32 32
33 33 from pylons import config, session, url, request
34 34 from pylons.controllers.util import abort, redirect
35 35 from pylons.i18n.translation import _
36 36
37 37 from rhodecode import __platform__, PLATFORM_WIN, PLATFORM_OTHERS
38 38
39 39 if __platform__ in PLATFORM_WIN:
40 40 from hashlib import sha256
41 41 if __platform__ in PLATFORM_OTHERS:
42 42 import bcrypt
43 43
44 44 from rhodecode.lib import str2bool
45 45 from rhodecode.lib.exceptions import LdapPasswordError, LdapUsernameError
46 46 from rhodecode.lib.utils import get_repo_slug
47 47 from rhodecode.lib.auth_ldap import AuthLdap
48 48
49 49 from rhodecode.model import meta
50 50 from rhodecode.model.user import UserModel
51 51 from rhodecode.model.db import Permission
52 52
53 53 log = logging.getLogger(__name__)
54 54
55 55
56 56 class PasswordGenerator(object):
57 57 """This is a simple class for generating password from
58 58 different sets of characters
59 59 usage:
60 60 passwd_gen = PasswordGenerator()
61 61 #print 8-letter password containing only big and small letters
62 62 of alphabet
63 63 print passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
64 64 """
65 65 ALPHABETS_NUM = r'''1234567890'''
66 66 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
67 67 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
68 68 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
69 69 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
70 70 + ALPHABETS_NUM + ALPHABETS_SPECIAL
71 71 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
72 72 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
73 73 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
74 74 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
75 75
76 76 def __init__(self, passwd=''):
77 77 self.passwd = passwd
78 78
79 79 def gen_password(self, len, type):
80 80 self.passwd = ''.join([random.choice(type) for _ in xrange(len)])
81 81 return self.passwd
82 82
83 83
84 84 class RhodeCodeCrypto(object):
85 85
86 86 @classmethod
87 87 def hash_string(cls, str_):
88 88 """
89 89 Cryptographic function used for password hashing based on pybcrypt
90 90 or pycrypto in windows
91 91
92 92 :param password: password to hash
93 93 """
94 94 if __platform__ in PLATFORM_WIN:
95 95 return sha256(str_).hexdigest()
96 96 elif __platform__ in PLATFORM_OTHERS:
97 97 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
98 98 else:
99 99 raise Exception('Unknown or unsupported platform %s' \
100 100 % __platform__)
101 101
102 102 @classmethod
103 103 def hash_check(cls, password, hashed):
104 104 """
105 105 Checks matching password with it's hashed value, runs different
106 106 implementation based on platform it runs on
107 107
108 108 :param password: password
109 109 :param hashed: password in hashed form
110 110 """
111 111
112 112 if __platform__ in PLATFORM_WIN:
113 113 return sha256(password).hexdigest() == hashed
114 114 elif __platform__ in PLATFORM_OTHERS:
115 115 return bcrypt.hashpw(password, hashed) == hashed
116 116 else:
117 117 raise Exception('Unknown or unsupported platform %s' \
118 118 % __platform__)
119 119
120 120
121 121 def get_crypt_password(password):
122 122 return RhodeCodeCrypto.hash_string(password)
123 123
124 124
125 125 def check_password(password, hashed):
126 126 return RhodeCodeCrypto.hash_check(password, hashed)
127 127
128 128
129 129 def generate_api_key(username, salt=None):
130 130 if salt is None:
131 131 salt = _RandomNameSequence().next()
132 132
133 133 return hashlib.sha1(username + salt).hexdigest()
134 134
135 135
136 136 def authfunc(environ, username, password):
137 137 """Dummy authentication function used in Mercurial/Git/ and access control,
138 138
139 139 :param environ: needed only for using in Basic auth
140 140 """
141 141 return authenticate(username, password)
142 142
143 143
144 144 def authenticate(username, password):
145 145 """Authentication function used for access control,
146 146 firstly checks for db authentication then if ldap is enabled for ldap
147 147 authentication, also creates ldap user if not in database
148 148
149 149 :param username: username
150 150 :param password: password
151 151 """
152 152 user_model = UserModel()
153 153 user = user_model.get_by_username(username, cache=False)
154 154
155 155 log.debug('Authenticating user using RhodeCode account')
156 156 if user is not None and not user.ldap_dn:
157 157 if user.active:
158 158 if user.username == 'default' and user.active:
159 159 log.info('user %s authenticated correctly as anonymous user',
160 160 username)
161 161 return True
162 162
163 163 elif user.username == username and check_password(password,
164 164 user.password):
165 165 log.info('user %s authenticated correctly', username)
166 166 return True
167 167 else:
168 168 log.warning('user %s is disabled', username)
169 169
170 170 else:
171 171 log.debug('Regular authentication failed')
172 172 user_obj = user_model.get_by_username(username, cache=False,
173 173 case_insensitive=True)
174 174
175 175 if user_obj is not None and not user_obj.ldap_dn:
176 176 log.debug('this user already exists as non ldap')
177 177 return False
178 178
179 179 from rhodecode.model.settings import SettingsModel
180 180 ldap_settings = SettingsModel().get_ldap_settings()
181 181
182 182 #======================================================================
183 183 # FALLBACK TO LDAP AUTH IF ENABLE
184 184 #======================================================================
185 185 if str2bool(ldap_settings.get('ldap_active')):
186 186 log.debug("Authenticating user using ldap")
187 187 kwargs = {
188 188 'server': ldap_settings.get('ldap_host', ''),
189 189 'base_dn': ldap_settings.get('ldap_base_dn', ''),
190 190 'port': ldap_settings.get('ldap_port'),
191 191 'bind_dn': ldap_settings.get('ldap_dn_user'),
192 192 'bind_pass': ldap_settings.get('ldap_dn_pass'),
193 'use_ldaps': str2bool(ldap_settings.get('ldap_ldaps')),
193 'tls_kind': ldap_settings.get('ldap_tls_kind'),
194 194 'tls_reqcert': ldap_settings.get('ldap_tls_reqcert'),
195 195 'ldap_filter': ldap_settings.get('ldap_filter'),
196 196 'search_scope': ldap_settings.get('ldap_search_scope'),
197 197 'attr_login': ldap_settings.get('ldap_attr_login'),
198 198 'ldap_version': 3,
199 199 }
200 200 log.debug('Checking for ldap authentication')
201 201 try:
202 202 aldap = AuthLdap(**kwargs)
203 203 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username,
204 204 password)
205 205 log.debug('Got ldap DN response %s', user_dn)
206 206
207 207 user_attrs = {
208 208 'name': ldap_attrs.get(ldap_settings\
209 209 .get('ldap_attr_firstname'), [''])[0],
210 210 'lastname': ldap_attrs.get(ldap_settings\
211 211 .get('ldap_attr_lastname'),[''])[0],
212 212 'email': ldap_attrs.get(ldap_settings\
213 213 .get('ldap_attr_email'), [''])[0],
214 214 }
215 215
216 216 if user_model.create_ldap(username, password, user_dn,
217 217 user_attrs):
218 218 log.info('created new ldap user %s', username)
219 219
220 220 return True
221 221 except (LdapUsernameError, LdapPasswordError,):
222 222 pass
223 223 except (Exception,):
224 224 log.error(traceback.format_exc())
225 225 pass
226 226 return False
227 227
228 228
229 229 class AuthUser(object):
230 230 """
231 231 A simple object that handles all attributes of user in RhodeCode
232 232
233 233 It does lookup based on API key,given user, or user present in session
234 234 Then it fills all required information for such user. It also checks if
235 235 anonymous access is enabled and if so, it returns default user as logged
236 236 in
237 237 """
238 238
239 239 def __init__(self, user_id=None, api_key=None):
240 240
241 241 self.user_id = user_id
242 242 self.api_key = None
243 243
244 244 self.username = 'None'
245 245 self.name = ''
246 246 self.lastname = ''
247 247 self.email = ''
248 248 self.is_authenticated = False
249 249 self.admin = False
250 250 self.permissions = {}
251 251 self._api_key = api_key
252 252 self.propagate_data()
253 253
254 254 def propagate_data(self):
255 255 user_model = UserModel()
256 256 self.anonymous_user = user_model.get_by_username('default', cache=True)
257 257 if self._api_key and self._api_key != self.anonymous_user.api_key:
258 258 #try go get user by api key
259 259 log.debug('Auth User lookup by API KEY %s', self._api_key)
260 260 user_model.fill_data(self, api_key=self._api_key)
261 261 else:
262 262 log.debug('Auth User lookup by USER ID %s', self.user_id)
263 263 if self.user_id is not None \
264 264 and self.user_id != self.anonymous_user.user_id:
265 265 user_model.fill_data(self, user_id=self.user_id)
266 266 else:
267 267 if self.anonymous_user.active is True:
268 268 user_model.fill_data(self,
269 269 user_id=self.anonymous_user.user_id)
270 270 #then we set this user is logged in
271 271 self.is_authenticated = True
272 272 else:
273 273 self.is_authenticated = False
274 274
275 275 log.debug('Auth User is now %s', self)
276 276 user_model.fill_perms(self)
277 277
278 278 @property
279 279 def is_admin(self):
280 280 return self.admin
281 281
282 282 def __repr__(self):
283 283 return "<AuthUser('id:%s:%s|%s')>" % (self.user_id, self.username,
284 284 self.is_authenticated)
285 285
286 286 def set_authenticated(self, authenticated=True):
287 287
288 288 if self.user_id != self.anonymous_user.user_id:
289 289 self.is_authenticated = authenticated
290 290
291 291
292 292 def set_available_permissions(config):
293 293 """This function will propagate pylons globals with all available defined
294 294 permission given in db. We don't want to check each time from db for new
295 295 permissions since adding a new permission also requires application restart
296 296 ie. to decorate new views with the newly created permission
297 297
298 298 :param config: current pylons config instance
299 299
300 300 """
301 301 log.info('getting information about all available permissions')
302 302 try:
303 303 sa = meta.Session()
304 304 all_perms = sa.query(Permission).all()
305 305 except:
306 306 pass
307 307 finally:
308 308 meta.Session.remove()
309 309
310 310 config['available_permissions'] = [x.permission_name for x in all_perms]
311 311
312 312
313 313 #==============================================================================
314 314 # CHECK DECORATORS
315 315 #==============================================================================
316 316 class LoginRequired(object):
317 317 """
318 318 Must be logged in to execute this function else
319 319 redirect to login page
320 320
321 321 :param api_access: if enabled this checks only for valid auth token
322 322 and grants access based on valid token
323 323 """
324 324
325 325 def __init__(self, api_access=False):
326 326 self.api_access = api_access
327 327
328 328 def __call__(self, func):
329 329 return decorator(self.__wrapper, func)
330 330
331 331 def __wrapper(self, func, *fargs, **fkwargs):
332 332 cls = fargs[0]
333 333 user = cls.rhodecode_user
334 334
335 335 api_access_ok = False
336 336 if self.api_access:
337 337 log.debug('Checking API KEY access for %s', cls)
338 338 if user.api_key == request.GET.get('api_key'):
339 339 api_access_ok = True
340 340 else:
341 341 log.debug("API KEY token not valid")
342 342
343 343 log.debug('Checking if %s is authenticated @ %s', user.username, cls)
344 344 if user.is_authenticated or api_access_ok:
345 345 log.debug('user %s is authenticated', user.username)
346 346 return func(*fargs, **fkwargs)
347 347 else:
348 348 log.warn('user %s NOT authenticated', user.username)
349 349 p = url.current()
350 350
351 351 log.debug('redirecting to login page with %s', p)
352 352 return redirect(url('login_home', came_from=p))
353 353
354 354
355 355 class NotAnonymous(object):
356 356 """Must be logged in to execute this function else
357 357 redirect to login page"""
358 358
359 359 def __call__(self, func):
360 360 return decorator(self.__wrapper, func)
361 361
362 362 def __wrapper(self, func, *fargs, **fkwargs):
363 363 cls = fargs[0]
364 364 self.user = cls.rhodecode_user
365 365
366 366 log.debug('Checking if user is not anonymous @%s', cls)
367 367
368 368 anonymous = self.user.username == 'default'
369 369
370 370 if anonymous:
371 371 p = ''
372 372 if request.environ.get('SCRIPT_NAME') != '/':
373 373 p += request.environ.get('SCRIPT_NAME')
374 374
375 375 p += request.environ.get('PATH_INFO')
376 376 if request.environ.get('QUERY_STRING'):
377 377 p += '?' + request.environ.get('QUERY_STRING')
378 378
379 379 import rhodecode.lib.helpers as h
380 380 h.flash(_('You need to be a registered user to '
381 381 'perform this action'),
382 382 category='warning')
383 383 return redirect(url('login_home', came_from=p))
384 384 else:
385 385 return func(*fargs, **fkwargs)
386 386
387 387
388 388 class PermsDecorator(object):
389 389 """Base class for controller decorators"""
390 390
391 391 def __init__(self, *required_perms):
392 392 available_perms = config['available_permissions']
393 393 for perm in required_perms:
394 394 if perm not in available_perms:
395 395 raise Exception("'%s' permission is not defined" % perm)
396 396 self.required_perms = set(required_perms)
397 397 self.user_perms = None
398 398
399 399 def __call__(self, func):
400 400 return decorator(self.__wrapper, func)
401 401
402 402 def __wrapper(self, func, *fargs, **fkwargs):
403 403 cls = fargs[0]
404 404 self.user = cls.rhodecode_user
405 405 self.user_perms = self.user.permissions
406 406 log.debug('checking %s permissions %s for %s %s',
407 407 self.__class__.__name__, self.required_perms, cls,
408 408 self.user)
409 409
410 410 if self.check_permissions():
411 411 log.debug('Permission granted for %s %s', cls, self.user)
412 412 return func(*fargs, **fkwargs)
413 413
414 414 else:
415 415 log.warning('Permission denied for %s %s', cls, self.user)
416 416 #redirect with forbidden ret code
417 417 return abort(403)
418 418
419 419 def check_permissions(self):
420 420 """Dummy function for overriding"""
421 421 raise Exception('You have to write this function in child class')
422 422
423 423
424 424 class HasPermissionAllDecorator(PermsDecorator):
425 425 """Checks for access permission for all given predicates. All of them
426 426 have to be meet in order to fulfill the request
427 427 """
428 428
429 429 def check_permissions(self):
430 430 if self.required_perms.issubset(self.user_perms.get('global')):
431 431 return True
432 432 return False
433 433
434 434
435 435 class HasPermissionAnyDecorator(PermsDecorator):
436 436 """Checks for access permission for any of given predicates. In order to
437 437 fulfill the request any of predicates must be meet
438 438 """
439 439
440 440 def check_permissions(self):
441 441 if self.required_perms.intersection(self.user_perms.get('global')):
442 442 return True
443 443 return False
444 444
445 445
446 446 class HasRepoPermissionAllDecorator(PermsDecorator):
447 447 """Checks for access permission for all given predicates for specific
448 448 repository. All of them have to be meet in order to fulfill the request
449 449 """
450 450
451 451 def check_permissions(self):
452 452 repo_name = get_repo_slug(request)
453 453 try:
454 454 user_perms = set([self.user_perms['repositories'][repo_name]])
455 455 except KeyError:
456 456 return False
457 457 if self.required_perms.issubset(user_perms):
458 458 return True
459 459 return False
460 460
461 461
462 462 class HasRepoPermissionAnyDecorator(PermsDecorator):
463 463 """Checks for access permission for any of given predicates for specific
464 464 repository. In order to fulfill the request any of predicates must be meet
465 465 """
466 466
467 467 def check_permissions(self):
468 468 repo_name = get_repo_slug(request)
469 469
470 470 try:
471 471 user_perms = set([self.user_perms['repositories'][repo_name]])
472 472 except KeyError:
473 473 return False
474 474 if self.required_perms.intersection(user_perms):
475 475 return True
476 476 return False
477 477
478 478
479 479 #==============================================================================
480 480 # CHECK FUNCTIONS
481 481 #==============================================================================
482 482 class PermsFunction(object):
483 483 """Base function for other check functions"""
484 484
485 485 def __init__(self, *perms):
486 486 available_perms = config['available_permissions']
487 487
488 488 for perm in perms:
489 489 if perm not in available_perms:
490 490 raise Exception("'%s' permission in not defined" % perm)
491 491 self.required_perms = set(perms)
492 492 self.user_perms = None
493 493 self.granted_for = ''
494 494 self.repo_name = None
495 495
496 496 def __call__(self, check_Location=''):
497 497 user = session.get('rhodecode_user', False)
498 498 if not user:
499 499 return False
500 500 self.user_perms = user.permissions
501 501 self.granted_for = user
502 502 log.debug('checking %s %s %s', self.__class__.__name__,
503 503 self.required_perms, user)
504 504
505 505 if self.check_permissions():
506 506 log.debug('Permission granted %s @ %s', self.granted_for,
507 507 check_Location or 'unspecified location')
508 508 return True
509 509
510 510 else:
511 511 log.warning('Permission denied for %s @ %s', self.granted_for,
512 512 check_Location or 'unspecified location')
513 513 return False
514 514
515 515 def check_permissions(self):
516 516 """Dummy function for overriding"""
517 517 raise Exception('You have to write this function in child class')
518 518
519 519
520 520 class HasPermissionAll(PermsFunction):
521 521 def check_permissions(self):
522 522 if self.required_perms.issubset(self.user_perms.get('global')):
523 523 return True
524 524 return False
525 525
526 526
527 527 class HasPermissionAny(PermsFunction):
528 528 def check_permissions(self):
529 529 if self.required_perms.intersection(self.user_perms.get('global')):
530 530 return True
531 531 return False
532 532
533 533
534 534 class HasRepoPermissionAll(PermsFunction):
535 535
536 536 def __call__(self, repo_name=None, check_Location=''):
537 537 self.repo_name = repo_name
538 538 return super(HasRepoPermissionAll, self).__call__(check_Location)
539 539
540 540 def check_permissions(self):
541 541 if not self.repo_name:
542 542 self.repo_name = get_repo_slug(request)
543 543
544 544 try:
545 545 self.user_perms = set([self.user_perms['reposit'
546 546 'ories'][self.repo_name]])
547 547 except KeyError:
548 548 return False
549 549 self.granted_for = self.repo_name
550 550 if self.required_perms.issubset(self.user_perms):
551 551 return True
552 552 return False
553 553
554 554
555 555 class HasRepoPermissionAny(PermsFunction):
556 556
557 557 def __call__(self, repo_name=None, check_Location=''):
558 558 self.repo_name = repo_name
559 559 return super(HasRepoPermissionAny, self).__call__(check_Location)
560 560
561 561 def check_permissions(self):
562 562 if not self.repo_name:
563 563 self.repo_name = get_repo_slug(request)
564 564
565 565 try:
566 566 self.user_perms = set([self.user_perms['reposi'
567 567 'tories'][self.repo_name]])
568 568 except KeyError:
569 569 return False
570 570 self.granted_for = self.repo_name
571 571 if self.required_perms.intersection(self.user_perms):
572 572 return True
573 573 return False
574 574
575 575
576 576 #==============================================================================
577 577 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
578 578 #==============================================================================
579 579 class HasPermissionAnyMiddleware(object):
580 580 def __init__(self, *perms):
581 581 self.required_perms = set(perms)
582 582
583 583 def __call__(self, user, repo_name):
584 584 usr = AuthUser(user.user_id)
585 585 try:
586 586 self.user_perms = set([usr.permissions['repositories'][repo_name]])
587 587 except:
588 588 self.user_perms = set()
589 589 self.granted_for = ''
590 590 self.username = user.username
591 591 self.repo_name = repo_name
592 592 return self.check_permissions()
593 593
594 594 def check_permissions(self):
595 595 log.debug('checking mercurial protocol '
596 596 'permissions %s for user:%s repository:%s', self.user_perms,
597 597 self.username, self.repo_name)
598 598 if self.required_perms.intersection(self.user_perms):
599 599 log.debug('permission granted')
600 600 return True
601 601 log.debug('permission denied')
602 602 return False
@@ -1,129 +1,135
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # ldap authentication lib
4 4 # Copyright (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
5 5 #
6 6 # This program is free software: you can redistribute it and/or modify
7 7 # it under the terms of the GNU General Public License as published by
8 8 # the Free Software Foundation, either version 3 of the License, or
9 9 # (at your option) any later version.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18 18 """
19 19 Created on Nov 17, 2010
20 20
21 21 @author: marcink
22 22 """
23 23
24 24 from rhodecode.lib.exceptions import *
25 25 import logging
26 26
27 27 log = logging.getLogger(__name__)
28 28
29 29 try:
30 30 import ldap
31 31 except ImportError:
32 32 pass
33 33
34 34 class AuthLdap(object):
35 35
36 36 def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='',
37 use_ldaps=False, tls_reqcert='DEMAND', ldap_version=3,
37 tls_kind = 'PLAIN', tls_reqcert='DEMAND', ldap_version=3,
38 38 ldap_filter='(&(objectClass=user)(!(objectClass=computer)))',
39 39 search_scope='SUBTREE',
40 40 attr_login='uid'):
41 41 self.ldap_version = ldap_version
42 if use_ldaps:
42 ldap_server_type = 'ldap'
43
44 self.TLS_KIND = tls_kind
45
46 if self.TLS_KIND == 'LDAPS':
43 47 port = port or 689
44 self.LDAP_USE_LDAPS = use_ldaps
48 ldap_server_type = ldap_server_type + 's'
49
45 50 self.TLS_REQCERT = ldap.__dict__['OPT_X_TLS_' + tls_reqcert]
46 51 self.LDAP_SERVER_ADDRESS = server
47 52 self.LDAP_SERVER_PORT = port
48 53
49 54 #USE FOR READ ONLY BIND TO LDAP SERVER
50 55 self.LDAP_BIND_DN = bind_dn
51 56 self.LDAP_BIND_PASS = bind_pass
52 57
53 ldap_server_type = 'ldap'
54 if self.LDAP_USE_LDAPS:ldap_server_type = ldap_server_type + 's'
55 58 self.LDAP_SERVER = "%s://%s:%s" % (ldap_server_type,
56 59 self.LDAP_SERVER_ADDRESS,
57 60 self.LDAP_SERVER_PORT)
58 61
59 62 self.BASE_DN = base_dn
60 63 self.LDAP_FILTER = ldap_filter
61 64 self.SEARCH_SCOPE = ldap.__dict__['SCOPE_' + search_scope]
62 65 self.attr_login = attr_login
63 66
64 67
65 68 def authenticate_ldap(self, username, password):
66 69 """Authenticate a user via LDAP and return his/her LDAP properties.
67 70
68 71 Raises AuthenticationError if the credentials are rejected, or
69 72 EnvironmentError if the LDAP server can't be reached.
70 73
71 74 :param username: username
72 75 :param password: password
73 76 """
74 77
75 78 from rhodecode.lib.helpers import chop_at
76 79
77 80 uid = chop_at(username, "@%s" % self.LDAP_SERVER_ADDRESS)
78 81
79 82 if "," in username:
80 83 raise LdapUsernameError("invalid character in username: ,")
81 84 try:
82 85 ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, '/etc/openldap/cacerts')
83 86 ldap.set_option(ldap.OPT_REFERRALS, ldap.OPT_OFF)
84 87 ldap.set_option(ldap.OPT_RESTART, ldap.OPT_ON)
85 88 ldap.set_option(ldap.OPT_TIMEOUT, 20)
86 89 ldap.set_option(ldap.OPT_NETWORK_TIMEOUT, 10)
87 90 ldap.set_option(ldap.OPT_TIMELIMIT, 15)
88 if self.LDAP_USE_LDAPS:
91 if self.TLS_KIND != 'PLAIN':
89 92 ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, self.TLS_REQCERT)
90 93 server = ldap.initialize(self.LDAP_SERVER)
91 94 if self.ldap_version == 2:
92 95 server.protocol = ldap.VERSION2
93 96 else:
94 97 server.protocol = ldap.VERSION3
95 98
99 if self.TLS_KIND == 'START_TLS':
100 server.start_tls_s()
101
96 102 if self.LDAP_BIND_DN and self.LDAP_BIND_PASS:
97 103 server.simple_bind_s(self.LDAP_BIND_DN, self.LDAP_BIND_PASS)
98 104
99 105 filt = '(&%s(%s=%s))' % (self.LDAP_FILTER, self.attr_login, username)
100 106 log.debug("Authenticating %r filt %s at %s", self.BASE_DN,
101 107 filt, self.LDAP_SERVER)
102 108 lobjects = server.search_ext_s(self.BASE_DN, self.SEARCH_SCOPE,
103 109 filt)
104 110
105 111 if not lobjects:
106 112 raise ldap.NO_SUCH_OBJECT()
107 113
108 114 for (dn, _attrs) in lobjects:
109 115 try:
110 116 server.simple_bind_s(dn, password)
111 117 attrs = server.search_ext_s(dn, ldap.SCOPE_BASE, '(objectClass=*)')[0][1]
112 118 break
113 119
114 120 except ldap.INVALID_CREDENTIALS, e:
115 121 log.debug("LDAP rejected password for user '%s' (%s): %s",
116 122 uid, username, dn)
117 123
118 124 else:
119 125 log.debug("No matching LDAP objects for authentication "
120 126 "of '%s' (%s)", uid, username)
121 127 raise LdapPasswordError()
122 128
123 129 except ldap.NO_SUCH_OBJECT, e:
124 130 log.debug("LDAP says no such user '%s' (%s)", uid, username)
125 131 raise LdapUsernameError()
126 132 except ldap.SERVER_DOWN, e:
127 133 raise LdapConnectionError("LDAP can't access authentication server")
128 134
129 135 return (dn, attrs)
@@ -1,514 +1,514
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.db_manage
4 4 ~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Database creation, and setup module for RhodeCode. Used for creation
7 7 of database as well as for migration operations
8 8
9 9 :created_on: Apr 10, 2010
10 10 :author: marcink
11 11 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 14 # This program is free software: you can redistribute it and/or modify
15 15 # it under the terms of the GNU General Public License as published by
16 16 # the Free Software Foundation, either version 3 of the License, or
17 17 # (at your option) any later version.
18 18 #
19 19 # This program is distributed in the hope that it will be useful,
20 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 22 # GNU General Public License for more details.
23 23 #
24 24 # You should have received a copy of the GNU General Public License
25 25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 26
27 27 import os
28 28 import sys
29 29 import uuid
30 30 import logging
31 31 from os.path import dirname as dn, join as jn
32 32
33 33 from rhodecode import __dbversion__
34 34 from rhodecode.model import meta
35 35
36 36 from rhodecode.lib.auth import get_crypt_password, generate_api_key
37 37 from rhodecode.lib.utils import ask_ok
38 38 from rhodecode.model import init_model
39 39 from rhodecode.model.db import User, Permission, RhodeCodeUi, \
40 40 RhodeCodeSettings, UserToPerm, DbMigrateVersion
41 41
42 42 from sqlalchemy.engine import create_engine
43 43
44 44 log = logging.getLogger(__name__)
45 45
46 46
47 47 class DbManage(object):
48 48 def __init__(self, log_sql, dbconf, root, tests=False):
49 49 self.dbname = dbconf.split('/')[-1]
50 50 self.tests = tests
51 51 self.root = root
52 52 self.dburi = dbconf
53 53 self.log_sql = log_sql
54 54 self.db_exists = False
55 55 self.init_db()
56 56
57 57 def init_db(self):
58 58 engine = create_engine(self.dburi, echo=self.log_sql)
59 59 init_model(engine)
60 60 self.sa = meta.Session()
61 61
62 62 def create_tables(self, override=False):
63 63 """Create a auth database
64 64 """
65 65
66 66 log.info("Any existing database is going to be destroyed")
67 67 if self.tests:
68 68 destroy = True
69 69 else:
70 70 destroy = ask_ok('Are you sure to destroy old database ? [y/n]')
71 71 if not destroy:
72 72 sys.exit()
73 73 if destroy:
74 74 meta.Base.metadata.drop_all()
75 75
76 76 checkfirst = not override
77 77 meta.Base.metadata.create_all(checkfirst=checkfirst)
78 78 log.info('Created tables for %s', self.dbname)
79 79
80 80 def set_db_version(self):
81 81 try:
82 82 ver = DbMigrateVersion()
83 83 ver.version = __dbversion__
84 84 ver.repository_id = 'rhodecode_db_migrations'
85 85 ver.repository_path = 'versions'
86 86 self.sa.add(ver)
87 87 self.sa.commit()
88 88 except:
89 89 self.sa.rollback()
90 90 raise
91 91 log.info('db version set to: %s', __dbversion__)
92 92
93 93 def upgrade(self):
94 94 """Upgrades given database schema to given revision following
95 95 all needed steps, to perform the upgrade
96 96
97 97 """
98 98
99 99 from rhodecode.lib.dbmigrate.migrate.versioning import api
100 100 from rhodecode.lib.dbmigrate.migrate.exceptions import \
101 101 DatabaseNotControlledError
102 102
103 103 upgrade = ask_ok('You are about to perform database upgrade, make '
104 104 'sure You backed up your database before. '
105 105 'Continue ? [y/n]')
106 106 if not upgrade:
107 107 sys.exit('Nothing done')
108 108
109 109 repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
110 110 'rhodecode/lib/dbmigrate')
111 111 db_uri = self.dburi
112 112
113 113 try:
114 114 curr_version = api.db_version(db_uri, repository_path)
115 115 msg = ('Found current database under version'
116 116 ' control with version %s' % curr_version)
117 117
118 118 except (RuntimeError, DatabaseNotControlledError), e:
119 119 curr_version = 1
120 120 msg = ('Current database is not under version control. Setting'
121 121 ' as version %s' % curr_version)
122 122 api.version_control(db_uri, repository_path, curr_version)
123 123
124 124 print (msg)
125 125
126 126 if curr_version == __dbversion__:
127 127 sys.exit('This database is already at the newest version')
128 128
129 129 #======================================================================
130 130 # UPGRADE STEPS
131 131 #======================================================================
132 132 class UpgradeSteps(object):
133 133 """Those steps follow schema versions so for example schema
134 134 for example schema with seq 002 == step_2 and so on.
135 135 """
136 136
137 137 def __init__(self, klass):
138 138 self.klass = klass
139 139
140 140 def step_0(self):
141 141 #step 0 is the schema upgrade, and than follow proper upgrades
142 142 print ('attempting to do database upgrade to version %s' \
143 143 % __dbversion__)
144 144 api.upgrade(db_uri, repository_path, __dbversion__)
145 145 print ('Schema upgrade completed')
146 146
147 147 def step_1(self):
148 148 pass
149 149
150 150 def step_2(self):
151 151 print ('Patching repo paths for newer version of RhodeCode')
152 152 self.klass.fix_repo_paths()
153 153
154 154 print ('Patching default user of RhodeCode')
155 155 self.klass.fix_default_user()
156 156
157 157 log.info('Changing ui settings')
158 158 self.klass.create_ui_settings()
159 159
160 160 def step_3(self):
161 161 print ('Adding additional settings into RhodeCode db')
162 162 self.klass.fix_settings()
163 163
164 164 upgrade_steps = [0] + range(curr_version + 1, __dbversion__ + 1)
165 165
166 166 #CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
167 167 for step in upgrade_steps:
168 168 print ('performing upgrade step %s' % step)
169 169 callable = getattr(UpgradeSteps(self), 'step_%s' % step)()
170 170
171 171 def fix_repo_paths(self):
172 172 """Fixes a old rhodecode version path into new one without a '*'
173 173 """
174 174
175 175 paths = self.sa.query(RhodeCodeUi)\
176 176 .filter(RhodeCodeUi.ui_key == '/')\
177 177 .scalar()
178 178
179 179 paths.ui_value = paths.ui_value.replace('*', '')
180 180
181 181 try:
182 182 self.sa.add(paths)
183 183 self.sa.commit()
184 184 except:
185 185 self.sa.rollback()
186 186 raise
187 187
188 188 def fix_default_user(self):
189 189 """Fixes a old default user with some 'nicer' default values,
190 190 used mostly for anonymous access
191 191 """
192 192 def_user = self.sa.query(User)\
193 193 .filter(User.username == 'default')\
194 194 .one()
195 195
196 196 def_user.name = 'Anonymous'
197 197 def_user.lastname = 'User'
198 198 def_user.email = 'anonymous@rhodecode.org'
199 199
200 200 try:
201 201 self.sa.add(def_user)
202 202 self.sa.commit()
203 203 except:
204 204 self.sa.rollback()
205 205 raise
206 206
207 207 def fix_settings(self):
208 208 """Fixes rhodecode settings adds ga_code key for google analytics
209 209 """
210 210
211 211 hgsettings3 = RhodeCodeSettings('ga_code', '')
212 212
213 213 try:
214 214 self.sa.add(hgsettings3)
215 215 self.sa.commit()
216 216 except:
217 217 self.sa.rollback()
218 218 raise
219 219
220 220 def admin_prompt(self, second=False):
221 221 if not self.tests:
222 222 import getpass
223 223
224 224 def get_password():
225 225 password = getpass.getpass('Specify admin password '
226 226 '(min 6 chars):')
227 227 confirm = getpass.getpass('Confirm password:')
228 228
229 229 if password != confirm:
230 230 log.error('passwords mismatch')
231 231 return False
232 232 if len(password) < 6:
233 233 log.error('password is to short use at least 6 characters')
234 234 return False
235 235
236 236 return password
237 237
238 238 username = raw_input('Specify admin username:')
239 239
240 240 password = get_password()
241 241 if not password:
242 242 #second try
243 243 password = get_password()
244 244 if not password:
245 245 sys.exit()
246 246
247 247 email = raw_input('Specify admin email:')
248 248 self.create_user(username, password, email, True)
249 249 else:
250 250 log.info('creating admin and regular test users')
251 251 self.create_user('test_admin', 'test12',
252 252 'test_admin@mail.com', True)
253 253 self.create_user('test_regular', 'test12',
254 254 'test_regular@mail.com', False)
255 255 self.create_user('test_regular2', 'test12',
256 256 'test_regular2@mail.com', False)
257 257
258 258 def create_ui_settings(self):
259 259 """Creates ui settings, fills out hooks
260 260 and disables dotencode
261 261
262 262 """
263 263 #HOOKS
264 264 hooks1_key = 'changegroup.update'
265 265 hooks1_ = self.sa.query(RhodeCodeUi)\
266 266 .filter(RhodeCodeUi.ui_key == hooks1_key).scalar()
267 267
268 268 hooks1 = RhodeCodeUi() if hooks1_ is None else hooks1_
269 269 hooks1.ui_section = 'hooks'
270 270 hooks1.ui_key = hooks1_key
271 271 hooks1.ui_value = 'hg update >&2'
272 272 hooks1.ui_active = False
273 273
274 274 hooks2_key = 'changegroup.repo_size'
275 275 hooks2_ = self.sa.query(RhodeCodeUi)\
276 276 .filter(RhodeCodeUi.ui_key == hooks2_key).scalar()
277 277
278 278 hooks2 = RhodeCodeUi() if hooks2_ is None else hooks2_
279 279 hooks2.ui_section = 'hooks'
280 280 hooks2.ui_key = hooks2_key
281 281 hooks2.ui_value = 'python:rhodecode.lib.hooks.repo_size'
282 282
283 283 hooks3 = RhodeCodeUi()
284 284 hooks3.ui_section = 'hooks'
285 285 hooks3.ui_key = 'pretxnchangegroup.push_logger'
286 286 hooks3.ui_value = 'python:rhodecode.lib.hooks.log_push_action'
287 287
288 288 hooks4 = RhodeCodeUi()
289 289 hooks4.ui_section = 'hooks'
290 290 hooks4.ui_key = 'preoutgoing.pull_logger'
291 291 hooks4.ui_value = 'python:rhodecode.lib.hooks.log_pull_action'
292 292
293 293 #For mercurial 1.7 set backward comapatibility with format
294 294 dotencode_disable = RhodeCodeUi()
295 295 dotencode_disable.ui_section = 'format'
296 296 dotencode_disable.ui_key = 'dotencode'
297 297 dotencode_disable.ui_value = 'false'
298 298
299 299 try:
300 300 self.sa.add(hooks1)
301 301 self.sa.add(hooks2)
302 302 self.sa.add(hooks3)
303 303 self.sa.add(hooks4)
304 304 self.sa.add(dotencode_disable)
305 305 self.sa.commit()
306 306 except:
307 307 self.sa.rollback()
308 308 raise
309 309
310 310 def create_ldap_options(self):
311 311 """Creates ldap settings"""
312 312
313 313 try:
314 314 for k, v in [('ldap_active', 'false'), ('ldap_host', ''),
315 ('ldap_port', '389'), ('ldap_ldaps', 'false'),
315 ('ldap_port', '389'), ('ldap_tls_kind', 'PLAIN'),
316 316 ('ldap_tls_reqcert', ''), ('ldap_dn_user', ''),
317 317 ('ldap_dn_pass', ''), ('ldap_base_dn', ''),
318 318 ('ldap_filter', ''), ('ldap_search_scope', ''),
319 319 ('ldap_attr_login', ''), ('ldap_attr_firstname', ''),
320 320 ('ldap_attr_lastname', ''), ('ldap_attr_email', '')]:
321 321
322 322 setting = RhodeCodeSettings(k, v)
323 323 self.sa.add(setting)
324 324 self.sa.commit()
325 325 except:
326 326 self.sa.rollback()
327 327 raise
328 328
329 329 def config_prompt(self, test_repo_path='', retries=3):
330 330 if retries == 3:
331 331 log.info('Setting up repositories config')
332 332
333 333 if not self.tests and not test_repo_path:
334 334 path = raw_input('Specify valid full path to your repositories'
335 335 ' you can change this later in application settings:')
336 336 else:
337 337 path = test_repo_path
338 338 path_ok = True
339 339
340 340 #check proper dir
341 341 if not os.path.isdir(path):
342 342 path_ok = False
343 343 log.error('Entered path is not a valid directory: %s [%s/3]',
344 344 path, retries)
345 345
346 346 #check write access
347 347 if not os.access(path, os.W_OK):
348 348 path_ok = False
349 349
350 350 log.error('No write permission to given path: %s [%s/3]',
351 351 path, retries)
352 352
353 353 if retries == 0:
354 354 sys.exit()
355 355 if path_ok is False:
356 356 retries -= 1
357 357 return self.config_prompt(test_repo_path, retries)
358 358
359 359 return path
360 360
361 361 def create_settings(self, path):
362 362
363 363 self.create_ui_settings()
364 364
365 365 #HG UI OPTIONS
366 366 web1 = RhodeCodeUi()
367 367 web1.ui_section = 'web'
368 368 web1.ui_key = 'push_ssl'
369 369 web1.ui_value = 'false'
370 370
371 371 web2 = RhodeCodeUi()
372 372 web2.ui_section = 'web'
373 373 web2.ui_key = 'allow_archive'
374 374 web2.ui_value = 'gz zip bz2'
375 375
376 376 web3 = RhodeCodeUi()
377 377 web3.ui_section = 'web'
378 378 web3.ui_key = 'allow_push'
379 379 web3.ui_value = '*'
380 380
381 381 web4 = RhodeCodeUi()
382 382 web4.ui_section = 'web'
383 383 web4.ui_key = 'baseurl'
384 384 web4.ui_value = '/'
385 385
386 386 paths = RhodeCodeUi()
387 387 paths.ui_section = 'paths'
388 388 paths.ui_key = '/'
389 389 paths.ui_value = path
390 390
391 391 hgsettings1 = RhodeCodeSettings('realm', 'RhodeCode authentication')
392 392 hgsettings2 = RhodeCodeSettings('title', 'RhodeCode')
393 393 hgsettings3 = RhodeCodeSettings('ga_code', '')
394 394
395 395 try:
396 396 self.sa.add(web1)
397 397 self.sa.add(web2)
398 398 self.sa.add(web3)
399 399 self.sa.add(web4)
400 400 self.sa.add(paths)
401 401 self.sa.add(hgsettings1)
402 402 self.sa.add(hgsettings2)
403 403 self.sa.add(hgsettings3)
404 404
405 405 self.sa.commit()
406 406 except:
407 407 self.sa.rollback()
408 408 raise
409 409
410 410 self.create_ldap_options()
411 411
412 412 log.info('created ui config')
413 413
414 414 def create_user(self, username, password, email='', admin=False):
415 415 log.info('creating administrator user %s', username)
416 416 new_user = User()
417 417 new_user.username = username
418 418 new_user.password = get_crypt_password(password)
419 419 new_user.api_key = generate_api_key(username)
420 420 new_user.name = 'RhodeCode'
421 421 new_user.lastname = 'Admin'
422 422 new_user.email = email
423 423 new_user.admin = admin
424 424 new_user.active = True
425 425
426 426 try:
427 427 self.sa.add(new_user)
428 428 self.sa.commit()
429 429 except:
430 430 self.sa.rollback()
431 431 raise
432 432
433 433 def create_default_user(self):
434 434 log.info('creating default user')
435 435 #create default user for handling default permissions.
436 436 def_user = User()
437 437 def_user.username = 'default'
438 438 def_user.password = get_crypt_password(str(uuid.uuid1())[:8])
439 439 def_user.api_key = generate_api_key('default')
440 440 def_user.name = 'Anonymous'
441 441 def_user.lastname = 'User'
442 442 def_user.email = 'anonymous@rhodecode.org'
443 443 def_user.admin = False
444 444 def_user.active = False
445 445 try:
446 446 self.sa.add(def_user)
447 447 self.sa.commit()
448 448 except:
449 449 self.sa.rollback()
450 450 raise
451 451
452 452 def create_permissions(self):
453 453 #module.(access|create|change|delete)_[name]
454 454 #module.(read|write|owner)
455 455 perms = [('repository.none', 'Repository no access'),
456 456 ('repository.read', 'Repository read access'),
457 457 ('repository.write', 'Repository write access'),
458 458 ('repository.admin', 'Repository admin access'),
459 459 ('hg.admin', 'Hg Administrator'),
460 460 ('hg.create.repository', 'Repository create'),
461 461 ('hg.create.none', 'Repository creation disabled'),
462 462 ('hg.register.none', 'Register disabled'),
463 463 ('hg.register.manual_activate', 'Register new user with '
464 464 'RhodeCode without manual'
465 465 'activation'),
466 466
467 467 ('hg.register.auto_activate', 'Register new user with '
468 468 'RhodeCode without auto '
469 469 'activation'),
470 470 ]
471 471
472 472 for p in perms:
473 473 new_perm = Permission()
474 474 new_perm.permission_name = p[0]
475 475 new_perm.permission_longname = p[1]
476 476 try:
477 477 self.sa.add(new_perm)
478 478 self.sa.commit()
479 479 except:
480 480 self.sa.rollback()
481 481 raise
482 482
483 483 def populate_default_permissions(self):
484 484 log.info('creating default user permissions')
485 485
486 486 default_user = self.sa.query(User)\
487 487 .filter(User.username == 'default').scalar()
488 488
489 489 reg_perm = UserToPerm()
490 490 reg_perm.user = default_user
491 491 reg_perm.permission = self.sa.query(Permission)\
492 492 .filter(Permission.permission_name == 'hg.register.manual_activate')\
493 493 .scalar()
494 494
495 495 create_repo_perm = UserToPerm()
496 496 create_repo_perm.user = default_user
497 497 create_repo_perm.permission = self.sa.query(Permission)\
498 498 .filter(Permission.permission_name == 'hg.create.repository')\
499 499 .scalar()
500 500
501 501 default_repo_perm = UserToPerm()
502 502 default_repo_perm.user = default_user
503 503 default_repo_perm.permission = self.sa.query(Permission)\
504 504 .filter(Permission.permission_name == 'repository.read')\
505 505 .scalar()
506 506
507 507 try:
508 508 self.sa.add(reg_perm)
509 509 self.sa.add(create_repo_perm)
510 510 self.sa.add(default_repo_perm)
511 511 self.sa.commit()
512 512 except:
513 513 self.sa.rollback()
514 514 raise
@@ -1,579 +1,579
1 1 """ this is forms validation classes
2 2 http://formencode.org/module-formencode.validators.html
3 3 for list off all availible validators
4 4
5 5 we can create our own validators
6 6
7 7 The table below outlines the options which can be used in a schema in addition to the validators themselves
8 8 pre_validators [] These validators will be applied before the schema
9 9 chained_validators [] These validators will be applied after the schema
10 10 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
11 11 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
12 12 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
13 13 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
14 14
15 15
16 16 <name> = formencode.validators.<name of validator>
17 17 <name> must equal form name
18 18 list=[1,2,3,4,5]
19 19 for SELECT use formencode.All(OneOf(list), Int())
20 20
21 21 """
22 22 import os
23 23 import re
24 24 import logging
25 25
26 26 import formencode
27 27 from formencode import All
28 28 from formencode.validators import UnicodeString, OneOf, Int, Number, Regex, \
29 29 Email, Bool, StringBoolean, Set
30 30
31 31 from pylons.i18n.translation import _
32 32 from webhelpers.pylonslib.secure_form import authentication_token
33 33
34 34 from rhodecode.lib.utils import repo_name_slug
35 35 from rhodecode.lib.auth import authenticate, get_crypt_password
36 36 from rhodecode.lib.exceptions import LdapImportError
37 37 from rhodecode.model import meta
38 38 from rhodecode.model.user import UserModel
39 39 from rhodecode.model.repo import RepoModel
40 40 from rhodecode.model.db import User, UsersGroup
41 41 from rhodecode import BACKENDS
42 42
43 43 log = logging.getLogger(__name__)
44 44
45 45 #this is needed to translate the messages using _() in validators
46 46 class State_obj(object):
47 47 _ = staticmethod(_)
48 48
49 49 #==============================================================================
50 50 # VALIDATORS
51 51 #==============================================================================
52 52 class ValidAuthToken(formencode.validators.FancyValidator):
53 53 messages = {'invalid_token':_('Token mismatch')}
54 54
55 55 def validate_python(self, value, state):
56 56
57 57 if value != authentication_token():
58 58 raise formencode.Invalid(self.message('invalid_token', state,
59 59 search_number=value), value, state)
60 60
61 61 def ValidUsername(edit, old_data):
62 62 class _ValidUsername(formencode.validators.FancyValidator):
63 63
64 64 def validate_python(self, value, state):
65 65 if value in ['default', 'new_user']:
66 66 raise formencode.Invalid(_('Invalid username'), value, state)
67 67 #check if user is unique
68 68 old_un = None
69 69 if edit:
70 70 old_un = UserModel().get(old_data.get('user_id')).username
71 71
72 72 if old_un != value or not edit:
73 73 if UserModel().get_by_username(value, cache=False,
74 74 case_insensitive=True):
75 75 raise formencode.Invalid(_('This username already '
76 76 'exists') , value, state)
77 77
78 78 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
79 79 raise formencode.Invalid(_('Username may only contain '
80 80 'alphanumeric characters '
81 81 'underscores, periods or dashes '
82 82 'and must begin with alphanumeric '
83 83 'character'), value, state)
84 84
85 85 return _ValidUsername
86 86
87 87
88 88 def ValidUsersGroup(edit, old_data):
89 89
90 90 class _ValidUsersGroup(formencode.validators.FancyValidator):
91 91
92 92 def validate_python(self, value, state):
93 93 if value in ['default']:
94 94 raise formencode.Invalid(_('Invalid group name'), value, state)
95 95 #check if group is unique
96 96 old_ugname = None
97 97 if edit:
98 98 old_ugname = UsersGroup.get(
99 99 old_data.get('users_group_id')).users_group_name
100 100
101 101 if old_ugname != value or not edit:
102 102 if UsersGroup.get_by_group_name(value, cache=False,
103 103 case_insensitive=True):
104 104 raise formencode.Invalid(_('This users group '
105 105 'already exists') , value,
106 106 state)
107 107
108 108
109 109 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
110 110 raise formencode.Invalid(_('Group name may only contain '
111 111 'alphanumeric characters '
112 112 'underscores, periods or dashes '
113 113 'and must begin with alphanumeric '
114 114 'character'), value, state)
115 115
116 116 return _ValidUsersGroup
117 117
118 118
119 119
120 120 class ValidPassword(formencode.validators.FancyValidator):
121 121
122 122 def to_python(self, value, state):
123 123
124 124 if value:
125 125
126 126 if value.get('password'):
127 127 try:
128 128 value['password'] = get_crypt_password(value['password'])
129 129 except UnicodeEncodeError:
130 130 e_dict = {'password':_('Invalid characters in password')}
131 131 raise formencode.Invalid('', value, state, error_dict=e_dict)
132 132
133 133 if value.get('password_confirmation'):
134 134 try:
135 135 value['password_confirmation'] = \
136 136 get_crypt_password(value['password_confirmation'])
137 137 except UnicodeEncodeError:
138 138 e_dict = {'password_confirmation':_('Invalid characters in password')}
139 139 raise formencode.Invalid('', value, state, error_dict=e_dict)
140 140
141 141 if value.get('new_password'):
142 142 try:
143 143 value['new_password'] = \
144 144 get_crypt_password(value['new_password'])
145 145 except UnicodeEncodeError:
146 146 e_dict = {'new_password':_('Invalid characters in password')}
147 147 raise formencode.Invalid('', value, state, error_dict=e_dict)
148 148
149 149 return value
150 150
151 151 class ValidPasswordsMatch(formencode.validators.FancyValidator):
152 152
153 153 def validate_python(self, value, state):
154 154
155 155 if value['password'] != value['password_confirmation']:
156 156 e_dict = {'password_confirmation':
157 157 _('Password do not match')}
158 158 raise formencode.Invalid('', value, state, error_dict=e_dict)
159 159
160 160 class ValidAuth(formencode.validators.FancyValidator):
161 161 messages = {
162 162 'invalid_password':_('invalid password'),
163 163 'invalid_login':_('invalid user name'),
164 164 'disabled_account':_('Your account is disabled')
165 165
166 166 }
167 167 #error mapping
168 168 e_dict = {'username':messages['invalid_login'],
169 169 'password':messages['invalid_password']}
170 170 e_dict_disable = {'username':messages['disabled_account']}
171 171
172 172 def validate_python(self, value, state):
173 173 password = value['password']
174 174 username = value['username']
175 175 user = UserModel().get_by_username(username)
176 176
177 177 if authenticate(username, password):
178 178 return value
179 179 else:
180 180 if user and user.active is False:
181 181 log.warning('user %s is disabled', username)
182 182 raise formencode.Invalid(self.message('disabled_account',
183 183 state=State_obj),
184 184 value, state,
185 185 error_dict=self.e_dict_disable)
186 186 else:
187 187 log.warning('user %s not authenticated', username)
188 188 raise formencode.Invalid(self.message('invalid_password',
189 189 state=State_obj), value, state,
190 190 error_dict=self.e_dict)
191 191
192 192 class ValidRepoUser(formencode.validators.FancyValidator):
193 193
194 194 def to_python(self, value, state):
195 195 sa = meta.Session()
196 196 try:
197 197 self.user_db = sa.query(User)\
198 198 .filter(User.active == True)\
199 199 .filter(User.username == value).one()
200 200 except Exception:
201 201 raise formencode.Invalid(_('This username is not valid'),
202 202 value, state)
203 203 finally:
204 204 meta.Session.remove()
205 205
206 206 return self.user_db.user_id
207 207
208 208 def ValidRepoName(edit, old_data):
209 209 class _ValidRepoName(formencode.validators.FancyValidator):
210 210
211 211 def to_python(self, value, state):
212 212 slug = repo_name_slug(value)
213 213 if slug in ['_admin']:
214 214 raise formencode.Invalid(_('This repository name is disallowed'),
215 215 value, state)
216 216 if old_data.get('repo_name') != value or not edit:
217 217 if RepoModel().get_by_repo_name(slug, cache=False):
218 218 raise formencode.Invalid(_('This repository already exists') ,
219 219 value, state)
220 220 return slug
221 221
222 222
223 223 return _ValidRepoName
224 224
225 225 def ValidCloneUri():
226 226 from mercurial.httprepo import httprepository, httpsrepository
227 227 from rhodecode.lib.utils import make_ui
228 228
229 229 class _ValidCloneUri(formencode.validators.FancyValidator):
230 230 def to_python(self, value, state):
231 231 if not value:
232 232 pass
233 233 elif value.startswith('https'):
234 234 try:
235 235 httpsrepository(make_ui('db'), value).capabilities()
236 236 except:
237 237 raise formencode.Invalid(_('invalid clone url'), value,
238 238 state)
239 239 elif value.startswith('http'):
240 240 try:
241 241 httprepository(make_ui('db'), value).capabilities()
242 242 except:
243 243 raise formencode.Invalid(_('invalid clone url'), value,
244 244 state)
245 245 else:
246 246 raise formencode.Invalid(_('Invalid clone url, provide a '
247 247 'valid clone http\s url'), value,
248 248 state)
249 249
250 250 return _ValidCloneUri
251 251
252 252 def ValidForkType(old_data):
253 253 class _ValidForkType(formencode.validators.FancyValidator):
254 254
255 255 def to_python(self, value, state):
256 256 if old_data['repo_type'] != value:
257 257 raise formencode.Invalid(_('Fork have to be the same '
258 258 'type as original'), value, state)
259 259 return value
260 260 return _ValidForkType
261 261
262 262 class ValidPerms(formencode.validators.FancyValidator):
263 263 messages = {'perm_new_member_name':_('This username or users group name'
264 264 ' is not valid')}
265 265
266 266 def to_python(self, value, state):
267 267 perms_update = []
268 268 perms_new = []
269 269 #build a list of permission to update and new permission to create
270 270 for k, v in value.items():
271 271 #means new added member to permissions
272 272 if k.startswith('perm_new_member'):
273 273 new_perm = value.get('perm_new_member', False)
274 274 new_member = value.get('perm_new_member_name', False)
275 275 new_type = value.get('perm_new_member_type')
276 276
277 277 if new_member and new_perm:
278 278 if (new_member, new_perm, new_type) not in perms_new:
279 279 perms_new.append((new_member, new_perm, new_type))
280 280 elif k.startswith('u_perm_') or k.startswith('g_perm_'):
281 281 member = k[7:]
282 282 t = {'u':'user',
283 283 'g':'users_group'}[k[0]]
284 284 if member == 'default':
285 285 if value['private']:
286 286 #set none for default when updating to private repo
287 287 v = 'repository.none'
288 288 perms_update.append((member, v, t))
289 289
290 290 value['perms_updates'] = perms_update
291 291 value['perms_new'] = perms_new
292 292
293 293 #update permissions
294 294 sa = meta.Session
295 295 for k, v, t in perms_new:
296 296 try:
297 297 if t is 'user':
298 298 self.user_db = sa.query(User)\
299 299 .filter(User.active == True)\
300 300 .filter(User.username == k).one()
301 301 if t is 'users_group':
302 302 self.user_db = sa.query(UsersGroup)\
303 303 .filter(UsersGroup.users_group_active == True)\
304 304 .filter(UsersGroup.users_group_name == k).one()
305 305
306 306 except Exception:
307 307 msg = self.message('perm_new_member_name',
308 308 state=State_obj)
309 309 raise formencode.Invalid(msg, value, state,
310 310 error_dict={'perm_new_member_name':msg})
311 311 return value
312 312
313 313 class ValidSettings(formencode.validators.FancyValidator):
314 314
315 315 def to_python(self, value, state):
316 316 #settings form can't edit user
317 317 if value.has_key('user'):
318 318 del['value']['user']
319 319
320 320 return value
321 321
322 322 class ValidPath(formencode.validators.FancyValidator):
323 323 def to_python(self, value, state):
324 324
325 325 if not os.path.isdir(value):
326 326 msg = _('This is not a valid path')
327 327 raise formencode.Invalid(msg, value, state,
328 328 error_dict={'paths_root_path':msg})
329 329 return value
330 330
331 331 def UniqSystemEmail(old_data):
332 332 class _UniqSystemEmail(formencode.validators.FancyValidator):
333 333 def to_python(self, value, state):
334 334 value = value.lower()
335 335 if old_data.get('email') != value:
336 336 sa = meta.Session()
337 337 try:
338 338 user = sa.query(User).filter(User.email == value).scalar()
339 339 if user:
340 340 raise formencode.Invalid(_("This e-mail address is already taken") ,
341 341 value, state)
342 342 finally:
343 343 meta.Session.remove()
344 344
345 345 return value
346 346
347 347 return _UniqSystemEmail
348 348
349 349 class ValidSystemEmail(formencode.validators.FancyValidator):
350 350 def to_python(self, value, state):
351 351 value = value.lower()
352 352 sa = meta.Session
353 353 try:
354 354 user = sa.query(User).filter(User.email == value).scalar()
355 355 if user is None:
356 356 raise formencode.Invalid(_("This e-mail address doesn't exist.") ,
357 357 value, state)
358 358 finally:
359 359 meta.Session.remove()
360 360
361 361 return value
362 362
363 363 class LdapLibValidator(formencode.validators.FancyValidator):
364 364
365 365 def to_python(self, value, state):
366 366
367 367 try:
368 368 import ldap
369 369 except ImportError:
370 370 raise LdapImportError
371 371 return value
372 372
373 373 class AttrLoginValidator(formencode.validators.FancyValidator):
374 374
375 375 def to_python(self, value, state):
376 376
377 377 if not value or not isinstance(value, (str, unicode)):
378 378 raise formencode.Invalid(_("The LDAP Login attribute of the CN "
379 379 "must be specified - this is the name "
380 380 "of the attribute that is equivalent "
381 381 "to 'username'"),
382 382 value, state)
383 383
384 384 return value
385 385
386 386 #===============================================================================
387 387 # FORMS
388 388 #===============================================================================
389 389 class LoginForm(formencode.Schema):
390 390 allow_extra_fields = True
391 391 filter_extra_fields = True
392 392 username = UnicodeString(
393 393 strip=True,
394 394 min=1,
395 395 not_empty=True,
396 396 messages={
397 397 'empty':_('Please enter a login'),
398 398 'tooShort':_('Enter a value %(min)i characters long or more')}
399 399 )
400 400
401 401 password = UnicodeString(
402 402 strip=True,
403 403 min=6,
404 404 not_empty=True,
405 405 messages={
406 406 'empty':_('Please enter a password'),
407 407 'tooShort':_('Enter %(min)i characters or more')}
408 408 )
409 409
410 410
411 411 #chained validators have access to all data
412 412 chained_validators = [ValidAuth]
413 413
414 414 def UserForm(edit=False, old_data={}):
415 415 class _UserForm(formencode.Schema):
416 416 allow_extra_fields = True
417 417 filter_extra_fields = True
418 418 username = All(UnicodeString(strip=True, min=1, not_empty=True),
419 419 ValidUsername(edit, old_data))
420 420 if edit:
421 421 new_password = All(UnicodeString(strip=True, min=6, not_empty=False))
422 422 admin = StringBoolean(if_missing=False)
423 423 else:
424 424 password = All(UnicodeString(strip=True, min=6, not_empty=True))
425 425 active = StringBoolean(if_missing=False)
426 426 name = UnicodeString(strip=True, min=1, not_empty=True)
427 427 lastname = UnicodeString(strip=True, min=1, not_empty=True)
428 428 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
429 429
430 430 chained_validators = [ValidPassword]
431 431
432 432 return _UserForm
433 433
434 434
435 435 def UsersGroupForm(edit=False, old_data={}, available_members=[]):
436 436 class _UsersGroupForm(formencode.Schema):
437 437 allow_extra_fields = True
438 438 filter_extra_fields = True
439 439
440 440 users_group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
441 441 ValidUsersGroup(edit, old_data))
442 442
443 443 users_group_active = StringBoolean(if_missing=False)
444 444
445 445 if edit:
446 446 users_group_members = OneOf(available_members, hideList=False,
447 447 testValueList=True,
448 448 if_missing=None, not_empty=False)
449 449
450 450 return _UsersGroupForm
451 451
452 452 def RegisterForm(edit=False, old_data={}):
453 453 class _RegisterForm(formencode.Schema):
454 454 allow_extra_fields = True
455 455 filter_extra_fields = True
456 456 username = All(ValidUsername(edit, old_data),
457 457 UnicodeString(strip=True, min=1, not_empty=True))
458 458 password = All(UnicodeString(strip=True, min=6, not_empty=True))
459 459 password_confirmation = All(UnicodeString(strip=True, min=6, not_empty=True))
460 460 active = StringBoolean(if_missing=False)
461 461 name = UnicodeString(strip=True, min=1, not_empty=True)
462 462 lastname = UnicodeString(strip=True, min=1, not_empty=True)
463 463 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
464 464
465 465 chained_validators = [ValidPasswordsMatch, ValidPassword]
466 466
467 467 return _RegisterForm
468 468
469 469 def PasswordResetForm():
470 470 class _PasswordResetForm(formencode.Schema):
471 471 allow_extra_fields = True
472 472 filter_extra_fields = True
473 473 email = All(ValidSystemEmail(), Email(not_empty=True))
474 474 return _PasswordResetForm
475 475
476 476 def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
477 477 repo_groups=[]):
478 478 class _RepoForm(formencode.Schema):
479 479 allow_extra_fields = True
480 480 filter_extra_fields = False
481 481 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
482 482 ValidRepoName(edit, old_data))
483 483 clone_uri = All(UnicodeString(strip=True, min=1, not_empty=False),
484 484 ValidCloneUri()())
485 485 repo_group = OneOf(repo_groups, hideList=True)
486 486 repo_type = OneOf(supported_backends)
487 487 description = UnicodeString(strip=True, min=1, not_empty=True)
488 488 private = StringBoolean(if_missing=False)
489 489 enable_statistics = StringBoolean(if_missing=False)
490 490 enable_downloads = StringBoolean(if_missing=False)
491 491
492 492 if edit:
493 493 #this is repo owner
494 494 user = All(Int(not_empty=True), ValidRepoUser)
495 495
496 496 chained_validators = [ValidPerms]
497 497 return _RepoForm
498 498
499 499 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()):
500 500 class _RepoForkForm(formencode.Schema):
501 501 allow_extra_fields = True
502 502 filter_extra_fields = False
503 503 fork_name = All(UnicodeString(strip=True, min=1, not_empty=True),
504 504 ValidRepoName(edit, old_data))
505 505 description = UnicodeString(strip=True, min=1, not_empty=True)
506 506 private = StringBoolean(if_missing=False)
507 507 repo_type = All(ValidForkType(old_data), OneOf(supported_backends))
508 508 return _RepoForkForm
509 509
510 510 def RepoSettingsForm(edit=False, old_data={}):
511 511 class _RepoForm(formencode.Schema):
512 512 allow_extra_fields = True
513 513 filter_extra_fields = False
514 514 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
515 515 ValidRepoName(edit, old_data))
516 516 description = UnicodeString(strip=True, min=1, not_empty=True)
517 517 private = StringBoolean(if_missing=False)
518 518
519 519 chained_validators = [ValidPerms, ValidSettings]
520 520 return _RepoForm
521 521
522 522
523 523 def ApplicationSettingsForm():
524 524 class _ApplicationSettingsForm(formencode.Schema):
525 525 allow_extra_fields = True
526 526 filter_extra_fields = False
527 527 rhodecode_title = UnicodeString(strip=True, min=1, not_empty=True)
528 528 rhodecode_realm = UnicodeString(strip=True, min=1, not_empty=True)
529 529 rhodecode_ga_code = UnicodeString(strip=True, min=1, not_empty=False)
530 530
531 531 return _ApplicationSettingsForm
532 532
533 533 def ApplicationUiSettingsForm():
534 534 class _ApplicationUiSettingsForm(formencode.Schema):
535 535 allow_extra_fields = True
536 536 filter_extra_fields = False
537 537 web_push_ssl = OneOf(['true', 'false'], if_missing='false')
538 538 paths_root_path = All(ValidPath(), UnicodeString(strip=True, min=1, not_empty=True))
539 539 hooks_changegroup_update = OneOf(['True', 'False'], if_missing=False)
540 540 hooks_changegroup_repo_size = OneOf(['True', 'False'], if_missing=False)
541 541 hooks_pretxnchangegroup_push_logger = OneOf(['True', 'False'], if_missing=False)
542 542 hooks_preoutgoing_pull_logger = OneOf(['True', 'False'], if_missing=False)
543 543
544 544 return _ApplicationUiSettingsForm
545 545
546 546 def DefaultPermissionsForm(perms_choices, register_choices, create_choices):
547 547 class _DefaultPermissionsForm(formencode.Schema):
548 548 allow_extra_fields = True
549 549 filter_extra_fields = True
550 550 overwrite_default = StringBoolean(if_missing=False)
551 551 anonymous = OneOf(['True', 'False'], if_missing=False)
552 552 default_perm = OneOf(perms_choices)
553 553 default_register = OneOf(register_choices)
554 554 default_create = OneOf(create_choices)
555 555
556 556 return _DefaultPermissionsForm
557 557
558 558
559 def LdapSettingsForm(tls_reqcert_choices, search_scope_choices):
559 def LdapSettingsForm(tls_reqcert_choices, search_scope_choices, tls_kind_choices):
560 560 class _LdapSettingsForm(formencode.Schema):
561 561 allow_extra_fields = True
562 562 filter_extra_fields = True
563 563 pre_validators = [LdapLibValidator]
564 564 ldap_active = StringBoolean(if_missing=False)
565 565 ldap_host = UnicodeString(strip=True,)
566 566 ldap_port = Number(strip=True,)
567 ldap_ldaps = StringBoolean(if_missing=False)
567 ldap_tls_kind = OneOf(tls_kind_choices)
568 568 ldap_tls_reqcert = OneOf(tls_reqcert_choices)
569 569 ldap_dn_user = UnicodeString(strip=True,)
570 570 ldap_dn_pass = UnicodeString(strip=True,)
571 571 ldap_base_dn = UnicodeString(strip=True,)
572 572 ldap_filter = UnicodeString(strip=True,)
573 573 ldap_search_scope = OneOf(search_scope_choices)
574 574 ldap_attr_login = All(AttrLoginValidator, UnicodeString(strip=True,))
575 575 ldap_attr_firstname = UnicodeString(strip=True,)
576 576 ldap_attr_lastname = UnicodeString(strip=True,)
577 577 ldap_attr_email = UnicodeString(strip=True,)
578 578
579 579 return _LdapSettingsForm
@@ -1,103 +1,103
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.settings
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Settings model for RhodeCode
7 7
8 8 :created on Nov 17, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27
28 28 from rhodecode.model import BaseModel
29 29 from rhodecode.model.caching_query import FromCache
30 30 from rhodecode.model.db import RhodeCodeSettings
31 31
32 32 log = logging.getLogger(__name__)
33 33
34 34 class SettingsModel(BaseModel):
35 35 """
36 36 Settings model
37 37 """
38 38
39 39 def get(self, settings_key, cache=False):
40 40 r = self.sa.query(RhodeCodeSettings)\
41 41 .filter(RhodeCodeSettings.app_settings_name == settings_key).scalar()
42 42 if cache:
43 43 r = r.options(FromCache("sql_cache_short",
44 44 "get_setting_%s" % settings_key))
45 45 return r
46 46
47 47 def get_app_settings(self, cache=False):
48 48 """Get's config from database, each config key is prefixed with
49 49 'rhodecode_' prefix, than global pylons config is updated with such
50 50 keys
51 51 """
52 52
53 53 ret = self.sa.query(RhodeCodeSettings)
54 54
55 55 if cache:
56 56 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
57 57
58 58 if not ret:
59 59 raise Exception('Could not get application settings !')
60 60 settings = {}
61 61 for each in ret:
62 62 settings['rhodecode_' + each.app_settings_name] = each.app_settings_value
63 63
64 64 return settings
65 65
66 66 def get_ldap_settings(self):
67 67 """
68 68 Returns ldap settings from database
69 69 :returns:
70 70 ldap_active
71 71 ldap_host
72 72 ldap_port
73 ldap_ldaps
73 ldap_tls_kind
74 74 ldap_tls_reqcert
75 75 ldap_dn_user
76 76 ldap_dn_pass
77 77 ldap_base_dn
78 78 ldap_filter
79 79 ldap_search_scope
80 80 ldap_attr_login
81 81 ldap_attr_firstname
82 82 ldap_attr_lastname
83 83 ldap_attr_email
84 84 """
85 85 # ldap_search_scope
86 86
87 87 r = self.sa.query(RhodeCodeSettings)\
88 88 .filter(RhodeCodeSettings.app_settings_name\
89 89 .startswith('ldap_'))\
90 90 .all()
91 91
92 92 fd = {}
93 93
94 94 for row in r:
95 95 v = row.app_settings_value
96 96 if v in ['true', 'yes', 'on', 'y', 't', '1']:
97 97 v = True
98 98 elif v in ['false', 'no', 'off', 'n', 'f', '0']:
99 99 v = False
100 100
101 101 fd.update({row.app_settings_name:v})
102 102
103 103 return fd
@@ -1,103 +1,103
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3
4 4 <%def name="title()">
5 5 ${_('LDAP administration')} - ${c.rhodecode_name}
6 6 </%def>
7 7
8 8 <%def name="breadcrumbs_links()">
9 9 ${h.link_to(_('Admin'),h.url('admin_home'))}
10 10 &raquo;
11 11 ${_('Ldap')}
12 12 </%def>
13 13
14 14 <%def name="page_nav()">
15 15 ${self.menu('admin')}
16 16 </%def>
17 17
18 18 <%def name="main()">
19 19 <div class="box">
20 20 <!-- box / title -->
21 21 <div class="title">
22 22 ${self.breadcrumbs()}
23 23 </div>
24 24 ${h.form(url('ldap_settings'))}
25 25 <div class="form">
26 26 <div class="fields">
27 27
28 28 <h3>${_('Connection settings')}</h3>
29 29 <div class="field">
30 30 <div class="label label-checkbox"><label for="ldap_active">${_('Enable LDAP')}</label></div>
31 31 <div class="checkboxes"><div class="checkbox">${h.checkbox('ldap_active',True,class_='small')}</div></div>
32 32 </div>
33 33 <div class="field">
34 34 <div class="label"><label for="ldap_host">${_('Host')}</label></div>
35 35 <div class="input">${h.text('ldap_host',class_='small')}</div>
36 36 </div>
37 37 <div class="field">
38 38 <div class="label"><label for="ldap_port">${_('Port')}</label></div>
39 39 <div class="input">${h.text('ldap_port',class_='small')}</div>
40 40 </div>
41 41 <div class="field">
42 42 <div class="label"><label for="ldap_dn_user">${_('Account')}</label></div>
43 43 <div class="input">${h.text('ldap_dn_user',class_='small')}</div>
44 44 </div>
45 45 <div class="field">
46 46 <div class="label"><label for="ldap_dn_pass">${_('Password')}</label></div>
47 47 <div class="input">${h.password('ldap_dn_pass',class_='small')}</div>
48 48 </div>
49 49 <div class="field">
50 <div class="label label-checkbox"><label for="ldap_ldaps">${_('Enable LDAPS')}</label></div>
51 <div class="checkboxes"><div class="checkbox">${h.checkbox('ldap_ldaps',True,class_='small')}</div></div>
50 <div class="label"><label for="ldap_tls_kind">${_('Connection security')}</label></div>
51 <div class="select">${h.select('ldap_tls_kind',c.tls_kind_cur,c.tls_kind_choices,class_='small')}</div>
52 52 </div>
53 53 <div class="field">
54 54 <div class="label"><label for="ldap_tls_reqcert">${_('Certificate Checks')}</label></div>
55 55 <div class="select">${h.select('ldap_tls_reqcert',c.tls_reqcert_cur,c.tls_reqcert_choices,class_='small')}</div>
56 56 </div>
57 57 <h3>${_('Search settings')}</h3>
58 58 <div class="field">
59 59 <div class="label"><label for="ldap_base_dn">${_('Base DN')}</label></div>
60 60 <div class="input">${h.text('ldap_base_dn',class_='small')}</div>
61 61 </div>
62 62 <div class="field">
63 63 <div class="label"><label for="ldap_filter">${_('LDAP Filter')}</label></div>
64 64 <div class="input">${h.text('ldap_filter',class_='small')}</div>
65 65 </div>
66 66 <div class="field">
67 67 <div class="label"><label for="ldap_search_scope">${_('LDAP Search Scope')}</label></div>
68 68 <div class="select">${h.select('ldap_search_scope',c.search_scope_cur,c.search_scope_choices,class_='small')}</div>
69 69 </div>
70 70 <h3>${_('Attribute mappings')}</h3>
71 71 <div class="field">
72 72 <div class="label"><label for="ldap_attr_login">${_('Login Attribute')}</label></div>
73 73 <div class="input">${h.text('ldap_attr_login',class_='small')}</div>
74 74 </div>
75 75 <div class="field">
76 76 <div class="label"><label for="ldap_attr_firstname">${_('First Name Attribute')}</label></div>
77 77 <div class="input">${h.text('ldap_attr_firstname',class_='small')}</div>
78 78 </div>
79 79 <div class="field">
80 80 <div class="label"><label for="ldap_attr_lastname">${_('Last Name Attribute')}</label></div>
81 81 <div class="input">${h.text('ldap_attr_lastname',class_='small')}</div>
82 82 </div>
83 83 <div class="field">
84 84 <div class="label"><label for="ldap_attr_email">${_('E-mail Attribute')}</label></div>
85 85 <div class="input">${h.text('ldap_attr_email',class_='small')}</div>
86 86 </div>
87 87
88 88 <div class="buttons">
89 89 ${h.submit('save','Save',class_="ui-button")}
90 90 </div>
91 91 </div>
92 92 </div>
93 93 ${h.end_form()}
94 94 </div>
95 95 </%def>
96 96
97 97
98 98
99 99
100 100
101 101
102 102
103 103
General Comments 0
You need to be logged in to leave comments. Login now