2026-09-08 01:07:59 +08:00
|
|
|
"""Server-configured identities; preview mode is limited to a local backend."""
|
|
|
|
|
import hmac
|
|
|
|
|
import json
|
|
|
|
|
import os
|
|
|
|
|
from urllib.parse import urlsplit
|
|
|
|
|
from fastapi import HTTPException, Request
|
|
|
|
|
|
|
|
|
|
|
2026-09-14 13:18:15 +08:00
|
|
|
ROLES = ("admin", "reviewer", "manager")
|
|
|
|
|
DEFAULT_DEMO_PROJECTS = "DEMO-A,DEMO-B,DEMO-C,LAB-001,XB-2026-001"
|
|
|
|
|
|
|
|
|
|
|
2026-09-08 01:07:59 +08:00
|
|
|
def authorize(request: Request):
|
|
|
|
|
configured = os.getenv("XINGBANG_AUTH_USERS", "")
|
2026-09-14 13:18:15 +08:00
|
|
|
demo_role = request.headers.get("x-xingbang-demo-role", "").strip()
|
|
|
|
|
if demo_role:
|
|
|
|
|
forwarded_host = request.headers.get("x-xingbang-client-host", request.headers.get("host", ""))
|
|
|
|
|
hostname = urlsplit("//"+forwarded_host).hostname
|
|
|
|
|
local_preview = not configured and os.getenv("XINGBANG_REQUIRE_AUTH") != "1" and request.client and request.client.host in ("127.0.0.1", "::1", "testclient") and hostname in ("localhost", "127.0.0.1", "::1", "testserver")
|
|
|
|
|
if not (os.getenv("XINGBANG_DEMO_ROLE_LOGIN") == "1" or local_preview) or demo_role not in ROLES:
|
|
|
|
|
raise HTTPException(401, "演示身份未启用或身份无效")
|
|
|
|
|
projects = [item.strip() for item in os.getenv("XINGBANG_DEMO_PROJECTS", DEFAULT_DEMO_PROJECTS).split(",") if item.strip()]
|
|
|
|
|
user = {"id": f"demo-{demo_role}", "role": demo_role, "projects": projects, "preview": False, "demo": True}
|
|
|
|
|
elif not configured:
|
2026-09-08 01:07:59 +08:00
|
|
|
forwarded_host = request.headers.get("x-xingbang-client-host", request.headers.get("host", ""))
|
|
|
|
|
hostname = urlsplit("//"+forwarded_host).hostname
|
|
|
|
|
if os.getenv("XINGBANG_REQUIRE_AUTH") == "1" or not request.client or request.client.host not in ("127.0.0.1", "::1", "testclient"):
|
|
|
|
|
raise HTTPException(401, "请配置服务器身份认证后访问")
|
|
|
|
|
if hostname not in ("localhost", "127.0.0.1", "::1", "testserver"):
|
|
|
|
|
raise HTTPException(401, "本机预览仅接受回环地址;远程访问必须配置身份认证")
|
2026-09-14 13:18:15 +08:00
|
|
|
user = {"id": "local-preview", "role": "admin", "projects": ["*"], "preview": True, "demo": True}
|
2026-09-08 01:07:59 +08:00
|
|
|
else:
|
|
|
|
|
try:
|
|
|
|
|
accounts = json.loads(configured)
|
|
|
|
|
except ValueError:
|
|
|
|
|
raise HTTPException(503, "身份配置无效")
|
|
|
|
|
supplied = request.headers.get("authorization", "").removeprefix("Bearer ")
|
|
|
|
|
user = next((dict(u) for u in accounts if isinstance(u.get("token"), str) and hmac.compare_digest(u["token"], supplied)), None)
|
2026-09-14 13:18:15 +08:00
|
|
|
if not user or user.get("role") not in ROLES:
|
2026-09-08 01:07:59 +08:00
|
|
|
raise HTTPException(401, "访问令牌无效")
|
|
|
|
|
user.pop("token", None)
|
|
|
|
|
user["preview"] = False
|
2026-09-14 13:18:15 +08:00
|
|
|
user["demo"] = False
|
2026-09-08 01:07:59 +08:00
|
|
|
request.state.identity = user
|
|
|
|
|
path = request.url.path.split("/risk-agent/", 1)[-1]
|
|
|
|
|
if user["role"] == "manager":
|
2026-09-08 02:49:06 +08:00
|
|
|
permitted = path in ("catalog", "access/context", "runs/history") or (path.startswith("plans/") and path.endswith("/run")) or (path.startswith("reports/") and request.method=="GET")
|
|
|
|
|
permitted = permitted or (path.startswith("coordination/") and (request.method == "GET" or path.endswith("/actions")))
|
2026-09-11 15:56:28 +08:00
|
|
|
permitted = permitted or path.startswith("project-assistant/")
|
2026-09-08 02:49:06 +08:00
|
|
|
permitted = permitted or (path.startswith(("collateral/", "workspace/")) and request.method == "GET")
|
2026-09-08 01:07:59 +08:00
|
|
|
if not permitted:
|
|
|
|
|
raise HTTPException(403, "客户经理无权管理配置")
|
2026-09-11 15:56:28 +08:00
|
|
|
if user["role"] == "reviewer" and request.method != "GET" and not path.endswith("/review") and not path.startswith(("reports/", "coordination/", "collateral/", "project-assistant/")):
|
2026-09-08 01:07:59 +08:00
|
|
|
raise HTTPException(403, "审核员仅可审核发布,不能修改配置或执行查询")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def check_project(request: Request, project_id: str):
|
|
|
|
|
user = request.state.identity
|
2026-09-11 15:56:28 +08:00
|
|
|
if "*" not in user.get("projects", []) and project_id not in user.get("projects", []):
|
2026-09-08 01:07:59 +08:00
|
|
|
raise HTTPException(403, "无权访问该项目")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def visible_result(result, user):
|
|
|
|
|
if user["role"] != "manager":
|
|
|
|
|
return result
|
|
|
|
|
# Project evidence remains visible, configuration and SQL stay in the admin area.
|
|
|
|
|
filtered={k: v for k, v in result.items() if k not in ("configuration", "connections")}
|
|
|
|
|
filtered["plan"]={k:result["plan"].get(k) for k in ("id","name","version","description")}
|
|
|
|
|
return filtered
|