修复普通用户对模型训练的权限操作
This commit is contained in:
@@ -225,12 +225,16 @@ def _require_approval_or_admin(
|
||||
"""
|
||||
高风险操作审批旁路:
|
||||
- admin 用户直接放行(返回 None)
|
||||
- 普通用户创建审批实例,返回审批待定响应(code=202,非 None)
|
||||
- 资源创建者(Owner)直接放行(返回 None)
|
||||
- 其他普通用户创建审批实例,返回审批待定响应(code=202,非 None)
|
||||
code=202 使前端响应拦截器走业务错误分支,弹提示并 reject,
|
||||
避免前端误认为删除成功。
|
||||
"""
|
||||
if is_admin(current_user):
|
||||
return None
|
||||
# 资源创建者直接放行,无需审批
|
||||
if _check_owner(resource_type, resource_id, current_user.get("id")):
|
||||
return None
|
||||
store = get_platform_store()
|
||||
instance = store.create_approval_instance({
|
||||
"resource_type": resource_type,
|
||||
@@ -245,6 +249,28 @@ def _require_approval_or_admin(
|
||||
}
|
||||
|
||||
|
||||
def _check_owner(resource_type: str, resource_id: str, user_id: str | None) -> bool:
|
||||
"""直接查数据库判断 user_id 是否为资源的 created_by。"""
|
||||
from app.core.auth import OWNER_TABLES
|
||||
table_info = OWNER_TABLES.get(resource_type)
|
||||
if not table_info:
|
||||
return False
|
||||
table, column = table_info
|
||||
store = get_platform_store()
|
||||
with store.connect() as conn:
|
||||
row = conn.execute(f"SELECT {column} FROM {table} WHERE id=?", (resource_id,)).fetchone()
|
||||
if not row:
|
||||
return False
|
||||
owner = row[column]
|
||||
if column == "payload":
|
||||
try:
|
||||
import json
|
||||
owner = json.loads(owner or "{}").get("created_by")
|
||||
except (TypeError, ValueError):
|
||||
owner = None
|
||||
return owner == user_id
|
||||
|
||||
|
||||
def _node_for_task(task: dict[str, Any]) -> dict[str, Any] | None:
|
||||
return next((node for node in get_platform_store().compute_nodes() if node["id"] == task.get("compute_node_id")), None)
|
||||
|
||||
@@ -1457,8 +1483,11 @@ async def fine_tune_list(current_user: dict = Depends(get_current_user)) -> dict
|
||||
tasks = get_platform_store().tasks()
|
||||
if current_user.get("role") == "admin" or current_user.get("protected"):
|
||||
return ok(tasks)
|
||||
# 普通用户可见:自己创建的 + ACL 授权的
|
||||
user_id = current_user.get("id")
|
||||
accessible = set(filter_accessible_resource_ids("fine-tune", [t["id"] for t in tasks], current_user))
|
||||
return ok([t for t in tasks if t["id"] in accessible])
|
||||
result = [t for t in tasks if t.get("created_by") == user_id or t["id"] in accessible]
|
||||
return ok(result)
|
||||
|
||||
|
||||
@router.post("/fine-tune")
|
||||
@@ -1740,8 +1769,11 @@ async def model_eval_list(current_user: dict = Depends(get_current_user)) -> dic
|
||||
tasks = get_platform_store().eval_tasks()
|
||||
if current_user.get("role") == "admin" or current_user.get("protected"):
|
||||
return ok(tasks)
|
||||
# 普通用户可见:自己创建的 + ACL 授权的
|
||||
user_id = current_user.get("id")
|
||||
accessible = set(filter_accessible_resource_ids("eval", [t["id"] for t in tasks], current_user))
|
||||
return ok([t for t in tasks if t["id"] in accessible])
|
||||
result = [t for t in tasks if t.get("created_by") == user_id or t["id"] in accessible]
|
||||
return ok(result)
|
||||
|
||||
|
||||
@router.get("/model-eval/{task_id}")
|
||||
@@ -1777,6 +1809,7 @@ async def model_eval_start(payload: dict[str, Any] = Body(...), current_user: di
|
||||
"""Start an evaluation task: submit eval job to compute node."""
|
||||
store = get_platform_store()
|
||||
# 1. Create eval task record
|
||||
payload.setdefault("created_by", current_user.get("id"))
|
||||
task = store.create_eval_task({**payload, "status": "pending"})
|
||||
|
||||
# 2. Resolve model path (supports both regular models and trained models)
|
||||
@@ -1978,8 +2011,11 @@ async def model_compare_list(current_user: dict = Depends(get_current_user)) ->
|
||||
tasks = get_platform_store().compare_tasks()
|
||||
if is_admin(current_user):
|
||||
return ok(tasks)
|
||||
# 普通用户可见:自己创建的 + ACL 授权的
|
||||
user_id = current_user.get("id")
|
||||
accessible = filter_accessible_resource_ids_batch("compare", [item["id"] for item in tasks], current_user)
|
||||
return ok([item for item in tasks if item["id"] in accessible])
|
||||
result = [item for item in tasks if item.get("created_by") == user_id or item["id"] in accessible]
|
||||
return ok(result)
|
||||
|
||||
|
||||
@router.post("/model-compare")
|
||||
|
||||
@@ -570,6 +570,16 @@ class PlatformStore:
|
||||
""")
|
||||
except Exception:
|
||||
pass # 列不存在时忽略
|
||||
# 修复历史数据:为 eval_tasks 表回填 created_by(从 payload JSON 中提取)
|
||||
try:
|
||||
conn.execute("""
|
||||
UPDATE eval_tasks SET created_by = payload::json->>'created_by'
|
||||
WHERE (created_by IS NULL OR created_by = '')
|
||||
AND payload IS NOT NULL
|
||||
AND payload::json->>'created_by' IS NOT NULL
|
||||
""")
|
||||
except Exception:
|
||||
pass # 列或语法不支持时忽略
|
||||
# 清理超过 7 天的操作日志
|
||||
try:
|
||||
cutoff = (datetime.now(timezone.utc) - timedelta(days=7)).isoformat()
|
||||
@@ -2031,6 +2041,7 @@ class PlatformStore:
|
||||
"process_id": None,
|
||||
"train_duration": "",
|
||||
"create_time": now,
|
||||
"created_by": payload.get("created_by"),
|
||||
}
|
||||
with self.connect() as conn:
|
||||
conn.execute(
|
||||
@@ -2529,8 +2540,8 @@ class PlatformStore:
|
||||
if data.get("metric") == "custom":
|
||||
data["metric"] = data["metric_label"]
|
||||
conn.execute(
|
||||
"INSERT INTO eval_tasks (id, name, payload, status, create_time) VALUES (?, ?, ?, ?, ?)",
|
||||
(task_id, name, json_dumps(data), status, now),
|
||||
"INSERT INTO eval_tasks (id, name, payload, status, create_time, created_by) VALUES (?, ?, ?, ?, ?, ?)",
|
||||
(task_id, name, json_dumps(data), status, now, data.get("created_by")),
|
||||
)
|
||||
return self.eval_task(task_id)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<script setup lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessage, type FormInstance, type FormRules } from 'element-plus'
|
||||
@@ -272,9 +272,14 @@ async function handleSubmit() {
|
||||
await startFineTune({ ...payload, task_id: taskId })
|
||||
ElMessage.success('训练任务已创建并启动')
|
||||
} catch {
|
||||
await updateFineTune(taskId, { status: 'failed' })
|
||||
try {
|
||||
await updateFineTune(taskId, { status: 'failed' })
|
||||
} catch {
|
||||
// 忽略状态更新失败
|
||||
}
|
||||
ElMessage.error('任务已创建,但训练启动失败')
|
||||
}
|
||||
// 无论启动成功还是失败,都跳转到训练列表
|
||||
router.push('/fine-tune')
|
||||
} catch {
|
||||
ElMessage.error('训练任务创建失败,请稍后重试')
|
||||
|
||||
Reference in New Issue
Block a user