Skip to content

Commit

Permalink
feat: 控制台配置管理支持数据权限控制; 188
Browse files Browse the repository at this point in the history
  • Loading branch information
heqingpan committed Dec 27, 2024
1 parent b7c4aab commit 17ba6b2
Show file tree
Hide file tree
Showing 9 changed files with 114 additions and 40 deletions.
1 change: 1 addition & 0 deletions src/common/constant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ lazy_static::lazy_static! {
pub static ref CACHE_TREE_NAME: Arc<String> = Arc::new("T_CACHE".to_string());
pub static ref NAMESPACE_TREE_NAME: Arc<String> = Arc::new("T_NAMESPACE".to_string());
pub static ref EMPTY_ARC_STRING: Arc<String> = Arc::new("".to_string());
pub static ref DEFAULT_NAMESPACE_ARC_STRING: Arc<String> = Arc::new("".to_string());
}
10 changes: 10 additions & 0 deletions src/common/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,13 @@ macro_rules! user_namespace_privilege {
}
}};
}

#[macro_export]
macro_rules! user_no_namespace_permission {
($param:expr) => {{
return actix_web::HttpResponse::Ok().json(crate::common::model::ApiResult::<()>::error(
crate::common::error_code::NO_PERMISSION.to_string(),
Some(format!("user no such namespace permission: {:?}", $param)),
));
}};
}
36 changes: 35 additions & 1 deletion src/common/model/privilege.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use crate::common::constant::DEFAULT_NAMESPACE_ARC_STRING;
use crate::namespace::is_default_namespace;
use bitflags::bitflags;
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
Expand Down Expand Up @@ -43,7 +45,7 @@ where
///
/// 数据权限组
/// 支持分别设置黑白名单
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PrivilegeGroup<T>
where
Expand All @@ -56,6 +58,15 @@ where
pub blacklist: Option<Arc<HashSet<T>>>,
}

impl<T> Default for PrivilegeGroup<T>
where
T: Sized + std::hash::Hash + std::cmp::Eq,
{
fn default() -> Self {
Self::all()
}
}

impl<T> PrivilegeGroup<T>
where
T: Sized + std::hash::Hash + std::cmp::Eq,
Expand Down Expand Up @@ -166,3 +177,26 @@ where
}
}
}

pub fn check_namespace_permission(
privilege_group: &PrivilegeGroup<Arc<String>>,
key: &Arc<String>,
) -> bool {
if is_default_namespace(key.as_str()) {
privilege_group.check_permission(&DEFAULT_NAMESPACE_ARC_STRING)
} else {
privilege_group.check_permission(key)
}
}

pub fn check_option_namespace_permission(
privilege_group: &PrivilegeGroup<Arc<String>>,
key: &Option<Arc<String>>,
empty_default: bool,
) -> bool {
if let Some(key) = key {
check_namespace_permission(privilege_group, key)
} else {
empty_default
}
}
20 changes: 13 additions & 7 deletions src/config/config_index.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::common::model::privilege::PrivilegeGroup;
use crate::common::string_utils::StringUtils;
use crate::config::core::ConfigKey;
use crate::namespace::model::{NamespaceActorReq, WeakNamespaceFromType, WeakNamespaceParam};
Expand All @@ -17,6 +18,7 @@ pub struct ConfigQueryParam {
pub data_id: Option<Arc<String>>,
pub like_group: Option<String>,
pub like_data_id: Option<String>,
pub namespace_privilege: PrivilegeGroup<Arc<String>>,
pub query_context: bool,
pub offset: usize,
pub limit: usize,
Expand Down Expand Up @@ -214,16 +216,20 @@ impl TenantIndex {
let mut size = 0;
let mut limit = param.limit;
if let Some(tenant) = &param.tenant {
if let Some(index) = self.tenant_group.get(tenant) {
return index.query_config_page(tenant, limit, param);
if param.namespace_privilege.check_permission(tenant) {
if let Some(index) = self.tenant_group.get(tenant) {
return index.query_config_page(tenant, limit, param);
}
}
} else {
for (tenant, service_index) in &self.tenant_group {
let (sub_size, mut sub_list) =
service_index.query_config_page(tenant, limit, param);
size += sub_size;
limit -= sub_list.len();
rlist.append(&mut sub_list);
if param.namespace_privilege.check_permission(tenant) {
let (sub_size, mut sub_list) =
service_index.query_config_page(tenant, limit, param);
size += sub_size;
limit -= sub_list.len();
rlist.append(&mut sub_list);
}
}
}
(size, rlist)
Expand Down
6 changes: 4 additions & 2 deletions src/console/config_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,11 @@ use super::model::config_model::OpsConfigImportInfo;
use super::model::PageResult;

pub async fn query_config_list(
req: HttpRequest,
request: web::Query<OpsConfigQueryListRequest>,
config_addr: web::Data<Addr<ConfigActor>>,
) -> impl Responder {
let cmd = ConfigCmd::QueryPageInfo(Box::new(request.0.to_param().unwrap()));
let cmd = ConfigCmd::QueryPageInfo(Box::new(request.0.to_param(&req).unwrap()));
match config_addr.send(cmd).await {
Ok(res) => {
let r: ConfigResult = res.unwrap();
Expand Down Expand Up @@ -177,10 +178,11 @@ fn zip_file(mut zip: ZipWriter<&mut File>, list: Vec<ConfigInfoDto>) -> anyhow::
///
/// 按查询条件导出配置
pub async fn download_config(
req: HttpRequest,
request: web::Query<OpsConfigQueryListRequest>,
config_addr: web::Data<Addr<ConfigActor>>,
) -> impl Responder {
let mut param = request.0.to_param().unwrap();
let mut param = request.0.to_param(&req).unwrap();
param.limit = 0xffff_ffff;
param.query_context = true;
let cmd = ConfigCmd::QueryPageInfo(Box::new(param));
Expand Down
7 changes: 6 additions & 1 deletion src/console/model/config_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ use crate::config::config_index::ConfigQueryParam;
use crate::config::core::{ConfigInfoDto, ConfigKey};
use crate::config::dal::ConfigHistoryParam;
use crate::config::ConfigUtils;
use crate::user_namespace_privilege;
use actix_http::HttpMessage;
use actix_web::HttpRequest;
use serde::{Deserialize, Serialize};
use std::sync::Arc;

Expand All @@ -24,14 +27,16 @@ pub struct OpsConfigQueryListRequest {
}

impl OpsConfigQueryListRequest {
pub fn to_param(self) -> anyhow::Result<ConfigQueryParam> {
pub fn to_param(self, req: &HttpRequest) -> anyhow::Result<ConfigQueryParam> {
let limit = self.page_size.unwrap_or(0xffff_ffff);
let offset = (self.page_no.unwrap_or(1) - 1) * limit;
let namespace_privilege = user_namespace_privilege!(req);
let mut param = ConfigQueryParam {
limit,
offset,
like_group: self.group_param,
like_data_id: self.data_param,
namespace_privilege,
..Default::default()
};
if let Some(tenant) = self.tenant {
Expand Down
43 changes: 37 additions & 6 deletions src/console/v2/config_api.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,30 @@
use crate::common::appdata::AppShareData;
use crate::common::model::{ApiResult, PageResult};
use crate::config::core::{ConfigActor, ConfigCmd, ConfigResult};
pub use crate::console::config_api::{download_config, import_config};
use crate::console::model::config_model::{ConfigInfo, ConfigParams, OpsConfigQueryListRequest};
use crate::console::v2::ERROR_CODE_SYSTEM_ERROR;
use crate::raft::cluster::model::{DelConfigReq, SetConfigReq};
use crate::{user_namespace_privilege, user_no_namespace_permission};
use actix::Addr;
use actix_web::web::Data;
use actix_web::{web, HttpResponse, Responder};
use actix_web::HttpMessage;
use actix_web::{web, HttpRequest, HttpResponse, Responder};
use std::sync::Arc;

pub use crate::console::config_api::{download_config, import_config};
use crate::console::v2::ERROR_CODE_SYSTEM_ERROR;
use crate::raft::cluster::model::{DelConfigReq, SetConfigReq};

pub async fn query_config_list(
req: HttpRequest,
request: web::Query<OpsConfigQueryListRequest>,
config_addr: web::Data<Addr<ConfigActor>>,
) -> impl Responder {
let cmd = ConfigCmd::QueryPageInfo(Box::new(request.0.to_param().unwrap()));
let param = request.0.to_param(&req).unwrap();
if !param
.namespace_privilege
.check_option_value_permission(&param.tenant, true)
{
user_no_namespace_permission!(&param.tenant);
}
let cmd = ConfigCmd::QueryPageInfo(Box::new(param));
match config_addr.send(cmd).await {
Ok(res) => {
let r: ConfigResult = res.unwrap();
Expand All @@ -36,6 +45,7 @@ pub async fn query_config_list(
}

pub async fn query_history_config_page(
req: HttpRequest,
request: web::Query<OpsConfigQueryListRequest>,
config_addr: web::Data<Addr<ConfigActor>>,
) -> impl Responder {
Expand All @@ -48,6 +58,12 @@ pub async fn query_history_config_page(
));
}
};
let namespace_privilege = user_namespace_privilege!(req);
if !namespace_privilege
.check_option_value_permission(&(param.tenant.clone().map(Arc::new)), false)
{
user_no_namespace_permission!(&param.tenant);
}
let cmd = ConfigCmd::QueryHistoryPageInfo(Box::new(param));
match config_addr.send(cmd).await {
Ok(res) => {
Expand All @@ -69,10 +85,15 @@ pub async fn query_history_config_page(
}

pub(crate) async fn get_config(
req: HttpRequest,
web::Query(param): web::Query<ConfigParams>,
appdata: Data<Arc<AppShareData>>,
) -> impl Responder {
let config_key = param.to_key();
let namespace_privilege = user_namespace_privilege!(req);
if !namespace_privilege.check_permission(&config_key.tenant) {
user_no_namespace_permission!(&config_key.tenant);
}
let cmd = ConfigCmd::GET(config_key);
if let Ok(Ok(ConfigResult::Data {
value: v,
Expand All @@ -97,11 +118,16 @@ pub(crate) async fn get_config(
}

pub async fn add_config(
req: HttpRequest,
appdata: Data<Arc<AppShareData>>,
web::Json(param): web::Json<ConfigParams>,
) -> impl Responder {
let content = param.content.clone().unwrap_or_default();
let config_key = param.to_key();
let namespace_privilege = user_namespace_privilege!(req);
if !namespace_privilege.check_permission(&config_key.tenant) {
user_no_namespace_permission!(&config_key.tenant);
}
if let Err(e) = config_key.is_valid() {
return HttpResponse::Ok().json(ApiResult::<()>::error(
ERROR_CODE_SYSTEM_ERROR.to_string(),
Expand All @@ -122,10 +148,15 @@ pub async fn add_config(
}

pub async fn remove_config(
req: HttpRequest,
appdata: Data<Arc<AppShareData>>,
web::Json(param): web::Json<ConfigParams>,
) -> impl Responder {
let config_key = param.to_key();
let namespace_privilege = user_namespace_privilege!(req);
if !namespace_privilege.check_permission(&config_key.tenant) {
user_no_namespace_permission!(&config_key.tenant);
}
let req = DelConfigReq::new(config_key);
if appdata.config_route.del_config(req).await.is_ok() {
HttpResponse::Ok().json(ApiResult::success(Some(true)))
Expand Down
27 changes: 4 additions & 23 deletions src/console/v2/namespace_api.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
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 crate::user_namespace_privilege;
use crate::{user_namespace_privilege, user_no_namespace_permission};
use actix_http::HttpMessage;
use actix_web::{web, HttpRequest, HttpResponse, Responder};
use std::sync::Arc;
Expand Down Expand Up @@ -40,13 +39,7 @@ pub async fn add_namespace(
}
let namespace_privilege = user_namespace_privilege!(req);
if !namespace_privilege.check_option_value_permission(&param.namespace_id, false) {
return HttpResponse::Ok().json(ApiResult::<()>::error(
NO_PERMISSION.to_string(),
Some(format!(
"user no such namespace permission: {:?}",
&param.namespace_id
)),
));
user_no_namespace_permission!(&param.namespace_id);
}
match NamespaceUtils::add_namespace(&app_data, param).await {
Ok(_) => HttpResponse::Ok().json(ApiResult::success(Some(true))),
Expand All @@ -64,13 +57,7 @@ pub async fn update_namespace(
) -> impl Responder {
let namespace_privilege = user_namespace_privilege!(req);
if !namespace_privilege.check_option_value_permission(&param.namespace_id, false) {
return HttpResponse::Ok().json(ApiResult::<()>::error(
NO_PERMISSION.to_string(),
Some(format!(
"user no such namespace permission: {:?}",
&param.namespace_id
)),
));
user_no_namespace_permission!(&param.namespace_id);
}
match NamespaceUtils::update_namespace(&app_data, param.0).await {
Ok(_) => HttpResponse::Ok().json(ApiResult::success(Some(true))),
Expand All @@ -88,13 +75,7 @@ pub async fn remove_namespace(
) -> impl Responder {
let namespace_privilege = user_namespace_privilege!(req);
if !namespace_privilege.check_option_value_permission(&param.namespace_id, false) {
return HttpResponse::Ok().json(ApiResult::<()>::error(
NO_PERMISSION.to_string(),
Some(format!(
"user no such namespace permission: {:?}",
&param.namespace_id
)),
));
user_no_namespace_permission!(&param.namespace_id);
}
match NamespaceUtils::remove_namespace(&app_data, param.0.namespace_id).await {
Ok(_) => HttpResponse::Ok().json(ApiResult::success(Some(true))),
Expand Down
4 changes: 4 additions & 0 deletions src/namespace/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ pub fn build_already_mark_param() -> NamespaceParam {
}
}

pub fn is_default_namespace(namespace: &str) -> bool {
namespace.is_empty() || namespace == DEFAULT_NAMESPACE
}

#[bean(inject)]
#[derive(Clone)]
pub struct NamespaceActor {
Expand Down

0 comments on commit 17ba6b2

Please sign in to comment.