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    /// Check that all entity types are defined in the schema, and each entity
194    /// literal that is an action or enum type is defined in the schema. These
195    /// checks are notably not performed by [`Typechecker::typecheck_by_single_request_env`]
196    /// so callers of that function will typically need to call this as well.
197    pub fn validate_entity_types_and_literals<'a>(
198        schema: &'a ValidatorSchema,
199        p: &'a Template,
200    ) -> impl Iterator<Item = ValidationError> + 'a {
201        Validator::validate_entity_types(&schema, p)
202            .chain(Validator::validate_enum_entity(&schema, p))
203            .chain(Validator::validate_action_ids(&schema, p))
204    }
205
206    /// Run relevant validations against a single template-linked policy,
207    /// gathering all validation errors together in the returned iterator.
208    fn validate_slots<'a>(
209        &'a self,
210        p: &'a Policy,
211        mode: ValidationMode,
212    ) -> Option<impl Iterator<Item = ValidationError> + 'a> {
213        // Ignore static policies since they are already handled by `validate_policy`
214        if p.is_static() {
215            return None;
216        }
217        // In partial validation, there may be arbitrary extra entity types and
218        // actions, so we can never claim that one doesn't exist or that the
219        // action application is invalid.
220        if mode.is_partial() {
221            return None;
222        }
223        // For template-linked policies `Policy::principal_constraint()` and
224        // `Policy::resource_constraint()` return a copy of the constraint with
225        // the slot filled by the appropriate value.
226        Some(
227            self.validate_entity_types_in_slots(p.id(), p.env())
228                .chain(self.validate_linked_action_application(p)),
229        )
230    }
231
232    /// Construct a Typechecker instance and use it to detect any type errors in
233    /// the argument static policy or template (note that Core `Template`
234    /// includes static policies as well) in the context of the schema for this
235    /// validator. Any detected type errors are wrapped and returned as
236    /// `ValidationErrorKind`s.
237    fn typecheck_policy<'a>(
238        &'a self,
239        t: &'a Template,
240        mode: ValidationMode,
241    ) -> (
242        impl Iterator<Item = ValidationError> + 'a,
243        impl Iterator<Item = ValidationWarning> + 'a,
244    ) {
245        let typecheck = Typechecker::new(&self.schema, mode);
246        let mut errors = HashSet::new();
247        let mut warnings = HashSet::new();
248        typecheck.typecheck_policy(t, &mut errors, &mut warnings);
249        (errors.into_iter(), warnings.into_iter())
250    }
251}
252
253#[cfg(test)]
254mod test {
255    use itertools::Itertools;
256    use std::{collections::HashMap, sync::Arc};
257
258    use crate::validator::types::Type;
259    use crate::validator::validation_errors::UnrecognizedActionIdHelp;
260    use crate::validator::Result;
261
262    use super::*;
263    use crate::{
264        ast::{self, PolicyID},
265        est::Annotations,
266        parser::{self, Loc},
267    };
268
269    use similar_asserts::assert_eq;
270
271    #[test]
272    fn top_level_validate() -> Result<()> {
273        let mut set = PolicySet::new();
274        let foo_type = "foo_type";
275        let bar_type = "bar_type";
276        let action_name = "action";
277        let schema_file = json_schema::NamespaceDefinition::new(
278            [
279                (
280                    foo_type.parse().unwrap(),
281                    json_schema::StandardEntityType {
282                        member_of_types: vec![],
283                        shape: json_schema::AttributesOrContext::default(),
284                        tags: None,
285                    }
286                    .into(),
287                ),
288                (
289                    bar_type.parse().unwrap(),
290                    json_schema::StandardEntityType {
291                        member_of_types: vec![],
292                        shape: json_schema::AttributesOrContext::default(),
293                        tags: None,
294                    }
295                    .into(),
296                ),
297            ],
298            [(
299                action_name.into(),
300                json_schema::ActionType {
301                    applies_to: Some(json_schema::ApplySpec {
302                        principal_types: vec!["foo_type".parse().unwrap()],
303                        resource_types: vec!["bar_type".parse().unwrap()],
304                        context: json_schema::AttributesOrContext::default(),
305                    }),
306                    member_of: None,
307                    attributes: None,
308                    annotations: Annotations::new(),
309                    loc: None,
310                    #[cfg(feature = "extended-schema")]
311                    defn_loc: None,
312                },
313            )],
314        );
315        let schema = schema_file.try_into().unwrap();
316        let validator = Validator::new(schema);
317
318        let policy_a_src = r#"permit(principal in foo_type::"a", action == Action::"actin", resource == bar_type::"b");"#;
319        let policy_a = parser::parse_policy(Some(PolicyID::from_string("pola")), policy_a_src)
320            .expect("Test Policy Should Parse");
321        set.add_static(policy_a)
322            .expect("Policy already present in PolicySet");
323
324        let policy_b_src = r#"permit(principal in foo_tye::"a", action == Action::"action", resource == br_type::"b");"#;
325        let policy_b = parser::parse_policy(Some(PolicyID::from_string("polb")), policy_b_src)
326            .expect("Test Policy Should Parse");
327        set.add_static(policy_b)
328            .expect("Policy already present in PolicySet");
329
330        let result = validator.validate(&set, ValidationMode::default());
331        let principal_err = ValidationError::unrecognized_entity_type(
332            Some(Loc::new(20..27, Arc::from(policy_b_src))),
333            PolicyID::from_string("polb"),
334            "foo_tye".to_string(),
335            Some("foo_type".to_string()),
336        );
337        let resource_err = ValidationError::unrecognized_entity_type(
338            Some(Loc::new(74..81, Arc::from(policy_b_src))),
339            PolicyID::from_string("polb"),
340            "br_type".to_string(),
341            Some("bar_type".to_string()),
342        );
343        let action_err = ValidationError::unrecognized_action_id(
344            Some(Loc::new(45..60, Arc::from(policy_a_src))),
345            PolicyID::from_string("pola"),
346            "Action::\"actin\"".to_string(),
347            Some(UnrecognizedActionIdHelp::SuggestAlternative(
348                "Action::\"action\"".to_string(),
349            )),
350        );
351
352        assert!(!result.validation_passed());
353        assert!(
354            result.validation_errors().contains(&principal_err),
355            "{result:?}"
356        );
357        assert!(
358            result.validation_errors().contains(&resource_err),
359            "{result:?}"
360        );
361        assert!(
362            result.validation_errors().contains(&action_err),
363            "{result:?}"
364        );
365        Ok(())
366    }
367
368    #[test]
369    fn top_level_validate_with_links() -> Result<()> {
370        let mut set = PolicySet::new();
371        let schema: ValidatorSchema = json_schema::Fragment::from_json_str(
372            r#"
373            {
374                "some_namespace": {
375                    "entityTypes": {
376                        "User": {
377                            "shape": {
378                                "type": "Record",
379                                "attributes": {
380                                    "department": {
381                                        "type": "String"
382                                    },
383                                    "jobLevel": {
384                                        "type": "Long"
385                                    }
386                                }
387                            },
388                            "memberOfTypes": [
389                                "UserGroup"
390                            ]
391                        },
392                        "UserGroup": {},
393                        "Photo" : {}
394                    },
395                    "actions": {
396                        "view": {
397                            "appliesTo": {
398                                "resourceTypes": [
399                                    "Photo"
400                                ],
401                                "principalTypes": [
402                                    "User"
403                                ]
404                            }
405                        }
406                    }
407                }
408            }
409        "#,
410        )
411        .expect("Schema parse error.")
412        .try_into()
413        .expect("Expected valid schema.");
414        let validator = Validator::new(schema);
415
416        let t = parser::parse_policy_or_template(
417            Some(PolicyID::from_string("template")),
418            r#"permit(principal == some_namespace::User::"Alice", action, resource in ?resource);"#,
419        )
420        .expect("Parse Error");
421        let loc = t.loc().cloned();
422        set.add_template(t)
423            .expect("Template already present in PolicySet");
424
425        // the template is valid by itself
426        let result = validator.validate(&set, ValidationMode::default());
427        assert_eq!(
428            result.validation_errors().collect::<Vec<_>>(),
429            Vec::<&ValidationError>::new()
430        );
431
432        // a valid link is valid
433        let mut values = HashMap::new();
434        values.insert(
435            ast::SlotId::resource(),
436            ast::EntityUID::from_components(
437                "some_namespace::Photo".parse().unwrap(),
438                ast::Eid::new("foo"),
439                None,
440            ),
441        );
442        set.link(
443            ast::PolicyID::from_string("template"),
444            ast::PolicyID::from_string("link1"),
445            values,
446        )
447        .expect("Linking failed!");
448        let result = validator.validate(&set, ValidationMode::default());
449        assert!(result.validation_passed());
450
451        // an invalid link results in an error
452        let mut values = HashMap::new();
453        values.insert(
454            ast::SlotId::resource(),
455            ast::EntityUID::from_components(
456                "some_namespace::Undefined".parse().unwrap(),
457                ast::Eid::new("foo"),
458                None,
459            ),
460        );
461        set.link(
462            ast::PolicyID::from_string("template"),
463            ast::PolicyID::from_string("link2"),
464            values,
465        )
466        .expect("Linking failed!");
467        let result = validator.validate(&set, ValidationMode::default());
468        assert!(!result.validation_passed());
469        assert_eq!(result.validation_errors().count(), 2);
470        let undefined_err = ValidationError::unrecognized_entity_type(
471            None,
472            PolicyID::from_string("link2"),
473            "some_namespace::Undefined".to_string(),
474            Some("some_namespace::User".to_string()),
475        );
476        let invalid_action_err = ValidationError::invalid_action_application(
477            loc.clone(),
478            PolicyID::from_string("link2"),
479            false,
480            false,
481        );
482
483        let actual_undef_error = result
484            .validation_errors()
485            .find(|e| matches!(e, ValidationError::UnrecognizedEntityType(_)))
486            .unwrap();
487        assert_eq!(actual_undef_error, &undefined_err);
488
489        let actual_action_error = result
490            .validation_errors()
491            .find(|e| matches!(e, ValidationError::InvalidActionApplication(_)))
492            .unwrap();
493        assert_eq!(actual_action_error, &invalid_action_err);
494
495        // this is also an invalid link (not a valid resource type for any action in the schema)
496        let mut values = HashMap::new();
497        values.insert(
498            ast::SlotId::resource(),
499            ast::EntityUID::from_components(
500                "some_namespace::User".parse().unwrap(),
501                ast::Eid::new("foo"),
502                None,
503            ),
504        );
505        set.link(
506            ast::PolicyID::from_string("template"),
507            ast::PolicyID::from_string("link3"),
508            values,
509        )
510        .expect("Linking failed!");
511        let result = validator.validate(&set, ValidationMode::default());
512        assert!(!result.validation_passed());
513        // `result` contains the two prior error messages plus one new one
514        assert_eq!(result.validation_errors().count(), 3);
515        let invalid_action_err = ValidationError::invalid_action_application(
516            loc,
517            PolicyID::from_string("link3"),
518            false,
519            false,
520        );
521        assert!(result.validation_errors().contains(&invalid_action_err));
522
523        Ok(())
524    }
525
526    #[test]
527    fn validate_finds_warning_and_error() {
528        let schema: ValidatorSchema = json_schema::Fragment::from_json_str(
529            r#"
530            {
531                "": {
532                    "entityTypes": {
533                        "User": { }
534                    },
535                    "actions": {
536                        "view": {
537                            "appliesTo": {
538                                "resourceTypes": [ "User" ],
539                                "principalTypes": [ "User" ]
540                            }
541                        }
542                    }
543                }
544            }
545        "#,
546        )
547        .expect("Schema parse error.")
548        .try_into()
549        .expect("Expected valid schema.");
550        let validator = Validator::new(schema);
551
552        let mut set = PolicySet::new();
553        let src = r#"permit(principal == User::"าปenry", action, resource) when {1 > true};"#;
554        let p = parser::parse_policy(None, src).unwrap();
555        set.add_static(p).unwrap();
556
557        let result = validator.validate(&set, ValidationMode::default());
558        assert_eq!(
559            result.validation_errors().collect::<Vec<_>>(),
560            vec![&ValidationError::expected_type(
561                typecheck::test::test_utils::get_loc(src, "true"),
562                PolicyID::from_string("policy0"),
563                Type::primitive_long(),
564                Type::singleton_boolean(true),
565                None,
566            )]
567        );
568        assert_eq!(
569            result.validation_warnings().collect::<Vec<_>>(),
570            vec![&ValidationWarning::mixed_script_identifier(
571                None,
572                PolicyID::from_string("policy0"),
573                "าปenry"
574            )]
575        );
576    }
577}
578
579#[cfg(test)]
580mod enumerated_entity_types {
581    use std::collections::HashMap;
582
583    use crate::{
584        ast::{Eid, EntityUID, ExprBuilder, PolicyID, PolicySet, SlotId, Template},
585        expr_builder::ExprBuilder as _,
586        extensions::Extensions,
587        parser::parse_policy_or_template,
588    };
589    use cool_asserts::assert_matches;
590    use itertools::Itertools;
591
592    use crate::validator::{
593        typecheck::test::test_utils::get_loc,
594        types::{EntityLUB, Type},
595        validation_errors::AttributeAccess,
596        ValidationError, ValidationWarning, Validator, ValidatorSchema,
597    };
598
599    #[track_caller]
600    fn schema() -> ValidatorSchema {
601        ValidatorSchema::from_json_value(
602            serde_json::json!(
603                {
604                    "":  {  "entityTypes": {
605                             "Foo": {
606                                "enum": [ "foo" ],
607                            },
608                            "Bar": {
609                                "memberOfTypes": ["Foo"],
610                            },
611                            "Other": { },
612                        },
613                        "actions": {
614                            "a": {
615                                "appliesTo": {
616                                    "principalTypes": ["Foo"],
617                                    "resourceTypes": ["Bar"],
618                                }
619                            }
620                        }
621                }
622            }
623            ),
624            Extensions::none(),
625        )
626        .unwrap()
627    }
628
629    #[test]
630    fn basic() {
631        let schema = schema();
632        let template = parse_policy_or_template(None, r#"permit(principal, action == Action::"a", resource) when { principal == Foo::"foo" };"#).unwrap();
633        let validator = Validator::new(schema);
634        let (errors, warnings) =
635            validator.validate_policy(&template, crate::validator::ValidationMode::Strict);
636        assert!(warnings.collect_vec().is_empty());
637        assert!(errors.collect_vec().is_empty());
638    }
639
640    #[test]
641    fn link() {
642        let schema = schema();
643        let template = parse_policy_or_template(
644            None,
645            r#"permit(principal in ?principal, action == Action::"a", resource);"#,
646        )
647        .unwrap();
648        let policy = Template::link(
649            std::sync::Arc::new(template),
650            PolicyID::from_string("test"),
651            HashMap::from_iter([(SlotId::principal(), r#"Other::"foo""#.parse().unwrap())]),
652        )
653        .unwrap();
654        let mut policy_set = PolicySet::new();
655        let _ = policy_set.add(policy);
656        let validator = Validator::new(schema);
657        let result = validator.validate(&policy_set, crate::validator::ValidationMode::Strict);
658
659        assert_eq!(result.validation_errors().collect_vec().len(), 1);
660    }
661
662    #[test]
663    #[allow(clippy::cognitive_complexity)]
664    fn basic_invalid() {
665        let schema = schema();
666        let template = parse_policy_or_template(None, r#"permit(principal, action == Action::"a", resource) when { principal == Foo::"fo" };"#).unwrap();
667        let validator = Validator::new(schema.clone());
668        let (errors, warnings) =
669            validator.validate_policy(&template, crate::validator::ValidationMode::Strict);
670        assert!(warnings.collect_vec().is_empty());
671        assert_matches!(&errors.collect_vec(), [ValidationError::InvalidEnumEntity(err)] => {
672            assert_eq!(err.err.choices, vec![Eid::new("foo")]);
673            assert_eq!(err.err.uid, EntityUID::with_eid_and_type("Foo", "fo").unwrap());
674        });
675
676        let template = parse_policy_or_template(
677            None,
678            r#"permit(principal == Foo::"๐Ÿˆ", action == Action::"a", resource);"#,
679        )
680        .unwrap();
681        let validator = Validator::new(schema.clone());
682        let (errors, warnings) =
683            validator.validate_policy(&template, crate::validator::ValidationMode::Strict);
684        assert!(warnings.collect_vec().is_empty());
685        assert_matches!(&errors.collect_vec(), [ValidationError::InvalidEnumEntity(err)] => {
686            assert_eq!(err.err.choices, vec![Eid::new("foo")]);
687            assert_eq!(err.err.uid, EntityUID::with_eid_and_type("Foo", "๐Ÿˆ").unwrap());
688        });
689
690        let template = parse_policy_or_template(
691            None,
692            r#"permit(principal in Foo::"๐Ÿˆ", action == Action::"a", resource);"#,
693        )
694        .unwrap();
695        let validator = Validator::new(schema.clone());
696        let (errors, warnings) =
697            validator.validate_policy(&template, crate::validator::ValidationMode::Strict);
698        assert!(warnings.collect_vec().is_empty());
699        assert_matches!(&errors.collect_vec(), [ValidationError::InvalidEnumEntity(err)] => {
700            assert_eq!(err.err.choices, vec![Eid::new("foo")]);
701            assert_eq!(err.err.uid, EntityUID::with_eid_and_type("Foo", "๐Ÿˆ").unwrap());
702        });
703
704        let template = parse_policy_or_template(
705            None,
706            r#"permit(principal, action == Action::"a", resource)
707            when { {"๐Ÿˆ": Foo::"๐Ÿˆ"} has "๐Ÿˆ" };
708        "#,
709        )
710        .unwrap();
711        let validator = Validator::new(schema.clone());
712        let (errors, warnings) =
713            validator.validate_policy(&template, crate::validator::ValidationMode::Strict);
714        assert!(warnings.collect_vec().is_empty());
715        assert_matches!(&errors.collect_vec(), [ValidationError::InvalidEnumEntity(err)] => {
716            assert_eq!(err.err.choices, vec![Eid::new("foo")]);
717            assert_eq!(err.err.uid, EntityUID::with_eid_and_type("Foo", "๐Ÿˆ").unwrap());
718        });
719
720        let template = parse_policy_or_template(
721            None,
722            r#"permit(principal, action == Action::"a", resource)
723            when { [Foo::"๐Ÿˆ"].isEmpty() };
724        "#,
725        )
726        .unwrap();
727        let validator = Validator::new(schema.clone());
728        let (errors, warnings) =
729            validator.validate_policy(&template, crate::validator::ValidationMode::Strict);
730        assert!(warnings.collect_vec().is_empty());
731        assert_matches!(&errors.collect_vec(), [ValidationError::InvalidEnumEntity(err)] => {
732            assert_eq!(err.err.choices, vec![Eid::new("foo")]);
733            assert_eq!(err.err.uid, EntityUID::with_eid_and_type("Foo", "๐Ÿˆ").unwrap());
734        });
735
736        let template = parse_policy_or_template(
737            None,
738            r#"permit(principal, action == Action::"a", resource)
739            when { [{"๐Ÿˆ": Foo::"๐Ÿˆ"}].isEmpty() };
740        "#,
741        )
742        .unwrap();
743        let validator = Validator::new(schema);
744        let (errors, warnings) =
745            validator.validate_policy(&template, crate::validator::ValidationMode::Strict);
746        assert!(warnings.collect_vec().is_empty());
747        assert_matches!(&errors.collect_vec(), [ValidationError::InvalidEnumEntity(err)] => {
748            assert_eq!(err.err.choices, vec![Eid::new("foo")]);
749            assert_eq!(err.err.uid, EntityUID::with_eid_and_type("Foo", "๐Ÿˆ").unwrap());
750        });
751    }
752
753    #[test]
754    fn no_attrs_allowed() {
755        let schema = schema();
756        let src = r#"permit(principal, action == Action::"a", resource) when { principal.foo == "foo" };"#;
757        let template = parse_policy_or_template(None, src).unwrap();
758        let validator = Validator::new(schema);
759        let (errors, warnings) =
760            validator.validate_policy(&template, crate::validator::ValidationMode::Strict);
761        assert!(warnings.collect_vec().is_empty());
762        assert_eq!(
763            errors.collect_vec(),
764            [ValidationError::unsafe_attribute_access(
765                get_loc(src, "principal.foo"),
766                PolicyID::from_string("policy0"),
767                AttributeAccess::EntityLUB(
768                    EntityLUB::single_entity("Foo".parse().unwrap()),
769                    vec!["foo".into()],
770                ),
771                None,
772                false,
773            )]
774        );
775    }
776
777    #[test]
778    fn no_ancestors() {
779        let schema = schema();
780        let src = r#"permit(principal, action == Action::"a", resource) when { principal in Bar::"bar" };"#;
781        let template = parse_policy_or_template(None, src).unwrap();
782        let validator = Validator::new(schema);
783        let (errors, warnings) =
784            validator.validate_policy(&template, crate::validator::ValidationMode::Strict);
785        assert_eq!(
786            warnings.collect_vec(),
787            [ValidationWarning::impossible_policy(
788                get_loc(src, src),
789                PolicyID::from_string("policy0")
790            )]
791        );
792        assert!(errors.collect_vec().is_empty());
793    }
794
795    #[test]
796    fn no_tags_allowed() {
797        let schema = schema();
798        let src = r#"permit(principal, action == Action::"a", resource) when { principal.getTag("foo") == "foo" };"#;
799        let template = parse_policy_or_template(None, src).unwrap();
800        let validator = Validator::new(schema);
801        let (errors, warnings) =
802            validator.validate_policy(&template, crate::validator::ValidationMode::Strict);
803        assert!(warnings.collect_vec().is_empty());
804        assert_eq!(
805            errors.collect_vec(),
806            [ValidationError::unsafe_tag_access(
807                get_loc(src, r#"principal.getTag("foo")"#),
808                PolicyID::from_string("policy0"),
809                Some(EntityLUB::single_entity("Foo".parse().unwrap()),),
810                {
811                    let builder = ExprBuilder::new();
812                    let mut expr = builder.val("foo");
813                    expr.set_data(Some(Type::primitive_string()));
814                    expr
815                },
816            )]
817        );
818    }
819}