##// 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 lock
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 This command can be executed only using api_key belonging to user with admin
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 INPUT::
163 INPUT::
163
164
@@ -166,7 +167,7 b' INPUT::'
166 method : "lock"
167 method : "lock"
167 args : {
168 args : {
168 "repoid" : "<reponame or repo_id>"
169 "repoid" : "<reponame or repo_id>"
169 "userid" : "<user_id or username>",
170 "userid" : "<user_id or username = Optional(=apiuser)>",
170 "locked" : "<bool true|false>"
171 "locked" : "<bool true|false>"
171 }
172 }
172
173
@@ -27,10 +27,12 b''
27
27
28 import traceback
28 import traceback
29 import logging
29 import logging
30 from pylons.controllers.util import abort
30
31
31 from rhodecode.controllers.api import JSONRPCController, JSONRPCError
32 from rhodecode.controllers.api import JSONRPCController, JSONRPCError
32 from rhodecode.lib.auth import HasPermissionAllDecorator, \
33 from rhodecode.lib.auth import PasswordGenerator, AuthUser, \
33 HasPermissionAnyDecorator, PasswordGenerator, AuthUser
34 HasPermissionAllDecorator, HasPermissionAnyDecorator, \
35 HasPermissionAnyApi, HasRepoPermissionAnyApi
34 from rhodecode.lib.utils import map_groups, repo2db_mapper
36 from rhodecode.lib.utils import map_groups, repo2db_mapper
35 from rhodecode.model.meta import Session
37 from rhodecode.model.meta import Session
36 from rhodecode.model.scm import ScmModel
38 from rhodecode.model.scm import ScmModel
@@ -43,6 +45,22 b' from rhodecode.model.db import Repositor'
43 log = logging.getLogger(__name__)
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 class Optional(object):
64 class Optional(object):
47 """
65 """
48 Defines an optional parameter::
66 Defines an optional parameter::
@@ -184,10 +202,11 b' class ApiController(JSONRPCController):'
184 'Error occurred during rescan repositories action'
202 'Error occurred during rescan repositories action'
185 )
203 )
186
204
187 @HasPermissionAllDecorator('hg.admin')
205 def lock(self, apiuser, repoid, locked, userid=Optional(OAttr('apiuser'))):
188 def lock(self, apiuser, repoid, userid, locked):
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 :param apiuser:
211 :param apiuser:
193 :param repoid:
212 :param repoid:
@@ -195,6 +214,20 b' class ApiController(JSONRPCController):'
195 :param locked:
214 :param locked:
196 """
215 """
197 repo = get_repo_or_error(repoid)
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 user = get_user_or_error(userid)
231 user = get_user_or_error(userid)
199 locked = bool(locked)
232 locked = bool(locked)
200 try:
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 def get_repo(self, apiuser, repoid):
532 def get_repo(self, apiuser, repoid):
500 """"
533 """"
501 Get repository by name
534 Get repository by name
@@ -526,7 +559,7 b' class ApiController(JSONRPCController):'
526 data['members'] = members
559 data['members'] = members
527 return data
560 return data
528
561
529 @HasPermissionAnyDecorator('hg.admin')
562 @HasPermissionAllDecorator('hg.admin')
530 def get_repos(self, apiuser):
563 def get_repos(self, apiuser):
531 """"
564 """"
532 Get all repositories
565 Get all repositories
@@ -539,7 +572,7 b' class ApiController(JSONRPCController):'
539 result.append(repo.get_api_data())
572 result.append(repo.get_api_data())
540 return result
573 return result
541
574
542 @HasPermissionAnyDecorator('hg.admin')
575 @HasPermissionAllDecorator('hg.admin')
543 def get_repo_nodes(self, apiuser, repoid, revision, root_path,
576 def get_repo_nodes(self, apiuser, repoid, revision, root_path,
544 ret_type='all'):
577 ret_type='all'):
545 """
578 """
@@ -642,7 +675,7 b' class ApiController(JSONRPCController):'
642 log.error(traceback.format_exc())
675 log.error(traceback.format_exc())
643 raise JSONRPCError('failed to create repository `%s`' % repo_name)
676 raise JSONRPCError('failed to create repository `%s`' % repo_name)
644
677
645 @HasPermissionAnyDecorator('hg.admin')
678 @HasPermissionAllDecorator('hg.admin')
646 def fork_repo(self, apiuser, repoid, fork_name, owner,
679 def fork_repo(self, apiuser, repoid, fork_name, owner,
647 description=Optional(''), copy_permissions=Optional(False),
680 description=Optional(''), copy_permissions=Optional(False),
648 private=Optional(False), landing_rev=Optional('tip')):
681 private=Optional(False), landing_rev=Optional('tip')):
@@ -685,7 +718,7 b' class ApiController(JSONRPCController):'
685 fork_name)
718 fork_name)
686 )
719 )
687
720
688 @HasPermissionAnyDecorator('hg.admin')
721 @HasPermissionAllDecorator('hg.admin')
689 def delete_repo(self, apiuser, repoid):
722 def delete_repo(self, apiuser, repoid):
690 """
723 """
691 Deletes a given repository
724 Deletes a given repository
@@ -708,7 +741,7 b' class ApiController(JSONRPCController):'
708 'failed to delete repository `%s`' % repo.repo_name
741 'failed to delete repository `%s`' % repo.repo_name
709 )
742 )
710
743
711 @HasPermissionAnyDecorator('hg.admin')
744 @HasPermissionAllDecorator('hg.admin')
712 def grant_user_permission(self, apiuser, repoid, userid, perm):
745 def grant_user_permission(self, apiuser, repoid, userid, perm):
713 """
746 """
714 Grant permission for user on given repository, or update existing one
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 def revoke_user_permission(self, apiuser, repoid, userid):
778 def revoke_user_permission(self, apiuser, repoid, userid):
746 """
779 """
747 Revoke permission for user on given repository
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 def grant_users_group_permission(self, apiuser, repoid, usersgroupid,
809 def grant_users_group_permission(self, apiuser, repoid, usersgroupid,
777 perm):
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 def revoke_users_group_permission(self, apiuser, repoid, usersgroupid):
848 def revoke_users_group_permission(self, apiuser, repoid, usersgroupid):
816 """
849 """
817 Revoke permission for users group on given repository
850 Revoke permission for users group on given repository
@@ -863,6 +863,109 b' class HasPermissionAnyMiddleware(object)'
863 return False
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 def check_ip_access(source_ip, allowed_ips=None):
969 def check_ip_access(source_ip, allowed_ips=None):
867 """
970 """
868 Checks if source_ip is a subnet of any of allowed_ips.
971 Checks if source_ip is a subnet of any of allowed_ips.
@@ -247,6 +247,15 b' class BaseTestApi(object):'
247 % (TEST_USER_ADMIN_LOGIN, self.REPO, False))
247 % (TEST_USER_ADMIN_LOGIN, self.REPO, False))
248 self._compare_ok(id_, expected, given=response.body)
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 @mock.patch.object(Repository, 'lock', crash)
259 @mock.patch.object(Repository, 'lock', crash)
251 def test_api_lock_error(self):
260 def test_api_lock_error(self):
252 id_, params = _build_data(self.apikey, 'lock',
261 id_, params = _build_data(self.apikey, 'lock',
General Comments 0
You need to be logged in to leave comments. Login now