role_system/
database.rs

1//! Database storage backend for the role system.
2
3#[cfg(feature = "database")]
4use crate::{
5    error::{Error, Result},
6    role::Role,
7    storage::Storage,
8};
9
10#[cfg(not(feature = "database"))]
11use crate::error::Error;
12
13#[cfg(feature = "database")]
14use async_trait::async_trait;
15#[cfg(feature = "database")]
16use serde_json;
17#[cfg(feature = "database")]
18use sqlx::{PgPool, Row};
19#[cfg(feature = "database")]
20use std::collections::HashMap;
21
22#[cfg(feature = "database")]
23/// PostgreSQL storage backend for the role system.
24pub struct DatabaseStorage {
25    pool: PgPool,
26    table_prefix: String,
27}
28
29#[cfg(feature = "database")]
30impl DatabaseStorage {
31    /// Create a new database storage backend.
32    pub async fn new(database_url: &str) -> Result<Self> {
33        let pool = PgPool::connect(database_url)
34            .await
35            .map_err(|e| Error::StorageError(format!("Database connection failed: {}", e)))?;
36
37        let storage = Self {
38            pool,
39            table_prefix: "rbac_".to_string(),
40        };
41
42        // Initialize database schema
43        storage.initialize_schema().await?;
44
45        Ok(storage)
46    }
47
48    /// Create a new database storage backend with custom table prefix.
49    pub async fn new_with_prefix(database_url: &str, table_prefix: String) -> Result<Self> {
50        let pool = PgPool::connect(database_url)
51            .await
52            .map_err(|e| Error::StorageError(format!("Database connection failed: {}", e)))?;
53
54        let storage = Self { pool, table_prefix };
55
56        // Initialize database schema
57        storage.initialize_schema().await?;
58
59        Ok(storage)
60    }
61
62    /// Initialize the database schema.
63    async fn initialize_schema(&self) -> Result<()> {
64        let roles_table = format!("{}roles", self.table_prefix);
65        let role_permissions_table = format!("{}role_permissions", self.table_prefix);
66        let subjects_table = format!("{}subjects", self.table_prefix);
67        let subject_roles_table = format!("{}subject_roles", self.table_prefix);
68
69        // Create roles table
70        let create_roles = format!(
71            r#"
72            CREATE TABLE IF NOT EXISTS {} (
73                name VARCHAR(255) PRIMARY KEY,
74                description TEXT,
75                active BOOLEAN NOT NULL DEFAULT true,
76                created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
77                updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
78            )
79            "#,
80            roles_table
81        );
82
83        // Create role permissions table
84        let create_role_permissions = format!(
85            r#"
86            CREATE TABLE IF NOT EXISTS {} (
87                id SERIAL PRIMARY KEY,
88                role_name VARCHAR(255) NOT NULL REFERENCES {}(name) ON DELETE CASCADE,
89                action VARCHAR(255) NOT NULL,
90                resource_type VARCHAR(255) NOT NULL,
91                instance_id VARCHAR(255),
92                condition_json JSONB,
93                created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
94                UNIQUE(role_name, action, resource_type, instance_id)
95            )
96            "#,
97            role_permissions_table, roles_table
98        );
99
100        // Create subjects table
101        let create_subjects = format!(
102            r#"
103            CREATE TABLE IF NOT EXISTS {} (
104                id VARCHAR(255) PRIMARY KEY,
105                subject_type VARCHAR(50) NOT NULL,
106                display_name VARCHAR(255),
107                metadata JSONB,
108                created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
109                updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
110            )
111            "#,
112            subjects_table
113        );
114
115        // Create subject roles table
116        let create_subject_roles = format!(
117            r#"
118            CREATE TABLE IF NOT EXISTS {} (
119                id SERIAL PRIMARY KEY,
120                subject_id VARCHAR(255) NOT NULL REFERENCES {}(id) ON DELETE CASCADE,
121                role_name VARCHAR(255) NOT NULL REFERENCES {}(name) ON DELETE CASCADE,
122                assigned_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
123                assigned_by VARCHAR(255),
124                UNIQUE(subject_id, role_name)
125            )
126            "#,
127            subject_roles_table, subjects_table, roles_table
128        );
129
130        // Execute schema creation
131        sqlx::query(&create_roles)
132            .execute(&self.pool)
133            .await
134            .map_err(|e| Error::StorageError(format!("Failed to create roles table: {}", e)))?;
135
136        sqlx::query(&create_role_permissions)
137            .execute(&self.pool)
138            .await
139            .map_err(|e| {
140                Error::StorageError(format!("Failed to create role_permissions table: {}", e))
141            })?;
142
143        sqlx::query(&create_subjects)
144            .execute(&self.pool)
145            .await
146            .map_err(|e| Error::StorageError(format!("Failed to create subjects table: {}", e)))?;
147
148        sqlx::query(&create_subject_roles)
149            .execute(&self.pool)
150            .await
151            .map_err(|e| {
152                Error::StorageError(format!("Failed to create subject_roles table: {}", e))
153            })?;
154
155        Ok(())
156    }
157
158    /// Get the roles table name.
159    fn roles_table(&self) -> String {
160        format!("{}roles", self.table_prefix)
161    }
162
163    /// Get the role permissions table name.
164    fn role_permissions_table(&self) -> String {
165        format!("{}role_permissions", self.table_prefix)
166    }
167
168    /// Get the subjects table name.
169    fn subjects_table(&self) -> String {
170        format!("{}subjects", self.table_prefix)
171    }
172
173    /// Get the subject roles table name.
174    fn subject_roles_table(&self) -> String {
175        format!("{}subject_roles", self.table_prefix)
176    }
177
178    /// Store role permissions in the database.
179    async fn store_role_permissions(&self, role_name: &str, role: &Role) -> Result<()> {
180        let table = self.role_permissions_table();
181
182        // Delete existing permissions for this role
183        let delete_query = format!("DELETE FROM {} WHERE role_name = $1", table);
184        sqlx::query(&delete_query)
185            .bind(role_name)
186            .execute(&self.pool)
187            .await
188            .map_err(|e| Error::StorageError(format!("Failed to delete old permissions: {}", e)))?;
189
190        // Insert new permissions
191        for permission in role.permissions().permissions() {
192            let insert_query = format!(
193                "INSERT INTO {} (role_name, action, resource_type, instance_id) VALUES ($1, $2, $3, $4)",
194                table
195            );
196
197            sqlx::query(&insert_query)
198                .bind(role_name)
199                .bind(permission.action())
200                .bind(permission.resource_type())
201                .bind(permission.instance())
202                .execute(&self.pool)
203                .await
204                .map_err(|e| Error::StorageError(format!("Failed to insert permission: {}", e)))?;
205        }
206
207        Ok(())
208    }
209
210    /// Load role permissions from the database.
211    async fn load_role_permissions(
212        &self,
213        role_name: &str,
214    ) -> Result<Vec<crate::permission::Permission>> {
215        let table = self.role_permissions_table();
216        let query = format!(
217            "SELECT action, resource_type, instance_id FROM {} WHERE role_name = $1",
218            table
219        );
220
221        let rows = sqlx::query(&query)
222            .bind(role_name)
223            .fetch_all(&self.pool)
224            .await
225            .map_err(|e| Error::StorageError(format!("Failed to load permissions: {}", e)))?;
226
227        let mut permissions = Vec::new();
228        for row in rows {
229            let action: String = row.get("action");
230            let resource_type: String = row.get("resource_type");
231            let instance_id: Option<String> = row.get("instance_id");
232
233            let mut permission = crate::permission::Permission::new(action, resource_type);
234            if let Some(instance) = instance_id {
235                permission = permission.with_instance(instance);
236            }
237
238            permissions.push(permission);
239        }
240
241        Ok(permissions)
242    }
243}
244
245#[cfg(feature = "database")]
246#[async_trait]
247impl Storage for DatabaseStorage {
248    async fn store_role(&mut self, role: Role) -> Result<()> {
249        let roles_table = self.roles_table();
250
251        // Start a transaction
252        let mut tx = self
253            .pool
254            .begin()
255            .await
256            .map_err(|e| Error::StorageError(format!("Failed to start transaction: {}", e)))?;
257
258        // Insert or update role
259        let upsert_query = format!(
260            r#"
261            INSERT INTO {} (name, description, active, updated_at)
262            VALUES ($1, $2, $3, NOW())
263            ON CONFLICT (name) DO UPDATE SET
264                description = EXCLUDED.description,
265                active = EXCLUDED.active,
266                updated_at = NOW()
267            "#,
268            roles_table
269        );
270
271        sqlx::query(&upsert_query)
272            .bind(role.name())
273            .bind(role.description())
274            .bind(role.is_active())
275            .execute(&mut *tx)
276            .await
277            .map_err(|e| Error::StorageError(format!("Failed to store role: {}", e)))?;
278
279        // Store permissions
280        self.store_role_permissions(role.name(), &role).await?;
281
282        // Commit transaction
283        tx.commit()
284            .await
285            .map_err(|e| Error::StorageError(format!("Failed to commit transaction: {}", e)))?;
286
287        Ok(())
288    }
289
290    async fn get_role(&self, name: &str) -> Result<Option<Role>> {
291        let roles_table = self.roles_table();
292        let query = format!(
293            "SELECT name, description, active FROM {} WHERE name = $1 AND active = true",
294            roles_table
295        );
296
297        let row = sqlx::query(&query)
298            .bind(name)
299            .fetch_optional(&self.pool)
300            .await
301            .map_err(|e| Error::StorageError(format!("Failed to get role: {}", e)))?;
302
303        if let Some(row) = row {
304            let role_name: String = row.get("name");
305            let description: Option<String> = row.get("description");
306            let is_active: bool = row.get("active");
307
308            let mut role = Role::new(role_name);
309            if let Some(desc) = description {
310                role = role.with_description(desc);
311            }
312            if !is_active {
313                role = role.deactivate();
314            }
315
316            // Load permissions
317            let permissions = self.load_role_permissions(name).await?;
318            for permission in permissions {
319                role = role.add_permission(permission);
320            }
321
322            Ok(Some(role))
323        } else {
324            Ok(None)
325        }
326    }
327
328    async fn update_role(&mut self, role: Role) -> Result<()> {
329        // For database storage, update is the same as store
330        self.store_role(role).await
331    }
332
333    async fn delete_role(&mut self, name: &str) -> Result<bool> {
334        let roles_table = self.roles_table();
335
336        // Soft delete by setting active = false
337        let update_query = format!(
338            "UPDATE {} SET active = false, updated_at = NOW() WHERE name = $1 AND active = true",
339            roles_table
340        );
341
342        let result = sqlx::query(&update_query)
343            .bind(name)
344            .execute(&self.pool)
345            .await
346            .map_err(|e| Error::StorageError(format!("Failed to delete role: {}", e)))?;
347
348        Ok(result.rows_affected() > 0)
349    }
350
351    async fn role_exists(&self, name: &str) -> Result<bool> {
352        let roles_table = self.roles_table();
353        let query = format!(
354            "SELECT 1 FROM {} WHERE name = $1 AND active = true LIMIT 1",
355            roles_table
356        );
357
358        let row = sqlx::query(&query)
359            .bind(name)
360            .fetch_optional(&self.pool)
361            .await
362            .map_err(|e| Error::StorageError(format!("Failed to check role existence: {}", e)))?;
363
364        Ok(row.is_some())
365    }
366
367    async fn list_roles(&self) -> Result<Vec<String>> {
368        let roles_table = self.roles_table();
369        let query = format!(
370            "SELECT name FROM {} WHERE active = true ORDER BY name",
371            roles_table
372        );
373
374        let rows = sqlx::query(&query)
375            .fetch_all(&self.pool)
376            .await
377            .map_err(|e| Error::StorageError(format!("Failed to list roles: {}", e)))?;
378
379        let roles: Vec<String> = rows.into_iter().map(|row| row.get("name")).collect();
380        Ok(roles)
381    }
382
383    fn role_count(&self) -> usize {
384        // For async implementation, this would need to be async too
385        // For now, return 0 as a placeholder
386        0
387    }
388}
389
390#[cfg(feature = "database")]
391impl DatabaseStorage {
392    /// Perform a health check on the database connection.
393    pub async fn health_check(&self) -> Result<bool> {
394        let result = sqlx::query("SELECT 1").fetch_optional(&self.pool).await;
395
396        match result {
397            Ok(Some(_)) => Ok(true),
398            Ok(None) => Ok(false),
399            Err(_) => Ok(false),
400        }
401    }
402
403    /// Get database connection statistics.
404    pub fn connection_stats(&self) -> DatabaseStats {
405        DatabaseStats {
406            active_connections: self.pool.size() as usize,
407            idle_connections: self.pool.num_idle(),
408            max_connections: self.pool.options().get_max_connections() as usize,
409        }
410    }
411
412    /// Close all database connections.
413    pub async fn close(&self) {
414        self.pool.close().await;
415    }
416}
417
418#[cfg(feature = "database")]
419/// Database connection statistics.
420#[derive(Debug, Clone)]
421pub struct DatabaseStats {
422    pub active_connections: usize,
423    pub idle_connections: usize,
424    pub max_connections: usize,
425}
426
427// Export types when database feature is enabled
428#[cfg(feature = "database")]
429pub use DatabaseStorage;
430
431#[cfg(not(feature = "database"))]
432/// Placeholder when database feature is not enabled.
433pub struct DatabaseStorage;
434
435#[cfg(not(feature = "database"))]
436impl DatabaseStorage {
437    /// Create a new database storage (disabled).
438    pub async fn new(_database_url: &str) -> std::result::Result<Self, Error> {
439        Err(Error::Storage(
440            "Database storage not available. Enable 'database' feature.".to_string(),
441        ))
442    }
443}
444
445#[cfg(all(test, feature = "database"))]
446mod tests {
447    use super::*;
448    use crate::{permission::Permission, role::Role};
449
450    // Note: These tests require a running PostgreSQL database
451    // and the DATABASE_URL environment variable to be set
452
453    async fn setup_test_db() -> DatabaseStorage {
454        let database_url = std::env::var("DATABASE_URL")
455            .unwrap_or_else(|_| "postgresql://postgres:password@localhost/rbac_test".to_string());
456
457        DatabaseStorage::new(&database_url)
458            .await
459            .expect("Failed to setup test database")
460    }
461
462    #[tokio::test]
463    #[ignore] // Ignore by default since it requires a database
464    async fn test_database_storage_role_operations() {
465        let mut storage = setup_test_db().await;
466
467        let role = Role::new("test_role")
468            .with_description("Test role for database storage")
469            .add_permission(Permission::new("read", "documents"))
470            .add_permission(Permission::new("write", "documents"));
471
472        // Store role
473        storage.store_role(role.clone()).await.unwrap();
474
475        // Check existence
476        assert!(storage.role_exists("test_role").await.unwrap());
477
478        // Get role
479        let retrieved = storage.get_role("test_role").await.unwrap().unwrap();
480        assert_eq!(retrieved.name(), "test_role");
481        assert_eq!(
482            retrieved.description(),
483            Some("Test role for database storage")
484        );
485        assert_eq!(retrieved.permissions().permissions().len(), 2);
486
487        // List roles
488        let roles = storage.list_roles().await.unwrap();
489        assert!(roles.contains(&"test_role".to_string()));
490
491        // Delete role
492        assert!(storage.delete_role("test_role").await.unwrap());
493        assert!(!storage.role_exists("test_role").await.unwrap());
494    }
495
496    #[tokio::test]
497    #[ignore] // Ignore by default since it requires a database
498    async fn test_database_health_check() {
499        let storage = setup_test_db().await;
500        assert!(storage.health_check().await.unwrap());
501    }
502}