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 sqlx::{PgPool, Row};
15
16#[cfg(feature = "database")]
17pub struct DatabaseStorage {
19 pool: PgPool,
20 table_prefix: String,
21}
22
23#[cfg(feature = "database")]
24impl DatabaseStorage {
25 pub async fn new(database_url: &str) -> Result<Self> {
27 let pool = PgPool::connect(database_url)
28 .await
29 .map_err(|e| Error::Storage(format!("Database connection failed: {}", e)))?;
30
31 let storage = Self {
32 pool,
33 table_prefix: "rbac_".to_string(),
34 };
35
36 storage.initialize_schema().await?;
38
39 Ok(storage)
40 }
41
42 pub async fn new_with_prefix(database_url: &str, table_prefix: String) -> Result<Self> {
44 let pool = PgPool::connect(database_url)
45 .await
46 .map_err(|e| Error::Storage(format!("Database connection failed: {}", e)))?;
47
48 let storage = Self { pool, table_prefix };
49
50 storage.initialize_schema().await?;
52
53 Ok(storage)
54 }
55
56 async fn initialize_schema(&self) -> Result<()> {
58 let roles_table = format!("{}roles", self.table_prefix);
59 let role_permissions_table = format!("{}role_permissions", self.table_prefix);
60 let subjects_table = format!("{}subjects", self.table_prefix);
61 let subject_roles_table = format!("{}subject_roles", self.table_prefix);
62
63 let create_roles = format!(
65 r#"
66 CREATE TABLE IF NOT EXISTS {} (
67 name VARCHAR(255) PRIMARY KEY,
68 description TEXT,
69 active BOOLEAN NOT NULL DEFAULT true,
70 created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
71 updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
72 )
73 "#,
74 roles_table
75 );
76
77 let create_role_permissions = format!(
79 r#"
80 CREATE TABLE IF NOT EXISTS {} (
81 id SERIAL PRIMARY KEY,
82 role_name VARCHAR(255) NOT NULL REFERENCES {}(name) ON DELETE CASCADE,
83 action VARCHAR(255) NOT NULL,
84 resource_type VARCHAR(255) NOT NULL,
85 instance_id VARCHAR(255),
86 condition_json JSONB,
87 created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
88 UNIQUE(role_name, action, resource_type, instance_id)
89 )
90 "#,
91 role_permissions_table, roles_table
92 );
93
94 let create_subjects = format!(
96 r#"
97 CREATE TABLE IF NOT EXISTS {} (
98 id VARCHAR(255) PRIMARY KEY,
99 subject_type VARCHAR(50) NOT NULL,
100 display_name VARCHAR(255),
101 metadata JSONB,
102 created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
103 updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
104 )
105 "#,
106 subjects_table
107 );
108
109 let create_subject_roles = format!(
111 r#"
112 CREATE TABLE IF NOT EXISTS {} (
113 id SERIAL PRIMARY KEY,
114 subject_id VARCHAR(255) NOT NULL REFERENCES {}(id) ON DELETE CASCADE,
115 role_name VARCHAR(255) NOT NULL REFERENCES {}(name) ON DELETE CASCADE,
116 assigned_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
117 assigned_by VARCHAR(255),
118 UNIQUE(subject_id, role_name)
119 )
120 "#,
121 subject_roles_table, subjects_table, roles_table
122 );
123
124 sqlx::query(&create_roles)
126 .execute(&self.pool)
127 .await
128 .map_err(|e| Error::Storage(format!("Failed to create roles table: {}", e)))?;
129
130 sqlx::query(&create_role_permissions)
131 .execute(&self.pool)
132 .await
133 .map_err(|e| {
134 Error::Storage(format!("Failed to create role_permissions table: {}", e))
135 })?;
136
137 sqlx::query(&create_subjects)
138 .execute(&self.pool)
139 .await
140 .map_err(|e| Error::Storage(format!("Failed to create subjects table: {}", e)))?;
141
142 sqlx::query(&create_subject_roles)
143 .execute(&self.pool)
144 .await
145 .map_err(|e| Error::Storage(format!("Failed to create subject_roles table: {}", e)))?;
146
147 Ok(())
148 }
149
150 fn roles_table(&self) -> String {
152 format!("{}roles", self.table_prefix)
153 }
154
155 fn role_permissions_table(&self) -> String {
157 format!("{}role_permissions", self.table_prefix)
158 }
159
160 #[allow(dead_code)]
162 fn subjects_table(&self) -> String {
163 format!("{}subjects", self.table_prefix)
164 }
165
166 #[allow(dead_code)]
168 fn subject_roles_table(&self) -> String {
169 format!("{}subject_roles", self.table_prefix)
170 }
171
172 async fn store_role_permissions(&self, role_name: &str, role: &Role) -> Result<()> {
174 let table = self.role_permissions_table();
175
176 let delete_query = format!("DELETE FROM {} WHERE role_name = $1", table);
178 sqlx::query(&delete_query)
179 .bind(role_name)
180 .execute(&self.pool)
181 .await
182 .map_err(|e| Error::Storage(format!("Failed to delete old permissions: {}", e)))?;
183
184 for permission in role.permissions().permissions() {
186 let insert_query = format!(
187 "INSERT INTO {} (role_name, action, resource_type, instance_id) VALUES ($1, $2, $3, $4)",
188 table
189 );
190
191 sqlx::query(&insert_query)
192 .bind(role_name)
193 .bind(permission.action())
194 .bind(permission.resource_type())
195 .bind(permission.instance())
196 .execute(&self.pool)
197 .await
198 .map_err(|e| Error::Storage(format!("Failed to insert permission: {}", e)))?;
199 }
200
201 Ok(())
202 }
203
204 async fn load_role_permissions(
206 &self,
207 role_name: &str,
208 ) -> Result<Vec<crate::permission::Permission>> {
209 let table = self.role_permissions_table();
210 let query = format!(
211 "SELECT action, resource_type, instance_id FROM {} WHERE role_name = $1",
212 table
213 );
214
215 let rows = sqlx::query(&query)
216 .bind(role_name)
217 .fetch_all(&self.pool)
218 .await
219 .map_err(|e| Error::Storage(format!("Failed to load permissions: {}", e)))?;
220
221 let mut permissions = Vec::new();
222 for row in rows {
223 let action: String = row.get("action");
224 let resource_type: String = row.get("resource_type");
225 let instance_id: Option<String> = row.get("instance_id");
226
227 let permission = if let Some(instance) = instance_id {
228 crate::permission::Permission::with_instance(action, resource_type, instance)
229 } else {
230 crate::permission::Permission::new(action, resource_type)
231 };
232
233 permissions.push(permission);
234 }
235
236 Ok(permissions)
237 }
238
239 pub async fn store_role_async(&mut self, role: Role) -> Result<()> {
241 let roles_table = self.roles_table();
242
243 let mut tx = self
245 .pool
246 .begin()
247 .await
248 .map_err(|e| Error::Storage(format!("Failed to start transaction: {}", e)))?;
249
250 let upsert_query = format!(
252 r#"
253 INSERT INTO {} (name, description, active, updated_at)
254 VALUES ($1, $2, $3, NOW())
255 ON CONFLICT (name) DO UPDATE SET
256 description = EXCLUDED.description,
257 active = EXCLUDED.active,
258 updated_at = NOW()
259 "#,
260 roles_table
261 );
262
263 sqlx::query(&upsert_query)
264 .bind(role.name())
265 .bind(role.description())
266 .bind(role.is_active())
267 .execute(&mut *tx)
268 .await
269 .map_err(|e| Error::Storage(format!("Failed to store role: {}", e)))?;
270
271 self.store_role_permissions(role.name(), &role).await?;
273
274 tx.commit()
276 .await
277 .map_err(|e| Error::Storage(format!("Failed to commit transaction: {}", e)))?;
278
279 Ok(())
280 }
281
282 pub async fn get_role_async(&self, name: &str) -> Result<Option<Role>> {
284 let roles_table = self.roles_table();
285 let query = format!(
286 "SELECT name, description, active FROM {} WHERE name = $1 AND active = true",
287 roles_table
288 );
289
290 let row = sqlx::query(&query)
291 .bind(name)
292 .fetch_optional(&self.pool)
293 .await
294 .map_err(|e| Error::Storage(format!("Failed to get role: {}", e)))?;
295
296 if let Some(row) = row {
297 let role_name: String = row.get("name");
298 let description: Option<String> = row.get("description");
299 let is_active: bool = row.get("active");
300
301 let mut role = Role::new(role_name);
302 if let Some(desc) = description {
303 role = role.with_description(desc);
304 }
305 if !is_active {
306 role = role.deactivate();
307 }
308
309 let permissions = self.load_role_permissions(name).await?;
311 for permission in permissions {
312 role = role.add_permission(permission);
313 }
314
315 Ok(Some(role))
316 } else {
317 Ok(None)
318 }
319 }
320
321 pub async fn delete_role_async(&mut self, name: &str) -> Result<bool> {
323 let roles_table = self.roles_table();
324
325 let update_query = format!(
327 "UPDATE {} SET active = false, updated_at = NOW() WHERE name = $1 AND active = true",
328 roles_table
329 );
330
331 let result = sqlx::query(&update_query)
332 .bind(name)
333 .execute(&self.pool)
334 .await
335 .map_err(|e| Error::Storage(format!("Failed to delete role: {}", e)))?;
336
337 Ok(result.rows_affected() > 0)
338 }
339
340 pub async fn role_exists_async(&self, name: &str) -> Result<bool> {
342 let roles_table = self.roles_table();
343 let query = format!(
344 "SELECT 1 FROM {} WHERE name = $1 AND active = true LIMIT 1",
345 roles_table
346 );
347
348 let row = sqlx::query(&query)
349 .bind(name)
350 .fetch_optional(&self.pool)
351 .await
352 .map_err(|e| Error::Storage(format!("Failed to check role existence: {}", e)))?;
353
354 Ok(row.is_some())
355 }
356
357 pub async fn list_roles_async(&self) -> Result<Vec<String>> {
359 let roles_table = self.roles_table();
360 let query = format!(
361 "SELECT name FROM {} WHERE active = true ORDER BY name",
362 roles_table
363 );
364
365 let rows = sqlx::query(&query)
366 .fetch_all(&self.pool)
367 .await
368 .map_err(|e| Error::Storage(format!("Failed to list roles: {}", e)))?;
369
370 let roles: Vec<String> = rows.into_iter().map(|row| row.get("name")).collect();
371 Ok(roles)
372 }
373
374 pub async fn role_count_async(&self) -> Result<usize> {
376 let roles_table = self.roles_table();
377 let query = format!(
378 "SELECT COUNT(*) as count FROM {} WHERE active = true",
379 roles_table
380 );
381
382 let row = sqlx::query(&query)
383 .fetch_one(&self.pool)
384 .await
385 .map_err(|e| Error::Storage(format!("Failed to count roles: {}", e)))?;
386
387 let count: i64 = row.get("count");
388 Ok(count as usize)
389 }
390}
391
392#[cfg(feature = "database")]
393impl Storage for DatabaseStorage {
394 fn store_role(&mut self, _role: Role) -> Result<()> {
395 Err(Error::Storage(
396 "Use store_role_async for database storage".to_string(),
397 ))
398 }
399
400 fn get_role(&self, _name: &str) -> Result<Option<Role>> {
401 Err(Error::Storage(
402 "Use get_role_async for database storage".to_string(),
403 ))
404 }
405
406 fn role_exists(&self, _name: &str) -> Result<bool> {
407 Err(Error::Storage(
408 "Use role_exists_async for database storage".to_string(),
409 ))
410 }
411
412 fn delete_role(&mut self, _name: &str) -> Result<bool> {
413 Err(Error::Storage(
414 "Use delete_role_async for database storage".to_string(),
415 ))
416 }
417
418 fn list_roles(&self) -> Result<Vec<String>> {
419 Err(Error::Storage(
420 "Use list_roles_async for database storage".to_string(),
421 ))
422 }
423
424 fn update_role(&mut self, role: Role) -> Result<()> {
425 self.store_role(role)
427 }
428
429 fn role_count(&self) -> usize {
430 0
433 }
434}
435
436#[cfg(feature = "database")]
437impl DatabaseStorage {
438 pub async fn health_check(&self) -> Result<bool> {
440 let result = sqlx::query("SELECT 1").fetch_optional(&self.pool).await;
441
442 match result {
443 Ok(Some(_)) => Ok(true),
444 Ok(None) => Ok(false),
445 Err(_) => Ok(false),
446 }
447 }
448
449 pub fn connection_stats(&self) -> DatabaseStats {
451 DatabaseStats {
452 active_connections: self.pool.size() as usize,
453 idle_connections: self.pool.num_idle(),
454 max_connections: self.pool.options().get_max_connections() as usize,
455 }
456 }
457
458 pub async fn close(&self) {
460 self.pool.close().await;
461 }
462}
463
464#[cfg(feature = "database")]
465#[derive(Debug, Clone)]
467pub struct DatabaseStats {
468 pub active_connections: usize,
469 pub idle_connections: usize,
470 pub max_connections: usize,
471}
472
473#[cfg(not(feature = "database"))]
474pub struct DatabaseStorage;
476
477#[cfg(not(feature = "database"))]
478impl DatabaseStorage {
479 pub async fn new(_database_url: &str) -> std::result::Result<Self, Error> {
481 Err(Error::Storage(
482 "Database storage not available. Enable 'database' feature.".to_string(),
483 ))
484 }
485}
486
487#[cfg(all(test, feature = "database"))]
488mod tests {
489 use super::*;
490 use crate::{permission::Permission, role::Role};
491
492 async fn setup_test_db() -> DatabaseStorage {
496 let database_url = std::env::var("DATABASE_URL")
497 .unwrap_or_else(|_| "postgresql://postgres:password@localhost/rbac_test".to_string());
498
499 DatabaseStorage::new(&database_url)
500 .await
501 .expect("Failed to setup test database")
502 }
503
504 #[tokio::test]
505 #[ignore] async fn test_database_storage_role_operations() {
507 let mut storage = setup_test_db().await;
508
509 let role = Role::new("test_role")
510 .with_description("Test role for database storage")
511 .add_permission(Permission::new("read", "documents"))
512 .add_permission(Permission::new("write", "documents"));
513
514 storage.store_role_async(role.clone()).await.unwrap();
516
517 assert!(storage.role_exists_async("test_role").await.unwrap());
519
520 let retrieved = storage.get_role_async("test_role").await.unwrap().unwrap();
522 assert_eq!(retrieved.name(), "test_role");
523 assert_eq!(
524 retrieved.description(),
525 Some("Test role for database storage")
526 );
527 assert_eq!(retrieved.permissions().permissions().len(), 2);
528
529 let roles = storage.list_roles_async().await.unwrap();
531 assert!(roles.contains(&"test_role".to_string()));
532
533 assert!(storage.delete_role_async("test_role").await.unwrap());
535 assert!(!storage.role_exists_async("test_role").await.unwrap());
536 }
537
538 #[tokio::test]
539 #[ignore] async fn test_database_health_check() {
541 let storage = setup_test_db().await;
542 assert!(storage.health_check().await.unwrap());
543 }
544}