cedar_policy_core/
authorizer.rs

1/*
2 * Copyright 2022-2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17//! This module contains the Cedar "authorizer", which implements the actual
18//! authorization logic.
19//!
20//! Together with the parser, evaluator, and other components, this comprises
21//! the "authorization engine".
22
23use 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
32/// Authorizer
33pub struct Authorizer {
34    /// Cedar `Extension`s which will be used during requests to this `Authorizer`
35    extensions: Extensions<'static>,
36    /// Error-handling behavior of this `Authorizer`
37    error_handling: ErrorHandling,
38}
39
40/// Describes the possible Cedar error-handling modes. Note that modes other than
41/// `SkipOnError` are vestigial: the only official behavior is `SkipOnError`.
42#[allow(dead_code)]
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44enum ErrorHandling {
45    /// Deny the entire request if _any_ policy encounters an evaluation error
46    Deny,
47    /// If a permit policy errors, skip it (implicit deny).  If a forbid policy
48    /// errors, enforce it (explicit deny).
49    Forbid,
50    /// If a policy encounters an evaluation error, skip it.  The decision will
51    /// be as if the erroring policy did not exist.
52    Skip,
53}
54
55/// A potentially partial response from the authorizer
56#[derive(Debug, Clone)]
57pub enum ResponseKind {
58    /// A fully evaluated response
59    FullyEvaluated(Response),
60    /// A response that has some residuals
61    Partial(PartialResponse),
62}
63
64impl ResponseKind {
65    /// The decision reached, if a decision could be reached
66    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    /// Create a new `Authorizer`
82    pub fn new() -> Self {
83        Self {
84            extensions: Extensions::all_available(), // set at compile time
85            error_handling: Default::default(),
86        }
87    }
88
89    /// Returns an authorization response for `q` with respect to the given `Slice`.
90    ///
91    /// The language spec and Dafny model give a precise definition of how this is
92    /// computed.
93    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                // If we get a residual, we have to treat every residual policy as an error, and obey the error semantics.
98                // This can result in an Accept in one case:
99                // `error_handling` is `SkipOnerror`, no forbids evaluated to a concrete response, and some permits evaluated to `true`
100                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 there were satisfied permits in the residual, then skipping errors means returning `Allow`
128                        // This is tricky logic, but it's correct as follows:
129                        //  If any permit policy is in the diagnostics, it means it evaluated to a concrete `true` and was not overridden by a `forbid` policy
130                        //  That means that all forbid policies evaluated to one of:
131                        //    concrete `false`
132                        //    concrete error
133                        //    a residual (effectively concrete error).
134                        // Thus all residuals should be `skipped`
135                        // However, if all of the policies are `forbid`, then we still have to return `Deny`, likewise if the set is empty.
136
137                        // This `unwrap` is safe as all policy ids in `diagnostics` are by definition in the policy set
138                        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    /// Returns an authorization response for `q` with respect to the given `Slice`.
161    /// Partial Evaluation of is_authorized
162    ///
163    /// The language spec and Dafny model give a precise definition of how this is
164    /// computed.
165    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        // Semantics ask for the set C_I^+ of all satisfied Permit policies
195        // which override all satisfied Forbid policies. We call this set
196        // `satisfied_permits`.
197        // Notice that this currently differs from the semantics stated in the Language Spec,
198        // which no longer consider overrides. The implementation is however equivalent,
199        // since forbids always trump permits.
200        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            // If we have a satisfied permit and _no_ residual forbids, we can return Allow (this is true regardless of residual permits)
217            (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            // If we have a satisfied permit, and there are residual forbids, we must return a residual response. (this is true regardless of residual permits)
226            (true, false | true, true) => {
227                let idset = satisfied_permits
228                    .map(|p| p.id().clone())
229                    .collect::<HashSet<_>>();
230                // The residual will consist of all of the residual forbids, and one trivially true `permit`
231                let id = idset.iter().next().unwrap().clone(); // This unwrap is safe as we know there are satisfied permits
232                let trivial_true = Policy::from_when_clause(Effect::Permit, Expr::val(true), id);
233                // This unwrap should be safe, all policy IDs should already be unique
234                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            // If there are no satisfied permits, and no residual permits, then the request cannot succeed
247            (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            // If there are no satisfied permits, but residual permits, then request may still succeed. Return residual
260            // Add in the forbid_residuals if any
261            (false, true, false | true) => {
262                // The request will definitely fail if there are satisfied forbids, check those
263                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                    // No satisfied forbids
276                    // This unwrap should be safe, all policy IDs should already be unique
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                        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    /// Private helper function which determines if policy `p1` overrides policy
352    /// `p2`.
353    ///
354    /// This only makes sense to call with one `Permit` and one `Forbid` policy.
355    /// If you call this with two `Permit`s or two `Forbid`s, this will panic.
356    fn overrides(p1: &Policy, p2: &Policy) -> bool {
357        // For now, we only support the default:
358        // all Forbid policies override all Permit policies.
359        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    /// Sanity unit test case for is_authorized.
411    /// More robust testing is accomplished through the integration tests.
412    #[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    /// Simple tests of skip-on-error semantics
428    #[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// by default, Coverlay does not track coverage for lines after a line
804// containing #[cfg(test)].
805// we use the following sentinel to "turn back on" coverage tracking for
806// remaining lines of this file, until the next #[cfg(test)]
807// GRCOV_BEGIN_COVERAGE
808
809/// Authorization response returned from the `Authorizer`
810#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
811pub struct Response {
812    /// Authorization decision
813    pub decision: Decision,
814    /// Diagnostics providing more information on how this decision was reached
815    pub diagnostics: Diagnostics,
816}
817
818/// Response that may contain a residual.
819#[derive(Debug, PartialEq, Clone)]
820pub struct PartialResponse {
821    /// Residual policies
822    pub residuals: PolicySet,
823    /// Diagnostics providing info
824    pub diagnostics: Diagnostics,
825}
826
827impl PartialResponse {
828    /// Create a partial response with a residual PolicySet
829    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/// Diagnostics providing more information on how a `Decision` was reached
838#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
839pub struct Diagnostics {
840    /// `PolicyID`s of the policies that contributed to the decision. If no
841    /// policies applied to the request, this set will be empty.
842    pub reason: HashSet<PolicyID>,
843    /// list of error messages which occurred
844    pub errors: Vec<String>,
845}
846
847impl Response {
848    /// Create a new `Response`
849    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/// Decision returned from the `Authorizer`
858#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy)]
859pub enum Decision {
860    /// The `Authorizer` determined that the request should be allowed
861    Allow,
862    /// The `Authorizer` determined that the request should be denied.
863    /// This is also returned if sufficiently fatal errors are encountered such
864    /// that no decision could be safely reached; for example, errors parsing
865    /// the policies.
866    Deny,
867}