Skip to main content

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