1use std::collections::{hash_map::Entry, BTreeMap, HashMap, HashSet};
24
25use cedar_policy_core::{
26 ast::{Entity, EntityType, EntityUID, Name},
27 entities::{Entities, EntitiesError, TCComputation},
28 extensions::Extensions,
29 transitive_closure::compute_tc,
30};
31use serde::{Deserialize, Serialize};
32use serde_with::serde_as;
33
34use super::NamespaceDefinition;
35use crate::{
36 err::*,
37 human_schema::SchemaWarning,
38 types::{Attributes, EntityRecordKind, OpenTag, Type},
39 SchemaFragment, SchemaType, SchemaTypeVariant, TypeOfAttribute,
40};
41
42mod action;
43pub use action::ValidatorActionId;
44pub(crate) use action::ValidatorApplySpec;
45mod entity_type;
46pub use entity_type::ValidatorEntityType;
47mod namespace_def;
48pub(crate) use namespace_def::is_action_entity_type;
49pub use namespace_def::ValidatorNamespaceDef;
50#[cfg(test)]
51pub(crate) use namespace_def::ACTION_ENTITY_TYPE;
52
53#[derive(Eq, PartialEq, Copy, Clone, Default)]
55pub enum ActionBehavior {
56 #[default]
59 ProhibitAttributes,
60 PermitAttributes,
62}
63
64#[derive(Debug)]
65pub struct ValidatorSchemaFragment(Vec<ValidatorNamespaceDef>);
66
67impl TryInto<ValidatorSchemaFragment> for SchemaFragment {
68 type Error = SchemaError;
69
70 fn try_into(self) -> Result<ValidatorSchemaFragment> {
71 ValidatorSchemaFragment::from_schema_fragment(
72 self,
73 ActionBehavior::default(),
74 Extensions::all_available(),
75 )
76 }
77}
78
79impl ValidatorSchemaFragment {
80 pub fn from_namespaces(namespaces: impl IntoIterator<Item = ValidatorNamespaceDef>) -> Self {
81 Self(namespaces.into_iter().collect())
82 }
83
84 pub fn from_schema_fragment(
85 fragment: SchemaFragment,
86 action_behavior: ActionBehavior,
87 extensions: Extensions<'_>,
88 ) -> Result<Self> {
89 Ok(Self(
90 fragment
91 .0
92 .into_iter()
93 .map(|(fragment_ns, ns_def)| {
94 ValidatorNamespaceDef::from_namespace_definition(
95 fragment_ns,
96 ns_def,
97 action_behavior,
98 extensions,
99 )
100 })
101 .collect::<Result<Vec<_>>>()?,
102 ))
103 }
104
105 pub fn namespaces(&self) -> impl Iterator<Item = &Option<Name>> {
107 self.0.iter().map(|d| d.namespace())
108 }
109}
110
111#[serde_as]
112#[derive(Clone, Debug, Serialize)]
113pub struct ValidatorSchema {
114 #[serde(rename = "entityTypes")]
116 #[serde_as(as = "Vec<(_, _)>")]
117 entity_types: HashMap<Name, ValidatorEntityType>,
118
119 #[serde(rename = "actionIds")]
121 #[serde_as(as = "Vec<(_, _)>")]
122 action_ids: HashMap<EntityUID, ValidatorActionId>,
123}
124
125impl std::str::FromStr for ValidatorSchema {
126 type Err = SchemaError;
127
128 fn from_str(s: &str) -> Result<Self> {
129 serde_json::from_str::<SchemaFragment>(s)?.try_into()
130 }
131}
132
133impl TryFrom<NamespaceDefinition> for ValidatorSchema {
134 type Error = SchemaError;
135
136 fn try_from(nsd: NamespaceDefinition) -> Result<ValidatorSchema> {
137 ValidatorSchema::from_schema_fragments([ValidatorSchemaFragment::from_namespaces([
138 nsd.try_into()?
139 ])])
140 }
141}
142
143impl TryFrom<SchemaFragment> for ValidatorSchema {
144 type Error = SchemaError;
145
146 fn try_from(frag: SchemaFragment) -> Result<ValidatorSchema> {
147 ValidatorSchema::from_schema_fragments([frag.try_into()?])
148 }
149}
150
151impl ValidatorSchema {
152 pub fn empty() -> ValidatorSchema {
154 Self {
155 entity_types: HashMap::new(),
156 action_ids: HashMap::new(),
157 }
158 }
159
160 pub fn from_json_value(json: serde_json::Value, extensions: Extensions<'_>) -> Result<Self> {
163 Self::from_schema_file(
164 SchemaFragment::from_json_value(json)?,
165 ActionBehavior::default(),
166 extensions,
167 )
168 }
169
170 pub fn from_file(file: impl std::io::Read, extensions: Extensions<'_>) -> Result<Self> {
172 Self::from_schema_file(
173 SchemaFragment::from_file(file)?,
174 ActionBehavior::default(),
175 extensions,
176 )
177 }
178
179 pub fn from_file_natural(
180 r: impl std::io::Read,
181 extensions: Extensions<'_>,
182 ) -> std::result::Result<(Self, impl Iterator<Item = SchemaWarning>), HumanSchemaError> {
183 let (fragment, warnings) = SchemaFragment::from_file_natural(r)?;
184 let schema_and_warnings =
185 Self::from_schema_file(fragment, ActionBehavior::default(), extensions)
186 .map(|schema| (schema, warnings))?;
187 Ok(schema_and_warnings)
188 }
189
190 pub fn from_str_natural(
191 src: &str,
192 extensions: Extensions<'_>,
193 ) -> std::result::Result<(Self, impl Iterator<Item = SchemaWarning>), HumanSchemaError> {
194 let (fragment, warnings) = SchemaFragment::from_str_natural(src)?;
195 let schema_and_warnings =
196 Self::from_schema_file(fragment, ActionBehavior::default(), extensions)
197 .map(|schema| (schema, warnings))?;
198 Ok(schema_and_warnings)
199 }
200
201 pub fn from_schema_file(
202 schema_file: SchemaFragment,
203 action_behavior: ActionBehavior,
204 extensions: Extensions<'_>,
205 ) -> Result<ValidatorSchema> {
206 Self::from_schema_fragments([ValidatorSchemaFragment::from_schema_fragment(
207 schema_file,
208 action_behavior,
209 extensions,
210 )?])
211 }
212
213 pub fn from_schema_fragments(
215 fragments: impl IntoIterator<Item = ValidatorSchemaFragment>,
216 ) -> Result<ValidatorSchema> {
217 let mut type_defs = HashMap::new();
218 let mut entity_type_fragments = HashMap::new();
219 let mut action_fragments = HashMap::new();
220
221 for ns_def in fragments.into_iter().flat_map(|f| f.0.into_iter()) {
222 for (name, ty) in ns_def.type_defs.type_defs {
228 match type_defs.entry(name) {
229 Entry::Vacant(v) => v.insert(ty),
230 Entry::Occupied(o) => {
231 return Err(SchemaError::DuplicateCommonType(o.key().to_string()));
232 }
233 };
234 }
235
236 for (name, entity_type) in ns_def.entity_types.entity_types {
237 match entity_type_fragments.entry(name) {
238 Entry::Vacant(v) => v.insert(entity_type),
239 Entry::Occupied(o) => {
240 return Err(SchemaError::DuplicateEntityType(o.key().to_string()))
241 }
242 };
243 }
244
245 for (action_euid, action) in ns_def.actions.actions {
246 match action_fragments.entry(action_euid) {
247 Entry::Vacant(v) => v.insert(action),
248 Entry::Occupied(o) => {
249 return Err(SchemaError::DuplicateAction(o.key().to_string()))
250 }
251 };
252 }
253 }
254
255 let resolver = CommonTypeResolver::new(&type_defs);
256 let type_defs = resolver.resolve()?;
257
258 let mut entity_children = HashMap::new();
261 for (name, entity_type) in entity_type_fragments.iter() {
262 for parent in entity_type.parents.iter() {
263 entity_children
264 .entry(parent.clone())
265 .or_insert_with(HashSet::new)
266 .insert(name.clone());
267 }
268 }
269
270 let mut entity_types = entity_type_fragments
271 .into_iter()
272 .map(|(name, entity_type)| -> Result<_> {
273 let descendants = entity_children.remove(&name).unwrap_or_default();
283 let (attributes, open_attributes) = Self::record_attributes_or_none(
284 entity_type.attributes.resolve_type_defs(&type_defs)?,
285 )
286 .ok_or(SchemaError::ContextOrShapeNotRecord(
287 ContextOrShape::EntityTypeShape(name.clone()),
288 ))?;
289 Ok((
290 name.clone(),
291 ValidatorEntityType {
292 name,
293 descendants,
294 attributes,
295 open_attributes,
296 },
297 ))
298 })
299 .collect::<Result<HashMap<_, _>>>()?;
300
301 let mut action_children = HashMap::new();
302 for (euid, action) in action_fragments.iter() {
303 for parent in action.parents.iter() {
304 action_children
305 .entry(parent.clone())
306 .or_insert_with(HashSet::new)
307 .insert(euid.clone());
308 }
309 }
310 let mut action_ids = action_fragments
311 .into_iter()
312 .map(|(name, action)| -> Result<_> {
313 let descendants = action_children.remove(&name).unwrap_or_default();
314 let (context, open_context_attributes) =
315 Self::record_attributes_or_none(action.context.resolve_type_defs(&type_defs)?)
316 .ok_or(SchemaError::ContextOrShapeNotRecord(
317 ContextOrShape::ActionContext(name.clone()),
318 ))?;
319 Ok((
320 name.clone(),
321 ValidatorActionId {
322 name,
323 applies_to: action.applies_to,
324 descendants,
325 context: Type::record_with_attributes(
326 context.attrs,
327 open_context_attributes,
328 ),
329 attribute_types: action.attribute_types,
330 attributes: action.attributes,
331 },
332 ))
333 })
334 .collect::<Result<HashMap<_, _>>>()?;
335
336 compute_tc(&mut entity_types, false)?;
339 compute_tc(&mut action_ids, true)?;
342
343 Self::check_for_undeclared(
350 &entity_types,
351 entity_children.into_keys(),
352 &action_ids,
353 action_children.into_keys(),
354 )?;
355
356 Ok(ValidatorSchema {
357 entity_types,
358 action_ids,
359 })
360 }
361
362 fn check_for_undeclared(
367 entity_types: &HashMap<Name, ValidatorEntityType>,
368 undeclared_parent_entities: impl IntoIterator<Item = Name>,
369 action_ids: &HashMap<EntityUID, ValidatorActionId>,
370 undeclared_parent_actions: impl IntoIterator<Item = EntityUID>,
371 ) -> Result<()> {
372 let mut undeclared_e = undeclared_parent_entities
377 .into_iter()
378 .map(|n| n.to_string())
379 .collect::<HashSet<_>>();
380 for entity_type in entity_types.values() {
386 for (_, attr_typ) in entity_type.attributes() {
387 Self::check_undeclared_in_type(
388 &attr_typ.attr_type,
389 entity_types,
390 &mut undeclared_e,
391 );
392 }
393 }
394
395 let undeclared_a = undeclared_parent_actions
397 .into_iter()
398 .map(|n| n.to_string())
399 .collect::<HashSet<_>>();
400 for action in action_ids.values() {
404 Self::check_undeclared_in_type(&action.context, entity_types, &mut undeclared_e);
405
406 for p_entity in action.applies_to.applicable_principal_types() {
407 match p_entity {
408 EntityType::Specified(p_entity) => {
409 if !entity_types.contains_key(&p_entity) {
410 undeclared_e.insert(p_entity.to_string());
411 }
412 }
413 EntityType::Unspecified => (),
414 }
415 }
416
417 for r_entity in action.applies_to.applicable_resource_types() {
418 match r_entity {
419 EntityType::Specified(r_entity) => {
420 if !entity_types.contains_key(&r_entity) {
421 undeclared_e.insert(r_entity.to_string());
422 }
423 }
424 EntityType::Unspecified => (),
425 }
426 }
427 }
428 if !undeclared_e.is_empty() {
429 return Err(SchemaError::UndeclaredEntityTypes(undeclared_e));
430 }
431 if !undeclared_a.is_empty() {
432 return Err(SchemaError::UndeclaredActions(undeclared_a));
433 }
434
435 Ok(())
436 }
437
438 fn record_attributes_or_none(ty: Type) -> Option<(Attributes, OpenTag)> {
439 match ty {
440 Type::EntityOrRecord(EntityRecordKind::Record {
441 attrs,
442 open_attributes,
443 }) => Some((attrs, open_attributes)),
444 _ => None,
445 }
446 }
447
448 fn check_undeclared_in_type(
452 ty: &Type,
453 entity_types: &HashMap<Name, ValidatorEntityType>,
454 undeclared_types: &mut HashSet<String>,
455 ) {
456 match ty {
457 Type::EntityOrRecord(EntityRecordKind::Entity(lub)) => {
458 for name in lub.iter() {
459 if !entity_types.contains_key(name) {
460 undeclared_types.insert(name.to_string());
461 }
462 }
463 }
464
465 Type::EntityOrRecord(EntityRecordKind::Record { attrs, .. }) => {
466 for (_, attr_ty) in attrs.iter() {
467 Self::check_undeclared_in_type(
468 &attr_ty.attr_type,
469 entity_types,
470 undeclared_types,
471 );
472 }
473 }
474
475 Type::Set {
476 element_type: Some(element_type),
477 } => Self::check_undeclared_in_type(element_type, entity_types, undeclared_types),
478
479 _ => (),
480 }
481 }
482
483 pub fn get_action_id(&self, action_id: &EntityUID) -> Option<&ValidatorActionId> {
485 self.action_ids.get(action_id)
486 }
487
488 pub fn get_entity_type<'a>(&'a self, entity_type_id: &Name) -> Option<&'a ValidatorEntityType> {
490 self.entity_types.get(entity_type_id)
491 }
492
493 pub(crate) fn is_known_action_id(&self, action_id: &EntityUID) -> bool {
495 self.action_ids.contains_key(action_id)
496 }
497
498 pub(crate) fn is_known_entity_type(&self, entity_type: &Name) -> bool {
500 is_action_entity_type(entity_type) || self.entity_types.contains_key(entity_type)
501 }
502
503 pub(crate) fn euid_has_known_entity_type(&self, euid: &EntityUID) -> bool {
508 match euid.entity_type() {
509 EntityType::Specified(ety) => self.is_known_entity_type(ety),
510 EntityType::Unspecified => true,
511 }
512 }
513
514 pub(crate) fn known_action_ids(&self) -> impl Iterator<Item = &EntityUID> {
516 self.action_ids.keys()
517 }
518
519 pub(crate) fn known_entity_types(&self) -> impl Iterator<Item = &Name> {
521 self.entity_types.keys()
522 }
523
524 pub fn entity_types(&self) -> impl Iterator<Item = (&Name, &ValidatorEntityType)> {
526 self.entity_types.iter()
527 }
528
529 pub(crate) fn get_entity_types_in<'a>(&'a self, entity: &'a EntityUID) -> Vec<&Name> {
535 match entity.entity_type() {
536 EntityType::Specified(ety) => {
537 let mut descendants = self
538 .get_entity_type(ety)
539 .map(|v_ety| v_ety.descendants.iter().collect::<Vec<_>>())
540 .unwrap_or_default();
541 descendants.push(ety);
542 descendants
543 }
544 EntityType::Unspecified => Vec::new(),
545 }
546 }
547
548 pub(crate) fn get_entity_types_in_set<'a>(
552 &'a self,
553 euids: impl IntoIterator<Item = &'a EntityUID> + 'a,
554 ) -> impl Iterator<Item = &Name> {
555 euids.into_iter().flat_map(|e| self.get_entity_types_in(e))
556 }
557
558 pub(crate) fn get_actions_in_set<'a>(
562 &'a self,
563 euids: impl IntoIterator<Item = &'a EntityUID> + 'a,
564 ) -> Option<Vec<&'a EntityUID>> {
565 euids
566 .into_iter()
567 .map(|e| {
568 self.get_action_id(e).map(|action| {
569 action
570 .descendants
571 .iter()
572 .chain(std::iter::once(&action.name))
573 })
574 })
575 .collect::<Option<Vec<_>>>()
576 .map(|v| v.into_iter().flatten().collect::<Vec<_>>())
577 }
578
579 pub fn context_type(&self, action: &EntityUID) -> Option<Type> {
584 self.get_action_id(action)
587 .map(ValidatorActionId::context_type)
588 }
589
590 pub(crate) fn action_entities_iter(
593 &self,
594 ) -> impl Iterator<Item = cedar_policy_core::ast::Entity> + '_ {
595 let mut action_ancestors: HashMap<&EntityUID, HashSet<EntityUID>> = HashMap::new();
601 for (action_euid, action_def) in &self.action_ids {
602 for descendant in &action_def.descendants {
603 action_ancestors
604 .entry(descendant)
605 .or_default()
606 .insert(action_euid.clone());
607 }
608 }
609 self.action_ids.iter().map(move |(action_id, action)| {
610 Entity::new_with_attr_partial_value_serialized_as_expr(
611 action_id.clone(),
612 action.attributes.clone(),
613 action_ancestors.remove(action_id).unwrap_or_default(),
614 )
615 })
616 }
617
618 pub fn action_entities(&self) -> std::result::Result<Entities, EntitiesError> {
620 let extensions = Extensions::all_available();
621 Entities::from_entities(
622 self.action_entities_iter(),
623 None::<&cedar_policy_core::entities::NoEntitiesSchema>, TCComputation::AssumeAlreadyComputed,
625 extensions,
626 )
627 .map_err(Into::into)
628 }
629}
630
631#[derive(Debug, Clone, Deserialize)]
634#[serde(transparent)]
635pub(crate) struct NamespaceDefinitionWithActionAttributes(pub(crate) NamespaceDefinition);
636
637impl TryInto<ValidatorSchema> for NamespaceDefinitionWithActionAttributes {
638 type Error = SchemaError;
639
640 fn try_into(self) -> Result<ValidatorSchema> {
641 ValidatorSchema::from_schema_fragments([ValidatorSchemaFragment::from_namespaces([
642 ValidatorNamespaceDef::from_namespace_definition(
643 None,
644 self.0,
645 crate::ActionBehavior::PermitAttributes,
646 Extensions::all_available(),
647 )?,
648 ])])
649 }
650}
651
652#[derive(Debug)]
654struct CommonTypeResolver<'a> {
655 type_defs: &'a HashMap<Name, SchemaType>,
657 graph: HashMap<Name, HashSet<Name>>,
661}
662
663impl<'a> CommonTypeResolver<'a> {
664 fn new(type_defs: &'a HashMap<Name, SchemaType>) -> Self {
666 let mut graph = HashMap::new();
667 for (name, ty) in type_defs {
668 graph.insert(
669 name.clone(),
670 HashSet::from_iter(ty.common_type_references()),
671 );
672 }
673 Self { type_defs, graph }
674 }
675
676 fn topo_sort(&self) -> std::result::Result<Vec<Name>, Name> {
683 let mut indegrees: HashMap<&Name, usize> = HashMap::new();
687 for (ty_name, deps) in self.graph.iter() {
688 indegrees.entry(ty_name).or_insert(0);
690 for dep in deps {
691 match indegrees.entry(dep) {
692 std::collections::hash_map::Entry::Occupied(mut o) => {
693 o.insert(o.get() + 1);
694 }
695 std::collections::hash_map::Entry::Vacant(v) => {
696 v.insert(1);
697 }
698 }
699 }
700 }
701
702 let mut work_set: HashSet<&Name> = HashSet::new();
704 let mut res: Vec<Name> = Vec::new();
705
706 for (name, degree) in indegrees.iter() {
708 let name = *name;
709 if *degree == 0 {
710 work_set.insert(name);
711 if self.graph.contains_key(name) {
713 res.push(name.clone());
714 }
715 }
716 }
717
718 while let Some(name) = work_set.iter().next().cloned() {
720 work_set.remove(name);
721 if let Some(deps) = self.graph.get(name) {
722 for dep in deps {
723 if let Some(degree) = indegrees.get_mut(dep) {
724 *degree -= 1;
734 if *degree == 0 {
735 work_set.insert(dep);
736 if self.graph.contains_key(dep) {
737 res.push(dep.clone());
738 }
739 }
740 }
741 }
742 }
743 }
744
745 let mut set: HashSet<&Name> = HashSet::from_iter(self.graph.keys().clone());
748 for name in res.iter() {
749 set.remove(name);
750 }
751
752 if let Some(cycle) = set.into_iter().next() {
753 Err(cycle.clone())
754 } else {
755 res.reverse();
758 Ok(res)
759 }
760 }
761
762 fn resolve_type(
764 resolve_table: &HashMap<&Name, SchemaType>,
765 ty: SchemaType,
766 ) -> Result<SchemaType> {
767 match ty {
768 SchemaType::TypeDef { type_name } => resolve_table
769 .get(&type_name)
770 .ok_or(SchemaError::UndeclaredCommonTypes(HashSet::from_iter(
771 std::iter::once(type_name.to_string()),
772 )))
773 .cloned(),
774 SchemaType::Type(SchemaTypeVariant::Set { element }) => {
775 Ok(SchemaType::Type(SchemaTypeVariant::Set {
776 element: Box::new(Self::resolve_type(resolve_table, *element)?),
777 }))
778 }
779 SchemaType::Type(SchemaTypeVariant::Record {
780 attributes,
781 additional_attributes,
782 }) => Ok(SchemaType::Type(SchemaTypeVariant::Record {
783 attributes: BTreeMap::from_iter(
784 attributes
785 .into_iter()
786 .map(|(attr, attr_ty)| {
787 Ok((
788 attr,
789 TypeOfAttribute {
790 required: attr_ty.required,
791 ty: Self::resolve_type(resolve_table, attr_ty.ty)?,
792 },
793 ))
794 })
795 .collect::<Result<Vec<(_, _)>>>()?,
796 ),
797 additional_attributes,
798 })),
799 _ => Ok(ty),
800 }
801 }
802
803 fn resolve(&self) -> Result<HashMap<Name, Type>> {
805 let sorted_names = self
806 .topo_sort()
807 .map_err(SchemaError::CycleInCommonTypeReferences)?;
808
809 let mut resolve_table = HashMap::new();
810 let mut tys = HashMap::new();
811
812 for name in sorted_names.iter() {
813 let ns: Option<Name> = if name.is_unqualified() {
814 None
815 } else {
816 #[allow(clippy::unwrap_used)]
818 Some(name.namespace().parse().unwrap())
819 };
820 #[allow(clippy::unwrap_used)]
822 let ty = self.type_defs.get(name).unwrap();
823 let substituted_ty = Self::resolve_type(&resolve_table, ty.clone())?;
824 resolve_table.insert(name, substituted_ty.clone());
825 tys.insert(
826 name.clone(),
827 ValidatorNamespaceDef::try_schema_type_into_validator_type(
828 ns.as_ref(),
829 substituted_ty,
830 )?
831 .resolve_type_defs(&HashMap::new())?,
832 );
833 }
834
835 Ok(tys)
836 }
837}
838
839#[allow(clippy::panic)]
841#[allow(clippy::indexing_slicing)]
843#[cfg(test)]
844mod test {
845 use std::{collections::BTreeMap, str::FromStr};
846
847 use crate::types::Type;
848 use crate::{SchemaType, SchemaTypeVariant};
849
850 use cedar_policy_core::ast::RestrictedExpr;
851 use cool_asserts::assert_matches;
852 use serde_json::json;
853
854 use super::*;
855
856 #[test]
858 fn test_from_schema_file() {
859 let src = json!(
860 {
861 "entityTypes": {
862 "User": {
863 "memberOfTypes": [ "Group" ]
864 },
865 "Group": {
866 "memberOfTypes": []
867 },
868 "Photo": {
869 "memberOfTypes": [ "Album" ]
870 },
871 "Album": {
872 "memberOfTypes": []
873 }
874 },
875 "actions": {
876 "view_photo": {
877 "appliesTo": {
878 "principalTypes": ["User", "Group"],
879 "resourceTypes": ["Photo"]
880 }
881 }
882 }
883 });
884 let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
885 let schema: Result<ValidatorSchema> = schema_file.try_into();
886 assert!(schema.is_ok());
887 }
888
889 #[test]
891 fn test_from_schema_file_duplicate_entity() {
892 let src = r#"
895 {"": {
896 "entityTypes": {
897 "User": {
898 "memberOfTypes": [ "Group" ]
899 },
900 "Group": {
901 "memberOfTypes": []
902 },
903 "Photo": {
904 "memberOfTypes": [ "Album" ]
905 },
906 "Photo": {
907 "memberOfTypes": []
908 }
909 },
910 "actions": {
911 "view_photo": {
912 "memberOf": [],
913 "appliesTo": {
914 "principalTypes": ["User", "Group"],
915 "resourceTypes": ["Photo"]
916 }
917 }
918 }
919 }}"#;
920
921 match ValidatorSchema::from_str(src) {
922 Err(SchemaError::Serde(_)) => (),
923 _ => panic!("Expected serde error due to duplicate entity type."),
924 }
925 }
926
927 #[test]
929 fn test_from_schema_file_duplicate_action() {
930 let src = r#"
933 {"": {
934 "entityTypes": {
935 "User": {
936 "memberOfTypes": [ "Group" ]
937 },
938 "Group": {
939 "memberOfTypes": []
940 },
941 "Photo": {
942 "memberOfTypes": []
943 }
944 },
945 "actions": {
946 "view_photo": {
947 "memberOf": [],
948 "appliesTo": {
949 "principalTypes": ["User", "Group"],
950 "resourceTypes": ["Photo"]
951 }
952 },
953 "view_photo": { }
954 }
955 }"#;
956 match ValidatorSchema::from_str(src) {
957 Err(SchemaError::Serde(_)) => (),
958 _ => panic!("Expected serde error due to duplicate action type."),
959 }
960 }
961
962 #[test]
964 fn test_from_schema_file_undefined_entities() {
965 let src = json!(
966 {
967 "entityTypes": {
968 "User": {
969 "memberOfTypes": [ "Grop" ]
970 },
971 "Group": {
972 "memberOfTypes": []
973 },
974 "Photo": {
975 "memberOfTypes": []
976 }
977 },
978 "actions": {
979 "view_photo": {
980 "appliesTo": {
981 "principalTypes": ["Usr", "Group"],
982 "resourceTypes": ["Phoot"]
983 }
984 }
985 }
986 });
987 let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
988 let schema: Result<ValidatorSchema> = schema_file.try_into();
989 match schema {
990 Ok(_) => panic!("from_schema_file should have failed"),
991 Err(SchemaError::UndeclaredEntityTypes(v)) => {
992 assert_eq!(v.len(), 3)
993 }
994 _ => panic!("Unexpected error from from_schema_file"),
995 }
996 }
997
998 #[test]
999 fn undefined_entity_namespace_member_of() {
1000 let src = json!(
1001 {"Foo": {
1002 "entityTypes": {
1003 "User": {
1004 "memberOfTypes": [ "Foo::Group", "Bar::Group" ]
1005 },
1006 "Group": { }
1007 },
1008 "actions": {}
1009 }});
1010 let schema_file: SchemaFragment = serde_json::from_value(src).expect("Parse Error");
1011 let schema: Result<ValidatorSchema> = schema_file.try_into();
1012 match schema {
1013 Ok(_) => panic!("try_into should have failed"),
1014 Err(SchemaError::UndeclaredEntityTypes(v)) => {
1015 assert_eq!(v, HashSet::from(["Bar::Group".to_string()]))
1016 }
1017 _ => panic!("Unexpected error from try_into"),
1018 }
1019 }
1020
1021 #[test]
1022 fn undefined_entity_namespace_applies_to() {
1023 let src = json!(
1024 {"Foo": {
1025 "entityTypes": { "User": { }, "Photo": { } },
1026 "actions": {
1027 "view_photo": {
1028 "appliesTo": {
1029 "principalTypes": ["Foo::User", "Bar::User"],
1030 "resourceTypes": ["Photo", "Bar::Photo"],
1031 }
1032 }
1033 }
1034 }});
1035 let schema_file: SchemaFragment = serde_json::from_value(src).expect("Parse Error");
1036 let schema: Result<ValidatorSchema> = schema_file.try_into();
1037 match schema {
1038 Ok(_) => panic!("try_into should have failed"),
1039 Err(SchemaError::UndeclaredEntityTypes(v)) => {
1040 assert_eq!(
1041 v,
1042 HashSet::from(["Bar::Photo".to_string(), "Bar::User".to_string()])
1043 )
1044 }
1045 _ => panic!("Unexpected error from try_into"),
1046 }
1047 }
1048
1049 #[test]
1051 fn test_from_schema_file_undefined_action() {
1052 let src = json!(
1053 {
1054 "entityTypes": {
1055 "User": {
1056 "memberOfTypes": [ "Group" ]
1057 },
1058 "Group": {
1059 "memberOfTypes": []
1060 },
1061 "Photo": {
1062 "memberOfTypes": []
1063 }
1064 },
1065 "actions": {
1066 "view_photo": {
1067 "memberOf": [ {"id": "photo_action"} ],
1068 "appliesTo": {
1069 "principalTypes": ["User", "Group"],
1070 "resourceTypes": ["Photo"]
1071 }
1072 }
1073 }
1074 });
1075 let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
1076 let schema: Result<ValidatorSchema> = schema_file.try_into();
1077 match schema {
1078 Ok(_) => panic!("from_schema_file should have failed"),
1079 Err(SchemaError::UndeclaredActions(v)) => assert_eq!(v.len(), 1),
1080 _ => panic!("Unexpected error from from_schema_file"),
1081 }
1082 }
1083
1084 #[test]
1087 fn test_from_schema_file_action_cycle1() {
1088 let src = json!(
1089 {
1090 "entityTypes": {},
1091 "actions": {
1092 "view_photo": {
1093 "memberOf": [ {"id": "view_photo"} ]
1094 }
1095 }
1096 });
1097 let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
1098 let schema: Result<ValidatorSchema> = schema_file.try_into();
1099 assert_matches!(
1100 schema,
1101 Err(SchemaError::CycleInActionHierarchy(euid)) => {
1102 assert_eq!(euid, r#"Action::"view_photo""#.parse().unwrap());
1103 }
1104 )
1105 }
1106
1107 #[test]
1110 fn test_from_schema_file_action_cycle2() {
1111 let src = json!(
1112 {
1113 "entityTypes": {},
1114 "actions": {
1115 "view_photo": {
1116 "memberOf": [ {"id": "edit_photo"} ]
1117 },
1118 "edit_photo": {
1119 "memberOf": [ {"id": "delete_photo"} ]
1120 },
1121 "delete_photo": {
1122 "memberOf": [ {"id": "view_photo"} ]
1123 },
1124 "other_action": {
1125 "memberOf": [ {"id": "edit_photo"} ]
1126 }
1127 }
1128 });
1129 let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
1130 let schema: Result<ValidatorSchema> = schema_file.try_into();
1131 assert_matches!(
1132 schema,
1133 Err(SchemaError::CycleInActionHierarchy(_)),
1135 )
1136 }
1137
1138 #[test]
1139 fn namespaced_schema() {
1140 let src = r#"
1141 { "N::S": {
1142 "entityTypes": {
1143 "User": {},
1144 "Photo": {}
1145 },
1146 "actions": {
1147 "view_photo": {
1148 "appliesTo": {
1149 "principalTypes": ["User"],
1150 "resourceTypes": ["Photo"]
1151 }
1152 }
1153 }
1154 } }
1155 "#;
1156 let schema_file: SchemaFragment = serde_json::from_str(src).expect("Parse Error");
1157 let schema: ValidatorSchema = schema_file
1158 .try_into()
1159 .expect("Namespaced schema failed to convert.");
1160 dbg!(&schema);
1161 let user_entity_type = &"N::S::User"
1162 .parse()
1163 .expect("Namespaced entity type should have parsed");
1164 let photo_entity_type = &"N::S::Photo"
1165 .parse()
1166 .expect("Namespaced entity type should have parsed");
1167 assert!(
1168 schema.entity_types.contains_key(user_entity_type),
1169 "Expected and entity type User."
1170 );
1171 assert!(
1172 schema.entity_types.contains_key(photo_entity_type),
1173 "Expected an entity type Photo."
1174 );
1175 assert_eq!(
1176 schema.entity_types.len(),
1177 2,
1178 "Expected exactly 2 entity types."
1179 );
1180 assert!(
1181 schema.action_ids.contains_key(
1182 &"N::S::Action::\"view_photo\""
1183 .parse()
1184 .expect("Namespaced action should have parsed")
1185 ),
1186 "Expected an action \"view_photo\"."
1187 );
1188 assert_eq!(schema.action_ids.len(), 1, "Expected exactly 1 action.");
1189
1190 let apply_spec = &schema
1191 .action_ids
1192 .values()
1193 .next()
1194 .expect("Expected Action")
1195 .applies_to;
1196 assert_eq!(
1197 apply_spec.applicable_principal_types().collect::<Vec<_>>(),
1198 vec![&EntityType::Specified(user_entity_type.clone())]
1199 );
1200 assert_eq!(
1201 apply_spec.applicable_resource_types().collect::<Vec<_>>(),
1202 vec![&EntityType::Specified(photo_entity_type.clone())]
1203 );
1204 }
1205
1206 #[test]
1207 fn cant_use_namespace_in_entity_type() {
1208 let src = r#"
1209 {
1210 "entityTypes": { "NS::User": {} },
1211 "actions": {}
1212 }
1213 "#;
1214 let schema_file: std::result::Result<NamespaceDefinition, _> = serde_json::from_str(src);
1215 assert!(schema_file.is_err());
1216 }
1217
1218 #[test]
1219 fn entity_attribute_entity_type_with_namespace() {
1220 let schema_json: SchemaFragment = serde_json::from_str(
1221 r#"
1222 {"A::B": {
1223 "entityTypes": {
1224 "Foo": {
1225 "shape": {
1226 "type": "Record",
1227 "attributes": {
1228 "name": { "type": "Entity", "name": "C::D::Foo" }
1229 }
1230 }
1231 }
1232 },
1233 "actions": {}
1234 }}
1235 "#,
1236 )
1237 .expect("Expected valid schema");
1238
1239 let schema: Result<ValidatorSchema> = schema_json.try_into();
1240 match schema {
1241 Err(SchemaError::UndeclaredEntityTypes(tys)) => {
1242 assert_eq!(tys, HashSet::from(["C::D::Foo".to_string()]))
1243 }
1244 _ => panic!("Schema construction should have failed due to undeclared entity type."),
1245 }
1246 }
1247
1248 #[test]
1249 fn entity_attribute_entity_type_with_declared_namespace() {
1250 let schema_json: SchemaFragment = serde_json::from_str(
1251 r#"
1252 {"A::B": {
1253 "entityTypes": {
1254 "Foo": {
1255 "shape": {
1256 "type": "Record",
1257 "attributes": {
1258 "name": { "type": "Entity", "name": "A::B::Foo" }
1259 }
1260 }
1261 }
1262 },
1263 "actions": {}
1264 }}
1265 "#,
1266 )
1267 .expect("Expected valid schema");
1268
1269 let schema: ValidatorSchema = schema_json
1270 .try_into()
1271 .expect("Expected schema to construct without error.");
1272
1273 let foo_name: Name = "A::B::Foo".parse().expect("Expected entity type name");
1274 let foo_type = schema
1275 .entity_types
1276 .get(&foo_name)
1277 .expect("Expected to find entity");
1278 let name_type = foo_type
1279 .attr("name")
1280 .expect("Expected attribute name")
1281 .attr_type
1282 .clone();
1283 let expected_name_type = Type::named_entity_reference(foo_name);
1284 assert_eq!(name_type, expected_name_type);
1285 }
1286
1287 #[test]
1288 fn cannot_declare_action_type_when_prohibited() {
1289 let schema_json: NamespaceDefinition = serde_json::from_str(
1290 r#"
1291 {
1292 "entityTypes": { "Action": {} },
1293 "actions": {}
1294 }
1295 "#,
1296 )
1297 .expect("Expected valid schema");
1298
1299 let schema: Result<ValidatorSchema> = schema_json.try_into();
1300 assert!(matches!(schema, Err(SchemaError::ActionEntityTypeDeclared)));
1301 }
1302
1303 #[test]
1304 fn can_declare_other_type_when_action_type_prohibited() {
1305 let schema_json: NamespaceDefinition = serde_json::from_str(
1306 r#"
1307 {
1308 "entityTypes": { "Foo": { } },
1309 "actions": {}
1310 }
1311 "#,
1312 )
1313 .expect("Expected valid schema");
1314
1315 TryInto::<ValidatorSchema>::try_into(schema_json).expect("Did not expect any errors.");
1316 }
1317
1318 #[test]
1319 fn cannot_declare_action_in_group_when_prohibited() {
1320 let schema_json: SchemaFragment = serde_json::from_str(
1321 r#"
1322 {"": {
1323 "entityTypes": {},
1324 "actions": {
1325 "universe": { },
1326 "view_photo": {
1327 "attributes": {"id": "universe"}
1328 },
1329 "edit_photo": {
1330 "attributes": {"id": "universe"}
1331 },
1332 "delete_photo": {
1333 "attributes": {"id": "universe"}
1334 }
1335 }
1336 }}
1337 "#,
1338 )
1339 .expect("Expected valid schema");
1340
1341 let schema = ValidatorSchemaFragment::from_schema_fragment(
1342 schema_json,
1343 ActionBehavior::ProhibitAttributes,
1344 Extensions::all_available(),
1345 );
1346 match schema {
1347 Err(SchemaError::UnsupportedFeature(UnsupportedFeature::ActionAttributes(actions))) => {
1348 assert_eq!(
1349 actions.into_iter().collect::<HashSet<_>>(),
1350 HashSet::from([
1351 "view_photo".to_string(),
1352 "edit_photo".to_string(),
1353 "delete_photo".to_string(),
1354 ])
1355 )
1356 }
1357 _ => panic!("Did not see expected error."),
1358 }
1359 }
1360
1361 #[test]
1362 fn test_entity_type_no_namespace() {
1363 let src = json!({"type": "Entity", "name": "Foo"});
1364 let schema_ty: SchemaType = serde_json::from_value(src).expect("Parse Error");
1365 assert_eq!(
1366 schema_ty,
1367 SchemaType::Type(SchemaTypeVariant::Entity {
1368 name: "Foo".parse().unwrap()
1369 })
1370 );
1371 let ty: Type = ValidatorNamespaceDef::try_schema_type_into_validator_type(
1372 Some(&Name::parse_unqualified_name("NS").expect("Expected namespace.")),
1373 schema_ty,
1374 )
1375 .expect("Error converting schema type to type.")
1376 .resolve_type_defs(&HashMap::new())
1377 .unwrap();
1378 assert_eq!(ty, Type::named_entity_reference_from_str("NS::Foo"));
1379 }
1380
1381 #[test]
1382 fn test_entity_type_namespace() {
1383 let src = json!({"type": "Entity", "name": "NS::Foo"});
1384 let schema_ty: SchemaType = serde_json::from_value(src).expect("Parse Error");
1385 assert_eq!(
1386 schema_ty,
1387 SchemaType::Type(SchemaTypeVariant::Entity {
1388 name: "NS::Foo".parse().unwrap()
1389 })
1390 );
1391 let ty: Type = ValidatorNamespaceDef::try_schema_type_into_validator_type(
1392 Some(&Name::parse_unqualified_name("NS").expect("Expected namespace.")),
1393 schema_ty,
1394 )
1395 .expect("Error converting schema type to type.")
1396 .resolve_type_defs(&HashMap::new())
1397 .unwrap();
1398 assert_eq!(ty, Type::named_entity_reference_from_str("NS::Foo"));
1399 }
1400
1401 #[test]
1402 fn test_entity_type_namespace_parse_error() {
1403 let src = json!({"type": "Entity", "name": "::Foo"});
1404 let schema_ty: std::result::Result<SchemaType, _> = serde_json::from_value(src);
1405 assert!(schema_ty.is_err());
1406 }
1407
1408 #[test]
1409 fn schema_type_record_is_validator_type_record() {
1410 let src = json!({"type": "Record", "attributes": {}});
1411 let schema_ty: SchemaType = serde_json::from_value(src).expect("Parse Error");
1412 assert_eq!(
1413 schema_ty,
1414 SchemaType::Type(SchemaTypeVariant::Record {
1415 attributes: BTreeMap::new(),
1416 additional_attributes: false,
1417 }),
1418 );
1419 let ty: Type = ValidatorNamespaceDef::try_schema_type_into_validator_type(None, schema_ty)
1420 .expect("Error converting schema type to type.")
1421 .resolve_type_defs(&HashMap::new())
1422 .unwrap();
1423 assert_eq!(ty, Type::closed_record_with_attributes(None));
1424 }
1425
1426 #[test]
1427 fn get_namespaces() {
1428 let fragment: SchemaFragment = serde_json::from_value(json!({
1429 "Foo::Bar::Baz": {
1430 "entityTypes": {},
1431 "actions": {}
1432 },
1433 "Foo": {
1434 "entityTypes": {},
1435 "actions": {}
1436 },
1437 "Bar": {
1438 "entityTypes": {},
1439 "actions": {}
1440 },
1441 }))
1442 .unwrap();
1443
1444 let schema_fragment: ValidatorSchemaFragment = fragment.try_into().unwrap();
1445 assert_eq!(
1446 schema_fragment
1447 .0
1448 .iter()
1449 .map(|f| f.namespace())
1450 .collect::<HashSet<_>>(),
1451 HashSet::from([
1452 &Some("Foo::Bar::Baz".parse().unwrap()),
1453 &Some("Foo".parse().unwrap()),
1454 &Some("Bar".parse().unwrap())
1455 ])
1456 );
1457 }
1458
1459 #[test]
1460 fn schema_no_fragments() {
1461 let schema = ValidatorSchema::from_schema_fragments([]).unwrap();
1462 assert!(schema.entity_types.is_empty());
1463 assert!(schema.action_ids.is_empty());
1464 }
1465
1466 #[test]
1467 fn same_action_different_namespace() {
1468 let fragment: SchemaFragment = serde_json::from_value(json!({
1469 "Foo::Bar": {
1470 "entityTypes": {},
1471 "actions": {
1472 "Baz": {}
1473 }
1474 },
1475 "Bar::Foo": {
1476 "entityTypes": {},
1477 "actions": {
1478 "Baz": { }
1479 }
1480 },
1481 "Biz": {
1482 "entityTypes": {},
1483 "actions": {
1484 "Baz": { }
1485 }
1486 }
1487 }))
1488 .unwrap();
1489
1490 let schema: ValidatorSchema = fragment.try_into().unwrap();
1491 assert!(schema
1492 .get_action_id(&"Foo::Bar::Action::\"Baz\"".parse().unwrap())
1493 .is_some());
1494 assert!(schema
1495 .get_action_id(&"Bar::Foo::Action::\"Baz\"".parse().unwrap())
1496 .is_some());
1497 assert!(schema
1498 .get_action_id(&"Biz::Action::\"Baz\"".parse().unwrap())
1499 .is_some());
1500 }
1501
1502 #[test]
1503 fn same_type_different_namespace() {
1504 let fragment: SchemaFragment = serde_json::from_value(json!({
1505 "Foo::Bar": {
1506 "entityTypes": {"Baz" : {}},
1507 "actions": { }
1508 },
1509 "Bar::Foo": {
1510 "entityTypes": {"Baz" : {}},
1511 "actions": { }
1512 },
1513 "Biz": {
1514 "entityTypes": {"Baz" : {}},
1515 "actions": { }
1516 }
1517 }))
1518 .unwrap();
1519 let schema: ValidatorSchema = fragment.try_into().unwrap();
1520
1521 assert!(schema
1522 .get_entity_type(&"Foo::Bar::Baz".parse().unwrap())
1523 .is_some());
1524 assert!(schema
1525 .get_entity_type(&"Bar::Foo::Baz".parse().unwrap())
1526 .is_some());
1527 assert!(schema
1528 .get_entity_type(&"Biz::Baz".parse().unwrap())
1529 .is_some());
1530 }
1531
1532 #[test]
1533 fn member_of_different_namespace() {
1534 let fragment: SchemaFragment = serde_json::from_value(json!({
1535 "Bar": {
1536 "entityTypes": {
1537 "Baz": {
1538 "memberOfTypes": ["Foo::Buz"]
1539 }
1540 },
1541 "actions": {}
1542 },
1543 "Foo": {
1544 "entityTypes": { "Buz": {} },
1545 "actions": { }
1546 }
1547 }))
1548 .unwrap();
1549 let schema: ValidatorSchema = fragment.try_into().unwrap();
1550
1551 let buz = schema
1552 .get_entity_type(&"Foo::Buz".parse().unwrap())
1553 .unwrap();
1554 assert_eq!(
1555 buz.descendants,
1556 HashSet::from(["Bar::Baz".parse().unwrap()])
1557 );
1558 }
1559
1560 #[test]
1561 fn attribute_different_namespace() {
1562 let fragment: SchemaFragment = serde_json::from_value(json!({
1563 "Bar": {
1564 "entityTypes": {
1565 "Baz": {
1566 "shape": {
1567 "type": "Record",
1568 "attributes": {
1569 "fiz": {
1570 "type": "Entity",
1571 "name": "Foo::Buz"
1572 }
1573 }
1574 }
1575 }
1576 },
1577 "actions": {}
1578 },
1579 "Foo": {
1580 "entityTypes": { "Buz": {} },
1581 "actions": { }
1582 }
1583 }))
1584 .unwrap();
1585
1586 let schema: ValidatorSchema = fragment.try_into().unwrap();
1587 let baz = schema
1588 .get_entity_type(&"Bar::Baz".parse().unwrap())
1589 .unwrap();
1590 assert_eq!(
1591 baz.attr("fiz").unwrap().attr_type,
1592 Type::named_entity_reference_from_str("Foo::Buz"),
1593 );
1594 }
1595
1596 #[test]
1597 fn applies_to_different_namespace() {
1598 let fragment: SchemaFragment = serde_json::from_value(json!({
1599 "Foo::Bar": {
1600 "entityTypes": { },
1601 "actions": {
1602 "Baz": {
1603 "appliesTo": {
1604 "principalTypes": [ "Fiz::Buz" ],
1605 "resourceTypes": [ "Fiz::Baz" ],
1606 }
1607 }
1608 }
1609 },
1610 "Fiz": {
1611 "entityTypes": {
1612 "Buz": {},
1613 "Baz": {}
1614 },
1615 "actions": { }
1616 }
1617 }))
1618 .unwrap();
1619 let schema: ValidatorSchema = fragment.try_into().unwrap();
1620
1621 let baz = schema
1622 .get_action_id(&"Foo::Bar::Action::\"Baz\"".parse().unwrap())
1623 .unwrap();
1624 assert_eq!(
1625 baz.applies_to
1626 .applicable_principal_types()
1627 .collect::<HashSet<_>>(),
1628 HashSet::from([&EntityType::Specified("Fiz::Buz".parse().unwrap())])
1629 );
1630 assert_eq!(
1631 baz.applies_to
1632 .applicable_resource_types()
1633 .collect::<HashSet<_>>(),
1634 HashSet::from([&EntityType::Specified("Fiz::Baz".parse().unwrap())])
1635 );
1636 }
1637
1638 #[test]
1639 fn simple_defined_type() {
1640 let fragment: SchemaFragment = serde_json::from_value(json!({
1641 "": {
1642 "commonTypes": {
1643 "MyLong": {"type": "Long"}
1644 },
1645 "entityTypes": {
1646 "User": {
1647 "shape": {
1648 "type": "Record",
1649 "attributes": {
1650 "a": {"type": "MyLong"}
1651 }
1652 }
1653 }
1654 },
1655 "actions": {}
1656 }
1657 }))
1658 .unwrap();
1659 let schema: ValidatorSchema = fragment.try_into().unwrap();
1660 assert_eq!(
1661 schema.entity_types.iter().next().unwrap().1.attributes,
1662 Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
1663 );
1664 }
1665
1666 #[test]
1667 fn defined_record_as_attrs() {
1668 let fragment: SchemaFragment = serde_json::from_value(json!({
1669 "": {
1670 "commonTypes": {
1671 "MyRecord": {
1672 "type": "Record",
1673 "attributes": {
1674 "a": {"type": "Long"}
1675 }
1676 }
1677 },
1678 "entityTypes": {
1679 "User": { "shape": { "type": "MyRecord", } }
1680 },
1681 "actions": {}
1682 }
1683 }))
1684 .unwrap();
1685 let schema: ValidatorSchema = fragment.try_into().unwrap();
1686 assert_eq!(
1687 schema.entity_types.iter().next().unwrap().1.attributes,
1688 Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
1689 );
1690 }
1691
1692 #[test]
1693 fn cross_namespace_type() {
1694 let fragment: SchemaFragment = serde_json::from_value(json!({
1695 "A": {
1696 "commonTypes": {
1697 "MyLong": {"type": "Long"}
1698 },
1699 "entityTypes": { },
1700 "actions": {}
1701 },
1702 "B": {
1703 "entityTypes": {
1704 "User": {
1705 "shape": {
1706 "type": "Record",
1707 "attributes": {
1708 "a": {"type": "A::MyLong"}
1709 }
1710 }
1711 }
1712 },
1713 "actions": {}
1714 }
1715 }))
1716 .unwrap();
1717 let schema: ValidatorSchema = fragment.try_into().unwrap();
1718 assert_eq!(
1719 schema.entity_types.iter().next().unwrap().1.attributes,
1720 Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
1721 );
1722 }
1723
1724 #[test]
1725 fn cross_fragment_type() {
1726 let fragment1: ValidatorSchemaFragment = serde_json::from_value::<SchemaFragment>(json!({
1727 "A": {
1728 "commonTypes": {
1729 "MyLong": {"type": "Long"}
1730 },
1731 "entityTypes": { },
1732 "actions": {}
1733 }
1734 }))
1735 .unwrap()
1736 .try_into()
1737 .unwrap();
1738 let fragment2: ValidatorSchemaFragment = serde_json::from_value::<SchemaFragment>(json!({
1739 "A": {
1740 "entityTypes": {
1741 "User": {
1742 "shape": {
1743 "type": "Record",
1744 "attributes": {
1745 "a": {"type": "MyLong"}
1746 }
1747 }
1748 }
1749 },
1750 "actions": {}
1751 }
1752 }))
1753 .unwrap()
1754 .try_into()
1755 .unwrap();
1756 let schema = ValidatorSchema::from_schema_fragments([fragment1, fragment2]).unwrap();
1757
1758 assert_eq!(
1759 schema.entity_types.iter().next().unwrap().1.attributes,
1760 Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
1761 );
1762 }
1763
1764 #[test]
1765 fn cross_fragment_duplicate_type() {
1766 let fragment1: ValidatorSchemaFragment = serde_json::from_value::<SchemaFragment>(json!({
1767 "A": {
1768 "commonTypes": {
1769 "MyLong": {"type": "Long"}
1770 },
1771 "entityTypes": {},
1772 "actions": {}
1773 }
1774 }))
1775 .unwrap()
1776 .try_into()
1777 .unwrap();
1778 let fragment2: ValidatorSchemaFragment = serde_json::from_value::<SchemaFragment>(json!({
1779 "A": {
1780 "commonTypes": {
1781 "MyLong": {"type": "Long"}
1782 },
1783 "entityTypes": {},
1784 "actions": {}
1785 }
1786 }))
1787 .unwrap()
1788 .try_into()
1789 .unwrap();
1790
1791 let schema = ValidatorSchema::from_schema_fragments([fragment1, fragment2]);
1792
1793 match schema {
1794 Err(SchemaError::DuplicateCommonType(s)) if s.contains("A::MyLong") => (),
1795 _ => panic!("should have errored because schema fragments have duplicate types"),
1796 };
1797 }
1798
1799 #[test]
1800 fn undeclared_type_in_attr() {
1801 let fragment: SchemaFragment = serde_json::from_value(json!({
1802 "": {
1803 "commonTypes": { },
1804 "entityTypes": {
1805 "User": {
1806 "shape": {
1807 "type": "Record",
1808 "attributes": {
1809 "a": {"type": "MyLong"}
1810 }
1811 }
1812 }
1813 },
1814 "actions": {}
1815 }
1816 }))
1817 .unwrap();
1818 match TryInto::<ValidatorSchema>::try_into(fragment) {
1819 Err(SchemaError::UndeclaredCommonTypes(_)) => (),
1820 s => panic!(
1821 "Expected Err(SchemaError::UndeclaredCommonType), got {:?}",
1822 s
1823 ),
1824 }
1825 }
1826
1827 #[test]
1828 fn undeclared_type_in_type_def() {
1829 let fragment: SchemaFragment = serde_json::from_value(json!({
1830 "": {
1831 "commonTypes": {
1832 "a": { "type": "b" }
1833 },
1834 "entityTypes": { },
1835 "actions": {}
1836 }
1837 }))
1838 .unwrap();
1839 match TryInto::<ValidatorSchema>::try_into(fragment) {
1840 Err(SchemaError::UndeclaredCommonTypes(_)) => (),
1841 s => panic!(
1842 "Expected Err(SchemaError::UndeclaredCommonType), got {:?}",
1843 s
1844 ),
1845 }
1846 }
1847
1848 #[test]
1849 fn shape_not_record() {
1850 let fragment: SchemaFragment = serde_json::from_value(json!({
1851 "": {
1852 "commonTypes": {
1853 "MyLong": { "type": "Long" }
1854 },
1855 "entityTypes": {
1856 "User": {
1857 "shape": { "type": "MyLong" }
1858 }
1859 },
1860 "actions": {}
1861 }
1862 }))
1863 .unwrap();
1864 match TryInto::<ValidatorSchema>::try_into(fragment) {
1865 Err(SchemaError::ContextOrShapeNotRecord(_)) => (),
1866 s => panic!(
1867 "Expected Err(SchemaError::ContextOrShapeNotRecord), got {:?}",
1868 s
1869 ),
1870 }
1871 }
1872
1873 #[test]
1877 fn counterexamples_from_cedar_134() {
1878 let bad1 = json!({
1880 "": {
1881 "entityTypes": {
1882 "User // comment": {
1883 "memberOfTypes": [
1884 "UserGroup"
1885 ]
1886 },
1887 "User": {
1888 "memberOfTypes": [
1889 "UserGroup"
1890 ]
1891 },
1892 "UserGroup": {}
1893 },
1894 "actions": {}
1895 }
1896 });
1897 let fragment = serde_json::from_value::<SchemaFragment>(bad1); assert!(fragment.is_err());
1900
1901 let bad2 = json!({
1903 "ABC :: //comment \n XYZ ": {
1904 "entityTypes": {
1905 "User": {
1906 "memberOfTypes": []
1907 }
1908 },
1909 "actions": {}
1910 }
1911 });
1912 let fragment = serde_json::from_value::<SchemaFragment>(bad2); assert!(fragment.is_err());
1915 }
1916
1917 #[test]
1918 fn simple_action_entity() {
1919 let src = json!(
1920 {
1921 "entityTypes": { },
1922 "actions": {
1923 "view_photo": { },
1924 }
1925 });
1926
1927 let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
1928 let schema: ValidatorSchema = schema_file.try_into().expect("Schema Error");
1929 let actions = schema.action_entities().expect("Entity Construct Error");
1930
1931 let action_uid = EntityUID::from_str("Action::\"view_photo\"").unwrap();
1932 let view_photo = actions.entity(&action_uid);
1933 assert_eq!(
1934 view_photo.unwrap(),
1935 &Entity::new_with_attr_partial_value(action_uid, HashMap::new(), HashSet::new())
1936 );
1937 }
1938
1939 #[test]
1940 fn action_entity_hierarchy() {
1941 let src = json!(
1942 {
1943 "entityTypes": { },
1944 "actions": {
1945 "read": {},
1946 "view": {
1947 "memberOf": [{"id": "read"}]
1948 },
1949 "view_photo": {
1950 "memberOf": [{"id": "view"}]
1951 },
1952 }
1953 });
1954
1955 let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
1956 let schema: ValidatorSchema = schema_file.try_into().expect("Schema Error");
1957 let actions = schema.action_entities().expect("Entity Construct Error");
1958
1959 let view_photo_uid = EntityUID::from_str("Action::\"view_photo\"").unwrap();
1960 let view_uid = EntityUID::from_str("Action::\"view\"").unwrap();
1961 let read_uid = EntityUID::from_str("Action::\"read\"").unwrap();
1962
1963 let view_photo_entity = actions.entity(&view_photo_uid);
1964 assert_eq!(
1965 view_photo_entity.unwrap(),
1966 &Entity::new_with_attr_partial_value(
1967 view_photo_uid,
1968 HashMap::new(),
1969 HashSet::from([view_uid.clone(), read_uid.clone()])
1970 )
1971 );
1972
1973 let view_entity = actions.entity(&view_uid);
1974 assert_eq!(
1975 view_entity.unwrap(),
1976 &Entity::new_with_attr_partial_value(
1977 view_uid,
1978 HashMap::new(),
1979 HashSet::from([read_uid.clone()])
1980 )
1981 );
1982
1983 let read_entity = actions.entity(&read_uid);
1984 assert_eq!(
1985 read_entity.unwrap(),
1986 &Entity::new_with_attr_partial_value(read_uid, HashMap::new(), HashSet::new())
1987 );
1988 }
1989
1990 #[test]
1991 fn action_entity_attribute() {
1992 let src = json!(
1993 {
1994 "entityTypes": { },
1995 "actions": {
1996 "view_photo": {
1997 "attributes": { "attr": "foo" }
1998 },
1999 }
2000 });
2001
2002 let schema_file: NamespaceDefinitionWithActionAttributes =
2003 serde_json::from_value(src).expect("Parse Error");
2004 let schema: ValidatorSchema = schema_file.try_into().expect("Schema Error");
2005 let actions = schema.action_entities().expect("Entity Construct Error");
2006
2007 let action_uid = EntityUID::from_str("Action::\"view_photo\"").unwrap();
2008 let view_photo = actions.entity(&action_uid);
2009 assert_eq!(
2010 view_photo.unwrap(),
2011 &Entity::new(
2012 action_uid,
2013 HashMap::from([("attr".into(), RestrictedExpr::val("foo"))]),
2014 HashSet::new(),
2015 &Extensions::none(),
2016 )
2017 .unwrap(),
2018 );
2019 }
2020
2021 #[test]
2022 fn test_action_namespace_inference_multi_success() {
2023 let src = json!({
2024 "Foo" : {
2025 "entityTypes" : {},
2026 "actions" : {
2027 "read" : {}
2028 }
2029 },
2030 "ExampleCo::Personnel" : {
2031 "entityTypes" : {},
2032 "actions" : {
2033 "viewPhoto" : {
2034 "memberOf" : [
2035 {
2036 "id" : "read",
2037 "type" : "Foo::Action"
2038 }
2039 ]
2040 }
2041 }
2042 },
2043 });
2044 let schema_fragment =
2045 serde_json::from_value::<SchemaFragment>(src).expect("Failed to parse schema");
2046 let schema: ValidatorSchema = schema_fragment.try_into().expect("Schema should construct");
2047 let view_photo = schema
2048 .action_entities_iter()
2049 .find(|e| e.uid() == &r#"ExampleCo::Personnel::Action::"viewPhoto""#.parse().unwrap())
2050 .unwrap();
2051 let ancestors = view_photo.ancestors().collect::<Vec<_>>();
2052 let read = ancestors[0];
2053 assert_eq!(read.eid().to_string(), "read");
2054 assert_eq!(read.entity_type().to_string(), "Foo::Action");
2055 }
2056
2057 #[test]
2058 fn test_action_namespace_inference_multi() {
2059 let src = json!({
2060 "ExampleCo::Personnel::Foo" : {
2061 "entityTypes" : {},
2062 "actions" : {
2063 "read" : {}
2064 }
2065 },
2066 "ExampleCo::Personnel" : {
2067 "entityTypes" : {},
2068 "actions" : {
2069 "viewPhoto" : {
2070 "memberOf" : [
2071 {
2072 "id" : "read",
2073 "type" : "Foo::Action"
2074 }
2075 ]
2076 }
2077 }
2078 },
2079 });
2080 let schema_fragment =
2081 serde_json::from_value::<SchemaFragment>(src).expect("Failed to parse schema");
2082 let schema: std::result::Result<ValidatorSchema, _> = schema_fragment.try_into();
2083 schema.expect_err("Schema should fail to construct as the normalization rules treat any qualification as starting from the root");
2084 }
2085
2086 #[test]
2087 fn test_action_namespace_inference() {
2088 let src = json!({
2089 "ExampleCo::Personnel" : {
2090 "entityTypes" : { },
2091 "actions" : {
2092 "read" : {},
2093 "viewPhoto" : {
2094 "memberOf" : [
2095 {
2096 "id" : "read",
2097 "type" : "Action"
2098 }
2099 ]
2100 }
2101 }
2102 }
2103 });
2104 let schema_fragment =
2105 serde_json::from_value::<SchemaFragment>(src).expect("Failed to parse schema");
2106 let schema: ValidatorSchema = schema_fragment.try_into().unwrap();
2107 let view_photo = schema
2108 .action_entities_iter()
2109 .find(|e| e.uid() == &r#"ExampleCo::Personnel::Action::"viewPhoto""#.parse().unwrap())
2110 .unwrap();
2111 let ancestors = view_photo.ancestors().collect::<Vec<_>>();
2112 let read = ancestors[0];
2113 assert_eq!(read.eid().to_string(), "read");
2114 assert_eq!(
2115 read.entity_type().to_string(),
2116 "ExampleCo::Personnel::Action"
2117 );
2118 }
2119
2120 #[test]
2121 fn qualified_undeclared_common_types() {
2122 let src = json!(
2123 {
2124 "Demo": {
2125 "entityTypes": {
2126 "User": {
2127 "memberOfTypes": [],
2128 "shape": {
2129 "type": "Record",
2130 "attributes": {
2131 "id": { "type": "id" },
2132 }
2133 }
2134 }
2135 },
2136 "actions": {}
2137 },
2138 "": {
2139 "commonTypes": {
2140 "id": {
2141 "type": "String"
2142 },
2143 },
2144 "entityTypes": {},
2145 "actions": {}
2146 }
2147 }
2148 );
2149 let schema = ValidatorSchema::from_json_value(src, Extensions::all_available());
2150 assert_matches!(schema, Err(SchemaError::UndeclaredCommonTypes(types)) =>
2151 assert_eq!(types, HashSet::from(["Demo::id".to_string()])));
2152 }
2153
2154 #[test]
2155 fn qualified_undeclared_common_types2() {
2156 let src = json!(
2157 {
2158 "Demo": {
2159 "entityTypes": {
2160 "User": {
2161 "memberOfTypes": [],
2162 "shape": {
2163 "type": "Record",
2164 "attributes": {
2165 "id": { "type": "Demo::id" },
2166 }
2167 }
2168 }
2169 },
2170 "actions": {}
2171 },
2172 "": {
2173 "commonTypes": {
2174 "id": {
2175 "type": "String"
2176 },
2177 },
2178 "entityTypes": {},
2179 "actions": {}
2180 }
2181 }
2182 );
2183 let schema = ValidatorSchema::from_json_value(src, Extensions::all_available());
2184 assert_matches!(schema, Err(SchemaError::UndeclaredCommonTypes(types)) =>
2185 assert_eq!(types, HashSet::from(["Demo::id".to_string()])));
2186 }
2187}
2188
2189#[cfg(test)]
2190mod test_resolver {
2191 use std::collections::HashMap;
2192
2193 use cedar_policy_core::ast::Name;
2194 use cool_asserts::assert_matches;
2195
2196 use super::CommonTypeResolver;
2197 use crate::{types::Type, SchemaError, SchemaFragment, ValidatorSchemaFragment};
2198
2199 fn resolve(schema: SchemaFragment) -> Result<HashMap<Name, Type>, SchemaError> {
2200 let schema: ValidatorSchemaFragment = schema.try_into().unwrap();
2201 let mut type_defs = HashMap::new();
2202 for def in schema.0 {
2203 type_defs.extend(def.type_defs.type_defs.into_iter());
2204 }
2205 let resolver = CommonTypeResolver::new(&type_defs);
2206 resolver.resolve()
2207 }
2208
2209 #[test]
2210 fn test_simple() {
2211 let schema = serde_json::from_value::<SchemaFragment>(serde_json::json!(
2212 {
2213 "": {
2214 "entityTypes": {},
2215 "actions": {},
2216 "commonTypes": {
2217 "a" : {
2218 "type": "b"
2219 },
2220 "b": {
2221 "type": "Boolean"
2222 }
2223 }
2224 }
2225 }
2226 ))
2227 .unwrap();
2228 let res = resolve(schema).unwrap();
2229 assert_eq!(
2230 res,
2231 HashMap::from_iter([
2232 ("a".parse().unwrap(), Type::primitive_boolean()),
2233 ("b".parse().unwrap(), Type::primitive_boolean())
2234 ])
2235 );
2236
2237 let schema = serde_json::from_value::<SchemaFragment>(serde_json::json!(
2238 {
2239 "": {
2240 "entityTypes": {},
2241 "actions": {},
2242 "commonTypes": {
2243 "a" : {
2244 "type": "b"
2245 },
2246 "b": {
2247 "type": "c"
2248 },
2249 "c": {
2250 "type": "Boolean"
2251 }
2252 }
2253 }
2254 }
2255 ))
2256 .unwrap();
2257 let res = resolve(schema).unwrap();
2258 assert_eq!(
2259 res,
2260 HashMap::from_iter([
2261 ("a".parse().unwrap(), Type::primitive_boolean()),
2262 ("b".parse().unwrap(), Type::primitive_boolean()),
2263 ("c".parse().unwrap(), Type::primitive_boolean())
2264 ])
2265 );
2266 }
2267
2268 #[test]
2269 fn test_set() {
2270 let schema = serde_json::from_value::<SchemaFragment>(serde_json::json!(
2271 {
2272 "": {
2273 "entityTypes": {},
2274 "actions": {},
2275 "commonTypes": {
2276 "a" : {
2277 "type": "Set",
2278 "element": {
2279 "type": "b"
2280 }
2281 },
2282 "b": {
2283 "type": "Boolean"
2284 }
2285 }
2286 }
2287 }
2288 ))
2289 .unwrap();
2290 let res = resolve(schema).unwrap();
2291 assert_eq!(
2292 res,
2293 HashMap::from_iter([
2294 ("a".parse().unwrap(), Type::set(Type::primitive_boolean())),
2295 ("b".parse().unwrap(), Type::primitive_boolean())
2296 ])
2297 );
2298 }
2299
2300 #[test]
2301 fn test_record() {
2302 let schema = serde_json::from_value::<SchemaFragment>(serde_json::json!(
2303 {
2304 "": {
2305 "entityTypes": {},
2306 "actions": {},
2307 "commonTypes": {
2308 "a" : {
2309 "type": "Record",
2310 "attributes": {
2311 "foo": {
2312 "type": "b"
2313 }
2314 }
2315 },
2316 "b": {
2317 "type": "Boolean"
2318 }
2319 }
2320 }
2321 }
2322 ))
2323 .unwrap();
2324 let res = resolve(schema).unwrap();
2325 assert_eq!(
2326 res,
2327 HashMap::from_iter([
2328 (
2329 "a".parse().unwrap(),
2330 Type::record_with_required_attributes(
2331 std::iter::once(("foo".into(), Type::primitive_boolean())),
2332 crate::types::OpenTag::ClosedAttributes
2333 )
2334 ),
2335 ("b".parse().unwrap(), Type::primitive_boolean())
2336 ])
2337 );
2338 }
2339
2340 #[test]
2341 fn test_names() {
2342 let schema = serde_json::from_value::<SchemaFragment>(serde_json::json!(
2343 {
2344 "A": {
2345 "entityTypes": {},
2346 "actions": {},
2347 "commonTypes": {
2348 "a" : {
2349 "type": "B::a"
2350 }
2351 }
2352 },
2353 "B": {
2354 "entityTypes": {},
2355 "actions": {},
2356 "commonTypes": {
2357 "a" : {
2358 "type": "Boolean"
2359 }
2360 }
2361 }
2362 }
2363 ))
2364 .unwrap();
2365 let res = resolve(schema).unwrap();
2366 assert_eq!(
2367 res,
2368 HashMap::from_iter([
2369 ("A::a".parse().unwrap(), Type::primitive_boolean()),
2370 ("B::a".parse().unwrap(), Type::primitive_boolean())
2371 ])
2372 );
2373 }
2374
2375 #[test]
2376 fn test_cycles() {
2377 let schema = serde_json::from_value::<SchemaFragment>(serde_json::json!(
2379 {
2380 "": {
2381 "entityTypes": {},
2382 "actions": {},
2383 "commonTypes": {
2384 "a" : {
2385 "type": "a"
2386 }
2387 }
2388 }
2389 }
2390 ))
2391 .unwrap();
2392 let res = resolve(schema);
2393 assert_matches!(res, Err(SchemaError::CycleInCommonTypeReferences(_)));
2394
2395 let schema = serde_json::from_value::<SchemaFragment>(serde_json::json!(
2397 {
2398 "": {
2399 "entityTypes": {},
2400 "actions": {},
2401 "commonTypes": {
2402 "a" : {
2403 "type": "b"
2404 },
2405 "b" : {
2406 "type": "a"
2407 }
2408 }
2409 }
2410 }
2411 ))
2412 .unwrap();
2413 let res = resolve(schema);
2414 assert_matches!(res, Err(SchemaError::CycleInCommonTypeReferences(_)));
2415
2416 let schema = serde_json::from_value::<SchemaFragment>(serde_json::json!(
2418 {
2419 "": {
2420 "entityTypes": {},
2421 "actions": {},
2422 "commonTypes": {
2423 "a" : {
2424 "type": "b"
2425 },
2426 "b" : {
2427 "type": "c"
2428 },
2429 "c" : {
2430 "type": "a"
2431 }
2432 }
2433 }
2434 }
2435 ))
2436 .unwrap();
2437 let res = resolve(schema);
2438 assert_matches!(res, Err(SchemaError::CycleInCommonTypeReferences(_)));
2439
2440 let schema = serde_json::from_value::<SchemaFragment>(serde_json::json!(
2442 {
2443 "A": {
2444 "entityTypes": {},
2445 "actions": {},
2446 "commonTypes": {
2447 "a" : {
2448 "type": "B::a"
2449 }
2450 }
2451 },
2452 "B": {
2453 "entityTypes": {},
2454 "actions": {},
2455 "commonTypes": {
2456 "a" : {
2457 "type": "A::a"
2458 }
2459 }
2460 }
2461 }
2462 ))
2463 .unwrap();
2464 let res = resolve(schema);
2465 assert_matches!(res, Err(SchemaError::CycleInCommonTypeReferences(_)));
2466
2467 let schema = serde_json::from_value::<SchemaFragment>(serde_json::json!(
2469 {
2470 "A": {
2471 "entityTypes": {},
2472 "actions": {},
2473 "commonTypes": {
2474 "a" : {
2475 "type": "B::a"
2476 }
2477 }
2478 },
2479 "B": {
2480 "entityTypes": {},
2481 "actions": {},
2482 "commonTypes": {
2483 "a" : {
2484 "type": "C::a"
2485 }
2486 }
2487 },
2488 "C": {
2489 "entityTypes": {},
2490 "actions": {},
2491 "commonTypes": {
2492 "a" : {
2493 "type": "A::a"
2494 }
2495 }
2496 }
2497 }
2498 ))
2499 .unwrap();
2500 let res = resolve(schema);
2501 assert_matches!(res, Err(SchemaError::CycleInCommonTypeReferences(_)));
2502
2503 let schema = serde_json::from_value::<SchemaFragment>(serde_json::json!(
2505 {
2506 "A": {
2507 "entityTypes": {},
2508 "actions": {},
2509 "commonTypes": {
2510 "a" : {
2511 "type": "B::a"
2512 }
2513 }
2514 },
2515 "B": {
2516 "entityTypes": {},
2517 "actions": {},
2518 "commonTypes": {
2519 "a" : {
2520 "type": "c"
2521 },
2522 "c": {
2523 "type": "A::a"
2524 }
2525 }
2526 }
2527 }
2528 ))
2529 .unwrap();
2530 let res = resolve(schema);
2531 assert_matches!(res, Err(SchemaError::CycleInCommonTypeReferences(_)));
2532 }
2533}