vecstore/
access_control.rs

1//! Access Control and RBAC (Role-Based Access Control)
2//!
3//! Provides fine-grained permission management for securing vector operations.
4
5use serde::{Deserialize, Serialize};
6use std::collections::{HashMap, HashSet};
7
8/// Permission types
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
10pub enum Permission {
11    /// Read vectors
12    Read,
13    /// Write/insert vectors
14    Write,
15    /// Update existing vectors
16    Update,
17    /// Delete vectors
18    Delete,
19    /// Query/search vectors
20    Query,
21    /// Create indexes
22    CreateIndex,
23    /// Delete indexes
24    DeleteIndex,
25    /// Manage collections
26    ManageCollections,
27    /// View statistics
28    ViewStats,
29    /// Manage users and roles
30    ManageUsers,
31    /// Full administrative access
32    Admin,
33}
34
35impl Permission {
36    /// Check if this permission implies another permission
37    pub fn implies(&self, other: &Permission) -> bool {
38        match self {
39            Permission::Admin => true, // Admin implies all permissions
40            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    /// Get all permissions
48    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/// Resource types that can be protected
66#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
67pub enum Resource {
68    /// Specific vector by ID
69    Vector(String),
70    /// All vectors in a collection
71    Collection(String),
72    /// Index by name
73    Index(String),
74    /// Namespace
75    Namespace(String),
76    /// Global resource
77    Global,
78}
79
80impl Resource {
81    /// Check if this resource is a parent of another
82    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/// Role definition
93#[derive(Debug, Clone, Serialize, Deserialize)]
94pub struct Role {
95    /// Role name
96    pub name: String,
97
98    /// Permissions granted by this role
99    pub permissions: HashSet<Permission>,
100
101    /// Description
102    pub description: String,
103
104    /// Parent roles (for inheritance)
105    pub inherits_from: Vec<String>,
106}
107
108impl Role {
109    /// Create a new role
110    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    /// Add a permission
120    pub fn with_permission(mut self, permission: Permission) -> Self {
121        self.permissions.insert(permission);
122        self
123    }
124
125    /// Add multiple permissions
126    pub fn with_permissions(mut self, permissions: Vec<Permission>) -> Self {
127        self.permissions.extend(permissions);
128        self
129    }
130
131    /// Inherit from another role
132    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    /// Check if this role has a specific permission
138    pub fn has_permission(&self, permission: &Permission) -> bool {
139        self.permissions.iter().any(|p| p.implies(permission))
140    }
141}
142
143/// Policy rule
144#[derive(Debug, Clone, Serialize, Deserialize)]
145pub struct Policy {
146    /// Policy ID
147    pub id: String,
148
149    /// Subject (user or role)
150    pub subject: String,
151
152    /// Resource being protected
153    pub resource: Resource,
154
155    /// Required permission
156    pub permission: Permission,
157
158    /// Allow or deny
159    pub effect: Effect,
160
161    /// Optional conditions (attribute-based)
162    pub conditions: Vec<Condition>,
163}
164
165/// Policy effect
166#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
167pub enum Effect {
168    Allow,
169    Deny,
170}
171
172/// Condition for attribute-based access control
173#[derive(Debug, Clone, Serialize, Deserialize)]
174pub struct Condition {
175    /// Attribute name (e.g., "ip_address", "time_of_day")
176    pub attribute: String,
177
178    /// Operator
179    pub operator: Operator,
180
181    /// Expected value
182    pub value: String,
183}
184
185/// Condition operators
186#[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    /// Evaluate the condition
199    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/// Access context for ABAC
222#[derive(Debug, Clone, Default)]
223pub struct AccessContext {
224    /// Request attributes (IP, time, etc.)
225    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/// User with assigned roles
240#[derive(Debug, Clone, Serialize, Deserialize)]
241pub struct User {
242    /// User ID
243    pub id: String,
244
245    /// Assigned roles
246    pub roles: Vec<String>,
247
248    /// User attributes
249    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
272/// Access control manager
273pub struct AccessControl {
274    /// Registered roles
275    roles: HashMap<String, Role>,
276
277    /// Users
278    users: HashMap<String, User>,
279
280    /// Policies
281    policies: Vec<Policy>,
282
283    /// Default effect when no policy matches
284    default_effect: Effect,
285}
286
287impl AccessControl {
288    /// Create a new access control manager
289    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        // Add default roles
298        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    /// Add a role
306    pub fn add_role(&mut self, role: Role) {
307        self.roles.insert(role.name.clone(), role);
308    }
309
310    /// Add a user
311    pub fn add_user(&mut self, user: User) {
312        self.users.insert(user.id.clone(), user);
313    }
314
315    /// Add a policy
316    pub fn add_policy(&mut self, policy: Policy) {
317        self.policies.push(policy);
318    }
319
320    /// Check if a user has permission for a resource
321    pub fn check_permission(
322        &self,
323        user_id: &str,
324        resource: &Resource,
325        permission: &Permission,
326        context: &AccessContext,
327    ) -> bool {
328        // Get user
329        let user = match self.users.get(user_id) {
330            Some(u) => u,
331            None => return false,
332        };
333
334        // Collect all permissions from user's roles
335        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        // Check if user has the required permission
343        let has_permission = user_permissions.iter().any(|p| p.implies(permission));
344
345        if !has_permission {
346            return false;
347        }
348
349        // Evaluate policies
350        self.evaluate_policies(user_id, resource, permission, context)
351    }
352
353    /// Collect permissions including inherited ones
354    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    /// Evaluate all policies
365    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            // Check if policy applies
376            if !self.policy_applies(policy, user_id, resource, permission) {
377                continue;
378            }
379
380            // Check conditions
381            if !policy.conditions.iter().all(|c| c.evaluate(context)) {
382                continue;
383            }
384
385            // Apply effect - deny takes precedence
386            if matches!(policy.effect, Effect::Deny) {
387                deny = true;
388            }
389        }
390
391        // If there's an explicit deny, reject access
392        if deny {
393            return false;
394        }
395
396        // Otherwise allow (user has permission through role)
397        true
398    }
399
400    /// Check if a policy applies to this request
401    fn policy_applies(
402        &self,
403        policy: &Policy,
404        user_id: &str,
405        resource: &Resource,
406        permission: &Permission,
407    ) -> bool {
408        // Check subject (user or role)
409        let subject_matches = if policy.subject == user_id {
410            true
411        } else {
412            // Check if subject is a role the user has
413            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        // Check resource (with hierarchy support)
425        let resource_matches = policy.resource.contains(resource) || policy.resource == *resource;
426
427        if !resource_matches {
428            return false;
429        }
430
431        // Check permission (with implication)
432        policy.permission.implies(permission)
433    }
434
435    /// Get user's effective permissions for a resource
436    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    // Predefined roles
459    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        // Viewer can read
537        assert!(ac.check_permission("alice", &Resource::Global, &Permission::Read, &context));
538
539        // Viewer cannot write
540        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        // Editor inherits from viewer, so can read
553        assert!(ac.check_permission("bob", &Resource::Global, &Permission::Read, &context));
554
555        // Editor can write
556        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        // Admin can do everything
569        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        // Add policy to allow writes for specific resource
582        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        // Carol (viewer) normally can't write, but policy allows it for col1
594        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        // Add deny policy
605        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        // Dave (editor) can normally delete, but not this resource
617        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}