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