##// 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
@@ -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 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
@@ -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 @HasPermissionAnyDecorator('hg.admin')
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 @HasPermissionAnyDecorator('hg.admin')
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 @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 """
@@ -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 @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')):
@@ -685,7 +718,7 b' class ApiController(JSONRPCController):'
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
@@ -708,7 +741,7 b' class ApiController(JSONRPCController):'
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
@@ -741,7 +774,7 b' class ApiController(JSONRPCController):'
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
@@ -772,7 +805,7 b' class ApiController(JSONRPCController):'
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 """
@@ -811,7 +844,7 b' class ApiController(JSONRPCController):'
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
@@ -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