Skip to main content

camel_auth/
built_in.rs

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
9/// Property key used to store the authenticated principal in the exchange.
10pub const PRINCIPAL_KEY: &str = "camel.auth.principal";
11
12/// Extracts and validates a Bearer token from the `Authorization` header.
13///
14/// If the header is present, validates it via the supplied [`TokenAuthenticator`] and stores
15/// the resulting [`Principal`] in `PRINCIPAL_KEY` for downstream processors.
16///
17/// If no `Authorization` header is present, falls back to an already-populated
18/// principal in the exchange (e.g. set by an upstream authentication filter).
19async fn authenticate(
20    exchange: &mut Exchange,
21    authenticator: &dyn TokenAuthenticator,
22) -> Result<Principal, CamelError> {
23    // Clone the token string so the borrow on exchange.input ends before the mut borrow for set_property.
24    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        // Store for downstream processors
34        if let Ok(value) = serde_json::to_value(&principal) {
35            exchange.set_property(PRINCIPAL_KEY, value);
36        }
37        return Ok(principal);
38    }
39
40    // Fall back: principal already populated by an upstream auth filter
41    extract_principal_from_exchange(exchange)
42}
43
44/// Extract a `Principal` from exchange properties, returning `Unauthenticated` if absent.
45fn 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
52/// Role-based access control policy.
53///
54/// Validates the incoming request via a token authenticator (Bearer token) and evaluates whether
55/// the principal holds the required roles.
56/// When `all_required` is true, every listed role must be present.
57/// When `all_required` is false, at least one listed role must be present.
58pub 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(", ")), // allow-secret
102                required: self.required_roles.clone(),
103                actual,
104            })
105        }
106    }
107}
108
109/// Scope-based access control policy.
110///
111/// Validates the incoming request via a token authenticator (Bearer token) and evaluates whether
112/// the principal holds the required scopes.
113/// When `all_required` is true, every listed scope must be present.
114/// When `all_required` is false, at least one listed scope must be present.
115pub 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    /// Mock validator that returns a fixed principal regardless of token content.
185    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    /// Build an exchange with a Bearer token in the Authorization header.
201    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        // Also embed principal in exchange so fallback path is testable if needed
209        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    /// Build an exchange with the principal in the exchange property (no Bearer header).
216    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        // No Bearer header, no exchange property — validator never called
274        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        // No Bearer header, but principal pre-populated (upstream filter scenario)
290        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); // no Authorization header
297        let decision = policy.evaluate(&mut ex).await.unwrap();
298        assert!(matches!(decision, AuthorizationDecision::Granted { .. }));
299    }
300}