82 lines
4.2 KiB
Python
82 lines
4.2 KiB
Python
"""Tool-Einstufung (Plattform-Admin) und Verschärfung je Workspace (Workspace-Admin)."""
|
|
|
|
from typing import Annotated
|
|
from urllib.parse import urlencode
|
|
|
|
from fastapi import APIRouter, Form, Request
|
|
from fastapi.responses import HTMLResponse, Response
|
|
from sqlalchemy import select
|
|
|
|
from app import web
|
|
from app.core.errors import Invalid
|
|
from app.identity.admin import require as require_platform_admin
|
|
from app.mcp import service as mcp
|
|
from app.tools import service as tools
|
|
from app.tools.models import DECISIONS, RISK_CLASSES
|
|
from app.workspaces.models import Workspace
|
|
|
|
Text = Annotated[str, Form()]
|
|
|
|
|
|
def router() -> APIRouter:
|
|
r = APIRouter()
|
|
|
|
def page(request: Request, principal, error: str = "", notice: str = "", status: int = 200) -> HTMLResponse:
|
|
require_platform_admin(principal)
|
|
with web.db(request).session() as session:
|
|
origins = mcp.origins(session)
|
|
workspace_names = dict(session.execute(select(Workspace.id, Workspace.name)).all())
|
|
# Tools der Datenbanken stuft man am Treiber ein
|
|
definitions = [d for d in tools.sync(session)
|
|
if not (d.kind in origins and origins[d.kind].template_id)]
|
|
for server in origins.values():
|
|
session.expunge(server)
|
|
return web.render(request, "admin_tools.html", {"definitions": definitions, "classes": RISK_CLASSES,
|
|
"origins": origins, "workspace_names": workspace_names,
|
|
"decisions": DECISIONS, "defaults": tools.DEFAULT_DECISION,
|
|
"error": error, "notice": notice},
|
|
status)
|
|
|
|
def act(request: Request, principal, action, notice) -> Response:
|
|
try:
|
|
with web.db(request).session() as session:
|
|
result = action(session)
|
|
except Invalid as e:
|
|
return page(request, principal, error=str(e), status=400)
|
|
return page(request, principal, notice=notice(result) if callable(notice) else notice)
|
|
|
|
@r.get("/admin/tools")
|
|
def overview(request: Request, principal: web.User) -> HTMLResponse:
|
|
return page(request, principal)
|
|
|
|
@r.post("/admin/tools/suggested")
|
|
def suggested(request: Request, principal: web.Checked) -> Response:
|
|
return act(request, principal, lambda s: tools.classify_suggested(s, principal),
|
|
lambda count: f"{count} Tools nach Vorschlag eingestuft.")
|
|
|
|
@r.post("/admin/tools/{name}")
|
|
def classify(request: Request, principal: web.Checked, name: str, risk_class: Text, decision: Text,
|
|
approval_level: Text = "workspace_admin", four_eyes: Annotated[bool, Form()] = False) -> Response:
|
|
return act(request, principal, lambda s: tools.classify(s, principal, name, risk_class, decision,
|
|
approval_level, four_eyes),
|
|
f"{name} eingestuft.")
|
|
|
|
@r.post("/workspaces/{workspace_id}/tools/{name}")
|
|
def workspace_policy(request: Request, principal: web.Checked, workspace_id: int, name: str,
|
|
decision: Text = "", four_eyes: Annotated[bool, Form()] = False) -> Response:
|
|
with web.db(request).session() as session:
|
|
tools.set_workspace_policy(session, principal, workspace_id, name, decision, four_eyes)
|
|
return web.redirect(f"/workspaces/{workspace_id}/settings?{urlencode({'tab': 'tools', 'tool': name})}")
|
|
|
|
@r.post("/workspaces/{workspace_id}/source-rules")
|
|
def source_rule(request: Request, principal: web.Checked, workspace_id: int,
|
|
rule: Text, decision: Text = "", four_eyes: Annotated[bool, Form()] = False) -> Response:
|
|
"""rule = <source_id>:<tool>; leere Entscheidung ohne Vier-Augen-Prinzip entfernt die Regel."""
|
|
source_id, _, name = rule.partition(":")
|
|
if not source_id.isdigit() or not name:
|
|
raise Invalid("Bitte Quelle und Tool wählen.")
|
|
with web.db(request).session() as session:
|
|
tools.set_source_policy(session, principal, workspace_id, int(source_id), name, decision, four_eyes)
|
|
return web.redirect(f"/workspaces/{workspace_id}/settings?tab=tools&saved=1#source-rules")
|
|
|
|
return r
|