oxigdal_security/access_control/
policies.rs1use crate::access_control::{
4 AccessControlEvaluator, AccessDecision, AccessRequest, abac::AbacEngine, rbac::RbacEngine,
5};
6use crate::error::Result;
7use serde::{Deserialize, Serialize};
8use std::sync::Arc;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
12pub enum EnforcementMode {
13 RbacOnly,
15 AbacOnly,
17 Both,
19 Either,
21 AbacThenRbac,
23 RbacThenAbac,
25}
26
27pub struct PolicyEngine {
29 rbac: Arc<RbacEngine>,
30 abac: Arc<AbacEngine>,
31 enforcement_mode: parking_lot::RwLock<EnforcementMode>,
32}
33
34impl PolicyEngine {
35 pub fn new(rbac: Arc<RbacEngine>, abac: Arc<AbacEngine>) -> Self {
37 Self {
38 rbac,
39 abac,
40 enforcement_mode: parking_lot::RwLock::new(EnforcementMode::Either),
41 }
42 }
43
44 pub fn set_enforcement_mode(&self, mode: EnforcementMode) {
46 *self.enforcement_mode.write() = mode;
47 }
48
49 pub fn get_enforcement_mode(&self) -> EnforcementMode {
51 *self.enforcement_mode.read()
52 }
53
54 pub fn rbac(&self) -> &Arc<RbacEngine> {
56 &self.rbac
57 }
58
59 pub fn abac(&self) -> &Arc<AbacEngine> {
61 &self.abac
62 }
63}
64
65impl AccessControlEvaluator for PolicyEngine {
66 fn evaluate(&self, request: &AccessRequest) -> Result<AccessDecision> {
67 let mode = self.get_enforcement_mode();
68
69 match mode {
70 EnforcementMode::RbacOnly => self.rbac.evaluate(request),
71 EnforcementMode::AbacOnly => self.abac.evaluate(request),
72 EnforcementMode::Both => {
73 let rbac_decision = self.rbac.evaluate(request)?;
74 let abac_decision = self.abac.evaluate(request)?;
75
76 if rbac_decision == AccessDecision::Allow && abac_decision == AccessDecision::Allow
77 {
78 Ok(AccessDecision::Allow)
79 } else {
80 Ok(AccessDecision::Deny)
81 }
82 }
83 EnforcementMode::Either => {
84 let rbac_decision = self.rbac.evaluate(request)?;
85 let abac_decision = self.abac.evaluate(request)?;
86
87 if rbac_decision == AccessDecision::Allow || abac_decision == AccessDecision::Allow
88 {
89 Ok(AccessDecision::Allow)
90 } else {
91 Ok(AccessDecision::Deny)
92 }
93 }
94 EnforcementMode::AbacThenRbac => {
95 let abac_decision = self.abac.evaluate(request)?;
96 if abac_decision == AccessDecision::Allow {
97 Ok(AccessDecision::Allow)
98 } else {
99 self.rbac.evaluate(request)
100 }
101 }
102 EnforcementMode::RbacThenAbac => {
103 let rbac_decision = self.rbac.evaluate(request)?;
104 if rbac_decision == AccessDecision::Allow {
105 Ok(AccessDecision::Allow)
106 } else {
107 self.abac.evaluate(request)
108 }
109 }
110 }
111 }
112}
113
114pub struct SpatialAccessControl {
116 regions: dashmap::DashMap<String, (f64, f64, f64, f64)>,
118 subject_regions: dashmap::DashMap<String, Vec<String>>,
120}
121
122impl SpatialAccessControl {
123 pub fn new() -> Self {
125 Self {
126 regions: dashmap::DashMap::new(),
127 subject_regions: dashmap::DashMap::new(),
128 }
129 }
130
131 pub fn define_region(
133 &self,
134 region_id: String,
135 min_lon: f64,
136 min_lat: f64,
137 max_lon: f64,
138 max_lat: f64,
139 ) -> Result<()> {
140 self.regions
141 .insert(region_id, (min_lon, min_lat, max_lon, max_lat));
142 Ok(())
143 }
144
145 pub fn grant_region_access(&self, subject_id: &str, region_id: String) -> Result<()> {
147 self.subject_regions
148 .entry(subject_id.to_string())
149 .or_default()
150 .push(region_id);
151 Ok(())
152 }
153
154 pub fn can_access_point(&self, subject_id: &str, lon: f64, lat: f64) -> bool {
156 if let Some(regions) = self.subject_regions.get(subject_id) {
157 for region_id in regions.iter() {
158 if let Some(bounds) = self.regions.get(region_id) {
159 let (min_lon, min_lat, max_lon, max_lat) = *bounds;
160 if lon >= min_lon && lon <= max_lon && lat >= min_lat && lat <= max_lat {
161 return true;
162 }
163 }
164 }
165 }
166 false
167 }
168
169 pub fn can_access_bbox(
171 &self,
172 subject_id: &str,
173 min_lon: f64,
174 min_lat: f64,
175 max_lon: f64,
176 max_lat: f64,
177 ) -> bool {
178 if let Some(regions) = self.subject_regions.get(subject_id) {
179 for region_id in regions.iter() {
180 if let Some(bounds) = self.regions.get(region_id) {
181 let (r_min_lon, r_min_lat, r_max_lon, r_max_lat) = *bounds;
182 if min_lon >= r_min_lon
184 && max_lon <= r_max_lon
185 && min_lat >= r_min_lat
186 && max_lat <= r_max_lat
187 {
188 return true;
189 }
190 }
191 }
192 }
193 false
194 }
195}
196
197impl Default for SpatialAccessControl {
198 fn default() -> Self {
199 Self::new()
200 }
201}
202
203pub struct TemporalAccessControl {
205 time_windows: dashmap::DashMap<String, Vec<(chrono::NaiveTime, chrono::NaiveTime)>>,
207 date_ranges: dashmap::DashMap<String, Vec<(chrono::NaiveDate, Option<chrono::NaiveDate>)>>,
209}
210
211impl TemporalAccessControl {
212 pub fn new() -> Self {
214 Self {
215 time_windows: dashmap::DashMap::new(),
216 date_ranges: dashmap::DashMap::new(),
217 }
218 }
219
220 pub fn set_time_window(
222 &self,
223 subject_id: String,
224 start: chrono::NaiveTime,
225 end: chrono::NaiveTime,
226 ) {
227 self.time_windows
228 .entry(subject_id)
229 .or_default()
230 .push((start, end));
231 }
232
233 pub fn set_date_range(
235 &self,
236 subject_id: String,
237 start: chrono::NaiveDate,
238 end: Option<chrono::NaiveDate>,
239 ) {
240 self.date_ranges
241 .entry(subject_id)
242 .or_default()
243 .push((start, end));
244 }
245
246 pub fn can_access_now(&self, subject_id: &str) -> bool {
248 let now = chrono::Utc::now();
249 let current_time = now.time();
250 let current_date = now.date_naive();
251
252 if let Some(windows) = self.time_windows.get(subject_id) {
254 let mut time_allowed = false;
255 for (start, end) in windows.iter() {
256 if current_time >= *start && current_time <= *end {
257 time_allowed = true;
258 break;
259 }
260 }
261 if !time_allowed && !windows.is_empty() {
262 return false;
263 }
264 }
265
266 if let Some(ranges) = self.date_ranges.get(subject_id) {
268 let mut date_allowed = false;
269 for (start, end) in ranges.iter() {
270 if current_date >= *start {
271 if let Some(end_date) = end {
272 if current_date <= *end_date {
273 date_allowed = true;
274 break;
275 }
276 } else {
277 date_allowed = true;
278 break;
279 }
280 }
281 }
282 if !date_allowed && !ranges.is_empty() {
283 return false;
284 }
285 }
286
287 true
288 }
289}
290
291impl Default for TemporalAccessControl {
292 fn default() -> Self {
293 Self::new()
294 }
295}
296
297#[cfg(test)]
298mod tests {
299 use super::*;
300
301 #[test]
302 fn test_policy_engine_either_mode() {
303 let rbac = Arc::new(RbacEngine::new());
304 let abac = Arc::new(AbacEngine::new());
305 let engine = PolicyEngine::new(rbac, abac);
306
307 engine.set_enforcement_mode(EnforcementMode::Either);
308 assert_eq!(engine.get_enforcement_mode(), EnforcementMode::Either);
309 }
310
311 #[test]
312 fn test_spatial_access_control() {
313 let sac = SpatialAccessControl::new();
314
315 sac.define_region(
317 "us".to_string(),
318 -125.0, 24.0, -66.0, 49.0, )
323 .expect("Failed to define region");
324
325 sac.grant_region_access("user-123", "us".to_string())
326 .expect("Failed to grant access");
327
328 assert!(sac.can_access_point("user-123", -100.0, 40.0));
330
331 assert!(!sac.can_access_point("user-123", 0.0, 51.0));
333 }
334
335 #[test]
336 fn test_spatial_bbox_access() {
337 let sac = SpatialAccessControl::new();
338
339 sac.define_region("region-1".to_string(), 0.0, 0.0, 10.0, 10.0)
340 .expect("Failed to define region");
341
342 sac.grant_region_access("user-123", "region-1".to_string())
343 .expect("Failed to grant access");
344
345 assert!(sac.can_access_bbox("user-123", 1.0, 1.0, 9.0, 9.0));
347
348 assert!(!sac.can_access_bbox("user-123", 5.0, 5.0, 15.0, 15.0));
350 }
351
352 #[test]
353 fn test_temporal_access_control() {
354 let tac = TemporalAccessControl::new();
355
356 let start = chrono::NaiveTime::from_hms_opt(9, 0, 0).expect("Invalid time");
358 let end = chrono::NaiveTime::from_hms_opt(17, 0, 0).expect("Invalid time");
359 tac.set_time_window("user-123".to_string(), start, end);
360
361 let _ = tac.can_access_now("user-123");
363 }
364}