1use 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#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
35pub enum ErrorState {
36 NoError,
38 Error,
40}
41
42#[derive(Debug, Clone)]
48pub struct PartialResponse {
49 pub satisfied_permits: HashMap<PolicyID, Arc<Annotations>>,
51 pub false_permits: HashMap<PolicyID, (ErrorState, Arc<Annotations>)>,
53 pub residual_permits: HashMap<PolicyID, (Arc<Expr>, Arc<Annotations>)>,
55 pub satisfied_forbids: HashMap<PolicyID, Arc<Annotations>>,
57 pub false_forbids: HashMap<PolicyID, (ErrorState, Arc<Annotations>)>,
59 pub residual_forbids: HashMap<PolicyID, (Arc<Expr>, Arc<Annotations>)>,
61 pub errors: Vec<AuthorizationError>,
63 true_expr: Arc<Expr>,
65 false_expr: Arc<Expr>,
67 request: Arc<Request>,
69}
70
71impl PartialResponse {
72 #[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 pub fn concretize(self) -> Response {
101 self.into()
102 }
103
104 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 (true, _, _, _) => Some(Decision::Deny),
115 (_, false, false, _) => Some(Decision::Deny),
117 (false, _, _, true) => None,
119 (false, false, true, false) => None,
121 (false, true, _, false) => Some(Decision::Allow),
123 }
124 }
125
126 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 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 pub fn definitely_satisfied(&self) -> impl Iterator<Item = Policy> + '_ {
142 self.definitely_satisfied_permits()
143 .chain(self.definitely_satisfied_forbids())
144 }
145
146 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 pub fn may_be_determining(&self) -> impl Iterator<Item = Policy> + '_ {
158 if self.satisfied_forbids.is_empty() {
159 Either::Left(
161 self.definitely_satisfied_permits()
162 .chain(self.residual_permits())
163 .chain(self.residual_forbids()),
164 )
165 } else {
166 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 pub fn must_be_determining(&self) -> impl Iterator<Item = Policy> + '_ {
195 if self.satisfied_forbids.is_empty() && self.residual_forbids.is_empty() {
198 Either::Left(self.definitely_satisfied_permits())
199 } else {
200 Either::Right(self.definitely_satisfied_forbids())
202 }
203 }
204
205 pub fn nontrivial_residuals(&'_ self) -> impl Iterator<Item = Policy> + '_ {
207 self.nontrival_permits().chain(self.nontrival_forbids())
208 }
209
210 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 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 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 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 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 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 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 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 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
458fn 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
469fn 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
486fn 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#[allow(clippy::indexing_slicing)]
499mod test {
500 use std::{
501 collections::HashSet,
502 iter::{empty, once},
503 };
504
505 #[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}