1use async_trait::async_trait;
2use std::sync::Arc;
3
4use camel_api::security_policy::{AuthorizationDecision, Principal, SecurityPolicy};
5use camel_api::{CamelError, Exchange};
6
7use crate::token_authenticator::TokenAuthenticator;
8
9pub const PRINCIPAL_KEY: &str = "camel.auth.principal";
11
12async fn authenticate(
20 exchange: &mut Exchange,
21 authenticator: &dyn TokenAuthenticator,
22) -> Result<Principal, CamelError> {
23 let token = exchange
25 .input
26 .header_ic("authorization")
27 .and_then(|v| v.as_str())
28 .and_then(|s| s.strip_prefix("Bearer "))
29 .map(|s| s.to_string());
30
31 if let Some(token) = token {
32 let principal = authenticator.authenticate_bearer(&token).await?;
33 if let Ok(value) = serde_json::to_value(&principal) {
35 exchange.set_property(PRINCIPAL_KEY, value);
36 }
37 return Ok(principal);
38 }
39
40 extract_principal_from_exchange(exchange)
42}
43
44fn extract_principal_from_exchange(exchange: &Exchange) -> Result<Principal, CamelError> {
46 exchange
47 .property(PRINCIPAL_KEY)
48 .and_then(|v| serde_json::from_value::<Principal>(v.clone()).ok())
49 .ok_or_else(|| CamelError::Unauthenticated("no principal in exchange".into()))
50}
51
52pub struct RolePolicy {
59 required_roles: Vec<String>,
60 all_required: bool,
61 authenticator: Arc<dyn TokenAuthenticator>,
62}
63
64impl RolePolicy {
65 pub fn new(
66 required_roles: Vec<String>,
67 all_required: bool,
68 authenticator: Arc<dyn TokenAuthenticator>,
69 ) -> Self {
70 Self {
71 required_roles,
72 all_required,
73 authenticator,
74 }
75 }
76}
77
78#[async_trait]
79impl SecurityPolicy for RolePolicy {
80 async fn evaluate(&self, exchange: &mut Exchange) -> Result<AuthorizationDecision, CamelError> {
81 let principal = authenticate(exchange, &*self.authenticator).await?;
82
83 let missing: Vec<String> = self
84 .required_roles
85 .iter()
86 .filter(|r| !principal.has_role(r))
87 .cloned()
88 .collect();
89
90 let granted = if self.all_required {
91 missing.is_empty()
92 } else {
93 self.required_roles.is_empty() || missing.len() < self.required_roles.len()
94 };
95
96 if granted {
97 Ok(AuthorizationDecision::Granted { principal })
98 } else {
99 let actual = principal.roles.clone();
100 Ok(AuthorizationDecision::Denied {
101 reason: format!("missing required role(s): {}", missing.join(", ")), required: self.required_roles.clone(),
103 actual,
104 })
105 }
106 }
107}
108
109pub struct ScopePolicy {
116 required_scopes: Vec<String>,
117 all_required: bool,
118 authenticator: Arc<dyn TokenAuthenticator>,
119}
120
121impl ScopePolicy {
122 pub fn new(
123 required_scopes: Vec<String>,
124 all_required: bool,
125 authenticator: Arc<dyn TokenAuthenticator>,
126 ) -> Self {
127 Self {
128 required_scopes,
129 all_required,
130 authenticator,
131 }
132 }
133}
134
135#[async_trait]
136impl SecurityPolicy for ScopePolicy {
137 async fn evaluate(&self, exchange: &mut Exchange) -> Result<AuthorizationDecision, CamelError> {
138 let principal = authenticate(exchange, &*self.authenticator).await?;
139
140 let missing: Vec<String> = self
141 .required_scopes
142 .iter()
143 .filter(|s| !principal.has_scope(s))
144 .cloned()
145 .collect();
146
147 let granted = if self.all_required {
148 missing.is_empty()
149 } else {
150 self.required_scopes.is_empty() || missing.len() < self.required_scopes.len()
151 };
152
153 if granted {
154 Ok(AuthorizationDecision::Granted { principal })
155 } else {
156 let actual = principal.scopes.clone();
157 Ok(AuthorizationDecision::Denied {
158 reason: format!("missing required scope(s): {}", missing.join(", ")),
159 required: self.required_scopes.clone(),
160 actual,
161 })
162 }
163 }
164}
165
166#[cfg(test)]
167mod tests {
168 use super::*;
169 use crate::jwt::JwtValidator;
170 use crate::types::AuthError;
171 use camel_api::Message;
172
173 fn test_principal(roles: Vec<&str>, scopes: Vec<&str>) -> Principal {
174 Principal {
175 subject: "test-user".into(),
176 issuer: "test".into(),
177 audience: vec![],
178 roles: roles.iter().map(|s| s.to_string()).collect(),
179 scopes: scopes.iter().map(|s| s.to_string()).collect(),
180 claims: serde_json::Value::Null,
181 }
182 }
183
184 struct MockJwtValidator {
186 principal: Principal,
187 }
188
189 #[async_trait]
190 impl JwtValidator for MockJwtValidator {
191 async fn validate(&self, _token: &str) -> Result<Principal, AuthError> {
192 Ok(self.principal.clone())
193 }
194 }
195
196 fn mock_validator(principal: Principal) -> Arc<dyn TokenAuthenticator> {
197 Arc::new(MockJwtValidator { principal })
198 }
199
200 fn exchange_with_bearer(principal: Principal) -> Exchange {
202 let validator_principal = principal.clone();
203 let mut msg = Message::default();
204 msg.set_header(
205 "Authorization",
206 serde_json::Value::String("Bearer mock-token".into()),
207 );
208 let mut ex = Exchange::new(msg);
210 let value = serde_json::to_value(&validator_principal).unwrap();
211 ex.set_property(PRINCIPAL_KEY, value);
212 ex
213 }
214
215 fn exchange_with_principal(principal: Principal) -> Exchange {
217 let mut ex = Exchange::new(Message::default());
218 let value = serde_json::to_value(&principal).unwrap();
219 ex.set_property(PRINCIPAL_KEY, value);
220 ex
221 }
222
223 #[tokio::test]
224 async fn role_policy_grants_when_role_present() {
225 let principal = test_principal(vec!["admin"], vec![]);
226 let policy = RolePolicy::new(
227 vec!["admin".into()],
228 true,
229 mock_validator(principal.clone()),
230 );
231 let mut ex = exchange_with_bearer(principal);
232 let decision = policy.evaluate(&mut ex).await.unwrap();
233 assert!(matches!(decision, AuthorizationDecision::Granted { .. }));
234 }
235
236 #[tokio::test]
237 async fn role_policy_denies_when_role_missing() {
238 let principal = test_principal(vec!["user"], vec![]);
239 let policy = RolePolicy::new(
240 vec!["admin".into()],
241 true,
242 mock_validator(principal.clone()),
243 );
244 let mut ex = exchange_with_bearer(principal);
245 let decision = policy.evaluate(&mut ex).await.unwrap();
246 assert!(matches!(decision, AuthorizationDecision::Denied { .. }));
247 }
248
249 #[tokio::test]
250 async fn role_policy_any_required() {
251 let principal = test_principal(vec!["user"], vec![]);
252 let policy = RolePolicy::new(
253 vec!["admin".into(), "user".into()],
254 false,
255 mock_validator(principal.clone()),
256 );
257 let mut ex = exchange_with_bearer(principal);
258 let decision = policy.evaluate(&mut ex).await.unwrap();
259 assert!(matches!(decision, AuthorizationDecision::Granted { .. }));
260 }
261
262 #[tokio::test]
263 async fn scope_policy_grants() {
264 let principal = test_principal(vec![], vec!["read"]);
265 let policy = ScopePolicy::new(vec!["read".into()], true, mock_validator(principal.clone()));
266 let mut ex = exchange_with_bearer(principal);
267 let decision = policy.evaluate(&mut ex).await.unwrap();
268 assert!(matches!(decision, AuthorizationDecision::Granted { .. }));
269 }
270
271 #[tokio::test]
272 async fn unauthenticated_when_no_principal_and_no_header() {
273 struct FailValidator;
275 #[async_trait]
276 impl JwtValidator for FailValidator {
277 async fn validate(&self, _token: &str) -> Result<Principal, AuthError> {
278 panic!("should not be called")
279 }
280 }
281 let policy = RolePolicy::new(vec!["admin".into()], true, Arc::new(FailValidator));
282 let mut ex = Exchange::new(Message::default());
283 let result = policy.evaluate(&mut ex).await;
284 assert!(matches!(result, Err(CamelError::Unauthenticated(_))));
285 }
286
287 #[tokio::test]
288 async fn fallback_to_exchange_principal_when_no_bearer_header() {
289 let principal = test_principal(vec!["admin"], vec![]);
291 let policy = RolePolicy::new(
292 vec!["admin".into()],
293 true,
294 mock_validator(principal.clone()),
295 );
296 let mut ex = exchange_with_principal(principal); let decision = policy.evaluate(&mut ex).await.unwrap();
298 assert!(matches!(decision, AuthorizationDecision::Granted { .. }));
299 }
300}