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
32mod err;
33pub use err::AuthorizationError;
34
35pub struct Authorizer {
37 extensions: Extensions<'static>,
39 error_handling: ErrorHandling,
41}
42
43#[allow(dead_code)]
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47enum ErrorHandling {
48 Deny,
50 Forbid,
53 Skip,
56}
57
58#[derive(Debug, Clone)]
60pub enum ResponseKind {
61 FullyEvaluated(Response),
63 Partial(PartialResponse),
65}
66
67impl ResponseKind {
68 pub fn decision(&self) -> Option<Decision> {
70 match self {
71 ResponseKind::FullyEvaluated(a) => Some(a.decision),
72 ResponseKind::Partial(_) => None,
73 }
74 }
75}
76
77impl Default for ErrorHandling {
78 fn default() -> Self {
79 Self::Skip
80 }
81}
82
83impl Authorizer {
84 pub fn new() -> Self {
86 Self {
87 extensions: Extensions::all_available(), error_handling: Default::default(),
89 }
90 }
91
92 pub fn is_authorized(&self, q: &Request, pset: &PolicySet, entities: &Entities) -> Response {
97 match self.is_authorized_core(q, pset, entities) {
98 ResponseKind::FullyEvaluated(response) => response,
99 ResponseKind::Partial(partial) => {
100 let mut errors = partial.diagnostics.errors;
104 errors.extend(partial.residuals.policies().map(|p| {
105 AuthorizationError::PolicyEvaluationError {
106 id: p.id().clone(),
107 error: EvaluationError::NonValue(p.condition()),
108 }
109 }));
110
111 let idset = partial.residuals.policies().map(|p| p.id().clone());
112
113 match self.error_handling {
114 ErrorHandling::Deny => Response::new(
115 Decision::Deny,
116 idset
117 .chain(partial.diagnostics.reason.into_iter())
118 .collect(),
119 errors,
120 ),
121 ErrorHandling::Forbid => Response::new(
122 Decision::Deny,
123 idset
124 .chain(partial.diagnostics.reason.into_iter())
125 .collect(),
126 errors,
127 ),
128 ErrorHandling::Skip => {
129 #[allow(clippy::unwrap_used)]
141 if partial
142 .diagnostics
143 .reason
144 .iter()
145 .any(|pid| pset.get(pid).unwrap().effect() == Effect::Permit)
146 {
147 Response::new(Decision::Allow, partial.diagnostics.reason, errors)
148 } else {
149 Response::new(
150 Decision::Deny,
151 idset
152 .chain(partial.diagnostics.reason.into_iter())
153 .collect(),
154 errors,
155 )
156 }
157 }
158 }
159 }
160 }
161 }
162
163 pub fn is_authorized_core(
169 &self,
170 q: &Request,
171 pset: &PolicySet,
172 entities: &Entities,
173 ) -> ResponseKind {
174 let eval = match Evaluator::new(q, entities, &self.extensions) {
175 Ok(eval) => eval,
176 Err(e) => {
177 return ResponseKind::FullyEvaluated(Response::new(
178 Decision::Deny,
179 HashSet::new(),
180 vec![AuthorizationError::AttributeEvaluationError(e)],
181 ));
182 }
183 };
184
185 let results = self.evaluate_policies(pset, eval);
186
187 let errors = results
188 .errors
189 .into_iter()
190 .map(|(pid, err)| AuthorizationError::PolicyEvaluationError {
191 id: pid,
192 error: err,
193 })
194 .collect();
195
196 if !results.global_deny_policies.is_empty() {
197 return ResponseKind::FullyEvaluated(Response::new(
198 Decision::Deny,
199 results.global_deny_policies,
200 errors,
201 ));
202 }
203 let mut satisfied_permits = results
210 .satisfied_permits
211 .into_iter()
212 .filter(|permit_p| {
213 results
214 .satisfied_forbids
215 .iter()
216 .all(|forbid_p| Self::overrides(permit_p, forbid_p))
217 })
218 .peekable();
219
220 match (
221 satisfied_permits.peek().is_some(),
222 !results.permit_residuals.is_empty(),
223 !results.forbid_residuals.is_empty(),
224 ) {
225 (true, false | true, false) => {
227 let idset = satisfied_permits.map(|p| p.id().clone()).collect();
228 ResponseKind::FullyEvaluated(Response::new(Decision::Allow, idset, errors))
229 }
230 (true, false | true, true) => {
232 let idset = satisfied_permits
234 .map(|p| p.id().clone())
235 .collect::<HashSet<_>>();
236 #[allow(clippy::unwrap_used)]
240 let id = idset.iter().next().unwrap().clone(); let trivial_true = Policy::from_when_clause(Effect::Permit, Expr::val(true), id);
242 #[allow(clippy::unwrap_used)]
244 let policy_set = PolicySet::try_from_iter(
245 results
246 .forbid_residuals
247 .into_iter()
248 .chain(once(trivial_true)),
249 )
250 .unwrap();
251 ResponseKind::Partial(PartialResponse::new(policy_set, idset, errors))
252 }
253 (false, false, false | true) => {
255 let idset = results
256 .satisfied_forbids
257 .into_iter()
258 .map(|p| p.id().clone())
259 .collect();
260 ResponseKind::FullyEvaluated(Response::new(Decision::Deny, idset, errors))
261 }
262 (false, true, false | true) => {
265 if !results.satisfied_forbids.is_empty() {
267 let idset = results
268 .satisfied_forbids
269 .into_iter()
270 .map(|p| p.id().clone())
271 .collect();
272 ResponseKind::FullyEvaluated(Response::new(Decision::Deny, idset, errors))
273 } else {
274 #[allow(clippy::unwrap_used)]
277 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 errors,
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.errors.push((p.id().clone(), e));
320 let satisfied = match self.error_handling {
321 ErrorHandling::Deny => {
322 results.global_deny_policies.insert(p.id().clone());
323 true
324 }
325 ErrorHandling::Forbid => match p.effect() {
326 Effect::Permit => false,
327 Effect::Forbid => true,
328 },
329 ErrorHandling::Skip => false,
330 };
331 if satisfied {
332 satisfied_policies.push(p);
333 }
334 }
335 };
336 }
337
338 let (satisfied_permits, satisfied_forbids) = satisfied_policies
339 .iter()
340 .partition(|p| p.effect() == Effect::Permit);
341
342 results.satisfied_forbids = satisfied_forbids;
343 results.satisfied_permits = satisfied_permits;
344
345 results
346 }
347
348 fn overrides(p1: &Policy, p2: &Policy) -> bool {
355 #[allow(clippy::unreachable)]
359 match (p1.effect(), p2.effect()) {
360 (Effect::Forbid, Effect::Permit) => true,
361 (Effect::Permit, Effect::Forbid) => false,
362 (Effect::Permit, Effect::Permit) => {
363 unreachable!("Shouldn't call overrides() with two Permits")
364 }
365 (Effect::Forbid, Effect::Forbid) => {
366 unreachable!("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 errors: Vec<(PolicyID, EvaluationError)>,
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, 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(
830 pset: PolicySet,
831 reason: HashSet<PolicyID>,
832 errors: Vec<AuthorizationError>,
833 ) -> Self {
834 PartialResponse {
835 residuals: pset,
836 diagnostics: Diagnostics { reason, errors },
837 }
838 }
839}
840
841#[derive(Debug, PartialEq, Clone)]
843pub struct Diagnostics {
844 pub reason: HashSet<PolicyID>,
847 pub errors: Vec<AuthorizationError>,
849}
850
851impl Response {
852 pub fn new(
854 decision: Decision,
855 reason: HashSet<PolicyID>,
856 errors: Vec<AuthorizationError>,
857 ) -> Self {
858 Response {
859 decision,
860 diagnostics: Diagnostics { reason, errors },
861 }
862 }
863}
864
865#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy)]
867pub enum Decision {
868 Allow,
870 Deny,
875}