Skip to main content

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