1use async_trait::async_trait;
2
3use camel_api::security_policy::{AuthContext, AuthorizationDecision, SecurityPolicy};
4use camel_api::{CamelError, Exchange};
5
6pub use camel_api::security_policy::PRINCIPAL_KEY;
11
12pub struct RolePolicy {
19 required_roles: Vec<String>,
20 all_required: bool,
21}
22
23impl RolePolicy {
24 pub fn new(required_roles: Vec<String>, all_required: bool) -> Self {
25 Self {
26 required_roles,
27 all_required,
28 }
29 }
30}
31
32#[async_trait]
33impl SecurityPolicy for RolePolicy {
34 async fn evaluate(
35 &self,
36 _exchange: &mut Exchange,
37 auth: &AuthContext<'_>,
38 ) -> Result<AuthorizationDecision, CamelError> {
39 let principal = auth.principal.principal();
40
41 let missing: Vec<String> = self
42 .required_roles
43 .iter()
44 .filter(|r| !principal.has_role(r))
45 .cloned()
46 .collect();
47
48 let granted = if self.all_required {
49 missing.is_empty()
50 } else {
51 self.required_roles.is_empty() || missing.len() < self.required_roles.len()
52 };
53
54 if granted {
55 Ok(AuthorizationDecision::Granted {
56 principal: principal.clone(),
57 })
58 } else {
59 let actual = principal.roles.clone();
60 Ok(AuthorizationDecision::Denied {
61 reason: format!("missing required role(s): {}", missing.join(", ")), required: self.required_roles.clone(),
63 actual,
64 })
65 }
66 }
67}
68
69pub struct ScopePolicy {
76 required_scopes: Vec<String>,
77 all_required: bool,
78}
79
80impl ScopePolicy {
81 pub fn new(required_scopes: Vec<String>, all_required: bool) -> Self {
82 Self {
83 required_scopes,
84 all_required,
85 }
86 }
87}
88
89#[async_trait]
90impl SecurityPolicy for ScopePolicy {
91 async fn evaluate(
92 &self,
93 _exchange: &mut Exchange,
94 auth: &AuthContext<'_>,
95 ) -> Result<AuthorizationDecision, CamelError> {
96 let principal = auth.principal.principal();
97
98 let missing: Vec<String> = self
99 .required_scopes
100 .iter()
101 .filter(|s| !principal.has_scope(s))
102 .cloned()
103 .collect();
104
105 let granted = if self.all_required {
106 missing.is_empty()
107 } else {
108 self.required_scopes.is_empty() || missing.len() < self.required_scopes.len()
109 };
110
111 if granted {
112 Ok(AuthorizationDecision::Granted {
113 principal: principal.clone(),
114 })
115 } else {
116 let actual = principal.scopes.clone();
117 Ok(AuthorizationDecision::Denied {
118 reason: format!("missing required scope(s): {}", missing.join(", ")),
119 required: self.required_scopes.clone(),
120 actual,
121 })
122 }
123 }
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129 use camel_api::Message;
130 use camel_api::security_policy::{
131 AuthPrincipal, Principal, TransportId, store_principal_properties,
132 };
133
134 fn test_principal(roles: Vec<&str>, scopes: Vec<&str>) -> Principal {
135 Principal {
136 subject: "test-user".into(),
137 issuer: "test".into(),
138 audience: vec![],
139 roles: roles.iter().map(|s| s.to_string()).collect(),
140 scopes: scopes.iter().map(|s| s.to_string()).collect(),
141 claims: serde_json::Value::Null,
142 }
143 }
144
145 struct TestPrincipal(Principal);
149
150 impl AuthPrincipal for TestPrincipal {
151 fn principal(&self) -> &Principal {
152 &self.0
153 }
154 fn provider_id(&self) -> &str {
155 "test"
156 }
157 }
158
159 fn auth_ctx<'a>(principal: &'a TestPrincipal) -> AuthContext<'a> {
160 AuthContext {
161 principal,
162 transport: TransportId::Http,
163 }
164 }
165
166 fn empty_exchange() -> Exchange {
167 Exchange::new(Message::default())
168 }
169
170 #[tokio::test]
171 async fn role_policy_reads_typed_principal_roles() {
172 let principal = TestPrincipal(test_principal(vec!["admin"], vec![]));
173 let policy = RolePolicy::new(vec!["admin".into()], true);
174 let mut ex = empty_exchange();
175 let auth = auth_ctx(&principal);
176 let decision = policy.evaluate(&mut ex, &auth).await.unwrap();
177 assert!(matches!(decision, AuthorizationDecision::Granted { .. }));
178 }
179
180 #[tokio::test]
181 async fn property_only_evidence_denies() {
182 let spoofed = test_principal(vec!["admin"], vec![]);
187 let mut ex = empty_exchange();
188 store_principal_properties(&mut ex, &spoofed);
189
190 let typed = TestPrincipal(test_principal(vec!["user"], vec![]));
191 let policy = RolePolicy::new(vec!["admin".into()], true);
192 let auth = auth_ctx(&typed);
193 let decision = policy.evaluate(&mut ex, &auth).await.unwrap();
194 assert!(matches!(decision, AuthorizationDecision::Denied { .. }));
195 }
196
197 #[tokio::test]
198 async fn role_policy_grants_when_role_present() {
199 let principal = TestPrincipal(test_principal(vec!["admin"], vec![]));
200 let policy = RolePolicy::new(vec!["admin".into()], true);
201 let mut ex = empty_exchange();
202 let auth = auth_ctx(&principal);
203 let decision = policy.evaluate(&mut ex, &auth).await.unwrap();
204 assert!(matches!(decision, AuthorizationDecision::Granted { .. }));
205 }
206
207 #[tokio::test]
208 async fn role_policy_denies_when_role_missing() {
209 let principal = TestPrincipal(test_principal(vec!["user"], vec![]));
210 let policy = RolePolicy::new(vec!["admin".into()], true);
211 let mut ex = empty_exchange();
212 let auth = auth_ctx(&principal);
213 let decision = policy.evaluate(&mut ex, &auth).await.unwrap();
214 assert!(matches!(decision, AuthorizationDecision::Denied { .. }));
215 }
216
217 #[tokio::test]
218 async fn role_policy_any_required() {
219 let principal = TestPrincipal(test_principal(vec!["user"], vec![]));
220 let policy = RolePolicy::new(vec!["admin".into(), "user".into()], false);
221 let mut ex = empty_exchange();
222 let auth = auth_ctx(&principal);
223 let decision = policy.evaluate(&mut ex, &auth).await.unwrap();
224 assert!(matches!(decision, AuthorizationDecision::Granted { .. }));
225 }
226
227 #[tokio::test]
228 async fn scope_policy_grants() {
229 let principal = TestPrincipal(test_principal(vec![], vec!["read"]));
230 let policy = ScopePolicy::new(vec!["read".into()], true);
231 let mut ex = empty_exchange();
232 let auth = auth_ctx(&principal);
233 let decision = policy.evaluate(&mut ex, &auth).await.unwrap();
234 assert!(matches!(decision, AuthorizationDecision::Granted { .. }));
235 }
236
237 #[tokio::test]
238 async fn scope_policy_denies_when_scope_missing() {
239 let principal = TestPrincipal(test_principal(vec![], vec!["read"]));
240 let policy = ScopePolicy::new(vec!["write".into()], true);
241 let mut ex = empty_exchange();
242 let auth = auth_ctx(&principal);
243 let decision = policy.evaluate(&mut ex, &auth).await.unwrap();
244 assert!(matches!(decision, AuthorizationDecision::Denied { .. }));
245 }
246
247 #[tokio::test]
248 async fn scope_policy_any_required() {
249 let principal = TestPrincipal(test_principal(vec![], vec!["read"]));
250 let policy = ScopePolicy::new(vec!["write".into(), "read".into()], false);
251 let mut ex = empty_exchange();
252 let auth = auth_ctx(&principal);
253 let decision = policy.evaluate(&mut ex, &auth).await.unwrap();
254 assert!(matches!(decision, AuthorizationDecision::Granted { .. }));
255 }
256}