use async_trait::async_trait;
use std::sync::Arc;
use camel_api::security_policy::{AuthorizationDecision, Principal, SecurityPolicy};
use camel_api::{CamelError, Exchange};
use crate::token_authenticator::TokenAuthenticator;
pub const PRINCIPAL_KEY: &str = "camel.auth.principal";
async fn authenticate(
exchange: &mut Exchange,
authenticator: &dyn TokenAuthenticator,
) -> Result<Principal, CamelError> {
let token = exchange
.input
.header_ic("authorization")
.and_then(|v| v.as_str())
.and_then(|s| s.strip_prefix("Bearer "))
.map(|s| s.to_string());
if let Some(token) = token {
let principal = authenticator.authenticate_bearer(&token).await?;
if let Ok(value) = serde_json::to_value(&principal) {
exchange.set_property(PRINCIPAL_KEY, value);
}
return Ok(principal);
}
extract_principal_from_exchange(exchange)
}
fn extract_principal_from_exchange(exchange: &Exchange) -> Result<Principal, CamelError> {
exchange
.property(PRINCIPAL_KEY)
.and_then(|v| serde_json::from_value::<Principal>(v.clone()).ok())
.ok_or_else(|| CamelError::Unauthenticated("no principal in exchange".into()))
}
pub struct RolePolicy {
required_roles: Vec<String>,
all_required: bool,
authenticator: Arc<dyn TokenAuthenticator>,
}
impl RolePolicy {
pub fn new(
required_roles: Vec<String>,
all_required: bool,
authenticator: Arc<dyn TokenAuthenticator>,
) -> Self {
Self {
required_roles,
all_required,
authenticator,
}
}
}
#[async_trait]
impl SecurityPolicy for RolePolicy {
async fn evaluate(&self, exchange: &mut Exchange) -> Result<AuthorizationDecision, CamelError> {
let principal = authenticate(exchange, &*self.authenticator).await?;
let missing: Vec<String> = self
.required_roles
.iter()
.filter(|r| !principal.has_role(r))
.cloned()
.collect();
let granted = if self.all_required {
missing.is_empty()
} else {
self.required_roles.is_empty() || missing.len() < self.required_roles.len()
};
if granted {
Ok(AuthorizationDecision::Granted { principal })
} else {
let actual = principal.roles.clone();
Ok(AuthorizationDecision::Denied {
reason: format!("missing required role(s): {}", missing.join(", ")), required: self.required_roles.clone(),
actual,
})
}
}
}
pub struct ScopePolicy {
required_scopes: Vec<String>,
all_required: bool,
authenticator: Arc<dyn TokenAuthenticator>,
}
impl ScopePolicy {
pub fn new(
required_scopes: Vec<String>,
all_required: bool,
authenticator: Arc<dyn TokenAuthenticator>,
) -> Self {
Self {
required_scopes,
all_required,
authenticator,
}
}
}
#[async_trait]
impl SecurityPolicy for ScopePolicy {
async fn evaluate(&self, exchange: &mut Exchange) -> Result<AuthorizationDecision, CamelError> {
let principal = authenticate(exchange, &*self.authenticator).await?;
let missing: Vec<String> = self
.required_scopes
.iter()
.filter(|s| !principal.has_scope(s))
.cloned()
.collect();
let granted = if self.all_required {
missing.is_empty()
} else {
self.required_scopes.is_empty() || missing.len() < self.required_scopes.len()
};
if granted {
Ok(AuthorizationDecision::Granted { principal })
} else {
let actual = principal.scopes.clone();
Ok(AuthorizationDecision::Denied {
reason: format!("missing required scope(s): {}", missing.join(", ")),
required: self.required_scopes.clone(),
actual,
})
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::jwt::JwtValidator;
use crate::types::AuthError;
use camel_api::Message;
fn test_principal(roles: Vec<&str>, scopes: Vec<&str>) -> Principal {
Principal {
subject: "test-user".into(),
issuer: "test".into(),
audience: vec![],
roles: roles.iter().map(|s| s.to_string()).collect(),
scopes: scopes.iter().map(|s| s.to_string()).collect(),
claims: serde_json::Value::Null,
}
}
struct MockJwtValidator {
principal: Principal,
}
#[async_trait]
impl JwtValidator for MockJwtValidator {
async fn validate(&self, _token: &str) -> Result<Principal, AuthError> {
Ok(self.principal.clone())
}
}
fn mock_validator(principal: Principal) -> Arc<dyn TokenAuthenticator> {
Arc::new(MockJwtValidator { principal })
}
fn exchange_with_bearer(principal: Principal) -> Exchange {
let validator_principal = principal.clone();
let mut msg = Message::default();
msg.set_header(
"Authorization",
serde_json::Value::String("Bearer mock-token".into()),
);
let mut ex = Exchange::new(msg);
let value = serde_json::to_value(&validator_principal).unwrap();
ex.set_property(PRINCIPAL_KEY, value);
ex
}
fn exchange_with_principal(principal: Principal) -> Exchange {
let mut ex = Exchange::new(Message::default());
let value = serde_json::to_value(&principal).unwrap();
ex.set_property(PRINCIPAL_KEY, value);
ex
}
#[tokio::test]
async fn role_policy_grants_when_role_present() {
let principal = test_principal(vec!["admin"], vec![]);
let policy = RolePolicy::new(
vec!["admin".into()],
true,
mock_validator(principal.clone()),
);
let mut ex = exchange_with_bearer(principal);
let decision = policy.evaluate(&mut ex).await.unwrap();
assert!(matches!(decision, AuthorizationDecision::Granted { .. }));
}
#[tokio::test]
async fn role_policy_denies_when_role_missing() {
let principal = test_principal(vec!["user"], vec![]);
let policy = RolePolicy::new(
vec!["admin".into()],
true,
mock_validator(principal.clone()),
);
let mut ex = exchange_with_bearer(principal);
let decision = policy.evaluate(&mut ex).await.unwrap();
assert!(matches!(decision, AuthorizationDecision::Denied { .. }));
}
#[tokio::test]
async fn role_policy_any_required() {
let principal = test_principal(vec!["user"], vec![]);
let policy = RolePolicy::new(
vec!["admin".into(), "user".into()],
false,
mock_validator(principal.clone()),
);
let mut ex = exchange_with_bearer(principal);
let decision = policy.evaluate(&mut ex).await.unwrap();
assert!(matches!(decision, AuthorizationDecision::Granted { .. }));
}
#[tokio::test]
async fn scope_policy_grants() {
let principal = test_principal(vec![], vec!["read"]);
let policy = ScopePolicy::new(vec!["read".into()], true, mock_validator(principal.clone()));
let mut ex = exchange_with_bearer(principal);
let decision = policy.evaluate(&mut ex).await.unwrap();
assert!(matches!(decision, AuthorizationDecision::Granted { .. }));
}
#[tokio::test]
async fn unauthenticated_when_no_principal_and_no_header() {
struct FailValidator;
#[async_trait]
impl JwtValidator for FailValidator {
async fn validate(&self, _token: &str) -> Result<Principal, AuthError> {
panic!("should not be called")
}
}
let policy = RolePolicy::new(vec!["admin".into()], true, Arc::new(FailValidator));
let mut ex = Exchange::new(Message::default());
let result = policy.evaluate(&mut ex).await;
assert!(matches!(result, Err(CamelError::Unauthenticated(_))));
}
#[tokio::test]
async fn fallback_to_exchange_principal_when_no_bearer_header() {
let principal = test_principal(vec!["admin"], vec![]);
let policy = RolePolicy::new(
vec!["admin".into()],
true,
mock_validator(principal.clone()),
);
let mut ex = exchange_with_principal(principal); let decision = policy.evaluate(&mut ex).await.unwrap();
assert!(matches!(decision, AuthorizationDecision::Granted { .. }));
}
}