Role based resource access control - get workflows
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. TODO: - Implement update workflow by admin - Implement delete workflow by admin - Implement for other resources(workfbook/execution/task/action, etc.) Partially implements: blueprint mistral-rbac Change-Id: I8b00e8a260a74457ad037ee7322a7cba9ae34fab
This commit is contained in:
parent
ba2f25c0e4
commit
965db538aa
@ -54,6 +54,7 @@
|
||||
"workflows:delete": "rule:admin_or_owner",
|
||||
"workflows:get": "rule:admin_or_owner",
|
||||
"workflows:list": "rule:admin_or_owner",
|
||||
"workflows:list:all_projects": "rule:admin_only",
|
||||
"workflows:update": "rule:admin_or_owner",
|
||||
|
||||
"event_triggers:create": "rule:admin_or_owner",
|
||||
|
@ -171,11 +171,12 @@ class WorkflowsController(rest.RestController, hooks.HookController):
|
||||
types.uniquelist, types.list, types.uniquelist,
|
||||
wtypes.text, wtypes.text, wtypes.text, wtypes.text,
|
||||
resources.SCOPE_TYPES, types.uuid, wtypes.text,
|
||||
wtypes.text)
|
||||
wtypes.text, bool)
|
||||
def get_all(self, marker=None, limit=None, sort_keys='created_at',
|
||||
sort_dirs='asc', fields='', name=None, input=None,
|
||||
definition=None, tags=None, scope=None,
|
||||
project_id=None, created_at=None, updated_at=None):
|
||||
project_id=None, created_at=None, updated_at=None,
|
||||
all_projects=False):
|
||||
"""Return a list of workflows.
|
||||
|
||||
:param marker: Optional. Pagination marker for large data sets.
|
||||
@ -203,9 +204,13 @@ class WorkflowsController(rest.RestController, hooks.HookController):
|
||||
time and date.
|
||||
:param updated_at: Optional. Keep only resources with specific latest
|
||||
update time and date.
|
||||
:param all_projects: Optional. Get resources of all projects.
|
||||
"""
|
||||
acl.enforce('workflows:list', context.ctx())
|
||||
|
||||
if all_projects:
|
||||
acl.enforce('workflows:list:all_projects', context.ctx())
|
||||
|
||||
filters = filter_utils.create_filters_from_request_params(
|
||||
created_at=created_at,
|
||||
name=name,
|
||||
@ -218,8 +223,9 @@ class WorkflowsController(rest.RestController, hooks.HookController):
|
||||
)
|
||||
|
||||
LOG.info("Fetch workflows. marker=%s, limit=%s, sort_keys=%s, "
|
||||
"sort_dirs=%s, fields=%s, filters=%s", marker, limit,
|
||||
sort_keys, sort_dirs, fields, filters)
|
||||
"sort_dirs=%s, fields=%s, filters=%s, all_projects=%s",
|
||||
marker, limit, sort_keys, sort_dirs, fields, filters,
|
||||
all_projects)
|
||||
|
||||
return rest_utils.get_all(
|
||||
resources.Workflows,
|
||||
@ -231,5 +237,6 @@ class WorkflowsController(rest.RestController, hooks.HookController):
|
||||
sort_keys=sort_keys,
|
||||
sort_dirs=sort_dirs,
|
||||
fields=fields,
|
||||
all_projects=all_projects,
|
||||
**filters
|
||||
)
|
||||
|
@ -124,6 +124,9 @@ def context_from_headers_and_env(headers, env):
|
||||
service_catalog = (params['service_catalog'] if is_target
|
||||
else token_info.get('token', {}))
|
||||
|
||||
roles = headers.get('X-Roles', "").split(",")
|
||||
is_admin = True if 'admin' in roles else False
|
||||
|
||||
return MistralContext(
|
||||
auth_uri=auth_uri,
|
||||
auth_cacert=auth_cacert,
|
||||
@ -135,9 +138,10 @@ def context_from_headers_and_env(headers, env):
|
||||
user_name=user_name,
|
||||
region_name=region_name,
|
||||
project_name=headers.get('X-Project-Name'),
|
||||
roles=headers.get('X-Roles', "").split(","),
|
||||
roles=roles,
|
||||
is_trust_scoped=False,
|
||||
expires_at=token_info['token']['expires_at'] if token_info else None,
|
||||
is_admin=is_admin
|
||||
)
|
||||
|
||||
|
||||
|
@ -15,12 +15,14 @@
|
||||
|
||||
import copy
|
||||
import datetime
|
||||
|
||||
import mock
|
||||
|
||||
from mistral.db.v2 import api as db_api
|
||||
from mistral.db.v2.sqlalchemy import models
|
||||
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
|
||||
|
||||
WF_DEFINITION = """
|
||||
@ -420,6 +422,26 @@ class TestWorkflowsController(base.APITest):
|
||||
|
||||
self.assertEqual(0, len(resp.json['workflows']))
|
||||
|
||||
@mock.patch('mistral.db.v2.api.get_workflow_definitions')
|
||||
@mock.patch('mistral.context.context_from_headers_and_env')
|
||||
def test_get_all_projects_admin(self, mock_context, mock_get_wf_defs):
|
||||
admin_ctx = unit_base.get_context(admin=True)
|
||||
mock_context.return_value = admin_ctx
|
||||
|
||||
resp = self.app.get('/v2/workflows?all_projects=true')
|
||||
|
||||
self.assertEqual(200, resp.status_int)
|
||||
|
||||
self.assertTrue(mock_get_wf_defs.call_args[1].get('insecure', False))
|
||||
|
||||
def test_get_all_projects_normal_user(self):
|
||||
resp = self.app.get(
|
||||
'/v2/workflows?all_projects=true',
|
||||
expect_errors=True
|
||||
)
|
||||
|
||||
self.assertEqual(403, resp.status_int)
|
||||
|
||||
@mock.patch.object(db_api, "get_workflow_definitions", MOCK_WFS)
|
||||
def test_get_all_pagination(self):
|
||||
resp = self.app.get(
|
||||
|
@ -68,5 +68,6 @@ policy_data = """{
|
||||
"workflows:delete": "rule:admin_or_owner",
|
||||
"workflows:get": "rule:admin_or_owner",
|
||||
"workflows:list": "rule:admin_or_owner",
|
||||
"workflows:list:all_projects": "rule:admin_only",
|
||||
"workflows:update": "rule:admin_or_owner",
|
||||
}"""
|
||||
|
@ -25,6 +25,7 @@ import six
|
||||
import webob
|
||||
from wsme import exc as wsme_exc
|
||||
|
||||
from mistral import context as auth_ctx
|
||||
from mistral.db.v2.sqlalchemy import api as db_api
|
||||
from mistral import exceptions as exc
|
||||
|
||||
@ -125,7 +126,8 @@ def filters_to_dict(**kwargs):
|
||||
|
||||
def get_all(list_cls, cls, get_all_function, get_function,
|
||||
resource_function=None, marker=None, limit=None,
|
||||
sort_keys='created_at', sort_dirs='asc', fields='', **filters):
|
||||
sort_keys='created_at', sort_dirs='asc', fields='',
|
||||
all_projects=False, **filters):
|
||||
"""Return a list of cls.
|
||||
|
||||
:param list_cls: Collection class (e.g.: Actions, Workflows, ...).
|
||||
@ -149,6 +151,7 @@ def get_all(list_cls, cls, get_all_function, get_function,
|
||||
fields if it's provided, since it will be used when
|
||||
constructing 'next' link.
|
||||
:param filters: Optional. A specified dictionary of filters to match.
|
||||
:param all_projects: Optional. Get resources of all projects.
|
||||
"""
|
||||
if fields and 'id' not in fields:
|
||||
fields.insert(0, 'id')
|
||||
@ -156,6 +159,13 @@ def get_all(list_cls, cls, get_all_function, get_function,
|
||||
validate_query_params(limit, sort_keys, sort_dirs)
|
||||
validate_fields(fields, cls.get_fields())
|
||||
|
||||
# Admin user can get all tenants resources, no matter they are private or
|
||||
# public.
|
||||
insecure = False
|
||||
if (all_projects or
|
||||
(auth_ctx.ctx().is_admin and filters.get('project_id', ''))):
|
||||
insecure = True
|
||||
|
||||
marker_obj = None
|
||||
|
||||
if marker:
|
||||
@ -171,6 +181,7 @@ def get_all(list_cls, cls, get_all_function, get_function,
|
||||
marker=marker_obj,
|
||||
sort_keys=sort_keys,
|
||||
sort_dirs=sort_dirs,
|
||||
insecure=insecure,
|
||||
**filters
|
||||
)
|
||||
|
||||
@ -196,6 +207,7 @@ def get_all(list_cls, cls, get_all_function, get_function,
|
||||
sort_keys=sort_keys,
|
||||
sort_dirs=sort_dirs,
|
||||
fields=fields,
|
||||
insecure=insecure,
|
||||
**filters
|
||||
)
|
||||
|
||||
|
@ -11,6 +11,7 @@
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
import json
|
||||
|
||||
from oslo_concurrency.fixture import lockutils
|
||||
from tempest.lib import exceptions
|
||||
@ -42,6 +43,62 @@ class WorkflowTestsV2(base.TestCase):
|
||||
|
||||
self.assertNotIn('next', body)
|
||||
|
||||
@test.attr(type='smoke')
|
||||
def test_get_list_workflows_by_admin(self):
|
||||
self.useFixture(lockutils.LockFixture('mistral-workflow'))
|
||||
|
||||
_, body = self.client.create_workflow('wf_v2.yaml')
|
||||
name = body['workflows'][0]['name']
|
||||
|
||||
resp, raw_body = self.admin_client.get('workflows?all_projects=true')
|
||||
body = json.loads(raw_body)
|
||||
|
||||
self.assertEqual(200, resp.status)
|
||||
|
||||
names = [wf['name'] for wf in body['workflows']]
|
||||
|
||||
self.assertIn(name, names)
|
||||
|
||||
@test.attr(type='smoke')
|
||||
def test_get_list_workflows_with_project_by_admin(self):
|
||||
self.useFixture(lockutils.LockFixture('mistral-workflow'))
|
||||
|
||||
_, body = self.client.create_workflow('wf_v2.yaml')
|
||||
|
||||
name = body['workflows'][0]['name']
|
||||
|
||||
resp, raw_body = self.admin_client.get(
|
||||
'workflows?project_id=%s' %
|
||||
self.client.auth_provider.credentials.tenant_id
|
||||
)
|
||||
body = json.loads(raw_body)
|
||||
|
||||
self.assertEqual(200, resp.status)
|
||||
|
||||
names = [wf['name'] for wf in body['workflows']]
|
||||
|
||||
self.assertIn(name, names)
|
||||
|
||||
@test.attr(type='smoke')
|
||||
def test_get_list_other_project_private_workflows(self):
|
||||
self.useFixture(lockutils.LockFixture('mistral-workflow'))
|
||||
|
||||
_, body = self.client.create_workflow('wf_v2.yaml')
|
||||
|
||||
name = body['workflows'][0]['name']
|
||||
|
||||
resp, raw_body = self.alt_client.get(
|
||||
'workflows?project_id=%s' %
|
||||
self.client.auth_provider.credentials.tenant_id
|
||||
)
|
||||
body = json.loads(raw_body)
|
||||
|
||||
self.assertEqual(200, resp.status)
|
||||
|
||||
names = [wf['name'] for wf in body['workflows']]
|
||||
|
||||
self.assertNotIn(name, names)
|
||||
|
||||
@test.attr(type='smoke')
|
||||
def test_get_list_workflows_with_fields(self):
|
||||
resp, body = self.client.get_list_obj('workflows?fields=name')
|
||||
|
@ -25,7 +25,7 @@ CONF = config.CONF
|
||||
|
||||
|
||||
class TestCase(test.BaseTestCase):
|
||||
credentials = ['primary', 'alt']
|
||||
credentials = ['admin', 'primary', 'alt']
|
||||
|
||||
@classmethod
|
||||
def skip_checks(cls):
|
||||
@ -46,12 +46,15 @@ class TestCase(test.BaseTestCase):
|
||||
if 'WITHOUT_AUTH' in os.environ:
|
||||
cls.mgr = mock.MagicMock()
|
||||
cls.mgr.auth_provider = service_base.AuthProv()
|
||||
cls.alt_mgr = cls.mgr
|
||||
cls.admin_mgr = cls.alt_mgr = cls.mgr
|
||||
else:
|
||||
cls.admin_mgr = cls.admin_manager
|
||||
cls.mgr = cls.manager
|
||||
cls.alt_mgr = cls.alt_manager
|
||||
|
||||
if cls._service == 'workflowv2':
|
||||
cls.admin_client = mistral_client.MistralClientV2(
|
||||
cls.admin_mgr.auth_provider, cls._service)
|
||||
cls.client = mistral_client.MistralClientV2(
|
||||
cls.mgr.auth_provider, cls._service)
|
||||
cls.alt_client = mistral_client.MistralClientV2(
|
||||
|
Loading…
Reference in New Issue
Block a user