"""鉴权依赖:从 Authorization header 解析当前用户,提供权限校验。""" from __future__ import annotations from typing import Any from fastapi import Depends, HTTPException, Query, Request, status from app.db.platform_store import get_platform_store # 无需鉴权的路径前缀(健康检查、登录等) PUBLIC_PATHS = ("/health", "/login", "/system-info") def _extract_token(request: Request) -> str | None: """从 Authorization header 提取 token(格式: Bearer platform-token-{user_id})。""" auth = request.headers.get("Authorization", "") token = auth.replace("Bearer ", "").strip() if token.startswith("platform-token-"): return token[len("platform-token-"):] return None def get_current_user(request: Request) -> dict[str, Any]: """ FastAPI 依赖:解析当前登录用户。 - 公开路径(/health, /login 等)直接放行,返回匿名用户。 - 无 token 或 token 无效时抛 401。 - admin 用户标记为超级管理员,拥有全部权限。 """ path = request.url.path # 去掉路由前缀后判断 for prefix in PUBLIC_PATHS: if path.endswith(prefix): return {"id": None, "username": "anonymous", "role": "viewer", "permissions": [], "protected": False} user_id = _extract_token(request) if not user_id: raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="missing or invalid token") store = get_platform_store() for u in store.users(): if u.get("id") == user_id: return u raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="user not found") def require_admin(current_user: dict[str, Any] = Depends(get_current_user)) -> dict[str, Any]: """FastAPI 依赖:要求当前用户是管理员(role=admin 或 protected)。""" if current_user.get("role") == "admin" or current_user.get("protected"): return current_user raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="admin permission required") def is_admin(user: dict[str, Any]) -> bool: """判断用户是否为管理员(admin 角色或 protected 标记)。""" return user.get("role") == "admin" or user.get("protected", False) def has_resource_access( resource_type: str, resource_id: str, user: dict[str, Any], permission: str = "read", ) -> bool: """ 检查用户对某资源是否有指定权限。 - admin/protected 用户直接放行(旁路)。 - 其他用户检查 acls 表中是否有对应授权。 """ if user.get("role") == "admin" or user.get("protected"): return True store = get_platform_store() acls = store.get_acl(resource_type, resource_id) user_id = user.get("id") user_role = user.get("role") for entry in acls: # 按 user 授权 if entry.get("principal_type") == "user" and entry.get("principal_id") == user_id: if _permission_covers(entry.get("permission"), permission): return True # 按 role 授权 if entry.get("principal_type") == "role" and entry.get("principal_id") == user_role: if _permission_covers(entry.get("permission"), permission): return True return False def _permission_covers(granted: str | None, required: str) -> bool: """权限覆盖判断:write/execute 覆盖 read;admin 覆盖一切。""" if not granted: return False if granted == "admin": return True if granted == required: return True # write 覆盖 read if required == "read" and granted in ("write", "execute"): return True return False def filter_accessible_resource_ids( resource_type: str, all_ids: list[str], user: dict[str, Any], ) -> list[str]: """ 从全部资源 ID 中过滤出当前用户可访问的 ID 列表。 - admin 直接返回全部。 - 普通用户查 acls 表取交集。 """ if user.get("role") == "admin" or user.get("protected"): return all_ids if not all_ids: return [] store = get_platform_store() user_id = user.get("id") user_role = user.get("role") # 查询该用户在该资源类型下有 read 权限的所有 resource_id with store.connect() as conn: rows = conn.execute( """ SELECT DISTINCT resource_id FROM acls WHERE resource_type=? AND ( (principal_type='user' AND principal_id=?) OR (principal_type='role' AND principal_id=?) ) """, (resource_type, user_id, user_role), ).fetchall() accessible = {r["resource_id"] for r in rows} return [rid for rid in all_ids if rid in accessible]