##// END OF EJS Templates
get_locks API function draft
marcink -
r3502:7cde75ea beta
parent child Browse files
Show More
@@ -1,978 +1,1017 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.api
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 API controller for RhodeCode
7 7
8 8 :created_on: Aug 20, 2011
9 9 :author: marcink
10 10 :copyright: (C) 2011-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
14 14 # modify it under the terms of the GNU General Public License
15 15 # as published by the Free Software Foundation; version 2
16 16 # of the License or (at your opinion) any later version of the license.
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, write to the Free Software
25 25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 26 # MA 02110-1301, USA.
27 27
28 28 import traceback
29 29 import logging
30 30
31 31 from rhodecode.controllers.api import JSONRPCController, JSONRPCError
32 32 from rhodecode.lib.auth import PasswordGenerator, AuthUser, \
33 33 HasPermissionAllDecorator, HasPermissionAnyDecorator, \
34 34 HasPermissionAnyApi, HasRepoPermissionAnyApi
35 35 from rhodecode.lib.utils import map_groups, repo2db_mapper
36 from rhodecode.lib.utils2 import str2bool, time_to_datetime
36 from rhodecode.lib.utils2 import str2bool, time_to_datetime, safe_int
37 37 from rhodecode.lib import helpers as h
38 38 from rhodecode.model.meta import Session
39 39 from rhodecode.model.scm import ScmModel
40 40 from rhodecode.model.repo import RepoModel
41 41 from rhodecode.model.user import UserModel
42 42 from rhodecode.model.users_group import UserGroupModel
43 43 from rhodecode.model.permission import PermissionModel
44 44 from rhodecode.model.db import Repository, RhodeCodeSetting, UserIpMap
45 from rhodecode.lib.compat import json
45 46
46 47 log = logging.getLogger(__name__)
47 48
48 49
49 50 class OptionalAttr(object):
50 51 """
51 52 Special Optional Option that defines other attribute
52 53 """
53 54 def __init__(self, attr_name):
54 55 self.attr_name = attr_name
55 56
56 57 def __repr__(self):
57 58 return '<OptionalAttr:%s>' % self.attr_name
58 59
59 60 def __call__(self):
60 61 return self
61 62 #alias
62 63 OAttr = OptionalAttr
63 64
64 65
65 66 class Optional(object):
66 67 """
67 68 Defines an optional parameter::
68 69
69 70 param = param.getval() if isinstance(param, Optional) else param
70 71 param = param() if isinstance(param, Optional) else param
71 72
72 73 is equivalent of::
73 74
74 75 param = Optional.extract(param)
75 76
76 77 """
77 78 def __init__(self, type_):
78 79 self.type_ = type_
79 80
80 81 def __repr__(self):
81 82 return '<Optional:%s>' % self.type_.__repr__()
82 83
83 84 def __call__(self):
84 85 return self.getval()
85 86
86 87 def getval(self):
87 88 """
88 89 returns value from this Optional instance
89 90 """
90 91 return self.type_
91 92
92 93 @classmethod
93 94 def extract(cls, val):
94 95 if isinstance(val, cls):
95 96 return val.getval()
96 97 return val
97 98
98 99
99 100 def get_user_or_error(userid):
100 101 """
101 102 Get user by id or name or return JsonRPCError if not found
102 103
103 104 :param userid:
104 105 """
105 106 user = UserModel().get_user(userid)
106 107 if user is None:
107 108 raise JSONRPCError("user `%s` does not exist" % userid)
108 109 return user
109 110
110 111
111 112 def get_repo_or_error(repoid):
112 113 """
113 114 Get repo by id or name or return JsonRPCError if not found
114 115
115 116 :param userid:
116 117 """
117 118 repo = RepoModel().get_repo(repoid)
118 119 if repo is None:
119 120 raise JSONRPCError('repository `%s` does not exist' % (repoid))
120 121 return repo
121 122
122 123
123 124 def get_users_group_or_error(usersgroupid):
124 125 """
125 126 Get user group by id or name or return JsonRPCError if not found
126 127
127 128 :param userid:
128 129 """
129 130 users_group = UserGroupModel().get_group(usersgroupid)
130 131 if users_group is None:
131 132 raise JSONRPCError('user group `%s` does not exist' % usersgroupid)
132 133 return users_group
133 134
134 135
135 136 def get_perm_or_error(permid):
136 137 """
137 138 Get permission by id or name or return JsonRPCError if not found
138 139
139 140 :param userid:
140 141 """
141 142 perm = PermissionModel().get_permission_by_name(permid)
142 143 if perm is None:
143 144 raise JSONRPCError('permission `%s` does not exist' % (permid))
144 145 return perm
145 146
146 147
147 148 class ApiController(JSONRPCController):
148 149 """
149 150 API Controller
150 151
151 152
152 153 Each method needs to have USER as argument this is then based on given
153 154 API_KEY propagated as instance of user object
154 155
155 156 Preferably this should be first argument also
156 157
157 158
158 159 Each function should also **raise** JSONRPCError for any
159 160 errors that happens
160 161
161 162 """
162 163
163 164 @HasPermissionAllDecorator('hg.admin')
164 165 def pull(self, apiuser, repoid):
165 166 """
166 167 Dispatch pull action on given repo
167 168
168 169 :param apiuser:
169 170 :param repoid:
170 171 """
171 172
172 173 repo = get_repo_or_error(repoid)
173 174
174 175 try:
175 176 ScmModel().pull_changes(repo.repo_name,
176 177 self.rhodecode_user.username)
177 178 return 'Pulled from `%s`' % repo.repo_name
178 179 except Exception:
179 180 log.error(traceback.format_exc())
180 181 raise JSONRPCError(
181 182 'Unable to pull changes from `%s`' % repo.repo_name
182 183 )
183 184
184 185 @HasPermissionAllDecorator('hg.admin')
185 186 def rescan_repos(self, apiuser, remove_obsolete=Optional(False)):
186 187 """
187 188 Dispatch rescan repositories action. If remove_obsolete is set
188 189 than also delete repos that are in database but not in the filesystem.
189 190 aka "clean zombies"
190 191
191 192 :param apiuser:
192 193 :param remove_obsolete:
193 194 """
194 195
195 196 try:
196 197 rm_obsolete = Optional.extract(remove_obsolete)
197 198 added, removed = repo2db_mapper(ScmModel().repo_scan(),
198 199 remove_obsolete=rm_obsolete)
199 200 return {'added': added, 'removed': removed}
200 201 except Exception:
201 202 log.error(traceback.format_exc())
202 203 raise JSONRPCError(
203 204 'Error occurred during rescan repositories action'
204 205 )
205 206
206 207 def invalidate_cache(self, apiuser, repoid):
207 208 """
208 209 Dispatch cache invalidation action on given repo
209 210
210 211 :param apiuser:
211 212 :param repoid:
212 213 """
213 214 repo = get_repo_or_error(repoid)
214 215 if HasPermissionAnyApi('hg.admin')(user=apiuser) is False:
215 216 # check if we have admin permission for this repo !
216 217 if HasRepoPermissionAnyApi('repository.admin',
217 218 'repository.write')(user=apiuser,
218 219 repo_name=repo.repo_name) is False:
219 220 raise JSONRPCError('repository `%s` does not exist' % (repoid))
220 221
221 222 try:
222 223 invalidated_keys = ScmModel().mark_for_invalidation(repo.repo_name)
223 224 Session().commit()
224 225 return ('Cache for repository `%s` was invalidated: '
225 226 'invalidated cache keys: %s' % (repoid, invalidated_keys))
226 227 except Exception:
227 228 log.error(traceback.format_exc())
228 229 raise JSONRPCError(
229 230 'Error occurred during cache invalidation action'
230 231 )
231 232
232 233 def lock(self, apiuser, repoid, locked=Optional(None),
233 234 userid=Optional(OAttr('apiuser'))):
234 235 """
235 236 Set locking state on particular repository by given user, if
236 237 this command is runned by non-admin account userid is set to user
237 238 who is calling this method
238 239
239 240 :param apiuser:
240 241 :param repoid:
241 242 :param userid:
242 243 :param locked:
243 244 """
244 245 repo = get_repo_or_error(repoid)
245 246 if HasPermissionAnyApi('hg.admin')(user=apiuser):
246 247 pass
247 248 elif HasRepoPermissionAnyApi('repository.admin',
248 249 'repository.write')(user=apiuser,
249 250 repo_name=repo.repo_name):
250 251 #make sure normal user does not pass someone else userid,
251 252 #he is not allowed to do that
252 253 if not isinstance(userid, Optional) and userid != apiuser.user_id:
253 254 raise JSONRPCError(
254 255 'userid is not the same as your user'
255 256 )
256 257 else:
257 258 raise JSONRPCError('repository `%s` does not exist' % (repoid))
258 259
259 260 if isinstance(userid, Optional):
260 261 userid = apiuser.user_id
261 262
262 263 user = get_user_or_error(userid)
263 264
264 265 if isinstance(locked, Optional):
265 266 lockobj = Repository.getlock(repo)
266 267
267 268 if lockobj[0] is None:
268 269 return ('Repo `%s` not locked. Locked=`False`.'
269 270 % (repo.repo_name))
270 271 else:
271 272 userid, time_ = lockobj
272 273 user = get_user_or_error(userid)
273 274
274 275 return ('Repo `%s` locked by `%s`. Locked=`True`. '
275 276 'Locked since: `%s`'
276 277 % (repo.repo_name, user.username,
277 h.fmt_date(time_to_datetime(time_))))
278 json.dumps(time_to_datetime(time_))))
278 279
279 280 else:
280 281 locked = str2bool(locked)
281 282 try:
282 283 if locked:
283 284 Repository.lock(repo, user.user_id)
284 285 else:
285 286 Repository.unlock(repo)
286 287
287 288 return ('User `%s` set lock state for repo `%s` to `%s`'
288 289 % (user.username, repo.repo_name, locked))
289 290 except Exception:
290 291 log.error(traceback.format_exc())
291 292 raise JSONRPCError(
292 293 'Error occurred locking repository `%s`' % repo.repo_name
293 294 )
294 295
296 def get_locks(self, apiuser, userid=Optional(OAttr('apiuser'))):
297 """
298 Get all locks for given userid, if
299 this command is runned by non-admin account userid is set to user
300 who is calling this method, thus returning locks for himself
301
302 :param apiuser:
303 :param userid:
304 """
305 if HasPermissionAnyApi('hg.admin')(user=apiuser):
306 pass
307 else:
308 #make sure normal user does not pass someone else userid,
309 #he is not allowed to do that
310 if not isinstance(userid, Optional) and userid != apiuser.user_id:
311 raise JSONRPCError(
312 'userid is not the same as your user'
313 )
314 ret = []
315 if isinstance(userid, Optional):
316 user = None
317 else:
318 user = get_user_or_error(userid)
319
320 #show all locks
321 for r in Repository.getAll():
322 userid, time_ = r.locked
323 if time_:
324 _api_data = r.get_api_data()
325 # if we use userfilter just show the locks for this user
326 if user:
327 if safe_int(userid) == user.user_id:
328 ret.append(_api_data)
329 else:
330 ret.append(_api_data)
331
332 return ret
333
295 334 @HasPermissionAllDecorator('hg.admin')
296 335 def show_ip(self, apiuser, userid):
297 336 """
298 337 Shows IP address as seen from RhodeCode server, together with all
299 338 defined IP addresses for given user
300 339
301 340 :param apiuser:
302 341 :param userid:
303 342 """
304 343 user = get_user_or_error(userid)
305 344 ips = UserIpMap.query().filter(UserIpMap.user == user).all()
306 345 return dict(
307 346 ip_addr_server=self.ip_addr,
308 347 user_ips=ips
309 348 )
310 349
311 350 def get_user(self, apiuser, userid=Optional(OAttr('apiuser'))):
312 351 """"
313 352 Get a user by username, or userid, if userid is given
314 353
315 354 :param apiuser:
316 355 :param userid:
317 356 """
318 357 if HasPermissionAnyApi('hg.admin')(user=apiuser) is False:
319 358 #make sure normal user does not pass someone else userid,
320 359 #he is not allowed to do that
321 360 if not isinstance(userid, Optional) and userid != apiuser.user_id:
322 361 raise JSONRPCError(
323 362 'userid is not the same as your user'
324 363 )
325 364
326 365 if isinstance(userid, Optional):
327 366 userid = apiuser.user_id
328 367
329 368 user = get_user_or_error(userid)
330 369 data = user.get_api_data()
331 370 data['permissions'] = AuthUser(user_id=user.user_id).permissions
332 371 return data
333 372
334 373 @HasPermissionAllDecorator('hg.admin')
335 374 def get_users(self, apiuser):
336 375 """"
337 376 Get all users
338 377
339 378 :param apiuser:
340 379 """
341 380
342 381 result = []
343 382 for user in UserModel().get_all():
344 383 result.append(user.get_api_data())
345 384 return result
346 385
347 386 @HasPermissionAllDecorator('hg.admin')
348 387 def create_user(self, apiuser, username, email, password,
349 388 firstname=Optional(None), lastname=Optional(None),
350 389 active=Optional(True), admin=Optional(False),
351 390 ldap_dn=Optional(None)):
352 391 """
353 392 Create new user
354 393
355 394 :param apiuser:
356 395 :param username:
357 396 :param email:
358 397 :param password:
359 398 :param firstname:
360 399 :param lastname:
361 400 :param active:
362 401 :param admin:
363 402 :param ldap_dn:
364 403 """
365 404
366 405 if UserModel().get_by_username(username):
367 406 raise JSONRPCError("user `%s` already exist" % username)
368 407
369 408 if UserModel().get_by_email(email, case_insensitive=True):
370 409 raise JSONRPCError("email `%s` already exist" % email)
371 410
372 411 if Optional.extract(ldap_dn):
373 412 # generate temporary password if ldap_dn
374 413 password = PasswordGenerator().gen_password(length=8)
375 414
376 415 try:
377 416 user = UserModel().create_or_update(
378 417 username=Optional.extract(username),
379 418 password=Optional.extract(password),
380 419 email=Optional.extract(email),
381 420 firstname=Optional.extract(firstname),
382 421 lastname=Optional.extract(lastname),
383 422 active=Optional.extract(active),
384 423 admin=Optional.extract(admin),
385 424 ldap_dn=Optional.extract(ldap_dn)
386 425 )
387 426 Session().commit()
388 427 return dict(
389 428 msg='created new user `%s`' % username,
390 429 user=user.get_api_data()
391 430 )
392 431 except Exception:
393 432 log.error(traceback.format_exc())
394 433 raise JSONRPCError('failed to create user `%s`' % username)
395 434
396 435 @HasPermissionAllDecorator('hg.admin')
397 436 def update_user(self, apiuser, userid, username=Optional(None),
398 437 email=Optional(None), firstname=Optional(None),
399 438 lastname=Optional(None), active=Optional(None),
400 439 admin=Optional(None), ldap_dn=Optional(None),
401 440 password=Optional(None)):
402 441 """
403 442 Updates given user
404 443
405 444 :param apiuser:
406 445 :param userid:
407 446 :param username:
408 447 :param email:
409 448 :param firstname:
410 449 :param lastname:
411 450 :param active:
412 451 :param admin:
413 452 :param ldap_dn:
414 453 :param password:
415 454 """
416 455
417 456 user = get_user_or_error(userid)
418 457
419 458 # call function and store only updated arguments
420 459 updates = {}
421 460
422 461 def store_update(attr, name):
423 462 if not isinstance(attr, Optional):
424 463 updates[name] = attr
425 464
426 465 try:
427 466
428 467 store_update(username, 'username')
429 468 store_update(password, 'password')
430 469 store_update(email, 'email')
431 470 store_update(firstname, 'name')
432 471 store_update(lastname, 'lastname')
433 472 store_update(active, 'active')
434 473 store_update(admin, 'admin')
435 474 store_update(ldap_dn, 'ldap_dn')
436 475
437 476 user = UserModel().update_user(user, **updates)
438 477 Session().commit()
439 478 return dict(
440 479 msg='updated user ID:%s %s' % (user.user_id, user.username),
441 480 user=user.get_api_data()
442 481 )
443 482 except Exception:
444 483 log.error(traceback.format_exc())
445 484 raise JSONRPCError('failed to update user `%s`' % userid)
446 485
447 486 @HasPermissionAllDecorator('hg.admin')
448 487 def delete_user(self, apiuser, userid):
449 488 """"
450 489 Deletes an user
451 490
452 491 :param apiuser:
453 492 :param userid:
454 493 """
455 494 user = get_user_or_error(userid)
456 495
457 496 try:
458 497 UserModel().delete(userid)
459 498 Session().commit()
460 499 return dict(
461 500 msg='deleted user ID:%s %s' % (user.user_id, user.username),
462 501 user=None
463 502 )
464 503 except Exception:
465 504 log.error(traceback.format_exc())
466 505 raise JSONRPCError('failed to delete ID:%s %s' % (user.user_id,
467 506 user.username))
468 507
469 508 @HasPermissionAllDecorator('hg.admin')
470 509 def get_users_group(self, apiuser, usersgroupid):
471 510 """"
472 511 Get user group by name or id
473 512
474 513 :param apiuser:
475 514 :param usersgroupid:
476 515 """
477 516 users_group = get_users_group_or_error(usersgroupid)
478 517
479 518 data = users_group.get_api_data()
480 519
481 520 members = []
482 521 for user in users_group.members:
483 522 user = user.user
484 523 members.append(user.get_api_data())
485 524 data['members'] = members
486 525 return data
487 526
488 527 @HasPermissionAllDecorator('hg.admin')
489 528 def get_users_groups(self, apiuser):
490 529 """"
491 530 Get all user groups
492 531
493 532 :param apiuser:
494 533 """
495 534
496 535 result = []
497 536 for users_group in UserGroupModel().get_all():
498 537 result.append(users_group.get_api_data())
499 538 return result
500 539
501 540 @HasPermissionAllDecorator('hg.admin')
502 541 def create_users_group(self, apiuser, group_name, active=Optional(True)):
503 542 """
504 543 Creates an new usergroup
505 544
506 545 :param apiuser:
507 546 :param group_name:
508 547 :param active:
509 548 """
510 549
511 550 if UserGroupModel().get_by_name(group_name):
512 551 raise JSONRPCError("user group `%s` already exist" % group_name)
513 552
514 553 try:
515 554 active = Optional.extract(active)
516 555 ug = UserGroupModel().create(name=group_name, active=active)
517 556 Session().commit()
518 557 return dict(
519 558 msg='created new user group `%s`' % group_name,
520 559 users_group=ug.get_api_data()
521 560 )
522 561 except Exception:
523 562 log.error(traceback.format_exc())
524 563 raise JSONRPCError('failed to create group `%s`' % group_name)
525 564
526 565 @HasPermissionAllDecorator('hg.admin')
527 566 def add_user_to_users_group(self, apiuser, usersgroupid, userid):
528 567 """"
529 568 Add a user to a user group
530 569
531 570 :param apiuser:
532 571 :param usersgroupid:
533 572 :param userid:
534 573 """
535 574 user = get_user_or_error(userid)
536 575 users_group = get_users_group_or_error(usersgroupid)
537 576
538 577 try:
539 578 ugm = UserGroupModel().add_user_to_group(users_group, user)
540 579 success = True if ugm != True else False
541 580 msg = 'added member `%s` to user group `%s`' % (
542 581 user.username, users_group.users_group_name
543 582 )
544 583 msg = msg if success else 'User is already in that group'
545 584 Session().commit()
546 585
547 586 return dict(
548 587 success=success,
549 588 msg=msg
550 589 )
551 590 except Exception:
552 591 log.error(traceback.format_exc())
553 592 raise JSONRPCError(
554 593 'failed to add member to user group `%s`' % (
555 594 users_group.users_group_name
556 595 )
557 596 )
558 597
559 598 @HasPermissionAllDecorator('hg.admin')
560 599 def remove_user_from_users_group(self, apiuser, usersgroupid, userid):
561 600 """
562 601 Remove user from a group
563 602
564 603 :param apiuser:
565 604 :param usersgroupid:
566 605 :param userid:
567 606 """
568 607 user = get_user_or_error(userid)
569 608 users_group = get_users_group_or_error(usersgroupid)
570 609
571 610 try:
572 611 success = UserGroupModel().remove_user_from_group(users_group,
573 612 user)
574 613 msg = 'removed member `%s` from user group `%s`' % (
575 614 user.username, users_group.users_group_name
576 615 )
577 616 msg = msg if success else "User wasn't in group"
578 617 Session().commit()
579 618 return dict(success=success, msg=msg)
580 619 except Exception:
581 620 log.error(traceback.format_exc())
582 621 raise JSONRPCError(
583 622 'failed to remove member from user group `%s`' % (
584 623 users_group.users_group_name
585 624 )
586 625 )
587 626
588 627 def get_repo(self, apiuser, repoid):
589 628 """"
590 629 Get repository by name
591 630
592 631 :param apiuser:
593 632 :param repoid:
594 633 """
595 634 repo = get_repo_or_error(repoid)
596 635
597 636 if HasPermissionAnyApi('hg.admin')(user=apiuser) is False:
598 637 # check if we have admin permission for this repo !
599 638 if HasRepoPermissionAnyApi('repository.admin')(user=apiuser,
600 639 repo_name=repo.repo_name) is False:
601 640 raise JSONRPCError('repository `%s` does not exist' % (repoid))
602 641
603 642 members = []
604 643 followers = []
605 644 for user in repo.repo_to_perm:
606 645 perm = user.permission.permission_name
607 646 user = user.user
608 647 user_data = user.get_api_data()
609 648 user_data['type'] = "user"
610 649 user_data['permission'] = perm
611 650 members.append(user_data)
612 651
613 652 for users_group in repo.users_group_to_perm:
614 653 perm = users_group.permission.permission_name
615 654 users_group = users_group.users_group
616 655 users_group_data = users_group.get_api_data()
617 656 users_group_data['type'] = "users_group"
618 657 users_group_data['permission'] = perm
619 658 members.append(users_group_data)
620 659
621 660 for user in repo.followers:
622 661 followers.append(user.user.get_api_data())
623 662
624 663 data = repo.get_api_data()
625 664 data['members'] = members
626 665 data['followers'] = followers
627 666 return data
628 667
629 668 def get_repos(self, apiuser):
630 669 """"
631 670 Get all repositories
632 671
633 672 :param apiuser:
634 673 """
635 674 result = []
636 675 if HasPermissionAnyApi('hg.admin')(user=apiuser) is False:
637 676 repos = RepoModel().get_all_user_repos(user=apiuser)
638 677 else:
639 678 repos = RepoModel().get_all()
640 679
641 680 for repo in repos:
642 681 result.append(repo.get_api_data())
643 682 return result
644 683
645 684 @HasPermissionAllDecorator('hg.admin')
646 685 def get_repo_nodes(self, apiuser, repoid, revision, root_path,
647 686 ret_type='all'):
648 687 """
649 688 returns a list of nodes and it's children
650 689 for a given path at given revision. It's possible to specify ret_type
651 690 to show only files or dirs
652 691
653 692 :param apiuser:
654 693 :param repoid: name or id of repository
655 694 :param revision: revision for which listing should be done
656 695 :param root_path: path from which start displaying
657 696 :param ret_type: return type 'all|files|dirs' nodes
658 697 """
659 698 repo = get_repo_or_error(repoid)
660 699 try:
661 700 _d, _f = ScmModel().get_nodes(repo, revision, root_path,
662 701 flat=False)
663 702 _map = {
664 703 'all': _d + _f,
665 704 'files': _f,
666 705 'dirs': _d,
667 706 }
668 707 return _map[ret_type]
669 708 except KeyError:
670 709 raise JSONRPCError('ret_type must be one of %s' % _map.keys())
671 710 except Exception:
672 711 log.error(traceback.format_exc())
673 712 raise JSONRPCError(
674 713 'failed to get repo: `%s` nodes' % repo.repo_name
675 714 )
676 715
677 716 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
678 717 def create_repo(self, apiuser, repo_name, owner=Optional(OAttr('apiuser')),
679 718 repo_type=Optional('hg'),
680 719 description=Optional(''), private=Optional(False),
681 720 clone_uri=Optional(None), landing_rev=Optional('tip'),
682 721 enable_statistics=Optional(False),
683 722 enable_locking=Optional(False),
684 723 enable_downloads=Optional(False)):
685 724 """
686 725 Create repository, if clone_url is given it makes a remote clone
687 726 if repo_name is within a group name the groups will be created
688 727 automatically if they aren't present
689 728
690 729 :param apiuser:
691 730 :param repo_name:
692 731 :param onwer:
693 732 :param repo_type:
694 733 :param description:
695 734 :param private:
696 735 :param clone_uri:
697 736 :param landing_rev:
698 737 """
699 738 if HasPermissionAnyApi('hg.admin')(user=apiuser) is False:
700 739 if not isinstance(owner, Optional):
701 740 #forbid setting owner for non-admins
702 741 raise JSONRPCError(
703 742 'Only RhodeCode admin can specify `owner` param'
704 743 )
705 744 if isinstance(owner, Optional):
706 745 owner = apiuser.user_id
707 746
708 747 owner = get_user_or_error(owner)
709 748
710 749 if RepoModel().get_by_repo_name(repo_name):
711 750 raise JSONRPCError("repo `%s` already exist" % repo_name)
712 751
713 752 defs = RhodeCodeSetting.get_default_repo_settings(strip_prefix=True)
714 753 if isinstance(private, Optional):
715 754 private = defs.get('repo_private') or Optional.extract(private)
716 755 if isinstance(repo_type, Optional):
717 756 repo_type = defs.get('repo_type')
718 757 if isinstance(enable_statistics, Optional):
719 758 enable_statistics = defs.get('repo_enable_statistics')
720 759 if isinstance(enable_locking, Optional):
721 760 enable_locking = defs.get('repo_enable_locking')
722 761 if isinstance(enable_downloads, Optional):
723 762 enable_downloads = defs.get('repo_enable_downloads')
724 763
725 764 clone_uri = Optional.extract(clone_uri)
726 765 description = Optional.extract(description)
727 766 landing_rev = Optional.extract(landing_rev)
728 767
729 768 try:
730 769 # create structure of groups and return the last group
731 770 group = map_groups(repo_name)
732 771
733 772 repo = RepoModel().create_repo(
734 773 repo_name=repo_name,
735 774 repo_type=repo_type,
736 775 description=description,
737 776 owner=owner,
738 777 private=private,
739 778 clone_uri=clone_uri,
740 779 repos_group=group,
741 780 landing_rev=landing_rev,
742 781 enable_statistics=enable_statistics,
743 782 enable_downloads=enable_downloads,
744 783 enable_locking=enable_locking
745 784 )
746 785
747 786 Session().commit()
748 787 return dict(
749 788 msg="Created new repository `%s`" % (repo.repo_name),
750 789 repo=repo.get_api_data()
751 790 )
752 791 except Exception:
753 792 log.error(traceback.format_exc())
754 793 raise JSONRPCError('failed to create repository `%s`' % repo_name)
755 794
756 795 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
757 796 def fork_repo(self, apiuser, repoid, fork_name, owner=Optional(OAttr('apiuser')),
758 797 description=Optional(''), copy_permissions=Optional(False),
759 798 private=Optional(False), landing_rev=Optional('tip')):
760 799 repo = get_repo_or_error(repoid)
761 800 repo_name = repo.repo_name
762 801
763 802 _repo = RepoModel().get_by_repo_name(fork_name)
764 803 if _repo:
765 804 type_ = 'fork' if _repo.fork else 'repo'
766 805 raise JSONRPCError("%s `%s` already exist" % (type_, fork_name))
767 806
768 807 if HasPermissionAnyApi('hg.admin')(user=apiuser):
769 808 pass
770 809 elif HasRepoPermissionAnyApi('repository.admin',
771 810 'repository.write',
772 811 'repository.read')(user=apiuser,
773 812 repo_name=repo.repo_name):
774 813 if not isinstance(owner, Optional):
775 814 #forbid setting owner for non-admins
776 815 raise JSONRPCError(
777 816 'Only RhodeCode admin can specify `owner` param'
778 817 )
779 818 else:
780 819 raise JSONRPCError('repository `%s` does not exist' % (repoid))
781 820
782 821 if isinstance(owner, Optional):
783 822 owner = apiuser.user_id
784 823
785 824 owner = get_user_or_error(owner)
786 825
787 826 try:
788 827 # create structure of groups and return the last group
789 828 group = map_groups(fork_name)
790 829
791 830 form_data = dict(
792 831 repo_name=fork_name,
793 832 repo_name_full=fork_name,
794 833 repo_group=group,
795 834 repo_type=repo.repo_type,
796 835 description=Optional.extract(description),
797 836 private=Optional.extract(private),
798 837 copy_permissions=Optional.extract(copy_permissions),
799 838 landing_rev=Optional.extract(landing_rev),
800 839 update_after_clone=False,
801 840 fork_parent_id=repo.repo_id,
802 841 )
803 842 RepoModel().create_fork(form_data, cur_user=owner)
804 843 return dict(
805 844 msg='Created fork of `%s` as `%s`' % (repo.repo_name,
806 845 fork_name),
807 846 success=True # cannot return the repo data here since fork
808 847 # cann be done async
809 848 )
810 849 except Exception:
811 850 log.error(traceback.format_exc())
812 851 raise JSONRPCError(
813 852 'failed to fork repository `%s` as `%s`' % (repo_name,
814 853 fork_name)
815 854 )
816 855
817 856 def delete_repo(self, apiuser, repoid):
818 857 """
819 858 Deletes a given repository
820 859
821 860 :param apiuser:
822 861 :param repoid:
823 862 """
824 863 repo = get_repo_or_error(repoid)
825 864
826 865 if HasPermissionAnyApi('hg.admin')(user=apiuser) is False:
827 866 # check if we have admin permission for this repo !
828 867 if HasRepoPermissionAnyApi('repository.admin')(user=apiuser,
829 868 repo_name=repo.repo_name) is False:
830 869 raise JSONRPCError('repository `%s` does not exist' % (repoid))
831 870
832 871 try:
833 872 RepoModel().delete(repo)
834 873 Session().commit()
835 874 return dict(
836 875 msg='Deleted repository `%s`' % repo.repo_name,
837 876 success=True
838 877 )
839 878 except Exception:
840 879 log.error(traceback.format_exc())
841 880 raise JSONRPCError(
842 881 'failed to delete repository `%s`' % repo.repo_name
843 882 )
844 883
845 884 @HasPermissionAllDecorator('hg.admin')
846 885 def grant_user_permission(self, apiuser, repoid, userid, perm):
847 886 """
848 887 Grant permission for user on given repository, or update existing one
849 888 if found
850 889
851 890 :param repoid:
852 891 :param userid:
853 892 :param perm:
854 893 """
855 894 repo = get_repo_or_error(repoid)
856 895 user = get_user_or_error(userid)
857 896 perm = get_perm_or_error(perm)
858 897
859 898 try:
860 899
861 900 RepoModel().grant_user_permission(repo=repo, user=user, perm=perm)
862 901
863 902 Session().commit()
864 903 return dict(
865 904 msg='Granted perm: `%s` for user: `%s` in repo: `%s`' % (
866 905 perm.permission_name, user.username, repo.repo_name
867 906 ),
868 907 success=True
869 908 )
870 909 except Exception:
871 910 log.error(traceback.format_exc())
872 911 raise JSONRPCError(
873 912 'failed to edit permission for user: `%s` in repo: `%s`' % (
874 913 userid, repoid
875 914 )
876 915 )
877 916
878 917 @HasPermissionAllDecorator('hg.admin')
879 918 def revoke_user_permission(self, apiuser, repoid, userid):
880 919 """
881 920 Revoke permission for user on given repository
882 921
883 922 :param apiuser:
884 923 :param repoid:
885 924 :param userid:
886 925 """
887 926
888 927 repo = get_repo_or_error(repoid)
889 928 user = get_user_or_error(userid)
890 929 try:
891 930
892 931 RepoModel().revoke_user_permission(repo=repo, user=user)
893 932
894 933 Session().commit()
895 934 return dict(
896 935 msg='Revoked perm for user: `%s` in repo: `%s`' % (
897 936 user.username, repo.repo_name
898 937 ),
899 938 success=True
900 939 )
901 940 except Exception:
902 941 log.error(traceback.format_exc())
903 942 raise JSONRPCError(
904 943 'failed to edit permission for user: `%s` in repo: `%s`' % (
905 944 userid, repoid
906 945 )
907 946 )
908 947
909 948 @HasPermissionAllDecorator('hg.admin')
910 949 def grant_users_group_permission(self, apiuser, repoid, usersgroupid,
911 950 perm):
912 951 """
913 952 Grant permission for user group on given repository, or update
914 953 existing one if found
915 954
916 955 :param apiuser:
917 956 :param repoid:
918 957 :param usersgroupid:
919 958 :param perm:
920 959 """
921 960 repo = get_repo_or_error(repoid)
922 961 perm = get_perm_or_error(perm)
923 962 users_group = get_users_group_or_error(usersgroupid)
924 963
925 964 try:
926 965 RepoModel().grant_users_group_permission(repo=repo,
927 966 group_name=users_group,
928 967 perm=perm)
929 968
930 969 Session().commit()
931 970 return dict(
932 971 msg='Granted perm: `%s` for user group: `%s` in '
933 972 'repo: `%s`' % (
934 973 perm.permission_name, users_group.users_group_name,
935 974 repo.repo_name
936 975 ),
937 976 success=True
938 977 )
939 978 except Exception:
940 979 log.error(traceback.format_exc())
941 980 raise JSONRPCError(
942 981 'failed to edit permission for user group: `%s` in '
943 982 'repo: `%s`' % (
944 983 usersgroupid, repo.repo_name
945 984 )
946 985 )
947 986
948 987 @HasPermissionAllDecorator('hg.admin')
949 988 def revoke_users_group_permission(self, apiuser, repoid, usersgroupid):
950 989 """
951 990 Revoke permission for user group on given repository
952 991
953 992 :param apiuser:
954 993 :param repoid:
955 994 :param usersgroupid:
956 995 """
957 996 repo = get_repo_or_error(repoid)
958 997 users_group = get_users_group_or_error(usersgroupid)
959 998
960 999 try:
961 1000 RepoModel().revoke_users_group_permission(repo=repo,
962 1001 group_name=users_group)
963 1002
964 1003 Session().commit()
965 1004 return dict(
966 1005 msg='Revoked perm for user group: `%s` in repo: `%s`' % (
967 1006 users_group.users_group_name, repo.repo_name
968 1007 ),
969 1008 success=True
970 1009 )
971 1010 except Exception:
972 1011 log.error(traceback.format_exc())
973 1012 raise JSONRPCError(
974 1013 'failed to edit permission for user group: `%s` in '
975 1014 'repo: `%s`' % (
976 1015 users_group.users_group_name, repo.repo_name
977 1016 )
978 1017 )
@@ -1,2053 +1,2057 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.db
4 4 ~~~~~~~~~~~~~~~~~~
5 5
6 6 Database Models for RhodeCode
7 7
8 8 :created_on: Apr 08, 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 os
27 27 import logging
28 28 import datetime
29 29 import traceback
30 30 import hashlib
31 31 import time
32 32 from collections import defaultdict
33 33
34 34 from sqlalchemy import *
35 35 from sqlalchemy.ext.hybrid import hybrid_property
36 36 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
37 37 from sqlalchemy.exc import DatabaseError
38 38 from beaker.cache import cache_region, region_invalidate
39 39 from webob.exc import HTTPNotFound
40 40
41 41 from pylons.i18n.translation import lazy_ugettext as _
42 42
43 43 from rhodecode.lib.vcs import get_backend
44 44 from rhodecode.lib.vcs.utils.helpers import get_scm
45 45 from rhodecode.lib.vcs.exceptions import VCSError
46 46 from rhodecode.lib.vcs.utils.lazy import LazyProperty
47 47 from rhodecode.lib.vcs.backends.base import EmptyChangeset
48 48
49 49 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
50 safe_unicode, remove_suffix, remove_prefix
50 safe_unicode, remove_suffix, remove_prefix, time_to_datetime
51 51 from rhodecode.lib.compat import json
52 52 from rhodecode.lib.caching_query import FromCache
53 53
54 54 from rhodecode.model.meta import Base, Session
55 55
56 56 URL_SEP = '/'
57 57 log = logging.getLogger(__name__)
58 58
59 59 #==============================================================================
60 60 # BASE CLASSES
61 61 #==============================================================================
62 62
63 63 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
64 64
65 65
66 66 class BaseModel(object):
67 67 """
68 68 Base Model for all classess
69 69 """
70 70
71 71 @classmethod
72 72 def _get_keys(cls):
73 73 """return column names for this model """
74 74 return class_mapper(cls).c.keys()
75 75
76 76 def get_dict(self):
77 77 """
78 78 return dict with keys and values corresponding
79 79 to this model data """
80 80
81 81 d = {}
82 82 for k in self._get_keys():
83 83 d[k] = getattr(self, k)
84 84
85 85 # also use __json__() if present to get additional fields
86 86 _json_attr = getattr(self, '__json__', None)
87 87 if _json_attr:
88 88 # update with attributes from __json__
89 89 if callable(_json_attr):
90 90 _json_attr = _json_attr()
91 91 for k, val in _json_attr.iteritems():
92 92 d[k] = val
93 93 return d
94 94
95 95 def get_appstruct(self):
96 96 """return list with keys and values tupples corresponding
97 97 to this model data """
98 98
99 99 l = []
100 100 for k in self._get_keys():
101 101 l.append((k, getattr(self, k),))
102 102 return l
103 103
104 104 def populate_obj(self, populate_dict):
105 105 """populate model with data from given populate_dict"""
106 106
107 107 for k in self._get_keys():
108 108 if k in populate_dict:
109 109 setattr(self, k, populate_dict[k])
110 110
111 111 @classmethod
112 112 def query(cls):
113 113 return Session().query(cls)
114 114
115 115 @classmethod
116 116 def get(cls, id_):
117 117 if id_:
118 118 return cls.query().get(id_)
119 119
120 120 @classmethod
121 121 def get_or_404(cls, id_):
122 122 try:
123 123 id_ = int(id_)
124 124 except (TypeError, ValueError):
125 125 raise HTTPNotFound
126 126
127 127 res = cls.query().get(id_)
128 128 if not res:
129 129 raise HTTPNotFound
130 130 return res
131 131
132 132 @classmethod
133 133 def getAll(cls):
134 134 return cls.query().all()
135 135
136 136 @classmethod
137 137 def delete(cls, id_):
138 138 obj = cls.query().get(id_)
139 139 Session().delete(obj)
140 140
141 141 def __repr__(self):
142 142 if hasattr(self, '__unicode__'):
143 143 # python repr needs to return str
144 144 return safe_str(self.__unicode__())
145 145 return '<DB:%s>' % (self.__class__.__name__)
146 146
147 147
148 148 class RhodeCodeSetting(Base, BaseModel):
149 149 __tablename__ = 'rhodecode_settings'
150 150 __table_args__ = (
151 151 UniqueConstraint('app_settings_name'),
152 152 {'extend_existing': True, 'mysql_engine': 'InnoDB',
153 153 'mysql_charset': 'utf8'}
154 154 )
155 155 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
156 156 app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
157 157 _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
158 158
159 159 def __init__(self, k='', v=''):
160 160 self.app_settings_name = k
161 161 self.app_settings_value = v
162 162
163 163 @validates('_app_settings_value')
164 164 def validate_settings_value(self, key, val):
165 165 assert type(val) == unicode
166 166 return val
167 167
168 168 @hybrid_property
169 169 def app_settings_value(self):
170 170 v = self._app_settings_value
171 171 if self.app_settings_name in ["ldap_active",
172 172 "default_repo_enable_statistics",
173 173 "default_repo_enable_locking",
174 174 "default_repo_private",
175 175 "default_repo_enable_downloads"]:
176 176 v = str2bool(v)
177 177 return v
178 178
179 179 @app_settings_value.setter
180 180 def app_settings_value(self, val):
181 181 """
182 182 Setter that will always make sure we use unicode in app_settings_value
183 183
184 184 :param val:
185 185 """
186 186 self._app_settings_value = safe_unicode(val)
187 187
188 188 def __unicode__(self):
189 189 return u"<%s('%s:%s')>" % (
190 190 self.__class__.__name__,
191 191 self.app_settings_name, self.app_settings_value
192 192 )
193 193
194 194 @classmethod
195 195 def get_by_name(cls, key):
196 196 return cls.query()\
197 197 .filter(cls.app_settings_name == key).scalar()
198 198
199 199 @classmethod
200 200 def get_by_name_or_create(cls, key):
201 201 res = cls.get_by_name(key)
202 202 if not res:
203 203 res = cls(key)
204 204 return res
205 205
206 206 @classmethod
207 207 def get_app_settings(cls, cache=False):
208 208
209 209 ret = cls.query()
210 210
211 211 if cache:
212 212 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
213 213
214 214 if not ret:
215 215 raise Exception('Could not get application settings !')
216 216 settings = {}
217 217 for each in ret:
218 218 settings['rhodecode_' + each.app_settings_name] = \
219 219 each.app_settings_value
220 220
221 221 return settings
222 222
223 223 @classmethod
224 224 def get_ldap_settings(cls, cache=False):
225 225 ret = cls.query()\
226 226 .filter(cls.app_settings_name.startswith('ldap_')).all()
227 227 fd = {}
228 228 for row in ret:
229 229 fd.update({row.app_settings_name: row.app_settings_value})
230 230
231 231 return fd
232 232
233 233 @classmethod
234 234 def get_default_repo_settings(cls, cache=False, strip_prefix=False):
235 235 ret = cls.query()\
236 236 .filter(cls.app_settings_name.startswith('default_')).all()
237 237 fd = {}
238 238 for row in ret:
239 239 key = row.app_settings_name
240 240 if strip_prefix:
241 241 key = remove_prefix(key, prefix='default_')
242 242 fd.update({key: row.app_settings_value})
243 243
244 244 return fd
245 245
246 246
247 247 class RhodeCodeUi(Base, BaseModel):
248 248 __tablename__ = 'rhodecode_ui'
249 249 __table_args__ = (
250 250 UniqueConstraint('ui_key'),
251 251 {'extend_existing': True, 'mysql_engine': 'InnoDB',
252 252 'mysql_charset': 'utf8'}
253 253 )
254 254
255 255 HOOK_UPDATE = 'changegroup.update'
256 256 HOOK_REPO_SIZE = 'changegroup.repo_size'
257 257 HOOK_PUSH = 'changegroup.push_logger'
258 258 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
259 259 HOOK_PULL = 'outgoing.pull_logger'
260 260 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
261 261
262 262 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
263 263 ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
264 264 ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
265 265 ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
266 266 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
267 267
268 268 @classmethod
269 269 def get_by_key(cls, key):
270 270 return cls.query().filter(cls.ui_key == key).scalar()
271 271
272 272 @classmethod
273 273 def get_builtin_hooks(cls):
274 274 q = cls.query()
275 275 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
276 276 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
277 277 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
278 278 return q.all()
279 279
280 280 @classmethod
281 281 def get_custom_hooks(cls):
282 282 q = cls.query()
283 283 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
284 284 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
285 285 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
286 286 q = q.filter(cls.ui_section == 'hooks')
287 287 return q.all()
288 288
289 289 @classmethod
290 290 def get_repos_location(cls):
291 291 return cls.get_by_key('/').ui_value
292 292
293 293 @classmethod
294 294 def create_or_update_hook(cls, key, val):
295 295 new_ui = cls.get_by_key(key) or cls()
296 296 new_ui.ui_section = 'hooks'
297 297 new_ui.ui_active = True
298 298 new_ui.ui_key = key
299 299 new_ui.ui_value = val
300 300
301 301 Session().add(new_ui)
302 302
303 303 def __repr__(self):
304 304 return '<DB:%s[%s:%s]>' % (self.__class__.__name__, self.ui_key,
305 305 self.ui_value)
306 306
307 307
308 308 class User(Base, BaseModel):
309 309 __tablename__ = 'users'
310 310 __table_args__ = (
311 311 UniqueConstraint('username'), UniqueConstraint('email'),
312 312 Index('u_username_idx', 'username'),
313 313 Index('u_email_idx', 'email'),
314 314 {'extend_existing': True, 'mysql_engine': 'InnoDB',
315 315 'mysql_charset': 'utf8'}
316 316 )
317 317 DEFAULT_USER = 'default'
318 318 DEFAULT_PERMISSIONS = [
319 319 'hg.register.manual_activate', 'hg.create.repository',
320 320 'hg.fork.repository', 'repository.read', 'group.read'
321 321 ]
322 322 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
323 323 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
324 324 password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
325 325 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
326 326 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
327 327 name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
328 328 lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
329 329 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
330 330 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
331 331 ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
332 332 api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
333 333 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
334 334
335 335 user_log = relationship('UserLog')
336 336 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
337 337
338 338 repositories = relationship('Repository')
339 339 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
340 340 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
341 341
342 342 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
343 343 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
344 344
345 345 group_member = relationship('UserGroupMember', cascade='all')
346 346
347 347 notifications = relationship('UserNotification', cascade='all')
348 348 # notifications assigned to this user
349 349 user_created_notifications = relationship('Notification', cascade='all')
350 350 # comments created by this user
351 351 user_comments = relationship('ChangesetComment', cascade='all')
352 352 #extra emails for this user
353 353 user_emails = relationship('UserEmailMap', cascade='all')
354 354
355 355 @hybrid_property
356 356 def email(self):
357 357 return self._email
358 358
359 359 @email.setter
360 360 def email(self, val):
361 361 self._email = val.lower() if val else None
362 362
363 363 @property
364 364 def firstname(self):
365 365 # alias for future
366 366 return self.name
367 367
368 368 @property
369 369 def emails(self):
370 370 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
371 371 return [self.email] + [x.email for x in other]
372 372
373 373 @property
374 374 def ip_addresses(self):
375 375 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
376 376 return [x.ip_addr for x in ret]
377 377
378 378 @property
379 379 def username_and_name(self):
380 380 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
381 381
382 382 @property
383 383 def full_name(self):
384 384 return '%s %s' % (self.firstname, self.lastname)
385 385
386 386 @property
387 387 def full_name_or_username(self):
388 388 return ('%s %s' % (self.firstname, self.lastname)
389 389 if (self.firstname and self.lastname) else self.username)
390 390
391 391 @property
392 392 def full_contact(self):
393 393 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
394 394
395 395 @property
396 396 def short_contact(self):
397 397 return '%s %s' % (self.firstname, self.lastname)
398 398
399 399 @property
400 400 def is_admin(self):
401 401 return self.admin
402 402
403 403 @property
404 404 def AuthUser(self):
405 405 """
406 406 Returns instance of AuthUser for this user
407 407 """
408 408 from rhodecode.lib.auth import AuthUser
409 409 return AuthUser(user_id=self.user_id, api_key=self.api_key,
410 410 username=self.username)
411 411
412 412 def __unicode__(self):
413 413 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
414 414 self.user_id, self.username)
415 415
416 416 @classmethod
417 417 def get_by_username(cls, username, case_insensitive=False, cache=False):
418 418 if case_insensitive:
419 419 q = cls.query().filter(cls.username.ilike(username))
420 420 else:
421 421 q = cls.query().filter(cls.username == username)
422 422
423 423 if cache:
424 424 q = q.options(FromCache(
425 425 "sql_cache_short",
426 426 "get_user_%s" % _hash_key(username)
427 427 )
428 428 )
429 429 return q.scalar()
430 430
431 431 @classmethod
432 432 def get_by_api_key(cls, api_key, cache=False):
433 433 q = cls.query().filter(cls.api_key == api_key)
434 434
435 435 if cache:
436 436 q = q.options(FromCache("sql_cache_short",
437 437 "get_api_key_%s" % api_key))
438 438 return q.scalar()
439 439
440 440 @classmethod
441 441 def get_by_email(cls, email, case_insensitive=False, cache=False):
442 442 if case_insensitive:
443 443 q = cls.query().filter(cls.email.ilike(email))
444 444 else:
445 445 q = cls.query().filter(cls.email == email)
446 446
447 447 if cache:
448 448 q = q.options(FromCache("sql_cache_short",
449 449 "get_email_key_%s" % email))
450 450
451 451 ret = q.scalar()
452 452 if ret is None:
453 453 q = UserEmailMap.query()
454 454 # try fetching in alternate email map
455 455 if case_insensitive:
456 456 q = q.filter(UserEmailMap.email.ilike(email))
457 457 else:
458 458 q = q.filter(UserEmailMap.email == email)
459 459 q = q.options(joinedload(UserEmailMap.user))
460 460 if cache:
461 461 q = q.options(FromCache("sql_cache_short",
462 462 "get_email_map_key_%s" % email))
463 463 ret = getattr(q.scalar(), 'user', None)
464 464
465 465 return ret
466 466
467 467 @classmethod
468 468 def get_from_cs_author(cls, author):
469 469 """
470 470 Tries to get User objects out of commit author string
471 471
472 472 :param author:
473 473 """
474 474 from rhodecode.lib.helpers import email, author_name
475 475 # Valid email in the attribute passed, see if they're in the system
476 476 _email = email(author)
477 477 if _email:
478 478 user = cls.get_by_email(_email, case_insensitive=True)
479 479 if user:
480 480 return user
481 481 # Maybe we can match by username?
482 482 _author = author_name(author)
483 483 user = cls.get_by_username(_author, case_insensitive=True)
484 484 if user:
485 485 return user
486 486
487 487 def update_lastlogin(self):
488 488 """Update user lastlogin"""
489 489 self.last_login = datetime.datetime.now()
490 490 Session().add(self)
491 491 log.debug('updated user %s lastlogin' % self.username)
492 492
493 493 def get_api_data(self):
494 494 """
495 495 Common function for generating user related data for API
496 496 """
497 497 user = self
498 498 data = dict(
499 499 user_id=user.user_id,
500 500 username=user.username,
501 501 firstname=user.name,
502 502 lastname=user.lastname,
503 503 email=user.email,
504 504 emails=user.emails,
505 505 api_key=user.api_key,
506 506 active=user.active,
507 507 admin=user.admin,
508 508 ldap_dn=user.ldap_dn,
509 509 last_login=user.last_login,
510 510 ip_addresses=user.ip_addresses
511 511 )
512 512 return data
513 513
514 514 def __json__(self):
515 515 data = dict(
516 516 full_name=self.full_name,
517 517 full_name_or_username=self.full_name_or_username,
518 518 short_contact=self.short_contact,
519 519 full_contact=self.full_contact
520 520 )
521 521 data.update(self.get_api_data())
522 522 return data
523 523
524 524
525 525 class UserEmailMap(Base, BaseModel):
526 526 __tablename__ = 'user_email_map'
527 527 __table_args__ = (
528 528 Index('uem_email_idx', 'email'),
529 529 UniqueConstraint('email'),
530 530 {'extend_existing': True, 'mysql_engine': 'InnoDB',
531 531 'mysql_charset': 'utf8'}
532 532 )
533 533 __mapper_args__ = {}
534 534
535 535 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
536 536 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
537 537 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
538 538 user = relationship('User', lazy='joined')
539 539
540 540 @validates('_email')
541 541 def validate_email(self, key, email):
542 542 # check if this email is not main one
543 543 main_email = Session().query(User).filter(User.email == email).scalar()
544 544 if main_email is not None:
545 545 raise AttributeError('email %s is present is user table' % email)
546 546 return email
547 547
548 548 @hybrid_property
549 549 def email(self):
550 550 return self._email
551 551
552 552 @email.setter
553 553 def email(self, val):
554 554 self._email = val.lower() if val else None
555 555
556 556
557 557 class UserIpMap(Base, BaseModel):
558 558 __tablename__ = 'user_ip_map'
559 559 __table_args__ = (
560 560 UniqueConstraint('user_id', 'ip_addr'),
561 561 {'extend_existing': True, 'mysql_engine': 'InnoDB',
562 562 'mysql_charset': 'utf8'}
563 563 )
564 564 __mapper_args__ = {}
565 565
566 566 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
567 567 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
568 568 ip_addr = Column("ip_addr", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
569 569 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
570 570 user = relationship('User', lazy='joined')
571 571
572 572 @classmethod
573 573 def _get_ip_range(cls, ip_addr):
574 574 from rhodecode.lib import ipaddr
575 575 net = ipaddr.IPNetwork(address=ip_addr)
576 576 return [str(net.network), str(net.broadcast)]
577 577
578 578 def __json__(self):
579 579 return dict(
580 580 ip_addr=self.ip_addr,
581 581 ip_range=self._get_ip_range(self.ip_addr)
582 582 )
583 583
584 584
585 585 class UserLog(Base, BaseModel):
586 586 __tablename__ = 'user_logs'
587 587 __table_args__ = (
588 588 {'extend_existing': True, 'mysql_engine': 'InnoDB',
589 589 'mysql_charset': 'utf8'},
590 590 )
591 591 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
592 592 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
593 593 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
594 594 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
595 595 repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
596 596 user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
597 597 action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
598 598 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
599 599
600 600 @property
601 601 def action_as_day(self):
602 602 return datetime.date(*self.action_date.timetuple()[:3])
603 603
604 604 user = relationship('User')
605 605 repository = relationship('Repository', cascade='')
606 606
607 607
608 608 class UserGroup(Base, BaseModel):
609 609 __tablename__ = 'users_groups'
610 610 __table_args__ = (
611 611 {'extend_existing': True, 'mysql_engine': 'InnoDB',
612 612 'mysql_charset': 'utf8'},
613 613 )
614 614
615 615 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
616 616 users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
617 617 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
618 618 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
619 619
620 620 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
621 621 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
622 622 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
623 623
624 624 def __unicode__(self):
625 625 return u'<userGroup(%s)>' % (self.users_group_name)
626 626
627 627 @classmethod
628 628 def get_by_group_name(cls, group_name, cache=False,
629 629 case_insensitive=False):
630 630 if case_insensitive:
631 631 q = cls.query().filter(cls.users_group_name.ilike(group_name))
632 632 else:
633 633 q = cls.query().filter(cls.users_group_name == group_name)
634 634 if cache:
635 635 q = q.options(FromCache(
636 636 "sql_cache_short",
637 637 "get_user_%s" % _hash_key(group_name)
638 638 )
639 639 )
640 640 return q.scalar()
641 641
642 642 @classmethod
643 643 def get(cls, users_group_id, cache=False):
644 644 users_group = cls.query()
645 645 if cache:
646 646 users_group = users_group.options(FromCache("sql_cache_short",
647 647 "get_users_group_%s" % users_group_id))
648 648 return users_group.get(users_group_id)
649 649
650 650 def get_api_data(self):
651 651 users_group = self
652 652
653 653 data = dict(
654 654 users_group_id=users_group.users_group_id,
655 655 group_name=users_group.users_group_name,
656 656 active=users_group.users_group_active,
657 657 )
658 658
659 659 return data
660 660
661 661
662 662 class UserGroupMember(Base, BaseModel):
663 663 __tablename__ = 'users_groups_members'
664 664 __table_args__ = (
665 665 {'extend_existing': True, 'mysql_engine': 'InnoDB',
666 666 'mysql_charset': 'utf8'},
667 667 )
668 668
669 669 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
670 670 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
671 671 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
672 672
673 673 user = relationship('User', lazy='joined')
674 674 users_group = relationship('UserGroup')
675 675
676 676 def __init__(self, gr_id='', u_id=''):
677 677 self.users_group_id = gr_id
678 678 self.user_id = u_id
679 679
680 680
681 681 class RepositoryField(Base, BaseModel):
682 682 __tablename__ = 'repositories_fields'
683 683 __table_args__ = (
684 684 UniqueConstraint('repository_id', 'field_key'), # no-multi field
685 685 {'extend_existing': True, 'mysql_engine': 'InnoDB',
686 686 'mysql_charset': 'utf8'},
687 687 )
688 688 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
689 689
690 690 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
691 691 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
692 692 field_key = Column("field_key", String(250, convert_unicode=False, assert_unicode=None))
693 693 field_label = Column("field_label", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
694 694 field_value = Column("field_value", String(10000, convert_unicode=False, assert_unicode=None), nullable=False)
695 695 field_desc = Column("field_desc", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
696 696 field_type = Column("field_type", String(256), nullable=False, unique=None)
697 697 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
698 698
699 699 repository = relationship('Repository')
700 700
701 701 @property
702 702 def field_key_prefixed(self):
703 703 return 'ex_%s' % self.field_key
704 704
705 705 @classmethod
706 706 def un_prefix_key(cls, key):
707 707 if key.startswith(cls.PREFIX):
708 708 return key[len(cls.PREFIX):]
709 709 return key
710 710
711 711 @classmethod
712 712 def get_by_key_name(cls, key, repo):
713 713 row = cls.query()\
714 714 .filter(cls.repository == repo)\
715 715 .filter(cls.field_key == key).scalar()
716 716 return row
717 717
718 718
719 719 class Repository(Base, BaseModel):
720 720 __tablename__ = 'repositories'
721 721 __table_args__ = (
722 722 UniqueConstraint('repo_name'),
723 723 Index('r_repo_name_idx', 'repo_name'),
724 724 {'extend_existing': True, 'mysql_engine': 'InnoDB',
725 725 'mysql_charset': 'utf8'},
726 726 )
727 727
728 728 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
729 729 repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
730 730 clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
731 731 repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
732 732 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
733 733 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
734 734 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
735 735 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
736 736 description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
737 737 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
738 738 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
739 739 landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
740 740 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
741 741 _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
742 742 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
743 743
744 744 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
745 745 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
746 746
747 747 user = relationship('User')
748 748 fork = relationship('Repository', remote_side=repo_id)
749 749 group = relationship('RepoGroup')
750 750 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
751 751 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
752 752 stats = relationship('Statistics', cascade='all', uselist=False)
753 753
754 754 followers = relationship('UserFollowing',
755 755 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
756 756 cascade='all')
757 757 extra_fields = relationship('RepositoryField',
758 758 cascade="all, delete, delete-orphan")
759 759
760 760 logs = relationship('UserLog')
761 761 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
762 762
763 763 pull_requests_org = relationship('PullRequest',
764 764 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
765 765 cascade="all, delete, delete-orphan")
766 766
767 767 pull_requests_other = relationship('PullRequest',
768 768 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
769 769 cascade="all, delete, delete-orphan")
770 770
771 771 def __unicode__(self):
772 772 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
773 773 self.repo_name)
774 774
775 775 @hybrid_property
776 776 def locked(self):
777 777 # always should return [user_id, timelocked]
778 778 if self._locked:
779 779 _lock_info = self._locked.split(':')
780 780 return int(_lock_info[0]), _lock_info[1]
781 781 return [None, None]
782 782
783 783 @locked.setter
784 784 def locked(self, val):
785 785 if val and isinstance(val, (list, tuple)):
786 786 self._locked = ':'.join(map(str, val))
787 787 else:
788 788 self._locked = None
789 789
790 790 @hybrid_property
791 791 def changeset_cache(self):
792 792 from rhodecode.lib.vcs.backends.base import EmptyChangeset
793 793 dummy = EmptyChangeset().__json__()
794 794 if not self._changeset_cache:
795 795 return dummy
796 796 try:
797 797 return json.loads(self._changeset_cache)
798 798 except TypeError:
799 799 return dummy
800 800
801 801 @changeset_cache.setter
802 802 def changeset_cache(self, val):
803 803 try:
804 804 self._changeset_cache = json.dumps(val)
805 805 except:
806 806 log.error(traceback.format_exc())
807 807
808 808 @classmethod
809 809 def url_sep(cls):
810 810 return URL_SEP
811 811
812 812 @classmethod
813 813 def normalize_repo_name(cls, repo_name):
814 814 """
815 815 Normalizes os specific repo_name to the format internally stored inside
816 816 dabatabase using URL_SEP
817 817
818 818 :param cls:
819 819 :param repo_name:
820 820 """
821 821 return cls.url_sep().join(repo_name.split(os.sep))
822 822
823 823 @classmethod
824 824 def get_by_repo_name(cls, repo_name):
825 825 q = Session().query(cls).filter(cls.repo_name == repo_name)
826 826 q = q.options(joinedload(Repository.fork))\
827 827 .options(joinedload(Repository.user))\
828 828 .options(joinedload(Repository.group))
829 829 return q.scalar()
830 830
831 831 @classmethod
832 832 def get_by_full_path(cls, repo_full_path):
833 833 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
834 834 repo_name = cls.normalize_repo_name(repo_name)
835 835 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
836 836
837 837 @classmethod
838 838 def get_repo_forks(cls, repo_id):
839 839 return cls.query().filter(Repository.fork_id == repo_id)
840 840
841 841 @classmethod
842 842 def base_path(cls):
843 843 """
844 844 Returns base path when all repos are stored
845 845
846 846 :param cls:
847 847 """
848 848 q = Session().query(RhodeCodeUi)\
849 849 .filter(RhodeCodeUi.ui_key == cls.url_sep())
850 850 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
851 851 return q.one().ui_value
852 852
853 853 @property
854 854 def forks(self):
855 855 """
856 856 Return forks of this repo
857 857 """
858 858 return Repository.get_repo_forks(self.repo_id)
859 859
860 860 @property
861 861 def parent(self):
862 862 """
863 863 Returns fork parent
864 864 """
865 865 return self.fork
866 866
867 867 @property
868 868 def just_name(self):
869 869 return self.repo_name.split(Repository.url_sep())[-1]
870 870
871 871 @property
872 872 def groups_with_parents(self):
873 873 groups = []
874 874 if self.group is None:
875 875 return groups
876 876
877 877 cur_gr = self.group
878 878 groups.insert(0, cur_gr)
879 879 while 1:
880 880 gr = getattr(cur_gr, 'parent_group', None)
881 881 cur_gr = cur_gr.parent_group
882 882 if gr is None:
883 883 break
884 884 groups.insert(0, gr)
885 885
886 886 return groups
887 887
888 888 @property
889 889 def groups_and_repo(self):
890 890 return self.groups_with_parents, self.just_name
891 891
892 892 @LazyProperty
893 893 def repo_path(self):
894 894 """
895 895 Returns base full path for that repository means where it actually
896 896 exists on a filesystem
897 897 """
898 898 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
899 899 Repository.url_sep())
900 900 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
901 901 return q.one().ui_value
902 902
903 903 @property
904 904 def repo_full_path(self):
905 905 p = [self.repo_path]
906 906 # we need to split the name by / since this is how we store the
907 907 # names in the database, but that eventually needs to be converted
908 908 # into a valid system path
909 909 p += self.repo_name.split(Repository.url_sep())
910 910 return os.path.join(*p)
911 911
912 912 @property
913 913 def cache_keys(self):
914 914 """
915 915 Returns associated cache keys for that repo
916 916 """
917 917 return CacheInvalidation.query()\
918 918 .filter(CacheInvalidation.cache_args == self.repo_name)\
919 919 .order_by(CacheInvalidation.cache_key)\
920 920 .all()
921 921
922 922 def get_new_name(self, repo_name):
923 923 """
924 924 returns new full repository name based on assigned group and new new
925 925
926 926 :param group_name:
927 927 """
928 928 path_prefix = self.group.full_path_splitted if self.group else []
929 929 return Repository.url_sep().join(path_prefix + [repo_name])
930 930
931 931 @property
932 932 def _ui(self):
933 933 """
934 934 Creates an db based ui object for this repository
935 935 """
936 936 from rhodecode.lib.utils import make_ui
937 937 return make_ui('db', clear_session=False)
938 938
939 939 @classmethod
940 940 def inject_ui(cls, repo, extras={}):
941 941 repo.inject_ui(extras)
942 942
943 943 @classmethod
944 944 def is_valid(cls, repo_name):
945 945 """
946 946 returns True if given repo name is a valid filesystem repository
947 947
948 948 :param cls:
949 949 :param repo_name:
950 950 """
951 951 from rhodecode.lib.utils import is_valid_repo
952 952
953 953 return is_valid_repo(repo_name, cls.base_path())
954 954
955 955 def get_api_data(self):
956 956 """
957 957 Common function for generating repo api data
958 958
959 959 """
960 960 repo = self
961 961 data = dict(
962 962 repo_id=repo.repo_id,
963 963 repo_name=repo.repo_name,
964 964 repo_type=repo.repo_type,
965 965 clone_uri=repo.clone_uri,
966 966 private=repo.private,
967 967 created_on=repo.created_on,
968 968 description=repo.description,
969 969 landing_rev=repo.landing_rev,
970 970 owner=repo.user.username,
971 971 fork_of=repo.fork.repo_name if repo.fork else None,
972 972 enable_statistics=repo.enable_statistics,
973 973 enable_locking=repo.enable_locking,
974 974 enable_downloads=repo.enable_downloads,
975 last_changeset=repo.changeset_cache
975 last_changeset=repo.changeset_cache,
976 locked_by=User.get(self.locked[0]).get_api_data() \
977 if self.locked[0] else None,
978 locked_date=time_to_datetime(self.locked[1]) \
979 if self.locked[1] else None
976 980 )
977 981 rc_config = RhodeCodeSetting.get_app_settings()
978 982 repository_fields = str2bool(rc_config.get('rhodecode_repository_fields'))
979 983 if repository_fields:
980 984 for f in self.extra_fields:
981 985 data[f.field_key_prefixed] = f.field_value
982 986
983 987 return data
984 988
985 989 @classmethod
986 990 def lock(cls, repo, user_id):
987 991 repo.locked = [user_id, time.time()]
988 992 Session().add(repo)
989 993 Session().commit()
990 994
991 995 @classmethod
992 996 def unlock(cls, repo):
993 997 repo.locked = None
994 998 Session().add(repo)
995 999 Session().commit()
996 1000
997 1001 @classmethod
998 1002 def getlock(cls, repo):
999 1003 return repo.locked
1000 1004
1001 1005 @property
1002 1006 def last_db_change(self):
1003 1007 return self.updated_on
1004 1008
1005 1009 def clone_url(self, **override):
1006 1010 from pylons import url
1007 1011 from urlparse import urlparse
1008 1012 import urllib
1009 1013 parsed_url = urlparse(url('home', qualified=True))
1010 1014 default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s'
1011 1015 decoded_path = safe_unicode(urllib.unquote(parsed_url.path))
1012 1016 args = {
1013 1017 'user': '',
1014 1018 'pass': '',
1015 1019 'scheme': parsed_url.scheme,
1016 1020 'netloc': parsed_url.netloc,
1017 1021 'prefix': decoded_path,
1018 1022 'path': self.repo_name
1019 1023 }
1020 1024
1021 1025 args.update(override)
1022 1026 return default_clone_uri % args
1023 1027
1024 1028 #==========================================================================
1025 1029 # SCM PROPERTIES
1026 1030 #==========================================================================
1027 1031
1028 1032 def get_changeset(self, rev=None):
1029 1033 return get_changeset_safe(self.scm_instance, rev)
1030 1034
1031 1035 def get_landing_changeset(self):
1032 1036 """
1033 1037 Returns landing changeset, or if that doesn't exist returns the tip
1034 1038 """
1035 1039 cs = self.get_changeset(self.landing_rev) or self.get_changeset()
1036 1040 return cs
1037 1041
1038 1042 def update_changeset_cache(self, cs_cache=None):
1039 1043 """
1040 1044 Update cache of last changeset for repository, keys should be::
1041 1045
1042 1046 short_id
1043 1047 raw_id
1044 1048 revision
1045 1049 message
1046 1050 date
1047 1051 author
1048 1052
1049 1053 :param cs_cache:
1050 1054 """
1051 1055 from rhodecode.lib.vcs.backends.base import BaseChangeset
1052 1056 if cs_cache is None:
1053 1057 cs_cache = EmptyChangeset()
1054 1058 # use no-cache version here
1055 1059 scm_repo = self.scm_instance_no_cache
1056 1060 if scm_repo:
1057 1061 cs_cache = scm_repo.get_changeset()
1058 1062
1059 1063 if isinstance(cs_cache, BaseChangeset):
1060 1064 cs_cache = cs_cache.__json__()
1061 1065
1062 1066 if (cs_cache != self.changeset_cache or not self.changeset_cache):
1063 1067 _default = datetime.datetime.fromtimestamp(0)
1064 1068 last_change = cs_cache.get('date') or _default
1065 1069 log.debug('updated repo %s with new cs cache %s' % (self, cs_cache))
1066 1070 self.updated_on = last_change
1067 1071 self.changeset_cache = cs_cache
1068 1072 Session().add(self)
1069 1073 Session().commit()
1070 1074 else:
1071 1075 log.debug('Skipping repo:%s already with latest changes' % self)
1072 1076
1073 1077 @property
1074 1078 def tip(self):
1075 1079 return self.get_changeset('tip')
1076 1080
1077 1081 @property
1078 1082 def author(self):
1079 1083 return self.tip.author
1080 1084
1081 1085 @property
1082 1086 def last_change(self):
1083 1087 return self.scm_instance.last_change
1084 1088
1085 1089 def get_comments(self, revisions=None):
1086 1090 """
1087 1091 Returns comments for this repository grouped by revisions
1088 1092
1089 1093 :param revisions: filter query by revisions only
1090 1094 """
1091 1095 cmts = ChangesetComment.query()\
1092 1096 .filter(ChangesetComment.repo == self)
1093 1097 if revisions:
1094 1098 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1095 1099 grouped = defaultdict(list)
1096 1100 for cmt in cmts.all():
1097 1101 grouped[cmt.revision].append(cmt)
1098 1102 return grouped
1099 1103
1100 1104 def statuses(self, revisions=None):
1101 1105 """
1102 1106 Returns statuses for this repository
1103 1107
1104 1108 :param revisions: list of revisions to get statuses for
1105 1109 :type revisions: list
1106 1110 """
1107 1111
1108 1112 statuses = ChangesetStatus.query()\
1109 1113 .filter(ChangesetStatus.repo == self)\
1110 1114 .filter(ChangesetStatus.version == 0)
1111 1115 if revisions:
1112 1116 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
1113 1117 grouped = {}
1114 1118
1115 1119 #maybe we have open new pullrequest without a status ?
1116 1120 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1117 1121 status_lbl = ChangesetStatus.get_status_lbl(stat)
1118 1122 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
1119 1123 for rev in pr.revisions:
1120 1124 pr_id = pr.pull_request_id
1121 1125 pr_repo = pr.other_repo.repo_name
1122 1126 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1123 1127
1124 1128 for stat in statuses.all():
1125 1129 pr_id = pr_repo = None
1126 1130 if stat.pull_request:
1127 1131 pr_id = stat.pull_request.pull_request_id
1128 1132 pr_repo = stat.pull_request.other_repo.repo_name
1129 1133 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1130 1134 pr_id, pr_repo]
1131 1135 return grouped
1132 1136
1133 1137 def _repo_size(self):
1134 1138 from rhodecode.lib import helpers as h
1135 1139 log.debug('calculating repository size...')
1136 1140 return h.format_byte_size(self.scm_instance.size)
1137 1141
1138 1142 #==========================================================================
1139 1143 # SCM CACHE INSTANCE
1140 1144 #==========================================================================
1141 1145
1142 1146 @property
1143 1147 def invalidate(self):
1144 1148 return CacheInvalidation.invalidate(self.repo_name)
1145 1149
1146 1150 def set_invalidate(self):
1147 1151 """
1148 1152 set a cache for invalidation for this instance
1149 1153 """
1150 1154 CacheInvalidation.set_invalidate(repo_name=self.repo_name)
1151 1155
1152 1156 @LazyProperty
1153 1157 def scm_instance_no_cache(self):
1154 1158 return self.__get_instance()
1155 1159
1156 1160 @LazyProperty
1157 1161 def scm_instance(self):
1158 1162 import rhodecode
1159 1163 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1160 1164 if full_cache:
1161 1165 return self.scm_instance_cached()
1162 1166 return self.__get_instance()
1163 1167
1164 1168 def scm_instance_cached(self, cache_map=None):
1165 1169 @cache_region('long_term')
1166 1170 def _c(repo_name):
1167 1171 return self.__get_instance()
1168 1172 rn = self.repo_name
1169 1173 log.debug('Getting cached instance of repo')
1170 1174
1171 1175 if cache_map:
1172 1176 # get using prefilled cache_map
1173 1177 invalidate_repo = cache_map[self.repo_name]
1174 1178 if invalidate_repo:
1175 1179 invalidate_repo = (None if invalidate_repo.cache_active
1176 1180 else invalidate_repo)
1177 1181 else:
1178 1182 # get from invalidate
1179 1183 invalidate_repo = self.invalidate
1180 1184
1181 1185 if invalidate_repo is not None:
1182 1186 region_invalidate(_c, None, rn)
1183 1187 # update our cache
1184 1188 CacheInvalidation.set_valid(invalidate_repo.cache_key)
1185 1189 return _c(rn)
1186 1190
1187 1191 def __get_instance(self):
1188 1192 repo_full_path = self.repo_full_path
1189 1193 try:
1190 1194 alias = get_scm(repo_full_path)[0]
1191 1195 log.debug('Creating instance of %s repository from %s'
1192 1196 % (alias, repo_full_path))
1193 1197 backend = get_backend(alias)
1194 1198 except VCSError:
1195 1199 log.error(traceback.format_exc())
1196 1200 log.error('Perhaps this repository is in db and not in '
1197 1201 'filesystem run rescan repositories with '
1198 1202 '"destroy old data " option from admin panel')
1199 1203 return
1200 1204
1201 1205 if alias == 'hg':
1202 1206
1203 1207 repo = backend(safe_str(repo_full_path), create=False,
1204 1208 baseui=self._ui)
1205 1209 # skip hidden web repository
1206 1210 if repo._get_hidden():
1207 1211 return
1208 1212 else:
1209 1213 repo = backend(repo_full_path, create=False)
1210 1214
1211 1215 return repo
1212 1216
1213 1217
1214 1218 class RepoGroup(Base, BaseModel):
1215 1219 __tablename__ = 'groups'
1216 1220 __table_args__ = (
1217 1221 UniqueConstraint('group_name', 'group_parent_id'),
1218 1222 CheckConstraint('group_id != group_parent_id'),
1219 1223 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1220 1224 'mysql_charset': 'utf8'},
1221 1225 )
1222 1226 __mapper_args__ = {'order_by': 'group_name'}
1223 1227
1224 1228 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1225 1229 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
1226 1230 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
1227 1231 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1228 1232 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
1229 1233
1230 1234 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
1231 1235 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1232 1236
1233 1237 parent_group = relationship('RepoGroup', remote_side=group_id)
1234 1238
1235 1239 def __init__(self, group_name='', parent_group=None):
1236 1240 self.group_name = group_name
1237 1241 self.parent_group = parent_group
1238 1242
1239 1243 def __unicode__(self):
1240 1244 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
1241 1245 self.group_name)
1242 1246
1243 1247 @classmethod
1244 1248 def groups_choices(cls, groups=None, show_empty_group=True):
1245 1249 from webhelpers.html import literal as _literal
1246 1250 if not groups:
1247 1251 groups = cls.query().all()
1248 1252
1249 1253 repo_groups = []
1250 1254 if show_empty_group:
1251 1255 repo_groups = [('-1', '-- no parent --')]
1252 1256 sep = ' &raquo; '
1253 1257 _name = lambda k: _literal(sep.join(k))
1254 1258
1255 1259 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
1256 1260 for x in groups])
1257 1261
1258 1262 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
1259 1263 return repo_groups
1260 1264
1261 1265 @classmethod
1262 1266 def url_sep(cls):
1263 1267 return URL_SEP
1264 1268
1265 1269 @classmethod
1266 1270 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
1267 1271 if case_insensitive:
1268 1272 gr = cls.query()\
1269 1273 .filter(cls.group_name.ilike(group_name))
1270 1274 else:
1271 1275 gr = cls.query()\
1272 1276 .filter(cls.group_name == group_name)
1273 1277 if cache:
1274 1278 gr = gr.options(FromCache(
1275 1279 "sql_cache_short",
1276 1280 "get_group_%s" % _hash_key(group_name)
1277 1281 )
1278 1282 )
1279 1283 return gr.scalar()
1280 1284
1281 1285 @property
1282 1286 def parents(self):
1283 1287 parents_recursion_limit = 5
1284 1288 groups = []
1285 1289 if self.parent_group is None:
1286 1290 return groups
1287 1291 cur_gr = self.parent_group
1288 1292 groups.insert(0, cur_gr)
1289 1293 cnt = 0
1290 1294 while 1:
1291 1295 cnt += 1
1292 1296 gr = getattr(cur_gr, 'parent_group', None)
1293 1297 cur_gr = cur_gr.parent_group
1294 1298 if gr is None:
1295 1299 break
1296 1300 if cnt == parents_recursion_limit:
1297 1301 # this will prevent accidental infinit loops
1298 1302 log.error('group nested more than %s' %
1299 1303 parents_recursion_limit)
1300 1304 break
1301 1305
1302 1306 groups.insert(0, gr)
1303 1307 return groups
1304 1308
1305 1309 @property
1306 1310 def children(self):
1307 1311 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1308 1312
1309 1313 @property
1310 1314 def name(self):
1311 1315 return self.group_name.split(RepoGroup.url_sep())[-1]
1312 1316
1313 1317 @property
1314 1318 def full_path(self):
1315 1319 return self.group_name
1316 1320
1317 1321 @property
1318 1322 def full_path_splitted(self):
1319 1323 return self.group_name.split(RepoGroup.url_sep())
1320 1324
1321 1325 @property
1322 1326 def repositories(self):
1323 1327 return Repository.query()\
1324 1328 .filter(Repository.group == self)\
1325 1329 .order_by(Repository.repo_name)
1326 1330
1327 1331 @property
1328 1332 def repositories_recursive_count(self):
1329 1333 cnt = self.repositories.count()
1330 1334
1331 1335 def children_count(group):
1332 1336 cnt = 0
1333 1337 for child in group.children:
1334 1338 cnt += child.repositories.count()
1335 1339 cnt += children_count(child)
1336 1340 return cnt
1337 1341
1338 1342 return cnt + children_count(self)
1339 1343
1340 1344 def _recursive_objects(self, include_repos=True):
1341 1345 all_ = []
1342 1346
1343 1347 def _get_members(root_gr):
1344 1348 if include_repos:
1345 1349 for r in root_gr.repositories:
1346 1350 all_.append(r)
1347 1351 childs = root_gr.children.all()
1348 1352 if childs:
1349 1353 for gr in childs:
1350 1354 all_.append(gr)
1351 1355 _get_members(gr)
1352 1356
1353 1357 _get_members(self)
1354 1358 return [self] + all_
1355 1359
1356 1360 def recursive_groups_and_repos(self):
1357 1361 """
1358 1362 Recursive return all groups, with repositories in those groups
1359 1363 """
1360 1364 return self._recursive_objects()
1361 1365
1362 1366 def recursive_groups(self):
1363 1367 """
1364 1368 Returns all children groups for this group including children of children
1365 1369 """
1366 1370 return self._recursive_objects(include_repos=False)
1367 1371
1368 1372 def get_new_name(self, group_name):
1369 1373 """
1370 1374 returns new full group name based on parent and new name
1371 1375
1372 1376 :param group_name:
1373 1377 """
1374 1378 path_prefix = (self.parent_group.full_path_splitted if
1375 1379 self.parent_group else [])
1376 1380 return RepoGroup.url_sep().join(path_prefix + [group_name])
1377 1381
1378 1382
1379 1383 class Permission(Base, BaseModel):
1380 1384 __tablename__ = 'permissions'
1381 1385 __table_args__ = (
1382 1386 Index('p_perm_name_idx', 'permission_name'),
1383 1387 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1384 1388 'mysql_charset': 'utf8'},
1385 1389 )
1386 1390 PERMS = [
1387 1391 ('repository.none', _('Repository no access')),
1388 1392 ('repository.read', _('Repository read access')),
1389 1393 ('repository.write', _('Repository write access')),
1390 1394 ('repository.admin', _('Repository admin access')),
1391 1395
1392 1396 ('group.none', _('Repository group no access')),
1393 1397 ('group.read', _('Repository group read access')),
1394 1398 ('group.write', _('Repository group write access')),
1395 1399 ('group.admin', _('Repository group admin access')),
1396 1400
1397 1401 ('hg.admin', _('RhodeCode Administrator')),
1398 1402 ('hg.create.none', _('Repository creation disabled')),
1399 1403 ('hg.create.repository', _('Repository creation enabled')),
1400 1404 ('hg.fork.none', _('Repository forking disabled')),
1401 1405 ('hg.fork.repository', _('Repository forking enabled')),
1402 1406 ('hg.register.none', _('Register disabled')),
1403 1407 ('hg.register.manual_activate', _('Register new user with RhodeCode '
1404 1408 'with manual activation')),
1405 1409
1406 1410 ('hg.register.auto_activate', _('Register new user with RhodeCode '
1407 1411 'with auto activation')),
1408 1412 ]
1409 1413
1410 1414 # defines which permissions are more important higher the more important
1411 1415 PERM_WEIGHTS = {
1412 1416 'repository.none': 0,
1413 1417 'repository.read': 1,
1414 1418 'repository.write': 3,
1415 1419 'repository.admin': 4,
1416 1420
1417 1421 'group.none': 0,
1418 1422 'group.read': 1,
1419 1423 'group.write': 3,
1420 1424 'group.admin': 4,
1421 1425
1422 1426 'hg.fork.none': 0,
1423 1427 'hg.fork.repository': 1,
1424 1428 'hg.create.none': 0,
1425 1429 'hg.create.repository':1
1426 1430 }
1427 1431
1428 1432 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1429 1433 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1430 1434 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1431 1435
1432 1436 def __unicode__(self):
1433 1437 return u"<%s('%s:%s')>" % (
1434 1438 self.__class__.__name__, self.permission_id, self.permission_name
1435 1439 )
1436 1440
1437 1441 @classmethod
1438 1442 def get_by_key(cls, key):
1439 1443 return cls.query().filter(cls.permission_name == key).scalar()
1440 1444
1441 1445 @classmethod
1442 1446 def get_default_perms(cls, default_user_id):
1443 1447 q = Session().query(UserRepoToPerm, Repository, cls)\
1444 1448 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1445 1449 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1446 1450 .filter(UserRepoToPerm.user_id == default_user_id)
1447 1451
1448 1452 return q.all()
1449 1453
1450 1454 @classmethod
1451 1455 def get_default_group_perms(cls, default_user_id):
1452 1456 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1453 1457 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1454 1458 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1455 1459 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1456 1460
1457 1461 return q.all()
1458 1462
1459 1463
1460 1464 class UserRepoToPerm(Base, BaseModel):
1461 1465 __tablename__ = 'repo_to_perm'
1462 1466 __table_args__ = (
1463 1467 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1464 1468 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1465 1469 'mysql_charset': 'utf8'}
1466 1470 )
1467 1471 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1468 1472 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1469 1473 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1470 1474 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1471 1475
1472 1476 user = relationship('User')
1473 1477 repository = relationship('Repository')
1474 1478 permission = relationship('Permission')
1475 1479
1476 1480 @classmethod
1477 1481 def create(cls, user, repository, permission):
1478 1482 n = cls()
1479 1483 n.user = user
1480 1484 n.repository = repository
1481 1485 n.permission = permission
1482 1486 Session().add(n)
1483 1487 return n
1484 1488
1485 1489 def __unicode__(self):
1486 1490 return u'<user:%s => %s >' % (self.user, self.repository)
1487 1491
1488 1492
1489 1493 class UserToPerm(Base, BaseModel):
1490 1494 __tablename__ = 'user_to_perm'
1491 1495 __table_args__ = (
1492 1496 UniqueConstraint('user_id', 'permission_id'),
1493 1497 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1494 1498 'mysql_charset': 'utf8'}
1495 1499 )
1496 1500 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1497 1501 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1498 1502 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1499 1503
1500 1504 user = relationship('User')
1501 1505 permission = relationship('Permission', lazy='joined')
1502 1506
1503 1507
1504 1508 class UserGroupRepoToPerm(Base, BaseModel):
1505 1509 __tablename__ = 'users_group_repo_to_perm'
1506 1510 __table_args__ = (
1507 1511 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1508 1512 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1509 1513 'mysql_charset': 'utf8'}
1510 1514 )
1511 1515 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1512 1516 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1513 1517 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1514 1518 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1515 1519
1516 1520 users_group = relationship('UserGroup')
1517 1521 permission = relationship('Permission')
1518 1522 repository = relationship('Repository')
1519 1523
1520 1524 @classmethod
1521 1525 def create(cls, users_group, repository, permission):
1522 1526 n = cls()
1523 1527 n.users_group = users_group
1524 1528 n.repository = repository
1525 1529 n.permission = permission
1526 1530 Session().add(n)
1527 1531 return n
1528 1532
1529 1533 def __unicode__(self):
1530 1534 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1531 1535
1532 1536
1533 1537 class UserGroupToPerm(Base, BaseModel):
1534 1538 __tablename__ = 'users_group_to_perm'
1535 1539 __table_args__ = (
1536 1540 UniqueConstraint('users_group_id', 'permission_id',),
1537 1541 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1538 1542 'mysql_charset': 'utf8'}
1539 1543 )
1540 1544 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1541 1545 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1542 1546 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1543 1547
1544 1548 users_group = relationship('UserGroup')
1545 1549 permission = relationship('Permission')
1546 1550
1547 1551
1548 1552 class UserRepoGroupToPerm(Base, BaseModel):
1549 1553 __tablename__ = 'user_repo_group_to_perm'
1550 1554 __table_args__ = (
1551 1555 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1552 1556 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1553 1557 'mysql_charset': 'utf8'}
1554 1558 )
1555 1559
1556 1560 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1557 1561 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1558 1562 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1559 1563 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1560 1564
1561 1565 user = relationship('User')
1562 1566 group = relationship('RepoGroup')
1563 1567 permission = relationship('Permission')
1564 1568
1565 1569
1566 1570 class UserGroupRepoGroupToPerm(Base, BaseModel):
1567 1571 __tablename__ = 'users_group_repo_group_to_perm'
1568 1572 __table_args__ = (
1569 1573 UniqueConstraint('users_group_id', 'group_id'),
1570 1574 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1571 1575 'mysql_charset': 'utf8'}
1572 1576 )
1573 1577
1574 1578 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1575 1579 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1576 1580 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1577 1581 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1578 1582
1579 1583 users_group = relationship('UserGroup')
1580 1584 permission = relationship('Permission')
1581 1585 group = relationship('RepoGroup')
1582 1586
1583 1587
1584 1588 class Statistics(Base, BaseModel):
1585 1589 __tablename__ = 'statistics'
1586 1590 __table_args__ = (
1587 1591 UniqueConstraint('repository_id'),
1588 1592 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1589 1593 'mysql_charset': 'utf8'}
1590 1594 )
1591 1595 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1592 1596 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1593 1597 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1594 1598 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1595 1599 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1596 1600 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1597 1601
1598 1602 repository = relationship('Repository', single_parent=True)
1599 1603
1600 1604
1601 1605 class UserFollowing(Base, BaseModel):
1602 1606 __tablename__ = 'user_followings'
1603 1607 __table_args__ = (
1604 1608 UniqueConstraint('user_id', 'follows_repository_id'),
1605 1609 UniqueConstraint('user_id', 'follows_user_id'),
1606 1610 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1607 1611 'mysql_charset': 'utf8'}
1608 1612 )
1609 1613
1610 1614 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1611 1615 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1612 1616 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1613 1617 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1614 1618 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1615 1619
1616 1620 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1617 1621
1618 1622 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1619 1623 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1620 1624
1621 1625 @classmethod
1622 1626 def get_repo_followers(cls, repo_id):
1623 1627 return cls.query().filter(cls.follows_repo_id == repo_id)
1624 1628
1625 1629
1626 1630 class CacheInvalidation(Base, BaseModel):
1627 1631 __tablename__ = 'cache_invalidation'
1628 1632 __table_args__ = (
1629 1633 UniqueConstraint('cache_key'),
1630 1634 Index('key_idx', 'cache_key'),
1631 1635 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1632 1636 'mysql_charset': 'utf8'},
1633 1637 )
1634 1638 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1635 1639 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1636 1640 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1637 1641 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1638 1642
1639 1643 def __init__(self, cache_key, cache_args=''):
1640 1644 self.cache_key = cache_key
1641 1645 self.cache_args = cache_args
1642 1646 self.cache_active = False
1643 1647
1644 1648 def __unicode__(self):
1645 1649 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1646 1650 self.cache_id, self.cache_key)
1647 1651
1648 1652 @property
1649 1653 def prefix(self):
1650 1654 _split = self.cache_key.split(self.cache_args, 1)
1651 1655 if _split and len(_split) == 2:
1652 1656 return _split[0]
1653 1657 return ''
1654 1658
1655 1659 @classmethod
1656 1660 def clear_cache(cls):
1657 1661 cls.query().delete()
1658 1662
1659 1663 @classmethod
1660 1664 def _get_key(cls, key):
1661 1665 """
1662 1666 Wrapper for generating a key, together with a prefix
1663 1667
1664 1668 :param key:
1665 1669 """
1666 1670 import rhodecode
1667 1671 prefix = ''
1668 1672 org_key = key
1669 1673 iid = rhodecode.CONFIG.get('instance_id')
1670 1674 if iid:
1671 1675 prefix = iid
1672 1676
1673 1677 return "%s%s" % (prefix, key), prefix, org_key
1674 1678
1675 1679 @classmethod
1676 1680 def get_by_key(cls, key):
1677 1681 return cls.query().filter(cls.cache_key == key).scalar()
1678 1682
1679 1683 @classmethod
1680 1684 def get_by_repo_name(cls, repo_name):
1681 1685 return cls.query().filter(cls.cache_args == repo_name).all()
1682 1686
1683 1687 @classmethod
1684 1688 def _get_or_create_key(cls, key, repo_name, commit=True):
1685 1689 inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar()
1686 1690 if not inv_obj:
1687 1691 try:
1688 1692 inv_obj = CacheInvalidation(key, repo_name)
1689 1693 Session().add(inv_obj)
1690 1694 if commit:
1691 1695 Session().commit()
1692 1696 except Exception:
1693 1697 log.error(traceback.format_exc())
1694 1698 Session().rollback()
1695 1699 return inv_obj
1696 1700
1697 1701 @classmethod
1698 1702 def invalidate(cls, key):
1699 1703 """
1700 1704 Returns Invalidation object if this given key should be invalidated
1701 1705 None otherwise. `cache_active = False` means that this cache
1702 1706 state is not valid and needs to be invalidated
1703 1707
1704 1708 :param key:
1705 1709 """
1706 1710 repo_name = key
1707 1711 repo_name = remove_suffix(repo_name, '_README')
1708 1712 repo_name = remove_suffix(repo_name, '_RSS')
1709 1713 repo_name = remove_suffix(repo_name, '_ATOM')
1710 1714
1711 1715 # adds instance prefix
1712 1716 key, _prefix, _org_key = cls._get_key(key)
1713 1717 inv = cls._get_or_create_key(key, repo_name)
1714 1718
1715 1719 if inv and inv.cache_active is False:
1716 1720 return inv
1717 1721
1718 1722 @classmethod
1719 1723 def set_invalidate(cls, key=None, repo_name=None):
1720 1724 """
1721 1725 Mark this Cache key for invalidation, either by key or whole
1722 1726 cache sets based on repo_name
1723 1727
1724 1728 :param key:
1725 1729 """
1726 1730 invalidated_keys = []
1727 1731 if key:
1728 1732 key, _prefix, _org_key = cls._get_key(key)
1729 1733 inv_objs = Session().query(cls).filter(cls.cache_key == key).all()
1730 1734 elif repo_name:
1731 1735 inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
1732 1736
1733 1737 try:
1734 1738 for inv_obj in inv_objs:
1735 1739 inv_obj.cache_active = False
1736 1740 log.debug('marking %s key for invalidation based on key=%s,repo_name=%s'
1737 1741 % (inv_obj, key, safe_str(repo_name)))
1738 1742 invalidated_keys.append(inv_obj.cache_key)
1739 1743 Session().add(inv_obj)
1740 1744 Session().commit()
1741 1745 except Exception:
1742 1746 log.error(traceback.format_exc())
1743 1747 Session().rollback()
1744 1748 return invalidated_keys
1745 1749
1746 1750 @classmethod
1747 1751 def set_valid(cls, key):
1748 1752 """
1749 1753 Mark this cache key as active and currently cached
1750 1754
1751 1755 :param key:
1752 1756 """
1753 1757 inv_obj = cls.get_by_key(key)
1754 1758 inv_obj.cache_active = True
1755 1759 Session().add(inv_obj)
1756 1760 Session().commit()
1757 1761
1758 1762 @classmethod
1759 1763 def get_cache_map(cls):
1760 1764
1761 1765 class cachemapdict(dict):
1762 1766
1763 1767 def __init__(self, *args, **kwargs):
1764 1768 fixkey = kwargs.get('fixkey')
1765 1769 if fixkey:
1766 1770 del kwargs['fixkey']
1767 1771 self.fixkey = fixkey
1768 1772 super(cachemapdict, self).__init__(*args, **kwargs)
1769 1773
1770 1774 def __getattr__(self, name):
1771 1775 key = name
1772 1776 if self.fixkey:
1773 1777 key, _prefix, _org_key = cls._get_key(key)
1774 1778 if key in self.__dict__:
1775 1779 return self.__dict__[key]
1776 1780 else:
1777 1781 return self[key]
1778 1782
1779 1783 def __getitem__(self, key):
1780 1784 if self.fixkey:
1781 1785 key, _prefix, _org_key = cls._get_key(key)
1782 1786 try:
1783 1787 return super(cachemapdict, self).__getitem__(key)
1784 1788 except KeyError:
1785 1789 return
1786 1790
1787 1791 cache_map = cachemapdict(fixkey=True)
1788 1792 for obj in cls.query().all():
1789 1793 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1790 1794 return cache_map
1791 1795
1792 1796
1793 1797 class ChangesetComment(Base, BaseModel):
1794 1798 __tablename__ = 'changeset_comments'
1795 1799 __table_args__ = (
1796 1800 Index('cc_revision_idx', 'revision'),
1797 1801 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1798 1802 'mysql_charset': 'utf8'},
1799 1803 )
1800 1804 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1801 1805 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1802 1806 revision = Column('revision', String(40), nullable=True)
1803 1807 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1804 1808 line_no = Column('line_no', Unicode(10), nullable=True)
1805 1809 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
1806 1810 f_path = Column('f_path', Unicode(1000), nullable=True)
1807 1811 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1808 1812 text = Column('text', UnicodeText(25000), nullable=False)
1809 1813 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1810 1814 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1811 1815
1812 1816 author = relationship('User', lazy='joined')
1813 1817 repo = relationship('Repository')
1814 1818 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
1815 1819 pull_request = relationship('PullRequest', lazy='joined')
1816 1820
1817 1821 @classmethod
1818 1822 def get_users(cls, revision=None, pull_request_id=None):
1819 1823 """
1820 1824 Returns user associated with this ChangesetComment. ie those
1821 1825 who actually commented
1822 1826
1823 1827 :param cls:
1824 1828 :param revision:
1825 1829 """
1826 1830 q = Session().query(User)\
1827 1831 .join(ChangesetComment.author)
1828 1832 if revision:
1829 1833 q = q.filter(cls.revision == revision)
1830 1834 elif pull_request_id:
1831 1835 q = q.filter(cls.pull_request_id == pull_request_id)
1832 1836 return q.all()
1833 1837
1834 1838
1835 1839 class ChangesetStatus(Base, BaseModel):
1836 1840 __tablename__ = 'changeset_statuses'
1837 1841 __table_args__ = (
1838 1842 Index('cs_revision_idx', 'revision'),
1839 1843 Index('cs_version_idx', 'version'),
1840 1844 UniqueConstraint('repo_id', 'revision', 'version'),
1841 1845 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1842 1846 'mysql_charset': 'utf8'}
1843 1847 )
1844 1848 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1845 1849 STATUS_APPROVED = 'approved'
1846 1850 STATUS_REJECTED = 'rejected'
1847 1851 STATUS_UNDER_REVIEW = 'under_review'
1848 1852
1849 1853 STATUSES = [
1850 1854 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1851 1855 (STATUS_APPROVED, _("Approved")),
1852 1856 (STATUS_REJECTED, _("Rejected")),
1853 1857 (STATUS_UNDER_REVIEW, _("Under Review")),
1854 1858 ]
1855 1859
1856 1860 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1857 1861 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1858 1862 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1859 1863 revision = Column('revision', String(40), nullable=False)
1860 1864 status = Column('status', String(128), nullable=False, default=DEFAULT)
1861 1865 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1862 1866 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1863 1867 version = Column('version', Integer(), nullable=False, default=0)
1864 1868 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1865 1869
1866 1870 author = relationship('User', lazy='joined')
1867 1871 repo = relationship('Repository')
1868 1872 comment = relationship('ChangesetComment', lazy='joined')
1869 1873 pull_request = relationship('PullRequest', lazy='joined')
1870 1874
1871 1875 def __unicode__(self):
1872 1876 return u"<%s('%s:%s')>" % (
1873 1877 self.__class__.__name__,
1874 1878 self.status, self.author
1875 1879 )
1876 1880
1877 1881 @classmethod
1878 1882 def get_status_lbl(cls, value):
1879 1883 return dict(cls.STATUSES).get(value)
1880 1884
1881 1885 @property
1882 1886 def status_lbl(self):
1883 1887 return ChangesetStatus.get_status_lbl(self.status)
1884 1888
1885 1889
1886 1890 class PullRequest(Base, BaseModel):
1887 1891 __tablename__ = 'pull_requests'
1888 1892 __table_args__ = (
1889 1893 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1890 1894 'mysql_charset': 'utf8'},
1891 1895 )
1892 1896
1893 1897 STATUS_NEW = u'new'
1894 1898 STATUS_OPEN = u'open'
1895 1899 STATUS_CLOSED = u'closed'
1896 1900
1897 1901 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1898 1902 title = Column('title', Unicode(256), nullable=True)
1899 1903 description = Column('description', UnicodeText(10240), nullable=True)
1900 1904 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1901 1905 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1902 1906 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1903 1907 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1904 1908 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1905 1909 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1906 1910 org_ref = Column('org_ref', Unicode(256), nullable=False)
1907 1911 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1908 1912 other_ref = Column('other_ref', Unicode(256), nullable=False)
1909 1913
1910 1914 @hybrid_property
1911 1915 def revisions(self):
1912 1916 return self._revisions.split(':')
1913 1917
1914 1918 @revisions.setter
1915 1919 def revisions(self, val):
1916 1920 self._revisions = ':'.join(val)
1917 1921
1918 1922 @property
1919 1923 def org_ref_parts(self):
1920 1924 return self.org_ref.split(':')
1921 1925
1922 1926 @property
1923 1927 def other_ref_parts(self):
1924 1928 return self.other_ref.split(':')
1925 1929
1926 1930 author = relationship('User', lazy='joined')
1927 1931 reviewers = relationship('PullRequestReviewers',
1928 1932 cascade="all, delete, delete-orphan")
1929 1933 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1930 1934 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1931 1935 statuses = relationship('ChangesetStatus')
1932 1936 comments = relationship('ChangesetComment',
1933 1937 cascade="all, delete, delete-orphan")
1934 1938
1935 1939 def is_closed(self):
1936 1940 return self.status == self.STATUS_CLOSED
1937 1941
1938 1942 @property
1939 1943 def last_review_status(self):
1940 1944 return self.statuses[-1].status if self.statuses else ''
1941 1945
1942 1946 def __json__(self):
1943 1947 return dict(
1944 1948 revisions=self.revisions
1945 1949 )
1946 1950
1947 1951
1948 1952 class PullRequestReviewers(Base, BaseModel):
1949 1953 __tablename__ = 'pull_request_reviewers'
1950 1954 __table_args__ = (
1951 1955 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1952 1956 'mysql_charset': 'utf8'},
1953 1957 )
1954 1958
1955 1959 def __init__(self, user=None, pull_request=None):
1956 1960 self.user = user
1957 1961 self.pull_request = pull_request
1958 1962
1959 1963 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1960 1964 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1961 1965 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1962 1966
1963 1967 user = relationship('User')
1964 1968 pull_request = relationship('PullRequest')
1965 1969
1966 1970
1967 1971 class Notification(Base, BaseModel):
1968 1972 __tablename__ = 'notifications'
1969 1973 __table_args__ = (
1970 1974 Index('notification_type_idx', 'type'),
1971 1975 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1972 1976 'mysql_charset': 'utf8'},
1973 1977 )
1974 1978
1975 1979 TYPE_CHANGESET_COMMENT = u'cs_comment'
1976 1980 TYPE_MESSAGE = u'message'
1977 1981 TYPE_MENTION = u'mention'
1978 1982 TYPE_REGISTRATION = u'registration'
1979 1983 TYPE_PULL_REQUEST = u'pull_request'
1980 1984 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1981 1985
1982 1986 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1983 1987 subject = Column('subject', Unicode(512), nullable=True)
1984 1988 body = Column('body', UnicodeText(50000), nullable=True)
1985 1989 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1986 1990 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1987 1991 type_ = Column('type', Unicode(256))
1988 1992
1989 1993 created_by_user = relationship('User')
1990 1994 notifications_to_users = relationship('UserNotification', lazy='joined',
1991 1995 cascade="all, delete, delete-orphan")
1992 1996
1993 1997 @property
1994 1998 def recipients(self):
1995 1999 return [x.user for x in UserNotification.query()\
1996 2000 .filter(UserNotification.notification == self)\
1997 2001 .order_by(UserNotification.user_id.asc()).all()]
1998 2002
1999 2003 @classmethod
2000 2004 def create(cls, created_by, subject, body, recipients, type_=None):
2001 2005 if type_ is None:
2002 2006 type_ = Notification.TYPE_MESSAGE
2003 2007
2004 2008 notification = cls()
2005 2009 notification.created_by_user = created_by
2006 2010 notification.subject = subject
2007 2011 notification.body = body
2008 2012 notification.type_ = type_
2009 2013 notification.created_on = datetime.datetime.now()
2010 2014
2011 2015 for u in recipients:
2012 2016 assoc = UserNotification()
2013 2017 assoc.notification = notification
2014 2018 u.notifications.append(assoc)
2015 2019 Session().add(notification)
2016 2020 return notification
2017 2021
2018 2022 @property
2019 2023 def description(self):
2020 2024 from rhodecode.model.notification import NotificationModel
2021 2025 return NotificationModel().make_description(self)
2022 2026
2023 2027
2024 2028 class UserNotification(Base, BaseModel):
2025 2029 __tablename__ = 'user_to_notification'
2026 2030 __table_args__ = (
2027 2031 UniqueConstraint('user_id', 'notification_id'),
2028 2032 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2029 2033 'mysql_charset': 'utf8'}
2030 2034 )
2031 2035 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
2032 2036 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
2033 2037 read = Column('read', Boolean, default=False)
2034 2038 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
2035 2039
2036 2040 user = relationship('User', lazy="joined")
2037 2041 notification = relationship('Notification', lazy="joined",
2038 2042 order_by=lambda: Notification.created_on.desc(),)
2039 2043
2040 2044 def mark_as_read(self):
2041 2045 self.read = True
2042 2046 Session().add(self)
2043 2047
2044 2048
2045 2049 class DbMigrateVersion(Base, BaseModel):
2046 2050 __tablename__ = 'db_migrate_version'
2047 2051 __table_args__ = (
2048 2052 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2049 2053 'mysql_charset': 'utf8'},
2050 2054 )
2051 2055 repository_id = Column('repository_id', String(250), primary_key=True)
2052 2056 repository_path = Column('repository_path', Text)
2053 2057 version = Column('version', Integer)
@@ -1,1306 +1,1331 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 UserGroupModel
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_USER_GROUP = 'test_users_group'
47 47
48 48
49 49 def make_users_group(name=TEST_USER_GROUP):
50 50 gr = UserGroupModel().create(name=name)
51 51 UserGroupModel().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_USER_GROUP):
58 58 UserGroupModel().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 def test_api_lock_repo_lock_optional_locked(self):
374 from rhodecode.lib import helpers
375 374 from rhodecode.lib.utils2 import time_to_datetime
376 _locked_since = helpers.fmt_date(time_to_datetime(Repository\
375 _locked_since = json.dumps(time_to_datetime(Repository\
377 376 .get_by_repo_name(self.REPO).locked[1]))
378 377 id_, params = _build_data(self.apikey, 'lock',
379 378 repoid=self.REPO)
380 379 response = api_call(self, params)
381 380 expected = ('Repo `%s` locked by `%s`. Locked=`True`. Locked since: `%s`'
382 381 % (self.REPO, TEST_USER_ADMIN_LOGIN, _locked_since))
383 382 self._compare_ok(id_, expected, given=response.body)
384 383
385 384 @mock.patch.object(Repository, 'lock', crash)
386 385 def test_api_lock_error(self):
387 386 id_, params = _build_data(self.apikey, 'lock',
388 387 userid=TEST_USER_ADMIN_LOGIN,
389 388 repoid=self.REPO,
390 389 locked=True)
391 390 response = api_call(self, params)
392 391
393 392 expected = 'Error occurred locking repository `%s`' % self.REPO
394 393 self._compare_error(id_, expected, given=response.body)
395 394
395 def test_api_get_locks_regular_user(self):
396 id_, params = _build_data(self.apikey_regular, 'get_locks')
397 response = api_call(self, params)
398 expected = []
399 self._compare_ok(id_, expected, given=response.body)
400
401 def test_api_get_locks_with_userid_regular_user(self):
402 id_, params = _build_data(self.apikey_regular, 'get_locks',
403 userid=TEST_USER_ADMIN_LOGIN)
404 response = api_call(self, params)
405 expected = 'userid is not the same as your user'
406 self._compare_error(id_, expected, given=response.body)
407
408 def test_api_get_locks(self):
409 id_, params = _build_data(self.apikey, 'get_locks')
410 response = api_call(self, params)
411 expected = []
412 self._compare_ok(id_, expected, given=response.body)
413
414 def test_api_get_locks_with_userid(self):
415 id_, params = _build_data(self.apikey, 'get_locks',
416 userid=TEST_USER_REGULAR_LOGIN)
417 response = api_call(self, params)
418 expected = []
419 self._compare_ok(id_, expected, given=response.body)
420
396 421 def test_api_create_existing_user(self):
397 422 id_, params = _build_data(self.apikey, 'create_user',
398 423 username=TEST_USER_ADMIN_LOGIN,
399 424 email='test@foo.com',
400 425 password='trololo')
401 426 response = api_call(self, params)
402 427
403 428 expected = "user `%s` already exist" % TEST_USER_ADMIN_LOGIN
404 429 self._compare_error(id_, expected, given=response.body)
405 430
406 431 def test_api_create_user_with_existing_email(self):
407 432 id_, params = _build_data(self.apikey, 'create_user',
408 433 username=TEST_USER_ADMIN_LOGIN + 'new',
409 434 email=TEST_USER_REGULAR_EMAIL,
410 435 password='trololo')
411 436 response = api_call(self, params)
412 437
413 438 expected = "email `%s` already exist" % TEST_USER_REGULAR_EMAIL
414 439 self._compare_error(id_, expected, given=response.body)
415 440
416 441 def test_api_create_user(self):
417 442 username = 'test_new_api_user'
418 443 email = username + "@foo.com"
419 444
420 445 id_, params = _build_data(self.apikey, 'create_user',
421 446 username=username,
422 447 email=email,
423 448 password='trololo')
424 449 response = api_call(self, params)
425 450
426 451 usr = UserModel().get_by_username(username)
427 452 ret = dict(
428 453 msg='created new user `%s`' % username,
429 454 user=jsonify(usr.get_api_data())
430 455 )
431 456
432 457 expected = ret
433 458 self._compare_ok(id_, expected, given=response.body)
434 459
435 460 UserModel().delete(usr.user_id)
436 461 Session().commit()
437 462
438 463 @mock.patch.object(UserModel, 'create_or_update', crash)
439 464 def test_api_create_user_when_exception_happened(self):
440 465
441 466 username = 'test_new_api_user'
442 467 email = username + "@foo.com"
443 468
444 469 id_, params = _build_data(self.apikey, 'create_user',
445 470 username=username,
446 471 email=email,
447 472 password='trololo')
448 473 response = api_call(self, params)
449 474 expected = 'failed to create user `%s`' % username
450 475 self._compare_error(id_, expected, given=response.body)
451 476
452 477 def test_api_delete_user(self):
453 478 usr = UserModel().create_or_update(username=u'test_user',
454 479 password=u'qweqwe',
455 480 email=u'u232@rhodecode.org',
456 481 firstname=u'u1', lastname=u'u1')
457 482 Session().commit()
458 483 username = usr.username
459 484 email = usr.email
460 485 usr_id = usr.user_id
461 486 ## DELETE THIS USER NOW
462 487
463 488 id_, params = _build_data(self.apikey, 'delete_user',
464 489 userid=username,)
465 490 response = api_call(self, params)
466 491
467 492 ret = {'msg': 'deleted user ID:%s %s' % (usr_id, username),
468 493 'user': None}
469 494 expected = ret
470 495 self._compare_ok(id_, expected, given=response.body)
471 496
472 497 @mock.patch.object(UserModel, 'delete', crash)
473 498 def test_api_delete_user_when_exception_happened(self):
474 499 usr = UserModel().create_or_update(username=u'test_user',
475 500 password=u'qweqwe',
476 501 email=u'u232@rhodecode.org',
477 502 firstname=u'u1', lastname=u'u1')
478 503 Session().commit()
479 504 username = usr.username
480 505
481 506 id_, params = _build_data(self.apikey, 'delete_user',
482 507 userid=username,)
483 508 response = api_call(self, params)
484 509 ret = 'failed to delete ID:%s %s' % (usr.user_id,
485 510 usr.username)
486 511 expected = ret
487 512 self._compare_error(id_, expected, given=response.body)
488 513
489 514 @parameterized.expand([('firstname', 'new_username'),
490 515 ('lastname', 'new_username'),
491 516 ('email', 'new_username'),
492 517 ('admin', True),
493 518 ('admin', False),
494 519 ('ldap_dn', 'test'),
495 520 ('ldap_dn', None),
496 521 ('active', False),
497 522 ('active', True),
498 523 ('password', 'newpass')
499 524 ])
500 525 def test_api_update_user(self, name, expected):
501 526 usr = UserModel().get_by_username(self.TEST_USER_LOGIN)
502 527 kw = {name: expected,
503 528 'userid': usr.user_id}
504 529 id_, params = _build_data(self.apikey, 'update_user', **kw)
505 530 response = api_call(self, params)
506 531
507 532 ret = {
508 533 'msg': 'updated user ID:%s %s' % (usr.user_id, self.TEST_USER_LOGIN),
509 534 'user': jsonify(UserModel()\
510 535 .get_by_username(self.TEST_USER_LOGIN)\
511 536 .get_api_data())
512 537 }
513 538
514 539 expected = ret
515 540 self._compare_ok(id_, expected, given=response.body)
516 541
517 542 def test_api_update_user_no_changed_params(self):
518 543 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
519 544 ret = jsonify(usr.get_api_data())
520 545 id_, params = _build_data(self.apikey, 'update_user',
521 546 userid=TEST_USER_ADMIN_LOGIN)
522 547
523 548 response = api_call(self, params)
524 549 ret = {
525 550 'msg': 'updated user ID:%s %s' % (usr.user_id, TEST_USER_ADMIN_LOGIN),
526 551 'user': ret
527 552 }
528 553 expected = ret
529 554 self._compare_ok(id_, expected, given=response.body)
530 555
531 556 def test_api_update_user_by_user_id(self):
532 557 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
533 558 ret = jsonify(usr.get_api_data())
534 559 id_, params = _build_data(self.apikey, 'update_user',
535 560 userid=usr.user_id)
536 561
537 562 response = api_call(self, params)
538 563 ret = {
539 564 'msg': 'updated user ID:%s %s' % (usr.user_id, TEST_USER_ADMIN_LOGIN),
540 565 'user': ret
541 566 }
542 567 expected = ret
543 568 self._compare_ok(id_, expected, given=response.body)
544 569
545 570 @mock.patch.object(UserModel, 'update_user', crash)
546 571 def test_api_update_user_when_exception_happens(self):
547 572 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
548 573 ret = jsonify(usr.get_api_data())
549 574 id_, params = _build_data(self.apikey, 'update_user',
550 575 userid=usr.user_id)
551 576
552 577 response = api_call(self, params)
553 578 ret = 'failed to update user `%s`' % usr.user_id
554 579
555 580 expected = ret
556 581 self._compare_error(id_, expected, given=response.body)
557 582
558 583 def test_api_get_repo(self):
559 584 new_group = 'some_new_group'
560 585 make_users_group(new_group)
561 586 RepoModel().grant_users_group_permission(repo=self.REPO,
562 587 group_name=new_group,
563 588 perm='repository.read')
564 589 Session().commit()
565 590 id_, params = _build_data(self.apikey, 'get_repo',
566 591 repoid=self.REPO)
567 592 response = api_call(self, params)
568 593
569 594 repo = RepoModel().get_by_repo_name(self.REPO)
570 595 ret = repo.get_api_data()
571 596
572 597 members = []
573 598 followers = []
574 599 for user in repo.repo_to_perm:
575 600 perm = user.permission.permission_name
576 601 user = user.user
577 602 user_data = user.get_api_data()
578 603 user_data['type'] = "user"
579 604 user_data['permission'] = perm
580 605 members.append(user_data)
581 606
582 607 for users_group in repo.users_group_to_perm:
583 608 perm = users_group.permission.permission_name
584 609 users_group = users_group.users_group
585 610 users_group_data = users_group.get_api_data()
586 611 users_group_data['type'] = "users_group"
587 612 users_group_data['permission'] = perm
588 613 members.append(users_group_data)
589 614
590 615 for user in repo.followers:
591 616 followers.append(user.user.get_api_data())
592 617
593 618 ret['members'] = members
594 619 ret['followers'] = followers
595 620
596 621 expected = ret
597 622 self._compare_ok(id_, expected, given=response.body)
598 623 destroy_users_group(new_group)
599 624
600 625 def test_api_get_repo_by_non_admin(self):
601 626 id_, params = _build_data(self.apikey, 'get_repo',
602 627 repoid=self.REPO)
603 628 response = api_call(self, params)
604 629
605 630 repo = RepoModel().get_by_repo_name(self.REPO)
606 631 ret = repo.get_api_data()
607 632
608 633 members = []
609 634 followers = []
610 635 for user in repo.repo_to_perm:
611 636 perm = user.permission.permission_name
612 637 user = user.user
613 638 user_data = user.get_api_data()
614 639 user_data['type'] = "user"
615 640 user_data['permission'] = perm
616 641 members.append(user_data)
617 642
618 643 for users_group in repo.users_group_to_perm:
619 644 perm = users_group.permission.permission_name
620 645 users_group = users_group.users_group
621 646 users_group_data = users_group.get_api_data()
622 647 users_group_data['type'] = "users_group"
623 648 users_group_data['permission'] = perm
624 649 members.append(users_group_data)
625 650
626 651 for user in repo.followers:
627 652 followers.append(user.user.get_api_data())
628 653
629 654 ret['members'] = members
630 655 ret['followers'] = followers
631 656
632 657 expected = ret
633 658 self._compare_ok(id_, expected, given=response.body)
634 659
635 660 def test_api_get_repo_by_non_admin_no_permission_to_repo(self):
636 661 RepoModel().grant_user_permission(repo=self.REPO,
637 662 user=self.TEST_USER_LOGIN,
638 663 perm='repository.none')
639 664
640 665 id_, params = _build_data(self.apikey_regular, 'get_repo',
641 666 repoid=self.REPO)
642 667 response = api_call(self, params)
643 668
644 669 expected = 'repository `%s` does not exist' % (self.REPO)
645 670 self._compare_error(id_, expected, given=response.body)
646 671
647 672 def test_api_get_repo_that_doesn_not_exist(self):
648 673 id_, params = _build_data(self.apikey, 'get_repo',
649 674 repoid='no-such-repo')
650 675 response = api_call(self, params)
651 676
652 677 ret = 'repository `%s` does not exist' % 'no-such-repo'
653 678 expected = ret
654 679 self._compare_error(id_, expected, given=response.body)
655 680
656 681 def test_api_get_repos(self):
657 682 id_, params = _build_data(self.apikey, 'get_repos')
658 683 response = api_call(self, params)
659 684
660 685 result = []
661 686 for repo in RepoModel().get_all():
662 687 result.append(repo.get_api_data())
663 688 ret = jsonify(result)
664 689
665 690 expected = ret
666 691 self._compare_ok(id_, expected, given=response.body)
667 692
668 693 def test_api_get_repos_non_admin(self):
669 694 id_, params = _build_data(self.apikey_regular, 'get_repos')
670 695 response = api_call(self, params)
671 696
672 697 result = []
673 698 for repo in RepoModel().get_all_user_repos(self.TEST_USER_LOGIN):
674 699 result.append(repo.get_api_data())
675 700 ret = jsonify(result)
676 701
677 702 expected = ret
678 703 self._compare_ok(id_, expected, given=response.body)
679 704
680 705 @parameterized.expand([('all', 'all'),
681 706 ('dirs', 'dirs'),
682 707 ('files', 'files'), ])
683 708 def test_api_get_repo_nodes(self, name, ret_type):
684 709 rev = 'tip'
685 710 path = '/'
686 711 id_, params = _build_data(self.apikey, 'get_repo_nodes',
687 712 repoid=self.REPO, revision=rev,
688 713 root_path=path,
689 714 ret_type=ret_type)
690 715 response = api_call(self, params)
691 716
692 717 # we don't the actual return types here since it's tested somewhere
693 718 # else
694 719 expected = json.loads(response.body)['result']
695 720 self._compare_ok(id_, expected, given=response.body)
696 721
697 722 def test_api_get_repo_nodes_bad_revisions(self):
698 723 rev = 'i-dont-exist'
699 724 path = '/'
700 725 id_, params = _build_data(self.apikey, 'get_repo_nodes',
701 726 repoid=self.REPO, revision=rev,
702 727 root_path=path,)
703 728 response = api_call(self, params)
704 729
705 730 expected = 'failed to get repo: `%s` nodes' % self.REPO
706 731 self._compare_error(id_, expected, given=response.body)
707 732
708 733 def test_api_get_repo_nodes_bad_path(self):
709 734 rev = 'tip'
710 735 path = '/idontexits'
711 736 id_, params = _build_data(self.apikey, 'get_repo_nodes',
712 737 repoid=self.REPO, revision=rev,
713 738 root_path=path,)
714 739 response = api_call(self, params)
715 740
716 741 expected = 'failed to get repo: `%s` nodes' % self.REPO
717 742 self._compare_error(id_, expected, given=response.body)
718 743
719 744 def test_api_get_repo_nodes_bad_ret_type(self):
720 745 rev = 'tip'
721 746 path = '/'
722 747 ret_type = 'error'
723 748 id_, params = _build_data(self.apikey, 'get_repo_nodes',
724 749 repoid=self.REPO, revision=rev,
725 750 root_path=path,
726 751 ret_type=ret_type)
727 752 response = api_call(self, params)
728 753
729 754 expected = 'ret_type must be one of %s' % (['files', 'dirs', 'all'])
730 755 self._compare_error(id_, expected, given=response.body)
731 756
732 757 def test_api_create_repo(self):
733 758 repo_name = 'api-repo'
734 759 id_, params = _build_data(self.apikey, 'create_repo',
735 760 repo_name=repo_name,
736 761 owner=TEST_USER_ADMIN_LOGIN,
737 762 repo_type='hg',
738 763 )
739 764 response = api_call(self, params)
740 765
741 766 repo = RepoModel().get_by_repo_name(repo_name)
742 767 ret = {
743 768 'msg': 'Created new repository `%s`' % repo_name,
744 769 'repo': jsonify(repo.get_api_data())
745 770 }
746 771 expected = ret
747 772 self._compare_ok(id_, expected, given=response.body)
748 773 destroy_repo(repo_name)
749 774
750 775 def test_api_create_repo_unknown_owner(self):
751 776 repo_name = 'api-repo'
752 777 owner = 'i-dont-exist'
753 778 id_, params = _build_data(self.apikey, 'create_repo',
754 779 repo_name=repo_name,
755 780 owner=owner,
756 781 repo_type='hg',
757 782 )
758 783 response = api_call(self, params)
759 784 expected = 'user `%s` does not exist' % owner
760 785 self._compare_error(id_, expected, given=response.body)
761 786
762 787 def test_api_create_repo_dont_specify_owner(self):
763 788 repo_name = 'api-repo'
764 789 owner = 'i-dont-exist'
765 790 id_, params = _build_data(self.apikey, 'create_repo',
766 791 repo_name=repo_name,
767 792 repo_type='hg',
768 793 )
769 794 response = api_call(self, params)
770 795
771 796 repo = RepoModel().get_by_repo_name(repo_name)
772 797 ret = {
773 798 'msg': 'Created new repository `%s`' % repo_name,
774 799 'repo': jsonify(repo.get_api_data())
775 800 }
776 801 expected = ret
777 802 self._compare_ok(id_, expected, given=response.body)
778 803 destroy_repo(repo_name)
779 804
780 805 def test_api_create_repo_by_non_admin(self):
781 806 repo_name = 'api-repo'
782 807 owner = 'i-dont-exist'
783 808 id_, params = _build_data(self.apikey_regular, 'create_repo',
784 809 repo_name=repo_name,
785 810 repo_type='hg',
786 811 )
787 812 response = api_call(self, params)
788 813
789 814 repo = RepoModel().get_by_repo_name(repo_name)
790 815 ret = {
791 816 'msg': 'Created new repository `%s`' % repo_name,
792 817 'repo': jsonify(repo.get_api_data())
793 818 }
794 819 expected = ret
795 820 self._compare_ok(id_, expected, given=response.body)
796 821 destroy_repo(repo_name)
797 822
798 823 def test_api_create_repo_by_non_admin_specify_owner(self):
799 824 repo_name = 'api-repo'
800 825 owner = 'i-dont-exist'
801 826 id_, params = _build_data(self.apikey_regular, 'create_repo',
802 827 repo_name=repo_name,
803 828 repo_type='hg',
804 829 owner=owner
805 830 )
806 831 response = api_call(self, params)
807 832
808 833 expected = 'Only RhodeCode admin can specify `owner` param'
809 834 self._compare_error(id_, expected, given=response.body)
810 835 destroy_repo(repo_name)
811 836
812 837 def test_api_create_repo_exists(self):
813 838 repo_name = self.REPO
814 839 id_, params = _build_data(self.apikey, 'create_repo',
815 840 repo_name=repo_name,
816 841 owner=TEST_USER_ADMIN_LOGIN,
817 842 repo_type='hg',
818 843 )
819 844 response = api_call(self, params)
820 845 expected = "repo `%s` already exist" % repo_name
821 846 self._compare_error(id_, expected, given=response.body)
822 847
823 848 @mock.patch.object(RepoModel, 'create_repo', crash)
824 849 def test_api_create_repo_exception_occurred(self):
825 850 repo_name = 'api-repo'
826 851 id_, params = _build_data(self.apikey, 'create_repo',
827 852 repo_name=repo_name,
828 853 owner=TEST_USER_ADMIN_LOGIN,
829 854 repo_type='hg',
830 855 )
831 856 response = api_call(self, params)
832 857 expected = 'failed to create repository `%s`' % repo_name
833 858 self._compare_error(id_, expected, given=response.body)
834 859
835 860 def test_api_delete_repo(self):
836 861 repo_name = 'api_delete_me'
837 862 create_repo(repo_name, self.REPO_TYPE)
838 863
839 864 id_, params = _build_data(self.apikey, 'delete_repo',
840 865 repoid=repo_name,)
841 866 response = api_call(self, params)
842 867
843 868 ret = {
844 869 'msg': 'Deleted repository `%s`' % repo_name,
845 870 'success': True
846 871 }
847 872 expected = ret
848 873 self._compare_ok(id_, expected, given=response.body)
849 874
850 875 def test_api_delete_repo_by_non_admin(self):
851 876 repo_name = 'api_delete_me'
852 877 create_repo(repo_name, self.REPO_TYPE, owner=self.TEST_USER_LOGIN)
853 878 try:
854 879 id_, params = _build_data(self.apikey_regular, 'delete_repo',
855 880 repoid=repo_name,)
856 881 response = api_call(self, params)
857 882
858 883 ret = {
859 884 'msg': 'Deleted repository `%s`' % repo_name,
860 885 'success': True
861 886 }
862 887 expected = ret
863 888 self._compare_ok(id_, expected, given=response.body)
864 889 finally:
865 890 destroy_repo(repo_name)
866 891
867 892 def test_api_delete_repo_by_non_admin_no_permission(self):
868 893 repo_name = 'api_delete_me'
869 894 create_repo(repo_name, self.REPO_TYPE)
870 895 try:
871 896 id_, params = _build_data(self.apikey_regular, 'delete_repo',
872 897 repoid=repo_name,)
873 898 response = api_call(self, params)
874 899 expected = 'repository `%s` does not exist' % (repo_name)
875 900 self._compare_error(id_, expected, given=response.body)
876 901 finally:
877 902 destroy_repo(repo_name)
878 903
879 904 def test_api_delete_repo_exception_occurred(self):
880 905 repo_name = 'api_delete_me'
881 906 create_repo(repo_name, self.REPO_TYPE)
882 907 try:
883 908 with mock.patch.object(RepoModel, 'delete', crash):
884 909 id_, params = _build_data(self.apikey, 'delete_repo',
885 910 repoid=repo_name,)
886 911 response = api_call(self, params)
887 912
888 913 expected = 'failed to delete repository `%s`' % repo_name
889 914 self._compare_error(id_, expected, given=response.body)
890 915 finally:
891 916 destroy_repo(repo_name)
892 917
893 918 def test_api_fork_repo(self):
894 919 fork_name = 'api-repo-fork'
895 920 id_, params = _build_data(self.apikey, 'fork_repo',
896 921 repoid=self.REPO,
897 922 fork_name=fork_name,
898 923 owner=TEST_USER_ADMIN_LOGIN,
899 924 )
900 925 response = api_call(self, params)
901 926
902 927 ret = {
903 928 'msg': 'Created fork of `%s` as `%s`' % (self.REPO,
904 929 fork_name),
905 930 'success': True
906 931 }
907 932 expected = ret
908 933 self._compare_ok(id_, expected, given=response.body)
909 934 destroy_repo(fork_name)
910 935
911 936 def test_api_fork_repo_non_admin(self):
912 937 fork_name = 'api-repo-fork'
913 938 id_, params = _build_data(self.apikey_regular, 'fork_repo',
914 939 repoid=self.REPO,
915 940 fork_name=fork_name,
916 941 )
917 942 response = api_call(self, params)
918 943
919 944 ret = {
920 945 'msg': 'Created fork of `%s` as `%s`' % (self.REPO,
921 946 fork_name),
922 947 'success': True
923 948 }
924 949 expected = ret
925 950 self._compare_ok(id_, expected, given=response.body)
926 951 destroy_repo(fork_name)
927 952
928 953 def test_api_fork_repo_non_admin_specify_owner(self):
929 954 fork_name = 'api-repo-fork'
930 955 id_, params = _build_data(self.apikey_regular, 'fork_repo',
931 956 repoid=self.REPO,
932 957 fork_name=fork_name,
933 958 owner=TEST_USER_ADMIN_LOGIN,
934 959 )
935 960 response = api_call(self, params)
936 961 expected = 'Only RhodeCode admin can specify `owner` param'
937 962 self._compare_error(id_, expected, given=response.body)
938 963 destroy_repo(fork_name)
939 964
940 965 def test_api_fork_repo_non_admin_no_permission_to_fork(self):
941 966 RepoModel().grant_user_permission(repo=self.REPO,
942 967 user=self.TEST_USER_LOGIN,
943 968 perm='repository.none')
944 969 fork_name = 'api-repo-fork'
945 970 id_, params = _build_data(self.apikey_regular, 'fork_repo',
946 971 repoid=self.REPO,
947 972 fork_name=fork_name,
948 973 )
949 974 response = api_call(self, params)
950 975 expected = 'repository `%s` does not exist' % (self.REPO)
951 976 self._compare_error(id_, expected, given=response.body)
952 977 destroy_repo(fork_name)
953 978
954 979 def test_api_fork_repo_unknown_owner(self):
955 980 fork_name = 'api-repo-fork'
956 981 owner = 'i-dont-exist'
957 982 id_, params = _build_data(self.apikey, 'fork_repo',
958 983 repoid=self.REPO,
959 984 fork_name=fork_name,
960 985 owner=owner,
961 986 )
962 987 response = api_call(self, params)
963 988 expected = 'user `%s` does not exist' % owner
964 989 self._compare_error(id_, expected, given=response.body)
965 990
966 991 def test_api_fork_repo_fork_exists(self):
967 992 fork_name = 'api-repo-fork'
968 993 create_fork(fork_name, self.REPO_TYPE, self.REPO)
969 994
970 995 try:
971 996 fork_name = 'api-repo-fork'
972 997
973 998 id_, params = _build_data(self.apikey, 'fork_repo',
974 999 repoid=self.REPO,
975 1000 fork_name=fork_name,
976 1001 owner=TEST_USER_ADMIN_LOGIN,
977 1002 )
978 1003 response = api_call(self, params)
979 1004
980 1005 expected = "fork `%s` already exist" % fork_name
981 1006 self._compare_error(id_, expected, given=response.body)
982 1007 finally:
983 1008 destroy_repo(fork_name)
984 1009
985 1010 def test_api_fork_repo_repo_exists(self):
986 1011 fork_name = self.REPO
987 1012
988 1013 id_, params = _build_data(self.apikey, 'fork_repo',
989 1014 repoid=self.REPO,
990 1015 fork_name=fork_name,
991 1016 owner=TEST_USER_ADMIN_LOGIN,
992 1017 )
993 1018 response = api_call(self, params)
994 1019
995 1020 expected = "repo `%s` already exist" % fork_name
996 1021 self._compare_error(id_, expected, given=response.body)
997 1022
998 1023 @mock.patch.object(RepoModel, 'create_fork', crash)
999 1024 def test_api_fork_repo_exception_occurred(self):
1000 1025 fork_name = 'api-repo-fork'
1001 1026 id_, params = _build_data(self.apikey, 'fork_repo',
1002 1027 repoid=self.REPO,
1003 1028 fork_name=fork_name,
1004 1029 owner=TEST_USER_ADMIN_LOGIN,
1005 1030 )
1006 1031 response = api_call(self, params)
1007 1032
1008 1033 expected = 'failed to fork repository `%s` as `%s`' % (self.REPO,
1009 1034 fork_name)
1010 1035 self._compare_error(id_, expected, given=response.body)
1011 1036
1012 1037 def test_api_get_users_group(self):
1013 1038 id_, params = _build_data(self.apikey, 'get_users_group',
1014 1039 usersgroupid=TEST_USER_GROUP)
1015 1040 response = api_call(self, params)
1016 1041
1017 1042 users_group = UserGroupModel().get_group(TEST_USER_GROUP)
1018 1043 members = []
1019 1044 for user in users_group.members:
1020 1045 user = user.user
1021 1046 members.append(user.get_api_data())
1022 1047
1023 1048 ret = users_group.get_api_data()
1024 1049 ret['members'] = members
1025 1050 expected = ret
1026 1051 self._compare_ok(id_, expected, given=response.body)
1027 1052
1028 1053 def test_api_get_users_groups(self):
1029 1054
1030 1055 make_users_group('test_users_group2')
1031 1056
1032 1057 id_, params = _build_data(self.apikey, 'get_users_groups',)
1033 1058 response = api_call(self, params)
1034 1059
1035 1060 expected = []
1036 1061 for gr_name in [TEST_USER_GROUP, 'test_users_group2']:
1037 1062 users_group = UserGroupModel().get_group(gr_name)
1038 1063 ret = users_group.get_api_data()
1039 1064 expected.append(ret)
1040 1065 self._compare_ok(id_, expected, given=response.body)
1041 1066
1042 1067 UserGroupModel().delete(users_group='test_users_group2')
1043 1068 Session().commit()
1044 1069
1045 1070 def test_api_create_users_group(self):
1046 1071 group_name = 'some_new_group'
1047 1072 id_, params = _build_data(self.apikey, 'create_users_group',
1048 1073 group_name=group_name)
1049 1074 response = api_call(self, params)
1050 1075
1051 1076 ret = {
1052 1077 'msg': 'created new user group `%s`' % group_name,
1053 1078 'users_group': jsonify(UserGroupModel()\
1054 1079 .get_by_name(group_name)\
1055 1080 .get_api_data())
1056 1081 }
1057 1082 expected = ret
1058 1083 self._compare_ok(id_, expected, given=response.body)
1059 1084
1060 1085 destroy_users_group(group_name)
1061 1086
1062 1087 def test_api_get_users_group_that_exist(self):
1063 1088 id_, params = _build_data(self.apikey, 'create_users_group',
1064 1089 group_name=TEST_USER_GROUP)
1065 1090 response = api_call(self, params)
1066 1091
1067 1092 expected = "user group `%s` already exist" % TEST_USER_GROUP
1068 1093 self._compare_error(id_, expected, given=response.body)
1069 1094
1070 1095 @mock.patch.object(UserGroupModel, 'create', crash)
1071 1096 def test_api_get_users_group_exception_occurred(self):
1072 1097 group_name = 'exception_happens'
1073 1098 id_, params = _build_data(self.apikey, 'create_users_group',
1074 1099 group_name=group_name)
1075 1100 response = api_call(self, params)
1076 1101
1077 1102 expected = 'failed to create group `%s`' % group_name
1078 1103 self._compare_error(id_, expected, given=response.body)
1079 1104
1080 1105 def test_api_add_user_to_users_group(self):
1081 1106 gr_name = 'test_group'
1082 1107 UserGroupModel().create(gr_name)
1083 1108 Session().commit()
1084 1109 id_, params = _build_data(self.apikey, 'add_user_to_users_group',
1085 1110 usersgroupid=gr_name,
1086 1111 userid=TEST_USER_ADMIN_LOGIN)
1087 1112 response = api_call(self, params)
1088 1113
1089 1114 expected = {
1090 1115 'msg': 'added member `%s` to user group `%s`' % (
1091 1116 TEST_USER_ADMIN_LOGIN, gr_name
1092 1117 ),
1093 1118 'success': True}
1094 1119 self._compare_ok(id_, expected, given=response.body)
1095 1120
1096 1121 UserGroupModel().delete(users_group=gr_name)
1097 1122 Session().commit()
1098 1123
1099 1124 def test_api_add_user_to_users_group_that_doesnt_exist(self):
1100 1125 id_, params = _build_data(self.apikey, 'add_user_to_users_group',
1101 1126 usersgroupid='false-group',
1102 1127 userid=TEST_USER_ADMIN_LOGIN)
1103 1128 response = api_call(self, params)
1104 1129
1105 1130 expected = 'user group `%s` does not exist' % 'false-group'
1106 1131 self._compare_error(id_, expected, given=response.body)
1107 1132
1108 1133 @mock.patch.object(UserGroupModel, 'add_user_to_group', crash)
1109 1134 def test_api_add_user_to_users_group_exception_occurred(self):
1110 1135 gr_name = 'test_group'
1111 1136 UserGroupModel().create(gr_name)
1112 1137 Session().commit()
1113 1138 id_, params = _build_data(self.apikey, 'add_user_to_users_group',
1114 1139 usersgroupid=gr_name,
1115 1140 userid=TEST_USER_ADMIN_LOGIN)
1116 1141 response = api_call(self, params)
1117 1142
1118 1143 expected = 'failed to add member to user group `%s`' % gr_name
1119 1144 self._compare_error(id_, expected, given=response.body)
1120 1145
1121 1146 UserGroupModel().delete(users_group=gr_name)
1122 1147 Session().commit()
1123 1148
1124 1149 def test_api_remove_user_from_users_group(self):
1125 1150 gr_name = 'test_group_3'
1126 1151 gr = UserGroupModel().create(gr_name)
1127 1152 UserGroupModel().add_user_to_group(gr, user=TEST_USER_ADMIN_LOGIN)
1128 1153 Session().commit()
1129 1154 id_, params = _build_data(self.apikey, 'remove_user_from_users_group',
1130 1155 usersgroupid=gr_name,
1131 1156 userid=TEST_USER_ADMIN_LOGIN)
1132 1157 response = api_call(self, params)
1133 1158
1134 1159 expected = {
1135 1160 'msg': 'removed member `%s` from user group `%s`' % (
1136 1161 TEST_USER_ADMIN_LOGIN, gr_name
1137 1162 ),
1138 1163 'success': True}
1139 1164 self._compare_ok(id_, expected, given=response.body)
1140 1165
1141 1166 UserGroupModel().delete(users_group=gr_name)
1142 1167 Session().commit()
1143 1168
1144 1169 @mock.patch.object(UserGroupModel, 'remove_user_from_group', crash)
1145 1170 def test_api_remove_user_from_users_group_exception_occurred(self):
1146 1171 gr_name = 'test_group_3'
1147 1172 gr = UserGroupModel().create(gr_name)
1148 1173 UserGroupModel().add_user_to_group(gr, user=TEST_USER_ADMIN_LOGIN)
1149 1174 Session().commit()
1150 1175 id_, params = _build_data(self.apikey, 'remove_user_from_users_group',
1151 1176 usersgroupid=gr_name,
1152 1177 userid=TEST_USER_ADMIN_LOGIN)
1153 1178 response = api_call(self, params)
1154 1179
1155 1180 expected = 'failed to remove member from user group `%s`' % gr_name
1156 1181 self._compare_error(id_, expected, given=response.body)
1157 1182
1158 1183 UserGroupModel().delete(users_group=gr_name)
1159 1184 Session().commit()
1160 1185
1161 1186 @parameterized.expand([('none', 'repository.none'),
1162 1187 ('read', 'repository.read'),
1163 1188 ('write', 'repository.write'),
1164 1189 ('admin', 'repository.admin')])
1165 1190 def test_api_grant_user_permission(self, name, perm):
1166 1191 id_, params = _build_data(self.apikey, 'grant_user_permission',
1167 1192 repoid=self.REPO,
1168 1193 userid=TEST_USER_ADMIN_LOGIN,
1169 1194 perm=perm)
1170 1195 response = api_call(self, params)
1171 1196
1172 1197 ret = {
1173 1198 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1174 1199 perm, TEST_USER_ADMIN_LOGIN, self.REPO
1175 1200 ),
1176 1201 'success': True
1177 1202 }
1178 1203 expected = ret
1179 1204 self._compare_ok(id_, expected, given=response.body)
1180 1205
1181 1206 def test_api_grant_user_permission_wrong_permission(self):
1182 1207 perm = 'haha.no.permission'
1183 1208 id_, params = _build_data(self.apikey, 'grant_user_permission',
1184 1209 repoid=self.REPO,
1185 1210 userid=TEST_USER_ADMIN_LOGIN,
1186 1211 perm=perm)
1187 1212 response = api_call(self, params)
1188 1213
1189 1214 expected = 'permission `%s` does not exist' % perm
1190 1215 self._compare_error(id_, expected, given=response.body)
1191 1216
1192 1217 @mock.patch.object(RepoModel, 'grant_user_permission', crash)
1193 1218 def test_api_grant_user_permission_exception_when_adding(self):
1194 1219 perm = 'repository.read'
1195 1220 id_, params = _build_data(self.apikey, 'grant_user_permission',
1196 1221 repoid=self.REPO,
1197 1222 userid=TEST_USER_ADMIN_LOGIN,
1198 1223 perm=perm)
1199 1224 response = api_call(self, params)
1200 1225
1201 1226 expected = 'failed to edit permission for user: `%s` in repo: `%s`' % (
1202 1227 TEST_USER_ADMIN_LOGIN, self.REPO
1203 1228 )
1204 1229 self._compare_error(id_, expected, given=response.body)
1205 1230
1206 1231 def test_api_revoke_user_permission(self):
1207 1232 id_, params = _build_data(self.apikey, 'revoke_user_permission',
1208 1233 repoid=self.REPO,
1209 1234 userid=TEST_USER_ADMIN_LOGIN,)
1210 1235 response = api_call(self, params)
1211 1236
1212 1237 expected = {
1213 1238 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
1214 1239 TEST_USER_ADMIN_LOGIN, self.REPO
1215 1240 ),
1216 1241 'success': True
1217 1242 }
1218 1243 self._compare_ok(id_, expected, given=response.body)
1219 1244
1220 1245 @mock.patch.object(RepoModel, 'revoke_user_permission', crash)
1221 1246 def test_api_revoke_user_permission_exception_when_adding(self):
1222 1247 id_, params = _build_data(self.apikey, 'revoke_user_permission',
1223 1248 repoid=self.REPO,
1224 1249 userid=TEST_USER_ADMIN_LOGIN,)
1225 1250 response = api_call(self, params)
1226 1251
1227 1252 expected = 'failed to edit permission for user: `%s` in repo: `%s`' % (
1228 1253 TEST_USER_ADMIN_LOGIN, self.REPO
1229 1254 )
1230 1255 self._compare_error(id_, expected, given=response.body)
1231 1256
1232 1257 @parameterized.expand([('none', 'repository.none'),
1233 1258 ('read', 'repository.read'),
1234 1259 ('write', 'repository.write'),
1235 1260 ('admin', 'repository.admin')])
1236 1261 def test_api_grant_users_group_permission(self, name, perm):
1237 1262 id_, params = _build_data(self.apikey, 'grant_users_group_permission',
1238 1263 repoid=self.REPO,
1239 1264 usersgroupid=TEST_USER_GROUP,
1240 1265 perm=perm)
1241 1266 response = api_call(self, params)
1242 1267
1243 1268 ret = {
1244 1269 'msg': 'Granted perm: `%s` for user group: `%s` in repo: `%s`' % (
1245 1270 perm, TEST_USER_GROUP, self.REPO
1246 1271 ),
1247 1272 'success': True
1248 1273 }
1249 1274 expected = ret
1250 1275 self._compare_ok(id_, expected, given=response.body)
1251 1276
1252 1277 def test_api_grant_users_group_permission_wrong_permission(self):
1253 1278 perm = 'haha.no.permission'
1254 1279 id_, params = _build_data(self.apikey, 'grant_users_group_permission',
1255 1280 repoid=self.REPO,
1256 1281 usersgroupid=TEST_USER_GROUP,
1257 1282 perm=perm)
1258 1283 response = api_call(self, params)
1259 1284
1260 1285 expected = 'permission `%s` does not exist' % perm
1261 1286 self._compare_error(id_, expected, given=response.body)
1262 1287
1263 1288 @mock.patch.object(RepoModel, 'grant_users_group_permission', crash)
1264 1289 def test_api_grant_users_group_permission_exception_when_adding(self):
1265 1290 perm = 'repository.read'
1266 1291 id_, params = _build_data(self.apikey, 'grant_users_group_permission',
1267 1292 repoid=self.REPO,
1268 1293 usersgroupid=TEST_USER_GROUP,
1269 1294 perm=perm)
1270 1295 response = api_call(self, params)
1271 1296
1272 1297 expected = 'failed to edit permission for user group: `%s` in repo: `%s`' % (
1273 1298 TEST_USER_GROUP, self.REPO
1274 1299 )
1275 1300 self._compare_error(id_, expected, given=response.body)
1276 1301
1277 1302 def test_api_revoke_users_group_permission(self):
1278 1303 RepoModel().grant_users_group_permission(repo=self.REPO,
1279 1304 group_name=TEST_USER_GROUP,
1280 1305 perm='repository.read')
1281 1306 Session().commit()
1282 1307 id_, params = _build_data(self.apikey, 'revoke_users_group_permission',
1283 1308 repoid=self.REPO,
1284 1309 usersgroupid=TEST_USER_GROUP,)
1285 1310 response = api_call(self, params)
1286 1311
1287 1312 expected = {
1288 1313 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
1289 1314 TEST_USER_GROUP, self.REPO
1290 1315 ),
1291 1316 'success': True
1292 1317 }
1293 1318 self._compare_ok(id_, expected, given=response.body)
1294 1319
1295 1320 @mock.patch.object(RepoModel, 'revoke_users_group_permission', crash)
1296 1321 def test_api_revoke_users_group_permission_exception_when_adding(self):
1297 1322
1298 1323 id_, params = _build_data(self.apikey, 'revoke_users_group_permission',
1299 1324 repoid=self.REPO,
1300 1325 usersgroupid=TEST_USER_GROUP,)
1301 1326 response = api_call(self, params)
1302 1327
1303 1328 expected = 'failed to edit permission for user group: `%s` in repo: `%s`' % (
1304 1329 TEST_USER_GROUP, self.REPO
1305 1330 )
1306 1331 self._compare_error(id_, expected, given=response.body)
General Comments 0
You need to be logged in to leave comments. Login now