Added role base authentication support

Mistral api access can be restricted base on role
by modifying /etc/mistral/policy.json file.

Change-Id: I4c78ca4cc04d25f46aea55948bce339cfe460ff0
Implements: blueprint mistral-customize-authorization
This commit is contained in:
hardik 2016-06-09 15:18:58 +05:30
parent e700a491d0
commit 11e443efeb
19 changed files with 355 additions and 1 deletions

View File

@ -62,6 +62,9 @@ function configure_mistral {
oslo-config-generator --config-file $MISTRAL_DIR/tools/config/config-generator.mistral.conf --output-file $MISTRAL_CONF_FILE
iniset $MISTRAL_CONF_FILE DEFAULT debug $MISTRAL_DEBUG
MISTRAL_POLICY_FILE=$MISTRAL_CONF_DIR/policy.json
cp $MISTRAL_DIR/etc/policy.json $MISTRAL_POLICY_FILE
# Run all Mistral processes as a single process
iniset $MISTRAL_CONF_FILE DEFAULT server all
@ -89,6 +92,9 @@ function configure_mistral {
# Configure action execution deletion policy
iniset $MISTRAL_CONF_FILE api allow_action_execution_deletion True
# Path of policy.json file.
iniset $MISTRAL_CONF oslo_policy policy_file $MISTRAL_POLICY_FILE
if [ "$LOG_COLOR" == "True" ] && [ "$SYSLOG" == "False" ]; then
setup_colorized_logging $MISTRAL_CONF_FILE DEFAULT tenant user
fi

58
etc/policy.json Normal file
View File

@ -0,0 +1,58 @@
{
"admin_only": "is_admin:True",
"admin_or_owner": "is_admin:True or project_id:%(project_id)s",
"default": "rule:admin_or_owner",
"action_executions:delete": "rule:admin_or_owner",
"action_execution:create": "rule:admin_or_owner",
"action_executions:get": "rule:admin_or_owner",
"action_executions:list": "rule:admin_or_owner",
"action_executions:update": "rule:admin_or_owner",
"actions:create": "rule:admin_or_owner",
"actions:delete": "rule:admin_or_owner",
"actions:get": "rule:admin_or_owner",
"actions:list": "rule:admin_or_owner",
"actions:update": "rule:admin_or_owner",
"cron_triggers:create": "rule:admin_or_owner",
"cron_triggers:delete": "rule:admin_or_owner",
"cron_triggers:get": "rule:admin_or_owner",
"cron_triggers:list": "rule:admin_or_owner",
"environments:create": "rule:admin_or_owner",
"environments:delete": "rule:admin_or_owner",
"environments:get": "rule:admin_or_owner",
"environments:list": "rule:admin_or_owner",
"environments:update": "rule:admin_or_owner",
"executions:create": "rule:admin_or_owner",
"executions:delete": "rule:admin_or_owner",
"executions:get": "rule:admin_or_owner",
"executions:list": "rule:admin_or_owner",
"executions:update": "rule:admin_or_owner",
"members:create": "rule:admin_or_owner",
"members:delete": "rule:admin_or_owner",
"members:get": "rule:admin_or_owner",
"members:list": "rule:admin_or_owner",
"members:update": "rule:admin_or_owner",
"services:list": "rule:admin_or_owner",
"tasks:get": "rule:admin_or_owner",
"tasks:list": "rule:admin_or_owner",
"tasks:update": "rule:admin_or_owner",
"workbooks:create": "rule:admin_or_owner",
"workbooks:delete": "rule:admin_or_owner",
"workbooks:get": "rule:admin_or_owner",
"workbooks:list": "rule:admin_or_owner",
"workbooks:update": "rule:admin_or_owner",
"workflows:create": "rule:admin_or_owner",
"workflows:delete": "rule:admin_or_owner",
"workflows:get": "rule:admin_or_owner",
"workflows:list": "rule:admin_or_owner",
"workflows:update": "rule:admin_or_owner"
}

View File

@ -18,6 +18,9 @@
from keystonemiddleware import auth_token
from oslo_config import cfg
from oslo_policy import policy
from mistral import exceptions as exc
_ENFORCER = None
@ -29,12 +32,39 @@ def setup(app):
# Change auth decisions of requests to the app itself.
conf.update({'delay_auth_decision': True})
_ensure_enforcer_initialization()
return auth_token.AuthProtocol(app, conf)
else:
return app
def enforce(action, context, target=None, do_raise=True,
exc=exc.NotAllowedException):
target_obj = {
'project_id': context.project_id,
'user_id': context.user_id,
}
target_obj.update(target or {})
_ensure_enforcer_initialization()
return _ENFORCER.enforce(
action,
target_obj,
context.to_dict(),
do_raise=do_raise,
exc=exc
)
def _ensure_enforcer_initialization():
global _ENFORCER
if not _ENFORCER:
_ENFORCER = policy.Enforcer(cfg.CONF)
_ENFORCER.load_rules()
def get_limited_to(headers):
"""Return the user and project the request should be limited to.

View File

@ -20,10 +20,12 @@ from pecan import rest
from wsme import types as wtypes
import wsmeext.pecan as wsme_pecan
from mistral.api import access_control as acl
from mistral.api.controllers import resource
from mistral.api.controllers.v2 import types
from mistral.api.controllers.v2 import validation
from mistral.api.hooks import content_type as ct_hook
from mistral import context
from mistral.db.v2 import api as db_api
from mistral import exceptions as exc
from mistral.services import actions
@ -102,6 +104,7 @@ class ActionsController(rest.RestController, hooks.HookController):
@wsme_pecan.wsexpose(Action, wtypes.text)
def get(self, name):
"""Return the named action."""
acl.enforce('actions:get', context.ctx())
LOG.info("Fetch action [name=%s]" % name)
db_model = db_api.get_action_definition(name)
@ -116,6 +119,7 @@ class ActionsController(rest.RestController, hooks.HookController):
NOTE: This text is allowed to have definitions
of multiple actions. In this case they all will be updated.
"""
acl.enforce('actions:update', context.ctx())
definition = pecan.request.text
LOG.info("Update action(s) [definition=%s]" % definition)
scope = pecan.request.GET.get('scope', 'private')
@ -141,6 +145,7 @@ class ActionsController(rest.RestController, hooks.HookController):
NOTE: This text is allowed to have definitions
of multiple actions. In this case they all will be created.
"""
acl.enforce('actions:create', context.ctx())
definition = pecan.request.text
scope = pecan.request.GET.get('scope', 'private')
pecan.response.status = 201
@ -164,6 +169,7 @@ class ActionsController(rest.RestController, hooks.HookController):
@wsme_pecan.wsexpose(None, wtypes.text, status_code=204)
def delete(self, name):
"""Delete the named action."""
acl.enforce('actions:delete', context.ctx())
LOG.info("Delete action [name=%s]" % name)
with db_api.transaction():
@ -194,6 +200,7 @@ class ActionsController(rest.RestController, hooks.HookController):
Where project_id is the same as the requester or
project_id is different but the scope is public.
"""
acl.enforce('actions:list', context.ctx())
LOG.info("Fetch actions. marker=%s, limit=%s, sort_keys=%s, "
"sort_dirs=%s", marker, limit, sort_keys, sort_dirs)

View File

@ -20,8 +20,10 @@ from pecan import rest
from wsme import types as wtypes
import wsmeext.pecan as wsme_pecan
from mistral.api import access_control as acl
from mistral.api.controllers import resource
from mistral.api.controllers.v2 import types
from mistral import context
from mistral.db.v2 import api as db_api
from mistral.engine import rpc
from mistral import exceptions as exc
@ -132,6 +134,7 @@ class ActionExecutionsController(rest.RestController):
@wsme_pecan.wsexpose(ActionExecution, wtypes.text)
def get(self, id):
"""Return the specified action_execution."""
acl.enforce('action_executions:get', context.ctx())
LOG.info("Fetch action_execution [id=%s]" % id)
return _get_action_execution(id)
@ -141,6 +144,7 @@ class ActionExecutionsController(rest.RestController):
body=ActionExecution, status_code=201)
def post(self, action_ex):
"""Create new action_execution."""
acl.enforce('action_executions:create', context.ctx())
LOG.info("Create action_execution [action_execution=%s]" % action_ex)
name = action_ex.name
@ -166,6 +170,7 @@ class ActionExecutionsController(rest.RestController):
@wsme_pecan.wsexpose(ActionExecution, wtypes.text, body=ActionExecution)
def put(self, id, action_ex):
"""Update the specified action_execution."""
acl.enforce('action_executions:update', context.ctx())
LOG.info(
"Update action_execution [id=%s, action_execution=%s]"
% (id, action_ex)
@ -192,6 +197,7 @@ class ActionExecutionsController(rest.RestController):
@wsme_pecan.wsexpose(ActionExecutions)
def get_all(self):
"""Return all action_executions within the execution."""
acl.enforce('action_executions:list', context.ctx())
LOG.info("Fetch action_executions")
return _get_action_executions()
@ -201,6 +207,7 @@ class ActionExecutionsController(rest.RestController):
def delete(self, id):
"""Delete the specified action_execution."""
acl.enforce('action_executions:delete', context.ctx())
LOG.info("Delete action_execution [id=%s]" % id)
if not cfg.CONF.api.allow_action_execution_deletion:
@ -224,6 +231,7 @@ class TasksActionExecutionController(rest.RestController):
@wsme_pecan.wsexpose(ActionExecutions, wtypes.text)
def get_all(self, task_execution_id):
"""Return all action executions within the task execution."""
acl.enforce('action_executions:list', context.ctx())
LOG.info("Fetch action executions")
return _get_action_executions(task_execution_id=task_execution_id)
@ -232,6 +240,7 @@ class TasksActionExecutionController(rest.RestController):
@wsme_pecan.wsexpose(ActionExecution, wtypes.text, wtypes.text)
def get(self, task_execution_id, action_ex_id):
"""Return the specified action_execution."""
acl.enforce('action_executions:get', context.ctx())
LOG.info("Fetch action_execution [id=%s]" % action_ex_id)
return _get_action_execution(action_ex_id)

View File

@ -17,8 +17,10 @@ from pecan import rest
from wsme import types as wtypes
import wsmeext.pecan as wsme_pecan
from mistral.api import access_control as acl
from mistral.api.controllers import resource
from mistral.api.controllers.v2 import types
from mistral import context
from mistral.db.v2 import api as db_api
from mistral.services import triggers
from mistral.utils import rest_utils
@ -78,6 +80,7 @@ class CronTriggersController(rest.RestController):
def get(self, name):
"""Returns the named cron_trigger."""
acl.enforce('cron_triggers:get', context.ctx())
LOG.info('Fetch cron trigger [name=%s]' % name)
db_model = db_api.get_cron_trigger(name)
@ -89,6 +92,7 @@ class CronTriggersController(rest.RestController):
def post(self, cron_trigger):
"""Creates a new cron trigger."""
acl.enforce('cron_triggers:create', context.ctx())
LOG.info('Create cron trigger: %s' % cron_trigger)
values = cron_trigger.to_dict()
@ -110,6 +114,7 @@ class CronTriggersController(rest.RestController):
@wsme_pecan.wsexpose(None, wtypes.text, status_code=204)
def delete(self, name):
"""Delete cron trigger."""
acl.enforce('cron_triggers:delete', context.ctx())
LOG.info("Delete cron trigger [name=%s]" % name)
db_api.delete_cron_trigger(name)
@ -118,6 +123,7 @@ class CronTriggersController(rest.RestController):
def get_all(self):
"""Return all cron triggers."""
acl.enforce('cron_triggers:list', context.ctx())
LOG.info("Fetch cron triggers.")
_list = [

View File

@ -20,8 +20,10 @@ from pecan import rest
from wsme import types as wtypes
import wsmeext.pecan as wsme_pecan
from mistral.api import access_control as acl
from mistral.api.controllers import resource
from mistral.api.controllers.v2 import types
from mistral import context
from mistral.db.v2 import api as db_api
from mistral import exceptions as exceptions
from mistral.utils import rest_utils
@ -77,6 +79,7 @@ class EnvironmentController(rest.RestController):
Where project_id is the same as the requestor or
project_id is different but the scope is public.
"""
acl.enforce('environments:list', context.ctx())
LOG.info("Fetch environments.")
environments = [
@ -90,6 +93,7 @@ class EnvironmentController(rest.RestController):
@wsme_pecan.wsexpose(Environment, wtypes.text)
def get(self, name):
"""Return the named environment."""
acl.enforce('environments:get', context.ctx())
LOG.info("Fetch environment [name=%s]" % name)
db_model = db_api.get_environment(name)
@ -100,6 +104,7 @@ class EnvironmentController(rest.RestController):
@wsme_pecan.wsexpose(Environment, body=Environment, status_code=201)
def post(self, env):
"""Create a new environment."""
acl.enforce('environments:create', context.ctx())
LOG.info("Create environment [env=%s]" % env)
self._validate_environment(
@ -115,6 +120,8 @@ class EnvironmentController(rest.RestController):
@wsme_pecan.wsexpose(Environment, body=Environment)
def put(self, env):
"""Update an environment."""
acl.enforce('environments:update', context.ctx())
if not env.name:
raise exceptions.InputException(
'Name of the environment is not provided.'
@ -138,6 +145,7 @@ class EnvironmentController(rest.RestController):
@wsme_pecan.wsexpose(None, wtypes.text, status_code=204)
def delete(self, name):
"""Delete the named environment."""
acl.enforce('environments:delete', context.ctx())
LOG.info("Delete environment [name=%s]" % name)
db_api.delete_environment(name)

View File

@ -20,9 +20,11 @@ from pecan import rest
from wsme import types as wtypes
import wsmeext.pecan as wsme_pecan
from mistral.api import access_control as acl
from mistral.api.controllers import resource
from mistral.api.controllers.v2 import task
from mistral.api.controllers.v2 import types
from mistral import context
from mistral.db.v2 import api as db_api
from mistral.engine import rpc
from mistral import exceptions as exc
@ -116,6 +118,7 @@ class ExecutionsController(rest.RestController):
@wsme_pecan.wsexpose(Execution, wtypes.text)
def get(self, id):
"""Return the specified Execution."""
acl.enforce("executions:get", context.ctx())
LOG.info("Fetch execution [id=%s]" % id)
return Execution.from_dict(db_api.get_workflow_execution(id).to_dict())
@ -128,6 +131,7 @@ class ExecutionsController(rest.RestController):
:param id: execution ID.
:param wf_ex: Execution object.
"""
acl.enforce('executions:update', context.ctx())
LOG.info('Update execution [id=%s, execution=%s]' % (id, wf_ex))
db_api.ensure_workflow_execution_exists(id)
@ -219,6 +223,7 @@ class ExecutionsController(rest.RestController):
:param wf_ex: Execution object with input content.
"""
acl.enforce('executions:create', context.ctx())
LOG.info('Create execution [execution=%s]' % wf_ex)
engine = rpc.get_engine_client()
@ -244,6 +249,7 @@ class ExecutionsController(rest.RestController):
@wsme_pecan.wsexpose(None, wtypes.text, status_code=204)
def delete(self, id):
"""Delete the specified Execution."""
acl.enforce('executions:delete', context.ctx())
LOG.info('Delete execution [id=%s]' % id)
return db_api.delete_workflow_execution(id)
@ -265,6 +271,7 @@ class ExecutionsController(rest.RestController):
Default: desc. The length of sort_dirs can be equal
or less than that of sort_keys.
"""
acl.enforce('executions:list', context.ctx())
LOG.info(
"Fetch executions. marker=%s, limit=%s, sort_keys=%s, "
"sort_dirs=%s", marker, limit, sort_keys, sort_dirs

View File

@ -20,8 +20,10 @@ from pecan import rest
from wsme import types as wtypes
import wsmeext.pecan as wsme_pecan
from mistral.api import access_control as acl
from mistral.api.controllers import resource
from mistral.api.controllers.v2 import types
from mistral import context
from mistral.db.v2 import api as db_api
from mistral import exceptions as exc
from mistral.utils import rest_utils
@ -87,6 +89,7 @@ class MembersController(rest.RestController):
@wsme_pecan.wsexpose(Member, wtypes.text)
def get(self, member_id):
"""Shows resource member details."""
acl.enforce('members:get', context.ctx())
LOG.info(
"Fetch resource member [resource_id=%s, resource_type=%s, "
"member_id=%s].",
@ -108,6 +111,7 @@ class MembersController(rest.RestController):
@wsme_pecan.wsexpose(Members)
def get_all(self):
"""Return all members with whom the resource has been shared."""
acl.enforce('members:list', context.ctx())
LOG.info(
"Fetch resource members [resource_id=%s, resource_type=%s].",
self.resource_id,
@ -127,6 +131,7 @@ class MembersController(rest.RestController):
@wsme_pecan.wsexpose(Member, body=Member, status_code=201)
def post(self, member_info):
"""Shares the resource to a new member."""
acl.enforce('members:create', context.ctx())
LOG.info(
"Share resource to a member. [resource_id=%s, "
"resource_type=%s, member_info=%s].",
@ -161,6 +166,7 @@ class MembersController(rest.RestController):
@wsme_pecan.wsexpose(Member, wtypes.text, body=Member)
def put(self, member_id, member_info):
"""Sets the status for a resource member."""
acl.enforce('members:update', context.ctx())
LOG.info(
"Update resource member status. [resource_id=%s, "
"member_id=%s, member_info=%s].",
@ -187,6 +193,7 @@ class MembersController(rest.RestController):
@wsme_pecan.wsexpose(None, wtypes.text, status_code=204)
def delete(self, member_id):
"""Deletes a member from the member list of a resource."""
acl.enforce('members:delete', context.ctx())
LOG.info(
"Delete resource member. [resource_id=%s, "
"resource_type=%s, member_id=%s].",

View File

@ -20,8 +20,10 @@ import tooz.coordination
from wsme import types as wtypes
import wsmeext.pecan as wsme_pecan
from mistral.api import access_control as acl
from mistral.api.controllers import resource
from mistral.cmd import launch
from mistral import context
from mistral import coordination
from mistral import exceptions as exc
from mistral.utils import rest_utils
@ -58,6 +60,7 @@ class ServicesController(rest.RestController):
def get_all(self):
"""Return all services."""
acl.enforce('services:list', context.ctx())
LOG.info("Fetch services.")
if not cfg.CONF.coordination.backend_url:

View File

@ -21,9 +21,11 @@ import wsme
from wsme import types as wtypes
import wsmeext.pecan as wsme_pecan
from mistral.api import access_control as acl
from mistral.api.controllers import resource
from mistral.api.controllers.v2 import action_execution
from mistral.api.controllers.v2 import types
from mistral import context
from mistral.db.v2 import api as db_api
from mistral.engine import rpc
from mistral import exceptions as exc
@ -119,6 +121,7 @@ class TasksController(rest.RestController):
@wsme_pecan.wsexpose(Task, wtypes.text)
def get(self, id):
"""Return the specified task."""
acl.enforce('tasks:get', context.ctx())
LOG.info("Fetch task [id=%s]" % id)
task_ex = db_api.get_task_execution(id)
@ -128,6 +131,7 @@ class TasksController(rest.RestController):
@wsme_pecan.wsexpose(Tasks)
def get_all(self):
"""Return all tasks within the execution."""
acl.enforce('tasks:list', context.ctx())
LOG.info("Fetch tasks")
return _get_task_resources_with_results()
@ -140,6 +144,7 @@ class TasksController(rest.RestController):
:param id: Task execution ID.
:param task: Task execution object.
"""
acl.enforce('tasks:update', context.ctx())
LOG.info("Update task execution [id=%s, task=%s]" % (id, task))
task_ex = db_api.get_task_execution(id)
@ -189,6 +194,7 @@ class ExecutionTasksController(rest.RestController):
@wsme_pecan.wsexpose(Tasks, wtypes.text)
def get_all(self, workflow_execution_id):
"""Return all tasks within the workflow execution."""
acl.enforce('tasks:list', context.ctx())
LOG.info("Fetch tasks.")
return _get_task_resources_with_results(workflow_execution_id)

View File

@ -20,9 +20,11 @@ from pecan import rest
from wsme import types as wtypes
import wsmeext.pecan as wsme_pecan
from mistral.api import access_control as acl
from mistral.api.controllers import resource
from mistral.api.controllers.v2 import validation
from mistral.api.hooks import content_type as ct_hook
from mistral import context
from mistral.db.v2 import api as db_api
from mistral.services import workbooks
from mistral.utils import rest_utils
@ -80,6 +82,7 @@ class WorkbooksController(rest.RestController, hooks.HookController):
@wsme_pecan.wsexpose(Workbook, wtypes.text)
def get(self, name):
"""Return the named workbook."""
acl.enforce('workbooks:get', context.ctx())
LOG.info("Fetch workbook [name=%s]" % name)
db_model = db_api.get_workbook(name)
@ -90,6 +93,7 @@ class WorkbooksController(rest.RestController, hooks.HookController):
@pecan.expose(content_type="text/plain")
def put(self):
"""Update a workbook."""
acl.enforce('workbooks:update', context.ctx())
definition = pecan.request.text
LOG.info("Update workbook [definition=%s]" % definition)
@ -101,6 +105,7 @@ class WorkbooksController(rest.RestController, hooks.HookController):
@pecan.expose(content_type="text/plain")
def post(self):
"""Create a new workbook."""
acl.enforce('workbooks:create', context.ctx())
definition = pecan.request.text
LOG.info("Create workbook [definition=%s]" % definition)
@ -113,6 +118,7 @@ class WorkbooksController(rest.RestController, hooks.HookController):
@wsme_pecan.wsexpose(None, wtypes.text, status_code=204)
def delete(self, name):
"""Delete the named workbook."""
acl.enforce('workbooks:delete', context.ctx())
LOG.info("Delete workbook [name=%s]" % name)
db_api.delete_workbook(name)
@ -124,6 +130,7 @@ class WorkbooksController(rest.RestController, hooks.HookController):
Where project_id is the same as the requestor or
project_id is different but the scope is public.
"""
acl.enforce('workbooks:list', context.ctx())
LOG.info("Fetch workbooks.")
workbooks_list = [Workbook.from_dict(db_model.to_dict())

View File

@ -22,11 +22,13 @@ from pecan import rest
from wsme import types as wtypes
import wsmeext.pecan as wsme_pecan
from mistral.api import access_control as acl
from mistral.api.controllers import resource
from mistral.api.controllers.v2 import member
from mistral.api.controllers.v2 import types
from mistral.api.controllers.v2 import validation
from mistral.api.hooks import content_type as ct_hook
from mistral import context
from mistral.db.v2 import api as db_api
from mistral import exceptions as exc
from mistral.services import workflows
@ -152,6 +154,7 @@ class WorkflowsController(rest.RestController, hooks.HookController):
@wsme_pecan.wsexpose(Workflow, wtypes.text)
def get(self, identifier):
"""Return the named workflow."""
acl.enforce('workflows:get', context.ctx())
LOG.info("Fetch workflow [identifier=%s]" % identifier)
db_model = db_api.get_workflow_definition(identifier)
@ -169,6 +172,7 @@ class WorkflowsController(rest.RestController, hooks.HookController):
The text is allowed to have definitions of multiple workflows. In this
case they all will be updated.
"""
acl.enforce('workflows:update', context.ctx())
definition = pecan.request.text
scope = pecan.request.GET.get('scope', 'private')
@ -200,6 +204,7 @@ class WorkflowsController(rest.RestController, hooks.HookController):
NOTE: The text is allowed to have definitions
of multiple workflows. In this case they all will be created.
"""
acl.enforce('workflows:create', context.ctx())
definition = pecan.request.text
scope = pecan.request.GET.get('scope', 'private')
pecan.response.status = 201
@ -223,6 +228,7 @@ class WorkflowsController(rest.RestController, hooks.HookController):
@wsme_pecan.wsexpose(None, wtypes.text, status_code=204)
def delete(self, identifier):
"""Delete a workflow."""
acl.enforce('workflows:delete', context.ctx())
LOG.info("Delete workflow [identifier=%s]" % identifier)
with db_api.transaction():
@ -252,6 +258,7 @@ class WorkflowsController(rest.RestController, hooks.HookController):
Where project_id is the same as the requester or
project_id is different but the scope is public.
"""
acl.enforce('workflows:list', context.ctx())
LOG.info("Fetch workflows. marker=%s, limit=%s, sort_keys=%s, "
"sort_dirs=%s, fields=%s", marker, limit, sort_keys,
sort_dirs, fields)

View File

@ -22,6 +22,7 @@ from webtest import app as webtest_app
from mistral.services import periodic
from mistral.tests.unit import base
from mistral.tests.unit.mstrlfixtures import policy_fixtures
# Disable authentication for functional tests.
cfg.CONF.set_default('auth_enable', False, group='pecan')
@ -61,6 +62,8 @@ class APITest(base.DbTestCase):
self.mock_ctx.return_value = self.ctx
self.addCleanup(self.patch_ctx.stop)
self.policy = self.useFixture(policy_fixtures.PolicyFixture())
def assertNotFound(self, url):
try:
self.app.get(url, headers={'Accept': 'application/json'})

View File

@ -0,0 +1,66 @@
# Copyright 2016 NEC Corporation. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from mistral.api import access_control as acl
from mistral import exceptions as exc
from mistral.tests.unit import base
from mistral.tests.unit.mstrlfixtures import policy_fixtures
class PolicyTestCase(base.BaseTest):
"""Tests whether the configuration of the policy engine is corect."""
def setUp(self):
super(PolicyTestCase, self).setUp()
self.policy = self.useFixture(policy_fixtures.PolicyFixture())
rules = {
"admin_only": "is_admin:True",
"admin_or_owner": "is_admin:True or project_id:%(project_id)s",
"example:admin": "rule:admin_only",
"example:admin_or_owner": "rule:admin_or_owner"
}
self.policy.set_rules(rules)
def test_admin_api_allowed(self):
auth_ctx = base.get_context(default=True, admin=True)
self.assertTrue(
acl.enforce('example:admin', auth_ctx, auth_ctx.to_dict())
)
def test_admin_api_disallowed(self):
auth_ctx = base.get_context(default=True)
self.assertRaises(
exc.NotAllowedException,
acl.enforce,
'example:admin',
auth_ctx,
auth_ctx.to_dict()
)
def test_admin_or_owner_api_allowed(self):
auth_ctx = base.get_context(default=True)
self.assertTrue(
acl.enforce('example:admin_or_owner', auth_ctx, auth_ctx.to_dict())
)
def test_admin_or_owner_api_disallowed(self):
auth_ctx = base.get_context(default=True)
target = {'project_id': 'another'}
self.assertRaises(
exc.NotAllowedException,
acl.enforce,
'example:admin_or_owner',
auth_ctx,
target
)

View File

@ -0,0 +1,72 @@
# Copyright 2016 NEC Corporation. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
policy_data = """{
"admin_only": "is_admin:True",
"admin_or_owner": "is_admin:True or project_id:%(project_id)s",
"default": "rule:admin_or_owner",
"action_executions:delete": "rule:admin_or_owner",
"action_execution:create": "rule:admin_or_owner",
"action_executions:get": "rule:admin_or_owner",
"action_executions:list": "rule:admin_or_owner",
"action_executions:update": "rule:admin_or_owner",
"actions:create": "rule:admin_or_owner",
"actions:delete": "rule:admin_or_owner",
"actions:get": "rule:admin_or_owner",
"actions:list": "rule:admin_or_owner",
"actions:update": "rule:admin_or_owner",
"cron_triggers:create": "rule:admin_or_owner",
"cron_triggers:delete": "rule:admin_or_owner",
"cron_triggers:get": "rule:admin_or_owner",
"cron_triggers:list": "rule:admin_or_owner",
"environments:create": "rule:admin_or_owner",
"environments:delete": "rule:admin_or_owner",
"environments:get": "rule:admin_or_owner",
"environments:list": "rule:admin_or_owner",
"environments:update": "rule:admin_or_owner",
"executions:create": "rule:admin_or_owner",
"executions:delete": "rule:admin_or_owner",
"executions:get": "rule:admin_or_owner",
"executions:list": "rule:admin_or_owner",
"executions:update": "rule:admin_or_owner",
"members:create": "rule:admin_or_owner",
"members:delete": "rule:admin_or_owner",
"members:get": "rule:admin_or_owner",
"members:list": "rule:admin_or_owner",
"members:update": "rule:admin_or_owner",
"services:list": "rule:admin_or_owner",
"tasks:get": "rule:admin_or_owner",
"tasks:list": "rule:admin_or_owner",
"tasks:update": "rule:admin_or_owner",
"workbooks:create": "rule:admin_or_owner",
"workbooks:delete": "rule:admin_or_owner",
"workbooks:get": "rule:admin_or_owner",
"workbooks:list": "rule:admin_or_owner",
"workbooks:update": "rule:admin_or_owner",
"workflows:create": "rule:admin_or_owner",
"workflows:delete": "rule:admin_or_owner",
"workflows:get": "rule:admin_or_owner",
"workflows:list": "rule:admin_or_owner",
"workflows:update": "rule:admin_or_owner",
}"""

View File

@ -0,0 +1,50 @@
# Copyright 2016 NEC Corporation. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# 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 os
import fixtures
from oslo_config import cfg
from oslo_policy import opts as policy_opts
from oslo_policy import policy as oslo_policy
from mistral.api import access_control as acl
from mistral.tests.unit import fake_policy
class PolicyFixture(fixtures.Fixture):
"""Load a fake policy from nova.tests.unit.fake_policy"""
def setUp(self):
super(PolicyFixture, self).setUp()
self.policy_dir = self.useFixture(fixtures.TempDir())
self.policy_file_name = os.path.join(
self.policy_dir.path,
'policy.json'
)
with open(self.policy_file_name, 'w') as policy_file:
policy_file.write(fake_policy.policy_data)
policy_opts.set_defaults(cfg.CONF)
cfg.CONF.set_override(
'policy_file',
self.policy_file_name,
'oslo_policy'
)
acl._ENFORCER = oslo_policy.Enforcer(cfg.CONF)
acl._ENFORCER.load_rules()
self.addCleanup(acl._ENFORCER.clear)
def set_rules(self, rules):
policy = acl._ENFORCER
policy.set_rules(oslo_policy.Rules.from_dict(rules))

View File

@ -15,6 +15,7 @@ oslo.config>=3.10.0 # Apache-2.0
oslo.db>=4.1.0 # Apache-2.0
oslo.messaging>=4.5.0 # Apache-2.0
oslo.middleware>=3.0.0 # Apache-2.0
oslo.policy>=1.9.0 # Apache-2.0
oslo.utils>=3.11.0 # Apache-2.0
oslo.log>=1.14.0 # Apache-2.0
oslo.serialization>=1.10.0 # Apache-2.0

View File

@ -5,4 +5,5 @@ namespace = oslo.messaging
namespace = oslo.middleware.cors
namespace = keystonemiddleware.auth_token
namespace = periodic.config
namespace = oslo.log
namespace = oslo.log
namespace = oslo.policy