Merge "Role based resource access control - get workflows"

This commit is contained in:
Jenkins 2017-01-10 11:54:03 +00:00 committed by Gerrit Code Review
commit 47a7274c76
8 changed files with 115 additions and 8 deletions

View File

@ -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",

View File

@ -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
)

View File

@ -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
)

View File

@ -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(

View File

@ -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",
}"""

View File

@ -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
)

View File

@ -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')

View File

@ -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(