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:delete": "rule:admin_or_owner",
|
||||||
"workflows:get": "rule:admin_or_owner",
|
"workflows:get": "rule:admin_or_owner",
|
||||||
"workflows:list": "rule:admin_or_owner",
|
"workflows:list": "rule:admin_or_owner",
|
||||||
|
"workflows:list:all_projects": "rule:admin_only",
|
||||||
"workflows:update": "rule:admin_or_owner",
|
"workflows:update": "rule:admin_or_owner",
|
||||||
|
|
||||||
"event_triggers:create": "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,
|
types.uniquelist, types.list, types.uniquelist,
|
||||||
wtypes.text, wtypes.text, wtypes.text, wtypes.text,
|
wtypes.text, wtypes.text, wtypes.text, wtypes.text,
|
||||||
resources.SCOPE_TYPES, types.uuid, 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',
|
def get_all(self, marker=None, limit=None, sort_keys='created_at',
|
||||||
sort_dirs='asc', fields='', name=None, input=None,
|
sort_dirs='asc', fields='', name=None, input=None,
|
||||||
definition=None, tags=None, scope=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.
|
"""Return a list of workflows.
|
||||||
|
|
||||||
:param marker: Optional. Pagination marker for large data sets.
|
:param marker: Optional. Pagination marker for large data sets.
|
||||||
@ -203,9 +204,13 @@ class WorkflowsController(rest.RestController, hooks.HookController):
|
|||||||
time and date.
|
time and date.
|
||||||
:param updated_at: Optional. Keep only resources with specific latest
|
:param updated_at: Optional. Keep only resources with specific latest
|
||||||
update time and date.
|
update time and date.
|
||||||
|
:param all_projects: Optional. Get resources of all projects.
|
||||||
"""
|
"""
|
||||||
acl.enforce('workflows:list', context.ctx())
|
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(
|
filters = filter_utils.create_filters_from_request_params(
|
||||||
created_at=created_at,
|
created_at=created_at,
|
||||||
name=name,
|
name=name,
|
||||||
@ -218,8 +223,9 @@ class WorkflowsController(rest.RestController, hooks.HookController):
|
|||||||
)
|
)
|
||||||
|
|
||||||
LOG.info("Fetch workflows. marker=%s, limit=%s, sort_keys=%s, "
|
LOG.info("Fetch workflows. marker=%s, limit=%s, sort_keys=%s, "
|
||||||
"sort_dirs=%s, fields=%s, filters=%s", marker, limit,
|
"sort_dirs=%s, fields=%s, filters=%s, all_projects=%s",
|
||||||
sort_keys, sort_dirs, fields, filters)
|
marker, limit, sort_keys, sort_dirs, fields, filters,
|
||||||
|
all_projects)
|
||||||
|
|
||||||
return rest_utils.get_all(
|
return rest_utils.get_all(
|
||||||
resources.Workflows,
|
resources.Workflows,
|
||||||
@ -231,5 +237,6 @@ class WorkflowsController(rest.RestController, hooks.HookController):
|
|||||||
sort_keys=sort_keys,
|
sort_keys=sort_keys,
|
||||||
sort_dirs=sort_dirs,
|
sort_dirs=sort_dirs,
|
||||||
fields=fields,
|
fields=fields,
|
||||||
|
all_projects=all_projects,
|
||||||
**filters
|
**filters
|
||||||
)
|
)
|
||||||
|
@ -124,6 +124,9 @@ def context_from_headers_and_env(headers, env):
|
|||||||
service_catalog = (params['service_catalog'] if is_target
|
service_catalog = (params['service_catalog'] if is_target
|
||||||
else token_info.get('token', {}))
|
else token_info.get('token', {}))
|
||||||
|
|
||||||
|
roles = headers.get('X-Roles', "").split(",")
|
||||||
|
is_admin = True if 'admin' in roles else False
|
||||||
|
|
||||||
return MistralContext(
|
return MistralContext(
|
||||||
auth_uri=auth_uri,
|
auth_uri=auth_uri,
|
||||||
auth_cacert=auth_cacert,
|
auth_cacert=auth_cacert,
|
||||||
@ -135,9 +138,10 @@ def context_from_headers_and_env(headers, env):
|
|||||||
user_name=user_name,
|
user_name=user_name,
|
||||||
region_name=region_name,
|
region_name=region_name,
|
||||||
project_name=headers.get('X-Project-Name'),
|
project_name=headers.get('X-Project-Name'),
|
||||||
roles=headers.get('X-Roles', "").split(","),
|
roles=roles,
|
||||||
is_trust_scoped=False,
|
is_trust_scoped=False,
|
||||||
expires_at=token_info['token']['expires_at'] if token_info else None,
|
expires_at=token_info['token']['expires_at'] if token_info else None,
|
||||||
|
is_admin=is_admin
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -15,12 +15,14 @@
|
|||||||
|
|
||||||
import copy
|
import copy
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
import mock
|
import mock
|
||||||
|
|
||||||
from mistral.db.v2 import api as db_api
|
from mistral.db.v2 import api as db_api
|
||||||
from mistral.db.v2.sqlalchemy import models
|
from mistral.db.v2.sqlalchemy import models
|
||||||
from mistral import exceptions as exc
|
from mistral import exceptions as exc
|
||||||
from mistral.tests.unit.api import base
|
from mistral.tests.unit.api import base
|
||||||
|
from mistral.tests.unit import base as unit_base
|
||||||
from mistral import utils
|
from mistral import utils
|
||||||
|
|
||||||
WF_DEFINITION = """
|
WF_DEFINITION = """
|
||||||
@ -420,6 +422,26 @@ class TestWorkflowsController(base.APITest):
|
|||||||
|
|
||||||
self.assertEqual(0, len(resp.json['workflows']))
|
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)
|
@mock.patch.object(db_api, "get_workflow_definitions", MOCK_WFS)
|
||||||
def test_get_all_pagination(self):
|
def test_get_all_pagination(self):
|
||||||
resp = self.app.get(
|
resp = self.app.get(
|
||||||
|
@ -68,5 +68,6 @@ policy_data = """{
|
|||||||
"workflows:delete": "rule:admin_or_owner",
|
"workflows:delete": "rule:admin_or_owner",
|
||||||
"workflows:get": "rule:admin_or_owner",
|
"workflows:get": "rule:admin_or_owner",
|
||||||
"workflows:list": "rule:admin_or_owner",
|
"workflows:list": "rule:admin_or_owner",
|
||||||
|
"workflows:list:all_projects": "rule:admin_only",
|
||||||
"workflows:update": "rule:admin_or_owner",
|
"workflows:update": "rule:admin_or_owner",
|
||||||
}"""
|
}"""
|
||||||
|
@ -25,6 +25,7 @@ import six
|
|||||||
import webob
|
import webob
|
||||||
from wsme import exc as wsme_exc
|
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.db.v2.sqlalchemy import api as db_api
|
||||||
from mistral import exceptions as exc
|
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,
|
def get_all(list_cls, cls, get_all_function, get_function,
|
||||||
resource_function=None, marker=None, limit=None,
|
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.
|
"""Return a list of cls.
|
||||||
|
|
||||||
:param list_cls: Collection class (e.g.: Actions, Workflows, ...).
|
: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
|
fields if it's provided, since it will be used when
|
||||||
constructing 'next' link.
|
constructing 'next' link.
|
||||||
:param filters: Optional. A specified dictionary of filters to match.
|
: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:
|
if fields and 'id' not in fields:
|
||||||
fields.insert(0, 'id')
|
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_query_params(limit, sort_keys, sort_dirs)
|
||||||
validate_fields(fields, cls.get_fields())
|
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
|
marker_obj = None
|
||||||
|
|
||||||
if marker:
|
if marker:
|
||||||
@ -171,6 +181,7 @@ def get_all(list_cls, cls, get_all_function, get_function,
|
|||||||
marker=marker_obj,
|
marker=marker_obj,
|
||||||
sort_keys=sort_keys,
|
sort_keys=sort_keys,
|
||||||
sort_dirs=sort_dirs,
|
sort_dirs=sort_dirs,
|
||||||
|
insecure=insecure,
|
||||||
**filters
|
**filters
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -196,6 +207,7 @@ def get_all(list_cls, cls, get_all_function, get_function,
|
|||||||
sort_keys=sort_keys,
|
sort_keys=sort_keys,
|
||||||
sort_dirs=sort_dirs,
|
sort_dirs=sort_dirs,
|
||||||
fields=fields,
|
fields=fields,
|
||||||
|
insecure=insecure,
|
||||||
**filters
|
**filters
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
import json
|
||||||
|
|
||||||
from oslo_concurrency.fixture import lockutils
|
from oslo_concurrency.fixture import lockutils
|
||||||
from tempest.lib import exceptions
|
from tempest.lib import exceptions
|
||||||
@ -42,6 +43,62 @@ class WorkflowTestsV2(base.TestCase):
|
|||||||
|
|
||||||
self.assertNotIn('next', body)
|
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')
|
@test.attr(type='smoke')
|
||||||
def test_get_list_workflows_with_fields(self):
|
def test_get_list_workflows_with_fields(self):
|
||||||
resp, body = self.client.get_list_obj('workflows?fields=name')
|
resp, body = self.client.get_list_obj('workflows?fields=name')
|
||||||
|
@ -25,7 +25,7 @@ CONF = config.CONF
|
|||||||
|
|
||||||
|
|
||||||
class TestCase(test.BaseTestCase):
|
class TestCase(test.BaseTestCase):
|
||||||
credentials = ['primary', 'alt']
|
credentials = ['admin', 'primary', 'alt']
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def skip_checks(cls):
|
def skip_checks(cls):
|
||||||
@ -46,12 +46,15 @@ class TestCase(test.BaseTestCase):
|
|||||||
if 'WITHOUT_AUTH' in os.environ:
|
if 'WITHOUT_AUTH' in os.environ:
|
||||||
cls.mgr = mock.MagicMock()
|
cls.mgr = mock.MagicMock()
|
||||||
cls.mgr.auth_provider = service_base.AuthProv()
|
cls.mgr.auth_provider = service_base.AuthProv()
|
||||||
cls.alt_mgr = cls.mgr
|
cls.admin_mgr = cls.alt_mgr = cls.mgr
|
||||||
else:
|
else:
|
||||||
|
cls.admin_mgr = cls.admin_manager
|
||||||
cls.mgr = cls.manager
|
cls.mgr = cls.manager
|
||||||
cls.alt_mgr = cls.alt_manager
|
cls.alt_mgr = cls.alt_manager
|
||||||
|
|
||||||
if cls._service == 'workflowv2':
|
if cls._service == 'workflowv2':
|
||||||
|
cls.admin_client = mistral_client.MistralClientV2(
|
||||||
|
cls.admin_mgr.auth_provider, cls._service)
|
||||||
cls.client = mistral_client.MistralClientV2(
|
cls.client = mistral_client.MistralClientV2(
|
||||||
cls.mgr.auth_provider, cls._service)
|
cls.mgr.auth_provider, cls._service)
|
||||||
cls.alt_client = mistral_client.MistralClientV2(
|
cls.alt_client = mistral_client.MistralClientV2(
|
||||||
|
Loading…
Reference in New Issue
Block a user