use std::sync::Arc; use tracing::info; use uuid::Uuid; use validator::Validate; use crate::error::{http, ControlCenterError, Result}; use crate::models::{Permission, PermissionCheckRequest, PermissionResponse}; use crate::services::DatabaseService; /// Permission service for managing permission operations #[derive(Clone)] pub struct PermissionService { db: Arc, } impl PermissionService { /// Create a new permission service pub fn new(db: Arc) -> Self { Self { db } } /// Get permission by ID pub async fn get_by_id(&self, permission_id: Uuid) -> Result { let query = "SELECT * FROM permissions WHERE permission_id = $permission_id"; let mut result = self .db .db .query(query) .bind(("permission_id", permission_id)) .await?; let permissions: Vec = result.take(0)?; permissions .into_iter() .next() .ok_or_else(|| ControlCenterError::PermissionNotFound(permission_id.to_string())) } /// Get permission response by ID pub async fn get_permission_response_by_id( &self, permission_id: Uuid, ) -> Result { let permission = self.get_by_id(permission_id).await?; Ok(PermissionResponse::from(permission)) } /// Find permission by name pub async fn find_by_name(&self, name: &str) -> Result> { let query = "SELECT * FROM permissions WHERE name = $name"; let mut result = self .db .db .query(query) .bind(("name", name.to_string())) .await?; let permissions: Vec = result.take(0)?; Ok(permissions.into_iter().next()) } /// List all permissions pub async fn list_permissions( &self, resource: Option, action: Option, limit: Option, offset: Option, ) -> Result> { let mut query = "SELECT * FROM permissions".to_string(); let mut conditions = Vec::new(); if let Some(resource) = resource { conditions.push(format!("resource = '{}'", resource)); } if let Some(action) = action { conditions.push(format!("action = '{}'", action)); } if !conditions.is_empty() { query.push_str(&format!(" WHERE {}", conditions.join(" AND "))); } query.push_str(" ORDER BY resource, action"); 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 permissions: Vec = result.take(0)?; Ok(permissions .into_iter() .map(PermissionResponse::from) .collect()) } /// Check if a permission exists pub async fn permission_exists(&self, name: &str) -> Result { let permission = self.find_by_name(name).await?; Ok(permission.is_some()) } /// Check permission by resource and action pub async fn check_permission(&self, request: PermissionCheckRequest) -> Result { // Validate request request .validate() .map_err(|e| ControlCenterError::Http(http::HttpError::Validation(e.to_string())))?; let permission_name = format!("{}:{}", request.resource, request.action); self.permission_exists(&permission_name).await } /// Get permissions by resource pub async fn get_permissions_by_resource( &self, resource: &str, ) -> Result> { self.list_permissions(Some(resource.to_string()), None, None, None) .await } /// Get permissions by action pub async fn get_permissions_by_action(&self, action: &str) -> Result> { self.list_permissions(None, Some(action.to_string()), None, None) .await } /// Get all unique resources pub async fn get_resources(&self) -> Result> { let query = "SELECT DISTINCT resource FROM permissions ORDER BY resource"; let mut result = self.db.db.query(query).await?; let resources: Vec = result.take(0)?; Ok(resources) } /// Get all unique actions pub async fn get_actions(&self) -> Result> { let query = "SELECT DISTINCT action FROM permissions ORDER BY action"; let mut result = self.db.db.query(query).await?; let actions: Vec = result.take(0)?; Ok(actions) } /// Get permission count pub async fn get_permission_count(&self, resource: Option) -> Result { let mut query = "SELECT count() FROM permissions".to_string(); if let Some(resource) = resource { query.push_str(&format!(" WHERE resource = '{}'", resource)); } let mut result = self.db.db.query(&query).await?; let counts: Vec = result.take(0)?; Ok(counts.into_iter().next().unwrap_or(0)) } /// Get permissions by names pub async fn get_permissions_by_names( &self, permission_names: &[String], ) -> Result> { if permission_names.is_empty() { return Ok(Vec::new()); } let query = "SELECT * FROM permissions WHERE name IN $permission_names"; let mut result = self .db .db .query(query) .bind(("permission_names", permission_names.to_vec())) .await?; let permissions: Vec = result.take(0)?; Ok(permissions) } /// Validate permissions exist pub async fn validate_permissions_exist( &self, permission_names: &[String], ) -> Result> { let existing_permissions = self.get_permissions_by_names(permission_names).await?; let existing_names: std::collections::HashSet<_> = existing_permissions .iter() .map(|p| p.name.clone()) .collect(); let missing_permissions: Vec = permission_names .iter() .filter(|name| !existing_names.contains(*name)) .cloned() .collect(); Ok(missing_permissions) } /// Create a custom permission (for advanced use cases) pub async fn create_permission( &self, name: String, resource: String, action: String, description: Option, ) -> Result { // Check if permission already exists if self.find_by_name(&name).await?.is_some() { let msg = format!("Permission '{}' already exists", name); return Err(ControlCenterError::Http(http::conflict(&msg))); } // Create permission let permission = Permission::new(name, resource, action, description); // Save to database let created_permission: Permission = self .db .db .create("permissions") .content(permission) .await? .ok_or_else(|| { ControlCenterError::from(crate::error::database::DatabaseError::Database( "Failed to create permission".to_string(), )) })?; info!("Created permission: {}", created_permission.name); Ok(PermissionResponse::from(created_permission)) } /// Delete a custom permission (system permissions cannot be deleted) pub async fn delete_permission(&self, permission_id: Uuid) -> Result<()> { let permission = self.get_by_id(permission_id).await?; // Check if permission is in use by any roles let roles_using_permission = self.get_roles_using_permission(&permission.name).await?; if !roles_using_permission.is_empty() { let msg = format!( "Cannot delete permission '{}' - it is used by {} roles: {}", permission.name, roles_using_permission.len(), roles_using_permission.join(", ") ); return Err(ControlCenterError::Http(http::conflict(&msg))); } // Delete permission let query = "DELETE permissions WHERE permission_id = $permission_id"; self.db .db .query(query) .bind(("permission_id", permission_id)) .await?; info!("Deleted permission: {}", permission.name); Ok(()) } /// Get roles that use a specific permission async fn get_roles_using_permission(&self, permission_name: &str) -> Result> { let query = "SELECT name FROM roles WHERE $permission_name IN permissions"; let mut result = self .db .db .query(query) .bind(("permission_name", permission_name.to_string())) .await?; let role_names: Vec = result.take(0)?; Ok(role_names) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_permission_check_request_validation() { // Valid request let valid_request = PermissionCheckRequest { resource: "users".to_string(), action: "read".to_string(), }; assert!(valid_request.validate().is_ok()); // Empty resource let empty_resource = PermissionCheckRequest { resource: "".to_string(), action: "read".to_string(), }; assert!(empty_resource.validate().is_err()); // Empty action let empty_action = PermissionCheckRequest { resource: "users".to_string(), action: "".to_string(), }; assert!(empty_action.validate().is_err()); // Both empty let both_empty = PermissionCheckRequest { resource: "".to_string(), action: "".to_string(), }; assert!(both_empty.validate().is_err()); } #[test] fn test_permission_name_format() { let resource = "users"; let action = "read"; let expected_name = "users:read"; let actual_name = format!("{}:{}", resource, action); assert_eq!(actual_name, expected_name); } }