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::{Eid, EntityType, EntityUID, Id, Name},
27    entities::JSONValue,
28    parser::{err::ParseError, parse_name, parse_namespace},
29    transitive_closure::{compute_tc, TCNode},
30};
31use serde::{Deserialize, Serialize};
32use serde_with::serde_as;
33use smol_str::SmolStr;
34
35use crate::{
36    schema_file_format,
37    types::{AttributeType, Attributes, EntityRecordKind, Type},
38    ActionEntityUID, ActionType, SchemaFragment, SchemaType, SchemaTypeVariant, TypeOfAttribute,
39    SCHEMA_TYPE_VARIANT_TAGS,
40};
41
42use super::err::*;
43use super::NamespaceDefinition;
44
45/// The current schema format specification does not include multiple action entity
46/// types. All action entities are required to use a single `Action` entity
47/// type. However, the action entity type may be namespaced, so an action entity
48/// may have a fully qualified entity type `My::Namespace::Action`.
49pub(crate) static ACTION_ENTITY_TYPE: &str = "Action";
50
51/// Return true when an entity type is an action entity type. This compares the
52/// base name for the type, so this will return true for any entity type named
53/// `Action` regardless of namespaces.
54pub(crate) fn is_action_entity_type(ty: &Name) -> bool {
55    ty.basename().as_ref() == ACTION_ENTITY_TYPE
56}
57
58// We do not have a dafny model for action attributes, so we disable them by defualt.
59#[derive(Eq, PartialEq, Copy, Clone, Default)]
60pub enum ActionBehavior {
61    /// Action entities cannot have attributes. Attempting to declare attributes
62    /// will result in a error when constructing the schema.
63    #[default]
64    ProhibitAttributes,
65    /// Action entities may have attributes.
66    PermitAttributes,
67}
68
69/// A single namespace definition from the schema json processed into a form
70/// which is closer to that used by the validator. The processing includes
71/// detection of some errors, for example, parse errors in entity type names or
72/// entity type which are declared multiple times. This does not detect
73/// references to undeclared entity types because any entity type may be
74/// declared in a different fragment that will only be known about when building
75/// the complete `ValidatorSchema`.
76#[derive(Debug)]
77pub struct ValidatorNamespaceDef {
78    /// The namespace declared for the schema fragment. We track a namespace for
79    /// fragments because they have at most one namespace that is applied
80    /// everywhere. It would be less useful to track all namespaces for a
81    /// complete schema.
82    namespace: Option<Name>,
83    /// Preprocessed common type definitions which can be used to define entity
84    /// type attributes and action contexts.
85    type_defs: TypeDefs,
86    /// The preprocessed entity type declarations from the schema fragment json.
87    entity_types: EntityTypesDef,
88    /// The preprocessed action declarations from the schema fragment json.
89    actions: ActionsDef,
90}
91
92/// Holds a map from `Name`s of common type definitions to their corresponding
93/// `Type`.
94#[derive(Debug)]
95pub struct TypeDefs {
96    type_defs: HashMap<Name, Type>,
97}
98
99/// Entity type declarations held in a `ValidatorNamespaceDef`. Entity type
100/// children and attributes may reference undeclared entity types.
101#[derive(Debug)]
102pub struct EntityTypesDef {
103    /// Entity type attributes and children are tracked separately because a
104    /// child of an entity type may be declared in a fragment without also
105    /// declaring the entity type and its attributes. Attribute types are
106    /// wrapped in a `WithUnresolvedTypeDefs` because types may contain
107    /// typedefs which are not defined in this schema fragment. All
108    /// entity type `Name` keys in this map are declared in this schema fragment.
109    attributes: HashMap<Name, WithUnresolvedTypeDefs<Type>>,
110    /// `Name`s which are keys in this map appear inside `memberOf` lists, so
111    /// they might not declared in this fragment. We will check if they are
112    /// declared in any fragment when constructing a `ValidatorSchema`. The
113    /// children are taken from the entity type declaration where the `memberOf`
114    /// list appeared, so we know that they were declared in this fragment.
115    /// This map contains children rather than descendants because the
116    /// transitive closure has not yet been computed. We need all child edges
117    /// between entity types from all fragments before we can compute the
118    /// transitive closure.
119    children: HashMap<Name, HashSet<Name>>,
120}
121
122/// Action declarations held in a `ValidatorNamespaceDef`. Entity types
123/// referenced here do not need to be declared in the schema.
124#[derive(Debug)]
125pub struct ActionsDef {
126    /// Action declaration components are tracked separately for the same reasons
127    /// as entity types. This map holds attributes and apply-specs because these
128    /// are always fully defined in the schema fragment containing the action
129    /// declarations. The attribute types are wrapped in a `WithUnresolvedTypeDefs` because they
130    /// may refer to common types which are not defined in this fragment. The `EntityUID` keys in
131    /// this map again were definitely declared in this fragment.
132    context_applies_to: HashMap<EntityUID, (WithUnresolvedTypeDefs<Type>, ValidatorApplySpec)>,
133    /// `EntityUID` keys in this map appear inside action `memberOf` lists, so
134    /// they might not be declared in this fragment while the entries in the
135    /// values hash set are taken directly from declared actions.
136    children: HashMap<EntityUID, HashSet<EntityUID>>,
137    /// Action attributes
138    attributes: HashMap<EntityUID, Attributes>,
139}
140
141type ResolveFunc<T> = dyn FnOnce(&HashMap<Name, Type>) -> Result<T>;
142/// Represent a type that might be defined in terms of some type definitions
143/// which are not necessarily available in the current namespace.
144pub enum WithUnresolvedTypeDefs<T> {
145    WithUnresolved(Box<ResolveFunc<T>>),
146    WithoutUnresolved(T),
147}
148
149impl<T: 'static> WithUnresolvedTypeDefs<T> {
150    pub fn new(f: impl FnOnce(&HashMap<Name, Type>) -> Result<T> + 'static) -> Self {
151        Self::WithUnresolved(Box::new(f))
152    }
153
154    pub fn map<U: 'static>(self, f: impl FnOnce(T) -> U + 'static) -> WithUnresolvedTypeDefs<U> {
155        match self {
156            Self::WithUnresolved(_) => {
157                WithUnresolvedTypeDefs::new(|type_defs| self.resolve_type_defs(type_defs).map(f))
158            }
159            Self::WithoutUnresolved(v) => WithUnresolvedTypeDefs::WithoutUnresolved(f(v)),
160        }
161    }
162
163    /// Instantiate any names referencing types with the definition of the type
164    /// from the input HashMap.
165    pub fn resolve_type_defs(self, type_defs: &HashMap<Name, Type>) -> Result<T> {
166        match self {
167            WithUnresolvedTypeDefs::WithUnresolved(f) => f(type_defs),
168            WithUnresolvedTypeDefs::WithoutUnresolved(v) => Ok(v),
169        }
170    }
171}
172
173impl<T: 'static> From<T> for WithUnresolvedTypeDefs<T> {
174    fn from(value: T) -> Self {
175        Self::WithoutUnresolved(value)
176    }
177}
178
179impl<T: std::fmt::Debug> std::fmt::Debug for WithUnresolvedTypeDefs<T> {
180    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
181        match self {
182            WithUnresolvedTypeDefs::WithUnresolved(_) => f.debug_tuple("WithUnresolved").finish(),
183            WithUnresolvedTypeDefs::WithoutUnresolved(v) => {
184                f.debug_tuple("WithoutUnresolved").field(v).finish()
185            }
186        }
187    }
188}
189
190impl TryInto<ValidatorNamespaceDef> for NamespaceDefinition {
191    type Error = SchemaError;
192
193    fn try_into(self) -> Result<ValidatorNamespaceDef> {
194        ValidatorNamespaceDef::from_namespace_definition(None, self, ActionBehavior::default())
195    }
196}
197
198impl ValidatorNamespaceDef {
199    // We need to treat this as if it had `pub(crate)` visibility to avoid sharing
200    // the file format. However, our fuzzing library currently needs it to be public.
201    /// Construct a new `ValidatorSchema` from the underlying `SchemaFragment`.
202    pub fn from_namespace_definition(
203        namespace: Option<SmolStr>,
204        namespace_def: NamespaceDefinition,
205        action_behavior: ActionBehavior,
206    ) -> Result<ValidatorNamespaceDef> {
207        // Check that each entity types and action is only declared once.
208        let mut e_types_ids: HashSet<SmolStr> = HashSet::new();
209        for name in namespace_def.entity_types.keys() {
210            if !e_types_ids.insert(name.clone()) {
211                // insert returns false for duplicates
212                return Err(SchemaError::DuplicateEntityType(name.to_string()));
213            }
214        }
215        let mut a_name_eids: HashSet<SmolStr> = HashSet::new();
216        for name in namespace_def.actions.keys() {
217            if !a_name_eids.insert(name.clone()) {
218                // insert returns false for duplicates
219                return Err(SchemaError::DuplicateAction(name.to_string()));
220            }
221        }
222
223        let schema_namespace = namespace
224            .as_ref()
225            .map(|ns| parse_namespace(ns).map_err(SchemaError::NamespaceParseError))
226            .transpose()?
227            .unwrap_or_default();
228
229        // Return early with an error if actions cannot be in groups or have
230        // attributes, but the schema contains action groups or attributes.
231        Self::check_action_behavior(&namespace_def, action_behavior)?;
232
233        // Convert the type defs, actions and entity types from the schema file
234        // into the representation used by the validator.
235        let type_defs =
236            Self::build_type_defs(namespace_def.common_types, schema_namespace.as_slice())?;
237        let actions = Self::build_action_ids(namespace_def.actions, schema_namespace.as_slice())?;
238        let entity_types =
239            Self::build_entity_types(namespace_def.entity_types, schema_namespace.as_slice())?;
240
241        Ok(ValidatorNamespaceDef {
242            namespace: {
243                let mut schema_namespace = schema_namespace;
244                schema_namespace
245                    .pop()
246                    .map(|last| Name::new(last, schema_namespace))
247            },
248            type_defs,
249            entity_types,
250            actions,
251        })
252    }
253
254    fn is_builtin_type_name(name: &SmolStr) -> bool {
255        SCHEMA_TYPE_VARIANT_TAGS
256            .iter()
257            .any(|type_name| name == type_name)
258    }
259
260    fn build_type_defs(
261        schema_file_type_def: HashMap<SmolStr, SchemaType>,
262        schema_namespace: &[Id],
263    ) -> Result<TypeDefs> {
264        let type_defs = schema_file_type_def
265            .into_iter()
266            .map(|(name_str, schema_ty)| -> Result<_> {
267                if Self::is_builtin_type_name(&name_str) {
268                    return Err(SchemaError::DuplicateCommonType(name_str.to_string()));
269                }
270                let name = Self::parse_unqualified_name_with_namespace(
271                    &name_str,
272                    schema_namespace.to_vec(),
273                )
274                .map_err(SchemaError::CommonTypeParseError)?;
275                let ty = Self::try_schema_type_into_validator_type(schema_namespace, schema_ty)?
276                    .resolve_type_defs(&HashMap::new())?;
277                Ok((name, ty))
278            })
279            .collect::<Result<HashMap<_, _>>>()?;
280        Ok(TypeDefs { type_defs })
281    }
282
283    // Transform the schema data structures for entity types into the structures
284    // used internally by the validator. This is mostly accomplished by directly
285    // copying data between fields. For the `descendants` this field, we first
286    // reverse the direction of the `member_of` relation and then compute the
287    // transitive closure.
288    fn build_entity_types(
289        schema_files_types: HashMap<SmolStr, schema_file_format::EntityType>,
290        schema_namespace: &[Id],
291    ) -> Result<EntityTypesDef> {
292        // Invert the `member_of` relationship, associating each entity type
293        // with its set of children instead of parents.
294        let mut children: HashMap<Name, HashSet<Name>> = HashMap::new();
295        for (name, e) in &schema_files_types {
296            for parent in &e.member_of_types {
297                let parent_type_name = Self::parse_possibly_qualified_name_with_default_namespace(
298                    parent,
299                    schema_namespace,
300                )
301                .map_err(SchemaError::EntityTypeParseError)?;
302                children
303                    .entry(parent_type_name)
304                    .or_insert_with(HashSet::new)
305                    .insert(
306                        Self::parse_unqualified_name_with_namespace(
307                            name,
308                            schema_namespace.to_vec(),
309                        )
310                        .map_err(SchemaError::EntityTypeParseError)?,
311                    );
312            }
313        }
314
315        let attributes = schema_files_types
316            .into_iter()
317            .map(|(name, e)| -> Result<_> {
318                let name: Name =
319                    Self::parse_unqualified_name_with_namespace(&name, schema_namespace.to_vec())
320                        .map_err(SchemaError::EntityTypeParseError)?;
321
322                let attributes = Self::try_schema_type_into_validator_type(
323                    schema_namespace,
324                    e.shape.into_inner(),
325                )?;
326
327                Ok((name, attributes))
328            })
329            .collect::<Result<HashMap<_, _>>>()?;
330
331        Ok(EntityTypesDef {
332            attributes,
333            children,
334        })
335    }
336
337    //Helper to get types from JSONValues
338    //Currently doesn't support all JSONValue types
339    fn jsonval_to_type_helper(v: &JSONValue) -> Result<Type> {
340        match v {
341            JSONValue::Bool(_) => Ok(Type::primitive_boolean()),
342            JSONValue::Long(_) => Ok(Type::primitive_long()),
343            JSONValue::String(_) => Ok(Type::primitive_string()),
344            JSONValue::Record(r) => {
345                let mut required_attrs: HashMap<SmolStr, Type> = HashMap::new();
346                for (k, v_prime) in r {
347                    let t = Self::jsonval_to_type_helper(v_prime);
348                    match t {
349                        Ok(ty) => required_attrs.insert(k.clone(), ty),
350                        Err(e) => return Err(e),
351                    };
352                }
353                Ok(Type::EntityOrRecord(EntityRecordKind::Record {
354                    attrs: Attributes::with_required_attributes(required_attrs),
355                }))
356            }
357            JSONValue::Set(v) => match v.get(0) {
358                //sets with elements of different types will be rejected elsewhere
359                None => Err(SchemaError::ActionEntityAttributeEmptySet),
360                Some(element) => {
361                    let element_type = Self::jsonval_to_type_helper(element);
362                    match element_type {
363                        Ok(t) => Ok(Type::Set {
364                            element_type: Some(Box::new(t)),
365                        }),
366                        Err(_) => element_type,
367                    }
368                }
369            },
370            _ => Err(SchemaError::ActionEntityAttributeUnsupportedType),
371        }
372    }
373
374    //Convert jsonval map to attributes
375    fn convert_attr_jsonval_map_to_attributes(
376        m: HashMap<SmolStr, JSONValue>,
377    ) -> Result<Attributes> {
378        let mut required_attrs: HashMap<SmolStr, Type> = HashMap::new();
379
380        for (k, v) in m {
381            let t = Self::jsonval_to_type_helper(&v);
382            match t {
383                Ok(ty) => required_attrs.insert(k.clone(), ty),
384                Err(e) => return Err(e),
385            };
386        }
387        Ok(Attributes::with_required_attributes(required_attrs))
388    }
389
390    // Transform the schema data structures for actions into the structures used
391    // internally by the validator. This is mostly accomplished by directly
392    // copying data between fields, except that for the `descendants` field we
393    // first reverse the direction of the `member_of` relation and then compute
394    // the transitive closure.
395    fn build_action_ids(
396        schema_file_actions: HashMap<SmolStr, ActionType>,
397        schema_namespace: &[Id],
398    ) -> Result<ActionsDef> {
399        // Invert the `member_of` relationship, associating each entity and
400        // action with it's set of children instead of parents.
401        let mut children: HashMap<EntityUID, HashSet<EntityUID>> = HashMap::new();
402        for (name, a) in &schema_file_actions {
403            let parents = match &a.member_of {
404                Some(parents) => parents,
405                None => continue,
406            };
407            for parent in parents {
408                let parent_euid =
409                    Self::parse_action_id_with_namespace(parent, schema_namespace.to_vec())?;
410                children
411                    .entry(parent_euid)
412                    .or_insert_with(HashSet::new)
413                    .insert(Self::parse_action_id_with_namespace(
414                        &ActionEntityUID::default_type(name.clone()),
415                        schema_namespace.to_vec(),
416                    )?);
417            }
418        }
419
420        let context_applies_to = schema_file_actions
421            .clone()
422            .into_iter()
423            .map(|(name, a)| -> Result<_> {
424                let action_euid = Self::parse_action_id_with_namespace(
425                    &ActionEntityUID::default_type(name),
426                    schema_namespace.to_vec(),
427                )?;
428
429                let (principal_types, resource_types, context) = a
430                    .applies_to
431                    .map(|applies_to| {
432                        (
433                            applies_to.principal_types,
434                            applies_to.resource_types,
435                            applies_to.context,
436                        )
437                    })
438                    .unwrap_or_default();
439
440                // Convert the entries in the `appliesTo` lists into sets of
441                // `EntityTypes`. If one of the lists is `None` (absent from the
442                // schema), then the specification is undefined.
443                let action_applies_to = ValidatorApplySpec::new(
444                    Self::parse_apply_spec_type_list(principal_types, schema_namespace)?,
445                    Self::parse_apply_spec_type_list(resource_types, schema_namespace)?,
446                );
447
448                let action_context = Self::try_schema_type_into_validator_type(
449                    schema_namespace,
450                    context.into_inner(),
451                )?;
452
453                Ok((action_euid, (action_context, action_applies_to)))
454            })
455            .collect::<Result<HashMap<_, _>>>()?;
456
457        let attributes = schema_file_actions
458            .into_iter()
459            .map(|(name, a)| -> Result<_> {
460                let action_euid = Self::parse_action_id_with_namespace(
461                    &ActionEntityUID::default_type(name),
462                    schema_namespace.to_vec(),
463                )?;
464
465                let action_attributes =
466                    Self::convert_attr_jsonval_map_to_attributes(a.attributes.unwrap_or_default());
467                match action_attributes {
468                    // We can't just use the last element of the vec without implementing `Clone` for `SchemaError`, which has some potentially very expensive variants
469                    Ok(attrs) => Ok((action_euid, attrs)),
470                    Err(e) => Err(e),
471                }
472            })
473            .collect::<Result<HashMap<_, _>>>()?;
474
475        Ok(ActionsDef {
476            context_applies_to,
477            children,
478            attributes,
479        })
480    }
481
482    // Check that `schema_file` uses actions in a way consistent with the
483    // specified `action_behavior`. When the behavior specifies that actions
484    // should not be used in groups and should not have attributes, then this
485    // function will return `Err` if it sees any action groups or attributes
486    // declared in the schema.
487    fn check_action_behavior(
488        schema_file: &NamespaceDefinition,
489        action_behavior: ActionBehavior,
490    ) -> Result<()> {
491        if schema_file
492            .entity_types
493            .iter()
494            // The `name` in an entity type declaration cannot be qualified
495            // with a namespace (it always implicitly takes the schema
496            // namespace), so we do this comparison directly.
497            .any(|(name, _)| name == ACTION_ENTITY_TYPE)
498        {
499            return Err(SchemaError::ActionEntityTypeDeclared);
500        }
501        if action_behavior == ActionBehavior::ProhibitAttributes {
502            let mut actions_with_attributes: Vec<String> = Vec::new();
503            for (name, a) in &schema_file.actions {
504                if a.attributes.is_some() {
505                    actions_with_attributes.push(name.to_string());
506                }
507            }
508            if !actions_with_attributes.is_empty() {
509                return Err(SchemaError::ActionEntityAttributes(actions_with_attributes));
510            }
511        }
512
513        Ok(())
514    }
515
516    /// Given the attributes for an entity type or action context as written in
517    /// a schema file, convert the types of the attributes into the `Type` data
518    /// structure used by the typechecker, and return the result as a map from
519    /// attribute name to type.
520    fn parse_record_attributes(
521        schema_namespace: &[Id],
522        attrs: impl IntoIterator<Item = (SmolStr, TypeOfAttribute)>,
523    ) -> Result<WithUnresolvedTypeDefs<Attributes>> {
524        let attrs_with_type_defs = attrs
525            .into_iter()
526            .map(|(attr, ty)| -> Result<_> {
527                Ok((
528                    attr,
529                    (
530                        Self::try_schema_type_into_validator_type(schema_namespace, ty.ty)?,
531                        ty.required,
532                    ),
533                ))
534            })
535            .collect::<Result<Vec<_>>>()?;
536        Ok(WithUnresolvedTypeDefs::new(|typ_defs| {
537            attrs_with_type_defs
538                .into_iter()
539                .map(|(s, (attr_ty, is_req))| {
540                    attr_ty
541                        .resolve_type_defs(typ_defs)
542                        .map(|ty| (s, AttributeType::new(ty, is_req)))
543                })
544                .collect::<Result<Vec<_>>>()
545                .map(Attributes::with_attributes)
546        }))
547    }
548
549    /// Take an optional list of entity type name strings from an action apply
550    /// spec and parse it into a set of `Name`s for those entity types. If any
551    /// of the entity type names cannot be parsed, then the `Err` case is
552    /// returned, and it will indicate which name did not parse.
553    fn parse_apply_spec_type_list(
554        types: Option<Vec<SmolStr>>,
555        namespace: &[Id],
556    ) -> Result<HashSet<EntityType>> {
557        types
558            .map(|types| {
559                types
560                    .iter()
561                    // Parse each type name string into a `Name`, generating an
562                    // `EntityTypeParseError` when the string is not a valid
563                    // name.
564                    .map(|ty_str| {
565                        Ok(EntityType::Concrete(
566                            Self::parse_possibly_qualified_name_with_default_namespace(
567                                ty_str, namespace,
568                            )
569                            .map_err(SchemaError::EntityTypeParseError)?,
570                        ))
571                    })
572                    // Fail if any of the types failed.
573                    .collect::<Result<HashSet<_>>>()
574            })
575            .unwrap_or_else(|| Ok(HashSet::from([EntityType::Unspecified])))
576    }
577
578    // Parse a `Name` from a string (possibly including namespaces). If it is
579    // not qualified with any namespace, then apply the  default namespace to
580    // create a qualified name.  Do not modify any existing namespace on the
581    // type.
582    pub(crate) fn parse_possibly_qualified_name_with_default_namespace(
583        name_str: &SmolStr,
584        default_namespace: &[Id],
585    ) -> std::result::Result<Name, Vec<ParseError>> {
586        let name = parse_name(name_str)?;
587
588        let qualified_name =
589            if name.namespace_components().next().is_none() && !default_namespace.is_empty() {
590                // The name does not have a namespace, and the schema has a
591                // namespace declared, so qualify the type to use the default.
592                Name::new(name.basename().clone(), default_namespace.to_vec())
593            } else {
594                // The name is already qualified. Don't touch it.
595                name
596            };
597
598        Ok(qualified_name)
599    }
600
601    /// Parse a name from a string into the `Id` (basename only).  Then
602    /// initialize the namespace for this type with the provided namespace vec
603    /// to create the qualified `Name`.
604    fn parse_unqualified_name_with_namespace(
605        type_name: &SmolStr,
606        namespace: Vec<Id>,
607    ) -> std::result::Result<Name, Vec<ParseError>> {
608        Ok(Name::new(type_name.parse()?, namespace))
609    }
610
611    /// Take an action identifier as a string and use it to construct an
612    /// EntityUID for that action. The entity type of the action will always
613    /// have the base type `Action`. The type will be qualified with any
614    /// namespace provided in the `namespace` argument or with the namespace
615    /// inside the ActionEntityUID if one is present.
616    fn parse_action_id_with_namespace(
617        action_id: &ActionEntityUID,
618        namespace: Vec<Id>,
619    ) -> Result<EntityUID> {
620        let namespaced_action_type = if let Some(action_ty) = &action_id.ty {
621            action_ty
622                .parse()
623                .map_err(SchemaError::EntityTypeParseError)?
624        } else {
625            Name::new(
626                ACTION_ENTITY_TYPE.parse().expect(
627                    "Expected that the constant ACTION_ENTITY_TYPE would be a valid entity type.",
628                ),
629                namespace,
630            )
631        };
632        Ok(EntityUID::from_components(
633            namespaced_action_type,
634            Eid::new(action_id.id.clone()),
635        ))
636    }
637
638    /// Implemented to convert a type as written in the schema json format into the
639    /// `Type` type used by the validator. Conversion can fail if an entity or
640    /// record attribute name is invalid. It will also fail for some types that can
641    /// be written in the schema, but are not yet implemented in the typechecking
642    /// logic.
643    pub(crate) fn try_schema_type_into_validator_type(
644        default_namespace: &[Id],
645        schema_ty: SchemaType,
646    ) -> Result<WithUnresolvedTypeDefs<Type>> {
647        match schema_ty {
648            SchemaType::Type(SchemaTypeVariant::String) => Ok(Type::primitive_string().into()),
649            SchemaType::Type(SchemaTypeVariant::Long) => Ok(Type::primitive_long().into()),
650            SchemaType::Type(SchemaTypeVariant::Boolean) => Ok(Type::primitive_boolean().into()),
651            SchemaType::Type(SchemaTypeVariant::Set { element }) => Ok(
652                Self::try_schema_type_into_validator_type(default_namespace, *element)?
653                    .map(Type::set),
654            ),
655            SchemaType::Type(SchemaTypeVariant::Record {
656                attributes,
657                additional_attributes,
658            }) => {
659                if additional_attributes {
660                    Err(SchemaError::UnsupportedSchemaFeature(
661                        UnsupportedFeature::OpenRecordsAndEntities,
662                    ))
663                } else {
664                    Ok(
665                        Self::parse_record_attributes(default_namespace, attributes)?
666                            .map(Type::record_with_attributes),
667                    )
668                }
669            }
670            SchemaType::Type(SchemaTypeVariant::Entity { name }) => {
671                let entity_type_name = Self::parse_possibly_qualified_name_with_default_namespace(
672                    &name,
673                    default_namespace,
674                )
675                .map_err(SchemaError::EntityTypeParseError)?;
676                Ok(Type::named_entity_reference(entity_type_name).into())
677            }
678            SchemaType::Type(SchemaTypeVariant::Extension { name }) => {
679                let extension_type_name =
680                    name.parse().map_err(SchemaError::ExtensionTypeParseError)?;
681                Ok(Type::extension(extension_type_name).into())
682            }
683            SchemaType::TypeDef { type_name } => {
684                let defined_type_name = Self::parse_possibly_qualified_name_with_default_namespace(
685                    &type_name,
686                    default_namespace,
687                )
688                .map_err(SchemaError::CommonTypeParseError)?;
689                Ok(WithUnresolvedTypeDefs::new(move |typ_defs| {
690                    typ_defs.get(&defined_type_name).cloned().ok_or(
691                        SchemaError::UndeclaredCommonType(HashSet::from([type_name.to_string()])),
692                    )
693                }))
694            }
695        }
696    }
697
698    /// Access the `Name` for the namespace of this definition.
699    pub fn namespace(&self) -> &Option<Name> {
700        &self.namespace
701    }
702}
703
704#[derive(Debug)]
705pub struct ValidatorSchemaFragment(Vec<ValidatorNamespaceDef>);
706
707impl TryInto<ValidatorSchemaFragment> for SchemaFragment {
708    type Error = SchemaError;
709
710    fn try_into(self) -> Result<ValidatorSchemaFragment> {
711        ValidatorSchemaFragment::from_schema_fragment(self, ActionBehavior::default())
712    }
713}
714
715impl ValidatorSchemaFragment {
716    pub fn from_namespaces(namespaces: impl IntoIterator<Item = ValidatorNamespaceDef>) -> Self {
717        Self(namespaces.into_iter().collect())
718    }
719
720    pub fn from_schema_fragment(
721        fragment: SchemaFragment,
722        action_behavior: ActionBehavior,
723    ) -> Result<Self> {
724        Ok(Self(
725            fragment
726                .0
727                .into_iter()
728                .map(|(fragment_ns, ns_def)| {
729                    ValidatorNamespaceDef::from_namespace_definition(
730                        Some(fragment_ns),
731                        ns_def,
732                        action_behavior,
733                    )
734                })
735                .collect::<Result<Vec<_>>>()?,
736        ))
737    }
738
739    /// Access the `Name`s for the namespaces in this fragment.
740    pub fn namespaces(&self) -> impl Iterator<Item = &Option<Name>> {
741        self.0.iter().map(|d| d.namespace())
742    }
743}
744
745#[serde_as]
746#[derive(Clone, Debug, Serialize)]
747pub struct ValidatorSchema {
748    /// Map from entity type names to the ValidatorEntityType object.
749    #[serde(rename = "entityTypes")]
750    #[serde_as(as = "Vec<(_, _)>")]
751    entity_types: HashMap<Name, ValidatorEntityType>,
752
753    /// Map from action id names to the ValidatorActionId object.
754    #[serde(rename = "actionIds")]
755    #[serde_as(as = "Vec<(_, _)>")]
756    action_ids: HashMap<EntityUID, ValidatorActionId>,
757}
758
759impl std::str::FromStr for ValidatorSchema {
760    type Err = SchemaError;
761
762    fn from_str(s: &str) -> Result<Self> {
763        serde_json::from_str::<SchemaFragment>(s)?.try_into()
764    }
765}
766
767impl TryInto<ValidatorSchema> for NamespaceDefinition {
768    type Error = SchemaError;
769
770    fn try_into(self) -> Result<ValidatorSchema> {
771        ValidatorSchema::from_schema_fragments([ValidatorSchemaFragment::from_namespaces([self
772            .try_into(
773        )?])])
774    }
775}
776
777impl TryInto<ValidatorSchema> for SchemaFragment {
778    type Error = SchemaError;
779
780    fn try_into(self) -> Result<ValidatorSchema> {
781        ValidatorSchema::from_schema_fragments([self.try_into()?])
782    }
783}
784
785impl ValidatorSchema {
786    // Create a ValidatorSchema without any entity types or actions ids.
787    pub fn empty() -> ValidatorSchema {
788        Self {
789            entity_types: HashMap::new(),
790            action_ids: HashMap::new(),
791        }
792    }
793
794    /// Construct a `ValidatorSchema` from a JSON value (which should be an
795    /// object matching the `SchemaFileFormat` shape).
796    pub fn from_json_value(json: serde_json::Value) -> Result<Self> {
797        Self::from_schema_file(
798            SchemaFragment::from_json_value(json)?,
799            ActionBehavior::default(),
800        )
801    }
802
803    /// Construct a `ValidatorSchema` directly from a file.
804    pub fn from_file(file: impl std::io::Read) -> Result<Self> {
805        Self::from_schema_file(SchemaFragment::from_file(file)?, ActionBehavior::default())
806    }
807
808    pub fn from_schema_file(
809        schema_file: SchemaFragment,
810        action_behavior: ActionBehavior,
811    ) -> Result<ValidatorSchema> {
812        Self::from_schema_fragments([ValidatorSchemaFragment::from_schema_fragment(
813            schema_file,
814            action_behavior,
815        )?])
816    }
817
818    /// Construct a new `ValidatorSchema` from some number of schema fragments.
819    pub fn from_schema_fragments(
820        fragments: impl IntoIterator<Item = ValidatorSchemaFragment>,
821    ) -> Result<ValidatorSchema> {
822        let mut type_defs = HashMap::new();
823        let mut entity_attributes = HashMap::new();
824        let mut entity_children = HashMap::new();
825        let mut action_context_applies_to = HashMap::new();
826        let mut action_children = HashMap::new();
827        let mut action_attributes = HashMap::new();
828
829        for ns_def in fragments.into_iter().flat_map(|f| f.0.into_iter()) {
830            for (name, ty) in ns_def.type_defs.type_defs {
831                match type_defs.entry(name) {
832                    Entry::Vacant(v) => v.insert(ty),
833                    Entry::Occupied(o) => {
834                        return Err(SchemaError::DuplicateCommonType(o.key().to_string()));
835                    }
836                };
837            }
838
839            // Build aggregate maps for the declared entity/action attributes and
840            // action context/applies_to lists, checking that no action or
841            // entity type is declared more than once.  Namespaces were already
842            // added by the `ValidatorNamespaceDef`, so the same base type
843            // name may appear multiple times so long as the namespaces are
844            // different.
845            for (name, attrs) in ns_def.entity_types.attributes {
846                match entity_attributes.entry(name) {
847                    Entry::Vacant(v) => v.insert(attrs),
848                    Entry::Occupied(o) => {
849                        return Err(SchemaError::DuplicateEntityType(o.key().to_string()))
850                    }
851                };
852            }
853            for (id, context_applies_to) in ns_def.actions.context_applies_to {
854                match action_context_applies_to.entry(id) {
855                    Entry::Vacant(v) => v.insert(context_applies_to),
856                    Entry::Occupied(o) => {
857                        return Err(SchemaError::DuplicateAction(o.key().to_string()))
858                    }
859                };
860            }
861            for (id, attrs) in ns_def.actions.attributes {
862                match action_attributes.entry(id) {
863                    Entry::Vacant(v) => v.insert(attrs),
864                    Entry::Occupied(o) => {
865                        return Err(SchemaError::DuplicateAction(o.key().to_string()))
866                    }
867                };
868            }
869
870            // Now build aggregate children maps. There may be keys duplicated
871            // between the fragments if an entity type has child entity type
872            // declared in multiple fragments.
873            for (name, children) in ns_def.entity_types.children {
874                let current_children: &mut HashSet<_> = entity_children.entry(name).or_default();
875                for child in children {
876                    current_children.insert(child);
877                }
878            }
879
880            for (id, children) in ns_def.actions.children {
881                let current_children: &mut HashSet<_> = action_children.entry(id).or_default();
882                for child in children {
883                    current_children.insert(child);
884                }
885            }
886        }
887
888        let mut entity_types = entity_attributes
889            .into_iter()
890            .map(|(name, attributes)| -> Result<_> {
891                // Keys of the `entity_children` map were values of an
892                // `memberOfTypes` list, so they might not have been declared in
893                // their fragment.  By removing entries from `entity_children`
894                // where the key is a declared name, we will be left with a map
895                // where the keys are undeclared. These keys are used to report
896                // an error when undeclared entity types are referenced inside a
897                // `memberOfTypes` list. The error is reported alongside the
898                // error for any other undeclared entity types by
899                // `check_for_undeclared`.
900                let descendants = entity_children.remove(&name).unwrap_or_default();
901                Ok((
902                    name.clone(),
903                    ValidatorEntityType {
904                        name,
905                        descendants,
906                        attributes: Self::record_attributes_or_error(
907                            attributes.resolve_type_defs(&type_defs)?,
908                        )?,
909                    },
910                ))
911            })
912            .collect::<Result<HashMap<_, _>>>()?;
913
914        let mut action_ids = action_context_applies_to
915            .into_iter()
916            .map(|(name, (context, applies_to))| -> Result<_> {
917                let descendants = action_children.remove(&name).unwrap_or_default();
918
919                let attributes = match action_attributes.get(&name) {
920                    Some(t) => t.clone(),
921                    None => Attributes::with_attributes([]),
922                };
923
924                Ok((
925                    name.clone(),
926                    ValidatorActionId {
927                        name,
928                        applies_to,
929                        descendants,
930                        context: Self::record_attributes_or_error(
931                            context.resolve_type_defs(&type_defs)?,
932                        )?,
933                        attributes,
934                    },
935                ))
936            })
937            .collect::<Result<HashMap<_, _>>>()?;
938
939        // We constructed entity types and actions with child maps, but we need
940        // transitively closed descendants.
941        compute_tc(&mut entity_types, false)?;
942        // Pass `true` here so that we also check that the action hierarchy does
943        // not contain cycles.
944        compute_tc(&mut action_ids, true)?;
945
946        // Return with an error if there is an undeclared entity or action
947        // referenced in any fragment. `{entity,action}_children` are provided
948        // for the `undeclared_parent_{entities,actions}` arguments because
949        // removed keys from these maps as we encountered declarations for the
950        // entity types or actions. Any keys left in the map are therefore
951        // undeclared.
952        Self::check_for_undeclared(
953            &entity_types,
954            entity_children.into_keys(),
955            &action_ids,
956            action_children.into_keys(),
957        )?;
958
959        Ok(ValidatorSchema {
960            entity_types,
961            action_ids,
962        })
963    }
964
965    /// Check that all entity types and actions referenced in the schema are in
966    /// the set of declared entity type or action names. Point of caution: this
967    /// function assumes that all entity types are fully qualified. This is
968    /// handled by the `SchemaFragment` constructor.
969    fn check_for_undeclared(
970        entity_types: &HashMap<Name, ValidatorEntityType>,
971        undeclared_parent_entities: impl IntoIterator<Item = Name>,
972        action_ids: &HashMap<EntityUID, ValidatorActionId>,
973        undeclared_parent_actions: impl IntoIterator<Item = EntityUID>,
974    ) -> Result<()> {
975        // When we constructed `entity_types`, we removed entity types from  the
976        // `entity_children` map as we encountered a declaration for that type.
977        // Any entity types left in the map are therefore undeclared. These are
978        // any undeclared entity types which appeared in a `memberOf` list.
979        let mut undeclared_e = undeclared_parent_entities
980            .into_iter()
981            .map(|n| n.to_string())
982            .collect::<HashSet<_>>();
983        // Looking at entity types, we need to check entity references in
984        // attribute types. We already know that all elements of the
985        // `descendants` list were declared because the list is a result of
986        // inverting the `memberOf` relationship which mapped declared entity
987        // types to their parent entity types.
988        for entity_type in entity_types.values() {
989            for (_, attr_typ) in entity_type.attributes() {
990                Self::check_undeclared_in_type(
991                    &attr_typ.attr_type,
992                    entity_types,
993                    &mut undeclared_e,
994                );
995            }
996        }
997
998        // Undeclared actions in a `memberOf` list.
999        let undeclared_a = undeclared_parent_actions
1000            .into_iter()
1001            .map(|n| n.to_string())
1002            .collect::<HashSet<_>>();
1003        // For actions, we check entity references in the context attribute
1004        // types and `appliesTo` lists. See the `entity_types` loop for why the
1005        // `descendants` list is not checked.
1006        for action in action_ids.values() {
1007            for (_, attr_typ) in action.context.iter() {
1008                Self::check_undeclared_in_type(
1009                    &attr_typ.attr_type,
1010                    entity_types,
1011                    &mut undeclared_e,
1012                );
1013            }
1014
1015            for p_entity in action.applies_to.applicable_principal_types() {
1016                match p_entity {
1017                    EntityType::Concrete(p_entity) => {
1018                        if !entity_types.contains_key(p_entity) {
1019                            undeclared_e.insert(p_entity.to_string());
1020                        }
1021                    }
1022                    EntityType::Unspecified => (),
1023                }
1024            }
1025
1026            for r_entity in action.applies_to.applicable_resource_types() {
1027                match r_entity {
1028                    EntityType::Concrete(r_entity) => {
1029                        if !entity_types.contains_key(r_entity) {
1030                            undeclared_e.insert(r_entity.to_string());
1031                        }
1032                    }
1033                    EntityType::Unspecified => (),
1034                }
1035            }
1036        }
1037        if !undeclared_e.is_empty() {
1038            return Err(SchemaError::UndeclaredEntityTypes(undeclared_e));
1039        }
1040        if !undeclared_a.is_empty() {
1041            return Err(SchemaError::UndeclaredActions(undeclared_a));
1042        }
1043
1044        Ok(())
1045    }
1046
1047    fn record_attributes_or_error(ty: Type) -> Result<Attributes> {
1048        match ty {
1049            Type::EntityOrRecord(EntityRecordKind::Record { attrs }) => Ok(attrs),
1050            _ => Err(SchemaError::ContextOrShapeNotRecord),
1051        }
1052    }
1053
1054    // Check that all entity types appearing inside a type are in the set of
1055    // declared entity types, adding any undeclared entity types to the
1056    // `undeclared_types` set.
1057    fn check_undeclared_in_type(
1058        ty: &Type,
1059        entity_types: &HashMap<Name, ValidatorEntityType>,
1060        undeclared_types: &mut HashSet<String>,
1061    ) {
1062        match ty {
1063            Type::EntityOrRecord(EntityRecordKind::Entity(lub)) => {
1064                for name in lub.iter() {
1065                    if !entity_types.contains_key(name) {
1066                        undeclared_types.insert(name.to_string());
1067                    }
1068                }
1069            }
1070
1071            Type::EntityOrRecord(EntityRecordKind::Record { attrs }) => {
1072                for (_, attr_ty) in attrs.iter() {
1073                    Self::check_undeclared_in_type(
1074                        &attr_ty.attr_type,
1075                        entity_types,
1076                        undeclared_types,
1077                    );
1078                }
1079            }
1080
1081            Type::Set {
1082                element_type: Some(element_type),
1083            } => Self::check_undeclared_in_type(element_type, entity_types, undeclared_types),
1084
1085            _ => (),
1086        }
1087    }
1088
1089    /// Lookup the ValidatorActionId object in the schema with the given name.
1090    pub fn get_action_id(&self, action_id: &EntityUID) -> Option<&ValidatorActionId> {
1091        self.action_ids.get(action_id)
1092    }
1093
1094    /// Lookup the ValidatorEntityType object in the schema with the given name.
1095    pub fn get_entity_type(&self, entity_type_id: &Name) -> Option<&ValidatorEntityType> {
1096        self.entity_types.get(entity_type_id)
1097    }
1098
1099    /// Return true when the entity_type_id corresponds to a valid entity type.
1100    pub(crate) fn is_known_action_id(&self, action_id: &EntityUID) -> bool {
1101        self.action_ids.contains_key(action_id)
1102    }
1103
1104    /// Return true when the entity_type_id corresponds to a valid entity type.
1105    pub(crate) fn is_known_entity_type(&self, entity_type: &Name) -> bool {
1106        self.entity_types.contains_key(entity_type)
1107    }
1108
1109    /// An iterator over the action ids in the schema.
1110    pub(crate) fn known_action_ids(&self) -> impl Iterator<Item = &EntityUID> {
1111        self.action_ids.keys()
1112    }
1113
1114    /// An iterator over the entity type names in the schema.
1115    pub(crate) fn known_entity_types(&self) -> impl Iterator<Item = &Name> {
1116        self.entity_types.keys()
1117    }
1118
1119    /// An iterator matching the entity Types to their Validator Types
1120    pub fn entity_types(&self) -> impl Iterator<Item = (&Name, &ValidatorEntityType)> {
1121        self.entity_types.iter()
1122    }
1123
1124    /// Get the validator entity equal to an EUID using the component for a head
1125    /// var kind.
1126    pub(crate) fn get_entity_eq<'a, H, K>(&self, var: H, euid: EntityUID) -> Option<K>
1127    where
1128        H: 'a + HeadVar<K>,
1129        K: 'a,
1130    {
1131        var.get_euid_component(euid)
1132    }
1133
1134    /// Get the validator entities that are in the descendants of an EUID using
1135    /// the component for a head var kind.
1136    pub(crate) fn get_entities_in<'a, H, K>(
1137        &'a self,
1138        var: H,
1139        euid: EntityUID,
1140    ) -> impl Iterator<Item = K> + 'a
1141    where
1142        H: 'a + HeadVar<K>,
1143        K: 'a + Clone,
1144    {
1145        var.get_descendants_if_present(self, euid.clone())
1146            .into_iter()
1147            .flatten()
1148            .map(Clone::clone)
1149            .chain(var.get_euid_component_if_present(self, euid).into_iter())
1150    }
1151
1152    /// Get the validator entities that are in the descendants of any of the
1153    /// entities in a set of EUID using the component for a head var kind.
1154    pub(crate) fn get_entities_in_set<'a, H, K>(
1155        &'a self,
1156        var: H,
1157        euids: impl IntoIterator<Item = EntityUID> + 'a,
1158    ) -> impl Iterator<Item = K> + 'a
1159    where
1160        H: 'a + HeadVar<K>,
1161        K: 'a + Clone,
1162    {
1163        euids
1164            .into_iter()
1165            .flat_map(move |e| self.get_entities_in(var, e))
1166    }
1167
1168    /// Since different Actions have different schemas for `Context`, you must
1169    /// specify the `Action` in order to get a `ContextSchema`.
1170    ///
1171    /// Returns `None` if the action is not in the schema.
1172    pub fn get_context_schema(
1173        &self,
1174        action: &EntityUID,
1175    ) -> Option<impl cedar_policy_core::entities::ContextSchema> {
1176        self.get_action_id(action).map(|action_id| {
1177            crate::types::Type::record_with_attributes(
1178                action_id
1179                    .context
1180                    .iter()
1181                    .map(|(k, v)| (k.clone(), v.clone())),
1182            )
1183        })
1184    }
1185}
1186
1187impl cedar_policy_core::entities::Schema for ValidatorSchema {
1188    fn attr_type(
1189        &self,
1190        entity_type: &cedar_policy_core::ast::EntityType,
1191        attr: &str,
1192    ) -> Option<cedar_policy_core::entities::SchemaType> {
1193        match entity_type {
1194            cedar_policy_core::ast::EntityType::Unspecified => None, // Unspecified entity does not have attributes
1195            cedar_policy_core::ast::EntityType::Concrete(name) => {
1196                let entity_type: &ValidatorEntityType = self.get_entity_type(name)?;
1197                let validator_type: &crate::types::Type = &entity_type.attr(attr)?.attr_type;
1198                let core_schema_type: cedar_policy_core::entities::SchemaType = validator_type
1199                    .clone()
1200                    .try_into()
1201                    .expect("failed to convert validator type into Core SchemaType");
1202                debug_assert!(validator_type.is_consistent_with(&core_schema_type));
1203                Some(core_schema_type)
1204            }
1205        }
1206    }
1207
1208    fn required_attrs<'s>(
1209        &'s self,
1210        entity_type: &cedar_policy_core::ast::EntityType,
1211    ) -> Box<dyn Iterator<Item = SmolStr> + 's> {
1212        match entity_type {
1213            cedar_policy_core::ast::EntityType::Unspecified => Box::new(std::iter::empty()), // Unspecified entity does not have attributes
1214            cedar_policy_core::ast::EntityType::Concrete(name) => {
1215                match self.get_entity_type(name) {
1216                    None => Box::new(std::iter::empty()),
1217                    Some(entity_type) => Box::new(
1218                        entity_type
1219                            .attributes
1220                            .iter()
1221                            .filter(|(_, ty)| ty.is_required)
1222                            .map(|(attr, _)| attr.clone()),
1223                    ),
1224                }
1225            }
1226        }
1227    }
1228}
1229
1230/// A `Type` contains all the information we need for a Core `ContextSchema`.
1231impl cedar_policy_core::entities::ContextSchema for crate::types::Type {
1232    fn context_type(&self) -> cedar_policy_core::entities::SchemaType {
1233        self.clone()
1234            .try_into()
1235            .expect("failed to convert validator type into Core SchemaType")
1236    }
1237}
1238
1239/// Contains entity type information for use by the validator. The contents of
1240/// the struct are the same as the schema entity type structure, but the
1241/// `member_of` relation is reversed to instead be `descendants`.
1242#[derive(Clone, Debug, Serialize)]
1243pub struct ValidatorEntityType {
1244    /// The name of the entity type.
1245    pub(crate) name: Name,
1246
1247    /// The set of entity types that can be members of this entity type. When
1248    /// this structure is initially constructed, the field will contain direct
1249    /// children, but it will be updated to contain the closure of all
1250    /// descendants before it is used in any validation.
1251    pub descendants: HashSet<Name>,
1252
1253    /// The attributes associated with this entity. Keys are the attribute
1254    /// identifiers while the values are the type of the attribute.
1255    pub(crate) attributes: Attributes,
1256}
1257
1258impl ValidatorEntityType {
1259    /// Get the type of the attribute with the given name, if it exists
1260    pub fn attr(&self, attr: &str) -> Option<&AttributeType> {
1261        self.attributes.get_attr(attr)
1262    }
1263
1264    /// An iterator over the attributes of this entity
1265    pub fn attributes(&self) -> impl Iterator<Item = (&SmolStr, &AttributeType)> {
1266        self.attributes.iter()
1267    }
1268}
1269
1270impl TCNode<Name> for ValidatorEntityType {
1271    fn get_key(&self) -> Name {
1272        self.name.clone()
1273    }
1274
1275    fn add_edge_to(&mut self, k: Name) {
1276        self.descendants.insert(k);
1277    }
1278
1279    fn out_edges(&self) -> Box<dyn Iterator<Item = &Name> + '_> {
1280        Box::new(self.descendants.iter())
1281    }
1282
1283    fn has_edge_to(&self, e: &Name) -> bool {
1284        self.descendants.contains(e)
1285    }
1286}
1287
1288/// Contains information about actions used by the validator.  The contents of
1289/// the struct are the same as the schema entity type structure, but the
1290/// `member_of` relation is reversed to instead be `descendants`.
1291#[derive(Clone, Debug, Serialize)]
1292pub struct ValidatorActionId {
1293    /// The name of the action.
1294    pub(crate) name: EntityUID,
1295
1296    /// The principals and resources that the action can be applied to.
1297    #[serde(rename = "appliesTo")]
1298    pub(crate) applies_to: ValidatorApplySpec,
1299
1300    /// The set of actions that can be members of this action. When this
1301    /// structure is initially constructed, the field will contain direct
1302    /// children, but it will be updated to contain the closure of all
1303    /// descendants before it is used in any validation.
1304    pub(crate) descendants: HashSet<EntityUID>,
1305
1306    /// The context attributes associated with this action. Keys are the context
1307    /// attribute identifiers while the values are the type of the attribute.
1308    pub(crate) context: Attributes,
1309
1310    /// The action attributes
1311    pub(crate) attributes: Attributes,
1312}
1313
1314impl ValidatorActionId {
1315    /// An iterator over the attributes of this action's required context
1316    pub fn context(&self) -> impl Iterator<Item = (&SmolStr, &AttributeType)> {
1317        self.context.iter()
1318    }
1319}
1320
1321impl TCNode<EntityUID> for ValidatorActionId {
1322    fn get_key(&self) -> EntityUID {
1323        self.name.clone()
1324    }
1325
1326    fn add_edge_to(&mut self, k: EntityUID) {
1327        self.descendants.insert(k);
1328    }
1329
1330    fn out_edges(&self) -> Box<dyn Iterator<Item = &EntityUID> + '_> {
1331        Box::new(self.descendants.iter())
1332    }
1333
1334    fn has_edge_to(&self, e: &EntityUID) -> bool {
1335        self.descendants.contains(e)
1336    }
1337}
1338
1339/// The principals and resources that an action can be applied to.
1340#[derive(Clone, Debug, Serialize)]
1341pub(crate) struct ValidatorApplySpec {
1342    /// The principal entity types the action can be applied to. This set may
1343    /// be a singleton set containing the unspecified entity type when the
1344    /// `principalTypes` list is omitted in the schema. A non-singleton set
1345    /// shouldn't contain the unspecified entity type, but validation will give
1346    /// the same success/failure result as when it is the only element of the
1347    /// set, perhaps with extra type errors.
1348    #[serde(rename = "principalApplySpec")]
1349    principal_apply_spec: HashSet<EntityType>,
1350
1351    /// The resource entity types the action can be applied to. See comments on
1352    /// `principal_apply_spec` about the unspecified entity type.
1353    #[serde(rename = "resourceApplySpec")]
1354    resource_apply_spec: HashSet<EntityType>,
1355}
1356
1357impl ValidatorApplySpec {
1358    /// Create an apply spec for an action that can only be applied to some
1359    /// specific entities.
1360    pub(crate) fn new(
1361        principal_apply_spec: HashSet<EntityType>,
1362        resource_apply_spec: HashSet<EntityType>,
1363    ) -> Self {
1364        Self {
1365            principal_apply_spec,
1366            resource_apply_spec,
1367        }
1368    }
1369
1370    /// Get the applicable principal types for this spec.
1371    pub(crate) fn applicable_principal_types(&self) -> impl Iterator<Item = &EntityType> {
1372        self.principal_apply_spec.iter()
1373    }
1374
1375    /// Get the applicable resource types for this spec.
1376    pub(crate) fn applicable_resource_types(&self) -> impl Iterator<Item = &EntityType> {
1377        self.resource_apply_spec.iter()
1378    }
1379}
1380
1381/// This trait configures what sort of entity (principals, actions, or resources)
1382/// are returned by the function `get_entities_satisfying_constraint`.
1383pub(crate) trait HeadVar<K>: Copy {
1384    /// For a validator, get the known entities for this sort of head variable.
1385    /// This is all entity types (for principals and resources), or actions ids
1386    /// (for actions) that appear in the service description.
1387    fn get_known_vars<'a>(
1388        &self,
1389        schema: &'a ValidatorSchema,
1390    ) -> Box<dyn Iterator<Item = &'a K> + 'a>;
1391
1392    /// Extract the relevant component of an entity uid. This is the entity type
1393    /// for principals and resources, and the entity id for actions.
1394    fn get_euid_component(&self, euid: EntityUID) -> Option<K>;
1395
1396    /// Extract the relevant component of an entity uid if the entity uid is in
1397    /// the schema. Otherwise return None.
1398    fn get_euid_component_if_present(&self, schema: &ValidatorSchema, euid: EntityUID)
1399        -> Option<K>;
1400
1401    /// Get and iterator containing the valid descendants of an entity, if that
1402    /// entity exists in the schema. Otherwise None.
1403    fn get_descendants_if_present<'a>(
1404        &self,
1405        schema: &'a ValidatorSchema,
1406        euid: EntityUID,
1407    ) -> Option<Box<dyn Iterator<Item = &'a K> + 'a>>;
1408}
1409
1410/// Used to have `get_entities_satisfying_constraint` return the
1411/// `EntityTypeNames` for either principals or resources satisfying the head
1412/// constraints.
1413#[derive(Debug, Clone, Copy)]
1414pub(crate) enum PrincipalOrResourceHeadVar {
1415    PrincipalOrResource,
1416}
1417
1418impl HeadVar<Name> for PrincipalOrResourceHeadVar {
1419    fn get_known_vars<'a>(
1420        &self,
1421        schema: &'a ValidatorSchema,
1422    ) -> Box<dyn Iterator<Item = &'a Name> + 'a> {
1423        Box::new(schema.known_entity_types())
1424    }
1425
1426    fn get_euid_component(&self, euid: EntityUID) -> Option<Name> {
1427        let (ty, _) = euid.components();
1428        match ty {
1429            EntityType::Unspecified => None,
1430            EntityType::Concrete(name) => Some(name),
1431        }
1432    }
1433
1434    fn get_euid_component_if_present(
1435        &self,
1436        schema: &ValidatorSchema,
1437        euid: EntityUID,
1438    ) -> Option<Name> {
1439        let euid_component = self.get_euid_component(euid)?;
1440        if schema.is_known_entity_type(&euid_component) {
1441            Some(euid_component)
1442        } else {
1443            None
1444        }
1445    }
1446
1447    fn get_descendants_if_present<'a>(
1448        &self,
1449        schema: &'a ValidatorSchema,
1450        euid: EntityUID,
1451    ) -> Option<Box<dyn Iterator<Item = &'a Name> + 'a>> {
1452        let euid_component = self.get_euid_component(euid)?;
1453        match schema.get_entity_type(&euid_component) {
1454            Some(entity_type) => Some(Box::new(entity_type.descendants.iter())),
1455            None => None,
1456        }
1457    }
1458}
1459
1460/// Used to have `get_entities_satisfying_constraint` return the
1461/// `ActionIdNames` for actions satisfying the head constraints
1462#[derive(Debug, Clone, Copy)]
1463pub(crate) enum ActionHeadVar {
1464    Action,
1465}
1466
1467impl HeadVar<EntityUID> for ActionHeadVar {
1468    fn get_known_vars<'a>(
1469        &self,
1470        schema: &'a ValidatorSchema,
1471    ) -> Box<dyn Iterator<Item = &'a EntityUID> + 'a> {
1472        Box::new(schema.known_action_ids())
1473    }
1474
1475    fn get_euid_component(&self, euid: EntityUID) -> Option<EntityUID> {
1476        Some(euid)
1477    }
1478
1479    fn get_euid_component_if_present(
1480        &self,
1481        schema: &ValidatorSchema,
1482        euid: EntityUID,
1483    ) -> Option<EntityUID> {
1484        let euid_component = self.get_euid_component(euid)?;
1485        if schema.is_known_action_id(&euid_component) {
1486            Some(euid_component)
1487        } else {
1488            None
1489        }
1490    }
1491
1492    fn get_descendants_if_present<'a>(
1493        &self,
1494        schema: &'a ValidatorSchema,
1495        euid: EntityUID,
1496    ) -> Option<Box<dyn Iterator<Item = &'a EntityUID> + 'a>> {
1497        let euid_component = self.get_euid_component(euid)?;
1498        match schema.get_action_id(&euid_component) {
1499            Some(action_id) => Some(Box::new(action_id.descendants.iter())),
1500            None => None,
1501        }
1502    }
1503}
1504
1505/// Used to write a schema implicitly overriding the default handling of action
1506/// groups.
1507#[derive(Debug, Clone, Deserialize)]
1508#[serde(transparent)]
1509pub(crate) struct NamespaceDefinitionWithActionAttributes(pub(crate) NamespaceDefinition);
1510
1511impl TryInto<ValidatorSchema> for NamespaceDefinitionWithActionAttributes {
1512    type Error = SchemaError;
1513
1514    fn try_into(self) -> Result<ValidatorSchema> {
1515        ValidatorSchema::from_schema_fragments([ValidatorSchemaFragment::from_namespaces([
1516            ValidatorNamespaceDef::from_namespace_definition(
1517                None,
1518                self.0,
1519                crate::ActionBehavior::PermitAttributes,
1520            )?,
1521        ])])
1522    }
1523}
1524
1525#[cfg(test)]
1526mod test {
1527    use std::{collections::BTreeMap, str::FromStr};
1528
1529    use crate::types::Type;
1530
1531    use serde_json::json;
1532
1533    use super::*;
1534
1535    // Well-formed schema
1536    #[test]
1537    fn test_from_schema_file() {
1538        let src = json!(
1539        {
1540            "entityTypes": {
1541                "User": {
1542                    "memberOfTypes": [ "Group" ]
1543                },
1544                "Group": {
1545                    "memberOfTypes": []
1546                },
1547                "Photo": {
1548                    "memberOfTypes": [ "Album" ]
1549                },
1550                "Album": {
1551                    "memberOfTypes": []
1552                }
1553            },
1554            "actions": {
1555                "view_photo": {
1556                    "appliesTo": {
1557                        "principalTypes": ["User", "Group"],
1558                        "resourceTypes": ["Photo"]
1559                    }
1560                }
1561            }
1562        });
1563        let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
1564        let schema: Result<ValidatorSchema> = schema_file.try_into();
1565        assert!(schema.is_ok());
1566    }
1567
1568    // Duplicate entity "Photo"
1569    #[test]
1570    fn test_from_schema_file_duplicate_entity() {
1571        // Test written using `from_str` instead of `from_value` because the
1572        // `json!` macro silently ignores duplicate map keys.
1573        let src = r#"
1574        {"": {
1575            "entityTypes": {
1576                "User": {
1577                    "memberOfTypes": [ "Group" ]
1578                },
1579                "Group": {
1580                    "memberOfTypes": []
1581                },
1582                "Photo": {
1583                    "memberOfTypes": [ "Album" ]
1584                },
1585                "Photo": {
1586                    "memberOfTypes": []
1587                }
1588            },
1589            "actions": {
1590                "view_photo": {
1591                    "memberOf": [],
1592                    "appliesTo": {
1593                        "principalTypes": ["User", "Group"],
1594                        "resourceTypes": ["Photo"]
1595                    }
1596                }
1597            }
1598        }}"#;
1599
1600        match ValidatorSchema::from_str(src) {
1601            Err(SchemaError::ParseFileFormat(_)) => (),
1602            _ => panic!("Expected serde error due to duplicate entity type."),
1603        }
1604    }
1605
1606    // Duplicate action "view_photo"
1607    #[test]
1608    fn test_from_schema_file_duplicate_action() {
1609        // Test written using `from_str` instead of `from_value` because the
1610        // `json!` macro silently ignores duplicate map keys.
1611        let src = r#"
1612        {"": {
1613            "entityTypes": {
1614                "User": {
1615                    "memberOfTypes": [ "Group" ]
1616                },
1617                "Group": {
1618                    "memberOfTypes": []
1619                },
1620                "Photo": {
1621                    "memberOfTypes": []
1622                }
1623            },
1624            "actions": {
1625                "view_photo": {
1626                    "memberOf": [],
1627                    "appliesTo": {
1628                        "principalTypes": ["User", "Group"],
1629                        "resourceTypes": ["Photo"]
1630                    }
1631                },
1632                "view_photo": { }
1633            }
1634        }"#;
1635        match ValidatorSchema::from_str(src) {
1636            Err(SchemaError::ParseFileFormat(_)) => (),
1637            _ => panic!("Expected serde error due to duplicate action type."),
1638        }
1639    }
1640
1641    // Undefined entity types "Grop", "Usr", "Phoot"
1642    #[test]
1643    fn test_from_schema_file_undefined_entities() {
1644        let src = json!(
1645        {
1646            "entityTypes": {
1647                "User": {
1648                    "memberOfTypes": [ "Grop" ]
1649                },
1650                "Group": {
1651                    "memberOfTypes": []
1652                },
1653                "Photo": {
1654                    "memberOfTypes": []
1655                }
1656            },
1657            "actions": {
1658                "view_photo": {
1659                    "appliesTo": {
1660                        "principalTypes": ["Usr", "Group"],
1661                        "resourceTypes": ["Phoot"]
1662                    }
1663                }
1664            }
1665        });
1666        let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
1667        let schema: Result<ValidatorSchema> = schema_file.try_into();
1668        match schema {
1669            Ok(_) => panic!("from_schema_file should have failed"),
1670            Err(SchemaError::UndeclaredEntityTypes(v)) => {
1671                assert_eq!(v.len(), 3)
1672            }
1673            _ => panic!("Unexpected error from from_schema_file"),
1674        }
1675    }
1676
1677    #[test]
1678    fn undefined_entity_namespace_member_of() {
1679        let src = json!(
1680        {"Foo": {
1681            "entityTypes": {
1682                "User": {
1683                    "memberOfTypes": [ "Foo::Group", "Bar::Group" ]
1684                },
1685                "Group": { }
1686            },
1687            "actions": {}
1688        }});
1689        let schema_file: SchemaFragment = serde_json::from_value(src).expect("Parse Error");
1690        let schema: Result<ValidatorSchema> = schema_file.try_into();
1691        match schema {
1692            Ok(_) => panic!("try_into should have failed"),
1693            Err(SchemaError::UndeclaredEntityTypes(v)) => {
1694                assert_eq!(v, HashSet::from(["Bar::Group".to_string()]))
1695            }
1696            _ => panic!("Unexpected error from try_into"),
1697        }
1698    }
1699
1700    #[test]
1701    fn undefined_entity_namespace_applies_to() {
1702        let src = json!(
1703        {"Foo": {
1704            "entityTypes": { "User": { }, "Photo": { } },
1705            "actions": {
1706                "view_photo": {
1707                    "appliesTo": {
1708                        "principalTypes": ["Foo::User", "Bar::User"],
1709                        "resourceTypes": ["Photo", "Bar::Photo"],
1710                    }
1711                }
1712            }
1713        }});
1714        let schema_file: SchemaFragment = serde_json::from_value(src).expect("Parse Error");
1715        let schema: Result<ValidatorSchema> = schema_file.try_into();
1716        match schema {
1717            Ok(_) => panic!("try_into should have failed"),
1718            Err(SchemaError::UndeclaredEntityTypes(v)) => {
1719                assert_eq!(
1720                    v,
1721                    HashSet::from(["Bar::Photo".to_string(), "Bar::User".to_string()])
1722                )
1723            }
1724            _ => panic!("Unexpected error from try_into"),
1725        }
1726    }
1727
1728    // Undefined action "photo_actions"
1729    #[test]
1730    fn test_from_schema_file_undefined_action() {
1731        let src = json!(
1732        {
1733            "entityTypes": {
1734                "User": {
1735                    "memberOfTypes": [ "Group" ]
1736                },
1737                "Group": {
1738                    "memberOfTypes": []
1739                },
1740                "Photo": {
1741                    "memberOfTypes": []
1742                }
1743            },
1744            "actions": {
1745                "view_photo": {
1746                    "memberOf": [ {"id": "photo_action"} ],
1747                    "appliesTo": {
1748                        "principalTypes": ["User", "Group"],
1749                        "resourceTypes": ["Photo"]
1750                    }
1751                }
1752            }
1753        });
1754        let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
1755        let schema: Result<ValidatorSchema> = schema_file.try_into();
1756        match schema {
1757            Ok(_) => panic!("from_schema_file should have failed"),
1758            Err(SchemaError::UndeclaredActions(v)) => assert_eq!(v.len(), 1),
1759            _ => panic!("Unexpected error from from_schema_file"),
1760        }
1761    }
1762
1763    // Trivial cycle in action hierarchy
1764    // view_photo -> view_photo
1765    #[test]
1766    fn test_from_schema_file_action_cycle1() {
1767        let src = json!(
1768        {
1769            "entityTypes": {},
1770            "actions": {
1771                "view_photo": {
1772                    "memberOf": [ {"id": "view_photo"} ]
1773                }
1774            }
1775        });
1776        let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
1777        let schema: Result<ValidatorSchema> = schema_file.try_into();
1778        match schema {
1779            Ok(_) => panic!("from_schema_file should have failed"),
1780            Err(SchemaError::CycleInActionHierarchy) => (), // expected result
1781            e => panic!("Unexpected error from from_schema_file: {:?}", e),
1782        }
1783    }
1784
1785    // Slightly more complex cycle in action hierarchy
1786    // view_photo -> edit_photo -> delete_photo -> view_photo
1787    #[test]
1788    fn test_from_schema_file_action_cycle2() {
1789        let src = json!(
1790        {
1791            "entityTypes": {},
1792            "actions": {
1793                "view_photo": {
1794                    "memberOf": [ {"id": "edit_photo"} ]
1795                },
1796                "edit_photo": {
1797                    "memberOf": [ {"id": "delete_photo"} ]
1798                },
1799                "delete_photo": {
1800                    "memberOf": [ {"id": "view_photo"} ]
1801                },
1802                "other_action": {
1803                    "memberOf": [ {"id": "edit_photo"} ]
1804                }
1805            }
1806        });
1807        let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
1808        let schema: Result<ValidatorSchema> = schema_file.try_into();
1809        match schema {
1810            Ok(x) => {
1811                println!("{:?}", x);
1812                panic!("from_schema_file should have failed");
1813            }
1814            Err(SchemaError::CycleInActionHierarchy) => (), // expected result
1815            e => panic!("Unexpected error from from_schema_file: {:?}", e),
1816        }
1817    }
1818
1819    #[test]
1820    fn namespaced_schema() {
1821        let src = r#"
1822        { "N::S": {
1823            "entityTypes": {
1824                "User": {},
1825                "Photo": {}
1826            },
1827            "actions": {
1828                "view_photo": {
1829                    "appliesTo": {
1830                        "principalTypes": ["User"],
1831                        "resourceTypes": ["Photo"]
1832                    }
1833                }
1834            }
1835        } }
1836        "#;
1837        let schema_file: SchemaFragment = serde_json::from_str(src).expect("Parse Error");
1838        let schema: ValidatorSchema = schema_file
1839            .try_into()
1840            .expect("Namespaced schema failed to convert.");
1841        dbg!(&schema);
1842        let user_entity_type = &"N::S::User"
1843            .parse()
1844            .expect("Namespaced entity type should have parsed");
1845        let photo_entity_type = &"N::S::Photo"
1846            .parse()
1847            .expect("Namespaced entity type should have parsed");
1848        assert!(
1849            schema.entity_types.contains_key(user_entity_type),
1850            "Expected and entity type User."
1851        );
1852        assert!(
1853            schema.entity_types.contains_key(photo_entity_type),
1854            "Expected an entity type Photo."
1855        );
1856        assert_eq!(
1857            schema.entity_types.len(),
1858            2,
1859            "Expected exactly 2 entity types."
1860        );
1861        assert!(
1862            schema.action_ids.contains_key(
1863                &"N::S::Action::\"view_photo\""
1864                    .parse()
1865                    .expect("Namespaced action should have parsed")
1866            ),
1867            "Expected an action \"view_photo\"."
1868        );
1869        assert_eq!(schema.action_ids.len(), 1, "Expected exactly 1 action.");
1870
1871        let apply_spec = &schema
1872            .action_ids
1873            .values()
1874            .next()
1875            .expect("Expected Action")
1876            .applies_to;
1877        assert_eq!(
1878            apply_spec.applicable_principal_types().collect::<Vec<_>>(),
1879            vec![&EntityType::Concrete(user_entity_type.clone())]
1880        );
1881        assert_eq!(
1882            apply_spec.applicable_resource_types().collect::<Vec<_>>(),
1883            vec![&EntityType::Concrete(photo_entity_type.clone())]
1884        );
1885    }
1886
1887    #[test]
1888    fn cant_use_namespace_in_entity_type() {
1889        let src = r#"
1890        {
1891            "entityTypes": { "NS::User": {} },
1892            "actions": {}
1893        }
1894        "#;
1895        let schema_file: NamespaceDefinition = serde_json::from_str(src).expect("Parse Error");
1896        assert!(
1897            matches!(TryInto::<ValidatorSchema>::try_into(schema_file), Err(SchemaError::EntityTypeParseError(_))),
1898            "Expected that namespace in the entity type NS::User would cause a EntityType parse error.");
1899    }
1900
1901    #[test]
1902    fn entity_attribute_entity_type_with_namespace() {
1903        let schema_json: SchemaFragment = serde_json::from_str(
1904            r#"
1905            {"A::B": {
1906                "entityTypes": {
1907                    "Foo": {
1908                        "shape": {
1909                            "type": "Record",
1910                            "attributes": {
1911                                "name": { "type": "Entity", "name": "C::D::Foo" }
1912                            }
1913                        }
1914                    }
1915                },
1916                "actions": {}
1917              }}
1918            "#,
1919        )
1920        .expect("Expected valid schema");
1921
1922        let schema: Result<ValidatorSchema> = schema_json.try_into();
1923        match schema {
1924            Err(SchemaError::UndeclaredEntityTypes(tys)) => {
1925                assert_eq!(tys, HashSet::from(["C::D::Foo".to_string()]))
1926            }
1927            _ => panic!("Schema construction should have failed due to undeclared entity type."),
1928        }
1929    }
1930
1931    #[test]
1932    fn entity_attribute_entity_type_with_declared_namespace() {
1933        let schema_json: SchemaFragment = serde_json::from_str(
1934            r#"
1935            {"A::B": {
1936                "entityTypes": {
1937                    "Foo": {
1938                        "shape": {
1939                            "type": "Record",
1940                            "attributes": {
1941                                "name": { "type": "Entity", "name": "A::B::Foo" }
1942                            }
1943                        }
1944                    }
1945                },
1946                "actions": {}
1947              }}
1948            "#,
1949        )
1950        .expect("Expected valid schema");
1951
1952        let schema: ValidatorSchema = schema_json
1953            .try_into()
1954            .expect("Expected schema to construct without error.");
1955
1956        let foo_name: Name = "A::B::Foo".parse().expect("Expected entity type name");
1957        let foo_type = schema
1958            .entity_types
1959            .get(&foo_name)
1960            .expect("Expected to find entity");
1961        let name_type = foo_type
1962            .attr("name")
1963            .expect("Expected attribute name")
1964            .attr_type
1965            .clone();
1966        let expected_name_type = Type::named_entity_reference(foo_name);
1967        assert_eq!(name_type, expected_name_type);
1968    }
1969
1970    #[test]
1971    fn cannot_declare_action_type_when_prohibited() {
1972        let schema_json: NamespaceDefinition = serde_json::from_str(
1973            r#"
1974            {
1975                "entityTypes": { "Action": {} },
1976                "actions": {}
1977              }
1978            "#,
1979        )
1980        .expect("Expected valid schema");
1981
1982        let schema: Result<ValidatorSchema> = schema_json.try_into();
1983        assert!(matches!(schema, Err(SchemaError::ActionEntityTypeDeclared)));
1984    }
1985
1986    #[test]
1987    fn can_declare_other_type_when_action_type_prohibited() {
1988        let schema_json: NamespaceDefinition = serde_json::from_str(
1989            r#"
1990            {
1991                "entityTypes": { "Foo": { } },
1992                "actions": {}
1993              }
1994            "#,
1995        )
1996        .expect("Expected valid schema");
1997
1998        TryInto::<ValidatorSchema>::try_into(schema_json).expect("Did not expect any errors.");
1999    }
2000
2001    #[test]
2002    fn cannot_declare_action_in_group_when_prohibited() {
2003        let schema_json: SchemaFragment = serde_json::from_str(
2004            r#"
2005            {"": {
2006                "entityTypes": {},
2007                "actions": {
2008                    "universe": { },
2009                    "view_photo": {
2010                        "attributes": {"id": "universe"}
2011                    },
2012                    "edit_photo": {
2013                        "attributes": {"id": "universe"}
2014                    },
2015                    "delete_photo": {
2016                        "attributes": {"id": "universe"}
2017                    }
2018                }
2019              }}
2020            "#,
2021        )
2022        .expect("Expected valid schema");
2023
2024        let schema = ValidatorSchemaFragment::from_schema_fragment(
2025            schema_json,
2026            ActionBehavior::ProhibitAttributes,
2027        );
2028        match schema {
2029            Err(SchemaError::ActionEntityAttributes(actions)) => {
2030                assert_eq!(
2031                    actions.into_iter().collect::<HashSet<_>>(),
2032                    HashSet::from([
2033                        "view_photo".to_string(),
2034                        "edit_photo".to_string(),
2035                        "delete_photo".to_string(),
2036                    ])
2037                )
2038            }
2039            _ => panic!("Did not see expected error."),
2040        }
2041    }
2042
2043    #[test]
2044    fn test_entity_type_no_namespace() {
2045        let src = json!({"type": "Entity", "name": "Foo"});
2046        let schema_ty: SchemaType = serde_json::from_value(src).expect("Parse Error");
2047        assert_eq!(
2048            schema_ty,
2049            SchemaType::Type(SchemaTypeVariant::Entity { name: "Foo".into() })
2050        );
2051        let ty: Type = ValidatorNamespaceDef::try_schema_type_into_validator_type(
2052            &parse_namespace("NS").expect("Expected namespace."),
2053            schema_ty,
2054        )
2055        .expect("Error converting schema type to type.")
2056        .resolve_type_defs(&HashMap::new())
2057        .unwrap();
2058        assert_eq!(ty, Type::named_entity_reference_from_str("NS::Foo"));
2059    }
2060
2061    #[test]
2062    fn test_entity_type_namespace() {
2063        let src = json!({"type": "Entity", "name": "NS::Foo"});
2064        let schema_ty: SchemaType = serde_json::from_value(src).expect("Parse Error");
2065        assert_eq!(
2066            schema_ty,
2067            SchemaType::Type(SchemaTypeVariant::Entity {
2068                name: "NS::Foo".into()
2069            })
2070        );
2071        let ty: Type = ValidatorNamespaceDef::try_schema_type_into_validator_type(
2072            &parse_namespace("NS").expect("Expected namespace."),
2073            schema_ty,
2074        )
2075        .expect("Error converting schema type to type.")
2076        .resolve_type_defs(&HashMap::new())
2077        .unwrap();
2078        assert_eq!(ty, Type::named_entity_reference_from_str("NS::Foo"));
2079    }
2080
2081    #[test]
2082    fn test_entity_type_namespace_parse_error() {
2083        let src = json!({"type": "Entity", "name": "::Foo"});
2084        let schema_ty: SchemaType = serde_json::from_value(src).expect("Parse Error");
2085        assert_eq!(
2086            schema_ty,
2087            SchemaType::Type(SchemaTypeVariant::Entity {
2088                name: "::Foo".into()
2089            })
2090        );
2091        match ValidatorNamespaceDef::try_schema_type_into_validator_type(
2092            &parse_namespace("NS").expect("Expected namespace."),
2093            schema_ty,
2094        ) {
2095            Err(SchemaError::EntityTypeParseError(_)) => (),
2096            _ => panic!("Did not see expected EntityTypeParseError."),
2097        }
2098    }
2099
2100    #[test]
2101    fn schema_type_record_is_validator_type_record() {
2102        let src = json!({"type": "Record", "attributes": {}});
2103        let schema_ty: SchemaType = serde_json::from_value(src).expect("Parse Error");
2104        assert_eq!(
2105            schema_ty,
2106            SchemaType::Type(SchemaTypeVariant::Record {
2107                attributes: BTreeMap::new(),
2108                additional_attributes: false,
2109            }),
2110        );
2111        let ty: Type =
2112            ValidatorNamespaceDef::try_schema_type_into_validator_type(&Vec::new(), schema_ty)
2113                .expect("Error converting schema type to type.")
2114                .resolve_type_defs(&HashMap::new())
2115                .unwrap();
2116        assert_eq!(ty, Type::record_with_attributes(None));
2117    }
2118
2119    #[test]
2120    fn get_namespaces() {
2121        let fragment: SchemaFragment = serde_json::from_value(json!({
2122            "Foo::Bar::Baz": {
2123                "entityTypes": {},
2124                "actions": {}
2125            },
2126            "Foo": {
2127                "entityTypes": {},
2128                "actions": {}
2129            },
2130            "Bar": {
2131                "entityTypes": {},
2132                "actions": {}
2133            },
2134        }))
2135        .unwrap();
2136
2137        let schema_fragment: ValidatorSchemaFragment = fragment.try_into().unwrap();
2138        assert_eq!(
2139            schema_fragment
2140                .0
2141                .iter()
2142                .map(|f| f.namespace())
2143                .collect::<HashSet<_>>(),
2144            HashSet::from([
2145                &Some("Foo::Bar::Baz".parse().unwrap()),
2146                &Some("Foo".parse().unwrap()),
2147                &Some("Bar".parse().unwrap())
2148            ])
2149        );
2150    }
2151
2152    #[test]
2153    fn schema_no_fragments() {
2154        let schema = ValidatorSchema::from_schema_fragments([]).unwrap();
2155        assert!(schema.entity_types.is_empty());
2156        assert!(schema.action_ids.is_empty());
2157    }
2158
2159    #[test]
2160    fn same_action_different_namespace() {
2161        let fragment: SchemaFragment = serde_json::from_value(json!({
2162            "Foo::Bar": {
2163                "entityTypes": {},
2164                "actions": {
2165                    "Baz": {}
2166                }
2167            },
2168            "Bar::Foo": {
2169                "entityTypes": {},
2170                "actions": {
2171                    "Baz": { }
2172                }
2173            },
2174            "Biz": {
2175                "entityTypes": {},
2176                "actions": {
2177                    "Baz": { }
2178                }
2179            }
2180        }))
2181        .unwrap();
2182
2183        let schema: ValidatorSchema = fragment.try_into().unwrap();
2184        assert!(schema
2185            .get_action_id(&"Foo::Bar::Action::\"Baz\"".parse().unwrap())
2186            .is_some());
2187        assert!(schema
2188            .get_action_id(&"Bar::Foo::Action::\"Baz\"".parse().unwrap())
2189            .is_some());
2190        assert!(schema
2191            .get_action_id(&"Biz::Action::\"Baz\"".parse().unwrap())
2192            .is_some());
2193    }
2194
2195    #[test]
2196    fn same_type_different_namespace() {
2197        let fragment: SchemaFragment = serde_json::from_value(json!({
2198            "Foo::Bar": {
2199                "entityTypes": {"Baz" : {}},
2200                "actions": { }
2201            },
2202            "Bar::Foo": {
2203                "entityTypes": {"Baz" : {}},
2204                "actions": { }
2205            },
2206            "Biz": {
2207                "entityTypes": {"Baz" : {}},
2208                "actions": { }
2209            }
2210        }))
2211        .unwrap();
2212        let schema: ValidatorSchema = fragment.try_into().unwrap();
2213
2214        assert!(schema
2215            .get_entity_type(&"Foo::Bar::Baz".parse().unwrap())
2216            .is_some());
2217        assert!(schema
2218            .get_entity_type(&"Bar::Foo::Baz".parse().unwrap())
2219            .is_some());
2220        assert!(schema
2221            .get_entity_type(&"Biz::Baz".parse().unwrap())
2222            .is_some());
2223    }
2224
2225    #[test]
2226    fn member_of_different_namespace() {
2227        let fragment: SchemaFragment = serde_json::from_value(json!({
2228            "Bar": {
2229                "entityTypes": {
2230                    "Baz": {
2231                        "memberOfTypes": ["Foo::Buz"]
2232                    }
2233                },
2234                "actions": {}
2235            },
2236            "Foo": {
2237                "entityTypes": { "Buz": {} },
2238                "actions": { }
2239            }
2240        }))
2241        .unwrap();
2242        let schema: ValidatorSchema = fragment.try_into().unwrap();
2243
2244        let buz = schema
2245            .get_entity_type(&"Foo::Buz".parse().unwrap())
2246            .unwrap();
2247        assert_eq!(
2248            buz.descendants,
2249            HashSet::from(["Bar::Baz".parse().unwrap()])
2250        );
2251    }
2252
2253    #[test]
2254    fn attribute_different_namespace() {
2255        let fragment: SchemaFragment = serde_json::from_value(json!({
2256            "Bar": {
2257                "entityTypes": {
2258                    "Baz": {
2259                        "shape": {
2260                            "type": "Record",
2261                            "attributes": {
2262                                "fiz": {
2263                                    "type": "Entity",
2264                                    "name": "Foo::Buz"
2265                                }
2266                            }
2267                        }
2268                    }
2269                },
2270                "actions": {}
2271            },
2272            "Foo": {
2273                "entityTypes": { "Buz": {} },
2274                "actions": { }
2275            }
2276        }))
2277        .unwrap();
2278
2279        let schema: ValidatorSchema = fragment.try_into().unwrap();
2280        let baz = schema
2281            .get_entity_type(&"Bar::Baz".parse().unwrap())
2282            .unwrap();
2283        assert_eq!(
2284            baz.attr("fiz").unwrap().attr_type,
2285            Type::named_entity_reference_from_str("Foo::Buz"),
2286        );
2287    }
2288
2289    #[test]
2290    fn applies_to_different_namespace() {
2291        let fragment: SchemaFragment = serde_json::from_value(json!({
2292            "Foo::Bar": {
2293                "entityTypes": { },
2294                "actions": {
2295                    "Baz": {
2296                        "appliesTo": {
2297                            "principalTypes": [ "Fiz::Buz" ],
2298                            "resourceTypes": [ "Fiz::Baz" ],
2299                        }
2300                    }
2301                }
2302            },
2303            "Fiz": {
2304                "entityTypes": {
2305                    "Buz": {},
2306                    "Baz": {}
2307                },
2308                "actions": { }
2309            }
2310        }))
2311        .unwrap();
2312        let schema: ValidatorSchema = fragment.try_into().unwrap();
2313
2314        let baz = schema
2315            .get_action_id(&"Foo::Bar::Action::\"Baz\"".parse().unwrap())
2316            .unwrap();
2317        assert_eq!(
2318            baz.applies_to
2319                .applicable_principal_types()
2320                .collect::<HashSet<_>>(),
2321            HashSet::from([&EntityType::Concrete("Fiz::Buz".parse().unwrap())])
2322        );
2323        assert_eq!(
2324            baz.applies_to
2325                .applicable_resource_types()
2326                .collect::<HashSet<_>>(),
2327            HashSet::from([&EntityType::Concrete("Fiz::Baz".parse().unwrap())])
2328        );
2329    }
2330
2331    #[test]
2332    fn simple_defined_type() {
2333        let fragment: SchemaFragment = serde_json::from_value(json!({
2334            "": {
2335                "commonTypes": {
2336                    "MyLong": {"type": "Long"}
2337                },
2338                "entityTypes": {
2339                    "User": {
2340                        "shape": {
2341                            "type": "Record",
2342                            "attributes": {
2343                                "a": {"type": "MyLong"}
2344                            }
2345                        }
2346                    }
2347                },
2348                "actions": {}
2349            }
2350        }))
2351        .unwrap();
2352        let schema: ValidatorSchema = fragment.try_into().unwrap();
2353        assert_eq!(
2354            schema.entity_types.iter().next().unwrap().1.attributes,
2355            Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
2356        );
2357    }
2358
2359    #[test]
2360    fn defined_record_as_attrs() {
2361        let fragment: SchemaFragment = serde_json::from_value(json!({
2362            "": {
2363                "commonTypes": {
2364                    "MyRecord": {
2365                        "type": "Record",
2366                        "attributes":  {
2367                            "a": {"type": "Long"}
2368                        }
2369                    }
2370                },
2371                "entityTypes": {
2372                    "User": { "shape": { "type": "MyRecord", } }
2373                },
2374                "actions": {}
2375            }
2376        }))
2377        .unwrap();
2378        let schema: ValidatorSchema = fragment.try_into().unwrap();
2379        assert_eq!(
2380            schema.entity_types.iter().next().unwrap().1.attributes,
2381            Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
2382        );
2383    }
2384
2385    #[test]
2386    fn cross_namespace_type() {
2387        let fragment: SchemaFragment = serde_json::from_value(json!({
2388            "A": {
2389                "commonTypes": {
2390                    "MyLong": {"type": "Long"}
2391                },
2392                "entityTypes": { },
2393                "actions": {}
2394            },
2395            "B": {
2396                "entityTypes": {
2397                    "User": {
2398                        "shape": {
2399                            "type": "Record",
2400                            "attributes": {
2401                                "a": {"type": "A::MyLong"}
2402                            }
2403                        }
2404                    }
2405                },
2406                "actions": {}
2407            }
2408        }))
2409        .unwrap();
2410        let schema: ValidatorSchema = fragment.try_into().unwrap();
2411        assert_eq!(
2412            schema.entity_types.iter().next().unwrap().1.attributes,
2413            Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
2414        );
2415    }
2416
2417    #[test]
2418    fn cross_fragment_type() {
2419        let fragment1: ValidatorSchemaFragment = serde_json::from_value::<SchemaFragment>(json!({
2420            "A": {
2421                "commonTypes": {
2422                    "MyLong": {"type": "Long"}
2423                },
2424                "entityTypes": { },
2425                "actions": {}
2426            }
2427        }))
2428        .unwrap()
2429        .try_into()
2430        .unwrap();
2431        let fragment2: ValidatorSchemaFragment = serde_json::from_value::<SchemaFragment>(json!({
2432            "A": {
2433                "entityTypes": {
2434                    "User": {
2435                        "shape": {
2436                            "type": "Record",
2437                            "attributes": {
2438                                "a": {"type": "MyLong"}
2439                            }
2440                        }
2441                    }
2442                },
2443                "actions": {}
2444            }
2445        }))
2446        .unwrap()
2447        .try_into()
2448        .unwrap();
2449        let schema = ValidatorSchema::from_schema_fragments([fragment1, fragment2]).unwrap();
2450
2451        assert_eq!(
2452            schema.entity_types.iter().next().unwrap().1.attributes,
2453            Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
2454        );
2455    }
2456
2457    #[test]
2458    #[should_panic]
2459    fn cross_fragment_duplicate_type() {
2460        let fragment1: ValidatorSchemaFragment = serde_json::from_value::<SchemaFragment>(json!({
2461            "A": {
2462                "commonTypes": {
2463                    "MyLong": {"type": "Long"}
2464                },
2465                "entityTypes": {},
2466                "actions": {}
2467            }
2468        }))
2469        .unwrap()
2470        .try_into()
2471        .unwrap();
2472        let fragment2: ValidatorSchemaFragment = serde_json::from_value::<SchemaFragment>(json!({
2473            "A": {
2474                "commonTypes": {
2475                    "MyLong": {"type": "Long"}
2476                },
2477                "entityTypes": {},
2478                "actions": {}
2479            }
2480        }))
2481        .unwrap()
2482        .try_into()
2483        .unwrap();
2484        let schema = ValidatorSchema::from_schema_fragments([fragment1, fragment2]).unwrap();
2485
2486        assert_eq!(
2487            schema.entity_types.iter().next().unwrap().1.attributes,
2488            Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
2489        );
2490    }
2491
2492    #[test]
2493    fn undeclared_type_in_attr() {
2494        let fragment: SchemaFragment = serde_json::from_value(json!({
2495            "": {
2496                "commonTypes": { },
2497                "entityTypes": {
2498                    "User": {
2499                        "shape": {
2500                            "type": "Record",
2501                            "attributes": {
2502                                "a": {"type": "MyLong"}
2503                            }
2504                        }
2505                    }
2506                },
2507                "actions": {}
2508            }
2509        }))
2510        .unwrap();
2511        match TryInto::<ValidatorSchema>::try_into(fragment) {
2512            Err(SchemaError::UndeclaredCommonType(_)) => (),
2513            s => panic!(
2514                "Expected Err(SchemaError::UndeclaredCommonType), got {:?}",
2515                s
2516            ),
2517        }
2518    }
2519
2520    #[test]
2521    fn undeclared_type_in_type_def() {
2522        let fragment: SchemaFragment = serde_json::from_value(json!({
2523            "": {
2524                "commonTypes": {
2525                    "a": { "type": "b" }
2526                },
2527                "entityTypes": { },
2528                "actions": {}
2529            }
2530        }))
2531        .unwrap();
2532        match TryInto::<ValidatorSchema>::try_into(fragment) {
2533            Err(SchemaError::UndeclaredCommonType(_)) => (),
2534            s => panic!(
2535                "Expected Err(SchemaError::UndeclaredCommonType), got {:?}",
2536                s
2537            ),
2538        }
2539    }
2540
2541    #[test]
2542    fn shape_not_record() {
2543        let fragment: SchemaFragment = serde_json::from_value(json!({
2544            "": {
2545                "commonTypes": {
2546                    "MyLong": { "type": "Long" }
2547                },
2548                "entityTypes": {
2549                    "User": {
2550                        "shape": { "type": "MyLong" }
2551                    }
2552                },
2553                "actions": {}
2554            }
2555        }))
2556        .unwrap();
2557        match TryInto::<ValidatorSchema>::try_into(fragment) {
2558            Err(SchemaError::ContextOrShapeNotRecord) => (),
2559            s => panic!(
2560                "Expected Err(SchemaError::ContextOrShapeNotRecord), got {:?}",
2561                s
2562            ),
2563        }
2564    }
2565}