Skip to main content

oxigdal_security/access_control/
mod.rs

1//! Access control framework.
2
3pub mod abac;
4pub mod permissions;
5pub mod policies;
6pub mod rbac;
7pub mod roles;
8
9use crate::error::Result;
10use serde::{Deserialize, Serialize};
11use std::collections::HashMap;
12
13/// Subject (user or service) requesting access.
14#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
15pub struct Subject {
16    /// Subject ID.
17    pub id: String,
18    /// Subject type (user, service, etc.).
19    pub subject_type: SubjectType,
20    /// Subject attributes.
21    pub attributes: HashMap<String, String>,
22}
23
24impl Subject {
25    /// Create a new subject.
26    pub fn new(id: String, subject_type: SubjectType) -> Self {
27        Self {
28            id,
29            subject_type,
30            attributes: HashMap::new(),
31        }
32    }
33
34    /// Add an attribute.
35    pub fn with_attribute(mut self, key: String, value: String) -> Self {
36        self.attributes.insert(key, value);
37        self
38    }
39
40    /// Get an attribute value.
41    pub fn get_attribute(&self, key: &str) -> Option<&String> {
42        self.attributes.get(key)
43    }
44}
45
46/// Subject type.
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
48pub enum SubjectType {
49    /// Human user.
50    User,
51    /// Service account.
52    Service,
53    /// API key.
54    ApiKey,
55    /// System (internal).
56    System,
57}
58
59/// Resource being accessed.
60#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
61pub struct Resource {
62    /// Resource ID.
63    pub id: String,
64    /// Resource type.
65    pub resource_type: ResourceType,
66    /// Resource attributes.
67    pub attributes: HashMap<String, String>,
68    /// Parent resource (for hierarchical resources).
69    pub parent: Option<Box<Resource>>,
70}
71
72impl Resource {
73    /// Create a new resource.
74    pub fn new(id: String, resource_type: ResourceType) -> Self {
75        Self {
76            id,
77            resource_type,
78            attributes: HashMap::new(),
79            parent: None,
80        }
81    }
82
83    /// Add an attribute.
84    pub fn with_attribute(mut self, key: String, value: String) -> Self {
85        self.attributes.insert(key, value);
86        self
87    }
88
89    /// Set parent resource.
90    pub fn with_parent(mut self, parent: Resource) -> Self {
91        self.parent = Some(Box::new(parent));
92        self
93    }
94
95    /// Get an attribute value.
96    pub fn get_attribute(&self, key: &str) -> Option<&String> {
97        self.attributes.get(key)
98    }
99}
100
101/// Resource type.
102#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
103pub enum ResourceType {
104    /// Dataset.
105    Dataset,
106    /// Layer.
107    Layer,
108    /// Feature.
109    Feature,
110    /// Raster.
111    Raster,
112    /// File.
113    File,
114    /// Directory.
115    Directory,
116    /// Service.
117    Service,
118    /// Tenant.
119    Tenant,
120}
121
122/// Action to be performed on a resource.
123#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
124pub enum Action {
125    /// Read access.
126    Read,
127    /// Write access.
128    Write,
129    /// Delete access.
130    Delete,
131    /// Execute access.
132    Execute,
133    /// List access.
134    List,
135    /// Create access.
136    Create,
137    /// Update access.
138    Update,
139    /// Admin access.
140    Admin,
141}
142
143impl Action {
144    /// Get all actions.
145    pub fn all() -> Vec<Action> {
146        vec![
147            Action::Read,
148            Action::Write,
149            Action::Delete,
150            Action::Execute,
151            Action::List,
152            Action::Create,
153            Action::Update,
154            Action::Admin,
155        ]
156    }
157}
158
159/// Access decision.
160#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
161pub enum AccessDecision {
162    /// Access allowed.
163    Allow,
164    /// Access denied.
165    Deny,
166}
167
168/// Access request.
169#[derive(Debug, Clone, Serialize, Deserialize)]
170pub struct AccessRequest {
171    /// Subject requesting access.
172    pub subject: Subject,
173    /// Resource being accessed.
174    pub resource: Resource,
175    /// Action to be performed.
176    pub action: Action,
177    /// Context information.
178    pub context: AccessContext,
179}
180
181impl AccessRequest {
182    /// Create a new access request.
183    pub fn new(
184        subject: Subject,
185        resource: Resource,
186        action: Action,
187        context: AccessContext,
188    ) -> Self {
189        Self {
190            subject,
191            resource,
192            action,
193            context,
194        }
195    }
196}
197
198/// Access context.
199#[derive(Debug, Clone, Serialize, Deserialize)]
200pub struct AccessContext {
201    /// Request time.
202    pub timestamp: chrono::DateTime<chrono::Utc>,
203    /// Source IP address.
204    pub source_ip: Option<String>,
205    /// Tenant ID.
206    pub tenant_id: Option<String>,
207    /// Additional context attributes.
208    pub attributes: HashMap<String, String>,
209}
210
211impl Default for AccessContext {
212    fn default() -> Self {
213        Self::new()
214    }
215}
216
217impl AccessContext {
218    /// Create a new access context.
219    pub fn new() -> Self {
220        Self {
221            timestamp: chrono::Utc::now(),
222            source_ip: None,
223            tenant_id: None,
224            attributes: HashMap::new(),
225        }
226    }
227
228    /// Set source IP.
229    pub fn with_source_ip(mut self, ip: String) -> Self {
230        self.source_ip = Some(ip);
231        self
232    }
233
234    /// Set tenant ID.
235    pub fn with_tenant_id(mut self, tenant_id: String) -> Self {
236        self.tenant_id = Some(tenant_id);
237        self
238    }
239
240    /// Add an attribute.
241    pub fn with_attribute(mut self, key: String, value: String) -> Self {
242        self.attributes.insert(key, value);
243        self
244    }
245
246    /// Get an attribute value.
247    pub fn get_attribute(&self, key: &str) -> Option<&String> {
248        self.attributes.get(key)
249    }
250}
251
252/// Access control evaluator trait.
253pub trait AccessControlEvaluator: Send + Sync {
254    /// Evaluate an access request.
255    fn evaluate(&self, request: &AccessRequest) -> Result<AccessDecision>;
256}
257
258#[cfg(test)]
259mod tests {
260    use super::*;
261
262    #[test]
263    fn test_subject_creation() {
264        let subject = Subject::new("user-123".to_string(), SubjectType::User)
265            .with_attribute("department".to_string(), "engineering".to_string());
266
267        assert_eq!(subject.id, "user-123");
268        assert_eq!(subject.subject_type, SubjectType::User);
269        assert_eq!(
270            subject.get_attribute("department"),
271            Some(&"engineering".to_string())
272        );
273    }
274
275    #[test]
276    fn test_resource_creation() {
277        let resource = Resource::new("dataset-456".to_string(), ResourceType::Dataset)
278            .with_attribute("classification".to_string(), "confidential".to_string());
279
280        assert_eq!(resource.id, "dataset-456");
281        assert_eq!(resource.resource_type, ResourceType::Dataset);
282        assert_eq!(
283            resource.get_attribute("classification"),
284            Some(&"confidential".to_string())
285        );
286    }
287
288    #[test]
289    fn test_access_context() {
290        let context = AccessContext::new()
291            .with_source_ip("192.168.1.1".to_string())
292            .with_tenant_id("tenant-001".to_string())
293            .with_attribute("region".to_string(), "us-west-2".to_string());
294
295        assert_eq!(context.source_ip, Some("192.168.1.1".to_string()));
296        assert_eq!(context.tenant_id, Some("tenant-001".to_string()));
297        assert_eq!(
298            context.get_attribute("region"),
299            Some(&"us-west-2".to_string())
300        );
301    }
302
303    #[test]
304    fn test_all_actions() {
305        let actions = Action::all();
306        assert_eq!(actions.len(), 8);
307        assert!(actions.contains(&Action::Read));
308        assert!(actions.contains(&Action::Write));
309    }
310}