Coverage Report

Created: 2025-02-21 07:11

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