Files
Unraid-AppOps-Gateway-Public/backend/app/api/routes_policies.py
T

92 lines
3.3 KiB
Python

from __future__ import annotations
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel
from sqlmodel import Session, select
from ..db.session import get_session
from ..db.models import Policy
from ..services.policy_engine import evaluate_policy
from ..services.action_registry import action_capability
from ..services.audit_logger import log_event
router = APIRouter(prefix='/api/policies')
DECISIONS = {'allowed', 'approval_required', 'blocked'}
RISKS = {'low', 'medium', 'high', 'critical'}
class PolicyUpdate(BaseModel):
voice: str
manual: str
risk: str
reason: str
@router.get('')
def list_policies(session: Session = Depends(get_session)):
return session.exec(select(Policy).order_by(Policy.action, Policy.target)).all()
@router.patch('/{policy_id}')
def update_policy(policy_id: str, payload: PolicyUpdate, session: Session = Depends(get_session)):
policy = session.get(Policy, policy_id)
if not policy:
raise HTTPException(status_code=404, detail='Policy not found.')
if payload.voice not in DECISIONS or payload.manual not in DECISIONS or payload.risk not in RISKS:
raise HTTPException(status_code=422, detail='Invalid policy decision or risk value.')
if not payload.reason.strip():
raise HTTPException(status_code=422, detail='A policy reason is required.')
capability = action_capability(policy.action)
if capability == 'destructive' and (payload.voice != 'blocked' or payload.manual != 'blocked'):
raise HTTPException(status_code=409, detail='Destructive actions remain blocked by the platform guardrail.')
before = {'voice': policy.voice, 'manual': policy.manual, 'risk': policy.risk, 'reason': policy.reason}
policy.voice = payload.voice
policy.manual = payload.manual
policy.risk = 'critical' if capability == 'destructive' else payload.risk
policy.reason = payload.reason.strip()
session.add(policy)
session.commit()
session.refresh(policy)
after = {'voice': policy.voice, 'manual': policy.manual, 'risk': policy.risk, 'reason': policy.reason}
log_event(
session,
source='manual',
identity='operator',
action_id='policy.update',
target=policy.id,
target_type='policy',
risk='medium',
policy_decision='allowed',
reason='Operator updated policy decisions within enforced platform guardrails.',
result='success',
before_state=before,
after_state=after,
redacted_metadata={'protectedGuardrail': capability == 'destructive', 'policyAction': policy.action},
)
return {
'id': policy.id,
'action': policy.action,
'target': policy.target,
'voice': policy.voice,
'manual': policy.manual,
'risk': policy.risk,
'reason': policy.reason,
}
@router.post('/evaluate')
def evaluate(payload: dict, session: Session = Depends(get_session)):
action = str(payload.get('action', '')).strip()
target = str(payload.get('target', '')).strip()
source = str(payload.get('source', 'manual')).strip() or 'manual'
result = evaluate_policy(session, action=action, target=target, source=source)
return {
'decision': result['decision'],
'reason': result['reason'],
'risk': result['risk'],
'policyId': result['policy_id'],
}