pub mod abac;
pub mod permissions;
pub mod policies;
pub mod rbac;
pub mod roles;
use crate::error::Result;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Subject {
pub id: String,
pub subject_type: SubjectType,
pub attributes: HashMap<String, String>,
}
impl Subject {
pub fn new(id: String, subject_type: SubjectType) -> Self {
Self {
id,
subject_type,
attributes: HashMap::new(),
}
}
pub fn with_attribute(mut self, key: String, value: String) -> Self {
self.attributes.insert(key, value);
self
}
pub fn get_attribute(&self, key: &str) -> Option<&String> {
self.attributes.get(key)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum SubjectType {
User,
Service,
ApiKey,
System,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Resource {
pub id: String,
pub resource_type: ResourceType,
pub attributes: HashMap<String, String>,
pub parent: Option<Box<Resource>>,
}
impl Resource {
pub fn new(id: String, resource_type: ResourceType) -> Self {
Self {
id,
resource_type,
attributes: HashMap::new(),
parent: None,
}
}
pub fn with_attribute(mut self, key: String, value: String) -> Self {
self.attributes.insert(key, value);
self
}
pub fn with_parent(mut self, parent: Resource) -> Self {
self.parent = Some(Box::new(parent));
self
}
pub fn get_attribute(&self, key: &str) -> Option<&String> {
self.attributes.get(key)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum ResourceType {
Dataset,
Layer,
Feature,
Raster,
File,
Directory,
Service,
Tenant,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Action {
Read,
Write,
Delete,
Execute,
List,
Create,
Update,
Admin,
}
impl Action {
pub fn all() -> Vec<Action> {
vec![
Action::Read,
Action::Write,
Action::Delete,
Action::Execute,
Action::List,
Action::Create,
Action::Update,
Action::Admin,
]
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum AccessDecision {
Allow,
Deny,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AccessRequest {
pub subject: Subject,
pub resource: Resource,
pub action: Action,
pub context: AccessContext,
}
impl AccessRequest {
pub fn new(
subject: Subject,
resource: Resource,
action: Action,
context: AccessContext,
) -> Self {
Self {
subject,
resource,
action,
context,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AccessContext {
pub timestamp: chrono::DateTime<chrono::Utc>,
pub source_ip: Option<String>,
pub tenant_id: Option<String>,
pub attributes: HashMap<String, String>,
}
impl Default for AccessContext {
fn default() -> Self {
Self::new()
}
}
impl AccessContext {
pub fn new() -> Self {
Self {
timestamp: chrono::Utc::now(),
source_ip: None,
tenant_id: None,
attributes: HashMap::new(),
}
}
pub fn with_source_ip(mut self, ip: String) -> Self {
self.source_ip = Some(ip);
self
}
pub fn with_tenant_id(mut self, tenant_id: String) -> Self {
self.tenant_id = Some(tenant_id);
self
}
pub fn with_attribute(mut self, key: String, value: String) -> Self {
self.attributes.insert(key, value);
self
}
pub fn get_attribute(&self, key: &str) -> Option<&String> {
self.attributes.get(key)
}
}
pub trait AccessControlEvaluator: Send + Sync {
fn evaluate(&self, request: &AccessRequest) -> Result<AccessDecision>;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_subject_creation() {
let subject = Subject::new("user-123".to_string(), SubjectType::User)
.with_attribute("department".to_string(), "engineering".to_string());
assert_eq!(subject.id, "user-123");
assert_eq!(subject.subject_type, SubjectType::User);
assert_eq!(
subject.get_attribute("department"),
Some(&"engineering".to_string())
);
}
#[test]
fn test_resource_creation() {
let resource = Resource::new("dataset-456".to_string(), ResourceType::Dataset)
.with_attribute("classification".to_string(), "confidential".to_string());
assert_eq!(resource.id, "dataset-456");
assert_eq!(resource.resource_type, ResourceType::Dataset);
assert_eq!(
resource.get_attribute("classification"),
Some(&"confidential".to_string())
);
}
#[test]
fn test_access_context() {
let context = AccessContext::new()
.with_source_ip("192.168.1.1".to_string())
.with_tenant_id("tenant-001".to_string())
.with_attribute("region".to_string(), "us-west-2".to_string());
assert_eq!(context.source_ip, Some("192.168.1.1".to_string()));
assert_eq!(context.tenant_id, Some("tenant-001".to_string()));
assert_eq!(
context.get_attribute("region"),
Some(&"us-west-2".to_string())
);
}
#[test]
fn test_all_actions() {
let actions = Action::all();
assert_eq!(actions.len(), 8);
assert!(actions.contains(&Action::Read));
assert!(actions.contains(&Action::Write));
}
}