1use std::collections::HashMap;
18
19use either::Either;
20use std::sync::Arc;
21
22use super::{
23 Annotations, AuthorizationError, Decision, Effect, EntityUIDEntry, Expr, Policy, Request,
24 Response,
25};
26use crate::{ast::PolicyID, evaluator::EvaluationError};
27
28#[cfg(feature = "partial-eval")]
29use smol_str::SmolStr;
30
31#[cfg(feature = "partial-eval")]
32use crate::{entities::Entities, evaluator::Evaluator};
33
34#[cfg(feature = "partial-eval")]
35use super::{
36 err::{ConcretizationError, ReauthorizationError},
37 Authorizer, Context, PolicySet, PolicySetError, Value,
38};
39
40type PolicyComponents<'a> = (Effect, &'a PolicyID, &'a Arc<Expr>, &'a Arc<Annotations>);
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
45pub enum ErrorState {
46 NoError,
48 Error,
50}
51
52#[derive(Debug, Clone)]
58pub struct PartialResponse {
59 pub satisfied_permits: HashMap<PolicyID, Arc<Annotations>>,
61 pub false_permits: HashMap<PolicyID, (ErrorState, Arc<Annotations>)>,
63 pub residual_permits: HashMap<PolicyID, (Arc<Expr>, Arc<Annotations>)>,
65 pub satisfied_forbids: HashMap<PolicyID, Arc<Annotations>>,
67 pub false_forbids: HashMap<PolicyID, (ErrorState, Arc<Annotations>)>,
69 pub residual_forbids: HashMap<PolicyID, (Arc<Expr>, Arc<Annotations>)>,
71 pub errors: Vec<AuthorizationError>,
73 true_expr: Arc<Expr>,
75 false_expr: Arc<Expr>,
77 #[cfg(feature = "partial-eval")]
79 request: Arc<Request>,
80}
81
82impl PartialResponse {
83 #[expect(
85 clippy::too_many_arguments,
86 reason = "maybe in the future we could group these arguments into single-use structs"
87 )]
88 pub fn new(
89 true_permits: impl IntoIterator<Item = (PolicyID, Arc<Annotations>)>,
90 false_permits: impl IntoIterator<Item = (PolicyID, (ErrorState, Arc<Annotations>))>,
91 residual_permits: impl IntoIterator<Item = (PolicyID, (Arc<Expr>, Arc<Annotations>))>,
92 true_forbids: impl IntoIterator<Item = (PolicyID, Arc<Annotations>)>,
93 false_forbids: impl IntoIterator<Item = (PolicyID, (ErrorState, Arc<Annotations>))>,
94 residual_forbids: impl IntoIterator<Item = (PolicyID, (Arc<Expr>, Arc<Annotations>))>,
95 errors: impl IntoIterator<Item = AuthorizationError>,
96 _request: Arc<Request>,
97 ) -> Self {
98 Self {
99 satisfied_permits: true_permits.into_iter().collect(),
100 false_permits: false_permits.into_iter().collect(),
101 residual_permits: residual_permits.into_iter().collect(),
102 satisfied_forbids: true_forbids.into_iter().collect(),
103 false_forbids: false_forbids.into_iter().collect(),
104 residual_forbids: residual_forbids.into_iter().collect(),
105 errors: errors.into_iter().collect(),
106 true_expr: Arc::new(Expr::val(true)),
107 false_expr: Arc::new(Expr::val(false)),
108 #[cfg(feature = "partial-eval")]
109 request: _request,
110 }
111 }
112
113 pub fn concretize(self) -> Response {
116 self.into()
117 }
118
119 pub fn decision(&self) -> Option<Decision> {
122 match (
123 !self.satisfied_forbids.is_empty(),
124 !self.satisfied_permits.is_empty(),
125 !self.residual_permits.is_empty(),
126 !self.residual_forbids.is_empty(),
127 ) {
128 (true, _, _, _) => Some(Decision::Deny),
130 (_, false, false, _) => Some(Decision::Deny),
132 (false, _, _, true) => None,
134 (false, false, true, false) => None,
136 (false, true, _, false) => Some(Decision::Allow),
138 }
139 }
140
141 fn definitely_satisfied_permits(&self) -> impl Iterator<Item = Policy> + '_ {
143 self.satisfied_permits.iter().map(|(id, annotations)| {
144 construct_policy((Effect::Permit, id, &self.true_expr, annotations))
145 })
146 }
147
148 fn definitely_satisfied_forbids(&self) -> impl Iterator<Item = Policy> + '_ {
150 self.satisfied_forbids.iter().map(|(id, annotations)| {
151 construct_policy((Effect::Forbid, id, &self.true_expr, annotations))
152 })
153 }
154
155 pub fn definitely_satisfied(&self) -> impl Iterator<Item = Policy> + '_ {
157 self.definitely_satisfied_permits()
158 .chain(self.definitely_satisfied_forbids())
159 }
160
161 pub fn definitely_errored(&self) -> impl Iterator<Item = &PolicyID> {
163 self.false_permits
164 .iter()
165 .chain(self.false_forbids.iter())
166 .filter_map(did_error)
167 }
168
169 pub fn may_be_determining(&self) -> impl Iterator<Item = Policy> + '_ {
173 if self.satisfied_forbids.is_empty() {
174 Either::Left(
176 self.definitely_satisfied_permits()
177 .chain(self.residual_permits())
178 .chain(self.residual_forbids()),
179 )
180 } else {
181 Either::Right(
184 self.definitely_satisfied_forbids()
185 .chain(self.residual_forbids()),
186 )
187 }
188 }
189
190 fn residual_permits(&self) -> impl Iterator<Item = Policy> + '_ {
191 self.residual_permits
192 .iter()
193 .map(|(id, (expr, annotations))| {
194 construct_policy((Effect::Permit, id, expr, annotations))
195 })
196 }
197
198 fn residual_forbids(&self) -> impl Iterator<Item = Policy> + '_ {
199 self.residual_forbids
200 .iter()
201 .map(|(id, (expr, annotations))| {
202 construct_policy((Effect::Forbid, id, expr, annotations))
203 })
204 }
205
206 pub fn must_be_determining(&self) -> impl Iterator<Item = Policy> + '_ {
210 if self.satisfied_forbids.is_empty() && self.residual_forbids.is_empty() {
213 Either::Left(self.definitely_satisfied_permits())
214 } else {
215 Either::Right(self.definitely_satisfied_forbids())
217 }
218 }
219
220 pub fn nontrivial_residuals(&'_ self) -> impl Iterator<Item = Policy> + '_ {
222 self.nontrivial_permits().chain(self.nontrivial_forbids())
223 }
224
225 pub fn nontrivial_residual_ids(&self) -> impl Iterator<Item = &PolicyID> {
227 self.residual_permits
228 .keys()
229 .chain(self.residual_forbids.keys())
230 }
231
232 fn nontrivial_permits(&self) -> impl Iterator<Item = Policy> + '_ {
234 self.residual_permits
235 .iter()
236 .map(|(id, (expr, annotations))| {
237 construct_policy((Effect::Permit, id, expr, annotations))
238 })
239 }
240
241 pub fn nontrivial_forbids(&self) -> impl Iterator<Item = Policy> + '_ {
243 self.residual_forbids
244 .iter()
245 .map(|(id, (expr, annotations))| {
246 construct_policy((Effect::Forbid, id, expr, annotations))
247 })
248 }
249
250 pub fn all_residuals(&'_ self) -> impl Iterator<Item = Policy> + '_ {
252 self.all_permit_residuals()
253 .chain(self.all_forbid_residuals())
254 .map(construct_policy)
255 }
256
257 fn all_permit_residuals(&'_ self) -> impl Iterator<Item = PolicyComponents<'_>> {
259 let trues = self
260 .satisfied_permits
261 .iter()
262 .map(|(id, a)| (id, (&self.true_expr, a)));
263 let falses = self
264 .false_permits
265 .iter()
266 .map(|(id, (_, a))| (id, (&self.false_expr, a)));
267 let nontrivial = self
268 .residual_permits
269 .iter()
270 .map(|(id, (r, a))| (id, (r, a)));
271 trues
272 .chain(falses)
273 .chain(nontrivial)
274 .map(|(id, (r, a))| (Effect::Permit, id, r, a))
275 }
276
277 fn all_forbid_residuals(&'_ self) -> impl Iterator<Item = PolicyComponents<'_>> {
279 let trues = self
280 .satisfied_forbids
281 .iter()
282 .map(|(id, a)| (id, (&self.true_expr, a)));
283 let falses = self
284 .false_forbids
285 .iter()
286 .map(|(id, (_, a))| (id, (&self.false_expr, a)));
287 let nontrivial = self
288 .residual_forbids
289 .iter()
290 .map(|(id, (r, a))| (id, (r, a)));
291 trues
292 .chain(falses)
293 .chain(nontrivial)
294 .map(|(id, (r, a))| (Effect::Forbid, id, r, a))
295 }
296
297 pub fn get(&self, id: &PolicyID) -> Option<Policy> {
299 self.get_permit(id).or_else(|| self.get_forbid(id))
300 }
301
302 fn get_permit(&self, id: &PolicyID) -> Option<Policy> {
303 self.residual_permits
304 .get(id)
305 .map(|(a, b)| (a, b))
306 .or_else(|| self.satisfied_permits.get(id).map(|a| (&self.true_expr, a)))
307 .or_else(|| {
308 self.false_permits
309 .get(id)
310 .map(|(_, a)| (&self.false_expr, a))
311 })
312 .map(|(expr, a)| construct_policy((Effect::Permit, id, expr, a)))
313 }
314
315 fn get_forbid(&self, id: &PolicyID) -> Option<Policy> {
316 self.residual_forbids
317 .get(id)
318 .map(|(a, b)| (a, b))
319 .or_else(|| self.satisfied_forbids.get(id).map(|a| (&self.true_expr, a)))
320 .or_else(|| {
321 self.false_forbids
322 .get(id)
323 .map(|(_, a)| (&self.false_expr, a))
324 })
325 .map(|(expr, a)| construct_policy((Effect::Forbid, id, expr, a)))
326 }
327
328 #[cfg(feature = "partial-eval")]
330 pub fn reauthorize(
331 &self,
332 mapping: &HashMap<SmolStr, Value>,
333 auth: &Authorizer,
334 es: &Entities,
335 ) -> Result<Self, ReauthorizationError> {
336 let policyset = self.all_residual_policies()?;
337 let new_request = self.concretize_request(mapping)?;
338 let unknowns_mapper =
340 |unknown_name: &str| -> Option<Value> { mapping.get(unknown_name).cloned() };
341 let eval = Evaluator::new(new_request.clone(), es, auth.extensions)
343 .with_unknowns_mapper(Box::new(unknowns_mapper));
344 Ok(auth.is_authorized_core_internal(&eval, new_request, &policyset))
345 }
346
347 #[cfg(feature = "partial-eval")]
348 fn all_residual_policies(&self) -> Result<PolicySet, PolicySetError> {
349 PolicySet::try_from_iter(
350 self.all_permit_residuals()
351 .chain(self.all_forbid_residuals())
352 .map(|(effect, id, expr, annotations)| {
353 Policy::from_when_clause_annos(
354 effect,
355 expr.clone(),
356 id.clone(),
357 expr.source_loc().cloned(),
358 annotations.clone(),
359 )
360 }),
361 )
362 }
363
364 #[cfg(feature = "partial-eval")]
365 fn concretize_request(
366 &self,
367 mapping: &HashMap<SmolStr, Value>,
368 ) -> Result<Request, ConcretizationError> {
369 let mut context = self.request.context.clone();
370
371 let principal = self.request.principal().concretize("principal", mapping)?;
372
373 let action = self.request.action.concretize("action", mapping)?;
374
375 let resource = self.request.resource.concretize("resource", mapping)?;
376
377 if let Some((key, val)) = mapping.get_key_value("context") {
378 if let Ok(attrs) = val.get_as_record() {
379 match self.request.context() {
380 Some(ctx) => {
381 return Err(ConcretizationError::VarConfictError {
382 id: key.to_owned(),
383 existing_value: ctx.clone().into(),
384 given_value: val.clone(),
385 });
386 }
387 None => context = Some(Context::Value(attrs.clone())),
388 }
389 } else {
390 return Err(ConcretizationError::ValueError {
391 id: key.to_owned(),
392 expected_type: "record",
393 given_value: val.to_owned(),
394 });
395 }
396 }
397
398 context = context
400 .map(|context| context.substitute(mapping))
401 .transpose()?;
402
403 Ok(Request {
404 principal,
405 action,
406 resource,
407 context,
408 })
409 }
410
411 fn errors(self) -> impl Iterator<Item = AuthorizationError> {
412 self.residual_forbids
413 .into_iter()
414 .chain(self.residual_permits)
415 .map(
416 |(id, (expr, _))| AuthorizationError::PolicyEvaluationError {
417 id,
418 error: EvaluationError::non_value(expr.as_ref().clone()),
419 },
420 )
421 .chain(self.errors)
422 .collect::<Vec<_>>()
423 .into_iter()
424 }
425}
426
427impl EntityUIDEntry {
428 #[cfg(feature = "partial-eval")]
429 fn concretize(
430 &self,
431 key: &str,
432 mapping: &HashMap<SmolStr, Value>,
433 ) -> Result<Self, ConcretizationError> {
434 if let Some(val) = mapping.get(key) {
435 if let Ok(uid) = val.get_as_entity() {
436 match self {
437 EntityUIDEntry::Known { euid, .. } => {
438 Err(ConcretizationError::VarConfictError {
439 id: key.into(),
440 existing_value: euid.as_ref().clone().into(),
441 given_value: val.clone(),
442 })
443 }
444 EntityUIDEntry::Unknown { ty: None, .. } => {
445 Ok(EntityUIDEntry::known(uid.clone(), None))
446 }
447 EntityUIDEntry::Unknown {
448 ty: Some(type_of_unknown),
449 ..
450 } => {
451 if type_of_unknown == uid.entity_type() {
452 Ok(EntityUIDEntry::known(uid.clone(), None))
453 } else {
454 Err(ConcretizationError::EntityTypeConfictError {
455 id: key.into(),
456 existing_value: type_of_unknown.clone(),
457 given_value: val.to_owned(),
458 })
459 }
460 }
461 }
462 } else {
463 Err(ConcretizationError::ValueError {
464 id: key.into(),
465 expected_type: "entity",
466 given_value: val.to_owned(),
467 })
468 }
469 } else {
470 Ok(self.clone())
471 }
472 }
473}
474
475impl From<PartialResponse> for Response {
476 fn from(p: PartialResponse) -> Self {
477 let decision = if !p.satisfied_permits.is_empty() && p.satisfied_forbids.is_empty() {
478 Decision::Allow
479 } else {
480 Decision::Deny
481 };
482 Response::new(
483 decision,
484 p.must_be_determining().map(|p| p.id().clone()).collect(),
485 p.errors().collect(),
486 )
487 }
488}
489
490fn construct_policy((effect, id, expr, annotations): PolicyComponents<'_>) -> Policy {
492 Policy::from_when_clause_annos(
493 effect,
494 expr.clone(),
495 id.clone(),
496 expr.source_loc().cloned(),
497 (*annotations).clone(),
498 )
499}
500
501fn did_error<'a>(
503 (id, (state, _)): (&'a PolicyID, &'_ (ErrorState, Arc<Annotations>)),
504) -> Option<&'a PolicyID> {
505 match *state {
506 ErrorState::NoError => None,
507 ErrorState::Error => Some(id),
508 }
509}
510
511#[cfg(test)]
512mod test {
513 use std::{
514 collections::HashSet,
515 iter::{empty, once},
516 };
517
518 #[derive(Debug, Default)]
521 struct SlowSet<T> {
522 contents: Vec<T>,
523 }
524
525 impl<T: PartialEq> SlowSet<T> {
526 pub fn from(iter: impl IntoIterator<Item = T>) -> Self {
527 let mut contents = vec![];
528 for item in iter.into_iter() {
529 if !contents.contains(&item) {
530 contents.push(item)
531 }
532 }
533 Self { contents }
534 }
535
536 pub fn len(&self) -> usize {
537 self.contents.len()
538 }
539
540 pub fn contains(&self, item: &T) -> bool {
541 self.contents.contains(item)
542 }
543 }
544
545 impl<T: PartialEq> PartialEq for SlowSet<T> {
546 fn eq(&self, rhs: &Self) -> bool {
547 if self.len() == rhs.len() {
548 self.contents.iter().all(|item| rhs.contains(item))
549 } else {
550 false
551 }
552 }
553 }
554
555 impl<T: PartialEq> FromIterator<T> for SlowSet<T> {
556 fn from_iter<I>(iter: I) -> Self
557 where
558 I: IntoIterator<Item = T>,
559 {
560 Self::from(iter)
561 }
562 }
563
564 use crate::authorizer::{ActionConstraint, Context, PrincipalConstraint, ResourceConstraint};
565
566 #[cfg(feature = "partial-eval")]
567 use crate::{
568 authorizer::{EntityUID, RestrictedExpr, Unknown},
569 extensions::Extensions,
570 parser::parse_policyset,
571 FromNormalizedStr,
572 };
573
574 use super::*;
575
576 #[test]
577 fn sanity_check() {
578 let empty_annotations: Arc<Annotations> = Arc::default();
579 let one_plus_two = Arc::new(Expr::add(Expr::val(1), Expr::val(2)));
580 let three_plus_four = Arc::new(Expr::add(Expr::val(3), Expr::val(4)));
581 let a = once((PolicyID::from_string("a"), empty_annotations.clone()));
582 let bc = [
583 (
584 PolicyID::from_string("b"),
585 (ErrorState::Error, empty_annotations.clone()),
586 ),
587 (
588 PolicyID::from_string("c"),
589 (ErrorState::NoError, empty_annotations.clone()),
590 ),
591 ];
592 let d = once((
593 PolicyID::from_string("d"),
594 (one_plus_two.clone(), empty_annotations.clone()),
595 ));
596 let e = once((PolicyID::from_string("e"), empty_annotations.clone()));
597 let fg = [
598 (
599 PolicyID::from_string("f"),
600 (ErrorState::Error, empty_annotations.clone()),
601 ),
602 (
603 PolicyID::from_string("g"),
604 (ErrorState::NoError, empty_annotations.clone()),
605 ),
606 ];
607 let h = once((
608 PolicyID::from_string("h"),
609 (three_plus_four.clone(), empty_annotations),
610 ));
611 let errs = empty();
612 let pr = PartialResponse::new(
613 a,
614 bc,
615 d,
616 e,
617 fg,
618 h,
619 errs,
620 Arc::new(Request::new_unchecked(
621 EntityUIDEntry::unknown(),
622 EntityUIDEntry::unknown(),
623 EntityUIDEntry::unknown(),
624 Some(Context::empty()),
625 )),
626 );
627
628 let a = Policy::from_when_clause(
629 Effect::Permit,
630 Expr::val(true),
631 PolicyID::from_string("a"),
632 None,
633 );
634 let b = Policy::from_when_clause(
635 Effect::Permit,
636 Expr::val(false),
637 PolicyID::from_string("b"),
638 None,
639 );
640 let c = Policy::from_when_clause(
641 Effect::Permit,
642 Expr::val(false),
643 PolicyID::from_string("c"),
644 None,
645 );
646 let d = Policy::from_when_clause_annos(
647 Effect::Permit,
648 one_plus_two,
649 PolicyID::from_string("d"),
650 None,
651 Arc::default(),
652 );
653 let e = Policy::from_when_clause(
654 Effect::Forbid,
655 Expr::val(true),
656 PolicyID::from_string("e"),
657 None,
658 );
659 let f = Policy::from_when_clause(
660 Effect::Forbid,
661 Expr::val(false),
662 PolicyID::from_string("f"),
663 None,
664 );
665 let g = Policy::from_when_clause(
666 Effect::Forbid,
667 Expr::val(false),
668 PolicyID::from_string("g"),
669 None,
670 );
671 let h = Policy::from_when_clause_annos(
672 Effect::Forbid,
673 three_plus_four,
674 PolicyID::from_string("h"),
675 None,
676 Arc::default(),
677 );
678
679 assert_eq!(
680 pr.definitely_satisfied_permits().collect::<SlowSet<_>>(),
681 SlowSet::from([a.clone()])
682 );
683 assert_eq!(
684 pr.definitely_satisfied_forbids().collect::<SlowSet<_>>(),
685 SlowSet::from([e.clone()])
686 );
687 assert_eq!(
688 pr.definitely_satisfied().collect::<SlowSet<_>>(),
689 SlowSet::from([a.clone(), e.clone()])
690 );
691 assert_eq!(
692 pr.definitely_errored().collect::<HashSet<_>>(),
693 HashSet::from([&PolicyID::from_string("b"), &PolicyID::from_string("f")])
694 );
695 assert_eq!(
696 pr.may_be_determining().collect::<SlowSet<_>>(),
697 SlowSet::from([e.clone(), h.clone()])
698 );
699 assert_eq!(
700 pr.must_be_determining().collect::<SlowSet<_>>(),
701 SlowSet::from([e.clone()])
702 );
703 assert_eq!(pr.nontrivial_residuals().count(), 2);
704
705 assert_eq!(
706 pr.nontrivial_residuals().collect::<SlowSet<_>>(),
707 SlowSet::from([d.clone(), h.clone()])
708 );
709 assert_eq!(
710 pr.all_residuals().collect::<SlowSet<_>>(),
711 SlowSet::from([&a, &b, &c, &d, &e, &f, &g, &h].into_iter().cloned())
712 );
713 assert_eq!(
714 pr.nontrivial_residual_ids().collect::<HashSet<_>>(),
715 HashSet::from([&PolicyID::from_string("d"), &PolicyID::from_string("h")])
716 );
717
718 assert_eq!(pr.get(&PolicyID::from_string("a")), Some(a));
719 assert_eq!(pr.get(&PolicyID::from_string("b")), Some(b));
720 assert_eq!(pr.get(&PolicyID::from_string("c")), Some(c));
721 assert_eq!(pr.get(&PolicyID::from_string("d")), Some(d));
722 assert_eq!(pr.get(&PolicyID::from_string("e")), Some(e));
723 assert_eq!(pr.get(&PolicyID::from_string("f")), Some(f));
724 assert_eq!(pr.get(&PolicyID::from_string("g")), Some(g));
725 assert_eq!(pr.get(&PolicyID::from_string("h")), Some(h));
726 assert_eq!(pr.get(&PolicyID::from_string("i")), None);
727 }
728
729 #[test]
730 fn build_policies_trivial_permit() {
731 let e = Arc::new(Expr::add(Expr::val(1), Expr::val(2)));
732 let id = PolicyID::from_string("foo");
733 let p = construct_policy((Effect::Permit, &id, &e, &Arc::default()));
734 assert_eq!(p.effect(), Effect::Permit);
735 assert!(p.annotations().next().is_none());
736 assert_eq!(p.action_constraint(), &ActionConstraint::Any);
737 assert_eq!(p.principal_constraint(), PrincipalConstraint::any());
738 assert_eq!(p.resource_constraint(), ResourceConstraint::any());
739 assert_eq!(p.id(), &id);
740 assert_eq!(p.non_scope_constraints(), Some(e.as_ref()));
741 }
742
743 #[test]
744 fn build_policies_trivial_forbid() {
745 let e = Arc::new(Expr::add(Expr::val(1), Expr::val(2)));
746 let id = PolicyID::from_string("foo");
747 let p = construct_policy((Effect::Forbid, &id, &e, &Arc::default()));
748 assert_eq!(p.effect(), Effect::Forbid);
749 assert!(p.annotations().next().is_none());
750 assert_eq!(p.action_constraint(), &ActionConstraint::Any);
751 assert_eq!(p.principal_constraint(), PrincipalConstraint::any());
752 assert_eq!(p.resource_constraint(), ResourceConstraint::any());
753 assert_eq!(p.id(), &id);
754 assert_eq!(p.non_scope_constraints(), Some(e.as_ref()));
755 }
756
757 #[test]
758 fn did_error_error() {
759 assert_eq!(
760 did_error((
761 &PolicyID::from_string("foo"),
762 &(ErrorState::Error, Arc::default())
763 )),
764 Some(&PolicyID::from_string("foo"))
765 );
766 }
767
768 #[test]
769 fn did_error_noerror() {
770 assert_eq!(
771 did_error((
772 &PolicyID::from_string("foo"),
773 &(ErrorState::NoError, Arc::default())
774 )),
775 None,
776 );
777 }
778
779 #[test]
780 #[cfg(feature = "partial-eval")]
781 fn reauthorize() {
782 let policies = parse_policyset(
783 r#"
784 permit(principal, action, resource) when {
785 principal == NS::"a" && resource == NS::"b"
786 };
787 forbid(principal, action, resource) when {
788 context.c && context.d == 10
789 };
790 "#,
791 )
792 .unwrap();
793
794 let context_unknown = Context::from_pairs(
795 [
796 (
797 "c".into(),
798 RestrictedExpr::unknown(Unknown::new_untyped("c")),
799 ),
800 (
801 "d".into(),
802 RestrictedExpr::unknown(Unknown::new_untyped("d")),
803 ),
804 ],
805 Extensions::all_available(),
806 )
807 .unwrap();
808
809 let partial_request = Request {
810 principal: EntityUIDEntry::known(r#"NS::"a""#.parse().unwrap(), None),
811 action: EntityUIDEntry::unknown(),
812 resource: EntityUIDEntry::unknown(),
813 context: Some(context_unknown),
814 };
815
816 let entities = Entities::new();
817
818 let authorizer = Authorizer::new();
819 let partial_response = authorizer.is_authorized_core(partial_request, &policies, &entities);
820
821 let response_with_concrete_resource = partial_response
822 .reauthorize(
823 &HashMap::from([(
824 "resource".into(),
825 EntityUID::from_normalized_str(r#"NS::"b""#).unwrap().into(),
826 )]),
827 &authorizer,
828 &entities,
829 )
830 .unwrap();
831
832 assert_eq!(
833 response_with_concrete_resource
834 .get(&PolicyID::from_string("policy0"))
835 .map(|p| p.effect()),
836 Some(Effect::Permit)
837 );
838
839 let response_with_concrete_context_attr = response_with_concrete_resource
840 .reauthorize(
841 &HashMap::from([("c".into(), true.into()), ("d".into(), 10.into())]),
842 &authorizer,
843 &entities,
844 )
845 .unwrap();
846
847 assert_eq!(
849 response_with_concrete_context_attr.decision(),
850 Some(Decision::Deny)
851 );
852 }
853}