added permission to run privileged scripts w/ burnettk
This commit is contained in:
parent
229d4af792
commit
fcc79e639a
|
@ -16,7 +16,7 @@ groups:
|
||||||
alex,
|
alex,
|
||||||
dan,
|
dan,
|
||||||
mike,
|
mike,
|
||||||
jason,
|
jason@sartography.com,
|
||||||
jarrad,
|
jarrad,
|
||||||
elizabeth,
|
elizabeth,
|
||||||
jon,
|
jon,
|
||||||
|
@ -29,7 +29,7 @@ groups:
|
||||||
alex,
|
alex,
|
||||||
dan,
|
dan,
|
||||||
mike,
|
mike,
|
||||||
jason,
|
jason@sartography.com,
|
||||||
amir,
|
amir,
|
||||||
jarrad,
|
jarrad,
|
||||||
elizabeth,
|
elizabeth,
|
||||||
|
@ -46,7 +46,7 @@ groups:
|
||||||
fin,
|
fin,
|
||||||
fin1,
|
fin1,
|
||||||
harmeet,
|
harmeet,
|
||||||
jason,
|
jason@sartography.com,
|
||||||
sasha,
|
sasha,
|
||||||
manuchehr,
|
manuchehr,
|
||||||
lead,
|
lead,
|
||||||
|
|
|
@ -105,7 +105,7 @@ def verify_token(
|
||||||
) from e
|
) from e
|
||||||
|
|
||||||
if (
|
if (
|
||||||
user_info is not None and "error" not in user_info
|
user_info is not None and "error" not in user_info and 'iss' in user_info
|
||||||
): # not sure what to test yet
|
): # not sure what to test yet
|
||||||
user_model = (
|
user_model = (
|
||||||
UserModel.query.filter(UserModel.service == user_info["iss"])
|
UserModel.query.filter(UserModel.service == user_info["iss"])
|
||||||
|
|
|
@ -14,6 +14,10 @@ from spiffworkflow_backend.services.group_service import GroupService
|
||||||
class AddPermission(Script):
|
class AddPermission(Script):
|
||||||
"""AddUserToGroup."""
|
"""AddUserToGroup."""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def requires_privileged_permissions() -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
def get_description(self) -> str:
|
def get_description(self) -> str:
|
||||||
"""Get_description."""
|
"""Get_description."""
|
||||||
return """Add a permission to a group. ex: add_permission("read", "test/*", "Editors") """
|
return """Add a permission to a group. ex: add_permission("read", "test/*", "Editors") """
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
"""Script."""
|
"""Script."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel, ProcessInstanceNotFoundError
|
||||||
|
from spiffworkflow_backend.services.authorization_service import AuthorizationService
|
||||||
|
|
||||||
import importlib
|
import importlib
|
||||||
import os
|
import os
|
||||||
|
@ -20,6 +22,10 @@ from spiffworkflow_backend.models.script_attributes_context import (
|
||||||
SCRIPT_SUB_CLASSES = None
|
SCRIPT_SUB_CLASSES = None
|
||||||
|
|
||||||
|
|
||||||
|
class ScriptUnauthorizedForUserError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Script:
|
class Script:
|
||||||
"""Provides an abstract class that defines how scripts should work, this must be extended in all Script Tasks."""
|
"""Provides an abstract class that defines how scripts should work, this must be extended in all Script Tasks."""
|
||||||
|
|
||||||
|
@ -43,6 +49,10 @@ class Script:
|
||||||
+ "does not properly implement the run function.",
|
+ "does not properly implement the run function.",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def requires_privileged_permissions() -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def generate_augmented_list(
|
def generate_augmented_list(
|
||||||
script_attributes_context: ScriptAttributesContext,
|
script_attributes_context: ScriptAttributesContext,
|
||||||
|
@ -71,18 +81,41 @@ class Script:
|
||||||
that we created.
|
that we created.
|
||||||
"""
|
"""
|
||||||
instance = subclass()
|
instance = subclass()
|
||||||
return lambda *ar, **kw: subclass.run(
|
|
||||||
|
def run_subclass(*ar: Any, **kw: Any) -> Any:
|
||||||
|
if subclass.requires_privileged_permissions():
|
||||||
|
script_function_name = get_script_function_name(subclass)
|
||||||
|
uri = f"/v1.0/can-run-privileged-script/{script_function_name}"
|
||||||
|
process_instance = ProcessInstanceModel.query.filter_by(id=script_attributes_context.process_instance_id).first()
|
||||||
|
if process_instance is None:
|
||||||
|
raise ProcessInstanceNotFoundError(
|
||||||
|
f"Could not find a process instance with id '{script_attributes_context.process_instance_id}' "
|
||||||
|
f"when running script '{script_function_name}'"
|
||||||
|
)
|
||||||
|
user = process_instance.process_initiator
|
||||||
|
has_permission = AuthorizationService.user_has_permission(
|
||||||
|
user=user, permission="create", target_uri=uri
|
||||||
|
)
|
||||||
|
if not has_permission:
|
||||||
|
raise ScriptUnauthorizedForUserError(
|
||||||
|
f"User {user.username} does not have access to run privileged script '{script_function_name}'"
|
||||||
|
)
|
||||||
|
return subclass.run(
|
||||||
instance,
|
instance,
|
||||||
script_attributes_context,
|
script_attributes_context,
|
||||||
*ar,
|
*ar,
|
||||||
**kw,
|
**kw,
|
||||||
)
|
)
|
||||||
|
return run_subclass
|
||||||
|
|
||||||
|
def get_script_function_name(subclass: type[Script]) -> str:
|
||||||
|
return subclass.__module__.split(".")[-1]
|
||||||
|
|
||||||
execlist = {}
|
execlist = {}
|
||||||
subclasses = Script.get_all_subclasses()
|
subclasses = Script.get_all_subclasses()
|
||||||
for x in range(len(subclasses)):
|
for x in range(len(subclasses)):
|
||||||
subclass = subclasses[x]
|
subclass = subclasses[x]
|
||||||
execlist[subclass.__module__.split(".")[-1]] = make_closure(
|
execlist[get_script_function_name(subclass)] = make_closure(
|
||||||
subclass, script_attributes_context=script_attributes_context
|
subclass, script_attributes_context=script_attributes_context
|
||||||
)
|
)
|
||||||
return execlist
|
return execlist
|
||||||
|
|
|
@ -224,10 +224,10 @@ class ProcessModelService(FileSystemService):
|
||||||
new_process_model_list = []
|
new_process_model_list = []
|
||||||
for process_model in process_models:
|
for process_model in process_models:
|
||||||
uri = f"/v1.0/process-instances/{process_model.id.replace('/', ':')}"
|
uri = f"/v1.0/process-instances/{process_model.id.replace('/', ':')}"
|
||||||
result = AuthorizationService.user_has_permission(
|
has_permission = AuthorizationService.user_has_permission(
|
||||||
user=user, permission="create", target_uri=uri
|
user=user, permission="create", target_uri=uri
|
||||||
)
|
)
|
||||||
if result:
|
if has_permission:
|
||||||
new_process_model_list.append(process_model)
|
new_process_model_list.append(process_model)
|
||||||
return new_process_model_list
|
return new_process_model_list
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_96f6665" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.0.0-dev">
|
||||||
|
<bpmn:process id="Process_02u675m" isExecutable="true">
|
||||||
|
<bpmn:startEvent id="StartEvent_1">
|
||||||
|
<bpmn:outgoing>Flow_01cweoc</bpmn:outgoing>
|
||||||
|
</bpmn:startEvent>
|
||||||
|
<bpmn:sequenceFlow id="Flow_01cweoc" sourceRef="StartEvent_1" targetRef="add_permission_script" />
|
||||||
|
<bpmn:endEvent id="Event_11584qn">
|
||||||
|
<bpmn:incoming>Flow_1xle2yo</bpmn:incoming>
|
||||||
|
</bpmn:endEvent>
|
||||||
|
<bpmn:sequenceFlow id="Flow_1xle2yo" sourceRef="add_permission_script" targetRef="Event_11584qn" />
|
||||||
|
<bpmn:scriptTask id="add_permission_script" name="Add Permission">
|
||||||
|
<bpmn:incoming>Flow_01cweoc</bpmn:incoming>
|
||||||
|
<bpmn:outgoing>Flow_1xle2yo</bpmn:outgoing>
|
||||||
|
<bpmn:script>add_permission('read', '/v1.0/test_permission_uri', "test_group")</bpmn:script>
|
||||||
|
</bpmn:scriptTask>
|
||||||
|
</bpmn:process>
|
||||||
|
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||||
|
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_02u675m">
|
||||||
|
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
|
||||||
|
<dc:Bounds x="179" y="159" width="36" height="36" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="Event_11584qn_di" bpmnElement="Event_11584qn">
|
||||||
|
<dc:Bounds x="432" y="159" width="36" height="36" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="Activity_1ymj79t_di" bpmnElement="add_permission_script">
|
||||||
|
<dc:Bounds x="270" y="137" width="100" height="80" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNEdge id="Flow_01cweoc_di" bpmnElement="Flow_01cweoc">
|
||||||
|
<di:waypoint x="215" y="177" />
|
||||||
|
<di:waypoint x="270" y="177" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNEdge id="Flow_1xle2yo_di" bpmnElement="Flow_1xle2yo">
|
||||||
|
<di:waypoint x="370" y="177" />
|
||||||
|
<di:waypoint x="432" y="177" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
</bpmndi:BPMNPlane>
|
||||||
|
</bpmndi:BPMNDiagram>
|
||||||
|
</bpmn:definitions>
|
|
@ -1,6 +1,11 @@
|
||||||
"""Test_get_localtime."""
|
"""Test_get_localtime."""
|
||||||
from flask.app import Flask
|
from flask.app import Flask
|
||||||
|
from flask_bpmn.api.api_error import ApiError
|
||||||
|
import pytest
|
||||||
|
from spiffworkflow_backend.scripts.script import ScriptUnauthorizedForUserError
|
||||||
|
from tests.spiffworkflow_backend.helpers.test_data import load_test_spec
|
||||||
from flask.testing import FlaskClient
|
from flask.testing import FlaskClient
|
||||||
|
from spiffworkflow_backend.services.process_instance_processor import ProcessInstanceProcessor
|
||||||
from tests.spiffworkflow_backend.helpers.base_test import BaseTest
|
from tests.spiffworkflow_backend.helpers.base_test import BaseTest
|
||||||
|
|
||||||
from spiffworkflow_backend.models.group import GroupModel
|
from spiffworkflow_backend.models.group import GroupModel
|
||||||
|
@ -58,3 +63,36 @@ class TestAddPermission(BaseTest):
|
||||||
assert group is not None
|
assert group is not None
|
||||||
assert permission_target is not None
|
assert permission_target is not None
|
||||||
assert len(permission_assignments) == 1
|
assert len(permission_assignments) == 1
|
||||||
|
|
||||||
|
def test_add_permission_script_through_bpmn(
|
||||||
|
self,
|
||||||
|
app: Flask,
|
||||||
|
client: FlaskClient,
|
||||||
|
with_db_and_bpmn_file_cleanup: None,
|
||||||
|
) -> None:
|
||||||
|
basic_user = self.find_or_create_user("basic_user")
|
||||||
|
privileged_user = self.find_or_create_user("privileged_user")
|
||||||
|
self.add_permissions_to_user(
|
||||||
|
privileged_user,
|
||||||
|
target_uri="/v1.0/can-run-privileged-script/*",
|
||||||
|
permission_names=["create"],
|
||||||
|
)
|
||||||
|
process_model = load_test_spec(
|
||||||
|
process_model_id="add_permission",
|
||||||
|
process_model_source_directory="script_add_permission",
|
||||||
|
)
|
||||||
|
process_instance = self.create_process_instance_from_process_model(
|
||||||
|
process_model=process_model, user=basic_user
|
||||||
|
)
|
||||||
|
processor = ProcessInstanceProcessor(process_instance)
|
||||||
|
|
||||||
|
with pytest.raises(ApiError) as exception:
|
||||||
|
processor.do_engine_steps(save=True)
|
||||||
|
assert "ScriptUnauthorizedForUserError" in str(exception)
|
||||||
|
|
||||||
|
process_instance = self.create_process_instance_from_process_model(
|
||||||
|
process_model=process_model, user=privileged_user
|
||||||
|
)
|
||||||
|
processor = ProcessInstanceProcessor(process_instance)
|
||||||
|
processor.do_engine_steps(save=True)
|
||||||
|
assert process_instance.status == "complete"
|
||||||
|
|
|
@ -5,7 +5,6 @@ import {
|
||||||
useParams,
|
useParams,
|
||||||
useSearchParams,
|
useSearchParams,
|
||||||
} from 'react-router-dom';
|
} from 'react-router-dom';
|
||||||
// @ts-ignore
|
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Modal,
|
Modal,
|
||||||
|
@ -15,6 +14,7 @@ import {
|
||||||
Tab,
|
Tab,
|
||||||
TabPanels,
|
TabPanels,
|
||||||
TabPanel,
|
TabPanel,
|
||||||
|
// @ts-ignore
|
||||||
} from '@carbon/react';
|
} from '@carbon/react';
|
||||||
import Row from 'react-bootstrap/Row';
|
import Row from 'react-bootstrap/Row';
|
||||||
import Col from 'react-bootstrap/Col';
|
import Col from 'react-bootstrap/Col';
|
||||||
|
|
Loading…
Reference in New Issue