helios_persistence/tenant/
permissions.rs1use std::collections::HashSet;
7use std::fmt;
8
9use serde::{Deserialize, Serialize};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
13#[serde(rename_all = "lowercase")]
14pub enum Operation {
15 Create,
17 Read,
19 Update,
21 Delete,
23 History,
25 Search,
27 Transaction,
29 Bulk,
31}
32
33impl fmt::Display for Operation {
34 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35 match self {
36 Operation::Create => write!(f, "create"),
37 Operation::Read => write!(f, "read"),
38 Operation::Update => write!(f, "update"),
39 Operation::Delete => write!(f, "delete"),
40 Operation::History => write!(f, "history"),
41 Operation::Search => write!(f, "search"),
42 Operation::Transaction => write!(f, "transaction"),
43 Operation::Bulk => write!(f, "bulk"),
44 }
45 }
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
79pub struct TenantPermissions {
80 allowed_operations: Option<HashSet<Operation>>,
82
83 allowed_resource_types: Option<HashSet<String>>,
85
86 compartment: Option<CompartmentRestriction>,
89
90 can_access_system_tenant: bool,
92
93 can_access_child_tenants: bool,
95}
96
97#[derive(Debug, Clone, Serialize, Deserialize)]
99pub struct CompartmentRestriction {
100 pub compartment_type: String,
102 pub compartment_id: String,
104}
105
106impl TenantPermissions {
107 pub fn full_access() -> Self {
109 Self {
110 allowed_operations: None,
111 allowed_resource_types: None,
112 compartment: None,
113 can_access_system_tenant: true,
114 can_access_child_tenants: false,
115 }
116 }
117
118 pub fn read_only() -> Self {
120 let mut ops = HashSet::new();
121 ops.insert(Operation::Read);
122 ops.insert(Operation::History);
123 ops.insert(Operation::Search);
124
125 Self {
126 allowed_operations: Some(ops),
127 allowed_resource_types: None,
128 compartment: None,
129 can_access_system_tenant: true,
130 can_access_child_tenants: false,
131 }
132 }
133
134 pub fn builder() -> TenantPermissionsBuilder {
136 TenantPermissionsBuilder::new()
137 }
138
139 pub fn can_perform(&self, operation: Operation, resource_type: &str) -> bool {
141 if let Some(ref allowed_ops) = self.allowed_operations {
143 if !allowed_ops.contains(&operation) {
144 return false;
145 }
146 }
147
148 if let Some(ref allowed_types) = self.allowed_resource_types {
150 if !allowed_types.contains(resource_type) {
151 return false;
152 }
153 }
154
155 true
156 }
157
158 pub fn can_access_system_tenant(&self) -> bool {
160 self.can_access_system_tenant
161 }
162
163 pub fn can_access_child_tenants(&self) -> bool {
165 self.can_access_child_tenants
166 }
167
168 pub fn compartment(&self) -> Option<&CompartmentRestriction> {
170 self.compartment.as_ref()
171 }
172
173 pub fn allowed_operations(&self) -> Option<&HashSet<Operation>> {
175 self.allowed_operations.as_ref()
176 }
177
178 pub fn allowed_resource_types(&self) -> Option<&HashSet<String>> {
180 self.allowed_resource_types.as_ref()
181 }
182}
183
184impl Default for TenantPermissions {
185 fn default() -> Self {
186 Self::full_access()
187 }
188}
189
190#[derive(Default)]
192pub struct TenantPermissionsBuilder {
193 allowed_operations: Option<HashSet<Operation>>,
194 allowed_resource_types: Option<HashSet<String>>,
195 compartment: Option<CompartmentRestriction>,
196 can_access_system_tenant: bool,
197 can_access_child_tenants: bool,
198}
199
200impl TenantPermissionsBuilder {
201 pub fn new() -> Self {
203 Self {
204 allowed_operations: None,
205 allowed_resource_types: None,
206 compartment: None,
207 can_access_system_tenant: true,
208 can_access_child_tenants: false,
209 }
210 }
211
212 pub fn allow_operations(mut self, operations: Vec<Operation>) -> Self {
214 self.allowed_operations = Some(operations.into_iter().collect());
215 self
216 }
217
218 pub fn allow_resource_types(mut self, types: Vec<&str>) -> Self {
220 self.allowed_resource_types = Some(types.into_iter().map(String::from).collect());
221 self
222 }
223
224 pub fn restrict_to_compartment(mut self, compartment_type: &str, compartment_id: &str) -> Self {
226 self.compartment = Some(CompartmentRestriction {
227 compartment_type: compartment_type.to_string(),
228 compartment_id: compartment_id.to_string(),
229 });
230 self
231 }
232
233 pub fn can_access_system_tenant(mut self, can_access: bool) -> Self {
235 self.can_access_system_tenant = can_access;
236 self
237 }
238
239 pub fn can_access_child_tenants(mut self, can_access: bool) -> Self {
241 self.can_access_child_tenants = can_access;
242 self
243 }
244
245 pub fn build(self) -> TenantPermissions {
247 TenantPermissions {
248 allowed_operations: self.allowed_operations,
249 allowed_resource_types: self.allowed_resource_types,
250 compartment: self.compartment,
251 can_access_system_tenant: self.can_access_system_tenant,
252 can_access_child_tenants: self.can_access_child_tenants,
253 }
254 }
255}
256
257#[cfg(test)]
258mod tests {
259 use super::*;
260
261 #[test]
262 fn test_full_access() {
263 let perms = TenantPermissions::full_access();
264 assert!(perms.can_perform(Operation::Create, "Patient"));
265 assert!(perms.can_perform(Operation::Read, "Observation"));
266 assert!(perms.can_perform(Operation::Delete, "Encounter"));
267 assert!(perms.can_access_system_tenant());
268 }
269
270 #[test]
271 fn test_read_only() {
272 let perms = TenantPermissions::read_only();
273 assert!(perms.can_perform(Operation::Read, "Patient"));
274 assert!(perms.can_perform(Operation::Search, "Observation"));
275 assert!(perms.can_perform(Operation::History, "Encounter"));
276 assert!(!perms.can_perform(Operation::Create, "Patient"));
277 assert!(!perms.can_perform(Operation::Update, "Patient"));
278 assert!(!perms.can_perform(Operation::Delete, "Patient"));
279 }
280
281 #[test]
282 fn test_custom_permissions() {
283 let perms = TenantPermissions::builder()
284 .allow_operations(vec![Operation::Read, Operation::Search])
285 .allow_resource_types(vec!["Patient", "Observation"])
286 .build();
287
288 assert!(perms.can_perform(Operation::Read, "Patient"));
290 assert!(perms.can_perform(Operation::Search, "Observation"));
291
292 assert!(!perms.can_perform(Operation::Create, "Patient"));
294
295 assert!(!perms.can_perform(Operation::Read, "Encounter"));
297 }
298
299 #[test]
300 fn test_compartment_restriction() {
301 let perms = TenantPermissions::builder()
302 .restrict_to_compartment("Patient", "123")
303 .build();
304
305 let compartment = perms.compartment().unwrap();
306 assert_eq!(compartment.compartment_type, "Patient");
307 assert_eq!(compartment.compartment_id, "123");
308 }
309
310 #[test]
311 fn test_operation_display() {
312 assert_eq!(Operation::Create.to_string(), "create");
313 assert_eq!(Operation::Read.to_string(), "read");
314 assert_eq!(Operation::Update.to_string(), "update");
315 assert_eq!(Operation::Delete.to_string(), "delete");
316 }
317
318 #[test]
319 fn test_child_tenant_access() {
320 let perms = TenantPermissions::builder()
321 .can_access_child_tenants(true)
322 .build();
323 assert!(perms.can_access_child_tenants());
324
325 let perms2 = TenantPermissions::full_access();
326 assert!(!perms2.can_access_child_tenants());
327 }
328}