##// END OF EJS Templates
fixed tests and missing replacements from 5f1850e4712a
marcink -
r3415:b8f929bf beta
parent child Browse files
Show More
@@ -1,1029 +1,1029 b''
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 :author: marcink
10 10 :copyright: (C) 2010-2012 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 random
27 27 import logging
28 28 import traceback
29 29 import hashlib
30 30
31 31 from tempfile import _RandomNameSequence
32 32 from decorator import decorator
33 33
34 34 from pylons import config, url, request
35 35 from pylons.controllers.util import abort, redirect
36 36 from pylons.i18n.translation import _
37 37 from sqlalchemy.orm.exc import ObjectDeletedError
38 38
39 39 from rhodecode import __platform__, is_windows, is_unix
40 40 from rhodecode.model.meta import Session
41 41
42 42 from rhodecode.lib.utils2 import str2bool, safe_unicode
43 43 from rhodecode.lib.exceptions import LdapPasswordError, LdapUsernameError
44 44 from rhodecode.lib.utils import get_repo_slug, get_repos_group_slug
45 45 from rhodecode.lib.auth_ldap import AuthLdap
46 46
47 47 from rhodecode.model import meta
48 48 from rhodecode.model.user import UserModel
49 49 from rhodecode.model.db import Permission, RhodeCodeSetting, User, UserIpMap
50 50 from rhodecode.lib.caching_query import FromCache
51 51
52 52 log = logging.getLogger(__name__)
53 53
54 54
55 55 class PasswordGenerator(object):
56 56 """
57 57 This is a simple class for generating password from different sets of
58 58 characters
59 59 usage::
60 60
61 61 passwd_gen = PasswordGenerator()
62 62 #print 8-letter password containing only big and small letters
63 63 of alphabet
64 64 passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
65 65 """
66 66 ALPHABETS_NUM = r'''1234567890'''
67 67 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
68 68 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
69 69 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
70 70 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
71 71 + ALPHABETS_NUM + ALPHABETS_SPECIAL
72 72 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
73 73 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
74 74 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
75 75 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
76 76
77 77 def __init__(self, passwd=''):
78 78 self.passwd = passwd
79 79
80 80 def gen_password(self, length, type_=None):
81 81 if type_ is None:
82 82 type_ = self.ALPHABETS_FULL
83 83 self.passwd = ''.join([random.choice(type_) for _ in xrange(length)])
84 84 return self.passwd
85 85
86 86
87 87 class RhodeCodeCrypto(object):
88 88
89 89 @classmethod
90 90 def hash_string(cls, str_):
91 91 """
92 92 Cryptographic function used for password hashing based on pybcrypt
93 93 or pycrypto in windows
94 94
95 95 :param password: password to hash
96 96 """
97 97 if is_windows:
98 98 from hashlib import sha256
99 99 return sha256(str_).hexdigest()
100 100 elif is_unix:
101 101 import bcrypt
102 102 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
103 103 else:
104 104 raise Exception('Unknown or unsupported platform %s' \
105 105 % __platform__)
106 106
107 107 @classmethod
108 108 def hash_check(cls, password, hashed):
109 109 """
110 110 Checks matching password with it's hashed value, runs different
111 111 implementation based on platform it runs on
112 112
113 113 :param password: password
114 114 :param hashed: password in hashed form
115 115 """
116 116
117 117 if is_windows:
118 118 from hashlib import sha256
119 119 return sha256(password).hexdigest() == hashed
120 120 elif is_unix:
121 121 import bcrypt
122 122 return bcrypt.hashpw(password, hashed) == hashed
123 123 else:
124 124 raise Exception('Unknown or unsupported platform %s' \
125 125 % __platform__)
126 126
127 127
128 128 def get_crypt_password(password):
129 129 return RhodeCodeCrypto.hash_string(password)
130 130
131 131
132 132 def check_password(password, hashed):
133 133 return RhodeCodeCrypto.hash_check(password, hashed)
134 134
135 135
136 136 def generate_api_key(str_, salt=None):
137 137 """
138 138 Generates API KEY from given string
139 139
140 140 :param str_:
141 141 :param salt:
142 142 """
143 143
144 144 if salt is None:
145 145 salt = _RandomNameSequence().next()
146 146
147 147 return hashlib.sha1(str_ + salt).hexdigest()
148 148
149 149
150 150 def authfunc(environ, username, password):
151 151 """
152 152 Dummy authentication wrapper function used in Mercurial and Git for
153 153 access control.
154 154
155 155 :param environ: needed only for using in Basic auth
156 156 """
157 157 return authenticate(username, password)
158 158
159 159
160 160 def authenticate(username, password):
161 161 """
162 162 Authentication function used for access control,
163 163 firstly checks for db authentication then if ldap is enabled for ldap
164 164 authentication, also creates ldap user if not in database
165 165
166 166 :param username: username
167 167 :param password: password
168 168 """
169 169
170 170 user_model = UserModel()
171 171 user = User.get_by_username(username)
172 172
173 173 log.debug('Authenticating user using RhodeCode account')
174 174 if user is not None and not user.ldap_dn:
175 175 if user.active:
176 176 if user.username == 'default' and user.active:
177 177 log.info('user %s authenticated correctly as anonymous user' %
178 178 username)
179 179 return True
180 180
181 181 elif user.username == username and check_password(password,
182 182 user.password):
183 183 log.info('user %s authenticated correctly' % username)
184 184 return True
185 185 else:
186 186 log.warning('user %s tried auth but is disabled' % username)
187 187
188 188 else:
189 189 log.debug('Regular authentication failed')
190 190 user_obj = User.get_by_username(username, case_insensitive=True)
191 191
192 192 if user_obj is not None and not user_obj.ldap_dn:
193 193 log.debug('this user already exists as non ldap')
194 194 return False
195 195
196 196 ldap_settings = RhodeCodeSetting.get_ldap_settings()
197 197 #======================================================================
198 198 # FALLBACK TO LDAP AUTH IF ENABLE
199 199 #======================================================================
200 200 if str2bool(ldap_settings.get('ldap_active')):
201 201 log.debug("Authenticating user using ldap")
202 202 kwargs = {
203 203 'server': ldap_settings.get('ldap_host', ''),
204 204 'base_dn': ldap_settings.get('ldap_base_dn', ''),
205 205 'port': ldap_settings.get('ldap_port'),
206 206 'bind_dn': ldap_settings.get('ldap_dn_user'),
207 207 'bind_pass': ldap_settings.get('ldap_dn_pass'),
208 208 'tls_kind': ldap_settings.get('ldap_tls_kind'),
209 209 'tls_reqcert': ldap_settings.get('ldap_tls_reqcert'),
210 210 'ldap_filter': ldap_settings.get('ldap_filter'),
211 211 'search_scope': ldap_settings.get('ldap_search_scope'),
212 212 'attr_login': ldap_settings.get('ldap_attr_login'),
213 213 'ldap_version': 3,
214 214 }
215 215 log.debug('Checking for ldap authentication')
216 216 try:
217 217 aldap = AuthLdap(**kwargs)
218 218 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username,
219 219 password)
220 220 log.debug('Got ldap DN response %s' % user_dn)
221 221
222 222 get_ldap_attr = lambda k: ldap_attrs.get(ldap_settings\
223 223 .get(k), [''])[0]
224 224
225 225 user_attrs = {
226 226 'name': safe_unicode(get_ldap_attr('ldap_attr_firstname')),
227 227 'lastname': safe_unicode(get_ldap_attr('ldap_attr_lastname')),
228 228 'email': get_ldap_attr('ldap_attr_email'),
229 229 'active': 'hg.register.auto_activate' in User\
230 230 .get_by_username('default').AuthUser.permissions['global']
231 231 }
232 232
233 233 # don't store LDAP password since we don't need it. Override
234 234 # with some random generated password
235 235 _password = PasswordGenerator().gen_password(length=8)
236 236 # create this user on the fly if it doesn't exist in rhodecode
237 237 # database
238 238 if user_model.create_ldap(username, _password, user_dn,
239 239 user_attrs):
240 240 log.info('created new ldap user %s' % username)
241 241
242 242 Session().commit()
243 243 return True
244 244 except (LdapUsernameError, LdapPasswordError,):
245 245 pass
246 246 except (Exception,):
247 247 log.error(traceback.format_exc())
248 248 pass
249 249 return False
250 250
251 251
252 252 def login_container_auth(username):
253 253 user = User.get_by_username(username)
254 254 if user is None:
255 255 user_attrs = {
256 256 'name': username,
257 257 'lastname': None,
258 258 'email': None,
259 259 'active': 'hg.register.auto_activate' in User\
260 260 .get_by_username('default').AuthUser.permissions['global']
261 261 }
262 262 user = UserModel().create_for_container_auth(username, user_attrs)
263 263 if not user:
264 264 return None
265 265 log.info('User %s was created by container authentication' % username)
266 266
267 267 if not user.active:
268 268 return None
269 269
270 270 user.update_lastlogin()
271 271 Session().commit()
272 272
273 273 log.debug('User %s is now logged in by container authentication',
274 274 user.username)
275 275 return user
276 276
277 277
278 278 def get_container_username(environ, config, clean_username=False):
279 279 """
280 280 Get's the container_auth username (or email). It tries to get username
281 281 from REMOTE_USER if container_auth_enabled is enabled, if that fails
282 282 it tries to get username from HTTP_X_FORWARDED_USER if proxypass_auth_enabled
283 283 is enabled. clean_username extracts the username from this data if it's
284 284 having @ in it.
285 285
286 286 :param environ:
287 287 :param config:
288 288 :param clean_username:
289 289 """
290 290 username = None
291 291
292 292 if str2bool(config.get('container_auth_enabled', False)):
293 293 from paste.httpheaders import REMOTE_USER
294 294 username = REMOTE_USER(environ)
295 295 log.debug('extracted REMOTE_USER:%s' % (username))
296 296
297 297 if not username and str2bool(config.get('proxypass_auth_enabled', False)):
298 298 username = environ.get('HTTP_X_FORWARDED_USER')
299 299 log.debug('extracted HTTP_X_FORWARDED_USER:%s' % (username))
300 300
301 301 if username and clean_username:
302 302 # Removing realm and domain from username
303 303 username = username.partition('@')[0]
304 304 username = username.rpartition('\\')[2]
305 305 log.debug('Received username %s from container' % username)
306 306
307 307 return username
308 308
309 309
310 310 class CookieStoreWrapper(object):
311 311
312 312 def __init__(self, cookie_store):
313 313 self.cookie_store = cookie_store
314 314
315 315 def __repr__(self):
316 316 return 'CookieStore<%s>' % (self.cookie_store)
317 317
318 318 def get(self, key, other=None):
319 319 if isinstance(self.cookie_store, dict):
320 320 return self.cookie_store.get(key, other)
321 321 elif isinstance(self.cookie_store, AuthUser):
322 322 return self.cookie_store.__dict__.get(key, other)
323 323
324 324
325 325 class AuthUser(object):
326 326 """
327 327 A simple object that handles all attributes of user in RhodeCode
328 328
329 329 It does lookup based on API key,given user, or user present in session
330 330 Then it fills all required information for such user. It also checks if
331 331 anonymous access is enabled and if so, it returns default user as logged
332 332 in
333 333 """
334 334
335 335 def __init__(self, user_id=None, api_key=None, username=None, ip_addr=None):
336 336
337 337 self.user_id = user_id
338 338 self.api_key = None
339 339 self.username = username
340 340 self.ip_addr = ip_addr
341 341
342 342 self.name = ''
343 343 self.lastname = ''
344 344 self.email = ''
345 345 self.is_authenticated = False
346 346 self.admin = False
347 347 self.inherit_default_permissions = False
348 348 self.permissions = {}
349 349 self._api_key = api_key
350 350 self.propagate_data()
351 351 self._instance = None
352 352
353 353 def propagate_data(self):
354 354 user_model = UserModel()
355 355 self.anonymous_user = User.get_by_username('default', cache=True)
356 356 is_user_loaded = False
357 357
358 358 # try go get user by api key
359 359 if self._api_key and self._api_key != self.anonymous_user.api_key:
360 360 log.debug('Auth User lookup by API KEY %s' % self._api_key)
361 361 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
362 362 # lookup by userid
363 363 elif (self.user_id is not None and
364 364 self.user_id != self.anonymous_user.user_id):
365 365 log.debug('Auth User lookup by USER ID %s' % self.user_id)
366 366 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
367 367 # lookup by username
368 368 elif self.username and \
369 369 str2bool(config.get('container_auth_enabled', False)):
370 370
371 371 log.debug('Auth User lookup by USER NAME %s' % self.username)
372 372 dbuser = login_container_auth(self.username)
373 373 if dbuser is not None:
374 374 log.debug('filling all attributes to object')
375 375 for k, v in dbuser.get_dict().items():
376 376 setattr(self, k, v)
377 377 self.set_authenticated()
378 378 is_user_loaded = True
379 379 else:
380 380 log.debug('No data in %s that could been used to log in' % self)
381 381
382 382 if not is_user_loaded:
383 383 # if we cannot authenticate user try anonymous
384 384 if self.anonymous_user.active is True:
385 385 user_model.fill_data(self, user_id=self.anonymous_user.user_id)
386 386 # then we set this user is logged in
387 387 self.is_authenticated = True
388 388 else:
389 389 self.user_id = None
390 390 self.username = None
391 391 self.is_authenticated = False
392 392
393 393 if not self.username:
394 394 self.username = 'None'
395 395
396 396 log.debug('Auth User is now %s' % self)
397 397 user_model.fill_perms(self)
398 398
399 399 @property
400 400 def is_admin(self):
401 401 return self.admin
402 402
403 403 @property
404 404 def repos_admin(self):
405 405 """
406 406 Returns list of repositories you're an admin of
407 407 """
408 408 return [x[0] for x in self.permissions['repositories'].iteritems()
409 409 if x[1] == 'repository.admin']
410 410
411 411 @property
412 412 def groups_admin(self):
413 413 """
414 Returns list of repositories groups you're an admin of
414 Returns list of repository groups you're an admin of
415 415 """
416 416 return [x[0] for x in self.permissions['repositories_groups'].iteritems()
417 417 if x[1] == 'group.admin']
418 418
419 419 @property
420 420 def ip_allowed(self):
421 421 """
422 422 Checks if ip_addr used in constructor is allowed from defined list of
423 423 allowed ip_addresses for user
424 424
425 425 :returns: boolean, True if ip is in allowed ip range
426 426 """
427 427 #check IP
428 428 allowed_ips = AuthUser.get_allowed_ips(self.user_id, cache=True)
429 429 if check_ip_access(source_ip=self.ip_addr, allowed_ips=allowed_ips):
430 430 log.debug('IP:%s is in range of %s' % (self.ip_addr, allowed_ips))
431 431 return True
432 432 else:
433 433 log.info('Access for IP:%s forbidden, '
434 434 'not in %s' % (self.ip_addr, allowed_ips))
435 435 return False
436 436
437 437 def __repr__(self):
438 438 return "<AuthUser('id:%s:%s|%s')>" % (self.user_id, self.username,
439 439 self.is_authenticated)
440 440
441 441 def set_authenticated(self, authenticated=True):
442 442 if self.user_id != self.anonymous_user.user_id:
443 443 self.is_authenticated = authenticated
444 444
445 445 def get_cookie_store(self):
446 446 return {'username': self.username,
447 447 'user_id': self.user_id,
448 448 'is_authenticated': self.is_authenticated}
449 449
450 450 @classmethod
451 451 def from_cookie_store(cls, cookie_store):
452 452 """
453 453 Creates AuthUser from a cookie store
454 454
455 455 :param cls:
456 456 :param cookie_store:
457 457 """
458 458 user_id = cookie_store.get('user_id')
459 459 username = cookie_store.get('username')
460 460 api_key = cookie_store.get('api_key')
461 461 return AuthUser(user_id, api_key, username)
462 462
463 463 @classmethod
464 464 def get_allowed_ips(cls, user_id, cache=False):
465 465 _set = set()
466 466 user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id)
467 467 if cache:
468 468 user_ips = user_ips.options(FromCache("sql_cache_short",
469 469 "get_user_ips_%s" % user_id))
470 470 for ip in user_ips:
471 471 try:
472 472 _set.add(ip.ip_addr)
473 473 except ObjectDeletedError:
474 474 # since we use heavy caching sometimes it happens that we get
475 475 # deleted objects here, we just skip them
476 476 pass
477 477 return _set or set(['0.0.0.0/0', '::/0'])
478 478
479 479
480 480 def set_available_permissions(config):
481 481 """
482 482 This function will propagate pylons globals with all available defined
483 483 permission given in db. We don't want to check each time from db for new
484 484 permissions since adding a new permission also requires application restart
485 485 ie. to decorate new views with the newly created permission
486 486
487 487 :param config: current pylons config instance
488 488
489 489 """
490 490 log.info('getting information about all available permissions')
491 491 try:
492 492 sa = meta.Session
493 493 all_perms = sa.query(Permission).all()
494 494 except Exception:
495 495 pass
496 496 finally:
497 497 meta.Session.remove()
498 498
499 499 config['available_permissions'] = [x.permission_name for x in all_perms]
500 500
501 501
502 502 #==============================================================================
503 503 # CHECK DECORATORS
504 504 #==============================================================================
505 505 class LoginRequired(object):
506 506 """
507 507 Must be logged in to execute this function else
508 508 redirect to login page
509 509
510 510 :param api_access: if enabled this checks only for valid auth token
511 511 and grants access based on valid token
512 512 """
513 513
514 514 def __init__(self, api_access=False):
515 515 self.api_access = api_access
516 516
517 517 def __call__(self, func):
518 518 return decorator(self.__wrapper, func)
519 519
520 520 def __wrapper(self, func, *fargs, **fkwargs):
521 521 cls = fargs[0]
522 522 user = cls.rhodecode_user
523 523 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
524 524
525 525 #check IP
526 526 ip_access_ok = True
527 527 if not user.ip_allowed:
528 528 from rhodecode.lib import helpers as h
529 529 h.flash(h.literal(_('IP %s not allowed' % (user.ip_addr))),
530 530 category='warning')
531 531 ip_access_ok = False
532 532
533 533 api_access_ok = False
534 534 if self.api_access:
535 535 log.debug('Checking API KEY access for %s' % cls)
536 536 if user.api_key == request.GET.get('api_key'):
537 537 api_access_ok = True
538 538 else:
539 539 log.debug("API KEY token not valid")
540 540
541 541 log.debug('Checking if %s is authenticated @ %s' % (user.username, loc))
542 542 if (user.is_authenticated or api_access_ok) and ip_access_ok:
543 543 reason = 'RegularAuth' if user.is_authenticated else 'APIAuth'
544 544 log.info('user %s is authenticated and granted access to %s '
545 545 'using %s' % (user.username, loc, reason)
546 546 )
547 547 return func(*fargs, **fkwargs)
548 548 else:
549 549 log.warn('user %s NOT authenticated on func: %s' % (
550 550 user, loc)
551 551 )
552 552 p = url.current()
553 553
554 554 log.debug('redirecting to login page with %s' % p)
555 555 return redirect(url('login_home', came_from=p))
556 556
557 557
558 558 class NotAnonymous(object):
559 559 """
560 560 Must be logged in to execute this function else
561 561 redirect to login page"""
562 562
563 563 def __call__(self, func):
564 564 return decorator(self.__wrapper, func)
565 565
566 566 def __wrapper(self, func, *fargs, **fkwargs):
567 567 cls = fargs[0]
568 568 self.user = cls.rhodecode_user
569 569
570 570 log.debug('Checking if user is not anonymous @%s' % cls)
571 571
572 572 anonymous = self.user.username == 'default'
573 573
574 574 if anonymous:
575 575 p = url.current()
576 576
577 577 import rhodecode.lib.helpers as h
578 578 h.flash(_('You need to be a registered user to '
579 579 'perform this action'),
580 580 category='warning')
581 581 return redirect(url('login_home', came_from=p))
582 582 else:
583 583 return func(*fargs, **fkwargs)
584 584
585 585
586 586 class PermsDecorator(object):
587 587 """Base class for controller decorators"""
588 588
589 589 def __init__(self, *required_perms):
590 590 available_perms = config['available_permissions']
591 591 for perm in required_perms:
592 592 if perm not in available_perms:
593 593 raise Exception("'%s' permission is not defined" % perm)
594 594 self.required_perms = set(required_perms)
595 595 self.user_perms = None
596 596
597 597 def __call__(self, func):
598 598 return decorator(self.__wrapper, func)
599 599
600 600 def __wrapper(self, func, *fargs, **fkwargs):
601 601 cls = fargs[0]
602 602 self.user = cls.rhodecode_user
603 603 self.user_perms = self.user.permissions
604 604 log.debug('checking %s permissions %s for %s %s',
605 605 self.__class__.__name__, self.required_perms, cls, self.user)
606 606
607 607 if self.check_permissions():
608 608 log.debug('Permission granted for %s %s' % (cls, self.user))
609 609 return func(*fargs, **fkwargs)
610 610
611 611 else:
612 612 log.debug('Permission denied for %s %s' % (cls, self.user))
613 613 anonymous = self.user.username == 'default'
614 614
615 615 if anonymous:
616 616 p = url.current()
617 617
618 618 import rhodecode.lib.helpers as h
619 619 h.flash(_('You need to be a signed in to '
620 620 'view this page'),
621 621 category='warning')
622 622 return redirect(url('login_home', came_from=p))
623 623
624 624 else:
625 625 # redirect with forbidden ret code
626 626 return abort(403)
627 627
628 628 def check_permissions(self):
629 629 """Dummy function for overriding"""
630 630 raise Exception('You have to write this function in child class')
631 631
632 632
633 633 class HasPermissionAllDecorator(PermsDecorator):
634 634 """
635 635 Checks for access permission for all given predicates. All of them
636 636 have to be meet in order to fulfill the request
637 637 """
638 638
639 639 def check_permissions(self):
640 640 if self.required_perms.issubset(self.user_perms.get('global')):
641 641 return True
642 642 return False
643 643
644 644
645 645 class HasPermissionAnyDecorator(PermsDecorator):
646 646 """
647 647 Checks for access permission for any of given predicates. In order to
648 648 fulfill the request any of predicates must be meet
649 649 """
650 650
651 651 def check_permissions(self):
652 652 if self.required_perms.intersection(self.user_perms.get('global')):
653 653 return True
654 654 return False
655 655
656 656
657 657 class HasRepoPermissionAllDecorator(PermsDecorator):
658 658 """
659 659 Checks for access permission for all given predicates for specific
660 660 repository. All of them have to be meet in order to fulfill the request
661 661 """
662 662
663 663 def check_permissions(self):
664 664 repo_name = get_repo_slug(request)
665 665 try:
666 666 user_perms = set([self.user_perms['repositories'][repo_name]])
667 667 except KeyError:
668 668 return False
669 669 if self.required_perms.issubset(user_perms):
670 670 return True
671 671 return False
672 672
673 673
674 674 class HasRepoPermissionAnyDecorator(PermsDecorator):
675 675 """
676 676 Checks for access permission for any of given predicates for specific
677 677 repository. In order to fulfill the request any of predicates must be meet
678 678 """
679 679
680 680 def check_permissions(self):
681 681 repo_name = get_repo_slug(request)
682 682 try:
683 683 user_perms = set([self.user_perms['repositories'][repo_name]])
684 684 except KeyError:
685 685 return False
686 686
687 687 if self.required_perms.intersection(user_perms):
688 688 return True
689 689 return False
690 690
691 691
692 692 class HasReposGroupPermissionAllDecorator(PermsDecorator):
693 693 """
694 694 Checks for access permission for all given predicates for specific
695 695 repository. All of them have to be meet in order to fulfill the request
696 696 """
697 697
698 698 def check_permissions(self):
699 699 group_name = get_repos_group_slug(request)
700 700 try:
701 701 user_perms = set([self.user_perms['repositories_groups'][group_name]])
702 702 except KeyError:
703 703 return False
704 704
705 705 if self.required_perms.issubset(user_perms):
706 706 return True
707 707 return False
708 708
709 709
710 710 class HasReposGroupPermissionAnyDecorator(PermsDecorator):
711 711 """
712 712 Checks for access permission for any of given predicates for specific
713 713 repository. In order to fulfill the request any of predicates must be meet
714 714 """
715 715
716 716 def check_permissions(self):
717 717 group_name = get_repos_group_slug(request)
718 718 try:
719 719 user_perms = set([self.user_perms['repositories_groups'][group_name]])
720 720 except KeyError:
721 721 return False
722 722
723 723 if self.required_perms.intersection(user_perms):
724 724 return True
725 725 return False
726 726
727 727
728 728 #==============================================================================
729 729 # CHECK FUNCTIONS
730 730 #==============================================================================
731 731 class PermsFunction(object):
732 732 """Base function for other check functions"""
733 733
734 734 def __init__(self, *perms):
735 735 available_perms = config['available_permissions']
736 736
737 737 for perm in perms:
738 738 if perm not in available_perms:
739 739 raise Exception("'%s' permission is not defined" % perm)
740 740 self.required_perms = set(perms)
741 741 self.user_perms = None
742 742 self.repo_name = None
743 743 self.group_name = None
744 744
745 745 def __call__(self, check_location=''):
746 746 #TODO: put user as attribute here
747 747 user = request.user
748 748 cls_name = self.__class__.__name__
749 749 check_scope = {
750 750 'HasPermissionAll': '',
751 751 'HasPermissionAny': '',
752 752 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
753 753 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
754 754 'HasReposGroupPermissionAll': 'group:%s' % self.group_name,
755 755 'HasReposGroupPermissionAny': 'group:%s' % self.group_name,
756 756 }.get(cls_name, '?')
757 757 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
758 758 self.required_perms, user, check_scope,
759 759 check_location or 'unspecified location')
760 760 if not user:
761 761 log.debug('Empty request user')
762 762 return False
763 763 self.user_perms = user.permissions
764 764 if self.check_permissions():
765 765 log.debug('Permission to %s granted for user: %s @ %s', self.repo_name, user,
766 766 check_location or 'unspecified location')
767 767 return True
768 768
769 769 else:
770 770 log.debug('Permission to %s denied for user: %s @ %s', self.repo_name, user,
771 771 check_location or 'unspecified location')
772 772 return False
773 773
774 774 def check_permissions(self):
775 775 """Dummy function for overriding"""
776 776 raise Exception('You have to write this function in child class')
777 777
778 778
779 779 class HasPermissionAll(PermsFunction):
780 780 def check_permissions(self):
781 781 if self.required_perms.issubset(self.user_perms.get('global')):
782 782 return True
783 783 return False
784 784
785 785
786 786 class HasPermissionAny(PermsFunction):
787 787 def check_permissions(self):
788 788 if self.required_perms.intersection(self.user_perms.get('global')):
789 789 return True
790 790 return False
791 791
792 792
793 793 class HasRepoPermissionAll(PermsFunction):
794 794 def __call__(self, repo_name=None, check_location=''):
795 795 self.repo_name = repo_name
796 796 return super(HasRepoPermissionAll, self).__call__(check_location)
797 797
798 798 def check_permissions(self):
799 799 if not self.repo_name:
800 800 self.repo_name = get_repo_slug(request)
801 801
802 802 try:
803 803 self._user_perms = set(
804 804 [self.user_perms['repositories'][self.repo_name]]
805 805 )
806 806 except KeyError:
807 807 return False
808 808 if self.required_perms.issubset(self._user_perms):
809 809 return True
810 810 return False
811 811
812 812
813 813 class HasRepoPermissionAny(PermsFunction):
814 814 def __call__(self, repo_name=None, check_location=''):
815 815 self.repo_name = repo_name
816 816 return super(HasRepoPermissionAny, self).__call__(check_location)
817 817
818 818 def check_permissions(self):
819 819 if not self.repo_name:
820 820 self.repo_name = get_repo_slug(request)
821 821
822 822 try:
823 823 self._user_perms = set(
824 824 [self.user_perms['repositories'][self.repo_name]]
825 825 )
826 826 except KeyError:
827 827 return False
828 828 if self.required_perms.intersection(self._user_perms):
829 829 return True
830 830 return False
831 831
832 832
833 833 class HasReposGroupPermissionAny(PermsFunction):
834 834 def __call__(self, group_name=None, check_location=''):
835 835 self.group_name = group_name
836 836 return super(HasReposGroupPermissionAny, self).__call__(check_location)
837 837
838 838 def check_permissions(self):
839 839 try:
840 840 self._user_perms = set(
841 841 [self.user_perms['repositories_groups'][self.group_name]]
842 842 )
843 843 except KeyError:
844 844 return False
845 845 if self.required_perms.intersection(self._user_perms):
846 846 return True
847 847 return False
848 848
849 849
850 850 class HasReposGroupPermissionAll(PermsFunction):
851 851 def __call__(self, group_name=None, check_location=''):
852 852 self.group_name = group_name
853 853 return super(HasReposGroupPermissionAll, self).__call__(check_location)
854 854
855 855 def check_permissions(self):
856 856 try:
857 857 self._user_perms = set(
858 858 [self.user_perms['repositories_groups'][self.group_name]]
859 859 )
860 860 except KeyError:
861 861 return False
862 862 if self.required_perms.issubset(self._user_perms):
863 863 return True
864 864 return False
865 865
866 866
867 867 #==============================================================================
868 868 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
869 869 #==============================================================================
870 870 class HasPermissionAnyMiddleware(object):
871 871 def __init__(self, *perms):
872 872 self.required_perms = set(perms)
873 873
874 874 def __call__(self, user, repo_name):
875 875 # repo_name MUST be unicode, since we handle keys in permission
876 876 # dict by unicode
877 877 repo_name = safe_unicode(repo_name)
878 878 usr = AuthUser(user.user_id)
879 879 try:
880 880 self.user_perms = set([usr.permissions['repositories'][repo_name]])
881 881 except Exception:
882 882 log.error('Exception while accessing permissions %s' %
883 883 traceback.format_exc())
884 884 self.user_perms = set()
885 885 self.username = user.username
886 886 self.repo_name = repo_name
887 887 return self.check_permissions()
888 888
889 889 def check_permissions(self):
890 890 log.debug('checking VCS protocol '
891 891 'permissions %s for user:%s repository:%s', self.user_perms,
892 892 self.username, self.repo_name)
893 893 if self.required_perms.intersection(self.user_perms):
894 894 log.debug('permission granted for user:%s on repo:%s' % (
895 895 self.username, self.repo_name
896 896 )
897 897 )
898 898 return True
899 899 log.debug('permission denied for user:%s on repo:%s' % (
900 900 self.username, self.repo_name
901 901 )
902 902 )
903 903 return False
904 904
905 905
906 906 #==============================================================================
907 907 # SPECIAL VERSION TO HANDLE API AUTH
908 908 #==============================================================================
909 909 class _BaseApiPerm(object):
910 910 def __init__(self, *perms):
911 911 self.required_perms = set(perms)
912 912
913 913 def __call__(self, check_location='unspecified', user=None, repo_name=None):
914 914 cls_name = self.__class__.__name__
915 915 check_scope = 'user:%s, repo:%s' % (user, repo_name)
916 916 log.debug('checking cls:%s %s %s @ %s', cls_name,
917 917 self.required_perms, check_scope, check_location)
918 918 if not user:
919 919 log.debug('Empty User passed into arguments')
920 920 return False
921 921
922 922 ## process user
923 923 if not isinstance(user, AuthUser):
924 924 user = AuthUser(user.user_id)
925 925
926 926 if self.check_permissions(user.permissions, repo_name):
927 927 log.debug('Permission to %s granted for user: %s @ %s', repo_name,
928 928 user, check_location)
929 929 return True
930 930
931 931 else:
932 932 log.debug('Permission to %s denied for user: %s @ %s', repo_name,
933 933 user, check_location)
934 934 return False
935 935
936 936 def check_permissions(self, perm_defs, repo_name):
937 937 """
938 938 implement in child class should return True if permissions are ok,
939 939 False otherwise
940 940
941 941 :param perm_defs: dict with permission definitions
942 942 :param repo_name: repo name
943 943 """
944 944 raise NotImplementedError()
945 945
946 946
947 947 class HasPermissionAllApi(_BaseApiPerm):
948 948 def __call__(self, user, check_location=''):
949 949 return super(HasPermissionAllApi, self)\
950 950 .__call__(check_location=check_location, user=user)
951 951
952 952 def check_permissions(self, perm_defs, repo):
953 953 if self.required_perms.issubset(perm_defs.get('global')):
954 954 return True
955 955 return False
956 956
957 957
958 958 class HasPermissionAnyApi(_BaseApiPerm):
959 959 def __call__(self, user, check_location=''):
960 960 return super(HasPermissionAnyApi, self)\
961 961 .__call__(check_location=check_location, user=user)
962 962
963 963 def check_permissions(self, perm_defs, repo):
964 964 if self.required_perms.intersection(perm_defs.get('global')):
965 965 return True
966 966 return False
967 967
968 968
969 969 class HasRepoPermissionAllApi(_BaseApiPerm):
970 970 def __call__(self, user, repo_name, check_location=''):
971 971 return super(HasRepoPermissionAllApi, self)\
972 972 .__call__(check_location=check_location, user=user,
973 973 repo_name=repo_name)
974 974
975 975 def check_permissions(self, perm_defs, repo_name):
976 976
977 977 try:
978 978 self._user_perms = set(
979 979 [perm_defs['repositories'][repo_name]]
980 980 )
981 981 except KeyError:
982 982 log.warning(traceback.format_exc())
983 983 return False
984 984 if self.required_perms.issubset(self._user_perms):
985 985 return True
986 986 return False
987 987
988 988
989 989 class HasRepoPermissionAnyApi(_BaseApiPerm):
990 990 def __call__(self, user, repo_name, check_location=''):
991 991 return super(HasRepoPermissionAnyApi, self)\
992 992 .__call__(check_location=check_location, user=user,
993 993 repo_name=repo_name)
994 994
995 995 def check_permissions(self, perm_defs, repo_name):
996 996
997 997 try:
998 998 _user_perms = set(
999 999 [perm_defs['repositories'][repo_name]]
1000 1000 )
1001 1001 except KeyError:
1002 1002 log.warning(traceback.format_exc())
1003 1003 return False
1004 1004 if self.required_perms.intersection(_user_perms):
1005 1005 return True
1006 1006 return False
1007 1007
1008 1008
1009 1009 def check_ip_access(source_ip, allowed_ips=None):
1010 1010 """
1011 1011 Checks if source_ip is a subnet of any of allowed_ips.
1012 1012
1013 1013 :param source_ip:
1014 1014 :param allowed_ips: list of allowed ips together with mask
1015 1015 """
1016 1016 from rhodecode.lib import ipaddr
1017 1017 log.debug('checking if ip:%s is subnet of %s' % (source_ip, allowed_ips))
1018 1018 if isinstance(allowed_ips, (tuple, list, set)):
1019 1019 for ip in allowed_ips:
1020 1020 try:
1021 1021 if ipaddr.IPAddress(source_ip) in ipaddr.IPNetwork(ip):
1022 1022 return True
1023 1023 # for any case we cannot determine the IP, don't crash just
1024 1024 # skip it and log as error, we want to say forbidden still when
1025 1025 # sending bad IP
1026 1026 except Exception:
1027 1027 log.error(traceback.format_exc())
1028 1028 continue
1029 1029 return False
@@ -1,1186 +1,1186 b''
1 1 """Helper functions
2 2
3 3 Consists of functions to typically be used within templates, but also
4 4 available to Controllers. This module is available to both as 'h'.
5 5 """
6 6 import random
7 7 import hashlib
8 8 import StringIO
9 9 import urllib
10 10 import math
11 11 import logging
12 12 import re
13 13 import urlparse
14 14 import textwrap
15 15
16 16 from datetime import datetime
17 17 from pygments.formatters.html import HtmlFormatter
18 18 from pygments import highlight as code_highlight
19 19 from pylons import url, request, config
20 20 from pylons.i18n.translation import _, ungettext
21 21 from hashlib import md5
22 22
23 23 from webhelpers.html import literal, HTML, escape
24 24 from webhelpers.html.tools import *
25 25 from webhelpers.html.builder import make_tag
26 26 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
27 27 end_form, file, form, hidden, image, javascript_link, link_to, \
28 28 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
29 29 submit, text, password, textarea, title, ul, xml_declaration, radio
30 30 from webhelpers.html.tools import auto_link, button_to, highlight, \
31 31 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
32 32 from webhelpers.number import format_byte_size, format_bit_size
33 33 from webhelpers.pylonslib import Flash as _Flash
34 34 from webhelpers.pylonslib.secure_form import secure_form
35 35 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
36 36 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
37 37 replace_whitespace, urlify, truncate, wrap_paragraphs
38 38 from webhelpers.date import time_ago_in_words
39 39 from webhelpers.paginate import Page
40 40 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
41 41 convert_boolean_attrs, NotGiven, _make_safe_id_component
42 42
43 43 from rhodecode.lib.annotate import annotate_highlight
44 44 from rhodecode.lib.utils import repo_name_slug, get_custom_lexer
45 45 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
46 46 get_changeset_safe, datetime_to_time, time_to_datetime, AttributeDict
47 47 from rhodecode.lib.markup_renderer import MarkupRenderer
48 48 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
49 49 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyChangeset
50 50 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
51 51 from rhodecode.model.changeset_status import ChangesetStatusModel
52 52 from rhodecode.model.db import URL_SEP, Permission
53 53
54 54 log = logging.getLogger(__name__)
55 55
56 56
57 57 html_escape_table = {
58 58 "&": "&amp;",
59 59 '"': "&quot;",
60 60 "'": "&apos;",
61 61 ">": "&gt;",
62 62 "<": "&lt;",
63 63 }
64 64
65 65
66 66 def html_escape(text):
67 67 """Produce entities within text."""
68 68 return "".join(html_escape_table.get(c, c) for c in text)
69 69
70 70
71 71 def shorter(text, size=20):
72 72 postfix = '...'
73 73 if len(text) > size:
74 74 return text[:size - len(postfix)] + postfix
75 75 return text
76 76
77 77
78 78 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
79 79 """
80 80 Reset button
81 81 """
82 82 _set_input_attrs(attrs, type, name, value)
83 83 _set_id_attr(attrs, id, name)
84 84 convert_boolean_attrs(attrs, ["disabled"])
85 85 return HTML.input(**attrs)
86 86
87 87 reset = _reset
88 88 safeid = _make_safe_id_component
89 89
90 90
91 91 def FID(raw_id, path):
92 92 """
93 93 Creates a uniqe ID for filenode based on it's hash of path and revision
94 94 it's safe to use in urls
95 95
96 96 :param raw_id:
97 97 :param path:
98 98 """
99 99
100 100 return 'C-%s-%s' % (short_id(raw_id), md5(safe_str(path)).hexdigest()[:12])
101 101
102 102
103 103 def get_token():
104 104 """Return the current authentication token, creating one if one doesn't
105 105 already exist.
106 106 """
107 107 token_key = "_authentication_token"
108 108 from pylons import session
109 109 if not token_key in session:
110 110 try:
111 111 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
112 112 except AttributeError: # Python < 2.4
113 113 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
114 114 session[token_key] = token
115 115 if hasattr(session, 'save'):
116 116 session.save()
117 117 return session[token_key]
118 118
119 119
120 120 class _GetError(object):
121 121 """Get error from form_errors, and represent it as span wrapped error
122 122 message
123 123
124 124 :param field_name: field to fetch errors for
125 125 :param form_errors: form errors dict
126 126 """
127 127
128 128 def __call__(self, field_name, form_errors):
129 129 tmpl = """<span class="error_msg">%s</span>"""
130 130 if form_errors and field_name in form_errors:
131 131 return literal(tmpl % form_errors.get(field_name))
132 132
133 133 get_error = _GetError()
134 134
135 135
136 136 class _ToolTip(object):
137 137
138 138 def __call__(self, tooltip_title, trim_at=50):
139 139 """
140 140 Special function just to wrap our text into nice formatted
141 141 autowrapped text
142 142
143 143 :param tooltip_title:
144 144 """
145 145 tooltip_title = escape(tooltip_title)
146 146 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
147 147 return tooltip_title
148 148 tooltip = _ToolTip()
149 149
150 150
151 151 class _FilesBreadCrumbs(object):
152 152
153 153 def __call__(self, repo_name, rev, paths):
154 154 if isinstance(paths, str):
155 155 paths = safe_unicode(paths)
156 156 url_l = [link_to(repo_name, url('files_home',
157 157 repo_name=repo_name,
158 158 revision=rev, f_path=''),
159 159 class_='ypjax-link')]
160 160 paths_l = paths.split('/')
161 161 for cnt, p in enumerate(paths_l):
162 162 if p != '':
163 163 url_l.append(link_to(p,
164 164 url('files_home',
165 165 repo_name=repo_name,
166 166 revision=rev,
167 167 f_path='/'.join(paths_l[:cnt + 1])
168 168 ),
169 169 class_='ypjax-link'
170 170 )
171 171 )
172 172
173 173 return literal('/'.join(url_l))
174 174
175 175 files_breadcrumbs = _FilesBreadCrumbs()
176 176
177 177
178 178 class CodeHtmlFormatter(HtmlFormatter):
179 179 """
180 180 My code Html Formatter for source codes
181 181 """
182 182
183 183 def wrap(self, source, outfile):
184 184 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
185 185
186 186 def _wrap_code(self, source):
187 187 for cnt, it in enumerate(source):
188 188 i, t = it
189 189 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
190 190 yield i, t
191 191
192 192 def _wrap_tablelinenos(self, inner):
193 193 dummyoutfile = StringIO.StringIO()
194 194 lncount = 0
195 195 for t, line in inner:
196 196 if t:
197 197 lncount += 1
198 198 dummyoutfile.write(line)
199 199
200 200 fl = self.linenostart
201 201 mw = len(str(lncount + fl - 1))
202 202 sp = self.linenospecial
203 203 st = self.linenostep
204 204 la = self.lineanchors
205 205 aln = self.anchorlinenos
206 206 nocls = self.noclasses
207 207 if sp:
208 208 lines = []
209 209
210 210 for i in range(fl, fl + lncount):
211 211 if i % st == 0:
212 212 if i % sp == 0:
213 213 if aln:
214 214 lines.append('<a href="#%s%d" class="special">%*d</a>' %
215 215 (la, i, mw, i))
216 216 else:
217 217 lines.append('<span class="special">%*d</span>' % (mw, i))
218 218 else:
219 219 if aln:
220 220 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
221 221 else:
222 222 lines.append('%*d' % (mw, i))
223 223 else:
224 224 lines.append('')
225 225 ls = '\n'.join(lines)
226 226 else:
227 227 lines = []
228 228 for i in range(fl, fl + lncount):
229 229 if i % st == 0:
230 230 if aln:
231 231 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
232 232 else:
233 233 lines.append('%*d' % (mw, i))
234 234 else:
235 235 lines.append('')
236 236 ls = '\n'.join(lines)
237 237
238 238 # in case you wonder about the seemingly redundant <div> here: since the
239 239 # content in the other cell also is wrapped in a div, some browsers in
240 240 # some configurations seem to mess up the formatting...
241 241 if nocls:
242 242 yield 0, ('<table class="%stable">' % self.cssclass +
243 243 '<tr><td><div class="linenodiv" '
244 244 'style="background-color: #f0f0f0; padding-right: 10px">'
245 245 '<pre style="line-height: 125%">' +
246 246 ls + '</pre></div></td><td id="hlcode" class="code">')
247 247 else:
248 248 yield 0, ('<table class="%stable">' % self.cssclass +
249 249 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
250 250 ls + '</pre></div></td><td id="hlcode" class="code">')
251 251 yield 0, dummyoutfile.getvalue()
252 252 yield 0, '</td></tr></table>'
253 253
254 254
255 255 def pygmentize(filenode, **kwargs):
256 256 """
257 257 pygmentize function using pygments
258 258
259 259 :param filenode:
260 260 """
261 261 lexer = get_custom_lexer(filenode.extension) or filenode.lexer
262 262 return literal(code_highlight(filenode.content, lexer,
263 263 CodeHtmlFormatter(**kwargs)))
264 264
265 265
266 266 def pygmentize_annotation(repo_name, filenode, **kwargs):
267 267 """
268 268 pygmentize function for annotation
269 269
270 270 :param filenode:
271 271 """
272 272
273 273 color_dict = {}
274 274
275 275 def gen_color(n=10000):
276 276 """generator for getting n of evenly distributed colors using
277 277 hsv color and golden ratio. It always return same order of colors
278 278
279 279 :returns: RGB tuple
280 280 """
281 281
282 282 def hsv_to_rgb(h, s, v):
283 283 if s == 0.0:
284 284 return v, v, v
285 285 i = int(h * 6.0) # XXX assume int() truncates!
286 286 f = (h * 6.0) - i
287 287 p = v * (1.0 - s)
288 288 q = v * (1.0 - s * f)
289 289 t = v * (1.0 - s * (1.0 - f))
290 290 i = i % 6
291 291 if i == 0:
292 292 return v, t, p
293 293 if i == 1:
294 294 return q, v, p
295 295 if i == 2:
296 296 return p, v, t
297 297 if i == 3:
298 298 return p, q, v
299 299 if i == 4:
300 300 return t, p, v
301 301 if i == 5:
302 302 return v, p, q
303 303
304 304 golden_ratio = 0.618033988749895
305 305 h = 0.22717784590367374
306 306
307 307 for _ in xrange(n):
308 308 h += golden_ratio
309 309 h %= 1
310 310 HSV_tuple = [h, 0.95, 0.95]
311 311 RGB_tuple = hsv_to_rgb(*HSV_tuple)
312 312 yield map(lambda x: str(int(x * 256)), RGB_tuple)
313 313
314 314 cgenerator = gen_color()
315 315
316 316 def get_color_string(cs):
317 317 if cs in color_dict:
318 318 col = color_dict[cs]
319 319 else:
320 320 col = color_dict[cs] = cgenerator.next()
321 321 return "color: rgb(%s)! important;" % (', '.join(col))
322 322
323 323 def url_func(repo_name):
324 324
325 325 def _url_func(changeset):
326 326 author = changeset.author
327 327 date = changeset.date
328 328 message = tooltip(changeset.message)
329 329
330 330 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
331 331 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
332 332 "</b> %s<br/></div>")
333 333
334 334 tooltip_html = tooltip_html % (author, date, message)
335 335 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
336 336 short_id(changeset.raw_id))
337 337 uri = link_to(
338 338 lnk_format,
339 339 url('changeset_home', repo_name=repo_name,
340 340 revision=changeset.raw_id),
341 341 style=get_color_string(changeset.raw_id),
342 342 class_='tooltip',
343 343 title=tooltip_html
344 344 )
345 345
346 346 uri += '\n'
347 347 return uri
348 348 return _url_func
349 349
350 350 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
351 351
352 352
353 353 def is_following_repo(repo_name, user_id):
354 354 from rhodecode.model.scm import ScmModel
355 355 return ScmModel().is_following_repo(repo_name, user_id)
356 356
357 357 flash = _Flash()
358 358
359 359 #==============================================================================
360 360 # SCM FILTERS available via h.
361 361 #==============================================================================
362 362 from rhodecode.lib.vcs.utils import author_name, author_email
363 363 from rhodecode.lib.utils2 import credentials_filter, age as _age
364 364 from rhodecode.model.db import User, ChangesetStatus
365 365
366 366 age = lambda x: _age(x)
367 367 capitalize = lambda x: x.capitalize()
368 368 email = author_email
369 369 short_id = lambda x: x[:12]
370 370 hide_credentials = lambda x: ''.join(credentials_filter(x))
371 371
372 372
373 373 def fmt_date(date):
374 374 if date:
375 375 _fmt = _(u"%a, %d %b %Y %H:%M:%S").encode('utf8')
376 376 return date.strftime(_fmt).decode('utf8')
377 377
378 378 return ""
379 379
380 380
381 381 def is_git(repository):
382 382 if hasattr(repository, 'alias'):
383 383 _type = repository.alias
384 384 elif hasattr(repository, 'repo_type'):
385 385 _type = repository.repo_type
386 386 else:
387 387 _type = repository
388 388 return _type == 'git'
389 389
390 390
391 391 def is_hg(repository):
392 392 if hasattr(repository, 'alias'):
393 393 _type = repository.alias
394 394 elif hasattr(repository, 'repo_type'):
395 395 _type = repository.repo_type
396 396 else:
397 397 _type = repository
398 398 return _type == 'hg'
399 399
400 400
401 401 def email_or_none(author):
402 402 # extract email from the commit string
403 403 _email = email(author)
404 404 if _email != '':
405 405 # check it against RhodeCode database, and use the MAIN email for this
406 406 # user
407 407 user = User.get_by_email(_email, case_insensitive=True, cache=True)
408 408 if user is not None:
409 409 return user.email
410 410 return _email
411 411
412 412 # See if it contains a username we can get an email from
413 413 user = User.get_by_username(author_name(author), case_insensitive=True,
414 414 cache=True)
415 415 if user is not None:
416 416 return user.email
417 417
418 418 # No valid email, not a valid user in the system, none!
419 419 return None
420 420
421 421
422 422 def person(author, show_attr="username_and_name"):
423 423 # attr to return from fetched user
424 424 person_getter = lambda usr: getattr(usr, show_attr)
425 425
426 426 # Valid email in the attribute passed, see if they're in the system
427 427 _email = email(author)
428 428 if _email != '':
429 429 user = User.get_by_email(_email, case_insensitive=True, cache=True)
430 430 if user is not None:
431 431 return person_getter(user)
432 432 return _email
433 433
434 434 # Maybe it's a username?
435 435 _author = author_name(author)
436 436 user = User.get_by_username(_author, case_insensitive=True,
437 437 cache=True)
438 438 if user is not None:
439 439 return person_getter(user)
440 440
441 441 # Still nothing? Just pass back the author name then
442 442 return _author
443 443
444 444
445 445 def person_by_id(id_, show_attr="username_and_name"):
446 446 # attr to return from fetched user
447 447 person_getter = lambda usr: getattr(usr, show_attr)
448 448
449 449 #maybe it's an ID ?
450 450 if str(id_).isdigit() or isinstance(id_, int):
451 451 id_ = int(id_)
452 452 user = User.get(id_)
453 453 if user is not None:
454 454 return person_getter(user)
455 455 return id_
456 456
457 457
458 458 def desc_stylize(value):
459 459 """
460 460 converts tags from value into html equivalent
461 461
462 462 :param value:
463 463 """
464 464 value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
465 465 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
466 466 value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
467 467 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
468 468 value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z0-9\-\/]*)\]',
469 469 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
470 470 value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/\#\+]*)\]',
471 471 '<div class="metatag" tag="lang">\\2</div>', value)
472 472 value = re.sub(r'\[([a-z]+)\]',
473 473 '<div class="metatag" tag="\\1">\\1</div>', value)
474 474
475 475 return value
476 476
477 477
478 478 def bool2icon(value):
479 479 """Returns True/False values represented as small html image of true/false
480 480 icons
481 481
482 482 :param value: bool value
483 483 """
484 484
485 485 if value is True:
486 486 return HTML.tag('img', src=url("/images/icons/accept.png"),
487 487 alt=_('True'))
488 488
489 489 if value is False:
490 490 return HTML.tag('img', src=url("/images/icons/cancel.png"),
491 491 alt=_('False'))
492 492
493 493 return value
494 494
495 495
496 496 def action_parser(user_log, feed=False, parse_cs=False):
497 497 """
498 498 This helper will action_map the specified string action into translated
499 499 fancy names with icons and links
500 500
501 501 :param user_log: user log instance
502 502 :param feed: use output for feeds (no html and fancy icons)
503 503 :param parse_cs: parse Changesets into VCS instances
504 504 """
505 505
506 506 action = user_log.action
507 507 action_params = ' '
508 508
509 509 x = action.split(':')
510 510
511 511 if len(x) > 1:
512 512 action, action_params = x
513 513
514 514 def get_cs_links():
515 515 revs_limit = 3 # display this amount always
516 516 revs_top_limit = 50 # show upto this amount of changesets hidden
517 517 revs_ids = action_params.split(',')
518 518 deleted = user_log.repository is None
519 519 if deleted:
520 520 return ','.join(revs_ids)
521 521
522 522 repo_name = user_log.repository.repo_name
523 523
524 524 def lnk(rev, repo_name):
525 525 if isinstance(rev, BaseChangeset) or isinstance(rev, AttributeDict):
526 526 lazy_cs = True
527 527 if getattr(rev, 'op', None) and getattr(rev, 'ref_name', None):
528 528 lazy_cs = False
529 529 lbl = '?'
530 530 if rev.op == 'delete_branch':
531 531 lbl = '%s' % _('Deleted branch: %s') % rev.ref_name
532 532 title = ''
533 533 elif rev.op == 'tag':
534 534 lbl = '%s' % _('Created tag: %s') % rev.ref_name
535 535 title = ''
536 536 _url = '#'
537 537
538 538 else:
539 539 lbl = '%s' % (rev.short_id[:8])
540 540 _url = url('changeset_home', repo_name=repo_name,
541 541 revision=rev.raw_id)
542 542 title = tooltip(rev.message)
543 543 else:
544 544 ## changeset cannot be found/striped/removed etc.
545 545 lbl = ('%s' % rev)[:12]
546 546 _url = '#'
547 547 title = _('Changeset not found')
548 548 if parse_cs:
549 549 return link_to(lbl, _url, title=title, class_='tooltip')
550 550 return link_to(lbl, _url, raw_id=rev.raw_id, repo_name=repo_name,
551 551 class_='lazy-cs' if lazy_cs else '')
552 552
553 553 revs = []
554 554 if len(filter(lambda v: v != '', revs_ids)) > 0:
555 555 repo = None
556 556 for rev in revs_ids[:revs_top_limit]:
557 557 _op = _name = None
558 558 if len(rev.split('=>')) == 2:
559 559 _op, _name = rev.split('=>')
560 560
561 561 # we want parsed changesets, or new log store format is bad
562 562 if parse_cs:
563 563 try:
564 564 if repo is None:
565 565 repo = user_log.repository.scm_instance
566 566 _rev = repo.get_changeset(rev)
567 567 revs.append(_rev)
568 568 except ChangesetDoesNotExistError:
569 569 log.error('cannot find revision %s in this repo' % rev)
570 570 revs.append(rev)
571 571 continue
572 572 else:
573 573 _rev = AttributeDict({
574 574 'short_id': rev[:12],
575 575 'raw_id': rev,
576 576 'message': '',
577 577 'op': _op,
578 578 'ref_name': _name
579 579 })
580 580 revs.append(_rev)
581 581 cs_links = []
582 582 cs_links.append(" " + ', '.join(
583 583 [lnk(rev, repo_name) for rev in revs[:revs_limit]]
584 584 )
585 585 )
586 586
587 587 compare_view = (
588 588 ' <div class="compare_view tooltip" title="%s">'
589 589 '<a href="%s">%s</a> </div>' % (
590 590 _('Show all combined changesets %s->%s') % (
591 591 revs_ids[0][:12], revs_ids[-1][:12]
592 592 ),
593 593 url('changeset_home', repo_name=repo_name,
594 594 revision='%s...%s' % (revs_ids[0], revs_ids[-1])
595 595 ),
596 596 _('compare view')
597 597 )
598 598 )
599 599
600 600 # if we have exactly one more than normally displayed
601 601 # just display it, takes less space than displaying
602 602 # "and 1 more revisions"
603 603 if len(revs_ids) == revs_limit + 1:
604 604 rev = revs[revs_limit]
605 605 cs_links.append(", " + lnk(rev, repo_name))
606 606
607 607 # hidden-by-default ones
608 608 if len(revs_ids) > revs_limit + 1:
609 609 uniq_id = revs_ids[0]
610 610 html_tmpl = (
611 611 '<span> %s <a class="show_more" id="_%s" '
612 612 'href="#more">%s</a> %s</span>'
613 613 )
614 614 if not feed:
615 615 cs_links.append(html_tmpl % (
616 616 _('and'),
617 617 uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
618 618 _('revisions')
619 619 )
620 620 )
621 621
622 622 if not feed:
623 623 html_tmpl = '<span id="%s" style="display:none">, %s </span>'
624 624 else:
625 625 html_tmpl = '<span id="%s"> %s </span>'
626 626
627 627 morelinks = ', '.join(
628 628 [lnk(rev, repo_name) for rev in revs[revs_limit:]]
629 629 )
630 630
631 631 if len(revs_ids) > revs_top_limit:
632 632 morelinks += ', ...'
633 633
634 634 cs_links.append(html_tmpl % (uniq_id, morelinks))
635 635 if len(revs) > 1:
636 636 cs_links.append(compare_view)
637 637 return ''.join(cs_links)
638 638
639 639 def get_fork_name():
640 640 repo_name = action_params
641 641 _url = url('summary_home', repo_name=repo_name)
642 642 return _('fork name %s') % link_to(action_params, _url)
643 643
644 644 def get_user_name():
645 645 user_name = action_params
646 646 return user_name
647 647
648 648 def get_users_group():
649 649 group_name = action_params
650 650 return group_name
651 651
652 652 def get_pull_request():
653 653 pull_request_id = action_params
654 654 deleted = user_log.repository is None
655 655 if deleted:
656 656 repo_name = user_log.repository_name
657 657 else:
658 658 repo_name = user_log.repository.repo_name
659 659 return link_to(_('Pull request #%s') % pull_request_id,
660 660 url('pullrequest_show', repo_name=repo_name,
661 661 pull_request_id=pull_request_id))
662 662
663 663 # action : translated str, callback(extractor), icon
664 664 action_map = {
665 665 'user_deleted_repo': (_('[deleted] repository'),
666 666 None, 'database_delete.png'),
667 667 'user_created_repo': (_('[created] repository'),
668 668 None, 'database_add.png'),
669 669 'user_created_fork': (_('[created] repository as fork'),
670 670 None, 'arrow_divide.png'),
671 671 'user_forked_repo': (_('[forked] repository'),
672 672 get_fork_name, 'arrow_divide.png'),
673 673 'user_updated_repo': (_('[updated] repository'),
674 674 None, 'database_edit.png'),
675 675 'admin_deleted_repo': (_('[delete] repository'),
676 676 None, 'database_delete.png'),
677 677 'admin_created_repo': (_('[created] repository'),
678 678 None, 'database_add.png'),
679 679 'admin_forked_repo': (_('[forked] repository'),
680 680 None, 'arrow_divide.png'),
681 681 'admin_updated_repo': (_('[updated] repository'),
682 682 None, 'database_edit.png'),
683 683 'admin_created_user': (_('[created] user'),
684 684 get_user_name, 'user_add.png'),
685 685 'admin_updated_user': (_('[updated] user'),
686 686 get_user_name, 'user_edit.png'),
687 'admin_created_users_group': (_('[created] users group'),
687 'admin_created_users_group': (_('[created] user group'),
688 688 get_users_group, 'group_add.png'),
689 'admin_updated_users_group': (_('[updated] users group'),
689 'admin_updated_users_group': (_('[updated] user group'),
690 690 get_users_group, 'group_edit.png'),
691 691 'user_commented_revision': (_('[commented] on revision in repository'),
692 692 get_cs_links, 'comment_add.png'),
693 693 'user_commented_pull_request': (_('[commented] on pull request for'),
694 694 get_pull_request, 'comment_add.png'),
695 695 'user_closed_pull_request': (_('[closed] pull request for'),
696 696 get_pull_request, 'tick.png'),
697 697 'push': (_('[pushed] into'),
698 698 get_cs_links, 'script_add.png'),
699 699 'push_local': (_('[committed via RhodeCode] into repository'),
700 700 get_cs_links, 'script_edit.png'),
701 701 'push_remote': (_('[pulled from remote] into repository'),
702 702 get_cs_links, 'connect.png'),
703 703 'pull': (_('[pulled] from'),
704 704 None, 'down_16.png'),
705 705 'started_following_repo': (_('[started following] repository'),
706 706 None, 'heart_add.png'),
707 707 'stopped_following_repo': (_('[stopped following] repository'),
708 708 None, 'heart_delete.png'),
709 709 }
710 710
711 711 action_str = action_map.get(action, action)
712 712 if feed:
713 713 action = action_str[0].replace('[', '').replace(']', '')
714 714 else:
715 715 action = action_str[0]\
716 716 .replace('[', '<span class="journal_highlight">')\
717 717 .replace(']', '</span>')
718 718
719 719 action_params_func = lambda: ""
720 720
721 721 if callable(action_str[1]):
722 722 action_params_func = action_str[1]
723 723
724 724 def action_parser_icon():
725 725 action = user_log.action
726 726 action_params = None
727 727 x = action.split(':')
728 728
729 729 if len(x) > 1:
730 730 action, action_params = x
731 731
732 732 tmpl = """<img src="%s%s" alt="%s"/>"""
733 733 ico = action_map.get(action, ['', '', ''])[2]
734 734 return literal(tmpl % ((url('/images/icons/')), ico, action))
735 735
736 736 # returned callbacks we need to call to get
737 737 return [lambda: literal(action), action_params_func, action_parser_icon]
738 738
739 739
740 740
741 741 #==============================================================================
742 742 # PERMS
743 743 #==============================================================================
744 744 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
745 745 HasRepoPermissionAny, HasRepoPermissionAll, HasReposGroupPermissionAll, \
746 746 HasReposGroupPermissionAny
747 747
748 748
749 749 #==============================================================================
750 750 # GRAVATAR URL
751 751 #==============================================================================
752 752
753 753 def gravatar_url(email_address, size=30):
754 754 from pylons import url # doh, we need to re-import url to mock it later
755 755 _def = 'anonymous@rhodecode.org'
756 756 use_gravatar = str2bool(config['app_conf'].get('use_gravatar'))
757 757 email_address = email_address or _def
758 758 if (not use_gravatar or not email_address or email_address == _def):
759 759 f = lambda a, l: min(l, key=lambda x: abs(x - a))
760 760 return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
761 761
762 762 if use_gravatar and config['app_conf'].get('alternative_gravatar_url'):
763 763 tmpl = config['app_conf'].get('alternative_gravatar_url', '')
764 764 parsed_url = urlparse.urlparse(url.current(qualified=True))
765 765 tmpl = tmpl.replace('{email}', email_address)\
766 766 .replace('{md5email}', hashlib.md5(email_address.lower()).hexdigest()) \
767 767 .replace('{netloc}', parsed_url.netloc)\
768 768 .replace('{scheme}', parsed_url.scheme)\
769 769 .replace('{size}', str(size))
770 770 return tmpl
771 771
772 772 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
773 773 default = 'identicon'
774 774 baseurl_nossl = "http://www.gravatar.com/avatar/"
775 775 baseurl_ssl = "https://secure.gravatar.com/avatar/"
776 776 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
777 777
778 778 if isinstance(email_address, unicode):
779 779 #hashlib crashes on unicode items
780 780 email_address = safe_str(email_address)
781 781 # construct the url
782 782 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
783 783 gravatar_url += urllib.urlencode({'d': default, 's': str(size)})
784 784
785 785 return gravatar_url
786 786
787 787
788 788 #==============================================================================
789 789 # REPO PAGER, PAGER FOR REPOSITORY
790 790 #==============================================================================
791 791 class RepoPage(Page):
792 792
793 793 def __init__(self, collection, page=1, items_per_page=20,
794 794 item_count=None, url=None, **kwargs):
795 795
796 796 """Create a "RepoPage" instance. special pager for paging
797 797 repository
798 798 """
799 799 self._url_generator = url
800 800
801 801 # Safe the kwargs class-wide so they can be used in the pager() method
802 802 self.kwargs = kwargs
803 803
804 804 # Save a reference to the collection
805 805 self.original_collection = collection
806 806
807 807 self.collection = collection
808 808
809 809 # The self.page is the number of the current page.
810 810 # The first page has the number 1!
811 811 try:
812 812 self.page = int(page) # make it int() if we get it as a string
813 813 except (ValueError, TypeError):
814 814 self.page = 1
815 815
816 816 self.items_per_page = items_per_page
817 817
818 818 # Unless the user tells us how many items the collections has
819 819 # we calculate that ourselves.
820 820 if item_count is not None:
821 821 self.item_count = item_count
822 822 else:
823 823 self.item_count = len(self.collection)
824 824
825 825 # Compute the number of the first and last available page
826 826 if self.item_count > 0:
827 827 self.first_page = 1
828 828 self.page_count = int(math.ceil(float(self.item_count) /
829 829 self.items_per_page))
830 830 self.last_page = self.first_page + self.page_count - 1
831 831
832 832 # Make sure that the requested page number is the range of
833 833 # valid pages
834 834 if self.page > self.last_page:
835 835 self.page = self.last_page
836 836 elif self.page < self.first_page:
837 837 self.page = self.first_page
838 838
839 839 # Note: the number of items on this page can be less than
840 840 # items_per_page if the last page is not full
841 841 self.first_item = max(0, (self.item_count) - (self.page *
842 842 items_per_page))
843 843 self.last_item = ((self.item_count - 1) - items_per_page *
844 844 (self.page - 1))
845 845
846 846 self.items = list(self.collection[self.first_item:self.last_item + 1])
847 847
848 848 # Links to previous and next page
849 849 if self.page > self.first_page:
850 850 self.previous_page = self.page - 1
851 851 else:
852 852 self.previous_page = None
853 853
854 854 if self.page < self.last_page:
855 855 self.next_page = self.page + 1
856 856 else:
857 857 self.next_page = None
858 858
859 859 # No items available
860 860 else:
861 861 self.first_page = None
862 862 self.page_count = 0
863 863 self.last_page = None
864 864 self.first_item = None
865 865 self.last_item = None
866 866 self.previous_page = None
867 867 self.next_page = None
868 868 self.items = []
869 869
870 870 # This is a subclass of the 'list' type. Initialise the list now.
871 871 list.__init__(self, reversed(self.items))
872 872
873 873
874 874 def changed_tooltip(nodes):
875 875 """
876 876 Generates a html string for changed nodes in changeset page.
877 877 It limits the output to 30 entries
878 878
879 879 :param nodes: LazyNodesGenerator
880 880 """
881 881 if nodes:
882 882 pref = ': <br/> '
883 883 suf = ''
884 884 if len(nodes) > 30:
885 885 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
886 886 return literal(pref + '<br/> '.join([safe_unicode(x.path)
887 887 for x in nodes[:30]]) + suf)
888 888 else:
889 889 return ': ' + _('No Files')
890 890
891 891
892 892 def repo_link(groups_and_repos, last_url=None):
893 893 """
894 894 Makes a breadcrumbs link to repo within a group
895 895 joins &raquo; on each group to create a fancy link
896 896
897 897 ex::
898 898 group >> subgroup >> repo
899 899
900 900 :param groups_and_repos:
901 901 :param last_url:
902 902 """
903 903 groups, repo_name = groups_and_repos
904 904 last_link = link_to(repo_name, last_url) if last_url else repo_name
905 905
906 906 if not groups:
907 907 if last_url:
908 908 return last_link
909 909 return repo_name
910 910 else:
911 911 def make_link(group):
912 912 return link_to(group.name,
913 913 url('repos_group_home', group_name=group.group_name))
914 914 return literal(' &raquo; '.join(map(make_link, groups) + [last_link]))
915 915
916 916
917 917 def fancy_file_stats(stats):
918 918 """
919 919 Displays a fancy two colored bar for number of added/deleted
920 920 lines of code on file
921 921
922 922 :param stats: two element list of added/deleted lines of code
923 923 """
924 924 def cgen(l_type, a_v, d_v):
925 925 mapping = {'tr': 'top-right-rounded-corner-mid',
926 926 'tl': 'top-left-rounded-corner-mid',
927 927 'br': 'bottom-right-rounded-corner-mid',
928 928 'bl': 'bottom-left-rounded-corner-mid'}
929 929 map_getter = lambda x: mapping[x]
930 930
931 931 if l_type == 'a' and d_v:
932 932 #case when added and deleted are present
933 933 return ' '.join(map(map_getter, ['tl', 'bl']))
934 934
935 935 if l_type == 'a' and not d_v:
936 936 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
937 937
938 938 if l_type == 'd' and a_v:
939 939 return ' '.join(map(map_getter, ['tr', 'br']))
940 940
941 941 if l_type == 'd' and not a_v:
942 942 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
943 943
944 944 a, d = stats[0], stats[1]
945 945 width = 100
946 946
947 947 if a == 'b':
948 948 #binary mode
949 949 b_d = '<div class="bin%s %s" style="width:100%%">%s</div>' % (d, cgen('a', a_v='', d_v=0), 'bin')
950 950 b_a = '<div class="bin1" style="width:0%%">%s</div>' % ('bin')
951 951 return literal('<div style="width:%spx">%s%s</div>' % (width, b_a, b_d))
952 952
953 953 t = stats[0] + stats[1]
954 954 unit = float(width) / (t or 1)
955 955
956 956 # needs > 9% of width to be visible or 0 to be hidden
957 957 a_p = max(9, unit * a) if a > 0 else 0
958 958 d_p = max(9, unit * d) if d > 0 else 0
959 959 p_sum = a_p + d_p
960 960
961 961 if p_sum > width:
962 962 #adjust the percentage to be == 100% since we adjusted to 9
963 963 if a_p > d_p:
964 964 a_p = a_p - (p_sum - width)
965 965 else:
966 966 d_p = d_p - (p_sum - width)
967 967
968 968 a_v = a if a > 0 else ''
969 969 d_v = d if d > 0 else ''
970 970
971 971 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
972 972 cgen('a', a_v, d_v), a_p, a_v
973 973 )
974 974 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
975 975 cgen('d', a_v, d_v), d_p, d_v
976 976 )
977 977 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
978 978
979 979
980 980 def urlify_text(text_, safe=True):
981 981 """
982 982 Extrac urls from text and make html links out of them
983 983
984 984 :param text_:
985 985 """
986 986
987 987 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
988 988 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
989 989
990 990 def url_func(match_obj):
991 991 url_full = match_obj.groups()[0]
992 992 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
993 993 _newtext = url_pat.sub(url_func, text_)
994 994 if safe:
995 995 return literal(_newtext)
996 996 return _newtext
997 997
998 998
999 999 def urlify_changesets(text_, repository):
1000 1000 """
1001 1001 Extract revision ids from changeset and make link from them
1002 1002
1003 1003 :param text_:
1004 1004 :param repository: repo name to build the URL with
1005 1005 """
1006 1006 from pylons import url # doh, we need to re-import url to mock it later
1007 1007 URL_PAT = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)')
1008 1008
1009 1009 def url_func(match_obj):
1010 1010 rev = match_obj.groups()[1]
1011 1011 pref = match_obj.groups()[0]
1012 1012 suf = match_obj.groups()[2]
1013 1013
1014 1014 tmpl = (
1015 1015 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1016 1016 '%(rev)s</a>%(suf)s'
1017 1017 )
1018 1018 return tmpl % {
1019 1019 'pref': pref,
1020 1020 'cls': 'revision-link',
1021 1021 'url': url('changeset_home', repo_name=repository, revision=rev),
1022 1022 'rev': rev,
1023 1023 'suf': suf
1024 1024 }
1025 1025
1026 1026 newtext = URL_PAT.sub(url_func, text_)
1027 1027
1028 1028 return newtext
1029 1029
1030 1030
1031 1031 def urlify_commit(text_, repository=None, link_=None):
1032 1032 """
1033 1033 Parses given text message and makes proper links.
1034 1034 issues are linked to given issue-server, and rest is a changeset link
1035 1035 if link_ is given, in other case it's a plain text
1036 1036
1037 1037 :param text_:
1038 1038 :param repository:
1039 1039 :param link_: changeset link
1040 1040 """
1041 1041 import traceback
1042 1042 from pylons import url # doh, we need to re-import url to mock it later
1043 1043
1044 1044 def escaper(string):
1045 1045 return string.replace('<', '&lt;').replace('>', '&gt;')
1046 1046
1047 1047 def linkify_others(t, l):
1048 1048 urls = re.compile(r'(\<a.*?\<\/a\>)',)
1049 1049 links = []
1050 1050 for e in urls.split(t):
1051 1051 if not urls.match(e):
1052 1052 links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
1053 1053 else:
1054 1054 links.append(e)
1055 1055
1056 1056 return ''.join(links)
1057 1057
1058 1058 # urlify changesets - extrac revisions and make link out of them
1059 1059 newtext = urlify_changesets(escaper(text_), repository)
1060 1060
1061 1061 # extract http/https links and make them real urls
1062 1062 newtext = urlify_text(newtext, safe=False)
1063 1063
1064 1064 try:
1065 1065 from rhodecode import CONFIG
1066 1066 conf = CONFIG
1067 1067
1068 1068 # allow multiple issue servers to be used
1069 1069 valid_indices = [
1070 1070 x.group(1)
1071 1071 for x in map(lambda x: re.match(r'issue_pat(.*)', x), conf.keys())
1072 1072 if x and 'issue_server_link%s' % x.group(1) in conf
1073 1073 and 'issue_prefix%s' % x.group(1) in conf
1074 1074 ]
1075 1075
1076 1076 log.debug('found issue server suffixes `%s` during valuation of: %s'
1077 1077 % (','.join(valid_indices), newtext))
1078 1078
1079 1079 for pattern_index in valid_indices:
1080 1080 ISSUE_PATTERN = conf.get('issue_pat%s' % pattern_index)
1081 1081 ISSUE_SERVER_LNK = conf.get('issue_server_link%s' % pattern_index)
1082 1082 ISSUE_PREFIX = conf.get('issue_prefix%s' % pattern_index)
1083 1083
1084 1084 log.debug('pattern suffix `%s` PAT:%s SERVER_LINK:%s PREFIX:%s'
1085 1085 % (pattern_index, ISSUE_PATTERN, ISSUE_SERVER_LNK,
1086 1086 ISSUE_PREFIX))
1087 1087
1088 1088 URL_PAT = re.compile(r'%s' % ISSUE_PATTERN)
1089 1089
1090 1090 def url_func(match_obj):
1091 1091 pref = ''
1092 1092 if match_obj.group().startswith(' '):
1093 1093 pref = ' '
1094 1094
1095 1095 issue_id = ''.join(match_obj.groups())
1096 1096 tmpl = (
1097 1097 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1098 1098 '%(issue-prefix)s%(id-repr)s'
1099 1099 '</a>'
1100 1100 )
1101 1101 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
1102 1102 if repository:
1103 1103 url = url.replace('{repo}', repository)
1104 1104 repo_name = repository.split(URL_SEP)[-1]
1105 1105 url = url.replace('{repo_name}', repo_name)
1106 1106
1107 1107 return tmpl % {
1108 1108 'pref': pref,
1109 1109 'cls': 'issue-tracker-link',
1110 1110 'url': url,
1111 1111 'id-repr': issue_id,
1112 1112 'issue-prefix': ISSUE_PREFIX,
1113 1113 'serv': ISSUE_SERVER_LNK,
1114 1114 }
1115 1115 newtext = URL_PAT.sub(url_func, newtext)
1116 1116 log.debug('processed prefix:`%s` => %s' % (pattern_index, newtext))
1117 1117
1118 1118 # if we actually did something above
1119 1119 if link_:
1120 1120 # wrap not links into final link => link_
1121 1121 newtext = linkify_others(newtext, link_)
1122 1122 except:
1123 1123 log.error(traceback.format_exc())
1124 1124 pass
1125 1125
1126 1126 return literal(newtext)
1127 1127
1128 1128
1129 1129 def rst(source):
1130 1130 return literal('<div class="rst-block">%s</div>' %
1131 1131 MarkupRenderer.rst(source))
1132 1132
1133 1133
1134 1134 def rst_w_mentions(source):
1135 1135 """
1136 1136 Wrapped rst renderer with @mention highlighting
1137 1137
1138 1138 :param source:
1139 1139 """
1140 1140 return literal('<div class="rst-block">%s</div>' %
1141 1141 MarkupRenderer.rst_with_mentions(source))
1142 1142
1143 1143
1144 1144 def changeset_status(repo, revision):
1145 1145 return ChangesetStatusModel().get_status(repo, revision)
1146 1146
1147 1147
1148 1148 def changeset_status_lbl(changeset_status):
1149 1149 return dict(ChangesetStatus.STATUSES).get(changeset_status)
1150 1150
1151 1151
1152 1152 def get_permission_name(key):
1153 1153 return dict(Permission.PERMS).get(key)
1154 1154
1155 1155
1156 1156 def journal_filter_help():
1157 1157 return _(textwrap.dedent('''
1158 1158 Example filter terms:
1159 1159 repository:vcs
1160 1160 username:marcin
1161 1161 action:*push*
1162 1162 ip:127.0.0.1
1163 1163 date:20120101
1164 1164 date:[20120101100000 TO 20120102]
1165 1165
1166 1166 Generate wildcards using '*' character:
1167 1167 "repositroy:vcs*" - search everything starting with 'vcs'
1168 1168 "repository:*vcs*" - search for repository containing 'vcs'
1169 1169
1170 1170 Optional AND / OR operators in queries
1171 1171 "repository:vcs OR repository:test"
1172 1172 "username:test AND repository:test*"
1173 1173 '''))
1174 1174
1175 1175
1176 1176 def not_mapped_error(repo_name):
1177 1177 flash(_('%s repository is not mapped to db perhaps'
1178 1178 ' it was created or renamed from the filesystem'
1179 1179 ' please run the application again'
1180 1180 ' in order to rescan repositories') % repo_name, category='error')
1181 1181
1182 1182
1183 1183 def ip_range(ip_addr):
1184 1184 from rhodecode.model.db import UserIpMap
1185 1185 s, e = UserIpMap._get_ip_range(ip_addr)
1186 1186 return '%s - %s' % (s, e)
@@ -1,705 +1,705 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.repo
4 4 ~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Repository model for rhodecode
7 7
8 8 :created_on: Jun 5, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 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 from __future__ import with_statement
26 26 import os
27 27 import shutil
28 28 import logging
29 29 import traceback
30 30 from datetime import datetime
31 31
32 32 from rhodecode.lib.vcs.backends import get_backend
33 33 from rhodecode.lib.compat import json
34 34 from rhodecode.lib.utils2 import LazyProperty, safe_str, safe_unicode,\
35 35 remove_prefix
36 36 from rhodecode.lib.caching_query import FromCache
37 37 from rhodecode.lib.hooks import log_create_repository, log_delete_repository
38 38
39 39 from rhodecode.model import BaseModel
40 40 from rhodecode.model.db import Repository, UserRepoToPerm, User, Permission, \
41 41 Statistics, UsersGroup, UsersGroupRepoToPerm, RhodeCodeUi, RepoGroup,\
42 42 RhodeCodeSetting, RepositoryField
43 43 from rhodecode.lib import helpers as h
44 44 from rhodecode.lib.auth import HasRepoPermissionAny
45 45 from rhodecode.lib.vcs.backends.base import EmptyChangeset
46 46
47 47
48 48 log = logging.getLogger(__name__)
49 49
50 50
51 51 class RepoModel(BaseModel):
52 52
53 53 cls = Repository
54 54 URL_SEPARATOR = Repository.url_sep()
55 55
56 56 def __get_users_group(self, users_group):
57 57 return self._get_instance(UsersGroup, users_group,
58 58 callback=UsersGroup.get_by_group_name)
59 59
60 60 def _get_repos_group(self, repos_group):
61 61 return self._get_instance(RepoGroup, repos_group,
62 62 callback=RepoGroup.get_by_group_name)
63 63
64 64 @LazyProperty
65 65 def repos_path(self):
66 66 """
67 67 Get's the repositories root path from database
68 68 """
69 69
70 70 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
71 71 return q.ui_value
72 72
73 73 def get(self, repo_id, cache=False):
74 74 repo = self.sa.query(Repository)\
75 75 .filter(Repository.repo_id == repo_id)
76 76
77 77 if cache:
78 78 repo = repo.options(FromCache("sql_cache_short",
79 79 "get_repo_%s" % repo_id))
80 80 return repo.scalar()
81 81
82 82 def get_repo(self, repository):
83 83 return self._get_repo(repository)
84 84
85 85 def get_by_repo_name(self, repo_name, cache=False):
86 86 repo = self.sa.query(Repository)\
87 87 .filter(Repository.repo_name == repo_name)
88 88
89 89 if cache:
90 90 repo = repo.options(FromCache("sql_cache_short",
91 91 "get_repo_%s" % repo_name))
92 92 return repo.scalar()
93 93
94 94 def get_all_user_repos(self, user):
95 95 """
96 96 Get's all repositories that user have at least read access
97 97
98 98 :param user:
99 99 :type user:
100 100 """
101 101 from rhodecode.lib.auth import AuthUser
102 102 user = self._get_user(user)
103 103 repos = AuthUser(user_id=user.user_id).permissions['repositories']
104 104 access_check = lambda r: r[1] in ['repository.read',
105 105 'repository.write',
106 106 'repository.admin']
107 107 repos = [x[0] for x in filter(access_check, repos.items())]
108 108 return Repository.query().filter(Repository.repo_name.in_(repos))
109 109
110 110 def get_users_js(self):
111 111 users = self.sa.query(User).filter(User.active == True).all()
112 112 return json.dumps([
113 113 {
114 114 'id': u.user_id,
115 115 'fname': u.name,
116 116 'lname': u.lastname,
117 117 'nname': u.username,
118 118 'gravatar_lnk': h.gravatar_url(u.email, 14)
119 119 } for u in users]
120 120 )
121 121
122 122 def get_users_groups_js(self):
123 123 users_groups = self.sa.query(UsersGroup)\
124 124 .filter(UsersGroup.users_group_active == True).all()
125 125
126 126 return json.dumps([
127 127 {
128 128 'id': gr.users_group_id,
129 129 'grname': gr.users_group_name,
130 130 'grmembers': len(gr.members),
131 131 } for gr in users_groups]
132 132 )
133 133
134 134 @classmethod
135 135 def _render_datatable(cls, tmpl, *args, **kwargs):
136 136 import rhodecode
137 137 from pylons import tmpl_context as c
138 138 from pylons.i18n.translation import _
139 139
140 140 _tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup
141 141 template = _tmpl_lookup.get_template('data_table/_dt_elements.html')
142 142
143 143 tmpl = template.get_def(tmpl)
144 144 kwargs.update(dict(_=_, h=h, c=c))
145 145 return tmpl.render(*args, **kwargs)
146 146
147 147 @classmethod
148 148 def update_repoinfo(cls, repositories=None):
149 149 if not repositories:
150 150 repositories = Repository.getAll()
151 151 for repo in repositories:
152 152 scm_repo = repo.scm_instance_no_cache
153 153 last_cs = EmptyChangeset()
154 154 if scm_repo:
155 155 last_cs = scm_repo.get_changeset()
156 156 repo.update_changeset_cache(last_cs)
157 157
158 158 def get_repos_as_dict(self, repos_list=None, admin=False, perm_check=True,
159 159 super_user_actions=False):
160 160 _render = self._render_datatable
161 161
162 162 def quick_menu(repo_name):
163 163 return _render('quick_menu', repo_name)
164 164
165 165 def repo_lnk(name, rtype, private, fork_of):
166 166 return _render('repo_name', name, rtype, private, fork_of,
167 167 short_name=not admin, admin=False)
168 168
169 169 def last_change(last_change):
170 170 return _render("last_change", last_change)
171 171
172 172 def rss_lnk(repo_name):
173 173 return _render("rss", repo_name)
174 174
175 175 def atom_lnk(repo_name):
176 176 return _render("atom", repo_name)
177 177
178 178 def last_rev(repo_name, cs_cache):
179 179 return _render('revision', repo_name, cs_cache.get('revision'),
180 180 cs_cache.get('raw_id'), cs_cache.get('author'),
181 181 cs_cache.get('message'))
182 182
183 183 def desc(desc):
184 184 from pylons import tmpl_context as c
185 185 if c.visual.stylify_metatags:
186 186 return h.urlify_text(h.desc_stylize(h.truncate(desc, 60)))
187 187 else:
188 188 return h.urlify_text(h.truncate(desc, 60))
189 189
190 190 def repo_actions(repo_name):
191 191 return _render('repo_actions', repo_name, super_user_actions)
192 192
193 193 def owner_actions(user_id, username):
194 194 return _render('user_name', user_id, username)
195 195
196 196 repos_data = []
197 197 for repo in repos_list:
198 198 if perm_check:
199 199 # check permission at this level
200 200 if not HasRepoPermissionAny(
201 201 'repository.read', 'repository.write', 'repository.admin'
202 202 )(repo.repo_name, 'get_repos_as_dict check'):
203 203 continue
204 204 cs_cache = repo.changeset_cache
205 205 row = {
206 206 "menu": quick_menu(repo.repo_name),
207 207 "raw_name": repo.repo_name.lower(),
208 208 "name": repo_lnk(repo.repo_name, repo.repo_type,
209 209 repo.private, repo.fork),
210 210 "last_change": last_change(repo.last_db_change),
211 211 "last_changeset": last_rev(repo.repo_name, cs_cache),
212 212 "raw_tip": cs_cache.get('revision'),
213 213 "desc": desc(repo.description),
214 214 "owner": h.person(repo.user.username),
215 215 "rss": rss_lnk(repo.repo_name),
216 216 "atom": atom_lnk(repo.repo_name),
217 217
218 218 }
219 219 if admin:
220 220 row.update({
221 221 "action": repo_actions(repo.repo_name),
222 222 "owner": owner_actions(repo.user.user_id,
223 223 h.person(repo.user.username))
224 224 })
225 225 repos_data.append(row)
226 226
227 227 return {
228 228 "totalRecords": len(repos_list),
229 229 "startIndex": 0,
230 230 "sort": "name",
231 231 "dir": "asc",
232 232 "records": repos_data
233 233 }
234 234
235 235 def _get_defaults(self, repo_name):
236 236 """
237 237 Get's information about repository, and returns a dict for
238 238 usage in forms
239 239
240 240 :param repo_name:
241 241 """
242 242
243 243 repo_info = Repository.get_by_repo_name(repo_name)
244 244
245 245 if repo_info is None:
246 246 return None
247 247
248 248 defaults = repo_info.get_dict()
249 249 group, repo_name = repo_info.groups_and_repo
250 250 defaults['repo_name'] = repo_name
251 251 defaults['repo_group'] = getattr(group[-1] if group else None,
252 252 'group_id', None)
253 253
254 254 for strip, k in [(0, 'repo_type'), (1, 'repo_enable_downloads'),
255 255 (1, 'repo_description'), (1, 'repo_enable_locking'),
256 256 (1, 'repo_landing_rev'), (0, 'clone_uri'),
257 257 (1, 'repo_private'), (1, 'repo_enable_statistics')]:
258 258 attr = k
259 259 if strip:
260 260 attr = remove_prefix(k, 'repo_')
261 261
262 262 defaults[k] = defaults[attr]
263 263
264 264 # fill owner
265 265 if repo_info.user:
266 266 defaults.update({'user': repo_info.user.username})
267 267 else:
268 268 replacement_user = User.query().filter(User.admin ==
269 269 True).first().username
270 270 defaults.update({'user': replacement_user})
271 271
272 272 # fill repository users
273 273 for p in repo_info.repo_to_perm:
274 274 defaults.update({'u_perm_%s' % p.user.username:
275 275 p.permission.permission_name})
276 276
277 277 # fill repository groups
278 278 for p in repo_info.users_group_to_perm:
279 279 defaults.update({'g_perm_%s' % p.users_group.users_group_name:
280 280 p.permission.permission_name})
281 281
282 282 return defaults
283 283
284 284 def update(self, org_repo_name, **kwargs):
285 285 try:
286 286 cur_repo = self.get_by_repo_name(org_repo_name, cache=False)
287 287
288 288 # update permissions
289 289 for member, perm, member_type in kwargs['perms_updates']:
290 290 if member_type == 'user':
291 291 # this updates existing one
292 292 RepoModel().grant_user_permission(
293 293 repo=cur_repo, user=member, perm=perm
294 294 )
295 295 else:
296 296 RepoModel().grant_users_group_permission(
297 297 repo=cur_repo, group_name=member, perm=perm
298 298 )
299 299 # set new permissions
300 300 for member, perm, member_type in kwargs['perms_new']:
301 301 if member_type == 'user':
302 302 RepoModel().grant_user_permission(
303 303 repo=cur_repo, user=member, perm=perm
304 304 )
305 305 else:
306 306 RepoModel().grant_users_group_permission(
307 307 repo=cur_repo, group_name=member, perm=perm
308 308 )
309 309
310 310 if 'user' in kwargs:
311 311 cur_repo.user = User.get_by_username(kwargs['user'])
312 312
313 313 if 'repo_group' in kwargs:
314 314 cur_repo.group = RepoGroup.get(kwargs['repo_group'])
315 315
316 316 for strip, k in [(0, 'repo_type'), (1, 'repo_enable_downloads'),
317 317 (1, 'repo_description'), (1, 'repo_enable_locking'),
318 318 (1, 'repo_landing_rev'), (0, 'clone_uri'),
319 319 (1, 'repo_private'), (1, 'repo_enable_statistics')]:
320 320 if k in kwargs:
321 321 val = kwargs[k]
322 322 if strip:
323 323 k = remove_prefix(k, 'repo_')
324 324 setattr(cur_repo, k, val)
325 325
326 326 new_name = cur_repo.get_new_name(kwargs['repo_name'])
327 327 cur_repo.repo_name = new_name
328 328
329 329 #handle extra fields
330 330 for field in filter(lambda k: k.startswith(RepositoryField.PREFIX), kwargs):
331 331 k = RepositoryField.un_prefix_key(field)
332 332 ex_field = RepositoryField.get_by_key_name(key=k, repo=cur_repo)
333 333 if ex_field:
334 334 ex_field.field_value = kwargs[field]
335 335 self.sa.add(ex_field)
336 336 self.sa.add(cur_repo)
337 337
338 338 if org_repo_name != new_name:
339 339 # rename repository
340 340 self.__rename_repo(old=org_repo_name, new=new_name)
341 341
342 342 return cur_repo
343 343 except:
344 344 log.error(traceback.format_exc())
345 345 raise
346 346
347 347 def create_repo(self, repo_name, repo_type, description, owner,
348 348 private=False, clone_uri=None, repos_group=None,
349 349 landing_rev='tip', just_db=False, fork_of=None,
350 350 copy_fork_permissions=False, enable_statistics=False,
351 351 enable_locking=False, enable_downloads=False):
352 352 """
353 353 Create repository
354 354
355 355 """
356 356 from rhodecode.model.scm import ScmModel
357 357
358 358 owner = self._get_user(owner)
359 359 fork_of = self._get_repo(fork_of)
360 360 repos_group = self._get_repos_group(repos_group)
361 361 try:
362 362
363 363 # repo name is just a name of repository
364 364 # while repo_name_full is a full qualified name that is combined
365 365 # with name and path of group
366 366 repo_name_full = repo_name
367 367 repo_name = repo_name.split(self.URL_SEPARATOR)[-1]
368 368
369 369 new_repo = Repository()
370 370 new_repo.enable_statistics = False
371 371 new_repo.repo_name = repo_name_full
372 372 new_repo.repo_type = repo_type
373 373 new_repo.user = owner
374 374 new_repo.group = repos_group
375 375 new_repo.description = description or repo_name
376 376 new_repo.private = private
377 377 new_repo.clone_uri = clone_uri
378 378 new_repo.landing_rev = landing_rev
379 379
380 380 new_repo.enable_statistics = enable_statistics
381 381 new_repo.enable_locking = enable_locking
382 382 new_repo.enable_downloads = enable_downloads
383 383
384 384 if repos_group:
385 385 new_repo.enable_locking = repos_group.enable_locking
386 386
387 387 if fork_of:
388 388 parent_repo = fork_of
389 389 new_repo.fork = parent_repo
390 390
391 391 self.sa.add(new_repo)
392 392
393 393 def _create_default_perms():
394 394 # create default permission
395 395 repo_to_perm = UserRepoToPerm()
396 396 default = 'repository.read'
397 397 for p in User.get_by_username('default').user_perms:
398 398 if p.permission.permission_name.startswith('repository.'):
399 399 default = p.permission.permission_name
400 400 break
401 401
402 402 default_perm = 'repository.none' if private else default
403 403
404 404 repo_to_perm.permission_id = self.sa.query(Permission)\
405 405 .filter(Permission.permission_name == default_perm)\
406 406 .one().permission_id
407 407
408 408 repo_to_perm.repository = new_repo
409 409 repo_to_perm.user_id = User.get_by_username('default').user_id
410 410
411 411 self.sa.add(repo_to_perm)
412 412
413 413 if fork_of:
414 414 if copy_fork_permissions:
415 415 repo = fork_of
416 416 user_perms = UserRepoToPerm.query()\
417 417 .filter(UserRepoToPerm.repository == repo).all()
418 418 group_perms = UsersGroupRepoToPerm.query()\
419 419 .filter(UsersGroupRepoToPerm.repository == repo).all()
420 420
421 421 for perm in user_perms:
422 422 UserRepoToPerm.create(perm.user, new_repo,
423 423 perm.permission)
424 424
425 425 for perm in group_perms:
426 426 UsersGroupRepoToPerm.create(perm.users_group, new_repo,
427 427 perm.permission)
428 428 else:
429 429 _create_default_perms()
430 430 else:
431 431 _create_default_perms()
432 432
433 433 if not just_db:
434 434 self.__create_repo(repo_name, repo_type,
435 435 repos_group,
436 436 clone_uri)
437 437 log_create_repository(new_repo.get_dict(),
438 438 created_by=owner.username)
439 439
440 440 # now automatically start following this repository as owner
441 441 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
442 442 owner.user_id)
443 443 return new_repo
444 444 except:
445 445 log.error(traceback.format_exc())
446 446 raise
447 447
448 448 def create(self, form_data, cur_user, just_db=False, fork=None):
449 449 """
450 450 Backward compatibility function, just a wrapper on top of create_repo
451 451
452 452 :param form_data:
453 453 :param cur_user:
454 454 :param just_db:
455 455 :param fork:
456 456 """
457 457 owner = cur_user
458 458 repo_name = form_data['repo_name_full']
459 459 repo_type = form_data['repo_type']
460 460 description = form_data['repo_description']
461 461 private = form_data['repo_private']
462 462 clone_uri = form_data.get('clone_uri')
463 463 repos_group = form_data['repo_group']
464 464 landing_rev = form_data['repo_landing_rev']
465 465 copy_fork_permissions = form_data.get('copy_permissions')
466 466 fork_of = form_data.get('fork_parent_id')
467 467
468 468 ## repo creation defaults, private and repo_type are filled in form
469 469 defs = RhodeCodeSetting.get_default_repo_settings(strip_prefix=True)
470 470 enable_statistics = defs.get('repo_enable_statistics')
471 471 enable_locking = defs.get('repo_enable_locking')
472 472 enable_downloads = defs.get('repo_enable_downloads')
473 473
474 474 return self.create_repo(
475 475 repo_name, repo_type, description, owner, private, clone_uri,
476 476 repos_group, landing_rev, just_db, fork_of, copy_fork_permissions,
477 477 enable_statistics, enable_locking, enable_downloads
478 478 )
479 479
480 480 def create_fork(self, form_data, cur_user):
481 481 """
482 482 Simple wrapper into executing celery task for fork creation
483 483
484 484 :param form_data:
485 485 :param cur_user:
486 486 """
487 487 from rhodecode.lib.celerylib import tasks, run_task
488 488 run_task(tasks.create_repo_fork, form_data, cur_user)
489 489
490 490 def delete(self, repo):
491 491 repo = self._get_repo(repo)
492 492 if repo:
493 493 old_repo_dict = repo.get_dict()
494 494 owner = repo.user
495 495 try:
496 496 self.sa.delete(repo)
497 497 self.__delete_repo(repo)
498 498 log_delete_repository(old_repo_dict,
499 499 deleted_by=owner.username)
500 500 except:
501 501 log.error(traceback.format_exc())
502 502 raise
503 503
504 504 def grant_user_permission(self, repo, user, perm):
505 505 """
506 506 Grant permission for user on given repository, or update existing one
507 507 if found
508 508
509 509 :param repo: Instance of Repository, repository_id, or repository name
510 510 :param user: Instance of User, user_id or username
511 511 :param perm: Instance of Permission, or permission_name
512 512 """
513 513 user = self._get_user(user)
514 514 repo = self._get_repo(repo)
515 515 permission = self._get_perm(perm)
516 516
517 517 # check if we have that permission already
518 518 obj = self.sa.query(UserRepoToPerm)\
519 519 .filter(UserRepoToPerm.user == user)\
520 520 .filter(UserRepoToPerm.repository == repo)\
521 521 .scalar()
522 522 if obj is None:
523 523 # create new !
524 524 obj = UserRepoToPerm()
525 525 obj.repository = repo
526 526 obj.user = user
527 527 obj.permission = permission
528 528 self.sa.add(obj)
529 529 log.debug('Granted perm %s to %s on %s' % (perm, user, repo))
530 530
531 531 def revoke_user_permission(self, repo, user):
532 532 """
533 533 Revoke permission for user on given repository
534 534
535 535 :param repo: Instance of Repository, repository_id, or repository name
536 536 :param user: Instance of User, user_id or username
537 537 """
538 538
539 539 user = self._get_user(user)
540 540 repo = self._get_repo(repo)
541 541
542 542 obj = self.sa.query(UserRepoToPerm)\
543 543 .filter(UserRepoToPerm.repository == repo)\
544 544 .filter(UserRepoToPerm.user == user)\
545 545 .scalar()
546 546 if obj:
547 547 self.sa.delete(obj)
548 548 log.debug('Revoked perm on %s on %s' % (repo, user))
549 549
550 550 def grant_users_group_permission(self, repo, group_name, perm):
551 551 """
552 Grant permission for users group on given repository, or update
552 Grant permission for user group on given repository, or update
553 553 existing one if found
554 554
555 555 :param repo: Instance of Repository, repository_id, or repository name
556 556 :param group_name: Instance of UserGroup, users_group_id,
557 or users group name
557 or user group name
558 558 :param perm: Instance of Permission, or permission_name
559 559 """
560 560 repo = self._get_repo(repo)
561 561 group_name = self.__get_users_group(group_name)
562 562 permission = self._get_perm(perm)
563 563
564 564 # check if we have that permission already
565 565 obj = self.sa.query(UsersGroupRepoToPerm)\
566 566 .filter(UsersGroupRepoToPerm.users_group == group_name)\
567 567 .filter(UsersGroupRepoToPerm.repository == repo)\
568 568 .scalar()
569 569
570 570 if obj is None:
571 571 # create new
572 572 obj = UsersGroupRepoToPerm()
573 573
574 574 obj.repository = repo
575 575 obj.users_group = group_name
576 576 obj.permission = permission
577 577 self.sa.add(obj)
578 578 log.debug('Granted perm %s to %s on %s' % (perm, group_name, repo))
579 579
580 580 def revoke_users_group_permission(self, repo, group_name):
581 581 """
582 Revoke permission for users group on given repository
582 Revoke permission for user group on given repository
583 583
584 584 :param repo: Instance of Repository, repository_id, or repository name
585 585 :param group_name: Instance of UserGroup, users_group_id,
586 or users group name
586 or user group name
587 587 """
588 588 repo = self._get_repo(repo)
589 589 group_name = self.__get_users_group(group_name)
590 590
591 591 obj = self.sa.query(UsersGroupRepoToPerm)\
592 592 .filter(UsersGroupRepoToPerm.repository == repo)\
593 593 .filter(UsersGroupRepoToPerm.users_group == group_name)\
594 594 .scalar()
595 595 if obj:
596 596 self.sa.delete(obj)
597 597 log.debug('Revoked perm to %s on %s' % (repo, group_name))
598 598
599 599 def delete_stats(self, repo_name):
600 600 """
601 601 removes stats for given repo
602 602
603 603 :param repo_name:
604 604 """
605 605 repo = self._get_repo(repo_name)
606 606 try:
607 607 obj = self.sa.query(Statistics)\
608 608 .filter(Statistics.repository == repo).scalar()
609 609 if obj:
610 610 self.sa.delete(obj)
611 611 except:
612 612 log.error(traceback.format_exc())
613 613 raise
614 614
615 615 def __create_repo(self, repo_name, alias, parent, clone_uri=False):
616 616 """
617 617 makes repository on filesystem. It's group aware means it'll create
618 618 a repository within a group, and alter the paths accordingly of
619 619 group location
620 620
621 621 :param repo_name:
622 622 :param alias:
623 623 :param parent_id:
624 624 :param clone_uri:
625 625 """
626 626 from rhodecode.lib.utils import is_valid_repo, is_valid_repos_group
627 627 from rhodecode.model.scm import ScmModel
628 628
629 629 if parent:
630 630 new_parent_path = os.sep.join(parent.full_path_splitted)
631 631 else:
632 632 new_parent_path = ''
633 633
634 634 # we need to make it str for mercurial
635 635 repo_path = os.path.join(*map(lambda x: safe_str(x),
636 636 [self.repos_path, new_parent_path, repo_name]))
637 637
638 638 # check if this path is not a repository
639 639 if is_valid_repo(repo_path, self.repos_path):
640 640 raise Exception('This path %s is a valid repository' % repo_path)
641 641
642 642 # check if this path is a group
643 643 if is_valid_repos_group(repo_path, self.repos_path):
644 644 raise Exception('This path %s is a valid group' % repo_path)
645 645
646 646 log.info('creating repo %s in %s @ %s' % (
647 647 repo_name, safe_unicode(repo_path), clone_uri
648 648 )
649 649 )
650 650 backend = get_backend(alias)
651 651 if alias == 'hg':
652 652 backend(repo_path, create=True, src_url=clone_uri)
653 653 elif alias == 'git':
654 654 r = backend(repo_path, create=True, src_url=clone_uri, bare=True)
655 655 # add rhodecode hook into this repo
656 656 ScmModel().install_git_hook(repo=r)
657 657 else:
658 658 raise Exception('Undefined alias %s' % alias)
659 659
660 660 def __rename_repo(self, old, new):
661 661 """
662 662 renames repository on filesystem
663 663
664 664 :param old: old name
665 665 :param new: new name
666 666 """
667 667 log.info('renaming repo from %s to %s' % (old, new))
668 668
669 669 old_path = os.path.join(self.repos_path, old)
670 670 new_path = os.path.join(self.repos_path, new)
671 671 if os.path.isdir(new_path):
672 672 raise Exception(
673 673 'Was trying to rename to already existing dir %s' % new_path
674 674 )
675 675 shutil.move(old_path, new_path)
676 676
677 677 def __delete_repo(self, repo):
678 678 """
679 679 removes repo from filesystem, the removal is acctually made by
680 680 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
681 681 repository is no longer valid for rhodecode, can be undeleted later on
682 682 by reverting the renames on this repository
683 683
684 684 :param repo: repo object
685 685 """
686 686 rm_path = os.path.join(self.repos_path, repo.repo_name)
687 687 log.info("Removing %s" % (rm_path))
688 688 # disable hg/git internal that it doesn't get detected as repo
689 689 alias = repo.repo_type
690 690
691 691 bare = getattr(repo.scm_instance, 'bare', False)
692 692
693 693 if not bare:
694 694 # skip this for bare git repos
695 695 shutil.move(os.path.join(rm_path, '.%s' % alias),
696 696 os.path.join(rm_path, 'rm__.%s' % alias))
697 697 # disable repo
698 698 _now = datetime.now()
699 699 _ms = str(_now.microsecond).rjust(6, '0')
700 700 _d = 'rm__%s__%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
701 701 repo.just_name)
702 702 if repo.group:
703 703 args = repo.group.full_path_splitted + [_d]
704 704 _d = os.path.join(*args)
705 705 shutil.move(rm_path, os.path.join(self.repos_path, _d))
@@ -1,763 +1,763 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.user
4 4 ~~~~~~~~~~~~~~~~~~~~
5 5
6 6 users model for RhodeCode
7 7
8 8 :created_on: Apr 9, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 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 import traceback
28 28 import itertools
29 29 import collections
30 30 from pylons import url
31 31 from pylons.i18n.translation import _
32 32
33 33 from sqlalchemy.exc import DatabaseError
34 34 from sqlalchemy.orm import joinedload
35 35
36 36 from rhodecode.lib.utils2 import safe_unicode, generate_api_key
37 37 from rhodecode.lib.caching_query import FromCache
38 38 from rhodecode.model import BaseModel
39 39 from rhodecode.model.db import User, UserRepoToPerm, Repository, Permission, \
40 40 UserToPerm, UsersGroupRepoToPerm, UsersGroupToPerm, UsersGroupMember, \
41 41 Notification, RepoGroup, UserRepoGroupToPerm, UsersGroupRepoGroupToPerm, \
42 42 UserEmailMap, UserIpMap
43 43 from rhodecode.lib.exceptions import DefaultUserException, \
44 44 UserOwnsReposException
45 45 from rhodecode.model.meta import Session
46 46
47 47
48 48 log = logging.getLogger(__name__)
49 49
50 50 PERM_WEIGHTS = Permission.PERM_WEIGHTS
51 51
52 52
53 53 class UserModel(BaseModel):
54 54 cls = User
55 55
56 56 def get(self, user_id, cache=False):
57 57 user = self.sa.query(User)
58 58 if cache:
59 59 user = user.options(FromCache("sql_cache_short",
60 60 "get_user_%s" % user_id))
61 61 return user.get(user_id)
62 62
63 63 def get_user(self, user):
64 64 return self._get_user(user)
65 65
66 66 def get_by_username(self, username, cache=False, case_insensitive=False):
67 67
68 68 if case_insensitive:
69 69 user = self.sa.query(User).filter(User.username.ilike(username))
70 70 else:
71 71 user = self.sa.query(User)\
72 72 .filter(User.username == username)
73 73 if cache:
74 74 user = user.options(FromCache("sql_cache_short",
75 75 "get_user_%s" % username))
76 76 return user.scalar()
77 77
78 78 def get_by_email(self, email, cache=False, case_insensitive=False):
79 79 return User.get_by_email(email, case_insensitive, cache)
80 80
81 81 def get_by_api_key(self, api_key, cache=False):
82 82 return User.get_by_api_key(api_key, cache)
83 83
84 84 def create(self, form_data):
85 85 from rhodecode.lib.auth import get_crypt_password
86 86 try:
87 87 new_user = User()
88 88 for k, v in form_data.items():
89 89 if k == 'password':
90 90 v = get_crypt_password(v)
91 91 if k == 'firstname':
92 92 k = 'name'
93 93 setattr(new_user, k, v)
94 94
95 95 new_user.api_key = generate_api_key(form_data['username'])
96 96 self.sa.add(new_user)
97 97 return new_user
98 98 except:
99 99 log.error(traceback.format_exc())
100 100 raise
101 101
102 102 def create_or_update(self, username, password, email, firstname='',
103 103 lastname='', active=True, admin=False, ldap_dn=None):
104 104 """
105 105 Creates a new instance if not found, or updates current one
106 106
107 107 :param username:
108 108 :param password:
109 109 :param email:
110 110 :param active:
111 111 :param firstname:
112 112 :param lastname:
113 113 :param active:
114 114 :param admin:
115 115 :param ldap_dn:
116 116 """
117 117
118 118 from rhodecode.lib.auth import get_crypt_password
119 119
120 120 log.debug('Checking for %s account in RhodeCode database' % username)
121 121 user = User.get_by_username(username, case_insensitive=True)
122 122 if user is None:
123 123 log.debug('creating new user %s' % username)
124 124 new_user = User()
125 125 edit = False
126 126 else:
127 127 log.debug('updating user %s' % username)
128 128 new_user = user
129 129 edit = True
130 130
131 131 try:
132 132 new_user.username = username
133 133 new_user.admin = admin
134 134 # set password only if creating an user or password is changed
135 135 if edit is False or user.password != password:
136 136 new_user.password = get_crypt_password(password)
137 137 new_user.api_key = generate_api_key(username)
138 138 new_user.email = email
139 139 new_user.active = active
140 140 new_user.ldap_dn = safe_unicode(ldap_dn) if ldap_dn else None
141 141 new_user.name = firstname
142 142 new_user.lastname = lastname
143 143 self.sa.add(new_user)
144 144 return new_user
145 145 except (DatabaseError,):
146 146 log.error(traceback.format_exc())
147 147 raise
148 148
149 149 def create_for_container_auth(self, username, attrs):
150 150 """
151 151 Creates the given user if it's not already in the database
152 152
153 153 :param username:
154 154 :param attrs:
155 155 """
156 156 if self.get_by_username(username, case_insensitive=True) is None:
157 157
158 158 # autogenerate email for container account without one
159 159 generate_email = lambda usr: '%s@container_auth.account' % usr
160 160
161 161 try:
162 162 new_user = User()
163 163 new_user.username = username
164 164 new_user.password = None
165 165 new_user.api_key = generate_api_key(username)
166 166 new_user.email = attrs['email']
167 167 new_user.active = attrs.get('active', True)
168 168 new_user.name = attrs['name'] or generate_email(username)
169 169 new_user.lastname = attrs['lastname']
170 170
171 171 self.sa.add(new_user)
172 172 return new_user
173 173 except (DatabaseError,):
174 174 log.error(traceback.format_exc())
175 175 self.sa.rollback()
176 176 raise
177 177 log.debug('User %s already exists. Skipping creation of account'
178 178 ' for container auth.', username)
179 179 return None
180 180
181 181 def create_ldap(self, username, password, user_dn, attrs):
182 182 """
183 183 Checks if user is in database, if not creates this user marked
184 184 as ldap user
185 185
186 186 :param username:
187 187 :param password:
188 188 :param user_dn:
189 189 :param attrs:
190 190 """
191 191 from rhodecode.lib.auth import get_crypt_password
192 192 log.debug('Checking for such ldap account in RhodeCode database')
193 193 if self.get_by_username(username, case_insensitive=True) is None:
194 194
195 195 # autogenerate email for ldap account without one
196 196 generate_email = lambda usr: '%s@ldap.account' % usr
197 197
198 198 try:
199 199 new_user = User()
200 200 username = username.lower()
201 201 # add ldap account always lowercase
202 202 new_user.username = username
203 203 new_user.password = get_crypt_password(password)
204 204 new_user.api_key = generate_api_key(username)
205 205 new_user.email = attrs['email'] or generate_email(username)
206 206 new_user.active = attrs.get('active', True)
207 207 new_user.ldap_dn = safe_unicode(user_dn)
208 208 new_user.name = attrs['name']
209 209 new_user.lastname = attrs['lastname']
210 210
211 211 self.sa.add(new_user)
212 212 return new_user
213 213 except (DatabaseError,):
214 214 log.error(traceback.format_exc())
215 215 self.sa.rollback()
216 216 raise
217 217 log.debug('this %s user exists skipping creation of ldap account',
218 218 username)
219 219 return None
220 220
221 221 def create_registration(self, form_data):
222 222 from rhodecode.model.notification import NotificationModel
223 223
224 224 try:
225 225 form_data['admin'] = False
226 226 new_user = self.create(form_data)
227 227
228 228 self.sa.add(new_user)
229 229 self.sa.flush()
230 230
231 231 # notification to admins
232 232 subject = _('new user registration')
233 233 body = ('New user registration\n'
234 234 '---------------------\n'
235 235 '- Username: %s\n'
236 236 '- Full Name: %s\n'
237 237 '- Email: %s\n')
238 238 body = body % (new_user.username, new_user.full_name,
239 239 new_user.email)
240 240 edit_url = url('edit_user', id=new_user.user_id, qualified=True)
241 241 kw = {'registered_user_url': edit_url}
242 242 NotificationModel().create(created_by=new_user, subject=subject,
243 243 body=body, recipients=None,
244 244 type_=Notification.TYPE_REGISTRATION,
245 245 email_kwargs=kw)
246 246
247 247 except:
248 248 log.error(traceback.format_exc())
249 249 raise
250 250
251 251 def update(self, user_id, form_data, skip_attrs=[]):
252 252 from rhodecode.lib.auth import get_crypt_password
253 253 try:
254 254 user = self.get(user_id, cache=False)
255 255 if user.username == 'default':
256 256 raise DefaultUserException(
257 257 _("You can't Edit this user since it's"
258 258 " crucial for entire application"))
259 259
260 260 for k, v in form_data.items():
261 261 if k in skip_attrs:
262 262 continue
263 263 if k == 'new_password' and v:
264 264 user.password = get_crypt_password(v)
265 265 user.api_key = generate_api_key(user.username)
266 266 else:
267 267 if k == 'firstname':
268 268 k = 'name'
269 269 setattr(user, k, v)
270 270 self.sa.add(user)
271 271 except:
272 272 log.error(traceback.format_exc())
273 273 raise
274 274
275 275 def update_user(self, user, **kwargs):
276 276 from rhodecode.lib.auth import get_crypt_password
277 277 try:
278 278 user = self._get_user(user)
279 279 if user.username == 'default':
280 280 raise DefaultUserException(
281 281 _("You can't Edit this user since it's"
282 282 " crucial for entire application")
283 283 )
284 284
285 285 for k, v in kwargs.items():
286 286 if k == 'password' and v:
287 287 v = get_crypt_password(v)
288 288 user.api_key = generate_api_key(user.username)
289 289
290 290 setattr(user, k, v)
291 291 self.sa.add(user)
292 292 return user
293 293 except:
294 294 log.error(traceback.format_exc())
295 295 raise
296 296
297 297 def delete(self, user):
298 298 user = self._get_user(user)
299 299
300 300 try:
301 301 if user.username == 'default':
302 302 raise DefaultUserException(
303 303 _(u"You can't remove this user since it's"
304 304 " crucial for entire application")
305 305 )
306 306 if user.repositories:
307 307 repos = [x.repo_name for x in user.repositories]
308 308 raise UserOwnsReposException(
309 309 _(u'user "%s" still owns %s repositories and cannot be '
310 310 'removed. Switch owners or remove those repositories. %s')
311 311 % (user.username, len(repos), ', '.join(repos))
312 312 )
313 313 self.sa.delete(user)
314 314 except:
315 315 log.error(traceback.format_exc())
316 316 raise
317 317
318 318 def reset_password_link(self, data):
319 319 from rhodecode.lib.celerylib import tasks, run_task
320 320 from rhodecode.model.notification import EmailNotificationModel
321 321 user_email = data['email']
322 322 try:
323 323 user = User.get_by_email(user_email)
324 324 if user:
325 325 log.debug('password reset user found %s' % user)
326 326 link = url('reset_password_confirmation', key=user.api_key,
327 327 qualified=True)
328 328 reg_type = EmailNotificationModel.TYPE_PASSWORD_RESET
329 329 body = EmailNotificationModel().get_email_tmpl(reg_type,
330 330 **{'user': user.short_contact,
331 331 'reset_url': link})
332 332 log.debug('sending email')
333 333 run_task(tasks.send_email, user_email,
334 334 _("password reset link"), body, body)
335 335 log.info('send new password mail to %s' % user_email)
336 336 else:
337 337 log.debug("password reset email %s not found" % user_email)
338 338 except:
339 339 log.error(traceback.format_exc())
340 340 return False
341 341
342 342 return True
343 343
344 344 def reset_password(self, data):
345 345 from rhodecode.lib.celerylib import tasks, run_task
346 346 from rhodecode.lib import auth
347 347 user_email = data['email']
348 348 try:
349 349 try:
350 350 user = User.get_by_email(user_email)
351 351 new_passwd = auth.PasswordGenerator().gen_password(8,
352 352 auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
353 353 if user:
354 354 user.password = auth.get_crypt_password(new_passwd)
355 355 user.api_key = auth.generate_api_key(user.username)
356 356 Session().add(user)
357 357 Session().commit()
358 358 log.info('change password for %s' % user_email)
359 359 if new_passwd is None:
360 360 raise Exception('unable to generate new password')
361 361 except:
362 362 log.error(traceback.format_exc())
363 363 Session().rollback()
364 364
365 365 run_task(tasks.send_email, user_email,
366 366 _('Your new password'),
367 367 _('Your new RhodeCode password:%s') % (new_passwd))
368 368 log.info('send new password mail to %s' % user_email)
369 369
370 370 except:
371 371 log.error('Failed to update user password')
372 372 log.error(traceback.format_exc())
373 373
374 374 return True
375 375
376 376 def fill_data(self, auth_user, user_id=None, api_key=None):
377 377 """
378 378 Fetches auth_user by user_id,or api_key if present.
379 379 Fills auth_user attributes with those taken from database.
380 380 Additionally set's is_authenitated if lookup fails
381 381 present in database
382 382
383 383 :param auth_user: instance of user to set attributes
384 384 :param user_id: user id to fetch by
385 385 :param api_key: api key to fetch by
386 386 """
387 387 if user_id is None and api_key is None:
388 388 raise Exception('You need to pass user_id or api_key')
389 389
390 390 try:
391 391 if api_key:
392 392 dbuser = self.get_by_api_key(api_key)
393 393 else:
394 394 dbuser = self.get(user_id)
395 395
396 396 if dbuser is not None and dbuser.active:
397 397 log.debug('filling %s data' % dbuser)
398 398 for k, v in dbuser.get_dict().items():
399 399 setattr(auth_user, k, v)
400 400 else:
401 401 return False
402 402
403 403 except:
404 404 log.error(traceback.format_exc())
405 405 auth_user.is_authenticated = False
406 406 return False
407 407
408 408 return True
409 409
410 410 def fill_perms(self, user, explicit=True, algo='higherwin'):
411 411 """
412 412 Fills user permission attribute with permissions taken from database
413 413 works for permissions given for repositories, and for permissions that
414 414 are granted to groups
415 415
416 416 :param user: user instance to fill his perms
417 417 :param explicit: In case there are permissions both for user and a group
418 418 that user is part of, explicit flag will defiine if user will
419 419 explicitly override permissions from group, if it's False it will
420 420 make decision based on the algo
421 421 :param algo: algorithm to decide what permission should be choose if
422 422 it's multiple defined, eg user in two different groups. It also
423 423 decides if explicit flag is turned off how to specify the permission
424 424 for case when user is in a group + have defined separate permission
425 425 """
426 426 RK = 'repositories'
427 427 GK = 'repositories_groups'
428 428 GLOBAL = 'global'
429 429 user.permissions[RK] = {}
430 430 user.permissions[GK] = {}
431 431 user.permissions[GLOBAL] = set()
432 432
433 433 def _choose_perm(new_perm, cur_perm):
434 434 new_perm_val = PERM_WEIGHTS[new_perm]
435 435 cur_perm_val = PERM_WEIGHTS[cur_perm]
436 436 if algo == 'higherwin':
437 437 if new_perm_val > cur_perm_val:
438 438 return new_perm
439 439 return cur_perm
440 440 elif algo == 'lowerwin':
441 441 if new_perm_val < cur_perm_val:
442 442 return new_perm
443 443 return cur_perm
444 444
445 445 #======================================================================
446 446 # fetch default permissions
447 447 #======================================================================
448 448 default_user = User.get_by_username('default', cache=True)
449 449 default_user_id = default_user.user_id
450 450
451 451 default_repo_perms = Permission.get_default_perms(default_user_id)
452 452 default_repo_groups_perms = Permission.get_default_group_perms(default_user_id)
453 453
454 454 if user.is_admin:
455 455 #==================================================================
456 456 # admin user have all default rights for repositories
457 457 # and groups set to admin
458 458 #==================================================================
459 459 user.permissions[GLOBAL].add('hg.admin')
460 460
461 461 # repositories
462 462 for perm in default_repo_perms:
463 463 r_k = perm.UserRepoToPerm.repository.repo_name
464 464 p = 'repository.admin'
465 465 user.permissions[RK][r_k] = p
466 466
467 467 # repository groups
468 468 for perm in default_repo_groups_perms:
469 469 rg_k = perm.UserRepoGroupToPerm.group.group_name
470 470 p = 'group.admin'
471 471 user.permissions[GK][rg_k] = p
472 472 return user
473 473
474 474 #==================================================================
475 475 # SET DEFAULTS GLOBAL, REPOS, REPOS GROUPS
476 476 #==================================================================
477 477 uid = user.user_id
478 478
479 479 # default global permissions taken fron the default user
480 480 default_global_perms = self.sa.query(UserToPerm)\
481 481 .filter(UserToPerm.user_id == default_user_id)
482 482
483 483 for perm in default_global_perms:
484 484 user.permissions[GLOBAL].add(perm.permission.permission_name)
485 485
486 486 # defaults for repositories, taken from default user
487 487 for perm in default_repo_perms:
488 488 r_k = perm.UserRepoToPerm.repository.repo_name
489 489 if perm.Repository.private and not (perm.Repository.user_id == uid):
490 490 # disable defaults for private repos,
491 491 p = 'repository.none'
492 492 elif perm.Repository.user_id == uid:
493 493 # set admin if owner
494 494 p = 'repository.admin'
495 495 else:
496 496 p = perm.Permission.permission_name
497 497
498 498 user.permissions[RK][r_k] = p
499 499
500 500 # defaults for repository groups taken from default user permission
501 501 # on given group
502 502 for perm in default_repo_groups_perms:
503 503 rg_k = perm.UserRepoGroupToPerm.group.group_name
504 504 p = perm.Permission.permission_name
505 505 user.permissions[GK][rg_k] = p
506 506
507 507 #======================================================================
508 508 # !! OVERRIDE GLOBALS !! with user permissions if any found
509 509 #======================================================================
510 510 # those can be configured from groups or users explicitly
511 511 _configurable = set(['hg.fork.none', 'hg.fork.repository',
512 512 'hg.create.none', 'hg.create.repository'])
513 513
514 514 # USER GROUPS comes first
515 # users group global permissions
515 # user group global permissions
516 516 user_perms_from_users_groups = self.sa.query(UsersGroupToPerm)\
517 517 .options(joinedload(UsersGroupToPerm.permission))\
518 518 .join((UsersGroupMember, UsersGroupToPerm.users_group_id ==
519 519 UsersGroupMember.users_group_id))\
520 520 .filter(UsersGroupMember.user_id == uid)\
521 521 .order_by(UsersGroupToPerm.users_group_id)\
522 522 .all()
523 523 #need to group here by groups since user can be in more than one group
524 524 _grouped = [[x, list(y)] for x, y in
525 525 itertools.groupby(user_perms_from_users_groups,
526 526 lambda x:x.users_group)]
527 527 for gr, perms in _grouped:
528 528 # since user can be in multiple groups iterate over them and
529 529 # select the lowest permissions first (more explicit)
530 530 ##TODO: do this^^
531 531 if not gr.inherit_default_permissions:
532 532 # NEED TO IGNORE all configurable permissions and
533 533 # replace them with explicitly set
534 534 user.permissions[GLOBAL] = user.permissions[GLOBAL]\
535 535 .difference(_configurable)
536 536 for perm in perms:
537 537 user.permissions[GLOBAL].add(perm.permission.permission_name)
538 538
539 539 # user specific global permissions
540 540 user_perms = self.sa.query(UserToPerm)\
541 541 .options(joinedload(UserToPerm.permission))\
542 542 .filter(UserToPerm.user_id == uid).all()
543 543
544 544 if not user.inherit_default_permissions:
545 545 # NEED TO IGNORE all configurable permissions and
546 546 # replace them with explicitly set
547 547 user.permissions[GLOBAL] = user.permissions[GLOBAL]\
548 548 .difference(_configurable)
549 549
550 550 for perm in user_perms:
551 551 user.permissions[GLOBAL].add(perm.permission.permission_name)
552 552
553 553 #======================================================================
554 554 # !! PERMISSIONS FOR REPOSITORIES !!
555 555 #======================================================================
556 556 #======================================================================
557 557 # check if user is part of user groups for this repository and
558 558 # fill in his permission from it. _choose_perm decides of which
559 559 # permission should be selected based on selected method
560 560 #======================================================================
561 561
562 # users group for repositories permissions
562 # user group for repositories permissions
563 563 user_repo_perms_from_users_groups = \
564 564 self.sa.query(UsersGroupRepoToPerm, Permission, Repository,)\
565 565 .join((Repository, UsersGroupRepoToPerm.repository_id ==
566 566 Repository.repo_id))\
567 567 .join((Permission, UsersGroupRepoToPerm.permission_id ==
568 568 Permission.permission_id))\
569 569 .join((UsersGroupMember, UsersGroupRepoToPerm.users_group_id ==
570 570 UsersGroupMember.users_group_id))\
571 571 .filter(UsersGroupMember.user_id == uid)\
572 572 .all()
573 573
574 574 multiple_counter = collections.defaultdict(int)
575 575 for perm in user_repo_perms_from_users_groups:
576 576 r_k = perm.UsersGroupRepoToPerm.repository.repo_name
577 577 multiple_counter[r_k] += 1
578 578 p = perm.Permission.permission_name
579 579 cur_perm = user.permissions[RK][r_k]
580 580
581 581 if perm.Repository.user_id == uid:
582 582 # set admin if owner
583 583 p = 'repository.admin'
584 584 else:
585 585 if multiple_counter[r_k] > 1:
586 586 p = _choose_perm(p, cur_perm)
587 587 user.permissions[RK][r_k] = p
588 588
589 589 # user explicit permissions for repositories, overrides any specified
590 590 # by the group permission
591 591 user_repo_perms = \
592 592 self.sa.query(UserRepoToPerm, Permission, Repository)\
593 593 .join((Repository, UserRepoToPerm.repository_id ==
594 594 Repository.repo_id))\
595 595 .join((Permission, UserRepoToPerm.permission_id ==
596 596 Permission.permission_id))\
597 597 .filter(UserRepoToPerm.user_id == uid)\
598 598 .all()
599 599
600 600 for perm in user_repo_perms:
601 601 r_k = perm.UserRepoToPerm.repository.repo_name
602 602 cur_perm = user.permissions[RK][r_k]
603 603 # set admin if owner
604 604 if perm.Repository.user_id == uid:
605 605 p = 'repository.admin'
606 606 else:
607 607 p = perm.Permission.permission_name
608 608 if not explicit:
609 609 p = _choose_perm(p, cur_perm)
610 610 user.permissions[RK][r_k] = p
611 611
612 612 #======================================================================
613 613 # !! PERMISSIONS FOR REPOSITORY GROUPS !!
614 614 #======================================================================
615 615 #======================================================================
616 616 # check if user is part of user groups for this repository groups and
617 617 # fill in his permission from it. _choose_perm decides of which
618 618 # permission should be selected based on selected method
619 619 #======================================================================
620 # users group for repo groups permissions
620 # user group for repo groups permissions
621 621 user_repo_group_perms_from_users_groups = \
622 622 self.sa.query(UsersGroupRepoGroupToPerm, Permission, RepoGroup)\
623 623 .join((RepoGroup, UsersGroupRepoGroupToPerm.group_id == RepoGroup.group_id))\
624 624 .join((Permission, UsersGroupRepoGroupToPerm.permission_id
625 625 == Permission.permission_id))\
626 626 .join((UsersGroupMember, UsersGroupRepoGroupToPerm.users_group_id
627 627 == UsersGroupMember.users_group_id))\
628 628 .filter(UsersGroupMember.user_id == uid)\
629 629 .all()
630 630
631 631 multiple_counter = collections.defaultdict(int)
632 632 for perm in user_repo_group_perms_from_users_groups:
633 633 g_k = perm.UsersGroupRepoGroupToPerm.group.group_name
634 634 multiple_counter[g_k] += 1
635 635 p = perm.Permission.permission_name
636 636 cur_perm = user.permissions[GK][g_k]
637 637 if multiple_counter[g_k] > 1:
638 638 p = _choose_perm(p, cur_perm)
639 639 user.permissions[GK][g_k] = p
640 640
641 641 # user explicit permissions for repository groups
642 642 user_repo_groups_perms = \
643 643 self.sa.query(UserRepoGroupToPerm, Permission, RepoGroup)\
644 644 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
645 645 .join((Permission, UserRepoGroupToPerm.permission_id
646 646 == Permission.permission_id))\
647 647 .filter(UserRepoGroupToPerm.user_id == uid)\
648 648 .all()
649 649
650 650 for perm in user_repo_groups_perms:
651 651 rg_k = perm.UserRepoGroupToPerm.group.group_name
652 652 p = perm.Permission.permission_name
653 653 cur_perm = user.permissions[GK][rg_k]
654 654 if not explicit:
655 655 p = _choose_perm(p, cur_perm)
656 656 user.permissions[GK][rg_k] = p
657 657
658 658 return user
659 659
660 660 def has_perm(self, user, perm):
661 661 perm = self._get_perm(perm)
662 662 user = self._get_user(user)
663 663
664 664 return UserToPerm.query().filter(UserToPerm.user == user)\
665 665 .filter(UserToPerm.permission == perm).scalar() is not None
666 666
667 667 def grant_perm(self, user, perm):
668 668 """
669 669 Grant user global permissions
670 670
671 671 :param user:
672 672 :param perm:
673 673 """
674 674 user = self._get_user(user)
675 675 perm = self._get_perm(perm)
676 676 # if this permission is already granted skip it
677 677 _perm = UserToPerm.query()\
678 678 .filter(UserToPerm.user == user)\
679 679 .filter(UserToPerm.permission == perm)\
680 680 .scalar()
681 681 if _perm:
682 682 return
683 683 new = UserToPerm()
684 684 new.user = user
685 685 new.permission = perm
686 686 self.sa.add(new)
687 687
688 688 def revoke_perm(self, user, perm):
689 689 """
690 690 Revoke users global permissions
691 691
692 692 :param user:
693 693 :param perm:
694 694 """
695 695 user = self._get_user(user)
696 696 perm = self._get_perm(perm)
697 697
698 698 obj = UserToPerm.query()\
699 699 .filter(UserToPerm.user == user)\
700 700 .filter(UserToPerm.permission == perm)\
701 701 .scalar()
702 702 if obj:
703 703 self.sa.delete(obj)
704 704
705 705 def add_extra_email(self, user, email):
706 706 """
707 707 Adds email address to UserEmailMap
708 708
709 709 :param user:
710 710 :param email:
711 711 """
712 712 from rhodecode.model import forms
713 713 form = forms.UserExtraEmailForm()()
714 714 data = form.to_python(dict(email=email))
715 715 user = self._get_user(user)
716 716
717 717 obj = UserEmailMap()
718 718 obj.user = user
719 719 obj.email = data['email']
720 720 self.sa.add(obj)
721 721 return obj
722 722
723 723 def delete_extra_email(self, user, email_id):
724 724 """
725 725 Removes email address from UserEmailMap
726 726
727 727 :param user:
728 728 :param email_id:
729 729 """
730 730 user = self._get_user(user)
731 731 obj = UserEmailMap.query().get(email_id)
732 732 if obj:
733 733 self.sa.delete(obj)
734 734
735 735 def add_extra_ip(self, user, ip):
736 736 """
737 737 Adds ip address to UserIpMap
738 738
739 739 :param user:
740 740 :param ip:
741 741 """
742 742 from rhodecode.model import forms
743 743 form = forms.UserExtraIpForm()()
744 744 data = form.to_python(dict(ip=ip))
745 745 user = self._get_user(user)
746 746
747 747 obj = UserIpMap()
748 748 obj.user = user
749 749 obj.ip_addr = data['ip']
750 750 self.sa.add(obj)
751 751 return obj
752 752
753 753 def delete_extra_ip(self, user, ip_id):
754 754 """
755 755 Removes ip address from UserIpMap
756 756
757 757 :param user:
758 758 :param ip_id:
759 759 """
760 760 user = self._get_user(user)
761 761 obj = UserIpMap.query().get(ip_id)
762 762 if obj:
763 763 self.sa.delete(obj)
@@ -1,809 +1,809 b''
1 1 """
2 2 Set of generic validators
3 3 """
4 4 import os
5 5 import re
6 6 import formencode
7 7 import logging
8 8 from collections import defaultdict
9 9 from pylons.i18n.translation import _
10 10 from webhelpers.pylonslib.secure_form import authentication_token
11 11
12 12 from formencode.validators import (
13 13 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set,
14 14 NotEmpty, IPAddress, CIDR
15 15 )
16 16 from rhodecode.lib.compat import OrderedSet
17 17 from rhodecode.lib import ipaddr
18 18 from rhodecode.lib.utils import repo_name_slug
19 19 from rhodecode.model.db import RepoGroup, Repository, UsersGroup, User,\
20 20 ChangesetStatus
21 21 from rhodecode.lib.exceptions import LdapImportError
22 22 from rhodecode.config.routing import ADMIN_PREFIX
23 23 from rhodecode.lib.auth import HasReposGroupPermissionAny, HasPermissionAny
24 24
25 25 # silence warnings and pylint
26 26 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set, \
27 27 NotEmpty, IPAddress, CIDR
28 28
29 29 log = logging.getLogger(__name__)
30 30
31 31
32 32 class UniqueList(formencode.FancyValidator):
33 33 """
34 34 Unique List !
35 35 """
36 36 messages = dict(
37 37 empty=_('Value cannot be an empty list'),
38 38 missing_value=_('Value cannot be an empty list'),
39 39 )
40 40
41 41 def _to_python(self, value, state):
42 42 if isinstance(value, list):
43 43 return value
44 44 elif isinstance(value, set):
45 45 return list(value)
46 46 elif isinstance(value, tuple):
47 47 return list(value)
48 48 elif value is None:
49 49 return []
50 50 else:
51 51 return [value]
52 52
53 53 def empty_value(self, value):
54 54 return []
55 55
56 56
57 57 class StateObj(object):
58 58 """
59 59 this is needed to translate the messages using _() in validators
60 60 """
61 61 _ = staticmethod(_)
62 62
63 63
64 64 def M(self, key, state=None, **kwargs):
65 65 """
66 66 returns string from self.message based on given key,
67 67 passed kw params are used to substitute %(named)s params inside
68 68 translated strings
69 69
70 70 :param msg:
71 71 :param state:
72 72 """
73 73 if state is None:
74 74 state = StateObj()
75 75 else:
76 76 state._ = staticmethod(_)
77 77 #inject validator into state object
78 78 return self.message(key, state, **kwargs)
79 79
80 80
81 81 def ValidUsername(edit=False, old_data={}):
82 82 class _validator(formencode.validators.FancyValidator):
83 83 messages = {
84 84 'username_exists': _(u'Username "%(username)s" already exists'),
85 85 'system_invalid_username':
86 86 _(u'Username "%(username)s" is forbidden'),
87 87 'invalid_username':
88 88 _(u'Username may only contain alphanumeric characters '
89 89 'underscores, periods or dashes and must begin with '
90 90 'alphanumeric character')
91 91 }
92 92
93 93 def validate_python(self, value, state):
94 94 if value in ['default', 'new_user']:
95 95 msg = M(self, 'system_invalid_username', state, username=value)
96 96 raise formencode.Invalid(msg, value, state)
97 97 #check if user is unique
98 98 old_un = None
99 99 if edit:
100 100 old_un = User.get(old_data.get('user_id')).username
101 101
102 102 if old_un != value or not edit:
103 103 if User.get_by_username(value, case_insensitive=True):
104 104 msg = M(self, 'username_exists', state, username=value)
105 105 raise formencode.Invalid(msg, value, state)
106 106
107 107 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]*$', value) is None:
108 108 msg = M(self, 'invalid_username', state)
109 109 raise formencode.Invalid(msg, value, state)
110 110 return _validator
111 111
112 112
113 113 def ValidRepoUser():
114 114 class _validator(formencode.validators.FancyValidator):
115 115 messages = {
116 116 'invalid_username': _(u'Username %(username)s is not valid')
117 117 }
118 118
119 119 def validate_python(self, value, state):
120 120 try:
121 121 User.query().filter(User.active == True)\
122 122 .filter(User.username == value).one()
123 123 except Exception:
124 124 msg = M(self, 'invalid_username', state, username=value)
125 125 raise formencode.Invalid(msg, value, state,
126 126 error_dict=dict(username=msg)
127 127 )
128 128
129 129 return _validator
130 130
131 131
132 132 def ValidUsersGroup(edit=False, old_data={}):
133 133 class _validator(formencode.validators.FancyValidator):
134 134 messages = {
135 'invalid_group': _(u'Invalid users group name'),
135 'invalid_group': _(u'Invalid user group name'),
136 136 'group_exist': _(u'Users group "%(usersgroup)s" already exists'),
137 137 'invalid_usersgroup_name':
138 _(u'users group name may only contain alphanumeric '
138 _(u'user group name may only contain alphanumeric '
139 139 'characters underscores, periods or dashes and must begin '
140 140 'with alphanumeric character')
141 141 }
142 142
143 143 def validate_python(self, value, state):
144 144 if value in ['default']:
145 145 msg = M(self, 'invalid_group', state)
146 146 raise formencode.Invalid(msg, value, state,
147 147 error_dict=dict(users_group_name=msg)
148 148 )
149 149 #check if group is unique
150 150 old_ugname = None
151 151 if edit:
152 152 old_id = old_data.get('users_group_id')
153 153 old_ugname = UsersGroup.get(old_id).users_group_name
154 154
155 155 if old_ugname != value or not edit:
156 156 is_existing_group = UsersGroup.get_by_group_name(value,
157 157 case_insensitive=True)
158 158 if is_existing_group:
159 159 msg = M(self, 'group_exist', state, usersgroup=value)
160 160 raise formencode.Invalid(msg, value, state,
161 161 error_dict=dict(users_group_name=msg)
162 162 )
163 163
164 164 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
165 165 msg = M(self, 'invalid_usersgroup_name', state)
166 166 raise formencode.Invalid(msg, value, state,
167 167 error_dict=dict(users_group_name=msg)
168 168 )
169 169
170 170 return _validator
171 171
172 172
173 173 def ValidReposGroup(edit=False, old_data={}):
174 174 class _validator(formencode.validators.FancyValidator):
175 175 messages = {
176 176 'group_parent_id': _(u'Cannot assign this group as parent'),
177 177 'group_exists': _(u'Group "%(group_name)s" already exists'),
178 178 'repo_exists':
179 179 _(u'Repository with name "%(group_name)s" already exists')
180 180 }
181 181
182 182 def validate_python(self, value, state):
183 183 # TODO WRITE VALIDATIONS
184 184 group_name = value.get('group_name')
185 185 group_parent_id = value.get('group_parent_id')
186 186
187 187 # slugify repo group just in case :)
188 188 slug = repo_name_slug(group_name)
189 189
190 190 # check for parent of self
191 191 parent_of_self = lambda: (
192 192 old_data['group_id'] == int(group_parent_id)
193 193 if group_parent_id else False
194 194 )
195 195 if edit and parent_of_self():
196 196 msg = M(self, 'group_parent_id', state)
197 197 raise formencode.Invalid(msg, value, state,
198 198 error_dict=dict(group_parent_id=msg)
199 199 )
200 200
201 201 old_gname = None
202 202 if edit:
203 203 old_gname = RepoGroup.get(old_data.get('group_id')).group_name
204 204
205 205 if old_gname != group_name or not edit:
206 206
207 207 # check group
208 208 gr = RepoGroup.query()\
209 209 .filter(RepoGroup.group_name == slug)\
210 210 .filter(RepoGroup.group_parent_id == group_parent_id)\
211 211 .scalar()
212 212
213 213 if gr:
214 214 msg = M(self, 'group_exists', state, group_name=slug)
215 215 raise formencode.Invalid(msg, value, state,
216 216 error_dict=dict(group_name=msg)
217 217 )
218 218
219 219 # check for same repo
220 220 repo = Repository.query()\
221 221 .filter(Repository.repo_name == slug)\
222 222 .scalar()
223 223
224 224 if repo:
225 225 msg = M(self, 'repo_exists', state, group_name=slug)
226 226 raise formencode.Invalid(msg, value, state,
227 227 error_dict=dict(group_name=msg)
228 228 )
229 229
230 230 return _validator
231 231
232 232
233 233 def ValidPassword():
234 234 class _validator(formencode.validators.FancyValidator):
235 235 messages = {
236 236 'invalid_password':
237 237 _(u'Invalid characters (non-ascii) in password')
238 238 }
239 239
240 240 def validate_python(self, value, state):
241 241 try:
242 242 (value or '').decode('ascii')
243 243 except UnicodeError:
244 244 msg = M(self, 'invalid_password', state)
245 245 raise formencode.Invalid(msg, value, state,)
246 246 return _validator
247 247
248 248
249 249 def ValidPasswordsMatch():
250 250 class _validator(formencode.validators.FancyValidator):
251 251 messages = {
252 252 'password_mismatch': _(u'Passwords do not match'),
253 253 }
254 254
255 255 def validate_python(self, value, state):
256 256
257 257 pass_val = value.get('password') or value.get('new_password')
258 258 if pass_val != value['password_confirmation']:
259 259 msg = M(self, 'password_mismatch', state)
260 260 raise formencode.Invalid(msg, value, state,
261 261 error_dict=dict(password_confirmation=msg)
262 262 )
263 263 return _validator
264 264
265 265
266 266 def ValidAuth():
267 267 class _validator(formencode.validators.FancyValidator):
268 268 messages = {
269 269 'invalid_password': _(u'invalid password'),
270 270 'invalid_username': _(u'invalid user name'),
271 271 'disabled_account': _(u'Your account is disabled')
272 272 }
273 273
274 274 def validate_python(self, value, state):
275 275 from rhodecode.lib.auth import authenticate
276 276
277 277 password = value['password']
278 278 username = value['username']
279 279
280 280 if not authenticate(username, password):
281 281 user = User.get_by_username(username)
282 282 if user and user.active is False:
283 283 log.warning('user %s is disabled' % username)
284 284 msg = M(self, 'disabled_account', state)
285 285 raise formencode.Invalid(msg, value, state,
286 286 error_dict=dict(username=msg)
287 287 )
288 288 else:
289 289 log.warning('user %s failed to authenticate' % username)
290 290 msg = M(self, 'invalid_username', state)
291 291 msg2 = M(self, 'invalid_password', state)
292 292 raise formencode.Invalid(msg, value, state,
293 293 error_dict=dict(username=msg, password=msg2)
294 294 )
295 295 return _validator
296 296
297 297
298 298 def ValidAuthToken():
299 299 class _validator(formencode.validators.FancyValidator):
300 300 messages = {
301 301 'invalid_token': _(u'Token mismatch')
302 302 }
303 303
304 304 def validate_python(self, value, state):
305 305 if value != authentication_token():
306 306 msg = M(self, 'invalid_token', state)
307 307 raise formencode.Invalid(msg, value, state)
308 308 return _validator
309 309
310 310
311 311 def ValidRepoName(edit=False, old_data={}):
312 312 class _validator(formencode.validators.FancyValidator):
313 313 messages = {
314 314 'invalid_repo_name':
315 315 _(u'Repository name %(repo)s is disallowed'),
316 316 'repository_exists':
317 317 _(u'Repository named %(repo)s already exists'),
318 318 'repository_in_group_exists': _(u'Repository "%(repo)s" already '
319 319 'exists in group "%(group)s"'),
320 320 'same_group_exists': _(u'Repositories group with name "%(repo)s" '
321 321 'already exists')
322 322 }
323 323
324 324 def _to_python(self, value, state):
325 325 repo_name = repo_name_slug(value.get('repo_name', ''))
326 326 repo_group = value.get('repo_group')
327 327 if repo_group:
328 328 gr = RepoGroup.get(repo_group)
329 329 group_path = gr.full_path
330 330 group_name = gr.group_name
331 331 # value needs to be aware of group name in order to check
332 332 # db key This is an actual just the name to store in the
333 333 # database
334 334 repo_name_full = group_path + RepoGroup.url_sep() + repo_name
335 335 else:
336 336 group_name = group_path = ''
337 337 repo_name_full = repo_name
338 338
339 339 value['repo_name'] = repo_name
340 340 value['repo_name_full'] = repo_name_full
341 341 value['group_path'] = group_path
342 342 value['group_name'] = group_name
343 343 return value
344 344
345 345 def validate_python(self, value, state):
346 346
347 347 repo_name = value.get('repo_name')
348 348 repo_name_full = value.get('repo_name_full')
349 349 group_path = value.get('group_path')
350 350 group_name = value.get('group_name')
351 351
352 352 if repo_name in [ADMIN_PREFIX, '']:
353 353 msg = M(self, 'invalid_repo_name', state, repo=repo_name)
354 354 raise formencode.Invalid(msg, value, state,
355 355 error_dict=dict(repo_name=msg)
356 356 )
357 357
358 358 rename = old_data.get('repo_name') != repo_name_full
359 359 create = not edit
360 360 if rename or create:
361 361
362 362 if group_path != '':
363 363 if Repository.get_by_repo_name(repo_name_full):
364 364 msg = M(self, 'repository_in_group_exists', state,
365 365 repo=repo_name, group=group_name)
366 366 raise formencode.Invalid(msg, value, state,
367 367 error_dict=dict(repo_name=msg)
368 368 )
369 369 elif RepoGroup.get_by_group_name(repo_name_full):
370 370 msg = M(self, 'same_group_exists', state,
371 371 repo=repo_name)
372 372 raise formencode.Invalid(msg, value, state,
373 373 error_dict=dict(repo_name=msg)
374 374 )
375 375
376 376 elif Repository.get_by_repo_name(repo_name_full):
377 377 msg = M(self, 'repository_exists', state,
378 378 repo=repo_name)
379 379 raise formencode.Invalid(msg, value, state,
380 380 error_dict=dict(repo_name=msg)
381 381 )
382 382 return value
383 383 return _validator
384 384
385 385
386 386 def ValidForkName(*args, **kwargs):
387 387 return ValidRepoName(*args, **kwargs)
388 388
389 389
390 390 def SlugifyName():
391 391 class _validator(formencode.validators.FancyValidator):
392 392
393 393 def _to_python(self, value, state):
394 394 return repo_name_slug(value)
395 395
396 396 def validate_python(self, value, state):
397 397 pass
398 398
399 399 return _validator
400 400
401 401
402 402 def ValidCloneUri():
403 403 from rhodecode.lib.utils import make_ui
404 404
405 405 def url_handler(repo_type, url, ui=None):
406 406 if repo_type == 'hg':
407 407 from rhodecode.lib.vcs.backends.hg.repository import MercurialRepository
408 408 from mercurial.httppeer import httppeer
409 409 if url.startswith('http'):
410 410 ## initially check if it's at least the proper URL
411 411 ## or does it pass basic auth
412 412 MercurialRepository._check_url(url)
413 413 httppeer(ui, url)._capabilities()
414 414 elif url.startswith('svn+http'):
415 415 from hgsubversion.svnrepo import svnremoterepo
416 416 svnremoterepo(ui, url).capabilities
417 417 elif url.startswith('git+http'):
418 418 raise NotImplementedError()
419 419
420 420 elif repo_type == 'git':
421 421 from rhodecode.lib.vcs.backends.git.repository import GitRepository
422 422 if url.startswith('http'):
423 423 ## initially check if it's at least the proper URL
424 424 ## or does it pass basic auth
425 425 GitRepository._check_url(url)
426 426 elif url.startswith('svn+http'):
427 427 raise NotImplementedError()
428 428 elif url.startswith('hg+http'):
429 429 raise NotImplementedError()
430 430
431 431 class _validator(formencode.validators.FancyValidator):
432 432 messages = {
433 433 'clone_uri': _(u'invalid clone url'),
434 434 'invalid_clone_uri': _(u'Invalid clone url, provide a '
435 435 'valid clone http(s)/svn+http(s) url')
436 436 }
437 437
438 438 def validate_python(self, value, state):
439 439 repo_type = value.get('repo_type')
440 440 url = value.get('clone_uri')
441 441
442 442 if not url:
443 443 pass
444 444 else:
445 445 try:
446 446 url_handler(repo_type, url, make_ui('db', clear_session=False))
447 447 except Exception:
448 448 log.exception('Url validation failed')
449 449 msg = M(self, 'clone_uri')
450 450 raise formencode.Invalid(msg, value, state,
451 451 error_dict=dict(clone_uri=msg)
452 452 )
453 453 return _validator
454 454
455 455
456 456 def ValidForkType(old_data={}):
457 457 class _validator(formencode.validators.FancyValidator):
458 458 messages = {
459 459 'invalid_fork_type': _(u'Fork have to be the same type as parent')
460 460 }
461 461
462 462 def validate_python(self, value, state):
463 463 if old_data['repo_type'] != value:
464 464 msg = M(self, 'invalid_fork_type', state)
465 465 raise formencode.Invalid(msg, value, state,
466 466 error_dict=dict(repo_type=msg)
467 467 )
468 468 return _validator
469 469
470 470
471 471 def CanWriteGroup():
472 472 class _validator(formencode.validators.FancyValidator):
473 473 messages = {
474 474 'permission_denied': _(u"You don't have permissions "
475 475 "to create repository in this group"),
476 476 'permission_denied_root': _(u"no permission to create repository "
477 477 "in root location")
478 478 }
479 479
480 480 def _to_python(self, value, state):
481 481 #root location
482 482 if value in [-1, "-1"]:
483 483 return None
484 484 return value
485 485
486 486 def validate_python(self, value, state):
487 487 gr = RepoGroup.get(value)
488 488 gr_name = gr.group_name if gr else None # None means ROOT location
489 489 val = HasReposGroupPermissionAny('group.write', 'group.admin')
490 490 can_create_repos = HasPermissionAny('hg.admin', 'hg.create.repository')
491 491 forbidden = not val(gr_name, 'can write into group validator')
492 492 #parent group need to be existing
493 493 if gr and forbidden:
494 494 msg = M(self, 'permission_denied', state)
495 495 raise formencode.Invalid(msg, value, state,
496 496 error_dict=dict(repo_type=msg)
497 497 )
498 498 ## check if we can write to root location !
499 499 elif gr is None and can_create_repos() is False:
500 500 msg = M(self, 'permission_denied_root', state)
501 501 raise formencode.Invalid(msg, value, state,
502 502 error_dict=dict(repo_type=msg)
503 503 )
504 504
505 505 return _validator
506 506
507 507
508 508 def CanCreateGroup(can_create_in_root=False):
509 509 class _validator(formencode.validators.FancyValidator):
510 510 messages = {
511 511 'permission_denied': _(u"You don't have permissions "
512 512 "to create a group in this location")
513 513 }
514 514
515 515 def to_python(self, value, state):
516 516 #root location
517 517 if value in [-1, "-1"]:
518 518 return None
519 519 return value
520 520
521 521 def validate_python(self, value, state):
522 522 gr = RepoGroup.get(value)
523 523 gr_name = gr.group_name if gr else None # None means ROOT location
524 524
525 525 if can_create_in_root and gr is None:
526 526 #we can create in root, we're fine no validations required
527 527 return
528 528
529 529 forbidden_in_root = gr is None and can_create_in_root is False
530 530 val = HasReposGroupPermissionAny('group.admin')
531 531 forbidden = not val(gr_name, 'can create group validator')
532 532 if forbidden_in_root or forbidden:
533 533 msg = M(self, 'permission_denied', state)
534 534 raise formencode.Invalid(msg, value, state,
535 535 error_dict=dict(group_parent_id=msg)
536 536 )
537 537
538 538 return _validator
539 539
540 540
541 541 def ValidPerms(type_='repo'):
542 542 if type_ == 'group':
543 543 EMPTY_PERM = 'group.none'
544 544 elif type_ == 'repo':
545 545 EMPTY_PERM = 'repository.none'
546 546
547 547 class _validator(formencode.validators.FancyValidator):
548 548 messages = {
549 549 'perm_new_member_name':
550 _(u'This username or users group name is not valid')
550 _(u'This username or user group name is not valid')
551 551 }
552 552
553 553 def to_python(self, value, state):
554 554 perms_update = OrderedSet()
555 555 perms_new = OrderedSet()
556 556 # build a list of permission to update and new permission to create
557 557
558 558 #CLEAN OUT ORG VALUE FROM NEW MEMBERS, and group them using
559 559 new_perms_group = defaultdict(dict)
560 560 for k, v in value.copy().iteritems():
561 561 if k.startswith('perm_new_member'):
562 562 del value[k]
563 563 _type, part = k.split('perm_new_member_')
564 564 args = part.split('_')
565 565 if len(args) == 1:
566 566 new_perms_group[args[0]]['perm'] = v
567 567 elif len(args) == 2:
568 568 _key, pos = args
569 569 new_perms_group[pos][_key] = v
570 570
571 571 # fill new permissions in order of how they were added
572 572 for k in sorted(map(int, new_perms_group.keys())):
573 573 perm_dict = new_perms_group[str(k)]
574 574 new_member = perm_dict.get('name')
575 575 new_perm = perm_dict.get('perm')
576 576 new_type = perm_dict.get('type')
577 577 if new_member and new_perm and new_type:
578 578 perms_new.add((new_member, new_perm, new_type))
579 579
580 580 for k, v in value.iteritems():
581 581 if k.startswith('u_perm_') or k.startswith('g_perm_'):
582 582 member = k[7:]
583 583 t = {'u': 'user',
584 584 'g': 'users_group'
585 585 }[k[0]]
586 586 if member == 'default':
587 587 if value.get('repo_private'):
588 588 # set none for default when updating to
589 589 # private repo
590 590 v = EMPTY_PERM
591 591 perms_update.add((member, v, t))
592 592 #always set NONE when private flag is set
593 593 if value.get('repo_private'):
594 594 perms_update.add(('default', EMPTY_PERM, 'user'))
595 595
596 596 value['perms_updates'] = list(perms_update)
597 597 value['perms_new'] = list(perms_new)
598 598
599 599 # update permissions
600 600 for k, v, t in perms_new:
601 601 try:
602 602 if t is 'user':
603 603 self.user_db = User.query()\
604 604 .filter(User.active == True)\
605 605 .filter(User.username == k).one()
606 606 if t is 'users_group':
607 607 self.user_db = UsersGroup.query()\
608 608 .filter(UsersGroup.users_group_active == True)\
609 609 .filter(UsersGroup.users_group_name == k).one()
610 610
611 611 except Exception:
612 612 log.exception('Updated permission failed')
613 613 msg = M(self, 'perm_new_member_type', state)
614 614 raise formencode.Invalid(msg, value, state,
615 615 error_dict=dict(perm_new_member_name=msg)
616 616 )
617 617 return value
618 618 return _validator
619 619
620 620
621 621 def ValidSettings():
622 622 class _validator(formencode.validators.FancyValidator):
623 623 def _to_python(self, value, state):
624 624 # settings form for users that are not admin
625 625 # can't edit certain parameters, it's extra backup if they mangle
626 626 # with forms
627 627
628 628 forbidden_params = [
629 629 'user', 'repo_type', 'repo_enable_locking',
630 630 'repo_enable_downloads', 'repo_enable_statistics'
631 631 ]
632 632
633 633 for param in forbidden_params:
634 634 if param in value:
635 635 del value[param]
636 636 return value
637 637
638 638 def validate_python(self, value, state):
639 639 pass
640 640 return _validator
641 641
642 642
643 643 def ValidPath():
644 644 class _validator(formencode.validators.FancyValidator):
645 645 messages = {
646 646 'invalid_path': _(u'This is not a valid path')
647 647 }
648 648
649 649 def validate_python(self, value, state):
650 650 if not os.path.isdir(value):
651 651 msg = M(self, 'invalid_path', state)
652 652 raise formencode.Invalid(msg, value, state,
653 653 error_dict=dict(paths_root_path=msg)
654 654 )
655 655 return _validator
656 656
657 657
658 658 def UniqSystemEmail(old_data={}):
659 659 class _validator(formencode.validators.FancyValidator):
660 660 messages = {
661 661 'email_taken': _(u'This e-mail address is already taken')
662 662 }
663 663
664 664 def _to_python(self, value, state):
665 665 return value.lower()
666 666
667 667 def validate_python(self, value, state):
668 668 if (old_data.get('email') or '').lower() != value:
669 669 user = User.get_by_email(value, case_insensitive=True)
670 670 if user:
671 671 msg = M(self, 'email_taken', state)
672 672 raise formencode.Invalid(msg, value, state,
673 673 error_dict=dict(email=msg)
674 674 )
675 675 return _validator
676 676
677 677
678 678 def ValidSystemEmail():
679 679 class _validator(formencode.validators.FancyValidator):
680 680 messages = {
681 681 'non_existing_email': _(u'e-mail "%(email)s" does not exist.')
682 682 }
683 683
684 684 def _to_python(self, value, state):
685 685 return value.lower()
686 686
687 687 def validate_python(self, value, state):
688 688 user = User.get_by_email(value, case_insensitive=True)
689 689 if user is None:
690 690 msg = M(self, 'non_existing_email', state, email=value)
691 691 raise formencode.Invalid(msg, value, state,
692 692 error_dict=dict(email=msg)
693 693 )
694 694
695 695 return _validator
696 696
697 697
698 698 def LdapLibValidator():
699 699 class _validator(formencode.validators.FancyValidator):
700 700 messages = {
701 701
702 702 }
703 703
704 704 def validate_python(self, value, state):
705 705 try:
706 706 import ldap
707 707 ldap # pyflakes silence !
708 708 except ImportError:
709 709 raise LdapImportError()
710 710
711 711 return _validator
712 712
713 713
714 714 def AttrLoginValidator():
715 715 class _validator(formencode.validators.FancyValidator):
716 716 messages = {
717 717 'invalid_cn':
718 718 _(u'The LDAP Login attribute of the CN must be specified - '
719 719 'this is the name of the attribute that is equivalent '
720 720 'to "username"')
721 721 }
722 722
723 723 def validate_python(self, value, state):
724 724 if not value or not isinstance(value, (str, unicode)):
725 725 msg = M(self, 'invalid_cn', state)
726 726 raise formencode.Invalid(msg, value, state,
727 727 error_dict=dict(ldap_attr_login=msg)
728 728 )
729 729
730 730 return _validator
731 731
732 732
733 733 def NotReviewedRevisions(repo_id):
734 734 class _validator(formencode.validators.FancyValidator):
735 735 messages = {
736 736 'rev_already_reviewed':
737 737 _(u'Revisions %(revs)s are already part of pull request '
738 738 'or have set status')
739 739 }
740 740
741 741 def validate_python(self, value, state):
742 742 # check revisions if they are not reviewed, or a part of another
743 743 # pull request
744 744 statuses = ChangesetStatus.query()\
745 745 .filter(ChangesetStatus.revision.in_(value))\
746 746 .filter(ChangesetStatus.repo_id == repo_id)\
747 747 .all()
748 748
749 749 errors = []
750 750 for cs in statuses:
751 751 if cs.pull_request_id:
752 752 errors.append(['pull_req', cs.revision[:12]])
753 753 elif cs.status:
754 754 errors.append(['status', cs.revision[:12]])
755 755
756 756 if errors:
757 757 revs = ','.join([x[1] for x in errors])
758 758 msg = M(self, 'rev_already_reviewed', state, revs=revs)
759 759 raise formencode.Invalid(msg, value, state,
760 760 error_dict=dict(revisions=revs)
761 761 )
762 762
763 763 return _validator
764 764
765 765
766 766 def ValidIp():
767 767 class _validator(CIDR):
768 768 messages = dict(
769 769 badFormat=_('Please enter a valid IPv4 or IpV6 address'),
770 770 illegalBits=_('The network size (bits) must be within the range'
771 771 ' of 0-32 (not %(bits)r)'))
772 772
773 773 def to_python(self, value, state):
774 774 v = super(_validator, self).to_python(value, state)
775 775 v = v.strip()
776 776 net = ipaddr.IPNetwork(address=v)
777 777 if isinstance(net, ipaddr.IPv4Network):
778 778 #if IPv4 doesn't end with a mask, add /32
779 779 if '/' not in value:
780 780 v += '/32'
781 781 if isinstance(net, ipaddr.IPv6Network):
782 782 #if IPv6 doesn't end with a mask, add /128
783 783 if '/' not in value:
784 784 v += '/128'
785 785 return v
786 786
787 787 def validate_python(self, value, state):
788 788 try:
789 789 addr = value.strip()
790 790 #this raises an ValueError if address is not IpV4 or IpV6
791 791 ipaddr.IPNetwork(address=addr)
792 792 except ValueError:
793 793 raise formencode.Invalid(self.message('badFormat', state),
794 794 value, state)
795 795
796 796 return _validator
797 797
798 798
799 799 def FieldKey():
800 800 class _validator(formencode.validators.FancyValidator):
801 801 messages = dict(
802 802 badFormat=_('Key name can only consist of letters, '
803 803 'underscore, dash or numbers'),)
804 804
805 805 def validate_python(self, value, state):
806 806 if not re.match('[a-zA-Z0-9_-]+$', value):
807 807 raise formencode.Invalid(self.message('badFormat', state),
808 808 value, state)
809 809 return _validator
@@ -1,78 +1,78 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Repository groups administration')} - ${c.rhodecode_name}
6 6 </%def>
7 7
8 8
9 9 <%def name="breadcrumbs_links()">
10 10 ${h.link_to(_('Admin'),h.url('admin_home'))}
11 11 &raquo;
12 ${_('repositories groups')}
12 ${_('repository groups')}
13 13 </%def>
14 14 <%def name="page_nav()">
15 15 ${self.menu('admin')}
16 16 </%def>
17 17 <%def name="main()">
18 18 <div class="box">
19 19 <!-- box / title -->
20 20 <div class="title">
21 21 ${self.breadcrumbs()}
22 22 <ul class="links">
23 23 <li>
24 24 %if h.HasPermissionAny('hg.admin')():
25 25 <span>${h.link_to(_(u'Add group'),h.url('new_repos_group'))}</span>
26 26 %endif
27 27 </li>
28 28 </ul>
29 29 </div>
30 30 <!-- end box / title -->
31 31 <div class="table">
32 32 % if c.groups:
33 33 <table class="table_disp">
34 34
35 35 <thead>
36 36 <tr>
37 37 <th class="left"><a href="#">${_('Group name')}</a></th>
38 38 <th class="left"><a href="#">${_('Description')}</a></th>
39 39 <th class="left"><a href="#">${_('Number of toplevel repositories')}</a></th>
40 40 <th class="left" colspan="2">${_('action')}</th>
41 41 </tr>
42 42 </thead>
43 43
44 44 ## REPO GROUPS
45 45
46 46 % for gr in c.groups:
47 47 <% gr_cn = gr.repositories.count() %>
48 48 <tr>
49 49 <td>
50 50 <div style="white-space: nowrap">
51 51 <img class="icon" alt="${_('Repository group')}" src="${h.url('/images/icons/database_link.png')}"/>
52 52 ${h.link_to(h.literal(' &raquo; '.join(map(h.safe_unicode,[g.name for g in gr.parents+[gr]]))), url('repos_group_home',group_name=gr.group_name))}
53 53 </div>
54 54 </td>
55 55 <td>${gr.group_description}</td>
56 56 <td><b>${gr_cn}</b></td>
57 57 <td>
58 58 <a href="${h.url('edit_repos_group',group_name=gr.group_name)}" title="${_('edit')}">
59 59 ${h.submit('edit_%s' % gr.group_name,_('edit'),class_="edit_icon action_button")}
60 60 </a>
61 61 </td>
62 62 <td>
63 63 ${h.form(url('repos_group', group_name=gr.group_name),method='delete')}
64 64 ${h.submit('remove_%s' % gr.name,_('delete'),class_="delete_icon action_button",onclick="return confirm('"+ungettext('Confirm to delete this group: %s with %s repository','Confirm to delete this group: %s with %s repositories',gr_cn) % (gr.name,gr_cn)+"');")}
65 65 ${h.end_form()}
66 66 </td>
67 67 </tr>
68 68 % endfor
69 69
70 70 </table>
71 71 % else:
72 72 ${_('There are no repository groups yet')}
73 73 % endif
74 74
75 75 </div>
76 76 </div>
77 77
78 78 </%def>
@@ -1,228 +1,228 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3
4 4 <%def name="title()">
5 ${_('Edit users group')} ${c.users_group.users_group_name} - ${c.rhodecode_name}
5 ${_('Edit user group')} ${c.users_group.users_group_name} - ${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 ${h.link_to(_('UsersGroups'),h.url('users_groups'))}
12 12 &raquo;
13 13 ${_('edit')} "${c.users_group.users_group_name}"
14 14 </%def>
15 15
16 16 <%def name="page_nav()">
17 17 ${self.menu('admin')}
18 18 </%def>
19 19
20 20 <%def name="main()">
21 21 <div class="box box-left">
22 22 <!-- box / title -->
23 23 <div class="title">
24 24 ${self.breadcrumbs()}
25 25 </div>
26 26 <!-- end box / title -->
27 27 ${h.form(url('users_group', id=c.users_group.users_group_id),method='put', id='edit_users_group')}
28 28 <div class="form">
29 29 <!-- fields -->
30 30 <div class="fields">
31 31 <div class="field">
32 32 <div class="label">
33 33 <label for="users_group_name">${_('Group name')}:</label>
34 34 </div>
35 35 <div class="input">
36 36 ${h.text('users_group_name',class_='small')}
37 37 </div>
38 38 </div>
39 39
40 40 <div class="field">
41 41 <div class="label label-checkbox">
42 42 <label for="users_group_active">${_('Active')}:</label>
43 43 </div>
44 44 <div class="checkboxes">
45 45 ${h.checkbox('users_group_active',value=True)}
46 46 </div>
47 47 </div>
48 48 <div class="field">
49 49 <div class="label">
50 50 <label for="users_group_active">${_('Members')}:</label>
51 51 </div>
52 52 <div class="select">
53 53 <table>
54 54 <tr>
55 55 <td>
56 56 <div>
57 57 <div style="float:left">
58 58 <div class="text" style="padding: 0px 0px 6px;">${_('Choosen group members')}</div>
59 59 ${h.select('users_group_members',[x[0] for x in c.group_members],c.group_members,multiple=True,size=8,style="min-width:210px")}
60 60 <div id="remove_all_elements" style="cursor:pointer;text-align:center">
61 61 ${_('Remove all elements')}
62 62 <img alt="remove" style="vertical-align:text-bottom" src="${h.url('/images/icons/arrow_right.png')}"/>
63 63 </div>
64 64 </div>
65 65 <div style="float:left;width:20px;padding-top:50px">
66 66 <img alt="add" id="add_element"
67 67 style="padding:2px;cursor:pointer"
68 68 src="${h.url('/images/icons/arrow_left.png')}"/>
69 69 <br />
70 70 <img alt="remove" id="remove_element"
71 71 style="padding:2px;cursor:pointer"
72 72 src="${h.url('/images/icons/arrow_right.png')}"/>
73 73 </div>
74 74 <div style="float:left">
75 75 <div class="text" style="padding: 0px 0px 6px;">${_('Available members')}</div>
76 76 ${h.select('available_members',[],c.available_members,multiple=True,size=8,style="min-width:210px")}
77 77 <div id="add_all_elements" style="cursor:pointer;text-align:center">
78 78 <img alt="add" style="vertical-align:text-bottom" src="${h.url('/images/icons/arrow_left.png')}"/>
79 79 ${_('Add all elements')}
80 80 </div>
81 81 </div>
82 82 </div>
83 83 </td>
84 84 </tr>
85 85 </table>
86 86 </div>
87 87
88 88 </div>
89 89 <div class="buttons">
90 90 ${h.submit('save',_('save'),class_="ui-btn large")}
91 91 </div>
92 92 </div>
93 93 </div>
94 94 ${h.end_form()}
95 95 </div>
96 96
97 97 <div class="box box-right">
98 98 <!-- box / title -->
99 99 <div class="title">
100 100 <h5>${_('Permissions')}</h5>
101 101 </div>
102 102 ${h.form(url('users_group_perm', id=c.users_group.users_group_id), method='put')}
103 103 <div class="form">
104 104 <!-- fields -->
105 105 <div class="fields">
106 106 <div class="field">
107 107 <div class="label label-checkbox">
108 108 <label for="inherit_permissions">${_('Inherit default permissions')}:</label>
109 109 </div>
110 110 <div class="checkboxes">
111 111 ${h.checkbox('inherit_default_permissions',value=True)}
112 112 </div>
113 113 <span class="help-block">${h.literal(_('Select to inherit permissions from %s settings. '
114 114 'With this selected below options does not have any action') % h.link_to('default', url('edit_permission', id='default')))}</span>
115 115 </div>
116 116 <div id="inherit_overlay" style="${'opacity:0.3' if c.users_group.inherit_default_permissions else ''}" >
117 117 <div class="field">
118 118 <div class="label label-checkbox">
119 119 <label for="create_repo_perm">${_('Create repositories')}:</label>
120 120 </div>
121 121 <div class="checkboxes">
122 122 ${h.checkbox('create_repo_perm',value=True)}
123 123 </div>
124 124 </div>
125 125 <div class="field">
126 126 <div class="label label-checkbox">
127 127 <label for="fork_repo_perm">${_('Fork repositories')}:</label>
128 128 </div>
129 129 <div class="checkboxes">
130 130 ${h.checkbox('fork_repo_perm',value=True)}
131 131 </div>
132 132 </div>
133 133 </div>
134 134 <div class="buttons">
135 135 ${h.submit('save',_('Save'),class_="ui-btn large")}
136 136 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
137 137 </div>
138 138 </div>
139 139 </div>
140 140 ${h.end_form()}
141 141 </div>
142 142
143 143 <div class="box box-right">
144 144 <!-- box / title -->
145 145 <div class="title">
146 146 <h5>${_('Group members')}</h5>
147 147 </div>
148 148
149 149 <div class="group_members_wrap">
150 150 % if c.group_members_obj:
151 151 <ul class="group_members">
152 152 %for user in c.group_members_obj:
153 153 <li>
154 154 <div class="group_member">
155 155 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(user.email,24)}"/> </div>
156 156 <div>${h.link_to(user.username, h.url('edit_user',id=user.user_id))}</div>
157 157 <div>${user.full_name}</div>
158 158 </div>
159 159 </li>
160 160 %endfor
161 161 </ul>
162 162 %else:
163 163 <span class="empty_data">${_('No members yet')}</span>
164 164 %endif
165 165 </div>
166 166 </div>
167 167
168 168 <div class="box box-left">
169 169 <!-- box / title -->
170 170 <div class="title">
171 171 <h5>${_('Permissions defined for this group')}</h5>
172 172 </div>
173 173 ## permissions overview
174 174 <div id="perms" class="table">
175 175 %for section in sorted(c.users_group.permissions.keys()):
176 176 <div class="perms_section_head">${section.replace("_"," ").capitalize()}</div>
177 177 %if not c.users_group.permissions:
178 178 <span class="empty_data">${_('No permissions set yet')}</span>
179 179 %else:
180 180 <div id='tbl_list_wrap_${section}' class="yui-skin-sam">
181 181 <table id="tbl_list_repository">
182 182 <thead>
183 183 <tr>
184 184 <th class="left">${_('Name')}</th>
185 185 <th class="left">${_('Permission')}</th>
186 186 <th class="left">${_('Edit Permission')}</th>
187 187 </thead>
188 188 <tbody>
189 189 %for k in c.users_group.permissions[section]:
190 190 <%
191 191 section_perm = c.users_group.permissions[section].get(k)
192 192 _perm = section_perm.split('.')[-1]
193 193 %>
194 194 <tr>
195 195 <td>
196 196 %if section == 'repositories':
197 197 <a href="${h.url('summary_home',repo_name=k)}">${k}</a>
198 198 %elif section == 'repositories_groups':
199 199 <a href="${h.url('repos_group_home',group_name=k)}">${k}</a>
200 200 %endif
201 201 </td>
202 202 <td>
203 203 <span class="perm_tag ${_perm}">${section_perm}</span>
204 204 </td>
205 205 <td>
206 206 %if section == 'repositories':
207 207 <a href="${h.url('edit_repo',repo_name=k,anchor='permissions_manage')}">${_('edit')}</a>
208 208 %elif section == 'repositories_groups':
209 209 <a href="${h.url('edit_repos_group',group_name=k,anchor='permissions_manage')}">${_('edit')}</a>
210 210 %else:
211 211 --
212 212 %endif
213 213 </td>
214 214 </tr>
215 215 %endfor
216 216 </tbody>
217 217 </table>
218 218 </div>
219 219 %endif
220 220 %endfor
221 221 </div>
222 222 </div>
223 223
224 224
225 225 <script type="text/javascript">
226 226 MultiSelectWidget('users_group_members','available_members','edit_users_group');
227 227 </script>
228 228 </%def>
@@ -1,1294 +1,1294 b''
1 1 from __future__ import with_statement
2 2 import random
3 3 import mock
4 4
5 5 from rhodecode.tests import *
6 6 from rhodecode.lib.compat import json
7 7 from rhodecode.lib.auth import AuthUser
8 8 from rhodecode.model.user import UserModel
9 9 from rhodecode.model.users_group import UsersGroupModel
10 10 from rhodecode.model.repo import RepoModel
11 11 from rhodecode.model.meta import Session
12 12 from rhodecode.model.scm import ScmModel
13 13 from rhodecode.model.db import Repository
14 14
15 15 API_URL = '/_admin/api'
16 16
17 17
18 18 def _build_data(apikey, method, **kw):
19 19 """
20 20 Builds API data with given random ID
21 21
22 22 :param random_id:
23 23 :type random_id:
24 24 """
25 25 random_id = random.randrange(1, 9999)
26 26 return random_id, json.dumps({
27 27 "id": random_id,
28 28 "api_key": apikey,
29 29 "method": method,
30 30 "args": kw
31 31 })
32 32
33 33 jsonify = lambda obj: json.loads(json.dumps(obj))
34 34
35 35
36 36 def crash(*args, **kwargs):
37 37 raise Exception('Total Crash !')
38 38
39 39
40 40 def api_call(test_obj, params):
41 41 response = test_obj.app.post(API_URL, content_type='application/json',
42 42 params=params)
43 43 return response
44 44
45 45
46 46 TEST_USERS_GROUP = 'test_users_group'
47 47
48 48
49 49 def make_users_group(name=TEST_USERS_GROUP):
50 50 gr = UsersGroupModel().create(name=name)
51 51 UsersGroupModel().add_user_to_group(users_group=gr,
52 52 user=TEST_USER_ADMIN_LOGIN)
53 53 Session().commit()
54 54 return gr
55 55
56 56
57 57 def destroy_users_group(name=TEST_USERS_GROUP):
58 58 UsersGroupModel().delete(users_group=name, force=True)
59 59 Session().commit()
60 60
61 61
62 62 def create_repo(repo_name, repo_type, owner=None):
63 63 # create new repo
64 64 form_data = _get_repo_create_params(
65 65 repo_name_full=repo_name,
66 66 repo_description='description %s' % repo_name,
67 67 )
68 68 cur_user = UserModel().get_by_username(owner or TEST_USER_ADMIN_LOGIN)
69 69 r = RepoModel().create(form_data, cur_user)
70 70 Session().commit()
71 71 return r
72 72
73 73
74 74 def create_fork(fork_name, fork_type, fork_of):
75 75 fork = RepoModel(Session())._get_repo(fork_of)
76 76 r = create_repo(fork_name, fork_type)
77 77 r.fork = fork
78 78 Session().add(r)
79 79 Session().commit()
80 80 return r
81 81
82 82
83 83 def destroy_repo(repo_name):
84 84 RepoModel().delete(repo_name)
85 85 Session().commit()
86 86
87 87
88 88 class BaseTestApi(object):
89 89 REPO = None
90 90 REPO_TYPE = None
91 91
92 92 @classmethod
93 93 def setUpClass(self):
94 94 self.usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
95 95 self.apikey = self.usr.api_key
96 96 self.test_user = UserModel().create_or_update(
97 97 username='test-api',
98 98 password='test',
99 99 email='test@api.rhodecode.org',
100 100 firstname='first',
101 101 lastname='last'
102 102 )
103 103 Session().commit()
104 104 self.TEST_USER_LOGIN = self.test_user.username
105 105 self.apikey_regular = self.test_user.api_key
106 106
107 107 @classmethod
108 108 def teardownClass(self):
109 109 pass
110 110
111 111 def setUp(self):
112 112 self.maxDiff = None
113 113 make_users_group()
114 114
115 115 def tearDown(self):
116 116 destroy_users_group()
117 117
118 118 def _compare_ok(self, id_, expected, given):
119 119 expected = jsonify({
120 120 'id': id_,
121 121 'error': None,
122 122 'result': expected
123 123 })
124 124 given = json.loads(given)
125 125 self.assertEqual(expected, given)
126 126
127 127 def _compare_error(self, id_, expected, given):
128 128 expected = jsonify({
129 129 'id': id_,
130 130 'error': expected,
131 131 'result': None
132 132 })
133 133 given = json.loads(given)
134 134 self.assertEqual(expected, given)
135 135
136 136 # def test_Optional(self):
137 137 # from rhodecode.controllers.api.api import Optional
138 138 # option1 = Optional(None)
139 139 # self.assertEqual('<Optional:%s>' % None, repr(option1))
140 140 #
141 141 # self.assertEqual(1, Optional.extract(Optional(1)))
142 142 # self.assertEqual('trololo', Optional.extract('trololo'))
143 143
144 144 def test_api_wrong_key(self):
145 145 id_, params = _build_data('trololo', 'get_user')
146 146 response = api_call(self, params)
147 147
148 148 expected = 'Invalid API KEY'
149 149 self._compare_error(id_, expected, given=response.body)
150 150
151 151 def test_api_missing_non_optional_param(self):
152 152 id_, params = _build_data(self.apikey, 'get_repo')
153 153 response = api_call(self, params)
154 154
155 155 expected = 'Missing non optional `repoid` arg in JSON DATA'
156 156 self._compare_error(id_, expected, given=response.body)
157 157
158 158 def test_api_missing_non_optional_param_args_null(self):
159 159 id_, params = _build_data(self.apikey, 'get_repo')
160 160 params = params.replace('"args": {}', '"args": null')
161 161 response = api_call(self, params)
162 162
163 163 expected = 'Missing non optional `repoid` arg in JSON DATA'
164 164 self._compare_error(id_, expected, given=response.body)
165 165
166 166 def test_api_missing_non_optional_param_args_bad(self):
167 167 id_, params = _build_data(self.apikey, 'get_repo')
168 168 params = params.replace('"args": {}', '"args": 1')
169 169 response = api_call(self, params)
170 170
171 171 expected = 'Missing non optional `repoid` arg in JSON DATA'
172 172 self._compare_error(id_, expected, given=response.body)
173 173
174 174 def test_api_args_is_null(self):
175 175 id_, params = _build_data(self.apikey, 'get_users',)
176 176 params = params.replace('"args": {}', '"args": null')
177 177 response = api_call(self, params)
178 178 self.assertEqual(response.status, '200 OK')
179 179
180 180 def test_api_args_is_bad(self):
181 181 id_, params = _build_data(self.apikey, 'get_users',)
182 182 params = params.replace('"args": {}', '"args": 1')
183 183 response = api_call(self, params)
184 184 self.assertEqual(response.status, '200 OK')
185 185
186 186 def test_api_get_users(self):
187 187 id_, params = _build_data(self.apikey, 'get_users',)
188 188 response = api_call(self, params)
189 189 ret_all = []
190 190 for usr in UserModel().get_all():
191 191 ret = usr.get_api_data()
192 192 ret_all.append(jsonify(ret))
193 193 expected = ret_all
194 194 self._compare_ok(id_, expected, given=response.body)
195 195
196 196 def test_api_get_user(self):
197 197 id_, params = _build_data(self.apikey, 'get_user',
198 198 userid=TEST_USER_ADMIN_LOGIN)
199 199 response = api_call(self, params)
200 200
201 201 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
202 202 ret = usr.get_api_data()
203 203 ret['permissions'] = AuthUser(usr.user_id).permissions
204 204
205 205 expected = ret
206 206 self._compare_ok(id_, expected, given=response.body)
207 207
208 208 def test_api_get_user_that_does_not_exist(self):
209 209 id_, params = _build_data(self.apikey, 'get_user',
210 210 userid='trololo')
211 211 response = api_call(self, params)
212 212
213 213 expected = "user `%s` does not exist" % 'trololo'
214 214 self._compare_error(id_, expected, given=response.body)
215 215
216 216 def test_api_get_user_without_giving_userid(self):
217 217 id_, params = _build_data(self.apikey, 'get_user')
218 218 response = api_call(self, params)
219 219
220 220 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
221 221 ret = usr.get_api_data()
222 222 ret['permissions'] = AuthUser(usr.user_id).permissions
223 223
224 224 expected = ret
225 225 self._compare_ok(id_, expected, given=response.body)
226 226
227 227 def test_api_get_user_without_giving_userid_non_admin(self):
228 228 id_, params = _build_data(self.apikey_regular, 'get_user')
229 229 response = api_call(self, params)
230 230
231 231 usr = UserModel().get_by_username(self.TEST_USER_LOGIN)
232 232 ret = usr.get_api_data()
233 233 ret['permissions'] = AuthUser(usr.user_id).permissions
234 234
235 235 expected = ret
236 236 self._compare_ok(id_, expected, given=response.body)
237 237
238 238 def test_api_get_user_with_giving_userid_non_admin(self):
239 239 id_, params = _build_data(self.apikey_regular, 'get_user',
240 240 userid=self.TEST_USER_LOGIN)
241 241 response = api_call(self, params)
242 242
243 243 expected = 'userid is not the same as your user'
244 244 self._compare_error(id_, expected, given=response.body)
245 245
246 246 def test_api_pull(self):
247 247 #TODO: issues with rhodecode_extras here.. not sure why !
248 248 pass
249 249
250 250 # repo_name = 'test_pull'
251 251 # r = create_repo(repo_name, self.REPO_TYPE)
252 252 # r.clone_uri = TEST_self.REPO
253 253 # Session.add(r)
254 254 # Session.commit()
255 255 #
256 256 # id_, params = _build_data(self.apikey, 'pull',
257 257 # repoid=repo_name,)
258 258 # response = self.app.post(API_URL, content_type='application/json',
259 259 # params=params)
260 260 #
261 261 # expected = 'Pulled from `%s`' % repo_name
262 262 # self._compare_ok(id_, expected, given=response.body)
263 263 #
264 264 # destroy_repo(repo_name)
265 265
266 266 def test_api_pull_error(self):
267 267 id_, params = _build_data(self.apikey, 'pull',
268 268 repoid=self.REPO,)
269 269 response = api_call(self, params)
270 270
271 271 expected = 'Unable to pull changes from `%s`' % self.REPO
272 272 self._compare_error(id_, expected, given=response.body)
273 273
274 274 def test_api_rescan_repos(self):
275 275 id_, params = _build_data(self.apikey, 'rescan_repos')
276 276 response = api_call(self, params)
277 277
278 278 expected = {'added': [], 'removed': []}
279 279 self._compare_ok(id_, expected, given=response.body)
280 280
281 281 @mock.patch.object(ScmModel, 'repo_scan', crash)
282 282 def test_api_rescann_error(self):
283 283 id_, params = _build_data(self.apikey, 'rescan_repos',)
284 284 response = api_call(self, params)
285 285
286 286 expected = 'Error occurred during rescan repositories action'
287 287 self._compare_error(id_, expected, given=response.body)
288 288
289 289 def test_api_invalidate_cache(self):
290 290 id_, params = _build_data(self.apikey, 'invalidate_cache',
291 291 repoid=self.REPO)
292 292 response = api_call(self, params)
293 293
294 294 expected = ("Cache for repository `%s` was invalidated: "
295 295 "invalidated cache keys: %s" % (self.REPO,
296 296 [unicode(self.REPO)]))
297 297 self._compare_ok(id_, expected, given=response.body)
298 298
299 299 @mock.patch.object(ScmModel, 'mark_for_invalidation', crash)
300 300 def test_api_invalidate_cache_error(self):
301 301 id_, params = _build_data(self.apikey, 'invalidate_cache',
302 302 repoid=self.REPO)
303 303 response = api_call(self, params)
304 304
305 305 expected = 'Error occurred during cache invalidation action'
306 306 self._compare_error(id_, expected, given=response.body)
307 307
308 308 def test_api_lock_repo_lock_aquire(self):
309 309 id_, params = _build_data(self.apikey, 'lock',
310 310 userid=TEST_USER_ADMIN_LOGIN,
311 311 repoid=self.REPO,
312 312 locked=True)
313 313 response = api_call(self, params)
314 314 expected = ('User `%s` set lock state for repo `%s` to `%s`'
315 315 % (TEST_USER_ADMIN_LOGIN, self.REPO, True))
316 316 self._compare_ok(id_, expected, given=response.body)
317 317
318 318 def test_api_lock_repo_lock_aquire_by_non_admin(self):
319 319 repo_name = 'api_delete_me'
320 320 create_repo(repo_name, self.REPO_TYPE, owner=self.TEST_USER_LOGIN)
321 321 try:
322 322 id_, params = _build_data(self.apikey_regular, 'lock',
323 323 repoid=repo_name,
324 324 locked=True)
325 325 response = api_call(self, params)
326 326 expected = ('User `%s` set lock state for repo `%s` to `%s`'
327 327 % (self.TEST_USER_LOGIN, repo_name, True))
328 328 self._compare_ok(id_, expected, given=response.body)
329 329 finally:
330 330 destroy_repo(repo_name)
331 331
332 332 def test_api_lock_repo_lock_aquire_non_admin_with_userid(self):
333 333 repo_name = 'api_delete_me'
334 334 create_repo(repo_name, self.REPO_TYPE, owner=self.TEST_USER_LOGIN)
335 335 try:
336 336 id_, params = _build_data(self.apikey_regular, 'lock',
337 337 userid=TEST_USER_ADMIN_LOGIN,
338 338 repoid=repo_name,
339 339 locked=True)
340 340 response = api_call(self, params)
341 341 expected = 'userid is not the same as your user'
342 342 self._compare_error(id_, expected, given=response.body)
343 343 finally:
344 344 destroy_repo(repo_name)
345 345
346 346 def test_api_lock_repo_lock_aquire_non_admin_not_his_repo(self):
347 347 id_, params = _build_data(self.apikey_regular, 'lock',
348 348 repoid=self.REPO,
349 349 locked=True)
350 350 response = api_call(self, params)
351 351 expected = 'repository `%s` does not exist' % (self.REPO)
352 352 self._compare_error(id_, expected, given=response.body)
353 353
354 354 def test_api_lock_repo_lock_release(self):
355 355 id_, params = _build_data(self.apikey, 'lock',
356 356 userid=TEST_USER_ADMIN_LOGIN,
357 357 repoid=self.REPO,
358 358 locked=False)
359 359 response = api_call(self, params)
360 360 expected = ('User `%s` set lock state for repo `%s` to `%s`'
361 361 % (TEST_USER_ADMIN_LOGIN, self.REPO, False))
362 362 self._compare_ok(id_, expected, given=response.body)
363 363
364 364 def test_api_lock_repo_lock_aquire_optional_userid(self):
365 365 id_, params = _build_data(self.apikey, 'lock',
366 366 repoid=self.REPO,
367 367 locked=True)
368 368 response = api_call(self, params)
369 369 expected = ('User `%s` set lock state for repo `%s` to `%s`'
370 370 % (TEST_USER_ADMIN_LOGIN, self.REPO, True))
371 371 self._compare_ok(id_, expected, given=response.body)
372 372
373 373 @mock.patch.object(Repository, 'lock', crash)
374 374 def test_api_lock_error(self):
375 375 id_, params = _build_data(self.apikey, 'lock',
376 376 userid=TEST_USER_ADMIN_LOGIN,
377 377 repoid=self.REPO,
378 378 locked=True)
379 379 response = api_call(self, params)
380 380
381 381 expected = 'Error occurred locking repository `%s`' % self.REPO
382 382 self._compare_error(id_, expected, given=response.body)
383 383
384 384 def test_api_create_existing_user(self):
385 385 id_, params = _build_data(self.apikey, 'create_user',
386 386 username=TEST_USER_ADMIN_LOGIN,
387 387 email='test@foo.com',
388 388 password='trololo')
389 389 response = api_call(self, params)
390 390
391 391 expected = "user `%s` already exist" % TEST_USER_ADMIN_LOGIN
392 392 self._compare_error(id_, expected, given=response.body)
393 393
394 394 def test_api_create_user_with_existing_email(self):
395 395 id_, params = _build_data(self.apikey, 'create_user',
396 396 username=TEST_USER_ADMIN_LOGIN + 'new',
397 397 email=TEST_USER_REGULAR_EMAIL,
398 398 password='trololo')
399 399 response = api_call(self, params)
400 400
401 401 expected = "email `%s` already exist" % TEST_USER_REGULAR_EMAIL
402 402 self._compare_error(id_, expected, given=response.body)
403 403
404 404 def test_api_create_user(self):
405 405 username = 'test_new_api_user'
406 406 email = username + "@foo.com"
407 407
408 408 id_, params = _build_data(self.apikey, 'create_user',
409 409 username=username,
410 410 email=email,
411 411 password='trololo')
412 412 response = api_call(self, params)
413 413
414 414 usr = UserModel().get_by_username(username)
415 415 ret = dict(
416 416 msg='created new user `%s`' % username,
417 417 user=jsonify(usr.get_api_data())
418 418 )
419 419
420 420 expected = ret
421 421 self._compare_ok(id_, expected, given=response.body)
422 422
423 423 UserModel().delete(usr.user_id)
424 424 Session().commit()
425 425
426 426 @mock.patch.object(UserModel, 'create_or_update', crash)
427 427 def test_api_create_user_when_exception_happened(self):
428 428
429 429 username = 'test_new_api_user'
430 430 email = username + "@foo.com"
431 431
432 432 id_, params = _build_data(self.apikey, 'create_user',
433 433 username=username,
434 434 email=email,
435 435 password='trololo')
436 436 response = api_call(self, params)
437 437 expected = 'failed to create user `%s`' % username
438 438 self._compare_error(id_, expected, given=response.body)
439 439
440 440 def test_api_delete_user(self):
441 441 usr = UserModel().create_or_update(username=u'test_user',
442 442 password=u'qweqwe',
443 443 email=u'u232@rhodecode.org',
444 444 firstname=u'u1', lastname=u'u1')
445 445 Session().commit()
446 446 username = usr.username
447 447 email = usr.email
448 448 usr_id = usr.user_id
449 449 ## DELETE THIS USER NOW
450 450
451 451 id_, params = _build_data(self.apikey, 'delete_user',
452 452 userid=username,)
453 453 response = api_call(self, params)
454 454
455 455 ret = {'msg': 'deleted user ID:%s %s' % (usr_id, username),
456 456 'user': None}
457 457 expected = ret
458 458 self._compare_ok(id_, expected, given=response.body)
459 459
460 460 @mock.patch.object(UserModel, 'delete', crash)
461 461 def test_api_delete_user_when_exception_happened(self):
462 462 usr = UserModel().create_or_update(username=u'test_user',
463 463 password=u'qweqwe',
464 464 email=u'u232@rhodecode.org',
465 465 firstname=u'u1', lastname=u'u1')
466 466 Session().commit()
467 467 username = usr.username
468 468
469 469 id_, params = _build_data(self.apikey, 'delete_user',
470 470 userid=username,)
471 471 response = api_call(self, params)
472 472 ret = 'failed to delete ID:%s %s' % (usr.user_id,
473 473 usr.username)
474 474 expected = ret
475 475 self._compare_error(id_, expected, given=response.body)
476 476
477 477 @parameterized.expand([('firstname', 'new_username'),
478 478 ('lastname', 'new_username'),
479 479 ('email', 'new_username'),
480 480 ('admin', True),
481 481 ('admin', False),
482 482 ('ldap_dn', 'test'),
483 483 ('ldap_dn', None),
484 484 ('active', False),
485 485 ('active', True),
486 486 ('password', 'newpass')
487 487 ])
488 488 def test_api_update_user(self, name, expected):
489 489 usr = UserModel().get_by_username(self.TEST_USER_LOGIN)
490 490 kw = {name: expected,
491 491 'userid': usr.user_id}
492 492 id_, params = _build_data(self.apikey, 'update_user', **kw)
493 493 response = api_call(self, params)
494 494
495 495 ret = {
496 496 'msg': 'updated user ID:%s %s' % (usr.user_id, self.TEST_USER_LOGIN),
497 497 'user': jsonify(UserModel()\
498 498 .get_by_username(self.TEST_USER_LOGIN)\
499 499 .get_api_data())
500 500 }
501 501
502 502 expected = ret
503 503 self._compare_ok(id_, expected, given=response.body)
504 504
505 505 def test_api_update_user_no_changed_params(self):
506 506 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
507 507 ret = jsonify(usr.get_api_data())
508 508 id_, params = _build_data(self.apikey, 'update_user',
509 509 userid=TEST_USER_ADMIN_LOGIN)
510 510
511 511 response = api_call(self, params)
512 512 ret = {
513 513 'msg': 'updated user ID:%s %s' % (usr.user_id, TEST_USER_ADMIN_LOGIN),
514 514 'user': ret
515 515 }
516 516 expected = ret
517 517 self._compare_ok(id_, expected, given=response.body)
518 518
519 519 def test_api_update_user_by_user_id(self):
520 520 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
521 521 ret = jsonify(usr.get_api_data())
522 522 id_, params = _build_data(self.apikey, 'update_user',
523 523 userid=usr.user_id)
524 524
525 525 response = api_call(self, params)
526 526 ret = {
527 527 'msg': 'updated user ID:%s %s' % (usr.user_id, TEST_USER_ADMIN_LOGIN),
528 528 'user': ret
529 529 }
530 530 expected = ret
531 531 self._compare_ok(id_, expected, given=response.body)
532 532
533 533 @mock.patch.object(UserModel, 'update_user', crash)
534 534 def test_api_update_user_when_exception_happens(self):
535 535 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
536 536 ret = jsonify(usr.get_api_data())
537 537 id_, params = _build_data(self.apikey, 'update_user',
538 538 userid=usr.user_id)
539 539
540 540 response = api_call(self, params)
541 541 ret = 'failed to update user `%s`' % usr.user_id
542 542
543 543 expected = ret
544 544 self._compare_error(id_, expected, given=response.body)
545 545
546 546 def test_api_get_repo(self):
547 547 new_group = 'some_new_group'
548 548 make_users_group(new_group)
549 549 RepoModel().grant_users_group_permission(repo=self.REPO,
550 550 group_name=new_group,
551 551 perm='repository.read')
552 552 Session().commit()
553 553 id_, params = _build_data(self.apikey, 'get_repo',
554 554 repoid=self.REPO)
555 555 response = api_call(self, params)
556 556
557 557 repo = RepoModel().get_by_repo_name(self.REPO)
558 558 ret = repo.get_api_data()
559 559
560 560 members = []
561 561 followers = []
562 562 for user in repo.repo_to_perm:
563 563 perm = user.permission.permission_name
564 564 user = user.user
565 565 user_data = user.get_api_data()
566 566 user_data['type'] = "user"
567 567 user_data['permission'] = perm
568 568 members.append(user_data)
569 569
570 570 for users_group in repo.users_group_to_perm:
571 571 perm = users_group.permission.permission_name
572 572 users_group = users_group.users_group
573 573 users_group_data = users_group.get_api_data()
574 574 users_group_data['type'] = "users_group"
575 575 users_group_data['permission'] = perm
576 576 members.append(users_group_data)
577 577
578 578 for user in repo.followers:
579 579 followers.append(user.user.get_api_data())
580 580
581 581 ret['members'] = members
582 582 ret['followers'] = followers
583 583
584 584 expected = ret
585 585 self._compare_ok(id_, expected, given=response.body)
586 586 destroy_users_group(new_group)
587 587
588 588 def test_api_get_repo_by_non_admin(self):
589 589 id_, params = _build_data(self.apikey, 'get_repo',
590 590 repoid=self.REPO)
591 591 response = api_call(self, params)
592 592
593 593 repo = RepoModel().get_by_repo_name(self.REPO)
594 594 ret = repo.get_api_data()
595 595
596 596 members = []
597 597 followers = []
598 598 for user in repo.repo_to_perm:
599 599 perm = user.permission.permission_name
600 600 user = user.user
601 601 user_data = user.get_api_data()
602 602 user_data['type'] = "user"
603 603 user_data['permission'] = perm
604 604 members.append(user_data)
605 605
606 606 for users_group in repo.users_group_to_perm:
607 607 perm = users_group.permission.permission_name
608 608 users_group = users_group.users_group
609 609 users_group_data = users_group.get_api_data()
610 610 users_group_data['type'] = "users_group"
611 611 users_group_data['permission'] = perm
612 612 members.append(users_group_data)
613 613
614 614 for user in repo.followers:
615 615 followers.append(user.user.get_api_data())
616 616
617 617 ret['members'] = members
618 618 ret['followers'] = followers
619 619
620 620 expected = ret
621 621 self._compare_ok(id_, expected, given=response.body)
622 622
623 623 def test_api_get_repo_by_non_admin_no_permission_to_repo(self):
624 624 RepoModel().grant_user_permission(repo=self.REPO,
625 625 user=self.TEST_USER_LOGIN,
626 626 perm='repository.none')
627 627
628 628 id_, params = _build_data(self.apikey_regular, 'get_repo',
629 629 repoid=self.REPO)
630 630 response = api_call(self, params)
631 631
632 632 expected = 'repository `%s` does not exist' % (self.REPO)
633 633 self._compare_error(id_, expected, given=response.body)
634 634
635 635 def test_api_get_repo_that_doesn_not_exist(self):
636 636 id_, params = _build_data(self.apikey, 'get_repo',
637 637 repoid='no-such-repo')
638 638 response = api_call(self, params)
639 639
640 640 ret = 'repository `%s` does not exist' % 'no-such-repo'
641 641 expected = ret
642 642 self._compare_error(id_, expected, given=response.body)
643 643
644 644 def test_api_get_repos(self):
645 645 id_, params = _build_data(self.apikey, 'get_repos')
646 646 response = api_call(self, params)
647 647
648 648 result = []
649 649 for repo in RepoModel().get_all():
650 650 result.append(repo.get_api_data())
651 651 ret = jsonify(result)
652 652
653 653 expected = ret
654 654 self._compare_ok(id_, expected, given=response.body)
655 655
656 656 def test_api_get_repos_non_admin(self):
657 657 id_, params = _build_data(self.apikey_regular, 'get_repos')
658 658 response = api_call(self, params)
659 659
660 660 result = []
661 661 for repo in RepoModel().get_all_user_repos(self.TEST_USER_LOGIN):
662 662 result.append(repo.get_api_data())
663 663 ret = jsonify(result)
664 664
665 665 expected = ret
666 666 self._compare_ok(id_, expected, given=response.body)
667 667
668 668 @parameterized.expand([('all', 'all'),
669 669 ('dirs', 'dirs'),
670 670 ('files', 'files'), ])
671 671 def test_api_get_repo_nodes(self, name, ret_type):
672 672 rev = 'tip'
673 673 path = '/'
674 674 id_, params = _build_data(self.apikey, 'get_repo_nodes',
675 675 repoid=self.REPO, revision=rev,
676 676 root_path=path,
677 677 ret_type=ret_type)
678 678 response = api_call(self, params)
679 679
680 680 # we don't the actual return types here since it's tested somewhere
681 681 # else
682 682 expected = json.loads(response.body)['result']
683 683 self._compare_ok(id_, expected, given=response.body)
684 684
685 685 def test_api_get_repo_nodes_bad_revisions(self):
686 686 rev = 'i-dont-exist'
687 687 path = '/'
688 688 id_, params = _build_data(self.apikey, 'get_repo_nodes',
689 689 repoid=self.REPO, revision=rev,
690 690 root_path=path,)
691 691 response = api_call(self, params)
692 692
693 693 expected = 'failed to get repo: `%s` nodes' % self.REPO
694 694 self._compare_error(id_, expected, given=response.body)
695 695
696 696 def test_api_get_repo_nodes_bad_path(self):
697 697 rev = 'tip'
698 698 path = '/idontexits'
699 699 id_, params = _build_data(self.apikey, 'get_repo_nodes',
700 700 repoid=self.REPO, revision=rev,
701 701 root_path=path,)
702 702 response = api_call(self, params)
703 703
704 704 expected = 'failed to get repo: `%s` nodes' % self.REPO
705 705 self._compare_error(id_, expected, given=response.body)
706 706
707 707 def test_api_get_repo_nodes_bad_ret_type(self):
708 708 rev = 'tip'
709 709 path = '/'
710 710 ret_type = 'error'
711 711 id_, params = _build_data(self.apikey, 'get_repo_nodes',
712 712 repoid=self.REPO, revision=rev,
713 713 root_path=path,
714 714 ret_type=ret_type)
715 715 response = api_call(self, params)
716 716
717 717 expected = 'ret_type must be one of %s' % (['files', 'dirs', 'all'])
718 718 self._compare_error(id_, expected, given=response.body)
719 719
720 720 def test_api_create_repo(self):
721 721 repo_name = 'api-repo'
722 722 id_, params = _build_data(self.apikey, 'create_repo',
723 723 repo_name=repo_name,
724 724 owner=TEST_USER_ADMIN_LOGIN,
725 725 repo_type='hg',
726 726 )
727 727 response = api_call(self, params)
728 728
729 729 repo = RepoModel().get_by_repo_name(repo_name)
730 730 ret = {
731 731 'msg': 'Created new repository `%s`' % repo_name,
732 732 'repo': jsonify(repo.get_api_data())
733 733 }
734 734 expected = ret
735 735 self._compare_ok(id_, expected, given=response.body)
736 736 destroy_repo(repo_name)
737 737
738 738 def test_api_create_repo_unknown_owner(self):
739 739 repo_name = 'api-repo'
740 740 owner = 'i-dont-exist'
741 741 id_, params = _build_data(self.apikey, 'create_repo',
742 742 repo_name=repo_name,
743 743 owner=owner,
744 744 repo_type='hg',
745 745 )
746 746 response = api_call(self, params)
747 747 expected = 'user `%s` does not exist' % owner
748 748 self._compare_error(id_, expected, given=response.body)
749 749
750 750 def test_api_create_repo_dont_specify_owner(self):
751 751 repo_name = 'api-repo'
752 752 owner = 'i-dont-exist'
753 753 id_, params = _build_data(self.apikey, 'create_repo',
754 754 repo_name=repo_name,
755 755 repo_type='hg',
756 756 )
757 757 response = api_call(self, params)
758 758
759 759 repo = RepoModel().get_by_repo_name(repo_name)
760 760 ret = {
761 761 'msg': 'Created new repository `%s`' % repo_name,
762 762 'repo': jsonify(repo.get_api_data())
763 763 }
764 764 expected = ret
765 765 self._compare_ok(id_, expected, given=response.body)
766 766 destroy_repo(repo_name)
767 767
768 768 def test_api_create_repo_by_non_admin(self):
769 769 repo_name = 'api-repo'
770 770 owner = 'i-dont-exist'
771 771 id_, params = _build_data(self.apikey_regular, 'create_repo',
772 772 repo_name=repo_name,
773 773 repo_type='hg',
774 774 )
775 775 response = api_call(self, params)
776 776
777 777 repo = RepoModel().get_by_repo_name(repo_name)
778 778 ret = {
779 779 'msg': 'Created new repository `%s`' % repo_name,
780 780 'repo': jsonify(repo.get_api_data())
781 781 }
782 782 expected = ret
783 783 self._compare_ok(id_, expected, given=response.body)
784 784 destroy_repo(repo_name)
785 785
786 786 def test_api_create_repo_by_non_admin_specify_owner(self):
787 787 repo_name = 'api-repo'
788 788 owner = 'i-dont-exist'
789 789 id_, params = _build_data(self.apikey_regular, 'create_repo',
790 790 repo_name=repo_name,
791 791 repo_type='hg',
792 792 owner=owner
793 793 )
794 794 response = api_call(self, params)
795 795
796 796 expected = 'Only RhodeCode admin can specify `owner` param'
797 797 self._compare_error(id_, expected, given=response.body)
798 798 destroy_repo(repo_name)
799 799
800 800 def test_api_create_repo_exists(self):
801 801 repo_name = self.REPO
802 802 id_, params = _build_data(self.apikey, 'create_repo',
803 803 repo_name=repo_name,
804 804 owner=TEST_USER_ADMIN_LOGIN,
805 805 repo_type='hg',
806 806 )
807 807 response = api_call(self, params)
808 808 expected = "repo `%s` already exist" % repo_name
809 809 self._compare_error(id_, expected, given=response.body)
810 810
811 811 @mock.patch.object(RepoModel, 'create_repo', crash)
812 812 def test_api_create_repo_exception_occurred(self):
813 813 repo_name = 'api-repo'
814 814 id_, params = _build_data(self.apikey, 'create_repo',
815 815 repo_name=repo_name,
816 816 owner=TEST_USER_ADMIN_LOGIN,
817 817 repo_type='hg',
818 818 )
819 819 response = api_call(self, params)
820 820 expected = 'failed to create repository `%s`' % repo_name
821 821 self._compare_error(id_, expected, given=response.body)
822 822
823 823 def test_api_delete_repo(self):
824 824 repo_name = 'api_delete_me'
825 825 create_repo(repo_name, self.REPO_TYPE)
826 826
827 827 id_, params = _build_data(self.apikey, 'delete_repo',
828 828 repoid=repo_name,)
829 829 response = api_call(self, params)
830 830
831 831 ret = {
832 832 'msg': 'Deleted repository `%s`' % repo_name,
833 833 'success': True
834 834 }
835 835 expected = ret
836 836 self._compare_ok(id_, expected, given=response.body)
837 837
838 838 def test_api_delete_repo_by_non_admin(self):
839 839 repo_name = 'api_delete_me'
840 840 create_repo(repo_name, self.REPO_TYPE, owner=self.TEST_USER_LOGIN)
841 841 try:
842 842 id_, params = _build_data(self.apikey_regular, 'delete_repo',
843 843 repoid=repo_name,)
844 844 response = api_call(self, params)
845 845
846 846 ret = {
847 847 'msg': 'Deleted repository `%s`' % repo_name,
848 848 'success': True
849 849 }
850 850 expected = ret
851 851 self._compare_ok(id_, expected, given=response.body)
852 852 finally:
853 853 destroy_repo(repo_name)
854 854
855 855 def test_api_delete_repo_by_non_admin_no_permission(self):
856 856 repo_name = 'api_delete_me'
857 857 create_repo(repo_name, self.REPO_TYPE)
858 858 try:
859 859 id_, params = _build_data(self.apikey_regular, 'delete_repo',
860 860 repoid=repo_name,)
861 861 response = api_call(self, params)
862 862 expected = 'repository `%s` does not exist' % (repo_name)
863 863 self._compare_error(id_, expected, given=response.body)
864 864 finally:
865 865 destroy_repo(repo_name)
866 866
867 867 def test_api_delete_repo_exception_occurred(self):
868 868 repo_name = 'api_delete_me'
869 869 create_repo(repo_name, self.REPO_TYPE)
870 870 try:
871 871 with mock.patch.object(RepoModel, 'delete', crash):
872 872 id_, params = _build_data(self.apikey, 'delete_repo',
873 873 repoid=repo_name,)
874 874 response = api_call(self, params)
875 875
876 876 expected = 'failed to delete repository `%s`' % repo_name
877 877 self._compare_error(id_, expected, given=response.body)
878 878 finally:
879 879 destroy_repo(repo_name)
880 880
881 881 def test_api_fork_repo(self):
882 882 fork_name = 'api-repo-fork'
883 883 id_, params = _build_data(self.apikey, 'fork_repo',
884 884 repoid=self.REPO,
885 885 fork_name=fork_name,
886 886 owner=TEST_USER_ADMIN_LOGIN,
887 887 )
888 888 response = api_call(self, params)
889 889
890 890 ret = {
891 891 'msg': 'Created fork of `%s` as `%s`' % (self.REPO,
892 892 fork_name),
893 893 'success': True
894 894 }
895 895 expected = ret
896 896 self._compare_ok(id_, expected, given=response.body)
897 897 destroy_repo(fork_name)
898 898
899 899 def test_api_fork_repo_non_admin(self):
900 900 fork_name = 'api-repo-fork'
901 901 id_, params = _build_data(self.apikey_regular, 'fork_repo',
902 902 repoid=self.REPO,
903 903 fork_name=fork_name,
904 904 )
905 905 response = api_call(self, params)
906 906
907 907 ret = {
908 908 'msg': 'Created fork of `%s` as `%s`' % (self.REPO,
909 909 fork_name),
910 910 'success': True
911 911 }
912 912 expected = ret
913 913 self._compare_ok(id_, expected, given=response.body)
914 914 destroy_repo(fork_name)
915 915
916 916 def test_api_fork_repo_non_admin_specify_owner(self):
917 917 fork_name = 'api-repo-fork'
918 918 id_, params = _build_data(self.apikey_regular, 'fork_repo',
919 919 repoid=self.REPO,
920 920 fork_name=fork_name,
921 921 owner=TEST_USER_ADMIN_LOGIN,
922 922 )
923 923 response = api_call(self, params)
924 924 expected = 'Only RhodeCode admin can specify `owner` param'
925 925 self._compare_error(id_, expected, given=response.body)
926 926 destroy_repo(fork_name)
927 927
928 928 def test_api_fork_repo_non_admin_no_permission_to_fork(self):
929 929 RepoModel().grant_user_permission(repo=self.REPO,
930 930 user=self.TEST_USER_LOGIN,
931 931 perm='repository.none')
932 932 fork_name = 'api-repo-fork'
933 933 id_, params = _build_data(self.apikey_regular, 'fork_repo',
934 934 repoid=self.REPO,
935 935 fork_name=fork_name,
936 936 )
937 937 response = api_call(self, params)
938 938 expected = 'repository `%s` does not exist' % (self.REPO)
939 939 self._compare_error(id_, expected, given=response.body)
940 940 destroy_repo(fork_name)
941 941
942 942 def test_api_fork_repo_unknown_owner(self):
943 943 fork_name = 'api-repo-fork'
944 944 owner = 'i-dont-exist'
945 945 id_, params = _build_data(self.apikey, 'fork_repo',
946 946 repoid=self.REPO,
947 947 fork_name=fork_name,
948 948 owner=owner,
949 949 )
950 950 response = api_call(self, params)
951 951 expected = 'user `%s` does not exist' % owner
952 952 self._compare_error(id_, expected, given=response.body)
953 953
954 954 def test_api_fork_repo_fork_exists(self):
955 955 fork_name = 'api-repo-fork'
956 956 create_fork(fork_name, self.REPO_TYPE, self.REPO)
957 957
958 958 try:
959 959 fork_name = 'api-repo-fork'
960 960
961 961 id_, params = _build_data(self.apikey, 'fork_repo',
962 962 repoid=self.REPO,
963 963 fork_name=fork_name,
964 964 owner=TEST_USER_ADMIN_LOGIN,
965 965 )
966 966 response = api_call(self, params)
967 967
968 968 expected = "fork `%s` already exist" % fork_name
969 969 self._compare_error(id_, expected, given=response.body)
970 970 finally:
971 971 destroy_repo(fork_name)
972 972
973 973 def test_api_fork_repo_repo_exists(self):
974 974 fork_name = self.REPO
975 975
976 976 id_, params = _build_data(self.apikey, 'fork_repo',
977 977 repoid=self.REPO,
978 978 fork_name=fork_name,
979 979 owner=TEST_USER_ADMIN_LOGIN,
980 980 )
981 981 response = api_call(self, params)
982 982
983 983 expected = "repo `%s` already exist" % fork_name
984 984 self._compare_error(id_, expected, given=response.body)
985 985
986 986 @mock.patch.object(RepoModel, 'create_fork', crash)
987 987 def test_api_fork_repo_exception_occurred(self):
988 988 fork_name = 'api-repo-fork'
989 989 id_, params = _build_data(self.apikey, 'fork_repo',
990 990 repoid=self.REPO,
991 991 fork_name=fork_name,
992 992 owner=TEST_USER_ADMIN_LOGIN,
993 993 )
994 994 response = api_call(self, params)
995 995
996 996 expected = 'failed to fork repository `%s` as `%s`' % (self.REPO,
997 997 fork_name)
998 998 self._compare_error(id_, expected, given=response.body)
999 999
1000 1000 def test_api_get_users_group(self):
1001 1001 id_, params = _build_data(self.apikey, 'get_users_group',
1002 1002 usersgroupid=TEST_USERS_GROUP)
1003 1003 response = api_call(self, params)
1004 1004
1005 1005 users_group = UsersGroupModel().get_group(TEST_USERS_GROUP)
1006 1006 members = []
1007 1007 for user in users_group.members:
1008 1008 user = user.user
1009 1009 members.append(user.get_api_data())
1010 1010
1011 1011 ret = users_group.get_api_data()
1012 1012 ret['members'] = members
1013 1013 expected = ret
1014 1014 self._compare_ok(id_, expected, given=response.body)
1015 1015
1016 1016 def test_api_get_users_groups(self):
1017 1017
1018 1018 make_users_group('test_users_group2')
1019 1019
1020 1020 id_, params = _build_data(self.apikey, 'get_users_groups',)
1021 1021 response = api_call(self, params)
1022 1022
1023 1023 expected = []
1024 1024 for gr_name in [TEST_USERS_GROUP, 'test_users_group2']:
1025 1025 users_group = UsersGroupModel().get_group(gr_name)
1026 1026 ret = users_group.get_api_data()
1027 1027 expected.append(ret)
1028 1028 self._compare_ok(id_, expected, given=response.body)
1029 1029
1030 1030 UsersGroupModel().delete(users_group='test_users_group2')
1031 1031 Session().commit()
1032 1032
1033 1033 def test_api_create_users_group(self):
1034 1034 group_name = 'some_new_group'
1035 1035 id_, params = _build_data(self.apikey, 'create_users_group',
1036 1036 group_name=group_name)
1037 1037 response = api_call(self, params)
1038 1038
1039 1039 ret = {
1040 'msg': 'created new users group `%s`' % group_name,
1040 'msg': 'created new user group `%s`' % group_name,
1041 1041 'users_group': jsonify(UsersGroupModel()\
1042 1042 .get_by_name(group_name)\
1043 1043 .get_api_data())
1044 1044 }
1045 1045 expected = ret
1046 1046 self._compare_ok(id_, expected, given=response.body)
1047 1047
1048 1048 destroy_users_group(group_name)
1049 1049
1050 1050 def test_api_get_users_group_that_exist(self):
1051 1051 id_, params = _build_data(self.apikey, 'create_users_group',
1052 1052 group_name=TEST_USERS_GROUP)
1053 1053 response = api_call(self, params)
1054 1054
1055 expected = "users group `%s` already exist" % TEST_USERS_GROUP
1055 expected = "user group `%s` already exist" % TEST_USERS_GROUP
1056 1056 self._compare_error(id_, expected, given=response.body)
1057 1057
1058 1058 @mock.patch.object(UsersGroupModel, 'create', crash)
1059 1059 def test_api_get_users_group_exception_occurred(self):
1060 1060 group_name = 'exception_happens'
1061 1061 id_, params = _build_data(self.apikey, 'create_users_group',
1062 1062 group_name=group_name)
1063 1063 response = api_call(self, params)
1064 1064
1065 1065 expected = 'failed to create group `%s`' % group_name
1066 1066 self._compare_error(id_, expected, given=response.body)
1067 1067
1068 1068 def test_api_add_user_to_users_group(self):
1069 1069 gr_name = 'test_group'
1070 1070 UsersGroupModel().create(gr_name)
1071 1071 Session().commit()
1072 1072 id_, params = _build_data(self.apikey, 'add_user_to_users_group',
1073 1073 usersgroupid=gr_name,
1074 1074 userid=TEST_USER_ADMIN_LOGIN)
1075 1075 response = api_call(self, params)
1076 1076
1077 1077 expected = {
1078 'msg': 'added member `%s` to users group `%s`' % (
1078 'msg': 'added member `%s` to user group `%s`' % (
1079 1079 TEST_USER_ADMIN_LOGIN, gr_name
1080 1080 ),
1081 1081 'success': True}
1082 1082 self._compare_ok(id_, expected, given=response.body)
1083 1083
1084 1084 UsersGroupModel().delete(users_group=gr_name)
1085 1085 Session().commit()
1086 1086
1087 1087 def test_api_add_user_to_users_group_that_doesnt_exist(self):
1088 1088 id_, params = _build_data(self.apikey, 'add_user_to_users_group',
1089 1089 usersgroupid='false-group',
1090 1090 userid=TEST_USER_ADMIN_LOGIN)
1091 1091 response = api_call(self, params)
1092 1092
1093 expected = 'users group `%s` does not exist' % 'false-group'
1093 expected = 'user group `%s` does not exist' % 'false-group'
1094 1094 self._compare_error(id_, expected, given=response.body)
1095 1095
1096 1096 @mock.patch.object(UsersGroupModel, 'add_user_to_group', crash)
1097 1097 def test_api_add_user_to_users_group_exception_occurred(self):
1098 1098 gr_name = 'test_group'
1099 1099 UsersGroupModel().create(gr_name)
1100 1100 Session().commit()
1101 1101 id_, params = _build_data(self.apikey, 'add_user_to_users_group',
1102 1102 usersgroupid=gr_name,
1103 1103 userid=TEST_USER_ADMIN_LOGIN)
1104 1104 response = api_call(self, params)
1105 1105
1106 expected = 'failed to add member to users group `%s`' % gr_name
1106 expected = 'failed to add member to user group `%s`' % gr_name
1107 1107 self._compare_error(id_, expected, given=response.body)
1108 1108
1109 1109 UsersGroupModel().delete(users_group=gr_name)
1110 1110 Session().commit()
1111 1111
1112 1112 def test_api_remove_user_from_users_group(self):
1113 1113 gr_name = 'test_group_3'
1114 1114 gr = UsersGroupModel().create(gr_name)
1115 1115 UsersGroupModel().add_user_to_group(gr, user=TEST_USER_ADMIN_LOGIN)
1116 1116 Session().commit()
1117 1117 id_, params = _build_data(self.apikey, 'remove_user_from_users_group',
1118 1118 usersgroupid=gr_name,
1119 1119 userid=TEST_USER_ADMIN_LOGIN)
1120 1120 response = api_call(self, params)
1121 1121
1122 1122 expected = {
1123 'msg': 'removed member `%s` from users group `%s`' % (
1123 'msg': 'removed member `%s` from user group `%s`' % (
1124 1124 TEST_USER_ADMIN_LOGIN, gr_name
1125 1125 ),
1126 1126 'success': True}
1127 1127 self._compare_ok(id_, expected, given=response.body)
1128 1128
1129 1129 UsersGroupModel().delete(users_group=gr_name)
1130 1130 Session().commit()
1131 1131
1132 1132 @mock.patch.object(UsersGroupModel, 'remove_user_from_group', crash)
1133 1133 def test_api_remove_user_from_users_group_exception_occurred(self):
1134 1134 gr_name = 'test_group_3'
1135 1135 gr = UsersGroupModel().create(gr_name)
1136 1136 UsersGroupModel().add_user_to_group(gr, user=TEST_USER_ADMIN_LOGIN)
1137 1137 Session().commit()
1138 1138 id_, params = _build_data(self.apikey, 'remove_user_from_users_group',
1139 1139 usersgroupid=gr_name,
1140 1140 userid=TEST_USER_ADMIN_LOGIN)
1141 1141 response = api_call(self, params)
1142 1142
1143 expected = 'failed to remove member from users group `%s`' % gr_name
1143 expected = 'failed to remove member from user group `%s`' % gr_name
1144 1144 self._compare_error(id_, expected, given=response.body)
1145 1145
1146 1146 UsersGroupModel().delete(users_group=gr_name)
1147 1147 Session().commit()
1148 1148
1149 1149 @parameterized.expand([('none', 'repository.none'),
1150 1150 ('read', 'repository.read'),
1151 1151 ('write', 'repository.write'),
1152 1152 ('admin', 'repository.admin')])
1153 1153 def test_api_grant_user_permission(self, name, perm):
1154 1154 id_, params = _build_data(self.apikey, 'grant_user_permission',
1155 1155 repoid=self.REPO,
1156 1156 userid=TEST_USER_ADMIN_LOGIN,
1157 1157 perm=perm)
1158 1158 response = api_call(self, params)
1159 1159
1160 1160 ret = {
1161 1161 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1162 1162 perm, TEST_USER_ADMIN_LOGIN, self.REPO
1163 1163 ),
1164 1164 'success': True
1165 1165 }
1166 1166 expected = ret
1167 1167 self._compare_ok(id_, expected, given=response.body)
1168 1168
1169 1169 def test_api_grant_user_permission_wrong_permission(self):
1170 1170 perm = 'haha.no.permission'
1171 1171 id_, params = _build_data(self.apikey, 'grant_user_permission',
1172 1172 repoid=self.REPO,
1173 1173 userid=TEST_USER_ADMIN_LOGIN,
1174 1174 perm=perm)
1175 1175 response = api_call(self, params)
1176 1176
1177 1177 expected = 'permission `%s` does not exist' % perm
1178 1178 self._compare_error(id_, expected, given=response.body)
1179 1179
1180 1180 @mock.patch.object(RepoModel, 'grant_user_permission', crash)
1181 1181 def test_api_grant_user_permission_exception_when_adding(self):
1182 1182 perm = 'repository.read'
1183 1183 id_, params = _build_data(self.apikey, 'grant_user_permission',
1184 1184 repoid=self.REPO,
1185 1185 userid=TEST_USER_ADMIN_LOGIN,
1186 1186 perm=perm)
1187 1187 response = api_call(self, params)
1188 1188
1189 1189 expected = 'failed to edit permission for user: `%s` in repo: `%s`' % (
1190 1190 TEST_USER_ADMIN_LOGIN, self.REPO
1191 1191 )
1192 1192 self._compare_error(id_, expected, given=response.body)
1193 1193
1194 1194 def test_api_revoke_user_permission(self):
1195 1195 id_, params = _build_data(self.apikey, 'revoke_user_permission',
1196 1196 repoid=self.REPO,
1197 1197 userid=TEST_USER_ADMIN_LOGIN,)
1198 1198 response = api_call(self, params)
1199 1199
1200 1200 expected = {
1201 1201 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
1202 1202 TEST_USER_ADMIN_LOGIN, self.REPO
1203 1203 ),
1204 1204 'success': True
1205 1205 }
1206 1206 self._compare_ok(id_, expected, given=response.body)
1207 1207
1208 1208 @mock.patch.object(RepoModel, 'revoke_user_permission', crash)
1209 1209 def test_api_revoke_user_permission_exception_when_adding(self):
1210 1210 id_, params = _build_data(self.apikey, 'revoke_user_permission',
1211 1211 repoid=self.REPO,
1212 1212 userid=TEST_USER_ADMIN_LOGIN,)
1213 1213 response = api_call(self, params)
1214 1214
1215 1215 expected = 'failed to edit permission for user: `%s` in repo: `%s`' % (
1216 1216 TEST_USER_ADMIN_LOGIN, self.REPO
1217 1217 )
1218 1218 self._compare_error(id_, expected, given=response.body)
1219 1219
1220 1220 @parameterized.expand([('none', 'repository.none'),
1221 1221 ('read', 'repository.read'),
1222 1222 ('write', 'repository.write'),
1223 1223 ('admin', 'repository.admin')])
1224 1224 def test_api_grant_users_group_permission(self, name, perm):
1225 1225 id_, params = _build_data(self.apikey, 'grant_users_group_permission',
1226 1226 repoid=self.REPO,
1227 1227 usersgroupid=TEST_USERS_GROUP,
1228 1228 perm=perm)
1229 1229 response = api_call(self, params)
1230 1230
1231 1231 ret = {
1232 'msg': 'Granted perm: `%s` for users group: `%s` in repo: `%s`' % (
1232 'msg': 'Granted perm: `%s` for user group: `%s` in repo: `%s`' % (
1233 1233 perm, TEST_USERS_GROUP, self.REPO
1234 1234 ),
1235 1235 'success': True
1236 1236 }
1237 1237 expected = ret
1238 1238 self._compare_ok(id_, expected, given=response.body)
1239 1239
1240 1240 def test_api_grant_users_group_permission_wrong_permission(self):
1241 1241 perm = 'haha.no.permission'
1242 1242 id_, params = _build_data(self.apikey, 'grant_users_group_permission',
1243 1243 repoid=self.REPO,
1244 1244 usersgroupid=TEST_USERS_GROUP,
1245 1245 perm=perm)
1246 1246 response = api_call(self, params)
1247 1247
1248 1248 expected = 'permission `%s` does not exist' % perm
1249 1249 self._compare_error(id_, expected, given=response.body)
1250 1250
1251 1251 @mock.patch.object(RepoModel, 'grant_users_group_permission', crash)
1252 1252 def test_api_grant_users_group_permission_exception_when_adding(self):
1253 1253 perm = 'repository.read'
1254 1254 id_, params = _build_data(self.apikey, 'grant_users_group_permission',
1255 1255 repoid=self.REPO,
1256 1256 usersgroupid=TEST_USERS_GROUP,
1257 1257 perm=perm)
1258 1258 response = api_call(self, params)
1259 1259
1260 expected = 'failed to edit permission for users group: `%s` in repo: `%s`' % (
1260 expected = 'failed to edit permission for user group: `%s` in repo: `%s`' % (
1261 1261 TEST_USERS_GROUP, self.REPO
1262 1262 )
1263 1263 self._compare_error(id_, expected, given=response.body)
1264 1264
1265 1265 def test_api_revoke_users_group_permission(self):
1266 1266 RepoModel().grant_users_group_permission(repo=self.REPO,
1267 1267 group_name=TEST_USERS_GROUP,
1268 1268 perm='repository.read')
1269 1269 Session().commit()
1270 1270 id_, params = _build_data(self.apikey, 'revoke_users_group_permission',
1271 1271 repoid=self.REPO,
1272 1272 usersgroupid=TEST_USERS_GROUP,)
1273 1273 response = api_call(self, params)
1274 1274
1275 1275 expected = {
1276 'msg': 'Revoked perm for users group: `%s` in repo: `%s`' % (
1276 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
1277 1277 TEST_USERS_GROUP, self.REPO
1278 1278 ),
1279 1279 'success': True
1280 1280 }
1281 1281 self._compare_ok(id_, expected, given=response.body)
1282 1282
1283 1283 @mock.patch.object(RepoModel, 'revoke_users_group_permission', crash)
1284 1284 def test_api_revoke_users_group_permission_exception_when_adding(self):
1285 1285
1286 1286 id_, params = _build_data(self.apikey, 'revoke_users_group_permission',
1287 1287 repoid=self.REPO,
1288 1288 usersgroupid=TEST_USERS_GROUP,)
1289 1289 response = api_call(self, params)
1290 1290
1291 expected = 'failed to edit permission for users group: `%s` in repo: `%s`' % (
1291 expected = 'failed to edit permission for user group: `%s` in repo: `%s`' % (
1292 1292 TEST_USERS_GROUP, self.REPO
1293 1293 )
1294 1294 self._compare_error(id_, expected, given=response.body)
@@ -1,224 +1,224 b''
1 1 from rhodecode.tests import *
2 2 from rhodecode.model.db import UsersGroup, UsersGroupToPerm, Permission
3 3
4 4 TEST_USERS_GROUP = 'admins_test'
5 5
6 6
7 7 class TestAdminUsersGroupsController(TestController):
8 8
9 9 def test_index(self):
10 10 response = self.app.get(url('users_groups'))
11 11 # Test response...
12 12
13 13 def test_index_as_xml(self):
14 14 response = self.app.get(url('formatted_users_groups', format='xml'))
15 15
16 16 def test_create(self):
17 17 self.log_user()
18 18 users_group_name = TEST_USERS_GROUP
19 19 response = self.app.post(url('users_groups'),
20 20 {'users_group_name': users_group_name,
21 21 'active':True})
22 22 response.follow()
23 23
24 24 self.checkSessionFlash(response,
25 'created users group %s' % TEST_USERS_GROUP)
25 'created user group %s' % TEST_USERS_GROUP)
26 26
27 27 def test_new(self):
28 28 response = self.app.get(url('new_users_group'))
29 29
30 30 def test_new_as_xml(self):
31 31 response = self.app.get(url('formatted_new_users_group', format='xml'))
32 32
33 33 def test_update(self):
34 34 response = self.app.put(url('users_group', id=1))
35 35
36 36 def test_update_browser_fakeout(self):
37 37 response = self.app.post(url('users_group', id=1),
38 38 params=dict(_method='put'))
39 39
40 40 def test_delete(self):
41 41 self.log_user()
42 42 users_group_name = TEST_USERS_GROUP + 'another'
43 43 response = self.app.post(url('users_groups'),
44 44 {'users_group_name':users_group_name,
45 45 'active':True})
46 46 response.follow()
47 47
48 48 self.checkSessionFlash(response,
49 'created users group %s' % users_group_name)
49 'created user group %s' % users_group_name)
50 50
51 51 gr = self.Session.query(UsersGroup)\
52 52 .filter(UsersGroup.users_group_name ==
53 53 users_group_name).one()
54 54
55 55 response = self.app.delete(url('users_group', id=gr.users_group_id))
56 56
57 57 gr = self.Session.query(UsersGroup)\
58 58 .filter(UsersGroup.users_group_name ==
59 59 users_group_name).scalar()
60 60
61 61 self.assertEqual(gr, None)
62 62
63 63 def test_enable_repository_read_on_group(self):
64 64 self.log_user()
65 65 users_group_name = TEST_USERS_GROUP + 'another2'
66 66 response = self.app.post(url('users_groups'),
67 67 {'users_group_name': users_group_name,
68 68 'active': True})
69 69 response.follow()
70 70
71 71 ug = UsersGroup.get_by_group_name(users_group_name)
72 72 self.checkSessionFlash(response,
73 'created users group %s' % users_group_name)
73 'created user group %s' % users_group_name)
74 74 ## ENABLE REPO CREATE ON A GROUP
75 75 response = self.app.put(url('users_group_perm', id=ug.users_group_id),
76 76 {'create_repo_perm': True})
77 77
78 78 response.follow()
79 79 ug = UsersGroup.get_by_group_name(users_group_name)
80 80 p = Permission.get_by_key('hg.create.repository')
81 81 p2 = Permission.get_by_key('hg.fork.none')
82 82 # check if user has this perms, they should be here since
83 83 # defaults are on
84 84 perms = UsersGroupToPerm.query()\
85 85 .filter(UsersGroupToPerm.users_group == ug).all()
86 86
87 87 self.assertEqual(
88 88 [[x.users_group_id, x.permission_id, ] for x in perms],
89 89 [[ug.users_group_id, p.permission_id],
90 90 [ug.users_group_id, p2.permission_id]]
91 91 )
92 92
93 93 ## DISABLE REPO CREATE ON A GROUP
94 94 response = self.app.put(url('users_group_perm', id=ug.users_group_id),
95 95 {})
96 96
97 97 response.follow()
98 98 ug = UsersGroup.get_by_group_name(users_group_name)
99 99 p = Permission.get_by_key('hg.create.none')
100 100 p2 = Permission.get_by_key('hg.fork.none')
101 101 # check if user has this perms, they should be here since
102 102 # defaults are on
103 103 perms = UsersGroupToPerm.query()\
104 104 .filter(UsersGroupToPerm.users_group == ug).all()
105 105
106 106 self.assertEqual(
107 107 sorted([[x.users_group_id, x.permission_id, ] for x in perms]),
108 108 sorted([[ug.users_group_id, p.permission_id],
109 109 [ug.users_group_id, p2.permission_id]])
110 110 )
111 111
112 112 # DELETE !
113 113 ug = UsersGroup.get_by_group_name(users_group_name)
114 114 ugid = ug.users_group_id
115 115 response = self.app.delete(url('users_group', id=ug.users_group_id))
116 116 response = response.follow()
117 117 gr = self.Session.query(UsersGroup)\
118 118 .filter(UsersGroup.users_group_name ==
119 119 users_group_name).scalar()
120 120
121 121 self.assertEqual(gr, None)
122 122 p = Permission.get_by_key('hg.create.repository')
123 123 perms = UsersGroupToPerm.query()\
124 124 .filter(UsersGroupToPerm.users_group_id == ugid).all()
125 125 perms = [[x.users_group_id,
126 126 x.permission_id, ] for x in perms]
127 127 self.assertEqual(
128 128 perms,
129 129 []
130 130 )
131 131
132 132 def test_enable_repository_fork_on_group(self):
133 133 self.log_user()
134 134 users_group_name = TEST_USERS_GROUP + 'another2'
135 135 response = self.app.post(url('users_groups'),
136 136 {'users_group_name': users_group_name,
137 137 'active': True})
138 138 response.follow()
139 139
140 140 ug = UsersGroup.get_by_group_name(users_group_name)
141 141 self.checkSessionFlash(response,
142 'created users group %s' % users_group_name)
142 'created user group %s' % users_group_name)
143 143 ## ENABLE REPO CREATE ON A GROUP
144 144 response = self.app.put(url('users_group_perm', id=ug.users_group_id),
145 145 {'fork_repo_perm': True})
146 146
147 147 response.follow()
148 148 ug = UsersGroup.get_by_group_name(users_group_name)
149 149 p = Permission.get_by_key('hg.create.none')
150 150 p2 = Permission.get_by_key('hg.fork.repository')
151 151 # check if user has this perms, they should be here since
152 152 # defaults are on
153 153 perms = UsersGroupToPerm.query()\
154 154 .filter(UsersGroupToPerm.users_group == ug).all()
155 155
156 156 self.assertEqual(
157 157 [[x.users_group_id, x.permission_id, ] for x in perms],
158 158 [[ug.users_group_id, p.permission_id],
159 159 [ug.users_group_id, p2.permission_id]]
160 160 )
161 161
162 162 ## DISABLE REPO CREATE ON A GROUP
163 163 response = self.app.put(url('users_group_perm', id=ug.users_group_id),
164 164 {})
165 165
166 166 response.follow()
167 167 ug = UsersGroup.get_by_group_name(users_group_name)
168 168 p = Permission.get_by_key('hg.create.none')
169 169 p2 = Permission.get_by_key('hg.fork.none')
170 170 # check if user has this perms, they should be here since
171 171 # defaults are on
172 172 perms = UsersGroupToPerm.query()\
173 173 .filter(UsersGroupToPerm.users_group == ug).all()
174 174
175 175 self.assertEqual(
176 176 [[x.users_group_id, x.permission_id, ] for x in perms],
177 177 [[ug.users_group_id, p.permission_id],
178 178 [ug.users_group_id, p2.permission_id]]
179 179 )
180 180
181 181 # DELETE !
182 182 ug = UsersGroup.get_by_group_name(users_group_name)
183 183 ugid = ug.users_group_id
184 184 response = self.app.delete(url('users_group', id=ug.users_group_id))
185 185 response = response.follow()
186 186 gr = self.Session.query(UsersGroup)\
187 187 .filter(UsersGroup.users_group_name ==
188 188 users_group_name).scalar()
189 189
190 190 self.assertEqual(gr, None)
191 191 p = Permission.get_by_key('hg.fork.repository')
192 192 perms = UsersGroupToPerm.query()\
193 193 .filter(UsersGroupToPerm.users_group_id == ugid).all()
194 194 perms = [[x.users_group_id,
195 195 x.permission_id, ] for x in perms]
196 196 self.assertEqual(
197 197 perms,
198 198 []
199 199 )
200 200
201 201 def test_delete_browser_fakeout(self):
202 202 response = self.app.post(url('users_group', id=1),
203 203 params=dict(_method='delete'))
204 204
205 205 def test_show(self):
206 206 response = self.app.get(url('users_group', id=1))
207 207
208 208 def test_show_as_xml(self):
209 209 response = self.app.get(url('formatted_users_group', id=1, format='xml'))
210 210
211 211 def test_edit(self):
212 212 response = self.app.get(url('edit_users_group', id=1))
213 213
214 214 def test_edit_as_xml(self):
215 215 response = self.app.get(url('formatted_edit_users_group', id=1, format='xml'))
216 216
217 217 def test_assign_members(self):
218 218 pass
219 219
220 220 def test_add_create_permission(self):
221 221 pass
222 222
223 223 def test_revoke_members(self):
224 224 pass
@@ -1,472 +1,472 b''
1 1 import os
2 2 import unittest
3 3 from rhodecode.tests import *
4 4 from rhodecode.tests.models.common import _make_group
5 5 from rhodecode.model.repos_group import ReposGroupModel
6 6 from rhodecode.model.repo import RepoModel
7 7 from rhodecode.model.db import RepoGroup, User, UsersGroupRepoGroupToPerm
8 8 from rhodecode.model.user import UserModel
9 9
10 10 from rhodecode.model.meta import Session
11 11 from rhodecode.model.users_group import UsersGroupModel
12 12 from rhodecode.lib.auth import AuthUser
13 13 from rhodecode.tests.api.api_base import create_repo
14 14
15 15
16 16 class TestPermissions(unittest.TestCase):
17 17 def __init__(self, methodName='runTest'):
18 18 super(TestPermissions, self).__init__(methodName=methodName)
19 19
20 20 def setUp(self):
21 21 self.u1 = UserModel().create_or_update(
22 22 username=u'u1', password=u'qweqwe',
23 23 email=u'u1@rhodecode.org', firstname=u'u1', lastname=u'u1'
24 24 )
25 25 self.u2 = UserModel().create_or_update(
26 26 username=u'u2', password=u'qweqwe',
27 27 email=u'u2@rhodecode.org', firstname=u'u2', lastname=u'u2'
28 28 )
29 29 self.u3 = UserModel().create_or_update(
30 30 username=u'u3', password=u'qweqwe',
31 31 email=u'u3@rhodecode.org', firstname=u'u3', lastname=u'u3'
32 32 )
33 33 self.anon = User.get_by_username('default')
34 34 self.a1 = UserModel().create_or_update(
35 35 username=u'a1', password=u'qweqwe',
36 36 email=u'a1@rhodecode.org', firstname=u'a1', lastname=u'a1', admin=True
37 37 )
38 38 Session().commit()
39 39
40 40 def tearDown(self):
41 41 if hasattr(self, 'test_repo'):
42 42 RepoModel().delete(repo=self.test_repo)
43 43
44 44 UserModel().delete(self.u1)
45 45 UserModel().delete(self.u2)
46 46 UserModel().delete(self.u3)
47 47 UserModel().delete(self.a1)
48 48 if hasattr(self, 'g1'):
49 49 ReposGroupModel().delete(self.g1.group_id)
50 50 if hasattr(self, 'g2'):
51 51 ReposGroupModel().delete(self.g2.group_id)
52 52
53 53 if hasattr(self, 'ug1'):
54 54 UsersGroupModel().delete(self.ug1, force=True)
55 55
56 56 Session().commit()
57 57
58 58 def test_default_perms_set(self):
59 59 u1_auth = AuthUser(user_id=self.u1.user_id)
60 60 perms = {
61 61 'repositories_groups': {},
62 62 'global': set([u'hg.create.repository', u'repository.read',
63 63 u'hg.register.manual_activate']),
64 64 'repositories': {u'vcs_test_hg': u'repository.read'}
65 65 }
66 66 self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
67 67 perms['repositories'][HG_REPO])
68 68 new_perm = 'repository.write'
69 69 RepoModel().grant_user_permission(repo=HG_REPO, user=self.u1,
70 70 perm=new_perm)
71 71 Session().commit()
72 72
73 73 u1_auth = AuthUser(user_id=self.u1.user_id)
74 74 self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
75 75 new_perm)
76 76
77 77 def test_default_admin_perms_set(self):
78 78 a1_auth = AuthUser(user_id=self.a1.user_id)
79 79 perms = {
80 80 'repositories_groups': {},
81 81 'global': set([u'hg.admin']),
82 82 'repositories': {u'vcs_test_hg': u'repository.admin'}
83 83 }
84 84 self.assertEqual(a1_auth.permissions['repositories'][HG_REPO],
85 85 perms['repositories'][HG_REPO])
86 86 new_perm = 'repository.write'
87 87 RepoModel().grant_user_permission(repo=HG_REPO, user=self.a1,
88 88 perm=new_perm)
89 89 Session().commit()
90 90 # cannot really downgrade admins permissions !? they still get's set as
91 91 # admin !
92 92 u1_auth = AuthUser(user_id=self.a1.user_id)
93 93 self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
94 94 perms['repositories'][HG_REPO])
95 95
96 96 def test_default_group_perms(self):
97 97 self.g1 = _make_group('test1', skip_if_exists=True)
98 98 self.g2 = _make_group('test2', skip_if_exists=True)
99 99 u1_auth = AuthUser(user_id=self.u1.user_id)
100 100 perms = {
101 101 'repositories_groups': {u'test1': 'group.read', u'test2': 'group.read'},
102 102 'global': set([u'hg.create.repository', u'repository.read', u'hg.register.manual_activate']),
103 103 'repositories': {u'vcs_test_hg': u'repository.read'}
104 104 }
105 105 self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
106 106 perms['repositories'][HG_REPO])
107 107 self.assertEqual(u1_auth.permissions['repositories_groups'],
108 108 perms['repositories_groups'])
109 109
110 110 def test_default_admin_group_perms(self):
111 111 self.g1 = _make_group('test1', skip_if_exists=True)
112 112 self.g2 = _make_group('test2', skip_if_exists=True)
113 113 a1_auth = AuthUser(user_id=self.a1.user_id)
114 114 perms = {
115 115 'repositories_groups': {u'test1': 'group.admin', u'test2': 'group.admin'},
116 116 'global': set(['hg.admin']),
117 117 'repositories': {u'vcs_test_hg': 'repository.admin'}
118 118 }
119 119
120 120 self.assertEqual(a1_auth.permissions['repositories'][HG_REPO],
121 121 perms['repositories'][HG_REPO])
122 122 self.assertEqual(a1_auth.permissions['repositories_groups'],
123 123 perms['repositories_groups'])
124 124
125 125 def test_propagated_permission_from_users_group_by_explicit_perms_exist(self):
126 126 # make group
127 127 self.ug1 = UsersGroupModel().create('G1')
128 128 # add user to group
129 129
130 130 UsersGroupModel().add_user_to_group(self.ug1, self.u1)
131 131
132 132 # set permission to lower
133 133 new_perm = 'repository.none'
134 134 RepoModel().grant_user_permission(repo=HG_REPO, user=self.u1, perm=new_perm)
135 135 Session().commit()
136 136 u1_auth = AuthUser(user_id=self.u1.user_id)
137 137 self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
138 138 new_perm)
139 139
140 140 # grant perm for group this should not override permission from user
141 141 # since it has explicitly set
142 142 new_perm_gr = 'repository.write'
143 143 RepoModel().grant_users_group_permission(repo=HG_REPO,
144 144 group_name=self.ug1,
145 145 perm=new_perm_gr)
146 146 # check perms
147 147 u1_auth = AuthUser(user_id=self.u1.user_id)
148 148 perms = {
149 149 'repositories_groups': {},
150 150 'global': set([u'hg.create.repository', u'repository.read',
151 151 u'hg.register.manual_activate']),
152 152 'repositories': {u'vcs_test_hg': u'repository.read'}
153 153 }
154 154 self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
155 155 new_perm)
156 156 self.assertEqual(u1_auth.permissions['repositories_groups'],
157 157 perms['repositories_groups'])
158 158
159 159 def test_propagated_permission_from_users_group(self):
160 160 # make group
161 161 self.ug1 = UsersGroupModel().create('G1')
162 162 # add user to group
163 163
164 164 UsersGroupModel().add_user_to_group(self.ug1, self.u3)
165 165
166 166 # grant perm for group this should override default permission from user
167 167 new_perm_gr = 'repository.write'
168 168 RepoModel().grant_users_group_permission(repo=HG_REPO,
169 169 group_name=self.ug1,
170 170 perm=new_perm_gr)
171 171 # check perms
172 172 u3_auth = AuthUser(user_id=self.u3.user_id)
173 173 perms = {
174 174 'repositories_groups': {},
175 175 'global': set([u'hg.create.repository', u'repository.read',
176 176 u'hg.register.manual_activate']),
177 177 'repositories': {u'vcs_test_hg': u'repository.read'}
178 178 }
179 179 self.assertEqual(u3_auth.permissions['repositories'][HG_REPO],
180 180 new_perm_gr)
181 181 self.assertEqual(u3_auth.permissions['repositories_groups'],
182 182 perms['repositories_groups'])
183 183
184 184 def test_propagated_permission_from_users_group_lower_weight(self):
185 185 # make group
186 186 self.ug1 = UsersGroupModel().create('G1')
187 187 # add user to group
188 188 UsersGroupModel().add_user_to_group(self.ug1, self.u1)
189 189
190 190 # set permission to lower
191 191 new_perm_h = 'repository.write'
192 192 RepoModel().grant_user_permission(repo=HG_REPO, user=self.u1,
193 193 perm=new_perm_h)
194 194 Session().commit()
195 195 u1_auth = AuthUser(user_id=self.u1.user_id)
196 196 self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
197 197 new_perm_h)
198 198
199 199 # grant perm for group this should NOT override permission from user
200 200 # since it's lower than granted
201 201 new_perm_l = 'repository.read'
202 202 RepoModel().grant_users_group_permission(repo=HG_REPO,
203 203 group_name=self.ug1,
204 204 perm=new_perm_l)
205 205 # check perms
206 206 u1_auth = AuthUser(user_id=self.u1.user_id)
207 207 perms = {
208 208 'repositories_groups': {},
209 209 'global': set([u'hg.create.repository', u'repository.read',
210 210 u'hg.register.manual_activate']),
211 211 'repositories': {u'vcs_test_hg': u'repository.write'}
212 212 }
213 213 self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
214 214 new_perm_h)
215 215 self.assertEqual(u1_auth.permissions['repositories_groups'],
216 216 perms['repositories_groups'])
217 217
218 218 def test_repo_in_group_permissions(self):
219 219 self.g1 = _make_group('group1', skip_if_exists=True)
220 220 self.g2 = _make_group('group2', skip_if_exists=True)
221 221 Session().commit()
222 222 # both perms should be read !
223 223 u1_auth = AuthUser(user_id=self.u1.user_id)
224 224 self.assertEqual(u1_auth.permissions['repositories_groups'],
225 225 {u'group1': u'group.read', u'group2': u'group.read'})
226 226
227 227 a1_auth = AuthUser(user_id=self.anon.user_id)
228 228 self.assertEqual(a1_auth.permissions['repositories_groups'],
229 229 {u'group1': u'group.read', u'group2': u'group.read'})
230 230
231 231 #Change perms to none for both groups
232 232 ReposGroupModel().grant_user_permission(repos_group=self.g1,
233 233 user=self.anon,
234 234 perm='group.none')
235 235 ReposGroupModel().grant_user_permission(repos_group=self.g2,
236 236 user=self.anon,
237 237 perm='group.none')
238 238
239 239 u1_auth = AuthUser(user_id=self.u1.user_id)
240 240 self.assertEqual(u1_auth.permissions['repositories_groups'],
241 241 {u'group1': u'group.none', u'group2': u'group.none'})
242 242
243 243 a1_auth = AuthUser(user_id=self.anon.user_id)
244 244 self.assertEqual(a1_auth.permissions['repositories_groups'],
245 245 {u'group1': u'group.none', u'group2': u'group.none'})
246 246
247 247 # add repo to group
248 248 name = RepoGroup.url_sep().join([self.g1.group_name, 'test_perm'])
249 249 self.test_repo = RepoModel().create_repo(
250 250 repo_name=name,
251 251 repo_type='hg',
252 252 description='',
253 253 repos_group=self.g1,
254 254 owner=self.u1,
255 255 )
256 256 Session().commit()
257 257
258 258 u1_auth = AuthUser(user_id=self.u1.user_id)
259 259 self.assertEqual(u1_auth.permissions['repositories_groups'],
260 260 {u'group1': u'group.none', u'group2': u'group.none'})
261 261
262 262 a1_auth = AuthUser(user_id=self.anon.user_id)
263 263 self.assertEqual(a1_auth.permissions['repositories_groups'],
264 264 {u'group1': u'group.none', u'group2': u'group.none'})
265 265
266 266 #grant permission for u2 !
267 267 ReposGroupModel().grant_user_permission(repos_group=self.g1,
268 268 user=self.u2,
269 269 perm='group.read')
270 270 ReposGroupModel().grant_user_permission(repos_group=self.g2,
271 271 user=self.u2,
272 272 perm='group.read')
273 273 Session().commit()
274 274 self.assertNotEqual(self.u1, self.u2)
275 275 #u1 and anon should have not change perms while u2 should !
276 276 u1_auth = AuthUser(user_id=self.u1.user_id)
277 277 self.assertEqual(u1_auth.permissions['repositories_groups'],
278 278 {u'group1': u'group.none', u'group2': u'group.none'})
279 279
280 280 u2_auth = AuthUser(user_id=self.u2.user_id)
281 281 self.assertEqual(u2_auth.permissions['repositories_groups'],
282 282 {u'group1': u'group.read', u'group2': u'group.read'})
283 283
284 284 a1_auth = AuthUser(user_id=self.anon.user_id)
285 285 self.assertEqual(a1_auth.permissions['repositories_groups'],
286 286 {u'group1': u'group.none', u'group2': u'group.none'})
287 287
288 288 def test_repo_group_user_as_user_group_member(self):
289 289 # create Group1
290 290 self.g1 = _make_group('group1', skip_if_exists=True)
291 291 Session().commit()
292 292 a1_auth = AuthUser(user_id=self.anon.user_id)
293 293
294 294 self.assertEqual(a1_auth.permissions['repositories_groups'],
295 295 {u'group1': u'group.read'})
296 296
297 297 # set default permission to none
298 298 ReposGroupModel().grant_user_permission(repos_group=self.g1,
299 299 user=self.anon,
300 300 perm='group.none')
301 301 # make group
302 302 self.ug1 = UsersGroupModel().create('G1')
303 303 # add user to group
304 304 UsersGroupModel().add_user_to_group(self.ug1, self.u1)
305 305 Session().commit()
306 306
307 307 # check if user is in the group
308 308 membrs = [x.user_id for x in UsersGroupModel().get(self.ug1.users_group_id).members]
309 309 self.assertEqual(membrs, [self.u1.user_id])
310 310 # add some user to that group
311 311
312 312 # check his permissions
313 313 a1_auth = AuthUser(user_id=self.anon.user_id)
314 314 self.assertEqual(a1_auth.permissions['repositories_groups'],
315 315 {u'group1': u'group.none'})
316 316
317 317 u1_auth = AuthUser(user_id=self.u1.user_id)
318 318 self.assertEqual(u1_auth.permissions['repositories_groups'],
319 319 {u'group1': u'group.none'})
320 320
321 321 # grant ug1 read permissions for
322 322 ReposGroupModel().grant_users_group_permission(repos_group=self.g1,
323 323 group_name=self.ug1,
324 324 perm='group.read')
325 325 Session().commit()
326 326 # check if the
327 327 obj = Session().query(UsersGroupRepoGroupToPerm)\
328 328 .filter(UsersGroupRepoGroupToPerm.group == self.g1)\
329 329 .filter(UsersGroupRepoGroupToPerm.users_group == self.ug1)\
330 330 .scalar()
331 331 self.assertEqual(obj.permission.permission_name, 'group.read')
332 332
333 333 a1_auth = AuthUser(user_id=self.anon.user_id)
334 334
335 335 self.assertEqual(a1_auth.permissions['repositories_groups'],
336 336 {u'group1': u'group.none'})
337 337
338 338 u1_auth = AuthUser(user_id=self.u1.user_id)
339 339 self.assertEqual(u1_auth.permissions['repositories_groups'],
340 340 {u'group1': u'group.read'})
341 341
342 342 def test_inherited_permissions_from_default_on_user_enabled(self):
343 343 user_model = UserModel()
344 344 # enable fork and create on default user
345 345 usr = 'default'
346 346 user_model.revoke_perm(usr, 'hg.create.none')
347 347 user_model.grant_perm(usr, 'hg.create.repository')
348 348 user_model.revoke_perm(usr, 'hg.fork.none')
349 349 user_model.grant_perm(usr, 'hg.fork.repository')
350 350 # make sure inherit flag is turned on
351 351 self.u1.inherit_default_permissions = True
352 352 Session().commit()
353 353 u1_auth = AuthUser(user_id=self.u1.user_id)
354 354 # this user will have inherited permissions from default user
355 355 self.assertEqual(u1_auth.permissions['global'],
356 356 set(['hg.create.repository', 'hg.fork.repository',
357 357 'hg.register.manual_activate',
358 358 'repository.read', 'group.read']))
359 359
360 360 def test_inherited_permissions_from_default_on_user_disabled(self):
361 361 user_model = UserModel()
362 362 # disable fork and create on default user
363 363 usr = 'default'
364 364 user_model.revoke_perm(usr, 'hg.create.repository')
365 365 user_model.grant_perm(usr, 'hg.create.none')
366 366 user_model.revoke_perm(usr, 'hg.fork.repository')
367 367 user_model.grant_perm(usr, 'hg.fork.none')
368 368 # make sure inherit flag is turned on
369 369 self.u1.inherit_default_permissions = True
370 370 Session().commit()
371 371 u1_auth = AuthUser(user_id=self.u1.user_id)
372 372 # this user will have inherited permissions from default user
373 373 self.assertEqual(u1_auth.permissions['global'],
374 374 set(['hg.create.none', 'hg.fork.none',
375 375 'hg.register.manual_activate',
376 376 'repository.read', 'group.read']))
377 377
378 378 def test_non_inherited_permissions_from_default_on_user_enabled(self):
379 379 user_model = UserModel()
380 380 # enable fork and create on default user
381 381 usr = 'default'
382 382 user_model.revoke_perm(usr, 'hg.create.none')
383 383 user_model.grant_perm(usr, 'hg.create.repository')
384 384 user_model.revoke_perm(usr, 'hg.fork.none')
385 385 user_model.grant_perm(usr, 'hg.fork.repository')
386 386
387 387 #disable global perms on specific user
388 388 user_model.revoke_perm(self.u1, 'hg.create.repository')
389 389 user_model.grant_perm(self.u1, 'hg.create.none')
390 390 user_model.revoke_perm(self.u1, 'hg.fork.repository')
391 391 user_model.grant_perm(self.u1, 'hg.fork.none')
392 392
393 393 # make sure inherit flag is turned off
394 394 self.u1.inherit_default_permissions = False
395 395 Session().commit()
396 396 u1_auth = AuthUser(user_id=self.u1.user_id)
397 397 # this user will have non inherited permissions from he's
398 398 # explicitly set permissions
399 399 self.assertEqual(u1_auth.permissions['global'],
400 400 set(['hg.create.none', 'hg.fork.none',
401 401 'hg.register.manual_activate',
402 402 'repository.read', 'group.read']))
403 403
404 404 def test_non_inherited_permissions_from_default_on_user_disabled(self):
405 405 user_model = UserModel()
406 406 # disable fork and create on default user
407 407 usr = 'default'
408 408 user_model.revoke_perm(usr, 'hg.create.repository')
409 409 user_model.grant_perm(usr, 'hg.create.none')
410 410 user_model.revoke_perm(usr, 'hg.fork.repository')
411 411 user_model.grant_perm(usr, 'hg.fork.none')
412 412
413 413 #enable global perms on specific user
414 414 user_model.revoke_perm(self.u1, 'hg.create.none')
415 415 user_model.grant_perm(self.u1, 'hg.create.repository')
416 416 user_model.revoke_perm(self.u1, 'hg.fork.none')
417 417 user_model.grant_perm(self.u1, 'hg.fork.repository')
418 418
419 419 # make sure inherit flag is turned off
420 420 self.u1.inherit_default_permissions = False
421 421 Session().commit()
422 422 u1_auth = AuthUser(user_id=self.u1.user_id)
423 423 # this user will have non inherited permissions from he's
424 424 # explicitly set permissions
425 425 self.assertEqual(u1_auth.permissions['global'],
426 426 set(['hg.create.repository', 'hg.fork.repository',
427 427 'hg.register.manual_activate',
428 428 'repository.read', 'group.read']))
429 429
430 430 def test_owner_permissions_doesnot_get_overwritten_by_group(self):
431 431 #create repo as USER,
432 432 self.test_repo = repo = RepoModel().create_repo(repo_name='myownrepo',
433 433 repo_type='hg',
434 434 description='desc',
435 435 owner=self.u1)
436 436
437 437 Session().commit()
438 438 #he has permissions of admin as owner
439 439 u1_auth = AuthUser(user_id=self.u1.user_id)
440 440 self.assertEqual(u1_auth.permissions['repositories']['myownrepo'],
441 441 'repository.admin')
442 #set his permission as users group, he should still be admin
442 #set his permission as user group, he should still be admin
443 443 self.ug1 = UsersGroupModel().create('G1')
444 444 # add user to group
445 445 UsersGroupModel().add_user_to_group(self.ug1, self.u1)
446 446 RepoModel().grant_users_group_permission(repo, group_name=self.ug1,
447 447 perm='repository.none')
448 448
449 449 Session().commit()
450 450 u1_auth = AuthUser(user_id=self.u1.user_id)
451 451 self.assertEqual(u1_auth.permissions['repositories']['myownrepo'],
452 452 'repository.admin')
453 453
454 454 def test_owner_permissions_doesnot_get_overwritten_by_others(self):
455 455 #create repo as USER,
456 456 self.test_repo = repo = RepoModel().create_repo(repo_name='myownrepo',
457 457 repo_type='hg',
458 458 description='desc',
459 459 owner=self.u1)
460 460
461 461 Session().commit()
462 462 #he has permissions of admin as owner
463 463 u1_auth = AuthUser(user_id=self.u1.user_id)
464 464 self.assertEqual(u1_auth.permissions['repositories']['myownrepo'],
465 465 'repository.admin')
466 466 #set his permission as user, he should still be admin
467 467 RepoModel().grant_user_permission(repo, user=self.u1,
468 468 perm='repository.none')
469 469 Session().commit()
470 470 u1_auth = AuthUser(user_id=self.u1.user_id)
471 471 self.assertEqual(u1_auth.permissions['repositories']['myownrepo'],
472 472 'repository.admin')
@@ -1,124 +1,124 b''
1 1 import unittest
2 2 from rhodecode.tests import *
3 3
4 4 from rhodecode.model.db import User, UsersGroup, UsersGroupMember, UserEmailMap,\
5 5 Permission
6 6 from rhodecode.model.user import UserModel
7 7
8 8 from rhodecode.model.meta import Session
9 9 from rhodecode.model.users_group import UsersGroupModel
10 10
11 11
12 12 class TestUser(unittest.TestCase):
13 13 def __init__(self, methodName='runTest'):
14 14 Session.remove()
15 15 super(TestUser, self).__init__(methodName=methodName)
16 16
17 17 def test_create_and_remove(self):
18 18 usr = UserModel().create_or_update(username=u'test_user',
19 19 password=u'qweqwe',
20 20 email=u'u232@rhodecode.org',
21 21 firstname=u'u1', lastname=u'u1')
22 22 Session().commit()
23 23 self.assertEqual(User.get_by_username(u'test_user'), usr)
24 24
25 # make users group
25 # make user group
26 26 users_group = UsersGroupModel().create('some_example_group')
27 27 Session().commit()
28 28
29 29 UsersGroupModel().add_user_to_group(users_group, usr)
30 30 Session().commit()
31 31
32 32 self.assertEqual(UsersGroup.get(users_group.users_group_id), users_group)
33 33 self.assertEqual(UsersGroupMember.query().count(), 1)
34 34 UserModel().delete(usr.user_id)
35 35 Session().commit()
36 36
37 37 self.assertEqual(UsersGroupMember.query().all(), [])
38 38
39 39 def test_additonal_email_as_main(self):
40 40 usr = UserModel().create_or_update(username=u'test_user',
41 41 password=u'qweqwe',
42 42 email=u'main_email@rhodecode.org',
43 43 firstname=u'u1', lastname=u'u1')
44 44 Session().commit()
45 45
46 46 def do():
47 47 m = UserEmailMap()
48 48 m.email = u'main_email@rhodecode.org'
49 49 m.user = usr
50 50 Session().add(m)
51 51 Session().commit()
52 52 self.assertRaises(AttributeError, do)
53 53
54 54 UserModel().delete(usr.user_id)
55 55 Session().commit()
56 56
57 57 def test_extra_email_map(self):
58 58 usr = UserModel().create_or_update(username=u'test_user',
59 59 password=u'qweqwe',
60 60 email=u'main_email@rhodecode.org',
61 61 firstname=u'u1', lastname=u'u1')
62 62 Session().commit()
63 63
64 64 m = UserEmailMap()
65 65 m.email = u'main_email2@rhodecode.org'
66 66 m.user = usr
67 67 Session().add(m)
68 68 Session().commit()
69 69
70 70 u = User.get_by_email(email='main_email@rhodecode.org')
71 71 self.assertEqual(usr.user_id, u.user_id)
72 72 self.assertEqual(usr.username, u.username)
73 73
74 74 u = User.get_by_email(email='main_email2@rhodecode.org')
75 75 self.assertEqual(usr.user_id, u.user_id)
76 76 self.assertEqual(usr.username, u.username)
77 77 u = User.get_by_email(email='main_email3@rhodecode.org')
78 78 self.assertEqual(None, u)
79 79
80 80 UserModel().delete(usr.user_id)
81 81 Session().commit()
82 82
83 83
84 84 class TestUsers(unittest.TestCase):
85 85
86 86 def __init__(self, methodName='runTest'):
87 87 super(TestUsers, self).__init__(methodName=methodName)
88 88
89 89 def setUp(self):
90 90 self.u1 = UserModel().create_or_update(username=u'u1',
91 91 password=u'qweqwe',
92 92 email=u'u1@rhodecode.org',
93 93 firstname=u'u1', lastname=u'u1')
94 94
95 95 def tearDown(self):
96 96 perm = Permission.query().all()
97 97 for p in perm:
98 98 UserModel().revoke_perm(self.u1, p)
99 99
100 100 UserModel().delete(self.u1)
101 101 Session().commit()
102 102
103 103 def test_add_perm(self):
104 104 perm = Permission.query().all()[0]
105 105 UserModel().grant_perm(self.u1, perm)
106 106 Session().commit()
107 107 self.assertEqual(UserModel().has_perm(self.u1, perm), True)
108 108
109 109 def test_has_perm(self):
110 110 perm = Permission.query().all()
111 111 for p in perm:
112 112 has_p = UserModel().has_perm(self.u1, p)
113 113 self.assertEqual(False, has_p)
114 114
115 115 def test_revoke_perm(self):
116 116 perm = Permission.query().all()[0]
117 117 UserModel().grant_perm(self.u1, perm)
118 118 Session().commit()
119 119 self.assertEqual(UserModel().has_perm(self.u1, perm), True)
120 120
121 121 #revoke
122 122 UserModel().revoke_perm(self.u1, perm)
123 123 Session().commit()
124 124 self.assertEqual(UserModel().has_perm(self.u1, perm), False)
General Comments 0
You need to be logged in to leave comments. Login now