385 lines
12 KiB
Rust
385 lines
12 KiB
Rust
use crate::error::{ControlCenterError, Result};
|
|
use crate::models::{CreateRoleRequest, Role, RoleResponse, UpdateRoleRequest};
|
|
use crate::services::DatabaseService;
|
|
use std::sync::Arc;
|
|
use tracing::info;
|
|
use uuid::Uuid;
|
|
use validator::Validate;
|
|
|
|
/// Role service for managing role operations
|
|
#[derive(Clone)]
|
|
pub struct RoleService {
|
|
db: Arc<DatabaseService>,
|
|
}
|
|
|
|
impl RoleService {
|
|
/// Create a new role service
|
|
pub fn new(db: Arc<DatabaseService>) -> Self {
|
|
Self { db }
|
|
}
|
|
|
|
/// Create a new role
|
|
pub async fn create_role(&self, request: CreateRoleRequest) -> Result<RoleResponse> {
|
|
// Validate request
|
|
request.validate()?;
|
|
|
|
// Check if role already exists
|
|
if self.find_by_name(&request.name).await?.is_some() {
|
|
return Err(ControlCenterError::Conflict(format!(
|
|
"Role with name '{}' already exists",
|
|
request.name
|
|
)));
|
|
}
|
|
|
|
// Create role
|
|
let role = Role::new(
|
|
request.name,
|
|
request.description,
|
|
request.permissions,
|
|
false, // User-created roles are not system roles
|
|
);
|
|
|
|
// Save to database
|
|
let created_role: Role = self
|
|
.db
|
|
.db
|
|
.create("roles")
|
|
.content(role)
|
|
.await?
|
|
.ok_or_else(|| ControlCenterError::Database("Failed to create role".to_string()))?;
|
|
|
|
info!("Created role: {}", created_role.name);
|
|
|
|
Ok(RoleResponse::from(created_role))
|
|
}
|
|
|
|
/// Get role by ID
|
|
pub async fn get_by_id(&self, role_id: Uuid) -> Result<Role> {
|
|
let query = "SELECT * FROM roles WHERE role_id = $role_id";
|
|
let mut result = self.db.db.query(query).bind(("role_id", role_id)).await?;
|
|
|
|
let roles: Vec<Role> = result.take(0)?;
|
|
roles
|
|
.into_iter()
|
|
.next()
|
|
.ok_or_else(|| ControlCenterError::RoleNotFound(role_id.to_string()))
|
|
}
|
|
|
|
/// Get role response by ID (without sensitive data)
|
|
pub async fn get_role_response_by_id(&self, role_id: Uuid) -> Result<RoleResponse> {
|
|
let role = self.get_by_id(role_id).await?;
|
|
Ok(RoleResponse::from(role))
|
|
}
|
|
|
|
/// Find role by name
|
|
pub async fn find_by_name(&self, name: &str) -> Result<Option<Role>> {
|
|
let query = "SELECT * FROM roles WHERE name = $name";
|
|
let mut result = self.db.db.query(query).bind(("name", name.to_string())).await?;
|
|
|
|
let roles: Vec<Role> = result.take(0)?;
|
|
Ok(roles.into_iter().next())
|
|
}
|
|
|
|
/// Update role
|
|
pub async fn update_role(
|
|
&self,
|
|
role_id: Uuid,
|
|
request: UpdateRoleRequest,
|
|
) -> Result<RoleResponse> {
|
|
// Validate request
|
|
request.validate()?;
|
|
|
|
// Get existing role
|
|
let mut role = self.get_by_id(role_id).await?;
|
|
|
|
// Check if it's a system role
|
|
if role.is_system {
|
|
return Err(ControlCenterError::Forbidden(
|
|
"Cannot modify system roles".to_string(),
|
|
));
|
|
}
|
|
|
|
// Check for conflicts if name is being changed
|
|
if let Some(ref new_name) = request.name {
|
|
if new_name != &role.name
|
|
&& self.find_by_name(new_name).await?.is_some() {
|
|
return Err(ControlCenterError::Conflict(format!(
|
|
"Role with name '{}' already exists",
|
|
new_name
|
|
)));
|
|
}
|
|
}
|
|
|
|
// Update role
|
|
role.update(request);
|
|
|
|
// Save to database
|
|
let query = "UPDATE roles SET
|
|
name = $name,
|
|
description = $description,
|
|
permissions = $permissions,
|
|
updated_at = $updated_at
|
|
WHERE role_id = $role_id";
|
|
|
|
// Clone data before using in database operations to avoid borrow issues
|
|
let name = role.name.clone();
|
|
let description = role.description.clone();
|
|
let permissions = role.permissions.clone();
|
|
let updated_at = role.updated_at;
|
|
|
|
self.db
|
|
.db
|
|
.query(query)
|
|
.bind(("name", name))
|
|
.bind(("description", description))
|
|
.bind(("permissions", permissions))
|
|
.bind(("updated_at", updated_at))
|
|
.bind(("role_id", role_id))
|
|
.await?;
|
|
|
|
info!("Updated role: {}", role.name);
|
|
|
|
Ok(RoleResponse::from(role))
|
|
}
|
|
|
|
/// Delete role
|
|
pub async fn delete_role(&self, role_id: Uuid) -> Result<()> {
|
|
// Get existing role
|
|
let role = self.get_by_id(role_id).await?;
|
|
|
|
// Check if it's a system role
|
|
if role.is_system {
|
|
return Err(ControlCenterError::Forbidden(
|
|
"Cannot delete system roles".to_string(),
|
|
));
|
|
}
|
|
|
|
// Check if role is in use by any users
|
|
let user_count = self.get_users_with_role_count(&role.name).await?;
|
|
if user_count > 0 {
|
|
return Err(ControlCenterError::Conflict(format!(
|
|
"Cannot delete role '{}' - it is assigned to {} users",
|
|
role.name, user_count
|
|
)));
|
|
}
|
|
|
|
// Delete role
|
|
let query = "DELETE roles WHERE role_id = $role_id";
|
|
self.db.db.query(query).bind(("role_id", role_id)).await?;
|
|
|
|
info!("Deleted role: {}", role.name);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// List all roles
|
|
pub async fn list_roles(
|
|
&self,
|
|
include_system: Option<bool>,
|
|
limit: Option<usize>,
|
|
offset: Option<usize>,
|
|
) -> Result<Vec<RoleResponse>> {
|
|
let mut query = "SELECT * FROM roles".to_string();
|
|
|
|
if let Some(include_system) = include_system {
|
|
if !include_system {
|
|
query.push_str(" WHERE is_system = false");
|
|
}
|
|
}
|
|
|
|
query.push_str(" ORDER BY created_at DESC");
|
|
|
|
if let Some(limit) = limit {
|
|
query.push_str(&format!(" LIMIT {}", limit));
|
|
}
|
|
|
|
if let Some(offset) = offset {
|
|
query.push_str(&format!(" START {}", offset));
|
|
}
|
|
|
|
let mut result = self.db.db.query(&query).await?;
|
|
let roles: Vec<Role> = result.take(0)?;
|
|
|
|
Ok(roles.into_iter().map(RoleResponse::from).collect())
|
|
}
|
|
|
|
/// Add permission to role
|
|
pub async fn add_permission(&self, role_id: Uuid, permission: String) -> Result<RoleResponse> {
|
|
let mut role = self.get_by_id(role_id).await?;
|
|
|
|
// Check if it's a system role
|
|
if role.is_system {
|
|
return Err(ControlCenterError::Forbidden(
|
|
"Cannot modify system roles".to_string(),
|
|
));
|
|
}
|
|
|
|
// Add permission if not already present
|
|
role.add_permission(permission.clone());
|
|
|
|
// Save to database
|
|
let query = "UPDATE roles SET permissions = $permissions, updated_at = $updated_at WHERE role_id = $role_id";
|
|
let permissions = role.permissions.clone();
|
|
let updated_at = role.updated_at;
|
|
self.db
|
|
.db
|
|
.query(query)
|
|
.bind(("permissions", permissions))
|
|
.bind(("updated_at", updated_at))
|
|
.bind(("role_id", role_id))
|
|
.await?;
|
|
|
|
info!("Added permission '{}' to role: {}", permission, role.name);
|
|
|
|
Ok(RoleResponse::from(role))
|
|
}
|
|
|
|
/// Remove permission from role
|
|
pub async fn remove_permission(
|
|
&self,
|
|
role_id: Uuid,
|
|
permission: String,
|
|
) -> Result<RoleResponse> {
|
|
let mut role = self.get_by_id(role_id).await?;
|
|
|
|
// Check if it's a system role
|
|
if role.is_system {
|
|
return Err(ControlCenterError::Forbidden(
|
|
"Cannot modify system roles".to_string(),
|
|
));
|
|
}
|
|
|
|
// Remove permission
|
|
role.remove_permission(&permission);
|
|
|
|
// Save to database
|
|
let query = "UPDATE roles SET permissions = $permissions, updated_at = $updated_at WHERE role_id = $role_id";
|
|
let permissions = role.permissions.clone();
|
|
let updated_at = role.updated_at;
|
|
self.db
|
|
.db
|
|
.query(query)
|
|
.bind(("permissions", permissions))
|
|
.bind(("updated_at", updated_at))
|
|
.bind(("role_id", role_id))
|
|
.await?;
|
|
|
|
info!("Removed permission '{}' from role: {}", permission, role.name);
|
|
|
|
Ok(RoleResponse::from(role))
|
|
}
|
|
|
|
/// Get count of users with a specific role
|
|
async fn get_users_with_role_count(&self, role_name: &str) -> Result<i64> {
|
|
let query = "SELECT count() FROM users WHERE $role_name IN roles";
|
|
let mut result = self.db.db.query(query).bind(("role_name", role_name.to_string())).await?;
|
|
|
|
let counts: Vec<i64> = result.take(0)?;
|
|
Ok(counts.into_iter().next().unwrap_or(0))
|
|
}
|
|
|
|
/// Get role count
|
|
pub async fn get_role_count(&self, include_system: Option<bool>) -> Result<i64> {
|
|
let mut query = "SELECT count() FROM roles".to_string();
|
|
|
|
if let Some(include_system) = include_system {
|
|
if !include_system {
|
|
query.push_str(" WHERE is_system = false");
|
|
}
|
|
}
|
|
|
|
let mut result = self.db.db.query(&query).await?;
|
|
let counts: Vec<i64> = result.take(0)?;
|
|
|
|
Ok(counts.into_iter().next().unwrap_or(0))
|
|
}
|
|
|
|
/// Get roles by names
|
|
pub async fn get_roles_by_names(&self, role_names: &[String]) -> Result<Vec<Role>> {
|
|
if role_names.is_empty() {
|
|
return Ok(Vec::new());
|
|
}
|
|
|
|
let query = "SELECT * FROM roles WHERE name IN $role_names";
|
|
let mut result = self.db.db.query(query).bind(("role_names", role_names.to_vec())).await?;
|
|
|
|
let roles: Vec<Role> = result.take(0)?;
|
|
Ok(roles)
|
|
}
|
|
|
|
/// Check if role has permission
|
|
pub async fn has_permission(&self, role_name: &str, permission: &str) -> Result<bool> {
|
|
if let Some(role) = self.find_by_name(role_name).await? {
|
|
Ok(role.has_permission(permission))
|
|
} else {
|
|
Ok(false)
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_create_role_request_validation() {
|
|
// Valid request
|
|
let valid_request = CreateRoleRequest {
|
|
name: "test_role".to_string(),
|
|
description: Some("Test role description".to_string()),
|
|
permissions: Some(vec!["read".to_string(), "write".to_string()]),
|
|
};
|
|
assert!(valid_request.validate().is_ok());
|
|
|
|
// Short name
|
|
let short_name = CreateRoleRequest {
|
|
name: "a".to_string(),
|
|
description: None,
|
|
permissions: None,
|
|
};
|
|
assert!(short_name.validate().is_err());
|
|
|
|
// Long name
|
|
let long_name = CreateRoleRequest {
|
|
name: "a".repeat(51),
|
|
description: None,
|
|
permissions: None,
|
|
};
|
|
assert!(long_name.validate().is_err());
|
|
|
|
// Long description
|
|
let long_description = CreateRoleRequest {
|
|
name: "test_role".to_string(),
|
|
description: Some("a".repeat(201)),
|
|
permissions: None,
|
|
};
|
|
assert!(long_description.validate().is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_update_role_request_validation() {
|
|
// Valid request
|
|
let valid_request = UpdateRoleRequest {
|
|
name: Some("updated_role".to_string()),
|
|
description: Some("Updated description".to_string()),
|
|
permissions: Some(vec!["read".to_string()]),
|
|
};
|
|
assert!(valid_request.validate().is_ok());
|
|
|
|
// Short name
|
|
let short_name = UpdateRoleRequest {
|
|
name: Some("a".to_string()),
|
|
description: None,
|
|
permissions: None,
|
|
};
|
|
assert!(short_name.validate().is_err());
|
|
|
|
// Valid empty update
|
|
let empty_update = UpdateRoleRequest {
|
|
name: None,
|
|
description: None,
|
|
permissions: None,
|
|
};
|
|
assert!(empty_update.validate().is_ok());
|
|
}
|
|
} |