1use crate::authn_cache::AuthnCache;
2use crate::permission::PermissionEvaluator;
3use crate::token_authenticator::TokenAuthenticator;
4use camel_api::security_policy::SecurityPolicy;
5use dashmap::DashMap;
6use std::sync::Arc;
7
8pub struct NamedRegistry<T: ?Sized> {
9 entries: DashMap<String, Arc<T>>,
10}
11
12impl<T: ?Sized> Default for NamedRegistry<T> {
13 fn default() -> Self {
14 Self::new()
15 }
16}
17
18impl<T: ?Sized> NamedRegistry<T> {
19 pub fn new() -> Self {
20 Self {
21 entries: DashMap::new(),
22 }
23 }
24
25 pub fn register(&self, name: impl Into<String>, entry: Arc<T>) {
26 self.entries.insert(name.into(), entry);
27 }
28
29 pub fn get(&self, name: &str) -> Option<Arc<T>> {
30 self.entries.get(name).map(|e| Arc::clone(&*e))
31 }
32
33 pub fn len(&self) -> usize {
34 self.entries.len()
35 }
36
37 pub fn entries(&self) -> Vec<(String, Arc<T>)> {
38 self.entries
39 .iter()
40 .map(|entry| (entry.key().clone(), Arc::clone(entry.value())))
41 .collect()
42 }
43
44 pub fn is_empty(&self) -> bool {
45 self.entries.is_empty()
46 }
47}
48
49pub type SecurityPolicyRegistry = NamedRegistry<dyn SecurityPolicy>;
50pub type PermissionEvaluatorRegistry = NamedRegistry<dyn PermissionEvaluator>;
51
52pub struct ProviderEntry {
61 pub authenticator: Arc<dyn TokenAuthenticator>,
62 pub audience_binding: Option<camel_api::security_policy::AudienceBinding>,
63}
64
65pub struct ProviderRegistry {
71 inner: NamedRegistry<ProviderEntry>,
72 authn_cache: Option<Arc<AuthnCache>>,
73}
74
75impl Default for ProviderRegistry {
76 fn default() -> Self {
77 Self::new()
78 }
79}
80
81impl ProviderRegistry {
82 pub fn new() -> Self {
83 Self {
84 inner: NamedRegistry::new(),
85 authn_cache: None,
86 }
87 }
88
89 pub fn with_authn_cache(mut self, cache: Arc<AuthnCache>) -> Self {
94 self.authn_cache = Some(cache);
95 self
96 }
97
98 pub fn authn_cache(&self) -> Option<&Arc<AuthnCache>> {
100 self.authn_cache.as_ref()
101 }
102
103 pub fn register(&self, name: impl Into<String>, entry: ProviderEntry) {
104 self.inner.register(name, Arc::new(entry));
105 }
106
107 pub fn resolve(&self, name: &str) -> Option<Arc<ProviderEntry>> {
108 self.inner.get(name)
109 }
110
111 pub fn len(&self) -> usize {
112 self.inner.len()
113 }
114
115 pub fn is_empty(&self) -> bool {
116 self.inner.is_empty()
117 }
118
119 pub fn names(&self) -> Vec<String> {
120 self.inner
121 .entries()
122 .into_iter()
123 .map(|(name, _)| name)
124 .collect()
125 }
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131 use async_trait::async_trait;
132 use camel_api::security_policy::{
133 AuthContext, AuthPrincipal, AuthorizationDecision, Principal, TransportId,
134 };
135 use camel_api::{CamelError, Exchange, Message};
136
137 struct AllowPolicy;
138 struct DenyPolicy;
139
140 #[async_trait]
141 impl SecurityPolicy for AllowPolicy {
142 async fn evaluate(
143 &self,
144 _exchange: &mut Exchange,
145 _auth: &AuthContext<'_>,
146 ) -> Result<AuthorizationDecision, CamelError> {
147 Ok(AuthorizationDecision::Granted {
148 principal: Principal {
149 subject: "allow-user".into(),
150 issuer: "test".into(),
151 audience: vec![],
152 scopes: vec![],
153 roles: vec![],
154 claims: serde_json::Value::Null,
155 },
156 })
157 }
158 }
159
160 #[async_trait]
161 impl SecurityPolicy for DenyPolicy {
162 async fn evaluate(
163 &self,
164 _exchange: &mut Exchange,
165 _auth: &AuthContext<'_>,
166 ) -> Result<AuthorizationDecision, CamelError> {
167 Ok(AuthorizationDecision::Denied {
168 reason: "deny".into(),
169 required: vec![],
170 actual: vec![],
171 })
172 }
173 }
174
175 struct TestPrincipal(Principal);
176
177 impl AuthPrincipal for TestPrincipal {
178 fn principal(&self) -> &Principal {
179 &self.0
180 }
181 fn provider_id(&self) -> &str {
182 "test"
183 }
184 }
185
186 fn auth_ctx<'a>(principal: &'a TestPrincipal) -> AuthContext<'a> {
187 AuthContext {
188 principal,
189 transport: TransportId::Http,
190 }
191 }
192
193 fn test_principal() -> TestPrincipal {
194 TestPrincipal(Principal {
195 subject: "allow-user".into(),
196 issuer: "test".into(),
197 audience: vec![],
198 scopes: vec![],
199 roles: vec![],
200 claims: serde_json::Value::Null,
201 })
202 }
203
204 #[test]
205 fn register_and_get() {
206 let registry = SecurityPolicyRegistry::new();
207 registry.register("admin-policy", Arc::new(AllowPolicy));
208 let policy = registry.get("admin-policy");
209 assert!(policy.is_some());
210 }
211
212 #[test]
213 fn get_missing_returns_none() {
214 let registry = SecurityPolicyRegistry::new();
215 assert!(registry.get("nonexistent").is_none());
216 }
217
218 #[tokio::test]
219 async fn register_replaces_existing() {
220 let registry = SecurityPolicyRegistry::new();
221 registry.register("my-policy", Arc::new(AllowPolicy));
222 registry.register("my-policy", Arc::new(DenyPolicy));
223 let policy = registry.get("my-policy").unwrap();
224 let mut ex = Exchange::new(Message::default());
225 let principal = test_principal();
226 let auth = auth_ctx(&principal);
227 let decision = policy.evaluate(&mut ex, &auth).await.unwrap();
229 assert!(matches!(decision, AuthorizationDecision::Denied { .. }));
230 }
231
232 use crate::permission::{PermissionDecision, PermissionRequest};
235
236 struct GrantEvaluator;
237
238 #[async_trait]
239 impl PermissionEvaluator for GrantEvaluator {
240 async fn evaluate(
241 &self,
242 _request: PermissionRequest,
243 ) -> Result<PermissionDecision, crate::types::AuthError> {
244 Ok(PermissionDecision::Granted)
245 }
246 }
247
248 struct DenyEvaluator {
249 reason: String,
250 }
251
252 #[async_trait]
253 impl PermissionEvaluator for DenyEvaluator {
254 async fn evaluate(
255 &self,
256 _request: PermissionRequest,
257 ) -> Result<PermissionDecision, crate::types::AuthError> {
258 Ok(PermissionDecision::Denied {
259 reason: self.reason.clone(),
260 })
261 }
262 }
263
264 #[test]
265 fn evaluator_register_and_get() {
266 let registry = PermissionEvaluatorRegistry::new();
267 registry.register("keycloak-uma", Arc::new(GrantEvaluator));
268 let evaluator = registry.get("keycloak-uma");
269 assert!(evaluator.is_some());
270 }
271
272 #[test]
273 fn evaluator_get_missing_returns_none() {
274 let registry = PermissionEvaluatorRegistry::new();
275 assert!(registry.get("nonexistent").is_none());
276 }
277
278 #[test]
279 fn entries_returns_registered_items() {
280 let registry = SecurityPolicyRegistry::new();
281 registry.register("admin-policy", Arc::new(AllowPolicy));
282 let entries = registry.entries();
283 assert_eq!(entries.len(), 1);
284 assert_eq!(entries[0].0, "admin-policy");
285 }
286
287 #[test]
288 fn is_empty_returns_true_when_no_entries() {
289 let registry = SecurityPolicyRegistry::new();
290 assert!(registry.is_empty());
291 }
292
293 #[test]
294 fn is_empty_returns_false_when_entries_exist() {
295 let registry = SecurityPolicyRegistry::new();
296 registry.register("p1", Arc::new(AllowPolicy));
297 assert!(!registry.is_empty());
298 }
299
300 #[tokio::test]
301 async fn evaluator_register_replaces_existing() {
302 let registry = PermissionEvaluatorRegistry::new();
303 registry.register("my-evaluator", Arc::new(GrantEvaluator));
304 registry.register(
305 "my-evaluator",
306 Arc::new(DenyEvaluator {
307 reason: "replaced".into(),
308 }),
309 );
310 let evaluator = registry.get("my-evaluator").unwrap();
311 let request = PermissionRequest {
312 principal: Principal {
313 subject: "test".into(),
314 issuer: "test".into(),
315 audience: vec![],
316 scopes: vec![],
317 roles: vec![],
318 claims: serde_json::Value::Null,
319 },
320 resource: "/test".into(),
321 action: "read".into(),
322 requested_scopes: vec![],
323 context: serde_json::Value::Null,
324 };
325 let decision = evaluator.evaluate(request).await.unwrap();
326 assert!(
327 matches!(decision, PermissionDecision::Denied { .. }),
328 "expected Denied, got {decision:?}"
329 );
330 }
331
332 struct StaticAuth;
335
336 #[async_trait]
337 impl TokenAuthenticator for StaticAuth {
338 async fn authenticate_bearer(&self, _token: &str) -> Result<Principal, CamelError> {
339 Ok(Principal {
340 subject: "static-user".into(),
341 issuer: "test".into(),
342 audience: vec![],
343 scopes: vec![],
344 roles: vec![],
345 claims: serde_json::Value::Null,
346 })
347 }
348 }
349
350 fn provider_entry() -> ProviderEntry {
351 ProviderEntry {
352 authenticator: Arc::new(StaticAuth),
353 audience_binding: None,
354 }
355 }
356
357 #[test]
358 fn provider_registry_registers_and_resolves() {
359 let registry = ProviderRegistry::new();
360 registry.register("idp-a", provider_entry());
361 assert!(registry.resolve("idp-a").is_some());
362 assert!(registry.resolve("ghost").is_none());
363 }
364
365 #[test]
366 fn sole_and_multiple_provider_counts() {
367 let registry = ProviderRegistry::new();
368 registry.register("idp-a", provider_entry());
369 assert_eq!(registry.len(), 1);
370 registry.register("idp-b", provider_entry());
371 assert_eq!(registry.len(), 2);
372 }
373}