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 |
|
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 |
@HasPermissionA |
|
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 |
@HasPermissionA |
|
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 |
@HasPermissionA |
|
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 |
@HasPermissionA |
|
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 |
@HasPermissionA |
|
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 |
@HasPermissionA |
|
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 |
@HasPermissionA |
|
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 |
@HasPermissionA |
|
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 |
@HasPermissionA |
|
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