Role based resource access control - update executions

We already supported role based api access control, this series patches
will implement resource access control for mistral, so that
administrator could define the rules of resource accessibility, e.g.
admin user could get/delete/update the workflows of other tenants
according to the policy.

This patch supports admin user to update executions of other tenants.

Partially implements: blueprint mistral-rbac

Change-Id: Id8445d28dcc8adfa12588ec59a4b143bd018899b
This commit is contained in:
Lingxian Kong 2017-03-29 16:29:21 +13:00
parent 93591469d9
commit 2ba3df9e1c
3 changed files with 104 additions and 50 deletions

View File

@ -28,6 +28,7 @@ import sqlalchemy as sa
from sqlalchemy.ext.compiler import compiles
from sqlalchemy.sql.expression import Insert
from mistral import context
from mistral.db.sqlalchemy import base as b
from mistral.db.sqlalchemy import model_base as mb
from mistral.db.sqlalchemy import sqlite_lock
@ -255,8 +256,10 @@ def _get_db_object_by_name(model, name):
return _secure_query(model).filter_by(name=name).first()
def _get_db_object_by_id(model, id):
return _secure_query(model).filter_by(id=id).first()
def _get_db_object_by_id(model, id, insecure=False):
query = b.model_query(model) if insecure else _secure_query(model)
return query.filter_by(id=id).first()
def _get_db_object_by_name_or_id(model, identifier, insecure=False):
@ -735,7 +738,13 @@ def _get_action_executions(**kwargs):
@b.session_aware()
def get_workflow_execution(id, session=None):
wf_ex = _get_db_object_by_id(models.WorkflowExecution, id)
ctx = context.ctx()
wf_ex = _get_db_object_by_id(
models.WorkflowExecution,
id,
insecure=ctx.is_admin
)
if not wf_ex:
raise exc.DBEntityNotFoundError(
@ -783,6 +792,8 @@ def create_workflow_execution(values, session=None):
def update_workflow_execution(id, values, session=None):
wf_ex = get_workflow_execution(id)
m_dbutils.check_db_obj_access(wf_ex)
wf_ex.update(values.copy())
return wf_ex

View File

@ -19,6 +19,7 @@ from oslo_config import cfg
import random
import testtools
from mistral import context as auth_context
from mistral.db.sqlalchemy import sqlite_lock
from mistral.db.v2.sqlalchemy import api as db_api
from mistral.db.v2.sqlalchemy import models as db_models
@ -89,6 +90,9 @@ class SQLiteLocksTest(test_base.DbTestCase):
self.assertEqual(0, len(sqlite_lock.get_locks()))
def _run_correct_locking(self, wf_ex):
# Set context info for the thread.
auth_context.set_ctx(test_base.get_context())
self._random_sleep()
with db_api.transaction():

View File

@ -29,7 +29,9 @@ from mistral.tests.unit import base as test_base
from mistral.utils import filter_utils
user_context = test_base.get_context(default=False)
DEFAULT_CTX = test_base.get_context()
USER_CTX = test_base.get_context(default=False)
ADM_CTX = test_base.get_context(default=False, admin=True)
WORKBOOKS = [
{
@ -305,7 +307,7 @@ class WorkbookTest(SQLAlchemyTest):
self.assertEqual(created, fetched[0])
# Create a new user.
auth_context.set_ctx(test_base.get_context(default=False))
auth_context.set_ctx(USER_CTX)
created = db_api.create_workbook(WORKBOOKS[1])
fetched = db_api.get_workbooks()
@ -324,7 +326,7 @@ class WorkbookTest(SQLAlchemyTest):
self.assertEqual(created1, fetched[0])
# Create a new user.
auth_context.set_ctx(test_base.get_context(default=False))
auth_context.set_ctx(USER_CTX)
fetched = db_api.get_workbooks()
@ -347,7 +349,7 @@ class WorkbookTest(SQLAlchemyTest):
auth_context.ctx().project_id)
# Create a new user.
auth_context.set_ctx(test_base.get_context(default=False))
auth_context.set_ctx(USER_CTX)
fetched = db_api.get_workbooks()
@ -606,7 +608,7 @@ class WorkflowDefinitionTest(SQLAlchemyTest):
created = db_api.create_workflow_definition(WF_DEFINITIONS[0])
# Switch to another project.
auth_context.set_ctx(test_base.get_context(default=False))
auth_context.set_ctx(USER_CTX)
self.assertRaises(
exc.NotAllowedException,
@ -619,7 +621,7 @@ class WorkflowDefinitionTest(SQLAlchemyTest):
created = db_api.create_workflow_definition(WF_DEFINITIONS[1])
# Switch to admin.
auth_context.set_ctx(test_base.get_context(default=False, admin=True))
auth_context.set_ctx(ADM_CTX)
updated = db_api.update_workflow_definition(
created['id'],
@ -632,7 +634,7 @@ class WorkflowDefinitionTest(SQLAlchemyTest):
self.assertEqual('my new definition', updated.definition)
# Switch back.
auth_context.set_ctx(test_base.get_context())
auth_context.set_ctx(DEFAULT_CTX)
fetched = db_api.get_workflow_definition(created['id'])
@ -645,7 +647,7 @@ class WorkflowDefinitionTest(SQLAlchemyTest):
created = db_api.create_workflow_definition(system_workflow)
# Switch to admin.
auth_context.set_ctx(test_base.get_context(default=False, admin=True))
auth_context.set_ctx(ADM_CTX)
updated = db_api.update_workflow_definition(
created['id'],
@ -689,13 +691,13 @@ class WorkflowDefinitionTest(SQLAlchemyTest):
created = db_api.create_workflow_definition(WF_DEFINITIONS[0])
# Create a new user.
auth_context.set_ctx(test_base.get_context(default=False))
auth_context.set_ctx(USER_CTX)
cron_trigger = copy.copy(CRON_TRIGGER)
cron_trigger['workflow_id'] = created.id
db_api.create_cron_trigger(cron_trigger)
auth_context.set_ctx(test_base.get_context(default=True))
auth_context.set_ctx(DEFAULT_CTX)
self.assertRaises(
exc.NotAllowedException,
@ -708,7 +710,7 @@ class WorkflowDefinitionTest(SQLAlchemyTest):
created = db_api.create_workflow_definition(WF_DEFINITIONS[0])
# Switch to another user.
auth_context.set_ctx(test_base.get_context(default=False))
auth_context.set_ctx(USER_CTX)
event_trigger = copy.copy(EVENT_TRIGGERS[0])
event_trigger.update({'workflow_id': created.id})
@ -716,7 +718,7 @@ class WorkflowDefinitionTest(SQLAlchemyTest):
db_api.create_event_trigger(event_trigger)
# Switch back.
auth_context.set_ctx(test_base.get_context(default=True))
auth_context.set_ctx(DEFAULT_CTX)
self.assertRaises(
exc.NotAllowedException,
@ -810,7 +812,7 @@ class WorkflowDefinitionTest(SQLAlchemyTest):
created = db_api.create_workflow_definition(WF_DEFINITIONS[0])
# Switch to another project.
auth_context.set_ctx(test_base.get_context(default=False))
auth_context.set_ctx(USER_CTX)
self.assertRaises(
exc.NotAllowedException,
@ -822,12 +824,12 @@ class WorkflowDefinitionTest(SQLAlchemyTest):
created = db_api.create_workflow_definition(WF_DEFINITIONS[0])
# Switch to admin.
auth_context.set_ctx(test_base.get_context(default=False, admin=True))
auth_context.set_ctx(ADM_CTX)
db_api.delete_workflow_definition(created['id'])
# Switch back.
auth_context.set_ctx(test_base.get_context())
auth_context.set_ctx(DEFAULT_CTX)
self.assertRaises(
exc.DBEntityNotFoundError,
@ -846,7 +848,7 @@ class WorkflowDefinitionTest(SQLAlchemyTest):
self.assertEqual(created1, fetched[0])
# Create a new user.
auth_context.set_ctx(test_base.get_context(default=False))
auth_context.set_ctx(USER_CTX)
fetched = db_api.get_workflow_definitions()
@ -871,7 +873,7 @@ class WorkflowDefinitionTest(SQLAlchemyTest):
)
# Create a new user.
auth_context.set_ctx(test_base.get_context(default=False))
auth_context.set_ctx(USER_CTX)
fetched = db_api.get_workflow_definitions()
@ -1352,7 +1354,7 @@ class ActionExecutionTest(SQLAlchemyTest):
created = db_api.create_action_execution(ACTION_EXECS[0])
# Create a new user.
auth_context.set_ctx(test_base.get_context(default=False))
auth_context.set_ctx(USER_CTX)
self.assertRaises(
exc.DBEntityNotFoundError,
@ -1455,6 +1457,43 @@ class WorkflowExecutionTest(SQLAlchemyTest):
self.assertEqual(updated, fetched)
self.assertIsNotNone(fetched.updated_at)
def test_update_workflow_execution_by_admin(self):
with db_api.transaction():
created = db_api.create_workflow_execution(WF_EXECS[0])
auth_context.set_ctx(ADM_CTX)
updated = db_api.update_workflow_execution(
created.id,
{'state': 'RUNNING', 'state_info': "Running..."}
)
auth_context.set_ctx(DEFAULT_CTX)
self.assertEqual('RUNNING', updated.state)
self.assertEqual(
'RUNNING',
db_api.load_workflow_execution(updated.id).state
)
fetched = db_api.get_workflow_execution(created.id)
self.assertEqual(updated, fetched)
self.assertIsNotNone(fetched.updated_at)
def test_update_workflow_execution_by_others_fail(self):
with db_api.transaction():
created = db_api.create_workflow_execution(WF_EXECS[0])
auth_context.set_ctx(USER_CTX)
self.assertRaises(
exc.DBEntityNotFoundError,
db_api.update_workflow_execution,
created.id,
{'state': 'RUNNING', 'state_info': "Running..."}
)
def test_create_or_update_workflow_execution(self):
id = 'not-existing-id'
@ -2243,7 +2282,7 @@ class CronTriggerTest(SQLAlchemyTest):
created0 = db_api.create_cron_trigger(CRON_TRIGGERS[0])
# Switch to another tenant.
auth_context.set_ctx(user_context)
auth_context.set_ctx(USER_CTX)
fetched = db_api.get_cron_triggers(
insecure=True,
@ -2559,7 +2598,7 @@ RESOURCE_MEMBERS = [
'resource_id': '123e4567-e89b-12d3-a456-426655440000',
'resource_type': 'workflow',
'project_id': security.get_project_id(),
'member_id': user_context.project_id,
'member_id': USER_CTX.project_id,
'status': 'pending',
},
{
@ -2580,18 +2619,18 @@ class ResourceMemberTest(SQLAlchemyTest):
fetched = db_api.get_resource_member(
'123e4567-e89b-12d3-a456-426655440000',
'workflow',
user_context.project_id
USER_CTX.project_id
)
self.assertEqual(created_1, fetched)
# Switch to another tenant.
auth_context.set_ctx(user_context)
auth_context.set_ctx(USER_CTX)
fetched = db_api.get_resource_member(
'123e4567-e89b-12d3-a456-426655440000',
'workflow',
user_context.project_id
USER_CTX.project_id
)
self.assertEqual(created_1, fetched)
@ -2630,7 +2669,7 @@ class ResourceMemberTest(SQLAlchemyTest):
db_api.create_resource_member(RESOURCE_MEMBERS[1])
# Switch to another tenant.
auth_context.set_ctx(user_context)
auth_context.set_ctx(USER_CTX)
fetched = db_api.get_resource_members(
created.resource_id,
@ -2644,12 +2683,12 @@ class ResourceMemberTest(SQLAlchemyTest):
created = db_api.create_resource_member(RESOURCE_MEMBERS[0])
# Switch to another tenant.
auth_context.set_ctx(user_context)
auth_context.set_ctx(USER_CTX)
updated = db_api.update_resource_member(
created.resource_id,
'workflow',
user_context.project_id,
USER_CTX.project_id,
{'status': 'accepted'}
)
@ -2664,7 +2703,7 @@ class ResourceMemberTest(SQLAlchemyTest):
db_api.update_resource_member,
created.resource_id,
'workflow',
user_context.project_id,
USER_CTX.project_id,
{'status': 'accepted'}
)
@ -2674,7 +2713,7 @@ class ResourceMemberTest(SQLAlchemyTest):
db_api.delete_resource_member(
created.resource_id,
'workflow',
user_context.project_id,
USER_CTX.project_id,
)
fetched = db_api.get_resource_members(
@ -2688,14 +2727,14 @@ class ResourceMemberTest(SQLAlchemyTest):
created = db_api.create_resource_member(RESOURCE_MEMBERS[0])
# Switch to another tenant.
auth_context.set_ctx(user_context)
auth_context.set_ctx(USER_CTX)
self.assertRaises(
exc.DBEntityNotFoundError,
db_api.delete_resource_member,
created.resource_id,
'workflow',
user_context.project_id,
USER_CTX.project_id,
)
def test_delete_resource_member_already_deleted(self):
@ -2704,7 +2743,7 @@ class ResourceMemberTest(SQLAlchemyTest):
db_api.delete_resource_member(
created.resource_id,
'workflow',
user_context.project_id,
USER_CTX.project_id,
)
self.assertRaises(
@ -2712,7 +2751,7 @@ class ResourceMemberTest(SQLAlchemyTest):
db_api.delete_resource_member,
created.resource_id,
'workflow',
user_context.project_id,
USER_CTX.project_id,
)
def test_delete_nonexistent_resource_member(self):
@ -2730,7 +2769,7 @@ class WorkflowSharingTest(SQLAlchemyTest):
wf = db_api.create_workflow_definition(WF_DEFINITIONS[1])
# Switch to another tenant.
auth_context.set_ctx(user_context)
auth_context.set_ctx(USER_CTX)
self.assertRaises(
exc.DBEntityNotFoundError,
@ -2739,25 +2778,25 @@ class WorkflowSharingTest(SQLAlchemyTest):
)
# Switch to original tenant, share workflow to another tenant.
auth_context.set_ctx(test_base.get_context())
auth_context.set_ctx(DEFAULT_CTX)
workflow_sharing = {
'resource_id': wf.id,
'resource_type': 'workflow',
'project_id': security.get_project_id(),
'member_id': user_context.project_id,
'member_id': USER_CTX.project_id,
'status': 'pending',
}
db_api.create_resource_member(workflow_sharing)
# Switch to another tenant, accept the sharing, get workflows.
auth_context.set_ctx(user_context)
auth_context.set_ctx(USER_CTX)
db_api.update_resource_member(
wf.id,
'workflow',
user_context.project_id,
USER_CTX.project_id,
{'status': 'accepted'}
)
@ -2772,19 +2811,19 @@ class WorkflowSharingTest(SQLAlchemyTest):
'resource_id': wf.id,
'resource_type': 'workflow',
'project_id': security.get_project_id(),
'member_id': user_context.project_id,
'member_id': USER_CTX.project_id,
'status': 'pending',
}
db_api.create_resource_member(workflow_sharing)
# Switch to another tenant, accept the sharing.
auth_context.set_ctx(user_context)
auth_context.set_ctx(USER_CTX)
db_api.update_resource_member(
wf.id,
'workflow',
user_context.project_id,
USER_CTX.project_id,
{'status': 'accepted'}
)
@ -2793,12 +2832,12 @@ class WorkflowSharingTest(SQLAlchemyTest):
self.assertEqual(wf, fetched)
# Switch to original tenant, delete the workflow.
auth_context.set_ctx(test_base.get_context())
auth_context.set_ctx(DEFAULT_CTX)
db_api.delete_workflow_definition(wf.id)
# Switch to another tenant, can not see that workflow.
auth_context.set_ctx(user_context)
auth_context.set_ctx(USER_CTX)
self.assertRaises(
exc.DBEntityNotFoundError,
@ -2813,19 +2852,19 @@ class WorkflowSharingTest(SQLAlchemyTest):
'resource_id': wf.id,
'resource_type': 'workflow',
'project_id': security.get_project_id(),
'member_id': user_context.project_id,
'member_id': USER_CTX.project_id,
'status': 'pending',
}
db_api.create_resource_member(workflow_sharing)
# Switch to another tenant, accept the sharing.
auth_context.set_ctx(user_context)
auth_context.set_ctx(USER_CTX)
db_api.update_resource_member(
wf.id,
'workflow',
user_context.project_id,
USER_CTX.project_id,
{'status': 'accepted'}
)
@ -2834,7 +2873,7 @@ class WorkflowSharingTest(SQLAlchemyTest):
db_api.create_cron_trigger(CRON_TRIGGERS[0])
# Switch to original tenant, try to delete the workflow.
auth_context.set_ctx(test_base.get_context())
auth_context.set_ctx(DEFAULT_CTX)
self.assertRaises(
exc.DBError,
@ -2893,7 +2932,7 @@ class EventTriggerTest(SQLAlchemyTest):
db_api.create_event_trigger(EVENT_TRIGGERS[0])
# Switch to another tenant.
auth_context.set_ctx(user_context)
auth_context.set_ctx(USER_CTX)
db_api.create_event_trigger(EVENT_TRIGGERS[1])
fetched = db_api.get_event_triggers()