cedar_policy_core/authorizer/
partial_response.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
17use std::collections::HashMap;
18
19use either::Either;
20use smol_str::SmolStr;
21use std::sync::Arc;
22
23use super::{
24    err::{ConcretizationError, ReauthorizationError},
25    Annotations, AuthorizationError, Authorizer, Context, Decision, Effect, EntityUIDEntry, Expr,
26    Policy, PolicySet, PolicySetError, Request, Response, Value,
27};
28use crate::{ast::PolicyID, entities::Entities, evaluator::EvaluationError};
29
30type PolicyComponents<'a> = (Effect, &'a PolicyID, &'a Arc<Expr>, &'a Arc<Annotations>);
31
32/// Enum representing whether a policy is not satisfied due to
33/// evaluating to `false`, or because it errored.
34#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
35pub enum ErrorState {
36    /// The policy did not error
37    NoError,
38    /// The policy did error
39    Error,
40}
41
42/// A partially evaluated authorization response.
43/// Splits the results into several categories: satisfied, false, and residual for each policy effect.
44/// Also tracks all the errors that were encountered during evaluation.
45/// This structure currently has to own all of the `PolicyID` objects due to the [`Self::reauthorize`]
46/// method. If [`PolicySet`] could borrow its PolicyID/contents then this whole structured could be borrowed.
47#[derive(Debug, Clone)]
48pub struct PartialResponse {
49    /// All of the [`Effect::Permit`] policies that were satisfied
50    pub satisfied_permits: HashMap<PolicyID, Arc<Annotations>>,
51    /// All of the [`Effect::Permit`] policies that were not satisfied
52    pub false_permits: HashMap<PolicyID, (ErrorState, Arc<Annotations>)>,
53    /// All of the [`Effect::Permit`] policies that evaluated to a residual
54    pub residual_permits: HashMap<PolicyID, (Arc<Expr>, Arc<Annotations>)>,
55    /// All of the [`Effect::Forbid`] policies that were satisfied
56    pub satisfied_forbids: HashMap<PolicyID, Arc<Annotations>>,
57    /// All of the [`Effect::Forbid`] policies that were not satisfied
58    pub false_forbids: HashMap<PolicyID, (ErrorState, Arc<Annotations>)>,
59    /// All of the [`Effect::Forbid`] policies that evaluated to a residual
60    pub residual_forbids: HashMap<PolicyID, (Arc<Expr>, Arc<Annotations>)>,
61    /// All of the policy errors encountered during evaluation
62    pub errors: Vec<AuthorizationError>,
63    /// The trivial `true` expression, used for materializing a residual for satisfied policies
64    true_expr: Arc<Expr>,
65    /// The trivial `false` expression, used for materializing a residual for non-satisfied policies
66    false_expr: Arc<Expr>,
67    /// The request associated with the partial response
68    request: Arc<Request>,
69}
70
71impl PartialResponse {
72    /// Create a partial response from each of the policy result categories
73    #[allow(clippy::too_many_arguments)]
74    pub fn new(
75        true_permits: impl IntoIterator<Item = (PolicyID, Arc<Annotations>)>,
76        false_permits: impl IntoIterator<Item = (PolicyID, (ErrorState, Arc<Annotations>))>,
77        residual_permits: impl IntoIterator<Item = (PolicyID, (Arc<Expr>, Arc<Annotations>))>,
78        true_forbids: impl IntoIterator<Item = (PolicyID, Arc<Annotations>)>,
79        false_forbids: impl IntoIterator<Item = (PolicyID, (ErrorState, Arc<Annotations>))>,
80        residual_forbids: impl IntoIterator<Item = (PolicyID, (Arc<Expr>, Arc<Annotations>))>,
81        errors: impl IntoIterator<Item = AuthorizationError>,
82        request: Arc<Request>,
83    ) -> Self {
84        Self {
85            satisfied_permits: true_permits.into_iter().collect(),
86            false_permits: false_permits.into_iter().collect(),
87            residual_permits: residual_permits.into_iter().collect(),
88            satisfied_forbids: true_forbids.into_iter().collect(),
89            false_forbids: false_forbids.into_iter().collect(),
90            residual_forbids: residual_forbids.into_iter().collect(),
91            errors: errors.into_iter().collect(),
92            true_expr: Arc::new(Expr::val(true)),
93            false_expr: Arc::new(Expr::val(false)),
94            request,
95        }
96    }
97
98    /// Convert this response into a concrete evaluation response.
99    /// All residuals are treated as errors
100    pub fn concretize(self) -> Response {
101        self.into()
102    }
103
104    /// Attempt to reach a partial decision; the presence of residuals may result in returning [`None`],
105    /// indicating that a decision could not be reached given the unknowns
106    pub fn decision(&self) -> Option<Decision> {
107        match (
108            !self.satisfied_forbids.is_empty(),
109            !self.satisfied_permits.is_empty(),
110            !self.residual_permits.is_empty(),
111            !self.residual_forbids.is_empty(),
112        ) {
113            // Any true forbids means we will deny
114            (true, _, _, _) => Some(Decision::Deny),
115            // No potentially or trivially true permits, means we default deny
116            (_, false, false, _) => Some(Decision::Deny),
117            // Potentially true forbids, means we can't know (as that forbid may evaluate to true, overriding any permits)
118            (false, _, _, true) => None,
119            // No true permits, but some potentially true permits + no true/potentially true forbids means we don't know
120            (false, false, true, false) => None,
121            // At least one trivially true permit, and no trivially or possible true forbids, means we allow
122            (false, true, _, false) => Some(Decision::Allow),
123        }
124    }
125
126    /// All of the [`Effect::Permit`] policies that were known to be satisfied
127    fn definitely_satisfied_permits(&self) -> impl Iterator<Item = Policy> + '_ {
128        self.satisfied_permits.iter().map(|(id, annotations)| {
129            construct_policy((Effect::Permit, id, &self.true_expr, annotations))
130        })
131    }
132
133    /// All of the [`Effect::Forbid`] policies that were known to be satisfied
134    fn definitely_satisfied_forbids(&self) -> impl Iterator<Item = Policy> + '_ {
135        self.satisfied_forbids.iter().map(|(id, annotations)| {
136            construct_policy((Effect::Forbid, id, &self.true_expr, annotations))
137        })
138    }
139
140    /// Returns the set of [`PolicyID`]s that were definitely satisfied -- both permits and forbids
141    pub fn definitely_satisfied(&self) -> impl Iterator<Item = Policy> + '_ {
142        self.definitely_satisfied_permits()
143            .chain(self.definitely_satisfied_forbids())
144    }
145
146    /// Returns the set of [`PolicyID`]s that encountered errors
147    pub fn definitely_errored(&self) -> impl Iterator<Item = &PolicyID> {
148        self.false_permits
149            .iter()
150            .chain(self.false_forbids.iter())
151            .filter_map(did_error)
152    }
153
154    /// Returns an over-approximation of the set of determining policies.
155    ///
156    /// This is all policies that may be determining for any substitution of the unknowns.
157    pub fn may_be_determining(&self) -> impl Iterator<Item = Policy> + '_ {
158        if self.satisfied_forbids.is_empty() {
159            // We have no definitely true forbids, so the over approx is everything that is true or potentially true
160            Either::Left(
161                self.definitely_satisfied_permits()
162                    .chain(self.residual_permits())
163                    .chain(self.residual_forbids()),
164            )
165        } else {
166            // We have definitely true forbids, so we know only things that can determine is
167            // true forbids and potentially true forbids
168            Either::Right(
169                self.definitely_satisfied_forbids()
170                    .chain(self.residual_forbids()),
171            )
172        }
173    }
174
175    fn residual_permits(&self) -> impl Iterator<Item = Policy> + '_ {
176        self.residual_permits
177            .iter()
178            .map(|(id, (expr, annotations))| {
179                construct_policy((Effect::Permit, id, expr, annotations))
180            })
181    }
182
183    fn residual_forbids(&self) -> impl Iterator<Item = Policy> + '_ {
184        self.residual_forbids
185            .iter()
186            .map(|(id, (expr, annotations))| {
187                construct_policy((Effect::Forbid, id, expr, annotations))
188            })
189    }
190
191    /// Returns an under-approximation of the set of determining policies.
192    ///
193    /// This is all policies that must be determining for all possible substitutions of the unknowns.
194    pub fn must_be_determining(&self) -> impl Iterator<Item = Policy> + '_ {
195        // If there are no true forbids or potentially true forbids,
196        // then the under approximation is the true permits
197        if self.satisfied_forbids.is_empty() && self.residual_forbids.is_empty() {
198            Either::Left(self.definitely_satisfied_permits())
199        } else {
200            // Otherwise it's the true forbids
201            Either::Right(self.definitely_satisfied_forbids())
202        }
203    }
204
205    /// Returns the set of non-trivial (meaning more than just `true` or `false`) residuals expressions
206    pub fn nontrivial_residuals(&'_ self) -> impl Iterator<Item = Policy> + '_ {
207        self.nontrival_permits().chain(self.nontrival_forbids())
208    }
209
210    /// Returns the set of ids of non-trivial (meaning more than just `true` or `false`) residuals expressions
211    pub fn nontrivial_residual_ids(&self) -> impl Iterator<Item = &PolicyID> {
212        self.residual_permits
213            .keys()
214            .chain(self.residual_forbids.keys())
215    }
216
217    /// Returns the set of non-trivial (meaning more than just `true` or `false`) residuals expressions from [`Effect::Permit`]
218    fn nontrival_permits(&self) -> impl Iterator<Item = Policy> + '_ {
219        self.residual_permits
220            .iter()
221            .map(|(id, (expr, annotations))| {
222                construct_policy((Effect::Permit, id, expr, annotations))
223            })
224    }
225
226    /// Returns the set of non-trivial (meaning more than just `true` or `false`) residuals expressions from [`Effect::Forbid`]
227    pub fn nontrival_forbids(&self) -> impl Iterator<Item = Policy> + '_ {
228        self.residual_forbids
229            .iter()
230            .map(|(id, (expr, annotations))| {
231                construct_policy((Effect::Forbid, id, expr, annotations))
232            })
233    }
234
235    /// Returns every policy residual, including trivial ones
236    pub fn all_residuals(&'_ self) -> impl Iterator<Item = Policy> + '_ {
237        self.all_permit_residuals()
238            .chain(self.all_forbid_residuals())
239            .map(construct_policy)
240    }
241
242    /// Returns all residuals expressions that come from [`Effect::Permit`] policies
243    fn all_permit_residuals(&'_ self) -> impl Iterator<Item = PolicyComponents<'_>> {
244        let trues = self
245            .satisfied_permits
246            .iter()
247            .map(|(id, a)| (id, (&self.true_expr, a)));
248        let falses = self
249            .false_permits
250            .iter()
251            .map(|(id, (_, a))| (id, (&self.false_expr, a)));
252        let nontrivial = self
253            .residual_permits
254            .iter()
255            .map(|(id, (r, a))| (id, (r, a)));
256        trues
257            .chain(falses)
258            .chain(nontrivial)
259            .map(|(id, (r, a))| (Effect::Permit, id, r, a))
260    }
261
262    /// Returns all residuals expressions that come from [`Effect::Forbid`] policies
263    fn all_forbid_residuals(&'_ self) -> impl Iterator<Item = PolicyComponents<'_>> {
264        let trues = self
265            .satisfied_forbids
266            .iter()
267            .map(|(id, a)| (id, (&self.true_expr, a)));
268        let falses = self
269            .false_forbids
270            .iter()
271            .map(|(id, (_, a))| (id, (&self.false_expr, a)));
272        let nontrivial = self
273            .residual_forbids
274            .iter()
275            .map(|(id, (r, a))| (id, (r, a)));
276        trues
277            .chain(falses)
278            .chain(nontrivial)
279            .map(|(id, (r, a))| (Effect::Forbid, id, r, a))
280    }
281
282    /// Return the residual for a given [`PolicyID`], if it exists in the response
283    pub fn get(&self, id: &PolicyID) -> Option<Policy> {
284        self.get_permit(id).or_else(|| self.get_forbid(id))
285    }
286
287    fn get_permit(&self, id: &PolicyID) -> Option<Policy> {
288        self.residual_permits
289            .get(id)
290            .map(|(a, b)| (a, b))
291            .or_else(|| self.satisfied_permits.get(id).map(|a| (&self.true_expr, a)))
292            .or_else(|| {
293                self.false_permits
294                    .get(id)
295                    .map(|(_, a)| (&self.false_expr, a))
296            })
297            .map(|(expr, a)| construct_policy((Effect::Permit, id, expr, a)))
298    }
299
300    fn get_forbid(&self, id: &PolicyID) -> Option<Policy> {
301        self.residual_forbids
302            .get(id)
303            .map(|(a, b)| (a, b))
304            .or_else(|| self.satisfied_forbids.get(id).map(|a| (&self.true_expr, a)))
305            .or_else(|| {
306                self.false_forbids
307                    .get(id)
308                    .map(|(_, a)| (&self.false_expr, a))
309            })
310            .map(|(expr, a)| construct_policy((Effect::Forbid, id, expr, a)))
311    }
312
313    /// Attempt to re-authorize this response given a mapping from unknowns to values
314    pub fn reauthorize(
315        &self,
316        mapping: &HashMap<SmolStr, Value>,
317        auth: &Authorizer,
318        es: &Entities,
319    ) -> Result<Self, ReauthorizationError> {
320        let policyset = self.all_policies(mapping)?;
321        let new_request = self.concretize_request(mapping)?;
322        Ok(auth.is_authorized_core(new_request, &policyset, es))
323    }
324
325    fn all_policies(&self, mapping: &HashMap<SmolStr, Value>) -> Result<PolicySet, PolicySetError> {
326        let mapper = map_unknowns(mapping);
327        PolicySet::try_from_iter(
328            self.all_permit_residuals()
329                .chain(self.all_forbid_residuals())
330                .map(mapper),
331        )
332    }
333
334    fn concretize_request(
335        &self,
336        mapping: &HashMap<SmolStr, Value>,
337    ) -> Result<Request, ConcretizationError> {
338        let mut context = self.request.context.clone();
339
340        let principal = self.request.principal().concretize("principal", mapping)?;
341
342        let action = self.request.action.concretize("action", mapping)?;
343
344        let resource = self.request.resource.concretize("resource", mapping)?;
345
346        if let Some((key, val)) = mapping.get_key_value("context") {
347            if let Ok(attrs) = val.get_as_record() {
348                match self.request.context() {
349                    Some(ctx) => {
350                        return Err(ConcretizationError::VarConfictError {
351                            id: key.to_owned(),
352                            existing_value: ctx.clone().into(),
353                            given_value: val.clone(),
354                        });
355                    }
356                    None => context = Some(Context::Value(attrs.clone())),
357                }
358            } else {
359                return Err(ConcretizationError::ValueError {
360                    id: key.to_owned(),
361                    expected_type: "record",
362                    given_value: val.to_owned(),
363                });
364            }
365        }
366
367        // We need to replace unknowns in the partial context as well
368        context = context
369            .map(|context| context.substitute(mapping))
370            .transpose()?;
371
372        Ok(Request {
373            principal,
374            action,
375            resource,
376            context,
377        })
378    }
379
380    fn errors(self) -> impl Iterator<Item = AuthorizationError> {
381        self.residual_forbids
382            .into_iter()
383            .chain(self.residual_permits)
384            .map(
385                |(id, (expr, _))| AuthorizationError::PolicyEvaluationError {
386                    id,
387                    error: EvaluationError::non_value(expr.as_ref().clone()),
388                },
389            )
390            .chain(self.errors)
391            .collect::<Vec<_>>()
392            .into_iter()
393    }
394}
395
396impl EntityUIDEntry {
397    fn concretize(
398        &self,
399        key: &str,
400        mapping: &HashMap<SmolStr, Value>,
401    ) -> Result<Self, ConcretizationError> {
402        if let Some(val) = mapping.get(key) {
403            if let Ok(uid) = val.get_as_entity() {
404                match self {
405                    EntityUIDEntry::Known { euid, .. } => {
406                        Err(ConcretizationError::VarConfictError {
407                            id: key.into(),
408                            existing_value: euid.as_ref().clone().into(),
409                            given_value: val.clone(),
410                        })
411                    }
412                    EntityUIDEntry::Unknown { ty: None, .. } => {
413                        Ok(EntityUIDEntry::known(uid.clone(), None))
414                    }
415                    EntityUIDEntry::Unknown {
416                        ty: Some(type_of_unknown),
417                        ..
418                    } => {
419                        if type_of_unknown == uid.entity_type() {
420                            Ok(EntityUIDEntry::known(uid.clone(), None))
421                        } else {
422                            Err(ConcretizationError::EntityTypeConfictError {
423                                id: key.into(),
424                                existing_value: type_of_unknown.clone(),
425                                given_value: val.to_owned(),
426                            })
427                        }
428                    }
429                }
430            } else {
431                Err(ConcretizationError::ValueError {
432                    id: key.into(),
433                    expected_type: "entity",
434                    given_value: val.to_owned(),
435                })
436            }
437        } else {
438            Ok(self.clone())
439        }
440    }
441}
442
443impl From<PartialResponse> for Response {
444    fn from(p: PartialResponse) -> Self {
445        let decision = if !p.satisfied_permits.is_empty() && p.satisfied_forbids.is_empty() {
446            Decision::Allow
447        } else {
448            Decision::Deny
449        };
450        Response::new(
451            decision,
452            p.must_be_determining().map(|p| p.id().clone()).collect(),
453            p.errors().collect(),
454        )
455    }
456}
457
458/// Build a policy from a policy components
459fn construct_policy((effect, id, expr, annotations): PolicyComponents<'_>) -> Policy {
460    Policy::from_when_clause_annos(
461        effect,
462        expr.clone(),
463        id.clone(),
464        expr.source_loc().cloned(),
465        (*annotations).clone(),
466    )
467}
468
469/// Given a mapping from unknown names to values and a policy prototype
470/// substitute the residual with the mapping and build a policy.
471/// Curried for convenience
472fn map_unknowns<'a>(
473    mapping: &'a HashMap<SmolStr, Value>,
474) -> impl Fn(PolicyComponents<'a>) -> Policy {
475    |(effect, id, expr, annotations)| {
476        Policy::from_when_clause_annos(
477            effect,
478            Arc::new(expr.substitute(mapping)),
479            id.clone(),
480            expr.source_loc().cloned(),
481            annotations.clone(),
482        )
483    }
484}
485
486/// Checks if a given residual record did error, returning the [`PolicyID`] if it did
487fn did_error<'a>(
488    (id, (state, _)): (&'a PolicyID, &'_ (ErrorState, Arc<Annotations>)),
489) -> Option<&'a PolicyID> {
490    match *state {
491        ErrorState::NoError => None,
492        ErrorState::Error => Some(id),
493    }
494}
495
496#[cfg(test)]
497// PANIC SAFETY testing
498#[allow(clippy::indexing_slicing)]
499mod test {
500    use std::{
501        collections::HashSet,
502        iter::{empty, once},
503    };
504
505    // An extremely slow and bad set, but it only requires that the contents be [`PartialEq`]
506    // Using this because I don't want to enforce an output order on the tests, but policies can't easily be Hash or Ord
507    #[derive(Debug, Default)]
508    struct SlowSet<T> {
509        contents: Vec<T>,
510    }
511
512    impl<T: PartialEq> SlowSet<T> {
513        pub fn from(iter: impl IntoIterator<Item = T>) -> Self {
514            let mut contents = vec![];
515            for item in iter.into_iter() {
516                if !contents.contains(&item) {
517                    contents.push(item)
518                }
519            }
520            Self { contents }
521        }
522
523        pub fn len(&self) -> usize {
524            self.contents.len()
525        }
526
527        pub fn contains(&self, item: &T) -> bool {
528            self.contents.contains(item)
529        }
530    }
531
532    impl<T: PartialEq> PartialEq for SlowSet<T> {
533        fn eq(&self, rhs: &Self) -> bool {
534            if self.len() == rhs.len() {
535                self.contents.iter().all(|item| rhs.contains(item))
536            } else {
537                false
538            }
539        }
540    }
541
542    impl<T: PartialEq> FromIterator<T> for SlowSet<T> {
543        fn from_iter<I>(iter: I) -> Self
544        where
545            I: IntoIterator<Item = T>,
546        {
547            Self::from(iter)
548        }
549    }
550
551    use crate::{
552        authorizer::{
553            ActionConstraint, EntityUID, PrincipalConstraint, ResourceConstraint, RestrictedExpr,
554            Unknown,
555        },
556        extensions::Extensions,
557        parser::parse_policyset,
558        FromNormalizedStr,
559    };
560
561    use super::*;
562
563    #[test]
564    fn sanity_check() {
565        let empty_annotations: Arc<Annotations> = Arc::default();
566        let one_plus_two = Arc::new(Expr::add(Expr::val(1), Expr::val(2)));
567        let three_plus_four = Arc::new(Expr::add(Expr::val(3), Expr::val(4)));
568        let a = once((PolicyID::from_string("a"), empty_annotations.clone()));
569        let bc = [
570            (
571                PolicyID::from_string("b"),
572                (ErrorState::Error, empty_annotations.clone()),
573            ),
574            (
575                PolicyID::from_string("c"),
576                (ErrorState::NoError, empty_annotations.clone()),
577            ),
578        ];
579        let d = once((
580            PolicyID::from_string("d"),
581            (one_plus_two.clone(), empty_annotations.clone()),
582        ));
583        let e = once((PolicyID::from_string("e"), empty_annotations.clone()));
584        let fg = [
585            (
586                PolicyID::from_string("f"),
587                (ErrorState::Error, empty_annotations.clone()),
588            ),
589            (
590                PolicyID::from_string("g"),
591                (ErrorState::NoError, empty_annotations.clone()),
592            ),
593        ];
594        let h = once((
595            PolicyID::from_string("h"),
596            (three_plus_four.clone(), empty_annotations),
597        ));
598        let errs = empty();
599        let pr = PartialResponse::new(
600            a,
601            bc,
602            d,
603            e,
604            fg,
605            h,
606            errs,
607            Arc::new(Request::new_unchecked(
608                EntityUIDEntry::unknown(),
609                EntityUIDEntry::unknown(),
610                EntityUIDEntry::unknown(),
611                Some(Context::empty()),
612            )),
613        );
614
615        let a = Policy::from_when_clause(
616            Effect::Permit,
617            Expr::val(true),
618            PolicyID::from_string("a"),
619            None,
620        );
621        let b = Policy::from_when_clause(
622            Effect::Permit,
623            Expr::val(false),
624            PolicyID::from_string("b"),
625            None,
626        );
627        let c = Policy::from_when_clause(
628            Effect::Permit,
629            Expr::val(false),
630            PolicyID::from_string("c"),
631            None,
632        );
633        let d = Policy::from_when_clause_annos(
634            Effect::Permit,
635            one_plus_two,
636            PolicyID::from_string("d"),
637            None,
638            Arc::default(),
639        );
640        let e = Policy::from_when_clause(
641            Effect::Forbid,
642            Expr::val(true),
643            PolicyID::from_string("e"),
644            None,
645        );
646        let f = Policy::from_when_clause(
647            Effect::Forbid,
648            Expr::val(false),
649            PolicyID::from_string("f"),
650            None,
651        );
652        let g = Policy::from_when_clause(
653            Effect::Forbid,
654            Expr::val(false),
655            PolicyID::from_string("g"),
656            None,
657        );
658        let h = Policy::from_when_clause_annos(
659            Effect::Forbid,
660            three_plus_four,
661            PolicyID::from_string("h"),
662            None,
663            Arc::default(),
664        );
665
666        assert_eq!(
667            pr.definitely_satisfied_permits().collect::<SlowSet<_>>(),
668            SlowSet::from([a.clone()])
669        );
670        assert_eq!(
671            pr.definitely_satisfied_forbids().collect::<SlowSet<_>>(),
672            SlowSet::from([e.clone()])
673        );
674        assert_eq!(
675            pr.definitely_satisfied().collect::<SlowSet<_>>(),
676            SlowSet::from([a.clone(), e.clone()])
677        );
678        assert_eq!(
679            pr.definitely_errored().collect::<HashSet<_>>(),
680            HashSet::from([&PolicyID::from_string("b"), &PolicyID::from_string("f")])
681        );
682        assert_eq!(
683            pr.may_be_determining().collect::<SlowSet<_>>(),
684            SlowSet::from([e.clone(), h.clone()])
685        );
686        assert_eq!(
687            pr.must_be_determining().collect::<SlowSet<_>>(),
688            SlowSet::from([e.clone()])
689        );
690        assert_eq!(pr.nontrivial_residuals().count(), 2);
691
692        assert_eq!(
693            pr.nontrivial_residuals().collect::<SlowSet<_>>(),
694            SlowSet::from([d.clone(), h.clone()])
695        );
696        assert_eq!(
697            pr.all_residuals().collect::<SlowSet<_>>(),
698            SlowSet::from([&a, &b, &c, &d, &e, &f, &g, &h].into_iter().cloned())
699        );
700        assert_eq!(
701            pr.nontrivial_residual_ids().collect::<HashSet<_>>(),
702            HashSet::from([&PolicyID::from_string("d"), &PolicyID::from_string("h")])
703        );
704
705        assert_eq!(pr.get(&PolicyID::from_string("a")), Some(a));
706        assert_eq!(pr.get(&PolicyID::from_string("b")), Some(b));
707        assert_eq!(pr.get(&PolicyID::from_string("c")), Some(c));
708        assert_eq!(pr.get(&PolicyID::from_string("d")), Some(d));
709        assert_eq!(pr.get(&PolicyID::from_string("e")), Some(e));
710        assert_eq!(pr.get(&PolicyID::from_string("f")), Some(f));
711        assert_eq!(pr.get(&PolicyID::from_string("g")), Some(g));
712        assert_eq!(pr.get(&PolicyID::from_string("h")), Some(h));
713        assert_eq!(pr.get(&PolicyID::from_string("i")), None);
714    }
715
716    #[test]
717    fn build_policies_trivial_permit() {
718        let e = Arc::new(Expr::add(Expr::val(1), Expr::val(2)));
719        let id = PolicyID::from_string("foo");
720        let p = construct_policy((Effect::Permit, &id, &e, &Arc::default()));
721        assert_eq!(p.effect(), Effect::Permit);
722        assert!(p.annotations().next().is_none());
723        assert_eq!(p.action_constraint(), &ActionConstraint::Any);
724        assert_eq!(p.principal_constraint(), PrincipalConstraint::any());
725        assert_eq!(p.resource_constraint(), ResourceConstraint::any());
726        assert_eq!(p.id(), &id);
727        assert_eq!(p.non_scope_constraints(), e.as_ref());
728    }
729
730    #[test]
731    fn build_policies_trivial_forbid() {
732        let e = Arc::new(Expr::add(Expr::val(1), Expr::val(2)));
733        let id = PolicyID::from_string("foo");
734        let p = construct_policy((Effect::Forbid, &id, &e, &Arc::default()));
735        assert_eq!(p.effect(), Effect::Forbid);
736        assert!(p.annotations().next().is_none());
737        assert_eq!(p.action_constraint(), &ActionConstraint::Any);
738        assert_eq!(p.principal_constraint(), PrincipalConstraint::any());
739        assert_eq!(p.resource_constraint(), ResourceConstraint::any());
740        assert_eq!(p.id(), &id);
741        assert_eq!(p.non_scope_constraints(), e.as_ref());
742    }
743
744    #[test]
745    fn did_error_error() {
746        assert_eq!(
747            did_error((
748                &PolicyID::from_string("foo"),
749                &(ErrorState::Error, Arc::default())
750            )),
751            Some(&PolicyID::from_string("foo"))
752        );
753    }
754
755    #[test]
756    fn did_error_noerror() {
757        assert_eq!(
758            did_error((
759                &PolicyID::from_string("foo"),
760                &(ErrorState::NoError, Arc::default())
761            )),
762            None,
763        );
764    }
765
766    #[test]
767    fn reauthorize() {
768        let policies = parse_policyset(
769            r#"
770            permit(principal, action, resource) when {
771                principal == NS::"a" && resource == NS::"b"
772            };
773            forbid(principal, action, resource) when {
774                context.b
775            };
776        "#,
777        )
778        .unwrap();
779
780        let context_unknown = Context::from_pairs(
781            std::iter::once((
782                "b".into(),
783                RestrictedExpr::unknown(Unknown::new_untyped("b")),
784            )),
785            Extensions::all_available(),
786        )
787        .unwrap();
788
789        let partial_request = Request {
790            principal: EntityUIDEntry::known(r#"NS::"a""#.parse().unwrap(), None),
791            action: EntityUIDEntry::unknown(),
792            resource: EntityUIDEntry::unknown(),
793            context: Some(context_unknown),
794        };
795
796        let entities = Entities::new();
797
798        let authorizer = Authorizer::new();
799        let partial_response = authorizer.is_authorized_core(partial_request, &policies, &entities);
800
801        let response_with_concrete_resource = partial_response
802            .reauthorize(
803                &HashMap::from_iter(std::iter::once((
804                    "resource".into(),
805                    EntityUID::from_normalized_str(r#"NS::"b""#).unwrap().into(),
806                ))),
807                &authorizer,
808                &entities,
809            )
810            .unwrap();
811
812        assert_eq!(
813            response_with_concrete_resource
814                .definitely_satisfied()
815                .next()
816                .unwrap()
817                .effect(),
818            Effect::Permit
819        );
820
821        let response_with_concrete_context_attr = response_with_concrete_resource
822            .reauthorize(
823                &HashMap::from_iter(std::iter::once(("b".into(), true.into()))),
824                &authorizer,
825                &entities,
826            )
827            .unwrap();
828
829        assert_eq!(
830            response_with_concrete_context_attr.decision(),
831            Some(Decision::Deny)
832        );
833    }
834}