Show More
@@ -155,9 +155,10 b' OUTPUT::' | |||
|
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 | |
@@ -166,7 +167,7 b' INPUT::' | |||
|
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 |
@@ -27,10 +27,12 b'' | |||
|
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 |
|
|
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 |
@@ -43,6 +45,22 b' from rhodecode.model.db import Repositor' | |||
|
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:: |
@@ -184,10 +202,11 b' class ApiController(JSONRPCController):' | |||
|
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: |
@@ -195,6 +214,20 b' class ApiController(JSONRPCController):' | |||
|
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: |
@@ -495,7 +528,7 b' class ApiController(JSONRPCController):' | |||
|
495 | 528 | ) |
|
496 | 529 | ) |
|
497 | 530 | |
|
498 |
@HasPermissionA |
|
|
531 | @HasPermissionAllDecorator('hg.admin') | |
|
499 | 532 | def get_repo(self, apiuser, repoid): |
|
500 | 533 | """" |
|
501 | 534 | Get repository by name |
@@ -526,7 +559,7 b' class ApiController(JSONRPCController):' | |||
|
526 | 559 | data['members'] = members |
|
527 | 560 | return data |
|
528 | 561 | |
|
529 |
@HasPermissionA |
|
|
562 | @HasPermissionAllDecorator('hg.admin') | |
|
530 | 563 | def get_repos(self, apiuser): |
|
531 | 564 | """" |
|
532 | 565 | Get all repositories |
@@ -539,7 +572,7 b' class ApiController(JSONRPCController):' | |||
|
539 | 572 | result.append(repo.get_api_data()) |
|
540 | 573 | return result |
|
541 | 574 | |
|
542 |
@HasPermissionA |
|
|
575 | @HasPermissionAllDecorator('hg.admin') | |
|
543 | 576 | def get_repo_nodes(self, apiuser, repoid, revision, root_path, |
|
544 | 577 | ret_type='all'): |
|
545 | 578 | """ |
@@ -642,7 +675,7 b' class ApiController(JSONRPCController):' | |||
|
642 | 675 | log.error(traceback.format_exc()) |
|
643 | 676 | raise JSONRPCError('failed to create repository `%s`' % repo_name) |
|
644 | 677 | |
|
645 |
@HasPermissionA |
|
|
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')): |
@@ -685,7 +718,7 b' class ApiController(JSONRPCController):' | |||
|
685 | 718 | fork_name) |
|
686 | 719 | ) |
|
687 | 720 | |
|
688 |
@HasPermissionA |
|
|
721 | @HasPermissionAllDecorator('hg.admin') | |
|
689 | 722 | def delete_repo(self, apiuser, repoid): |
|
690 | 723 | """ |
|
691 | 724 | Deletes a given repository |
@@ -708,7 +741,7 b' class ApiController(JSONRPCController):' | |||
|
708 | 741 | 'failed to delete repository `%s`' % repo.repo_name |
|
709 | 742 | ) |
|
710 | 743 | |
|
711 |
@HasPermissionA |
|
|
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 |
@@ -741,7 +774,7 b' class ApiController(JSONRPCController):' | |||
|
741 | 774 | ) |
|
742 | 775 | ) |
|
743 | 776 | |
|
744 |
@HasPermissionA |
|
|
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 |
@@ -772,7 +805,7 b' class ApiController(JSONRPCController):' | |||
|
772 | 805 | ) |
|
773 | 806 | ) |
|
774 | 807 | |
|
775 |
@HasPermissionA |
|
|
808 | @HasPermissionAllDecorator('hg.admin') | |
|
776 | 809 | def grant_users_group_permission(self, apiuser, repoid, usersgroupid, |
|
777 | 810 | perm): |
|
778 | 811 | """ |
@@ -811,7 +844,7 b' class ApiController(JSONRPCController):' | |||
|
811 | 844 | ) |
|
812 | 845 | ) |
|
813 | 846 | |
|
814 |
@HasPermissionA |
|
|
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 |
@@ -863,6 +863,109 b' class HasPermissionAnyMiddleware(object)' | |||
|
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. |
@@ -247,6 +247,15 b' class BaseTestApi(object):' | |||
|
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', |
General Comments 0
You need to be logged in to leave comments.
Login now