1use std::sync::Arc;
3
4#[cfg(feature = "partial-eval")]
5use cedar_policy::{Diagnostics, PartialResponse};
6use cedar_policy::{Entities, Request, Response};
7use derive_builder::Builder;
8use thiserror::Error;
9use tokio::join;
10use tracing::{debug, error, event, info, instrument};
11use tracing_core::Level;
12use uuid::Uuid;
13
14use crate::public::log::schema::OpenCyberSecurityFramework;
15use crate::public::{log, EntityProviderError, SimpleEntityProvider};
16use crate::public::{PolicySetProviderError, SimplePolicySetProvider};
17
18#[derive(Default, Builder, Debug)]
21#[builder(pattern = "owned")]
22pub struct AuthorizerConfig<P, E> {
23 pub policy_set_provider: Arc<P>,
25 pub entity_provider: Arc<E>,
27 #[builder(setter(into, strip_option), default)]
29 pub log_config: Option<log::Config>,
30}
31
32#[derive(Error, Debug)]
34pub enum AuthorizerError {
35 #[error("The Policy Set Provider failed to get a policy set: {0}")]
37 PolicySetProviderError(#[source] PolicySetProviderError),
38 #[error("The Entity provider failed to get the entities: {0}")]
40 EntityProviderError(#[source] EntityProviderError),
41 #[error("General error that can occur within the Authorizer: {0}")]
43 General(#[source] Box<dyn std::error::Error + Send + Sync + 'static>),
44}
45
46impl From<PolicySetProviderError> for AuthorizerError {
47 fn from(value: PolicySetProviderError) -> Self {
48 Self::PolicySetProviderError(value)
49 }
50}
51
52impl From<EntityProviderError> for AuthorizerError {
53 fn from(value: EntityProviderError) -> Self {
54 Self::EntityProviderError(value)
55 }
56}
57
58#[derive(Debug)]
60pub struct Authorizer<P, E>
61where
62 P: SimplePolicySetProvider + 'static,
63 E: SimpleEntityProvider + 'static,
64{
65 policy_set_provider: Arc<P>,
67 entity_provider: Arc<E>,
69 log_config: log::Config,
71}
72
73impl<P, E> Authorizer<P, E>
74where
75 P: SimplePolicySetProvider,
76 E: SimpleEntityProvider,
77{
78 #[instrument(skip(configuration))]
100 pub fn new(configuration: AuthorizerConfig<P, E>) -> Self {
101 let log_config = configuration.log_config.unwrap_or_default();
102 let entity_provider = configuration.entity_provider;
103 info!("Initialized Entity Provider");
104 let policy_set_provider = configuration.policy_set_provider;
105 info!("Initialized Policy Set Provider");
106 info!(
107 "Initialize Simple Authorizer: authorizer_id= {:?}",
108 log_config.requester
109 );
110 Self {
111 policy_set_provider,
112 entity_provider,
113 log_config,
114 }
115 }
116
117 #[instrument(fields(request_id = %Uuid::new_v4(), authorizer_id = %self.log_config.requester), skip_all, err(Debug))]
123 pub async fn is_authorized(
124 &self,
125 request: &Request,
126 entities: &Entities,
127 ) -> Result<Response, AuthorizerError> {
128 info!("Received request, running is_authorized...");
129
130 let entities_future = self.entity_provider.get_entities(request);
131 let policy_set_future = self.policy_set_provider.get_policy_set(request);
132 let (fetched_entities, policy_set) = join!(entities_future, policy_set_future);
133 let merged_entities = Entities::from_entities(
134 fetched_entities?
135 .as_ref()
136 .iter()
137 .chain(entities.iter())
138 .cloned(),
139 None,
140 )
141 .map_err(|e| AuthorizerError::General(Box::new(e)))?;
142
143 let response = cedar_policy::Authorizer::new().is_authorized(
144 request,
145 policy_set?.as_ref(),
146 &merged_entities,
147 );
148 info!("Fetched Authorization data from Policy Set Provider and Entity Provider");
149
150 info!("Generated OCSF log record.");
151 self.log(request, &response, entities);
152
153 info!(
154 "Is_authorized completed: response_decision={:?}",
155 response.decision()
156 );
157 debug!(
158 "This decision was reached because: response_diagnostics={:?}",
159 response.diagnostics()
160 );
161
162 Ok(response)
163 }
164
165 #[instrument(skip_all)]
166 fn log(&self, request: &Request, response: &Response, entities: &Entities) {
167 event!(target: "cedar::simple::authorizer", Level::INFO, "{}",
168 serde_json::to_string(
169 &OpenCyberSecurityFramework::create(
170 request,
171 response,
172 entities,
173 &self.log_config.field_set,
174 self.log_config.requester.as_str(),
175 )
176 .unwrap_or_else(|e| {
177 OpenCyberSecurityFramework::error(e.to_string(), self.log_config.requester.clone())
178 })
179 ).unwrap_or_else(|_| "Failed to deserialize a known Open Cyber Security Framework string.".to_string()),
180 );
181 }
182
183 #[cfg(feature = "partial-eval")]
189 #[instrument(fields(request_id = %Uuid::new_v4(), authorizer_id = %self.log_config.requester), skip_all, err(Debug))]
190 pub async fn is_authorized_partial(
191 &self,
192 request: &Request,
193 entities: &Entities,
194 ) -> Result<PartialResponse, AuthorizerError> {
195 info!("Received request, running is_authorized_partial...");
196
197 let entities_future = self.entity_provider.get_entities(request);
198 let policy_set_future = self.policy_set_provider.get_policy_set(request);
199 let (fetched_entities, policy_set) = join!(entities_future, policy_set_future);
200 let merged_entities = Entities::from_entities(
201 fetched_entities?
202 .as_ref()
203 .iter()
204 .chain(entities.iter())
205 .cloned(),
206 None,
207 )
208 .map_err(|e| AuthorizerError::General(Box::new(e)))?;
209
210 let partial_response = cedar_policy::Authorizer::new().is_authorized_partial(
211 request,
212 policy_set?.as_ref(),
213 &merged_entities.partial(),
214 );
215 info!("Fetched Authorization data from Policy Set Provider and Entity Provider");
216
217 let concrete_response = partial_response.clone().concretize();
218
219 info!("Generated OCSF log record.");
221
222 match partial_response.decision() {
223 Some(_) => self.log(request, &concrete_response, entities),
224 None => self.log_residual(
225 request,
226 concrete_response.diagnostics(),
227 &partial_response,
228 entities,
229 ),
230 }
231
232 info!(
233 "Is_authorized_partial completed: response_decision={:?}",
234 partial_response.decision()
235 );
236 debug!(
237 "This decision was reached because: response_diagnostics={:?}",
238 partial_response.clone().concretize().diagnostics()
239 );
240
241 Ok(partial_response)
242 }
243
244 #[cfg(feature = "partial-eval")]
245 #[instrument(skip_all)]
246 fn log_residual(
247 &self,
248 request: &Request,
249 diagnostics: &Diagnostics,
250 policies: &PartialResponse,
251 entities: &Entities,
252 ) {
253 event!(target: "cedar::simple::authorizer", Level::INFO, "{}",
254 serde_json::to_string(
255 &OpenCyberSecurityFramework::create_generic(
256 request,
257 diagnostics,
258 policies.all_residuals()
259 .map(|policy| format!("{}", policy.id()))
260 .collect::<Vec<String>>()
261 .join(", ")
262 .as_str(),
263
264 String::from("Residuals"),
265 entities,
266 &self.log_config.field_set,
267 self.log_config.requester.as_str()
268 ).unwrap_or_else(|e| {
269 OpenCyberSecurityFramework::error(e.to_string(), self.log_config.requester.clone())
270 })
271 ).unwrap_or_else(|_| "Failed to deserialize a known Open Cyber Security Framework string.".to_string()),
272 );
273 }
274}
275
276#[cfg(test)]
277mod test {
278 use std::fmt::Error;
279 use std::sync::Arc;
280
281 use async_trait::async_trait;
282 use cedar_policy::{Context, Decision, Entities, PolicySet, Request};
283
284 use crate::public::log::DEFAULT_REQUESTER_NAME;
285 use crate::public::simple::{Authorizer, AuthorizerConfigBuilder};
286 use crate::public::{
287 EntityProviderError, PolicySetProviderError, SimpleEntityProvider, SimplePolicySetProvider,
288 };
289
290 #[derive(Debug, Default)]
291 pub struct MockPolicySetProvider;
292
293 #[async_trait]
294 impl SimplePolicySetProvider for MockPolicySetProvider {
295 async fn get_policy_set(
296 &self,
297 _: &Request,
298 ) -> Result<Arc<PolicySet>, PolicySetProviderError> {
299 Ok(Arc::new(PolicySet::new()))
300 }
301 }
302
303 #[derive(Debug, Default)]
304 pub struct MockEntityProvider;
305
306 #[async_trait]
307 impl SimpleEntityProvider for MockEntityProvider {
308 async fn get_entities(&self, _: &Request) -> Result<Arc<Entities>, EntityProviderError> {
309 Ok(Arc::new(Entities::empty()))
310 }
311 }
312
313 #[tokio::test]
314 async fn simple_authorizer_is_ok() {
315 let authorizer: Authorizer<MockPolicySetProvider, MockEntityProvider> = Authorizer::new(
316 AuthorizerConfigBuilder::default()
317 .policy_set_provider(Arc::new(MockPolicySetProvider))
318 .entity_provider(Arc::new(MockEntityProvider))
319 .build()
320 .unwrap(),
321 );
322
323 let result = authorizer
324 .is_authorized(
325 &Request::new(
326 r#"User::"Mike""#.parse().unwrap(),
327 r#"Action::"View""#.parse().unwrap(),
328 r#"Box::"10""#.parse().unwrap(),
329 Context::empty(),
330 None,
331 )
332 .unwrap(),
333 &Entities::empty(),
334 )
335 .await;
336
337 assert!(result.is_ok());
338 assert_eq!(result.unwrap().decision(), Decision::Deny);
339 assert_eq!(authorizer.log_config.requester, DEFAULT_REQUESTER_NAME);
340 assert!(!authorizer.log_config.field_set.principal);
341 }
342
343 #[derive(Debug, Default)]
344 pub struct MockEntityProviderError;
345
346 #[async_trait]
347 impl SimpleEntityProvider for MockEntityProviderError {
348 async fn get_entities(&self, _: &Request) -> Result<Arc<Entities>, EntityProviderError> {
349 Err(EntityProviderError::General(Box::<Error>::default()))
350 }
351 }
352
353 #[tokio::test]
354 async fn simple_authorizer_bad_entity_provider() {
355 let authorizer: Authorizer<MockPolicySetProvider, MockEntityProviderError> =
356 Authorizer::new(
357 AuthorizerConfigBuilder::default()
358 .policy_set_provider(Arc::new(MockPolicySetProvider))
359 .entity_provider(Arc::new(MockEntityProviderError))
360 .build()
361 .unwrap(),
362 );
363
364 let result = authorizer
365 .is_authorized(
366 &Request::new(
367 r#"User::"Mike""#.parse().unwrap(),
368 r#"Action::"View""#.parse().unwrap(),
369 r#"Box::"2""#.parse().unwrap(),
370 Context::empty(),
371 None,
372 )
373 .unwrap(),
374 &Entities::empty(),
375 )
376 .await;
377
378 assert!(result.is_err());
379 }
380
381 #[derive(Debug, Default)]
382 pub struct MockPolicySetProviderError;
383
384 #[async_trait]
385 impl SimplePolicySetProvider for MockPolicySetProviderError {
386 async fn get_policy_set(
387 &self,
388 _: &Request,
389 ) -> Result<Arc<PolicySet>, PolicySetProviderError> {
390 Err(PolicySetProviderError::General(Box::<Error>::default()))
391 }
392 }
393
394 #[tokio::test]
395 async fn simple_authorizer_bad_policy_set_provider() {
396 let authorizer: Authorizer<MockPolicySetProviderError, MockEntityProvider> =
397 Authorizer::new(
398 AuthorizerConfigBuilder::default()
399 .policy_set_provider(Arc::new(MockPolicySetProviderError))
400 .entity_provider(Arc::new(MockEntityProvider))
401 .build()
402 .unwrap(),
403 );
404
405 let result = authorizer
406 .is_authorized(
407 &Request::new(
408 r#"User::"Mike""#.parse().unwrap(),
409 r#"Action::"View""#.parse().unwrap(),
410 r#"Box::"3""#.parse().unwrap(),
411 Context::empty(),
412 None,
413 )
414 .unwrap(),
415 &Entities::empty(),
416 )
417 .await;
418
419 assert!(result.is_err());
420 }
421}
422
423#[cfg(test)]
424#[cfg(feature = "partial-eval")]
425mod test_partial {
426 use std::sync::Arc;
427
428 use async_trait::async_trait;
429 use cedar_policy::{Context, Decision, Entities, Policy, PolicyId, PolicySet, Request};
430 use cool_asserts::assert_matches;
431
432 use crate::public::log::DEFAULT_REQUESTER_NAME;
433 use crate::public::simple::test::{MockEntityProvider, MockPolicySetProvider};
434 use crate::public::simple::{Authorizer, AuthorizerConfigBuilder};
435 use crate::public::{PolicySetProviderError, SimplePolicySetProvider};
436
437 #[tokio::test]
438 async fn simple_authorizer_partial_no_resource_is_ok() {
439 let authorizer: Authorizer<MockPolicySetProvider, MockEntityProvider> = Authorizer::new(
440 AuthorizerConfigBuilder::default()
441 .policy_set_provider(Arc::new(MockPolicySetProvider))
442 .entity_provider(Arc::new(MockEntityProvider))
443 .build()
444 .unwrap(),
445 );
446
447 let result = authorizer
448 .is_authorized_partial(
449 &Request::builder()
450 .principal(r#"User::"Mike""#.parse().unwrap())
451 .action(r#"Action::"View""#.parse().unwrap())
452 .context(Context::empty())
453 .build(),
454 &Entities::empty(),
455 )
456 .await;
457
458 assert_matches!(result, Ok(partial_response) =>
459 assert_eq!(partial_response.decision(), Some(Decision::Deny))
460 );
461 assert_eq!(authorizer.log_config.requester, DEFAULT_REQUESTER_NAME);
462 assert!(!authorizer.log_config.field_set.principal);
463 }
464
465 #[tokio::test]
466 async fn simple_authorizer_partial_no_principal_is_ok() {
467 let authorizer: Authorizer<MockPolicySetProvider, MockEntityProvider> = Authorizer::new(
468 AuthorizerConfigBuilder::default()
469 .policy_set_provider(Arc::new(MockPolicySetProvider))
470 .entity_provider(Arc::new(MockEntityProvider))
471 .build()
472 .unwrap(),
473 );
474
475 let result = authorizer
476 .is_authorized_partial(
477 &Request::builder()
478 .action(r#"Action::"View""#.parse().unwrap())
479 .resource(r#"Box::"10""#.parse().unwrap())
480 .context(Context::empty())
481 .build(),
482 &Entities::empty(),
483 )
484 .await;
485
486 assert_matches!(result, Ok(partial_response) =>
487 assert_eq!(partial_response.decision(), Some(Decision::Deny))
488 );
489 assert_eq!(authorizer.log_config.requester, DEFAULT_REQUESTER_NAME);
490 assert!(!authorizer.log_config.field_set.principal);
491 }
492
493 #[derive(Debug, Default)]
494 pub struct MockPolicySetProviderPartial;
495
496 #[async_trait]
497 impl SimplePolicySetProvider for MockPolicySetProviderPartial {
498 async fn get_policy_set(
499 &self,
500 _: &Request,
501 ) -> Result<Arc<PolicySet>, PolicySetProviderError> {
502 let policy = Policy::parse(
503 Some(PolicyId::new("test")),
504 r#"permit(principal == User::"Mike", action, resource == Box::"10");"#,
505 )
506 .expect("Failed to parse");
507 let mut policy_set = PolicySet::new();
508 policy_set.add(policy).expect("Failed to add");
509 Ok(Arc::new(policy_set))
510 }
511 }
512
513 #[tokio::test]
514 async fn simple_authorizer_partial_result() {
515 let authorizer: Authorizer<MockPolicySetProviderPartial, MockEntityProvider> =
516 Authorizer::new(
517 AuthorizerConfigBuilder::default()
518 .policy_set_provider(Arc::new(MockPolicySetProviderPartial))
519 .entity_provider(Arc::new(MockEntityProvider))
520 .build()
521 .unwrap(),
522 );
523
524 let result = authorizer
525 .is_authorized_partial(
526 &Request::builder()
527 .action(r#"Action::"View""#.parse().unwrap())
528 .resource(r#"Box::"10""#.parse().unwrap())
529 .context(Context::empty())
530 .build(),
531 &Entities::empty(),
532 )
533 .await;
534
535 assert_matches!(result, Ok(partial_response) =>
536 assert_eq!(partial_response.all_residuals().count(), 1)
537 );
538 assert_eq!(authorizer.log_config.requester, DEFAULT_REQUESTER_NAME);
539 assert!(!authorizer.log_config.field_set.principal);
540 }
541}