初始化提交
This commit is contained in:
commit
887c1f449e
963 files changed
+166869
No files matched your search
@@ -0,0 +1,186 @@
|
||||
"""API 层依赖注入。
|
||||
|
||||
从 ``app.state`` 取 lifespan 装配的共享运行时(ToolRuntime / ConfirmationStore / EventStore)。
|
||||
CRUD 类路由直接调用 ``services`` 函数(函数内部自管 ``AsyncSessionLocal``),不在此注入 session。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hmac
|
||||
|
||||
from fastapi import Depends, HTTPException, Request
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.application.agent.confirmation import ConfirmationRegistry
|
||||
from app.application.tool_runtime.runtime import ToolRuntime
|
||||
from app.core.config import settings
|
||||
from app.db.session import get_session
|
||||
from app.modules.runtime.events import EventStore
|
||||
from app.modules.safety.confirmation import ConfirmationStore
|
||||
from app.schemas.auth import CurrentUser as CurrentUserSchema
|
||||
from app.services.auth import decode_token, get_user_by_id, get_user_permissions
|
||||
|
||||
|
||||
def _unavailable(message: str) -> HTTPException:
|
||||
return HTTPException(
|
||||
status_code=503,
|
||||
detail={"error": {"code": "service_unavailable", "message": message}},
|
||||
)
|
||||
|
||||
|
||||
def get_tool_runtime(request: Request) -> ToolRuntime:
|
||||
runtime: ToolRuntime | None = getattr(request.app.state, "tool_runtime", None)
|
||||
if runtime is None:
|
||||
raise _unavailable("工具运行时未就绪(沙箱未配置或未启动)")
|
||||
return runtime
|
||||
|
||||
|
||||
def get_optional_tool_runtime(request: Request) -> ToolRuntime | None:
|
||||
"""宽松版:tool_runtime 未就绪时返回 None(不抛 503),供聚合视图降级呈现。"""
|
||||
return getattr(request.app.state, "tool_runtime", None)
|
||||
|
||||
|
||||
def get_confirmation_store(request: Request) -> ConfirmationStore:
|
||||
store = getattr(request.app.state, "confirmation_store", None)
|
||||
if store is None:
|
||||
raise _unavailable("确认存储未就绪")
|
||||
return store
|
||||
|
||||
|
||||
def get_confirmation_registry(request: Request) -> ConfirmationRegistry:
|
||||
registry: ConfirmationRegistry = getattr(request.app.state, "confirmation_registry", None)
|
||||
if registry is None:
|
||||
raise _unavailable("确认注册表未就绪")
|
||||
return registry
|
||||
|
||||
|
||||
def get_event_store(request: Request) -> EventStore:
|
||||
store = getattr(request.app.state, "event_store", None)
|
||||
if store is None:
|
||||
raise _unavailable("事件存储未就绪")
|
||||
return store
|
||||
|
||||
|
||||
async def require_admin(
|
||||
request: Request,
|
||||
db: AsyncSession = Depends(get_session),
|
||||
) -> None:
|
||||
"""管理端点鉴权:JWT + is_admin 优先,ADMIN_TOKEN fallback。
|
||||
|
||||
JWT 模式下:
|
||||
- 有效 JWT 且 is_admin=True → 放行
|
||||
- 有效 JWT 且 is_admin=False → 403
|
||||
- 无效 JWT → 回退 ADMIN_TOKEN 校验
|
||||
ADMIN_TOKEN 模式下(原 v1 逻辑):
|
||||
- 同现有逻辑,常数时间比较
|
||||
- ADMIN_TOKEN 未配置且无 JWT → 503 fail-closed
|
||||
"""
|
||||
# 0) fail-closed:两种鉴权均未配置时直接拒绝
|
||||
if not settings.jwt_secret and not settings.admin_token:
|
||||
raise HTTPException(
|
||||
status_code=503,
|
||||
detail={"error": {"code": "admin_not_configured", "message": "管理端点未启用。"}},
|
||||
)
|
||||
|
||||
credential = request.headers.get("Authorization", "")
|
||||
if not credential.startswith("Bearer "):
|
||||
raise HTTPException(
|
||||
status_code=401,
|
||||
detail={"error": {"code": "unauthorized", "message": "缺少管理员凭证。"}},
|
||||
)
|
||||
presented = credential[len("Bearer ") :]
|
||||
|
||||
# 1) 尝试 JWT 模式(如果 jwt_secret 已配置)
|
||||
if settings.jwt_secret:
|
||||
try:
|
||||
payload = decode_token(presented)
|
||||
if payload.get("type") == "access":
|
||||
user_id = payload.get("sub")
|
||||
user = await get_user_by_id(db, user_id)
|
||||
if user is None or not user.is_active:
|
||||
raise HTTPException(
|
||||
status_code=401,
|
||||
detail={
|
||||
"error": {"code": "unauthorized", "message": "用户不存在或已禁用。"}
|
||||
},
|
||||
)
|
||||
if not user.is_admin:
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
detail={
|
||||
"error": {"code": "forbidden", "message": "仅管理员可访问此接口。"}
|
||||
},
|
||||
)
|
||||
return # JWT 模式鉴权通过
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception:
|
||||
pass # JWT 无效,回退 ADMIN_TOKEN 校验
|
||||
|
||||
# 2) Fallback: ADMIN_TOKEN 校验(原 v1 逻辑)
|
||||
token = settings.admin_token
|
||||
if not token:
|
||||
raise HTTPException(
|
||||
status_code=503,
|
||||
detail={"error": {"code": "admin_not_configured", "message": "管理端点未启用。"}},
|
||||
)
|
||||
if not hmac.compare_digest(presented, token):
|
||||
raise HTTPException(
|
||||
status_code=401,
|
||||
detail={"error": {"code": "unauthorized", "message": "管理员凭证无效。"}},
|
||||
)
|
||||
|
||||
|
||||
async def get_current_user(
|
||||
request: Request,
|
||||
db: AsyncSession = Depends(get_session),
|
||||
) -> CurrentUserSchema:
|
||||
"""从 Authorization: Bearer <access_token> 解析当前用户。
|
||||
|
||||
JWT 未配置(jwt_secret 为空)时 503 fail-closed。
|
||||
"""
|
||||
if not settings.jwt_secret:
|
||||
raise HTTPException(
|
||||
status_code=503,
|
||||
detail={
|
||||
"error": {
|
||||
"code": "service_unavailable",
|
||||
"message": "认证服务未配置(JWT_SECRET 为空)。",
|
||||
}
|
||||
},
|
||||
)
|
||||
credential = request.headers.get("Authorization", "")
|
||||
if not credential.startswith("Bearer "):
|
||||
raise HTTPException(
|
||||
status_code=401,
|
||||
detail={"error": {"code": "unauthorized", "message": "缺少认证凭证。"}},
|
||||
)
|
||||
token = credential[len("Bearer ") :]
|
||||
try:
|
||||
payload = decode_token(token)
|
||||
except Exception:
|
||||
raise HTTPException(
|
||||
status_code=401,
|
||||
detail={"error": {"code": "unauthorized", "message": "Token 无效或已过期。"}},
|
||||
) from None
|
||||
if payload.get("type") != "access":
|
||||
raise HTTPException(
|
||||
status_code=401,
|
||||
detail={"error": {"code": "unauthorized", "message": "Token 类型不匹配。"}},
|
||||
)
|
||||
user_id = payload.get("sub")
|
||||
user = await get_user_by_id(db, user_id)
|
||||
if user is None or not user.is_active:
|
||||
raise HTTPException(
|
||||
status_code=401,
|
||||
detail={"error": {"code": "unauthorized", "message": "用户不存在或已禁用。"}},
|
||||
)
|
||||
perms = await get_user_permissions(db, user.id)
|
||||
return CurrentUserSchema(
|
||||
user_id=user.id,
|
||||
username=user.username,
|
||||
email=user.email,
|
||||
owner_scope=user.id,
|
||||
is_admin=user.is_admin,
|
||||
permissions=perms,
|
||||
)
|
||||
Reference in new issue
Block a user