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