From 02d06acfb87dafa4279403484a6267fdb4e7c10c Mon Sep 17 00:00:00 2001 From: chihlasm Date: Thu, 5 Feb 2026 23:25:56 -0500 Subject: [PATCH] feat: add super admin bypass in tree list filter Super admins now see all trees regardless of ownership, team, or public/default status. Previously the build_tree_access_filter function had no super_admin check, so admins could only see their own trees plus public/default/team trees. Co-Authored-By: Claude Opus 4.6 --- backend/app/api/endpoints/trees.py | 13 +++++++++---- backend/tests/test_trees.py | 31 ++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/backend/app/api/endpoints/trees.py b/backend/app/api/endpoints/trees.py index 2dfde8ff..858f1490 100644 --- a/backend/app/api/endpoints/trees.py +++ b/backend/app/api/endpoints/trees.py @@ -2,7 +2,7 @@ from typing import Annotated, Optional from uuid import UUID from fastapi import APIRouter, Depends, HTTPException, status, Query from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy import select, func, or_ +from sqlalchemy import select, func, or_, true as sa_true from sqlalchemy.orm import selectinload from app.core.database import get_db @@ -22,17 +22,22 @@ def build_tree_access_filter(current_user: User): """Build the access filter for trees based on user permissions. Returns trees that are: + - All trees (for super admins) - Default/system trees (visible to all) - Public trees - User's own trees - Trees from user's team """ - return or_( + if current_user.is_super_admin: + return sa_true() + conditions = [ Tree.is_default == True, Tree.is_public == True, Tree.author_id == current_user.id, - Tree.team_id == current_user.team_id if current_user.team_id else False - ) + ] + if current_user.team_id: + conditions.append(Tree.team_id == current_user.team_id) + return or_(*conditions) def build_tree_response(tree: Tree) -> TreeListResponse: diff --git a/backend/tests/test_trees.py b/backend/tests/test_trees.py index f7d4bc4a..6d8afd65 100644 --- a/backend/tests/test_trees.py +++ b/backend/tests/test_trees.py @@ -172,6 +172,37 @@ class TestTrees: active_ids = [tree["id"] for tree in active_trees] assert test_tree["id"] not in active_ids + @pytest.mark.asyncio + async def test_super_admin_sees_all_trees( + self, client: AsyncClient, auth_headers: dict, admin_auth_headers: dict + ): + """Test that super admin can see all trees including private ones from other users.""" + # Create a private (non-public, non-default) tree as a regular user + tree_data = { + "name": "Private User Tree", + "description": "Only visible to author and super admin", + "tree_structure": { + "id": "root", + "type": "solution", + "title": "Private", + "description": "Private tree" + }, + "is_public": False, + "is_default": False + } + + create_response = await client.post( + "/api/v1/trees", json=tree_data, headers=auth_headers + ) + assert create_response.status_code == 201 + private_tree_id = create_response.json()["id"] + + # Super admin should see it in list + list_response = await client.get("/api/v1/trees", headers=admin_auth_headers) + assert list_response.status_code == 200 + tree_ids = [t["id"] for t in list_response.json()] + assert private_tree_id in tree_ids + @pytest.mark.asyncio async def test_create_tree_unauthorized(self, client: AsyncClient): """Test that creating a tree without auth fails."""