##// END OF EJS Templates
Implemented API calls for non-admin users for locking/unlocking repositories
marcink -
r3161:3563c47e beta
parent child Browse files
Show More
@@ -1,928 +1,929 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 lock
156 156 ----
157 157
158 Set locking state on given repository by given user.
158 Set locking state on given repository by given user. If userid param is skipped
159 , then it is set to id of user whos calling this method.
159 160 This command can be executed only using api_key belonging to user with admin
160 rights.
161 rights or regular user that have admin or write access to repository.
161 162
162 163 INPUT::
163 164
164 165 id : <id_for_response>
165 166 api_key : "<api_key>"
166 167 method : "lock"
167 168 args : {
168 169 "repoid" : "<reponame or repo_id>"
169 "userid" : "<user_id or username>",
170 "userid" : "<user_id or username = Optional(=apiuser)>",
170 171 "locked" : "<bool true|false>"
171 172 }
172 173
173 174 OUTPUT::
174 175
175 176 id : <id_given_in_input>
176 177 result : "User `<username>` set lock state for repo `<reponame>` to `true|false`"
177 178 error : null
178 179
179 180
180 181 show_ip
181 182 -------
182 183
183 184 Shows IP address as seen from RhodeCode server, together with all
184 185 defined IP addresses for given user.
185 186 This command can be executed only using api_key belonging to user with admin
186 187 rights.
187 188
188 189 INPUT::
189 190
190 191 id : <id_for_response>
191 192 api_key : "<api_key>"
192 193 method : "show_ip"
193 194 args : {
194 195 "userid" : "<user_id or username>",
195 196 }
196 197
197 198 OUTPUT::
198 199
199 200 id : <id_given_in_input>
200 201 result : {
201 202 "ip_addr_server": <ip_from_clien>",
202 203 "user_ips": [
203 204 {
204 205 "ip_addr": "<ip_with_mask>",
205 206 "ip_range": ["<start_ip>", "<end_ip>"],
206 207 },
207 208 ...
208 209 ]
209 210 }
210 211
211 212 error : null
212 213
213 214
214 215 get_user
215 216 --------
216 217
217 218 Get's an user by username or user_id, Returns empty result if user is not found.
218 219 This command can be executed only using api_key belonging to user with admin
219 220 rights.
220 221
221 222
222 223 INPUT::
223 224
224 225 id : <id_for_response>
225 226 api_key : "<api_key>"
226 227 method : "get_user"
227 228 args : {
228 229 "userid" : "<username or user_id>"
229 230 }
230 231
231 232 OUTPUT::
232 233
233 234 id : <id_given_in_input>
234 235 result: None if user does not exist or
235 236 {
236 237 "user_id" : "<user_id>",
237 238 "username" : "<username>",
238 239 "firstname": "<firstname>",
239 240 "lastname" : "<lastname>",
240 241 "email" : "<email>",
241 242 "emails": "<list_of_all_additional_emails>",
242 243 "ip_addresses": "<list_of_ip_addresses_for_user>",
243 244 "active" : "<bool>",
244 245 "admin" :Β  "<bool>",
245 246 "ldap_dn" : "<ldap_dn>",
246 247 "last_login": "<last_login>",
247 248 "permissions": {
248 249 "global": ["hg.create.repository",
249 250 "repository.read",
250 251 "hg.register.manual_activate"],
251 252 "repositories": {"repo1": "repository.none"},
252 253 "repositories_groups": {"Group1": "group.read"}
253 254 },
254 255 }
255 256
256 257 error: null
257 258
258 259
259 260 get_users
260 261 ---------
261 262
262 263 Lists all existing users. This command can be executed only using api_key
263 264 belonging to user with admin rights.
264 265
265 266
266 267 INPUT::
267 268
268 269 id : <id_for_response>
269 270 api_key : "<api_key>"
270 271 method : "get_users"
271 272 args : { }
272 273
273 274 OUTPUT::
274 275
275 276 id : <id_given_in_input>
276 277 result: [
277 278 {
278 279 "user_id" : "<user_id>",
279 280 "username" : "<username>",
280 281 "firstname": "<firstname>",
281 282 "lastname" : "<lastname>",
282 283 "email" : "<email>",
283 284 "emails": "<list_of_all_additional_emails>",
284 285 "ip_addresses": "<list_of_ip_addresses_for_user>",
285 286 "active" : "<bool>",
286 287 "admin" :Β  "<bool>",
287 288 "ldap_dn" : "<ldap_dn>",
288 289 "last_login": "<last_login>",
289 290 },
290 291 …
291 292 ]
292 293 error: null
293 294
294 295
295 296 create_user
296 297 -----------
297 298
298 299 Creates new user. This command can
299 300 be executed only using api_key belonging to user with admin rights.
300 301
301 302
302 303 INPUT::
303 304
304 305 id : <id_for_response>
305 306 api_key : "<api_key>"
306 307 method : "create_user"
307 308 args : {
308 309 "username" : "<username>",
309 310 "email" : "<useremail>",
310 311 "password" : "<password>",
311 312 "firstname" : "<firstname> = Optional(None)",
312 313 "lastname" : "<lastname> = Optional(None)",
313 314 "active" : "<bool> = Optional(True)",
314 315 "admin" : "<bool> = Optional(False)",
315 316 "ldap_dn" : "<ldap_dn> = Optional(None)"
316 317 }
317 318
318 319 OUTPUT::
319 320
320 321 id : <id_given_in_input>
321 322 result: {
322 323 "msg" : "created new user `<username>`",
323 324 "user": {
324 325 "user_id" : "<user_id>",
325 326 "username" : "<username>",
326 327 "firstname": "<firstname>",
327 328 "lastname" : "<lastname>",
328 329 "email" : "<email>",
329 330 "emails": "<list_of_all_additional_emails>",
330 331 "active" : "<bool>",
331 332 "admin" :Β  "<bool>",
332 333 "ldap_dn" : "<ldap_dn>",
333 334 "last_login": "<last_login>",
334 335 },
335 336 }
336 337 error: null
337 338
338 339
339 340 update_user
340 341 -----------
341 342
342 343 updates given user if such user exists. This command can
343 344 be executed only using api_key belonging to user with admin rights.
344 345
345 346
346 347 INPUT::
347 348
348 349 id : <id_for_response>
349 350 api_key : "<api_key>"
350 351 method : "update_user"
351 352 args : {
352 353 "userid" : "<user_id or username>",
353 354 "username" : "<username> = Optional",
354 355 "email" : "<useremail> = Optional",
355 356 "password" : "<password> = Optional",
356 357 "firstname" : "<firstname> = Optional",
357 358 "lastname" : "<lastname> = Optional",
358 359 "active" : "<bool> = Optional",
359 360 "admin" : "<bool> = Optional",
360 361 "ldap_dn" : "<ldap_dn> = Optional"
361 362 }
362 363
363 364 OUTPUT::
364 365
365 366 id : <id_given_in_input>
366 367 result: {
367 368 "msg" : "updated user ID:<userid> <username>",
368 369 "user": {
369 370 "user_id" : "<user_id>",
370 371 "username" : "<username>",
371 372 "firstname": "<firstname>",
372 373 "lastname" : "<lastname>",
373 374 "email" : "<email>",
374 375 "emails": "<list_of_all_additional_emails>",
375 376 "active" : "<bool>",
376 377 "admin" :Β  "<bool>",
377 378 "ldap_dn" : "<ldap_dn>",
378 379 "last_login": "<last_login>",
379 380 },
380 381 }
381 382 error: null
382 383
383 384
384 385 delete_user
385 386 -----------
386 387
387 388
388 389 deletes givenuser if such user exists. This command can
389 390 be executed only using api_key belonging to user with admin rights.
390 391
391 392
392 393 INPUT::
393 394
394 395 id : <id_for_response>
395 396 api_key : "<api_key>"
396 397 method : "delete_user"
397 398 args : {
398 399 "userid" : "<user_id or username>",
399 400 }
400 401
401 402 OUTPUT::
402 403
403 404 id : <id_given_in_input>
404 405 result: {
405 406 "msg" : "deleted user ID:<userid> <username>",
406 407 "user": null
407 408 }
408 409 error: null
409 410
410 411
411 412 get_users_group
412 413 ---------------
413 414
414 415 Gets an existing users group. This command can be executed only using api_key
415 416 belonging to user with admin rights.
416 417
417 418
418 419 INPUT::
419 420
420 421 id : <id_for_response>
421 422 api_key : "<api_key>"
422 423 method : "get_users_group"
423 424 args : {
424 425 "usersgroupid" : "<users group id or name>"
425 426 }
426 427
427 428 OUTPUT::
428 429
429 430 id : <id_given_in_input>
430 431 result : None if group not exist
431 432 {
432 433 "users_group_id" : "<id>",
433 434 "group_name" : "<groupname>",
434 435 "active": "<bool>",
435 436 "members" : [
436 437 {
437 438 "user_id" : "<user_id>",
438 439 "username" : "<username>",
439 440 "firstname": "<firstname>",
440 441 "lastname" : "<lastname>",
441 442 "email" : "<email>",
442 443 "emails": "<list_of_all_additional_emails>",
443 444 "active" : "<bool>",
444 445 "admin" :Β  "<bool>",
445 446 "ldap_dn" : "<ldap_dn>",
446 447 "last_login": "<last_login>",
447 448 },
448 449 …
449 450 ]
450 451 }
451 452 error : null
452 453
453 454
454 455 get_users_groups
455 456 ----------------
456 457
457 458 Lists all existing users groups. This command can be executed only using
458 459 api_key belonging to user with admin rights.
459 460
460 461
461 462 INPUT::
462 463
463 464 id : <id_for_response>
464 465 api_key : "<api_key>"
465 466 method : "get_users_groups"
466 467 args : { }
467 468
468 469 OUTPUT::
469 470
470 471 id : <id_given_in_input>
471 472 result : [
472 473 {
473 474 "users_group_id" : "<id>",
474 475 "group_name" : "<groupname>",
475 476 "active": "<bool>",
476 477 },
477 478 …
478 479 ]
479 480 error : null
480 481
481 482
482 483 create_users_group
483 484 ------------------
484 485
485 486 Creates new users group. This command can be executed only using api_key
486 487 belonging to user with admin rights
487 488
488 489
489 490 INPUT::
490 491
491 492 id : <id_for_response>
492 493 api_key : "<api_key>"
493 494 method : "create_users_group"
494 495 args: {
495 496 "group_name": "<groupname>",
496 497 "active":"<bool> = Optional(True)"
497 498 }
498 499
499 500 OUTPUT::
500 501
501 502 id : <id_given_in_input>
502 503 result: {
503 504 "msg": "created new users group `<groupname>`",
504 505 "users_group": {
505 506 "users_group_id" : "<id>",
506 507 "group_name" : "<groupname>",
507 508 "active": "<bool>",
508 509 },
509 510 }
510 511 error: null
511 512
512 513
513 514 add_user_to_users_group
514 515 -----------------------
515 516
516 517 Adds a user to a users group. If user exists in that group success will be
517 518 `false`. This command can be executed only using api_key
518 519 belonging to user with admin rights
519 520
520 521
521 522 INPUT::
522 523
523 524 id : <id_for_response>
524 525 api_key : "<api_key>"
525 526 method : "add_user_users_group"
526 527 args: {
527 528 "usersgroupid" : "<users group id or name>",
528 529 "userid" : "<user_id or username>",
529 530 }
530 531
531 532 OUTPUT::
532 533
533 534 id : <id_given_in_input>
534 535 result: {
535 536 "success": True|False # depends on if member is in group
536 537 "msg": "added member `<username>` to users group `<groupname>` |
537 538 User is already in that group"
538 539 }
539 540 error: null
540 541
541 542
542 543 remove_user_from_users_group
543 544 ----------------------------
544 545
545 546 Removes a user from a users group. If user is not in given group success will
546 547 be `false`. This command can be executed only
547 548 using api_key belonging to user with admin rights
548 549
549 550
550 551 INPUT::
551 552
552 553 id : <id_for_response>
553 554 api_key : "<api_key>"
554 555 method : "remove_user_from_users_group"
555 556 args: {
556 557 "usersgroupid" : "<users group id or name>",
557 558 "userid" : "<user_id or username>",
558 559 }
559 560
560 561 OUTPUT::
561 562
562 563 id : <id_given_in_input>
563 564 result: {
564 565 "success": True|False, # depends on if member is in group
565 566 "msg": "removed member <username> from users group <groupname> |
566 567 User wasn't in group"
567 568 }
568 569 error: null
569 570
570 571
571 572 get_repo
572 573 --------
573 574
574 575 Gets an existing repository by it's name or repository_id. Members will return
575 576 either users_group or user associated to that repository. This command can
576 577 be executed only using api_key belonging to user with admin rights.
577 578
578 579
579 580 INPUT::
580 581
581 582 id : <id_for_response>
582 583 api_key : "<api_key>"
583 584 method : "get_repo"
584 585 args: {
585 586 "repoid" : "<reponame or repo_id>"
586 587 }
587 588
588 589 OUTPUT::
589 590
590 591 id : <id_given_in_input>
591 592 result: None if repository does not exist or
592 593 {
593 594 "repo_id" : "<repo_id>",
594 595 "repo_name" : "<reponame>"
595 596 "repo_type" : "<repo_type>",
596 597 "clone_uri" : "<clone_uri>",
597 598 "enable_downloads": "<bool>",
598 599 "enable_locking": "<bool>",
599 600 "enable_statistics": "<bool>",
600 601 "private": "<bool>",
601 602 "created_on" : "<datetimecreated>",
602 603 "description" : "<description>",
603 604 "landing_rev": "<landing_rev>",
604 605 "owner": "<repo_owner>",
605 606 "fork_of": "<name_of_fork_parent>",
606 607 "members" : [
607 608 {
608 609 "type": "user",
609 610 "user_id" : "<user_id>",
610 611 "username" : "<username>",
611 612 "firstname": "<firstname>",
612 613 "lastname" : "<lastname>",
613 614 "email" : "<email>",
614 615 "emails": "<list_of_all_additional_emails>",
615 616 "active" : "<bool>",
616 617 "admin" :Β  "<bool>",
617 618 "ldap_dn" : "<ldap_dn>",
618 619 "last_login": "<last_login>",
619 620 "permission" : "repository.(read|write|admin)"
620 621 },
621 622 …
622 623 {
623 624 "type": "users_group",
624 625 "id" : "<usersgroupid>",
625 626 "name" : "<usersgroupname>",
626 627 "active": "<bool>",
627 628 "permission" : "repository.(read|write|admin)"
628 629 },
629 630 …
630 631 ]
631 632 }
632 633 error: null
633 634
634 635
635 636 get_repos
636 637 ---------
637 638
638 639 Lists all existing repositories. This command can be executed only using api_key
639 640 belonging to user with admin rights
640 641
641 642
642 643 INPUT::
643 644
644 645 id : <id_for_response>
645 646 api_key : "<api_key>"
646 647 method : "get_repos"
647 648 args: { }
648 649
649 650 OUTPUT::
650 651
651 652 id : <id_given_in_input>
652 653 result: [
653 654 {
654 655 "repo_id" : "<repo_id>",
655 656 "repo_name" : "<reponame>"
656 657 "repo_type" : "<repo_type>",
657 658 "clone_uri" : "<clone_uri>",
658 659 "private": : "<bool>",
659 660 "created_on" : "<datetimecreated>",
660 661 "description" : "<description>",
661 662 "landing_rev": "<landing_rev>",
662 663 "owner": "<repo_owner>",
663 664 "fork_of": "<name_of_fork_parent>",
664 665 "enable_downloads": "<bool>",
665 666 "enable_locking": "<bool>",
666 667 "enable_statistics": "<bool>",
667 668 },
668 669 …
669 670 ]
670 671 error: null
671 672
672 673
673 674 get_repo_nodes
674 675 --------------
675 676
676 677 returns a list of nodes and it's children in a flat list for a given path
677 678 at given revision. It's possible to specify ret_type to show only `files` or
678 679 `dirs`. This command can be executed only using api_key belonging to user
679 680 with admin rights
680 681
681 682
682 683 INPUT::
683 684
684 685 id : <id_for_response>
685 686 api_key : "<api_key>"
686 687 method : "get_repo_nodes"
687 688 args: {
688 689 "repoid" : "<reponame or repo_id>"
689 690 "revision" : "<revision>",
690 691 "root_path" : "<root_path>",
691 692 "ret_type" : "<ret_type> = Optional('all')"
692 693 }
693 694
694 695 OUTPUT::
695 696
696 697 id : <id_given_in_input>
697 698 result: [
698 699 {
699 700 "name" : "<name>"
700 701 "type" : "<type>",
701 702 },
702 703 …
703 704 ]
704 705 error: null
705 706
706 707
707 708 create_repo
708 709 -----------
709 710
710 711 Creates a repository. This command can be executed only using api_key
711 712 belonging to user with admin rights.
712 713 If repository name contains "/", all needed repository groups will be created.
713 714 For example "foo/bar/baz" will create groups "foo", "bar" (with "foo" as parent),
714 715 and create "baz" repository with "bar" as group.
715 716
716 717
717 718 INPUT::
718 719
719 720 id : <id_for_response>
720 721 api_key : "<api_key>"
721 722 method : "create_repo"
722 723 args: {
723 724 "repo_name" : "<reponame>",
724 725 "owner" : "<onwer_name_or_id>",
725 726 "repo_type" : "<repo_type> = Optional('hg')",
726 727 "description" : "<description> = Optional('')",
727 728 "private" : "<bool> = Optional(False)",
728 729 "clone_uri" : "<clone_uri> = Optional(None)",
729 730 "landing_rev" : "<landing_rev> = Optional('tip')",
730 731 "enable_downloads": "<bool> = Optional(False)",
731 732 "enable_locking": "<bool> = Optional(False)",
732 733 "enable_statistics": "<bool> = Optional(False)",
733 734 }
734 735
735 736 OUTPUT::
736 737
737 738 id : <id_given_in_input>
738 739 result: {
739 740 "msg": "Created new repository `<reponame>`",
740 741 "repo": {
741 742 "repo_id" : "<repo_id>",
742 743 "repo_name" : "<reponame>"
743 744 "repo_type" : "<repo_type>",
744 745 "clone_uri" : "<clone_uri>",
745 746 "private": : "<bool>",
746 747 "created_on" : "<datetimecreated>",
747 748 "description" : "<description>",
748 749 "landing_rev": "<landing_rev>",
749 750 "owner": "<username or user_id>",
750 751 "fork_of": "<name_of_fork_parent>",
751 752 "enable_downloads": "<bool>",
752 753 "enable_locking": "<bool>",
753 754 "enable_statistics": "<bool>",
754 755 },
755 756 }
756 757 error: null
757 758
758 759
759 760 fork_repo
760 761 ---------
761 762
762 763 Creates a fork of given repo. This command can be executed only using api_key
763 764 belonging to user with admin rights. In case of using celery this will
764 765 immidiatelly return success message, while fork is going to be created
765 766 asynchronous
766 767
767 768
768 769 INPUT::
769 770
770 771 id : <id_for_response>
771 772 api_key : "<api_key>"
772 773 method : "fork_repo"
773 774 args: {
774 775 "repoid" : "<reponame or repo_id>",
775 776 "fork_name": "<forkname>",
776 777 "owner": "<username or user_id>",
777 778 "description": "<description>",
778 779 "copy_permissions": "<bool>",
779 780 "private": "<bool>",
780 781 "landing_rev": "<landing_rev>"
781 782
782 783 }
783 784
784 785 OUTPUT::
785 786
786 787 id : <id_given_in_input>
787 788 result: {
788 789 "msg": "Created fork of `<reponame>` as `<forkname>`",
789 790 "success": true
790 791 }
791 792 error: null
792 793
793 794
794 795 delete_repo
795 796 -----------
796 797
797 798 Deletes a repository. This command can be executed only using api_key
798 799 belonging to user with admin rights.
799 800
800 801
801 802 INPUT::
802 803
803 804 id : <id_for_response>
804 805 api_key : "<api_key>"
805 806 method : "delete_repo"
806 807 args: {
807 808 "repoid" : "<reponame or repo_id>"
808 809 }
809 810
810 811 OUTPUT::
811 812
812 813 id : <id_given_in_input>
813 814 result: {
814 815 "msg": "Deleted repository `<reponame>`",
815 816 "success": true
816 817 }
817 818 error: null
818 819
819 820
820 821 grant_user_permission
821 822 ---------------------
822 823
823 824 Grant permission for user on given repository, or update existing one
824 825 if found. This command can be executed only using api_key belonging to user
825 826 with admin rights.
826 827
827 828
828 829 INPUT::
829 830
830 831 id : <id_for_response>
831 832 api_key : "<api_key>"
832 833 method : "grant_user_permission"
833 834 args: {
834 835 "repoid" : "<reponame or repo_id>"
835 836 "userid" : "<username or user_id>"
836 837 "perm" : "(repository.(none|read|write|admin))",
837 838 }
838 839
839 840 OUTPUT::
840 841
841 842 id : <id_given_in_input>
842 843 result: {
843 844 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
844 845 "success": true
845 846 }
846 847 error: null
847 848
848 849
849 850 revoke_user_permission
850 851 ----------------------
851 852
852 853 Revoke permission for user on given repository. This command can be executed
853 854 only using api_key belonging to user with admin rights.
854 855
855 856
856 857 INPUT::
857 858
858 859 id : <id_for_response>
859 860 api_key : "<api_key>"
860 861 method : "revoke_user_permission"
861 862 args: {
862 863 "repoid" : "<reponame or repo_id>"
863 864 "userid" : "<username or user_id>"
864 865 }
865 866
866 867 OUTPUT::
867 868
868 869 id : <id_given_in_input>
869 870 result: {
870 871 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
871 872 "success": true
872 873 }
873 874 error: null
874 875
875 876
876 877 grant_users_group_permission
877 878 ----------------------------
878 879
879 880 Grant permission for users group on given repository, or update
880 881 existing one if found. This command can be executed only using
881 882 api_key belonging to user with admin rights.
882 883
883 884
884 885 INPUT::
885 886
886 887 id : <id_for_response>
887 888 api_key : "<api_key>"
888 889 method : "grant_users_group_permission"
889 890 args: {
890 891 "repoid" : "<reponame or repo_id>"
891 892 "usersgroupid" : "<users group id or name>"
892 893 "perm" : "(repository.(none|read|write|admin))",
893 894 }
894 895
895 896 OUTPUT::
896 897
897 898 id : <id_given_in_input>
898 899 result: {
899 900 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
900 901 "success": true
901 902 }
902 903 error: null
903 904
904 905
905 906 revoke_users_group_permission
906 907 -----------------------------
907 908
908 909 Revoke permission for users group on given repository.This command can be
909 910 executed only using api_key belonging to user with admin rights.
910 911
911 912 INPUT::
912 913
913 914 id : <id_for_response>
914 915 api_key : "<api_key>"
915 916 method : "revoke_users_group_permission"
916 917 args: {
917 918 "repoid" : "<reponame or repo_id>"
918 919 "usersgroupid" : "<users group id or name>"
919 920 }
920 921
921 922 OUTPUT::
922 923
923 924 id : <id_given_in_input>
924 925 result: {
925 926 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
926 927 "success": true
927 928 }
928 929 error: null No newline at end of file
@@ -1,844 +1,877 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
30 31
31 32 from rhodecode.controllers.api import JSONRPCController, JSONRPCError
32 from rhodecode.lib.auth import HasPermissionAllDecorator, \
33 HasPermissionAnyDecorator, PasswordGenerator, AuthUser
33 from rhodecode.lib.auth import PasswordGenerator, AuthUser, \
34 HasPermissionAllDecorator, HasPermissionAnyDecorator, \
35 HasPermissionAnyApi, HasRepoPermissionAnyApi
34 36 from rhodecode.lib.utils import map_groups, repo2db_mapper
35 37 from rhodecode.model.meta import Session
36 38 from rhodecode.model.scm import ScmModel
37 39 from rhodecode.model.repo import RepoModel
38 40 from rhodecode.model.user import UserModel
39 41 from rhodecode.model.users_group import UsersGroupModel
40 42 from rhodecode.model.permission import PermissionModel
41 43 from rhodecode.model.db import Repository, RhodeCodeSetting, UserIpMap
42 44
43 45 log = logging.getLogger(__name__)
44 46
45 47
48 class OptionalAttr(object):
49 """
50 Special Optional Option that defines other attribute
51 """
52 def __init__(self, attr_name):
53 self.attr_name = attr_name
54
55 def __repr__(self):
56 return '<OptionalAttr:%s>' % self.attr_name
57
58 def __call__(self):
59 return self
60 #alias
61 OAttr = OptionalAttr
62
63
46 64 class Optional(object):
47 65 """
48 66 Defines an optional parameter::
49 67
50 68 param = param.getval() if isinstance(param, Optional) else param
51 69 param = param() if isinstance(param, Optional) else param
52 70
53 71 is equivalent of::
54 72
55 73 param = Optional.extract(param)
56 74
57 75 """
58 76 def __init__(self, type_):
59 77 self.type_ = type_
60 78
61 79 def __repr__(self):
62 80 return '<Optional:%s>' % self.type_.__repr__()
63 81
64 82 def __call__(self):
65 83 return self.getval()
66 84
67 85 def getval(self):
68 86 """
69 87 returns value from this Optional instance
70 88 """
71 89 return self.type_
72 90
73 91 @classmethod
74 92 def extract(cls, val):
75 93 if isinstance(val, cls):
76 94 return val.getval()
77 95 return val
78 96
79 97
80 98 def get_user_or_error(userid):
81 99 """
82 100 Get user by id or name or return JsonRPCError if not found
83 101
84 102 :param userid:
85 103 """
86 104 user = UserModel().get_user(userid)
87 105 if user is None:
88 106 raise JSONRPCError("user `%s` does not exist" % userid)
89 107 return user
90 108
91 109
92 110 def get_repo_or_error(repoid):
93 111 """
94 112 Get repo by id or name or return JsonRPCError if not found
95 113
96 114 :param userid:
97 115 """
98 116 repo = RepoModel().get_repo(repoid)
99 117 if repo is None:
100 118 raise JSONRPCError('repository `%s` does not exist' % (repoid))
101 119 return repo
102 120
103 121
104 122 def get_users_group_or_error(usersgroupid):
105 123 """
106 124 Get users group by id or name or return JsonRPCError if not found
107 125
108 126 :param userid:
109 127 """
110 128 users_group = UsersGroupModel().get_group(usersgroupid)
111 129 if users_group is None:
112 130 raise JSONRPCError('users group `%s` does not exist' % usersgroupid)
113 131 return users_group
114 132
115 133
116 134 def get_perm_or_error(permid):
117 135 """
118 136 Get permission by id or name or return JsonRPCError if not found
119 137
120 138 :param userid:
121 139 """
122 140 perm = PermissionModel().get_permission_by_name(permid)
123 141 if perm is None:
124 142 raise JSONRPCError('permission `%s` does not exist' % (permid))
125 143 return perm
126 144
127 145
128 146 class ApiController(JSONRPCController):
129 147 """
130 148 API Controller
131 149
132 150
133 151 Each method needs to have USER as argument this is then based on given
134 152 API_KEY propagated as instance of user object
135 153
136 154 Preferably this should be first argument also
137 155
138 156
139 157 Each function should also **raise** JSONRPCError for any
140 158 errors that happens
141 159
142 160 """
143 161
144 162 @HasPermissionAllDecorator('hg.admin')
145 163 def pull(self, apiuser, repoid):
146 164 """
147 165 Dispatch pull action on given repo
148 166
149 167 :param apiuser:
150 168 :param repoid:
151 169 """
152 170
153 171 repo = get_repo_or_error(repoid)
154 172
155 173 try:
156 174 ScmModel().pull_changes(repo.repo_name,
157 175 self.rhodecode_user.username)
158 176 return 'Pulled from `%s`' % repo.repo_name
159 177 except Exception:
160 178 log.error(traceback.format_exc())
161 179 raise JSONRPCError(
162 180 'Unable to pull changes from `%s`' % repo.repo_name
163 181 )
164 182
165 183 @HasPermissionAllDecorator('hg.admin')
166 184 def rescan_repos(self, apiuser, remove_obsolete=Optional(False)):
167 185 """
168 186 Dispatch rescan repositories action. If remove_obsolete is set
169 187 than also delete repos that are in database but not in the filesystem.
170 188 aka "clean zombies"
171 189
172 190 :param apiuser:
173 191 :param remove_obsolete:
174 192 """
175 193
176 194 try:
177 195 rm_obsolete = Optional.extract(remove_obsolete)
178 196 added, removed = repo2db_mapper(ScmModel().repo_scan(),
179 197 remove_obsolete=rm_obsolete)
180 198 return {'added': added, 'removed': removed}
181 199 except Exception:
182 200 log.error(traceback.format_exc())
183 201 raise JSONRPCError(
184 202 'Error occurred during rescan repositories action'
185 203 )
186 204
187 @HasPermissionAllDecorator('hg.admin')
188 def lock(self, apiuser, repoid, userid, locked):
205 def lock(self, apiuser, repoid, locked, userid=Optional(OAttr('apiuser'))):
189 206 """
190 Set locking state on particular repository by given user
207 Set locking state on particular repository by given user, if
208 this command is runned by non-admin account userid is set to user
209 who is calling this method
191 210
192 211 :param apiuser:
193 212 :param repoid:
194 213 :param userid:
195 214 :param locked:
196 215 """
197 216 repo = get_repo_or_error(repoid)
217 if HasPermissionAnyApi('hg.admin')(user=apiuser):
218 pass
219 elif HasRepoPermissionAnyApi('repository.admin',
220 'repository.write')(user=apiuser,
221 repo_name=repo.repo_name):
222 #make sure normal user does not pass userid, he is not allowed to do that
223 if not isinstance(userid, Optional):
224 raise JSONRPCError(
225 'Only RhodeCode admin can specify `userid` params'
226 )
227 else:
228 return abort(403)
229 if isinstance(userid, Optional):
230 userid = apiuser.user_id
198 231 user = get_user_or_error(userid)
199 232 locked = bool(locked)
200 233 try:
201 234 if locked:
202 235 Repository.lock(repo, user.user_id)
203 236 else:
204 237 Repository.unlock(repo)
205 238
206 239 return ('User `%s` set lock state for repo `%s` to `%s`'
207 240 % (user.username, repo.repo_name, locked))
208 241 except Exception:
209 242 log.error(traceback.format_exc())
210 243 raise JSONRPCError(
211 244 'Error occurred locking repository `%s`' % repo.repo_name
212 245 )
213 246
214 247 @HasPermissionAllDecorator('hg.admin')
215 248 def show_ip(self, apiuser, userid):
216 249 """
217 250 Shows IP address as seen from RhodeCode server, together with all
218 251 defined IP addresses for given user
219 252
220 253 :param apiuser:
221 254 :param userid:
222 255 """
223 256 user = get_user_or_error(userid)
224 257 ips = UserIpMap.query().filter(UserIpMap.user == user).all()
225 258 return dict(
226 259 ip_addr_server=self.ip_addr,
227 260 user_ips=ips
228 261 )
229 262
230 263 @HasPermissionAllDecorator('hg.admin')
231 264 def get_user(self, apiuser, userid):
232 265 """"
233 266 Get a user by username
234 267
235 268 :param apiuser:
236 269 :param userid:
237 270 """
238 271
239 272 user = get_user_or_error(userid)
240 273 data = user.get_api_data()
241 274 data['permissions'] = AuthUser(user_id=user.user_id).permissions
242 275 return data
243 276
244 277 @HasPermissionAllDecorator('hg.admin')
245 278 def get_users(self, apiuser):
246 279 """"
247 280 Get all users
248 281
249 282 :param apiuser:
250 283 """
251 284
252 285 result = []
253 286 for user in UserModel().get_all():
254 287 result.append(user.get_api_data())
255 288 return result
256 289
257 290 @HasPermissionAllDecorator('hg.admin')
258 291 def create_user(self, apiuser, username, email, password,
259 292 firstname=Optional(None), lastname=Optional(None),
260 293 active=Optional(True), admin=Optional(False),
261 294 ldap_dn=Optional(None)):
262 295 """
263 296 Create new user
264 297
265 298 :param apiuser:
266 299 :param username:
267 300 :param email:
268 301 :param password:
269 302 :param firstname:
270 303 :param lastname:
271 304 :param active:
272 305 :param admin:
273 306 :param ldap_dn:
274 307 """
275 308
276 309 if UserModel().get_by_username(username):
277 310 raise JSONRPCError("user `%s` already exist" % username)
278 311
279 312 if UserModel().get_by_email(email, case_insensitive=True):
280 313 raise JSONRPCError("email `%s` already exist" % email)
281 314
282 315 if Optional.extract(ldap_dn):
283 316 # generate temporary password if ldap_dn
284 317 password = PasswordGenerator().gen_password(length=8)
285 318
286 319 try:
287 320 user = UserModel().create_or_update(
288 321 username=Optional.extract(username),
289 322 password=Optional.extract(password),
290 323 email=Optional.extract(email),
291 324 firstname=Optional.extract(firstname),
292 325 lastname=Optional.extract(lastname),
293 326 active=Optional.extract(active),
294 327 admin=Optional.extract(admin),
295 328 ldap_dn=Optional.extract(ldap_dn)
296 329 )
297 330 Session().commit()
298 331 return dict(
299 332 msg='created new user `%s`' % username,
300 333 user=user.get_api_data()
301 334 )
302 335 except Exception:
303 336 log.error(traceback.format_exc())
304 337 raise JSONRPCError('failed to create user `%s`' % username)
305 338
306 339 @HasPermissionAllDecorator('hg.admin')
307 340 def update_user(self, apiuser, userid, username=Optional(None),
308 341 email=Optional(None), firstname=Optional(None),
309 342 lastname=Optional(None), active=Optional(None),
310 343 admin=Optional(None), ldap_dn=Optional(None),
311 344 password=Optional(None)):
312 345 """
313 346 Updates given user
314 347
315 348 :param apiuser:
316 349 :param userid:
317 350 :param username:
318 351 :param email:
319 352 :param firstname:
320 353 :param lastname:
321 354 :param active:
322 355 :param admin:
323 356 :param ldap_dn:
324 357 :param password:
325 358 """
326 359
327 360 user = get_user_or_error(userid)
328 361
329 362 # call function and store only updated arguments
330 363 updates = {}
331 364
332 365 def store_update(attr, name):
333 366 if not isinstance(attr, Optional):
334 367 updates[name] = attr
335 368
336 369 try:
337 370
338 371 store_update(username, 'username')
339 372 store_update(password, 'password')
340 373 store_update(email, 'email')
341 374 store_update(firstname, 'name')
342 375 store_update(lastname, 'lastname')
343 376 store_update(active, 'active')
344 377 store_update(admin, 'admin')
345 378 store_update(ldap_dn, 'ldap_dn')
346 379
347 380 user = UserModel().update_user(user, **updates)
348 381 Session().commit()
349 382 return dict(
350 383 msg='updated user ID:%s %s' % (user.user_id, user.username),
351 384 user=user.get_api_data()
352 385 )
353 386 except Exception:
354 387 log.error(traceback.format_exc())
355 388 raise JSONRPCError('failed to update user `%s`' % userid)
356 389
357 390 @HasPermissionAllDecorator('hg.admin')
358 391 def delete_user(self, apiuser, userid):
359 392 """"
360 393 Deletes an user
361 394
362 395 :param apiuser:
363 396 :param userid:
364 397 """
365 398 user = get_user_or_error(userid)
366 399
367 400 try:
368 401 UserModel().delete(userid)
369 402 Session().commit()
370 403 return dict(
371 404 msg='deleted user ID:%s %s' % (user.user_id, user.username),
372 405 user=None
373 406 )
374 407 except Exception:
375 408 log.error(traceback.format_exc())
376 409 raise JSONRPCError('failed to delete ID:%s %s' % (user.user_id,
377 410 user.username))
378 411
379 412 @HasPermissionAllDecorator('hg.admin')
380 413 def get_users_group(self, apiuser, usersgroupid):
381 414 """"
382 415 Get users group by name or id
383 416
384 417 :param apiuser:
385 418 :param usersgroupid:
386 419 """
387 420 users_group = get_users_group_or_error(usersgroupid)
388 421
389 422 data = users_group.get_api_data()
390 423
391 424 members = []
392 425 for user in users_group.members:
393 426 user = user.user
394 427 members.append(user.get_api_data())
395 428 data['members'] = members
396 429 return data
397 430
398 431 @HasPermissionAllDecorator('hg.admin')
399 432 def get_users_groups(self, apiuser):
400 433 """"
401 434 Get all users groups
402 435
403 436 :param apiuser:
404 437 """
405 438
406 439 result = []
407 440 for users_group in UsersGroupModel().get_all():
408 441 result.append(users_group.get_api_data())
409 442 return result
410 443
411 444 @HasPermissionAllDecorator('hg.admin')
412 445 def create_users_group(self, apiuser, group_name, active=Optional(True)):
413 446 """
414 447 Creates an new usergroup
415 448
416 449 :param apiuser:
417 450 :param group_name:
418 451 :param active:
419 452 """
420 453
421 454 if UsersGroupModel().get_by_name(group_name):
422 455 raise JSONRPCError("users group `%s` already exist" % group_name)
423 456
424 457 try:
425 458 active = Optional.extract(active)
426 459 ug = UsersGroupModel().create(name=group_name, active=active)
427 460 Session().commit()
428 461 return dict(
429 462 msg='created new users group `%s`' % group_name,
430 463 users_group=ug.get_api_data()
431 464 )
432 465 except Exception:
433 466 log.error(traceback.format_exc())
434 467 raise JSONRPCError('failed to create group `%s`' % group_name)
435 468
436 469 @HasPermissionAllDecorator('hg.admin')
437 470 def add_user_to_users_group(self, apiuser, usersgroupid, userid):
438 471 """"
439 472 Add a user to a users group
440 473
441 474 :param apiuser:
442 475 :param usersgroupid:
443 476 :param userid:
444 477 """
445 478 user = get_user_or_error(userid)
446 479 users_group = get_users_group_or_error(usersgroupid)
447 480
448 481 try:
449 482 ugm = UsersGroupModel().add_user_to_group(users_group, user)
450 483 success = True if ugm != True else False
451 484 msg = 'added member `%s` to users group `%s`' % (
452 485 user.username, users_group.users_group_name
453 486 )
454 487 msg = msg if success else 'User is already in that group'
455 488 Session().commit()
456 489
457 490 return dict(
458 491 success=success,
459 492 msg=msg
460 493 )
461 494 except Exception:
462 495 log.error(traceback.format_exc())
463 496 raise JSONRPCError(
464 497 'failed to add member to users group `%s`' % (
465 498 users_group.users_group_name
466 499 )
467 500 )
468 501
469 502 @HasPermissionAllDecorator('hg.admin')
470 503 def remove_user_from_users_group(self, apiuser, usersgroupid, userid):
471 504 """
472 505 Remove user from a group
473 506
474 507 :param apiuser:
475 508 :param usersgroupid:
476 509 :param userid:
477 510 """
478 511 user = get_user_or_error(userid)
479 512 users_group = get_users_group_or_error(usersgroupid)
480 513
481 514 try:
482 515 success = UsersGroupModel().remove_user_from_group(users_group,
483 516 user)
484 517 msg = 'removed member `%s` from users group `%s`' % (
485 518 user.username, users_group.users_group_name
486 519 )
487 520 msg = msg if success else "User wasn't in group"
488 521 Session().commit()
489 522 return dict(success=success, msg=msg)
490 523 except Exception:
491 524 log.error(traceback.format_exc())
492 525 raise JSONRPCError(
493 526 'failed to remove member from users group `%s`' % (
494 527 users_group.users_group_name
495 528 )
496 529 )
497 530
498 @HasPermissionAnyDecorator('hg.admin')
531 @HasPermissionAllDecorator('hg.admin')
499 532 def get_repo(self, apiuser, repoid):
500 533 """"
501 534 Get repository by name
502 535
503 536 :param apiuser:
504 537 :param repoid:
505 538 """
506 539 repo = get_repo_or_error(repoid)
507 540
508 541 members = []
509 542 for user in repo.repo_to_perm:
510 543 perm = user.permission.permission_name
511 544 user = user.user
512 545 user_data = user.get_api_data()
513 546 user_data['type'] = "user"
514 547 user_data['permission'] = perm
515 548 members.append(user_data)
516 549
517 550 for users_group in repo.users_group_to_perm:
518 551 perm = users_group.permission.permission_name
519 552 users_group = users_group.users_group
520 553 users_group_data = users_group.get_api_data()
521 554 users_group_data['type'] = "users_group"
522 555 users_group_data['permission'] = perm
523 556 members.append(users_group_data)
524 557
525 558 data = repo.get_api_data()
526 559 data['members'] = members
527 560 return data
528 561
529 @HasPermissionAnyDecorator('hg.admin')
562 @HasPermissionAllDecorator('hg.admin')
530 563 def get_repos(self, apiuser):
531 564 """"
532 565 Get all repositories
533 566
534 567 :param apiuser:
535 568 """
536 569
537 570 result = []
538 571 for repo in RepoModel().get_all():
539 572 result.append(repo.get_api_data())
540 573 return result
541 574
542 @HasPermissionAnyDecorator('hg.admin')
575 @HasPermissionAllDecorator('hg.admin')
543 576 def get_repo_nodes(self, apiuser, repoid, revision, root_path,
544 577 ret_type='all'):
545 578 """
546 579 returns a list of nodes and it's children
547 580 for a given path at given revision. It's possible to specify ret_type
548 581 to show only files or dirs
549 582
550 583 :param apiuser:
551 584 :param repoid: name or id of repository
552 585 :param revision: revision for which listing should be done
553 586 :param root_path: path from which start displaying
554 587 :param ret_type: return type 'all|files|dirs' nodes
555 588 """
556 589 repo = get_repo_or_error(repoid)
557 590 try:
558 591 _d, _f = ScmModel().get_nodes(repo, revision, root_path,
559 592 flat=False)
560 593 _map = {
561 594 'all': _d + _f,
562 595 'files': _f,
563 596 'dirs': _d,
564 597 }
565 598 return _map[ret_type]
566 599 except KeyError:
567 600 raise JSONRPCError('ret_type must be one of %s' % _map.keys())
568 601 except Exception:
569 602 log.error(traceback.format_exc())
570 603 raise JSONRPCError(
571 604 'failed to get repo: `%s` nodes' % repo.repo_name
572 605 )
573 606
574 607 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
575 608 def create_repo(self, apiuser, repo_name, owner, repo_type=Optional('hg'),
576 609 description=Optional(''), private=Optional(False),
577 610 clone_uri=Optional(None), landing_rev=Optional('tip'),
578 611 enable_statistics=Optional(False),
579 612 enable_locking=Optional(False),
580 613 enable_downloads=Optional(False)):
581 614 """
582 615 Create repository, if clone_url is given it makes a remote clone
583 616 if repo_name is withina group name the groups will be created
584 617 automatically if they aren't present
585 618
586 619 :param apiuser:
587 620 :param repo_name:
588 621 :param onwer:
589 622 :param repo_type:
590 623 :param description:
591 624 :param private:
592 625 :param clone_uri:
593 626 :param landing_rev:
594 627 """
595 628 owner = get_user_or_error(owner)
596 629
597 630 if RepoModel().get_by_repo_name(repo_name):
598 631 raise JSONRPCError("repo `%s` already exist" % repo_name)
599 632
600 633 defs = RhodeCodeSetting.get_default_repo_settings(strip_prefix=True)
601 634 if isinstance(private, Optional):
602 635 private = defs.get('repo_private') or Optional.extract(private)
603 636 if isinstance(repo_type, Optional):
604 637 repo_type = defs.get('repo_type')
605 638 if isinstance(enable_statistics, Optional):
606 639 enable_statistics = defs.get('repo_enable_statistics')
607 640 if isinstance(enable_locking, Optional):
608 641 enable_locking = defs.get('repo_enable_locking')
609 642 if isinstance(enable_downloads, Optional):
610 643 enable_downloads = defs.get('repo_enable_downloads')
611 644
612 645 clone_uri = Optional.extract(clone_uri)
613 646 description = Optional.extract(description)
614 647 landing_rev = Optional.extract(landing_rev)
615 648
616 649 try:
617 650 # create structure of groups and return the last group
618 651 group = map_groups(repo_name)
619 652
620 653 repo = RepoModel().create_repo(
621 654 repo_name=repo_name,
622 655 repo_type=repo_type,
623 656 description=description,
624 657 owner=owner,
625 658 private=private,
626 659 clone_uri=clone_uri,
627 660 repos_group=group,
628 661 landing_rev=landing_rev,
629 662 enable_statistics=enable_statistics,
630 663 enable_downloads=enable_downloads,
631 664 enable_locking=enable_locking
632 665 )
633 666
634 667 Session().commit()
635 668
636 669 return dict(
637 670 msg="Created new repository `%s`" % (repo.repo_name),
638 671 repo=repo.get_api_data()
639 672 )
640 673
641 674 except Exception:
642 675 log.error(traceback.format_exc())
643 676 raise JSONRPCError('failed to create repository `%s`' % repo_name)
644 677
645 @HasPermissionAnyDecorator('hg.admin')
678 @HasPermissionAllDecorator('hg.admin')
646 679 def fork_repo(self, apiuser, repoid, fork_name, owner,
647 680 description=Optional(''), copy_permissions=Optional(False),
648 681 private=Optional(False), landing_rev=Optional('tip')):
649 682 repo = get_repo_or_error(repoid)
650 683 repo_name = repo.repo_name
651 684 owner = get_user_or_error(owner)
652 685
653 686 _repo = RepoModel().get_by_repo_name(fork_name)
654 687 if _repo:
655 688 type_ = 'fork' if _repo.fork else 'repo'
656 689 raise JSONRPCError("%s `%s` already exist" % (type_, fork_name))
657 690
658 691 try:
659 692 # create structure of groups and return the last group
660 693 group = map_groups(fork_name)
661 694
662 695 form_data = dict(
663 696 repo_name=fork_name,
664 697 repo_name_full=fork_name,
665 698 repo_group=group,
666 699 repo_type=repo.repo_type,
667 700 description=Optional.extract(description),
668 701 private=Optional.extract(private),
669 702 copy_permissions=Optional.extract(copy_permissions),
670 703 landing_rev=Optional.extract(landing_rev),
671 704 update_after_clone=False,
672 705 fork_parent_id=repo.repo_id,
673 706 )
674 707 RepoModel().create_fork(form_data, cur_user=owner)
675 708 return dict(
676 709 msg='Created fork of `%s` as `%s`' % (repo.repo_name,
677 710 fork_name),
678 711 success=True # cannot return the repo data here since fork
679 712 # cann be done async
680 713 )
681 714 except Exception:
682 715 log.error(traceback.format_exc())
683 716 raise JSONRPCError(
684 717 'failed to fork repository `%s` as `%s`' % (repo_name,
685 718 fork_name)
686 719 )
687 720
688 @HasPermissionAnyDecorator('hg.admin')
721 @HasPermissionAllDecorator('hg.admin')
689 722 def delete_repo(self, apiuser, repoid):
690 723 """
691 724 Deletes a given repository
692 725
693 726 :param apiuser:
694 727 :param repoid:
695 728 """
696 729 repo = get_repo_or_error(repoid)
697 730
698 731 try:
699 732 RepoModel().delete(repo)
700 733 Session().commit()
701 734 return dict(
702 735 msg='Deleted repository `%s`' % repo.repo_name,
703 736 success=True
704 737 )
705 738 except Exception:
706 739 log.error(traceback.format_exc())
707 740 raise JSONRPCError(
708 741 'failed to delete repository `%s`' % repo.repo_name
709 742 )
710 743
711 @HasPermissionAnyDecorator('hg.admin')
744 @HasPermissionAllDecorator('hg.admin')
712 745 def grant_user_permission(self, apiuser, repoid, userid, perm):
713 746 """
714 747 Grant permission for user on given repository, or update existing one
715 748 if found
716 749
717 750 :param repoid:
718 751 :param userid:
719 752 :param perm:
720 753 """
721 754 repo = get_repo_or_error(repoid)
722 755 user = get_user_or_error(userid)
723 756 perm = get_perm_or_error(perm)
724 757
725 758 try:
726 759
727 760 RepoModel().grant_user_permission(repo=repo, user=user, perm=perm)
728 761
729 762 Session().commit()
730 763 return dict(
731 764 msg='Granted perm: `%s` for user: `%s` in repo: `%s`' % (
732 765 perm.permission_name, user.username, repo.repo_name
733 766 ),
734 767 success=True
735 768 )
736 769 except Exception:
737 770 log.error(traceback.format_exc())
738 771 raise JSONRPCError(
739 772 'failed to edit permission for user: `%s` in repo: `%s`' % (
740 773 userid, repoid
741 774 )
742 775 )
743 776
744 @HasPermissionAnyDecorator('hg.admin')
777 @HasPermissionAllDecorator('hg.admin')
745 778 def revoke_user_permission(self, apiuser, repoid, userid):
746 779 """
747 780 Revoke permission for user on given repository
748 781
749 782 :param apiuser:
750 783 :param repoid:
751 784 :param userid:
752 785 """
753 786
754 787 repo = get_repo_or_error(repoid)
755 788 user = get_user_or_error(userid)
756 789 try:
757 790
758 791 RepoModel().revoke_user_permission(repo=repo, user=user)
759 792
760 793 Session().commit()
761 794 return dict(
762 795 msg='Revoked perm for user: `%s` in repo: `%s`' % (
763 796 user.username, repo.repo_name
764 797 ),
765 798 success=True
766 799 )
767 800 except Exception:
768 801 log.error(traceback.format_exc())
769 802 raise JSONRPCError(
770 803 'failed to edit permission for user: `%s` in repo: `%s`' % (
771 804 userid, repoid
772 805 )
773 806 )
774 807
775 @HasPermissionAnyDecorator('hg.admin')
808 @HasPermissionAllDecorator('hg.admin')
776 809 def grant_users_group_permission(self, apiuser, repoid, usersgroupid,
777 810 perm):
778 811 """
779 812 Grant permission for users group on given repository, or update
780 813 existing one if found
781 814
782 815 :param apiuser:
783 816 :param repoid:
784 817 :param usersgroupid:
785 818 :param perm:
786 819 """
787 820 repo = get_repo_or_error(repoid)
788 821 perm = get_perm_or_error(perm)
789 822 users_group = get_users_group_or_error(usersgroupid)
790 823
791 824 try:
792 825 RepoModel().grant_users_group_permission(repo=repo,
793 826 group_name=users_group,
794 827 perm=perm)
795 828
796 829 Session().commit()
797 830 return dict(
798 831 msg='Granted perm: `%s` for users group: `%s` in '
799 832 'repo: `%s`' % (
800 833 perm.permission_name, users_group.users_group_name,
801 834 repo.repo_name
802 835 ),
803 836 success=True
804 837 )
805 838 except Exception:
806 839 log.error(traceback.format_exc())
807 840 raise JSONRPCError(
808 841 'failed to edit permission for users group: `%s` in '
809 842 'repo: `%s`' % (
810 843 usersgroupid, repo.repo_name
811 844 )
812 845 )
813 846
814 @HasPermissionAnyDecorator('hg.admin')
847 @HasPermissionAllDecorator('hg.admin')
815 848 def revoke_users_group_permission(self, apiuser, repoid, usersgroupid):
816 849 """
817 850 Revoke permission for users group on given repository
818 851
819 852 :param apiuser:
820 853 :param repoid:
821 854 :param usersgroupid:
822 855 """
823 856 repo = get_repo_or_error(repoid)
824 857 users_group = get_users_group_or_error(usersgroupid)
825 858
826 859 try:
827 860 RepoModel().revoke_users_group_permission(repo=repo,
828 861 group_name=users_group)
829 862
830 863 Session().commit()
831 864 return dict(
832 865 msg='Revoked perm for users group: `%s` in repo: `%s`' % (
833 866 users_group.users_group_name, repo.repo_name
834 867 ),
835 868 success=True
836 869 )
837 870 except Exception:
838 871 log.error(traceback.format_exc())
839 872 raise JSONRPCError(
840 873 'failed to edit permission for users group: `%s` in '
841 874 'repo: `%s`' % (
842 875 users_group.users_group_name, repo.repo_name
843 876 )
844 877 )
@@ -1,879 +1,982 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.auth
4 4 ~~~~~~~~~~~~~~~~~~
5 5
6 6 authentication and permission libraries
7 7
8 8 :created_on: Apr 4, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import random
27 27 import logging
28 28 import traceback
29 29 import hashlib
30 30
31 31 from tempfile import _RandomNameSequence
32 32 from decorator import decorator
33 33
34 34 from pylons import config, url, request
35 35 from pylons.controllers.util import abort, redirect
36 36 from pylons.i18n.translation import _
37 37
38 38 from rhodecode import __platform__, is_windows, is_unix
39 39 from rhodecode.model.meta import Session
40 40
41 41 from rhodecode.lib.utils2 import str2bool, safe_unicode
42 42 from rhodecode.lib.exceptions import LdapPasswordError, LdapUsernameError
43 43 from rhodecode.lib.utils import get_repo_slug, get_repos_group_slug
44 44 from rhodecode.lib.auth_ldap import AuthLdap
45 45
46 46 from rhodecode.model import meta
47 47 from rhodecode.model.user import UserModel
48 48 from rhodecode.model.db import Permission, RhodeCodeSetting, User, UserIpMap
49 49 from rhodecode.lib.caching_query import FromCache
50 50
51 51 log = logging.getLogger(__name__)
52 52
53 53
54 54 class PasswordGenerator(object):
55 55 """
56 56 This is a simple class for generating password from different sets of
57 57 characters
58 58 usage::
59 59
60 60 passwd_gen = PasswordGenerator()
61 61 #print 8-letter password containing only big and small letters
62 62 of alphabet
63 63 passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
64 64 """
65 65 ALPHABETS_NUM = r'''1234567890'''
66 66 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
67 67 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
68 68 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
69 69 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
70 70 + ALPHABETS_NUM + ALPHABETS_SPECIAL
71 71 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
72 72 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
73 73 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
74 74 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
75 75
76 76 def __init__(self, passwd=''):
77 77 self.passwd = passwd
78 78
79 79 def gen_password(self, length, type_=None):
80 80 if type_ is None:
81 81 type_ = self.ALPHABETS_FULL
82 82 self.passwd = ''.join([random.choice(type_) for _ in xrange(length)])
83 83 return self.passwd
84 84
85 85
86 86 class RhodeCodeCrypto(object):
87 87
88 88 @classmethod
89 89 def hash_string(cls, str_):
90 90 """
91 91 Cryptographic function used for password hashing based on pybcrypt
92 92 or pycrypto in windows
93 93
94 94 :param password: password to hash
95 95 """
96 96 if is_windows:
97 97 from hashlib import sha256
98 98 return sha256(str_).hexdigest()
99 99 elif is_unix:
100 100 import bcrypt
101 101 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
102 102 else:
103 103 raise Exception('Unknown or unsupported platform %s' \
104 104 % __platform__)
105 105
106 106 @classmethod
107 107 def hash_check(cls, password, hashed):
108 108 """
109 109 Checks matching password with it's hashed value, runs different
110 110 implementation based on platform it runs on
111 111
112 112 :param password: password
113 113 :param hashed: password in hashed form
114 114 """
115 115
116 116 if is_windows:
117 117 from hashlib import sha256
118 118 return sha256(password).hexdigest() == hashed
119 119 elif is_unix:
120 120 import bcrypt
121 121 return bcrypt.hashpw(password, hashed) == hashed
122 122 else:
123 123 raise Exception('Unknown or unsupported platform %s' \
124 124 % __platform__)
125 125
126 126
127 127 def get_crypt_password(password):
128 128 return RhodeCodeCrypto.hash_string(password)
129 129
130 130
131 131 def check_password(password, hashed):
132 132 return RhodeCodeCrypto.hash_check(password, hashed)
133 133
134 134
135 135 def generate_api_key(str_, salt=None):
136 136 """
137 137 Generates API KEY from given string
138 138
139 139 :param str_:
140 140 :param salt:
141 141 """
142 142
143 143 if salt is None:
144 144 salt = _RandomNameSequence().next()
145 145
146 146 return hashlib.sha1(str_ + salt).hexdigest()
147 147
148 148
149 149 def authfunc(environ, username, password):
150 150 """
151 151 Dummy authentication wrapper function used in Mercurial and Git for
152 152 access control.
153 153
154 154 :param environ: needed only for using in Basic auth
155 155 """
156 156 return authenticate(username, password)
157 157
158 158
159 159 def authenticate(username, password):
160 160 """
161 161 Authentication function used for access control,
162 162 firstly checks for db authentication then if ldap is enabled for ldap
163 163 authentication, also creates ldap user if not in database
164 164
165 165 :param username: username
166 166 :param password: password
167 167 """
168 168
169 169 user_model = UserModel()
170 170 user = User.get_by_username(username)
171 171
172 172 log.debug('Authenticating user using RhodeCode account')
173 173 if user is not None and not user.ldap_dn:
174 174 if user.active:
175 175 if user.username == 'default' and user.active:
176 176 log.info('user %s authenticated correctly as anonymous user' %
177 177 username)
178 178 return True
179 179
180 180 elif user.username == username and check_password(password,
181 181 user.password):
182 182 log.info('user %s authenticated correctly' % username)
183 183 return True
184 184 else:
185 185 log.warning('user %s tried auth but is disabled' % username)
186 186
187 187 else:
188 188 log.debug('Regular authentication failed')
189 189 user_obj = User.get_by_username(username, case_insensitive=True)
190 190
191 191 if user_obj is not None and not user_obj.ldap_dn:
192 192 log.debug('this user already exists as non ldap')
193 193 return False
194 194
195 195 ldap_settings = RhodeCodeSetting.get_ldap_settings()
196 196 #======================================================================
197 197 # FALLBACK TO LDAP AUTH IF ENABLE
198 198 #======================================================================
199 199 if str2bool(ldap_settings.get('ldap_active')):
200 200 log.debug("Authenticating user using ldap")
201 201 kwargs = {
202 202 'server': ldap_settings.get('ldap_host', ''),
203 203 'base_dn': ldap_settings.get('ldap_base_dn', ''),
204 204 'port': ldap_settings.get('ldap_port'),
205 205 'bind_dn': ldap_settings.get('ldap_dn_user'),
206 206 'bind_pass': ldap_settings.get('ldap_dn_pass'),
207 207 'tls_kind': ldap_settings.get('ldap_tls_kind'),
208 208 'tls_reqcert': ldap_settings.get('ldap_tls_reqcert'),
209 209 'ldap_filter': ldap_settings.get('ldap_filter'),
210 210 'search_scope': ldap_settings.get('ldap_search_scope'),
211 211 'attr_login': ldap_settings.get('ldap_attr_login'),
212 212 'ldap_version': 3,
213 213 }
214 214 log.debug('Checking for ldap authentication')
215 215 try:
216 216 aldap = AuthLdap(**kwargs)
217 217 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username,
218 218 password)
219 219 log.debug('Got ldap DN response %s' % user_dn)
220 220
221 221 get_ldap_attr = lambda k: ldap_attrs.get(ldap_settings\
222 222 .get(k), [''])[0]
223 223
224 224 user_attrs = {
225 225 'name': safe_unicode(get_ldap_attr('ldap_attr_firstname')),
226 226 'lastname': safe_unicode(get_ldap_attr('ldap_attr_lastname')),
227 227 'email': get_ldap_attr('ldap_attr_email'),
228 228 }
229 229
230 230 # don't store LDAP password since we don't need it. Override
231 231 # with some random generated password
232 232 _password = PasswordGenerator().gen_password(length=8)
233 233 # create this user on the fly if it doesn't exist in rhodecode
234 234 # database
235 235 if user_model.create_ldap(username, _password, user_dn,
236 236 user_attrs):
237 237 log.info('created new ldap user %s' % username)
238 238
239 239 Session().commit()
240 240 return True
241 241 except (LdapUsernameError, LdapPasswordError,):
242 242 pass
243 243 except (Exception,):
244 244 log.error(traceback.format_exc())
245 245 pass
246 246 return False
247 247
248 248
249 249 def login_container_auth(username):
250 250 user = User.get_by_username(username)
251 251 if user is None:
252 252 user_attrs = {
253 253 'name': username,
254 254 'lastname': None,
255 255 'email': None,
256 256 }
257 257 user = UserModel().create_for_container_auth(username, user_attrs)
258 258 if not user:
259 259 return None
260 260 log.info('User %s was created by container authentication' % username)
261 261
262 262 if not user.active:
263 263 return None
264 264
265 265 user.update_lastlogin()
266 266 Session().commit()
267 267
268 268 log.debug('User %s is now logged in by container authentication',
269 269 user.username)
270 270 return user
271 271
272 272
273 273 def get_container_username(environ, config):
274 274 username = None
275 275
276 276 if str2bool(config.get('container_auth_enabled', False)):
277 277 from paste.httpheaders import REMOTE_USER
278 278 username = REMOTE_USER(environ)
279 279
280 280 if not username and str2bool(config.get('proxypass_auth_enabled', False)):
281 281 username = environ.get('HTTP_X_FORWARDED_USER')
282 282
283 283 if username:
284 284 # Removing realm and domain from username
285 285 username = username.partition('@')[0]
286 286 username = username.rpartition('\\')[2]
287 287 log.debug('Received username %s from container' % username)
288 288
289 289 return username
290 290
291 291
292 292 class CookieStoreWrapper(object):
293 293
294 294 def __init__(self, cookie_store):
295 295 self.cookie_store = cookie_store
296 296
297 297 def __repr__(self):
298 298 return 'CookieStore<%s>' % (self.cookie_store)
299 299
300 300 def get(self, key, other=None):
301 301 if isinstance(self.cookie_store, dict):
302 302 return self.cookie_store.get(key, other)
303 303 elif isinstance(self.cookie_store, AuthUser):
304 304 return self.cookie_store.__dict__.get(key, other)
305 305
306 306
307 307 class AuthUser(object):
308 308 """
309 309 A simple object that handles all attributes of user in RhodeCode
310 310
311 311 It does lookup based on API key,given user, or user present in session
312 312 Then it fills all required information for such user. It also checks if
313 313 anonymous access is enabled and if so, it returns default user as logged
314 314 in
315 315 """
316 316
317 317 def __init__(self, user_id=None, api_key=None, username=None, ip_addr=None):
318 318
319 319 self.user_id = user_id
320 320 self.api_key = None
321 321 self.username = username
322 322 self.ip_addr = ip_addr
323 323
324 324 self.name = ''
325 325 self.lastname = ''
326 326 self.email = ''
327 327 self.is_authenticated = False
328 328 self.admin = False
329 329 self.inherit_default_permissions = False
330 330 self.permissions = {}
331 331 self._api_key = api_key
332 332 self.propagate_data()
333 333 self._instance = None
334 334
335 335 def propagate_data(self):
336 336 user_model = UserModel()
337 337 self.anonymous_user = User.get_by_username('default', cache=True)
338 338 is_user_loaded = False
339 339
340 340 # try go get user by api key
341 341 if self._api_key and self._api_key != self.anonymous_user.api_key:
342 342 log.debug('Auth User lookup by API KEY %s' % self._api_key)
343 343 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
344 344 # lookup by userid
345 345 elif (self.user_id is not None and
346 346 self.user_id != self.anonymous_user.user_id):
347 347 log.debug('Auth User lookup by USER ID %s' % self.user_id)
348 348 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
349 349 # lookup by username
350 350 elif self.username and \
351 351 str2bool(config.get('container_auth_enabled', False)):
352 352
353 353 log.debug('Auth User lookup by USER NAME %s' % self.username)
354 354 dbuser = login_container_auth(self.username)
355 355 if dbuser is not None:
356 356 log.debug('filling all attributes to object')
357 357 for k, v in dbuser.get_dict().items():
358 358 setattr(self, k, v)
359 359 self.set_authenticated()
360 360 is_user_loaded = True
361 361 else:
362 362 log.debug('No data in %s that could been used to log in' % self)
363 363
364 364 if not is_user_loaded:
365 365 # if we cannot authenticate user try anonymous
366 366 if self.anonymous_user.active is True:
367 367 user_model.fill_data(self, user_id=self.anonymous_user.user_id)
368 368 # then we set this user is logged in
369 369 self.is_authenticated = True
370 370 else:
371 371 self.user_id = None
372 372 self.username = None
373 373 self.is_authenticated = False
374 374
375 375 if not self.username:
376 376 self.username = 'None'
377 377
378 378 log.debug('Auth User is now %s' % self)
379 379 user_model.fill_perms(self)
380 380
381 381 @property
382 382 def is_admin(self):
383 383 return self.admin
384 384
385 385 @property
386 386 def ip_allowed(self):
387 387 """
388 388 Checks if ip_addr used in constructor is allowed from defined list of
389 389 allowed ip_addresses for user
390 390
391 391 :returns: boolean, True if ip is in allowed ip range
392 392 """
393 393 #check IP
394 394 allowed_ips = AuthUser.get_allowed_ips(self.user_id, cache=True)
395 395 if check_ip_access(source_ip=self.ip_addr, allowed_ips=allowed_ips):
396 396 log.debug('IP:%s is in range of %s' % (self.ip_addr, allowed_ips))
397 397 return True
398 398 else:
399 399 log.info('Access for IP:%s forbidden, '
400 400 'not in %s' % (self.ip_addr, allowed_ips))
401 401 return False
402 402
403 403 def __repr__(self):
404 404 return "<AuthUser('id:%s:%s|%s')>" % (self.user_id, self.username,
405 405 self.is_authenticated)
406 406
407 407 def set_authenticated(self, authenticated=True):
408 408 if self.user_id != self.anonymous_user.user_id:
409 409 self.is_authenticated = authenticated
410 410
411 411 def get_cookie_store(self):
412 412 return {'username': self.username,
413 413 'user_id': self.user_id,
414 414 'is_authenticated': self.is_authenticated}
415 415
416 416 @classmethod
417 417 def from_cookie_store(cls, cookie_store):
418 418 """
419 419 Creates AuthUser from a cookie store
420 420
421 421 :param cls:
422 422 :param cookie_store:
423 423 """
424 424 user_id = cookie_store.get('user_id')
425 425 username = cookie_store.get('username')
426 426 api_key = cookie_store.get('api_key')
427 427 return AuthUser(user_id, api_key, username)
428 428
429 429 @classmethod
430 430 def get_allowed_ips(cls, user_id, cache=False):
431 431 _set = set()
432 432 user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id)
433 433 if cache:
434 434 user_ips = user_ips.options(FromCache("sql_cache_short",
435 435 "get_user_ips_%s" % user_id))
436 436 for ip in user_ips:
437 437 _set.add(ip.ip_addr)
438 438 return _set or set(['0.0.0.0/0'])
439 439
440 440
441 441 def set_available_permissions(config):
442 442 """
443 443 This function will propagate pylons globals with all available defined
444 444 permission given in db. We don't want to check each time from db for new
445 445 permissions since adding a new permission also requires application restart
446 446 ie. to decorate new views with the newly created permission
447 447
448 448 :param config: current pylons config instance
449 449
450 450 """
451 451 log.info('getting information about all available permissions')
452 452 try:
453 453 sa = meta.Session
454 454 all_perms = sa.query(Permission).all()
455 455 except Exception:
456 456 pass
457 457 finally:
458 458 meta.Session.remove()
459 459
460 460 config['available_permissions'] = [x.permission_name for x in all_perms]
461 461
462 462
463 463 #==============================================================================
464 464 # CHECK DECORATORS
465 465 #==============================================================================
466 466 class LoginRequired(object):
467 467 """
468 468 Must be logged in to execute this function else
469 469 redirect to login page
470 470
471 471 :param api_access: if enabled this checks only for valid auth token
472 472 and grants access based on valid token
473 473 """
474 474
475 475 def __init__(self, api_access=False):
476 476 self.api_access = api_access
477 477
478 478 def __call__(self, func):
479 479 return decorator(self.__wrapper, func)
480 480
481 481 def __wrapper(self, func, *fargs, **fkwargs):
482 482 cls = fargs[0]
483 483 user = cls.rhodecode_user
484 484 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
485 485
486 486 #check IP
487 487 ip_access_ok = True
488 488 if not user.ip_allowed:
489 489 from rhodecode.lib import helpers as h
490 490 h.flash(h.literal(_('IP %s not allowed' % (user.ip_addr))),
491 491 category='warning')
492 492 ip_access_ok = False
493 493
494 494 api_access_ok = False
495 495 if self.api_access:
496 496 log.debug('Checking API KEY access for %s' % cls)
497 497 if user.api_key == request.GET.get('api_key'):
498 498 api_access_ok = True
499 499 else:
500 500 log.debug("API KEY token not valid")
501 501
502 502 log.debug('Checking if %s is authenticated @ %s' % (user.username, loc))
503 503 if (user.is_authenticated or api_access_ok) and ip_access_ok:
504 504 reason = 'RegularAuth' if user.is_authenticated else 'APIAuth'
505 505 log.info('user %s is authenticated and granted access to %s '
506 506 'using %s' % (user.username, loc, reason)
507 507 )
508 508 return func(*fargs, **fkwargs)
509 509 else:
510 510 log.warn('user %s NOT authenticated on func: %s' % (
511 511 user, loc)
512 512 )
513 513 p = url.current()
514 514
515 515 log.debug('redirecting to login page with %s' % p)
516 516 return redirect(url('login_home', came_from=p))
517 517
518 518
519 519 class NotAnonymous(object):
520 520 """
521 521 Must be logged in to execute this function else
522 522 redirect to login page"""
523 523
524 524 def __call__(self, func):
525 525 return decorator(self.__wrapper, func)
526 526
527 527 def __wrapper(self, func, *fargs, **fkwargs):
528 528 cls = fargs[0]
529 529 self.user = cls.rhodecode_user
530 530
531 531 log.debug('Checking if user is not anonymous @%s' % cls)
532 532
533 533 anonymous = self.user.username == 'default'
534 534
535 535 if anonymous:
536 536 p = url.current()
537 537
538 538 import rhodecode.lib.helpers as h
539 539 h.flash(_('You need to be a registered user to '
540 540 'perform this action'),
541 541 category='warning')
542 542 return redirect(url('login_home', came_from=p))
543 543 else:
544 544 return func(*fargs, **fkwargs)
545 545
546 546
547 547 class PermsDecorator(object):
548 548 """Base class for controller decorators"""
549 549
550 550 def __init__(self, *required_perms):
551 551 available_perms = config['available_permissions']
552 552 for perm in required_perms:
553 553 if perm not in available_perms:
554 554 raise Exception("'%s' permission is not defined" % perm)
555 555 self.required_perms = set(required_perms)
556 556 self.user_perms = None
557 557
558 558 def __call__(self, func):
559 559 return decorator(self.__wrapper, func)
560 560
561 561 def __wrapper(self, func, *fargs, **fkwargs):
562 562 cls = fargs[0]
563 563 self.user = cls.rhodecode_user
564 564 self.user_perms = self.user.permissions
565 565 log.debug('checking %s permissions %s for %s %s',
566 566 self.__class__.__name__, self.required_perms, cls, self.user)
567 567
568 568 if self.check_permissions():
569 569 log.debug('Permission granted for %s %s' % (cls, self.user))
570 570 return func(*fargs, **fkwargs)
571 571
572 572 else:
573 573 log.debug('Permission denied for %s %s' % (cls, self.user))
574 574 anonymous = self.user.username == 'default'
575 575
576 576 if anonymous:
577 577 p = url.current()
578 578
579 579 import rhodecode.lib.helpers as h
580 580 h.flash(_('You need to be a signed in to '
581 581 'view this page'),
582 582 category='warning')
583 583 return redirect(url('login_home', came_from=p))
584 584
585 585 else:
586 586 # redirect with forbidden ret code
587 587 return abort(403)
588 588
589 589 def check_permissions(self):
590 590 """Dummy function for overriding"""
591 591 raise Exception('You have to write this function in child class')
592 592
593 593
594 594 class HasPermissionAllDecorator(PermsDecorator):
595 595 """
596 596 Checks for access permission for all given predicates. All of them
597 597 have to be meet in order to fulfill the request
598 598 """
599 599
600 600 def check_permissions(self):
601 601 if self.required_perms.issubset(self.user_perms.get('global')):
602 602 return True
603 603 return False
604 604
605 605
606 606 class HasPermissionAnyDecorator(PermsDecorator):
607 607 """
608 608 Checks for access permission for any of given predicates. In order to
609 609 fulfill the request any of predicates must be meet
610 610 """
611 611
612 612 def check_permissions(self):
613 613 if self.required_perms.intersection(self.user_perms.get('global')):
614 614 return True
615 615 return False
616 616
617 617
618 618 class HasRepoPermissionAllDecorator(PermsDecorator):
619 619 """
620 620 Checks for access permission for all given predicates for specific
621 621 repository. All of them have to be meet in order to fulfill the request
622 622 """
623 623
624 624 def check_permissions(self):
625 625 repo_name = get_repo_slug(request)
626 626 try:
627 627 user_perms = set([self.user_perms['repositories'][repo_name]])
628 628 except KeyError:
629 629 return False
630 630 if self.required_perms.issubset(user_perms):
631 631 return True
632 632 return False
633 633
634 634
635 635 class HasRepoPermissionAnyDecorator(PermsDecorator):
636 636 """
637 637 Checks for access permission for any of given predicates for specific
638 638 repository. In order to fulfill the request any of predicates must be meet
639 639 """
640 640
641 641 def check_permissions(self):
642 642 repo_name = get_repo_slug(request)
643 643
644 644 try:
645 645 user_perms = set([self.user_perms['repositories'][repo_name]])
646 646 except KeyError:
647 647 return False
648 648
649 649 if self.required_perms.intersection(user_perms):
650 650 return True
651 651 return False
652 652
653 653
654 654 class HasReposGroupPermissionAllDecorator(PermsDecorator):
655 655 """
656 656 Checks for access permission for all given predicates for specific
657 657 repository. All of them have to be meet in order to fulfill the request
658 658 """
659 659
660 660 def check_permissions(self):
661 661 group_name = get_repos_group_slug(request)
662 662 try:
663 663 user_perms = set([self.user_perms['repositories_groups'][group_name]])
664 664 except KeyError:
665 665 return False
666 666 if self.required_perms.issubset(user_perms):
667 667 return True
668 668 return False
669 669
670 670
671 671 class HasReposGroupPermissionAnyDecorator(PermsDecorator):
672 672 """
673 673 Checks for access permission for any of given predicates for specific
674 674 repository. In order to fulfill the request any of predicates must be meet
675 675 """
676 676
677 677 def check_permissions(self):
678 678 group_name = get_repos_group_slug(request)
679 679
680 680 try:
681 681 user_perms = set([self.user_perms['repositories_groups'][group_name]])
682 682 except KeyError:
683 683 return False
684 684 if self.required_perms.intersection(user_perms):
685 685 return True
686 686 return False
687 687
688 688
689 689 #==============================================================================
690 690 # CHECK FUNCTIONS
691 691 #==============================================================================
692 692 class PermsFunction(object):
693 693 """Base function for other check functions"""
694 694
695 695 def __init__(self, *perms):
696 696 available_perms = config['available_permissions']
697 697
698 698 for perm in perms:
699 699 if perm not in available_perms:
700 700 raise Exception("'%s' permission is not defined" % perm)
701 701 self.required_perms = set(perms)
702 702 self.user_perms = None
703 703 self.repo_name = None
704 704 self.group_name = None
705 705
706 706 def __call__(self, check_Location=''):
707 707 user = request.user
708 708 cls_name = self.__class__.__name__
709 709 check_scope = {
710 710 'HasPermissionAll': '',
711 711 'HasPermissionAny': '',
712 712 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
713 713 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
714 714 'HasReposGroupPermissionAll': 'group:%s' % self.group_name,
715 715 'HasReposGroupPermissionAny': 'group:%s' % self.group_name,
716 716 }.get(cls_name, '?')
717 717 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
718 718 self.required_perms, user, check_scope,
719 719 check_Location or 'unspecified location')
720 720 if not user:
721 721 log.debug('Empty request user')
722 722 return False
723 723 self.user_perms = user.permissions
724 724 if self.check_permissions():
725 725 log.debug('Permission to %s granted for user: %s @ %s', self.repo_name, user,
726 726 check_Location or 'unspecified location')
727 727 return True
728 728
729 729 else:
730 730 log.debug('Permission to %s denied for user: %s @ %s', self.repo_name, user,
731 731 check_Location or 'unspecified location')
732 732 return False
733 733
734 734 def check_permissions(self):
735 735 """Dummy function for overriding"""
736 736 raise Exception('You have to write this function in child class')
737 737
738 738
739 739 class HasPermissionAll(PermsFunction):
740 740 def check_permissions(self):
741 741 if self.required_perms.issubset(self.user_perms.get('global')):
742 742 return True
743 743 return False
744 744
745 745
746 746 class HasPermissionAny(PermsFunction):
747 747 def check_permissions(self):
748 748 if self.required_perms.intersection(self.user_perms.get('global')):
749 749 return True
750 750 return False
751 751
752 752
753 753 class HasRepoPermissionAll(PermsFunction):
754 754 def __call__(self, repo_name=None, check_Location=''):
755 755 self.repo_name = repo_name
756 756 return super(HasRepoPermissionAll, self).__call__(check_Location)
757 757
758 758 def check_permissions(self):
759 759 if not self.repo_name:
760 760 self.repo_name = get_repo_slug(request)
761 761
762 762 try:
763 763 self._user_perms = set(
764 764 [self.user_perms['repositories'][self.repo_name]]
765 765 )
766 766 except KeyError:
767 767 return False
768 768 if self.required_perms.issubset(self._user_perms):
769 769 return True
770 770 return False
771 771
772 772
773 773 class HasRepoPermissionAny(PermsFunction):
774 774 def __call__(self, repo_name=None, check_Location=''):
775 775 self.repo_name = repo_name
776 776 return super(HasRepoPermissionAny, self).__call__(check_Location)
777 777
778 778 def check_permissions(self):
779 779 if not self.repo_name:
780 780 self.repo_name = get_repo_slug(request)
781 781
782 782 try:
783 783 self._user_perms = set(
784 784 [self.user_perms['repositories'][self.repo_name]]
785 785 )
786 786 except KeyError:
787 787 return False
788 788 if self.required_perms.intersection(self._user_perms):
789 789 return True
790 790 return False
791 791
792 792
793 793 class HasReposGroupPermissionAny(PermsFunction):
794 794 def __call__(self, group_name=None, check_Location=''):
795 795 self.group_name = group_name
796 796 return super(HasReposGroupPermissionAny, self).__call__(check_Location)
797 797
798 798 def check_permissions(self):
799 799 try:
800 800 self._user_perms = set(
801 801 [self.user_perms['repositories_groups'][self.group_name]]
802 802 )
803 803 except KeyError:
804 804 return False
805 805 if self.required_perms.intersection(self._user_perms):
806 806 return True
807 807 return False
808 808
809 809
810 810 class HasReposGroupPermissionAll(PermsFunction):
811 811 def __call__(self, group_name=None, check_Location=''):
812 812 self.group_name = group_name
813 813 return super(HasReposGroupPermissionAll, self).__call__(check_Location)
814 814
815 815 def check_permissions(self):
816 816 try:
817 817 self._user_perms = set(
818 818 [self.user_perms['repositories_groups'][self.group_name]]
819 819 )
820 820 except KeyError:
821 821 return False
822 822 if self.required_perms.issubset(self._user_perms):
823 823 return True
824 824 return False
825 825
826 826
827 827 #==============================================================================
828 828 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
829 829 #==============================================================================
830 830 class HasPermissionAnyMiddleware(object):
831 831 def __init__(self, *perms):
832 832 self.required_perms = set(perms)
833 833
834 834 def __call__(self, user, repo_name):
835 835 # repo_name MUST be unicode, since we handle keys in permission
836 836 # dict by unicode
837 837 repo_name = safe_unicode(repo_name)
838 838 usr = AuthUser(user.user_id)
839 839 try:
840 840 self.user_perms = set([usr.permissions['repositories'][repo_name]])
841 841 except Exception:
842 842 log.error('Exception while accessing permissions %s' %
843 843 traceback.format_exc())
844 844 self.user_perms = set()
845 845 self.username = user.username
846 846 self.repo_name = repo_name
847 847 return self.check_permissions()
848 848
849 849 def check_permissions(self):
850 850 log.debug('checking VCS protocol '
851 851 'permissions %s for user:%s repository:%s', self.user_perms,
852 852 self.username, self.repo_name)
853 853 if self.required_perms.intersection(self.user_perms):
854 854 log.debug('permission granted for user:%s on repo:%s' % (
855 855 self.username, self.repo_name
856 856 )
857 857 )
858 858 return True
859 859 log.debug('permission denied for user:%s on repo:%s' % (
860 860 self.username, self.repo_name
861 861 )
862 862 )
863 863 return False
864 864
865 865
866 #==============================================================================
867 # SPECIAL VERSION TO HANDLE API AUTH
868 #==============================================================================
869 class _BaseApiPerm(object):
870 def __init__(self, *perms):
871 self.required_perms = set(perms)
872
873 def __call__(self, check_location='unspecified', user=None, repo_name=None):
874 cls_name = self.__class__.__name__
875 check_scope = 'user:%s, repo:%s' % (user, repo_name)
876 log.debug('checking cls:%s %s %s @ %s', cls_name,
877 self.required_perms, check_scope, check_location)
878 if not user:
879 log.debug('Empty User passed into arguments')
880 return False
881
882 ## process user
883 if not isinstance(user, AuthUser):
884 user = AuthUser(user.user_id)
885
886 if self.check_permissions(user.permissions, repo_name):
887 log.debug('Permission to %s granted for user: %s @ %s', repo_name,
888 user, check_location)
889 return True
890
891 else:
892 log.debug('Permission to %s denied for user: %s @ %s', repo_name,
893 user, check_location)
894 return False
895
896 def check_permissions(self, perm_defs, repo_name):
897 """
898 implement in child class should return True if permissions are ok,
899 False otherwise
900
901 :param perm_defs: dict with permission definitions
902 :param repo_name: repo name
903 """
904 raise NotImplementedError()
905
906
907 class HasPermissionAllApi(_BaseApiPerm):
908 def __call__(self, user, check_location=''):
909 return super(HasPermissionAllApi, self)\
910 .__call__(check_location=check_location, user=user)
911
912 def check_permissions(self, perm_defs, repo):
913 if self.required_perms.issubset(perm_defs.get('global')):
914 return True
915 return False
916
917
918 class HasPermissionAnyApi(_BaseApiPerm):
919 def __call__(self, user, check_location=''):
920 return super(HasPermissionAnyApi, self)\
921 .__call__(check_location=check_location, user=user)
922
923 def check_permissions(self, perm_defs, repo):
924 if self.required_perms.intersection(perm_defs.get('global')):
925 return True
926 return False
927
928
929 class HasRepoPermissionAllApi(_BaseApiPerm):
930 def __call__(self, user, repo_name, check_location=''):
931 return super(HasRepoPermissionAllApi, self)\
932 .__call__(check_location=check_location, user=user,
933 repo_name=repo_name)
934
935 def check_permissions(self, perm_defs, repo_name):
936
937 try:
938 self._user_perms = set(
939 [perm_defs['repositories'][repo_name]]
940 )
941 except KeyError:
942 log.warning(traceback.format_exc())
943 return False
944 if self.required_perms.issubset(self._user_perms):
945 return True
946 return False
947
948
949 class HasRepoPermissionAnyApi(_BaseApiPerm):
950 def __call__(self, user, repo_name, check_location=''):
951 return super(HasRepoPermissionAnyApi, self)\
952 .__call__(check_location=check_location, user=user,
953 repo_name=repo_name)
954
955 def check_permissions(self, perm_defs, repo_name):
956
957 try:
958 _user_perms = set(
959 [perm_defs['repositories'][repo_name]]
960 )
961 except KeyError:
962 log.warning(traceback.format_exc())
963 return False
964 if self.required_perms.intersection(_user_perms):
965 return True
966 return False
967
968
866 969 def check_ip_access(source_ip, allowed_ips=None):
867 970 """
868 971 Checks if source_ip is a subnet of any of allowed_ips.
869 972
870 973 :param source_ip:
871 974 :param allowed_ips: list of allowed ips together with mask
872 975 """
873 976 from rhodecode.lib import ipaddr
874 977 log.debug('checking if ip:%s is subnet of %s' % (source_ip, allowed_ips))
875 978 if isinstance(allowed_ips, (tuple, list, set)):
876 979 for ip in allowed_ips:
877 980 if ipaddr.IPAddress(source_ip) in ipaddr.IPNetwork(ip):
878 981 return True
879 982 return False
@@ -1,985 +1,994 b''
1 1 from __future__ import with_statement
2 2 import random
3 3 import mock
4 4
5 5 from rhodecode.tests import *
6 6 from rhodecode.lib.compat import json
7 7 from rhodecode.lib.auth import AuthUser
8 8 from rhodecode.model.user import UserModel
9 9 from rhodecode.model.users_group import UsersGroupModel
10 10 from rhodecode.model.repo import RepoModel
11 11 from rhodecode.model.meta import Session
12 12 from rhodecode.model.scm import ScmModel
13 13 from rhodecode.model.db import Repository
14 14
15 15 API_URL = '/_admin/api'
16 16
17 17
18 18 def _build_data(apikey, method, **kw):
19 19 """
20 20 Builds API data with given random ID
21 21
22 22 :param random_id:
23 23 :type random_id:
24 24 """
25 25 random_id = random.randrange(1, 9999)
26 26 return random_id, json.dumps({
27 27 "id": random_id,
28 28 "api_key": apikey,
29 29 "method": method,
30 30 "args": kw
31 31 })
32 32
33 33 jsonify = lambda obj: json.loads(json.dumps(obj))
34 34
35 35
36 36 def crash(*args, **kwargs):
37 37 raise Exception('Total Crash !')
38 38
39 39
40 40 def api_call(test_obj, params):
41 41 response = test_obj.app.post(API_URL, content_type='application/json',
42 42 params=params)
43 43 return response
44 44
45 45
46 46 TEST_USERS_GROUP = 'test_users_group'
47 47
48 48
49 49 def make_users_group(name=TEST_USERS_GROUP):
50 50 gr = UsersGroupModel().create(name=name)
51 51 UsersGroupModel().add_user_to_group(users_group=gr,
52 52 user=TEST_USER_ADMIN_LOGIN)
53 53 Session().commit()
54 54 return gr
55 55
56 56
57 57 def destroy_users_group(name=TEST_USERS_GROUP):
58 58 UsersGroupModel().delete(users_group=name, force=True)
59 59 Session().commit()
60 60
61 61
62 62 def create_repo(repo_name, repo_type):
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(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
106 106 @classmethod
107 107 def teardownClass(self):
108 108 pass
109 109
110 110 def setUp(self):
111 111 self.maxDiff = None
112 112 make_users_group()
113 113
114 114 def tearDown(self):
115 115 destroy_users_group()
116 116
117 117 def _compare_ok(self, id_, expected, given):
118 118 expected = jsonify({
119 119 'id': id_,
120 120 'error': None,
121 121 'result': expected
122 122 })
123 123 given = json.loads(given)
124 124 self.assertEqual(expected, given)
125 125
126 126 def _compare_error(self, id_, expected, given):
127 127 expected = jsonify({
128 128 'id': id_,
129 129 'error': expected,
130 130 'result': None
131 131 })
132 132 given = json.loads(given)
133 133 self.assertEqual(expected, given)
134 134
135 135 # def test_Optional(self):
136 136 # from rhodecode.controllers.api.api import Optional
137 137 # option1 = Optional(None)
138 138 # self.assertEqual('<Optional:%s>' % None, repr(option1))
139 139 #
140 140 # self.assertEqual(1, Optional.extract(Optional(1)))
141 141 # self.assertEqual('trololo', Optional.extract('trololo'))
142 142
143 143 def test_api_wrong_key(self):
144 144 id_, params = _build_data('trololo', 'get_user')
145 145 response = api_call(self, params)
146 146
147 147 expected = 'Invalid API KEY'
148 148 self._compare_error(id_, expected, given=response.body)
149 149
150 150 def test_api_missing_non_optional_param(self):
151 151 id_, params = _build_data(self.apikey, 'get_user')
152 152 response = api_call(self, params)
153 153
154 154 expected = 'Missing non optional `userid` arg in JSON DATA'
155 155 self._compare_error(id_, expected, given=response.body)
156 156
157 157 def test_api_get_users(self):
158 158 id_, params = _build_data(self.apikey, 'get_users',)
159 159 response = api_call(self, params)
160 160 ret_all = []
161 161 for usr in UserModel().get_all():
162 162 ret = usr.get_api_data()
163 163 ret_all.append(jsonify(ret))
164 164 expected = ret_all
165 165 self._compare_ok(id_, expected, given=response.body)
166 166
167 167 def test_api_get_user(self):
168 168 id_, params = _build_data(self.apikey, 'get_user',
169 169 userid=TEST_USER_ADMIN_LOGIN)
170 170 response = api_call(self, params)
171 171
172 172 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
173 173 ret = usr.get_api_data()
174 174 ret['permissions'] = AuthUser(usr.user_id).permissions
175 175
176 176 expected = ret
177 177 self._compare_ok(id_, expected, given=response.body)
178 178
179 179 def test_api_get_user_that_does_not_exist(self):
180 180 id_, params = _build_data(self.apikey, 'get_user',
181 181 userid='trololo')
182 182 response = api_call(self, params)
183 183
184 184 expected = "user `%s` does not exist" % 'trololo'
185 185 self._compare_error(id_, expected, given=response.body)
186 186
187 187 def test_api_pull(self):
188 188 #TODO: issues with rhodecode_extras here.. not sure why !
189 189 pass
190 190
191 191 # repo_name = 'test_pull'
192 192 # r = create_repo(repo_name, self.REPO_TYPE)
193 193 # r.clone_uri = TEST_self.REPO
194 194 # Session.add(r)
195 195 # Session.commit()
196 196 #
197 197 # id_, params = _build_data(self.apikey, 'pull',
198 198 # repoid=repo_name,)
199 199 # response = self.app.post(API_URL, content_type='application/json',
200 200 # params=params)
201 201 #
202 202 # expected = 'Pulled from `%s`' % repo_name
203 203 # self._compare_ok(id_, expected, given=response.body)
204 204 #
205 205 # destroy_repo(repo_name)
206 206
207 207 def test_api_pull_error(self):
208 208 id_, params = _build_data(self.apikey, 'pull',
209 209 repoid=self.REPO,)
210 210 response = api_call(self, params)
211 211
212 212 expected = 'Unable to pull changes from `%s`' % self.REPO
213 213 self._compare_error(id_, expected, given=response.body)
214 214
215 215 def test_api_rescan_repos(self):
216 216 id_, params = _build_data(self.apikey, 'rescan_repos')
217 217 response = api_call(self, params)
218 218
219 219 expected = {'added': [], 'removed': []}
220 220 self._compare_ok(id_, expected, given=response.body)
221 221
222 222 @mock.patch.object(ScmModel, 'repo_scan', crash)
223 223 def test_api_rescann_error(self):
224 224 id_, params = _build_data(self.apikey, 'rescan_repos',)
225 225 response = api_call(self, params)
226 226
227 227 expected = 'Error occurred during rescan repositories action'
228 228 self._compare_error(id_, expected, given=response.body)
229 229
230 230 def test_api_lock_repo_lock_aquire(self):
231 231 id_, params = _build_data(self.apikey, 'lock',
232 232 userid=TEST_USER_ADMIN_LOGIN,
233 233 repoid=self.REPO,
234 234 locked=True)
235 235 response = api_call(self, params)
236 236 expected = ('User `%s` set lock state for repo `%s` to `%s`'
237 237 % (TEST_USER_ADMIN_LOGIN, self.REPO, True))
238 238 self._compare_ok(id_, expected, given=response.body)
239 239
240 240 def test_api_lock_repo_lock_release(self):
241 241 id_, params = _build_data(self.apikey, 'lock',
242 242 userid=TEST_USER_ADMIN_LOGIN,
243 243 repoid=self.REPO,
244 244 locked=False)
245 245 response = api_call(self, params)
246 246 expected = ('User `%s` set lock state for repo `%s` to `%s`'
247 247 % (TEST_USER_ADMIN_LOGIN, self.REPO, False))
248 248 self._compare_ok(id_, expected, given=response.body)
249 249
250 def test_api_lock_repo_lock_aquire_optional_userid(self):
251 id_, params = _build_data(self.apikey, 'lock',
252 repoid=self.REPO,
253 locked=True)
254 response = api_call(self, params)
255 expected = ('User `%s` set lock state for repo `%s` to `%s`'
256 % (TEST_USER_ADMIN_LOGIN, self.REPO, True))
257 self._compare_ok(id_, expected, given=response.body)
258
250 259 @mock.patch.object(Repository, 'lock', crash)
251 260 def test_api_lock_error(self):
252 261 id_, params = _build_data(self.apikey, 'lock',
253 262 userid=TEST_USER_ADMIN_LOGIN,
254 263 repoid=self.REPO,
255 264 locked=True)
256 265 response = api_call(self, params)
257 266
258 267 expected = 'Error occurred locking repository `%s`' % self.REPO
259 268 self._compare_error(id_, expected, given=response.body)
260 269
261 270 def test_api_create_existing_user(self):
262 271 id_, params = _build_data(self.apikey, 'create_user',
263 272 username=TEST_USER_ADMIN_LOGIN,
264 273 email='test@foo.com',
265 274 password='trololo')
266 275 response = api_call(self, params)
267 276
268 277 expected = "user `%s` already exist" % TEST_USER_ADMIN_LOGIN
269 278 self._compare_error(id_, expected, given=response.body)
270 279
271 280 def test_api_create_user_with_existing_email(self):
272 281 id_, params = _build_data(self.apikey, 'create_user',
273 282 username=TEST_USER_ADMIN_LOGIN + 'new',
274 283 email=TEST_USER_REGULAR_EMAIL,
275 284 password='trololo')
276 285 response = api_call(self, params)
277 286
278 287 expected = "email `%s` already exist" % TEST_USER_REGULAR_EMAIL
279 288 self._compare_error(id_, expected, given=response.body)
280 289
281 290 def test_api_create_user(self):
282 291 username = 'test_new_api_user'
283 292 email = username + "@foo.com"
284 293
285 294 id_, params = _build_data(self.apikey, 'create_user',
286 295 username=username,
287 296 email=email,
288 297 password='trololo')
289 298 response = api_call(self, params)
290 299
291 300 usr = UserModel().get_by_username(username)
292 301 ret = dict(
293 302 msg='created new user `%s`' % username,
294 303 user=jsonify(usr.get_api_data())
295 304 )
296 305
297 306 expected = ret
298 307 self._compare_ok(id_, expected, given=response.body)
299 308
300 309 UserModel().delete(usr.user_id)
301 310 Session().commit()
302 311
303 312 @mock.patch.object(UserModel, 'create_or_update', crash)
304 313 def test_api_create_user_when_exception_happened(self):
305 314
306 315 username = 'test_new_api_user'
307 316 email = username + "@foo.com"
308 317
309 318 id_, params = _build_data(self.apikey, 'create_user',
310 319 username=username,
311 320 email=email,
312 321 password='trololo')
313 322 response = api_call(self, params)
314 323 expected = 'failed to create user `%s`' % username
315 324 self._compare_error(id_, expected, given=response.body)
316 325
317 326 def test_api_delete_user(self):
318 327 usr = UserModel().create_or_update(username=u'test_user',
319 328 password=u'qweqwe',
320 329 email=u'u232@rhodecode.org',
321 330 firstname=u'u1', lastname=u'u1')
322 331 Session().commit()
323 332 username = usr.username
324 333 email = usr.email
325 334 usr_id = usr.user_id
326 335 ## DELETE THIS USER NOW
327 336
328 337 id_, params = _build_data(self.apikey, 'delete_user',
329 338 userid=username,)
330 339 response = api_call(self, params)
331 340
332 341 ret = {'msg': 'deleted user ID:%s %s' % (usr_id, username),
333 342 'user': None}
334 343 expected = ret
335 344 self._compare_ok(id_, expected, given=response.body)
336 345
337 346 @mock.patch.object(UserModel, 'delete', crash)
338 347 def test_api_delete_user_when_exception_happened(self):
339 348 usr = UserModel().create_or_update(username=u'test_user',
340 349 password=u'qweqwe',
341 350 email=u'u232@rhodecode.org',
342 351 firstname=u'u1', lastname=u'u1')
343 352 Session().commit()
344 353 username = usr.username
345 354
346 355 id_, params = _build_data(self.apikey, 'delete_user',
347 356 userid=username,)
348 357 response = api_call(self, params)
349 358 ret = 'failed to delete ID:%s %s' % (usr.user_id,
350 359 usr.username)
351 360 expected = ret
352 361 self._compare_error(id_, expected, given=response.body)
353 362
354 363 @parameterized.expand([('firstname', 'new_username'),
355 364 ('lastname', 'new_username'),
356 365 ('email', 'new_username'),
357 366 ('admin', True),
358 367 ('admin', False),
359 368 ('ldap_dn', 'test'),
360 369 ('ldap_dn', None),
361 370 ('active', False),
362 371 ('active', True),
363 372 ('password', 'newpass')
364 373 ])
365 374 def test_api_update_user(self, name, expected):
366 375 usr = UserModel().get_by_username(self.TEST_USER_LOGIN)
367 376 kw = {name: expected,
368 377 'userid': usr.user_id}
369 378 id_, params = _build_data(self.apikey, 'update_user', **kw)
370 379 response = api_call(self, params)
371 380
372 381 ret = {
373 382 'msg': 'updated user ID:%s %s' % (usr.user_id, self.TEST_USER_LOGIN),
374 383 'user': jsonify(UserModel()\
375 384 .get_by_username(self.TEST_USER_LOGIN)\
376 385 .get_api_data())
377 386 }
378 387
379 388 expected = ret
380 389 self._compare_ok(id_, expected, given=response.body)
381 390
382 391 def test_api_update_user_no_changed_params(self):
383 392 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
384 393 ret = jsonify(usr.get_api_data())
385 394 id_, params = _build_data(self.apikey, 'update_user',
386 395 userid=TEST_USER_ADMIN_LOGIN)
387 396
388 397 response = api_call(self, params)
389 398 ret = {
390 399 'msg': 'updated user ID:%s %s' % (usr.user_id, TEST_USER_ADMIN_LOGIN),
391 400 'user': ret
392 401 }
393 402 expected = ret
394 403 self._compare_ok(id_, expected, given=response.body)
395 404
396 405 def test_api_update_user_by_user_id(self):
397 406 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
398 407 ret = jsonify(usr.get_api_data())
399 408 id_, params = _build_data(self.apikey, 'update_user',
400 409 userid=usr.user_id)
401 410
402 411 response = api_call(self, params)
403 412 ret = {
404 413 'msg': 'updated user ID:%s %s' % (usr.user_id, TEST_USER_ADMIN_LOGIN),
405 414 'user': ret
406 415 }
407 416 expected = ret
408 417 self._compare_ok(id_, expected, given=response.body)
409 418
410 419 @mock.patch.object(UserModel, 'update_user', crash)
411 420 def test_api_update_user_when_exception_happens(self):
412 421 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
413 422 ret = jsonify(usr.get_api_data())
414 423 id_, params = _build_data(self.apikey, 'update_user',
415 424 userid=usr.user_id)
416 425
417 426 response = api_call(self, params)
418 427 ret = 'failed to update user `%s`' % usr.user_id
419 428
420 429 expected = ret
421 430 self._compare_error(id_, expected, given=response.body)
422 431
423 432 def test_api_get_repo(self):
424 433 new_group = 'some_new_group'
425 434 make_users_group(new_group)
426 435 RepoModel().grant_users_group_permission(repo=self.REPO,
427 436 group_name=new_group,
428 437 perm='repository.read')
429 438 Session().commit()
430 439 id_, params = _build_data(self.apikey, 'get_repo',
431 440 repoid=self.REPO)
432 441 response = api_call(self, params)
433 442
434 443 repo = RepoModel().get_by_repo_name(self.REPO)
435 444 ret = repo.get_api_data()
436 445
437 446 members = []
438 447 for user in repo.repo_to_perm:
439 448 perm = user.permission.permission_name
440 449 user = user.user
441 450 user_data = user.get_api_data()
442 451 user_data['type'] = "user"
443 452 user_data['permission'] = perm
444 453 members.append(user_data)
445 454
446 455 for users_group in repo.users_group_to_perm:
447 456 perm = users_group.permission.permission_name
448 457 users_group = users_group.users_group
449 458 users_group_data = users_group.get_api_data()
450 459 users_group_data['type'] = "users_group"
451 460 users_group_data['permission'] = perm
452 461 members.append(users_group_data)
453 462
454 463 ret['members'] = members
455 464
456 465 expected = ret
457 466 self._compare_ok(id_, expected, given=response.body)
458 467 destroy_users_group(new_group)
459 468
460 469 def test_api_get_repo_that_doesn_not_exist(self):
461 470 id_, params = _build_data(self.apikey, 'get_repo',
462 471 repoid='no-such-repo')
463 472 response = api_call(self, params)
464 473
465 474 ret = 'repository `%s` does not exist' % 'no-such-repo'
466 475 expected = ret
467 476 self._compare_error(id_, expected, given=response.body)
468 477
469 478 def test_api_get_repos(self):
470 479 id_, params = _build_data(self.apikey, 'get_repos')
471 480 response = api_call(self, params)
472 481
473 482 result = []
474 483 for repo in RepoModel().get_all():
475 484 result.append(repo.get_api_data())
476 485 ret = jsonify(result)
477 486
478 487 expected = ret
479 488 self._compare_ok(id_, expected, given=response.body)
480 489
481 490 @parameterized.expand([('all', 'all'),
482 491 ('dirs', 'dirs'),
483 492 ('files', 'files'), ])
484 493 def test_api_get_repo_nodes(self, name, ret_type):
485 494 rev = 'tip'
486 495 path = '/'
487 496 id_, params = _build_data(self.apikey, 'get_repo_nodes',
488 497 repoid=self.REPO, revision=rev,
489 498 root_path=path,
490 499 ret_type=ret_type)
491 500 response = api_call(self, params)
492 501
493 502 # we don't the actual return types here since it's tested somewhere
494 503 # else
495 504 expected = json.loads(response.body)['result']
496 505 self._compare_ok(id_, expected, given=response.body)
497 506
498 507 def test_api_get_repo_nodes_bad_revisions(self):
499 508 rev = 'i-dont-exist'
500 509 path = '/'
501 510 id_, params = _build_data(self.apikey, 'get_repo_nodes',
502 511 repoid=self.REPO, revision=rev,
503 512 root_path=path,)
504 513 response = api_call(self, params)
505 514
506 515 expected = 'failed to get repo: `%s` nodes' % self.REPO
507 516 self._compare_error(id_, expected, given=response.body)
508 517
509 518 def test_api_get_repo_nodes_bad_path(self):
510 519 rev = 'tip'
511 520 path = '/idontexits'
512 521 id_, params = _build_data(self.apikey, 'get_repo_nodes',
513 522 repoid=self.REPO, revision=rev,
514 523 root_path=path,)
515 524 response = api_call(self, params)
516 525
517 526 expected = 'failed to get repo: `%s` nodes' % self.REPO
518 527 self._compare_error(id_, expected, given=response.body)
519 528
520 529 def test_api_get_repo_nodes_bad_ret_type(self):
521 530 rev = 'tip'
522 531 path = '/'
523 532 ret_type = 'error'
524 533 id_, params = _build_data(self.apikey, 'get_repo_nodes',
525 534 repoid=self.REPO, revision=rev,
526 535 root_path=path,
527 536 ret_type=ret_type)
528 537 response = api_call(self, params)
529 538
530 539 expected = 'ret_type must be one of %s' % (['files', 'dirs', 'all'])
531 540 self._compare_error(id_, expected, given=response.body)
532 541
533 542 def test_api_create_repo(self):
534 543 repo_name = 'api-repo'
535 544 id_, params = _build_data(self.apikey, 'create_repo',
536 545 repo_name=repo_name,
537 546 owner=TEST_USER_ADMIN_LOGIN,
538 547 repo_type='hg',
539 548 )
540 549 response = api_call(self, params)
541 550
542 551 repo = RepoModel().get_by_repo_name(repo_name)
543 552 ret = {
544 553 'msg': 'Created new repository `%s`' % repo_name,
545 554 'repo': jsonify(repo.get_api_data())
546 555 }
547 556 expected = ret
548 557 self._compare_ok(id_, expected, given=response.body)
549 558 destroy_repo(repo_name)
550 559
551 560 def test_api_create_repo_unknown_owner(self):
552 561 repo_name = 'api-repo'
553 562 owner = 'i-dont-exist'
554 563 id_, params = _build_data(self.apikey, 'create_repo',
555 564 repo_name=repo_name,
556 565 owner=owner,
557 566 repo_type='hg',
558 567 )
559 568 response = api_call(self, params)
560 569 expected = 'user `%s` does not exist' % owner
561 570 self._compare_error(id_, expected, given=response.body)
562 571
563 572 def test_api_create_repo_exists(self):
564 573 repo_name = self.REPO
565 574 id_, params = _build_data(self.apikey, 'create_repo',
566 575 repo_name=repo_name,
567 576 owner=TEST_USER_ADMIN_LOGIN,
568 577 repo_type='hg',
569 578 )
570 579 response = api_call(self, params)
571 580 expected = "repo `%s` already exist" % repo_name
572 581 self._compare_error(id_, expected, given=response.body)
573 582
574 583 @mock.patch.object(RepoModel, 'create_repo', crash)
575 584 def test_api_create_repo_exception_occurred(self):
576 585 repo_name = 'api-repo'
577 586 id_, params = _build_data(self.apikey, 'create_repo',
578 587 repo_name=repo_name,
579 588 owner=TEST_USER_ADMIN_LOGIN,
580 589 repo_type='hg',
581 590 )
582 591 response = api_call(self, params)
583 592 expected = 'failed to create repository `%s`' % repo_name
584 593 self._compare_error(id_, expected, given=response.body)
585 594
586 595 def test_api_delete_repo(self):
587 596 repo_name = 'api_delete_me'
588 597 create_repo(repo_name, self.REPO_TYPE)
589 598
590 599 id_, params = _build_data(self.apikey, 'delete_repo',
591 600 repoid=repo_name,)
592 601 response = api_call(self, params)
593 602
594 603 ret = {
595 604 'msg': 'Deleted repository `%s`' % repo_name,
596 605 'success': True
597 606 }
598 607 expected = ret
599 608 self._compare_ok(id_, expected, given=response.body)
600 609
601 610 def test_api_delete_repo_exception_occurred(self):
602 611 repo_name = 'api_delete_me'
603 612 create_repo(repo_name, self.REPO_TYPE)
604 613 try:
605 614 with mock.patch.object(RepoModel, 'delete', crash):
606 615 id_, params = _build_data(self.apikey, 'delete_repo',
607 616 repoid=repo_name,)
608 617 response = api_call(self, params)
609 618
610 619 expected = 'failed to delete repository `%s`' % repo_name
611 620 self._compare_error(id_, expected, given=response.body)
612 621 finally:
613 622 destroy_repo(repo_name)
614 623
615 624 def test_api_fork_repo(self):
616 625 fork_name = 'api-repo-fork'
617 626 id_, params = _build_data(self.apikey, 'fork_repo',
618 627 repoid=self.REPO,
619 628 fork_name=fork_name,
620 629 owner=TEST_USER_ADMIN_LOGIN,
621 630 )
622 631 response = api_call(self, params)
623 632
624 633 ret = {
625 634 'msg': 'Created fork of `%s` as `%s`' % (self.REPO,
626 635 fork_name),
627 636 'success': True
628 637 }
629 638 expected = ret
630 639 self._compare_ok(id_, expected, given=response.body)
631 640 destroy_repo(fork_name)
632 641
633 642 def test_api_fork_repo_unknown_owner(self):
634 643 fork_name = 'api-repo-fork'
635 644 owner = 'i-dont-exist'
636 645 id_, params = _build_data(self.apikey, 'fork_repo',
637 646 repoid=self.REPO,
638 647 fork_name=fork_name,
639 648 owner=owner,
640 649 )
641 650 response = api_call(self, params)
642 651 expected = 'user `%s` does not exist' % owner
643 652 self._compare_error(id_, expected, given=response.body)
644 653
645 654 def test_api_fork_repo_fork_exists(self):
646 655 fork_name = 'api-repo-fork'
647 656 create_fork(fork_name, self.REPO_TYPE, self.REPO)
648 657
649 658 try:
650 659 fork_name = 'api-repo-fork'
651 660
652 661 id_, params = _build_data(self.apikey, 'fork_repo',
653 662 repoid=self.REPO,
654 663 fork_name=fork_name,
655 664 owner=TEST_USER_ADMIN_LOGIN,
656 665 )
657 666 response = api_call(self, params)
658 667
659 668 expected = "fork `%s` already exist" % fork_name
660 669 self._compare_error(id_, expected, given=response.body)
661 670 finally:
662 671 destroy_repo(fork_name)
663 672
664 673 def test_api_fork_repo_repo_exists(self):
665 674 fork_name = self.REPO
666 675
667 676 id_, params = _build_data(self.apikey, 'fork_repo',
668 677 repoid=self.REPO,
669 678 fork_name=fork_name,
670 679 owner=TEST_USER_ADMIN_LOGIN,
671 680 )
672 681 response = api_call(self, params)
673 682
674 683 expected = "repo `%s` already exist" % fork_name
675 684 self._compare_error(id_, expected, given=response.body)
676 685
677 686 @mock.patch.object(RepoModel, 'create_fork', crash)
678 687 def test_api_fork_repo_exception_occurred(self):
679 688 fork_name = 'api-repo-fork'
680 689 id_, params = _build_data(self.apikey, 'fork_repo',
681 690 repoid=self.REPO,
682 691 fork_name=fork_name,
683 692 owner=TEST_USER_ADMIN_LOGIN,
684 693 )
685 694 response = api_call(self, params)
686 695
687 696 expected = 'failed to fork repository `%s` as `%s`' % (self.REPO,
688 697 fork_name)
689 698 self._compare_error(id_, expected, given=response.body)
690 699
691 700 def test_api_get_users_group(self):
692 701 id_, params = _build_data(self.apikey, 'get_users_group',
693 702 usersgroupid=TEST_USERS_GROUP)
694 703 response = api_call(self, params)
695 704
696 705 users_group = UsersGroupModel().get_group(TEST_USERS_GROUP)
697 706 members = []
698 707 for user in users_group.members:
699 708 user = user.user
700 709 members.append(user.get_api_data())
701 710
702 711 ret = users_group.get_api_data()
703 712 ret['members'] = members
704 713 expected = ret
705 714 self._compare_ok(id_, expected, given=response.body)
706 715
707 716 def test_api_get_users_groups(self):
708 717
709 718 make_users_group('test_users_group2')
710 719
711 720 id_, params = _build_data(self.apikey, 'get_users_groups',)
712 721 response = api_call(self, params)
713 722
714 723 expected = []
715 724 for gr_name in [TEST_USERS_GROUP, 'test_users_group2']:
716 725 users_group = UsersGroupModel().get_group(gr_name)
717 726 ret = users_group.get_api_data()
718 727 expected.append(ret)
719 728 self._compare_ok(id_, expected, given=response.body)
720 729
721 730 UsersGroupModel().delete(users_group='test_users_group2')
722 731 Session().commit()
723 732
724 733 def test_api_create_users_group(self):
725 734 group_name = 'some_new_group'
726 735 id_, params = _build_data(self.apikey, 'create_users_group',
727 736 group_name=group_name)
728 737 response = api_call(self, params)
729 738
730 739 ret = {
731 740 'msg': 'created new users group `%s`' % group_name,
732 741 'users_group': jsonify(UsersGroupModel()\
733 742 .get_by_name(group_name)\
734 743 .get_api_data())
735 744 }
736 745 expected = ret
737 746 self._compare_ok(id_, expected, given=response.body)
738 747
739 748 destroy_users_group(group_name)
740 749
741 750 def test_api_get_users_group_that_exist(self):
742 751 id_, params = _build_data(self.apikey, 'create_users_group',
743 752 group_name=TEST_USERS_GROUP)
744 753 response = api_call(self, params)
745 754
746 755 expected = "users group `%s` already exist" % TEST_USERS_GROUP
747 756 self._compare_error(id_, expected, given=response.body)
748 757
749 758 @mock.patch.object(UsersGroupModel, 'create', crash)
750 759 def test_api_get_users_group_exception_occurred(self):
751 760 group_name = 'exception_happens'
752 761 id_, params = _build_data(self.apikey, 'create_users_group',
753 762 group_name=group_name)
754 763 response = api_call(self, params)
755 764
756 765 expected = 'failed to create group `%s`' % group_name
757 766 self._compare_error(id_, expected, given=response.body)
758 767
759 768 def test_api_add_user_to_users_group(self):
760 769 gr_name = 'test_group'
761 770 UsersGroupModel().create(gr_name)
762 771 Session().commit()
763 772 id_, params = _build_data(self.apikey, 'add_user_to_users_group',
764 773 usersgroupid=gr_name,
765 774 userid=TEST_USER_ADMIN_LOGIN)
766 775 response = api_call(self, params)
767 776
768 777 expected = {
769 778 'msg': 'added member `%s` to users group `%s`' % (
770 779 TEST_USER_ADMIN_LOGIN, gr_name
771 780 ),
772 781 'success': True}
773 782 self._compare_ok(id_, expected, given=response.body)
774 783
775 784 UsersGroupModel().delete(users_group=gr_name)
776 785 Session().commit()
777 786
778 787 def test_api_add_user_to_users_group_that_doesnt_exist(self):
779 788 id_, params = _build_data(self.apikey, 'add_user_to_users_group',
780 789 usersgroupid='false-group',
781 790 userid=TEST_USER_ADMIN_LOGIN)
782 791 response = api_call(self, params)
783 792
784 793 expected = 'users group `%s` does not exist' % 'false-group'
785 794 self._compare_error(id_, expected, given=response.body)
786 795
787 796 @mock.patch.object(UsersGroupModel, 'add_user_to_group', crash)
788 797 def test_api_add_user_to_users_group_exception_occurred(self):
789 798 gr_name = 'test_group'
790 799 UsersGroupModel().create(gr_name)
791 800 Session().commit()
792 801 id_, params = _build_data(self.apikey, 'add_user_to_users_group',
793 802 usersgroupid=gr_name,
794 803 userid=TEST_USER_ADMIN_LOGIN)
795 804 response = api_call(self, params)
796 805
797 806 expected = 'failed to add member to users group `%s`' % gr_name
798 807 self._compare_error(id_, expected, given=response.body)
799 808
800 809 UsersGroupModel().delete(users_group=gr_name)
801 810 Session().commit()
802 811
803 812 def test_api_remove_user_from_users_group(self):
804 813 gr_name = 'test_group_3'
805 814 gr = UsersGroupModel().create(gr_name)
806 815 UsersGroupModel().add_user_to_group(gr, user=TEST_USER_ADMIN_LOGIN)
807 816 Session().commit()
808 817 id_, params = _build_data(self.apikey, 'remove_user_from_users_group',
809 818 usersgroupid=gr_name,
810 819 userid=TEST_USER_ADMIN_LOGIN)
811 820 response = api_call(self, params)
812 821
813 822 expected = {
814 823 'msg': 'removed member `%s` from users group `%s`' % (
815 824 TEST_USER_ADMIN_LOGIN, gr_name
816 825 ),
817 826 'success': True}
818 827 self._compare_ok(id_, expected, given=response.body)
819 828
820 829 UsersGroupModel().delete(users_group=gr_name)
821 830 Session().commit()
822 831
823 832 @mock.patch.object(UsersGroupModel, 'remove_user_from_group', crash)
824 833 def test_api_remove_user_from_users_group_exception_occurred(self):
825 834 gr_name = 'test_group_3'
826 835 gr = UsersGroupModel().create(gr_name)
827 836 UsersGroupModel().add_user_to_group(gr, user=TEST_USER_ADMIN_LOGIN)
828 837 Session().commit()
829 838 id_, params = _build_data(self.apikey, 'remove_user_from_users_group',
830 839 usersgroupid=gr_name,
831 840 userid=TEST_USER_ADMIN_LOGIN)
832 841 response = api_call(self, params)
833 842
834 843 expected = 'failed to remove member from users group `%s`' % gr_name
835 844 self._compare_error(id_, expected, given=response.body)
836 845
837 846 UsersGroupModel().delete(users_group=gr_name)
838 847 Session().commit()
839 848
840 849 @parameterized.expand([('none', 'repository.none'),
841 850 ('read', 'repository.read'),
842 851 ('write', 'repository.write'),
843 852 ('admin', 'repository.admin')])
844 853 def test_api_grant_user_permission(self, name, perm):
845 854 id_, params = _build_data(self.apikey, 'grant_user_permission',
846 855 repoid=self.REPO,
847 856 userid=TEST_USER_ADMIN_LOGIN,
848 857 perm=perm)
849 858 response = api_call(self, params)
850 859
851 860 ret = {
852 861 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
853 862 perm, TEST_USER_ADMIN_LOGIN, self.REPO
854 863 ),
855 864 'success': True
856 865 }
857 866 expected = ret
858 867 self._compare_ok(id_, expected, given=response.body)
859 868
860 869 def test_api_grant_user_permission_wrong_permission(self):
861 870 perm = 'haha.no.permission'
862 871 id_, params = _build_data(self.apikey, 'grant_user_permission',
863 872 repoid=self.REPO,
864 873 userid=TEST_USER_ADMIN_LOGIN,
865 874 perm=perm)
866 875 response = api_call(self, params)
867 876
868 877 expected = 'permission `%s` does not exist' % perm
869 878 self._compare_error(id_, expected, given=response.body)
870 879
871 880 @mock.patch.object(RepoModel, 'grant_user_permission', crash)
872 881 def test_api_grant_user_permission_exception_when_adding(self):
873 882 perm = 'repository.read'
874 883 id_, params = _build_data(self.apikey, 'grant_user_permission',
875 884 repoid=self.REPO,
876 885 userid=TEST_USER_ADMIN_LOGIN,
877 886 perm=perm)
878 887 response = api_call(self, params)
879 888
880 889 expected = 'failed to edit permission for user: `%s` in repo: `%s`' % (
881 890 TEST_USER_ADMIN_LOGIN, self.REPO
882 891 )
883 892 self._compare_error(id_, expected, given=response.body)
884 893
885 894 def test_api_revoke_user_permission(self):
886 895 id_, params = _build_data(self.apikey, 'revoke_user_permission',
887 896 repoid=self.REPO,
888 897 userid=TEST_USER_ADMIN_LOGIN,)
889 898 response = api_call(self, params)
890 899
891 900 expected = {
892 901 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
893 902 TEST_USER_ADMIN_LOGIN, self.REPO
894 903 ),
895 904 'success': True
896 905 }
897 906 self._compare_ok(id_, expected, given=response.body)
898 907
899 908 @mock.patch.object(RepoModel, 'revoke_user_permission', crash)
900 909 def test_api_revoke_user_permission_exception_when_adding(self):
901 910 id_, params = _build_data(self.apikey, 'revoke_user_permission',
902 911 repoid=self.REPO,
903 912 userid=TEST_USER_ADMIN_LOGIN,)
904 913 response = api_call(self, params)
905 914
906 915 expected = 'failed to edit permission for user: `%s` in repo: `%s`' % (
907 916 TEST_USER_ADMIN_LOGIN, self.REPO
908 917 )
909 918 self._compare_error(id_, expected, given=response.body)
910 919
911 920 @parameterized.expand([('none', 'repository.none'),
912 921 ('read', 'repository.read'),
913 922 ('write', 'repository.write'),
914 923 ('admin', 'repository.admin')])
915 924 def test_api_grant_users_group_permission(self, name, perm):
916 925 id_, params = _build_data(self.apikey, 'grant_users_group_permission',
917 926 repoid=self.REPO,
918 927 usersgroupid=TEST_USERS_GROUP,
919 928 perm=perm)
920 929 response = api_call(self, params)
921 930
922 931 ret = {
923 932 'msg': 'Granted perm: `%s` for users group: `%s` in repo: `%s`' % (
924 933 perm, TEST_USERS_GROUP, self.REPO
925 934 ),
926 935 'success': True
927 936 }
928 937 expected = ret
929 938 self._compare_ok(id_, expected, given=response.body)
930 939
931 940 def test_api_grant_users_group_permission_wrong_permission(self):
932 941 perm = 'haha.no.permission'
933 942 id_, params = _build_data(self.apikey, 'grant_users_group_permission',
934 943 repoid=self.REPO,
935 944 usersgroupid=TEST_USERS_GROUP,
936 945 perm=perm)
937 946 response = api_call(self, params)
938 947
939 948 expected = 'permission `%s` does not exist' % perm
940 949 self._compare_error(id_, expected, given=response.body)
941 950
942 951 @mock.patch.object(RepoModel, 'grant_users_group_permission', crash)
943 952 def test_api_grant_users_group_permission_exception_when_adding(self):
944 953 perm = 'repository.read'
945 954 id_, params = _build_data(self.apikey, 'grant_users_group_permission',
946 955 repoid=self.REPO,
947 956 usersgroupid=TEST_USERS_GROUP,
948 957 perm=perm)
949 958 response = api_call(self, params)
950 959
951 960 expected = 'failed to edit permission for users group: `%s` in repo: `%s`' % (
952 961 TEST_USERS_GROUP, self.REPO
953 962 )
954 963 self._compare_error(id_, expected, given=response.body)
955 964
956 965 def test_api_revoke_users_group_permission(self):
957 966 RepoModel().grant_users_group_permission(repo=self.REPO,
958 967 group_name=TEST_USERS_GROUP,
959 968 perm='repository.read')
960 969 Session().commit()
961 970 id_, params = _build_data(self.apikey, 'revoke_users_group_permission',
962 971 repoid=self.REPO,
963 972 usersgroupid=TEST_USERS_GROUP,)
964 973 response = api_call(self, params)
965 974
966 975 expected = {
967 976 'msg': 'Revoked perm for users group: `%s` in repo: `%s`' % (
968 977 TEST_USERS_GROUP, self.REPO
969 978 ),
970 979 'success': True
971 980 }
972 981 self._compare_ok(id_, expected, given=response.body)
973 982
974 983 @mock.patch.object(RepoModel, 'revoke_users_group_permission', crash)
975 984 def test_api_revoke_users_group_permission_exception_when_adding(self):
976 985
977 986 id_, params = _build_data(self.apikey, 'revoke_users_group_permission',
978 987 repoid=self.REPO,
979 988 usersgroupid=TEST_USERS_GROUP,)
980 989 response = api_call(self, params)
981 990
982 991 expected = 'failed to edit permission for users group: `%s` in repo: `%s`' % (
983 992 TEST_USERS_GROUP, self.REPO
984 993 )
985 994 self._compare_error(id_, expected, given=response.body)
General Comments 0
You need to be logged in to leave comments. Login now