1use crate::ast::*;
24use crate::entities::Entities;
25use crate::evaluator::{EvaluationError, Evaluator};
26use crate::extensions::Extensions;
27use itertools::Either;
28use serde::{Deserialize, Serialize};
29use std::collections::HashSet;
30use std::iter::once;
31
32pub struct Authorizer {
34 extensions: Extensions<'static>,
36 error_handling: ErrorHandling,
38}
39
40#[allow(dead_code)]
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44enum ErrorHandling {
45 Deny,
47 Forbid,
50 Skip,
53}
54
55#[derive(Debug, Clone)]
57pub enum ResponseKind {
58 FullyEvaluated(Response),
60 Partial(PartialResponse),
62}
63
64impl ResponseKind {
65 pub fn decision(&self) -> Option<Decision> {
67 match self {
68 ResponseKind::FullyEvaluated(a) => Some(a.decision),
69 ResponseKind::Partial(_) => None,
70 }
71 }
72}
73
74impl Default for ErrorHandling {
75 fn default() -> Self {
76 Self::Skip
77 }
78}
79
80impl Authorizer {
81 pub fn new() -> Self {
83 Self {
84 extensions: Extensions::all_available(), error_handling: Default::default(),
86 }
87 }
88
89 pub fn is_authorized(&self, q: &Request, pset: &PolicySet, entities: &Entities) -> Response {
94 match self.is_authorized_core(q, pset, entities) {
95 ResponseKind::FullyEvaluated(response) => response,
96 ResponseKind::Partial(partial) => {
97 let mut errors = partial.diagnostics.errors;
101 errors.extend(partial.residuals.policies().map(|p| {
102 format!(
103 "while evaluating policy {}, encountered the following error: {}",
104 p.id(),
105 EvaluationError::NonValue(p.condition())
106 )
107 }));
108
109 let idset = partial.residuals.policies().map(|p| p.id().clone());
110
111 match self.error_handling {
112 ErrorHandling::Deny => Response::new(
113 Decision::Deny,
114 idset
115 .chain(partial.diagnostics.reason.into_iter())
116 .collect(),
117 errors,
118 ),
119 ErrorHandling::Forbid => Response::new(
120 Decision::Deny,
121 idset
122 .chain(partial.diagnostics.reason.into_iter())
123 .collect(),
124 errors,
125 ),
126 ErrorHandling::Skip => {
127 if partial
139 .diagnostics
140 .reason
141 .iter()
142 .any(|pid| pset.get(pid).unwrap().effect() == Effect::Permit)
143 {
144 Response::new(Decision::Allow, partial.diagnostics.reason, errors)
145 } else {
146 Response::new(
147 Decision::Deny,
148 idset
149 .chain(partial.diagnostics.reason.into_iter())
150 .collect(),
151 errors,
152 )
153 }
154 }
155 }
156 }
157 }
158 }
159
160 pub fn is_authorized_core(
166 &self,
167 q: &Request,
168 pset: &PolicySet,
169 entities: &Entities,
170 ) -> ResponseKind {
171 let eval = match Evaluator::new(q, entities, &self.extensions) {
172 Ok(eval) => eval,
173 Err(e) => {
174 let msg = format!(
175 "while initializing the Evaluator, encountered the following error: {e}"
176 );
177 return ResponseKind::FullyEvaluated(Response::new(
178 Decision::Deny,
179 HashSet::new(),
180 vec![msg],
181 ));
182 }
183 };
184
185 let results = self.evaluate_policies(pset, eval);
186
187 if !results.global_deny_policies.is_empty() {
188 return ResponseKind::FullyEvaluated(Response::new(
189 Decision::Deny,
190 results.global_deny_policies,
191 results.all_warnings,
192 ));
193 }
194 let mut satisfied_permits = results
201 .satisfied_permits
202 .into_iter()
203 .filter(|permit_p| {
204 results
205 .satisfied_forbids
206 .iter()
207 .all(|forbid_p| Self::overrides(permit_p, forbid_p))
208 })
209 .peekable();
210
211 match (
212 satisfied_permits.peek().is_some(),
213 !results.permit_residuals.is_empty(),
214 !results.forbid_residuals.is_empty(),
215 ) {
216 (true, false | true, false) => {
218 let idset = satisfied_permits.map(|p| p.id().clone()).collect();
219 ResponseKind::FullyEvaluated(Response::new(
220 Decision::Allow,
221 idset,
222 results.all_warnings,
223 ))
224 }
225 (true, false | true, true) => {
227 let idset = satisfied_permits
228 .map(|p| p.id().clone())
229 .collect::<HashSet<_>>();
230 let id = idset.iter().next().unwrap().clone(); let trivial_true = Policy::from_when_clause(Effect::Permit, Expr::val(true), id);
233 ResponseKind::Partial(PartialResponse::new(
235 PolicySet::try_from_iter(
236 results
237 .forbid_residuals
238 .into_iter()
239 .chain(once(trivial_true)),
240 )
241 .unwrap(),
242 idset,
243 results.all_warnings,
244 ))
245 }
246 (false, false, false | true) => {
248 let idset = results
249 .satisfied_forbids
250 .into_iter()
251 .map(|p| p.id().clone())
252 .collect();
253 ResponseKind::FullyEvaluated(Response::new(
254 Decision::Deny,
255 idset,
256 results.all_warnings,
257 ))
258 }
259 (false, true, false | true) => {
262 if !results.satisfied_forbids.is_empty() {
264 let idset = results
265 .satisfied_forbids
266 .into_iter()
267 .map(|p| p.id().clone())
268 .collect();
269 ResponseKind::FullyEvaluated(Response::new(
270 Decision::Deny,
271 idset,
272 results.all_warnings,
273 ))
274 } else {
275 let all_residuals = PolicySet::try_from_iter(
278 [results.forbid_residuals, results.permit_residuals].concat(),
279 )
280 .unwrap();
281 ResponseKind::Partial(PartialResponse::new(
282 all_residuals,
283 HashSet::new(),
284 results.all_warnings,
285 ))
286 }
287 }
288 }
289 }
290
291 fn evaluate_policies<'a>(
292 &'a self,
293 pset: &'a PolicySet,
294 eval: Evaluator<'_>,
295 ) -> EvaluationResults<'a> {
296 let mut results = EvaluationResults::default();
297 let mut satisfied_policies = vec![];
298
299 for p in pset.policies() {
300 match eval.partial_evaluate(p) {
301 Ok(Either::Left(response)) => {
302 if response {
303 satisfied_policies.push(p)
304 }
305 }
306 Ok(Either::Right(residual)) => match p.effect() {
307 Effect::Permit => results.permit_residuals.push(Policy::from_when_clause(
308 p.effect(),
309 residual,
310 p.id().clone(),
311 )),
312 Effect::Forbid => results.forbid_residuals.push(Policy::from_when_clause(
313 p.effect(),
314 residual,
315 p.id().clone(),
316 )),
317 },
318 Err(e) => {
319 results.all_warnings.push(format!(
320 "while evaluating policy {}, encountered the following error: {e}",
321 p.id()
322 ));
323 let satisfied = match self.error_handling {
324 ErrorHandling::Deny => {
325 results.global_deny_policies.insert(p.id().clone());
326 true
327 }
328 ErrorHandling::Forbid => match p.effect() {
329 Effect::Permit => false,
330 Effect::Forbid => true,
331 },
332 ErrorHandling::Skip => false,
333 };
334 if satisfied {
335 satisfied_policies.push(p);
336 }
337 }
338 };
339 }
340
341 let (satisfied_permits, satisfied_forbids) = satisfied_policies
342 .iter()
343 .partition(|p| p.effect() == Effect::Permit);
344
345 results.satisfied_forbids = satisfied_forbids;
346 results.satisfied_permits = satisfied_permits;
347
348 results
349 }
350
351 fn overrides(p1: &Policy, p2: &Policy) -> bool {
357 match (p1.effect(), p2.effect()) {
360 (Effect::Forbid, Effect::Permit) => true,
361 (Effect::Permit, Effect::Forbid) => false,
362 (Effect::Permit, Effect::Permit) => {
363 panic!("Shouldn't call overrides() with two Permits")
364 }
365 (Effect::Forbid, Effect::Forbid) => {
366 panic!("Shouldn't call overrides() with two Forbids")
367 }
368 }
369 }
370}
371
372impl Default for Authorizer {
373 fn default() -> Self {
374 Self::new()
375 }
376}
377
378#[derive(Debug, Clone, Default)]
379struct EvaluationResults<'a> {
380 satisfied_permits: Vec<&'a Policy>,
381 satisfied_forbids: Vec<&'a Policy>,
382 global_deny_policies: HashSet<PolicyID>,
383 all_warnings: Vec<String>,
384 permit_residuals: Vec<Policy>,
385 forbid_residuals: Vec<Policy>,
386}
387
388impl std::fmt::Debug for Authorizer {
389 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
390 if self.extensions.ext_names().next().is_none() {
391 write!(f, "<Authorizer with no extensions>")
392 } else {
393 write!(
394 f,
395 "<Authorizer with the following extensions: {:?}>",
396 self.extensions.ext_names().collect::<Vec<_>>()
397 )
398 }
399 }
400}
401
402#[cfg(test)]
403mod test {
404 use std::collections::BTreeMap;
405
406 use crate::parser;
407
408 use super::*;
409
410 #[test]
413 fn authorizer_sanity_check_empty() {
414 let a = Authorizer::new();
415 let q = Request::new(
416 EntityUID::with_eid("p"),
417 EntityUID::with_eid("a"),
418 EntityUID::with_eid("r"),
419 Context::empty(),
420 );
421 let pset = PolicySet::new();
422 let entities = Entities::new();
423 let ans = a.is_authorized(&q, &pset, &entities);
424 assert_eq!(ans.decision, Decision::Deny);
425 }
426
427 #[test]
429 fn skip_on_error_tests() {
430 let a = Authorizer::new();
431 let q = Request::new(
432 EntityUID::with_eid("p"),
433 EntityUID::with_eid("a"),
434 EntityUID::with_eid("r"),
435 Context::empty(),
436 );
437 let mut pset = PolicySet::new();
438 let entities = Entities::new();
439
440 let p1_src = r#"
441 permit(principal, action, resource);
442 "#;
443
444 let p2_src = r#"
445 permit(principal, action, resource) when { context.bad == 2 };
446 "#;
447
448 let p3_src = r#"
449 forbid(principal, action, resource) when { context.bad == 2 };
450 "#;
451 let p4_src = r#"
452 forbid(principal, action, resource);
453 "#;
454
455 let p1 = parser::parse_policy(Some("1".into()), p1_src).unwrap();
456 pset.add_static(p1).unwrap();
457
458 let ans = a.is_authorized(&q, &pset, &entities);
459 assert_eq!(ans.decision, Decision::Allow);
460
461 pset.add_static(parser::parse_policy(Some("2".into()), p2_src).unwrap())
462 .unwrap();
463
464 let ans = a.is_authorized(&q, &pset, &entities);
465 assert_eq!(ans.decision, Decision::Allow);
466
467 pset.add_static(parser::parse_policy(Some("3".into()), p3_src).unwrap())
468 .unwrap();
469
470 let ans = a.is_authorized(&q, &pset, &entities);
471 assert_eq!(ans.decision, Decision::Allow);
472
473 pset.add_static(parser::parse_policy(Some("4".into()), p4_src).unwrap())
474 .unwrap();
475
476 let ans = a.is_authorized(&q, &pset, &entities);
477 assert_eq!(ans.decision, Decision::Deny);
478 }
479
480 fn true_policy(id: &str, e: Effect) -> StaticPolicy {
481 let pid = PolicyID::from_string(id);
482 StaticPolicy::new(
483 pid,
484 BTreeMap::new(),
485 e,
486 PrincipalConstraint::any(),
487 ActionConstraint::any(),
488 ResourceConstraint::any(),
489 Expr::val(true),
490 )
491 .expect("Policy Creation Failed")
492 }
493
494 fn context_pol(id: &str, effect: Effect) -> StaticPolicy {
495 let pid = PolicyID::from_string(id);
496 StaticPolicy::new(
497 pid,
498 BTreeMap::new(),
499 effect,
500 PrincipalConstraint::any(),
501 ActionConstraint::any(),
502 ResourceConstraint::any(),
503 Expr::get_attr(Expr::var(Var::Context), "test".into()),
504 )
505 .expect("Policy Creation Failed")
506 }
507
508 #[test]
509 fn authorizer_sanity_check_allow() {
510 let a = Authorizer::new();
511 let q = Request::new(
512 EntityUID::with_eid("p"),
513 EntityUID::with_eid("a"),
514 EntityUID::with_eid("r"),
515 Context::empty(),
516 );
517 let mut pset = PolicySet::new();
518 pset.add_static(true_policy("0", Effect::Permit))
519 .expect("Policy ID already in PolicySet");
520 let entities = Entities::new();
521 let ans = a.is_authorized(&q, &pset, &entities);
522 assert!(ans.decision == Decision::Allow);
523 }
524
525 #[test]
526 fn authorizer_sanity_check_partial_deny() {
527 let context = Context::from_expr(RestrictedExpr::record([(
528 "test".into(),
529 RestrictedExpr::new(Expr::unknown("name")).unwrap(),
530 )]));
531 let a = Authorizer::new();
532 let q = Request::new(
533 EntityUID::with_eid("p"),
534 EntityUID::with_eid("a"),
535 EntityUID::with_eid("r"),
536 context,
537 );
538 let mut pset = PolicySet::new();
539 pset.add_static(true_policy("0", Effect::Permit))
540 .expect("Policy ID already in PolicySet");
541 let entities = Entities::new();
542 let ans = a.is_authorized(&q, &pset, &entities);
543 assert_eq!(ans.decision, Decision::Allow);
544 pset.add_static(context_pol("1", Effect::Forbid))
545 .expect("Policy ID overlap");
546 let ans = a.is_authorized(&q, &pset, &entities);
547 assert_eq!(ans.decision, Decision::Allow);
548
549 let mut pset = PolicySet::new();
550 let entities = Entities::new();
551 pset.add_static(context_pol("1", Effect::Forbid))
552 .expect("Policy ID overlap");
553 let ans = a.is_authorized(&q, &pset, &entities);
554 assert_eq!(ans.decision, Decision::Deny);
555
556 let mut pset = PolicySet::new();
557 let entities = Entities::new();
558 pset.add_static(context_pol("1", Effect::Permit))
559 .expect("Policy ID overlap");
560 let ans = a.is_authorized(&q, &pset, &entities);
561 assert_eq!(ans.decision, Decision::Deny);
562 }
563
564 #[test]
565 fn authorizer_sanity_check_deny() {
566 let a = Authorizer::new();
567 let q = Request::new(
568 EntityUID::with_eid("p"),
569 EntityUID::with_eid("a"),
570 EntityUID::with_eid("r"),
571 Context::empty(),
572 );
573 let mut pset = PolicySet::new();
574 pset.add_static(true_policy("0", Effect::Permit))
575 .expect("Policy ID already in PolicySet");
576 pset.add_static(true_policy("1", Effect::Forbid))
577 .expect("Policy ID already in PolicySet");
578 let entities = Entities::new();
579 let ans = a.is_authorized(&q, &pset, &entities);
580 assert!(ans.decision == Decision::Deny);
581 }
582
583 #[test]
584 fn satisfied_permit_no_forbids() {
585 let q = Request::new(
586 EntityUID::with_eid("p"),
587 EntityUID::with_eid("a"),
588 EntityUID::with_eid("r"),
589 Context::empty(),
590 );
591 let a = Authorizer::new();
592 let mut pset = PolicySet::new();
593 let es = Entities::new();
594
595 let src1 = r#"
596 permit(principal == test_entity_type::"p",action,resource);
597 "#;
598 let src2 = r#"
599 forbid(principal == test_entity_type::"p",action,resource) when {
600 false
601 };
602 "#;
603 let src3 = r#"
604 permit(principal == test_entity_type::"p",action,resource) when {
605 unknown("test")
606 };
607 "#;
608
609 pset.add_static(parser::parse_policy(Some("1".to_string()), src1).unwrap())
610 .unwrap();
611 pset.add_static(parser::parse_policy(Some("2".to_string()), src2).unwrap())
612 .unwrap();
613
614 let r = a.is_authorized_core(&q, &pset, &es).decision();
615 assert_eq!(r, Some(Decision::Allow));
616
617 pset.add_static(parser::parse_policy(Some("3".to_string()), src3).unwrap())
618 .unwrap();
619
620 let r = a.is_authorized_core(&q, &pset, &es).decision();
621 assert_eq!(r, Some(Decision::Allow));
622 }
623
624 #[test]
625 fn satisfied_permit_residual_forbid() {
626 let q = Request::new(
627 EntityUID::with_eid("p"),
628 EntityUID::with_eid("a"),
629 EntityUID::with_eid("r"),
630 Context::empty(),
631 );
632 let a = Authorizer::new();
633 let mut pset = PolicySet::new();
634 let es = Entities::new();
635
636 let src1 = r#"
637 permit(principal,action,resource);
638 "#;
639 let src2 = r#"
640 forbid(principal,action,resource) when {
641 unknown("test")
642 };
643 "#;
644 pset.add_static(parser::parse_policy(Some("1".to_string()), src1).unwrap())
645 .unwrap();
646 pset.add_static(parser::parse_policy(Some("2".to_string()), src2).unwrap())
647 .unwrap();
648
649 let r = a.is_authorized_core(&q, &pset, &es);
650 match r {
651 ResponseKind::FullyEvaluated(_) => {
652 panic!("Reached response, should have gotten residual.")
653 }
654 ResponseKind::Partial(p) => {
655 let map = [("test".into(), Value::Lit(false.into()))]
656 .into_iter()
657 .collect();
658 let new = p.residuals.policies().map(|p| {
659 Policy::from_when_clause(
660 p.effect(),
661 p.condition().substitute(&map).unwrap(),
662 p.id().clone(),
663 )
664 });
665 let pset = PolicySet::try_from_iter(new).unwrap();
666 let r = a.is_authorized(&q, &pset, &es);
667 assert_eq!(r.decision, Decision::Allow);
668
669 let map = [("test".into(), Value::Lit(true.into()))]
670 .into_iter()
671 .collect();
672 let new = p.residuals.policies().map(|p| {
673 Policy::from_when_clause(
674 p.effect(),
675 p.condition().substitute(&map).unwrap(),
676 p.id().clone(),
677 )
678 });
679 let pset = PolicySet::try_from_iter(new).unwrap();
680 let r = a.is_authorized(&q, &pset, &es);
681 assert_eq!(r.decision, Decision::Deny);
682 }
683 }
684 }
685
686 #[test]
687 fn no_permits() {
688 let q = Request::new(
689 EntityUID::with_eid("p"),
690 EntityUID::with_eid("a"),
691 EntityUID::with_eid("r"),
692 Context::empty(),
693 );
694 let a = Authorizer::new();
695 let mut pset = PolicySet::new();
696 let es = Entities::new();
697
698 let r = a.is_authorized_core(&q, &pset, &es);
699 assert_eq!(r.decision(), Some(Decision::Deny));
700
701 let src1 = r#"
702 permit(principal, action, resource) when { false };
703 "#;
704
705 pset.add_static(parser::parse_policy(Some("1".into()), src1).unwrap())
706 .unwrap();
707 let r = a.is_authorized_core(&q, &pset, &es);
708 assert_eq!(r.decision(), Some(Decision::Deny));
709
710 let src2 = r#"
711 forbid(principal, action, resource) when { unknown("a") };
712 "#;
713
714 pset.add_static(parser::parse_policy(Some("2".into()), src2).unwrap())
715 .unwrap();
716 let r = a.is_authorized_core(&q, &pset, &es);
717 assert_eq!(r.decision(), Some(Decision::Deny));
718
719 let src3 = r#"
720 forbid(principal, action, resource) when { true };
721 "#;
722 let src4 = r#"
723 permit(principal, action, resource) when { true };
724 "#;
725
726 pset.add_static(parser::parse_policy(Some("3".into()), src3).unwrap())
727 .unwrap();
728 pset.add_static(parser::parse_policy(Some("4".into()), src4).unwrap())
729 .unwrap();
730 let r = a.is_authorized_core(&q, &pset, &es);
731 assert_eq!(r.decision(), Some(Decision::Deny));
732 }
733
734 #[test]
735 fn residual_permits() {
736 let q = Request::new(
737 EntityUID::with_eid("p"),
738 EntityUID::with_eid("a"),
739 EntityUID::with_eid("r"),
740 Context::empty(),
741 );
742 let a = Authorizer::new();
743 let mut pset = PolicySet::new();
744 let es = Entities::new();
745
746 let src1 = r#"
747 permit(principal, action, resource) when { false };
748 "#;
749 let src2 = r#"
750 permit(principal, action, resource) when { unknown("a") };
751 "#;
752 let src3 = r#"
753 forbid(principal, action, resource) when { true };
754 "#;
755
756 pset.add_static(parser::parse_policy(Some("1".into()), src1).unwrap())
757 .unwrap();
758 pset.add_static(parser::parse_policy(Some("2".into()), src2).unwrap())
759 .unwrap();
760
761 let r = a.is_authorized_core(&q, &pset, &es);
762 match r {
763 ResponseKind::FullyEvaluated(_) => {
764 panic!("Reached response, should have gotten residual.")
765 }
766 ResponseKind::Partial(p) => {
767 let map = [("a".into(), Value::Lit(false.into()))]
768 .into_iter()
769 .collect();
770 let new = p.residuals.policies().map(|p| {
771 Policy::from_when_clause(
772 p.effect(),
773 p.condition().substitute(&map).unwrap(),
774 p.id().clone(),
775 )
776 });
777 let pset = PolicySet::try_from_iter(new).unwrap();
778 let r = a.is_authorized(&q, &pset, &es);
779 assert_eq!(r.decision, Decision::Deny);
780
781 let map = [("a".into(), Value::Lit(true.into()))]
782 .into_iter()
783 .collect();
784 let new = p.residuals.policies().map(|p| {
785 Policy::from_when_clause(
786 p.effect(),
787 p.condition().substitute(&map).unwrap(),
788 p.id().clone(),
789 )
790 });
791 let pset = PolicySet::try_from_iter(new).unwrap();
792 let r = a.is_authorized(&q, &pset, &es);
793 assert_eq!(r.decision, Decision::Allow);
794 }
795 }
796
797 pset.add_static(parser::parse_policy(Some("3".into()), src3).unwrap())
798 .unwrap();
799 let r = a.is_authorized_core(&q, &pset, &es);
800 assert_eq!(r.decision(), Some(Decision::Deny));
801 }
802}
803#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
811pub struct Response {
812 pub decision: Decision,
814 pub diagnostics: Diagnostics,
816}
817
818#[derive(Debug, PartialEq, Clone)]
820pub struct PartialResponse {
821 pub residuals: PolicySet,
823 pub diagnostics: Diagnostics,
825}
826
827impl PartialResponse {
828 pub fn new(pset: PolicySet, reason: HashSet<PolicyID>, errors: Vec<String>) -> Self {
830 PartialResponse {
831 residuals: pset,
832 diagnostics: Diagnostics { reason, errors },
833 }
834 }
835}
836
837#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
839pub struct Diagnostics {
840 pub reason: HashSet<PolicyID>,
843 pub errors: Vec<String>,
845}
846
847impl Response {
848 pub fn new(decision: Decision, reason: HashSet<PolicyID>, errors: Vec<String>) -> Self {
850 Response {
851 decision,
852 diagnostics: Diagnostics { reason, errors },
853 }
854 }
855}
856
857#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy)]
859pub enum Decision {
860 Allow,
862 Deny,
867}