diff --git a/backend/open_webui/routers/chats.py b/backend/open_webui/routers/chats.py index e618eb3f06..519c05ed35 100644 --- a/backend/open_webui/routers/chats.py +++ b/backend/open_webui/routers/chats.py @@ -1302,8 +1302,14 @@ async def compact_chat_by_id( ############################ -@router.get('/{id}', response_model=ChatResponse | None) -async def get_chat_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)): +async def get_readable_chat(user, id: str, db: AsyncSession): + """Resolve a chat the given user has read access to, or None. + + Read access is granted by any of: ownership, an admin override under + ENABLE_ADMIN_CHAT_ACCESS, an explicit shared_chat read grant, or access + to the folder the chat sits in. Kept as a single helper so every route + that needs to check "can this user read this chat" stays in step. + """ chat = await Chats.get_chat_by_id_and_user_id(id, user.id, db=db) if not chat and user.role == 'admin': @@ -1332,6 +1338,13 @@ async def get_chat_by_id(id: str, user=Depends(get_verified_user), db: AsyncSess if folder and await has_folder_access(user.id, folder, 'read', db): chat = candidate + return chat + + +@router.get('/{id}', response_model=ChatResponse | None) +async def get_chat_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)): + chat = await get_readable_chat(user, id, db) + if chat: data = ChatResponse.model_validate(chat, from_attributes=True).model_dump() data['context_usage'] = await get_chat_context_usage(chat) @@ -2153,9 +2166,12 @@ async def update_chat_folder_id_by_id( @router.get('/{id}/tags', response_model=list[TagModel]) async def get_chat_tags_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)): - chat = await Chats.get_chat_by_id_and_user_id(id, user.id, db=db) + chat = await get_readable_chat(user, id, db) if chat: tags = chat.meta.get('tags', []) + # Tags are resolved against the requesting user's own tag rows, so a + # viewer who does not own the chat simply gets an empty list rather + # than another user's tags leaking through. return await Tags.get_tags_by_ids_and_user_id(tags, user.id, db=db) else: raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.NOT_FOUND)