diff --git a/app/api/routers/rbac_operator.py b/app/api/routers/rbac_operator.py index bee7707..e84fc83 100644 --- a/app/api/routers/rbac_operator.py +++ b/app/api/routers/rbac_operator.py @@ -11,7 +11,7 @@ from fastapi import APIRouter, Depends, File, HTTPException, Request, Response, from pydantic import BaseModel from ..dependencies import get_db -from ..schemas.operator import TaskCreateRequest, TaskUpdateRequest, TaskStatusRequest, TaskAssignRequest, TaskRecommendRequest, TaskSelectRecommendRequest, ProviderCreateRequest, ProviderUpdateRequest, ContentCreateRequest, ContentStatusRequest, ContentUpdateRequest, ContentReviewRequest, ConfigUpdateRequest, ComputePingResponse, ComputeBalanceRequest, ComputeProvisionRequest, ComputeProvisionResponse, CourseCreateRequest, CourseStatusRequest, CourseUpdateRequest, ActivityCreateRequest, ActivityStatusRequest, ActivityUpdateRequest, BookingUpdateRequest, TestCreateRequest, TestStatusRequest +from ..schemas.operator import TaskCreateRequest, TaskUpdateRequest, TaskStatusRequest, TaskAssignRequest, TaskRecommendRequest, TaskSelectRecommendRequest, ProviderCreateRequest, ProviderUpdateRequest, ContentCreateRequest, ContentStatusRequest, ContentUpdateRequest, ContentReviewRequest, ConfigUpdateRequest, UrlRuleRequest, ComputePingResponse, ComputeBalanceRequest, ComputeProvisionRequest, ComputeProvisionResponse, CourseCreateRequest, CourseStatusRequest, CourseUpdateRequest, ActivityCreateRequest, ActivityStatusRequest, ActivityUpdateRequest, BookingUpdateRequest, TestCreateRequest, TestStatusRequest from ...rbac import require_permission, require_roles, write_audit from ...infrastructure.repositories import Database from ...services import compute_client @@ -619,6 +619,93 @@ async def list_config( return await db.config.all() + +# ── 站点 URL 白名单 / 黑名单(应用内网页容器认证标识)────────────────────── +URL_WHITELIST_KEY = "site.url_whitelist" +URL_BLACKLIST_KEY = "site.url_blacklist" + + +def _split_host_list(raw: str | None) -> list[str]: + if not raw: + return [] + return [ + h for h in (x.strip() for x in raw.replace(",", ",").split(",")) if h + ] + + +def _normalize_host(host: str) -> str: + h = host.strip().lower() + h = h.replace("https://", "").replace("http://", "") + h = h.split("/")[0].split(":")[0].strip() + if not h or any(ch.isspace() for ch in h): + raise HTTPException(status_code=422, detail="域名格式不正确") + return h + + +async def _get_url_rules(db: Database) -> dict: + return { + "whitelist": _split_host_list(await db.config.get(URL_WHITELIST_KEY)), + "blacklist": _split_host_list(await db.config.get(URL_BLACKLIST_KEY)), + } + + +async def _save_url_rules(db: Database, rules: dict) -> None: + await db.config.set(URL_WHITELIST_KEY, ",".join(rules["whitelist"]), + description="应用内网页容器白名单(域名)") + await db.config.set(URL_BLACKLIST_KEY, ",".join(rules["blacklist"]), + description="应用内网页容器黑名单(域名,命中禁止打开)") + + +@router.get("/site/url-rules", summary="站点 URL 白名单/黑名单列表") +async def list_url_rules( + db: Database = Depends(get_db), + _u: dict = Depends(require_roles("operator")), +): + return await _get_url_rules(db) + + +@router.post("/site/url-rules", summary="新增白名单/黑名单域名") +async def add_url_rule( + req: UrlRuleRequest, + request: Request, + db: Database = Depends(get_db), + actor: dict = Depends(require_permission("action:config.manage")), +): + if req.rule_type not in ("whitelist", "blacklist"): + raise HTTPException(status_code=422, detail="rule_type 仅支持 whitelist/blacklist") + host = _normalize_host(req.host) + rules = await _get_url_rules(db) + key = req.rule_type + if host in rules[key]: + raise HTTPException(status_code=409, detail="该域名已在名单中") + rules[key].append(host) + await _save_url_rules(db, rules) + await write_audit(db, action="url_rule.add", resource="site", resource_id=f"{key}:{host}", + detail=req.host, user=actor, request=request) + return rules + + +@router.delete("/site/url-rules", summary="删除白名单/黑名单域名") +async def remove_url_rule( + rule_type: str, + host: str, + request: Request, + db: Database = Depends(get_db), + actor: dict = Depends(require_permission("action:config.manage")), +): + if rule_type not in ("whitelist", "blacklist"): + raise HTTPException(status_code=422, detail="rule_type 仅支持 whitelist/blacklist") + host = _normalize_host(host) + rules = await _get_url_rules(db) + key = rule_type + if host not in rules[key]: + raise HTTPException(status_code=404, detail="该域名不在名单中") + rules[key].remove(host) + await _save_url_rules(db, rules) + await write_audit(db, action="url_rule.remove", resource="site", resource_id=f"{key}:{host}", + detail=host, user=actor, request=request) + return rules + @router.put("/config/{key}", summary="更新系统配置") async def set_config( key: str, diff --git a/app/api/schemas/operator.py b/app/api/schemas/operator.py index e69a810..9e569be 100644 --- a/app/api/schemas/operator.py +++ b/app/api/schemas/operator.py @@ -164,6 +164,12 @@ class ConfigUpdateRequest(BaseModel): description: str = "" +class UrlRuleRequest(BaseModel): + """站点 URL 白名单 / 黑名单单条操作。""" + rule_type: str # whitelist | blacklist + host: str + + # ── 任务管理 ──────────────────────────────────────────────────────────────