From a412ee8b48b12c491c008309a81ac09a880b4522 Mon Sep 17 00:00:00 2001 From: Lingxian Kong Date: Mon, 27 Mar 2017 21:29:53 +1300 Subject: [PATCH] Role based resource access control - get executions We already supported role based api access control, this series patches will implement resource access control for mistral, so that administrator could define the rules of resource accessibility, e.g. admin user could get/delete/update the workflows of other tenants according to the policy. This patch supports admin user to query executions of other tenants. Partially implements: blueprint mistral-rbac Change-Id: I7a5de13d4a067456bb001640189e54a230f196ad --- mistral/api/controllers/v2/execution.py | 22 ++++++++--- mistral/tests/unit/api/v2/test_executions.py | 40 +++++++++++++++++++- mistral/tests/unit/fake_policy.py | 1 + 3 files changed, 56 insertions(+), 7 deletions(-) diff --git a/mistral/api/controllers/v2/execution.py b/mistral/api/controllers/v2/execution.py index d0049bf0..19a0dcb2 100644 --- a/mistral/api/controllers/v2/execution.py +++ b/mistral/api/controllers/v2/execution.py @@ -227,13 +227,14 @@ class ExecutionsController(rest.RestController): types.uniquelist, types.list, types.uniquelist, wtypes.text, types.uuid, wtypes.text, types.jsontype, types.uuid, STATE_TYPES, wtypes.text, types.jsontype, - types.jsontype, wtypes.text, wtypes.text, bool) + types.jsontype, wtypes.text, wtypes.text, bool, + types.uuid, bool) def get_all(self, marker=None, limit=None, sort_keys='created_at', sort_dirs='asc', fields='', workflow_name=None, workflow_id=None, description=None, params=None, task_execution_id=None, state=None, state_info=None, input=None, output=None, created_at=None, updated_at=None, - include_output=None): + include_output=None, project_id=None, all_projects=False): """Return all Executions. :param marker: Optional. Pagination marker for large data sets. @@ -269,10 +270,17 @@ class ExecutionsController(rest.RestController): :param updated_at: Optional. Keep only resources with specific latest update time and date. :param include_output: Optional. Include the output for all executions - in the list + in the list. + :param project_id: Optional. Only get exectuions belong to the project. + Admin required. + :param all_projects: Optional. Get resources of all projects. Admin + required. """ acl.enforce('executions:list', context.ctx()) + if all_projects or project_id: + acl.enforce('executions:list:all_projects', context.ctx()) + filters = filter_utils.create_filters_from_request_params( created_at=created_at, workflow_name=workflow_name, @@ -284,13 +292,14 @@ class ExecutionsController(rest.RestController): input=input, output=output, updated_at=updated_at, - description=description + description=description, + project_id=project_id ) LOG.info( "Fetch executions. marker=%s, limit=%s, sort_keys=%s, " - "sort_dirs=%s, filters=%s", marker, limit, sort_keys, sort_dirs, - filters + "sort_dirs=%s, filters=%s, all_projects=%s", marker, limit, + sort_keys, sort_dirs, filters, all_projects ) if include_output: @@ -309,5 +318,6 @@ class ExecutionsController(rest.RestController): sort_keys=sort_keys, sort_dirs=sort_dirs, fields=fields, + all_projects=all_projects, **filters ) diff --git a/mistral/tests/unit/api/v2/test_executions.py b/mistral/tests/unit/api/v2/test_executions.py index 3e1d4e6e..ec01787e 100644 --- a/mistral/tests/unit/api/v2/test_executions.py +++ b/mistral/tests/unit/api/v2/test_executions.py @@ -22,6 +22,7 @@ import json import mock from oslo_config import cfg import oslo_messaging +from oslo_utils import uuidutils from webtest import app as webtest_app from mistral.api.controllers.v2 import execution @@ -31,10 +32,10 @@ from mistral.db.v2.sqlalchemy import models from mistral.engine.rpc_backend import rpc from mistral import exceptions as exc from mistral.tests.unit.api import base +from mistral.tests.unit import base as unit_base from mistral import utils from mistral.utils import rest_utils from mistral.workflow import states -from oslo_utils import uuidutils # This line is needed for correct initialization of messaging config. oslo_messaging.get_transport(cfg.CONF) @@ -630,3 +631,40 @@ class TestExecutionsController(base.APITest): resource_function = kwargs['resource_function'] self.assertIsNone(resource_function) + + @mock.patch('mistral.db.v2.api.get_workflow_executions') + @mock.patch('mistral.context.context_from_headers_and_env') + def test_get_all_projects_admin(self, mock_context, mock_get_execs): + admin_ctx = unit_base.get_context(admin=True) + mock_context.return_value = admin_ctx + + resp = self.app.get('/v2/executions?all_projects=true') + + self.assertEqual(200, resp.status_int) + + self.assertTrue(mock_get_execs.call_args[1].get('insecure', False)) + + def test_get_all_projects_normal_user(self): + resp = self.app.get( + '/v2/executions?all_projects=true', + expect_errors=True + ) + + self.assertEqual(403, resp.status_int) + + @mock.patch('mistral.db.v2.api.get_workflow_executions') + @mock.patch('mistral.context.context_from_headers_and_env') + def test_get_all_filter_by_project_id(self, mock_context, mock_get_execs): + admin_ctx = unit_base.get_context(admin=True) + mock_context.return_value = admin_ctx + + fake_project_id = uuidutils.generate_uuid() + + resp = self.app.get('/v2/executions?project_id=%s' % fake_project_id) + + self.assertEqual(200, resp.status_int) + + self.assertTrue(mock_get_execs.call_args[1].get('insecure', False)) + self.assertTrue( + mock_get_execs.call_args[1].get('project_id', fake_project_id) + ) diff --git a/mistral/tests/unit/fake_policy.py b/mistral/tests/unit/fake_policy.py index 62d1132e..fc28f729 100644 --- a/mistral/tests/unit/fake_policy.py +++ b/mistral/tests/unit/fake_policy.py @@ -44,6 +44,7 @@ policy_data = """{ "executions:delete": "rule:admin_or_owner", "executions:get": "rule:admin_or_owner", "executions:list": "rule:admin_or_owner", + "executions:list:all_projects": "rule:admin_only", "executions:update": "rule:admin_or_owner", "members:create": "rule:admin_or_owner",