role_system/
context_integration.rs1use crate::{
4 auth_context::AuthenticationContext,
5 core::RoleSystem,
6 error::Result,
7 resource::Resource,
8 subject::Subject,
9 storage::Storage,
10};
11use std::collections::HashMap;
12
13pub trait ContextualPermissions<T: AuthenticationContext> {
15 fn check_contextual_permission(
17 &self,
18 context: &T,
19 action: &str,
20 resource: &Resource,
21 additional_context: Option<HashMap<String, String>>,
22 ) -> Result<bool>;
23
24 fn check_scope_permission(
26 &self,
27 context: &T,
28 required_scopes: &[String],
29 ) -> Result<bool>;
30}
31
32impl<S, T> ContextualPermissions<T> for RoleSystem<S>
33where
34 S: Storage,
35 T: AuthenticationContext,
36{
37 fn check_contextual_permission(
53 &self,
54 context: &T,
55 action: &str,
56 resource: &Resource,
57 _additional_context: Option<HashMap<String, String>>, ) -> Result<bool> {
59 if !context.is_valid() {
61 #[cfg(feature = "audit")]
62 log::warn!(
63 "Permission check denied: invalid authentication context for action '{}' on resource '{}'",
64 action,
65 resource.id()
66 );
67
68 return Ok(false);
69 }
70
71 let permission_string = format!("{}:{}", action, resource.resource_type());
73 let instance_permission_string = format!("{}:{}:{}", action, resource.resource_type(), resource.id());
74
75 let granted_scopes = context.get_granted_scopes();
76 let has_scope = granted_scopes.iter().any(|scope| {
77 scope == &permission_string ||
78 scope == &instance_permission_string ||
79 scope == "*:*" ||
80 scope == &format!("*:{}", resource.resource_type()) ||
81 scope == &format!("{}:*", action)
82 });
83
84 if has_scope {
85 #[cfg(feature = "audit")]
86 log::info!(
87 "Permission granted via authentication context scope for action '{}' on resource '{}'",
88 action,
89 resource.id()
90 );
91
92 return Ok(true);
93 }
94
95 let subject = Subject::user(context.get_user_id().as_ref() as &str);
97
98 self.check_permission(&subject, action, resource)
103 }
104
105 fn check_scope_permission(
118 &self,
119 context: &T,
120 required_scopes: &[String],
121 ) -> Result<bool> {
122 if !context.is_valid() {
124 return Ok(false);
125 }
126
127 let granted_scopes = context.get_granted_scopes();
128
129 for required in required_scopes {
131 if granted_scopes.contains(required) {
132 return Ok(true);
133 }
134
135 if required.contains(':') {
137 let parts: Vec<&str> = required.split(':').collect();
139
140 if granted_scopes.contains(&"*:*".to_string()) {
142 return Ok(true);
143 }
144
145 if parts.len() >= 2 {
146 let action = parts[0];
147 let resource = parts[1];
148
149 if granted_scopes.contains(&format!("*:{}", resource)) {
151 return Ok(true);
152 }
153
154 if granted_scopes.contains(&format!("{}:*", action)) {
156 return Ok(true);
157 }
158 }
159 }
160 }
161
162 Ok(false)
163 }
164}
165
166#[cfg(test)]
167mod tests {
168 use super::*;
169 use crate::auth_context::JwtContext;
170 use crate::core::RoleSystem;
171 use crate::permission::Permission;
172 use crate::role::Role;
173 use std::collections::HashMap;
174
175 #[test]
176 fn test_jwt_context_permissions() {
177 let mut role_system = RoleSystem::new();
178
179 let editor = Role::new("editor")
181 .add_permission(Permission::new("edit", "documents"))
182 .add_permission(Permission::new("read", "documents"));
183
184 role_system.register_role(editor).unwrap();
185
186 let mut payload = HashMap::new();
188 payload.insert("exp".to_string(), "1625097600".to_string());
189 payload.insert("iat".to_string(), "1625011200".to_string());
190
191 let context = JwtContext::new(
193 "user123".to_string(),
194 vec!["read:documents".to_string()],
195 true,
196 payload
197 );
198
199 let document = Resource::new("doc1", "documents");
201
202 assert!(role_system.check_contextual_permission(&context, "read", &document, None).unwrap());
204
205 assert!(!role_system.check_contextual_permission(&context, "edit", &document, None).unwrap());
207
208 let subject = Subject::user(context.get_user_id().as_ref() as &str);
210 role_system.assign_role(&subject, "editor").unwrap();
211
212 assert!(role_system.check_contextual_permission(&context, "edit", &document, None).unwrap());
214 }
215
216 #[test]
217 fn test_scope_permission() {
218 let role_system = RoleSystem::new();
219
220 let payload = HashMap::new();
222
223 let context = JwtContext::new(
225 "user123".to_string(),
226 vec!["read:documents".to_string(), "write:posts".to_string()],
227 true,
228 payload
229 );
230
231 assert!(role_system.check_scope_permission(&context, &["read:documents".to_string()]).unwrap());
233 assert!(role_system.check_scope_permission(&context, &["write:posts".to_string()]).unwrap());
234 assert!(!role_system.check_scope_permission(&context, &["admin:users".to_string()]).unwrap());
235
236 assert!(role_system.check_scope_permission(
238 &context,
239 &["read:documents".to_string(), "admin:users".to_string()]
240 ).unwrap());
241
242 let admin_context = JwtContext::new(
244 "admin".to_string(),
245 vec!["*:*".to_string()],
246 true,
247 HashMap::new()
248 );
249
250 assert!(role_system.check_scope_permission(&admin_context, &["read:any".to_string()]).unwrap());
251 assert!(role_system.check_scope_permission(&admin_context, &["admin:users".to_string()]).unwrap());
252 }
253}