adk_auth/
access_control.rs1use crate::audit::{AuditEvent, AuditOutcome, AuditSink};
4use crate::error::{AccessDenied, AuthError};
5use crate::permission::Permission;
6use crate::role::Role;
7use std::collections::HashMap;
8use std::sync::Arc;
9
10#[derive(Clone)]
12pub struct AccessControl {
13 roles: HashMap<String, Role>,
15 user_roles: HashMap<String, Vec<String>>,
17 audit: Option<Arc<dyn AuditSink>>,
19}
20
21impl AccessControl {
22 pub fn builder() -> AccessControlBuilder {
24 AccessControlBuilder::default()
25 }
26
27 pub fn check(&self, user: &str, permission: &Permission) -> Result<(), AccessDenied> {
29 let role_names = self.user_roles.get(user);
30
31 if let Some(role_names) = role_names {
32 for role_name in role_names {
33 if let Some(role) = self.roles.get(role_name) {
34 if role.can_access(permission) {
35 return Ok(());
36 }
37 }
38 }
39 }
40
41 Err(AccessDenied::new(user, permission.to_string()))
42 }
43
44 pub async fn check_and_audit(
46 &self,
47 user: &str,
48 permission: &Permission,
49 ) -> Result<(), AuthError> {
50 let result = self.check(user, permission);
51
52 if let Some(audit) = &self.audit {
54 let outcome = if result.is_ok() { AuditOutcome::Allowed } else { AuditOutcome::Denied };
55
56 let event = match permission {
57 Permission::Tool(name) => AuditEvent::tool_access(user, name.as_str(), outcome),
58 Permission::AllTools => AuditEvent::tool_access(user, "*", outcome),
59 Permission::Agent(name) => AuditEvent::agent_access(user, name.as_str(), outcome),
60 Permission::AllAgents => AuditEvent::agent_access(user, "*", outcome),
61 };
62
63 audit.log(event).await?;
64 }
65
66 result.map_err(AuthError::from)
67 }
68
69 pub fn user_roles(&self, user: &str) -> Vec<&Role> {
71 self.user_roles
72 .get(user)
73 .map(|names| names.iter().filter_map(|name| self.roles.get(name)).collect())
74 .unwrap_or_default()
75 }
76
77 pub fn role_names(&self) -> Vec<&str> {
79 self.roles.keys().map(|s| s.as_str()).collect()
80 }
81
82 pub fn get_role(&self, name: &str) -> Option<&Role> {
84 self.roles.get(name)
85 }
86}
87
88#[derive(Default)]
90pub struct AccessControlBuilder {
91 roles: HashMap<String, Role>,
92 user_roles: HashMap<String, Vec<String>>,
93 audit: Option<Arc<dyn AuditSink>>,
94}
95
96impl AccessControlBuilder {
97 pub fn role(mut self, role: Role) -> Self {
99 self.roles.insert(role.name.clone(), role);
100 self
101 }
102
103 pub fn assign(mut self, user: impl Into<String>, role: impl Into<String>) -> Self {
105 self.user_roles.entry(user.into()).or_default().push(role.into());
106 self
107 }
108
109 pub fn audit_sink(mut self, sink: impl AuditSink + 'static) -> Self {
111 self.audit = Some(Arc::new(sink));
112 self
113 }
114
115 pub fn build(self) -> Result<AccessControl, AuthError> {
117 for (user, roles) in &self.user_roles {
119 for role in roles {
120 if !self.roles.contains_key(role) {
121 return Err(AuthError::RoleNotFound(format!(
122 "Role '{}' assigned to user '{}' does not exist",
123 role, user
124 )));
125 }
126 }
127 }
128
129 Ok(AccessControl { roles: self.roles, user_roles: self.user_roles, audit: self.audit })
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 fn setup_ac() -> AccessControl {
138 let admin = Role::new("admin").allow(Permission::AllTools).allow(Permission::AllAgents);
139
140 let user = Role::new("user")
141 .allow(Permission::Tool("search".into()))
142 .deny(Permission::Tool("exec".into()));
143
144 AccessControl::builder()
145 .role(admin)
146 .role(user)
147 .assign("alice", "admin")
148 .assign("bob", "user")
149 .build()
150 .unwrap()
151 }
152
153 #[test]
154 fn test_admin_has_full_access() {
155 let ac = setup_ac();
156 assert!(ac.check("alice", &Permission::Tool("anything".into())).is_ok());
157 assert!(ac.check("alice", &Permission::AllTools).is_ok());
158 assert!(ac.check("alice", &Permission::Agent("any".into())).is_ok());
159 }
160
161 #[test]
162 fn test_user_limited_access() {
163 let ac = setup_ac();
164 assert!(ac.check("bob", &Permission::Tool("search".into())).is_ok());
166 assert!(ac.check("bob", &Permission::Tool("exec".into())).is_err());
168 assert!(ac.check("bob", &Permission::Tool("other".into())).is_err());
170 }
171
172 #[test]
173 fn test_unknown_user_denied() {
174 let ac = setup_ac();
175 assert!(ac.check("unknown", &Permission::Tool("search".into())).is_err());
176 }
177
178 #[test]
179 fn test_invalid_role_assignment() {
180 let result = AccessControl::builder()
181 .role(Role::new("admin"))
182 .assign("alice", "nonexistent")
183 .build();
184
185 assert!(result.is_err());
186 }
187
188 #[test]
189 fn test_multi_role_user() {
190 let roles = [
191 Role::new("reader").allow(Permission::Tool("read".into())),
192 Role::new("writer").allow(Permission::Tool("write".into())),
193 ];
194
195 let ac = AccessControl::builder()
196 .role(roles[0].clone())
197 .role(roles[1].clone())
198 .assign("bob", "reader")
199 .assign("bob", "writer")
200 .build()
201 .unwrap();
202
203 assert!(ac.check("bob", &Permission::Tool("read".into())).is_ok());
205 assert!(ac.check("bob", &Permission::Tool("write".into())).is_ok());
206 }
207}