Opening a purged note, or one of its files, is a 404
get_note and get_attachment selected with the ACL inline and skipped the purged filter, so a tombstone came back 200 (#2128 says a purged note reads as absent). Both now go through _get_visible, which cannot skip it. Reorder's batch lookup gains the same filter, so a stale id cannot write a place onto a tombstone. Fixes #5383. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -387,7 +387,9 @@ async def reorder_notes():
|
||||
for n in (
|
||||
await db.scalars(
|
||||
select(Note).where(
|
||||
Note.id.in_(parsed), visible_to_user("note", Note.owner_id, Note.id, g.user_id)
|
||||
Note.id.in_(parsed),
|
||||
visible_to_user("note", Note.owner_id, Note.id, g.user_id),
|
||||
Note.purged_at.is_(None),
|
||||
)
|
||||
)
|
||||
).all()
|
||||
@@ -431,13 +433,8 @@ async def create_note():
|
||||
@bp.get("/<note_id>")
|
||||
@login_required
|
||||
async def get_note(note_id: str):
|
||||
nid = parse_uuid(note_id)
|
||||
if nid is None:
|
||||
return not_found()
|
||||
async with session_scope() as db:
|
||||
note = await db.scalar(
|
||||
select(Note).where(Note.id == nid, visible_to_user("note", Note.owner_id, Note.id, g.user_id))
|
||||
)
|
||||
note = await _get_visible(db, note_id)
|
||||
if note is None:
|
||||
return not_found()
|
||||
return jsonify(await _serialize_note(db, note, g.user_id))
|
||||
@@ -653,18 +650,17 @@ async def upload_attachment(note_id: str):
|
||||
@bp.get("/<note_id>/attachments/<att_id>")
|
||||
@login_required
|
||||
async def get_attachment(note_id: str, att_id: str):
|
||||
nid = parse_uuid(note_id)
|
||||
aid = parse_uuid(att_id)
|
||||
if nid is None or aid is None:
|
||||
if aid is None:
|
||||
return not_found()
|
||||
async with session_scope() as db:
|
||||
# Owner OR shared may view (rule 47).
|
||||
note = await db.scalar(
|
||||
select(Note).where(Note.id == nid, visible_to_user("note", Note.owner_id, Note.id, g.user_id))
|
||||
)
|
||||
# Owner OR shared may view (rule 47). A purged note's files are gone with it.
|
||||
note = await _get_visible(db, note_id)
|
||||
if note is None:
|
||||
return not_found()
|
||||
att = await db.scalar(select(NoteAttachment).where(NoteAttachment.id == aid, NoteAttachment.note_id == nid))
|
||||
att = await db.scalar(
|
||||
select(NoteAttachment).where(NoteAttachment.id == aid, NoteAttachment.note_id == note.id)
|
||||
)
|
||||
if att is None:
|
||||
return not_found()
|
||||
mime = att.mime
|
||||
|
||||
@@ -891,6 +891,22 @@ async def test_an_offline_attachment_uploads_once_under_its_own_id(app_client, d
|
||||
assert (await _put(app_client, aid, other, b"%PDF-1")).status_code == 409, "one id, one note"
|
||||
|
||||
|
||||
async def test_a_purged_note_and_its_files_read_as_gone(app_client, db):
|
||||
"""A purged note reads as absent (#2128). Opening it and its attachment selected
|
||||
with the ACL inline and skipped that filter, so a tombstone came back 200 (#5383)."""
|
||||
await _signed_in(app_client, "purged")
|
||||
nid = await _pushed_note(app_client)
|
||||
aid = str(uuid.uuid4())
|
||||
assert (await _put(app_client, aid, nid, b"%PDF-1")).status_code == 201
|
||||
assert (await app_client.get(f"/api/notes/{nid}/attachments/{aid}")).status_code == 200
|
||||
|
||||
assert (await app_client.post(f"/api/notes/{nid}/trash")).status_code == 200
|
||||
assert (await app_client.delete(f"/api/notes/{nid}")).status_code == 200
|
||||
|
||||
assert (await app_client.get(f"/api/notes/{nid}")).status_code == 404
|
||||
assert (await app_client.get(f"/api/notes/{nid}/attachments/{aid}")).status_code == 404
|
||||
|
||||
|
||||
async def test_an_upload_to_a_note_the_caller_does_not_own_is_not_found(app_client, db):
|
||||
# Signed in first: registration is open only to the first account.
|
||||
await _signed_in(app_client, "intruder")
|
||||
|
||||
Reference in New Issue
Block a user