some updates to validate xml when uploading and saving w/ burnettk

This commit is contained in:
jasquat 2023-01-10 12:16:24 -05:00
parent 338cf8fa5a
commit a24fca0e30
12 changed files with 144 additions and 79 deletions

View File

@ -15,6 +15,7 @@ from flask import jsonify
from flask import make_response
from flask.wrappers import Response
from flask_bpmn.api.api_error import ApiError
from werkzeug.datastructures import FileStorage
from spiffworkflow_backend.interfaces import IdToProcessGroupMapping
from spiffworkflow_backend.models.file import FileSchema
@ -38,6 +39,9 @@ from spiffworkflow_backend.services.process_instance_report_service import (
ProcessInstanceReportService,
)
from spiffworkflow_backend.services.process_model_service import ProcessModelService
from spiffworkflow_backend.services.spec_file_service import (
ProcessModelFileInvalidError,
)
from spiffworkflow_backend.services.spec_file_service import SpecFileService
@ -222,26 +226,11 @@ def process_model_file_update(
modified_process_model_identifier: str, file_name: str
) -> flask.wrappers.Response:
"""Process_model_file_update."""
process_model_identifier = modified_process_model_identifier.replace(":", "/")
process_model = _get_process_model(process_model_identifier)
request_file = _get_file_from_request()
request_file_contents = request_file.stream.read()
if not request_file_contents:
raise ApiError(
error_code="file_contents_empty",
message="Given request file does not have any content",
status_code=400,
)
SpecFileService.update_file(process_model, file_name, request_file_contents)
_commit_and_push_to_git(
f"User: {g.user.username} clicked save for"
f" {process_model_identifier}/{file_name}"
message = f"User: {g.user.username} clicked save for"
return _create_or_update_process_model_file(
modified_process_model_identifier, message, 200
)
return Response(json.dumps({"ok": True}), status=200, mimetype="application/json")
def process_model_file_delete(
modified_process_model_identifier: str, file_name: str
@ -271,28 +260,9 @@ def process_model_file_create(
modified_process_model_identifier: str,
) -> flask.wrappers.Response:
"""Process_model_file_create."""
process_model_identifier = modified_process_model_identifier.replace(":", "/")
process_model = _get_process_model(process_model_identifier)
request_file = _get_file_from_request()
if not request_file.filename:
raise ApiError(
error_code="could_not_get_filename",
message="Could not get filename from request",
status_code=400,
)
file = SpecFileService.add_file(
process_model, request_file.filename, request_file.stream.read()
)
file_contents = SpecFileService.get_data(process_model, file.name)
file.file_contents = file_contents
file.process_model_id = process_model.id
_commit_and_push_to_git(
f"User: {g.user.username} added process model file"
f" {process_model_identifier}/{file.name}"
)
return Response(
json.dumps(FileSchema().dump(file)), status=201, mimetype="application/json"
message = f"User: {g.user.username} added process model file"
return _create_or_update_process_model_file(
modified_process_model_identifier, message, 201
)
@ -462,9 +432,9 @@ def process_model_create_with_natural_language(
)
def _get_file_from_request() -> Any:
def _get_file_from_request() -> FileStorage:
"""Get_file_from_request."""
request_file = connexion.request.files.get("file")
request_file: FileStorage = connexion.request.files.get("file")
if not request_file:
raise ApiError(
error_code="no_file_given",
@ -502,3 +472,58 @@ def _get_process_group_from_modified_identifier(
status_code=400,
)
return process_group
def _create_or_update_process_model_file(
modified_process_model_identifier: str,
message_for_git_commit: str,
http_status_to_return: int,
) -> flask.wrappers.Response:
"""_create_or_update_process_model_file."""
process_model_identifier = modified_process_model_identifier.replace(":", "/")
process_model = _get_process_model(process_model_identifier)
request_file = _get_file_from_request()
# for mypy
request_file_contents = request_file.stream.read()
if not request_file_contents:
raise ApiError(
error_code="file_contents_empty",
message="Given request file does not have any content",
status_code=400,
)
if not request_file.filename:
raise ApiError(
error_code="could_not_get_filename",
message="Could not get filename from request",
status_code=400,
)
file = None
try:
file = SpecFileService.update_file(
process_model, request_file.filename, request_file_contents
)
except ProcessModelFileInvalidError as exception:
raise (
ApiError(
error_code="process_model_file_invalid",
message=(
f"Invalid Process model file cannot be save: {request_file.name}."
f" Received error: {str(exception)}"
),
status_code=400,
)
) from exception
file_contents = SpecFileService.get_data(process_model, file.name)
file.file_contents = file_contents
file.process_model_id = process_model.id
_commit_and_push_to_git(
f"{message_for_git_commit} {process_model_identifier}/{file.name}"
)
return Response(
json.dumps(FileSchema().dump(file)),
status=http_status_to_return,
mimetype="application/json",
)

View File

@ -6,7 +6,8 @@ from typing import List
from typing import Optional
from flask_bpmn.models.db import db
from SpiffWorkflow.bpmn.parser.ValidationException import ValidationException # type: ignore
from lxml import etree # type: ignore
from SpiffWorkflow.bpmn.parser.BpmnParser import BpmnValidator # type: ignore
from spiffworkflow_backend.models.file import File
from spiffworkflow_backend.models.file import FileType
@ -29,6 +30,10 @@ class ProcessModelFileNotFoundError(Exception):
"""ProcessModelFileNotFoundError."""
class ProcessModelFileInvalidError(Exception):
"""ProcessModelFileInvalidError."""
class SpecFileService(FileSystemService):
"""SpecFileService."""
@ -44,7 +49,6 @@ class SpecFileService(FileSystemService):
extension_filter: str = "",
) -> List[File]:
"""Return all files associated with a workflow specification."""
# path = SpecFileService.workflow_path(process_model_info)
path = os.path.join(
FileSystemService.root_path(), process_model_info.id_for_file_path()
)
@ -147,12 +151,27 @@ class SpecFileService(FileSystemService):
# Same as update
return SpecFileService.update_file(process_model_info, file_name, binary_data)
@staticmethod
@classmethod
def validate_bpmn_xml(cls, file_name: str, binary_data: bytes) -> None:
"""Validate_bpmn_xml."""
file_type = FileSystemService.file_type(file_name)
if file_type.value == FileType.bpmn.value:
validator = BpmnValidator()
parser = MyCustomParser(validator=validator)
try:
parser.add_bpmn_xml(etree.fromstring(binary_data), filename=file_name)
except etree.XMLSyntaxError as exception:
raise ProcessModelFileInvalidError(
f"Received error trying to parse bpmn xml: {str(exception)}"
) from exception
@classmethod
def update_file(
process_model_info: ProcessModelInfo, file_name: str, binary_data: bytes
cls, process_model_info: ProcessModelInfo, file_name: str, binary_data: bytes
) -> File:
"""Update_file."""
SpecFileService.assert_valid_file_name(file_name)
cls.validate_bpmn_xml(file_name, binary_data)
full_file_path = SpecFileService.full_file_path(process_model_info, file_name)
SpecFileService.write_file_data_to_system(full_file_path, binary_data)
file = SpecFileService.to_file_object(file_name, full_file_path)
@ -282,7 +301,7 @@ class SpecFileService(FileSystemService):
# if the old relative bpmn file no longer exists, then assume things were moved around
# on the file system. Otherwise, assume it is a duplicate process id and error.
if os.path.isfile(full_bpmn_file_path):
raise ValidationException(
raise ProcessModelFileInvalidError(
f"Process id ({ref.identifier}) has already been used for "
f"{process_id_lookup.relative_path}. It cannot be reused."
)
@ -314,7 +333,7 @@ class SpecFileService(FileSystemService):
identifier=message_model_identifier
).first()
if message_model is None:
raise ValidationException(
raise ProcessModelFileInvalidError(
"Could not find message model with identifier"
f" '{message_model_identifier}'Required by a Start Event in :"
f" {ref.file_name}"
@ -336,7 +355,7 @@ class SpecFileService(FileSystemService):
message_triggerable_process_model.process_model_identifier
!= ref.process_model_id
):
raise ValidationException(
raise ProcessModelFileInvalidError(
"Message model is already used to start process model"
f" {ref.process_model_id}"
)
@ -355,7 +374,7 @@ class SpecFileService(FileSystemService):
identifier=message_model_identifier
).first()
if message_model is None:
raise ValidationException(
raise ProcessModelFileInvalidError(
"Could not find message model with identifier"
f" '{message_model_identifier}'specified by correlation"
f" property: {cpre}"

View File

@ -2,10 +2,10 @@
<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" xmlns:spiffworkflow="http://spiffworkflow.org/bpmn/schema/1.0/core" id="Definitions_96f6665" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.0.0-dev">
<bpmn:correlationProperty id="message_correlation_property" name="Message Correlation Property">
<bpmn:correlationPropertyRetrievalExpression messageRef="message_send">
<bpmn:formalExpression>to</bpmn:formalExpression>
<bpmn:messagePath>to</bpmn:messagePath>
</bpmn:correlationPropertyRetrievalExpression>
<bpmn:correlationPropertyRetrievalExpression messageRef="message_response">
<bpmn:formalExpression>from.name</bpmn:formalExpression>
<bpmn:messagePath>from.name</bpmn:messagePath>
</bpmn:correlationPropertyRetrievalExpression>
</bpmn:correlationProperty>
<bpmn:message id="message_send" name="Message Send">
@ -20,7 +20,7 @@
</bpmn:message>
<bpmn:correlationProperty id="correlation_property_one" name="Correlation Property One">
<bpmn:correlationPropertyRetrievalExpression messageRef="message_send">
<bpmn:formalExpression>new</bpmn:formalExpression>
<bpmn:messagePath>new</bpmn:messagePath>
</bpmn:correlationPropertyRetrievalExpression>
</bpmn:correlationProperty>
<bpmn:process id="test_dot_notation" name="Test Dot Notation" isExecutable="true">

View File

@ -12,18 +12,18 @@
</bpmn:collaboration>
<bpmn:correlationProperty id="message_correlation_property_topica" name="Message Correlation Property TopicA">
<bpmn:correlationPropertyRetrievalExpression messageRef="message_send">
<bpmn:formalExpression>topica</bpmn:formalExpression>
<bpmn:messagePath>topica</bpmn:messagePath>
</bpmn:correlationPropertyRetrievalExpression>
<bpmn:correlationPropertyRetrievalExpression messageRef="message_response">
<bpmn:formalExpression>the_payload.topica</bpmn:formalExpression>
<bpmn:messagePath>the_payload.topica</bpmn:messagePath>
</bpmn:correlationPropertyRetrievalExpression>
</bpmn:correlationProperty>
<bpmn:correlationProperty id="message_correlation_property_topicb" name="Message Correlation Property TopicB">
<bpmn:correlationPropertyRetrievalExpression messageRef="message_send">
<bpmn:formalExpression>topicb</bpmn:formalExpression>
<bpmn:messagePath>topicb</bpmn:messagePath>
</bpmn:correlationPropertyRetrievalExpression>
<bpmn:correlationPropertyRetrievalExpression messageRef="message_response">
<bpmn:formalExpression>the_payload.topicb</bpmn:formalExpression>
<bpmn:messagePath>the_payload.topicb</bpmn:messagePath>
</bpmn:correlationPropertyRetrievalExpression>
</bpmn:correlationProperty>
<bpmn:message id="message_send" name="Message Send">

View File

@ -12,18 +12,18 @@
</bpmn:collaboration>
<bpmn:correlationProperty id="message_correlation_property_topica" name="Message Correlation Property TopicA">
<bpmn:correlationPropertyRetrievalExpression messageRef="message_send">
<bpmn:formalExpression>topica</bpmn:formalExpression>
<bpmn:messagePath>topica</bpmn:messagePath>
</bpmn:correlationPropertyRetrievalExpression>
<bpmn:correlationPropertyRetrievalExpression messageRef="message_response">
<bpmn:formalExpression>the_payload.topica</bpmn:formalExpression>
<bpmn:messagePath>the_payload.topica</bpmn:messagePath>
</bpmn:correlationPropertyRetrievalExpression>
</bpmn:correlationProperty>
<bpmn:correlationProperty id="message_correlation_property_topicb" name="Message Correlation Property TopicB">
<bpmn:correlationPropertyRetrievalExpression messageRef="message_send">
<bpmn:formalExpression>topicb</bpmn:formalExpression>
<bpmn:messagePath>topicb</bpmn:messagePath>
</bpmn:correlationPropertyRetrievalExpression>
<bpmn:correlationPropertyRetrievalExpression messageRef="message_response">
<bpmn:formalExpression>the_payload.topicb</bpmn:formalExpression>
<bpmn:messagePath>the_payload.topicb</bpmn:messagePath>
</bpmn:correlationPropertyRetrievalExpression>
</bpmn:correlationProperty>
<bpmn:process id="message_send_process" name="Message Send Process" isExecutable="true">

View File

@ -12,18 +12,18 @@
</bpmn:collaboration>
<bpmn:correlationProperty id="mcp_topica_one" name="MCP TopicA One">
<bpmn:correlationPropertyRetrievalExpression messageRef="message_send_one">
<bpmn:formalExpression>topica_one</bpmn:formalExpression>
<bpmn:messagePath>topica_one</bpmn:messagePath>
</bpmn:correlationPropertyRetrievalExpression>
<bpmn:correlationPropertyRetrievalExpression messageRef="message_response_one">
<bpmn:formalExpression>topica_one</bpmn:formalExpression>
<bpmn:messagePath>topica_one</bpmn:messagePath>
</bpmn:correlationPropertyRetrievalExpression>
</bpmn:correlationProperty>
<bpmn:correlationProperty id="mcp_topicb_one" name="MCP TopicB_one">
<bpmn:correlationPropertyRetrievalExpression messageRef="message_send_one">
<bpmn:formalExpression>topicb_one</bpmn:formalExpression>
<bpmn:messagePath>topicb_one</bpmn:messagePath>
</bpmn:correlationPropertyRetrievalExpression>
<bpmn:correlationPropertyRetrievalExpression messageRef="message_response_one">
<bpmn:formalExpression>topicb_one</bpmn:formalExpression>
<bpmn:messagePath>topicb_one</bpmn:messagePath>
</bpmn:correlationPropertyRetrievalExpression>
</bpmn:correlationProperty>
<bpmn:message id="message_send_one" name="Message Send One">

View File

@ -12,18 +12,18 @@
</bpmn:collaboration>
<bpmn:correlationProperty id="mcp_topica_two" name="MCP TopicA Two">
<bpmn:correlationPropertyRetrievalExpression messageRef="message_send_two">
<bpmn:formalExpression>topica_two</bpmn:formalExpression>
<bpmn:messagePath>topica_two</bpmn:messagePath>
</bpmn:correlationPropertyRetrievalExpression>
<bpmn:correlationPropertyRetrievalExpression messageRef="message_response_two">
<bpmn:formalExpression>topica_two</bpmn:formalExpression>
<bpmn:messagePath>topica_two</bpmn:messagePath>
</bpmn:correlationPropertyRetrievalExpression>
</bpmn:correlationProperty>
<bpmn:correlationProperty id="mcp_topicb_two" name="MCP TopicB_two">
<bpmn:correlationPropertyRetrievalExpression messageRef="message_send_two">
<bpmn:formalExpression>topicb_two</bpmn:formalExpression>
<bpmn:messagePath>topicb_two</bpmn:messagePath>
</bpmn:correlationPropertyRetrievalExpression>
<bpmn:correlationPropertyRetrievalExpression messageRef="message_response_two">
<bpmn:formalExpression>topicb_two</bpmn:formalExpression>
<bpmn:messagePath>topicb_two</bpmn:messagePath>
</bpmn:correlationPropertyRetrievalExpression>
</bpmn:correlationProperty>
<bpmn:message id="message_send_two" name="Message Send Two">

View File

@ -19,18 +19,18 @@
</bpmn:collaboration>
<bpmn:correlationProperty id="mcp_topica_one" name="MCP TopicA One">
<bpmn:correlationPropertyRetrievalExpression messageRef="message_send_one">
<bpmn:formalExpression>topica_one</bpmn:formalExpression>
<bpmn:messagePath>topica_one</bpmn:messagePath>
</bpmn:correlationPropertyRetrievalExpression>
<bpmn:correlationPropertyRetrievalExpression messageRef="message_response_one">
<bpmn:formalExpression>payload_var_one.topica_one</bpmn:formalExpression>
<bpmn:messagePath>payload_var_one.topica_one</bpmn:messagePath>
</bpmn:correlationPropertyRetrievalExpression>
</bpmn:correlationProperty>
<bpmn:correlationProperty id="mcp_topicb_one" name="MCP TopicB_one">
<bpmn:correlationPropertyRetrievalExpression messageRef="message_send_one">
<bpmn:formalExpression>topicb_one</bpmn:formalExpression>
<bpmn:messagePath>topicb_one</bpmn:messagePath>
</bpmn:correlationPropertyRetrievalExpression>
<bpmn:correlationPropertyRetrievalExpression messageRef="message_response_one">
<bpmn:formalExpression>payload_var_one.topicb</bpmn:formalExpression>
<bpmn:messagePath>payload_var_one.topicb</bpmn:messagePath>
</bpmn:correlationPropertyRetrievalExpression>
</bpmn:correlationProperty>
<bpmn:process id="message_send_process" name="Message Send Process" isExecutable="true">
@ -117,18 +117,18 @@ del time</bpmn:script>
</bpmn:message>
<bpmn:correlationProperty id="mcp_topica_two" name="MCP Topica Two">
<bpmn:correlationPropertyRetrievalExpression messageRef="message_send_two">
<bpmn:formalExpression>topica_two</bpmn:formalExpression>
<bpmn:messagePath>topica_two</bpmn:messagePath>
</bpmn:correlationPropertyRetrievalExpression>
<bpmn:correlationPropertyRetrievalExpression messageRef="message_response_two">
<bpmn:formalExpression>topica_two</bpmn:formalExpression>
<bpmn:messagePath>topica_two</bpmn:messagePath>
</bpmn:correlationPropertyRetrievalExpression>
</bpmn:correlationProperty>
<bpmn:correlationProperty id="mcp_topicb_two" name="MCP Topicb Two">
<bpmn:correlationPropertyRetrievalExpression messageRef="message_send_two">
<bpmn:formalExpression>topicb_two</bpmn:formalExpression>
<bpmn:messagePath>topicb_two</bpmn:messagePath>
</bpmn:correlationPropertyRetrievalExpression>
<bpmn:correlationPropertyRetrievalExpression messageRef="message_response_two">
<bpmn:formalExpression>topicb_two</bpmn:formalExpression>
<bpmn:messagePath>topicb_two</bpmn:messagePath>
</bpmn:correlationPropertyRetrievalExpression>
</bpmn:correlationProperty>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">

View File

@ -860,7 +860,7 @@ class TestProcessApi(BaseTest):
assert response.status_code == 200
assert response.json is not None
assert response.json["ok"]
assert response.json["file_contents"] is not None
response = client.get(
f"/v1.0/process-models/{modified_process_model_id}/files/random_fact.svg",

View File

@ -12,6 +12,9 @@ from tests.spiffworkflow_backend.helpers.test_data import load_test_spec
from spiffworkflow_backend.models.spec_reference import SpecReferenceCache
from spiffworkflow_backend.models.user import UserModel
from spiffworkflow_backend.services.process_model_service import ProcessModelService
from spiffworkflow_backend.services.spec_file_service import (
ProcessModelFileInvalidError,
)
from spiffworkflow_backend.services.spec_file_service import SpecFileService
@ -206,3 +209,20 @@ class TestSpecFileService(BaseTest):
assert dmn1[0].display_name == "Decision 1"
assert dmn1[0].identifier == "Decision_0vrtcmk"
assert dmn1[0].type == "decision"
def test_validate_bpmn_xml_with_invalid_xml(
self,
app: Flask,
client: FlaskClient,
with_db_and_bpmn_file_cleanup: None,
) -> None:
"""Test_validate_bpmn_xml_with_invalid_xml."""
process_model = load_test_spec(
process_model_id="group/invalid_xml",
bpmn_file_name="script_error_with_task_data.bpmn",
process_model_source_directory="error",
)
with pytest.raises(ProcessModelFileInvalidError):
SpecFileService.update_file(
process_model, "bad_xml.bpmn", b"THIS_IS_NOT_VALID_XML"
)

View File

@ -966,7 +966,6 @@ export default function ProcessModelEditDiagram() {
{scriptEditorAndTests()}
{markdownEditor()}
{processModelSelector()}
{`Processes length: ${processes.length}`}
<div id="diagram-container" />
</>
);

View File

@ -374,6 +374,7 @@ export default function ProcessModelShow() {
const doFileUpload = (event: any) => {
event.preventDefault();
setErrorObject(null);
const url = `/process-models/${modifiedProcessModelId}/files`;
const formData = new FormData();
formData.append('file', filesToUpload[0]);
@ -383,6 +384,7 @@ export default function ProcessModelShow() {
successCallback: onUploadedCallback,
httpMethod: 'POST',
postBody: formData,
failureCallback: setErrorObject,
});
setFilesToUpload(null);
};