use crate::access_control::{
AccessControlEvaluator, AccessDecision, AccessRequest, abac::AbacEngine, rbac::RbacEngine,
};
use crate::error::Result;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum EnforcementMode {
RbacOnly,
AbacOnly,
Both,
Either,
AbacThenRbac,
RbacThenAbac,
}
pub struct PolicyEngine {
rbac: Arc<RbacEngine>,
abac: Arc<AbacEngine>,
enforcement_mode: parking_lot::RwLock<EnforcementMode>,
}
impl PolicyEngine {
pub fn new(rbac: Arc<RbacEngine>, abac: Arc<AbacEngine>) -> Self {
Self {
rbac,
abac,
enforcement_mode: parking_lot::RwLock::new(EnforcementMode::Either),
}
}
pub fn set_enforcement_mode(&self, mode: EnforcementMode) {
*self.enforcement_mode.write() = mode;
}
pub fn get_enforcement_mode(&self) -> EnforcementMode {
*self.enforcement_mode.read()
}
pub fn rbac(&self) -> &Arc<RbacEngine> {
&self.rbac
}
pub fn abac(&self) -> &Arc<AbacEngine> {
&self.abac
}
}
impl AccessControlEvaluator for PolicyEngine {
fn evaluate(&self, request: &AccessRequest) -> Result<AccessDecision> {
let mode = self.get_enforcement_mode();
match mode {
EnforcementMode::RbacOnly => self.rbac.evaluate(request),
EnforcementMode::AbacOnly => self.abac.evaluate(request),
EnforcementMode::Both => {
let rbac_decision = self.rbac.evaluate(request)?;
let abac_decision = self.abac.evaluate(request)?;
if rbac_decision == AccessDecision::Allow && abac_decision == AccessDecision::Allow
{
Ok(AccessDecision::Allow)
} else {
Ok(AccessDecision::Deny)
}
}
EnforcementMode::Either => {
let rbac_decision = self.rbac.evaluate(request)?;
let abac_decision = self.abac.evaluate(request)?;
if rbac_decision == AccessDecision::Allow || abac_decision == AccessDecision::Allow
{
Ok(AccessDecision::Allow)
} else {
Ok(AccessDecision::Deny)
}
}
EnforcementMode::AbacThenRbac => {
let abac_decision = self.abac.evaluate(request)?;
if abac_decision == AccessDecision::Allow {
Ok(AccessDecision::Allow)
} else {
self.rbac.evaluate(request)
}
}
EnforcementMode::RbacThenAbac => {
let rbac_decision = self.rbac.evaluate(request)?;
if rbac_decision == AccessDecision::Allow {
Ok(AccessDecision::Allow)
} else {
self.abac.evaluate(request)
}
}
}
}
}
pub struct SpatialAccessControl {
regions: dashmap::DashMap<String, (f64, f64, f64, f64)>,
subject_regions: dashmap::DashMap<String, Vec<String>>,
}
impl SpatialAccessControl {
pub fn new() -> Self {
Self {
regions: dashmap::DashMap::new(),
subject_regions: dashmap::DashMap::new(),
}
}
pub fn define_region(
&self,
region_id: String,
min_lon: f64,
min_lat: f64,
max_lon: f64,
max_lat: f64,
) -> Result<()> {
self.regions
.insert(region_id, (min_lon, min_lat, max_lon, max_lat));
Ok(())
}
pub fn grant_region_access(&self, subject_id: &str, region_id: String) -> Result<()> {
self.subject_regions
.entry(subject_id.to_string())
.or_default()
.push(region_id);
Ok(())
}
pub fn can_access_point(&self, subject_id: &str, lon: f64, lat: f64) -> bool {
if let Some(regions) = self.subject_regions.get(subject_id) {
for region_id in regions.iter() {
if let Some(bounds) = self.regions.get(region_id) {
let (min_lon, min_lat, max_lon, max_lat) = *bounds;
if lon >= min_lon && lon <= max_lon && lat >= min_lat && lat <= max_lat {
return true;
}
}
}
}
false
}
pub fn can_access_bbox(
&self,
subject_id: &str,
min_lon: f64,
min_lat: f64,
max_lon: f64,
max_lat: f64,
) -> bool {
if let Some(regions) = self.subject_regions.get(subject_id) {
for region_id in regions.iter() {
if let Some(bounds) = self.regions.get(region_id) {
let (r_min_lon, r_min_lat, r_max_lon, r_max_lat) = *bounds;
if min_lon >= r_min_lon
&& max_lon <= r_max_lon
&& min_lat >= r_min_lat
&& max_lat <= r_max_lat
{
return true;
}
}
}
}
false
}
}
impl Default for SpatialAccessControl {
fn default() -> Self {
Self::new()
}
}
pub struct TemporalAccessControl {
time_windows: dashmap::DashMap<String, Vec<(chrono::NaiveTime, chrono::NaiveTime)>>,
date_ranges: dashmap::DashMap<String, Vec<(chrono::NaiveDate, Option<chrono::NaiveDate>)>>,
}
impl TemporalAccessControl {
pub fn new() -> Self {
Self {
time_windows: dashmap::DashMap::new(),
date_ranges: dashmap::DashMap::new(),
}
}
pub fn set_time_window(
&self,
subject_id: String,
start: chrono::NaiveTime,
end: chrono::NaiveTime,
) {
self.time_windows
.entry(subject_id)
.or_default()
.push((start, end));
}
pub fn set_date_range(
&self,
subject_id: String,
start: chrono::NaiveDate,
end: Option<chrono::NaiveDate>,
) {
self.date_ranges
.entry(subject_id)
.or_default()
.push((start, end));
}
pub fn can_access_now(&self, subject_id: &str) -> bool {
let now = chrono::Utc::now();
let current_time = now.time();
let current_date = now.date_naive();
if let Some(windows) = self.time_windows.get(subject_id) {
let mut time_allowed = false;
for (start, end) in windows.iter() {
if current_time >= *start && current_time <= *end {
time_allowed = true;
break;
}
}
if !time_allowed && !windows.is_empty() {
return false;
}
}
if let Some(ranges) = self.date_ranges.get(subject_id) {
let mut date_allowed = false;
for (start, end) in ranges.iter() {
if current_date >= *start {
if let Some(end_date) = end {
if current_date <= *end_date {
date_allowed = true;
break;
}
} else {
date_allowed = true;
break;
}
}
}
if !date_allowed && !ranges.is_empty() {
return false;
}
}
true
}
}
impl Default for TemporalAccessControl {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_policy_engine_either_mode() {
let rbac = Arc::new(RbacEngine::new());
let abac = Arc::new(AbacEngine::new());
let engine = PolicyEngine::new(rbac, abac);
engine.set_enforcement_mode(EnforcementMode::Either);
assert_eq!(engine.get_enforcement_mode(), EnforcementMode::Either);
}
#[test]
fn test_spatial_access_control() {
let sac = SpatialAccessControl::new();
sac.define_region(
"us".to_string(),
-125.0, 24.0, -66.0, 49.0, )
.expect("Failed to define region");
sac.grant_region_access("user-123", "us".to_string())
.expect("Failed to grant access");
assert!(sac.can_access_point("user-123", -100.0, 40.0));
assert!(!sac.can_access_point("user-123", 0.0, 51.0));
}
#[test]
fn test_spatial_bbox_access() {
let sac = SpatialAccessControl::new();
sac.define_region("region-1".to_string(), 0.0, 0.0, 10.0, 10.0)
.expect("Failed to define region");
sac.grant_region_access("user-123", "region-1".to_string())
.expect("Failed to grant access");
assert!(sac.can_access_bbox("user-123", 1.0, 1.0, 9.0, 9.0));
assert!(!sac.can_access_bbox("user-123", 5.0, 5.0, 15.0, 15.0));
}
#[test]
fn test_temporal_access_control() {
let tac = TemporalAccessControl::new();
let start = chrono::NaiveTime::from_hms_opt(9, 0, 0).expect("Invalid time");
let end = chrono::NaiveTime::from_hms_opt(17, 0, 0).expect("Invalid time");
tac.set_time_window("user-123".to_string(), start, end);
let _ = tac.can_access_now("user-123");
}
}