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