Harden auth, redaction, upload size checks, and compose token requirements
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
"""Document CRUD, lifecycle, metadata, file access, and content export endpoints."""
|
||||
"""Authenticated document CRUD, lifecycle, metadata, file access, and content export endpoints."""
|
||||
|
||||
import io
|
||||
import re
|
||||
@@ -14,7 +14,7 @@ from fastapi.responses import FileResponse, Response, StreamingResponse
|
||||
from sqlalchemy import or_, func, select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.services.app_settings import read_predefined_paths_settings, read_predefined_tags_settings
|
||||
from app.core.config import get_settings
|
||||
from app.db.base import get_session
|
||||
from app.models.document import Document, DocumentStatus
|
||||
from app.schemas.documents import (
|
||||
@@ -26,6 +26,7 @@ from app.schemas.documents import (
|
||||
UploadConflict,
|
||||
UploadResponse,
|
||||
)
|
||||
from app.services.app_settings import read_predefined_paths_settings, read_predefined_tags_settings
|
||||
from app.services.extractor import sniff_mime
|
||||
from app.services.handwriting_style import delete_many_handwriting_style_documents
|
||||
from app.services.processing_logs import log_processing_event, set_processing_log_autocommit
|
||||
@@ -35,6 +36,7 @@ from app.worker.queue import get_processing_queue
|
||||
|
||||
|
||||
router = APIRouter()
|
||||
settings = get_settings()
|
||||
|
||||
|
||||
def _parse_csv(value: str | None) -> list[str]:
|
||||
@@ -227,6 +229,33 @@ def _build_document_list_statement(
|
||||
return statement
|
||||
|
||||
|
||||
def _enforce_upload_shape(files: list[UploadFile]) -> None:
|
||||
"""Validates upload request shape against configured file-count bounds."""
|
||||
|
||||
if not files:
|
||||
raise HTTPException(status_code=400, detail="Upload request must include at least one file")
|
||||
if len(files) > settings.max_upload_files_per_request:
|
||||
raise HTTPException(
|
||||
status_code=413,
|
||||
detail=(
|
||||
"Upload request exceeds file count limit "
|
||||
f"({len(files)} > {settings.max_upload_files_per_request})"
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def _read_upload_bytes(file: UploadFile, max_bytes: int) -> bytes:
|
||||
"""Reads one upload file while enforcing per-file byte limits."""
|
||||
|
||||
data = await file.read(max_bytes + 1)
|
||||
if len(data) > max_bytes:
|
||||
raise HTTPException(
|
||||
status_code=413,
|
||||
detail=f"File '{file.filename or 'upload'}' exceeds per-file limit of {max_bytes} bytes",
|
||||
)
|
||||
return data
|
||||
|
||||
|
||||
def _collect_document_tree(session: Session, root_document_id: UUID) -> list[tuple[int, Document]]:
|
||||
"""Collects a document and all descendants for recursive permanent deletion."""
|
||||
|
||||
@@ -472,18 +501,29 @@ async def upload_documents(
|
||||
) -> UploadResponse:
|
||||
"""Uploads files, records metadata, and enqueues asynchronous extraction tasks."""
|
||||
|
||||
_enforce_upload_shape(files)
|
||||
set_processing_log_autocommit(session, True)
|
||||
normalized_tags = _normalize_tags(tags)
|
||||
queue = get_processing_queue()
|
||||
uploaded: list[DocumentResponse] = []
|
||||
conflicts: list[UploadConflict] = []
|
||||
total_request_bytes = 0
|
||||
|
||||
indexed_relative_paths = relative_paths or []
|
||||
prepared_uploads: list[dict[str, object]] = []
|
||||
|
||||
for idx, file in enumerate(files):
|
||||
filename = file.filename or f"uploaded_{idx}"
|
||||
data = await file.read()
|
||||
data = await _read_upload_bytes(file, settings.max_upload_file_size_bytes)
|
||||
total_request_bytes += len(data)
|
||||
if total_request_bytes > settings.max_upload_request_size_bytes:
|
||||
raise HTTPException(
|
||||
status_code=413,
|
||||
detail=(
|
||||
"Upload request exceeds total size limit "
|
||||
f"({total_request_bytes} > {settings.max_upload_request_size_bytes} bytes)"
|
||||
),
|
||||
)
|
||||
sha256 = compute_sha256(data)
|
||||
source_relative_path = indexed_relative_paths[idx] if idx < len(indexed_relative_paths) else filename
|
||||
extension = Path(filename).suffix.lower()
|
||||
|
||||
Reference in New Issue
Block a user