1use crate::{
20 ast::{
21 self, ActionConstraint, EntityReference, EntityUID, Policy, PolicyID, PrincipalConstraint,
22 PrincipalOrResourceConstraint, ResourceConstraint, SlotEnv, Template,
23 },
24 entities::conformance::is_valid_enumerated_entity,
25 parser::Loc,
26 validator::validation_errors::get_suggested_entity_type,
27};
28
29use std::{collections::HashSet, sync::Arc};
30
31use crate::validator::{
32 expr_iterator::{policy_entity_type_names, policy_entity_uids},
33 validation_errors::unrecognized_action_id_help,
34 ValidationError,
35};
36
37use super::{schema::*, Validator};
38
39impl Validator {
40 pub fn validate_enum_entity<'a>(
43 schema: &'a ValidatorSchema,
44 template: &'a Template,
45 ) -> impl Iterator<Item = ValidationError> + 'a {
46 policy_entity_uids(template)
47 .filter(|e| !e.is_action())
48 .filter_map(|e: &EntityUID| {
49 if let Some(ValidatorEntityType {
50 kind: ValidatorEntityTypeKind::Enum(choices),
51 ..
52 }) = schema.get_entity_type(e.entity_type())
53 {
54 match is_valid_enumerated_entity(choices, e) {
55 Ok(_) => {}
56 Err(err) => {
57 return Some(ValidationError::invalid_enum_entity(
58 e.loc().cloned(),
59 template.id().clone(),
60 err,
61 ));
62 }
63 };
64 }
65 None
66 })
67 }
68 pub fn validate_entity_types<'a>(
71 schema: &'a ValidatorSchema,
72 template: &'a Template,
73 ) -> impl Iterator<Item = ValidationError> + 'a {
74 policy_entity_type_names(template)
75 .filter(|ety| !schema.is_known_entity_type(ety))
76 .map(|ety| {
77 ValidationError::unrecognized_entity_type(
78 ety.loc().cloned(),
79 template.id().clone(),
80 ety.to_string(),
81 get_suggested_entity_type(ety, schema),
82 )
83 })
84 }
85
86 pub fn validate_action_ids<'a>(
90 schema: &'a ValidatorSchema,
91 template: &'a Template,
92 ) -> impl Iterator<Item = ValidationError> + 'a {
93 policy_entity_uids(template).filter_map(move |euid| {
94 let entity_type = euid.entity_type();
95 if entity_type.is_action() && !schema.is_known_action_id(euid) {
96 Some(ValidationError::unrecognized_action_id(
97 euid.loc().cloned(),
98 template.id().clone(),
99 euid.to_string(),
100 unrecognized_action_id_help(euid, schema),
101 ))
102 } else {
103 None
104 }
105 })
106 }
107
108 pub(crate) fn validate_entity_types_in_slots<'a>(
111 &'a self,
112 policy_id: &'a PolicyID,
113 slots: &'a SlotEnv,
114 ) -> impl Iterator<Item = ValidationError> + 'a {
115 slots
116 .values()
117 .filter(|euid| !self.schema.is_known_entity_type(euid.entity_type()))
118 .map(|euid| {
119 ValidationError::unrecognized_entity_type(
120 None,
121 policy_id.clone(),
122 euid.entity_type().to_string(),
123 get_suggested_entity_type(euid.entity_type(), &self.schema),
124 )
125 })
126 }
127
128 fn check_if_in_fixes_principal(
129 &self,
130 principal_constraint: &PrincipalConstraint,
131 action_constraint: &ActionConstraint,
132 ) -> bool {
133 self.check_if_in_fixes(
134 principal_constraint.as_inner(),
135 &self
136 .get_apply_specs_for_action(action_constraint)
137 .collect::<Vec<_>>(),
138 &|spec| Box::new(spec.applicable_principal_types()),
139 )
140 }
141
142 fn check_if_in_fixes_resource(
143 &self,
144 resource_constraint: &ResourceConstraint,
145 action_constraint: &ActionConstraint,
146 ) -> bool {
147 self.check_if_in_fixes(
148 resource_constraint.as_inner(),
149 &self
150 .get_apply_specs_for_action(action_constraint)
151 .collect::<Vec<_>>(),
152 &|spec| Box::new(spec.applicable_resource_types()),
153 )
154 }
155
156 fn check_if_in_fixes<'a>(
157 &'a self,
158 scope_constraint: &PrincipalOrResourceConstraint,
159 apply_specs: &[&'a ValidatorApplySpec<ast::EntityType>],
160 select_apply_spec: &impl Fn(
161 &'a ValidatorApplySpec<ast::EntityType>,
162 ) -> Box<dyn Iterator<Item = &'a ast::EntityType> + 'a>,
163 ) -> bool {
164 let entity_type = Validator::get_eq_comparison(scope_constraint);
165
166 self.check_if_none_equal(apply_specs, entity_type, &select_apply_spec)
172 && self.check_if_any_contain(apply_specs, entity_type, &select_apply_spec)
173 }
174
175 fn check_if_none_equal<'a>(
178 &'a self,
179 specs: &[&'a ValidatorApplySpec<ast::EntityType>],
180 lit_opt: Option<&ast::EntityType>,
181 select_apply_spec: &impl Fn(
182 &'a ValidatorApplySpec<ast::EntityType>,
183 ) -> Box<dyn Iterator<Item = &'a ast::EntityType> + 'a>,
184 ) -> bool {
185 if let Some(lit) = lit_opt {
186 !specs
187 .iter()
188 .any(|spec| select_apply_spec(spec).any(|e| e == lit))
189 } else {
190 false
191 }
192 }
193
194 fn check_if_any_contain<'a>(
197 &'a self,
198 specs: &[&'a ValidatorApplySpec<ast::EntityType>],
199 lit_opt: Option<&ast::EntityType>,
200 select_apply_spec: &impl Fn(
201 &'a ValidatorApplySpec<ast::EntityType>,
202 ) -> Box<dyn Iterator<Item = &'a ast::EntityType> + 'a>,
203 ) -> bool {
204 if let Some(etype) = lit_opt.and_then(|typename| self.schema.get_entity_type(typename)) {
205 specs
206 .iter()
207 .any(|spec| select_apply_spec(spec).any(|p| etype.descendants.contains(p)))
208 } else {
209 false
210 }
211 }
212
213 fn get_eq_comparison(
216 scope_constraint: &PrincipalOrResourceConstraint,
217 ) -> Option<&ast::EntityType> {
218 match scope_constraint {
219 PrincipalOrResourceConstraint::Eq(EntityReference::EUID(euid)) => {
220 Some(euid.entity_type())
221 }
222 _ => None,
223 }
224 }
225
226 pub(crate) fn validate_linked_action_application<'a>(
227 &self,
228 p: &'a Policy,
229 ) -> impl Iterator<Item = ValidationError> + 'a {
230 self.validate_action_application(
231 p.loc(),
232 p.id(),
233 &p.principal_constraint(),
234 p.action_constraint(),
235 &p.resource_constraint(),
236 )
237 }
238
239 pub(crate) fn validate_template_action_application<'a>(
240 &self,
241 t: &'a Template,
242 ) -> impl Iterator<Item = ValidationError> + 'a {
243 self.validate_action_application(
244 t.loc(),
245 t.id(),
246 t.principal_constraint(),
247 t.action_constraint(),
248 t.resource_constraint(),
249 )
250 }
251
252 fn validate_action_application(
257 &self,
258 source_loc: Option<&Loc>,
259 policy_id: &PolicyID,
260 principal_constraint: &PrincipalConstraint,
261 action_constraint: &ActionConstraint,
262 resource_constraint: &ResourceConstraint,
263 ) -> impl Iterator<Item = ValidationError> {
264 let mut apply_specs = self.get_apply_specs_for_action(action_constraint);
265 let resources_for_scope: HashSet<&ast::EntityType> = self
266 .get_resources_satisfying_constraint(resource_constraint)
267 .collect();
268 let principals_for_scope: HashSet<&ast::EntityType> = self
269 .get_principals_satisfying_constraint(principal_constraint)
270 .collect();
271
272 let would_in_fix_principal =
273 self.check_if_in_fixes_principal(principal_constraint, action_constraint);
274 let would_in_fix_resource =
275 self.check_if_in_fixes_resource(resource_constraint, action_constraint);
276
277 Some(ValidationError::invalid_action_application(
278 source_loc.cloned(),
279 policy_id.clone(),
280 would_in_fix_principal,
281 would_in_fix_resource,
282 ))
283 .filter(|_| {
284 !apply_specs.any(|spec| {
285 let action_principals = spec.applicable_principal_types().collect::<HashSet<_>>();
286 let action_resources = spec.applicable_resource_types().collect::<HashSet<_>>();
287 let matching_principal = !principals_for_scope.is_disjoint(&action_principals);
288 let matching_resource = !resources_for_scope.is_disjoint(&action_resources);
289 matching_principal && matching_resource
290 })
291 })
292 .into_iter()
293 }
294
295 pub(crate) fn get_apply_specs_for_action<'a>(
297 &'a self,
298 action_constraint: &'a ActionConstraint,
299 ) -> impl Iterator<Item = &'a ValidatorApplySpec<ast::EntityType>> + 'a {
300 self.get_actions_satisfying_constraint(action_constraint)
301 .filter_map(|action_id| self.schema.get_action_id(action_id))
304 .map(|action| &action.applies_to)
305 }
306
307 fn get_actions_satisfying_constraint<'a>(
310 &'a self,
311 action_constraint: &'a ActionConstraint,
312 ) -> Box<dyn Iterator<Item = &'a EntityUID> + 'a> {
313 match action_constraint {
314 ActionConstraint::Any => {
316 Box::new(self.schema.action_ids().map(ValidatorActionId::name))
317 }
318 ActionConstraint::Eq(euid) => Box::new(std::iter::once(euid.as_ref())),
320 ActionConstraint::In(euids) => Box::new(
322 self.schema
323 .get_actions_in_set(euids.iter().map(Arc::as_ref))
324 .unwrap_or_default()
325 .into_iter(),
326 ),
327 #[cfg(feature = "tolerant-ast")]
328 ActionConstraint::ErrorConstraint => {
329 let v = vec![].into_iter();
330 Box::new(v)
331 }
332 }
333 }
334
335 pub(crate) fn get_principals_satisfying_constraint<'a>(
338 &'a self,
339 principal_constraint: &'a PrincipalConstraint,
340 ) -> impl Iterator<Item = &'a ast::EntityType> + 'a {
341 self.get_entity_types_satisfying_constraint(principal_constraint.as_inner())
342 }
343
344 pub(crate) fn get_resources_satisfying_constraint<'a>(
347 &'a self,
348 resource_constraint: &'a ResourceConstraint,
349 ) -> impl Iterator<Item = &'a ast::EntityType> + 'a {
350 self.get_entity_types_satisfying_constraint(resource_constraint.as_inner())
351 }
352
353 fn get_entity_types_satisfying_constraint<'a>(
356 &'a self,
357 scope_constraint: &'a PrincipalOrResourceConstraint,
358 ) -> Box<dyn Iterator<Item = &'a ast::EntityType> + 'a> {
359 match scope_constraint {
360 PrincipalOrResourceConstraint::Any => Box::new(self.schema.entity_type_names()),
362 PrincipalOrResourceConstraint::Eq(EntityReference::EUID(euid)) => {
364 Box::new(std::iter::once(euid.entity_type()))
365 }
366 PrincipalOrResourceConstraint::In(EntityReference::EUID(euid)) => Box::new(
368 self.schema
369 .get_entity_types_in(euid.entity_type())
370 .into_iter(),
371 ),
372 PrincipalOrResourceConstraint::Eq(EntityReference::Slot(_))
373 | PrincipalOrResourceConstraint::In(EntityReference::Slot(_)) => {
374 Box::new(self.schema.entity_type_names())
375 }
376 PrincipalOrResourceConstraint::Is(entity_type)
377 | PrincipalOrResourceConstraint::IsIn(entity_type, EntityReference::Slot(_)) => {
378 Box::new(
379 if self.schema.is_known_entity_type(entity_type) {
380 Some(entity_type.as_ref())
381 } else {
382 None
383 }
384 .into_iter(),
385 )
386 }
387 PrincipalOrResourceConstraint::IsIn(entity_type, EntityReference::EUID(in_entity)) => {
388 Box::new(
389 self.schema
390 .get_entity_types_in(in_entity.entity_type())
391 .into_iter()
392 .filter(move |k| &entity_type.as_ref() == k),
393 )
394 }
395 }
396 }
397}
398
399#[cfg(test)]
400#[expect(clippy::panic, clippy::indexing_slicing, reason = "unit tests")]
401mod test {
402 use std::collections::{HashMap, HashSet};
403
404 use crate::{
405 ast::{Effect, Eid, EntityUID, PolicyID, PrincipalConstraint, ResourceConstraint},
406 est::Annotations,
407 parser::{parse_policy, parse_policy_or_template},
408 test_utils::{expect_err, ExpectedErrorMessageBuilder},
409 };
410 use miette::Report;
411
412 use super::*;
413 use crate::validator::{
414 json_schema, validation_errors::UnrecognizedEntityType, RawName, ValidationMode,
415 ValidationWarning, Validator,
416 };
417
418 #[test]
419 fn validate_entity_type_empty_schema() {
420 let src = r#"permit(principal, action, resource == foo_type::"foo_name");"#;
421 let policy = parse_policy_or_template(None, src).unwrap();
422 let validate = Validator::new(ValidatorSchema::empty());
423 let notes: Vec<ValidationError> =
424 Validator::validate_entity_types(validate.schema(), &policy).collect();
425 expect_err(
426 src,
427 &Report::new(notes.first().unwrap().clone()),
428 &ExpectedErrorMessageBuilder::error(
429 "for policy `policy0`, unrecognized entity type `foo_type`",
430 )
431 .exactly_one_underline("foo_type")
432 .build(),
433 );
434 assert_eq!(notes.len(), 1, "{notes:?}");
435 }
436
437 #[test]
438 fn validate_equals_instead_of_in() {
439 let schema_file: json_schema::NamespaceDefinition<RawName> =
440 serde_json::from_value(serde_json::json!(
441 {
442 "entityTypes": {
443 "user": {
444 "memberOfTypes": ["admins"]
445 },
446 "admins": {},
447 "widget": {
448 "memberOfTypes": ["bin"]
449 },
450 "bin": {}
451 },
452 "actions": {
453 "act": {
454 "appliesTo": {
455 "principalTypes": ["user"],
456 "resourceTypes": ["widget"]
457 }
458 }
459 }
460 }
461 ))
462 .unwrap();
463 let schema = schema_file.try_into().unwrap();
464
465 let src = r#"permit(principal == admins::"admin1", action == Action::"act", resource == bin::"bin");"#;
466 let p = parse_policy_or_template(None, src).unwrap();
467
468 let validate = Validator::new(schema);
469 let notes: Vec<ValidationError> =
470 validate.validate_template_action_application(&p).collect();
471
472 expect_err(
473 src,
474 &Report::new(notes.first().unwrap().clone()),
475 &ExpectedErrorMessageBuilder::error(
476 r#"for policy `policy0`, unable to find an applicable action given the policy scope constraints"#,
477 )
478 .help("try replacing `==` with `in` in the principal clause and the resource clause")
479 .exactly_one_underline(src)
480 .build(),
481 );
482 assert_eq!(notes.len(), 1, "{notes:?}");
483 }
484
485 #[test]
486 fn validate_entity_type_in_singleton_schema() {
487 let foo_type = "foo_type";
488 let schema_file = json_schema::NamespaceDefinition::new(
489 [(
490 foo_type.parse().unwrap(),
491 json_schema::StandardEntityType {
492 member_of_types: vec![],
493 shape: json_schema::AttributesOrContext::default(),
494 tags: None,
495 }
496 .into(),
497 )],
498 [],
499 );
500 let singleton_schema = schema_file.try_into().unwrap();
501 let policy = Template::new(
502 PolicyID::from_string("policy0"),
503 None,
504 ast::Annotations::new(),
505 Effect::Permit,
506 PrincipalConstraint::any(),
507 ActionConstraint::any(),
508 ResourceConstraint::is_eq(Arc::new(
509 EntityUID::with_eid_and_type(foo_type, "foo_name")
510 .expect("should be a valid identifier"),
511 )),
512 None,
513 );
514
515 let validate = Validator::new(singleton_schema);
516 assert!(
517 Validator::validate_entity_types(validate.schema(), &policy)
518 .next()
519 .is_none(),
520 "Did not expect any validation errors."
521 );
522 }
523
524 #[test]
525 fn validate_entity_type_not_in_singleton_schema() {
526 let schema_file = json_schema::NamespaceDefinition::new(
527 [(
528 "foo_type".parse().unwrap(),
529 json_schema::StandardEntityType {
530 member_of_types: vec![],
531 shape: json_schema::AttributesOrContext::default(),
532 tags: None,
533 }
534 .into(),
535 )],
536 [],
537 );
538 let singleton_schema = schema_file.try_into().unwrap();
539
540 let src = r#"permit(principal, action, resource == bar_type::"bar_name");"#;
541 let policy = parse_policy_or_template(None, src).unwrap();
542 let validate = Validator::new(singleton_schema);
543 let notes: Vec<ValidationError> =
544 Validator::validate_entity_types(validate.schema(), &policy).collect();
545 expect_err(
546 src,
547 &Report::new(notes.first().unwrap().clone()),
548 &ExpectedErrorMessageBuilder::error(
549 "for policy `policy0`, unrecognized entity type `bar_type`",
550 )
551 .exactly_one_underline("bar_type")
552 .help("did you mean `foo_type`?")
553 .build(),
554 );
555 assert_eq!(notes.len(), 1, "{notes:?}");
556 }
557
558 #[test]
559 fn validate_action_id_empty_schema() {
560 let src = r#"permit(principal, action == Action::"foo_name", resource);"#;
561 let policy = parse_policy_or_template(None, src).unwrap();
562 let validate = Validator::new(ValidatorSchema::empty());
563 let notes: Vec<ValidationError> =
564 Validator::validate_action_ids(validate.schema(), &policy).collect();
565 expect_err(
566 src,
567 &Report::new(notes.first().unwrap().clone()),
568 &ExpectedErrorMessageBuilder::error(
569 r#"for policy `policy0`, unrecognized action `Action::"foo_name"`"#,
570 )
571 .exactly_one_underline(r#"Action::"foo_name""#)
572 .build(),
573 );
574 assert_eq!(notes.len(), 1, "{notes:?}");
575 }
576
577 #[test]
578 fn validate_action_id_in_singleton_schema() {
579 let foo_name = "foo_name";
580 let schema_file = json_schema::NamespaceDefinition::new(
581 [],
582 [(
583 foo_name.into(),
584 json_schema::ActionType {
585 applies_to: None,
586 member_of: None,
587 attributes: None,
588 annotations: Annotations::new(),
589 loc: None,
590 #[cfg(feature = "extended-schema")]
591 defn_loc: None,
592 },
593 )],
594 );
595 let singleton_schema = schema_file.try_into().unwrap();
596 let entity =
597 EntityUID::with_eid_and_type("Action", foo_name).expect("should be a valid identifier");
598 let policy = Template::new(
599 PolicyID::from_string("policy0"),
600 None,
601 ast::Annotations::new(),
602 Effect::Permit,
603 PrincipalConstraint::any(),
604 ActionConstraint::is_eq(entity),
605 ResourceConstraint::any(),
606 None,
607 );
608
609 let validate = Validator::new(singleton_schema);
610 assert!(
611 Validator::validate_action_ids(validate.schema(), &policy)
612 .next()
613 .is_none(),
614 "Did not expect any validation errors."
615 );
616 }
617
618 #[test]
619 fn validate_principal_slot_in_singleton_schema() {
620 let p_name = "User";
621 let schema_file = json_schema::NamespaceDefinition::new(
622 [(
623 p_name.parse().unwrap(),
624 json_schema::StandardEntityType {
625 member_of_types: vec![],
626 shape: json_schema::AttributesOrContext::default(),
627 tags: None,
628 }
629 .into(),
630 )],
631 [],
632 );
633 let schema = schema_file.try_into().unwrap();
634 let principal_constraint = PrincipalConstraint::is_eq_slot();
635 let validator = Validator::new(schema);
636 let entities = validator
637 .get_principals_satisfying_constraint(&principal_constraint)
638 .collect::<Vec<_>>();
639 assert_eq!(entities.len(), 1);
640 let name = entities[0];
641 assert_eq!(name, &p_name.parse().expect("Expected valid entity type."));
642 }
643
644 #[test]
645 fn validate_resource_slot_in_singleton_schema() {
646 let p_name = "Package";
647 let schema_file = json_schema::NamespaceDefinition::new(
648 [(
649 p_name.parse().unwrap(),
650 json_schema::StandardEntityType {
651 member_of_types: vec![],
652 shape: json_schema::AttributesOrContext::default(),
653 tags: None,
654 }
655 .into(),
656 )],
657 [],
658 );
659 let schema = schema_file.try_into().unwrap();
660 let principal_constraint = PrincipalConstraint::any();
661 let validator = Validator::new(schema);
662 let entities = validator
663 .get_principals_satisfying_constraint(&principal_constraint)
664 .collect::<Vec<_>>();
665 assert_eq!(entities.len(), 1);
666 let name = entities[0];
667 assert_eq!(name, &p_name.parse().expect("Expected valid entity type."));
668 }
669
670 #[test]
671 fn undefined_entity_type_in_principal_slot() {
672 let p_name = "User";
673 let schema_file = json_schema::NamespaceDefinition::new(
674 [(
675 p_name.parse().unwrap(),
676 json_schema::StandardEntityType {
677 member_of_types: vec![],
678 shape: json_schema::AttributesOrContext::default(),
679 tags: None,
680 }
681 .into(),
682 )],
683 [],
684 );
685 let schema = schema_file.try_into().expect("Invalid schema");
686
687 let undefined_euid: EntityUID = "Undefined::\"foo\""
688 .parse()
689 .expect("Expected entity UID to parse.");
690 let env = HashMap::from([(ast::SlotId::principal(), undefined_euid)]);
691
692 let validator = Validator::new(schema);
693 let notes: Vec<ValidationError> = validator
694 .validate_entity_types_in_slots(&PolicyID::from_string("0"), &env)
695 .collect();
696
697 assert_eq!(1, notes.len());
698 match notes.first() {
699 Some(ValidationError::UnrecognizedEntityType(UnrecognizedEntityType {
700 actual_entity_type,
701 suggested_entity_type,
702 ..
703 })) => {
704 assert_eq!("Undefined", actual_entity_type);
705 assert_eq!(
706 "User",
707 suggested_entity_type
708 .as_ref()
709 .expect("Expected a suggested entity type")
710 );
711 }
712 _ => panic!("Unexpected variant of ValidationErrorKind."),
713 };
714 }
715
716 #[test]
717 fn validate_action_id_not_in_singleton_schema() {
718 let schema_file = json_schema::NamespaceDefinition::new(
719 [],
720 [(
721 "foo_name".into(),
722 json_schema::ActionType {
723 applies_to: None,
724 member_of: None,
725 attributes: None,
726 annotations: Annotations::new(),
727 loc: None,
728 #[cfg(feature = "extended-schema")]
729 defn_loc: None,
730 },
731 )],
732 );
733 let singleton_schema = schema_file.try_into().unwrap();
734
735 let src = r#"permit(principal, action == Action::"bar_name", resource);"#;
736 let policy = parse_policy_or_template(None, src).unwrap();
737 let validate = Validator::new(singleton_schema);
738 let notes: Vec<ValidationError> =
739 Validator::validate_action_ids(validate.schema(), &policy).collect();
740 expect_err(
741 src,
742 &Report::new(notes.first().unwrap().clone()),
743 &ExpectedErrorMessageBuilder::error(
744 r#"for policy `policy0`, unrecognized action `Action::"bar_name"`"#,
745 )
746 .exactly_one_underline(r#"Action::"bar_name""#)
747 .help(r#"did you mean `Action::"foo_name"`?"#)
748 .build(),
749 );
750 assert_eq!(notes.len(), 1, "{notes:?}");
751 }
752
753 #[test]
754 fn validate_action_id_with_action_type() {
755 let schema_file = json_schema::NamespaceDefinition::new(
756 [],
757 [(
758 "Action::view".into(),
759 json_schema::ActionType {
760 applies_to: None,
761 member_of: None,
762 attributes: None,
763 annotations: Annotations::new(),
764 loc: None,
765 #[cfg(feature = "extended-schema")]
766 defn_loc: None,
767 },
768 )],
769 );
770 let singleton_schema = schema_file.try_into().unwrap();
771
772 let src = r#"permit(principal, action == Action::"view", resource);"#;
773 let policy = parse_policy_or_template(None, src).unwrap();
774 let validate = Validator::new(singleton_schema);
775 let notes: Vec<ValidationError> =
776 Validator::validate_action_ids(validate.schema(), &policy).collect();
777 expect_err(
778 src,
779 &Report::new(notes.first().unwrap().clone()),
780 &ExpectedErrorMessageBuilder::error(
781 r#"for policy `policy0`, unrecognized action `Action::"view"`"#,
782 )
783 .exactly_one_underline(r#"Action::"view""#)
784 .help(r#"did you intend to include the type in action `Action::"Action::view"`?"#)
785 .build(),
786 );
787 assert_eq!(notes.len(), 1, "{notes:?}");
788 }
789
790 #[test]
791 fn validate_action_id_with_action_type_namespace() {
792 let schema_src = r#"
793 {
794 "foo::foo::bar::baz": {
795 "entityTypes": {},
796 "actions": {
797 "Action::view": {}
798 }
799 }
800 }"#;
801
802 let schema_fragment: json_schema::Fragment<RawName> =
803 serde_json::from_str(schema_src).expect("Parse Error");
804 let schema = schema_fragment.try_into().unwrap();
805
806 let src = r#"permit(principal, action == Action::"view", resource);"#;
807 let policy = parse_policy_or_template(None, src).unwrap();
808 let validate = Validator::new(schema);
809 let notes: Vec<ValidationError> =
810 Validator::validate_action_ids(validate.schema(), &policy).collect();
811 expect_err(
812 src,
813 &Report::new(notes.first().unwrap().clone()),
814 &ExpectedErrorMessageBuilder::error(
815 r#"for policy `policy0`, unrecognized action `Action::"view"`"#,
816 )
817 .exactly_one_underline(r#"Action::"view""#)
818 .help(r#"did you intend to include the type in action `foo::foo::bar::baz::Action::"Action::view"`?"#)
819 .build(),
820 );
821 assert_eq!(notes.len(), 1, "{notes:?}");
822 }
823
824 #[test]
825 fn validate_namespaced_action_id_in_schema() {
826 let descriptors = json_schema::Fragment::from_json_str(
827 r#"
828 {
829 "NS": {
830 "entityTypes": {},
831 "actions": { "foo_name": {} }
832 }
833 }"#,
834 )
835 .expect("Expected schema parse.");
836 let schema = descriptors.try_into().unwrap();
837 let entity: EntityUID = "NS::Action::\"foo_name\""
838 .parse()
839 .expect("Expected entity parse.");
840 let policy = Template::new(
841 PolicyID::from_string("policy0"),
842 None,
843 ast::Annotations::new(),
844 Effect::Permit,
845 PrincipalConstraint::any(),
846 ActionConstraint::is_eq(entity),
847 ResourceConstraint::any(),
848 None,
849 );
850
851 let validate = Validator::new(schema);
852 let notes: Vec<ValidationError> =
853 Validator::validate_action_ids(validate.schema(), &policy).collect();
854 assert_eq!(notes, vec![], "Did not expect any invalid action.");
855 }
856
857 #[test]
858 fn validate_namespaced_invalid_action() {
859 let descriptors = json_schema::Fragment::from_json_str(
860 r#"
861 {
862 "NS": {
863 "entityTypes": {},
864 "actions": { "foo_name": {} }
865 }
866 }"#,
867 )
868 .expect("Expected schema parse.");
869 let schema = descriptors.try_into().unwrap();
870
871 let src = r#"permit(principal, action == Bogus::Action::"foo_name", resource);"#;
872 let policy = parse_policy_or_template(None, src).unwrap();
873 let validate = Validator::new(schema);
874 let notes: Vec<ValidationError> =
875 Validator::validate_action_ids(validate.schema(), &policy).collect();
876 expect_err(
877 src,
878 &Report::new(notes.first().unwrap().clone()),
879 &ExpectedErrorMessageBuilder::error(
880 r#"for policy `policy0`, unrecognized action `Bogus::Action::"foo_name"`"#,
881 )
882 .exactly_one_underline(r#"Bogus::Action::"foo_name""#)
883 .help(r#"did you mean `NS::Action::"foo_name"`?"#)
884 .build(),
885 );
886 assert_eq!(notes.len(), 1, "{notes:?}");
887 }
888
889 #[test]
890 fn validate_namespaced_entity_type_in_schema() {
891 let descriptors = json_schema::Fragment::from_json_str(
892 r#"
893 {
894 "NS": {
895 "entityTypes": {"Foo": {} },
896 "actions": {}
897 }
898 }"#,
899 )
900 .expect("Expected schema parse.");
901 let schema = descriptors.try_into().unwrap();
902 let entity_type: ast::EntityType = "NS::Foo".parse().expect("Expected entity type parse.");
903 let policy = Template::new(
904 PolicyID::from_string("policy0"),
905 None,
906 ast::Annotations::new(),
907 Effect::Permit,
908 PrincipalConstraint::is_eq(Arc::new(EntityUID::from_components(
909 entity_type,
910 Eid::new("bar"),
911 None,
912 ))),
913 ActionConstraint::any(),
914 ResourceConstraint::any(),
915 None,
916 );
917
918 let validate = Validator::new(schema);
919 let notes: Vec<ValidationError> =
920 Validator::validate_entity_types(validate.schema(), &policy).collect();
921
922 assert_eq!(notes, vec![], "Did not expect any invalid action.");
923 }
924
925 #[test]
926 fn validate_namespaced_invalid_entity_type() {
927 let descriptors = json_schema::Fragment::from_json_str(
928 r#"
929 {
930 "NS": {
931 "entityTypes": {"Foo": {} },
932 "actions": {}
933 }
934 }"#,
935 )
936 .expect("Expected schema parse.");
937 let schema = descriptors.try_into().unwrap();
938
939 let src = r#"permit(principal == Bogus::Foo::"bar", action, resource);"#;
940 let policy = parse_policy_or_template(None, src).unwrap();
941 let validate = Validator::new(schema);
942 let notes: Vec<ValidationError> =
943 Validator::validate_entity_types(validate.schema(), &policy).collect();
944 expect_err(
945 src,
946 &Report::new(notes.first().unwrap().clone()),
947 &ExpectedErrorMessageBuilder::error(
948 "for policy `policy0`, unrecognized entity type `Bogus::Foo`",
949 )
950 .exactly_one_underline("Bogus::Foo")
951 .help("did you mean `NS::Foo`?")
952 .build(),
953 );
954 assert_eq!(notes.len(), 1, "{notes:?}");
955 }
956
957 #[test]
958 fn get_possible_actions_eq() {
959 let foo_name = "foo_name";
960 let euid_foo =
961 EntityUID::with_eid_and_type("Action", foo_name).expect("should be a valid identifier");
962 let action_constraint = ActionConstraint::is_eq(euid_foo.clone());
963
964 let schema_file = json_schema::NamespaceDefinition::new(
965 [],
966 [(
967 foo_name.into(),
968 json_schema::ActionType {
969 applies_to: None,
970 member_of: None,
971 attributes: None,
972 annotations: Annotations::new(),
973 loc: None,
974 #[cfg(feature = "extended-schema")]
975 defn_loc: None,
976 },
977 )],
978 );
979 let singleton_schema = schema_file.try_into().unwrap();
980
981 let validate = Validator::new(singleton_schema);
982 let actions = validate
983 .get_actions_satisfying_constraint(&action_constraint)
984 .collect();
985 assert_eq!(HashSet::from([&euid_foo]), actions);
986 }
987
988 #[test]
989 fn get_possible_actions_in_no_parents() {
990 let foo_name = "foo_name";
991 let euid_foo =
992 EntityUID::with_eid_and_type("Action", foo_name).expect("should be a valid identifier");
993 let action_constraint = ActionConstraint::is_in(vec![euid_foo.clone()]);
994
995 let schema_file = json_schema::NamespaceDefinition::new(
996 [],
997 [(
998 foo_name.into(),
999 json_schema::ActionType {
1000 applies_to: None,
1001 member_of: None,
1002 attributes: None,
1003 annotations: Annotations::new(),
1004 loc: None,
1005 #[cfg(feature = "extended-schema")]
1006 defn_loc: None,
1007 },
1008 )],
1009 );
1010 let singleton_schema = schema_file.try_into().unwrap();
1011
1012 let validate = Validator::new(singleton_schema);
1013 let actions = validate
1014 .get_actions_satisfying_constraint(&action_constraint)
1015 .collect();
1016 assert_eq!(HashSet::from([&euid_foo]), actions);
1017 }
1018
1019 #[test]
1020 fn get_possible_actions_in_set_no_parents() {
1021 let foo_name = "foo_name";
1022 let euid_foo =
1023 EntityUID::with_eid_and_type("Action", foo_name).expect("should be a valid identifier");
1024 let action_constraint = ActionConstraint::is_in(vec![euid_foo.clone()]);
1025
1026 let schema_file = json_schema::NamespaceDefinition::new(
1027 [],
1028 [(
1029 foo_name.into(),
1030 json_schema::ActionType {
1031 applies_to: None,
1032 member_of: None,
1033 attributes: None,
1034 annotations: Annotations::new(),
1035 loc: None,
1036 #[cfg(feature = "extended-schema")]
1037 defn_loc: None,
1038 },
1039 )],
1040 );
1041 let singleton_schema = schema_file.try_into().unwrap();
1042
1043 let validate = Validator::new(singleton_schema);
1044 let actions = validate
1045 .get_actions_satisfying_constraint(&action_constraint)
1046 .collect();
1047 assert_eq!(HashSet::from([&euid_foo]), actions);
1048 }
1049
1050 #[test]
1051 fn get_possible_principals_eq() {
1052 let foo_type = "foo_type";
1053 let euid_foo = EntityUID::with_eid_and_type(foo_type, "foo_name")
1054 .expect("should be a valid identifier");
1055 let principal_constraint = PrincipalConstraint::is_eq(Arc::new(euid_foo.clone()));
1056
1057 let schema_file = json_schema::NamespaceDefinition::new(
1058 [(
1059 foo_type.parse().unwrap(),
1060 json_schema::StandardEntityType {
1061 member_of_types: vec![],
1062 shape: json_schema::AttributesOrContext::default(),
1063 tags: None,
1064 }
1065 .into(),
1066 )],
1067 [],
1068 );
1069 let singleton_schema = schema_file.try_into().unwrap();
1070
1071 let validate = Validator::new(singleton_schema);
1072 let principals = validate
1073 .get_principals_satisfying_constraint(&principal_constraint)
1074 .cloned()
1075 .collect::<HashSet<_>>();
1076 assert_eq!(HashSet::from([euid_foo.components().0]), principals);
1077 }
1078
1079 fn schema_with_single_principal_action_resource(
1080 ) -> (EntityUID, EntityUID, EntityUID, ValidatorSchema) {
1081 let action_name = "foo";
1082 let action_euid = EntityUID::with_eid_and_type("Action", action_name)
1083 .expect("should be a valid identifier");
1084 let principal_type = "bar";
1085 let principal_euid = EntityUID::with_eid_and_type(principal_type, "principal")
1086 .expect("should be a valid identifier");
1087 let resource_type = "baz";
1088 let resource_euid = EntityUID::with_eid_and_type(resource_type, "resource")
1089 .expect("should be a valid identifier");
1090
1091 let schema = json_schema::NamespaceDefinition::new(
1092 [
1093 (
1094 principal_type.parse().unwrap(),
1095 json_schema::StandardEntityType {
1096 member_of_types: vec![],
1097 shape: json_schema::AttributesOrContext::default(),
1098 tags: None,
1099 }
1100 .into(),
1101 ),
1102 (
1103 resource_type.parse().unwrap(),
1104 json_schema::StandardEntityType {
1105 member_of_types: vec![],
1106 shape: json_schema::AttributesOrContext::default(),
1107 tags: None,
1108 }
1109 .into(),
1110 ),
1111 ],
1112 [(
1113 action_name.into(),
1114 json_schema::ActionType {
1115 applies_to: Some(json_schema::ApplySpec {
1116 resource_types: vec![resource_type.parse().unwrap()],
1117 principal_types: vec![principal_type.parse().unwrap()],
1118 context: json_schema::AttributesOrContext::default(),
1119 }),
1120 member_of: Some(vec![]),
1121 attributes: None,
1122 annotations: Annotations::new(),
1123 loc: None,
1124 #[cfg(feature = "extended-schema")]
1125 defn_loc: None,
1126 },
1127 )],
1128 )
1129 .try_into()
1130 .expect("Expected valid schema file.");
1131 (principal_euid, action_euid, resource_euid, schema)
1132 }
1133
1134 #[track_caller] fn assert_validate_policy_succeeds(validator: &Validator, policy: &Template) {
1136 assert!(
1137 validator
1138 .validate_policy(policy, ValidationMode::default())
1139 .0
1140 .next()
1141 .is_none(),
1142 "Did not expect any validation errors."
1143 );
1144 assert!(
1145 validator
1146 .validate_policy(policy, ValidationMode::default())
1147 .1
1148 .next()
1149 .is_none(),
1150 "Did not expect any validation warnings."
1151 );
1152 }
1153
1154 #[track_caller] fn assert_validate_policy_fails(
1156 validator: &Validator,
1157 policy: &Template,
1158 expected: &[ValidationError],
1159 ) {
1160 assert_eq!(
1161 validator
1162 .validate_policy(policy, ValidationMode::default())
1163 .0
1164 .collect::<Vec<ValidationError>>(),
1165 expected,
1166 "Unexpected validation errors."
1167 );
1168 }
1169
1170 #[track_caller] fn assert_validate_policy_flags_impossible_policy(validator: &Validator, policy: &Template) {
1172 assert_eq!(
1173 validator
1174 .validate_policy(policy, ValidationMode::default())
1175 .1
1176 .collect::<Vec<ValidationWarning>>(),
1177 vec![ValidationWarning::impossible_policy(
1178 policy.loc().cloned(),
1179 policy.id().clone()
1180 )],
1181 "Unexpected validation warnings."
1182 );
1183 }
1184
1185 #[test]
1186 fn validate_action_apply_correct() {
1187 let (principal, action, resource, schema) = schema_with_single_principal_action_resource();
1188
1189 let policy = Template::new(
1190 PolicyID::from_string("policy0"),
1191 None,
1192 ast::Annotations::new(),
1193 Effect::Permit,
1194 PrincipalConstraint::is_eq(Arc::new(principal)),
1195 ActionConstraint::is_eq(action),
1196 ResourceConstraint::is_eq(Arc::new(resource)),
1197 None,
1198 );
1199
1200 let validator = Validator::new(schema);
1201 assert_validate_policy_succeeds(&validator, &policy);
1202 }
1203
1204 #[test]
1205 fn validate_action_apply_incorrect_principal() {
1206 let (_, _, _, schema) = schema_with_single_principal_action_resource();
1207
1208 let src =
1209 r#"permit(principal == baz::"p", action == Action::"foo", resource == baz::"r");"#;
1210 let p = parse_policy_or_template(None, src).unwrap();
1211
1212 let validate = Validator::new(schema);
1213 let notes: Vec<ValidationError> =
1214 validate.validate_template_action_application(&p).collect();
1215
1216 expect_err(
1217 src,
1218 &Report::new(notes.first().unwrap().clone()),
1219 &ExpectedErrorMessageBuilder::error(
1220 r#"for policy `policy0`, unable to find an applicable action given the policy scope constraints"#,
1221 )
1222 .exactly_one_underline(src)
1223 .build(),
1224 );
1225 assert_eq!(notes.len(), 1, "{notes:?}");
1226 }
1227
1228 #[test]
1229 fn validate_action_apply_incorrect_resource() {
1230 let (_, _, _, schema) = schema_with_single_principal_action_resource();
1231
1232 let src =
1233 r#"permit(principal == bar::"p", action == Action::"foo", resource == bar::"r");"#;
1234 let p = parse_policy_or_template(None, src).unwrap();
1235
1236 let validate = Validator::new(schema);
1237 let notes: Vec<ValidationError> =
1238 validate.validate_template_action_application(&p).collect();
1239
1240 expect_err(
1241 src,
1242 &Report::new(notes.first().unwrap().clone()),
1243 &ExpectedErrorMessageBuilder::error(
1244 r#"for policy `policy0`, unable to find an applicable action given the policy scope constraints"#,
1245 )
1246 .exactly_one_underline(src)
1247 .build(),
1248 );
1249 assert_eq!(notes.len(), 1, "{notes:?}");
1250 }
1251
1252 #[test]
1253 fn validate_action_apply_incorrect_principal_and_resource() {
1254 let (_, _, _, schema) = schema_with_single_principal_action_resource();
1255
1256 let src =
1257 r#"permit(principal == baz::"p", action == Action::"foo", resource == bar::"r");"#;
1258 let p = parse_policy_or_template(None, src).unwrap();
1259
1260 let validate = Validator::new(schema);
1261 let notes: Vec<ValidationError> =
1262 validate.validate_template_action_application(&p).collect();
1263
1264 expect_err(
1265 src,
1266 &Report::new(notes.first().unwrap().clone()),
1267 &ExpectedErrorMessageBuilder::error(
1268 r#"for policy `policy0`, unable to find an applicable action given the policy scope constraints"#,
1269 )
1270 .exactly_one_underline(src)
1271 .build(),
1272 );
1273 assert_eq!(notes.len(), 1, "{notes:?}");
1274 }
1275
1276 #[test]
1277 fn validate_principal_is() {
1278 let (_, _, _, schema) = schema_with_single_principal_action_resource();
1279
1280 let policy =
1281 parse_policy_or_template(None, "permit(principal is bar, action, resource);").unwrap();
1282
1283 let validator = Validator::new(schema);
1284 assert_validate_policy_succeeds(&validator, &policy);
1285
1286 let policy = parse_policy_or_template(
1287 None,
1288 r#"permit(principal is bar in bar::"baz", action, resource);"#,
1289 )
1290 .unwrap();
1291
1292 assert_validate_policy_succeeds(&validator, &policy);
1293 }
1294
1295 #[test]
1296 fn validate_principal_is_err() {
1297 let (_, _, _, schema) = schema_with_single_principal_action_resource();
1298
1299 let src = "permit(principal is baz, action, resource);";
1300 let policy = parse_policy_or_template(None, src).unwrap();
1301
1302 let validator = Validator::new(schema);
1303 assert_validate_policy_fails(
1304 &validator,
1305 &policy,
1306 &[ValidationError::invalid_action_application(
1307 Some(Loc::new(0..43, Arc::from(src))),
1308 PolicyID::from_string("policy0"),
1309 false,
1310 false,
1311 )],
1312 );
1313 assert_validate_policy_flags_impossible_policy(&validator, &policy);
1314
1315 let src = r#"permit(principal is biz in faz::"a", action, resource);"#;
1316 let policy = parse_policy_or_template(None, src).unwrap();
1317
1318 assert_validate_policy_fails(
1319 &validator,
1320 &policy,
1321 &[
1322 ValidationError::unrecognized_entity_type(
1323 Some(Loc::new(27..30, Arc::from(src))),
1324 PolicyID::from_string("policy0"),
1325 "faz".into(),
1326 Some("baz".into()),
1327 ),
1328 ValidationError::unrecognized_entity_type(
1329 Some(Loc::new(20..23, Arc::from(src))),
1330 PolicyID::from_string("policy0"),
1331 "biz".into(),
1332 Some("baz".into()),
1333 ),
1334 ValidationError::invalid_action_application(
1335 Some(Loc::new(0..55, Arc::from(src))),
1336 PolicyID::from_string("policy0"),
1337 false,
1338 false,
1339 ),
1340 ],
1341 );
1342 assert_validate_policy_flags_impossible_policy(&validator, &policy);
1343
1344 let src = r#"permit(principal is bar in baz::"buz", action, resource);"#;
1345 let policy = parse_policy_or_template(None, src).unwrap();
1346
1347 assert_validate_policy_fails(
1348 &validator,
1349 &policy,
1350 &[ValidationError::invalid_action_application(
1351 Some(Loc::new(0..57, Arc::from(src))),
1352 PolicyID::from_string("policy0"),
1353 false,
1354 false,
1355 )],
1356 );
1357 assert_validate_policy_flags_impossible_policy(&validator, &policy);
1358 }
1359
1360 #[test]
1361 fn validate_resource_is() {
1362 let (_, _, _, schema) = schema_with_single_principal_action_resource();
1363
1364 let policy =
1365 parse_policy_or_template(None, "permit(principal, action, resource is baz);").unwrap();
1366
1367 let validator = Validator::new(schema);
1368 assert_validate_policy_succeeds(&validator, &policy);
1369
1370 let policy = parse_policy_or_template(
1371 None,
1372 r#"permit(principal, action, resource is baz in baz::"bar");"#,
1373 )
1374 .unwrap();
1375
1376 assert_validate_policy_succeeds(&validator, &policy);
1377 }
1378
1379 #[test]
1380 fn validate_resource_is_err() {
1381 let (_, _, _, schema) = schema_with_single_principal_action_resource();
1382
1383 let src = "permit(principal, action, resource is bar);";
1384 let policy = parse_policy_or_template(None, src).unwrap();
1385
1386 let validator = Validator::new(schema);
1387 assert_validate_policy_fails(
1388 &validator,
1389 &policy,
1390 &[ValidationError::invalid_action_application(
1391 Some(Loc::new(0..43, Arc::from(src))),
1392 PolicyID::from_string("policy0"),
1393 false,
1394 false,
1395 )],
1396 );
1397 assert_validate_policy_flags_impossible_policy(&validator, &policy);
1398
1399 let src = r#"permit(principal, action, resource is baz in bar::"buz");"#;
1400 let policy = parse_policy_or_template(None, src).unwrap();
1401
1402 assert_validate_policy_fails(
1403 &validator,
1404 &policy,
1405 &[ValidationError::invalid_action_application(
1406 Some(Loc::new(0..57, Arc::from(src))),
1407 PolicyID::from_string("policy0"),
1408 false,
1409 false,
1410 )],
1411 );
1412 assert_validate_policy_flags_impossible_policy(&validator, &policy);
1413
1414 let src = r#"permit(principal, action, resource is biz in faz::"a");"#;
1415 let policy = parse_policy_or_template(None, src).unwrap();
1416
1417 assert_validate_policy_fails(
1418 &validator,
1419 &policy,
1420 &[
1421 ValidationError::unrecognized_entity_type(
1422 Some(Loc::new(45..48, Arc::from(src))),
1423 PolicyID::from_string("policy0"),
1424 "faz".into(),
1425 Some("baz".into()),
1426 ),
1427 ValidationError::unrecognized_entity_type(
1428 Some(Loc::new(38..41, Arc::from(src))),
1429 PolicyID::from_string("policy0"),
1430 "biz".into(),
1431 Some("baz".into()),
1432 ),
1433 ValidationError::invalid_action_application(
1434 Some(Loc::new(0..55, Arc::from(src))),
1435 PolicyID::from_string("policy0"),
1436 false,
1437 false,
1438 ),
1439 ],
1440 );
1441 assert_validate_policy_flags_impossible_policy(&validator, &policy);
1442 }
1443
1444 #[test]
1445 fn is_unknown_entity_condition() {
1446 let (_, _, _, schema) = schema_with_single_principal_action_resource();
1447 let src = r#"permit(principal, action, resource) when { resource is biz };"#;
1448 let policy = parse_policy_or_template(None, src).unwrap();
1449
1450 let validator = Validator::new(schema);
1451 let err = validator
1452 .validate_policy(&policy, ValidationMode::default())
1453 .0
1454 .next()
1455 .unwrap();
1456 expect_err(
1457 src,
1458 &Report::new(err),
1459 &ExpectedErrorMessageBuilder::error(
1460 "for policy `policy0`, unrecognized entity type `biz`",
1461 )
1462 .exactly_one_underline("biz")
1463 .help("did you mean `baz`?")
1464 .build(),
1465 );
1466
1467 assert_validate_policy_flags_impossible_policy(&validator, &policy);
1468 }
1469
1470 #[test]
1471 fn test_with_tc_computation() {
1472 let action_name = "foo";
1473 let action_parent_name = "foo_parent";
1474 let action_grandparent_name = "foo_grandparent";
1475 let action_grandparent_euid =
1476 EntityUID::with_eid_and_type("Action", action_grandparent_name)
1477 .expect("should be a valid identifier");
1478
1479 let principal_type = "bar";
1480
1481 let resource_type = "baz";
1482 let resource_parent_type = "baz_parent";
1483 let resource_grandparent_type = "baz_grandparent";
1484 let resource_grandparent_euid =
1485 EntityUID::with_eid_and_type(resource_parent_type, "resource")
1486 .expect("should be a valid identifier");
1487
1488 let schema_file = json_schema::NamespaceDefinition::new(
1489 [
1490 (
1491 principal_type.parse().unwrap(),
1492 json_schema::StandardEntityType {
1493 member_of_types: vec![],
1494 shape: json_schema::AttributesOrContext::default(),
1495 tags: None,
1496 }
1497 .into(),
1498 ),
1499 (
1500 resource_type.parse().unwrap(),
1501 json_schema::StandardEntityType {
1502 member_of_types: vec![resource_parent_type.parse().unwrap()],
1503 shape: json_schema::AttributesOrContext::default(),
1504 tags: None,
1505 }
1506 .into(),
1507 ),
1508 (
1509 resource_parent_type.parse().unwrap(),
1510 json_schema::StandardEntityType {
1511 member_of_types: vec![resource_grandparent_type.parse().unwrap()],
1512 shape: json_schema::AttributesOrContext::default(),
1513 tags: None,
1514 }
1515 .into(),
1516 ),
1517 (
1518 resource_grandparent_type.parse().unwrap(),
1519 json_schema::StandardEntityType {
1520 member_of_types: vec![],
1521 shape: json_schema::AttributesOrContext::default(),
1522 tags: None,
1523 }
1524 .into(),
1525 ),
1526 ],
1527 [
1528 (
1529 action_name.into(),
1530 json_schema::ActionType {
1531 applies_to: Some(json_schema::ApplySpec {
1532 resource_types: vec![resource_type.parse().unwrap()],
1533 principal_types: vec![principal_type.parse().unwrap()],
1534 context: json_schema::AttributesOrContext::default(),
1535 }),
1536 member_of: Some(vec![json_schema::ActionEntityUID::new(
1537 None,
1538 action_parent_name.into(),
1539 )]),
1540 attributes: None,
1541 annotations: Annotations::new(),
1542 loc: None,
1543 #[cfg(feature = "extended-schema")]
1544 defn_loc: None,
1545 },
1546 ),
1547 (
1548 action_parent_name.into(),
1549 json_schema::ActionType {
1550 applies_to: None,
1551 member_of: Some(vec![json_schema::ActionEntityUID::new(
1552 None,
1553 action_grandparent_name.into(),
1554 )]),
1555 attributes: None,
1556 annotations: Annotations::new(),
1557 loc: None,
1558 #[cfg(feature = "extended-schema")]
1559 defn_loc: None,
1560 },
1561 ),
1562 (
1563 action_grandparent_name.into(),
1564 json_schema::ActionType {
1565 applies_to: None,
1566 member_of: Some(vec![]),
1567 attributes: None,
1568 annotations: Annotations::new(),
1569 loc: None,
1570 #[cfg(feature = "extended-schema")]
1571 defn_loc: None,
1572 },
1573 ),
1574 ],
1575 );
1576 let schema = schema_file.try_into().unwrap();
1577
1578 let policy = Template::new(
1579 PolicyID::from_string("policy0"),
1580 None,
1581 ast::Annotations::new(),
1582 Effect::Permit,
1583 PrincipalConstraint::any(),
1584 ActionConstraint::is_in([action_grandparent_euid]),
1585 ResourceConstraint::is_in(Arc::new(resource_grandparent_euid)),
1586 None,
1587 );
1588
1589 let validator = Validator::new(schema);
1590 assert_validate_policy_succeeds(&validator, &policy);
1591 }
1592
1593 #[test]
1594 fn unspecified_principal_resource_with_scope_conditions() {
1595 let schema = serde_json::from_str::<json_schema::NamespaceDefinition<RawName>>(
1596 r#"
1597 {
1598 "entityTypes": {"a": {}},
1599 "actions": {
1600 "": { }
1601 }
1602 }
1603 "#,
1604 )
1605 .unwrap()
1606 .try_into()
1607 .unwrap();
1608 let policy = parse_policy(
1609 Some(PolicyID::from_string("0")),
1610 r#"permit(principal == a::"p", action, resource == a::"r");"#,
1611 )
1612 .unwrap();
1613
1614 let validator = Validator::new(schema);
1615 let (template, _) = Template::link_static_policy(policy);
1616 assert_validate_policy_flags_impossible_policy(&validator, &template);
1617 }
1618}
1619
1620#[cfg(test)]
1621#[cfg(feature = "partial-validate")]
1622mod partial_schema {
1623 use crate::{
1624 ast::{PolicyID, StaticPolicy, Template},
1625 parser::parse_policy,
1626 };
1627
1628 use crate::validator::{json_schema, RawName, Validator};
1629
1630 #[track_caller] fn assert_validates_with_empty_schema(policy: StaticPolicy) {
1632 let schema: json_schema::NamespaceDefinition<RawName> = serde_json::from_str(
1633 r#"
1634 {
1635 "entityTypes": { },
1636 "actions": {}
1637 }
1638 "#,
1639 )
1640 .unwrap();
1641 let schema = schema.try_into().unwrap();
1642
1643 let (template, _) = Template::link_static_policy(policy);
1644 let validate = Validator::new(schema);
1645 let errs = validate
1646 .validate_policy(&template, crate::validator::ValidationMode::Partial)
1647 .0
1648 .collect::<Vec<_>>();
1649 assert_eq!(errs, vec![], "Did not expect any validation errors.");
1650 }
1651
1652 #[test]
1653 fn undeclared_entity_type_partial_schema() {
1654 let policy = parse_policy(
1655 Some(PolicyID::from_string("0")),
1656 r#"permit(principal == User::"alice", action, resource);"#,
1657 )
1658 .unwrap();
1659 assert_validates_with_empty_schema(policy);
1660
1661 let policy = parse_policy(
1662 Some(PolicyID::from_string("0")),
1663 r#"permit(principal, action == Action::"view", resource);"#,
1664 )
1665 .unwrap();
1666 assert_validates_with_empty_schema(policy);
1667
1668 let policy = parse_policy(
1669 Some(PolicyID::from_string("0")),
1670 r#"permit(principal, action, resource == Photo::"party.jpg");"#,
1671 )
1672 .unwrap();
1673 assert_validates_with_empty_schema(policy);
1674 }
1675}