From 7542f2d7c1720280ef1169de3a3f43da7206176c Mon Sep 17 00:00:00 2001 From: zhi Date: Tue, 17 Mar 2026 12:04:12 +0000 Subject: [PATCH] =?UTF-8?q?feat(P5.7):=20task=20edit=20restrictions=20?= =?UTF-8?q?=E2=80=94=20block=20body=20edits=20in=20undergoing/completed/cl?= =?UTF-8?q?osed,=20enforce=20assignee-only=20edit=20in=20open+assigned?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/routers/tasks.py | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/app/api/routers/tasks.py b/app/api/routers/tasks.py index 33cb8c8..abcbf64 100644 --- a/app/api/routers/tasks.py +++ b/app/api/routers/tasks.py @@ -181,9 +181,38 @@ def update_task(task_id: int, task_update: schemas.TaskUpdate, db: Session = Dep task = db.query(Task).filter(Task.id == task_id).first() if not task: raise HTTPException(status_code=404, detail="Task not found") - ensure_can_edit_task(db, current_user.id, task) - + + # P5.7: status-based edit restrictions + current_status = task.status.value if hasattr(task.status, 'value') else task.status update_data = task_update.model_dump(exclude_unset=True) + + # Fields that are always allowed regardless of status (non-body edits) + _always_allowed = {"status"} + body_fields = {k for k in update_data.keys() if k not in _always_allowed} + + if body_fields: + # undergoing/completed/closed: body edits forbidden + if current_status in ("undergoing", "completed", "closed"): + raise HTTPException( + status_code=400, + detail=f"Cannot edit task body fields in '{current_status}' status. " + f"Blocked fields: {sorted(body_fields)}", + ) + # open + assignee set: only assignee or admin can edit body + if current_status == "open" and task.assignee_id is not None: + from app.api.rbac import is_global_admin, has_project_admin_role + is_admin = ( + is_global_admin(db, current_user.id) + or has_project_admin_role(db, current_user.id, task.project_id) + ) + if current_user.id != task.assignee_id and not is_admin: + raise HTTPException( + status_code=403, + detail="Only the current assignee or an admin can edit this task", + ) + + # Legacy general permission check (covers project membership etc.) + ensure_can_edit_task(db, current_user.id, task) if "status" in update_data: new_status = update_data["status"] old_status = task.status.value if hasattr(task.status, 'value') else task.status