some fixes and updates to help with running an acceptance test model (#323)

Co-authored-by: jasquat <jasquat@users.noreply.github.com>
This commit is contained in:
jasquat 2023-06-09 14:19:02 -04:00 committed by GitHub
parent ee18052be9
commit 121b3c7cc9
5 changed files with 70 additions and 14 deletions

View File

@ -10,6 +10,7 @@ from werkzeug.utils import ImportStringError
from spiffworkflow_backend.services.logging_service import setup_logger
HTTP_REQUEST_TIMEOUT_SECONDS = 15
CONNECTOR_PROXY_COMMAND_TIMEOUT = 30
class ConfigurationError(Exception):

View File

@ -350,7 +350,9 @@ class CustomBpmnScriptEngine(PythonScriptEngine): # type: ignore
methods = self.__get_augment_methods(task)
if external_methods:
methods.update(external_methods)
super().execute(task, script, methods)
# do not run script if it is blank
if script:
super().execute(task, script, methods)
return True
except WorkflowException as e:
raise e

View File

@ -1,10 +1,12 @@
import json
import re
from typing import Any
import requests
import sentry_sdk
from flask import current_app
from flask import g
from spiffworkflow_backend.config import CONNECTOR_PROXY_COMMAND_TIMEOUT
from spiffworkflow_backend.config import HTTP_REQUEST_TIMEOUT_SECONDS
from spiffworkflow_backend.services.file_system_service import FileSystemService
from spiffworkflow_backend.services.secret_service import SecretService
@ -21,8 +23,8 @@ def connector_proxy_url() -> Any:
class ServiceTaskDelegate:
@staticmethod
def check_prefixes(value: Any) -> Any:
@classmethod
def handle_template_substitutions(cls, value: Any) -> Any:
if isinstance(value, str):
secret_prefix = "secret:" # noqa: S105
if value.startswith(secret_prefix):
@ -38,6 +40,24 @@ class ServiceTaskDelegate:
with open(full_path) as f:
return f.read()
if "SPIFF_SECRET:" in value:
spiff_secret_match = re.match(r".*SPIFF_SECRET:(?P<variable_name>\w+).*", value)
if spiff_secret_match is not None:
spiff_variable_name = spiff_secret_match.group("variable_name")
secret = SecretService.get_secret(spiff_variable_name)
with sentry_sdk.start_span(op="task", description="decrypt_secret"):
decrypted_value = SecretService._decrypt(secret.value)
return re.sub(r"\bSPIFF_SECRET:\w+", decrypted_value, value)
return value
@classmethod
def value_with_secrets_replaced(cls, value: Any) -> Any:
if isinstance(value, str):
return cls.handle_template_substitutions(value)
elif isinstance(value, dict):
for key, v in value.items():
value[key] = cls.value_with_secrets_replaced(v)
return value
@staticmethod
@ -65,17 +85,17 @@ class ServiceTaskDelegate:
)
return msg
@staticmethod
def call_connector(name: str, bpmn_params: Any, task_data: Any) -> str:
@classmethod
def call_connector(cls, name: str, bpmn_params: Any, task_data: Any) -> str:
"""Calls a connector via the configured proxy."""
call_url = f"{connector_proxy_url()}/v1/do/{name}"
current_app.logger.info(f"Calling connector proxy using connector: {name}")
with sentry_sdk.start_span(op="connector_by_name", description=name):
with sentry_sdk.start_span(op="call-connector", description=call_url):
params = {k: ServiceTaskDelegate.check_prefixes(v["value"]) for k, v in bpmn_params.items()}
params = {k: cls.value_with_secrets_replaced(v["value"]) for k, v in bpmn_params.items()}
params["spiff__task_data"] = task_data
proxied_response = requests.post(call_url, json=params, timeout=HTTP_REQUEST_TIMEOUT_SECONDS)
proxied_response = requests.post(call_url, json=params, timeout=CONNECTOR_PROXY_COMMAND_TIMEOUT)
response_text = proxied_response.text
json_parse_error = None
@ -98,7 +118,10 @@ class ServiceTaskDelegate:
message = ServiceTaskDelegate.get_message_for_status(proxied_response.status_code)
error = f"Received an unexpected response from service {name} : {message}"
if "error" in parsed_response:
error += parsed_response["error"]
error_response = parsed_response["error"]
if isinstance(error_response, list | dict):
error_response = json.dumps(parsed_response["error"])
error += error_response
if json_parse_error:
error += "A critical component (The connector proxy) is not responding correctly."
raise ConnectorProxyError(error)

View File

@ -11,19 +11,33 @@ from tests.spiffworkflow_backend.helpers.base_test import BaseTest
class TestServiceTaskDelegate(BaseTest):
def test_check_prefixes_without_secret(self, app: Flask, with_db_and_bpmn_file_cleanup: None) -> None:
result = ServiceTaskDelegate.check_prefixes("hey")
result = ServiceTaskDelegate.value_with_secrets_replaced("hey")
assert result == "hey"
def test_check_prefixes_with_int(self, app: Flask, with_db_and_bpmn_file_cleanup: None) -> None:
result = ServiceTaskDelegate.check_prefixes(1)
result = ServiceTaskDelegate.value_with_secrets_replaced(1)
assert result == 1
def test_check_prefixes_with_secret(self, app: Flask, with_db_and_bpmn_file_cleanup: None) -> None:
user = self.find_or_create_user("test_user")
SecretService().add_secret("hot_secret", "my_secret_value", user.id)
result = ServiceTaskDelegate.check_prefixes("secret:hot_secret")
result = ServiceTaskDelegate.value_with_secrets_replaced("secret:hot_secret")
assert result == "my_secret_value"
def test_check_prefixes_with_spiff_secret(self, app: Flask, with_db_and_bpmn_file_cleanup: None) -> None:
user = self.find_or_create_user("test_user")
SecretService().add_secret("hot_secret", "my_secret_value", user.id)
result = ServiceTaskDelegate.value_with_secrets_replaced("TOKEN SPIFF_SECRET:hot_secret-haha")
assert result == "TOKEN my_secret_value-haha"
def test_check_prefixes_with_spiff_secret_in_dict(self, app: Flask, with_db_and_bpmn_file_cleanup: None) -> None:
user = self.find_or_create_user("test_user")
SecretService().add_secret("hot_secret", "my_secret_value", user.id)
result = ServiceTaskDelegate.value_with_secrets_replaced(
{"Authorization": "TOKEN SPIFF_SECRET:hot_secret-haha"}
)
assert result == {"Authorization": "TOKEN my_secret_value-haha"}
def test_invalid_call_returns_good_error_message(self, app: Flask, with_db_and_bpmn_file_cleanup: None) -> None:
with patch("requests.post") as mock_post:
mock_post.return_value.status_code = 404

View File

@ -4,6 +4,7 @@ import {
// @ts-ignore
} from '@carbon/react';
import { Can } from '@casl/react';
import { useState } from 'react';
import {
PermissionsToCheck,
ProcessModel,
@ -78,6 +79,7 @@ export default function ProcessInstanceRun({
}: OwnProps) {
const navigate = useNavigate();
const { addError, removeError } = useAPIError();
const [disableStartButton, setDisableStartButton] = useState<boolean>(false);
const modifiedProcessModelId = modifyProcessIdentifierForPathParam(
processModel.id
);
@ -109,20 +111,29 @@ export default function ProcessInstanceRun({
HttpService.makeCallToBackend({
path: `/process-instances/${modifiedProcessModelId}/${processInstance.id}/run`,
successCallback: onProcessInstanceRun,
failureCallback: addError,
failureCallback: (result: any) => {
addError(result);
setDisableStartButton(false);
},
httpMethod: 'POST',
});
};
const processInstanceCreateAndRun = () => {
removeError();
setDisableStartButton(true);
HttpService.makeCallToBackend({
path: processInstanceCreatePath,
successCallback: processModelRun,
failureCallback: addError,
failureCallback: (result: any) => {
addError(result);
setDisableStartButton(false);
},
httpMethod: 'POST',
});
};
// if checkPermissions is false then assume the page using this component has already checked the permissions
if (checkPermissions) {
return (
<Can I="POST" a={processInstanceCreatePath} ability={ability}>
@ -130,6 +141,7 @@ export default function ProcessInstanceRun({
data-qa="start-process-instance"
onClick={processInstanceCreateAndRun}
className={className}
disabled={disableStartButton}
>
Start
</Button>
@ -137,7 +149,11 @@ export default function ProcessInstanceRun({
);
}
return (
<Button onClick={processInstanceCreateAndRun} className={className}>
<Button
onClick={processInstanceCreateAndRun}
className={className}
disabled={disableStartButton}
>
Start
</Button>
);