ricecoder_permissions/
storage.rs1use crate::audit::AuditLogEntry;
6use crate::error::Result;
7use crate::permission::PermissionConfig;
8use std::path::Path;
9
10pub trait PermissionRepository: Send + Sync {
12 fn load_config(&self) -> Result<PermissionConfig>;
14
15 fn save_config(&self, config: &PermissionConfig) -> Result<()>;
17
18 fn load_audit_logs(&self) -> Result<Vec<AuditLogEntry>>;
20
21 fn save_audit_logs(&self, logs: &[AuditLogEntry]) -> Result<()>;
23
24 fn append_audit_log(&self, entry: &AuditLogEntry) -> Result<()>;
26}
27
28pub struct FilePermissionRepository {
30 config_path: std::path::PathBuf,
32 audit_path: std::path::PathBuf,
34}
35
36impl FilePermissionRepository {
37 pub fn new<P: AsRef<Path>>(config_path: P, audit_path: P) -> Self {
39 Self {
40 config_path: config_path.as_ref().to_path_buf(),
41 audit_path: audit_path.as_ref().to_path_buf(),
42 }
43 }
44
45 pub fn with_defaults<P: AsRef<Path>>(base_path: P) -> Self {
47 let base = base_path.as_ref();
48 Self {
49 config_path: base.join("permissions.json"),
50 audit_path: base.join("audit_logs.json"),
51 }
52 }
53}
54
55impl PermissionRepository for FilePermissionRepository {
56 fn load_config(&self) -> Result<PermissionConfig> {
57 if !self.config_path.exists() {
58 return Ok(PermissionConfig::new());
60 }
61
62 let content = std::fs::read_to_string(&self.config_path)?;
63 let config = serde_json::from_str(&content)?;
64 Ok(config)
65 }
66
67 fn save_config(&self, config: &PermissionConfig) -> Result<()> {
68 if let Some(parent) = self.config_path.parent() {
70 std::fs::create_dir_all(parent)?;
71 }
72
73 let content = serde_json::to_string_pretty(config)?;
74 std::fs::write(&self.config_path, content)?;
75 Ok(())
76 }
77
78 fn load_audit_logs(&self) -> Result<Vec<AuditLogEntry>> {
79 if !self.audit_path.exists() {
80 return Ok(Vec::new());
82 }
83
84 let content = std::fs::read_to_string(&self.audit_path)?;
85 let logs = serde_json::from_str(&content)?;
86 Ok(logs)
87 }
88
89 fn save_audit_logs(&self, logs: &[AuditLogEntry]) -> Result<()> {
90 if let Some(parent) = self.audit_path.parent() {
92 std::fs::create_dir_all(parent)?;
93 }
94
95 let content = serde_json::to_string_pretty(logs)?;
96 std::fs::write(&self.audit_path, content)?;
97 Ok(())
98 }
99
100 fn append_audit_log(&self, entry: &AuditLogEntry) -> Result<()> {
101 let mut logs = self.load_audit_logs()?;
103
104 logs.push(entry.clone());
106
107 self.save_audit_logs(&logs)?;
109 Ok(())
110 }
111}
112
113pub struct InMemoryPermissionRepository {
115 config: std::sync::Arc<std::sync::RwLock<PermissionConfig>>,
116 logs: std::sync::Arc<std::sync::RwLock<Vec<AuditLogEntry>>>,
117}
118
119impl InMemoryPermissionRepository {
120 pub fn new() -> Self {
122 Self {
123 config: std::sync::Arc::new(std::sync::RwLock::new(PermissionConfig::new())),
124 logs: std::sync::Arc::new(std::sync::RwLock::new(Vec::new())),
125 }
126 }
127}
128
129impl Default for InMemoryPermissionRepository {
130 fn default() -> Self {
131 Self::new()
132 }
133}
134
135impl PermissionRepository for InMemoryPermissionRepository {
136 fn load_config(&self) -> Result<PermissionConfig> {
137 let config = self
138 .config
139 .read()
140 .map_err(|e| crate::error::Error::Internal(format!("Failed to read config: {}", e)))?;
141 Ok(config.clone())
142 }
143
144 fn save_config(&self, config: &PermissionConfig) -> Result<()> {
145 let mut stored_config = self
146 .config
147 .write()
148 .map_err(|e| crate::error::Error::Internal(format!("Failed to write config: {}", e)))?;
149 *stored_config = config.clone();
150 Ok(())
151 }
152
153 fn load_audit_logs(&self) -> Result<Vec<AuditLogEntry>> {
154 let logs = self
155 .logs
156 .read()
157 .map_err(|e| crate::error::Error::Internal(format!("Failed to read logs: {}", e)))?;
158 Ok(logs.clone())
159 }
160
161 fn save_audit_logs(&self, logs: &[AuditLogEntry]) -> Result<()> {
162 let mut stored_logs = self
163 .logs
164 .write()
165 .map_err(|e| crate::error::Error::Internal(format!("Failed to write logs: {}", e)))?;
166 *stored_logs = logs.to_vec();
167 Ok(())
168 }
169
170 fn append_audit_log(&self, entry: &AuditLogEntry) -> Result<()> {
171 let mut logs = self
172 .logs
173 .write()
174 .map_err(|e| crate::error::Error::Internal(format!("Failed to write logs: {}", e)))?;
175 logs.push(entry.clone());
176 Ok(())
177 }
178}
179
180#[cfg(test)]
181mod tests {
182 use super::*;
183 use crate::audit::AuditAction;
184 use crate::audit::AuditResult;
185 use crate::permission::PermissionLevel;
186 use crate::permission::ToolPermission;
187
188 #[test]
189 fn test_in_memory_repository_save_and_load_config() {
190 let repo = InMemoryPermissionRepository::new();
191
192 let mut config = PermissionConfig::new();
193 config.add_permission(ToolPermission::new(
194 "test_tool".to_string(),
195 PermissionLevel::Allow,
196 ));
197
198 repo.save_config(&config).unwrap();
199
200 let loaded = repo.load_config().unwrap();
201 assert_eq!(loaded.get_permissions().len(), 1);
202 assert_eq!(loaded.get_permissions()[0].tool_pattern, "test_tool");
203 }
204
205 #[test]
206 fn test_in_memory_repository_save_and_load_logs() {
207 let repo = InMemoryPermissionRepository::new();
208
209 let entry = AuditLogEntry::new(
210 "test_tool".to_string(),
211 AuditAction::Allowed,
212 AuditResult::Success,
213 );
214
215 repo.append_audit_log(&entry).unwrap();
216
217 let logs = repo.load_audit_logs().unwrap();
218 assert_eq!(logs.len(), 1);
219 assert_eq!(logs[0].tool, "test_tool");
220 }
221
222 #[test]
223 fn test_in_memory_repository_append_multiple_logs() {
224 let repo = InMemoryPermissionRepository::new();
225
226 let entry1 = AuditLogEntry::new(
227 "tool1".to_string(),
228 AuditAction::Allowed,
229 AuditResult::Success,
230 );
231 let entry2 = AuditLogEntry::new(
232 "tool2".to_string(),
233 AuditAction::Denied,
234 AuditResult::Blocked,
235 );
236
237 repo.append_audit_log(&entry1).unwrap();
238 repo.append_audit_log(&entry2).unwrap();
239
240 let logs = repo.load_audit_logs().unwrap();
241 assert_eq!(logs.len(), 2);
242 assert_eq!(logs[0].tool, "tool1");
243 assert_eq!(logs[1].tool, "tool2");
244 }
245
246 #[test]
247 fn test_in_memory_repository_default() {
248 let repo = InMemoryPermissionRepository::default();
249 let config = repo.load_config().unwrap();
250 assert_eq!(config.get_permissions().len(), 0);
251 }
252}