v1.0.176: spec_current cascade fix + keep_ids DoS limit

This commit is contained in:
2026-06-24 18:04:59 +04:00
parent 044a83a43d
commit cd5ba7470e
2 changed files with 28 additions and 10 deletions
+14 -2
View File
@@ -163,7 +163,11 @@ class Handler(BaseHTTPRequestHandler):
doc_id = parsed.path.split("/")[-1] doc_id = parsed.path.split("/")[-1]
supps = query("SELECT id, contract_id FROM supplements WHERE document_id = %s", (doc_id,)) supps = query("SELECT id, contract_id FROM supplements WHERE document_id = %s", (doc_id,))
for s in (supps or []): for s in (supps or []):
execute("DELETE FROM spec_current WHERE contract_id = %s", (s["contract_id"],)) execute(
"""DELETE FROM spec_current WHERE contract_id = %s
AND last_event_id IN (SELECT id FROM spec_events WHERE supplement_id = %s)""",
(s["contract_id"], s["id"]),
)
execute("DELETE FROM spec_events WHERE supplement_id = %s", (s["id"],)) execute("DELETE FROM spec_events WHERE supplement_id = %s", (s["id"],))
execute("DELETE FROM supplements WHERE id = %s", (s["id"],)) execute("DELETE FROM supplements WHERE id = %s", (s["id"],))
execute("DELETE FROM documents WHERE id = %s", (doc_id,)) execute("DELETE FROM documents WHERE id = %s", (doc_id,))
@@ -177,6 +181,10 @@ class Handler(BaseHTTPRequestHandler):
length = int(self.headers.get("Content-Length", 0)) length = int(self.headers.get("Content-Length", 0))
body = json.loads(self.rfile.read(length)) if length > 0 else {} body = json.loads(self.rfile.read(length)) if length > 0 else {}
keep_ids = set(body.get("keep_ids", [])) keep_ids = set(body.get("keep_ids", []))
# Prevent DoS: too many IDs
if len(keep_ids) > 1000:
self._json({"ok": False, "error": "too many keep_ids (max 1000)"}, 400)
return
docs = query("SELECT id FROM documents", ()) docs = query("SELECT id FROM documents", ())
deleted = 0 deleted = 0
for d in (docs or []): for d in (docs or []):
@@ -184,7 +192,11 @@ class Handler(BaseHTTPRequestHandler):
continue continue
supps = query("SELECT id, contract_id FROM supplements WHERE document_id = %s", (d["id"],)) supps = query("SELECT id, contract_id FROM supplements WHERE document_id = %s", (d["id"],))
for s in (supps or []): for s in (supps or []):
execute("DELETE FROM spec_current WHERE contract_id = %s", (s["contract_id"],)) execute(
"""DELETE FROM spec_current WHERE contract_id = %s
AND last_event_id IN (SELECT id FROM spec_events WHERE supplement_id = %s)""",
(s["contract_id"], s["id"]),
)
execute("DELETE FROM spec_events WHERE supplement_id = %s", (s["id"],)) execute("DELETE FROM spec_events WHERE supplement_id = %s", (s["id"],))
execute("DELETE FROM supplements WHERE id = %s", (s["id"],)) execute("DELETE FROM supplements WHERE id = %s", (s["id"],))
execute("DELETE FROM documents WHERE id = %s", (d["id"],)) execute("DELETE FROM documents WHERE id = %s", (d["id"],))
+14 -8
View File
@@ -28,25 +28,31 @@ def get(supp_id):
def delete_by_document(contract_id, filename): def delete_by_document(contract_id, filename):
"""Delete supplement+document by contract+filename (cascade: spec_events first).""" """Delete ALL supplements+documents by contract+filename (cascade: spec_events first).
row = query( Handles duplicates from previously failed uploads."""
rows = query(
"""SELECT s.id as sid, s.document_id FROM supplements s """SELECT s.id as sid, s.document_id FROM supplements s
JOIN documents d ON d.id = s.document_id JOIN documents d ON d.id = s.document_id
WHERE s.contract_id = %s AND d.filename = %s""", WHERE s.contract_id = %s AND d.filename = %s""",
(contract_id, filename), (contract_id, filename),
) )
if row: if not rows:
r = row[0] return False
# 1. Delete spec_current for this contract (FK to spec_events)
execute("DELETE FROM spec_current WHERE contract_id = %s", (contract_id,)) for r in rows:
# 1. Delete spec_current rows referencing this supplement's events
execute(
"""DELETE FROM spec_current WHERE contract_id = %s
AND last_event_id IN (SELECT id FROM spec_events WHERE supplement_id = %s)""",
(contract_id, r["sid"]),
)
# 2. Delete spec_events referencing this supplement # 2. Delete spec_events referencing this supplement
execute("DELETE FROM spec_events WHERE supplement_id = %s", (r["sid"],)) execute("DELETE FROM spec_events WHERE supplement_id = %s", (r["sid"],))
# 3. Delete supplement # 3. Delete supplement
execute("DELETE FROM supplements WHERE id = %s", (r["sid"],)) execute("DELETE FROM supplements WHERE id = %s", (r["sid"],))
# 4. Delete document # 4. Delete document
execute("DELETE FROM documents WHERE id = %s", (r["document_id"],)) execute("DELETE FROM documents WHERE id = %s", (r["document_id"],))
return True return True
return False
def delete(supp_id): def delete(supp_id):