Merge "Role based resource access control - get workflows"
This commit is contained in:
commit
47a7274c76
@ -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