1use std::sync::Arc;
12
13use camel_api::security_policy::{AccessMode, AuthPrincipal, Principal, RouteSecurityPlan};
14use camel_api::{CamelError, Exchange};
15
16use crate::authn_cache::AuthnCacheKey;
17use crate::credential_source::ExtractedToken;
18use crate::registry::ProviderRegistry;
19use crate::token_authenticator::AuthnRequest;
20
21#[derive(Clone)]
37pub struct AuthenticatedPrincipal {
38 principal: Principal,
39 provider_id: String,
40}
41
42impl AuthenticatedPrincipal {
43 fn mint(principal: Principal, provider_id: String) -> Self {
46 Self {
47 principal,
48 provider_id,
49 }
50 }
51}
52
53impl AuthPrincipal for AuthenticatedPrincipal {
54 fn principal(&self) -> &Principal {
55 &self.principal
56 }
57
58 fn provider_id(&self) -> &str {
59 &self.provider_id
60 }
61}
62
63pub const KERNEL_PRINCIPAL_KEY: &str = "camel.auth.principal.typed";
71
72pub async fn kernel_authenticate(
78 plan: &RouteSecurityPlan,
79 providers: &ProviderRegistry,
80 credentials: &ExtractedToken,
81) -> Result<AuthenticatedPrincipal, CamelError> {
82 let provider_ref = plan.provider_ref.as_deref().ok_or_else(|| {
83 CamelError::Unauthenticated("route has no provider_ref; cannot authenticate".to_string())
84 })?;
85
86 let entry = providers.resolve(provider_ref).ok_or_else(|| {
87 CamelError::Unauthenticated(format!("unknown auth provider: {provider_ref}"))
88 })?;
89
90 let binding = plan
94 .audience_binding
95 .as_ref()
96 .or(entry.audience_binding.as_ref());
97 let audiences: &[String] = binding.map(|b| b.audiences.as_slice()).unwrap_or(&[]);
98 let issuers: &[String] = binding.map(|b| b.issuers.as_slice()).unwrap_or(&[]);
99
100 if let Some(cache) = providers.authn_cache() {
103 let key = AuthnCacheKey::new(
104 provider_ref,
105 audiences,
106 issuers,
107 plan.transport,
108 &credentials.token,
109 );
110 if let Some(principal) = cache.get(&key) {
111 tracing::debug!(target: "camel_auth::authn_cache", cache_outcome = "hit");
112 return Ok(principal);
113 }
114 }
115
116 let req = AuthnRequest {
117 token: &credentials.token,
118 audiences,
119 accepted_issuers: issuers,
120 transport: plan.transport,
121 };
122
123 let principal = entry.authenticator.authenticate(req).await?;
124
125 let minted = AuthenticatedPrincipal::mint(principal, provider_ref.to_string());
126
127 if let Some(cache) = providers.authn_cache() {
130 let key = AuthnCacheKey::new(
131 provider_ref,
132 audiences,
133 issuers,
134 plan.transport,
135 &credentials.token,
136 );
137 cache.insert(key, minted.clone());
138 }
139
140 Ok(minted)
141}
142
143pub fn enforce_dispatch(plan: &RouteSecurityPlan, exchange: &Exchange) -> Result<(), CamelError> {
152 if matches!(&plan.access_mode, AccessMode::Public) {
153 return Ok(());
154 }
155
156 let carrier = read_carrier(exchange).ok_or_else(|| {
157 CamelError::Unauthenticated("no authenticated principal present".to_string())
158 })?;
159
160 if plan.provider_ref.as_deref() == Some(carrier.provider_id()) {
161 Ok(())
162 } else {
163 Err(CamelError::Unauthenticated(format!(
164 "principal from provider {:?} does not satisfy route provider {:?}",
165 carrier.provider_id(),
166 plan.provider_ref
167 )))
168 }
169}
170
171pub fn install_carrier(exchange: &mut Exchange, principal: &AuthenticatedPrincipal) {
176 exchange.set_extension(KERNEL_PRINCIPAL_KEY, Arc::new(principal.clone()));
177}
178
179pub fn read_carrier(exchange: &Exchange) -> Option<AuthenticatedPrincipal> {
184 exchange
185 .get_extension::<AuthenticatedPrincipal>(KERNEL_PRINCIPAL_KEY)
186 .cloned()
187}
188
189#[cfg(test)]
190mod tests {
191 use super::*;
192 use crate::credential_source::CredentialSource;
193 use crate::native_auth::{NativeCredential, NativeCredentialSecret, StaticTokenAuthenticator};
194 use crate::registry::ProviderEntry;
195 use camel_api::Message;
196 use camel_api::security_policy::TransportId;
197 use zeroize::Zeroizing;
198
199 fn test_principal() -> Principal {
200 Principal {
201 subject: "svc-user".into(),
202 issuer: "test".into(),
203 audience: vec![],
204 scopes: vec![],
205 roles: vec![],
206 claims: serde_json::Value::Null,
207 }
208 }
209
210 fn static_provider(id: &str, token: &str) -> ProviderRegistry {
212 let store = crate::native_auth::NativeCredentialStore::try_new(vec![NativeCredential {
213 secret: NativeCredentialSecret::Plaintext {
214 value: Zeroizing::new(token.to_string()),
215 },
216 principal: test_principal(),
217 }])
218 .unwrap();
219 let registry = ProviderRegistry::new();
220 registry.register(
221 id,
222 ProviderEntry {
223 authenticator: Arc::new(StaticTokenAuthenticator::new(store)),
224 audience_binding: None,
225 },
226 );
227 registry
228 }
229
230 fn authenticated_plan(provider_ref: &str) -> RouteSecurityPlan {
231 RouteSecurityPlan {
232 access_mode: AccessMode::Authenticated,
233 provider_ref: Some(provider_ref.to_string()),
234 transport: TransportId::Http,
235 credential_sources: vec![CredentialSource::AuthorizationHeader],
236 audience_binding: None,
237 }
238 }
239
240 fn public_plan() -> RouteSecurityPlan {
241 RouteSecurityPlan {
242 access_mode: AccessMode::Public,
243 provider_ref: None,
244 transport: TransportId::Http,
245 credential_sources: vec![],
246 audience_binding: None,
247 }
248 }
249
250 fn credentials(token: &str) -> ExtractedToken {
251 ExtractedToken {
252 token: token.to_string(),
253 source: CredentialSource::AuthorizationHeader,
254 }
255 }
256
257 fn empty_exchange() -> Exchange {
258 Exchange::new(Message::default())
259 }
260
261 #[tokio::test]
262 async fn kernel_authenticate_mints_with_provider() {
263 let providers = static_provider("idp-a", "t-a");
264 let plan = authenticated_plan("idp-a");
265 let principal = kernel_authenticate(&plan, &providers, &credentials("t-a"))
266 .await
267 .unwrap();
268 assert_eq!(principal.provider_id(), "idp-a");
269 assert_eq!(principal.principal().subject, "svc-user");
270 }
271
272 #[tokio::test]
273 async fn kernel_authenticate_denies_wrong_token() {
274 let providers = static_provider("idp-a", "t-a");
275 let plan = authenticated_plan("idp-a");
276 let result = kernel_authenticate(&plan, &providers, &credentials("wrong")).await;
277 assert!(matches!(result, Err(CamelError::Unauthenticated(_))));
278 }
279
280 #[tokio::test]
281 async fn kernel_authenticate_names_unresolved_provider() {
282 let providers = static_provider("idp-a", "t-a");
283 let plan = authenticated_plan("idp-ghost");
284 match kernel_authenticate(&plan, &providers, &credentials("t-a")).await {
285 Ok(_) => panic!("expected Unauthenticated"),
286 Err(CamelError::Unauthenticated(msg)) => assert!(msg.contains("idp-ghost")),
287 Err(other) => panic!("expected Unauthenticated, got: {other}"),
288 }
289 }
290
291 #[test]
292 fn enforce_dispatch_public_passes_without_carrier() {
293 let plan = public_plan();
294 let exchange = empty_exchange();
295 assert!(enforce_dispatch(&plan, &exchange).is_ok());
296 }
297
298 #[tokio::test]
299 async fn enforce_dispatch_nonpublic_requires_carrier() {
300 let providers = static_provider("idp-a", "t-a");
301 let plan = authenticated_plan("idp-a");
302 let mut exchange = empty_exchange();
303
304 assert!(matches!(
306 enforce_dispatch(&plan, &exchange),
307 Err(CamelError::Unauthenticated(_))
308 ));
309
310 let principal = kernel_authenticate(&plan, &providers, &credentials("t-a"))
311 .await
312 .unwrap();
313 install_carrier(&mut exchange, &principal);
314
315 assert!(enforce_dispatch(&plan, &exchange).is_ok());
316 }
317
318 #[tokio::test]
319 async fn enforce_dispatch_rejects_cross_provider_carrier() {
320 let providers = static_provider("idp-a", "t-a");
321 let mint_plan = authenticated_plan("idp-a");
322 let principal = kernel_authenticate(&mint_plan, &providers, &credentials("t-a"))
323 .await
324 .unwrap();
325
326 let mut exchange = empty_exchange();
327 install_carrier(&mut exchange, &principal);
328
329 let target_plan = authenticated_plan("idp-b");
330 assert!(matches!(
331 enforce_dispatch(&target_plan, &exchange),
332 Err(CamelError::Unauthenticated(_))
333 ));
334 }
335
336 #[test]
337 fn read_carrier_returns_none_when_absent() {
338 let exchange = empty_exchange();
339 assert!(read_carrier(&exchange).is_none());
340 }
341
342 #[tokio::test]
343 async fn enforce_dispatch_fails_closed_when_provider_ref_none() {
344 let providers = static_provider("idp-a", "t-a");
347 let mint_plan = authenticated_plan("idp-a");
348 let principal = kernel_authenticate(&mint_plan, &providers, &credentials("t-a"))
349 .await
350 .unwrap();
351
352 let mut exchange = empty_exchange();
353 install_carrier(&mut exchange, &principal);
354
355 let mut unwired_plan = authenticated_plan("idp-a");
356 unwired_plan.provider_ref = None;
357 assert!(matches!(
358 enforce_dispatch(&unwired_plan, &exchange),
359 Err(CamelError::Unauthenticated(_))
360 ));
361 }
362
363 #[test]
364 fn wrong_type_value_under_carrier_key_does_not_authorize() {
365 let mut exchange = empty_exchange();
368 exchange.set_extension(
369 KERNEL_PRINCIPAL_KEY,
370 std::sync::Arc::new("forged".to_string()),
371 );
372
373 assert!(read_carrier(&exchange).is_none());
374 let plan = authenticated_plan("idp-a");
375 assert!(matches!(
376 enforce_dispatch(&plan, &exchange),
377 Err(CamelError::Unauthenticated(_))
378 ));
379 }
380}