Fence managed file object effects

This commit is contained in:
2026-08-03 04:21:52 +02:00
parent b993d8e31a
commit 6baf2a421b
10 changed files with 1162 additions and 25 deletions
+47 -5
View File
@@ -20,8 +20,9 @@ from govoplan_files.backend.storage.backends import (
get_storage_backend,
)
from govoplan_files.backend.storage.common import FileConflictResolution, FileStorageError, UploadedStoredFile, utcnow
from govoplan_files.backend.storage.paths import filename_from_path, join_folder_filename, normalize_folder, normalize_logical_path, safe_storage_component
from govoplan_files.backend.storage.paths import filename_from_path, join_folder_filename, normalize_folder, normalize_logical_path
from govoplan_files.backend.storage.provenance import source_provenance_from_metadata
from govoplan_files.backend.storage.recovery import begin_blob_write_recovery
from govoplan_files.backend.storage.integrity import (
QUARANTINED_BLOB_STATUSES,
read_verified_blob_bytes,
@@ -61,8 +62,9 @@ def _storage_backend_name() -> str:
return settings.file_storage_backend.lower().strip()
def _storage_key(*, tenant_id: str, checksum: str, filename: str) -> str:
return f"tenants/{tenant_id}/files/{checksum[:2]}/{uuid4().hex}-{safe_storage_component(filename)}"
def _storage_key(*, tenant_id: str, checksum: str) -> str:
# Object locators remain opaque so recovery evidence never persists names.
return f"tenants/{tenant_id}/files/{checksum[:2]}/{uuid4().hex}.blob"
def _get_or_create_blob(
@@ -97,7 +99,30 @@ def _get_or_create_blob(
except StorageBackendError as exc:
raise FileStorageError(str(exc)) from exc
if repair_required:
repair_token = hashlib.sha256(
repr(
(
blob.integrity_status,
blob.integrity_checked_at,
blob.quarantined_at,
blob.storage_checksum_sha256,
)
).encode("utf-8")
).hexdigest()
recovery = begin_blob_write_recovery(
session,
backend=backend,
tenant_id=tenant_id,
blob_id=blob.id,
storage_key=blob.storage_key,
semantic_checksum_sha256=checksum,
semantic_size_bytes=size,
protection_discriminator=protection_discriminator,
created_new=False,
repair_token=repair_token,
)
stored_data = data
expected_envelope_id = blob.encryption_envelope_id
if vault_id:
from govoplan_files.backend.storage.content_protection import protect_blob_content
@@ -115,8 +140,13 @@ def _get_or_create_blob(
raise FileStorageError("The existing encrypted blob has another protection envelope.")
stored_data = protected.ciphertext
blob.encryption_envelope_id = protected.envelope.envelope_id
expected_envelope_id = protected.envelope.envelope_id
blob.storage_checksum_sha256 = hashlib.sha256(stored_data).hexdigest()
blob.storage_size_bytes = len(stored_data)
recovery.prepare_stored_bytes(
stored_data,
envelope_id=expected_envelope_id,
)
try:
backend.put_bytes(blob.storage_key, stored_data, content_type="application/octet-stream" if vault_id else content_type)
except StorageBackendError as exc:
@@ -130,7 +160,19 @@ def _get_or_create_blob(
return blob
blob_id = str(uuid4())
storage_key = _storage_key(tenant_id=tenant_id, checksum=checksum, filename=filename)
storage_key = _storage_key(tenant_id=tenant_id, checksum=checksum)
backend = get_storage_backend()
recovery = begin_blob_write_recovery(
session,
backend=backend,
tenant_id=tenant_id,
blob_id=blob_id,
storage_key=storage_key,
semantic_checksum_sha256=checksum,
semantic_size_bytes=size,
protection_discriminator=protection_discriminator,
created_new=True,
)
stored_data = data
envelope_id = None
if vault_id:
@@ -148,7 +190,7 @@ def _get_or_create_blob(
)
stored_data = protected.ciphertext
envelope_id = protected.envelope.envelope_id
backend = get_storage_backend()
recovery.prepare_stored_bytes(stored_data, envelope_id=envelope_id)
try:
backend.put_bytes(storage_key, stored_data, content_type="application/octet-stream" if vault_id else content_type)
except StorageBackendError as exc: