cedar_policy_core/
authorizer.rs

1/*
2 * Copyright Cedar Contributors
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::Evaluator;
26use crate::extensions::Extensions;
27use itertools::{Either, Itertools};
28use serde::{Deserialize, Serialize};
29use std::collections::HashSet;
30use std::sync::Arc;
31
32#[cfg(feature = "wasm")]
33extern crate tsify;
34
35mod err;
36mod partial_response;
37pub use err::{AuthorizationError, ConcretizationError, ReauthorizationError};
38
39pub use partial_response::ErrorState;
40pub use partial_response::PartialResponse;
41
42/// Authorizer
43#[derive(Clone)] // `Debug` implemented manually below
44pub struct Authorizer {
45    /// Cedar `Extension`s which will be used during requests to this `Authorizer`
46    extensions: &'static Extensions<'static>,
47    /// Error-handling behavior of this `Authorizer`
48    error_handling: ErrorHandling,
49}
50
51/// Describes the possible Cedar error-handling modes.
52/// We currently only have one mode: [`ErrorHandling::Skip`].
53/// Other modes were debated during development, so this is here as an easy
54/// way to add modes if the future if we so decide.
55#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56enum ErrorHandling {
57    /// If a policy encounters an evaluation error, skip it.  The decision will
58    /// be as if the erroring policy did not exist.
59    Skip,
60}
61
62impl Default for ErrorHandling {
63    fn default() -> Self {
64        Self::Skip
65    }
66}
67
68impl Authorizer {
69    /// Create a new `Authorizer`
70    pub fn new() -> Self {
71        Self {
72            extensions: Extensions::all_available(), // set at compile time
73            error_handling: Default::default(),
74        }
75    }
76
77    /// Returns an authorization response for `q` with respect to the given `Slice`.
78    ///
79    /// The language spec and formal model give a precise definition of how this is
80    /// computed.
81    pub fn is_authorized(&self, q: Request, pset: &PolicySet, entities: &Entities) -> Response {
82        self.is_authorized_core(q, pset, entities).concretize()
83    }
84
85    /// Returns an authorization response for `q` with respect to the given `Slice`.
86    /// Partial Evaluation of is_authorized
87    ///
88    pub fn is_authorized_core(
89        &self,
90        q: Request,
91        pset: &PolicySet,
92        entities: &Entities,
93    ) -> PartialResponse {
94        let eval = Evaluator::new(q.clone(), entities, self.extensions);
95        let mut true_permits = vec![];
96        let mut true_forbids = vec![];
97        let mut false_permits = vec![];
98        let mut false_forbids = vec![];
99        let mut residual_permits = vec![];
100        let mut residual_forbids = vec![];
101        let mut errors = vec![];
102
103        for p in pset.policies() {
104            let (id, annotations) = (p.id().clone(), p.annotations_arc().clone());
105            match eval.partial_evaluate(p) {
106                Ok(Either::Left(satisfied)) => match (satisfied, p.effect()) {
107                    (true, Effect::Permit) => true_permits.push((id, annotations)),
108                    (true, Effect::Forbid) => true_forbids.push((id, annotations)),
109                    (false, Effect::Permit) => {
110                        false_permits.push((id, (ErrorState::NoError, annotations)))
111                    }
112                    (false, Effect::Forbid) => {
113                        false_forbids.push((id, (ErrorState::NoError, annotations)))
114                    }
115                },
116                Ok(Either::Right(residual)) => match p.effect() {
117                    Effect::Permit => {
118                        residual_permits.push((id, (Arc::new(residual), annotations)))
119                    }
120                    Effect::Forbid => {
121                        residual_forbids.push((id, (Arc::new(residual), annotations)))
122                    }
123                },
124                Err(e) => {
125                    errors.push(AuthorizationError::PolicyEvaluationError {
126                        id: id.clone(),
127                        error: e,
128                    });
129                    let satisfied = match self.error_handling {
130                        ErrorHandling::Skip => false,
131                    };
132                    match (satisfied, p.effect()) {
133                        (true, Effect::Permit) => true_permits.push((id, annotations)),
134                        (true, Effect::Forbid) => true_forbids.push((id, annotations)),
135                        (false, Effect::Permit) => {
136                            false_permits.push((id, (ErrorState::Error, annotations)))
137                        }
138                        (false, Effect::Forbid) => {
139                            false_forbids.push((id, (ErrorState::Error, annotations)))
140                        }
141                    }
142                }
143            };
144        }
145
146        PartialResponse::new(
147            true_permits,
148            false_permits,
149            residual_permits,
150            true_forbids,
151            false_forbids,
152            residual_forbids,
153            errors,
154            Arc::new(q),
155        )
156    }
157}
158
159impl Default for Authorizer {
160    fn default() -> Self {
161        Self::new()
162    }
163}
164
165impl std::fmt::Debug for Authorizer {
166    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
167        if self.extensions.ext_names().next().is_none() {
168            write!(f, "<Authorizer with no extensions>")
169        } else {
170            write!(
171                f,
172                "<Authorizer with the following extensions: [{}]>",
173                self.extensions.ext_names().join(", ")
174            )
175        }
176    }
177}
178
179// PANIC SAFETY: Unit Test Code
180#[allow(clippy::panic)]
181#[cfg(test)]
182mod test {
183    use crate::ast::Annotations;
184
185    use super::*;
186    use crate::parser;
187
188    /// Sanity unit test case for is_authorized.
189    /// More robust testing is accomplished through the integration tests.
190    #[test]
191    fn authorizer_sanity_check_empty() {
192        let a = Authorizer::new();
193        let q = Request::new(
194            (EntityUID::with_eid("p"), None),
195            (EntityUID::with_eid("a"), None),
196            (EntityUID::with_eid("r"), None),
197            Context::empty(),
198            None::<&RequestSchemaAllPass>,
199            Extensions::none(),
200        )
201        .unwrap();
202        let pset = PolicySet::new();
203        let entities = Entities::new();
204        let ans = a.is_authorized(q, &pset, &entities);
205        assert_eq!(ans.decision, Decision::Deny);
206    }
207
208    /// Simple tests of skip-on-error semantics
209    #[test]
210    fn skip_on_error_tests() {
211        let a = Authorizer::new();
212        let q = Request::new(
213            (EntityUID::with_eid("p"), None),
214            (EntityUID::with_eid("a"), None),
215            (EntityUID::with_eid("r"), None),
216            Context::empty(),
217            None::<&RequestSchemaAllPass>,
218            Extensions::none(),
219        )
220        .unwrap();
221        let mut pset = PolicySet::new();
222        let entities = Entities::new();
223
224        let p1_src = r#"
225        permit(principal, action, resource);
226        "#;
227
228        let p2_src = r#"
229        permit(principal, action, resource) when { context.bad == 2 };
230        "#;
231
232        let p3_src = r#"
233        forbid(principal, action, resource) when { context.bad == 2 };
234        "#;
235        let p4_src = r#"
236        forbid(principal, action, resource);
237        "#;
238
239        let p1 = parser::parse_policy(Some(PolicyID::from_string("1")), p1_src).unwrap();
240        pset.add_static(p1).unwrap();
241
242        let ans = a.is_authorized(q.clone(), &pset, &entities);
243        assert_eq!(ans.decision, Decision::Allow);
244
245        pset.add_static(parser::parse_policy(Some(PolicyID::from_string("2")), p2_src).unwrap())
246            .unwrap();
247
248        let ans = a.is_authorized(q.clone(), &pset, &entities);
249        assert_eq!(ans.decision, Decision::Allow);
250
251        pset.add_static(parser::parse_policy(Some(PolicyID::from_string("3")), p3_src).unwrap())
252            .unwrap();
253
254        let ans = a.is_authorized(q.clone(), &pset, &entities);
255        assert_eq!(ans.decision, Decision::Allow);
256
257        pset.add_static(parser::parse_policy(Some(PolicyID::from_string("4")), p4_src).unwrap())
258            .unwrap();
259
260        let ans = a.is_authorized(q, &pset, &entities);
261        assert_eq!(ans.decision, Decision::Deny);
262    }
263
264    fn true_policy(id: &str, e: Effect) -> StaticPolicy {
265        let pid = PolicyID::from_string(id);
266        StaticPolicy::new(
267            pid,
268            None,
269            Annotations::new(),
270            e,
271            PrincipalConstraint::any(),
272            ActionConstraint::any(),
273            ResourceConstraint::any(),
274            Expr::val(true),
275        )
276        .expect("Policy Creation Failed")
277    }
278
279    #[cfg(feature = "partial-eval")]
280    fn context_pol(id: &str, effect: Effect) -> StaticPolicy {
281        let pid = PolicyID::from_string(id);
282        StaticPolicy::new(
283            pid,
284            None,
285            Annotations::new(),
286            effect,
287            PrincipalConstraint::any(),
288            ActionConstraint::any(),
289            ResourceConstraint::any(),
290            Expr::get_attr(Expr::var(Var::Context), "test".into()),
291        )
292        .expect("Policy Creation Failed")
293    }
294
295    #[test]
296    fn authorizer_sanity_check_allow() {
297        let a = Authorizer::new();
298        let q = Request::new(
299            (EntityUID::with_eid("p"), None),
300            (EntityUID::with_eid("a"), None),
301            (EntityUID::with_eid("r"), None),
302            Context::empty(),
303            None::<&RequestSchemaAllPass>,
304            Extensions::none(),
305        )
306        .unwrap();
307        let mut pset = PolicySet::new();
308        pset.add_static(true_policy("0", Effect::Permit))
309            .expect("Policy ID already in PolicySet");
310        let entities = Entities::new();
311        let ans = a.is_authorized(q, &pset, &entities);
312        assert!(ans.decision == Decision::Allow);
313    }
314
315    #[test]
316    #[cfg(feature = "partial-eval")]
317    fn authorizer_sanity_check_partial_deny() {
318        let context = Context::from_expr(
319            RestrictedExpr::record([(
320                "test".into(),
321                RestrictedExpr::unknown(Unknown::new_untyped("name")),
322            )])
323            .unwrap()
324            .as_borrowed(),
325            Extensions::none(),
326        )
327        .unwrap();
328        let a = Authorizer::new();
329        let q = Request::new(
330            (EntityUID::with_eid("p"), None),
331            (EntityUID::with_eid("a"), None),
332            (EntityUID::with_eid("r"), None),
333            context,
334            None::<&RequestSchemaAllPass>,
335            Extensions::none(),
336        )
337        .unwrap();
338        let mut pset = PolicySet::new();
339        pset.add_static(true_policy("0", Effect::Permit))
340            .expect("Policy ID already in PolicySet");
341        let entities = Entities::new();
342        let ans = a.is_authorized(q.clone(), &pset, &entities);
343        assert_eq!(ans.decision, Decision::Allow);
344        pset.add_static(context_pol("1", Effect::Forbid))
345            .expect("Policy ID overlap");
346        let ans = a.is_authorized(q.clone(), &pset, &entities);
347        assert_eq!(ans.decision, Decision::Allow);
348
349        let mut pset = PolicySet::new();
350        let entities = Entities::new();
351        pset.add_static(context_pol("1", Effect::Forbid))
352            .expect("Policy ID overlap");
353        let ans = a.is_authorized(q.clone(), &pset, &entities);
354        assert_eq!(ans.decision, Decision::Deny);
355
356        let mut pset = PolicySet::new();
357        let entities = Entities::new();
358        pset.add_static(context_pol("1", Effect::Permit))
359            .expect("Policy ID overlap");
360        let ans = a.is_authorized(q, &pset, &entities);
361        assert_eq!(ans.decision, Decision::Deny);
362    }
363
364    #[test]
365    fn authorizer_sanity_check_deny() {
366        let a = Authorizer::new();
367        let q = Request::new(
368            (EntityUID::with_eid("p"), None),
369            (EntityUID::with_eid("a"), None),
370            (EntityUID::with_eid("r"), None),
371            Context::empty(),
372            None::<&RequestSchemaAllPass>,
373            Extensions::none(),
374        )
375        .unwrap();
376        let mut pset = PolicySet::new();
377        pset.add_static(true_policy("0", Effect::Permit))
378            .expect("Policy ID already in PolicySet");
379        pset.add_static(true_policy("1", Effect::Forbid))
380            .expect("Policy ID already in PolicySet");
381        let entities = Entities::new();
382        let ans = a.is_authorized(q, &pset, &entities);
383        assert!(ans.decision == Decision::Deny);
384    }
385
386    #[test]
387    fn satisfied_permit_no_forbids() {
388        let q = Request::new(
389            (EntityUID::with_eid("p"), None),
390            (EntityUID::with_eid("a"), None),
391            (EntityUID::with_eid("r"), None),
392            Context::empty(),
393            None::<&RequestSchemaAllPass>,
394            Extensions::none(),
395        )
396        .unwrap();
397        let a = Authorizer::new();
398        let mut pset = PolicySet::new();
399        let es = Entities::new();
400
401        let src1 = r#"
402        permit(principal == test_entity_type::"p",action,resource);
403        "#;
404        let src2 = r#"
405        forbid(principal == test_entity_type::"p",action,resource) when {
406            false
407        };
408        "#;
409
410        pset.add_static(parser::parse_policy(Some(PolicyID::from_string("1")), src1).unwrap())
411            .unwrap();
412        pset.add_static(parser::parse_policy(Some(PolicyID::from_string("2")), src2).unwrap())
413            .unwrap();
414
415        let r = a.is_authorized_core(q, &pset, &es).decision();
416        assert_eq!(r, Some(Decision::Allow));
417    }
418
419    #[test]
420    #[cfg(feature = "partial-eval")]
421    fn satisfied_permit_no_forbids_unknown() {
422        let q = Request::new(
423            (EntityUID::with_eid("p"), None),
424            (EntityUID::with_eid("a"), None),
425            (EntityUID::with_eid("r"), None),
426            Context::empty(),
427            None::<&RequestSchemaAllPass>,
428            Extensions::none(),
429        )
430        .unwrap();
431        let a = Authorizer::new();
432        let mut pset = PolicySet::new();
433        let es = Entities::new();
434
435        let src1 = r#"
436        permit(principal == test_entity_type::"p",action,resource);
437        "#;
438        let src2 = r#"
439        forbid(principal == test_entity_type::"p",action,resource) when {
440            false
441        };
442        "#;
443        let src3 = r#"
444        permit(principal == test_entity_type::"p",action,resource) when {
445            unknown("test")
446        };
447        "#;
448
449        pset.add_static(parser::parse_policy(Some(PolicyID::from_string("1")), src1).unwrap())
450            .unwrap();
451        pset.add_static(parser::parse_policy(Some(PolicyID::from_string("2")), src2).unwrap())
452            .unwrap();
453
454        let r = a.is_authorized_core(q.clone(), &pset, &es).decision();
455        assert_eq!(r, Some(Decision::Allow));
456
457        pset.add_static(parser::parse_policy(Some(PolicyID::from_string("3")), src3).unwrap())
458            .unwrap();
459
460        let r = a.is_authorized_core(q.clone(), &pset, &es).decision();
461        assert_eq!(r, Some(Decision::Allow));
462
463        let r = a.is_authorized_core(q, &pset, &es);
464        assert!(r
465            .satisfied_permits
466            .contains_key(&PolicyID::from_string("1")));
467        assert!(r.satisfied_forbids.is_empty());
468        assert!(r.residual_permits.contains_key(&PolicyID::from_string("3")));
469        assert!(r.residual_forbids.is_empty());
470        assert!(r.errors.is_empty());
471    }
472
473    #[test]
474    #[cfg(feature = "partial-eval")]
475    fn satisfied_permit_residual_forbid() {
476        let q = Request::new(
477            (EntityUID::with_eid("p"), None),
478            (EntityUID::with_eid("a"), None),
479            (EntityUID::with_eid("r"), None),
480            Context::empty(),
481            None::<&RequestSchemaAllPass>,
482            Extensions::none(),
483        )
484        .unwrap();
485        let a = Authorizer::new();
486        let mut pset = PolicySet::new();
487        let es = Entities::new();
488
489        let src1 = r#"
490        permit(principal,action,resource);
491        "#;
492        let src2 = r#"
493        forbid(principal,action,resource) when {
494            unknown("test")
495        };
496        "#;
497        pset.add_static(parser::parse_policy(Some(PolicyID::from_string("1")), src1).unwrap())
498            .unwrap();
499        pset.add_static(parser::parse_policy(Some(PolicyID::from_string("2")), src2).unwrap())
500            .unwrap();
501
502        let r = a.is_authorized_core(q.clone(), &pset, &es);
503        let map = [("test".into(), Value::from(false))].into_iter().collect();
504        let r2: Response = r.reauthorize(&map, &a, &es).unwrap().into();
505        assert_eq!(r2.decision, Decision::Allow);
506        drop(r2);
507
508        let map = [("test".into(), Value::from(true))].into_iter().collect();
509        let r2: Response = r.reauthorize(&map, &a, &es).unwrap().into();
510        assert_eq!(r2.decision, Decision::Deny);
511
512        let r = a.is_authorized_core(q, &pset, &es);
513        assert!(r
514            .satisfied_permits
515            .contains_key(&PolicyID::from_string("1")));
516        assert!(r.satisfied_forbids.is_empty());
517        assert!(r.errors.is_empty());
518        assert!(r.residual_permits.is_empty());
519        assert!(r.residual_forbids.contains_key(&PolicyID::from_string("2")));
520    }
521
522    #[test]
523    #[cfg(feature = "partial-eval")]
524    fn no_permits() {
525        let q = Request::new(
526            (EntityUID::with_eid("p"), None),
527            (EntityUID::with_eid("a"), None),
528            (EntityUID::with_eid("r"), None),
529            Context::empty(),
530            None::<&RequestSchemaAllPass>,
531            Extensions::none(),
532        )
533        .unwrap();
534        let a = Authorizer::new();
535        let mut pset = PolicySet::new();
536        let es = Entities::new();
537
538        let r = a.is_authorized_core(q.clone(), &pset, &es);
539        assert_eq!(r.decision(), Some(Decision::Deny));
540
541        let src1 = r#"
542        permit(principal, action, resource) when { false };
543        "#;
544
545        pset.add_static(parser::parse_policy(Some(PolicyID::from_string("1")), src1).unwrap())
546            .unwrap();
547        let r = a.is_authorized_core(q.clone(), &pset, &es);
548        assert_eq!(r.decision(), Some(Decision::Deny));
549
550        let src2 = r#"
551        forbid(principal, action, resource) when { unknown("a") };
552        "#;
553
554        pset.add_static(parser::parse_policy(Some(PolicyID::from_string("2")), src2).unwrap())
555            .unwrap();
556        let r = a.is_authorized_core(q.clone(), &pset, &es);
557        assert_eq!(r.decision(), Some(Decision::Deny));
558
559        let src3 = r#"
560        forbid(principal, action, resource) when { true };
561        "#;
562        let src4 = r#"
563        permit(principal, action, resource) when { true };
564        "#;
565
566        pset.add_static(parser::parse_policy(Some(PolicyID::from_string("3")), src3).unwrap())
567            .unwrap();
568        pset.add_static(parser::parse_policy(Some(PolicyID::from_string("4")), src4).unwrap())
569            .unwrap();
570        let r = a.is_authorized_core(q.clone(), &pset, &es);
571        assert_eq!(r.decision(), Some(Decision::Deny));
572
573        let r = a.is_authorized_core(q, &pset, &es);
574        assert!(r
575            .satisfied_permits
576            .contains_key(&PolicyID::from_string("4")));
577        assert!(r
578            .satisfied_forbids
579            .contains_key(&PolicyID::from_string("3")));
580        assert!(r.errors.is_empty());
581        assert!(r.residual_permits.is_empty());
582        assert!(r.residual_forbids.contains_key(&PolicyID::from_string("2")));
583    }
584
585    #[test]
586    #[cfg(feature = "partial-eval")]
587    fn residual_permits() {
588        let q = Request::new(
589            (EntityUID::with_eid("p"), None),
590            (EntityUID::with_eid("a"), None),
591            (EntityUID::with_eid("r"), None),
592            Context::empty(),
593            None::<&RequestSchemaAllPass>,
594            Extensions::none(),
595        )
596        .unwrap();
597        let a = Authorizer::new();
598        let mut pset = PolicySet::new();
599        let es = Entities::new();
600
601        let src1 = r#"
602        permit(principal, action, resource) when { false };
603        "#;
604        let src2 = r#"
605        permit(principal, action, resource) when { unknown("a") };
606        "#;
607        let src3 = r#"
608        forbid(principal, action, resource) when { true };
609        "#;
610
611        pset.add_static(parser::parse_policy(Some(PolicyID::from_string("1")), src1).unwrap())
612            .unwrap();
613        pset.add_static(parser::parse_policy(Some(PolicyID::from_string("2")), src2).unwrap())
614            .unwrap();
615
616        let r = a.is_authorized_core(q.clone(), &pset, &es);
617        let map = [("a".into(), Value::from(false))].into_iter().collect();
618        let r2: Response = r.reauthorize(&map, &a, &es).unwrap().into();
619        assert_eq!(r2.decision, Decision::Deny);
620
621        let map = [("a".into(), Value::from(true))].into_iter().collect();
622        let r2: Response = r.reauthorize(&map, &a, &es).unwrap().into();
623        assert_eq!(r2.decision, Decision::Allow);
624
625        pset.add_static(parser::parse_policy(Some(PolicyID::from_string("3")), src3).unwrap())
626            .unwrap();
627        let r = a.is_authorized_core(q.clone(), &pset, &es);
628        assert_eq!(r.decision(), Some(Decision::Deny));
629
630        let r = a.is_authorized_core(q, &pset, &es);
631        assert!(r.satisfied_permits.is_empty());
632        assert!(r
633            .satisfied_forbids
634            .contains_key(&PolicyID::from_string("3")));
635        assert!(r.errors.is_empty());
636        assert!(r.residual_permits.contains_key(&PolicyID::from_string("2")));
637        assert!(r.residual_forbids.is_empty());
638    }
639}
640// by default, Coverlay does not track coverage for lines after a line
641// containing #[cfg(test)].
642// we use the following sentinel to "turn back on" coverage tracking for
643// remaining lines of this file, until the next #[cfg(test)]
644// GRCOV_BEGIN_COVERAGE
645
646/// Authorization response returned from the `Authorizer`
647#[derive(Debug, PartialEq, Eq, Clone)]
648pub struct Response {
649    /// Authorization decision
650    pub decision: Decision,
651    /// Diagnostics providing more information on how this decision was reached
652    pub diagnostics: Diagnostics,
653}
654
655/// Policy evaluation response returned from the `Authorizer`.
656#[derive(Debug, PartialEq, Eq, Clone)]
657pub struct EvaluationResponse {
658    /// `PolicyID`s of the fully evaluated policies with a permit [`Effect`].
659    pub satisfied_permits: HashSet<PolicyID>,
660    /// `PolicyID`s of the fully evaluated policies with a forbid [`Effect`].
661    pub satisfied_forbids: HashSet<PolicyID>,
662    /// List of errors that occurred
663    pub errors: Vec<AuthorizationError>,
664    /// Residual policies with a permit [`Effect`].
665    pub permit_residuals: PolicySet,
666    /// Residual policies with a forbid [`Effect`].
667    pub forbid_residuals: PolicySet,
668}
669
670/// Diagnostics providing more information on how a `Decision` was reached
671#[derive(Debug, PartialEq, Eq, Clone)]
672pub struct Diagnostics {
673    /// `PolicyID`s of the policies that contributed to the decision. If no
674    /// policies applied to the request, this set will be empty.
675    pub reason: HashSet<PolicyID>,
676    /// List of errors that occurred
677    pub errors: Vec<AuthorizationError>,
678}
679
680impl Response {
681    /// Create a new `Response`
682    pub fn new(
683        decision: Decision,
684        reason: HashSet<PolicyID>,
685        errors: Vec<AuthorizationError>,
686    ) -> Self {
687        Response {
688            decision,
689            diagnostics: Diagnostics { reason, errors },
690        }
691    }
692}
693
694/// Decision returned from the `Authorizer`
695#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Copy)]
696#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
697#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
698#[serde(rename_all = "camelCase")]
699pub enum Decision {
700    /// The `Authorizer` determined that the request should be allowed
701    Allow,
702    /// The `Authorizer` determined that the request should be denied.
703    /// This is also returned if sufficiently fatal errors are encountered such
704    /// that no decision could be safely reached; for example, errors parsing
705    /// the policies.
706    Deny,
707}