sayr_engine/
governance.rs1use 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}