use super::PermissionResult;
use crate::config::AuthConfig;
use crate::permission::{PermissionManager, GOD_SPACE_ID};
use crate::session::ClientSession;
use linkrs_core::{Permission, RoleType};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OperationType {
ReadSpace, WriteSpace,
ReadSchema, WriteSchema,
ReadData, WriteData,
ReadUser, WriteUser,
WriteRole,
Show, ChangePassword, }
pub struct PermissionChecker {
permission_manager: PermissionManager,
auth_config: AuthConfig,
}
impl PermissionChecker {
pub fn new(permission_manager: PermissionManager, auth_config: AuthConfig) -> Self {
Self {
permission_manager,
auth_config,
}
}
fn is_authorization_enabled(&self) -> bool {
self.auth_config.enable_authorize
}
pub fn check_permission(
&self,
session: &ClientSession,
operation: OperationType,
target_space: Option<i64>,
target_user: Option<&str>,
target_role: Option<RoleType>,
) -> PermissionResult<()> {
use crate::permission::PermissionError;
if !self.is_authorization_enabled() {
return Ok(());
}
let username = session.user();
if self.permission_manager.is_god(&username) {
match operation {
OperationType::ChangePassword => {
return self.check_change_password(&username, target_user, session);
}
_ => return Ok(()),
}
}
match operation {
OperationType::ReadSpace => self.check_read_space(&username, target_space),
OperationType::WriteSpace => {
if self.permission_manager.is_admin(&username) {
Ok(())
} else {
Err(PermissionError::only_god_can_manage_spaces())
}
}
OperationType::ReadSchema => self.check_read_schema(&username, target_space),
OperationType::WriteSchema => self.check_write_schema(&username, target_space),
OperationType::ReadData => self.check_read_data(&username, target_space),
OperationType::WriteData => self.check_write_data(session, &username, target_space),
OperationType::ReadUser => self.check_read_user(&username, target_user, session),
OperationType::WriteUser => {
if self.permission_manager.is_admin(&username) {
Ok(())
} else {
Err(PermissionError::only_god_can_manage_users())
}
}
OperationType::WriteRole => {
self.check_write_role(&username, target_space, target_user, target_role)
}
OperationType::Show => {
Ok(())
}
OperationType::ChangePassword => {
self.check_change_password(&username, target_user, session)
}
}
}
fn check_read_space(&self, username: &str, target_space: Option<i64>) -> PermissionResult<()> {
use crate::permission::PermissionError;
let space_id = target_space.ok_or(PermissionError::space_id_required())?;
self.permission_manager
.check_permission(username, space_id, Permission::Read)
}
fn check_read_schema(&self, username: &str, target_space: Option<i64>) -> PermissionResult<()> {
use crate::permission::PermissionError;
let space_id = target_space.ok_or(PermissionError::schema_space_id_required())?;
self.permission_manager
.check_permission(username, space_id, Permission::Read)
}
fn check_write_schema(
&self,
username: &str,
target_space: Option<i64>,
) -> PermissionResult<()> {
use crate::permission::PermissionError;
let space_id = target_space.ok_or(PermissionError::schema_write_space_id_required())?;
if !self.permission_manager.is_schema_admin(username) {
return Err(PermissionError::schema_write_permission_denied(
space_id,
username.to_string(),
));
}
self.permission_manager
.check_permission(username, space_id, Permission::Write)
}
fn check_read_data(&self, username: &str, target_space: Option<i64>) -> PermissionResult<()> {
use crate::permission::PermissionError;
let space_id = target_space.ok_or(PermissionError::data_read_space_id_required())?;
self.permission_manager
.check_permission(username, space_id, Permission::Read)
}
fn check_write_data(
&self,
session: &ClientSession,
username: &str,
target_space: Option<i64>,
) -> PermissionResult<()> {
use crate::permission::PermissionError;
let space_id = target_space.ok_or(PermissionError::data_write_space_id_required())?;
if let Some(role) = session.role_with_space(space_id) {
if role == RoleType::Guest {
return Err(PermissionError::guest_cannot_write_data());
}
}
self.permission_manager
.check_permission(username, space_id, Permission::Write)
}
fn check_read_user(
&self,
username: &str,
target_user: Option<&str>,
_session: &ClientSession,
) -> PermissionResult<()> {
use crate::permission::PermissionError;
if let Some(target) = target_user {
if username == target {
return Ok(());
}
}
if self.permission_manager.is_admin(username) {
return Ok(());
}
Err(PermissionError::cannot_read_user_info())
}
fn check_write_role(
&self,
username: &str,
target_space: Option<i64>,
target_user: Option<&str>,
target_role: Option<RoleType>,
) -> PermissionResult<()> {
use crate::permission::PermissionError;
let space_id = target_space.ok_or(PermissionError::role_operation_space_id_required())?;
let role = target_role.ok_or(PermissionError::role_operation_target_role_required())?;
let operator_role = self
.permission_manager
.get_role(username, space_id)
.or_else(|| self.permission_manager.get_role(username, GOD_SPACE_ID));
let can_manage = matches!(
operator_role,
Some(RoleType::God) | Some(RoleType::Admin) | Some(RoleType::Dba)
);
if !can_manage {
return Err(PermissionError::only_admin_or_god_can_manage_roles());
}
if let Some(target) = target_user {
if username == target {
return Err(PermissionError::cannot_modify_own_role());
}
}
if let Some(op_role) = operator_role {
if !op_role.can_grant(role) {
return Err(PermissionError::cannot_grant_role(format!("{:?}", role)));
}
} else {
return Err(PermissionError::only_admin_or_god_can_manage_roles());
}
Ok(())
}
fn check_change_password(
&self,
username: &str,
target_user: Option<&str>,
session: &ClientSession,
) -> PermissionResult<()> {
use crate::permission::PermissionError;
let target = target_user.ok_or(PermissionError::change_password_target_user_required())?;
if username == target {
return Ok(());
}
if session.is_god() {
return Ok(());
}
Err(PermissionError::can_only_change_own_password())
}
pub fn can_read_space(&self, session: &ClientSession, space_id: i64) -> PermissionResult<()> {
self.check_permission(
session,
OperationType::ReadSpace,
Some(space_id),
None,
None,
)
}
pub fn can_write_space(&self, session: &ClientSession) -> PermissionResult<()> {
self.check_permission(session, OperationType::WriteSpace, None, None, None)
}
pub fn can_read_schema(&self, session: &ClientSession, space_id: i64) -> PermissionResult<()> {
self.check_permission(
session,
OperationType::ReadSchema,
Some(space_id),
None,
None,
)
}
pub fn can_write_schema(&self, session: &ClientSession, space_id: i64) -> PermissionResult<()> {
self.check_permission(
session,
OperationType::WriteSchema,
Some(space_id),
None,
None,
)
}
pub fn can_read_data(&self, session: &ClientSession, space_id: i64) -> PermissionResult<()> {
self.check_permission(session, OperationType::ReadData, Some(space_id), None, None)
}
pub fn can_write_data(&self, session: &ClientSession, space_id: i64) -> PermissionResult<()> {
self.check_permission(
session,
OperationType::WriteData,
Some(space_id),
None,
None,
)
}
pub fn can_read_user(
&self,
session: &ClientSession,
target_user: &str,
) -> PermissionResult<()> {
self.check_permission(
session,
OperationType::ReadUser,
None,
Some(target_user),
None,
)
}
pub fn can_write_user(&self, session: &ClientSession) -> PermissionResult<()> {
self.check_permission(session, OperationType::WriteUser, None, None, None)
}
pub fn can_write_role(
&self,
session: &ClientSession,
space_id: i64,
target_user: &str,
target_role: RoleType,
) -> PermissionResult<()> {
self.check_permission(
session,
OperationType::WriteRole,
Some(space_id),
Some(target_user),
Some(target_role),
)
}
pub fn can_change_password(
&self,
session: &ClientSession,
target_user: &str,
) -> PermissionResult<()> {
self.check_permission(
session,
OperationType::ChangePassword,
None,
Some(target_user),
None,
)
}
pub fn can_show(&self, session: &ClientSession) -> PermissionResult<()> {
self.check_permission(session, OperationType::Show, None, None, None)
}
pub fn permission_manager(&self) -> &PermissionManager {
&self.permission_manager
}
pub fn auth_config(&self) -> &AuthConfig {
&self.auth_config
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::AuthConfig;
use crate::permission::GOD_SPACE_ID;
use crate::session::{ClientSession, Session};
use std::sync::Arc;
fn create_test_config() -> AuthConfig {
AuthConfig {
enable_authorize: true,
failed_login_attempts: 5,
session_idle_timeout_secs: 3600,
default_username: "root".to_string(),
bootstrap_password: "root".to_string(),
force_change_default_password: true,
bcrypt_cost: 12,
}
}
fn create_test_checker() -> PermissionChecker {
let pm = PermissionManager::new();
pm.grant_role("user1", 1, RoleType::User)
.expect("Failed to grant role");
pm.grant_role("admin1", 1, RoleType::Admin)
.expect("Failed to grant role");
pm.grant_role("dba1", 1, RoleType::Dba)
.expect("Failed to grant role");
pm.grant_role("guest1", 1, RoleType::Guest)
.expect("Failed to grant role");
let config = create_test_config();
PermissionChecker::new(pm, config)
}
fn create_test_session(username: &str, is_god: bool) -> Arc<ClientSession> {
let session = Session {
session_id: 1,
user_name: username.to_string(),
space_name: None,
graph_addr: Some("127.0.0.1:1234".to_string()),
timezone: None,
};
let client_session = ClientSession::new(session);
if is_god {
client_session.set_role(GOD_SPACE_ID, RoleType::God);
}
client_session
}
fn create_user_session(username: &str, role: RoleType, space_id: i64) -> Arc<ClientSession> {
let session = Session {
session_id: 1,
user_name: username.to_string(),
space_name: Some("test_space".to_string()),
graph_addr: Some("127.0.0.1:1234".to_string()),
timezone: None,
};
let client_session = ClientSession::new(session);
client_session.set_role(space_id, role);
client_session
}
#[test]
fn test_disabled_authorization() {
let mut config = create_test_config();
config.enable_authorize = false;
let pm = PermissionManager::new();
let checker = PermissionChecker::new(pm, config);
let session = create_test_session("any_user", false);
assert!(checker.can_write_space(&session).is_ok());
assert!(checker.can_write_schema(&session, 1).is_ok());
assert!(checker.can_write_data(&session, 1).is_ok());
}
#[test]
fn test_god_role_has_all_permissions() {
let checker = create_test_checker();
let god_session = create_test_session("root", true);
assert!(checker.can_write_space(&god_session).is_ok());
assert!(checker.can_write_schema(&god_session, 1).is_ok());
assert!(checker.can_write_data(&god_session, 1).is_ok());
assert!(checker.can_write_user(&god_session).is_ok());
assert!(checker
.can_write_role(&god_session, 1, "admin", RoleType::Admin)
.is_ok());
}
#[test]
fn test_user_cannot_write_space() {
let checker = create_test_checker();
let user_session = create_user_session("user1", RoleType::User, 1);
assert!(checker.can_write_space(&user_session).is_err());
}
#[test]
fn test_user_cannot_write_schema() {
let checker = create_test_checker();
let user_session = create_user_session("user1", RoleType::User, 1);
assert!(checker.can_write_schema(&user_session, 1).is_err());
}
#[test]
fn test_admin_can_write_schema() {
let checker = create_test_checker();
let admin_session = create_user_session("admin1", RoleType::Admin, 1);
assert!(checker.can_write_schema(&admin_session, 1).is_ok());
}
#[test]
fn test_guest_cannot_write_data() {
let checker = create_test_checker();
let guest_session = create_user_session("guest1", RoleType::Guest, 1);
assert!(checker.can_write_data(&guest_session, 1).is_err());
assert!(checker.can_read_data(&guest_session, 1).is_ok());
}
#[test]
fn test_user_can_read_own_info() {
let checker = create_test_checker();
let user_session = create_user_session("user1", RoleType::User, 1);
assert!(checker.can_read_user(&user_session, "user1").is_ok());
assert!(checker.can_read_user(&user_session, "user2").is_err());
}
#[test]
fn test_change_password() {
let checker = create_test_checker();
let user_session = create_user_session("user1", RoleType::User, 1);
assert!(checker.can_change_password(&user_session, "user1").is_ok());
assert!(checker.can_change_password(&user_session, "user2").is_err());
}
#[test]
fn test_admin_can_grant_lower_roles() {
let checker = create_test_checker();
let admin_session = create_user_session("admin1", RoleType::Admin, 1);
assert!(checker
.can_write_role(&admin_session, 1, "user1", RoleType::User)
.is_ok());
assert!(checker
.can_write_role(&admin_session, 1, "guest1", RoleType::Guest)
.is_ok());
assert!(checker
.can_write_role(&admin_session, 1, "admin2", RoleType::Admin)
.is_err());
}
#[test]
fn test_operation_matrix_space_and_user() {
let checker = create_test_checker();
let god_session = create_test_session("root", true);
let admin_session = create_user_session("admin1", RoleType::Admin, 1);
let dba_session = create_user_session("dba1", RoleType::Dba, 1);
let user_session = create_user_session("user1", RoleType::User, 1);
let guest_session = create_user_session("guest1", RoleType::Guest, 1);
assert!(checker.can_write_space(&god_session).is_ok());
assert!(checker.can_write_space(&admin_session).is_ok());
assert!(checker.can_write_space(&dba_session).is_err());
assert!(checker.can_write_space(&user_session).is_err());
assert!(checker.can_write_space(&guest_session).is_err());
assert!(checker.can_write_user(&god_session).is_ok());
assert!(checker.can_write_user(&admin_session).is_ok());
assert!(checker.can_write_user(&dba_session).is_err());
assert!(checker.can_write_user(&user_session).is_err());
assert!(checker.can_write_user(&guest_session).is_err());
}
#[test]
fn test_operation_matrix_schema_includes_dba() {
let checker = create_test_checker();
let admin_session = create_user_session("admin1", RoleType::Admin, 1);
let dba_session = create_user_session("dba1", RoleType::Dba, 1);
let user_session = create_user_session("user1", RoleType::User, 1);
let guest_session = create_user_session("guest1", RoleType::Guest, 1);
assert!(checker.can_write_schema(&admin_session, 1).is_ok());
assert!(checker.can_write_schema(&dba_session, 1).is_ok());
assert!(checker.can_write_schema(&user_session, 1).is_err());
assert!(checker.can_write_schema(&guest_session, 1).is_err());
assert!(checker.can_read_schema(&guest_session, 1).is_ok());
assert!(checker.can_read_space(&guest_session, 1).is_ok());
assert!(checker.can_read_data(&guest_session, 1).is_ok());
assert!(checker.can_show(&guest_session).is_ok());
}
#[test]
fn test_operation_matrix_role_and_password() {
let checker = create_test_checker();
let god_session = create_test_session("root", true);
let admin_session = create_user_session("admin1", RoleType::Admin, 1);
let dba_session = create_user_session("dba1", RoleType::Dba, 1);
let user_session = create_user_session("user1", RoleType::User, 1);
assert!(checker
.can_write_role(&dba_session, 1, "someone", RoleType::User)
.is_ok());
assert!(checker
.can_write_role(&dba_session, 1, "someone", RoleType::Guest)
.is_ok());
assert!(checker
.can_write_role(&dba_session, 1, "someone", RoleType::Admin)
.is_err());
assert!(checker
.can_write_role(&user_session, 1, "someone", RoleType::Guest)
.is_err());
assert!(checker
.can_write_role(&admin_session, 1, "admin1", RoleType::User)
.is_err());
assert!(checker.can_change_password(&user_session, "user1").is_ok());
assert!(checker.can_change_password(&user_session, "other").is_err());
assert!(checker.can_change_password(&god_session, "other").is_ok());
}
}