WIP: adding access management control to db instances
This commit is contained in:
		
							
								
								
									
										75
									
								
								src/cmd.rs
									
									
									
									
									
								
							
							
						
						
									
										75
									
								
								src/cmd.rs
									
									
									
									
									
								
							| @@ -6,7 +6,7 @@ use futures::future::select_all; | ||||
| pub enum Cmd { | ||||
|     Ping, | ||||
|     Echo(String), | ||||
|     Select(u64), // Changed from u16 to u64 | ||||
|     Select(u64, Option<String>), // db_index, optional_key | ||||
|     Get(String), | ||||
|     Set(String, String), | ||||
|     SetPx(String, String, u128), | ||||
| @@ -98,11 +98,18 @@ impl Cmd { | ||||
|                 Ok(( | ||||
|                     match cmd[0].to_lowercase().as_str() { | ||||
|                         "select" => { | ||||
|                             if cmd.len() != 2 { | ||||
|                             if cmd.len() < 2 || cmd.len() > 4 { | ||||
|                                 return Err(DBError("wrong number of arguments for SELECT".to_string())); | ||||
|                             } | ||||
|                             let idx = cmd[1].parse::<u64>().map_err(|_| DBError("ERR DB index is not an integer".to_string()))?; | ||||
|                             Cmd::Select(idx) | ||||
|                             let key = if cmd.len() == 4 && cmd[2].to_lowercase() == "key" { | ||||
|                                 Some(cmd[3].clone()) | ||||
|                             } else if cmd.len() == 2 { | ||||
|                                 None | ||||
|                             } else { | ||||
|                                 return Err(DBError("ERR syntax error".to_string())); | ||||
|                             }; | ||||
|                             Cmd::Select(idx, key) | ||||
|                         } | ||||
|                         "echo" => Cmd::Echo(cmd[1].clone()), | ||||
|                         "ping" => Cmd::Ping, | ||||
| @@ -642,7 +649,7 @@ impl Cmd { | ||||
|         } | ||||
|  | ||||
|         match self { | ||||
|             Cmd::Select(db) => select_cmd(server, db).await, | ||||
|             Cmd::Select(db, key) => select_cmd(server, db, key).await, | ||||
|             Cmd::Ping => Ok(Protocol::SimpleString("PONG".to_string())), | ||||
|             Cmd::Echo(s) => Ok(Protocol::BulkString(s)), | ||||
|             Cmd::Get(k) => get_cmd(server, &k).await, | ||||
| @@ -736,7 +743,14 @@ impl Cmd { | ||||
|      | ||||
|     pub fn to_protocol(self) -> Protocol { | ||||
|         match self { | ||||
|             Cmd::Select(db) => Protocol::Array(vec![Protocol::BulkString("select".to_string()), Protocol::BulkString(db.to_string())]), | ||||
|             Cmd::Select(db, key) => { | ||||
|                 let mut arr = vec![Protocol::BulkString("select".to_string()), Protocol::BulkString(db.to_string())]; | ||||
|                 if let Some(k) = key { | ||||
|                     arr.push(Protocol::BulkString("key".to_string())); | ||||
|                     arr.push(Protocol::BulkString(k)); | ||||
|                 } | ||||
|                 Protocol::Array(arr) | ||||
|             } | ||||
|             Cmd::Ping => Protocol::Array(vec![Protocol::BulkString("ping".to_string())]), | ||||
|             Cmd::Echo(s) => Protocol::Array(vec![Protocol::BulkString("echo".to_string()), Protocol::BulkString(s)]), | ||||
|             Cmd::Get(k) => Protocol::Array(vec![Protocol::BulkString("get".to_string()), Protocol::BulkString(k)]), | ||||
| @@ -753,9 +767,44 @@ async fn flushdb_cmd(server: &mut Server) -> Result<Protocol, DBError> { | ||||
|     } | ||||
| } | ||||
|  | ||||
| async fn select_cmd(server: &mut Server, db: u64) -> Result<Protocol, DBError> { | ||||
|     // Test if we can access the database (this will create it if needed) | ||||
| async fn select_cmd(server: &mut Server, db: u64, key: Option<String>) -> Result<Protocol, DBError> { | ||||
|     // Load database metadata | ||||
|     let meta = match crate::rpc::RpcServerImpl::load_meta_static(&server.option.dir, db).await { | ||||
|         Ok(m) => m, | ||||
|         Err(_) => { | ||||
|             // If meta doesn't exist, create default | ||||
|             let default_meta = crate::rpc::DatabaseMeta { | ||||
|                 public: true, | ||||
|                 keys: std::collections::HashMap::new(), | ||||
|             }; | ||||
|             if let Err(_) = crate::rpc::RpcServerImpl::save_meta_static(&server.option.dir, db, &default_meta).await { | ||||
|                 return Ok(Protocol::err("ERR failed to initialize database metadata")); | ||||
|             } | ||||
|             default_meta | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     // Check access permissions | ||||
|     let permissions = if meta.public { | ||||
|         // Public database - full access | ||||
|         Some(crate::rpc::Permissions::ReadWrite) | ||||
|     } else if let Some(key_str) = key { | ||||
|         // Private database - check key | ||||
|         let hash = crate::rpc::hash_key(&key_str); | ||||
|         if let Some(access_key) = meta.keys.get(&hash) { | ||||
|             Some(access_key.permissions.clone()) | ||||
|         } else { | ||||
|             return Ok(Protocol::err("ERR invalid access key")); | ||||
|         } | ||||
|     } else { | ||||
|         return Ok(Protocol::err("ERR access key required for private database")); | ||||
|     }; | ||||
|  | ||||
|     // Set selected database and permissions | ||||
|     server.selected_db = db; | ||||
|     server.current_permissions = permissions; | ||||
|  | ||||
|     // Test if we can access the database (this will create it if needed) | ||||
|     match server.current_storage() { | ||||
|         Ok(_) => Ok(Protocol::SimpleString("OK".to_string())), | ||||
|         Err(e) => Ok(Protocol::err(&e.0)), | ||||
| @@ -1003,6 +1052,9 @@ async fn brpop_cmd(server: &Server, keys: &[String], timeout_secs: f64) -> Resul | ||||
| } | ||||
|  | ||||
| async fn lpush_cmd(server: &Server, key: &str, elements: &[String]) -> Result<Protocol, DBError> { | ||||
|     if !server.has_write_permission() { | ||||
|         return Ok(Protocol::err("ERR write permission denied")); | ||||
|     } | ||||
|     match server.current_storage()?.lpush(key, elements.to_vec()) { | ||||
|         Ok(len) => { | ||||
|             // Attempt to deliver to any blocked BLPOP waiters | ||||
| @@ -1134,6 +1186,9 @@ async fn type_cmd(server: &Server, k: &String) -> Result<Protocol, DBError> { | ||||
| } | ||||
|  | ||||
| async fn del_cmd(server: &Server, k: &str) -> Result<Protocol, DBError> { | ||||
|     if !server.has_write_permission() { | ||||
|         return Ok(Protocol::err("ERR write permission denied")); | ||||
|     } | ||||
|     server.current_storage()?.del(k.to_string())?; | ||||
|     Ok(Protocol::SimpleString("1".to_string())) | ||||
| } | ||||
| @@ -1159,6 +1214,9 @@ async fn set_px_cmd( | ||||
| } | ||||
|  | ||||
| async fn set_cmd(server: &Server, k: &str, v: &str) -> Result<Protocol, DBError> { | ||||
|     if !server.has_write_permission() { | ||||
|         return Ok(Protocol::err("ERR write permission denied")); | ||||
|     } | ||||
|     server.current_storage()?.set(k.to_string(), v.to_string())?; | ||||
|     Ok(Protocol::SimpleString("OK".to_string())) | ||||
| } | ||||
| @@ -1273,6 +1331,9 @@ async fn get_cmd(server: &Server, k: &str) -> Result<Protocol, DBError> { | ||||
|  | ||||
| // Hash command implementations | ||||
| async fn hset_cmd(server: &Server, key: &str, pairs: &[(String, String)]) -> Result<Protocol, DBError> { | ||||
|     if !server.has_write_permission() { | ||||
|         return Ok(Protocol::err("ERR write permission denied")); | ||||
|     } | ||||
|     let new_fields = server.current_storage()?.hset(key, pairs.to_vec())?; | ||||
|     Ok(Protocol::SimpleString(new_fields.to_string())) | ||||
| } | ||||
|   | ||||
							
								
								
									
										294
									
								
								src/rpc.rs
									
									
									
									
									
								
							
							
						
						
									
										294
									
								
								src/rpc.rs
									
									
									
									
									
								
							| @@ -3,6 +3,7 @@ use std::sync::Arc; | ||||
| use tokio::sync::RwLock; | ||||
| use jsonrpsee::{core::RpcResult, proc_macros::rpc}; | ||||
| use serde::{Deserialize, Serialize}; | ||||
| use sha2::{Digest, Sha256}; | ||||
|  | ||||
| use crate::server::Server; | ||||
| use crate::options::DBOption; | ||||
| @@ -39,6 +40,43 @@ pub struct DatabaseInfo { | ||||
|     pub last_access: Option<u64>, | ||||
| } | ||||
|  | ||||
| /// Access permissions for database keys | ||||
| #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] | ||||
| pub enum Permissions { | ||||
|     Read, | ||||
|     ReadWrite, | ||||
| } | ||||
|  | ||||
| /// Access key information | ||||
| #[derive(Debug, Clone, Serialize, Deserialize)] | ||||
| pub struct AccessKey { | ||||
|     pub hash: String, | ||||
|     pub permissions: Permissions, | ||||
|     pub created_at: u64, | ||||
| } | ||||
|  | ||||
| /// Database metadata containing access keys | ||||
| #[derive(Debug, Clone, Serialize, Deserialize)] | ||||
| pub struct DatabaseMeta { | ||||
|     pub public: bool, | ||||
|     pub keys: HashMap<String, AccessKey>, | ||||
| } | ||||
|  | ||||
| /// Access key information returned by RPC | ||||
| #[derive(Debug, Clone, Serialize, Deserialize)] | ||||
| pub struct AccessKeyInfo { | ||||
|     pub hash: String, | ||||
|     pub permissions: Permissions, | ||||
|     pub created_at: u64, | ||||
| } | ||||
|  | ||||
| /// Hash a plaintext key using SHA-256 | ||||
| pub fn hash_key(key: &str) -> String { | ||||
|     let mut hasher = Sha256::new(); | ||||
|     hasher.update(key.as_bytes()); | ||||
|     format!("{:x}", hasher.finalize()) | ||||
| } | ||||
|  | ||||
| /// RPC trait for HeroDB management | ||||
| #[rpc(server, client, namespace = "herodb")] | ||||
| pub trait Rpc { | ||||
| @@ -70,6 +108,22 @@ pub trait Rpc { | ||||
|     /// Get server statistics | ||||
|     #[method(name = "getServerStats")] | ||||
|     async fn get_server_stats(&self) -> RpcResult<HashMap<String, serde_json::Value>>; | ||||
|  | ||||
|     /// Add an access key to a database | ||||
|     #[method(name = "addAccessKey")] | ||||
|     async fn add_access_key(&self, db_id: u64, key: String, permissions: String) -> RpcResult<bool>; | ||||
|  | ||||
|     /// Delete an access key from a database | ||||
|     #[method(name = "deleteAccessKey")] | ||||
|     async fn delete_access_key(&self, db_id: u64, key_hash: String) -> RpcResult<bool>; | ||||
|  | ||||
|     /// List all access keys for a database | ||||
|     #[method(name = "listAccessKeys")] | ||||
|     async fn list_access_keys(&self, db_id: u64) -> RpcResult<Vec<AccessKeyInfo>>; | ||||
|  | ||||
|     /// Set database public/private status | ||||
|     #[method(name = "setDatabasePublic")] | ||||
|     async fn set_database_public(&self, db_id: u64, public: bool) -> RpcResult<bool>; | ||||
| } | ||||
|  | ||||
| /// RPC Server implementation | ||||
| @@ -84,6 +138,8 @@ pub struct RpcServerImpl { | ||||
|     next_encrypted_id: Arc<RwLock<u64>>, | ||||
|     /// Default backend type | ||||
|     backend: crate::options::BackendType, | ||||
|     /// Encryption keys for databases | ||||
|     encryption_keys: Arc<RwLock<HashMap<u64, Option<String>>>>, | ||||
| } | ||||
|  | ||||
| impl RpcServerImpl { | ||||
| @@ -95,6 +151,7 @@ impl RpcServerImpl { | ||||
|             next_unencrypted_id: Arc::new(RwLock::new(0)), | ||||
|             next_encrypted_id: Arc::new(RwLock::new(10)), | ||||
|             backend, | ||||
|             encryption_keys: Arc::new(RwLock::new(HashMap::new())), | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -177,6 +234,166 @@ impl RpcServerImpl { | ||||
|             current_id | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Load database metadata from file (static version) | ||||
|     pub async fn load_meta_static(base_dir: &str, db_id: u64) -> Result<DatabaseMeta, jsonrpsee::types::ErrorObjectOwned> { | ||||
|         let meta_path = std::path::PathBuf::from(base_dir).join(format!("{}_meta.json", db_id)); | ||||
|  | ||||
|         // If meta file doesn't exist, return default | ||||
|         if !meta_path.exists() { | ||||
|             return Ok(DatabaseMeta { | ||||
|                 public: true, | ||||
|                 keys: HashMap::new(), | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         // Read file | ||||
|         let content = std::fs::read(&meta_path) | ||||
|             .map_err(|e| jsonrpsee::types::ErrorObjectOwned::owned( | ||||
|                 -32000, | ||||
|                 format!("Failed to read meta file: {}", e), | ||||
|                 None::<()> | ||||
|             ))?; | ||||
|  | ||||
|         let json_str = String::from_utf8(content) | ||||
|             .map_err(|_| jsonrpsee::types::ErrorObjectOwned::owned( | ||||
|                 -32000, | ||||
|                 "Invalid UTF-8 in meta file", | ||||
|                 None::<()> | ||||
|             ))?; | ||||
|  | ||||
|         serde_json::from_str(&json_str) | ||||
|             .map_err(|e| jsonrpsee::types::ErrorObjectOwned::owned( | ||||
|                 -32000, | ||||
|                 format!("Failed to parse meta JSON: {}", e), | ||||
|                 None::<()> | ||||
|             )) | ||||
|     } | ||||
|  | ||||
|     /// Load database metadata from file | ||||
|     async fn load_meta(&self, db_id: u64) -> Result<DatabaseMeta, jsonrpsee::types::ErrorObjectOwned> { | ||||
|         let meta_path = std::path::PathBuf::from(&self.base_dir).join(format!("{}_meta.json", db_id)); | ||||
|  | ||||
|         // If meta file doesn't exist, create default | ||||
|         if !meta_path.exists() { | ||||
|             let default_meta = DatabaseMeta { | ||||
|                 public: true, | ||||
|                 keys: HashMap::new(), | ||||
|             }; | ||||
|             self.save_meta(db_id, &default_meta).await?; | ||||
|             return Ok(default_meta); | ||||
|         } | ||||
|  | ||||
|         // Read and potentially decrypt | ||||
|         let content = std::fs::read(&meta_path) | ||||
|             .map_err(|e| jsonrpsee::types::ErrorObjectOwned::owned( | ||||
|                 -32000, | ||||
|                 format!("Failed to read meta file: {}", e), | ||||
|                 None::<()> | ||||
|             ))?; | ||||
|  | ||||
|         let json_str = if db_id >= 10 { | ||||
|             // Encrypted database, decrypt meta | ||||
|             if let Some(key) = self.encryption_keys.read().await.get(&db_id).and_then(|k| k.as_ref()) { | ||||
|                 use crate::crypto::CryptoFactory; | ||||
|                 let crypto = CryptoFactory::new(key.as_bytes()); | ||||
|                 String::from_utf8(crypto.decrypt(&content) | ||||
|                     .map_err(|_| jsonrpsee::types::ErrorObjectOwned::owned( | ||||
|                         -32000, | ||||
|                         "Failed to decrypt meta file", | ||||
|                         None::<()> | ||||
|                     ))?) | ||||
|                     .map_err(|_| jsonrpsee::types::ErrorObjectOwned::owned( | ||||
|                         -32000, | ||||
|                         "Invalid UTF-8 in decrypted meta", | ||||
|                         None::<()> | ||||
|                     ))? | ||||
|             } else { | ||||
|                 return Err(jsonrpsee::types::ErrorObjectOwned::owned( | ||||
|                     -32000, | ||||
|                     "Encryption key not found for encrypted database", | ||||
|                     None::<()> | ||||
|                 )); | ||||
|             } | ||||
|         } else { | ||||
|             String::from_utf8(content) | ||||
|                 .map_err(|_| jsonrpsee::types::ErrorObjectOwned::owned( | ||||
|                     -32000, | ||||
|                     "Invalid UTF-8 in meta file", | ||||
|                     None::<()> | ||||
|                 ))? | ||||
|         }; | ||||
|  | ||||
|         serde_json::from_str(&json_str) | ||||
|             .map_err(|e| jsonrpsee::types::ErrorObjectOwned::owned( | ||||
|                 -32000, | ||||
|                 format!("Failed to parse meta JSON: {}", e), | ||||
|                 None::<()> | ||||
|             )) | ||||
|     } | ||||
|  | ||||
|     /// Save database metadata to file (static version) | ||||
|     pub async fn save_meta_static(base_dir: &str, db_id: u64, meta: &DatabaseMeta) -> Result<(), jsonrpsee::types::ErrorObjectOwned> { | ||||
|         let meta_path = std::path::PathBuf::from(base_dir).join(format!("{}_meta.json", db_id)); | ||||
|  | ||||
|         let json_str = serde_json::to_string(meta) | ||||
|             .map_err(|e| jsonrpsee::types::ErrorObjectOwned::owned( | ||||
|                 -32000, | ||||
|                 format!("Failed to serialize meta: {}", e), | ||||
|                 None::<()> | ||||
|             ))?; | ||||
|  | ||||
|         std::fs::write(&meta_path, json_str) | ||||
|             .map_err(|e| jsonrpsee::types::ErrorObjectOwned::owned( | ||||
|                 -32000, | ||||
|                 format!("Failed to write meta file: {}", e), | ||||
|                 None::<()> | ||||
|             ))?; | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     /// Save database metadata to file | ||||
|     async fn save_meta(&self, db_id: u64, meta: &DatabaseMeta) -> Result<(), jsonrpsee::types::ErrorObjectOwned> { | ||||
|         let meta_path = std::path::PathBuf::from(&self.base_dir).join(format!("{}_meta.json", db_id)); | ||||
|  | ||||
|         let json_str = serde_json::to_string(meta) | ||||
|             .map_err(|e| jsonrpsee::types::ErrorObjectOwned::owned( | ||||
|                 -32000, | ||||
|                 format!("Failed to serialize meta: {}", e), | ||||
|                 None::<()> | ||||
|             ))?; | ||||
|  | ||||
|         if db_id >= 10 { | ||||
|             // Encrypted database, encrypt meta | ||||
|             if let Some(key) = self.encryption_keys.read().await.get(&db_id).and_then(|k| k.as_ref()) { | ||||
|                 use crate::crypto::CryptoFactory; | ||||
|                 let crypto = CryptoFactory::new(key.as_bytes()); | ||||
|                 let encrypted = crypto.encrypt(json_str.as_bytes()); | ||||
|                 std::fs::write(&meta_path, encrypted) | ||||
|                     .map_err(|e| jsonrpsee::types::ErrorObjectOwned::owned( | ||||
|                         -32000, | ||||
|                         format!("Failed to write encrypted meta file: {}", e), | ||||
|                         None::<()> | ||||
|                     ))?; | ||||
|             } else { | ||||
|                 return Err(jsonrpsee::types::ErrorObjectOwned::owned( | ||||
|                     -32000, | ||||
|                     "Encryption key not found for encrypted database", | ||||
|                     None::<()> | ||||
|                 )); | ||||
|             } | ||||
|         } else { | ||||
|             std::fs::write(&meta_path, json_str) | ||||
|                 .map_err(|e| jsonrpsee::types::ErrorObjectOwned::owned( | ||||
|                     -32000, | ||||
|                     format!("Failed to write meta file: {}", e), | ||||
|                     None::<()> | ||||
|                 ))?; | ||||
|         } | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[jsonrpsee::core::async_trait] | ||||
| @@ -213,7 +430,7 @@ impl RpcServer for RpcServerImpl { | ||||
|                     dir: db_dir.to_string_lossy().to_string(), | ||||
|                     port: 0, // Not used for RPC-managed databases | ||||
|                     debug: false, | ||||
|                     encryption_key, | ||||
|                     encryption_key: encryption_key.clone(), | ||||
|                     encrypt, | ||||
|                     backend: match backend { | ||||
|                         BackendType::Redb => crate::options::BackendType::Redb, | ||||
| @@ -230,6 +447,19 @@ impl RpcServer for RpcServerImpl { | ||||
|                 // Initialize the storage to create the database file | ||||
|                 let _ = server.current_storage(); | ||||
|  | ||||
|                 // Store the encryption key | ||||
|                 { | ||||
|                     let mut keys = self.encryption_keys.write().await; | ||||
|                     keys.insert(db_id, encryption_key.clone()); | ||||
|                 } | ||||
|  | ||||
|                 // Initialize meta file | ||||
|                 let meta = DatabaseMeta { | ||||
|                     public: true, | ||||
|                     keys: HashMap::new(), | ||||
|                 }; | ||||
|                 self.save_meta(db_id, &meta).await?; | ||||
|  | ||||
|                 // Store the server | ||||
|                 let mut servers = self.servers.write().await; | ||||
|                 servers.insert(db_id, Arc::new(server)); | ||||
| @@ -339,4 +569,66 @@ impl RpcServer for RpcServerImpl { | ||||
|  | ||||
|         Ok(stats) | ||||
|     } | ||||
|  | ||||
|     async fn add_access_key(&self, db_id: u64, key: String, permissions: String) -> RpcResult<bool> { | ||||
|         let mut meta = self.load_meta(db_id).await?; | ||||
|  | ||||
|         let perms = match permissions.to_lowercase().as_str() { | ||||
|             "read" => Permissions::Read, | ||||
|             "readwrite" => Permissions::ReadWrite, | ||||
|             _ => return Err(jsonrpsee::types::ErrorObjectOwned::owned( | ||||
|                 -32000, | ||||
|                 "Invalid permissions: use 'read' or 'readwrite'", | ||||
|                 None::<()> | ||||
|             )), | ||||
|         }; | ||||
|  | ||||
|         let hash = hash_key(&key); | ||||
|         let access_key = AccessKey { | ||||
|             hash: hash.clone(), | ||||
|             permissions: perms, | ||||
|             created_at: std::time::SystemTime::now() | ||||
|                 .duration_since(std::time::UNIX_EPOCH) | ||||
|                 .unwrap() | ||||
|                 .as_secs(), | ||||
|         }; | ||||
|  | ||||
|         meta.keys.insert(hash, access_key); | ||||
|         self.save_meta(db_id, &meta).await?; | ||||
|         Ok(true) | ||||
|     } | ||||
|  | ||||
|     async fn delete_access_key(&self, db_id: u64, key_hash: String) -> RpcResult<bool> { | ||||
|         let mut meta = self.load_meta(db_id).await?; | ||||
|  | ||||
|         if meta.keys.remove(&key_hash).is_some() { | ||||
|             // If no keys left, make database public | ||||
|             if meta.keys.is_empty() { | ||||
|                 meta.public = true; | ||||
|             } | ||||
|             self.save_meta(db_id, &meta).await?; | ||||
|             Ok(true) | ||||
|         } else { | ||||
|             Ok(false) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     async fn list_access_keys(&self, db_id: u64) -> RpcResult<Vec<AccessKeyInfo>> { | ||||
|         let meta = self.load_meta(db_id).await?; | ||||
|         let keys: Vec<AccessKeyInfo> = meta.keys.values() | ||||
|             .map(|k| AccessKeyInfo { | ||||
|                 hash: k.hash.clone(), | ||||
|                 permissions: k.permissions.clone(), | ||||
|                 created_at: k.created_at, | ||||
|             }) | ||||
|             .collect(); | ||||
|         Ok(keys) | ||||
|     } | ||||
|  | ||||
|     async fn set_database_public(&self, db_id: u64, public: bool) -> RpcResult<bool> { | ||||
|         let mut meta = self.load_meta(db_id).await?; | ||||
|         meta.public = public; | ||||
|         self.save_meta(db_id, &meta).await?; | ||||
|         Ok(true) | ||||
|     } | ||||
| } | ||||
| @@ -22,6 +22,7 @@ pub struct Server { | ||||
|     pub client_name: Option<String>, | ||||
|     pub selected_db: u64, // Changed from usize to u64 | ||||
|     pub queued_cmd: Option<Vec<(Cmd, Protocol)>>, | ||||
|     pub current_permissions: Option<crate::rpc::Permissions>, | ||||
|  | ||||
|     // BLPOP waiter registry: per (db_index, key) FIFO of waiters | ||||
|     pub list_waiters: Arc<Mutex<HashMap<u64, HashMap<String, Vec<Waiter>>>>>, | ||||
| @@ -48,6 +49,7 @@ impl Server { | ||||
|             client_name: None, | ||||
|             selected_db: 0, | ||||
|             queued_cmd: None, | ||||
|             current_permissions: None, | ||||
|  | ||||
|             list_waiters: Arc::new(Mutex::new(HashMap::new())), | ||||
|             waiter_seq: Arc::new(AtomicU64::new(1)), | ||||
| @@ -101,6 +103,16 @@ impl Server { | ||||
|         self.option.encrypt && db_index >= 10 | ||||
|     } | ||||
|  | ||||
|     /// Check if current permissions allow read operations | ||||
|     pub fn has_read_permission(&self) -> bool { | ||||
|         matches!(self.current_permissions, Some(crate::rpc::Permissions::Read) | Some(crate::rpc::Permissions::ReadWrite)) | ||||
|     } | ||||
|  | ||||
|     /// Check if current permissions allow write operations | ||||
|     pub fn has_write_permission(&self) -> bool { | ||||
|         matches!(self.current_permissions, Some(crate::rpc::Permissions::ReadWrite)) | ||||
|     } | ||||
|  | ||||
|     // ----- BLPOP waiter helpers ----- | ||||
|  | ||||
|     pub async fn register_waiter(&self, db_index: u64, key: &str, side: PopSide) -> (u64, oneshot::Receiver<(String, String)>) { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user