From b7c4aabd92a665aeecc68a7ceb8b9daabc290c37 Mon Sep 17 00:00:00 2001 From: heqingpan Date: Wed, 25 Dec 2024 22:26:49 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=8E=A7=E5=88=B6=E5=8F=B0=E5=91=BD?= =?UTF-8?q?=E5=90=8D=E7=A9=BA=E9=97=B4=E6=9F=A5=E8=AF=A2=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E6=8C=89=E7=94=A8=E6=88=B7=E5=AF=B9=E5=BA=94?= =?UTF-8?q?=E5=91=BD=E5=90=8D=E7=A9=BA=E9=97=B4=E6=95=B0=E6=8D=AE=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E6=9D=83=E9=99=90=E6=8E=A7=E5=88=B6=20#186?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/error_code.rs | 2 ++ src/common/macros.rs | 17 +++++++++ src/common/mod.rs | 1 + src/common/model/mod.rs | 2 ++ src/common/model/privilege.rs | 64 +++++++++++++++++++++++++++++---- src/console/api.rs | 47 ++++++++++++++++++------ src/console/login_api.rs | 1 + src/console/v2/namespace_api.rs | 52 +++++++++++++++++++++++++-- src/user/model.rs | 6 ++-- 9 files changed, 170 insertions(+), 22 deletions(-) create mode 100644 src/common/error_code.rs diff --git a/src/common/error_code.rs b/src/common/error_code.rs new file mode 100644 index 00000000..e5e6c2e7 --- /dev/null +++ b/src/common/error_code.rs @@ -0,0 +1,2 @@ +//ERROR CODE +pub const NO_PERMISSION: &str = "NO_PERMISSION"; diff --git a/src/common/macros.rs b/src/common/macros.rs index 5f4e3485..f212c938 100644 --- a/src/common/macros.rs +++ b/src/common/macros.rs @@ -35,3 +35,20 @@ macro_rules! merge_web_param_with_result { $param.merge(_b) }}; } + +#[macro_export] +macro_rules! user_namespace_privilege { + ($req:expr) => {{ + if let Some(session) = $req + .extensions() + .get::>() + { + session + .namespace_privilege + .clone() + .unwrap_or($crate::common::model::privilege::PrivilegeGroup::all()) + } else { + $crate::common::model::privilege::PrivilegeGroup::all() + } + }}; +} diff --git a/src/common/mod.rs b/src/common/mod.rs index c75e57e1..bd8e9a04 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -10,6 +10,7 @@ pub mod crypto_utils; pub mod cycle_queue; pub mod datetime_utils; pub mod delay_notify; +pub mod error_code; pub mod hash_utils; pub mod limiter_utils; pub mod macros; diff --git a/src/common/model/mod.rs b/src/common/model/mod.rs index 07408af2..9d1b5770 100644 --- a/src/common/model/mod.rs +++ b/src/common/model/mod.rs @@ -2,6 +2,7 @@ pub mod privilege; use std::{collections::HashMap, sync::Arc}; +use crate::common::model::privilege::PrivilegeGroup; use serde::{Deserialize, Serialize}; #[derive(Debug, Default, Deserialize, Serialize)] @@ -90,6 +91,7 @@ pub struct UserSession { pub username: Arc, pub nickname: Option, pub roles: Vec>, + pub namespace_privilege: Option>>, pub extend_infos: HashMap, } diff --git a/src/common/model/privilege.rs b/src/common/model/privilege.rs index 876b1d03..3bad9f0b 100644 --- a/src/common/model/privilege.rs +++ b/src/common/model/privilege.rs @@ -1,6 +1,7 @@ use bitflags::bitflags; use serde::{Deserialize, Serialize}; use std::collections::HashSet; +use std::sync::Arc; bitflags! { /// Represents a set of flags. @@ -22,9 +23,9 @@ where T: Sized + std::hash::Hash + std::cmp::Eq, { pub whitelist_is_all: Option, - pub whitelist: Option>, + pub whitelist: Option>>, pub blacklist_is_all: Option, - pub blacklist: Option>, + pub blacklist: Option>>, } impl PrivilegeGroupOptionParam @@ -42,7 +43,7 @@ where /// /// 数据权限组 /// 支持分别设置黑白名单 -#[derive(Clone, Serialize, Deserialize, Default)] +#[derive(Debug, Clone, Serialize, Deserialize, Default)] #[serde(rename_all = "camelCase")] pub struct PrivilegeGroup where @@ -50,9 +51,9 @@ where { pub enabled: bool, pub whitelist_is_all: bool, - pub whitelist: Option>, + pub whitelist: Option>>, pub blacklist_is_all: bool, - pub blacklist: Option>, + pub blacklist: Option>>, } impl PrivilegeGroup @@ -61,8 +62,8 @@ where { pub fn new( flags: u8, - whitelist: Option>, - blacklist: Option>, + whitelist: Option>>, + blacklist: Option>>, ) -> PrivilegeGroup { let enabled = flags & PrivilegeGroupFlags::ENABLE.bits() > 0; let white_list_is_all = flags & PrivilegeGroupFlags::WHILE_LIST_IS_ALL.bits() > 0; @@ -96,6 +97,21 @@ where } } + pub fn is_all(&self) -> bool { + self.enabled && self.whitelist_is_all && self.blacklist_is_empty() + } + + fn blacklist_is_empty(&self) -> bool { + if self.blacklist_is_all { + return false; + } + if let Some(blacklist) = &self.blacklist { + blacklist.is_empty() + } else { + true + } + } + pub fn get_flags(&self) -> u8 { let mut v = 0; if self.enabled { @@ -115,4 +131,38 @@ where self.whitelist_is_all = flags & PrivilegeGroupFlags::WHILE_LIST_IS_ALL.bits() > 0; self.blacklist_is_all = flags & PrivilegeGroupFlags::BLACK_LIST_IS_ALL.bits() > 0; } + + pub fn check_permission(&self, key: &T) -> bool { + self.at_whitelist(key) && !self.at_blacklist(key) + } + + pub fn check_option_value_permission(&self, key: &Option, empty_default: bool) -> bool { + if let Some(key) = key { + self.at_whitelist(key) && !self.at_blacklist(key) + } else { + empty_default + } + } + + fn at_whitelist(&self, key: &T) -> bool { + if self.whitelist_is_all { + return true; + } + if let Some(list) = &self.whitelist { + list.contains(key) + } else { + false + } + } + + fn at_blacklist(&self, key: &T) -> bool { + if self.blacklist_is_all { + return true; + } + if let Some(list) = &self.blacklist { + list.contains(key) + } else { + false + } + } } diff --git a/src/console/api.rs b/src/console/api.rs index e1f6976b..e589c37b 100644 --- a/src/console/api.rs +++ b/src/console/api.rs @@ -1,14 +1,5 @@ use std::sync::Arc; -use crate::common::appdata::AppShareData; -use crate::common::string_utils::StringUtils; -use actix_web::{http::header, web, HttpResponse, Responder}; -use uuid::Uuid; - -use crate::naming::ops::ops_api::query_opt_service_list; -use crate::openapi::naming::instance::{del_instance, get_instance, update_instance}; -use crate::openapi::naming::service::{query_service, remove_service, update_service}; - use super::cluster_api::query_cluster_info; use super::config_api::query_config_list; use super::{ @@ -19,14 +10,35 @@ use super::{ transfer_api, NamespaceUtils, }; use super::{login_api, user_api}; +use crate::common::appdata::AppShareData; +use crate::common::error_code::NO_PERMISSION; +use crate::common::string_utils::StringUtils; +use crate::naming::ops::ops_api::query_opt_service_list; +use crate::openapi::naming::instance::{del_instance, get_instance, update_instance}; +use crate::openapi::naming::service::{query_service, remove_service, update_service}; +use crate::user_namespace_privilege; +use actix_web::{http::header, web, HttpMessage, HttpRequest, HttpResponse, Responder}; +use uuid::Uuid; use super::v2; -pub async fn query_namespace_list(app_data: web::Data>) -> impl Responder { +pub async fn query_namespace_list( + req: HttpRequest, + app_data: web::Data>, +) -> impl Responder { + let namespace_privilege = user_namespace_privilege!(req); //HttpResponse::InternalServerError().body("system error") let namespaces = NamespaceUtils::get_namespaces(&app_data) .await .unwrap_or_default(); + let namespaces = if namespace_privilege.is_all() { + namespaces + } else { + namespaces + .into_iter() + .filter(|e| namespace_privilege.check_option_value_permission(&e.namespace_id, false)) + .collect() + }; let result = ConsoleResult::success(namespaces); let v = serde_json::to_string(&result).unwrap(); HttpResponse::Ok() @@ -35,6 +47,7 @@ pub async fn query_namespace_list(app_data: web::Data>) -> imp } pub async fn add_namespace( + req: HttpRequest, param: web::Form, app_data: web::Data>, ) -> impl Responder { @@ -42,6 +55,10 @@ pub async fn add_namespace( if StringUtils::is_option_empty_arc(¶m.namespace_id) { param.namespace_id = Some(Arc::new(Uuid::new_v4().to_string())); } + let namespace_privilege = user_namespace_privilege!(req); + if !namespace_privilege.check_option_value_permission(¶m.namespace_id, false) { + return HttpResponse::Ok().json(ConsoleResult::<()>::error(NO_PERMISSION.to_string())); + } match NamespaceUtils::add_namespace(&app_data, param).await { Ok(_) => { let result = ConsoleResult::success(true); @@ -61,9 +78,14 @@ pub async fn add_namespace( } pub async fn update_namespace( + req: HttpRequest, param: web::Form, app_data: web::Data>, ) -> impl Responder { + let namespace_privilege = user_namespace_privilege!(req); + if !namespace_privilege.check_option_value_permission(¶m.namespace_id, false) { + return HttpResponse::Ok().json(ConsoleResult::<()>::error(NO_PERMISSION.to_string())); + } match NamespaceUtils::update_namespace(&app_data, param.0).await { Ok(_) => { let result = ConsoleResult::success(true); @@ -83,9 +105,14 @@ pub async fn update_namespace( } pub async fn remove_namespace( + req: HttpRequest, param: web::Form, app_data: web::Data>, ) -> impl Responder { + let namespace_privilege = user_namespace_privilege!(req); + if !namespace_privilege.check_option_value_permission(¶m.namespace_id, false) { + return HttpResponse::Ok().json(ConsoleResult::<()>::error(NO_PERMISSION.to_string())); + } match NamespaceUtils::remove_namespace(&app_data, param.0.namespace_id).await { Ok(_) => { let result = ConsoleResult::success(true); diff --git a/src/console/login_api.rs b/src/console/login_api.rs index f378d85c..5d392cc5 100644 --- a/src/console/login_api.rs +++ b/src/console/login_api.rs @@ -113,6 +113,7 @@ pub async fn login( nickname: user.nickname, roles: user.roles.unwrap_or_default(), extend_infos: user.extend_info.unwrap_or_default(), + namespace_privilege: user.namespace_privilege, }); let cache_req = CacheManagerReq::Set { key: CacheKey::new(CacheType::UserSession, token.clone()), diff --git a/src/console/v2/namespace_api.rs b/src/console/v2/namespace_api.rs index 4b5e64c0..c852a519 100644 --- a/src/console/v2/namespace_api.rs +++ b/src/console/v2/namespace_api.rs @@ -1,20 +1,36 @@ use crate::common::appdata::AppShareData; +use crate::common::error_code::NO_PERMISSION; use crate::common::model::ApiResult; use crate::common::string_utils::StringUtils; use crate::console::model::NamespaceInfo; use crate::console::NamespaceUtils; -use actix_web::{web, HttpResponse, Responder}; +use crate::user_namespace_privilege; +use actix_http::HttpMessage; +use actix_web::{web, HttpRequest, HttpResponse, Responder}; use std::sync::Arc; use uuid::Uuid; -pub async fn query_namespace_list(app_data: web::Data>) -> impl Responder { +pub async fn query_namespace_list( + req: HttpRequest, + app_data: web::Data>, +) -> impl Responder { + let namespace_privilege = user_namespace_privilege!(req); let namespaces = NamespaceUtils::get_namespaces(&app_data) .await .unwrap_or_default(); + let namespaces = if namespace_privilege.is_all() { + namespaces + } else { + namespaces + .into_iter() + .filter(|e| namespace_privilege.check_option_value_permission(&e.namespace_id, false)) + .collect() + }; HttpResponse::Ok().json(ApiResult::success(Some(namespaces))) } pub async fn add_namespace( + req: HttpRequest, param: web::Json, app_data: web::Data>, ) -> impl Responder { @@ -22,6 +38,16 @@ pub async fn add_namespace( if StringUtils::is_option_empty_arc(¶m.namespace_id) { param.namespace_id = Some(Arc::new(Uuid::new_v4().to_string())); } + let namespace_privilege = user_namespace_privilege!(req); + if !namespace_privilege.check_option_value_permission(¶m.namespace_id, false) { + return HttpResponse::Ok().json(ApiResult::<()>::error( + NO_PERMISSION.to_string(), + Some(format!( + "user no such namespace permission: {:?}", + ¶m.namespace_id + )), + )); + } match NamespaceUtils::add_namespace(&app_data, param).await { Ok(_) => HttpResponse::Ok().json(ApiResult::success(Some(true))), Err(e) => HttpResponse::Ok().json(ApiResult::<()>::error( @@ -32,9 +58,20 @@ pub async fn add_namespace( } pub async fn update_namespace( + req: HttpRequest, param: web::Json, app_data: web::Data>, ) -> impl Responder { + let namespace_privilege = user_namespace_privilege!(req); + if !namespace_privilege.check_option_value_permission(¶m.namespace_id, false) { + return HttpResponse::Ok().json(ApiResult::<()>::error( + NO_PERMISSION.to_string(), + Some(format!( + "user no such namespace permission: {:?}", + ¶m.namespace_id + )), + )); + } match NamespaceUtils::update_namespace(&app_data, param.0).await { Ok(_) => HttpResponse::Ok().json(ApiResult::success(Some(true))), Err(e) => HttpResponse::Ok().json(ApiResult::<()>::error( @@ -45,9 +82,20 @@ pub async fn update_namespace( } pub async fn remove_namespace( + req: HttpRequest, param: web::Json, app_data: web::Data>, ) -> impl Responder { + let namespace_privilege = user_namespace_privilege!(req); + if !namespace_privilege.check_option_value_permission(¶m.namespace_id, false) { + return HttpResponse::Ok().json(ApiResult::<()>::error( + NO_PERMISSION.to_string(), + Some(format!( + "user no such namespace permission: {:?}", + ¶m.namespace_id + )), + )); + } match NamespaceUtils::remove_namespace(&app_data, param.0.namespace_id).await { Ok(_) => HttpResponse::Ok().json(ApiResult::success(Some(true))), Err(e) => HttpResponse::Ok().json(ApiResult::<()>::error( diff --git a/src/user/model.rs b/src/user/model.rs index efb668ee..f98f874b 100644 --- a/src/user/model.rs +++ b/src/user/model.rs @@ -59,11 +59,11 @@ impl UserDo { } PrivilegeGroup::new( self.namespace_privilege_flags.unwrap_or_default() as u8, - Some(namespace_whitelist), - Some(namespace_black_list), + Some(Arc::new(namespace_whitelist)), + Some(Arc::new(namespace_black_list)), ) } else { - PrivilegeGroup::default() + PrivilegeGroup::all() }; namespace_privilege }