sayr_engine/
governance.rs

1use std::collections::{HashMap, HashSet};
2use std::sync::{Arc, RwLock};
3
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
7pub enum Role {
8    Admin,
9    User,
10    Service,
11}
12
13#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
14pub enum Action {
15    SendMessage,
16    CallTool(String),
17    ReadTranscript,
18    ManageDeployment,
19}
20
21#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
22pub struct Principal {
23    pub id: String,
24    pub role: Role,
25    pub tenant: Option<String>,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
29pub struct PrivacyRule {
30    pub field: String,
31    pub redaction: String,
32}
33
34#[derive(Default, Clone)]
35pub struct AccessController {
36    rules: Arc<RwLock<HashMap<Role, HashSet<Action>>>>,
37    privacy: Arc<RwLock<Vec<PrivacyRule>>>,
38}
39
40impl AccessController {
41    pub fn new() -> Self {
42        let controller = Self::default();
43        controller.allow(Role::Admin, Action::ManageDeployment);
44        controller.allow(Role::Admin, Action::ReadTranscript);
45        controller
46    }
47
48    pub fn allow(&self, role: Role, action: Action) {
49        let mut rules = self.rules.write().unwrap();
50        rules.entry(role).or_default().insert(action);
51    }
52
53    pub fn authorize(&self, principal: &Principal, action: &Action) -> bool {
54        let rules = self.rules.read().unwrap();
55        if let Some(actions) = rules.get(&principal.role) {
56            actions.contains(action)
57        } else {
58            false
59        }
60    }
61
62    pub fn add_privacy_rule(&mut self, rule: PrivacyRule) {
63        self.privacy.write().unwrap().push(rule);
64    }
65
66    pub fn scrub_payload(&self, payload: &mut serde_json::Value) {
67        let rules = self.privacy.read().unwrap();
68        for rule in rules.iter() {
69            if let Some(obj) = payload.as_object_mut() {
70                if obj.contains_key(&rule.field) {
71                    obj.insert(
72                        rule.field.clone(),
73                        serde_json::Value::String(rule.redaction.clone()),
74                    );
75                }
76            }
77        }
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84
85    #[test]
86    fn denies_missing_action() {
87        let controller = AccessController::new();
88        let user = Principal {
89            id: "user1".into(),
90            role: Role::User,
91            tenant: None,
92        };
93        assert!(!controller.authorize(&user, &Action::ManageDeployment));
94    }
95
96    #[test]
97    fn scrubs_fields() {
98        let mut controller = AccessController::new();
99        controller.add_privacy_rule(PrivacyRule {
100            field: "secret".into(),
101            redaction: "***".into(),
102        });
103        let mut payload = serde_json::json!({"secret": "value", "other": "ok"});
104        controller.scrub_payload(&mut payload);
105        assert_eq!(payload["secret"], "***");
106    }
107}