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 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 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 # Run all Mistral processes as a single process
iniset $MISTRAL_CONF_FILE DEFAULT server all iniset $MISTRAL_CONF_FILE DEFAULT server all
@ -89,6 +92,9 @@ function configure_mistral {
# Configure action execution deletion policy # Configure action execution deletion policy
iniset $MISTRAL_CONF_FILE api allow_action_execution_deletion True 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 if [ "$LOG_COLOR" == "True" ] && [ "$SYSLOG" == "False" ]; then
setup_colorized_logging $MISTRAL_CONF_FILE DEFAULT tenant user setup_colorized_logging $MISTRAL_CONF_FILE DEFAULT tenant user
fi 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 keystonemiddleware import auth_token
from oslo_config import cfg from oslo_config import cfg
from oslo_policy import policy
from mistral import exceptions as exc
_ENFORCER = None _ENFORCER = None
@ -29,12 +32,39 @@ def setup(app):
# Change auth decisions of requests to the app itself. # Change auth decisions of requests to the app itself.
conf.update({'delay_auth_decision': True}) conf.update({'delay_auth_decision': True})
_ensure_enforcer_initialization()
return auth_token.AuthProtocol(app, conf) return auth_token.AuthProtocol(app, conf)
else: else:
return app 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): def get_limited_to(headers):
"""Return the user and project the request should be limited to. """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 from wsme import types as wtypes
import wsmeext.pecan as wsme_pecan import wsmeext.pecan as wsme_pecan
from mistral.api import access_control as acl
from mistral.api.controllers import resource from mistral.api.controllers import resource
from mistral.api.controllers.v2 import types from mistral.api.controllers.v2 import types
from mistral.api.controllers.v2 import validation from mistral.api.controllers.v2 import validation
from mistral.api.hooks import content_type as ct_hook 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.db.v2 import api as db_api
from mistral import exceptions as exc from mistral import exceptions as exc
from mistral.services import actions from mistral.services import actions
@ -102,6 +104,7 @@ class ActionsController(rest.RestController, hooks.HookController):
@wsme_pecan.wsexpose(Action, wtypes.text) @wsme_pecan.wsexpose(Action, wtypes.text)
def get(self, name): def get(self, name):
"""Return the named action.""" """Return the named action."""
acl.enforce('actions:get', context.ctx())
LOG.info("Fetch action [name=%s]" % name) LOG.info("Fetch action [name=%s]" % name)
db_model = db_api.get_action_definition(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 NOTE: This text is allowed to have definitions
of multiple actions. In this case they all will be updated. of multiple actions. In this case they all will be updated.
""" """
acl.enforce('actions:update', context.ctx())
definition = pecan.request.text definition = pecan.request.text
LOG.info("Update action(s) [definition=%s]" % definition) LOG.info("Update action(s) [definition=%s]" % definition)
scope = pecan.request.GET.get('scope', 'private') 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 NOTE: This text is allowed to have definitions
of multiple actions. In this case they all will be created. of multiple actions. In this case they all will be created.
""" """
acl.enforce('actions:create', context.ctx())
definition = pecan.request.text definition = pecan.request.text
scope = pecan.request.GET.get('scope', 'private') scope = pecan.request.GET.get('scope', 'private')
pecan.response.status = 201 pecan.response.status = 201
@ -164,6 +169,7 @@ class ActionsController(rest.RestController, hooks.HookController):
@wsme_pecan.wsexpose(None, wtypes.text, status_code=204) @wsme_pecan.wsexpose(None, wtypes.text, status_code=204)
def delete(self, name): def delete(self, name):
"""Delete the named action.""" """Delete the named action."""
acl.enforce('actions:delete', context.ctx())
LOG.info("Delete action [name=%s]" % name) LOG.info("Delete action [name=%s]" % name)
with db_api.transaction(): with db_api.transaction():
@ -194,6 +200,7 @@ class ActionsController(rest.RestController, hooks.HookController):
Where project_id is the same as the requester or Where project_id is the same as the requester or
project_id is different but the scope is public. 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, " LOG.info("Fetch actions. marker=%s, limit=%s, sort_keys=%s, "
"sort_dirs=%s", marker, limit, sort_keys, sort_dirs) "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 from wsme import types as wtypes
import wsmeext.pecan as wsme_pecan import wsmeext.pecan as wsme_pecan
from mistral.api import access_control as acl
from mistral.api.controllers import resource from mistral.api.controllers import resource
from mistral.api.controllers.v2 import types from mistral.api.controllers.v2 import types
from mistral import context
from mistral.db.v2 import api as db_api from mistral.db.v2 import api as db_api
from mistral.engine import rpc from mistral.engine import rpc
from mistral import exceptions as exc from mistral import exceptions as exc
@ -132,6 +134,7 @@ class ActionExecutionsController(rest.RestController):
@wsme_pecan.wsexpose(ActionExecution, wtypes.text) @wsme_pecan.wsexpose(ActionExecution, wtypes.text)
def get(self, id): def get(self, id):
"""Return the specified action_execution.""" """Return the specified action_execution."""
acl.enforce('action_executions:get', context.ctx())
LOG.info("Fetch action_execution [id=%s]" % id) LOG.info("Fetch action_execution [id=%s]" % id)
return _get_action_execution(id) return _get_action_execution(id)
@ -141,6 +144,7 @@ class ActionExecutionsController(rest.RestController):
body=ActionExecution, status_code=201) body=ActionExecution, status_code=201)
def post(self, action_ex): def post(self, action_ex):
"""Create new action_execution.""" """Create new action_execution."""
acl.enforce('action_executions:create', context.ctx())
LOG.info("Create action_execution [action_execution=%s]" % action_ex) LOG.info("Create action_execution [action_execution=%s]" % action_ex)
name = action_ex.name name = action_ex.name
@ -166,6 +170,7 @@ class ActionExecutionsController(rest.RestController):
@wsme_pecan.wsexpose(ActionExecution, wtypes.text, body=ActionExecution) @wsme_pecan.wsexpose(ActionExecution, wtypes.text, body=ActionExecution)
def put(self, id, action_ex): def put(self, id, action_ex):
"""Update the specified action_execution.""" """Update the specified action_execution."""
acl.enforce('action_executions:update', context.ctx())
LOG.info( LOG.info(
"Update action_execution [id=%s, action_execution=%s]" "Update action_execution [id=%s, action_execution=%s]"
% (id, action_ex) % (id, action_ex)
@ -192,6 +197,7 @@ class ActionExecutionsController(rest.RestController):
@wsme_pecan.wsexpose(ActionExecutions) @wsme_pecan.wsexpose(ActionExecutions)
def get_all(self): def get_all(self):
"""Return all action_executions within the execution.""" """Return all action_executions within the execution."""
acl.enforce('action_executions:list', context.ctx())
LOG.info("Fetch action_executions") LOG.info("Fetch action_executions")
return _get_action_executions() return _get_action_executions()
@ -201,6 +207,7 @@ class ActionExecutionsController(rest.RestController):
def delete(self, id): def delete(self, id):
"""Delete the specified action_execution.""" """Delete the specified action_execution."""
acl.enforce('action_executions:delete', context.ctx())
LOG.info("Delete action_execution [id=%s]" % id) LOG.info("Delete action_execution [id=%s]" % id)
if not cfg.CONF.api.allow_action_execution_deletion: if not cfg.CONF.api.allow_action_execution_deletion:
@ -224,6 +231,7 @@ class TasksActionExecutionController(rest.RestController):
@wsme_pecan.wsexpose(ActionExecutions, wtypes.text) @wsme_pecan.wsexpose(ActionExecutions, wtypes.text)
def get_all(self, task_execution_id): def get_all(self, task_execution_id):
"""Return all action executions within the task execution.""" """Return all action executions within the task execution."""
acl.enforce('action_executions:list', context.ctx())
LOG.info("Fetch action executions") LOG.info("Fetch action executions")
return _get_action_executions(task_execution_id=task_execution_id) 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) @wsme_pecan.wsexpose(ActionExecution, wtypes.text, wtypes.text)
def get(self, task_execution_id, action_ex_id): def get(self, task_execution_id, action_ex_id):
"""Return the specified action_execution.""" """Return the specified action_execution."""
acl.enforce('action_executions:get', context.ctx())
LOG.info("Fetch action_execution [id=%s]" % action_ex_id) LOG.info("Fetch action_execution [id=%s]" % action_ex_id)
return _get_action_execution(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 from wsme import types as wtypes
import wsmeext.pecan as wsme_pecan import wsmeext.pecan as wsme_pecan
from mistral.api import access_control as acl
from mistral.api.controllers import resource from mistral.api.controllers import resource
from mistral.api.controllers.v2 import types from mistral.api.controllers.v2 import types
from mistral import context
from mistral.db.v2 import api as db_api from mistral.db.v2 import api as db_api
from mistral.services import triggers from mistral.services import triggers
from mistral.utils import rest_utils from mistral.utils import rest_utils
@ -78,6 +80,7 @@ class CronTriggersController(rest.RestController):
def get(self, name): def get(self, name):
"""Returns the named cron_trigger.""" """Returns the named cron_trigger."""
acl.enforce('cron_triggers:get', context.ctx())
LOG.info('Fetch cron trigger [name=%s]' % name) LOG.info('Fetch cron trigger [name=%s]' % name)
db_model = db_api.get_cron_trigger(name) db_model = db_api.get_cron_trigger(name)
@ -89,6 +92,7 @@ class CronTriggersController(rest.RestController):
def post(self, cron_trigger): def post(self, cron_trigger):
"""Creates a new cron trigger.""" """Creates a new cron trigger."""
acl.enforce('cron_triggers:create', context.ctx())
LOG.info('Create cron trigger: %s' % cron_trigger) LOG.info('Create cron trigger: %s' % cron_trigger)
values = cron_trigger.to_dict() values = cron_trigger.to_dict()
@ -110,6 +114,7 @@ class CronTriggersController(rest.RestController):
@wsme_pecan.wsexpose(None, wtypes.text, status_code=204) @wsme_pecan.wsexpose(None, wtypes.text, status_code=204)
def delete(self, name): def delete(self, name):
"""Delete cron trigger.""" """Delete cron trigger."""
acl.enforce('cron_triggers:delete', context.ctx())
LOG.info("Delete cron trigger [name=%s]" % name) LOG.info("Delete cron trigger [name=%s]" % name)
db_api.delete_cron_trigger(name) db_api.delete_cron_trigger(name)
@ -118,6 +123,7 @@ class CronTriggersController(rest.RestController):
def get_all(self): def get_all(self):
"""Return all cron triggers.""" """Return all cron triggers."""
acl.enforce('cron_triggers:list', context.ctx())
LOG.info("Fetch cron triggers.") LOG.info("Fetch cron triggers.")
_list = [ _list = [

View File

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

View File

@ -20,9 +20,11 @@ from pecan import rest
from wsme import types as wtypes from wsme import types as wtypes
import wsmeext.pecan as wsme_pecan import wsmeext.pecan as wsme_pecan
from mistral.api import access_control as acl
from mistral.api.controllers import resource from mistral.api.controllers import resource
from mistral.api.controllers.v2 import task from mistral.api.controllers.v2 import task
from mistral.api.controllers.v2 import types from mistral.api.controllers.v2 import types
from mistral import context
from mistral.db.v2 import api as db_api from mistral.db.v2 import api as db_api
from mistral.engine import rpc from mistral.engine import rpc
from mistral import exceptions as exc from mistral import exceptions as exc
@ -116,6 +118,7 @@ class ExecutionsController(rest.RestController):
@wsme_pecan.wsexpose(Execution, wtypes.text) @wsme_pecan.wsexpose(Execution, wtypes.text)
def get(self, id): def get(self, id):
"""Return the specified Execution.""" """Return the specified Execution."""
acl.enforce("executions:get", context.ctx())
LOG.info("Fetch execution [id=%s]" % id) LOG.info("Fetch execution [id=%s]" % id)
return Execution.from_dict(db_api.get_workflow_execution(id).to_dict()) 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 id: execution ID.
:param wf_ex: Execution object. :param wf_ex: Execution object.
""" """
acl.enforce('executions:update', context.ctx())
LOG.info('Update execution [id=%s, execution=%s]' % (id, wf_ex)) LOG.info('Update execution [id=%s, execution=%s]' % (id, wf_ex))
db_api.ensure_workflow_execution_exists(id) db_api.ensure_workflow_execution_exists(id)
@ -219,6 +223,7 @@ class ExecutionsController(rest.RestController):
:param wf_ex: Execution object with input content. :param wf_ex: Execution object with input content.
""" """
acl.enforce('executions:create', context.ctx())
LOG.info('Create execution [execution=%s]' % wf_ex) LOG.info('Create execution [execution=%s]' % wf_ex)
engine = rpc.get_engine_client() engine = rpc.get_engine_client()
@ -244,6 +249,7 @@ class ExecutionsController(rest.RestController):
@wsme_pecan.wsexpose(None, wtypes.text, status_code=204) @wsme_pecan.wsexpose(None, wtypes.text, status_code=204)
def delete(self, id): def delete(self, id):
"""Delete the specified Execution.""" """Delete the specified Execution."""
acl.enforce('executions:delete', context.ctx())
LOG.info('Delete execution [id=%s]' % id) LOG.info('Delete execution [id=%s]' % id)
return db_api.delete_workflow_execution(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 Default: desc. The length of sort_dirs can be equal
or less than that of sort_keys. or less than that of sort_keys.
""" """
acl.enforce('executions:list', context.ctx())
LOG.info( LOG.info(
"Fetch executions. marker=%s, limit=%s, sort_keys=%s, " "Fetch executions. marker=%s, limit=%s, sort_keys=%s, "
"sort_dirs=%s", marker, limit, sort_keys, sort_dirs "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 from wsme import types as wtypes
import wsmeext.pecan as wsme_pecan import wsmeext.pecan as wsme_pecan
from mistral.api import access_control as acl
from mistral.api.controllers import resource from mistral.api.controllers import resource
from mistral.api.controllers.v2 import types from mistral.api.controllers.v2 import types
from mistral import context
from mistral.db.v2 import api as db_api from mistral.db.v2 import api as db_api
from mistral import exceptions as exc from mistral import exceptions as exc
from mistral.utils import rest_utils from mistral.utils import rest_utils
@ -87,6 +89,7 @@ class MembersController(rest.RestController):
@wsme_pecan.wsexpose(Member, wtypes.text) @wsme_pecan.wsexpose(Member, wtypes.text)
def get(self, member_id): def get(self, member_id):
"""Shows resource member details.""" """Shows resource member details."""
acl.enforce('members:get', context.ctx())
LOG.info( LOG.info(
"Fetch resource member [resource_id=%s, resource_type=%s, " "Fetch resource member [resource_id=%s, resource_type=%s, "
"member_id=%s].", "member_id=%s].",
@ -108,6 +111,7 @@ class MembersController(rest.RestController):
@wsme_pecan.wsexpose(Members) @wsme_pecan.wsexpose(Members)
def get_all(self): def get_all(self):
"""Return all members with whom the resource has been shared.""" """Return all members with whom the resource has been shared."""
acl.enforce('members:list', context.ctx())
LOG.info( LOG.info(
"Fetch resource members [resource_id=%s, resource_type=%s].", "Fetch resource members [resource_id=%s, resource_type=%s].",
self.resource_id, self.resource_id,
@ -127,6 +131,7 @@ class MembersController(rest.RestController):
@wsme_pecan.wsexpose(Member, body=Member, status_code=201) @wsme_pecan.wsexpose(Member, body=Member, status_code=201)
def post(self, member_info): def post(self, member_info):
"""Shares the resource to a new member.""" """Shares the resource to a new member."""
acl.enforce('members:create', context.ctx())
LOG.info( LOG.info(
"Share resource to a member. [resource_id=%s, " "Share resource to a member. [resource_id=%s, "
"resource_type=%s, member_info=%s].", "resource_type=%s, member_info=%s].",
@ -161,6 +166,7 @@ class MembersController(rest.RestController):
@wsme_pecan.wsexpose(Member, wtypes.text, body=Member) @wsme_pecan.wsexpose(Member, wtypes.text, body=Member)
def put(self, member_id, member_info): def put(self, member_id, member_info):
"""Sets the status for a resource member.""" """Sets the status for a resource member."""
acl.enforce('members:update', context.ctx())
LOG.info( LOG.info(
"Update resource member status. [resource_id=%s, " "Update resource member status. [resource_id=%s, "
"member_id=%s, member_info=%s].", "member_id=%s, member_info=%s].",
@ -187,6 +193,7 @@ class MembersController(rest.RestController):
@wsme_pecan.wsexpose(None, wtypes.text, status_code=204) @wsme_pecan.wsexpose(None, wtypes.text, status_code=204)
def delete(self, member_id): def delete(self, member_id):
"""Deletes a member from the member list of a resource.""" """Deletes a member from the member list of a resource."""
acl.enforce('members:delete', context.ctx())
LOG.info( LOG.info(
"Delete resource member. [resource_id=%s, " "Delete resource member. [resource_id=%s, "
"resource_type=%s, member_id=%s].", "resource_type=%s, member_id=%s].",

View File

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

View File

@ -21,9 +21,11 @@ import wsme
from wsme import types as wtypes from wsme import types as wtypes
import wsmeext.pecan as wsme_pecan import wsmeext.pecan as wsme_pecan
from mistral.api import access_control as acl
from mistral.api.controllers import resource from mistral.api.controllers import resource
from mistral.api.controllers.v2 import action_execution from mistral.api.controllers.v2 import action_execution
from mistral.api.controllers.v2 import types from mistral.api.controllers.v2 import types
from mistral import context
from mistral.db.v2 import api as db_api from mistral.db.v2 import api as db_api
from mistral.engine import rpc from mistral.engine import rpc
from mistral import exceptions as exc from mistral import exceptions as exc
@ -119,6 +121,7 @@ class TasksController(rest.RestController):
@wsme_pecan.wsexpose(Task, wtypes.text) @wsme_pecan.wsexpose(Task, wtypes.text)
def get(self, id): def get(self, id):
"""Return the specified task.""" """Return the specified task."""
acl.enforce('tasks:get', context.ctx())
LOG.info("Fetch task [id=%s]" % id) LOG.info("Fetch task [id=%s]" % id)
task_ex = db_api.get_task_execution(id) task_ex = db_api.get_task_execution(id)
@ -128,6 +131,7 @@ class TasksController(rest.RestController):
@wsme_pecan.wsexpose(Tasks) @wsme_pecan.wsexpose(Tasks)
def get_all(self): def get_all(self):
"""Return all tasks within the execution.""" """Return all tasks within the execution."""
acl.enforce('tasks:list', context.ctx())
LOG.info("Fetch tasks") LOG.info("Fetch tasks")
return _get_task_resources_with_results() return _get_task_resources_with_results()
@ -140,6 +144,7 @@ class TasksController(rest.RestController):
:param id: Task execution ID. :param id: Task execution ID.
:param task: Task execution object. :param task: Task execution object.
""" """
acl.enforce('tasks:update', context.ctx())
LOG.info("Update task execution [id=%s, task=%s]" % (id, task)) LOG.info("Update task execution [id=%s, task=%s]" % (id, task))
task_ex = db_api.get_task_execution(id) task_ex = db_api.get_task_execution(id)
@ -189,6 +194,7 @@ class ExecutionTasksController(rest.RestController):
@wsme_pecan.wsexpose(Tasks, wtypes.text) @wsme_pecan.wsexpose(Tasks, wtypes.text)
def get_all(self, workflow_execution_id): def get_all(self, workflow_execution_id):
"""Return all tasks within the workflow execution.""" """Return all tasks within the workflow execution."""
acl.enforce('tasks:list', context.ctx())
LOG.info("Fetch tasks.") LOG.info("Fetch tasks.")
return _get_task_resources_with_results(workflow_execution_id) 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 from wsme import types as wtypes
import wsmeext.pecan as wsme_pecan import wsmeext.pecan as wsme_pecan
from mistral.api import access_control as acl
from mistral.api.controllers import resource from mistral.api.controllers import resource
from mistral.api.controllers.v2 import validation from mistral.api.controllers.v2 import validation
from mistral.api.hooks import content_type as ct_hook 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.db.v2 import api as db_api
from mistral.services import workbooks from mistral.services import workbooks
from mistral.utils import rest_utils from mistral.utils import rest_utils
@ -80,6 +82,7 @@ class WorkbooksController(rest.RestController, hooks.HookController):
@wsme_pecan.wsexpose(Workbook, wtypes.text) @wsme_pecan.wsexpose(Workbook, wtypes.text)
def get(self, name): def get(self, name):
"""Return the named workbook.""" """Return the named workbook."""
acl.enforce('workbooks:get', context.ctx())
LOG.info("Fetch workbook [name=%s]" % name) LOG.info("Fetch workbook [name=%s]" % name)
db_model = db_api.get_workbook(name) db_model = db_api.get_workbook(name)
@ -90,6 +93,7 @@ class WorkbooksController(rest.RestController, hooks.HookController):
@pecan.expose(content_type="text/plain") @pecan.expose(content_type="text/plain")
def put(self): def put(self):
"""Update a workbook.""" """Update a workbook."""
acl.enforce('workbooks:update', context.ctx())
definition = pecan.request.text definition = pecan.request.text
LOG.info("Update workbook [definition=%s]" % definition) LOG.info("Update workbook [definition=%s]" % definition)
@ -101,6 +105,7 @@ class WorkbooksController(rest.RestController, hooks.HookController):
@pecan.expose(content_type="text/plain") @pecan.expose(content_type="text/plain")
def post(self): def post(self):
"""Create a new workbook.""" """Create a new workbook."""
acl.enforce('workbooks:create', context.ctx())
definition = pecan.request.text definition = pecan.request.text
LOG.info("Create workbook [definition=%s]" % definition) 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) @wsme_pecan.wsexpose(None, wtypes.text, status_code=204)
def delete(self, name): def delete(self, name):
"""Delete the named workbook.""" """Delete the named workbook."""
acl.enforce('workbooks:delete', context.ctx())
LOG.info("Delete workbook [name=%s]" % name) LOG.info("Delete workbook [name=%s]" % name)
db_api.delete_workbook(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 Where project_id is the same as the requestor or
project_id is different but the scope is public. project_id is different but the scope is public.
""" """
acl.enforce('workbooks:list', context.ctx())
LOG.info("Fetch workbooks.") LOG.info("Fetch workbooks.")
workbooks_list = [Workbook.from_dict(db_model.to_dict()) 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 from wsme import types as wtypes
import wsmeext.pecan as wsme_pecan import wsmeext.pecan as wsme_pecan
from mistral.api import access_control as acl
from mistral.api.controllers import resource from mistral.api.controllers import resource
from mistral.api.controllers.v2 import member from mistral.api.controllers.v2 import member
from mistral.api.controllers.v2 import types from mistral.api.controllers.v2 import types
from mistral.api.controllers.v2 import validation from mistral.api.controllers.v2 import validation
from mistral.api.hooks import content_type as ct_hook 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.db.v2 import api as db_api
from mistral import exceptions as exc from mistral import exceptions as exc
from mistral.services import workflows from mistral.services import workflows
@ -152,6 +154,7 @@ class WorkflowsController(rest.RestController, hooks.HookController):
@wsme_pecan.wsexpose(Workflow, wtypes.text) @wsme_pecan.wsexpose(Workflow, wtypes.text)
def get(self, identifier): def get(self, identifier):
"""Return the named workflow.""" """Return the named workflow."""
acl.enforce('workflows:get', context.ctx())
LOG.info("Fetch workflow [identifier=%s]" % identifier) LOG.info("Fetch workflow [identifier=%s]" % identifier)
db_model = db_api.get_workflow_definition(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 The text is allowed to have definitions of multiple workflows. In this
case they all will be updated. case they all will be updated.
""" """
acl.enforce('workflows:update', context.ctx())
definition = pecan.request.text definition = pecan.request.text
scope = pecan.request.GET.get('scope', 'private') 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 NOTE: The text is allowed to have definitions
of multiple workflows. In this case they all will be created. of multiple workflows. In this case they all will be created.
""" """
acl.enforce('workflows:create', context.ctx())
definition = pecan.request.text definition = pecan.request.text
scope = pecan.request.GET.get('scope', 'private') scope = pecan.request.GET.get('scope', 'private')
pecan.response.status = 201 pecan.response.status = 201
@ -223,6 +228,7 @@ class WorkflowsController(rest.RestController, hooks.HookController):
@wsme_pecan.wsexpose(None, wtypes.text, status_code=204) @wsme_pecan.wsexpose(None, wtypes.text, status_code=204)
def delete(self, identifier): def delete(self, identifier):
"""Delete a workflow.""" """Delete a workflow."""
acl.enforce('workflows:delete', context.ctx())
LOG.info("Delete workflow [identifier=%s]" % identifier) LOG.info("Delete workflow [identifier=%s]" % identifier)
with db_api.transaction(): with db_api.transaction():
@ -252,6 +258,7 @@ class WorkflowsController(rest.RestController, hooks.HookController):
Where project_id is the same as the requester or Where project_id is the same as the requester or
project_id is different but the scope is public. 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, " LOG.info("Fetch workflows. marker=%s, limit=%s, sort_keys=%s, "
"sort_dirs=%s, fields=%s", marker, limit, sort_keys, "sort_dirs=%s, fields=%s", marker, limit, sort_keys,
sort_dirs, fields) sort_dirs, fields)

View File

@ -22,6 +22,7 @@ from webtest import app as webtest_app
from mistral.services import periodic from mistral.services import periodic
from mistral.tests.unit import base from mistral.tests.unit import base
from mistral.tests.unit.mstrlfixtures import policy_fixtures
# Disable authentication for functional tests. # Disable authentication for functional tests.
cfg.CONF.set_default('auth_enable', False, group='pecan') 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.mock_ctx.return_value = self.ctx
self.addCleanup(self.patch_ctx.stop) self.addCleanup(self.patch_ctx.stop)
self.policy = self.useFixture(policy_fixtures.PolicyFixture())
def assertNotFound(self, url): def assertNotFound(self, url):
try: try:
self.app.get(url, headers={'Accept': 'application/json'}) 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.db>=4.1.0 # Apache-2.0
oslo.messaging>=4.5.0 # Apache-2.0 oslo.messaging>=4.5.0 # Apache-2.0
oslo.middleware>=3.0.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.utils>=3.11.0 # Apache-2.0
oslo.log>=1.14.0 # Apache-2.0 oslo.log>=1.14.0 # Apache-2.0
oslo.serialization>=1.10.0 # Apache-2.0 oslo.serialization>=1.10.0 # Apache-2.0

View File

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