fix: add cross-team authorization to maintenance schedule endpoints

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
chihlasm
2026-02-17 13:42:23 -05:00
parent 25cc16da3a
commit 829b7cf5a7
2 changed files with 65 additions and 0 deletions

View File

@@ -10,6 +10,7 @@ import pytz
from app.api.deps import get_current_active_user, get_db
from app.models.maintenance_schedule import MaintenanceSchedule
from app.models.tree import Tree
from app.models.user import User
from app.schemas.maintenance_schedule import (
MaintenanceScheduleCreate,
@@ -28,6 +29,19 @@ def _compute_next_run(cron_expression: str, tz_name: str) -> datetime:
return cron.get_next(datetime).astimezone(timezone.utc)
async def _get_tree_or_403(tree_id: UUID, current_user: User, db: AsyncSession) -> "Tree":
"""Fetch tree and verify the current user's team owns it."""
result = await db.execute(select(Tree).where(Tree.id == tree_id))
tree = result.scalar_one_or_none()
if not tree:
raise HTTPException(status_code=404, detail="Tree not found")
# Super admins can access any tree; regular users must be on the same team
if not getattr(current_user, 'is_super_admin', False):
if tree.team_id != current_user.team_id:
raise HTTPException(status_code=403, detail="Access denied")
return tree
@router.post("", response_model=MaintenanceScheduleResponse, status_code=201)
async def create_schedule(
data: MaintenanceScheduleCreate,
@@ -35,6 +49,9 @@ async def create_schedule(
db: Annotated[AsyncSession, Depends(get_db)],
):
"""Create a cron schedule for a maintenance flow. One per flow."""
# Verify user's team owns the tree
await _get_tree_or_403(data.tree_id, current_user, db)
# Check no existing schedule for this tree
existing = await db.execute(
select(MaintenanceSchedule).where(MaintenanceSchedule.tree_id == data.tree_id)
@@ -69,6 +86,9 @@ async def get_schedule_for_tree(
db: Annotated[AsyncSession, Depends(get_db)],
):
"""Get the schedule for a specific maintenance flow."""
# Verify user's team owns the tree before returning schedule data
await _get_tree_or_403(tree_id, current_user, db)
result = await db.execute(
select(MaintenanceSchedule).where(MaintenanceSchedule.tree_id == tree_id)
)
@@ -93,6 +113,9 @@ async def update_schedule(
if not schedule:
raise HTTPException(status_code=404, detail="Schedule not found")
# Verify user's team owns the tree this schedule belongs to
await _get_tree_or_403(schedule.tree_id, current_user, db)
update_fields = data.model_fields_set
if "cron_expression" in update_fields and data.cron_expression is not None:
schedule.cron_expression = data.cron_expression