disallow saving a process model file if it has changed w/ burnettk

This commit is contained in:
jasquat 2023-05-02 16:31:29 -04:00
parent 1ec0deaf3c
commit 558c616744
No known key found for this signature in database
7 changed files with 39 additions and 44 deletions

View File

@ -1245,6 +1245,13 @@ paths:
put:
operationId: spiffworkflow_backend.routes.process_models_controller.process_model_file_update
summary: save the contents to the given file
parameters:
- name: file_contents_hash
in: query
required: true
description: The hash of the file contents that originally came with the file.
schema:
type: string
tags:
- Process Model Files
requestBody:

View File

@ -1,13 +1,10 @@
"""File."""
from __future__ import annotations
from dataclasses import dataclass
from typing import Any
from dataclasses import field
from datetime import datetime
from typing import Optional
import marshmallow
from marshmallow import INCLUDE
from marshmallow import Schema
from spiffworkflow_backend.helpers.spiff_enum import SpiffEnum
from spiffworkflow_backend.models.spec_reference import SpecReference
@ -77,7 +74,7 @@ class File:
references: Optional[list[SpecReference]] = None
file_contents: Optional[bytes] = None
process_model_id: Optional[str] = None
process_group_id: Optional[str] = None
file_contents_hash: Optional[str] = None
def __post_init__(self) -> None:
"""__post_init__."""
@ -102,28 +99,9 @@ class File:
)
return instance
class FileSchema(Schema):
"""FileSchema."""
class Meta:
"""Meta."""
model = File
fields = [
"id",
"name",
"content_type",
"last_modified",
"type",
"size",
"data_store",
"user_uid",
"url",
"file_contents",
"references",
"process_group_id",
"process_model_id",
]
unknown = INCLUDE
references = marshmallow.fields.List(marshmallow.fields.Nested("SpecReferenceSchema"))
@property
def serialized(self) -> dict[str, Any]:
dictionary = self.__dict__
if isinstance(self.file_contents, bytes):
dictionary['file_contents'] = self.file_contents.decode('utf-8')
return dictionary

View File

@ -87,7 +87,7 @@ class ProcessModelInfoSchema(Schema):
display_order = marshmallow.fields.Integer(allow_none=True)
primary_file_name = marshmallow.fields.String(allow_none=True)
primary_process_id = marshmallow.fields.String(allow_none=True)
files = marshmallow.fields.List(marshmallow.fields.Nested("FileSchema"))
files = marshmallow.fields.List(marshmallow.fields.Nested("File"))
fault_or_suspend_on_exception = marshmallow.fields.String()
exception_notification_addresses = marshmallow.fields.List(marshmallow.fields.String)
metadata_extraction_paths = marshmallow.fields.List(

View File

@ -1,4 +1,5 @@
"""APIs for dealing with process groups, process models, and process instances."""
from hashlib import sha256
import json
import os
import re
@ -18,7 +19,6 @@ from werkzeug.datastructures import FileStorage
from spiffworkflow_backend.exceptions.api_error import ApiError
from spiffworkflow_backend.interfaces import IdToProcessGroupMapping
from spiffworkflow_backend.models.file import FileSchema
from spiffworkflow_backend.models.process_group import ProcessGroup
from spiffworkflow_backend.models.process_instance_report import (
ProcessInstanceReportModel,
@ -245,10 +245,9 @@ def process_model_list(
return make_response(jsonify(response_json), 200)
def process_model_file_update(modified_process_model_identifier: str, file_name: str) -> flask.wrappers.Response:
"""Process_model_file_update."""
def process_model_file_update(modified_process_model_identifier: str, file_name: str, file_contents_hash: str) -> flask.wrappers.Response:
message = f"User: {g.user.username} clicked save for"
return _create_or_update_process_model_file(modified_process_model_identifier, message, 200)
return _create_or_update_process_model_file(modified_process_model_identifier, message, 200, file_contents_hash=file_contents_hash)
def process_model_file_delete(modified_process_model_identifier: str, file_name: str) -> flask.wrappers.Response:
@ -293,7 +292,6 @@ def process_model_file_create(
def process_model_file_show(modified_process_model_identifier: str, file_name: str) -> Any:
"""Process_model_file_show."""
process_model_identifier = modified_process_model_identifier.replace(":", "/")
process_model = _get_process_model(process_model_identifier)
files = SpecFileService.get_files(process_model, file_name)
@ -309,8 +307,10 @@ def process_model_file_show(modified_process_model_identifier: str, file_name: s
file = files[0]
file_contents = SpecFileService.get_data(process_model, file.name)
file.file_contents = file_contents
file_contents_hash = sha256(file_contents).hexdigest()
file.file_contents_hash = file_contents_hash
file.process_model_id = process_model.id
return FileSchema().dump(file)
return make_response(jsonify(file), 200)
# {
@ -477,6 +477,7 @@ def _create_or_update_process_model_file(
modified_process_model_identifier: str,
message_for_git_commit: str,
http_status_to_return: int,
file_contents_hash: Optional[str],
) -> flask.wrappers.Response:
"""_create_or_update_process_model_file."""
process_model_identifier = modified_process_model_identifier.replace(":", "/")
@ -498,6 +499,16 @@ def _create_or_update_process_model_file(
status_code=400,
)
current_file_contents_bytes = SpecFileService.get_data(process_model, request_file.filename)
if current_file_contents_bytes and file_contents_hash:
current_file_contents_hash = sha256(current_file_contents_bytes).hexdigest()
if current_file_contents_hash != file_contents_hash:
raise ApiError(
error_code="process_model_file_has_changed",
message=f"Process model file: {request_file.filename} was already changed by someone else. Please reload before making changes.",
status_code=409,
)
file = None
try:
file = SpecFileService.update_file(process_model, request_file.filename, request_file_contents)
@ -514,8 +525,4 @@ def _create_or_update_process_model_file(
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",
)
return make_response(jsonify(file), http_status_to_return)

View File

@ -175,7 +175,6 @@ class SpecFileService(FileSystemService):
@classmethod
def update_file(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)

View File

@ -114,6 +114,7 @@ export interface ProcessFile {
size: number;
type: string;
file_contents?: string;
file_contents_hash?: string;
}
export interface ProcessInstanceMetadata {

View File

@ -206,6 +206,9 @@ export default function ProcessModelEditDiagram() {
httpMethod = 'POST';
} else {
url += `/${fileNameWithExtension}`;
if (processModelFile && processModelFile.file_contents_hash) {
url += `?file_contents_hash=${processModelFile.file_contents_hash}`;
}
}
if (!fileNameWithExtension) {
handleShowFileNameEditor();