fix: keep folder parent references acyclic (#28748)

Moving a folder under one of its own subfolders was accepted. A folder in a parent loop is never a root, so it and everything under it silently disappeared from the sidebar, and there was no way to get it back from the UI.

The move is now rejected with a 400, folders whose parent chain loops are put back at the root on the next folder list, and the folder tree traversals skip ids they have already visited so existing data in that state stays workable.
This commit is contained in:
Classic298
2026-08-24 17:06:19 -04:00
committed by GitHub
parent 18edfff2d6
commit 23b3a69bc2
3 changed files with 51 additions and 21 deletions
@@ -5,22 +5,24 @@ from sqlalchemy.ext.asyncio import AsyncSession
async def has_folder_access(user_id: str, folder: FolderModel, permission: str, db: AsyncSession | None) -> bool:
"""Check if user has access to folder directly or via ancestor inheritance."""
if folder.user_id == user_id:
return True
# A corrupt parent loop must not spin forever
seen_ids = set()
while folder and folder.id not in seen_ids:
seen_ids.add(folder.id)
if await AccessGrants.has_access(
user_id=user_id,
resource_type='folder',
resource_id=folder.id,
permission=permission,
db=db,
):
return True
# Check ancestor chain for inherited access
if folder.parent_id:
parent = await Folders.get_folder_by_id(folder.parent_id, db=db)
if parent:
return await has_folder_access(user_id, parent, permission, db)
if folder.user_id == user_id:
return True
if await AccessGrants.has_access(
user_id=user_id,
resource_type='folder',
resource_id=folder.id,
permission=permission,
db=db,
):
return True
folder = await Folders.get_folder_by_id(folder.parent_id, db=db) if folder.parent_id else None
return False