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