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 principals(&self) -> impl Iterator<Item = &EntityType> {
154 self.action_ids
155 .values()
156 .flat_map(ValidatorActionId::principals)
157 }
158
159 pub fn resources(&self) -> impl Iterator<Item = &EntityType> {
161 self.action_ids
162 .values()
163 .flat_map(ValidatorActionId::resources)
164 }
165
166 pub fn principals_for_action(
172 &self,
173 action: &EntityUID,
174 ) -> Option<impl Iterator<Item = &EntityType>> {
175 self.action_ids
176 .get(action)
177 .map(ValidatorActionId::principals)
178 }
179
180 pub fn resources_for_action(
186 &self,
187 action: &EntityUID,
188 ) -> Option<impl Iterator<Item = &EntityType>> {
189 self.action_ids
190 .get(action)
191 .map(ValidatorActionId::resources)
192 }
193
194 pub fn ancestors<'a>(&'a self, ty: &'a Name) -> Option<impl Iterator<Item = &Name> + 'a> {
200 if self.entity_types.contains_key(ty) {
201 Some(self.entity_types.values().filter_map(|ety| {
202 if ety.descendants.contains(ty) {
203 Some(&ety.name)
204 } else {
205 None
206 }
207 }))
208 } else {
209 None
210 }
211 }
212
213 pub fn action_groups(&self) -> impl Iterator<Item = &EntityUID> {
215 self.action_ids.values().filter_map(|action| {
216 if action.descendants.is_empty() {
217 None
218 } else {
219 Some(&action.name)
220 }
221 })
222 }
223
224 pub fn actions(&self) -> impl Iterator<Item = &EntityUID> {
226 self.action_ids.keys()
227 }
228
229 pub fn empty() -> ValidatorSchema {
232 Self {
233 entity_types: HashMap::new(),
234 action_ids: HashMap::new(),
235 }
236 }
237
238 pub fn from_json_value(json: serde_json::Value, extensions: Extensions<'_>) -> Result<Self> {
241 Self::from_schema_file(
242 SchemaFragment::from_json_value(json)?,
243 ActionBehavior::default(),
244 extensions,
245 )
246 }
247
248 pub fn from_file(file: impl std::io::Read, extensions: Extensions<'_>) -> Result<Self> {
250 Self::from_schema_file(
251 SchemaFragment::from_file(file)?,
252 ActionBehavior::default(),
253 extensions,
254 )
255 }
256
257 pub fn from_file_natural(
258 r: impl std::io::Read,
259 extensions: Extensions<'_>,
260 ) -> std::result::Result<(Self, impl Iterator<Item = SchemaWarning>), HumanSchemaError> {
261 let (fragment, warnings) = SchemaFragment::from_file_natural(r)?;
262 let schema_and_warnings =
263 Self::from_schema_file(fragment, ActionBehavior::default(), extensions)
264 .map(|schema| (schema, warnings))?;
265 Ok(schema_and_warnings)
266 }
267
268 pub fn from_str_natural(
269 src: &str,
270 extensions: Extensions<'_>,
271 ) -> std::result::Result<(Self, impl Iterator<Item = SchemaWarning>), HumanSchemaError> {
272 let (fragment, warnings) = SchemaFragment::from_str_natural(src)?;
273 let schema_and_warnings =
274 Self::from_schema_file(fragment, ActionBehavior::default(), extensions)
275 .map(|schema| (schema, warnings))?;
276 Ok(schema_and_warnings)
277 }
278
279 pub fn from_schema_file(
280 schema_file: SchemaFragment,
281 action_behavior: ActionBehavior,
282 extensions: Extensions<'_>,
283 ) -> Result<ValidatorSchema> {
284 Self::from_schema_fragments([ValidatorSchemaFragment::from_schema_fragment(
285 schema_file,
286 action_behavior,
287 extensions,
288 )?])
289 }
290
291 pub fn from_schema_fragments(
293 fragments: impl IntoIterator<Item = ValidatorSchemaFragment>,
294 ) -> Result<ValidatorSchema> {
295 let mut type_defs = HashMap::new();
296 let mut entity_type_fragments = HashMap::new();
297 let mut action_fragments = HashMap::new();
298
299 for ns_def in fragments.into_iter().flat_map(|f| f.0.into_iter()) {
300 for (name, ty) in ns_def.type_defs.type_defs {
306 match type_defs.entry(name) {
307 Entry::Vacant(v) => v.insert(ty),
308 Entry::Occupied(o) => {
309 return Err(SchemaError::DuplicateCommonType(o.key().to_string()));
310 }
311 };
312 }
313
314 for (name, entity_type) in ns_def.entity_types.entity_types {
315 match entity_type_fragments.entry(name) {
316 Entry::Vacant(v) => v.insert(entity_type),
317 Entry::Occupied(o) => {
318 return Err(SchemaError::DuplicateEntityType(o.key().to_string()))
319 }
320 };
321 }
322
323 for (action_euid, action) in ns_def.actions.actions {
324 match action_fragments.entry(action_euid) {
325 Entry::Vacant(v) => v.insert(action),
326 Entry::Occupied(o) => {
327 return Err(SchemaError::DuplicateAction(o.key().to_string()))
328 }
329 };
330 }
331 }
332
333 let resolver = CommonTypeResolver::new(&type_defs);
334 let type_defs = resolver.resolve()?;
335
336 let mut entity_children = HashMap::new();
339 for (name, entity_type) in entity_type_fragments.iter() {
340 for parent in entity_type.parents.iter() {
341 entity_children
342 .entry(parent.clone())
343 .or_insert_with(HashSet::new)
344 .insert(name.clone());
345 }
346 }
347
348 let mut entity_types = entity_type_fragments
349 .into_iter()
350 .map(|(name, entity_type)| -> Result<_> {
351 let descendants = entity_children.remove(&name).unwrap_or_default();
361 let (attributes, open_attributes) = Self::record_attributes_or_none(
362 entity_type.attributes.resolve_type_defs(&type_defs)?,
363 )
364 .ok_or(SchemaError::ContextOrShapeNotRecord(
365 ContextOrShape::EntityTypeShape(name.clone()),
366 ))?;
367 Ok((
368 name.clone(),
369 ValidatorEntityType {
370 name,
371 descendants,
372 attributes,
373 open_attributes,
374 },
375 ))
376 })
377 .collect::<Result<HashMap<_, _>>>()?;
378
379 let mut action_children = HashMap::new();
380 for (euid, action) in action_fragments.iter() {
381 for parent in action.parents.iter() {
382 action_children
383 .entry(parent.clone())
384 .or_insert_with(HashSet::new)
385 .insert(euid.clone());
386 }
387 }
388 let mut action_ids = action_fragments
389 .into_iter()
390 .map(|(name, action)| -> Result<_> {
391 let descendants = action_children.remove(&name).unwrap_or_default();
392 let (context, open_context_attributes) =
393 Self::record_attributes_or_none(action.context.resolve_type_defs(&type_defs)?)
394 .ok_or(SchemaError::ContextOrShapeNotRecord(
395 ContextOrShape::ActionContext(name.clone()),
396 ))?;
397 Ok((
398 name.clone(),
399 ValidatorActionId {
400 name,
401 applies_to: action.applies_to,
402 descendants,
403 context: Type::record_with_attributes(
404 context.attrs,
405 open_context_attributes,
406 ),
407 attribute_types: action.attribute_types,
408 attributes: action.attributes,
409 },
410 ))
411 })
412 .collect::<Result<HashMap<_, _>>>()?;
413
414 compute_tc(&mut entity_types, false)?;
417 compute_tc(&mut action_ids, true)?;
420
421 Self::check_for_undeclared(
428 &entity_types,
429 entity_children.into_keys(),
430 &action_ids,
431 action_children.into_keys(),
432 )?;
433
434 Ok(ValidatorSchema {
435 entity_types,
436 action_ids,
437 })
438 }
439
440 fn check_for_undeclared(
445 entity_types: &HashMap<Name, ValidatorEntityType>,
446 undeclared_parent_entities: impl IntoIterator<Item = Name>,
447 action_ids: &HashMap<EntityUID, ValidatorActionId>,
448 undeclared_parent_actions: impl IntoIterator<Item = EntityUID>,
449 ) -> Result<()> {
450 let mut undeclared_e = undeclared_parent_entities
455 .into_iter()
456 .map(|n| n.to_string())
457 .collect::<HashSet<_>>();
458 for entity_type in entity_types.values() {
464 for (_, attr_typ) in entity_type.attributes() {
465 Self::check_undeclared_in_type(
466 &attr_typ.attr_type,
467 entity_types,
468 &mut undeclared_e,
469 );
470 }
471 }
472
473 let undeclared_a = undeclared_parent_actions
475 .into_iter()
476 .map(|n| n.to_string())
477 .collect::<HashSet<_>>();
478 for action in action_ids.values() {
482 Self::check_undeclared_in_type(&action.context, entity_types, &mut undeclared_e);
483
484 for p_entity in action.applies_to.applicable_principal_types() {
485 match p_entity {
486 EntityType::Specified(p_entity) => {
487 if !entity_types.contains_key(&p_entity) {
488 undeclared_e.insert(p_entity.to_string());
489 }
490 }
491 EntityType::Unspecified => (),
492 }
493 }
494
495 for r_entity in action.applies_to.applicable_resource_types() {
496 match r_entity {
497 EntityType::Specified(r_entity) => {
498 if !entity_types.contains_key(&r_entity) {
499 undeclared_e.insert(r_entity.to_string());
500 }
501 }
502 EntityType::Unspecified => (),
503 }
504 }
505 }
506 if !undeclared_e.is_empty() {
507 return Err(SchemaError::UndeclaredEntityTypes(undeclared_e));
508 }
509 if !undeclared_a.is_empty() {
510 return Err(SchemaError::UndeclaredActions(undeclared_a));
511 }
512
513 Ok(())
514 }
515
516 fn record_attributes_or_none(ty: Type) -> Option<(Attributes, OpenTag)> {
517 match ty {
518 Type::EntityOrRecord(EntityRecordKind::Record {
519 attrs,
520 open_attributes,
521 }) => Some((attrs, open_attributes)),
522 _ => None,
523 }
524 }
525
526 fn check_undeclared_in_type(
530 ty: &Type,
531 entity_types: &HashMap<Name, ValidatorEntityType>,
532 undeclared_types: &mut HashSet<String>,
533 ) {
534 match ty {
535 Type::EntityOrRecord(EntityRecordKind::Entity(lub)) => {
536 for name in lub.iter() {
537 if !entity_types.contains_key(name) {
538 undeclared_types.insert(name.to_string());
539 }
540 }
541 }
542
543 Type::EntityOrRecord(EntityRecordKind::Record { attrs, .. }) => {
544 for (_, attr_ty) in attrs.iter() {
545 Self::check_undeclared_in_type(
546 &attr_ty.attr_type,
547 entity_types,
548 undeclared_types,
549 );
550 }
551 }
552
553 Type::Set {
554 element_type: Some(element_type),
555 } => Self::check_undeclared_in_type(element_type, entity_types, undeclared_types),
556
557 _ => (),
558 }
559 }
560
561 pub fn get_action_id(&self, action_id: &EntityUID) -> Option<&ValidatorActionId> {
563 self.action_ids.get(action_id)
564 }
565
566 pub fn get_entity_type<'a>(&'a self, entity_type_id: &Name) -> Option<&'a ValidatorEntityType> {
568 self.entity_types.get(entity_type_id)
569 }
570
571 pub(crate) fn is_known_action_id(&self, action_id: &EntityUID) -> bool {
573 self.action_ids.contains_key(action_id)
574 }
575
576 pub(crate) fn is_known_entity_type(&self, entity_type: &Name) -> bool {
578 is_action_entity_type(entity_type) || self.entity_types.contains_key(entity_type)
579 }
580
581 pub(crate) fn euid_has_known_entity_type(&self, euid: &EntityUID) -> bool {
586 match euid.entity_type() {
587 EntityType::Specified(ety) => self.is_known_entity_type(ety),
588 EntityType::Unspecified => true,
589 }
590 }
591
592 pub(crate) fn known_action_ids(&self) -> impl Iterator<Item = &EntityUID> {
594 self.action_ids.keys()
595 }
596
597 pub(crate) fn known_entity_types(&self) -> impl Iterator<Item = &Name> {
599 self.entity_types.keys()
600 }
601
602 pub fn entity_types(&self) -> impl Iterator<Item = (&Name, &ValidatorEntityType)> {
604 self.entity_types.iter()
605 }
606
607 pub(crate) fn get_entity_types_in<'a>(&'a self, entity: &'a EntityUID) -> Vec<&Name> {
613 match entity.entity_type() {
614 EntityType::Specified(ety) => {
615 let mut descendants = self
616 .get_entity_type(ety)
617 .map(|v_ety| v_ety.descendants.iter().collect::<Vec<_>>())
618 .unwrap_or_default();
619 descendants.push(ety);
620 descendants
621 }
622 EntityType::Unspecified => Vec::new(),
623 }
624 }
625
626 pub(crate) fn get_entity_types_in_set<'a>(
630 &'a self,
631 euids: impl IntoIterator<Item = &'a EntityUID> + 'a,
632 ) -> impl Iterator<Item = &Name> {
633 euids.into_iter().flat_map(|e| self.get_entity_types_in(e))
634 }
635
636 pub(crate) fn get_actions_in_set<'a>(
640 &'a self,
641 euids: impl IntoIterator<Item = &'a EntityUID> + 'a,
642 ) -> Option<Vec<&'a EntityUID>> {
643 euids
644 .into_iter()
645 .map(|e| {
646 self.get_action_id(e).map(|action| {
647 action
648 .descendants
649 .iter()
650 .chain(std::iter::once(&action.name))
651 })
652 })
653 .collect::<Option<Vec<_>>>()
654 .map(|v| v.into_iter().flatten().collect::<Vec<_>>())
655 }
656
657 pub fn context_type(&self, action: &EntityUID) -> Option<Type> {
662 self.get_action_id(action)
665 .map(ValidatorActionId::context_type)
666 }
667
668 pub(crate) fn action_entities_iter(
671 &self,
672 ) -> impl Iterator<Item = cedar_policy_core::ast::Entity> + '_ {
673 let mut action_ancestors: HashMap<&EntityUID, HashSet<EntityUID>> = HashMap::new();
679 for (action_euid, action_def) in &self.action_ids {
680 for descendant in &action_def.descendants {
681 action_ancestors
682 .entry(descendant)
683 .or_default()
684 .insert(action_euid.clone());
685 }
686 }
687 self.action_ids.iter().map(move |(action_id, action)| {
688 Entity::new_with_attr_partial_value_serialized_as_expr(
689 action_id.clone(),
690 action.attributes.clone(),
691 action_ancestors.remove(action_id).unwrap_or_default(),
692 )
693 })
694 }
695
696 pub fn action_entities(&self) -> std::result::Result<Entities, EntitiesError> {
698 let extensions = Extensions::all_available();
699 Entities::from_entities(
700 self.action_entities_iter(),
701 None::<&cedar_policy_core::entities::NoEntitiesSchema>, TCComputation::AssumeAlreadyComputed,
703 extensions,
704 )
705 .map_err(Into::into)
706 }
707}
708
709#[derive(Debug, Clone, Deserialize)]
712#[serde(transparent)]
713pub(crate) struct NamespaceDefinitionWithActionAttributes(pub(crate) NamespaceDefinition);
714
715impl TryInto<ValidatorSchema> for NamespaceDefinitionWithActionAttributes {
716 type Error = SchemaError;
717
718 fn try_into(self) -> Result<ValidatorSchema> {
719 ValidatorSchema::from_schema_fragments([ValidatorSchemaFragment::from_namespaces([
720 ValidatorNamespaceDef::from_namespace_definition(
721 None,
722 self.0,
723 crate::ActionBehavior::PermitAttributes,
724 Extensions::all_available(),
725 )?,
726 ])])
727 }
728}
729
730#[derive(Debug)]
732struct CommonTypeResolver<'a> {
733 type_defs: &'a HashMap<Name, SchemaType>,
735 graph: HashMap<Name, HashSet<Name>>,
739}
740
741impl<'a> CommonTypeResolver<'a> {
742 fn new(type_defs: &'a HashMap<Name, SchemaType>) -> Self {
744 let mut graph = HashMap::new();
745 for (name, ty) in type_defs {
746 graph.insert(
747 name.clone(),
748 HashSet::from_iter(ty.common_type_references()),
749 );
750 }
751 Self { type_defs, graph }
752 }
753
754 fn topo_sort(&self) -> std::result::Result<Vec<Name>, Name> {
761 let mut indegrees: HashMap<&Name, usize> = HashMap::new();
765 for (ty_name, deps) in self.graph.iter() {
766 indegrees.entry(ty_name).or_insert(0);
768 for dep in deps {
769 match indegrees.entry(dep) {
770 std::collections::hash_map::Entry::Occupied(mut o) => {
771 o.insert(o.get() + 1);
772 }
773 std::collections::hash_map::Entry::Vacant(v) => {
774 v.insert(1);
775 }
776 }
777 }
778 }
779
780 let mut work_set: HashSet<&Name> = HashSet::new();
782 let mut res: Vec<Name> = Vec::new();
783
784 for (name, degree) in indegrees.iter() {
786 let name = *name;
787 if *degree == 0 {
788 work_set.insert(name);
789 if self.graph.contains_key(name) {
791 res.push(name.clone());
792 }
793 }
794 }
795
796 while let Some(name) = work_set.iter().next().cloned() {
798 work_set.remove(name);
799 if let Some(deps) = self.graph.get(name) {
800 for dep in deps {
801 if let Some(degree) = indegrees.get_mut(dep) {
802 *degree -= 1;
812 if *degree == 0 {
813 work_set.insert(dep);
814 if self.graph.contains_key(dep) {
815 res.push(dep.clone());
816 }
817 }
818 }
819 }
820 }
821 }
822
823 let mut set: HashSet<&Name> = HashSet::from_iter(self.graph.keys().clone());
826 for name in res.iter() {
827 set.remove(name);
828 }
829
830 if let Some(cycle) = set.into_iter().next() {
831 Err(cycle.clone())
832 } else {
833 res.reverse();
836 Ok(res)
837 }
838 }
839
840 fn resolve_type(
842 resolve_table: &HashMap<&Name, SchemaType>,
843 ty: SchemaType,
844 ) -> Result<SchemaType> {
845 match ty {
846 SchemaType::TypeDef { type_name } => resolve_table
847 .get(&type_name)
848 .ok_or(SchemaError::UndeclaredCommonTypes(HashSet::from_iter(
849 std::iter::once(type_name.to_string()),
850 )))
851 .cloned(),
852 SchemaType::Type(SchemaTypeVariant::Set { element }) => {
853 Ok(SchemaType::Type(SchemaTypeVariant::Set {
854 element: Box::new(Self::resolve_type(resolve_table, *element)?),
855 }))
856 }
857 SchemaType::Type(SchemaTypeVariant::Record {
858 attributes,
859 additional_attributes,
860 }) => Ok(SchemaType::Type(SchemaTypeVariant::Record {
861 attributes: BTreeMap::from_iter(
862 attributes
863 .into_iter()
864 .map(|(attr, attr_ty)| {
865 Ok((
866 attr,
867 TypeOfAttribute {
868 required: attr_ty.required,
869 ty: Self::resolve_type(resolve_table, attr_ty.ty)?,
870 },
871 ))
872 })
873 .collect::<Result<Vec<(_, _)>>>()?,
874 ),
875 additional_attributes,
876 })),
877 _ => Ok(ty),
878 }
879 }
880
881 fn resolve(&self) -> Result<HashMap<Name, Type>> {
883 let sorted_names = self
884 .topo_sort()
885 .map_err(SchemaError::CycleInCommonTypeReferences)?;
886
887 let mut resolve_table = HashMap::new();
888 let mut tys = HashMap::new();
889
890 for name in sorted_names.iter() {
891 let ns: Option<Name> = if name.is_unqualified() {
892 None
893 } else {
894 #[allow(clippy::unwrap_used)]
896 Some(name.namespace().parse().unwrap())
897 };
898 #[allow(clippy::unwrap_used)]
900 let ty = self.type_defs.get(name).unwrap();
901 let substituted_ty = Self::resolve_type(&resolve_table, ty.clone())?;
902 resolve_table.insert(name, substituted_ty.clone());
903 tys.insert(
904 name.clone(),
905 ValidatorNamespaceDef::try_schema_type_into_validator_type(
906 ns.as_ref(),
907 substituted_ty,
908 )?
909 .resolve_type_defs(&HashMap::new())?,
910 );
911 }
912
913 Ok(tys)
914 }
915}
916
917#[allow(clippy::panic)]
919#[allow(clippy::indexing_slicing)]
921#[cfg(test)]
922mod test {
923 use std::{collections::BTreeMap, str::FromStr};
924
925 use crate::types::Type;
926 use crate::{SchemaType, SchemaTypeVariant};
927
928 use cedar_policy_core::ast::RestrictedExpr;
929 use cool_asserts::assert_matches;
930 use serde_json::json;
931
932 use super::*;
933
934 #[test]
936 fn test_from_schema_file() {
937 let src = json!(
938 {
939 "entityTypes": {
940 "User": {
941 "memberOfTypes": [ "Group" ]
942 },
943 "Group": {
944 "memberOfTypes": []
945 },
946 "Photo": {
947 "memberOfTypes": [ "Album" ]
948 },
949 "Album": {
950 "memberOfTypes": []
951 }
952 },
953 "actions": {
954 "view_photo": {
955 "appliesTo": {
956 "principalTypes": ["User", "Group"],
957 "resourceTypes": ["Photo"]
958 }
959 }
960 }
961 });
962 let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
963 let schema: Result<ValidatorSchema> = schema_file.try_into();
964 assert!(schema.is_ok());
965 }
966
967 #[test]
969 fn test_from_schema_file_duplicate_entity() {
970 let src = r#"
973 {"": {
974 "entityTypes": {
975 "User": {
976 "memberOfTypes": [ "Group" ]
977 },
978 "Group": {
979 "memberOfTypes": []
980 },
981 "Photo": {
982 "memberOfTypes": [ "Album" ]
983 },
984 "Photo": {
985 "memberOfTypes": []
986 }
987 },
988 "actions": {
989 "view_photo": {
990 "memberOf": [],
991 "appliesTo": {
992 "principalTypes": ["User", "Group"],
993 "resourceTypes": ["Photo"]
994 }
995 }
996 }
997 }}"#;
998
999 match ValidatorSchema::from_str(src) {
1000 Err(SchemaError::Serde(_)) => (),
1001 _ => panic!("Expected serde error due to duplicate entity type."),
1002 }
1003 }
1004
1005 #[test]
1007 fn test_from_schema_file_duplicate_action() {
1008 let src = r#"
1011 {"": {
1012 "entityTypes": {
1013 "User": {
1014 "memberOfTypes": [ "Group" ]
1015 },
1016 "Group": {
1017 "memberOfTypes": []
1018 },
1019 "Photo": {
1020 "memberOfTypes": []
1021 }
1022 },
1023 "actions": {
1024 "view_photo": {
1025 "memberOf": [],
1026 "appliesTo": {
1027 "principalTypes": ["User", "Group"],
1028 "resourceTypes": ["Photo"]
1029 }
1030 },
1031 "view_photo": { }
1032 }
1033 }"#;
1034 match ValidatorSchema::from_str(src) {
1035 Err(SchemaError::Serde(_)) => (),
1036 _ => panic!("Expected serde error due to duplicate action type."),
1037 }
1038 }
1039
1040 #[test]
1042 fn test_from_schema_file_undefined_entities() {
1043 let src = json!(
1044 {
1045 "entityTypes": {
1046 "User": {
1047 "memberOfTypes": [ "Grop" ]
1048 },
1049 "Group": {
1050 "memberOfTypes": []
1051 },
1052 "Photo": {
1053 "memberOfTypes": []
1054 }
1055 },
1056 "actions": {
1057 "view_photo": {
1058 "appliesTo": {
1059 "principalTypes": ["Usr", "Group"],
1060 "resourceTypes": ["Phoot"]
1061 }
1062 }
1063 }
1064 });
1065 let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
1066 let schema: Result<ValidatorSchema> = schema_file.try_into();
1067 match schema {
1068 Ok(_) => panic!("from_schema_file should have failed"),
1069 Err(SchemaError::UndeclaredEntityTypes(v)) => {
1070 assert_eq!(v.len(), 3)
1071 }
1072 _ => panic!("Unexpected error from from_schema_file"),
1073 }
1074 }
1075
1076 #[test]
1077 fn undefined_entity_namespace_member_of() {
1078 let src = json!(
1079 {"Foo": {
1080 "entityTypes": {
1081 "User": {
1082 "memberOfTypes": [ "Foo::Group", "Bar::Group" ]
1083 },
1084 "Group": { }
1085 },
1086 "actions": {}
1087 }});
1088 let schema_file: SchemaFragment = serde_json::from_value(src).expect("Parse Error");
1089 let schema: Result<ValidatorSchema> = schema_file.try_into();
1090 match schema {
1091 Ok(_) => panic!("try_into should have failed"),
1092 Err(SchemaError::UndeclaredEntityTypes(v)) => {
1093 assert_eq!(v, HashSet::from(["Bar::Group".to_string()]))
1094 }
1095 _ => panic!("Unexpected error from try_into"),
1096 }
1097 }
1098
1099 #[test]
1100 fn undefined_entity_namespace_applies_to() {
1101 let src = json!(
1102 {"Foo": {
1103 "entityTypes": { "User": { }, "Photo": { } },
1104 "actions": {
1105 "view_photo": {
1106 "appliesTo": {
1107 "principalTypes": ["Foo::User", "Bar::User"],
1108 "resourceTypes": ["Photo", "Bar::Photo"],
1109 }
1110 }
1111 }
1112 }});
1113 let schema_file: SchemaFragment = serde_json::from_value(src).expect("Parse Error");
1114 let schema: Result<ValidatorSchema> = schema_file.try_into();
1115 match schema {
1116 Ok(_) => panic!("try_into should have failed"),
1117 Err(SchemaError::UndeclaredEntityTypes(v)) => {
1118 assert_eq!(
1119 v,
1120 HashSet::from(["Bar::Photo".to_string(), "Bar::User".to_string()])
1121 )
1122 }
1123 _ => panic!("Unexpected error from try_into"),
1124 }
1125 }
1126
1127 #[test]
1129 fn test_from_schema_file_undefined_action() {
1130 let src = json!(
1131 {
1132 "entityTypes": {
1133 "User": {
1134 "memberOfTypes": [ "Group" ]
1135 },
1136 "Group": {
1137 "memberOfTypes": []
1138 },
1139 "Photo": {
1140 "memberOfTypes": []
1141 }
1142 },
1143 "actions": {
1144 "view_photo": {
1145 "memberOf": [ {"id": "photo_action"} ],
1146 "appliesTo": {
1147 "principalTypes": ["User", "Group"],
1148 "resourceTypes": ["Photo"]
1149 }
1150 }
1151 }
1152 });
1153 let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
1154 let schema: Result<ValidatorSchema> = schema_file.try_into();
1155 match schema {
1156 Ok(_) => panic!("from_schema_file should have failed"),
1157 Err(SchemaError::UndeclaredActions(v)) => assert_eq!(v.len(), 1),
1158 _ => panic!("Unexpected error from from_schema_file"),
1159 }
1160 }
1161
1162 #[test]
1165 fn test_from_schema_file_action_cycle1() {
1166 let src = json!(
1167 {
1168 "entityTypes": {},
1169 "actions": {
1170 "view_photo": {
1171 "memberOf": [ {"id": "view_photo"} ]
1172 }
1173 }
1174 });
1175 let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
1176 let schema: Result<ValidatorSchema> = schema_file.try_into();
1177 assert_matches!(
1178 schema,
1179 Err(SchemaError::CycleInActionHierarchy(euid)) => {
1180 assert_eq!(euid, r#"Action::"view_photo""#.parse().unwrap());
1181 }
1182 )
1183 }
1184
1185 #[test]
1188 fn test_from_schema_file_action_cycle2() {
1189 let src = json!(
1190 {
1191 "entityTypes": {},
1192 "actions": {
1193 "view_photo": {
1194 "memberOf": [ {"id": "edit_photo"} ]
1195 },
1196 "edit_photo": {
1197 "memberOf": [ {"id": "delete_photo"} ]
1198 },
1199 "delete_photo": {
1200 "memberOf": [ {"id": "view_photo"} ]
1201 },
1202 "other_action": {
1203 "memberOf": [ {"id": "edit_photo"} ]
1204 }
1205 }
1206 });
1207 let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
1208 let schema: Result<ValidatorSchema> = schema_file.try_into();
1209 assert_matches!(
1210 schema,
1211 Err(SchemaError::CycleInActionHierarchy(_)),
1213 )
1214 }
1215
1216 #[test]
1217 fn namespaced_schema() {
1218 let src = r#"
1219 { "N::S": {
1220 "entityTypes": {
1221 "User": {},
1222 "Photo": {}
1223 },
1224 "actions": {
1225 "view_photo": {
1226 "appliesTo": {
1227 "principalTypes": ["User"],
1228 "resourceTypes": ["Photo"]
1229 }
1230 }
1231 }
1232 } }
1233 "#;
1234 let schema_file: SchemaFragment = serde_json::from_str(src).expect("Parse Error");
1235 let schema: ValidatorSchema = schema_file
1236 .try_into()
1237 .expect("Namespaced schema failed to convert.");
1238 dbg!(&schema);
1239 let user_entity_type = &"N::S::User"
1240 .parse()
1241 .expect("Namespaced entity type should have parsed");
1242 let photo_entity_type = &"N::S::Photo"
1243 .parse()
1244 .expect("Namespaced entity type should have parsed");
1245 assert!(
1246 schema.entity_types.contains_key(user_entity_type),
1247 "Expected and entity type User."
1248 );
1249 assert!(
1250 schema.entity_types.contains_key(photo_entity_type),
1251 "Expected an entity type Photo."
1252 );
1253 assert_eq!(
1254 schema.entity_types.len(),
1255 2,
1256 "Expected exactly 2 entity types."
1257 );
1258 assert!(
1259 schema.action_ids.contains_key(
1260 &"N::S::Action::\"view_photo\""
1261 .parse()
1262 .expect("Namespaced action should have parsed")
1263 ),
1264 "Expected an action \"view_photo\"."
1265 );
1266 assert_eq!(schema.action_ids.len(), 1, "Expected exactly 1 action.");
1267
1268 let apply_spec = &schema
1269 .action_ids
1270 .values()
1271 .next()
1272 .expect("Expected Action")
1273 .applies_to;
1274 assert_eq!(
1275 apply_spec.applicable_principal_types().collect::<Vec<_>>(),
1276 vec![&EntityType::Specified(user_entity_type.clone())]
1277 );
1278 assert_eq!(
1279 apply_spec.applicable_resource_types().collect::<Vec<_>>(),
1280 vec![&EntityType::Specified(photo_entity_type.clone())]
1281 );
1282 }
1283
1284 #[test]
1285 fn cant_use_namespace_in_entity_type() {
1286 let src = r#"
1287 {
1288 "entityTypes": { "NS::User": {} },
1289 "actions": {}
1290 }
1291 "#;
1292 let schema_file: std::result::Result<NamespaceDefinition, _> = serde_json::from_str(src);
1293 assert!(schema_file.is_err());
1294 }
1295
1296 #[test]
1297 fn entity_attribute_entity_type_with_namespace() {
1298 let schema_json: SchemaFragment = serde_json::from_str(
1299 r#"
1300 {"A::B": {
1301 "entityTypes": {
1302 "Foo": {
1303 "shape": {
1304 "type": "Record",
1305 "attributes": {
1306 "name": { "type": "Entity", "name": "C::D::Foo" }
1307 }
1308 }
1309 }
1310 },
1311 "actions": {}
1312 }}
1313 "#,
1314 )
1315 .expect("Expected valid schema");
1316
1317 let schema: Result<ValidatorSchema> = schema_json.try_into();
1318 match schema {
1319 Err(SchemaError::UndeclaredEntityTypes(tys)) => {
1320 assert_eq!(tys, HashSet::from(["C::D::Foo".to_string()]))
1321 }
1322 _ => panic!("Schema construction should have failed due to undeclared entity type."),
1323 }
1324 }
1325
1326 #[test]
1327 fn entity_attribute_entity_type_with_declared_namespace() {
1328 let schema_json: SchemaFragment = serde_json::from_str(
1329 r#"
1330 {"A::B": {
1331 "entityTypes": {
1332 "Foo": {
1333 "shape": {
1334 "type": "Record",
1335 "attributes": {
1336 "name": { "type": "Entity", "name": "A::B::Foo" }
1337 }
1338 }
1339 }
1340 },
1341 "actions": {}
1342 }}
1343 "#,
1344 )
1345 .expect("Expected valid schema");
1346
1347 let schema: ValidatorSchema = schema_json
1348 .try_into()
1349 .expect("Expected schema to construct without error.");
1350
1351 let foo_name: Name = "A::B::Foo".parse().expect("Expected entity type name");
1352 let foo_type = schema
1353 .entity_types
1354 .get(&foo_name)
1355 .expect("Expected to find entity");
1356 let name_type = foo_type
1357 .attr("name")
1358 .expect("Expected attribute name")
1359 .attr_type
1360 .clone();
1361 let expected_name_type = Type::named_entity_reference(foo_name);
1362 assert_eq!(name_type, expected_name_type);
1363 }
1364
1365 #[test]
1366 fn cannot_declare_action_type_when_prohibited() {
1367 let schema_json: NamespaceDefinition = serde_json::from_str(
1368 r#"
1369 {
1370 "entityTypes": { "Action": {} },
1371 "actions": {}
1372 }
1373 "#,
1374 )
1375 .expect("Expected valid schema");
1376
1377 let schema: Result<ValidatorSchema> = schema_json.try_into();
1378 assert!(matches!(schema, Err(SchemaError::ActionEntityTypeDeclared)));
1379 }
1380
1381 #[test]
1382 fn can_declare_other_type_when_action_type_prohibited() {
1383 let schema_json: NamespaceDefinition = serde_json::from_str(
1384 r#"
1385 {
1386 "entityTypes": { "Foo": { } },
1387 "actions": {}
1388 }
1389 "#,
1390 )
1391 .expect("Expected valid schema");
1392
1393 TryInto::<ValidatorSchema>::try_into(schema_json).expect("Did not expect any errors.");
1394 }
1395
1396 #[test]
1397 fn cannot_declare_action_in_group_when_prohibited() {
1398 let schema_json: SchemaFragment = serde_json::from_str(
1399 r#"
1400 {"": {
1401 "entityTypes": {},
1402 "actions": {
1403 "universe": { },
1404 "view_photo": {
1405 "attributes": {"id": "universe"}
1406 },
1407 "edit_photo": {
1408 "attributes": {"id": "universe"}
1409 },
1410 "delete_photo": {
1411 "attributes": {"id": "universe"}
1412 }
1413 }
1414 }}
1415 "#,
1416 )
1417 .expect("Expected valid schema");
1418
1419 let schema = ValidatorSchemaFragment::from_schema_fragment(
1420 schema_json,
1421 ActionBehavior::ProhibitAttributes,
1422 Extensions::all_available(),
1423 );
1424 match schema {
1425 Err(SchemaError::UnsupportedFeature(UnsupportedFeature::ActionAttributes(actions))) => {
1426 assert_eq!(
1427 actions.into_iter().collect::<HashSet<_>>(),
1428 HashSet::from([
1429 "view_photo".to_string(),
1430 "edit_photo".to_string(),
1431 "delete_photo".to_string(),
1432 ])
1433 )
1434 }
1435 _ => panic!("Did not see expected error."),
1436 }
1437 }
1438
1439 #[test]
1440 fn test_entity_type_no_namespace() {
1441 let src = json!({"type": "Entity", "name": "Foo"});
1442 let schema_ty: SchemaType = serde_json::from_value(src).expect("Parse Error");
1443 assert_eq!(
1444 schema_ty,
1445 SchemaType::Type(SchemaTypeVariant::Entity {
1446 name: "Foo".parse().unwrap()
1447 })
1448 );
1449 let ty: Type = ValidatorNamespaceDef::try_schema_type_into_validator_type(
1450 Some(&Name::parse_unqualified_name("NS").expect("Expected namespace.")),
1451 schema_ty,
1452 )
1453 .expect("Error converting schema type to type.")
1454 .resolve_type_defs(&HashMap::new())
1455 .unwrap();
1456 assert_eq!(ty, Type::named_entity_reference_from_str("NS::Foo"));
1457 }
1458
1459 #[test]
1460 fn test_entity_type_namespace() {
1461 let src = json!({"type": "Entity", "name": "NS::Foo"});
1462 let schema_ty: SchemaType = serde_json::from_value(src).expect("Parse Error");
1463 assert_eq!(
1464 schema_ty,
1465 SchemaType::Type(SchemaTypeVariant::Entity {
1466 name: "NS::Foo".parse().unwrap()
1467 })
1468 );
1469 let ty: Type = ValidatorNamespaceDef::try_schema_type_into_validator_type(
1470 Some(&Name::parse_unqualified_name("NS").expect("Expected namespace.")),
1471 schema_ty,
1472 )
1473 .expect("Error converting schema type to type.")
1474 .resolve_type_defs(&HashMap::new())
1475 .unwrap();
1476 assert_eq!(ty, Type::named_entity_reference_from_str("NS::Foo"));
1477 }
1478
1479 #[test]
1480 fn test_entity_type_namespace_parse_error() {
1481 let src = json!({"type": "Entity", "name": "::Foo"});
1482 let schema_ty: std::result::Result<SchemaType, _> = serde_json::from_value(src);
1483 assert!(schema_ty.is_err());
1484 }
1485
1486 #[test]
1487 fn schema_type_record_is_validator_type_record() {
1488 let src = json!({"type": "Record", "attributes": {}});
1489 let schema_ty: SchemaType = serde_json::from_value(src).expect("Parse Error");
1490 assert_eq!(
1491 schema_ty,
1492 SchemaType::Type(SchemaTypeVariant::Record {
1493 attributes: BTreeMap::new(),
1494 additional_attributes: false,
1495 }),
1496 );
1497 let ty: Type = ValidatorNamespaceDef::try_schema_type_into_validator_type(None, schema_ty)
1498 .expect("Error converting schema type to type.")
1499 .resolve_type_defs(&HashMap::new())
1500 .unwrap();
1501 assert_eq!(ty, Type::closed_record_with_attributes(None));
1502 }
1503
1504 #[test]
1505 fn get_namespaces() {
1506 let fragment: SchemaFragment = serde_json::from_value(json!({
1507 "Foo::Bar::Baz": {
1508 "entityTypes": {},
1509 "actions": {}
1510 },
1511 "Foo": {
1512 "entityTypes": {},
1513 "actions": {}
1514 },
1515 "Bar": {
1516 "entityTypes": {},
1517 "actions": {}
1518 },
1519 }))
1520 .unwrap();
1521
1522 let schema_fragment: ValidatorSchemaFragment = fragment.try_into().unwrap();
1523 assert_eq!(
1524 schema_fragment
1525 .0
1526 .iter()
1527 .map(|f| f.namespace())
1528 .collect::<HashSet<_>>(),
1529 HashSet::from([
1530 &Some("Foo::Bar::Baz".parse().unwrap()),
1531 &Some("Foo".parse().unwrap()),
1532 &Some("Bar".parse().unwrap())
1533 ])
1534 );
1535 }
1536
1537 #[test]
1538 fn schema_no_fragments() {
1539 let schema = ValidatorSchema::from_schema_fragments([]).unwrap();
1540 assert!(schema.entity_types.is_empty());
1541 assert!(schema.action_ids.is_empty());
1542 }
1543
1544 #[test]
1545 fn same_action_different_namespace() {
1546 let fragment: SchemaFragment = serde_json::from_value(json!({
1547 "Foo::Bar": {
1548 "entityTypes": {},
1549 "actions": {
1550 "Baz": {}
1551 }
1552 },
1553 "Bar::Foo": {
1554 "entityTypes": {},
1555 "actions": {
1556 "Baz": { }
1557 }
1558 },
1559 "Biz": {
1560 "entityTypes": {},
1561 "actions": {
1562 "Baz": { }
1563 }
1564 }
1565 }))
1566 .unwrap();
1567
1568 let schema: ValidatorSchema = fragment.try_into().unwrap();
1569 assert!(schema
1570 .get_action_id(&"Foo::Bar::Action::\"Baz\"".parse().unwrap())
1571 .is_some());
1572 assert!(schema
1573 .get_action_id(&"Bar::Foo::Action::\"Baz\"".parse().unwrap())
1574 .is_some());
1575 assert!(schema
1576 .get_action_id(&"Biz::Action::\"Baz\"".parse().unwrap())
1577 .is_some());
1578 }
1579
1580 #[test]
1581 fn same_type_different_namespace() {
1582 let fragment: SchemaFragment = serde_json::from_value(json!({
1583 "Foo::Bar": {
1584 "entityTypes": {"Baz" : {}},
1585 "actions": { }
1586 },
1587 "Bar::Foo": {
1588 "entityTypes": {"Baz" : {}},
1589 "actions": { }
1590 },
1591 "Biz": {
1592 "entityTypes": {"Baz" : {}},
1593 "actions": { }
1594 }
1595 }))
1596 .unwrap();
1597 let schema: ValidatorSchema = fragment.try_into().unwrap();
1598
1599 assert!(schema
1600 .get_entity_type(&"Foo::Bar::Baz".parse().unwrap())
1601 .is_some());
1602 assert!(schema
1603 .get_entity_type(&"Bar::Foo::Baz".parse().unwrap())
1604 .is_some());
1605 assert!(schema
1606 .get_entity_type(&"Biz::Baz".parse().unwrap())
1607 .is_some());
1608 }
1609
1610 #[test]
1611 fn member_of_different_namespace() {
1612 let fragment: SchemaFragment = serde_json::from_value(json!({
1613 "Bar": {
1614 "entityTypes": {
1615 "Baz": {
1616 "memberOfTypes": ["Foo::Buz"]
1617 }
1618 },
1619 "actions": {}
1620 },
1621 "Foo": {
1622 "entityTypes": { "Buz": {} },
1623 "actions": { }
1624 }
1625 }))
1626 .unwrap();
1627 let schema: ValidatorSchema = fragment.try_into().unwrap();
1628
1629 let buz = schema
1630 .get_entity_type(&"Foo::Buz".parse().unwrap())
1631 .unwrap();
1632 assert_eq!(
1633 buz.descendants,
1634 HashSet::from(["Bar::Baz".parse().unwrap()])
1635 );
1636 }
1637
1638 #[test]
1639 fn attribute_different_namespace() {
1640 let fragment: SchemaFragment = serde_json::from_value(json!({
1641 "Bar": {
1642 "entityTypes": {
1643 "Baz": {
1644 "shape": {
1645 "type": "Record",
1646 "attributes": {
1647 "fiz": {
1648 "type": "Entity",
1649 "name": "Foo::Buz"
1650 }
1651 }
1652 }
1653 }
1654 },
1655 "actions": {}
1656 },
1657 "Foo": {
1658 "entityTypes": { "Buz": {} },
1659 "actions": { }
1660 }
1661 }))
1662 .unwrap();
1663
1664 let schema: ValidatorSchema = fragment.try_into().unwrap();
1665 let baz = schema
1666 .get_entity_type(&"Bar::Baz".parse().unwrap())
1667 .unwrap();
1668 assert_eq!(
1669 baz.attr("fiz").unwrap().attr_type,
1670 Type::named_entity_reference_from_str("Foo::Buz"),
1671 );
1672 }
1673
1674 #[test]
1675 fn applies_to_different_namespace() {
1676 let fragment: SchemaFragment = serde_json::from_value(json!({
1677 "Foo::Bar": {
1678 "entityTypes": { },
1679 "actions": {
1680 "Baz": {
1681 "appliesTo": {
1682 "principalTypes": [ "Fiz::Buz" ],
1683 "resourceTypes": [ "Fiz::Baz" ],
1684 }
1685 }
1686 }
1687 },
1688 "Fiz": {
1689 "entityTypes": {
1690 "Buz": {},
1691 "Baz": {}
1692 },
1693 "actions": { }
1694 }
1695 }))
1696 .unwrap();
1697 let schema: ValidatorSchema = fragment.try_into().unwrap();
1698
1699 let baz = schema
1700 .get_action_id(&"Foo::Bar::Action::\"Baz\"".parse().unwrap())
1701 .unwrap();
1702 assert_eq!(
1703 baz.applies_to
1704 .applicable_principal_types()
1705 .collect::<HashSet<_>>(),
1706 HashSet::from([&EntityType::Specified("Fiz::Buz".parse().unwrap())])
1707 );
1708 assert_eq!(
1709 baz.applies_to
1710 .applicable_resource_types()
1711 .collect::<HashSet<_>>(),
1712 HashSet::from([&EntityType::Specified("Fiz::Baz".parse().unwrap())])
1713 );
1714 }
1715
1716 #[test]
1717 fn simple_defined_type() {
1718 let fragment: SchemaFragment = serde_json::from_value(json!({
1719 "": {
1720 "commonTypes": {
1721 "MyLong": {"type": "Long"}
1722 },
1723 "entityTypes": {
1724 "User": {
1725 "shape": {
1726 "type": "Record",
1727 "attributes": {
1728 "a": {"type": "MyLong"}
1729 }
1730 }
1731 }
1732 },
1733 "actions": {}
1734 }
1735 }))
1736 .unwrap();
1737 let schema: ValidatorSchema = fragment.try_into().unwrap();
1738 assert_eq!(
1739 schema.entity_types.iter().next().unwrap().1.attributes,
1740 Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
1741 );
1742 }
1743
1744 #[test]
1745 fn defined_record_as_attrs() {
1746 let fragment: SchemaFragment = serde_json::from_value(json!({
1747 "": {
1748 "commonTypes": {
1749 "MyRecord": {
1750 "type": "Record",
1751 "attributes": {
1752 "a": {"type": "Long"}
1753 }
1754 }
1755 },
1756 "entityTypes": {
1757 "User": { "shape": { "type": "MyRecord", } }
1758 },
1759 "actions": {}
1760 }
1761 }))
1762 .unwrap();
1763 let schema: ValidatorSchema = fragment.try_into().unwrap();
1764 assert_eq!(
1765 schema.entity_types.iter().next().unwrap().1.attributes,
1766 Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
1767 );
1768 }
1769
1770 #[test]
1771 fn cross_namespace_type() {
1772 let fragment: SchemaFragment = serde_json::from_value(json!({
1773 "A": {
1774 "commonTypes": {
1775 "MyLong": {"type": "Long"}
1776 },
1777 "entityTypes": { },
1778 "actions": {}
1779 },
1780 "B": {
1781 "entityTypes": {
1782 "User": {
1783 "shape": {
1784 "type": "Record",
1785 "attributes": {
1786 "a": {"type": "A::MyLong"}
1787 }
1788 }
1789 }
1790 },
1791 "actions": {}
1792 }
1793 }))
1794 .unwrap();
1795 let schema: ValidatorSchema = fragment.try_into().unwrap();
1796 assert_eq!(
1797 schema.entity_types.iter().next().unwrap().1.attributes,
1798 Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
1799 );
1800 }
1801
1802 #[test]
1803 fn cross_fragment_type() {
1804 let fragment1: ValidatorSchemaFragment = serde_json::from_value::<SchemaFragment>(json!({
1805 "A": {
1806 "commonTypes": {
1807 "MyLong": {"type": "Long"}
1808 },
1809 "entityTypes": { },
1810 "actions": {}
1811 }
1812 }))
1813 .unwrap()
1814 .try_into()
1815 .unwrap();
1816 let fragment2: ValidatorSchemaFragment = serde_json::from_value::<SchemaFragment>(json!({
1817 "A": {
1818 "entityTypes": {
1819 "User": {
1820 "shape": {
1821 "type": "Record",
1822 "attributes": {
1823 "a": {"type": "MyLong"}
1824 }
1825 }
1826 }
1827 },
1828 "actions": {}
1829 }
1830 }))
1831 .unwrap()
1832 .try_into()
1833 .unwrap();
1834 let schema = ValidatorSchema::from_schema_fragments([fragment1, fragment2]).unwrap();
1835
1836 assert_eq!(
1837 schema.entity_types.iter().next().unwrap().1.attributes,
1838 Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
1839 );
1840 }
1841
1842 #[test]
1843 fn cross_fragment_duplicate_type() {
1844 let fragment1: ValidatorSchemaFragment = serde_json::from_value::<SchemaFragment>(json!({
1845 "A": {
1846 "commonTypes": {
1847 "MyLong": {"type": "Long"}
1848 },
1849 "entityTypes": {},
1850 "actions": {}
1851 }
1852 }))
1853 .unwrap()
1854 .try_into()
1855 .unwrap();
1856 let fragment2: ValidatorSchemaFragment = serde_json::from_value::<SchemaFragment>(json!({
1857 "A": {
1858 "commonTypes": {
1859 "MyLong": {"type": "Long"}
1860 },
1861 "entityTypes": {},
1862 "actions": {}
1863 }
1864 }))
1865 .unwrap()
1866 .try_into()
1867 .unwrap();
1868
1869 let schema = ValidatorSchema::from_schema_fragments([fragment1, fragment2]);
1870
1871 match schema {
1872 Err(SchemaError::DuplicateCommonType(s)) if s.contains("A::MyLong") => (),
1873 _ => panic!("should have errored because schema fragments have duplicate types"),
1874 };
1875 }
1876
1877 #[test]
1878 fn undeclared_type_in_attr() {
1879 let fragment: SchemaFragment = serde_json::from_value(json!({
1880 "": {
1881 "commonTypes": { },
1882 "entityTypes": {
1883 "User": {
1884 "shape": {
1885 "type": "Record",
1886 "attributes": {
1887 "a": {"type": "MyLong"}
1888 }
1889 }
1890 }
1891 },
1892 "actions": {}
1893 }
1894 }))
1895 .unwrap();
1896 match TryInto::<ValidatorSchema>::try_into(fragment) {
1897 Err(SchemaError::UndeclaredCommonTypes(_)) => (),
1898 s => panic!(
1899 "Expected Err(SchemaError::UndeclaredCommonType), got {:?}",
1900 s
1901 ),
1902 }
1903 }
1904
1905 #[test]
1906 fn undeclared_type_in_type_def() {
1907 let fragment: SchemaFragment = serde_json::from_value(json!({
1908 "": {
1909 "commonTypes": {
1910 "a": { "type": "b" }
1911 },
1912 "entityTypes": { },
1913 "actions": {}
1914 }
1915 }))
1916 .unwrap();
1917 match TryInto::<ValidatorSchema>::try_into(fragment) {
1918 Err(SchemaError::UndeclaredCommonTypes(_)) => (),
1919 s => panic!(
1920 "Expected Err(SchemaError::UndeclaredCommonType), got {:?}",
1921 s
1922 ),
1923 }
1924 }
1925
1926 #[test]
1927 fn shape_not_record() {
1928 let fragment: SchemaFragment = serde_json::from_value(json!({
1929 "": {
1930 "commonTypes": {
1931 "MyLong": { "type": "Long" }
1932 },
1933 "entityTypes": {
1934 "User": {
1935 "shape": { "type": "MyLong" }
1936 }
1937 },
1938 "actions": {}
1939 }
1940 }))
1941 .unwrap();
1942 match TryInto::<ValidatorSchema>::try_into(fragment) {
1943 Err(SchemaError::ContextOrShapeNotRecord(_)) => (),
1944 s => panic!(
1945 "Expected Err(SchemaError::ContextOrShapeNotRecord), got {:?}",
1946 s
1947 ),
1948 }
1949 }
1950
1951 #[test]
1955 fn counterexamples_from_cedar_134() {
1956 let bad1 = json!({
1958 "": {
1959 "entityTypes": {
1960 "User // comment": {
1961 "memberOfTypes": [
1962 "UserGroup"
1963 ]
1964 },
1965 "User": {
1966 "memberOfTypes": [
1967 "UserGroup"
1968 ]
1969 },
1970 "UserGroup": {}
1971 },
1972 "actions": {}
1973 }
1974 });
1975 let fragment = serde_json::from_value::<SchemaFragment>(bad1); assert!(fragment.is_err());
1978
1979 let bad2 = json!({
1981 "ABC :: //comment \n XYZ ": {
1982 "entityTypes": {
1983 "User": {
1984 "memberOfTypes": []
1985 }
1986 },
1987 "actions": {}
1988 }
1989 });
1990 let fragment = serde_json::from_value::<SchemaFragment>(bad2); assert!(fragment.is_err());
1993 }
1994
1995 #[test]
1996 fn simple_action_entity() {
1997 let src = json!(
1998 {
1999 "entityTypes": { },
2000 "actions": {
2001 "view_photo": { },
2002 }
2003 });
2004
2005 let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
2006 let schema: ValidatorSchema = schema_file.try_into().expect("Schema Error");
2007 let actions = schema.action_entities().expect("Entity Construct Error");
2008
2009 let action_uid = EntityUID::from_str("Action::\"view_photo\"").unwrap();
2010 let view_photo = actions.entity(&action_uid);
2011 assert_eq!(
2012 view_photo.unwrap(),
2013 &Entity::new_with_attr_partial_value(action_uid, HashMap::new(), HashSet::new())
2014 );
2015 }
2016
2017 #[test]
2018 fn action_entity_hierarchy() {
2019 let src = json!(
2020 {
2021 "entityTypes": { },
2022 "actions": {
2023 "read": {},
2024 "view": {
2025 "memberOf": [{"id": "read"}]
2026 },
2027 "view_photo": {
2028 "memberOf": [{"id": "view"}]
2029 },
2030 }
2031 });
2032
2033 let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
2034 let schema: ValidatorSchema = schema_file.try_into().expect("Schema Error");
2035 let actions = schema.action_entities().expect("Entity Construct Error");
2036
2037 let view_photo_uid = EntityUID::from_str("Action::\"view_photo\"").unwrap();
2038 let view_uid = EntityUID::from_str("Action::\"view\"").unwrap();
2039 let read_uid = EntityUID::from_str("Action::\"read\"").unwrap();
2040
2041 let view_photo_entity = actions.entity(&view_photo_uid);
2042 assert_eq!(
2043 view_photo_entity.unwrap(),
2044 &Entity::new_with_attr_partial_value(
2045 view_photo_uid,
2046 HashMap::new(),
2047 HashSet::from([view_uid.clone(), read_uid.clone()])
2048 )
2049 );
2050
2051 let view_entity = actions.entity(&view_uid);
2052 assert_eq!(
2053 view_entity.unwrap(),
2054 &Entity::new_with_attr_partial_value(
2055 view_uid,
2056 HashMap::new(),
2057 HashSet::from([read_uid.clone()])
2058 )
2059 );
2060
2061 let read_entity = actions.entity(&read_uid);
2062 assert_eq!(
2063 read_entity.unwrap(),
2064 &Entity::new_with_attr_partial_value(read_uid, HashMap::new(), HashSet::new())
2065 );
2066 }
2067
2068 #[test]
2069 fn action_entity_attribute() {
2070 let src = json!(
2071 {
2072 "entityTypes": { },
2073 "actions": {
2074 "view_photo": {
2075 "attributes": { "attr": "foo" }
2076 },
2077 }
2078 });
2079
2080 let schema_file: NamespaceDefinitionWithActionAttributes =
2081 serde_json::from_value(src).expect("Parse Error");
2082 let schema: ValidatorSchema = schema_file.try_into().expect("Schema Error");
2083 let actions = schema.action_entities().expect("Entity Construct Error");
2084
2085 let action_uid = EntityUID::from_str("Action::\"view_photo\"").unwrap();
2086 let view_photo = actions.entity(&action_uid);
2087 assert_eq!(
2088 view_photo.unwrap(),
2089 &Entity::new(
2090 action_uid,
2091 HashMap::from([("attr".into(), RestrictedExpr::val("foo"))]),
2092 HashSet::new(),
2093 &Extensions::none(),
2094 )
2095 .unwrap(),
2096 );
2097 }
2098
2099 #[test]
2100 fn test_action_namespace_inference_multi_success() {
2101 let src = json!({
2102 "Foo" : {
2103 "entityTypes" : {},
2104 "actions" : {
2105 "read" : {}
2106 }
2107 },
2108 "ExampleCo::Personnel" : {
2109 "entityTypes" : {},
2110 "actions" : {
2111 "viewPhoto" : {
2112 "memberOf" : [
2113 {
2114 "id" : "read",
2115 "type" : "Foo::Action"
2116 }
2117 ]
2118 }
2119 }
2120 },
2121 });
2122 let schema_fragment =
2123 serde_json::from_value::<SchemaFragment>(src).expect("Failed to parse schema");
2124 let schema: ValidatorSchema = schema_fragment.try_into().expect("Schema should construct");
2125 let view_photo = schema
2126 .action_entities_iter()
2127 .find(|e| e.uid() == &r#"ExampleCo::Personnel::Action::"viewPhoto""#.parse().unwrap())
2128 .unwrap();
2129 let ancestors = view_photo.ancestors().collect::<Vec<_>>();
2130 let read = ancestors[0];
2131 assert_eq!(read.eid().to_string(), "read");
2132 assert_eq!(read.entity_type().to_string(), "Foo::Action");
2133 }
2134
2135 #[test]
2136 fn test_action_namespace_inference_multi() {
2137 let src = json!({
2138 "ExampleCo::Personnel::Foo" : {
2139 "entityTypes" : {},
2140 "actions" : {
2141 "read" : {}
2142 }
2143 },
2144 "ExampleCo::Personnel" : {
2145 "entityTypes" : {},
2146 "actions" : {
2147 "viewPhoto" : {
2148 "memberOf" : [
2149 {
2150 "id" : "read",
2151 "type" : "Foo::Action"
2152 }
2153 ]
2154 }
2155 }
2156 },
2157 });
2158 let schema_fragment =
2159 serde_json::from_value::<SchemaFragment>(src).expect("Failed to parse schema");
2160 let schema: std::result::Result<ValidatorSchema, _> = schema_fragment.try_into();
2161 schema.expect_err("Schema should fail to construct as the normalization rules treat any qualification as starting from the root");
2162 }
2163
2164 #[test]
2165 fn test_action_namespace_inference() {
2166 let src = json!({
2167 "ExampleCo::Personnel" : {
2168 "entityTypes" : { },
2169 "actions" : {
2170 "read" : {},
2171 "viewPhoto" : {
2172 "memberOf" : [
2173 {
2174 "id" : "read",
2175 "type" : "Action"
2176 }
2177 ]
2178 }
2179 }
2180 }
2181 });
2182 let schema_fragment =
2183 serde_json::from_value::<SchemaFragment>(src).expect("Failed to parse schema");
2184 let schema: ValidatorSchema = schema_fragment.try_into().unwrap();
2185 let view_photo = schema
2186 .action_entities_iter()
2187 .find(|e| e.uid() == &r#"ExampleCo::Personnel::Action::"viewPhoto""#.parse().unwrap())
2188 .unwrap();
2189 let ancestors = view_photo.ancestors().collect::<Vec<_>>();
2190 let read = ancestors[0];
2191 assert_eq!(read.eid().to_string(), "read");
2192 assert_eq!(
2193 read.entity_type().to_string(),
2194 "ExampleCo::Personnel::Action"
2195 );
2196 }
2197
2198 #[test]
2199 fn qualified_undeclared_common_types() {
2200 let src = json!(
2201 {
2202 "Demo": {
2203 "entityTypes": {
2204 "User": {
2205 "memberOfTypes": [],
2206 "shape": {
2207 "type": "Record",
2208 "attributes": {
2209 "id": { "type": "id" },
2210 }
2211 }
2212 }
2213 },
2214 "actions": {}
2215 },
2216 "": {
2217 "commonTypes": {
2218 "id": {
2219 "type": "String"
2220 },
2221 },
2222 "entityTypes": {},
2223 "actions": {}
2224 }
2225 }
2226 );
2227 let schema = ValidatorSchema::from_json_value(src, Extensions::all_available());
2228 assert_matches!(schema, Err(SchemaError::UndeclaredCommonTypes(types)) =>
2229 assert_eq!(types, HashSet::from(["Demo::id".to_string()])));
2230 }
2231
2232 #[test]
2233 fn qualified_undeclared_common_types2() {
2234 let src = json!(
2235 {
2236 "Demo": {
2237 "entityTypes": {
2238 "User": {
2239 "memberOfTypes": [],
2240 "shape": {
2241 "type": "Record",
2242 "attributes": {
2243 "id": { "type": "Demo::id" },
2244 }
2245 }
2246 }
2247 },
2248 "actions": {}
2249 },
2250 "": {
2251 "commonTypes": {
2252 "id": {
2253 "type": "String"
2254 },
2255 },
2256 "entityTypes": {},
2257 "actions": {}
2258 }
2259 }
2260 );
2261 let schema = ValidatorSchema::from_json_value(src, Extensions::all_available());
2262 assert_matches!(schema, Err(SchemaError::UndeclaredCommonTypes(types)) =>
2263 assert_eq!(types, HashSet::from(["Demo::id".to_string()])));
2264 }
2265}
2266
2267#[cfg(test)]
2268mod test_resolver {
2269 use std::collections::HashMap;
2270
2271 use cedar_policy_core::ast::Name;
2272 use cool_asserts::assert_matches;
2273
2274 use super::CommonTypeResolver;
2275 use crate::{types::Type, SchemaError, SchemaFragment, ValidatorSchemaFragment};
2276
2277 fn resolve(schema: SchemaFragment) -> Result<HashMap<Name, Type>, SchemaError> {
2278 let schema: ValidatorSchemaFragment = schema.try_into().unwrap();
2279 let mut type_defs = HashMap::new();
2280 for def in schema.0 {
2281 type_defs.extend(def.type_defs.type_defs.into_iter());
2282 }
2283 let resolver = CommonTypeResolver::new(&type_defs);
2284 resolver.resolve()
2285 }
2286
2287 #[test]
2288 fn test_simple() {
2289 let schema = serde_json::from_value::<SchemaFragment>(serde_json::json!(
2290 {
2291 "": {
2292 "entityTypes": {},
2293 "actions": {},
2294 "commonTypes": {
2295 "a" : {
2296 "type": "b"
2297 },
2298 "b": {
2299 "type": "Boolean"
2300 }
2301 }
2302 }
2303 }
2304 ))
2305 .unwrap();
2306 let res = resolve(schema).unwrap();
2307 assert_eq!(
2308 res,
2309 HashMap::from_iter([
2310 ("a".parse().unwrap(), Type::primitive_boolean()),
2311 ("b".parse().unwrap(), Type::primitive_boolean())
2312 ])
2313 );
2314
2315 let schema = serde_json::from_value::<SchemaFragment>(serde_json::json!(
2316 {
2317 "": {
2318 "entityTypes": {},
2319 "actions": {},
2320 "commonTypes": {
2321 "a" : {
2322 "type": "b"
2323 },
2324 "b": {
2325 "type": "c"
2326 },
2327 "c": {
2328 "type": "Boolean"
2329 }
2330 }
2331 }
2332 }
2333 ))
2334 .unwrap();
2335 let res = resolve(schema).unwrap();
2336 assert_eq!(
2337 res,
2338 HashMap::from_iter([
2339 ("a".parse().unwrap(), Type::primitive_boolean()),
2340 ("b".parse().unwrap(), Type::primitive_boolean()),
2341 ("c".parse().unwrap(), Type::primitive_boolean())
2342 ])
2343 );
2344 }
2345
2346 #[test]
2347 fn test_set() {
2348 let schema = serde_json::from_value::<SchemaFragment>(serde_json::json!(
2349 {
2350 "": {
2351 "entityTypes": {},
2352 "actions": {},
2353 "commonTypes": {
2354 "a" : {
2355 "type": "Set",
2356 "element": {
2357 "type": "b"
2358 }
2359 },
2360 "b": {
2361 "type": "Boolean"
2362 }
2363 }
2364 }
2365 }
2366 ))
2367 .unwrap();
2368 let res = resolve(schema).unwrap();
2369 assert_eq!(
2370 res,
2371 HashMap::from_iter([
2372 ("a".parse().unwrap(), Type::set(Type::primitive_boolean())),
2373 ("b".parse().unwrap(), Type::primitive_boolean())
2374 ])
2375 );
2376 }
2377
2378 #[test]
2379 fn test_record() {
2380 let schema = serde_json::from_value::<SchemaFragment>(serde_json::json!(
2381 {
2382 "": {
2383 "entityTypes": {},
2384 "actions": {},
2385 "commonTypes": {
2386 "a" : {
2387 "type": "Record",
2388 "attributes": {
2389 "foo": {
2390 "type": "b"
2391 }
2392 }
2393 },
2394 "b": {
2395 "type": "Boolean"
2396 }
2397 }
2398 }
2399 }
2400 ))
2401 .unwrap();
2402 let res = resolve(schema).unwrap();
2403 assert_eq!(
2404 res,
2405 HashMap::from_iter([
2406 (
2407 "a".parse().unwrap(),
2408 Type::record_with_required_attributes(
2409 std::iter::once(("foo".into(), Type::primitive_boolean())),
2410 crate::types::OpenTag::ClosedAttributes
2411 )
2412 ),
2413 ("b".parse().unwrap(), Type::primitive_boolean())
2414 ])
2415 );
2416 }
2417
2418 #[test]
2419 fn test_names() {
2420 let schema = serde_json::from_value::<SchemaFragment>(serde_json::json!(
2421 {
2422 "A": {
2423 "entityTypes": {},
2424 "actions": {},
2425 "commonTypes": {
2426 "a" : {
2427 "type": "B::a"
2428 }
2429 }
2430 },
2431 "B": {
2432 "entityTypes": {},
2433 "actions": {},
2434 "commonTypes": {
2435 "a" : {
2436 "type": "Boolean"
2437 }
2438 }
2439 }
2440 }
2441 ))
2442 .unwrap();
2443 let res = resolve(schema).unwrap();
2444 assert_eq!(
2445 res,
2446 HashMap::from_iter([
2447 ("A::a".parse().unwrap(), Type::primitive_boolean()),
2448 ("B::a".parse().unwrap(), Type::primitive_boolean())
2449 ])
2450 );
2451 }
2452
2453 #[test]
2454 fn test_cycles() {
2455 let schema = serde_json::from_value::<SchemaFragment>(serde_json::json!(
2457 {
2458 "": {
2459 "entityTypes": {},
2460 "actions": {},
2461 "commonTypes": {
2462 "a" : {
2463 "type": "a"
2464 }
2465 }
2466 }
2467 }
2468 ))
2469 .unwrap();
2470 let res = resolve(schema);
2471 assert_matches!(res, Err(SchemaError::CycleInCommonTypeReferences(_)));
2472
2473 let schema = serde_json::from_value::<SchemaFragment>(serde_json::json!(
2475 {
2476 "": {
2477 "entityTypes": {},
2478 "actions": {},
2479 "commonTypes": {
2480 "a" : {
2481 "type": "b"
2482 },
2483 "b" : {
2484 "type": "a"
2485 }
2486 }
2487 }
2488 }
2489 ))
2490 .unwrap();
2491 let res = resolve(schema);
2492 assert_matches!(res, Err(SchemaError::CycleInCommonTypeReferences(_)));
2493
2494 let schema = serde_json::from_value::<SchemaFragment>(serde_json::json!(
2496 {
2497 "": {
2498 "entityTypes": {},
2499 "actions": {},
2500 "commonTypes": {
2501 "a" : {
2502 "type": "b"
2503 },
2504 "b" : {
2505 "type": "c"
2506 },
2507 "c" : {
2508 "type": "a"
2509 }
2510 }
2511 }
2512 }
2513 ))
2514 .unwrap();
2515 let res = resolve(schema);
2516 assert_matches!(res, Err(SchemaError::CycleInCommonTypeReferences(_)));
2517
2518 let schema = serde_json::from_value::<SchemaFragment>(serde_json::json!(
2520 {
2521 "A": {
2522 "entityTypes": {},
2523 "actions": {},
2524 "commonTypes": {
2525 "a" : {
2526 "type": "B::a"
2527 }
2528 }
2529 },
2530 "B": {
2531 "entityTypes": {},
2532 "actions": {},
2533 "commonTypes": {
2534 "a" : {
2535 "type": "A::a"
2536 }
2537 }
2538 }
2539 }
2540 ))
2541 .unwrap();
2542 let res = resolve(schema);
2543 assert_matches!(res, Err(SchemaError::CycleInCommonTypeReferences(_)));
2544
2545 let schema = serde_json::from_value::<SchemaFragment>(serde_json::json!(
2547 {
2548 "A": {
2549 "entityTypes": {},
2550 "actions": {},
2551 "commonTypes": {
2552 "a" : {
2553 "type": "B::a"
2554 }
2555 }
2556 },
2557 "B": {
2558 "entityTypes": {},
2559 "actions": {},
2560 "commonTypes": {
2561 "a" : {
2562 "type": "C::a"
2563 }
2564 }
2565 },
2566 "C": {
2567 "entityTypes": {},
2568 "actions": {},
2569 "commonTypes": {
2570 "a" : {
2571 "type": "A::a"
2572 }
2573 }
2574 }
2575 }
2576 ))
2577 .unwrap();
2578 let res = resolve(schema);
2579 assert_matches!(res, Err(SchemaError::CycleInCommonTypeReferences(_)));
2580
2581 let schema = serde_json::from_value::<SchemaFragment>(serde_json::json!(
2583 {
2584 "A": {
2585 "entityTypes": {},
2586 "actions": {},
2587 "commonTypes": {
2588 "a" : {
2589 "type": "B::a"
2590 }
2591 }
2592 },
2593 "B": {
2594 "entityTypes": {},
2595 "actions": {},
2596 "commonTypes": {
2597 "a" : {
2598 "type": "c"
2599 },
2600 "c": {
2601 "type": "A::a"
2602 }
2603 }
2604 }
2605 }
2606 ))
2607 .unwrap();
2608 let res = resolve(schema);
2609 assert_matches!(res, Err(SchemaError::CycleInCommonTypeReferences(_)));
2610 }
2611}
2612
2613#[cfg(test)]
2614mod test_access {
2615 use super::*;
2616
2617 fn schema() -> ValidatorSchema {
2618 let src = r#"
2619 type Task = {
2620 "id": Long,
2621 "name": String,
2622 "state": String,
2623};
2624
2625type Tasks = Set<Task>;
2626entity List in [Application] = {
2627 "editors": Team,
2628 "name": String,
2629 "owner": User,
2630 "readers": Team,
2631 "tasks": Tasks,
2632};
2633entity Application;
2634entity User in [Team, Application] = {
2635 "joblevel": Long,
2636 "location": String,
2637};
2638
2639entity CoolList;
2640
2641entity Team in [Team, Application];
2642
2643action Read, Write, Create;
2644
2645action DeleteList, EditShare, UpdateList, CreateTask, UpdateTask, DeleteTask in Write appliesTo {
2646 principal: [User],
2647 resource : [List]
2648};
2649
2650action GetList in Read appliesTo {
2651 principal : [User],
2652 resource : [List, CoolList]
2653};
2654
2655action GetLists in Read appliesTo {
2656 principal : [User],
2657 resource : [Application]
2658};
2659
2660action CreateList in Create appliesTo {
2661 principal : [User],
2662 resource : [Application]
2663};
2664
2665 "#;
2666
2667 ValidatorSchema::from_str_natural(src, Extensions::all_available())
2668 .unwrap()
2669 .0
2670 }
2671
2672 #[test]
2673 fn principals() {
2674 let schema = schema();
2675 let principals = schema.principals().collect::<HashSet<_>>();
2676 assert_eq!(principals.len(), 1);
2677 let user: EntityType = EntityType::Specified("User".parse().unwrap());
2678 assert!(principals.contains(&user));
2679 let principals = schema.principals().collect::<Vec<_>>();
2680 assert!(principals.len() > 1);
2681 assert!(principals.iter().all(|ety| **ety == user));
2682 }
2683
2684 #[test]
2685 fn empty_schema_principals_and_resources() {
2686 let empty: ValidatorSchema =
2687 ValidatorSchema::from_str_natural("", Extensions::all_available())
2688 .unwrap()
2689 .0;
2690 assert!(empty.principals().collect::<Vec<_>>().is_empty());
2691 assert!(empty.resources().collect::<Vec<_>>().is_empty());
2692 }
2693
2694 #[test]
2695 fn resources() {
2696 let schema = schema();
2697 let resources = schema.resources().cloned().collect::<HashSet<_>>();
2698 let expected: HashSet<EntityType> = HashSet::from([
2699 EntityType::Specified("List".parse().unwrap()),
2700 EntityType::Specified("Application".parse().unwrap()),
2701 EntityType::Specified("CoolList".parse().unwrap()),
2702 ]);
2703 assert_eq!(resources, expected);
2704 }
2705
2706 #[test]
2707 fn principals_for_action() {
2708 let schema = schema();
2709 let delete_list: EntityUID = r#"Action::"DeleteList""#.parse().unwrap();
2710 let delete_user: EntityUID = r#"Action::"DeleteUser""#.parse().unwrap();
2711 let got = schema
2712 .principals_for_action(&delete_list)
2713 .unwrap()
2714 .cloned()
2715 .collect::<Vec<_>>();
2716 assert_eq!(got, vec![EntityType::Specified("User".parse().unwrap())]);
2717 assert!(schema.principals_for_action(&delete_user).is_none());
2718 }
2719
2720 #[test]
2721 fn resources_for_action() {
2722 let schema = schema();
2723 let delete_list: EntityUID = r#"Action::"DeleteList""#.parse().unwrap();
2724 let delete_user: EntityUID = r#"Action::"DeleteUser""#.parse().unwrap();
2725 let create_list: EntityUID = r#"Action::"CreateList""#.parse().unwrap();
2726 let get_list: EntityUID = r#"Action::"GetList""#.parse().unwrap();
2727 let got = schema
2728 .resources_for_action(&delete_list)
2729 .unwrap()
2730 .cloned()
2731 .collect::<Vec<_>>();
2732 assert_eq!(got, vec![EntityType::Specified("List".parse().unwrap())]);
2733 let got = schema
2734 .resources_for_action(&create_list)
2735 .unwrap()
2736 .cloned()
2737 .collect::<Vec<_>>();
2738 assert_eq!(
2739 got,
2740 vec![EntityType::Specified("Application".parse().unwrap())]
2741 );
2742 let got = schema
2743 .resources_for_action(&get_list)
2744 .unwrap()
2745 .cloned()
2746 .collect::<HashSet<_>>();
2747 assert_eq!(
2748 got,
2749 HashSet::from([
2750 EntityType::Specified("List".parse().unwrap()),
2751 EntityType::Specified("CoolList".parse().unwrap())
2752 ])
2753 );
2754 assert!(schema.principals_for_action(&delete_user).is_none());
2755 }
2756
2757 #[test]
2758 fn principal_parents() {
2759 let schema = schema();
2760 let user: Name = "User".parse().unwrap();
2761 let parents = schema
2762 .ancestors(&user)
2763 .unwrap()
2764 .cloned()
2765 .collect::<HashSet<_>>();
2766 let expected = HashSet::from(["Team".parse().unwrap(), "Application".parse().unwrap()]);
2767 assert_eq!(parents, expected);
2768 let parents = schema
2769 .ancestors(&"List".parse().unwrap())
2770 .unwrap()
2771 .cloned()
2772 .collect::<HashSet<_>>();
2773 let expected = HashSet::from(["Application".parse().unwrap()]);
2774 assert_eq!(parents, expected);
2775 assert!(schema.ancestors(&"Foo".parse().unwrap()).is_none());
2776 let parents = schema
2777 .ancestors(&"CoolList".parse().unwrap())
2778 .unwrap()
2779 .cloned()
2780 .collect::<HashSet<_>>();
2781 let expected = HashSet::from([]);
2782 assert_eq!(parents, expected);
2783 }
2784
2785 #[test]
2786 fn action_groups() {
2787 let schema = schema();
2788 let groups = schema.action_groups().cloned().collect::<HashSet<_>>();
2789 let expected = ["Read", "Write", "Create"]
2790 .into_iter()
2791 .map(|ty| format!("Action::\"{ty}\"").parse().unwrap())
2792 .collect::<HashSet<EntityUID>>();
2793 assert_eq!(groups, expected);
2794 }
2795
2796 #[test]
2797 fn actions() {
2798 let schema = schema();
2799 let actions = schema.actions().cloned().collect::<HashSet<_>>();
2800 let expected = [
2801 "Read",
2802 "Write",
2803 "Create",
2804 "DeleteList",
2805 "EditShare",
2806 "UpdateList",
2807 "CreateTask",
2808 "UpdateTask",
2809 "DeleteTask",
2810 "GetList",
2811 "GetLists",
2812 "CreateList",
2813 ]
2814 .into_iter()
2815 .map(|ty| format!("Action::\"{ty}\"").parse().unwrap())
2816 .collect::<HashSet<EntityUID>>();
2817 assert_eq!(actions, expected);
2818 }
2819
2820 #[test]
2821 fn entities() {
2822 let schema = schema();
2823 let entities = schema
2824 .entity_types()
2825 .map(|(ty, _)| ty)
2826 .cloned()
2827 .collect::<HashSet<_>>();
2828 let expected = ["List", "Application", "User", "CoolList", "Team"]
2829 .into_iter()
2830 .map(|ty| ty.parse().unwrap())
2831 .collect::<HashSet<Name>>();
2832 assert_eq!(entities, expected);
2833 }
2834}
2835
2836#[cfg(test)]
2837mod test_access_namespace {
2838 use super::*;
2839
2840 fn schema() -> ValidatorSchema {
2841 let src = r#"
2842 namespace Foo {
2843 type Task = {
2844 "id": Long,
2845 "name": String,
2846 "state": String,
2847};
2848
2849type Tasks = Set<Task>;
2850entity List in [Application] = {
2851 "editors": Team,
2852 "name": String,
2853 "owner": User,
2854 "readers": Team,
2855 "tasks": Tasks,
2856};
2857entity Application;
2858entity User in [Team, Application] = {
2859 "joblevel": Long,
2860 "location": String,
2861};
2862
2863entity CoolList;
2864
2865entity Team in [Team, Application];
2866
2867action Read, Write, Create;
2868
2869action DeleteList, EditShare, UpdateList, CreateTask, UpdateTask, DeleteTask in Write appliesTo {
2870 principal: [User],
2871 resource : [List]
2872};
2873
2874action GetList in Read appliesTo {
2875 principal : [User],
2876 resource : [List, CoolList]
2877};
2878
2879action GetLists in Read appliesTo {
2880 principal : [User],
2881 resource : [Application]
2882};
2883
2884action CreateList in Create appliesTo {
2885 principal : [User],
2886 resource : [Application]
2887};
2888 }
2889
2890 "#;
2891
2892 ValidatorSchema::from_str_natural(src, Extensions::all_available())
2893 .unwrap()
2894 .0
2895 }
2896
2897 #[test]
2898 fn principals() {
2899 let schema = schema();
2900 let principals = schema.principals().collect::<HashSet<_>>();
2901 assert_eq!(principals.len(), 1);
2902 let user: EntityType = EntityType::Specified("Foo::User".parse().unwrap());
2903 assert!(principals.contains(&user));
2904 let principals = schema.principals().collect::<Vec<_>>();
2905 assert!(principals.len() > 1);
2906 assert!(principals.iter().all(|ety| **ety == user));
2907 }
2908
2909 #[test]
2910 fn empty_schema_principals_and_resources() {
2911 let empty: ValidatorSchema =
2912 ValidatorSchema::from_str_natural("", Extensions::all_available())
2913 .unwrap()
2914 .0;
2915 assert!(empty.principals().collect::<Vec<_>>().is_empty());
2916 assert!(empty.resources().collect::<Vec<_>>().is_empty());
2917 }
2918
2919 #[test]
2920 fn resources() {
2921 let schema = schema();
2922 let resources = schema.resources().cloned().collect::<HashSet<_>>();
2923 let expected: HashSet<EntityType> = HashSet::from([
2924 EntityType::Specified("Foo::List".parse().unwrap()),
2925 EntityType::Specified("Foo::Application".parse().unwrap()),
2926 EntityType::Specified("Foo::CoolList".parse().unwrap()),
2927 ]);
2928 assert_eq!(resources, expected);
2929 }
2930
2931 #[test]
2932 fn principals_for_action() {
2933 let schema = schema();
2934 let delete_list: EntityUID = r#"Foo::Action::"DeleteList""#.parse().unwrap();
2935 let delete_user: EntityUID = r#"Foo::Action::"DeleteUser""#.parse().unwrap();
2936 let got = schema
2937 .principals_for_action(&delete_list)
2938 .unwrap()
2939 .cloned()
2940 .collect::<Vec<_>>();
2941 assert_eq!(
2942 got,
2943 vec![EntityType::Specified("Foo::User".parse().unwrap())]
2944 );
2945 assert!(schema.principals_for_action(&delete_user).is_none());
2946 }
2947
2948 #[test]
2949 fn resources_for_action() {
2950 let schema = schema();
2951 let delete_list: EntityUID = r#"Foo::Action::"DeleteList""#.parse().unwrap();
2952 let delete_user: EntityUID = r#"Foo::Action::"DeleteUser""#.parse().unwrap();
2953 let create_list: EntityUID = r#"Foo::Action::"CreateList""#.parse().unwrap();
2954 let get_list: EntityUID = r#"Foo::Action::"GetList""#.parse().unwrap();
2955 let got = schema
2956 .resources_for_action(&delete_list)
2957 .unwrap()
2958 .cloned()
2959 .collect::<Vec<_>>();
2960 assert_eq!(
2961 got,
2962 vec![EntityType::Specified("Foo::List".parse().unwrap())]
2963 );
2964 let got = schema
2965 .resources_for_action(&create_list)
2966 .unwrap()
2967 .cloned()
2968 .collect::<Vec<_>>();
2969 assert_eq!(
2970 got,
2971 vec![EntityType::Specified("Foo::Application".parse().unwrap())]
2972 );
2973 let got = schema
2974 .resources_for_action(&get_list)
2975 .unwrap()
2976 .cloned()
2977 .collect::<HashSet<_>>();
2978 assert_eq!(
2979 got,
2980 HashSet::from([
2981 EntityType::Specified("Foo::List".parse().unwrap()),
2982 EntityType::Specified("Foo::CoolList".parse().unwrap())
2983 ])
2984 );
2985 assert!(schema.principals_for_action(&delete_user).is_none());
2986 }
2987
2988 #[test]
2989 fn principal_parents() {
2990 let schema = schema();
2991 let user: Name = "Foo::User".parse().unwrap();
2992 let parents = schema
2993 .ancestors(&user)
2994 .unwrap()
2995 .cloned()
2996 .collect::<HashSet<_>>();
2997 let expected = HashSet::from([
2998 "Foo::Team".parse().unwrap(),
2999 "Foo::Application".parse().unwrap(),
3000 ]);
3001 assert_eq!(parents, expected);
3002 let parents = schema
3003 .ancestors(&"Foo::List".parse().unwrap())
3004 .unwrap()
3005 .cloned()
3006 .collect::<HashSet<_>>();
3007 let expected = HashSet::from(["Foo::Application".parse().unwrap()]);
3008 assert_eq!(parents, expected);
3009 assert!(schema.ancestors(&"Foo::Foo".parse().unwrap()).is_none());
3010 let parents = schema
3011 .ancestors(&"Foo::CoolList".parse().unwrap())
3012 .unwrap()
3013 .cloned()
3014 .collect::<HashSet<_>>();
3015 let expected = HashSet::from([]);
3016 assert_eq!(parents, expected);
3017 }
3018
3019 #[test]
3020 fn action_groups() {
3021 let schema = schema();
3022 let groups = schema.action_groups().cloned().collect::<HashSet<_>>();
3023 let expected = ["Read", "Write", "Create"]
3024 .into_iter()
3025 .map(|ty| format!("Foo::Action::\"{ty}\"").parse().unwrap())
3026 .collect::<HashSet<EntityUID>>();
3027 assert_eq!(groups, expected);
3028 }
3029
3030 #[test]
3031 fn actions() {
3032 let schema = schema();
3033 let actions = schema.actions().cloned().collect::<HashSet<_>>();
3034 let expected = [
3035 "Read",
3036 "Write",
3037 "Create",
3038 "DeleteList",
3039 "EditShare",
3040 "UpdateList",
3041 "CreateTask",
3042 "UpdateTask",
3043 "DeleteTask",
3044 "GetList",
3045 "GetLists",
3046 "CreateList",
3047 ]
3048 .into_iter()
3049 .map(|ty| format!("Foo::Action::\"{ty}\"").parse().unwrap())
3050 .collect::<HashSet<EntityUID>>();
3051 assert_eq!(actions, expected);
3052 }
3053
3054 #[test]
3055 fn entities() {
3056 let schema = schema();
3057 let entities = schema
3058 .entity_types()
3059 .map(|(ty, _)| ty)
3060 .cloned()
3061 .collect::<HashSet<_>>();
3062 let expected = [
3063 "Foo::List",
3064 "Foo::Application",
3065 "Foo::User",
3066 "Foo::CoolList",
3067 "Foo::Team",
3068 ]
3069 .into_iter()
3070 .map(|ty| ty.parse().unwrap())
3071 .collect::<HashSet<Name>>();
3072 assert_eq!(entities, expected);
3073 }
3074}