From a0d59b19477f0f7f2748a7608452caf1860dcbd2 Mon Sep 17 00:00:00 2001 From: iven Date: Fri, 27 Mar 2026 13:12:09 +0800 Subject: [PATCH] =?UTF-8?q?fix(saas):=20=E7=BB=9F=E4=B8=80=E6=9D=83?= =?UTF-8?q?=E9=99=90=E4=BD=93=E7=B3=BB=20=E2=80=94=20check=5Fpermission=20?= =?UTF-8?q?=E8=BE=85=E5=8A=A9=E5=87=BD=E6=95=B0=20+=20admin:full=20?= =?UTF-8?q?=E8=B6=85=E7=BA=A7=E6=9D=83=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 check_permission() 统一权限检查,admin:full 自动通过所有检查 - 统一种子角色权限名称与 handler 检查一致 (provider:manage, model:manage, config:write) - super_admin 拥有 admin:full + 所有模块管理权限 - 全部 handler 迁移到 check_permission(),消除手动 contains 检查 --- crates/zclaw-saas/src/account/handlers.rs | 9 ++---- crates/zclaw-saas/src/auth/handlers.rs | 11 ++++++++ crates/zclaw-saas/src/db.rs | 4 +-- crates/zclaw-saas/src/migration/handlers.rs | 17 ++++------- .../zclaw-saas/src/model_config/handlers.rs | 28 ++++++------------- crates/zclaw-saas/src/relay/handlers.rs | 11 +++----- 6 files changed, 33 insertions(+), 47 deletions(-) diff --git a/crates/zclaw-saas/src/account/handlers.rs b/crates/zclaw-saas/src/account/handlers.rs index 1671bb7..e07ac9c 100644 --- a/crates/zclaw-saas/src/account/handlers.rs +++ b/crates/zclaw-saas/src/account/handlers.rs @@ -5,16 +5,13 @@ use axum::{ Json, }; use crate::state::AppState; -use crate::error::{SaasError, SaasResult}; +use crate::error::SaasResult; use crate::auth::types::AuthContext; -use crate::auth::handlers::log_operation; +use crate::auth::handlers::{log_operation, check_permission}; use super::{types::*, service}; fn require_admin(ctx: &AuthContext) -> SaasResult<()> { - if !ctx.permissions.contains(&"account:admin".to_string()) { - return Err(SaasError::Forbidden("需要 account:admin 权限".into())); - } - Ok(()) + check_permission(ctx, "account:admin") } /// GET /api/v1/accounts (admin only) diff --git a/crates/zclaw-saas/src/auth/handlers.rs b/crates/zclaw-saas/src/auth/handlers.rs index b8a0af5..8e7663f 100644 --- a/crates/zclaw-saas/src/auth/handlers.rs +++ b/crates/zclaw-saas/src/auth/handlers.rs @@ -152,6 +152,17 @@ async fn get_role_permissions(db: &sqlx::SqlitePool, role: &str) -> SaasResult SaasResult<()> { + if ctx.permissions.contains(&"admin:full".to_string()) { + return Ok(()); + } + if !ctx.permissions.contains(&permission.to_string()) { + return Err(SaasError::Forbidden(format!("需要 {} 权限", permission))); + } + Ok(()) +} + /// 记录操作日志 pub async fn log_operation( db: &sqlx::SqlitePool, diff --git a/crates/zclaw-saas/src/db.rs b/crates/zclaw-saas/src/db.rs index ecae8dd..d41cd4c 100644 --- a/crates/zclaw-saas/src/db.rs +++ b/crates/zclaw-saas/src/db.rs @@ -200,8 +200,8 @@ CREATE INDEX IF NOT EXISTS idx_sync_account ON config_sync_log(account_id); const SEED_ROLES: &str = r#" INSERT OR IGNORE INTO roles (id, name, description, permissions, is_system, created_at, updated_at) VALUES - ('super_admin', '超级管理员', '拥有所有权限', '["admin:full"]', 1, datetime('now'), datetime('now')), - ('admin', '管理员', '管理账号和配置', '["account:read","account:write","model:read","model:write","relay:use","relay:admin","config:read","config:write"]', 1, datetime('now'), datetime('now')), + ('super_admin', '超级管理员', '拥有所有权限', '["admin:full","account:admin","provider:manage","model:manage","relay:admin","config:write"]', 1, datetime('now'), datetime('now')), + ('admin', '管理员', '管理账号和配置', '["account:read","account:admin","provider:manage","model:read","model:manage","relay:use","relay:admin","config:read","config:write"]', 1, datetime('now'), datetime('now')), ('user', '普通用户', '基础使用权限', '["model:read","relay:use","config:read"]', 1, datetime('now'), datetime('now')); "#; diff --git a/crates/zclaw-saas/src/migration/handlers.rs b/crates/zclaw-saas/src/migration/handlers.rs index af61dc9..7e3f94e 100644 --- a/crates/zclaw-saas/src/migration/handlers.rs +++ b/crates/zclaw-saas/src/migration/handlers.rs @@ -7,6 +7,7 @@ use axum::{ use crate::state::AppState; use crate::error::SaasResult; use crate::auth::types::AuthContext; +use crate::auth::handlers::check_permission; use super::{types::*, service}; /// GET /api/v1/config/items?category=xxx&source=xxx @@ -33,9 +34,7 @@ pub async fn create_config_item( Extension(ctx): Extension, Json(req): Json, ) -> SaasResult<(StatusCode, Json)> { - if !ctx.permissions.contains(&"config:manage".to_string()) { - return Err(crate::error::SaasError::Forbidden("需要 config:manage 权限".into())); - } + check_permission(&ctx, "config:write")?; let item = service::create_config_item(&state.db, &req).await?; Ok((StatusCode::CREATED, Json(item))) } @@ -47,9 +46,7 @@ pub async fn update_config_item( Extension(ctx): Extension, Json(req): Json, ) -> SaasResult> { - if !ctx.permissions.contains(&"config:manage".to_string()) { - return Err(crate::error::SaasError::Forbidden("需要 config:manage 权限".into())); - } + check_permission(&ctx, "config:write")?; service::update_config_item(&state.db, &id, &req).await.map(Json) } @@ -59,9 +56,7 @@ pub async fn delete_config_item( Path(id): Path, Extension(ctx): Extension, ) -> SaasResult> { - if !ctx.permissions.contains(&"config:manage".to_string()) { - return Err(crate::error::SaasError::Forbidden("需要 config:manage 权限".into())); - } + check_permission(&ctx, "config:write")?; service::delete_config_item(&state.db, &id).await?; Ok(Json(serde_json::json!({"ok": true}))) } @@ -79,9 +74,7 @@ pub async fn seed_config( State(state): State, Extension(ctx): Extension, ) -> SaasResult> { - if !ctx.permissions.contains(&"config:manage".to_string()) { - return Err(crate::error::SaasError::Forbidden("需要 config:manage 权限".into())); - } + check_permission(&ctx, "config:write")?; let count = service::seed_default_config_items(&state.db).await?; Ok(Json(serde_json::json!({"created": count}))) } diff --git a/crates/zclaw-saas/src/model_config/handlers.rs b/crates/zclaw-saas/src/model_config/handlers.rs index 4a2be00..532b2c3 100644 --- a/crates/zclaw-saas/src/model_config/handlers.rs +++ b/crates/zclaw-saas/src/model_config/handlers.rs @@ -5,9 +5,9 @@ use axum::{ http::StatusCode, Json, }; use crate::state::AppState; -use crate::error::{SaasError, SaasResult}; +use crate::error::SaasResult; use crate::auth::types::AuthContext; -use crate::auth::handlers::log_operation; +use crate::auth::handlers::{log_operation, check_permission}; use super::{types::*, service}; // ============ Providers ============ @@ -35,9 +35,7 @@ pub async fn create_provider( Extension(ctx): Extension, Json(req): Json, ) -> SaasResult<(StatusCode, Json)> { - if !ctx.permissions.contains(&"provider:manage".to_string()) { - return Err(SaasError::Forbidden("需要 provider:manage 权限".into())); - } + check_permission(&ctx, "provider:manage")?; let provider = service::create_provider(&state.db, &req).await?; log_operation(&state.db, &ctx.account_id, "provider.create", "provider", &provider.id, Some(serde_json::json!({"name": &req.name})), None).await?; @@ -51,9 +49,7 @@ pub async fn update_provider( Extension(ctx): Extension, Json(req): Json, ) -> SaasResult> { - if !ctx.permissions.contains(&"provider:manage".to_string()) { - return Err(SaasError::Forbidden("需要 provider:manage 权限".into())); - } + check_permission(&ctx, "provider:manage")?; let provider = service::update_provider(&state.db, &id, &req).await?; log_operation(&state.db, &ctx.account_id, "provider.update", "provider", &id, None, None).await?; Ok(Json(provider)) @@ -65,9 +61,7 @@ pub async fn delete_provider( Path(id): Path, Extension(ctx): Extension, ) -> SaasResult> { - if !ctx.permissions.contains(&"provider:manage".to_string()) { - return Err(SaasError::Forbidden("需要 provider:manage 权限".into())); - } + check_permission(&ctx, "provider:manage")?; service::delete_provider(&state.db, &id).await?; log_operation(&state.db, &ctx.account_id, "provider.delete", "provider", &id, None, None).await?; Ok(Json(serde_json::json!({"ok": true}))) @@ -100,9 +94,7 @@ pub async fn create_model( Extension(ctx): Extension, Json(req): Json, ) -> SaasResult<(StatusCode, Json)> { - if !ctx.permissions.contains(&"model:manage".to_string()) { - return Err(SaasError::Forbidden("需要 model:manage 权限".into())); - } + check_permission(&ctx, "model:manage")?; let model = service::create_model(&state.db, &req).await?; log_operation(&state.db, &ctx.account_id, "model.create", "model", &model.id, Some(serde_json::json!({"model_id": &req.model_id, "provider_id": &req.provider_id})), None).await?; @@ -116,9 +108,7 @@ pub async fn update_model( Extension(ctx): Extension, Json(req): Json, ) -> SaasResult> { - if !ctx.permissions.contains(&"model:manage".to_string()) { - return Err(SaasError::Forbidden("需要 model:manage 权限".into())); - } + check_permission(&ctx, "model:manage")?; let model = service::update_model(&state.db, &id, &req).await?; log_operation(&state.db, &ctx.account_id, "model.update", "model", &id, None, None).await?; Ok(Json(model)) @@ -130,9 +120,7 @@ pub async fn delete_model( Path(id): Path, Extension(ctx): Extension, ) -> SaasResult> { - if !ctx.permissions.contains(&"model:manage".to_string()) { - return Err(SaasError::Forbidden("需要 model:manage 权限".into())); - } + check_permission(&ctx, "model:manage")?; service::delete_model(&state.db, &id).await?; log_operation(&state.db, &ctx.account_id, "model.delete", "model", &id, None, None).await?; Ok(Json(serde_json::json!({"ok": true}))) diff --git a/crates/zclaw-saas/src/relay/handlers.rs b/crates/zclaw-saas/src/relay/handlers.rs index 0ce12c6..94efe43 100644 --- a/crates/zclaw-saas/src/relay/handlers.rs +++ b/crates/zclaw-saas/src/relay/handlers.rs @@ -9,7 +9,7 @@ use axum::{ use crate::state::AppState; use crate::error::{SaasError, SaasResult}; use crate::auth::types::AuthContext; -use crate::auth::handlers::log_operation; +use crate::auth::handlers::{log_operation, check_permission}; use crate::model_config::service as model_service; use super::{types::*, service}; @@ -21,10 +21,7 @@ pub async fn chat_completions( _headers: HeaderMap, Json(req): Json, ) -> SaasResult { - // 检查 relay:use 权限 - if !ctx.permissions.contains(&"relay:use".to_string()) { - return Err(SaasError::Forbidden("需要 relay:use 权限".into())); - } + check_permission(&ctx, "relay:use")?; let model_name = req.get("model") .and_then(|v| v.as_str()) @@ -129,8 +126,8 @@ pub async fn get_task( ) -> SaasResult> { let task = service::get_relay_task(&state.db, &id).await?; // 只允许查看自己的任务 (admin 可查看全部) - if task.account_id != ctx.account_id && !ctx.permissions.contains(&"relay:admin".to_string()) { - return Err(SaasError::Forbidden("无权查看此任务".into())); + if task.account_id != ctx.account_id { + check_permission(&ctx, "relay:admin")?; } Ok(Json(task)) }