mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
fix(permissions): give the CEO full authority over every task action
The panel operates as the CEO, but most task-action routes gated to (assignee | cell_pm/main_pm) and omitted the CEO — so the CEO could approve (CEO-only routes) yet got 403 ACCESS_DENIED on unblock, block, reassign, update, delete, cancel. The whole UI write-path was unusable. can_perform_task_action() now short-circuits true for the CEO (fixes update/reassign, delete, cancel and anything routed through it), and the inline block/unblock checks add AgentRole.CEO. The override is a CEO-only early return, so it cannot affect any other role.
This commit is contained in:
@@ -712,10 +712,11 @@ async def block_task(
|
|||||||
status_code=status.HTTP_404_NOT_FOUND, detail="Task not found"
|
status_code=status.HTTP_404_NOT_FOUND, detail="Task not found"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Only assigned agent or PM can block a task
|
# Only assigned agent, PM, or the CEO can block a task
|
||||||
if task.assigned_to != agent.agent_id and agent.role not in (
|
if task.assigned_to != agent.agent_id and agent.role not in (
|
||||||
AgentRole.CELL_PM,
|
AgentRole.CELL_PM,
|
||||||
AgentRole.MAIN_PM,
|
AgentRole.MAIN_PM,
|
||||||
|
AgentRole.CEO,
|
||||||
):
|
):
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_403_FORBIDDEN,
|
status_code=status.HTTP_403_FORBIDDEN,
|
||||||
@@ -771,10 +772,11 @@ async def unblock_task(
|
|||||||
status_code=status.HTTP_404_NOT_FOUND, detail="Task not found"
|
status_code=status.HTTP_404_NOT_FOUND, detail="Task not found"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Only assigned agent or PM can unblock a task
|
# Only assigned agent, PM, or the CEO can unblock a task
|
||||||
if task.assigned_to != agent.agent_id and agent.role not in (
|
if task.assigned_to != agent.agent_id and agent.role not in (
|
||||||
AgentRole.CELL_PM,
|
AgentRole.CELL_PM,
|
||||||
AgentRole.MAIN_PM,
|
AgentRole.MAIN_PM,
|
||||||
|
AgentRole.CEO,
|
||||||
):
|
):
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_403_FORBIDDEN,
|
status_code=status.HTTP_403_FORBIDDEN,
|
||||||
|
|||||||
@@ -299,6 +299,12 @@ class PermissionService(SingletonService):
|
|||||||
task_team: Team | None = None,
|
task_team: Team | None = None,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Check if agent can perform a task action."""
|
"""Check if agent can perform a task action."""
|
||||||
|
# The CEO is the ultimate authority and may perform any task action on
|
||||||
|
# any task — unblock, reassign, cancel, override status, etc. Every
|
||||||
|
# route that gates a write through this helper therefore lets the CEO
|
||||||
|
# through (the panel operates as the CEO).
|
||||||
|
if agent.role == AgentRole.CEO:
|
||||||
|
return True
|
||||||
allowed_actions = TASK_PERMISSIONS.get(agent.role, set())
|
allowed_actions = TASK_PERMISSIONS.get(agent.role, set())
|
||||||
|
|
||||||
if action in allowed_actions:
|
if action in allowed_actions:
|
||||||
|
|||||||
@@ -153,6 +153,21 @@ def test_cell_pm_can_close_in_own_cell(svc: PermissionService) -> None:
|
|||||||
assert svc.can_perform_task_action(cell_pm, TaskAction.CLOSE, Team.BACKEND) is True
|
assert svc.can_perform_task_action(cell_pm, TaskAction.CLOSE, Team.BACKEND) is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_ceo_can_perform_any_task_action(svc: PermissionService) -> None:
|
||||||
|
"""The CEO is the ultimate authority — it may perform ANY task action on any
|
||||||
|
task (assign/reassign, change priority, close, claim, view). The panel
|
||||||
|
operates as the CEO, so this override is what unblocks the whole UI."""
|
||||||
|
ceo = _ctx(AgentRole.CEO)
|
||||||
|
for action in (
|
||||||
|
TaskAction.ASSIGN,
|
||||||
|
TaskAction.CHANGE_PRIORITY,
|
||||||
|
TaskAction.CLOSE,
|
||||||
|
TaskAction.CLAIM,
|
||||||
|
TaskAction.VIEW_ALL,
|
||||||
|
):
|
||||||
|
assert svc.can_perform_task_action(ceo, action, Team.BACKEND) is True
|
||||||
|
|
||||||
|
|
||||||
def test_get_task_actions_returns_set(svc: PermissionService) -> None:
|
def test_get_task_actions_returns_set(svc: PermissionService) -> None:
|
||||||
dev = _ctx(AgentRole.DEVELOPER, team=Team.BACKEND)
|
dev = _ctx(AgentRole.DEVELOPER, team=Team.BACKEND)
|
||||||
actions = svc.get_task_actions(dev)
|
actions = svc.get_task_actions(dev)
|
||||||
|
|||||||
Reference in New Issue
Block a user