
From: Aline Manera <alinefm@linux.vnet.ibm.com> An user only can perform actions to a Resource if it is not protected or if the logged user is listed in the resource users and groups parameters. Also update the tests to reflect those changes. Signed-off-by: Aline Manera <alinefm@linux.vnet.ibm.com> --- src/kimchi/control/base.py | 28 ++++++++++++++++++++++------ src/kimchi/control/host.py | 2 +- src/kimchi/exception.py | 4 ++++ src/kimchi/i18n.py | 1 + tests/test_authorization.py | 12 ++++++++++-- tests/test_rest.py | 2 +- 6 files changed, 39 insertions(+), 10 deletions(-) diff --git a/src/kimchi/control/base.py b/src/kimchi/control/base.py index 674c13b..ac24b3f 100644 --- a/src/kimchi/control/base.py +++ b/src/kimchi/control/base.py @@ -27,8 +27,8 @@ from kimchi.control.utils import parse_request, validate_method from kimchi.control.utils import validate_params from kimchi.exception import InvalidOperation, InvalidParameter -from kimchi.exception import KimchiException -from kimchi.exception import MissingParameter, NotFoundError, OperationFailed +from kimchi.exception import KimchiException, MissingParameter, NotFoundError +from kimchi.exception import OperationFailed, UnauthorizedError class Resource(object): @@ -65,8 +65,14 @@ def _redirect(self, ident, code=303): def generate_action_handler(self, action_name, action_args=None): def wrapper(*args, **kwargs): - validate_method(('POST')) + method = validate_method(('POST'), + self.role_key, self.admin_methods) + try: + self.lookup() + if not self.is_authorized(): + raise UnauthorizedError('KCHAPI0009E') + model_args = list(self.model_args) if action_args is not None: request = parse_request() @@ -87,10 +93,12 @@ def wrapper(*args, **kwargs): raise cherrypy.HTTPError(400, e.message) except InvalidOperation, e: raise cherrypy.HTTPError(400, e.message) - except OperationFailed, e: - raise cherrypy.HTTPError(500, e.message) + except UnauthorizedError, e: + raise cherrypy.HTTPError(403, e.message) except NotFoundError, e: raise cherrypy.HTTPError(404, e.message) + except OperationFailed, e: + raise cherrypy.HTTPError(500, e.message) except KimchiException, e: raise cherrypy.HTTPError(500, e.message) @@ -121,8 +129,14 @@ def delete(self): @cherrypy.expose def index(self): - method = validate_method(('GET', 'DELETE', 'PUT')) + method = validate_method(('GET', 'DELETE', 'PUT'), + self.role_key, self.admin_methods) + try: + self.lookup() + if not self.is_authorized(): + raise UnauthorizedError('KCHAPI0009E') + return {'GET': self.get, 'DELETE': self.delete, 'PUT': self.update}[method]() @@ -130,6 +144,8 @@ def index(self): raise cherrypy.HTTPError(400, e.message) except InvalidParameter, e: raise cherrypy.HTTPError(400, e.message) + except UnauthorizedError, e: + raise cherrypy.HTTPError(403, e.message) except NotFoundError, e: raise cherrypy.HTTPError(404, e.message) except OperationFailed, e: diff --git a/src/kimchi/control/host.py b/src/kimchi/control/host.py index e1971cc..c962472 100644 --- a/src/kimchi/control/host.py +++ b/src/kimchi/control/host.py @@ -42,7 +42,7 @@ def __init__(self, model, id=None): @cherrypy.expose def swupdate(self): - validate_method(('POST')) + validate_method(('POST'), self.role_key, self.admin_methods) try: task = self.model.host_swupdate() cherrypy.response.status = 202 diff --git a/src/kimchi/exception.py b/src/kimchi/exception.py index d84ddb9..9828223 100644 --- a/src/kimchi/exception.py +++ b/src/kimchi/exception.py @@ -92,3 +92,7 @@ class IsoFormatError(KimchiException): class TimeoutExpired(KimchiException): pass + + +class UnauthorizedError(KimchiException): + pass diff --git a/src/kimchi/i18n.py b/src/kimchi/i18n.py index 0c76145..be6c532 100644 --- a/src/kimchi/i18n.py +++ b/src/kimchi/i18n.py @@ -31,6 +31,7 @@ "KCHAPI0006E": _("Unable to parse JSON request"), "KCHAPI0007E": _("This API only supports JSON"), "KCHAPI0008E": _("Parameters does not match requirement in schema: %(err)s"), + "KCHAPI0009E": _("You don't have permission to perform this operation."), "KCHASYNC0001E": _("Datastore is not initiated in the model object."), "KCHASYNC0002E": _("Unable to start task due error: %(err)s"), diff --git a/tests/test_authorization.py b/tests/test_authorization.py index 03f8a88..3d0b357 100644 --- a/tests/test_authorization.py +++ b/tests/test_authorization.py @@ -116,7 +116,15 @@ def test_nonroot_access(self): self.assertEquals(200, resp.status) resp = self.request('/vms', req, 'POST') self.assertEquals(403, resp.status) - resp = self.request('/vms', '{}', 'PUT') + + # Create a vm using mockmodel directly to test Resource access + model.templates_create({'name': 'test', 'cdrom': '/nonexistent.iso'}) + model.vms_create({'name': 'test', 'template': '/templates/test'}) + + resp = self.request('/vms/test', '{}', 'PUT') self.assertEquals(403, resp.status) - resp = self.request('/vms', '{}', 'DELETE') + resp = self.request('/vms/test', '{}', 'DELETE') self.assertEquals(403, resp.status) + + model.template_delete('test') + model.vm_delete('test') diff --git a/tests/test_rest.py b/tests/test_rest.py index 54209ef..3c8c537 100644 --- a/tests/test_rest.py +++ b/tests/test_rest.py @@ -1207,7 +1207,7 @@ def verify_template(t, res): # Test nonexistent fields, specify a field 'foo' isn't in the Template t['foo'] = "bar" req = json.dumps(t) - resp = self.request('/templates/%s' % oldname, req, 'PUT') + resp = self.request('/templates/%s' % tmpl_name, req, 'PUT') self.assertEquals(400, resp.status) # Delete the template -- 1.9.3