aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarcin Kuzminski <marcin@python-works.com>2013-01-13 22:55:56 +0100
committerMarcin Kuzminski <marcin@python-works.com>2013-01-13 22:55:56 +0100
commit445b01322399b3fc78587bf2078dad70559c0556 (patch)
tree38832d56c8ea0c71949f3fc86f34d9445ed0fac1
parentb853a854d15dfa2c19003a21d7e5e88f4e1e4cd2 (diff)
Implemented API calls for non-admin users for locking/unlocking repositories
--HG-- branch : beta extra : amend_source : 6a03208e97dd2a7a8146f7b45b36ad568a9f82ec
-rw-r--r--docs/api/api.rst7
-rw-r--r--rhodecode/controllers/api/api.py61
-rw-r--r--rhodecode/lib/auth.py103
-rw-r--r--rhodecode/tests/api/api_base.py9
4 files changed, 163 insertions, 17 deletions
diff --git a/docs/api/api.rst b/docs/api/api.rst
index e15cb033..d597a698 100644
--- a/docs/api/api.rst
+++ b/docs/api/api.rst
@@ -155,9 +155,10 @@ OUTPUT::
lock
----
-Set locking state on given repository by given user.
+Set locking state on given repository by given user. If userid param is skipped
+, then it is set to id of user whos calling this method.
This command can be executed only using api_key belonging to user with admin
-rights.
+rights or regular user that have admin or write access to repository.
INPUT::
@@ -166,7 +167,7 @@ INPUT::
method : "lock"
args : {
"repoid" : "<reponame or repo_id>"
- "userid" : "<user_id or username>",
+ "userid" : "<user_id or username = Optional(=apiuser)>",
"locked" : "<bool true|false>"
}
diff --git a/rhodecode/controllers/api/api.py b/rhodecode/controllers/api/api.py
index 4176ee8c..e4170b62 100644
--- a/rhodecode/controllers/api/api.py
+++ b/rhodecode/controllers/api/api.py
@@ -27,10 +27,12 @@
import traceback
import logging
+from pylons.controllers.util import abort
from rhodecode.controllers.api import JSONRPCController, JSONRPCError
-from rhodecode.lib.auth import HasPermissionAllDecorator, \
- HasPermissionAnyDecorator, PasswordGenerator, AuthUser
+from rhodecode.lib.auth import PasswordGenerator, AuthUser, \
+ HasPermissionAllDecorator, HasPermissionAnyDecorator, \
+ HasPermissionAnyApi, HasRepoPermissionAnyApi
from rhodecode.lib.utils import map_groups, repo2db_mapper
from rhodecode.model.meta import Session
from rhodecode.model.scm import ScmModel
@@ -43,6 +45,22 @@ from rhodecode.model.db import Repository, RhodeCodeSetting, UserIpMap
log = logging.getLogger(__name__)
+class OptionalAttr(object):
+ """
+ Special Optional Option that defines other attribute
+ """
+ def __init__(self, attr_name):
+ self.attr_name = attr_name
+
+ def __repr__(self):
+ return '<OptionalAttr:%s>' % self.attr_name
+
+ def __call__(self):
+ return self
+#alias
+OAttr = OptionalAttr
+
+
class Optional(object):
"""
Defines an optional parameter::
@@ -184,10 +202,11 @@ class ApiController(JSONRPCController):
'Error occurred during rescan repositories action'
)
- @HasPermissionAllDecorator('hg.admin')
- def lock(self, apiuser, repoid, userid, locked):
+ def lock(self, apiuser, repoid, locked, userid=Optional(OAttr('apiuser'))):
"""
- Set locking state on particular repository by given user
+ Set locking state on particular repository by given user, if
+ this command is runned by non-admin account userid is set to user
+ who is calling this method
:param apiuser:
:param repoid:
@@ -195,6 +214,20 @@ class ApiController(JSONRPCController):
:param locked:
"""
repo = get_repo_or_error(repoid)
+ if HasPermissionAnyApi('hg.admin')(user=apiuser):
+ pass
+ elif HasRepoPermissionAnyApi('repository.admin',
+ 'repository.write')(user=apiuser,
+ repo_name=repo.repo_name):
+ #make sure normal user does not pass userid, he is not allowed to do that
+ if not isinstance(userid, Optional):
+ raise JSONRPCError(
+ 'Only RhodeCode admin can specify `userid` params'
+ )
+ else:
+ return abort(403)
+ if isinstance(userid, Optional):
+ userid = apiuser.user_id
user = get_user_or_error(userid)
locked = bool(locked)
try:
@@ -495,7 +528,7 @@ class ApiController(JSONRPCController):
)
)
- @HasPermissionAnyDecorator('hg.admin')
+ @HasPermissionAllDecorator('hg.admin')
def get_repo(self, apiuser, repoid):
""""
Get repository by name
@@ -526,7 +559,7 @@ class ApiController(JSONRPCController):
data['members'] = members
return data
- @HasPermissionAnyDecorator('hg.admin')
+ @HasPermissionAllDecorator('hg.admin')
def get_repos(self, apiuser):
""""
Get all repositories
@@ -539,7 +572,7 @@ class ApiController(JSONRPCController):
result.append(repo.get_api_data())
return result
- @HasPermissionAnyDecorator('hg.admin')
+ @HasPermissionAllDecorator('hg.admin')
def get_repo_nodes(self, apiuser, repoid, revision, root_path,
ret_type='all'):
"""
@@ -642,7 +675,7 @@ class ApiController(JSONRPCController):
log.error(traceback.format_exc())
raise JSONRPCError('failed to create repository `%s`' % repo_name)
- @HasPermissionAnyDecorator('hg.admin')
+ @HasPermissionAllDecorator('hg.admin')
def fork_repo(self, apiuser, repoid, fork_name, owner,
description=Optional(''), copy_permissions=Optional(False),
private=Optional(False), landing_rev=Optional('tip')):
@@ -685,7 +718,7 @@ class ApiController(JSONRPCController):
fork_name)
)
- @HasPermissionAnyDecorator('hg.admin')
+ @HasPermissionAllDecorator('hg.admin')
def delete_repo(self, apiuser, repoid):
"""
Deletes a given repository
@@ -708,7 +741,7 @@ class ApiController(JSONRPCController):
'failed to delete repository `%s`' % repo.repo_name
)
- @HasPermissionAnyDecorator('hg.admin')
+ @HasPermissionAllDecorator('hg.admin')
def grant_user_permission(self, apiuser, repoid, userid, perm):
"""
Grant permission for user on given repository, or update existing one
@@ -741,7 +774,7 @@ class ApiController(JSONRPCController):
)
)
- @HasPermissionAnyDecorator('hg.admin')
+ @HasPermissionAllDecorator('hg.admin')
def revoke_user_permission(self, apiuser, repoid, userid):
"""
Revoke permission for user on given repository
@@ -772,7 +805,7 @@ class ApiController(JSONRPCController):
)
)
- @HasPermissionAnyDecorator('hg.admin')
+ @HasPermissionAllDecorator('hg.admin')
def grant_users_group_permission(self, apiuser, repoid, usersgroupid,
perm):
"""
@@ -811,7 +844,7 @@ class ApiController(JSONRPCController):
)
)
- @HasPermissionAnyDecorator('hg.admin')
+ @HasPermissionAllDecorator('hg.admin')
def revoke_users_group_permission(self, apiuser, repoid, usersgroupid):
"""
Revoke permission for users group on given repository
diff --git a/rhodecode/lib/auth.py b/rhodecode/lib/auth.py
index 360777fb..07409459 100644
--- a/rhodecode/lib/auth.py
+++ b/rhodecode/lib/auth.py
@@ -863,6 +863,109 @@ class HasPermissionAnyMiddleware(object):
return False
+#==============================================================================
+# SPECIAL VERSION TO HANDLE API AUTH
+#==============================================================================
+class _BaseApiPerm(object):
+ def __init__(self, *perms):
+ self.required_perms = set(perms)
+
+ def __call__(self, check_location='unspecified', user=None, repo_name=None):
+ cls_name = self.__class__.__name__
+ check_scope = 'user:%s, repo:%s' % (user, repo_name)
+ log.debug('checking cls:%s %s %s @ %s', cls_name,
+ self.required_perms, check_scope, check_location)
+ if not user:
+ log.debug('Empty User passed into arguments')
+ return False
+
+ ## process user
+ if not isinstance(user, AuthUser):
+ user = AuthUser(user.user_id)
+
+ if self.check_permissions(user.permissions, repo_name):
+ log.debug('Permission to %s granted for user: %s @ %s', repo_name,
+ user, check_location)
+ return True
+
+ else:
+ log.debug('Permission to %s denied for user: %s @ %s', repo_name,
+ user, check_location)
+ return False
+
+ def check_permissions(self, perm_defs, repo_name):
+ """
+ implement in child class should return True if permissions are ok,
+ False otherwise
+
+ :param perm_defs: dict with permission definitions
+ :param repo_name: repo name
+ """
+ raise NotImplementedError()
+
+
+class HasPermissionAllApi(_BaseApiPerm):
+ def __call__(self, user, check_location=''):
+ return super(HasPermissionAllApi, self)\
+ .__call__(check_location=check_location, user=user)
+
+ def check_permissions(self, perm_defs, repo):
+ if self.required_perms.issubset(perm_defs.get('global')):
+ return True
+ return False
+
+
+class HasPermissionAnyApi(_BaseApiPerm):
+ def __call__(self, user, check_location=''):
+ return super(HasPermissionAnyApi, self)\
+ .__call__(check_location=check_location, user=user)
+
+ def check_permissions(self, perm_defs, repo):
+ if self.required_perms.intersection(perm_defs.get('global')):
+ return True
+ return False
+
+
+class HasRepoPermissionAllApi(_BaseApiPerm):
+ def __call__(self, user, repo_name, check_location=''):
+ return super(HasRepoPermissionAllApi, self)\
+ .__call__(check_location=check_location, user=user,
+ repo_name=repo_name)
+
+ def check_permissions(self, perm_defs, repo_name):
+
+ try:
+ self._user_perms = set(
+ [perm_defs['repositories'][repo_name]]
+ )
+ except KeyError:
+ log.warning(traceback.format_exc())
+ return False
+ if self.required_perms.issubset(self._user_perms):
+ return True
+ return False
+
+
+class HasRepoPermissionAnyApi(_BaseApiPerm):
+ def __call__(self, user, repo_name, check_location=''):
+ return super(HasRepoPermissionAnyApi, self)\
+ .__call__(check_location=check_location, user=user,
+ repo_name=repo_name)
+
+ def check_permissions(self, perm_defs, repo_name):
+
+ try:
+ _user_perms = set(
+ [perm_defs['repositories'][repo_name]]
+ )
+ except KeyError:
+ log.warning(traceback.format_exc())
+ return False
+ if self.required_perms.intersection(_user_perms):
+ return True
+ return False
+
+
def check_ip_access(source_ip, allowed_ips=None):
"""
Checks if source_ip is a subnet of any of allowed_ips.
diff --git a/rhodecode/tests/api/api_base.py b/rhodecode/tests/api/api_base.py
index 7ada1a9b..5d0b5c60 100644
--- a/rhodecode/tests/api/api_base.py
+++ b/rhodecode/tests/api/api_base.py
@@ -247,6 +247,15 @@ class BaseTestApi(object):
% (TEST_USER_ADMIN_LOGIN, self.REPO, False))
self._compare_ok(id_, expected, given=response.body)
+ def test_api_lock_repo_lock_aquire_optional_userid(self):
+ id_, params = _build_data(self.apikey, 'lock',
+ repoid=self.REPO,
+ locked=True)
+ response = api_call(self, params)
+ expected = ('User `%s` set lock state for repo `%s` to `%s`'
+ % (TEST_USER_ADMIN_LOGIN, self.REPO, True))
+ self._compare_ok(id_, expected, given=response.body)
+
@mock.patch.object(Repository, 'lock', crash)
def test_api_lock_error(self):
id_, params = _build_data(self.apikey, 'lock',