ForcePilot/backend/server/routers/external_systems/auth_plugin_router.py

316 lines
12 KiB
Python
Raw Normal View History

"""AuthPlugin 子域 Router。
外部系统限界上下文的认证插件查询 API所有端点直接读取 ``auth_plugins`` 注册表
全局单例框架级 + 协议专属 ``token_refresh_registry`` 全局单例不引入
use_cases 数据源为代码注册的内存对象无持久化需求 adapter_router
元数据端点保持一致
设计依据docs/vibe/v1.1/设计方案/RouterAPI扩展设计/11-auth_plugin_router扩展设计方案.md
"""
from __future__ import annotations
from typing import Any
from fastapi import APIRouter, Depends, Query
from pydantic import BaseModel
from yuxi.external_systems.exceptions import AuthError, SecretResolutionError
from yuxi.external_systems.framework.auth_plugins import (
AUTH_PLUGINS,
get_auth_json_schema,
get_auth_plugin,
get_protocol_auth_plugin,
is_token_based_auth_type,
list_all_protocol_auth_plugins,
list_auth_plugins,
resolve_credential,
)
from yuxi.external_systems.framework.runtime.token_refresh_registry import (
token_refresh_registry,
)
from yuxi.storage.postgres.models_business import User
from server.utils.auth_middleware import get_admin_user, get_required_user
# =============================================================================
# === Request Schemas ===
# =============================================================================
class ValidateAuthConfigRequest(BaseModel):
"""认证配置校验请求体。"""
auth_config: dict[str, Any]
auth_plugin_router = APIRouter(
prefix="/auth-plugins",
tags=["external-systems-auth-plugin"],
)
# =============================================================================
# === 静态路径端点(必须在 /{auth_type} 之前声明) ===
# =============================================================================
@auth_plugin_router.get("", response_model=dict)
async def list_auth_plugins_all(
current_user: User = Depends(get_required_user),
) -> dict[str, Any]:
"""查询系统中所有认证插件(框架级 + 所有协议专属),返回扁平列表。
框架级条目``is_framework=True````adapter_type=None``在前协议专属
条目``is_framework=False````adapter_type=str``在后每个条目额外
标记 ``is_token_based`` ``has_refresh_strategy``便于前端区分静态凭证
Token 类无刷新Token 类有刷新三类认证
``GET /auth-plugins/overview`` 的差异本端点返回扁平列表适合表格展示
overview 端点返回分组结构适合树形展示
"""
framework_plugins = list_auth_plugins(None)
protocol_plugins = list_all_protocol_auth_plugins()
items: list[dict[str, Any]] = []
for plugin in framework_plugins:
auth_type = plugin["type"]
items.append(
{
"type": auth_type,
"display_name": plugin["display_name"],
"is_framework": True,
"adapter_type": None,
"is_token_based": is_token_based_auth_type(auth_type, adapter_type=None),
"has_refresh_strategy": token_refresh_registry.get_strategy(auth_type) is not None,
}
)
for plugin in protocol_plugins:
auth_type = plugin["type"]
adapter_type = plugin["adapter_type"]
items.append(
{
"type": auth_type,
"display_name": plugin["display_name"],
"is_framework": False,
"adapter_type": adapter_type,
"is_token_based": is_token_based_auth_type(auth_type, adapter_type=adapter_type),
"has_refresh_strategy": token_refresh_registry.get_strategy(auth_type) is not None,
}
)
framework_count = len(framework_plugins)
protocol_count = len(protocol_plugins)
return {
"success": True,
"data": {
"items": items,
"total": framework_count + protocol_count,
"framework_count": framework_count,
"protocol_count": protocol_count,
},
}
@auth_plugin_router.get("/overview", response_model=dict)
async def get_auth_plugins_overview(
current_user: User = Depends(get_required_user),
) -> dict[str, Any]:
"""查询认证插件的分组总览(框架级 + 按适配器分组的协议专属)。
适合前端管理页树形渲染``framework`` 列表为框架级插件``protocol`` 字典
``adapter_type`` 分组协议专属插件每个条目标记 ``is_token_based``
``has_refresh_strategy``
``GET /auth-plugins`` 的差异本端点返回分组结构适合树形展示
清单端点返回扁平列表适合表格展示
"""
framework_plugins = list_auth_plugins(None)
protocol_plugins = list_all_protocol_auth_plugins()
framework_items = [
{
"type": plugin["type"],
"display_name": plugin["display_name"],
"is_token_based": is_token_based_auth_type(plugin["type"], adapter_type=None),
"has_refresh_strategy": token_refresh_registry.get_strategy(plugin["type"]) is not None,
}
for plugin in framework_plugins
]
protocol_dict: dict[str, list[dict[str, Any]]] = {}
for plugin in protocol_plugins:
adapter_type = plugin["adapter_type"]
auth_type = plugin["type"]
item = {
"type": auth_type,
"display_name": plugin["display_name"],
"is_token_based": is_token_based_auth_type(auth_type, adapter_type=adapter_type),
"has_refresh_strategy": token_refresh_registry.get_strategy(auth_type) is not None,
}
protocol_dict.setdefault(adapter_type, []).append(item)
total = len(framework_items) + sum(len(items) for items in protocol_dict.values())
return {
"success": True,
"data": {
"framework": framework_items,
"protocol": protocol_dict,
"total": total,
},
}
@auth_plugin_router.get("/token-strategies", response_model=dict)
async def get_token_strategies(
current_user: User = Depends(get_required_user),
) -> dict[str, Any]:
"""查询已注册的 Token 刷新策略清单。
数据来源为 ``token_refresh_registry.list_strategies()``仅返回已注册刷新策略
auth_type 列表所有条目的 ``is_token_based`` ``has_refresh_strategy``
均为 True刷新策略仅注册于 Token 类认证
设计价值前端可据此区分三类认证静态凭证Token 类无自动刷新
Token 类有自动刷新
"""
auth_types = token_refresh_registry.list_strategies()
framework_plugins = list_auth_plugins(None)
display_name_map = {plugin["type"]: plugin["display_name"] for plugin in framework_plugins}
items = [
{
"auth_type": auth_type,
"display_name": display_name_map.get(auth_type, auth_type),
"is_token_based": True,
"has_refresh_strategy": True,
}
for auth_type in auth_types
]
return {"success": True, "data": {"items": items, "total": len(items)}}
# =============================================================================
# === 路径参数端点(/{auth_type} ===
# =============================================================================
@auth_plugin_router.get("/{auth_type}", response_model=dict)
async def get_auth_plugin_detail(
auth_type: str,
adapter_type: str | None = Query(None, description="协议专属插件适配器类型"),
current_user: User = Depends(get_required_user),
) -> dict[str, Any]:
"""查询指定认证插件的详情含展示信息、Token 标记、刷新策略、凭证字段)。
``adapter_type`` None 时查框架级插件不为 None 时优先查协议专属插件
未命中回退框架级协议复用框架插件``is_framework`` 据此判断
框架级插件为 True协议专属插件为 False
``AuthPluginNotFoundError`` 由全局处理器映射为 404Router 内不捕获
"""
plugin = get_auth_plugin(auth_type, adapter_type=adapter_type)
if adapter_type is None:
is_framework = AUTH_PLUGINS.get(auth_type) is not None
response_adapter_type: str | None = None
elif get_protocol_auth_plugin(adapter_type, auth_type) is not None:
is_framework = False
response_adapter_type = adapter_type
else:
# 协议复用框架插件
is_framework = True
response_adapter_type = None
has_refresh_strategy = token_refresh_registry.get_strategy(auth_type) is not None
credential_type = plugin.credential_type
credential_fields = list(credential_type.model_fields.keys()) if credential_type is not None else []
data = {
"type": auth_type,
"display_name": plugin.display_name or auth_type,
"is_framework": is_framework,
"adapter_type": response_adapter_type,
"is_token_based": is_token_based_auth_type(auth_type, adapter_type=adapter_type),
"has_refresh_strategy": has_refresh_strategy,
"credential_type": credential_type.__name__ if credential_type is not None else None,
"credential_fields": credential_fields,
}
return {"success": True, "data": data}
@auth_plugin_router.get("/{auth_type}/config-schema", response_model=dict)
async def get_auth_plugin_config_schema(
auth_type: str,
adapter_type: str | None = Query(None, description="协议专属插件适配器类型"),
current_user: User = Depends(get_required_user),
) -> dict[str, Any]:
"""获取认证配置的 JSON Schema用于前端动态表单渲染。
``adapter_type`` 不为 None 时优先查协议专属凭证类型未命中回退框架级
未知 ``auth_type``插件不存在 ``AuthPluginNotFoundError`` (404)
插件存在但无凭证类型``credential_type=None`` ``DomainValidationError``
(400)均由全局处理器映射Router 内不捕获
"""
# 校验插件存在(抛 AuthPluginNotFoundError → 404Router 内不捕获)
get_auth_plugin(auth_type, adapter_type=adapter_type)
schema = get_auth_json_schema(auth_type, adapter_type=adapter_type)
return {
"success": True,
"data": {
"auth_type": auth_type,
"adapter_type": adapter_type,
"schema": schema,
},
}
@auth_plugin_router.post("/{auth_type}/validate", response_model=dict)
async def validate_auth_config(
auth_type: str,
body: ValidateAuthConfigRequest,
adapter_type: str | None = Query(None, description="协议专属插件适配器类型"),
current_user: User = Depends(get_admin_user),
) -> dict[str, Any]:
"""校验认证配置是否有效(解析凭证,不执行网络调用)。
调用 ``resolve_credential`` 进行完整凭证解析校验含必填字段检查与密钥引用
解析而非仅 Pydantic 结构校验``AuthPluginNotFoundError`` (404) 由全局
处理器映射Router 内不捕获``AuthError`` / ``SecretResolutionError``
校验失败结果Router 内显式捕获并返回 200 + ``valid=False``
不返回解析后的凭证对象含敏感信息
"""
# 校验插件存在(抛 AuthPluginNotFoundError → 404Router 内不捕获)
get_auth_plugin(auth_type, adapter_type=adapter_type)
is_token_based = is_token_based_auth_type(auth_type, adapter_type=adapter_type)
has_refresh_strategy = token_refresh_registry.get_strategy(auth_type) is not None
try:
await resolve_credential(
auth_type,
body.auth_config,
resolver=None,
adapter_type=adapter_type,
)
except (AuthError, SecretResolutionError) as exc:
return {
"success": True,
"data": {
"valid": False,
"errors": [str(exc)],
"is_token_based": is_token_based,
"has_refresh_strategy": has_refresh_strategy,
},
}
return {
"success": True,
"data": {
"valid": True,
"errors": [],
"is_token_based": is_token_based,
"has_refresh_strategy": has_refresh_strategy,
},
}