# 平台权限设计文档

> 版本：v1.0
> 日期：2026-08-02
> 状态：设计基线，供后端实现和前端联调参照

---

## 目录

1. [设计目标](#1-设计目标)
2. [整体架构](#2-整体架构)
3. [角色体系](#3-角色体系)
4. [页面权限码](#4-页面权限码)
5. [资源所有权与可见性](#5-资源所有权与可见性)
6. [资源级 ACL（访问控制列表）](#6-资源级-acl访问控制列表)
7. [GPU 算力分配与隔离](#7-gpu-算力分配与隔离)
8. [审批拦截机制](#8-审批拦截机制)
9. [审计日志](#9-审计日志)
10. [接口鉴权流程](#10-接口鉴权流程)
11. [数据库表结构](#11-数据库表结构)
12. [API 接口清单](#12-api-接口清单)
13. [前端权限控制](#13-前端权限控制)
14. [安全设计补充](#14-安全设计补充)
15. [实施计划](#15-实施计划)

---

## 1. 设计目标

| 目标 | 说明 |
|---|---|
| **数据隔离** | 用户自己创建的数据集、模型、训练任务默认只有自己可见可操作；管理员可见全部 |
| **权限分层** | 页面级（菜单/路由可见性）+ 资源级（单条数据的读/写/删）两层控制 |
| **GPU 管控** | 多卡服务器上，管理员可指定哪些用户能使用哪些 GPU 卡 |
| **审批拦截** | 删除他人资源、停止他人任务、发布模型等高风险操作需审批或管理员旁路 |
| **审计可追溯** | 所有写操作和敏感操作产生审计日志，可按用户、动作、资源、时间筛选 |
| **权限最小变更** | 只有管理员可修改用户角色和权限码；普通用户无法提权 |

---

## 2. 整体架构

```
┌──────────────────────────────────────────────────────┐
│                     前端（Vue3）                       │
│  ┌────────────┐  ┌────────────┐  ┌───────────────┐ │
│  │ 路由守卫    │  │ 侧边栏过滤  │  │ 页面内按钮控制  │ │
│  │ (permission)│  │ (permission)│  │ (ACL/owner)   │ │
│  └──────┬─────┘  └──────┬─────┘  └───────┬───────┘ │
│         └───────────────┴─────────────────┘          │
│                         │ HTTP (Bearer token)        │
└─────────────────────────┼────────────────────────────┘
                          │
┌─────────────────────────┼────────────────────────────┐
│                    后端（FastAPI）                     │
│  ┌──────────────┐  ┌────┴───────┐  ┌──────────────┐  │
│  │ get_current  │  │ 资源可见性  │  │ GPU 分配校验  │  │
│  │ _user (鉴权) │  │ 过滤器      │  │              │  │
│  └──────┬───────┘  └────┬───────┘  └──────┬───────┘  │
│         │               │                  │          │
│  ┌──────┴───────────────┴──────────────────┘          │
│  │              PlatformStore                        │  │
│  │  users | acls | roles | gpu_assignments |         │  │
│  │  datasets | models | fine_tune_tasks | ...        │  │
│  └───────────────────────────────────────────────────│  │
│  │              audit_logs (审计日志)                │  │
│  └───────────────────────────────────────────────────│  │
└─────────────────────────────────────────────────────────┘
```

**鉴权链路**：
1. 请求到达 → `get_current_user` 从 `Authorization: Bearer platform-token-{user_id}` 解析当前用户
2. 页面级权限 → 检查 `user.permissions` 是否包含路由对应的权限码
3. 资源级权限 → 检查资源的 `created_by` 字段（所有权）或 `acls` 表（ACL 授权）
4. GPU 权限 → 检查 `gpu_assignments` 表确认用户是否被分配了请求的 GPU

---

## 3. 角色体系

### 3.1 内置角色

| 角色 code | 中文名 | 说明 |
|---|---|---|
| `admin` | 超级管理员 | 拥有全部权限码；可见全部资源；可管理用户和 GPU 分配 |
| `operator` | 操作员 | 可创建/操作自己的数据集、模型、训练任务；不可管理用户 |
| `viewer` | 观察员 | 只读权限；可查看被授权的资源；不可创建或修改 |
| `guest` | 访客 | 仅登录和看板；无业务操作权限（扩展预留） |

### 3.2 角色与权限码映射

| 权限码 | admin | operator | viewer |
|---|---|---|---|
| `dashboard` | ✅ | ✅ | ✅ |
| `fine-tune` | ✅ | ✅ | — |
| `model-eval` | ✅ | ✅ | — |
| `model-inference` | ✅ | ✅ | — |
| `model-manage` | ✅ | ✅ | — |
| `dataset` | ✅ | ✅ | — |
| `data-process` | ✅ | ✅ | — |
| `data-convert` | ✅ | ✅ | — |
| `compute` | ✅ | ✅ | — |
| `hardware` | ✅ | ✅ | ✅ |
| `logs` | ✅ | ✅ | ✅ |
| `user-settings` | ✅ | — | — |

### 3.3 权限修改规则

- **只有 admin 角色的用户**可以修改其他用户的角色和权限码
- admin 用户的 `protected=True` 标记，防止被删除或降级
- 权限修改操作产生审计日志：`action=user.permission.update`
- 用户可以查看自己的权限，不能修改自己的权限

---

## 4. 页面权限码

| 权限码 | 对应路由 | 功能 |
|---|---|---|
| `dashboard` | `/dashboard` | 服务看板 |
| `fine-tune` | `/fine-tune`, `/fine-tune/create`, `/training-log/:id` | 模型训练 |
| `model-eval` | `/model-eval`, `/model-eval/create`, `/model-eval/:id` | 模型评测 |
| `model-inference` | `/model-inference`, `/model-inference/create`, `/model-inference/chat/:id` | 模型推理 |
| `model-manage` | `/model-manage`, `/model-manage/create`, `/model-manage/:id/edit`, `/model-manage/merge` | 模型管理 |
| `dataset` | `/dataset`, `/dataset/create`, `/dataset/:id/preview` | 数据集管理 |
| `data-process` | `/data-process`, `/data-process/create`, `/data-process/:id` | 数据处理 |
| `data-convert` | `/data-convert`, `/tools` | 数据转换与工具 |
| `compute` | `/compute` | 算力节点 |
| `hardware` | `/hardware` | 平台性能 |
| `logs` | `/logs`, `/training-log/:id` | 查看日志 |
| `user-settings` | `/user-settings`, `/tenants`, `/projects`, `/approvals`, `/audit-logs` | 系统设置与平台治理 |

---

## 5. 资源所有权与可见性

### 5.1 所有权模型

每个用户可创建的资源都携带 `created_by`（或 `owner_id`）字段，标识资源所有者。

| 资源类型 | 表 | 所有者字段 | 说明 |
|---|---|---|---|
| 数据集 | `datasets` | `created_by` | 用户上传/创建的数据集 |
| 基座模型 | `models` | `created_by` | 登记的本地/API 模型 |
| 训练产物 | `trained_models` | `created_by` | 微调产出的模型 |
| 训练任务 | `fine_tune_tasks` | `payload.created_by` | 微调任务 |
| 评测任务 | `eval_tasks` | `created_by` | 评测任务 |
| 推理任务 | `compare_tasks` (payload) | `created_by` | 推理/对比任务 |
| 数据处理任务 | `data_process_tasks` | `created_by` | 数据处理任务 |
| 数据转换任务 | `data_convert_jobs` | `created_by` | 数据转换任务 |

### 5.2 可见性规则

```
资源列表查询过滤逻辑：

if user.role == "admin":
    返回全部资源
elif resource.created_by == user.id:
    返回（资源所有者可见自己的资源）
elif acl 中存在 (principal_type="user", principal_id=user.id, permission 包含 "read"):
    返回（被 ACL 显式授权的资源）
elif acl 中存在 (principal_type="role", principal_id=user.role, permission 包含 "read"):
    返回（被角色级 ACL 授权的资源）
else:
    不可见
```

### 5.3 所有权操作矩阵

| 操作 | admin | 资源所有者 | 其他被授权用户 | 其他用户 |
|---|---|---|---|---|
| 查看资源 | ✅ 全部 | ✅ 自己的 | ✅ ACL 授权范围内 | ❌ |
| 编辑资源 | ✅ | ✅ 自己的 | ✅ ACL 含 write 时 | ❌ |
| 删除资源 | ✅ | ✅ 自己的（需审批） | ❌ | ❌ |
| 分享/授权 | ✅ | ✅ 自己的 | ❌ | ❌ |
| 使用资源（训练/推理/评测） | ✅ | ✅ 自己的 | ✅ ACL 含 execute 时 | ❌ |

### 5.4 数据集可见性示例

```
用户 A 创建了数据集 ds_A1 → 只有 A 和 admin 可见
用户 A 通过 ACL 把 ds_A1 的 read 权限授给用户 B → B 也可见
用户 A 通过 ACL 把 ds_A1 的 write 权限授给 operator 角色 → 所有 operator 可编辑
管理员可在任何数据集上设置 ACL
```

---

## 6. 资源级 ACL（访问控制列表）

### 6.1 ACL 表结构

```sql
CREATE TABLE IF NOT EXISTS acls (
    id              TEXT PRIMARY KEY,
    resource_type   TEXT NOT NULL,      -- 资源类型: dataset / model / trained_model / fine_tune_task / ...
    resource_id     TEXT NOT NULL,      -- 资源 ID
    principal_type  TEXT NOT NULL,      -- 授权主体类型: user / role
    principal_id    TEXT NOT NULL,      -- 授权主体 ID: user_id 或 role name
    permission      TEXT NOT NULL,      -- 权限: read / write / execute / download / delete / admin
    create_time     TEXT
);
```

### 6.2 权限粒度

| 权限值 | 含义 | 覆盖关系 |
|---|---|---|
| `read` | 查看资源详情、列表 | — |
| `write` | 编辑资源内容/元数据 | 覆盖 `read` |
| `execute` | 使用资源（如用数据集训练、用模型推理） | 覆盖 `read` |
| `download` | 下载资源文件 | 独立权限 |
| `delete` | 删除资源 | 独立权限（通常需审批） |
| `admin` | 完全控制（含 ACL 管理） | 覆盖以上全部 |

### 6.3 ACL 管理接口

| 接口 | 方法 | 权限要求 | 说明 |
|---|---|---|---|
| `/resources/{type}/{id}/acl` | GET | admin 或资源所有者 | 查询资源 ACL |
| `/resources/{type}/{id}/acl` | PUT | admin 或资源所有者 | 设置资源 ACL（全量替换） |

### 6.4 ACL 管理规则

- **admin** 可以在任何资源上设置 ACL
- **资源所有者** 可以在自己的资源上设置 ACL
- **被授权用户** 不能转授自己获得的权限
- ACL 变更产生审计日志：`action=resource.acl.set`
- 设置 ACL 时全量替换该资源的所有 ACL 条目

### 6.5 前端 ACL 管理入口

在数据集详情、模型详情、训练任务详情页面提供「资源授权」按钮，弹出 ACL 管理对话框：
- 显示当前 ACL 列表（主体类型 + 主体名称 + 权限勾选）
- 支持按用户或按角色添加授权
- 权限以多选框形式展示（read / write / execute / download / delete）

---

## 7. GPU 算力分配与隔离

### 7.1 设计背景

服务器可能安装多张 GPU 卡（如 8×A100），需要精细化管控：
- 管理员指定哪些用户可以使用哪些 GPU 卡
- 未被分配的 GPU 卡对用户不可见或不可选
- admin 可以使用全部 GPU

### 7.2 GPU 分配表

```sql
CREATE TABLE IF NOT EXISTS gpu_assignments (
    id          TEXT PRIMARY KEY,
    gpu_id      TEXT NOT NULL,          -- gpus 表的外键（node_id + gpu_index 组合）
    node_id     TEXT NOT NULL,          -- 算力节点 ID
    gpu_index   INTEGER NOT NULL,       -- GPU 卡序号
    user_id     TEXT NOT NULL,          -- 被分配的用户 ID
    assigned_by TEXT,                  -- 分配操作人 ID（admin）
    assigned_at TEXT NOT NULL,          -- 分配时间
    UNIQUE (node_id, gpu_index, user_id)  -- 一张卡可分配给多个用户，但每对 (卡, 用户) 唯一
);
```

### 7.3 分配规则

| 规则 | 说明 |
|---|---|
| 谁可分配 | 只有 `admin` 角色可以分配 GPU |
| admin 使用 | admin 可使用全部 GPU，不需要显式分配 |
| 普通用户 | 只能使用 `gpu_assignments` 中分配给自己的 GPU |
| 共享分配 | 一张 GPU 可分配给多个用户（非独占），但同时只能被一个任务占用 |
| 默认策略 | 新用户默认不分配任何 GPU，由管理员显式分配 |

### 7.4 GPU 分配接口

| 接口 | 方法 | 权限 | 说明 |
|---|---|---|---|
| `/compute/gpu-assignments` | GET | admin | 查看全部分配关系 |
| `/compute/gpu-assignments` | POST | admin | 批量分配（body: `{ assignments: [{ node_id, gpu_index, user_id }] }`） |
| `/compute/gpu-assignments/{id}` | DELETE | admin | 撤销某条分配 |
| `/compute/my-gpus` | GET | 登录用户 | 查看自己可用的 GPU 列表 |

### 7.5 训练/评测/推理 GPU 选择校验

当普通用户创建训练任务、评测任务、推理任务并选择 GPU 时：
1. 后端检查 `gpu_assignments` 表，确认用户被分配了所选 GPU
2. 未被分配的 GPU → 返回 403 `"无权使用 GPU {node}:{index}"`
3. admin 用户跳过此校验

### 7.6 前端 GPU 选择交互

- 普通用户在创建任务选择 GPU 时，下拉列表只显示自己被分配的 GPU
- admin 用户在下拉列表中可看到全部 GPU
- 未分配任何 GPU 的用户，GPU 选择区域显示提示："未分配 GPU，请联系管理员"

---

## 8. 审批拦截机制

### 8.1 需要审批的操作

| 操作 | 触发条件 | 审批动作 code |
|---|---|---|
| 删除他人数据集 | 非 admin 删除 `created_by != user.id` 的数据集 | `dataset.delete` |
| 删除他人模型 | 非 admin 删除 `created_by != user.id` 的模型 | `model.delete` |
| 停止他人训练任务 | 非 admin 停止 `created_by != user.id` 的任务 | `fine_tune.stop` |
| 发布模型到推理服务 | 任何用户（含 admin）发布到生产环境 | `model_service.publish` |
| 删除项目空间 | 存在待审批变更时拒绝 | `project.delete` |
| 归档项目空间 | 存在待审批变更时拒绝 | `project.archive` |
| 导出训练产物 | 非 admin 导出他人训练的模型 | `trained_model.export` |

### 8.2 审批流程

```
普通用户发起高风险操作
         │
         ▼
  ┌──────────────┐        ┌──────────────────────┐
  │ admin 旁路？ │───是──▶│ 直接执行 + 审计日志   │
  └──────┬───────┘        └──────────────────────┘
         │ 否
         ▼
  ┌──────────────────────┐
  │ 创建审批实例          │
  │ status=pending       │
  │ 返回 202（待审批）    │
  └──────────┬───────────┘
             │
             ▼
  ┌──────────────────────┐
  │ 管理员审批            │
  │ POST /approvals/:id  │
  │ /steps/:idx/decision │
  └──────────┬───────────┘
             │
      ┌──────┴──────┐
      │             │
   approved      rejected
      │             │
      ▼             ▼
  执行操作      不执行
  +审计日志    +审计日志
```

### 8.3 审批模板

审批模板定义了特定操作需要几步审批、每步的审批人是谁：

```json
{
  "id": "tpl_001",
  "name": "删除数据集审批",
  "action": "dataset.delete",
  "steps": [
    { "approver_id": "u_admin", "step_name": "管理员审核" }
  ]
}
```

### 8.4 审批拦截点

在项目模块的 `_require_no_pending_approval` 函数中，当存在待审批实例时拒绝执行新操作。其他模块通过 `_require_approval_or_admin` 函数实现 admin 旁路或创建审批实例。

---

## 9. 审计日志

### 9.1 审计范围

所有写操作和敏感操作必须产生审计日志：

| 动作分类 | action 示例 |
|---|---|
| 用户管理 | `user.create`, `user.update`, `user.delete`, `user.permission.update` |
| 租户管理 | `tenant.create`, `tenant.update`, `tenant.quota.set`, `tenant.retention.set` |
| 项目管理 | `project.create`, `project.update`, `project.archive`, `project.delete`, `project.member.add`, `project.member.update`, `project.member.remove` |
| 资源 ACL | `resource.acl.set` |
| 模型管理 | `model.create`, `model.update`, `model.delete`, `model.merge` |
| 数据集 | `dataset.create`, `dataset.update`, `dataset.delete`, `dataset.upload` |
| 训练任务 | `fine_tune.create`, `fine_tune.start`, `fine_tune.stop`, `fine_tune.delete` |
| 评测任务 | `eval.create`, `eval.start`, `eval.stop` |
| 推理任务 | `inference.create`, `inference.start`, `inference.stop` |
| 审批 | `approval.create`, `approval.decide` |
| 留存策略 | `retention.create`, `retention.update`, `retention.delete` |
| GPU 分配 | `gpu.assign`, `gpu.unassign` |

### 9.2 审计日志字段

```sql
CREATE TABLE IF NOT EXISTS audit_logs (
    id          TEXT PRIMARY KEY,
    time        TEXT NOT NULL,         -- ISO8601 时间戳
    tenant_id   TEXT,                 -- 租户 ID（可选）
    project_id  TEXT,                 -- 项目 ID（可选）
    actor_id    TEXT,                 -- 操作人 ID
    action      TEXT NOT NULL,         -- 动作类型
    target_type TEXT NOT NULL,         -- 目标资源类型
    target_id   TEXT,                 -- 目标资源 ID
    detail      TEXT,                 -- 详情摘要
    client_ip   TEXT                  -- 客户端 IP
);
```

### 9.3 查询与导出

| 接口 | 方法 | 说明 |
|---|---|---|
| `/system/audit-logs` | GET | 分页查询，支持按 tenant_id / project_id / actor_id / action / target_type / start_time / end_time 筛选 |
| `/system/audit-logs/export` | GET | CSV 导出，与应用查询相同的过滤条件 |

---

## 10. 接口鉴权流程

### 10.1 Token 格式

```
Authorization: Bearer platform-token-{user_id}
```

登录成功后返回 `token` 和 `user` 信息。Token 中编码了 `user_id`，后端通过 `get_current_user` 解析。

### 10.2 鉴权层级

```
请求到达
  │
  ├─ 1. 公开路径检查（/health, /login, /system-info）→ 直接放行
  │
  ├─ 2. Token 解析 → get_current_user
  │     ├─ 无 token / token 无效 → 401
  │     └─ 用户不存在 / 状态 disabled → 401
  │
  ├─ 3. 页面级权限码检查（路由守卫 / Depends）
  │     └─ user.permissions 不含所需权限码 → 403
  │
  ├─ 4. 资源级权限检查（路由函数内）
  │     ├─ admin → 全部放行
  │     ├─ resource.created_by == user.id → 放行
  │     ├─ ACL 检查 has_resource_access() → 有授权则放行
  │     └─ 否则 → 403
  │
  ├─ 5. GPU 权限检查（训练/评测/推理创建时）
  │     ├─ admin → 全部放行
  │     ├─ gpu_assignments 检查 → 有分配则放行
  │     └─ 否则 → 403
  │
  └─ 6. 审批拦截检查（高风险操作）
        ├─ admin → 旁路，直接执行
        ├─ 无待审批实例 → 可执行
        ├─ 有待审批实例 → 409 "存在待审批的变更"
        └─ 需要审批 → 202 "已创建审批实例"
```

### 10.3 FastAPI 依赖注入

```python
# 任何需要登录的接口
@router.get("/datasets")
async def list_datasets(user: dict = Depends(get_current_user)):
    ...

# 需要管理员权限的接口
@router.post("/users")
async def create_user(user: dict = Depends(require_admin)):
    ...

# 需要资源级权限检查的接口
@router.delete("/datasets/{dataset_id}")
async def delete_dataset(
    dataset_id: str,
    user: dict = Depends(get_current_user),
):
    if not has_resource_access("dataset", dataset_id, user, "delete"):
        raise HTTPException(403, "forbidden")
    ...
```

---

## 11. 数据库表结构

### 11.1 现有表（已实现）

| 表名 | 用途 |
|---|---|
| `users` | 用户表（id, username, password_hash, role, status, permissions, protected） |
| `roles` | 角色定义（name, permissions） |
| `sessions` | 登录会话（user_id, issued_at, expires_at, ip） |
| `acls` | 资源访问控制列表（resource_type, resource_id, principal_type, principal_id, permission） |
| `audit_logs` | 审计日志（actor_id, action, target_type, target_id, time） |
| `datasets` | 数据集（需补充 `created_by` 字段） |
| `models` | 基座模型（需补充 `created_by` 字段） |
| `trained_models` | 训练产物（需补充 `created_by` 字段） |
| `fine_tune_tasks` | 训练任务（payload 中存储 `created_by`） |
| `gpus` | GPU 设备（node_id, gpu_index, uuid, name, memory） |
| `compute_nodes` | 算力节点 |
| `tenants` | 租户 |
| `projects` | 项目空间 |
| `project_members` | 项目成员 |
| `approval_templates` | 审批模板 |
| `approval_instances` | 审批实例 |
| `retention_policies` | 留存策略 |

### 11.2 需新增/补充的表和字段

#### 新增 `gpu_assignments` 表

```sql
CREATE TABLE IF NOT EXISTS gpu_assignments (
    id           TEXT PRIMARY KEY,
    node_id      TEXT NOT NULL REFERENCES compute_nodes(id) ON DELETE CASCADE,
    gpu_index    INTEGER NOT NULL,
    user_id      TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    assigned_by  TEXT,
    assigned_at  TEXT NOT NULL,
    UNIQUE (node_id, gpu_index, user_id)
);
CREATE INDEX IF NOT EXISTS idx_gpu_assignments_user ON gpu_assignments(user_id);
CREATE INDEX IF NOT EXISTS idx_gpu_assignments_gpu ON gpu_assignments(node_id, gpu_index);
```

#### 补充 `created_by` 字段

```sql
-- 数据集表补充所有者
ALTER TABLE datasets ADD COLUMN IF NOT EXISTS created_by TEXT;

-- 基座模型表补充所有者
ALTER TABLE models ADD COLUMN IF NOT EXISTS created_by TEXT;

-- 训练产物表补充所有者
ALTER TABLE trained_models ADD COLUMN IF NOT EXISTS created_by TEXT;

-- 评测任务表补充所有者
ALTER TABLE eval_tasks ADD COLUMN IF NOT EXISTS created_by TEXT;

-- 推理任务表补充所有者
-- 注意：inference_tasks 表尚未创建，后续建表时直接包含 created_by 字段
```

---

## 12. API 接口清单

### 12.1 鉴权接口

| 接口 | 方法 | 鉴权 | 说明 |
|---|---|---|---|
| `/modelTF/login` | POST | 公开 | 登录，返回 token + user |
| `/modelTF/me` | GET | Bearer token | 获取当前用户信息 |
| `/modelTF/users` | GET | admin | 用户列表 |
| `/modelTF/users` | POST | admin | 创建用户 |
| `/modelTF/users/:id` | PUT | admin | 更新用户（角色/状态/权限） |
| `/modelTF/users/:id` | DELETE | admin | 删除用户（protected 用户不可删） |
| `/modelTF/users/:id/reset-password` | POST | admin | 重置密码 |
| `/modelTF/system/permissions/codes` | GET | 登录 | 权限码清单 |
| `/modelTF/system/permissions` | GET | 登录 | 权限码 + 角色定义 |

### 12.2 资源 ACL 接口

| 接口 | 方法 | 鉴权 | 说明 |
|---|---|---|---|
| `/modelTF/resources/:type/:id/acl` | GET | admin 或所有者 | 查询资源 ACL |
| `/modelTF/resources/:type/:id/acl` | PUT | admin 或所有者 | 设置资源 ACL |

### 12.3 GPU 分配接口

| 接口 | 方法 | 鉴权 | 说明 |
|---|---|---|---|
| `/modelTF/compute/gpu-assignments` | GET | admin | 查看全部分配 |
| `/modelTF/compute/gpu-assignments` | POST | admin | 批量分配 |
| `/modelTF/compute/gpu-assignments/:id` | DELETE | admin | 撤销分配 |
| `/modelTF/compute/my-gpus` | GET | 登录 | 查看自己可用 GPU |

### 12.4 审批接口

| 接口 | 方法 | 鉴权 | 说明 |
|---|---|---|---|
| `/modelTF/approvals/templates` | GET/POST | admin | 审批模板列表/创建 |
| `/modelTF/approvals` | GET/POST | 登录 | 审批实例列表/创建 |
| `/modelTF/approvals/:id` | GET | 登录 | 审批实例详情 |
| `/modelTF/approvals/:id/steps/:idx/decision` | POST | 审批人 | 审批决策 |

### 12.5 审计接口

| 接口 | 方法 | 鉴权 | 说明 |
|---|---|---|---|
| `/modelTF/system/audit-logs` | GET | admin | 审计日志分页查询 |
| `/modelTF/system/audit-logs/export` | GET | admin | CSV 导出 |

### 12.6 租户/项目接口

| 接口 | 方法 | 鉴权 | 说明 |
|---|---|---|---|
| `/modelTF/tenants` | GET/POST | admin | 租户列表/创建 |
| `/modelTF/tenants/:id` | GET/PUT | admin | 租户详情/更新 |
| `/modelTF/tenants/:id/quota` | PUT | admin | 设置配额 |
| `/modelTF/tenants/:id/retention-policy` | PUT | admin | 绑定留存策略 |
| `/modelTF/projects` | GET/POST | 登录 | 项目列表/创建 |
| `/modelTF/projects/:id` | GET/PUT | 登录 | 项目详情/更新 |
| `/modelTF/projects/:id/archive` | POST | admin 或所有者 | 归档（审批拦截） |
| `/modelTF/projects/:id/members` | GET/POST | 登录 | 成员列表/添加 |
| `/modelTF/projects/:id/members/:uid` | PUT/DELETE | admin 或所有者 | 改角色/移除 |

### 12.7 留存策略接口

| 接口 | 方法 | 鉴权 | 说明 |
|---|---|---|---|
| `/modelTF/retention-policies` | GET/POST | admin | 策略列表/创建 |
| `/modelTF/retention-policies/:id` | GET/PUT/DELETE | admin | 策略详情/更新/删除 |

---

## 13. 前端权限控制

### 13.1 路由守卫

```typescript
// router/index.ts
router.beforeEach((to, _from, next) => {
  const auth = useAuthStore()
  auth.syncSession()

  if (to.meta.public) {
    next()
    return
  }

  if (!auth.isLoggedIn) {
    next({ name: 'login' })
    return
  }

  if (!to.meta.skipPermission) {
    const permission = requiredPermission(to.path, to.meta.permission)
    if (permission && !auth.hasPermission(permission)) {
      next({ name: 'permission-denied', replace: true })
      return
    }
  }

  next()
})
```

### 13.2 侧边栏过滤

```typescript
// layouts/MainLayout.vue
const visibleMenus = computed(() =>
  allMenus.filter(menu => {
    if (!menu.permission) return true
    return auth.hasPermission(menu.permission)
  })
)
```

### 13.3 资源级按钮控制

```vue
<!-- 数据集详情页 -->
<template>
  <el-button v-if="canEdit" @click="handleEdit">编辑</el-button>
  <el-button v-if="canDelete" @click="handleDelete">删除</el-button>
  <el-button v-if="canManageAcl" @click="showAclDialog = true">资源授权</el-button>
</template>

<script setup>
const canEdit = computed(() =>
  isAdmin.value || resource.value.created_by === userId.value
)
const canDelete = computed(() =>
  isAdmin.value || resource.value.created_by === userId.value
)
const canManageAcl = computed(() =>
  isAdmin.value || resource.value.created_by === userId.value
)
</script>
```

### 13.4 GPU 选择过滤

```vue
<!-- 创建训练任务页 -->
<template>
  <el-select v-model="selectedGpus" multiple>
    <el-option
      v-for="gpu in availableGpus"
      :key="gpu.id"
      :label="`${gpu.node_name} GPU ${gpu.gpu_index}`"
      :value="gpu.id"
    />
  </el-select>
  <el-alert v-if="availableGpus.length === 0 && !isAdmin" type="warning">
    未分配 GPU，请联系管理员
  </el-alert>
</template>

<script setup>
// 普通用户只看到 my-gpus 返回的列表
// admin 看到全部 GPU
const availableGpus = ref([])
async function loadGpus() {
  if (isAdmin.value) {
    availableGpus.value = await getAllGpus()
  } else {
    availableGpus.value = await getMyGpus()
  }
}
</script>
```

---

## 14. 安全设计补充

### 14.1 密码安全

- 密码使用 PBKDF2-SHA256 存储（salt + 390000 次迭代）
- 旧系统明文密码在首次登录时自动升级为哈希
- 管理员可重置用户密码，用户不可自行修改密码（本期设计）
- 默认密码：`platform123`（创建用户时由管理员设定）

### 14.2 会话安全

| 规则 | 说明 |
|---|---|
| Token 格式 | `platform-token-{user_id}` |
| 会话超时 | 默认 30 分钟无操作自动过期 |
| 并发会话 | 同一用户可有多会话，各自独立计时 |
| 会话续期 | 前端定时调用 `auth.refresh()` 续期 |
| 强制下线 | admin 可通过修改用户 status=disabled 使其 token 失效 |

### 14.3 操作限流

| 接口 | 限制 |
|---|---|
| `/login` | 同一 IP 5 次/分钟，失败后 30 秒冷却 |
| 文件上传 | 单文件最大由配置控制，默认 2GB |
| 训练任务创建 | 同一用户并发运行任务数受 GPU 分配限制 |

### 14.4 数据安全

| 规则 | 说明 |
|---|---|
| 软删除 | 数据集、模型、任务使用 `deleted_at` 标记，保留审计可追溯 |
| 敏感字段 | API 密钥（`api_key`）在列表接口不返回明文 |
| 下载审计 | 数据集下载产生审计日志，记录下载人和时间 |
| 导出审计 | 训练产物导出产生审计日志 |

### 14.5 多租户隔离

| 规则 | 说明 |
|---|---|
| 租户隔离 | 同一租户内的资源相互可见；跨租户默认不可见 |
| 项目隔离 | 项目内资源受项目 ACL 控制；项目间默认不可见 |
| admin 旁路 | admin 可跨租户/项目访问全部资源 |
| 配额管控 | 租户级配额限制 GPU 并发数、存储容量、最大项目数 |

---

## 15. 实施计划

### 15.1 已实现

| 功能 | 状态 |
|---|---|
| 登录/会话/Token | ✅ 已实现 |
| 用户 CRUD + 权限码 | ✅ 已实现 |
| 角色定义 | ✅ 已实现 |
| 资源 ACL（acls 表 + 接口） | ✅ 已实现 |
| 审计日志（查询 + 导出） | ✅ 已实现 |
| 审批模板/实例 | ✅ 已实现 |
| 项目空间 + 成员 | ✅ 已实现 |
| 租户 + 配额 + 留存 | ✅ 已实现 |
| 审批拦截（项目归档/删除） | ✅ 已实现 |
| 资源所有权 ACL 字段适配（subject_type/permissions[]） | ✅ 已实现 |

### 15.2 待实现

| 功能 | 优先级 | 涉及表/接口 |
|---|---|---|
| GPU 分配表 + 接口 | P0 | `gpu_assignments` 表 + `/compute/gpu-assignments` + `/compute/my-gpus` |
| 资源 `created_by` 字段补充 | P0 | `datasets` / `models` / `trained_models` / `eval_tasks` 表 ALTER |
| 资源列表按 `created_by` + ACL 过滤 | P0 | `platform_store.py` 中 datasets/models/tasks 列表方法 |
| GPU 选择校验（训练/评测/推理创建时） | P0 | `platform.py` 中 create_task/eval/inference |
| 前端 GPU 下拉过滤 | P1 | 前端创建任务页面 |
| 前端资源授权按钮 | P1 | 前端数据集/模型/任务详情页 |
| 前端权限管理页面优化 | P1 | 前端用户设置页面 |
| 审批拦截扩展（删除数据集/模型/停止任务） | P1 | `platform.py` 中 delete/stop 接口 |
| 密码安全策略（用户自行修改） | P2 | 新增 `/users/me/password` 接口 |
| 操作限流（login 限流） | P2 | 中间件或 SlowAPI |
| 多租户隔离（按 tenant_id 过滤） | P2 | 各列表接口增加 tenant_id 过滤 |

### 15.3 实施步骤

1. **数据库迁移**：创建 `gpu_assignments` 表，为资源表补充 `created_by` 字段
2. **后端接口**：实现 GPU 分配 CRUD + `my-gpus` + 创建任务时的 GPU 权限校验
3. **资源过滤**：在 `datasets()` / `models()` / `tasks()` 等列表方法中按 `created_by` + ACL 过滤
4. **前端适配**：GPU 下拉过滤、资源授权按钮、权限管理页面优化
5. **审批扩展**：在删除/停止接口中接入 `_require_approval_or_admin`
6. **测试补充**：扩展 `test_governance.py` 覆盖 GPU 分配、资源过滤、审批扩展场景
## 16. 2.0 权限增强基线（MinIO、缓存与跨节点场景）

### 16.1 默认拒绝

所有受保护接口采用 deny by default。权限判定依次执行：认证、租户边界、项目成员关系、页面权限、资源动作权限、审批校验和审计记录。任何一步无法确定时返回 403，不得因为字段缺失、资源不存在或 ACL 查询异常而自动放行。

### 16.2 资源归属和继承

资源统一使用 `tenant_id`、`project_id`、`created_by`、`visibility` 表达边界。训练任务继承模型、数据集和项目边界；训练模型继承训练任务边界；合并模型继承被合并模型边界；评测必须同时校验模型和数据集；推理必须校验模型、项目和算力节点。

### 16.3 MinIO 对象权限

- 所有对象访问必须经过 Backend 鉴权，前端不得持有 MinIO 密钥。
- Compute API 只使用 Backend 签发的预签名 URL。
- 预签名 URL 默认有效期不超过 15 分钟，上传和下载分别签发。
- 生成 URL 前必须校验资源权限、对象状态和版本归属。
- bucket 由服务端配置，禁止客户端提交任意 bucket。
- 禁止通过修改 `object_key`、`version_id` 或文件名越权访问对象。
- 删除对象使用 `deleting -> deleted` 状态，失败时保留错误信息并可重试。

### 16.4 缓存和算力节点权限

- 缓存是资源副本，不产生新的资源所有权。
- 只有拥有源模型或数据集 `execute` 权限的用户才能触发缓存。
- 用户不能直接调用 Compute API 的缓存、文件、上传和推理管理接口。
- Compute API 只接受 Backend 服务令牌，不能转发用户 Token。
- 产物归档必须校验任务、节点、资源和项目关联关系。
- 任务运行期间缓存引用不可被普通用户清理。
- 缓存清理只能删除节点副本，不得删除 MinIO 正式对象。

### 16.5 训练、合并、推理和评测动作矩阵

| 动作 | 必要权限 | 额外约束 |
|---|---|---|
| 创建训练 | 项目 `write` + 模型/数据集 `execute` | GPU、配额和节点权限同时通过 |
| 查看训练 | 任务 `read` | 日志、曲线、checkpoint 继承任务权限 |
| 停止训练 | 任务 `write` 或 `admin` | 停止他人任务需要审批或管理员权限 |
| 权重合并 | 训练模型 `execute` | 使用绑定节点或有权限的指定节点 |
| 归档训练产物 | 任务 `write` | 只能归档任务输出目录内文件 |
| 创建推理 | 模型 `execute` | 节点、GPU 配额和项目权限通过 |
| 删除推理 | 推理服务 `delete` 或管理员 | 释放 GPU 和缓存引用 |
| 创建评测 | 模型/数据集 `execute` | 两个资源必须在允许范围内 |
| 下载报告 | 评测任务 `read` + 报告 `download` | 预签名 URL 短时有效 |

### 16.6 服务身份和密钥边界

| 身份 | 用途 | 禁止事项 |
|---|---|---|
| 用户 Token | 调用 Backend 业务接口 | 直接调用 Compute 或 MinIO |
| Backend 服务令牌 | 调用 Compute API | 返回前端或写入任务参数 |
| MinIO 管理密钥 | Backend 对象操作 | 注入浏览器、Compute 容器或日志 |
| Compute 节点身份 | 节点心跳和任务执行 | 访问其他节点本地路径 |

生产环境禁止使用默认凭据；服务令牌必须从环境变量或密钥管理系统读取并脱敏记录。

### 16.7 必须补充的接口保护

```text
POST /storage/objects/presign
POST /storage/resources/{type}/{id}/prepare/{node_id}
POST /storage/resources/{type}/{id}/archive-node/{node_id}
GET  /storage/cache/jobs/{node_id}
POST /model-manage/merge
POST /model-chat/local/preload
POST /model-chat/trained/preload
POST /model-chat/local/unload
POST /model-compare/{task_id}/load
POST /model-compare/{task_id}/unload
```

请求体中的所有资源 ID 都必须校验。`node_id`、`model_id`、`dataset_id`、`task_id` 不一致时返回 403 或 409。

### 16.8 权限审计验收用例

必须覆盖：用户 A 不能读取用户 B 资源；项目 A 不能使用项目 B 数据集；评测必须同时拥有模型和数据集 `execute` 权限；修改对象 key、版本或资源 ID 不能获取预签名 URL；普通用户不能调用 Compute API；产物路径不能越出任务数据根目录；用户不能清理其他项目正在使用的缓存；无节点权限时推理返回 403；删除、停止、合并和归档他人资源按规则进入审批。

## 17. 2.0 实施顺序

1. 建立统一 `authorize_resource_action()` 和 `authorize_task_resources()` 后端辅助函数。
2. 为模型、数据集、训练、推理、评测和对象接口补齐租户/项目过滤。
3. 统一预签名 URL 权限校验、有效期和审计日志。
4. 为 Compute API 增加服务令牌、节点归属和路径范围校验。
5. 将 GPU、缓存、配额和节点选择校验合并到任务创建事务中。
6. 增加跨资源权限测试和越权回归测试。
7. 前端仅负责隐藏操作按钮，最终权限以 Backend 返回为准。
