1#[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")]
23pub struct DatabaseStorage {
25 pool: PgPool,
26 table_prefix: String,
27}
28
29#[cfg(feature = "database")]
30impl DatabaseStorage {
31 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 storage.initialize_schema().await?;
44
45 Ok(storage)
46 }
47
48 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 storage.initialize_schema().await?;
58
59 Ok(storage)
60 }
61
62 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 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 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 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 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 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 fn roles_table(&self) -> String {
160 format!("{}roles", self.table_prefix)
161 }
162
163 fn role_permissions_table(&self) -> String {
165 format!("{}role_permissions", self.table_prefix)
166 }
167
168 fn subjects_table(&self) -> String {
170 format!("{}subjects", self.table_prefix)
171 }
172
173 fn subject_roles_table(&self) -> String {
175 format!("{}subject_roles", self.table_prefix)
176 }
177
178 async fn store_role_permissions(&self, role_name: &str, role: &Role) -> Result<()> {
180 let table = self.role_permissions_table();
181
182 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 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 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 let mut tx = self
253 .pool
254 .begin()
255 .await
256 .map_err(|e| Error::StorageError(format!("Failed to start transaction: {}", e)))?;
257
258 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 self.store_role_permissions(role.name(), &role).await?;
281
282 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 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 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 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 0
387 }
388}
389
390#[cfg(feature = "database")]
391impl DatabaseStorage {
392 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 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 pub async fn close(&self) {
414 self.pool.close().await;
415 }
416}
417
418#[cfg(feature = "database")]
419#[derive(Debug, Clone)]
421pub struct DatabaseStats {
422 pub active_connections: usize,
423 pub idle_connections: usize,
424 pub max_connections: usize,
425}
426
427#[cfg(feature = "database")]
429pub use DatabaseStorage;
430
431#[cfg(not(feature = "database"))]
432pub struct DatabaseStorage;
434
435#[cfg(not(feature = "database"))]
436impl DatabaseStorage {
437 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 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] 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 storage.store_role(role.clone()).await.unwrap();
474
475 assert!(storage.role_exists("test_role").await.unwrap());
477
478 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 let roles = storage.list_roles().await.unwrap();
489 assert!(roles.contains(&"test_role".to_string()));
490
491 assert!(storage.delete_role("test_role").await.unwrap());
493 assert!(!storage.role_exists("test_role").await.unwrap());
494 }
495
496 #[tokio::test]
497 #[ignore] async fn test_database_health_check() {
499 let storage = setup_test_db().await;
500 assert!(storage.health_check().await.unwrap());
501 }
502}