cedar_policy_core/
validator.rs

1/*
2 * Copyright Cedar Contributors
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17//! Validator for Cedar policies
18#![deny(
19    missing_docs,
20    rustdoc::broken_intra_doc_links,
21    rustdoc::private_intra_doc_links,
22    rustdoc::invalid_codeblock_attributes,
23    rustdoc::invalid_html_tags,
24    rustdoc::invalid_rust_codeblocks,
25    rustdoc::bare_urls,
26    clippy::doc_markdown
27)]
28#![cfg_attr(feature = "wasm", allow(non_snake_case))]
29
30use crate::ast::{Policy, PolicySet, Template};
31use std::collections::HashSet;
32mod level_validate;
33
34mod coreschema;
35#[cfg(feature = "entity-manifest")]
36pub mod entity_manifest;
37pub use coreschema::*;
38mod diagnostics;
39pub use diagnostics::*;
40mod expr_iterator;
41mod extension_schema;
42mod extensions;
43mod rbac;
44mod schema;
45pub use schema::err::*;
46pub use schema::*;
47mod deprecated_schema_compat;
48pub mod json_schema;
49mod str_checks;
50pub use str_checks::confusable_string_checks;
51pub mod cedar_schema;
52pub mod typecheck;
53use typecheck::Typechecker;
54mod partition_nonempty;
55pub mod types;
56
57/// Used to select how a policy will be validated.
58#[derive(Default, Eq, PartialEq, Copy, Clone, Debug)]
59pub enum ValidationMode {
60    /// Strict mode
61    #[default]
62    Strict,
63    /// Permissive mode
64    Permissive,
65    /// Partial validation, allowing you to use an incomplete schema, but
66    /// providing no formal guarantees
67    #[cfg(feature = "partial-validate")]
68    Partial,
69}
70
71impl ValidationMode {
72    /// Does this mode use partial validation. We could conceivably have a
73    /// strict/partial validation mode.
74    fn is_partial(self) -> bool {
75        match self {
76            ValidationMode::Strict | ValidationMode::Permissive => false,
77            #[cfg(feature = "partial-validate")]
78            ValidationMode::Partial => true,
79        }
80    }
81
82    /// Does this mode apply strict validation rules.
83    fn is_strict(self) -> bool {
84        match self {
85            ValidationMode::Strict => true,
86            ValidationMode::Permissive => false,
87            #[cfg(feature = "partial-validate")]
88            ValidationMode::Partial => false,
89        }
90    }
91}
92
93/// Structure containing the context needed for policy validation. This is
94/// currently only the `EntityType`s and `ActionType`s from a single schema.
95#[derive(Debug, Clone)]
96pub struct Validator {
97    schema: ValidatorSchema,
98}
99
100impl Validator {
101    /// Construct a new Validator from a schema file.
102    pub fn new(schema: ValidatorSchema) -> Validator {
103        Self { schema }
104    }
105
106    /// Get the `ValidatorSchema` this `Validator` is using.
107    pub fn schema(&self) -> &ValidatorSchema {
108        &self.schema
109    }
110
111    /// Validate all templates, links, and static policies in a policy set.
112    /// Return a `ValidationResult`.
113    pub fn validate(&self, policies: &PolicySet, mode: ValidationMode) -> ValidationResult {
114        let validate_policy_results: (Vec<_>, Vec<_>) = policies
115            .all_templates()
116            .map(|p| self.validate_policy(p, mode))
117            .unzip();
118        let template_and_static_policy_errs = validate_policy_results.0.into_iter().flatten();
119        let template_and_static_policy_warnings = validate_policy_results.1.into_iter().flatten();
120        let link_errs = policies
121            .policies()
122            .filter_map(|p| self.validate_slots(p, mode))
123            .flatten();
124        ValidationResult::new(
125            template_and_static_policy_errs.chain(link_errs),
126            template_and_static_policy_warnings
127                .chain(confusable_string_checks(policies.all_templates())),
128        )
129    }
130
131    /// Validate all templates, links, and static policies in a policy set.
132    /// If validation passes, also run level validation with `max_deref_level`
133    /// (see RFC 76).
134    /// Return a `ValidationResult`.
135    pub fn validate_with_level(
136        &self,
137        policies: &PolicySet,
138        mode: ValidationMode,
139        max_deref_level: u32,
140    ) -> ValidationResult {
141        let validate_policy_results: (Vec<_>, Vec<_>) = policies
142            .all_templates()
143            .map(|p| self.validate_policy_with_level(p, mode, max_deref_level))
144            .unzip();
145        let template_and_static_policy_errs = validate_policy_results.0.into_iter().flatten();
146        let template_and_static_policy_warnings = validate_policy_results.1.into_iter().flatten();
147        let link_errs = policies
148            .policies()
149            .filter_map(|p| self.validate_slots(p, mode))
150            .flatten();
151        ValidationResult::new(
152            template_and_static_policy_errs.chain(link_errs),
153            template_and_static_policy_warnings
154                .chain(confusable_string_checks(policies.all_templates())),
155        )
156    }
157
158    /// Run all validations against a single static policy or template (note
159    /// that Core `Template` includes static policies as well), gathering all
160    /// validation errors and warnings in the returned iterators.
161    fn validate_policy<'a>(
162        &'a self,
163        p: &'a Template,
164        mode: ValidationMode,
165    ) -> (
166        impl Iterator<Item = ValidationError> + 'a,
167        impl Iterator<Item = ValidationWarning> + 'a,
168    ) {
169        let validation_errors = if mode.is_partial() {
170            // We skip `validate_entity_types`, `validate_action_ids`, and
171            // `validate_action_application` passes for partial schema
172            // validation because there may be arbitrary extra entity types and
173            // actions, so we can never claim that one doesn't exist.
174            None
175        } else {
176            Some(
177                Validator::validate_entity_types(&self.schema, p)
178                    .chain(Validator::validate_enum_entity(&self.schema, p))
179                    .chain(Validator::validate_action_ids(&self.schema, p))
180                    // We could usefully update this pass to apply to partial
181                    // schema if it only failed when there is a known action
182                    // applied to known principal/resource entity types that are
183                    // not in its `appliesTo`.
184                    .chain(self.validate_template_action_application(p)),
185            )
186        }
187        .into_iter()
188        .flatten();
189        let (errors, warnings) = self.typecheck_policy(p, mode);
190        (validation_errors.chain(errors), warnings)
191    }
192
193    /// Run relevant validations against a single template-linked policy,
194    /// gathering all validation errors together in the returned iterator.
195    fn validate_slots<'a>(
196        &'a self,
197        p: &'a Policy,
198        mode: ValidationMode,
199    ) -> Option<impl Iterator<Item = ValidationError> + 'a> {
200        // Ignore static policies since they are already handled by `validate_policy`
201        if p.is_static() {
202            return None;
203        }
204        // In partial validation, there may be arbitrary extra entity types and
205        // actions, so we can never claim that one doesn't exist or that the
206        // action application is invalid.
207        if mode.is_partial() {
208            return None;
209        }
210        // For template-linked policies `Policy::principal_constraint()` and
211        // `Policy::resource_constraint()` return a copy of the constraint with
212        // the slot filled by the appropriate value.
213        Some(
214            self.validate_entity_types_in_slots(p.id(), p.env())
215                .chain(self.validate_linked_action_application(p)),
216        )
217    }
218
219    /// Construct a Typechecker instance and use it to detect any type errors in
220    /// the argument static policy or template (note that Core `Template`
221    /// includes static policies as well) in the context of the schema for this
222    /// validator. Any detected type errors are wrapped and returned as
223    /// `ValidationErrorKind`s.
224    fn typecheck_policy<'a>(
225        &'a self,
226        t: &'a Template,
227        mode: ValidationMode,
228    ) -> (
229        impl Iterator<Item = ValidationError> + 'a,
230        impl Iterator<Item = ValidationWarning> + 'a,
231    ) {
232        let typecheck = Typechecker::new(&self.schema, mode);
233        let mut errors = HashSet::new();
234        let mut warnings = HashSet::new();
235        typecheck.typecheck_policy(t, &mut errors, &mut warnings);
236        (errors.into_iter(), warnings.into_iter())
237    }
238}
239
240#[cfg(test)]
241mod test {
242    use itertools::Itertools;
243    use std::{collections::HashMap, sync::Arc};
244
245    use crate::validator::types::Type;
246    use crate::validator::validation_errors::UnrecognizedActionIdHelp;
247    use crate::validator::Result;
248
249    use super::*;
250    use crate::{
251        ast::{self, PolicyID},
252        est::Annotations,
253        parser::{self, Loc},
254    };
255
256    #[test]
257    fn top_level_validate() -> Result<()> {
258        let mut set = PolicySet::new();
259        let foo_type = "foo_type";
260        let bar_type = "bar_type";
261        let action_name = "action";
262        let schema_file = json_schema::NamespaceDefinition::new(
263            [
264                (
265                    foo_type.parse().unwrap(),
266                    json_schema::StandardEntityType {
267                        member_of_types: vec![],
268                        shape: json_schema::AttributesOrContext::default(),
269                        tags: None,
270                    }
271                    .into(),
272                ),
273                (
274                    bar_type.parse().unwrap(),
275                    json_schema::StandardEntityType {
276                        member_of_types: vec![],
277                        shape: json_schema::AttributesOrContext::default(),
278                        tags: None,
279                    }
280                    .into(),
281                ),
282            ],
283            [(
284                action_name.into(),
285                json_schema::ActionType {
286                    applies_to: Some(json_schema::ApplySpec {
287                        principal_types: vec!["foo_type".parse().unwrap()],
288                        resource_types: vec!["bar_type".parse().unwrap()],
289                        context: json_schema::AttributesOrContext::default(),
290                    }),
291                    member_of: None,
292                    attributes: None,
293                    annotations: Annotations::new(),
294                    loc: None,
295                    #[cfg(feature = "extended-schema")]
296                    defn_loc: None,
297                },
298            )],
299        );
300        let schema = schema_file.try_into().unwrap();
301        let validator = Validator::new(schema);
302
303        let policy_a_src = r#"permit(principal in foo_type::"a", action == Action::"actin", resource == bar_type::"b");"#;
304        let policy_a = parser::parse_policy(Some(PolicyID::from_string("pola")), policy_a_src)
305            .expect("Test Policy Should Parse");
306        set.add_static(policy_a)
307            .expect("Policy already present in PolicySet");
308
309        let policy_b_src = r#"permit(principal in foo_tye::"a", action == Action::"action", resource == br_type::"b");"#;
310        let policy_b = parser::parse_policy(Some(PolicyID::from_string("polb")), policy_b_src)
311            .expect("Test Policy Should Parse");
312        set.add_static(policy_b)
313            .expect("Policy already present in PolicySet");
314
315        let result = validator.validate(&set, ValidationMode::default());
316        let principal_err = ValidationError::unrecognized_entity_type(
317            Some(Loc::new(20..27, Arc::from(policy_b_src))),
318            PolicyID::from_string("polb"),
319            "foo_tye".to_string(),
320            Some("foo_type".to_string()),
321        );
322        let resource_err = ValidationError::unrecognized_entity_type(
323            Some(Loc::new(74..81, Arc::from(policy_b_src))),
324            PolicyID::from_string("polb"),
325            "br_type".to_string(),
326            Some("bar_type".to_string()),
327        );
328        let action_err = ValidationError::unrecognized_action_id(
329            Some(Loc::new(45..60, Arc::from(policy_a_src))),
330            PolicyID::from_string("pola"),
331            "Action::\"actin\"".to_string(),
332            Some(UnrecognizedActionIdHelp::SuggestAlternative(
333                "Action::\"action\"".to_string(),
334            )),
335        );
336
337        assert!(!result.validation_passed());
338        assert!(
339            result.validation_errors().contains(&principal_err),
340            "{result:?}"
341        );
342        assert!(
343            result.validation_errors().contains(&resource_err),
344            "{result:?}"
345        );
346        assert!(
347            result.validation_errors().contains(&action_err),
348            "{result:?}"
349        );
350        Ok(())
351    }
352
353    #[test]
354    fn top_level_validate_with_links() -> Result<()> {
355        let mut set = PolicySet::new();
356        let schema: ValidatorSchema = json_schema::Fragment::from_json_str(
357            r#"
358            {
359                "some_namespace": {
360                    "entityTypes": {
361                        "User": {
362                            "shape": {
363                                "type": "Record",
364                                "attributes": {
365                                    "department": {
366                                        "type": "String"
367                                    },
368                                    "jobLevel": {
369                                        "type": "Long"
370                                    }
371                                }
372                            },
373                            "memberOfTypes": [
374                                "UserGroup"
375                            ]
376                        },
377                        "UserGroup": {},
378                        "Photo" : {}
379                    },
380                    "actions": {
381                        "view": {
382                            "appliesTo": {
383                                "resourceTypes": [
384                                    "Photo"
385                                ],
386                                "principalTypes": [
387                                    "User"
388                                ]
389                            }
390                        }
391                    }
392                }
393            }
394        "#,
395        )
396        .expect("Schema parse error.")
397        .try_into()
398        .expect("Expected valid schema.");
399        let validator = Validator::new(schema);
400
401        let t = parser::parse_policy_or_template(
402            Some(PolicyID::from_string("template")),
403            r#"permit(principal == some_namespace::User::"Alice", action, resource in ?resource);"#,
404        )
405        .expect("Parse Error");
406        let loc = t.loc().cloned();
407        set.add_template(t)
408            .expect("Template already present in PolicySet");
409
410        // the template is valid by itself
411        let result = validator.validate(&set, ValidationMode::default());
412        assert_eq!(
413            result.validation_errors().collect::<Vec<_>>(),
414            Vec::<&ValidationError>::new()
415        );
416
417        // a valid link is valid
418        let mut values = HashMap::new();
419        values.insert(
420            ast::SlotId::resource(),
421            ast::EntityUID::from_components(
422                "some_namespace::Photo".parse().unwrap(),
423                ast::Eid::new("foo"),
424                None,
425            ),
426        );
427        set.link(
428            ast::PolicyID::from_string("template"),
429            ast::PolicyID::from_string("link1"),
430            values,
431        )
432        .expect("Linking failed!");
433        let result = validator.validate(&set, ValidationMode::default());
434        assert!(result.validation_passed());
435
436        // an invalid link results in an error
437        let mut values = HashMap::new();
438        values.insert(
439            ast::SlotId::resource(),
440            ast::EntityUID::from_components(
441                "some_namespace::Undefined".parse().unwrap(),
442                ast::Eid::new("foo"),
443                None,
444            ),
445        );
446        set.link(
447            ast::PolicyID::from_string("template"),
448            ast::PolicyID::from_string("link2"),
449            values,
450        )
451        .expect("Linking failed!");
452        let result = validator.validate(&set, ValidationMode::default());
453        assert!(!result.validation_passed());
454        assert_eq!(result.validation_errors().count(), 2);
455        let undefined_err = ValidationError::unrecognized_entity_type(
456            None,
457            PolicyID::from_string("link2"),
458            "some_namespace::Undefined".to_string(),
459            Some("some_namespace::User".to_string()),
460        );
461        let invalid_action_err = ValidationError::invalid_action_application(
462            loc.clone(),
463            PolicyID::from_string("link2"),
464            false,
465            false,
466        );
467        assert!(result.validation_errors().any(|x| x == &undefined_err));
468        assert!(result.validation_errors().any(|x| x == &invalid_action_err));
469
470        // this is also an invalid link (not a valid resource type for any action in the schema)
471        let mut values = HashMap::new();
472        values.insert(
473            ast::SlotId::resource(),
474            ast::EntityUID::from_components(
475                "some_namespace::User".parse().unwrap(),
476                ast::Eid::new("foo"),
477                None,
478            ),
479        );
480        set.link(
481            ast::PolicyID::from_string("template"),
482            ast::PolicyID::from_string("link3"),
483            values,
484        )
485        .expect("Linking failed!");
486        let result = validator.validate(&set, ValidationMode::default());
487        assert!(!result.validation_passed());
488        // `result` contains the two prior error messages plus one new one
489        assert_eq!(result.validation_errors().count(), 3);
490        let invalid_action_err = ValidationError::invalid_action_application(
491            loc,
492            PolicyID::from_string("link3"),
493            false,
494            false,
495        );
496        assert!(result.validation_errors().contains(&invalid_action_err));
497
498        Ok(())
499    }
500
501    #[test]
502    fn validate_finds_warning_and_error() {
503        let schema: ValidatorSchema = json_schema::Fragment::from_json_str(
504            r#"
505            {
506                "": {
507                    "entityTypes": {
508                        "User": { }
509                    },
510                    "actions": {
511                        "view": {
512                            "appliesTo": {
513                                "resourceTypes": [ "User" ],
514                                "principalTypes": [ "User" ]
515                            }
516                        }
517                    }
518                }
519            }
520        "#,
521        )
522        .expect("Schema parse error.")
523        .try_into()
524        .expect("Expected valid schema.");
525        let validator = Validator::new(schema);
526
527        let mut set = PolicySet::new();
528        let src = r#"permit(principal == User::"าปenry", action, resource) when {1 > true};"#;
529        let p = parser::parse_policy(None, src).unwrap();
530        set.add_static(p).unwrap();
531
532        let result = validator.validate(&set, ValidationMode::default());
533        assert_eq!(
534            result.validation_errors().collect::<Vec<_>>(),
535            vec![&ValidationError::expected_type(
536                typecheck::test::test_utils::get_loc(src, "true"),
537                PolicyID::from_string("policy0"),
538                Type::primitive_long(),
539                Type::singleton_boolean(true),
540                None,
541            )]
542        );
543        assert_eq!(
544            result.validation_warnings().collect::<Vec<_>>(),
545            vec![&ValidationWarning::mixed_script_identifier(
546                None,
547                PolicyID::from_string("policy0"),
548                "าปenry"
549            )]
550        );
551    }
552}
553
554#[cfg(test)]
555mod enumerated_entity_types {
556    use std::collections::HashMap;
557
558    use crate::{
559        ast::{Eid, EntityUID, ExprBuilder, PolicyID, PolicySet, SlotId, Template},
560        expr_builder::ExprBuilder as _,
561        extensions::Extensions,
562        parser::parse_policy_or_template,
563    };
564    use cool_asserts::assert_matches;
565    use itertools::Itertools;
566
567    use crate::validator::{
568        typecheck::test::test_utils::get_loc,
569        types::{EntityLUB, Type},
570        validation_errors::AttributeAccess,
571        ValidationError, ValidationWarning, Validator, ValidatorSchema,
572    };
573
574    #[track_caller]
575    fn schema() -> ValidatorSchema {
576        ValidatorSchema::from_json_value(
577            serde_json::json!(
578                {
579                    "":  {  "entityTypes": {
580                             "Foo": {
581                                "enum": [ "foo" ],
582                            },
583                            "Bar": {
584                                "memberOfTypes": ["Foo"],
585                            },
586                            "Other": { },
587                        },
588                        "actions": {
589                            "a": {
590                                "appliesTo": {
591                                    "principalTypes": ["Foo"],
592                                    "resourceTypes": ["Bar"],
593                                }
594                            }
595                        }
596                }
597            }
598            ),
599            Extensions::none(),
600        )
601        .unwrap()
602    }
603
604    #[test]
605    fn basic() {
606        let schema = schema();
607        let template = parse_policy_or_template(None, r#"permit(principal, action == Action::"a", resource) when { principal == Foo::"foo" };"#).unwrap();
608        let validator = Validator::new(schema);
609        let (errors, warnings) =
610            validator.validate_policy(&template, crate::validator::ValidationMode::Strict);
611        assert!(warnings.collect_vec().is_empty());
612        assert!(errors.collect_vec().is_empty());
613    }
614
615    #[test]
616    fn link() {
617        let schema = schema();
618        let template = parse_policy_or_template(
619            None,
620            r#"permit(principal in ?principal, action == Action::"a", resource);"#,
621        )
622        .unwrap();
623        let policy = Template::link(
624            std::sync::Arc::new(template),
625            PolicyID::from_string("test"),
626            HashMap::from_iter([(SlotId::principal(), r#"Other::"foo""#.parse().unwrap())]),
627        )
628        .unwrap();
629        let mut policy_set = PolicySet::new();
630        let _ = policy_set.add(policy);
631        let validator = Validator::new(schema);
632        let result = validator.validate(&policy_set, crate::validator::ValidationMode::Strict);
633
634        assert_eq!(result.validation_errors().collect_vec().len(), 1);
635    }
636
637    #[test]
638    #[allow(clippy::cognitive_complexity)]
639    fn basic_invalid() {
640        let schema = schema();
641        let template = parse_policy_or_template(None, r#"permit(principal, action == Action::"a", resource) when { principal == Foo::"fo" };"#).unwrap();
642        let validator = Validator::new(schema.clone());
643        let (errors, warnings) =
644            validator.validate_policy(&template, crate::validator::ValidationMode::Strict);
645        assert!(warnings.collect_vec().is_empty());
646        assert_matches!(&errors.collect_vec(), [ValidationError::InvalidEnumEntity(err)] => {
647            assert_eq!(err.err.choices, vec![Eid::new("foo")]);
648            assert_eq!(err.err.uid, EntityUID::with_eid_and_type("Foo", "fo").unwrap());
649        });
650
651        let template = parse_policy_or_template(
652            None,
653            r#"permit(principal == Foo::"๐Ÿˆ", action == Action::"a", resource);"#,
654        )
655        .unwrap();
656        let validator = Validator::new(schema.clone());
657        let (errors, warnings) =
658            validator.validate_policy(&template, crate::validator::ValidationMode::Strict);
659        assert!(warnings.collect_vec().is_empty());
660        assert_matches!(&errors.collect_vec(), [ValidationError::InvalidEnumEntity(err)] => {
661            assert_eq!(err.err.choices, vec![Eid::new("foo")]);
662            assert_eq!(err.err.uid, EntityUID::with_eid_and_type("Foo", "๐Ÿˆ").unwrap());
663        });
664
665        let template = parse_policy_or_template(
666            None,
667            r#"permit(principal in Foo::"๐Ÿˆ", action == Action::"a", resource);"#,
668        )
669        .unwrap();
670        let validator = Validator::new(schema.clone());
671        let (errors, warnings) =
672            validator.validate_policy(&template, crate::validator::ValidationMode::Strict);
673        assert!(warnings.collect_vec().is_empty());
674        assert_matches!(&errors.collect_vec(), [ValidationError::InvalidEnumEntity(err)] => {
675            assert_eq!(err.err.choices, vec![Eid::new("foo")]);
676            assert_eq!(err.err.uid, EntityUID::with_eid_and_type("Foo", "๐Ÿˆ").unwrap());
677        });
678
679        let template = parse_policy_or_template(
680            None,
681            r#"permit(principal, action == Action::"a", resource)
682            when { {"๐Ÿˆ": Foo::"๐Ÿˆ"} has "๐Ÿˆ" };
683        "#,
684        )
685        .unwrap();
686        let validator = Validator::new(schema.clone());
687        let (errors, warnings) =
688            validator.validate_policy(&template, crate::validator::ValidationMode::Strict);
689        assert!(warnings.collect_vec().is_empty());
690        assert_matches!(&errors.collect_vec(), [ValidationError::InvalidEnumEntity(err)] => {
691            assert_eq!(err.err.choices, vec![Eid::new("foo")]);
692            assert_eq!(err.err.uid, EntityUID::with_eid_and_type("Foo", "๐Ÿˆ").unwrap());
693        });
694
695        let template = parse_policy_or_template(
696            None,
697            r#"permit(principal, action == Action::"a", resource)
698            when { [Foo::"๐Ÿˆ"].isEmpty() };
699        "#,
700        )
701        .unwrap();
702        let validator = Validator::new(schema.clone());
703        let (errors, warnings) =
704            validator.validate_policy(&template, crate::validator::ValidationMode::Strict);
705        assert!(warnings.collect_vec().is_empty());
706        assert_matches!(&errors.collect_vec(), [ValidationError::InvalidEnumEntity(err)] => {
707            assert_eq!(err.err.choices, vec![Eid::new("foo")]);
708            assert_eq!(err.err.uid, EntityUID::with_eid_and_type("Foo", "๐Ÿˆ").unwrap());
709        });
710
711        let template = parse_policy_or_template(
712            None,
713            r#"permit(principal, action == Action::"a", resource)
714            when { [{"๐Ÿˆ": Foo::"๐Ÿˆ"}].isEmpty() };
715        "#,
716        )
717        .unwrap();
718        let validator = Validator::new(schema);
719        let (errors, warnings) =
720            validator.validate_policy(&template, crate::validator::ValidationMode::Strict);
721        assert!(warnings.collect_vec().is_empty());
722        assert_matches!(&errors.collect_vec(), [ValidationError::InvalidEnumEntity(err)] => {
723            assert_eq!(err.err.choices, vec![Eid::new("foo")]);
724            assert_eq!(err.err.uid, EntityUID::with_eid_and_type("Foo", "๐Ÿˆ").unwrap());
725        });
726    }
727
728    #[test]
729    fn no_attrs_allowed() {
730        let schema = schema();
731        let src = r#"permit(principal, action == Action::"a", resource) when { principal.foo == "foo" };"#;
732        let template = parse_policy_or_template(None, src).unwrap();
733        let validator = Validator::new(schema);
734        let (errors, warnings) =
735            validator.validate_policy(&template, crate::validator::ValidationMode::Strict);
736        assert!(warnings.collect_vec().is_empty());
737        assert_eq!(
738            errors.collect_vec(),
739            [ValidationError::unsafe_attribute_access(
740                get_loc(src, "principal.foo"),
741                PolicyID::from_string("policy0"),
742                AttributeAccess::EntityLUB(
743                    EntityLUB::single_entity("Foo".parse().unwrap()),
744                    vec!["foo".into()],
745                ),
746                None,
747                false,
748            )]
749        );
750    }
751
752    #[test]
753    fn no_ancestors() {
754        let schema = schema();
755        let src = r#"permit(principal, action == Action::"a", resource) when { principal in Bar::"bar" };"#;
756        let template = parse_policy_or_template(None, src).unwrap();
757        let validator = Validator::new(schema);
758        let (errors, warnings) =
759            validator.validate_policy(&template, crate::validator::ValidationMode::Strict);
760        assert_eq!(
761            warnings.collect_vec(),
762            [ValidationWarning::impossible_policy(
763                get_loc(src, src),
764                PolicyID::from_string("policy0")
765            )]
766        );
767        assert!(errors.collect_vec().is_empty());
768    }
769
770    #[test]
771    fn no_tags_allowed() {
772        let schema = schema();
773        let src = r#"permit(principal, action == Action::"a", resource) when { principal.getTag("foo") == "foo" };"#;
774        let template = parse_policy_or_template(None, src).unwrap();
775        let validator = Validator::new(schema);
776        let (errors, warnings) =
777            validator.validate_policy(&template, crate::validator::ValidationMode::Strict);
778        assert!(warnings.collect_vec().is_empty());
779        assert_eq!(
780            errors.collect_vec(),
781            [ValidationError::unsafe_tag_access(
782                get_loc(src, r#"principal.getTag("foo")"#),
783                PolicyID::from_string("policy0"),
784                Some(EntityLUB::single_entity("Foo".parse().unwrap()),),
785                {
786                    let builder = ExprBuilder::new();
787                    let mut expr = builder.val("foo");
788                    expr.set_data(Some(Type::primitive_string()));
789                    expr
790                },
791            )]
792        );
793    }
794}