cedar_policy_validator/
schema.rs

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//! Defines structures for entity type and action id information used by the
18//! validator. The contents of these structures should be populated from and schema
19//! with a few transformations applied to the data. Specifically, the
20//! `member_of` relation from the schema is reversed and the transitive closure is
21//! computed to obtain a `descendants` relation.
22
23use std::collections::{hash_map::Entry, HashMap, HashSet};
24
25use cedar_policy_core::{
26    ast::{Entity, EntityType, EntityUID, Name},
27    entities::{Entities, EntitiesError, TCComputation},
28    extensions::Extensions,
29    transitive_closure::compute_tc,
30};
31use serde::{Deserialize, Serialize};
32use serde_with::serde_as;
33
34use super::NamespaceDefinition;
35use crate::{
36    err::*,
37    human_schema::SchemaWarning,
38    types::{Attributes, EntityRecordKind, OpenTag, Type},
39    SchemaFragment,
40};
41
42mod action;
43pub use action::ValidatorActionId;
44pub(crate) use action::ValidatorApplySpec;
45mod entity_type;
46pub use entity_type::ValidatorEntityType;
47mod namespace_def;
48pub(crate) use namespace_def::is_action_entity_type;
49pub use namespace_def::ValidatorNamespaceDef;
50#[cfg(test)]
51pub(crate) use namespace_def::ACTION_ENTITY_TYPE;
52
53// We do not have a formal model for action attributes, so we disable them by default.
54#[derive(Eq, PartialEq, Copy, Clone, Default)]
55pub enum ActionBehavior {
56    /// Action entities cannot have attributes. Attempting to declare attributes
57    /// will result in a error when constructing the schema.
58    #[default]
59    ProhibitAttributes,
60    /// Action entities may have attributes.
61    PermitAttributes,
62}
63
64#[derive(Debug)]
65pub struct ValidatorSchemaFragment(Vec<ValidatorNamespaceDef>);
66
67impl TryInto<ValidatorSchemaFragment> for SchemaFragment {
68    type Error = SchemaError;
69
70    fn try_into(self) -> Result<ValidatorSchemaFragment> {
71        ValidatorSchemaFragment::from_schema_fragment(
72            self,
73            ActionBehavior::default(),
74            Extensions::all_available(),
75        )
76    }
77}
78
79impl ValidatorSchemaFragment {
80    pub fn from_namespaces(namespaces: impl IntoIterator<Item = ValidatorNamespaceDef>) -> Self {
81        Self(namespaces.into_iter().collect())
82    }
83
84    pub fn from_schema_fragment(
85        fragment: SchemaFragment,
86        action_behavior: ActionBehavior,
87        extensions: Extensions<'_>,
88    ) -> Result<Self> {
89        Ok(Self(
90            fragment
91                .0
92                .into_iter()
93                .map(|(fragment_ns, ns_def)| {
94                    ValidatorNamespaceDef::from_namespace_definition(
95                        Some(fragment_ns),
96                        ns_def,
97                        action_behavior,
98                        extensions,
99                    )
100                })
101                .collect::<Result<Vec<_>>>()?,
102        ))
103    }
104
105    /// Access the `Name`s for the namespaces in this fragment.
106    pub fn namespaces(&self) -> impl Iterator<Item = &Option<Name>> {
107        self.0.iter().map(|d| d.namespace())
108    }
109}
110
111#[serde_as]
112#[derive(Clone, Debug, Serialize)]
113pub struct ValidatorSchema {
114    /// Map from entity type names to the ValidatorEntityType object.
115    #[serde(rename = "entityTypes")]
116    #[serde_as(as = "Vec<(_, _)>")]
117    entity_types: HashMap<Name, ValidatorEntityType>,
118
119    /// Map from action id names to the ValidatorActionId object.
120    #[serde(rename = "actionIds")]
121    #[serde_as(as = "Vec<(_, _)>")]
122    action_ids: HashMap<EntityUID, ValidatorActionId>,
123}
124
125impl std::str::FromStr for ValidatorSchema {
126    type Err = SchemaError;
127
128    fn from_str(s: &str) -> Result<Self> {
129        serde_json::from_str::<SchemaFragment>(s)?.try_into()
130    }
131}
132
133impl TryFrom<NamespaceDefinition> for ValidatorSchema {
134    type Error = SchemaError;
135
136    fn try_from(nsd: NamespaceDefinition) -> Result<ValidatorSchema> {
137        ValidatorSchema::from_schema_fragments([ValidatorSchemaFragment::from_namespaces([
138            nsd.try_into()?
139        ])])
140    }
141}
142
143impl TryFrom<SchemaFragment> for ValidatorSchema {
144    type Error = SchemaError;
145
146    fn try_from(frag: SchemaFragment) -> Result<ValidatorSchema> {
147        ValidatorSchema::from_schema_fragments([frag.try_into()?])
148    }
149}
150
151impl ValidatorSchema {
152    // Create a ValidatorSchema without any entity types or actions ids.
153    pub fn empty() -> ValidatorSchema {
154        Self {
155            entity_types: HashMap::new(),
156            action_ids: HashMap::new(),
157        }
158    }
159
160    /// Construct a `ValidatorSchema` from a JSON value (which should be an
161    /// object matching the `SchemaFileFormat` shape).
162    pub fn from_json_value(json: serde_json::Value, extensions: Extensions<'_>) -> Result<Self> {
163        Self::from_schema_file(
164            SchemaFragment::from_json_value(json)?,
165            ActionBehavior::default(),
166            extensions,
167        )
168    }
169
170    /// Construct a `ValidatorSchema` directly from a file.
171    pub fn from_file(file: impl std::io::Read, extensions: Extensions<'_>) -> Result<Self> {
172        Self::from_schema_file(
173            SchemaFragment::from_file(file)?,
174            ActionBehavior::default(),
175            extensions,
176        )
177    }
178
179    pub fn from_file_natural(
180        r: impl std::io::Read,
181        extensions: Extensions<'_>,
182    ) -> std::result::Result<(Self, impl Iterator<Item = SchemaWarning>), HumanSchemaError> {
183        let (fragment, warnings) = SchemaFragment::from_file_natural(r)?;
184        let schema_and_warnings =
185            Self::from_schema_file(fragment, ActionBehavior::default(), extensions)
186                .map(|schema| (schema, warnings))?;
187        Ok(schema_and_warnings)
188    }
189
190    pub fn from_str_natural(
191        src: &str,
192        extensions: Extensions<'_>,
193    ) -> std::result::Result<(Self, impl Iterator<Item = SchemaWarning>), HumanSchemaError> {
194        let (fragment, warnings) = SchemaFragment::from_str_natural(src)?;
195        let schema_and_warnings =
196            Self::from_schema_file(fragment, ActionBehavior::default(), extensions)
197                .map(|schema| (schema, warnings))?;
198        Ok(schema_and_warnings)
199    }
200
201    pub fn from_schema_file(
202        schema_file: SchemaFragment,
203        action_behavior: ActionBehavior,
204        extensions: Extensions<'_>,
205    ) -> Result<ValidatorSchema> {
206        Self::from_schema_fragments([ValidatorSchemaFragment::from_schema_fragment(
207            schema_file,
208            action_behavior,
209            extensions,
210        )?])
211    }
212
213    /// Construct a new `ValidatorSchema` from some number of schema fragments.
214    pub fn from_schema_fragments(
215        fragments: impl IntoIterator<Item = ValidatorSchemaFragment>,
216    ) -> Result<ValidatorSchema> {
217        let mut type_defs = HashMap::new();
218        let mut entity_type_fragments = HashMap::new();
219        let mut action_fragments = HashMap::new();
220
221        for ns_def in fragments.into_iter().flat_map(|f| f.0.into_iter()) {
222            // Build aggregate maps for the declared typedefs, entity types, and
223            // actions, checking that nothing is defined twice.  Namespaces were
224            // already added by the `ValidatorNamespaceDef`, so the same base
225            // type name may appear multiple times so long as the namespaces are
226            // different.
227            for (name, ty) in ns_def.type_defs.type_defs {
228                match type_defs.entry(name) {
229                    Entry::Vacant(v) => v.insert(ty),
230                    Entry::Occupied(o) => {
231                        return Err(SchemaError::DuplicateCommonType(o.key().to_string()));
232                    }
233                };
234            }
235
236            for (name, entity_type) in ns_def.entity_types.entity_types {
237                match entity_type_fragments.entry(name) {
238                    Entry::Vacant(v) => v.insert(entity_type),
239                    Entry::Occupied(o) => {
240                        return Err(SchemaError::DuplicateEntityType(o.key().to_string()))
241                    }
242                };
243            }
244
245            for (action_euid, action) in ns_def.actions.actions {
246                match action_fragments.entry(action_euid) {
247                    Entry::Vacant(v) => v.insert(action),
248                    Entry::Occupied(o) => {
249                        return Err(SchemaError::DuplicateAction(o.key().to_string()))
250                    }
251                };
252            }
253        }
254
255        // Invert the `parents` relation defined by entities and action so far
256        // to get a `children` relation.
257        let mut entity_children = HashMap::new();
258        for (name, entity_type) in entity_type_fragments.iter() {
259            for parent in entity_type.parents.iter() {
260                entity_children
261                    .entry(parent.clone())
262                    .or_insert_with(HashSet::new)
263                    .insert(name.clone());
264            }
265        }
266
267        let mut entity_types = entity_type_fragments
268            .into_iter()
269            .map(|(name, entity_type)| -> Result<_> {
270                // Keys of the `entity_children` map were values of an
271                // `memberOfTypes` list, so they might not have been declared in
272                // their fragment.  By removing entries from `entity_children`
273                // where the key is a declared name, we will be left with a map
274                // where the keys are undeclared. These keys are used to report
275                // an error when undeclared entity types are referenced inside a
276                // `memberOfTypes` list. The error is reported alongside the
277                // error for any other undeclared entity types by
278                // `check_for_undeclared`.
279                let descendants = entity_children.remove(&name).unwrap_or_default();
280                let (attributes, open_attributes) = Self::record_attributes_or_none(
281                    entity_type.attributes.resolve_type_defs(&type_defs)?,
282                )
283                .ok_or(SchemaError::ContextOrShapeNotRecord(
284                    ContextOrShape::EntityTypeShape(name.clone()),
285                ))?;
286                Ok((
287                    name.clone(),
288                    ValidatorEntityType {
289                        name,
290                        descendants,
291                        attributes,
292                        open_attributes,
293                    },
294                ))
295            })
296            .collect::<Result<HashMap<_, _>>>()?;
297
298        let mut action_children = HashMap::new();
299        for (euid, action) in action_fragments.iter() {
300            for parent in action.parents.iter() {
301                action_children
302                    .entry(parent.clone())
303                    .or_insert_with(HashSet::new)
304                    .insert(euid.clone());
305            }
306        }
307        let mut action_ids = action_fragments
308            .into_iter()
309            .map(|(name, action)| -> Result<_> {
310                let descendants = action_children.remove(&name).unwrap_or_default();
311                let (context, open_context_attributes) =
312                    Self::record_attributes_or_none(action.context.resolve_type_defs(&type_defs)?)
313                        .ok_or(SchemaError::ContextOrShapeNotRecord(
314                            ContextOrShape::ActionContext(name.clone()),
315                        ))?;
316                Ok((
317                    name.clone(),
318                    ValidatorActionId {
319                        name,
320                        applies_to: action.applies_to,
321                        descendants,
322                        context: Type::record_with_attributes(
323                            context.attrs,
324                            open_context_attributes,
325                        ),
326                        attribute_types: action.attribute_types,
327                        attributes: action.attributes,
328                    },
329                ))
330            })
331            .collect::<Result<HashMap<_, _>>>()?;
332
333        // We constructed entity types and actions with child maps, but we need
334        // transitively closed descendants.
335        compute_tc(&mut entity_types, false)?;
336        // Pass `true` here so that we also check that the action hierarchy does
337        // not contain cycles.
338        compute_tc(&mut action_ids, true)?;
339
340        // Return with an error if there is an undeclared entity or action
341        // referenced in any fragment. `{entity,action}_children` are provided
342        // for the `undeclared_parent_{entities,actions}` arguments because
343        // removed keys from these maps as we encountered declarations for the
344        // entity types or actions. Any keys left in the map are therefore
345        // undeclared.
346        Self::check_for_undeclared(
347            &entity_types,
348            entity_children.into_keys(),
349            &action_ids,
350            action_children.into_keys(),
351        )?;
352
353        Ok(ValidatorSchema {
354            entity_types,
355            action_ids,
356        })
357    }
358
359    /// Check that all entity types and actions referenced in the schema are in
360    /// the set of declared entity type or action names. Point of caution: this
361    /// function assumes that all entity types are fully qualified. This is
362    /// handled by the `SchemaFragment` constructor.
363    fn check_for_undeclared(
364        entity_types: &HashMap<Name, ValidatorEntityType>,
365        undeclared_parent_entities: impl IntoIterator<Item = Name>,
366        action_ids: &HashMap<EntityUID, ValidatorActionId>,
367        undeclared_parent_actions: impl IntoIterator<Item = EntityUID>,
368    ) -> Result<()> {
369        // When we constructed `entity_types`, we removed entity types from  the
370        // `entity_children` map as we encountered a declaration for that type.
371        // Any entity types left in the map are therefore undeclared. These are
372        // any undeclared entity types which appeared in a `memberOf` list.
373        let mut undeclared_e = undeclared_parent_entities
374            .into_iter()
375            .map(|n| n.to_string())
376            .collect::<HashSet<_>>();
377        // Looking at entity types, we need to check entity references in
378        // attribute types. We already know that all elements of the
379        // `descendants` list were declared because the list is a result of
380        // inverting the `memberOf` relationship which mapped declared entity
381        // types to their parent entity types.
382        for entity_type in entity_types.values() {
383            for (_, attr_typ) in entity_type.attributes() {
384                Self::check_undeclared_in_type(
385                    &attr_typ.attr_type,
386                    entity_types,
387                    &mut undeclared_e,
388                );
389            }
390        }
391
392        // Undeclared actions in a `memberOf` list.
393        let undeclared_a = undeclared_parent_actions
394            .into_iter()
395            .map(|n| n.to_string())
396            .collect::<HashSet<_>>();
397        // For actions, we check entity references in the context attribute
398        // types and `appliesTo` lists. See the `entity_types` loop for why the
399        // `descendants` list is not checked.
400        for action in action_ids.values() {
401            Self::check_undeclared_in_type(&action.context, entity_types, &mut undeclared_e);
402
403            for p_entity in action.applies_to.applicable_principal_types() {
404                match p_entity {
405                    EntityType::Specified(p_entity) => {
406                        if !entity_types.contains_key(p_entity) {
407                            undeclared_e.insert(p_entity.to_string());
408                        }
409                    }
410                    EntityType::Unspecified => (),
411                }
412            }
413
414            for r_entity in action.applies_to.applicable_resource_types() {
415                match r_entity {
416                    EntityType::Specified(r_entity) => {
417                        if !entity_types.contains_key(r_entity) {
418                            undeclared_e.insert(r_entity.to_string());
419                        }
420                    }
421                    EntityType::Unspecified => (),
422                }
423            }
424        }
425        if !undeclared_e.is_empty() {
426            return Err(SchemaError::UndeclaredEntityTypes(undeclared_e));
427        }
428        if !undeclared_a.is_empty() {
429            return Err(SchemaError::UndeclaredActions(undeclared_a));
430        }
431
432        Ok(())
433    }
434
435    fn record_attributes_or_none(ty: Type) -> Option<(Attributes, OpenTag)> {
436        match ty {
437            Type::EntityOrRecord(EntityRecordKind::Record {
438                attrs,
439                open_attributes,
440            }) => Some((attrs, open_attributes)),
441            _ => None,
442        }
443    }
444
445    // Check that all entity types appearing inside a type are in the set of
446    // declared entity types, adding any undeclared entity types to the
447    // `undeclared_types` set.
448    fn check_undeclared_in_type(
449        ty: &Type,
450        entity_types: &HashMap<Name, ValidatorEntityType>,
451        undeclared_types: &mut HashSet<String>,
452    ) {
453        match ty {
454            Type::EntityOrRecord(EntityRecordKind::Entity(lub)) => {
455                for name in lub.iter() {
456                    if !entity_types.contains_key(name) {
457                        undeclared_types.insert(name.to_string());
458                    }
459                }
460            }
461
462            Type::EntityOrRecord(EntityRecordKind::Record { attrs, .. }) => {
463                for (_, attr_ty) in attrs.iter() {
464                    Self::check_undeclared_in_type(
465                        &attr_ty.attr_type,
466                        entity_types,
467                        undeclared_types,
468                    );
469                }
470            }
471
472            Type::Set {
473                element_type: Some(element_type),
474            } => Self::check_undeclared_in_type(element_type, entity_types, undeclared_types),
475
476            _ => (),
477        }
478    }
479
480    /// Lookup the ValidatorActionId object in the schema with the given name.
481    pub fn get_action_id(&self, action_id: &EntityUID) -> Option<&ValidatorActionId> {
482        self.action_ids.get(action_id)
483    }
484
485    /// Lookup the ValidatorEntityType object in the schema with the given name.
486    pub fn get_entity_type<'a>(&'a self, entity_type_id: &Name) -> Option<&'a ValidatorEntityType> {
487        self.entity_types.get(entity_type_id)
488    }
489
490    /// Return true when the entity_type_id corresponds to a valid entity type.
491    pub(crate) fn is_known_action_id(&self, action_id: &EntityUID) -> bool {
492        self.action_ids.contains_key(action_id)
493    }
494
495    /// Return true when the entity_type_id corresponds to a valid entity type.
496    pub(crate) fn is_known_entity_type(&self, entity_type: &Name) -> bool {
497        is_action_entity_type(entity_type) || self.entity_types.contains_key(entity_type)
498    }
499
500    /// Return true when `euid` has an entity type declared by the schema. We
501    /// treat an Unspecified as "known" because it is always possible to declare
502    /// an action using an unspecified principal/resource type without first
503    /// declaring unspecified as an entity type in the entity types list.
504    pub(crate) fn euid_has_known_entity_type(&self, euid: &EntityUID) -> bool {
505        match euid.entity_type() {
506            EntityType::Specified(ety) => self.is_known_entity_type(ety),
507            EntityType::Unspecified => true,
508        }
509    }
510
511    /// An iterator over the action ids in the schema.
512    pub(crate) fn known_action_ids(&self) -> impl Iterator<Item = &EntityUID> {
513        self.action_ids.keys()
514    }
515
516    /// An iterator over the entity type names in the schema.
517    pub(crate) fn known_entity_types(&self) -> impl Iterator<Item = &Name> {
518        self.entity_types.keys()
519    }
520
521    /// An iterator matching the entity Types to their Validator Types
522    pub fn entity_types(&self) -> impl Iterator<Item = (&Name, &ValidatorEntityType)> {
523        self.entity_types.iter()
524    }
525
526    /// Get all entity types in the schema where an `{entity0} in {entity}` can
527    /// evaluate to `true` for some `entity0` with that entity type. This
528    /// includes all entity types that are descendants of the type of `entity`
529    /// according  to the schema, and the type of `entity` itself because
530    /// `entity in entity` evaluates to `true`.
531    pub(crate) fn get_entity_types_in<'a>(&'a self, entity: &'a EntityUID) -> Vec<&Name> {
532        match entity.entity_type() {
533            EntityType::Specified(ety) => {
534                let mut descendants = self
535                    .get_entity_type(ety)
536                    .map(|v_ety| v_ety.descendants.iter().collect::<Vec<_>>())
537                    .unwrap_or_default();
538                descendants.push(ety);
539                descendants
540            }
541            EntityType::Unspecified => Vec::new(),
542        }
543    }
544
545    /// Get all entity types in the schema where an `{entity0} in {euids}` can
546    /// evaluate to `true` for some `entity0` with that entity type. See comment
547    /// on `get_entity_types_in`.
548    pub(crate) fn get_entity_types_in_set<'a>(
549        &'a self,
550        euids: impl IntoIterator<Item = &'a EntityUID> + 'a,
551    ) -> impl Iterator<Item = &Name> {
552        euids.into_iter().flat_map(|e| self.get_entity_types_in(e))
553    }
554
555    /// Get all action entities in the schema where `action in euids` evaluates
556    /// to `true`. This includes all actions which are descendants of some
557    /// element of `euids`, and all elements of `euids`.
558    pub(crate) fn get_actions_in_set<'a>(
559        &'a self,
560        euids: impl IntoIterator<Item = &'a EntityUID> + 'a,
561    ) -> Option<Vec<&'a EntityUID>> {
562        euids
563            .into_iter()
564            .map(|e| {
565                self.get_action_id(e).map(|action| {
566                    action
567                        .descendants
568                        .iter()
569                        .chain(std::iter::once(&action.name))
570                })
571            })
572            .collect::<Option<Vec<_>>>()
573            .map(|v| v.into_iter().flatten().collect::<Vec<_>>())
574    }
575
576    /// Get the `Type` of context expected for the given `action`.
577    /// This always reutrns a closed record type.
578    ///
579    /// Returns `None` if the action is not in the schema.
580    pub fn context_type(&self, action: &EntityUID) -> Option<Type> {
581        // INVARIANT: `ValidatorActionId::context_type` always returns a closed
582        // record type
583        self.get_action_id(action)
584            .map(ValidatorActionId::context_type)
585    }
586
587    /// Invert the action hierarchy to get the ancestor relation expected for
588    /// the `Entity` datatype instead of descendants as stored by the schema.
589    pub(crate) fn action_entities_iter(
590        &self,
591    ) -> impl Iterator<Item = cedar_policy_core::ast::Entity> + '_ {
592        // We could store the un-inverted `memberOf` relation for each action,
593        // but I [john-h-kastner-aws] judge that the current implementation is
594        // actually less error prone, as it minimizes the threading of data
595        // structures through some complicated bits of schema construction code,
596        // and avoids computing the TC twice.
597        let mut action_ancestors: HashMap<&EntityUID, HashSet<EntityUID>> = HashMap::new();
598        for (action_euid, action_def) in &self.action_ids {
599            for descendant in &action_def.descendants {
600                action_ancestors
601                    .entry(descendant)
602                    .or_default()
603                    .insert(action_euid.clone());
604            }
605        }
606        self.action_ids.iter().map(move |(action_id, action)| {
607            Entity::new_with_attr_partial_value_serialized_as_expr(
608                action_id.clone(),
609                action.attributes.clone(),
610                action_ancestors.remove(action_id).unwrap_or_default(),
611            )
612        })
613    }
614
615    /// Construct an `Entity` object for each action in the schema
616    pub fn action_entities(&self) -> std::result::Result<Entities, EntitiesError> {
617        let extensions = Extensions::all_available();
618        Entities::from_entities(
619            self.action_entities_iter(),
620            None::<&cedar_policy_core::entities::NoEntitiesSchema>, // we don't want to tell `Entities::from_entities()` to add the schema's action entities, that would infinitely recurse
621            TCComputation::AssumeAlreadyComputed,
622            extensions,
623        )
624        .map_err(Into::into)
625    }
626}
627
628/// Used to write a schema implicitly overriding the default handling of action
629/// groups.
630#[derive(Debug, Clone, Deserialize)]
631#[serde(transparent)]
632pub(crate) struct NamespaceDefinitionWithActionAttributes(pub(crate) NamespaceDefinition);
633
634impl TryInto<ValidatorSchema> for NamespaceDefinitionWithActionAttributes {
635    type Error = SchemaError;
636
637    fn try_into(self) -> Result<ValidatorSchema> {
638        ValidatorSchema::from_schema_fragments([ValidatorSchemaFragment::from_namespaces([
639            ValidatorNamespaceDef::from_namespace_definition(
640                None,
641                self.0,
642                crate::ActionBehavior::PermitAttributes,
643                Extensions::all_available(),
644            )?,
645        ])])
646    }
647}
648
649// PANIC SAFETY unit tests
650#[allow(clippy::panic)]
651// PANIC SAFETY unit tests
652#[allow(clippy::indexing_slicing)]
653#[cfg(test)]
654mod test {
655    use std::sync::Arc;
656    use std::{collections::BTreeMap, str::FromStr};
657
658    use crate::types::Type;
659    use crate::{SchemaType, SchemaTypeVariant};
660
661    use cedar_policy_core::ast::RestrictedExpr;
662    use cedar_policy_core::parser::err::{ParseError, ToASTError, ToASTErrorKind};
663    use cedar_policy_core::parser::Loc;
664    use cool_asserts::assert_matches;
665    use serde_json::json;
666
667    use super::*;
668
669    // Well-formed schema
670    #[test]
671    fn test_from_schema_file() {
672        let src = json!(
673        {
674            "entityTypes": {
675                "User": {
676                    "memberOfTypes": [ "Group" ]
677                },
678                "Group": {
679                    "memberOfTypes": []
680                },
681                "Photo": {
682                    "memberOfTypes": [ "Album" ]
683                },
684                "Album": {
685                    "memberOfTypes": []
686                }
687            },
688            "actions": {
689                "view_photo": {
690                    "appliesTo": {
691                        "principalTypes": ["User", "Group"],
692                        "resourceTypes": ["Photo"]
693                    }
694                }
695            }
696        });
697        let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
698        let schema: Result<ValidatorSchema> = schema_file.try_into();
699        assert!(schema.is_ok());
700    }
701
702    // Duplicate entity "Photo"
703    #[test]
704    fn test_from_schema_file_duplicate_entity() {
705        // Test written using `from_str` instead of `from_value` because the
706        // `json!` macro silently ignores duplicate map keys.
707        let src = r#"
708        {"": {
709            "entityTypes": {
710                "User": {
711                    "memberOfTypes": [ "Group" ]
712                },
713                "Group": {
714                    "memberOfTypes": []
715                },
716                "Photo": {
717                    "memberOfTypes": [ "Album" ]
718                },
719                "Photo": {
720                    "memberOfTypes": []
721                }
722            },
723            "actions": {
724                "view_photo": {
725                    "memberOf": [],
726                    "appliesTo": {
727                        "principalTypes": ["User", "Group"],
728                        "resourceTypes": ["Photo"]
729                    }
730                }
731            }
732        }}"#;
733
734        match ValidatorSchema::from_str(src) {
735            Err(SchemaError::Serde(_)) => (),
736            _ => panic!("Expected serde error due to duplicate entity type."),
737        }
738    }
739
740    // Duplicate action "view_photo"
741    #[test]
742    fn test_from_schema_file_duplicate_action() {
743        // Test written using `from_str` instead of `from_value` because the
744        // `json!` macro silently ignores duplicate map keys.
745        let src = r#"
746        {"": {
747            "entityTypes": {
748                "User": {
749                    "memberOfTypes": [ "Group" ]
750                },
751                "Group": {
752                    "memberOfTypes": []
753                },
754                "Photo": {
755                    "memberOfTypes": []
756                }
757            },
758            "actions": {
759                "view_photo": {
760                    "memberOf": [],
761                    "appliesTo": {
762                        "principalTypes": ["User", "Group"],
763                        "resourceTypes": ["Photo"]
764                    }
765                },
766                "view_photo": { }
767            }
768        }"#;
769        match ValidatorSchema::from_str(src) {
770            Err(SchemaError::Serde(_)) => (),
771            _ => panic!("Expected serde error due to duplicate action type."),
772        }
773    }
774
775    // Undefined entity types "Grop", "Usr", "Phoot"
776    #[test]
777    fn test_from_schema_file_undefined_entities() {
778        let src = json!(
779        {
780            "entityTypes": {
781                "User": {
782                    "memberOfTypes": [ "Grop" ]
783                },
784                "Group": {
785                    "memberOfTypes": []
786                },
787                "Photo": {
788                    "memberOfTypes": []
789                }
790            },
791            "actions": {
792                "view_photo": {
793                    "appliesTo": {
794                        "principalTypes": ["Usr", "Group"],
795                        "resourceTypes": ["Phoot"]
796                    }
797                }
798            }
799        });
800        let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
801        let schema: Result<ValidatorSchema> = schema_file.try_into();
802        match schema {
803            Ok(_) => panic!("from_schema_file should have failed"),
804            Err(SchemaError::UndeclaredEntityTypes(v)) => {
805                assert_eq!(v.len(), 3)
806            }
807            _ => panic!("Unexpected error from from_schema_file"),
808        }
809    }
810
811    #[test]
812    fn undefined_entity_namespace_member_of() {
813        let src = json!(
814        {"Foo": {
815            "entityTypes": {
816                "User": {
817                    "memberOfTypes": [ "Foo::Group", "Bar::Group" ]
818                },
819                "Group": { }
820            },
821            "actions": {}
822        }});
823        let schema_file: SchemaFragment = serde_json::from_value(src).expect("Parse Error");
824        let schema: Result<ValidatorSchema> = schema_file.try_into();
825        match schema {
826            Ok(_) => panic!("try_into should have failed"),
827            Err(SchemaError::UndeclaredEntityTypes(v)) => {
828                assert_eq!(v, HashSet::from(["Bar::Group".to_string()]))
829            }
830            _ => panic!("Unexpected error from try_into"),
831        }
832    }
833
834    #[test]
835    fn undefined_entity_namespace_applies_to() {
836        let src = json!(
837        {"Foo": {
838            "entityTypes": { "User": { }, "Photo": { } },
839            "actions": {
840                "view_photo": {
841                    "appliesTo": {
842                        "principalTypes": ["Foo::User", "Bar::User"],
843                        "resourceTypes": ["Photo", "Bar::Photo"],
844                    }
845                }
846            }
847        }});
848        let schema_file: SchemaFragment = serde_json::from_value(src).expect("Parse Error");
849        let schema: Result<ValidatorSchema> = schema_file.try_into();
850        match schema {
851            Ok(_) => panic!("try_into should have failed"),
852            Err(SchemaError::UndeclaredEntityTypes(v)) => {
853                assert_eq!(
854                    v,
855                    HashSet::from(["Bar::Photo".to_string(), "Bar::User".to_string()])
856                )
857            }
858            _ => panic!("Unexpected error from try_into"),
859        }
860    }
861
862    // Undefined action "photo_actions"
863    #[test]
864    fn test_from_schema_file_undefined_action() {
865        let src = json!(
866        {
867            "entityTypes": {
868                "User": {
869                    "memberOfTypes": [ "Group" ]
870                },
871                "Group": {
872                    "memberOfTypes": []
873                },
874                "Photo": {
875                    "memberOfTypes": []
876                }
877            },
878            "actions": {
879                "view_photo": {
880                    "memberOf": [ {"id": "photo_action"} ],
881                    "appliesTo": {
882                        "principalTypes": ["User", "Group"],
883                        "resourceTypes": ["Photo"]
884                    }
885                }
886            }
887        });
888        let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
889        let schema: Result<ValidatorSchema> = schema_file.try_into();
890        match schema {
891            Ok(_) => panic!("from_schema_file should have failed"),
892            Err(SchemaError::UndeclaredActions(v)) => assert_eq!(v.len(), 1),
893            _ => panic!("Unexpected error from from_schema_file"),
894        }
895    }
896
897    // Trivial cycle in action hierarchy
898    // view_photo -> view_photo
899    #[test]
900    fn test_from_schema_file_action_cycle1() {
901        let src = json!(
902        {
903            "entityTypes": {},
904            "actions": {
905                "view_photo": {
906                    "memberOf": [ {"id": "view_photo"} ]
907                }
908            }
909        });
910        let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
911        let schema: Result<ValidatorSchema> = schema_file.try_into();
912        assert_matches!(
913            schema,
914            Err(SchemaError::CycleInActionHierarchy(euid)) => {
915                assert_eq!(euid, r#"Action::"view_photo""#.parse().unwrap());
916            }
917        )
918    }
919
920    // Slightly more complex cycle in action hierarchy
921    // view_photo -> edit_photo -> delete_photo -> view_photo
922    #[test]
923    fn test_from_schema_file_action_cycle2() {
924        let src = json!(
925        {
926            "entityTypes": {},
927            "actions": {
928                "view_photo": {
929                    "memberOf": [ {"id": "edit_photo"} ]
930                },
931                "edit_photo": {
932                    "memberOf": [ {"id": "delete_photo"} ]
933                },
934                "delete_photo": {
935                    "memberOf": [ {"id": "view_photo"} ]
936                },
937                "other_action": {
938                    "memberOf": [ {"id": "edit_photo"} ]
939                }
940            }
941        });
942        let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
943        let schema: Result<ValidatorSchema> = schema_file.try_into();
944        assert_matches!(
945            schema,
946            // The exact action reported as being in the cycle isn't deterministic.
947            Err(SchemaError::CycleInActionHierarchy(_)),
948        )
949    }
950
951    #[test]
952    fn namespaced_schema() {
953        let src = r#"
954        { "N::S": {
955            "entityTypes": {
956                "User": {},
957                "Photo": {}
958            },
959            "actions": {
960                "view_photo": {
961                    "appliesTo": {
962                        "principalTypes": ["User"],
963                        "resourceTypes": ["Photo"]
964                    }
965                }
966            }
967        } }
968        "#;
969        let schema_file: SchemaFragment = serde_json::from_str(src).expect("Parse Error");
970        let schema: ValidatorSchema = schema_file
971            .try_into()
972            .expect("Namespaced schema failed to convert.");
973        dbg!(&schema);
974        let user_entity_type = &"N::S::User"
975            .parse()
976            .expect("Namespaced entity type should have parsed");
977        let photo_entity_type = &"N::S::Photo"
978            .parse()
979            .expect("Namespaced entity type should have parsed");
980        assert!(
981            schema.entity_types.contains_key(user_entity_type),
982            "Expected and entity type User."
983        );
984        assert!(
985            schema.entity_types.contains_key(photo_entity_type),
986            "Expected an entity type Photo."
987        );
988        assert_eq!(
989            schema.entity_types.len(),
990            2,
991            "Expected exactly 2 entity types."
992        );
993        assert!(
994            schema.action_ids.contains_key(
995                &"N::S::Action::\"view_photo\""
996                    .parse()
997                    .expect("Namespaced action should have parsed")
998            ),
999            "Expected an action \"view_photo\"."
1000        );
1001        assert_eq!(schema.action_ids.len(), 1, "Expected exactly 1 action.");
1002
1003        let apply_spec = &schema
1004            .action_ids
1005            .values()
1006            .next()
1007            .expect("Expected Action")
1008            .applies_to;
1009        assert_eq!(
1010            apply_spec.applicable_principal_types().collect::<Vec<_>>(),
1011            vec![&EntityType::Specified(user_entity_type.clone())]
1012        );
1013        assert_eq!(
1014            apply_spec.applicable_resource_types().collect::<Vec<_>>(),
1015            vec![&EntityType::Specified(photo_entity_type.clone())]
1016        );
1017    }
1018
1019    #[test]
1020    fn cant_use_namespace_in_entity_type() {
1021        let src = r#"
1022        {
1023            "entityTypes": { "NS::User": {} },
1024            "actions": {}
1025        }
1026        "#;
1027        let schema_file: NamespaceDefinition = serde_json::from_str(src).expect("Parse Error");
1028        assert!(
1029            matches!(TryInto::<ValidatorSchema>::try_into(schema_file), Err(SchemaError::ParseEntityType(_))),
1030            "Expected that namespace in the entity type NS::User would cause a EntityType parse error.");
1031    }
1032
1033    #[test]
1034    fn entity_attribute_entity_type_with_namespace() {
1035        let schema_json: SchemaFragment = serde_json::from_str(
1036            r#"
1037            {"A::B": {
1038                "entityTypes": {
1039                    "Foo": {
1040                        "shape": {
1041                            "type": "Record",
1042                            "attributes": {
1043                                "name": { "type": "Entity", "name": "C::D::Foo" }
1044                            }
1045                        }
1046                    }
1047                },
1048                "actions": {}
1049              }}
1050            "#,
1051        )
1052        .expect("Expected valid schema");
1053
1054        let schema: Result<ValidatorSchema> = schema_json.try_into();
1055        match schema {
1056            Err(SchemaError::UndeclaredEntityTypes(tys)) => {
1057                assert_eq!(tys, HashSet::from(["C::D::Foo".to_string()]))
1058            }
1059            _ => panic!("Schema construction should have failed due to undeclared entity type."),
1060        }
1061    }
1062
1063    #[test]
1064    fn entity_attribute_entity_type_with_declared_namespace() {
1065        let schema_json: SchemaFragment = serde_json::from_str(
1066            r#"
1067            {"A::B": {
1068                "entityTypes": {
1069                    "Foo": {
1070                        "shape": {
1071                            "type": "Record",
1072                            "attributes": {
1073                                "name": { "type": "Entity", "name": "A::B::Foo" }
1074                            }
1075                        }
1076                    }
1077                },
1078                "actions": {}
1079              }}
1080            "#,
1081        )
1082        .expect("Expected valid schema");
1083
1084        let schema: ValidatorSchema = schema_json
1085            .try_into()
1086            .expect("Expected schema to construct without error.");
1087
1088        let foo_name: Name = "A::B::Foo".parse().expect("Expected entity type name");
1089        let foo_type = schema
1090            .entity_types
1091            .get(&foo_name)
1092            .expect("Expected to find entity");
1093        let name_type = foo_type
1094            .attr("name")
1095            .expect("Expected attribute name")
1096            .attr_type
1097            .clone();
1098        let expected_name_type = Type::named_entity_reference(foo_name);
1099        assert_eq!(name_type, expected_name_type);
1100    }
1101
1102    #[test]
1103    fn cannot_declare_action_type_when_prohibited() {
1104        let schema_json: NamespaceDefinition = serde_json::from_str(
1105            r#"
1106            {
1107                "entityTypes": { "Action": {} },
1108                "actions": {}
1109              }
1110            "#,
1111        )
1112        .expect("Expected valid schema");
1113
1114        let schema: Result<ValidatorSchema> = schema_json.try_into();
1115        assert!(matches!(schema, Err(SchemaError::ActionEntityTypeDeclared)));
1116    }
1117
1118    #[test]
1119    fn can_declare_other_type_when_action_type_prohibited() {
1120        let schema_json: NamespaceDefinition = serde_json::from_str(
1121            r#"
1122            {
1123                "entityTypes": { "Foo": { } },
1124                "actions": {}
1125              }
1126            "#,
1127        )
1128        .expect("Expected valid schema");
1129
1130        TryInto::<ValidatorSchema>::try_into(schema_json).expect("Did not expect any errors.");
1131    }
1132
1133    #[test]
1134    fn cannot_declare_action_in_group_when_prohibited() {
1135        let schema_json: SchemaFragment = serde_json::from_str(
1136            r#"
1137            {"": {
1138                "entityTypes": {},
1139                "actions": {
1140                    "universe": { },
1141                    "view_photo": {
1142                        "attributes": {"id": "universe"}
1143                    },
1144                    "edit_photo": {
1145                        "attributes": {"id": "universe"}
1146                    },
1147                    "delete_photo": {
1148                        "attributes": {"id": "universe"}
1149                    }
1150                }
1151              }}
1152            "#,
1153        )
1154        .expect("Expected valid schema");
1155
1156        let schema = ValidatorSchemaFragment::from_schema_fragment(
1157            schema_json,
1158            ActionBehavior::ProhibitAttributes,
1159            Extensions::all_available(),
1160        );
1161        match schema {
1162            Err(SchemaError::UnsupportedFeature(UnsupportedFeature::ActionAttributes(actions))) => {
1163                assert_eq!(
1164                    actions.into_iter().collect::<HashSet<_>>(),
1165                    HashSet::from([
1166                        "view_photo".to_string(),
1167                        "edit_photo".to_string(),
1168                        "delete_photo".to_string(),
1169                    ])
1170                )
1171            }
1172            _ => panic!("Did not see expected error."),
1173        }
1174    }
1175
1176    #[test]
1177    fn test_entity_type_no_namespace() {
1178        let src = json!({"type": "Entity", "name": "Foo"});
1179        let schema_ty: SchemaType = serde_json::from_value(src).expect("Parse Error");
1180        assert_eq!(
1181            schema_ty,
1182            SchemaType::Type(SchemaTypeVariant::Entity { name: "Foo".into() })
1183        );
1184        let ty: Type = ValidatorNamespaceDef::try_schema_type_into_validator_type(
1185            Some(&Name::parse_unqualified_name("NS").expect("Expected namespace.")),
1186            schema_ty,
1187        )
1188        .expect("Error converting schema type to type.")
1189        .resolve_type_defs(&HashMap::new())
1190        .unwrap();
1191        assert_eq!(ty, Type::named_entity_reference_from_str("NS::Foo"));
1192    }
1193
1194    #[test]
1195    fn test_entity_type_namespace() {
1196        let src = json!({"type": "Entity", "name": "NS::Foo"});
1197        let schema_ty: SchemaType = serde_json::from_value(src).expect("Parse Error");
1198        assert_eq!(
1199            schema_ty,
1200            SchemaType::Type(SchemaTypeVariant::Entity {
1201                name: "NS::Foo".into()
1202            })
1203        );
1204        let ty: Type = ValidatorNamespaceDef::try_schema_type_into_validator_type(
1205            Some(&Name::parse_unqualified_name("NS").expect("Expected namespace.")),
1206            schema_ty,
1207        )
1208        .expect("Error converting schema type to type.")
1209        .resolve_type_defs(&HashMap::new())
1210        .unwrap();
1211        assert_eq!(ty, Type::named_entity_reference_from_str("NS::Foo"));
1212    }
1213
1214    #[test]
1215    fn test_entity_type_namespace_parse_error() {
1216        let src = json!({"type": "Entity", "name": "::Foo"});
1217        let schema_ty: SchemaType = serde_json::from_value(src).expect("Parse Error");
1218        assert_eq!(
1219            schema_ty,
1220            SchemaType::Type(SchemaTypeVariant::Entity {
1221                name: "::Foo".into()
1222            })
1223        );
1224        match ValidatorNamespaceDef::try_schema_type_into_validator_type(
1225            Some(&Name::parse_unqualified_name("NS").expect("Expected namespace.")),
1226            schema_ty,
1227        ) {
1228            Err(SchemaError::ParseEntityType(_)) => (),
1229            _ => panic!("Did not see expected entity type parse error."),
1230        }
1231    }
1232
1233    #[test]
1234    fn schema_type_record_is_validator_type_record() {
1235        let src = json!({"type": "Record", "attributes": {}});
1236        let schema_ty: SchemaType = serde_json::from_value(src).expect("Parse Error");
1237        assert_eq!(
1238            schema_ty,
1239            SchemaType::Type(SchemaTypeVariant::Record {
1240                attributes: BTreeMap::new(),
1241                additional_attributes: false,
1242            }),
1243        );
1244        let ty: Type = ValidatorNamespaceDef::try_schema_type_into_validator_type(None, schema_ty)
1245            .expect("Error converting schema type to type.")
1246            .resolve_type_defs(&HashMap::new())
1247            .unwrap();
1248        assert_eq!(ty, Type::closed_record_with_attributes(None));
1249    }
1250
1251    #[test]
1252    fn get_namespaces() {
1253        let fragment: SchemaFragment = serde_json::from_value(json!({
1254            "Foo::Bar::Baz": {
1255                "entityTypes": {},
1256                "actions": {}
1257            },
1258            "Foo": {
1259                "entityTypes": {},
1260                "actions": {}
1261            },
1262            "Bar": {
1263                "entityTypes": {},
1264                "actions": {}
1265            },
1266        }))
1267        .unwrap();
1268
1269        let schema_fragment: ValidatorSchemaFragment = fragment.try_into().unwrap();
1270        assert_eq!(
1271            schema_fragment
1272                .0
1273                .iter()
1274                .map(|f| f.namespace())
1275                .collect::<HashSet<_>>(),
1276            HashSet::from([
1277                &Some("Foo::Bar::Baz".parse().unwrap()),
1278                &Some("Foo".parse().unwrap()),
1279                &Some("Bar".parse().unwrap())
1280            ])
1281        );
1282    }
1283
1284    #[test]
1285    fn schema_no_fragments() {
1286        let schema = ValidatorSchema::from_schema_fragments([]).unwrap();
1287        assert!(schema.entity_types.is_empty());
1288        assert!(schema.action_ids.is_empty());
1289    }
1290
1291    #[test]
1292    fn same_action_different_namespace() {
1293        let fragment: SchemaFragment = serde_json::from_value(json!({
1294            "Foo::Bar": {
1295                "entityTypes": {},
1296                "actions": {
1297                    "Baz": {}
1298                }
1299            },
1300            "Bar::Foo": {
1301                "entityTypes": {},
1302                "actions": {
1303                    "Baz": { }
1304                }
1305            },
1306            "Biz": {
1307                "entityTypes": {},
1308                "actions": {
1309                    "Baz": { }
1310                }
1311            }
1312        }))
1313        .unwrap();
1314
1315        let schema: ValidatorSchema = fragment.try_into().unwrap();
1316        assert!(schema
1317            .get_action_id(&"Foo::Bar::Action::\"Baz\"".parse().unwrap())
1318            .is_some());
1319        assert!(schema
1320            .get_action_id(&"Bar::Foo::Action::\"Baz\"".parse().unwrap())
1321            .is_some());
1322        assert!(schema
1323            .get_action_id(&"Biz::Action::\"Baz\"".parse().unwrap())
1324            .is_some());
1325    }
1326
1327    #[test]
1328    fn same_type_different_namespace() {
1329        let fragment: SchemaFragment = serde_json::from_value(json!({
1330            "Foo::Bar": {
1331                "entityTypes": {"Baz" : {}},
1332                "actions": { }
1333            },
1334            "Bar::Foo": {
1335                "entityTypes": {"Baz" : {}},
1336                "actions": { }
1337            },
1338            "Biz": {
1339                "entityTypes": {"Baz" : {}},
1340                "actions": { }
1341            }
1342        }))
1343        .unwrap();
1344        let schema: ValidatorSchema = fragment.try_into().unwrap();
1345
1346        assert!(schema
1347            .get_entity_type(&"Foo::Bar::Baz".parse().unwrap())
1348            .is_some());
1349        assert!(schema
1350            .get_entity_type(&"Bar::Foo::Baz".parse().unwrap())
1351            .is_some());
1352        assert!(schema
1353            .get_entity_type(&"Biz::Baz".parse().unwrap())
1354            .is_some());
1355    }
1356
1357    #[test]
1358    fn member_of_different_namespace() {
1359        let fragment: SchemaFragment = serde_json::from_value(json!({
1360            "Bar": {
1361                "entityTypes": {
1362                    "Baz": {
1363                        "memberOfTypes": ["Foo::Buz"]
1364                    }
1365                },
1366                "actions": {}
1367            },
1368            "Foo": {
1369                "entityTypes": { "Buz": {} },
1370                "actions": { }
1371            }
1372        }))
1373        .unwrap();
1374        let schema: ValidatorSchema = fragment.try_into().unwrap();
1375
1376        let buz = schema
1377            .get_entity_type(&"Foo::Buz".parse().unwrap())
1378            .unwrap();
1379        assert_eq!(
1380            buz.descendants,
1381            HashSet::from(["Bar::Baz".parse().unwrap()])
1382        );
1383    }
1384
1385    #[test]
1386    fn attribute_different_namespace() {
1387        let fragment: SchemaFragment = serde_json::from_value(json!({
1388            "Bar": {
1389                "entityTypes": {
1390                    "Baz": {
1391                        "shape": {
1392                            "type": "Record",
1393                            "attributes": {
1394                                "fiz": {
1395                                    "type": "Entity",
1396                                    "name": "Foo::Buz"
1397                                }
1398                            }
1399                        }
1400                    }
1401                },
1402                "actions": {}
1403            },
1404            "Foo": {
1405                "entityTypes": { "Buz": {} },
1406                "actions": { }
1407            }
1408        }))
1409        .unwrap();
1410
1411        let schema: ValidatorSchema = fragment.try_into().unwrap();
1412        let baz = schema
1413            .get_entity_type(&"Bar::Baz".parse().unwrap())
1414            .unwrap();
1415        assert_eq!(
1416            baz.attr("fiz").unwrap().attr_type,
1417            Type::named_entity_reference_from_str("Foo::Buz"),
1418        );
1419    }
1420
1421    #[test]
1422    fn applies_to_different_namespace() {
1423        let fragment: SchemaFragment = serde_json::from_value(json!({
1424            "Foo::Bar": {
1425                "entityTypes": { },
1426                "actions": {
1427                    "Baz": {
1428                        "appliesTo": {
1429                            "principalTypes": [ "Fiz::Buz" ],
1430                            "resourceTypes": [ "Fiz::Baz" ],
1431                        }
1432                    }
1433                }
1434            },
1435            "Fiz": {
1436                "entityTypes": {
1437                    "Buz": {},
1438                    "Baz": {}
1439                },
1440                "actions": { }
1441            }
1442        }))
1443        .unwrap();
1444        let schema: ValidatorSchema = fragment.try_into().unwrap();
1445
1446        let baz = schema
1447            .get_action_id(&"Foo::Bar::Action::\"Baz\"".parse().unwrap())
1448            .unwrap();
1449        assert_eq!(
1450            baz.applies_to
1451                .applicable_principal_types()
1452                .collect::<HashSet<_>>(),
1453            HashSet::from([&EntityType::Specified("Fiz::Buz".parse().unwrap())])
1454        );
1455        assert_eq!(
1456            baz.applies_to
1457                .applicable_resource_types()
1458                .collect::<HashSet<_>>(),
1459            HashSet::from([&EntityType::Specified("Fiz::Baz".parse().unwrap())])
1460        );
1461    }
1462
1463    #[test]
1464    fn simple_defined_type() {
1465        let fragment: SchemaFragment = serde_json::from_value(json!({
1466            "": {
1467                "commonTypes": {
1468                    "MyLong": {"type": "Long"}
1469                },
1470                "entityTypes": {
1471                    "User": {
1472                        "shape": {
1473                            "type": "Record",
1474                            "attributes": {
1475                                "a": {"type": "MyLong"}
1476                            }
1477                        }
1478                    }
1479                },
1480                "actions": {}
1481            }
1482        }))
1483        .unwrap();
1484        let schema: ValidatorSchema = fragment.try_into().unwrap();
1485        assert_eq!(
1486            schema.entity_types.iter().next().unwrap().1.attributes,
1487            Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
1488        );
1489    }
1490
1491    #[test]
1492    fn defined_record_as_attrs() {
1493        let fragment: SchemaFragment = serde_json::from_value(json!({
1494            "": {
1495                "commonTypes": {
1496                    "MyRecord": {
1497                        "type": "Record",
1498                        "attributes":  {
1499                            "a": {"type": "Long"}
1500                        }
1501                    }
1502                },
1503                "entityTypes": {
1504                    "User": { "shape": { "type": "MyRecord", } }
1505                },
1506                "actions": {}
1507            }
1508        }))
1509        .unwrap();
1510        let schema: ValidatorSchema = fragment.try_into().unwrap();
1511        assert_eq!(
1512            schema.entity_types.iter().next().unwrap().1.attributes,
1513            Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
1514        );
1515    }
1516
1517    #[test]
1518    fn cross_namespace_type() {
1519        let fragment: SchemaFragment = serde_json::from_value(json!({
1520            "A": {
1521                "commonTypes": {
1522                    "MyLong": {"type": "Long"}
1523                },
1524                "entityTypes": { },
1525                "actions": {}
1526            },
1527            "B": {
1528                "entityTypes": {
1529                    "User": {
1530                        "shape": {
1531                            "type": "Record",
1532                            "attributes": {
1533                                "a": {"type": "A::MyLong"}
1534                            }
1535                        }
1536                    }
1537                },
1538                "actions": {}
1539            }
1540        }))
1541        .unwrap();
1542        let schema: ValidatorSchema = fragment.try_into().unwrap();
1543        assert_eq!(
1544            schema.entity_types.iter().next().unwrap().1.attributes,
1545            Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
1546        );
1547    }
1548
1549    #[test]
1550    fn cross_fragment_type() {
1551        let fragment1: ValidatorSchemaFragment = serde_json::from_value::<SchemaFragment>(json!({
1552            "A": {
1553                "commonTypes": {
1554                    "MyLong": {"type": "Long"}
1555                },
1556                "entityTypes": { },
1557                "actions": {}
1558            }
1559        }))
1560        .unwrap()
1561        .try_into()
1562        .unwrap();
1563        let fragment2: ValidatorSchemaFragment = serde_json::from_value::<SchemaFragment>(json!({
1564            "A": {
1565                "entityTypes": {
1566                    "User": {
1567                        "shape": {
1568                            "type": "Record",
1569                            "attributes": {
1570                                "a": {"type": "MyLong"}
1571                            }
1572                        }
1573                    }
1574                },
1575                "actions": {}
1576            }
1577        }))
1578        .unwrap()
1579        .try_into()
1580        .unwrap();
1581        let schema = ValidatorSchema::from_schema_fragments([fragment1, fragment2]).unwrap();
1582
1583        assert_eq!(
1584            schema.entity_types.iter().next().unwrap().1.attributes,
1585            Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
1586        );
1587    }
1588
1589    #[test]
1590    fn cross_fragment_duplicate_type() {
1591        let fragment1: ValidatorSchemaFragment = serde_json::from_value::<SchemaFragment>(json!({
1592            "A": {
1593                "commonTypes": {
1594                    "MyLong": {"type": "Long"}
1595                },
1596                "entityTypes": {},
1597                "actions": {}
1598            }
1599        }))
1600        .unwrap()
1601        .try_into()
1602        .unwrap();
1603        let fragment2: ValidatorSchemaFragment = serde_json::from_value::<SchemaFragment>(json!({
1604            "A": {
1605                "commonTypes": {
1606                    "MyLong": {"type": "Long"}
1607                },
1608                "entityTypes": {},
1609                "actions": {}
1610            }
1611        }))
1612        .unwrap()
1613        .try_into()
1614        .unwrap();
1615
1616        let schema = ValidatorSchema::from_schema_fragments([fragment1, fragment2]);
1617
1618        match schema {
1619            Err(SchemaError::DuplicateCommonType(s)) if s.contains("A::MyLong") => (),
1620            _ => panic!("should have errored because schema fragments have duplicate types"),
1621        };
1622    }
1623
1624    #[test]
1625    fn undeclared_type_in_attr() {
1626        let fragment: SchemaFragment = serde_json::from_value(json!({
1627            "": {
1628                "commonTypes": { },
1629                "entityTypes": {
1630                    "User": {
1631                        "shape": {
1632                            "type": "Record",
1633                            "attributes": {
1634                                "a": {"type": "MyLong"}
1635                            }
1636                        }
1637                    }
1638                },
1639                "actions": {}
1640            }
1641        }))
1642        .unwrap();
1643        match TryInto::<ValidatorSchema>::try_into(fragment) {
1644            Err(SchemaError::UndeclaredCommonTypes(_)) => (),
1645            s => panic!(
1646                "Expected Err(SchemaError::UndeclaredCommonType), got {:?}",
1647                s
1648            ),
1649        }
1650    }
1651
1652    #[test]
1653    fn undeclared_type_in_type_def() {
1654        let fragment: SchemaFragment = serde_json::from_value(json!({
1655            "": {
1656                "commonTypes": {
1657                    "a": { "type": "b" }
1658                },
1659                "entityTypes": { },
1660                "actions": {}
1661            }
1662        }))
1663        .unwrap();
1664        match TryInto::<ValidatorSchema>::try_into(fragment) {
1665            Err(SchemaError::UndeclaredCommonTypes(_)) => (),
1666            s => panic!(
1667                "Expected Err(SchemaError::UndeclaredCommonType), got {:?}",
1668                s
1669            ),
1670        }
1671    }
1672
1673    #[test]
1674    fn shape_not_record() {
1675        let fragment: SchemaFragment = serde_json::from_value(json!({
1676            "": {
1677                "commonTypes": {
1678                    "MyLong": { "type": "Long" }
1679                },
1680                "entityTypes": {
1681                    "User": {
1682                        "shape": { "type": "MyLong" }
1683                    }
1684                },
1685                "actions": {}
1686            }
1687        }))
1688        .unwrap();
1689        match TryInto::<ValidatorSchema>::try_into(fragment) {
1690            Err(SchemaError::ContextOrShapeNotRecord(_)) => (),
1691            s => panic!(
1692                "Expected Err(SchemaError::ContextOrShapeNotRecord), got {:?}",
1693                s
1694            ),
1695        }
1696    }
1697
1698    /// This test checks for regressions on (adapted versions of) the examples
1699    /// mentioned in the thread at
1700    /// [cedar#134](https://github.com/cedar-policy/cedar/pull/134)
1701    #[test]
1702    fn counterexamples_from_cedar_134() {
1703        // non-normalized entity type name
1704        let bad1 = json!({
1705            "": {
1706                "entityTypes": {
1707                    "User // comment": {
1708                        "memberOfTypes": [
1709                            "UserGroup"
1710                        ]
1711                    },
1712                    "User": {
1713                        "memberOfTypes": [
1714                            "UserGroup"
1715                        ]
1716                    },
1717                    "UserGroup": {}
1718                },
1719                "actions": {}
1720            }
1721        });
1722        let fragment = serde_json::from_value::<SchemaFragment>(bad1)
1723            .expect("constructing the fragment itself should succeed"); // should this fail in the future?
1724        let err = ValidatorSchema::try_from(fragment)
1725            .expect_err("should error due to invalid entity type name");
1726        let problem_field = "User // comment";
1727        let expected_err = ParseError::ToAST(ToASTError::new(
1728            ToASTErrorKind::NonNormalizedString {
1729                kind: "Id",
1730                src: problem_field.to_string(),
1731                normalized_src: "User".to_string(),
1732            },
1733            Loc::new(4, Arc::from(problem_field)),
1734        ))
1735        .into();
1736
1737        match err {
1738            SchemaError::ParseEntityType(parse_error) => assert_eq!(parse_error, expected_err),
1739            err => panic!("Incorrect error {err}"),
1740        }
1741
1742        // non-normalized schema namespace
1743        let bad2 = json!({
1744            "ABC     :: //comment \n XYZ  ": {
1745                "entityTypes": {
1746                    "User": {
1747                        "memberOfTypes": []
1748                    }
1749                },
1750                "actions": {}
1751            }
1752        });
1753        let fragment = serde_json::from_value::<SchemaFragment>(bad2)
1754            .expect("constructing the fragment itself should succeed"); // should this fail in the future?
1755        let err = ValidatorSchema::try_from(fragment)
1756            .expect_err("should error due to invalid schema namespace");
1757        let problem_field = "ABC     :: //comment \n XYZ  ";
1758        let expected_err = ParseError::ToAST(ToASTError::new(
1759            ToASTErrorKind::NonNormalizedString {
1760                kind: "Name",
1761                src: problem_field.to_string(),
1762                normalized_src: "ABC::XYZ".to_string(),
1763            },
1764            Loc::new(3, Arc::from(problem_field)),
1765        ))
1766        .into();
1767        match err {
1768            SchemaError::ParseNamespace(parse_error) => assert_eq!(parse_error, expected_err),
1769            err => panic!("Incorrect error {:?}", err),
1770        };
1771    }
1772
1773    #[test]
1774    fn simple_action_entity() {
1775        let src = json!(
1776        {
1777            "entityTypes": { },
1778            "actions": {
1779                "view_photo": { },
1780            }
1781        });
1782
1783        let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
1784        let schema: ValidatorSchema = schema_file.try_into().expect("Schema Error");
1785        let actions = schema.action_entities().expect("Entity Construct Error");
1786
1787        let action_uid = EntityUID::from_str("Action::\"view_photo\"").unwrap();
1788        let view_photo = actions.entity(&action_uid);
1789        assert_eq!(
1790            view_photo.unwrap(),
1791            &Entity::new_with_attr_partial_value(action_uid, HashMap::new(), HashSet::new())
1792        );
1793    }
1794
1795    #[test]
1796    fn action_entity_hierarchy() {
1797        let src = json!(
1798        {
1799            "entityTypes": { },
1800            "actions": {
1801                "read": {},
1802                "view": {
1803                    "memberOf": [{"id": "read"}]
1804                },
1805                "view_photo": {
1806                    "memberOf": [{"id": "view"}]
1807                },
1808            }
1809        });
1810
1811        let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
1812        let schema: ValidatorSchema = schema_file.try_into().expect("Schema Error");
1813        let actions = schema.action_entities().expect("Entity Construct Error");
1814
1815        let view_photo_uid = EntityUID::from_str("Action::\"view_photo\"").unwrap();
1816        let view_uid = EntityUID::from_str("Action::\"view\"").unwrap();
1817        let read_uid = EntityUID::from_str("Action::\"read\"").unwrap();
1818
1819        let view_photo_entity = actions.entity(&view_photo_uid);
1820        assert_eq!(
1821            view_photo_entity.unwrap(),
1822            &Entity::new_with_attr_partial_value(
1823                view_photo_uid,
1824                HashMap::new(),
1825                HashSet::from([view_uid.clone(), read_uid.clone()])
1826            )
1827        );
1828
1829        let view_entity = actions.entity(&view_uid);
1830        assert_eq!(
1831            view_entity.unwrap(),
1832            &Entity::new_with_attr_partial_value(
1833                view_uid,
1834                HashMap::new(),
1835                HashSet::from([read_uid.clone()])
1836            )
1837        );
1838
1839        let read_entity = actions.entity(&read_uid);
1840        assert_eq!(
1841            read_entity.unwrap(),
1842            &Entity::new_with_attr_partial_value(read_uid, HashMap::new(), HashSet::new())
1843        );
1844    }
1845
1846    #[test]
1847    fn action_entity_attribute() {
1848        let src = json!(
1849        {
1850            "entityTypes": { },
1851            "actions": {
1852                "view_photo": {
1853                    "attributes": { "attr": "foo" }
1854                },
1855            }
1856        });
1857
1858        let schema_file: NamespaceDefinitionWithActionAttributes =
1859            serde_json::from_value(src).expect("Parse Error");
1860        let schema: ValidatorSchema = schema_file.try_into().expect("Schema Error");
1861        let actions = schema.action_entities().expect("Entity Construct Error");
1862
1863        let action_uid = EntityUID::from_str("Action::\"view_photo\"").unwrap();
1864        let view_photo = actions.entity(&action_uid);
1865        assert_eq!(
1866            view_photo.unwrap(),
1867            &Entity::new(
1868                action_uid,
1869                HashMap::from([("attr".into(), RestrictedExpr::val("foo"))]),
1870                HashSet::new(),
1871                &Extensions::none(),
1872            )
1873            .unwrap(),
1874        );
1875    }
1876
1877    #[test]
1878    fn test_action_namespace_inference_multi_success() {
1879        let src = json!({
1880            "Foo" : {
1881                "entityTypes" : {},
1882                "actions" : {
1883                    "read" : {}
1884                }
1885            },
1886            "ExampleCo::Personnel" : {
1887                "entityTypes" : {},
1888                "actions" : {
1889                    "viewPhoto" : {
1890                        "memberOf" : [
1891                            {
1892                                "id" : "read",
1893                                "type" : "Foo::Action"
1894                            }
1895                        ]
1896                    }
1897                }
1898            },
1899        });
1900        let schema_fragment =
1901            serde_json::from_value::<SchemaFragment>(src).expect("Failed to parse schema");
1902        let schema: ValidatorSchema = schema_fragment.try_into().expect("Schema should construct");
1903        let view_photo = schema
1904            .action_entities_iter()
1905            .find(|e| e.uid() == &r#"ExampleCo::Personnel::Action::"viewPhoto""#.parse().unwrap())
1906            .unwrap();
1907        let ancestors = view_photo.ancestors().collect::<Vec<_>>();
1908        let read = ancestors[0];
1909        assert_eq!(read.eid().to_string(), "read");
1910        assert_eq!(read.entity_type().to_string(), "Foo::Action");
1911    }
1912
1913    #[test]
1914    fn test_action_namespace_inference_multi() {
1915        let src = json!({
1916            "ExampleCo::Personnel::Foo" : {
1917                "entityTypes" : {},
1918                "actions" : {
1919                    "read" : {}
1920                }
1921            },
1922            "ExampleCo::Personnel" : {
1923                "entityTypes" : {},
1924                "actions" : {
1925                    "viewPhoto" : {
1926                        "memberOf" : [
1927                            {
1928                                "id" : "read",
1929                                "type" : "Foo::Action"
1930                            }
1931                        ]
1932                    }
1933                }
1934            },
1935        });
1936        let schema_fragment =
1937            serde_json::from_value::<SchemaFragment>(src).expect("Failed to parse schema");
1938        let schema: std::result::Result<ValidatorSchema, _> = schema_fragment.try_into();
1939        schema.expect_err("Schema should fail to construct as the normalization rules treat any qualification as starting from the root");
1940    }
1941
1942    #[test]
1943    fn test_action_namespace_inference() {
1944        let src = json!({
1945            "ExampleCo::Personnel" : {
1946                "entityTypes" : { },
1947                "actions" : {
1948                    "read" : {},
1949                    "viewPhoto" : {
1950                        "memberOf" : [
1951                            {
1952                                "id" :  "read",
1953                                "type" : "Action"
1954                            }
1955                        ]
1956                    }
1957                }
1958            }
1959        });
1960        let schema_fragment =
1961            serde_json::from_value::<SchemaFragment>(src).expect("Failed to parse schema");
1962        let schema: ValidatorSchema = schema_fragment.try_into().unwrap();
1963        let view_photo = schema
1964            .action_entities_iter()
1965            .find(|e| e.uid() == &r#"ExampleCo::Personnel::Action::"viewPhoto""#.parse().unwrap())
1966            .unwrap();
1967        let ancestors = view_photo.ancestors().collect::<Vec<_>>();
1968        let read = ancestors[0];
1969        assert_eq!(read.eid().to_string(), "read");
1970        assert_eq!(
1971            read.entity_type().to_string(),
1972            "ExampleCo::Personnel::Action"
1973        );
1974    }
1975
1976    #[test]
1977    fn qualified_undeclared_common_types() {
1978        let src = json!(
1979            {
1980                "Demo": {
1981                  "entityTypes": {
1982                    "User": {
1983                      "memberOfTypes": [],
1984                      "shape": {
1985                        "type": "Record",
1986                        "attributes": {
1987                          "id": { "type": "id" },
1988                        }
1989                      }
1990                    }
1991                  },
1992                  "actions": {}
1993                },
1994                "": {
1995                  "commonTypes": {
1996                    "id": {
1997                      "type": "String"
1998                    },
1999                  },
2000                  "entityTypes": {},
2001                  "actions": {}
2002                }
2003              }
2004        );
2005        let schema = ValidatorSchema::from_json_value(src, Extensions::all_available());
2006        assert_matches!(schema, Err(SchemaError::UndeclaredCommonTypes(types)) =>
2007            assert_eq!(types, HashSet::from(["Demo::id".to_string()])));
2008    }
2009
2010    #[test]
2011    fn qualified_undeclared_common_types2() {
2012        let src = json!(
2013            {
2014                "Demo": {
2015                  "entityTypes": {
2016                    "User": {
2017                      "memberOfTypes": [],
2018                      "shape": {
2019                        "type": "Record",
2020                        "attributes": {
2021                          "id": { "type": "Demo::id" },
2022                        }
2023                      }
2024                    }
2025                  },
2026                  "actions": {}
2027                },
2028                "": {
2029                  "commonTypes": {
2030                    "id": {
2031                      "type": "String"
2032                    },
2033                  },
2034                  "entityTypes": {},
2035                  "actions": {}
2036                }
2037              }
2038        );
2039        let schema = ValidatorSchema::from_json_value(src, Extensions::all_available());
2040        assert_matches!(schema, Err(SchemaError::UndeclaredCommonTypes(types)) =>
2041            assert_eq!(types, HashSet::from(["Demo::id".to_string()])));
2042    }
2043}