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