Skip to content

Commit

Permalink
feat: 控制台命名空间查询管理支持按用户对应命名空间数据增加权限控制 nacos-group#186
Browse files Browse the repository at this point in the history
  • Loading branch information
heqingpan committed Dec 25, 2024
1 parent 003b71d commit b7c4aab
Show file tree
Hide file tree
Showing 9 changed files with 170 additions and 22 deletions.
2 changes: 2 additions & 0 deletions src/common/error_code.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
//ERROR CODE
pub const NO_PERMISSION: &str = "NO_PERMISSION";
17 changes: 17 additions & 0 deletions src/common/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<Arc<$crate::common::model::UserSession>>()
{
session
.namespace_privilege
.clone()
.unwrap_or($crate::common::model::privilege::PrivilegeGroup::all())
} else {
$crate::common::model::privilege::PrivilegeGroup::all()
}
}};
}
1 change: 1 addition & 0 deletions src/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions src/common/model/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -90,6 +91,7 @@ pub struct UserSession {
pub username: Arc<String>,
pub nickname: Option<String>,
pub roles: Vec<Arc<String>>,
pub namespace_privilege: Option<PrivilegeGroup<Arc<String>>>,
pub extend_infos: HashMap<String, String>,
}

Expand Down
64 changes: 57 additions & 7 deletions src/common/model/privilege.rs
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -22,9 +23,9 @@ where
T: Sized + std::hash::Hash + std::cmp::Eq,
{
pub whitelist_is_all: Option<bool>,
pub whitelist: Option<HashSet<T>>,
pub whitelist: Option<Arc<HashSet<T>>>,
pub blacklist_is_all: Option<bool>,
pub blacklist: Option<HashSet<T>>,
pub blacklist: Option<Arc<HashSet<T>>>,
}

impl<T> PrivilegeGroupOptionParam<T>
Expand All @@ -42,17 +43,17 @@ where
///
/// 数据权限组
/// 支持分别设置黑白名单
#[derive(Clone, Serialize, Deserialize, Default)]
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct PrivilegeGroup<T>
where
T: Sized + std::hash::Hash + std::cmp::Eq,
{
pub enabled: bool,
pub whitelist_is_all: bool,
pub whitelist: Option<HashSet<T>>,
pub whitelist: Option<Arc<HashSet<T>>>,
pub blacklist_is_all: bool,
pub blacklist: Option<HashSet<T>>,
pub blacklist: Option<Arc<HashSet<T>>>,
}

impl<T> PrivilegeGroup<T>
Expand All @@ -61,8 +62,8 @@ where
{
pub fn new(
flags: u8,
whitelist: Option<HashSet<T>>,
blacklist: Option<HashSet<T>>,
whitelist: Option<Arc<HashSet<T>>>,
blacklist: Option<Arc<HashSet<T>>>,
) -> PrivilegeGroup<T> {
let enabled = flags & PrivilegeGroupFlags::ENABLE.bits() > 0;
let white_list_is_all = flags & PrivilegeGroupFlags::WHILE_LIST_IS_ALL.bits() > 0;
Expand Down Expand Up @@ -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 {
Expand All @@ -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<T>, 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
}
}
}
47 changes: 37 additions & 10 deletions src/console/api.rs
Original file line number Diff line number Diff line change
@@ -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::{
Expand All @@ -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<Arc<AppShareData>>) -> impl Responder {
pub async fn query_namespace_list(
req: HttpRequest,
app_data: web::Data<Arc<AppShareData>>,
) -> 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()
Expand All @@ -35,13 +47,18 @@ pub async fn query_namespace_list(app_data: web::Data<Arc<AppShareData>>) -> imp
}

pub async fn add_namespace(
req: HttpRequest,
param: web::Form<NamespaceInfo>,
app_data: web::Data<Arc<AppShareData>>,
) -> impl Responder {
let mut param = param.0;
if StringUtils::is_option_empty_arc(&param.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(&param.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);
Expand All @@ -61,9 +78,14 @@ pub async fn add_namespace(
}

pub async fn update_namespace(
req: HttpRequest,
param: web::Form<NamespaceInfo>,
app_data: web::Data<Arc<AppShareData>>,
) -> impl Responder {
let namespace_privilege = user_namespace_privilege!(req);
if !namespace_privilege.check_option_value_permission(&param.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);
Expand All @@ -83,9 +105,14 @@ pub async fn update_namespace(
}

pub async fn remove_namespace(
req: HttpRequest,
param: web::Form<NamespaceInfo>,
app_data: web::Data<Arc<AppShareData>>,
) -> impl Responder {
let namespace_privilege = user_namespace_privilege!(req);
if !namespace_privilege.check_option_value_permission(&param.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);
Expand Down
1 change: 1 addition & 0 deletions src/console/login_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()),
Expand Down
52 changes: 50 additions & 2 deletions src/console/v2/namespace_api.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,53 @@
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<Arc<AppShareData>>) -> impl Responder {
pub async fn query_namespace_list(
req: HttpRequest,
app_data: web::Data<Arc<AppShareData>>,
) -> 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<NamespaceInfo>,
app_data: web::Data<Arc<AppShareData>>,
) -> impl Responder {
let mut param = param.0;
if StringUtils::is_option_empty_arc(&param.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(&param.namespace_id, false) {
return HttpResponse::Ok().json(ApiResult::<()>::error(
NO_PERMISSION.to_string(),
Some(format!(
"user no such namespace permission: {:?}",
&param.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(
Expand All @@ -32,9 +58,20 @@ pub async fn add_namespace(
}

pub async fn update_namespace(
req: HttpRequest,
param: web::Json<NamespaceInfo>,
app_data: web::Data<Arc<AppShareData>>,
) -> 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
)),
));
}
match NamespaceUtils::update_namespace(&app_data, param.0).await {
Ok(_) => HttpResponse::Ok().json(ApiResult::success(Some(true))),
Err(e) => HttpResponse::Ok().json(ApiResult::<()>::error(
Expand All @@ -45,9 +82,20 @@ pub async fn update_namespace(
}

pub async fn remove_namespace(
req: HttpRequest,
param: web::Json<NamespaceInfo>,
app_data: web::Data<Arc<AppShareData>>,
) -> 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
)),
));
}
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(
Expand Down
6 changes: 3 additions & 3 deletions src/user/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down

0 comments on commit b7c4aab

Please sign in to comment.