feat: add resource-level authorization for OPC
- Add OPCAuthz module with fine-grained access control
- Users can only view/modify tasks they created or are assigned to
- Admins have full access to all resources
- Members can trigger PM agent, only admins can trigger CTO/COO
- Only admins can approve CxO-level agent executions
- Track task creator in metadata for authorization
- Add metadata column with default '{}' to tasks table
- Filter task and execution lists based on user permissions
- Add 10 integration tests for authorization logic
This commit is contained in:
@@ -8,6 +8,7 @@ from fastapi import APIRouter, Depends, HTTPException, Request
|
||||
from pydantic import BaseModel
|
||||
|
||||
from db import opc_db
|
||||
from opc_authz import OPCAuthz
|
||||
from rbac import User, require_admin
|
||||
|
||||
router = APIRouter()
|
||||
@@ -80,6 +81,18 @@ async def trigger_agent(
|
||||
):
|
||||
"""Manually trigger agent execution"""
|
||||
user: User = request.state.user
|
||||
|
||||
# Check if user can trigger this agent
|
||||
OPCAuthz.require_agent_trigger(user, agent_id)
|
||||
|
||||
# Get task to check authorization
|
||||
task = await opc_db.get_task_by_id(task_id)
|
||||
if not task:
|
||||
raise HTTPException(status_code=404, detail="Task not found")
|
||||
|
||||
# Check if user can modify this task
|
||||
OPCAuthz.require_task_modify(user, task)
|
||||
|
||||
execution = await opc_db.create_agent_execution(task_id=task_id, agent_id=agent_id, actor=user.username)
|
||||
return execution
|
||||
|
||||
@@ -95,6 +108,19 @@ async def list_executions(
|
||||
"""List agent executions"""
|
||||
user: User = request.state.user
|
||||
executions = await opc_db.get_agent_executions(task_id=task_id, agent_id=agent_id, status=status, limit=limit)
|
||||
|
||||
# Get all tasks for filtering
|
||||
if user.role != "admin":
|
||||
# Fetch tasks to check authorization
|
||||
task_ids = list(set(e["task_id"] for e in executions))
|
||||
tasks = []
|
||||
for tid in task_ids:
|
||||
task = await opc_db.get_task_by_id(tid)
|
||||
if task:
|
||||
tasks.append(task)
|
||||
tasks_by_id = {t["id"]: t for t in tasks}
|
||||
executions = OPCAuthz.filter_executions(user, executions, tasks_by_id)
|
||||
|
||||
return {"items": executions}
|
||||
|
||||
|
||||
@@ -108,6 +134,15 @@ async def get_execution(
|
||||
execution = await opc_db.get_agent_execution(execution_id)
|
||||
if not execution:
|
||||
raise HTTPException(status_code=404, detail="Execution not found")
|
||||
|
||||
# Get task to check authorization
|
||||
task = await opc_db.get_task_by_id(execution["task_id"])
|
||||
if not task:
|
||||
raise HTTPException(status_code=404, detail="Task not found")
|
||||
|
||||
# Check authorization
|
||||
OPCAuthz.require_execution_view(user, execution, task)
|
||||
|
||||
return execution
|
||||
|
||||
|
||||
@@ -119,6 +154,15 @@ async def approve_execution(
|
||||
):
|
||||
"""Approve or reject agent execution (for CxO level actions)"""
|
||||
user: User = request.state.user
|
||||
|
||||
# Get execution
|
||||
execution = await opc_db.get_agent_execution(execution_id)
|
||||
if not execution:
|
||||
raise HTTPException(status_code=404, detail="Execution not found")
|
||||
|
||||
# Check authorization (only admins can approve)
|
||||
OPCAuthz.require_execution_approve(user, execution)
|
||||
|
||||
try:
|
||||
execution = await opc_db.approve_agent_execution(
|
||||
execution_id=execution_id, approved=approval.approved, approved_by=user.username, feedback=approval.feedback
|
||||
|
||||
Reference in New Issue
Block a user