Merge "Added role base authentication support"
This commit is contained in:
commit
8015d66490
@ -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
58
etc/policy.json
Normal 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"
|
||||
}
|
@ -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.
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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 = [
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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].",
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
@ -188,6 +193,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)
|
||||
|
@ -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())
|
||||
|
@ -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)
|
||||
|
@ -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'})
|
||||
|
66
mistral/tests/unit/api/test_access_control.py
Normal file
66
mistral/tests/unit/api/test_access_control.py
Normal 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
|
||||
)
|
72
mistral/tests/unit/fake_policy.py
Normal file
72
mistral/tests/unit/fake_policy.py
Normal 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",
|
||||
}"""
|
50
mistral/tests/unit/mstrlfixtures/policy_fixtures.py
Normal file
50
mistral/tests/unit/mstrlfixtures/policy_fixtures.py
Normal 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))
|
@ -15,6 +15,7 @@ oslo.config>=3.10.0 # Apache-2.0
|
||||
oslo.db>=4.1.0 # Apache-2.0
|
||||
oslo.messaging>=5.2.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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user