1use serde::{Deserialize, Serialize};
6use std::collections::{HashMap, HashSet};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
10pub enum Permission {
11 Read,
13 Write,
15 Update,
17 Delete,
19 Query,
21 CreateIndex,
23 DeleteIndex,
25 ManageCollections,
27 ViewStats,
29 ManageUsers,
31 Admin,
33}
34
35impl Permission {
36 pub fn implies(&self, other: &Permission) -> bool {
38 match self {
39 Permission::Admin => true, Permission::Write => matches!(other, Permission::Read | Permission::Write),
41 Permission::Update => matches!(other, Permission::Read | Permission::Update),
42 Permission::Delete => matches!(other, Permission::Read | Permission::Delete),
43 _ => self == other,
44 }
45 }
46
47 pub fn all() -> Vec<Permission> {
49 vec![
50 Permission::Read,
51 Permission::Write,
52 Permission::Update,
53 Permission::Delete,
54 Permission::Query,
55 Permission::CreateIndex,
56 Permission::DeleteIndex,
57 Permission::ManageCollections,
58 Permission::ViewStats,
59 Permission::ManageUsers,
60 Permission::Admin,
61 ]
62 }
63}
64
65#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
67pub enum Resource {
68 Vector(String),
70 Collection(String),
72 Index(String),
74 Namespace(String),
76 Global,
78}
79
80impl Resource {
81 pub fn contains(&self, other: &Resource) -> bool {
83 match (self, other) {
84 (Resource::Global, _) => true,
85 (Resource::Namespace(ns1), Resource::Collection(col)) => col.starts_with(ns1),
86 (Resource::Collection(col1), Resource::Vector(vec)) => vec.starts_with(col1),
87 _ => self == other,
88 }
89 }
90}
91
92#[derive(Debug, Clone, Serialize, Deserialize)]
94pub struct Role {
95 pub name: String,
97
98 pub permissions: HashSet<Permission>,
100
101 pub description: String,
103
104 pub inherits_from: Vec<String>,
106}
107
108impl Role {
109 pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
111 Self {
112 name: name.into(),
113 permissions: HashSet::new(),
114 description: description.into(),
115 inherits_from: Vec::new(),
116 }
117 }
118
119 pub fn with_permission(mut self, permission: Permission) -> Self {
121 self.permissions.insert(permission);
122 self
123 }
124
125 pub fn with_permissions(mut self, permissions: Vec<Permission>) -> Self {
127 self.permissions.extend(permissions);
128 self
129 }
130
131 pub fn inherits_from(mut self, role_name: impl Into<String>) -> Self {
133 self.inherits_from.push(role_name.into());
134 self
135 }
136
137 pub fn has_permission(&self, permission: &Permission) -> bool {
139 self.permissions.iter().any(|p| p.implies(permission))
140 }
141}
142
143#[derive(Debug, Clone, Serialize, Deserialize)]
145pub struct Policy {
146 pub id: String,
148
149 pub subject: String,
151
152 pub resource: Resource,
154
155 pub permission: Permission,
157
158 pub effect: Effect,
160
161 pub conditions: Vec<Condition>,
163}
164
165#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
167pub enum Effect {
168 Allow,
169 Deny,
170}
171
172#[derive(Debug, Clone, Serialize, Deserialize)]
174pub struct Condition {
175 pub attribute: String,
177
178 pub operator: Operator,
180
181 pub value: String,
183}
184
185#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
187pub enum Operator {
188 Equals,
189 NotEquals,
190 Contains,
191 StartsWith,
192 EndsWith,
193 GreaterThan,
194 LessThan,
195}
196
197impl Condition {
198 pub fn evaluate(&self, context: &AccessContext) -> bool {
200 let actual = context.attributes.get(&self.attribute);
201
202 match actual {
203 Some(actual_value) => self.check_operator(actual_value),
204 None => false,
205 }
206 }
207
208 fn check_operator(&self, actual: &str) -> bool {
209 match self.operator {
210 Operator::Equals => actual == self.value,
211 Operator::NotEquals => actual != self.value,
212 Operator::Contains => actual.contains(&self.value),
213 Operator::StartsWith => actual.starts_with(&self.value),
214 Operator::EndsWith => actual.ends_with(&self.value),
215 Operator::GreaterThan => actual > self.value.as_str(),
216 Operator::LessThan => actual < self.value.as_str(),
217 }
218 }
219}
220
221#[derive(Debug, Clone, Default)]
223pub struct AccessContext {
224 pub attributes: HashMap<String, String>,
226}
227
228impl AccessContext {
229 pub fn new() -> Self {
230 Self::default()
231 }
232
233 pub fn with_attribute(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
234 self.attributes.insert(key.into(), value.into());
235 self
236 }
237}
238
239#[derive(Debug, Clone, Serialize, Deserialize)]
241pub struct User {
242 pub id: String,
244
245 pub roles: Vec<String>,
247
248 pub attributes: HashMap<String, String>,
250}
251
252impl User {
253 pub fn new(id: impl Into<String>) -> Self {
254 Self {
255 id: id.into(),
256 roles: Vec::new(),
257 attributes: HashMap::new(),
258 }
259 }
260
261 pub fn with_role(mut self, role: impl Into<String>) -> Self {
262 self.roles.push(role.into());
263 self
264 }
265
266 pub fn with_attribute(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
267 self.attributes.insert(key.into(), value.into());
268 self
269 }
270}
271
272pub struct AccessControl {
274 roles: HashMap<String, Role>,
276
277 users: HashMap<String, User>,
279
280 policies: Vec<Policy>,
282
283 default_effect: Effect,
285}
286
287impl AccessControl {
288 pub fn new() -> Self {
290 let mut ac = Self {
291 roles: HashMap::new(),
292 users: HashMap::new(),
293 policies: Vec::new(),
294 default_effect: Effect::Deny,
295 };
296
297 ac.add_role(Self::viewer_role());
299 ac.add_role(Self::editor_role());
300 ac.add_role(Self::admin_role());
301
302 ac
303 }
304
305 pub fn add_role(&mut self, role: Role) {
307 self.roles.insert(role.name.clone(), role);
308 }
309
310 pub fn add_user(&mut self, user: User) {
312 self.users.insert(user.id.clone(), user);
313 }
314
315 pub fn add_policy(&mut self, policy: Policy) {
317 self.policies.push(policy);
318 }
319
320 pub fn check_permission(
322 &self,
323 user_id: &str,
324 resource: &Resource,
325 permission: &Permission,
326 context: &AccessContext,
327 ) -> bool {
328 let user = match self.users.get(user_id) {
330 Some(u) => u,
331 None => return false,
332 };
333
334 let mut user_permissions = HashSet::new();
336 for role_name in &user.roles {
337 if let Some(role) = self.roles.get(role_name) {
338 self.collect_permissions(role, &mut user_permissions);
339 }
340 }
341
342 let has_permission = user_permissions.iter().any(|p| p.implies(permission));
344
345 if !has_permission {
346 return false;
347 }
348
349 self.evaluate_policies(user_id, resource, permission, context)
351 }
352
353 fn collect_permissions(&self, role: &Role, permissions: &mut HashSet<Permission>) {
355 permissions.extend(&role.permissions);
356
357 for parent_name in &role.inherits_from {
358 if let Some(parent) = self.roles.get(parent_name) {
359 self.collect_permissions(parent, permissions);
360 }
361 }
362 }
363
364 fn evaluate_policies(
366 &self,
367 user_id: &str,
368 resource: &Resource,
369 permission: &Permission,
370 context: &AccessContext,
371 ) -> bool {
372 let mut deny = false;
373
374 for policy in &self.policies {
375 if !self.policy_applies(policy, user_id, resource, permission) {
377 continue;
378 }
379
380 if !policy.conditions.iter().all(|c| c.evaluate(context)) {
382 continue;
383 }
384
385 if matches!(policy.effect, Effect::Deny) {
387 deny = true;
388 }
389 }
390
391 if deny {
393 return false;
394 }
395
396 true
398 }
399
400 fn policy_applies(
402 &self,
403 policy: &Policy,
404 user_id: &str,
405 resource: &Resource,
406 permission: &Permission,
407 ) -> bool {
408 let subject_matches = if policy.subject == user_id {
410 true
411 } else {
412 if let Some(user) = self.users.get(user_id) {
414 user.roles.contains(&policy.subject)
415 } else {
416 false
417 }
418 };
419
420 if !subject_matches {
421 return false;
422 }
423
424 let resource_matches = policy.resource.contains(resource) || policy.resource == *resource;
426
427 if !resource_matches {
428 return false;
429 }
430
431 policy.permission.implies(permission)
433 }
434
435 pub fn get_user_permissions(&self, user_id: &str, resource: &Resource) -> Vec<Permission> {
437 let user = match self.users.get(user_id) {
438 Some(u) => u,
439 None => return Vec::new(),
440 };
441
442 let mut permissions = HashSet::new();
443
444 for role_name in &user.roles {
445 if let Some(role) = self.roles.get(role_name) {
446 self.collect_permissions(role, &mut permissions);
447 }
448 }
449
450 let context = AccessContext::new();
451
452 permissions
453 .into_iter()
454 .filter(|p| self.check_permission(user_id, resource, p, &context))
455 .collect()
456 }
457
458 fn viewer_role() -> Role {
460 Role::new("viewer", "Can read and query vectors").with_permissions(vec![
461 Permission::Read,
462 Permission::Query,
463 Permission::ViewStats,
464 ])
465 }
466
467 fn editor_role() -> Role {
468 Role::new("editor", "Can read, write, and modify vectors")
469 .with_permissions(vec![
470 Permission::Read,
471 Permission::Write,
472 Permission::Update,
473 Permission::Query,
474 Permission::ViewStats,
475 ])
476 .inherits_from("viewer")
477 }
478
479 fn admin_role() -> Role {
480 Role::new("admin", "Full administrative access").with_permission(Permission::Admin)
481 }
482}
483
484impl Default for AccessControl {
485 fn default() -> Self {
486 Self::new()
487 }
488}
489
490#[cfg(test)]
491mod tests {
492 use super::*;
493
494 #[test]
495 fn test_permission_implies() {
496 assert!(Permission::Admin.implies(&Permission::Read));
497 assert!(Permission::Admin.implies(&Permission::Write));
498 assert!(Permission::Write.implies(&Permission::Read));
499 assert!(!Permission::Read.implies(&Permission::Write));
500 }
501
502 #[test]
503 fn test_resource_hierarchy() {
504 let global = Resource::Global;
505 let namespace = Resource::Namespace("ns1".to_string());
506 let collection = Resource::Collection("ns1/col1".to_string());
507 let vector = Resource::Vector("ns1/col1/vec1".to_string());
508
509 assert!(global.contains(&namespace));
510 assert!(global.contains(&collection));
511 assert!(namespace.contains(&collection));
512 assert!(collection.contains(&vector));
513 assert!(!collection.contains(&namespace));
514 }
515
516 #[test]
517 fn test_role_permissions() {
518 let role = Role::new("test", "Test role")
519 .with_permission(Permission::Read)
520 .with_permission(Permission::Write);
521
522 assert!(role.has_permission(&Permission::Read));
523 assert!(role.has_permission(&Permission::Write));
524 assert!(!role.has_permission(&Permission::Delete));
525 }
526
527 #[test]
528 fn test_basic_access_control() {
529 let mut ac = AccessControl::new();
530
531 let user = User::new("alice").with_role("viewer");
532 ac.add_user(user);
533
534 let context = AccessContext::new();
535
536 assert!(ac.check_permission("alice", &Resource::Global, &Permission::Read, &context));
538
539 assert!(!ac.check_permission("alice", &Resource::Global, &Permission::Write, &context));
541 }
542
543 #[test]
544 fn test_role_inheritance() {
545 let mut ac = AccessControl::new();
546
547 let user = User::new("bob").with_role("editor");
548 ac.add_user(user);
549
550 let context = AccessContext::new();
551
552 assert!(ac.check_permission("bob", &Resource::Global, &Permission::Read, &context));
554
555 assert!(ac.check_permission("bob", &Resource::Global, &Permission::Write, &context));
557 }
558
559 #[test]
560 fn test_admin_role() {
561 let mut ac = AccessControl::new();
562
563 let user = User::new("admin").with_role("admin");
564 ac.add_user(user);
565
566 let context = AccessContext::new();
567
568 for permission in Permission::all() {
570 assert!(ac.check_permission("admin", &Resource::Global, &permission, &context));
571 }
572 }
573
574 #[test]
575 fn test_policy_allow() {
576 let mut ac = AccessControl::new();
577
578 let user = User::new("carol").with_role("viewer");
579 ac.add_user(user);
580
581 ac.add_policy(Policy {
583 id: "allow_write".to_string(),
584 subject: "carol".to_string(),
585 resource: Resource::Collection("col1".to_string()),
586 permission: Permission::Write,
587 effect: Effect::Allow,
588 conditions: Vec::new(),
589 });
590
591 let context = AccessContext::new();
592
593 assert!(!ac.check_permission("carol", &Resource::Global, &Permission::Write, &context));
595 }
596
597 #[test]
598 fn test_policy_deny() {
599 let mut ac = AccessControl::new();
600
601 let user = User::new("dave").with_role("editor");
602 ac.add_user(user);
603
604 ac.add_policy(Policy {
606 id: "deny_delete".to_string(),
607 subject: "dave".to_string(),
608 resource: Resource::Collection("restricted".to_string()),
609 permission: Permission::Delete,
610 effect: Effect::Deny,
611 conditions: Vec::new(),
612 });
613
614 let context = AccessContext::new();
615
616 assert!(!ac.check_permission(
618 "dave",
619 &Resource::Collection("restricted".to_string()),
620 &Permission::Delete,
621 &context
622 ));
623 }
624
625 #[test]
626 fn test_condition_evaluation() {
627 let condition = Condition {
628 attribute: "ip_address".to_string(),
629 operator: Operator::StartsWith,
630 value: "192.168".to_string(),
631 };
632
633 let context = AccessContext::new().with_attribute("ip_address", "192.168.1.100");
634
635 assert!(condition.evaluate(&context));
636
637 let context2 = AccessContext::new().with_attribute("ip_address", "10.0.0.1");
638
639 assert!(!condition.evaluate(&context2));
640 }
641
642 #[test]
643 fn test_get_user_permissions() {
644 let mut ac = AccessControl::new();
645
646 let user = User::new("eve").with_role("editor");
647 ac.add_user(user);
648
649 let permissions = ac.get_user_permissions("eve", &Resource::Global);
650
651 assert!(!permissions.is_empty());
652 assert!(permissions.contains(&Permission::Read));
653 assert!(permissions.contains(&Permission::Write));
654 }
655
656 #[test]
657 fn test_unknown_user() {
658 let ac = AccessControl::new();
659 let context = AccessContext::new();
660
661 assert!(!ac.check_permission("unknown", &Resource::Global, &Permission::Read, &context));
662 }
663}