1use std::collections::hash_map::Entry;
20use std::collections::HashMap;
21use std::fmt::{Display, Formatter};
22
23use crate::ast::{
24 BinaryOp, EntityUID, Expr, ExprKind, Literal, PolicySet, RequestType, UnaryOp, Var,
25};
26use crate::entities::err::EntitiesError;
27use miette::Diagnostic;
28use serde::{Deserialize, Serialize};
29use serde_with::serde_as;
30use smol_str::SmolStr;
31use thiserror::Error;
32
33mod analysis;
34mod loader;
35pub mod slicing;
36mod type_annotations;
37
38use crate::validator::entity_manifest::analysis::{
39 EntityManifestAnalysisResult, WrappedAccessPaths,
40};
41use crate::validator::{
42 typecheck::{PolicyCheck, Typechecker},
43 types::Type,
44 ValidationMode, ValidatorSchema,
45};
46use crate::validator::{ValidationResult, Validator};
47
48#[doc = include_str!("../../experimental_warning.md")]
59#[serde_as]
60#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
61#[serde(rename_all = "camelCase")]
62pub struct EntityManifest {
63 #[serde_as(as = "Vec<(_, _)>")]
65 pub(crate) per_action: HashMap<RequestType, RootAccessTrie>,
66}
67
68#[doc = include_str!("../../experimental_warning.md")]
75pub type Fields = HashMap<SmolStr, Box<AccessTrie>>;
76
77#[doc = include_str!("../../experimental_warning.md")]
82#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
83#[serde(rename_all = "camelCase")]
84pub enum EntityRoot {
85 Literal(EntityUID),
87 Var(Var),
89}
90
91impl Display for EntityRoot {
92 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
93 match self {
94 EntityRoot::Literal(l) => write!(f, "{l}"),
95 EntityRoot::Var(v) => write!(f, "{v}"),
96 }
97 }
98}
99
100#[doc = include_str!("../../experimental_warning.md")]
113#[serde_as]
114#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
115#[serde(rename_all = "camelCase")]
116pub struct RootAccessTrie {
117 #[serde_as(as = "Vec<(_, _)>")]
119 pub(crate) trie: HashMap<EntityRoot, AccessTrie>,
120}
121
122#[doc = include_str!("../../experimental_warning.md")]
132#[serde_as]
133#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
134#[serde(rename_all = "camelCase")]
135pub struct AccessTrie {
136 #[serde_as(as = "Vec<(_, _)>")]
139 pub(crate) children: Fields,
140 pub(crate) ancestors_trie: RootAccessTrie,
145 pub(crate) is_ancestor: bool,
150 #[serde(skip_serializing)]
154 #[serde(skip_deserializing)]
155 pub(crate) node_type: Option<Type>,
156}
157
158#[derive(Debug, Clone, PartialEq, Eq, Hash)]
162pub(crate) struct AccessPath {
163 pub root: EntityRoot,
165 pub path: Vec<SmolStr>,
167}
168
169#[derive(Debug, Clone, Error, Hash, Eq, PartialEq)]
175#[error("entity slicing requires fully concrete policies. Got a policy with an unknown expression")]
176pub struct PartialExpressionError {}
177
178impl Diagnostic for PartialExpressionError {}
179
180#[derive(Debug, Clone, Error, Hash, Eq, PartialEq)]
186#[error("entity slicing requires a fully concrete request. Got a partial request")]
187pub struct PartialRequestError {}
188impl Diagnostic for PartialRequestError {}
189
190#[derive(Debug, Error)]
192pub enum EntityManifestError {
193 #[error("a validation error occurred")]
196 Validation(ValidationResult),
197 #[error(transparent)]
199 Entities(#[from] EntitiesError),
200
201 #[error(transparent)]
203 PartialRequest(#[from] PartialRequestError),
204 #[error(transparent)]
206 PartialExpression(#[from] PartialExpressionError),
207 #[error(transparent)]
209 UnsupportedCedarFeature(#[from] UnsupportedCedarFeatureError),
210}
211
212#[derive(Debug, Clone, Error, Diagnostic)]
217#[error("entity manifest analysis currently doesn't support Cedar feature: {feature}")]
218pub struct UnsupportedCedarFeatureError {
219 pub(crate) feature: SmolStr,
220}
221
222#[derive(Debug, Clone, Error, Hash, Eq, PartialEq)]
227#[error("entity manifest doesn't match schema. Schema is missing entity {entity}. Either you wrote an entity manifest by hand (not recommended) or you are using an out-of-date entity manifest with respect to the schema")]
228pub struct MismatchedMissingEntityError {
229 pub(crate) entity: EntityUID,
230}
231
232#[derive(Debug, Clone, Error, Hash, Eq, PartialEq)]
237#[error("entity manifests are only compatible with schemas that validate in strict mode. Tried to use an invalid schema with an entity manifest")]
238pub struct MismatchedNotStrictSchemaError {}
239
240#[derive(Debug, Clone, Error, Hash, Eq, PartialEq)]
251pub enum MismatchedEntityManifestError {
252 #[error(transparent)]
254 MismatchedMissingEntity(#[from] MismatchedMissingEntityError),
255 #[error(transparent)]
257 MismatchedNotStrictSchema(#[from] MismatchedNotStrictSchemaError),
258}
259
260#[derive(Debug, Error)]
262pub enum EntityManifestFromJsonError {
263 #[error(transparent)]
265 SerdeJsonParseError(#[from] serde_json::Error),
266 #[error(transparent)]
268 MismatchedEntityManifest(#[from] MismatchedEntityManifestError),
269}
270
271impl EntityManifest {
272 pub fn per_action(&self) -> &HashMap<RequestType, RootAccessTrie> {
275 &self.per_action
276 }
277
278 pub fn from_json_str(
281 json: &str,
282 schema: &ValidatorSchema,
283 ) -> Result<Self, EntityManifestFromJsonError> {
284 match serde_json::from_str::<EntityManifest>(json) {
285 Ok(manifest) => manifest.to_typed(schema).map_err(|e| e.into()),
286 Err(e) => Err(e.into()),
287 }
288 }
289
290 pub fn from_json_value(
293 value: serde_json::Value,
294 schema: &ValidatorSchema,
295 ) -> Result<Self, EntityManifestFromJsonError> {
296 match serde_json::from_value::<EntityManifest>(value) {
297 Ok(manifest) => manifest.to_typed(schema).map_err(|e| e.into()),
298 Err(e) => Err(e.into()),
299 }
300 }
301}
302
303fn union_fields_mut(first: &mut Fields, second: Fields) {
305 for (key, value) in second {
306 match first.entry(key) {
307 Entry::Occupied(mut occupied) => {
308 occupied.get_mut().union_mut(*value);
309 }
310 Entry::Vacant(vacant) => {
311 vacant.insert(value);
312 }
313 }
314 }
315}
316
317impl AccessPath {
318 pub fn to_root_access_trie(&self) -> RootAccessTrie {
320 self.to_root_access_trie_with_leaf(AccessTrie::default())
321 }
322
323 pub(crate) fn to_root_access_trie_with_leaf(&self, leaf_trie: AccessTrie) -> RootAccessTrie {
326 let mut current = leaf_trie;
327
328 for field in self.path.iter().rev() {
330 let mut fields = HashMap::new();
331 fields.insert(field.clone(), Box::new(current));
332
333 current = AccessTrie {
334 ancestors_trie: Default::default(),
335 is_ancestor: false,
336 children: fields,
337 node_type: None,
338 };
339 }
340
341 let mut primary_map = HashMap::new();
342
343 if current != AccessTrie::new() {
346 primary_map.insert(self.root.clone(), current);
347 }
348 RootAccessTrie { trie: primary_map }
349 }
350}
351
352impl RootAccessTrie {
353 pub fn trie(&self) -> &HashMap<EntityRoot, AccessTrie> {
356 &self.trie
357 }
358}
359
360impl RootAccessTrie {
361 pub fn new() -> Self {
363 Self {
364 trie: Default::default(),
365 }
366 }
367}
368
369impl RootAccessTrie {
370 pub fn union(mut self, other: Self) -> Self {
373 self.union_mut(other);
374 self
375 }
376
377 pub fn union_mut(&mut self, other: Self) {
379 for (key, value) in other.trie {
380 match self.trie.entry(key) {
381 Entry::Occupied(mut occupied) => {
382 occupied.get_mut().union_mut(value);
383 }
384 Entry::Vacant(vacant) => {
385 vacant.insert(value);
386 }
387 }
388 }
389 }
390}
391
392impl Default for RootAccessTrie {
393 fn default() -> Self {
394 Self::new()
395 }
396}
397
398impl AccessTrie {
399 pub fn union(mut self, other: Self) -> Self {
402 self.union_mut(other);
403 self
404 }
405
406 pub fn union_mut(&mut self, other: Self) {
408 union_fields_mut(&mut self.children, other.children);
409 self.ancestors_trie.union_mut(other.ancestors_trie);
410 self.is_ancestor = self.is_ancestor || other.is_ancestor;
411 }
412
413 pub fn children(&self) -> &Fields {
415 &self.children
416 }
417
418 pub fn ancestors_required(&self) -> &RootAccessTrie {
421 &self.ancestors_trie
422 }
423}
424
425impl AccessTrie {
426 pub(crate) fn new() -> Self {
428 Self {
429 children: Default::default(),
430 ancestors_trie: Default::default(),
431 is_ancestor: false,
432 node_type: None,
433 }
434 }
435}
436
437impl Default for AccessTrie {
438 fn default() -> Self {
439 Self::new()
440 }
441}
442
443pub fn compute_entity_manifest(
447 validator: &Validator,
448 policies: &PolicySet,
449) -> Result<EntityManifest, EntityManifestError> {
450 let validation_res = validator.validate(policies, ValidationMode::Strict);
452 if !validation_res.validation_passed() {
453 return Err(EntityManifestError::Validation(validation_res));
454 }
455
456 let mut manifest: HashMap<RequestType, RootAccessTrie> = HashMap::new();
457
458 let typechecker = Typechecker::new(validator.schema(), ValidationMode::Strict);
459 for policy in policies.policies() {
461 let request_envs = typechecker.typecheck_by_request_env(policy.template());
463 for (request_env, policy_check) in request_envs {
464 let new_primary_slice = match policy_check {
465 PolicyCheck::Success(typechecked_expr) => {
466 entity_manifest_from_expr(&typechecked_expr).map(|val| val.global_trie)
469 }
470 PolicyCheck::Irrelevant(_, _) => {
471 Ok(RootAccessTrie::new())
473 }
474
475 #[expect(
476 clippy::panic,
477 reason = "policy check should not fail after full strict validation above"
478 )]
479 PolicyCheck::Fail(_errors) => {
480 panic!("Policy check failed after validation succeeded")
481 }
482 }?;
483
484 let request_type = request_env
485 .to_request_type()
486 .ok_or(PartialRequestError {})?;
487 match manifest.entry(request_type) {
488 Entry::Occupied(mut occupied) => {
489 occupied.get_mut().union_mut(new_primary_slice);
490 }
491 Entry::Vacant(vacant) => {
492 vacant.insert(new_primary_slice);
493 }
494 }
495 }
496 }
497
498 #[expect(
499 clippy::unwrap_used,
500 reason = "entity manifest cannot be out of date, since it was computed from the schema given"
501 )]
502 Ok(EntityManifest {
503 per_action: manifest,
504 }
505 .to_typed(validator.schema())
506 .unwrap())
507}
508
509fn entity_manifest_from_expr(
513 expr: &Expr<Option<Type>>,
514) -> Result<EntityManifestAnalysisResult, EntityManifestError> {
515 match expr.expr_kind() {
516 ExprKind::Slot(slot_id) => {
517 if slot_id.is_principal() {
518 Ok(EntityManifestAnalysisResult::from_root(EntityRoot::Var(
519 Var::Principal,
520 )))
521 } else {
522 assert!(slot_id.is_resource());
523 Ok(EntityManifestAnalysisResult::from_root(EntityRoot::Var(
524 Var::Resource,
525 )))
526 }
527 }
528 ExprKind::Var(var) => Ok(EntityManifestAnalysisResult::from_root(EntityRoot::Var(
529 *var,
530 ))),
531 ExprKind::Lit(Literal::EntityUID(literal)) => Ok(EntityManifestAnalysisResult::from_root(
532 EntityRoot::Literal((**literal).clone()),
533 )),
534 ExprKind::Unknown(_) => Err(PartialExpressionError {})?,
535
536 ExprKind::Lit(_) => Ok(EntityManifestAnalysisResult::default()),
538 ExprKind::If {
539 test_expr,
540 then_expr,
541 else_expr,
542 } => Ok(entity_manifest_from_expr(test_expr)?
543 .empty_paths()
544 .union(entity_manifest_from_expr(then_expr)?)
545 .union(entity_manifest_from_expr(else_expr)?)),
546 ExprKind::And { left, right }
547 | ExprKind::Or { left, right }
548 | ExprKind::BinaryApp {
549 op: BinaryOp::Less | BinaryOp::LessEq | BinaryOp::Add | BinaryOp::Sub | BinaryOp::Mul,
550 arg1: left,
551 arg2: right,
552 } => Ok(entity_manifest_from_expr(left)?
553 .empty_paths()
554 .union(entity_manifest_from_expr(right)?.empty_paths())),
555 ExprKind::UnaryApp { op, arg } => {
556 match op {
557 UnaryOp::Not | UnaryOp::Neg => Ok(entity_manifest_from_expr(arg)?.empty_paths()),
559 UnaryOp::IsEmpty => {
560 #[expect(
561 clippy::expect_used,
562 reason = "Typechecking succeeded, so type annotations are present"
563 )]
564 let ty = arg
565 .data()
566 .as_ref()
567 .expect("Expected annotated types after typechecking");
568 Ok(entity_manifest_from_expr(arg)?
569 .full_type_required(ty)
570 .empty_paths())
571 }
572 }
573 }
574 ExprKind::BinaryApp {
575 op:
576 op @ (BinaryOp::Eq
577 | BinaryOp::In
578 | BinaryOp::Contains
579 | BinaryOp::ContainsAll
580 | BinaryOp::ContainsAny),
581 arg1,
582 arg2,
583 } => {
584 let mut arg1_res = entity_manifest_from_expr(arg1)?;
586 let arg2_res = entity_manifest_from_expr(arg2)?;
587
588 #[expect(
589 clippy::expect_used,
590 reason = "Typechecking succeeded, so type annotations are present"
591 )]
592 let ty1 = arg1
593 .data()
594 .as_ref()
595 .expect("Expected annotated types after typechecking");
596 #[expect(
597 clippy::expect_used,
598 reason = "Typechecking succeeded, so type annotations are present"
599 )]
600 let ty2 = arg2
601 .data()
602 .as_ref()
603 .expect("Expected annotated types after typechecking");
604
605 if matches!(op, BinaryOp::In) {
607 arg1_res = arg1_res
608 .with_ancestors_required(&arg2_res.resulting_paths.to_ancestor_access_trie());
609 }
610
611 Ok(arg1_res
614 .full_type_required(ty1)
615 .union(arg2_res.full_type_required(ty2))
616 .empty_paths())
617 }
618 ExprKind::BinaryApp {
619 op: BinaryOp::GetTag | BinaryOp::HasTag,
620 arg1: _,
621 arg2: _,
622 } => Err(UnsupportedCedarFeatureError {
623 feature: "entity tags".into(),
624 }
625 .into()),
626 ExprKind::ExtensionFunctionApp { fn_name: _, args } => {
627 let mut res = EntityManifestAnalysisResult::default();
633
634 for arg in args.iter() {
635 res = res.union(entity_manifest_from_expr(arg)?);
636 }
637 Ok(res)
638 }
639 ExprKind::Like { expr, pattern: _ }
640 | ExprKind::Is {
641 expr,
642 entity_type: _,
643 } => {
644 Ok(entity_manifest_from_expr(expr)?.empty_paths())
646 }
647 ExprKind::Set(contents) => {
648 let mut res = EntityManifestAnalysisResult::default();
649
650 for expr in &**contents {
652 let content = entity_manifest_from_expr(expr)?;
653
654 res = res.union(content);
655 }
656
657 res.resulting_paths = WrappedAccessPaths::SetLiteral(Box::new(res.resulting_paths));
659
660 Ok(res)
661 }
662 ExprKind::Record(content) => {
663 let mut record_contents = HashMap::new();
664 let mut global_trie = RootAccessTrie::default();
665
666 for (key, child_expr) in content.iter() {
667 let res = entity_manifest_from_expr(child_expr)?;
668 record_contents.insert(key.clone(), Box::new(res.resulting_paths));
669
670 global_trie = global_trie.union(res.global_trie);
671 }
672
673 Ok(EntityManifestAnalysisResult {
674 resulting_paths: WrappedAccessPaths::RecordLiteral(record_contents),
675 global_trie,
676 })
677 }
678 ExprKind::GetAttr { expr, attr } => {
679 Ok(entity_manifest_from_expr(expr)?.get_or_has_attr(attr))
680 }
681 ExprKind::HasAttr { expr, attr } => Ok(entity_manifest_from_expr(expr)?
682 .get_or_has_attr(attr)
683 .empty_paths()),
684 #[cfg(feature = "tolerant-ast")]
685 ExprKind::Error { .. } => Err(EntityManifestError::UnsupportedCedarFeature(
686 UnsupportedCedarFeatureError {
687 feature: "No support for AST error nodes".into(),
688 },
689 )),
690 }
691}
692
693#[cfg(test)]
694mod entity_slice_tests {
695 use crate::{ast::PolicyID, extensions::Extensions, parser::parse_policy};
696 use similar_asserts::assert_eq;
697
698 use super::*;
699
700 fn schema() -> ValidatorSchema {
702 ValidatorSchema::from_cedarschema_str(
703 "
704entity User = {
705 name: String,
706};
707
708entity Document;
709
710action Read appliesTo {
711 principal: [User],
712 resource: [Document]
713};
714 ",
715 Extensions::all_available(),
716 )
717 .unwrap()
718 .0
719 }
720
721 fn document_fields_schema() -> ValidatorSchema {
722 ValidatorSchema::from_cedarschema_str(
723 "
724entity User = {
725name: String,
726};
727
728entity Document = {
729owner: User,
730viewer: User,
731};
732
733action Read appliesTo {
734principal: [User],
735resource: [Document]
736};
737",
738 Extensions::all_available(),
739 )
740 .unwrap()
741 .0
742 }
743
744 #[test]
745 fn test_simple_entity_manifest() {
746 let mut pset = PolicySet::new();
747 let policy = parse_policy(
748 None,
749 r#"permit(principal, action, resource)
750when {
751 principal.name == "John"
752};"#,
753 )
754 .expect("should succeed");
755 pset.add(policy.into()).expect("should succeed");
756
757 let validator = Validator::new(schema());
758
759 let entity_manifest = compute_entity_manifest(&validator, &pset).expect("Should succeed");
760 let expected_rust = EntityManifest {
761 per_action: HashMap::from([(
762 RequestType {
763 principal: "User".parse().unwrap(),
764 resource: "Document".parse().unwrap(),
765 action: r#"Action::"Read""#.parse().unwrap(),
766 },
767 RootAccessTrie {
768 trie: HashMap::from([(
769 EntityRoot::Var(Var::Principal),
770 AccessTrie {
771 children: HashMap::from([(
772 SmolStr::new_static("name"),
773 Box::new(AccessTrie {
774 children: HashMap::new(),
775 ancestors_trie: RootAccessTrie::new(),
776 is_ancestor: false,
777 node_type: Some(Type::primitive_string()),
778 }),
779 )]),
780 ancestors_trie: RootAccessTrie::new(),
781 is_ancestor: false,
782 node_type: Some(Type::named_entity_reference("User".parse().unwrap())),
783 },
784 )]),
785 },
786 )]),
787 };
788 let expected = serde_json::json! ({
789 "perAction": [
790 [
791 {
792 "principal": "User",
793 "action": {
794 "ty": "Action",
795 "eid": "Read"
796 },
797 "resource": "Document"
798 },
799 {
800 "trie": [
801 [
802 {
803 "var": "principal"
804 },
805 {
806 "children": [
807 [
808 "name",
809 {
810 "children": [],
811 "ancestorsTrie": { "trie": []},
812 "isAncestor": false
813 }
814 ]
815 ],
816 "ancestorsTrie": { "trie": []},
817 "isAncestor": false
818 }
819 ]
820 ]
821 }
822 ]
823 ]
824 });
825 let expected_manifest =
826 EntityManifest::from_json_value(expected, validator.schema()).unwrap();
827 assert_eq!(entity_manifest, expected_manifest);
828 assert_eq!(entity_manifest, expected_rust);
829 }
830
831 #[test]
832 fn test_empty_entity_manifest() {
833 let mut pset = PolicySet::new();
834 let policy =
835 parse_policy(None, "permit(principal, action, resource);").expect("should succeed");
836 pset.add(policy.into()).expect("should succeed");
837
838 let validator = Validator::new(schema());
839
840 let entity_manifest = compute_entity_manifest(&validator, &pset).expect("Should succeed");
841 let expected = serde_json::json!(
842 {
843 "perAction": [
844 [
845 {
846 "principal": "User",
847 "action": {
848 "ty": "Action",
849 "eid": "Read"
850 },
851 "resource": "Document"
852 },
853 {
854 "trie": [
855 ]
856 }
857 ]
858 ]
859 });
860 let expected_manifest =
861 EntityManifest::from_json_value(expected, validator.schema()).unwrap();
862 assert_eq!(entity_manifest, expected_manifest);
863 }
864
865 #[test]
866 fn test_entity_manifest_ancestors_required() {
867 let mut pset = PolicySet::new();
868 let policy = parse_policy(
869 None,
870 "permit(principal, action, resource)
871when {
872 principal in resource || principal.manager in resource
873};",
874 )
875 .expect("should succeed");
876 pset.add(policy.into()).expect("should succeed");
877
878 let schema = ValidatorSchema::from_cedarschema_str(
879 "
880entity User in [Document] = {
881 name: String,
882 manager: User
883};
884entity Document;
885action Read appliesTo {
886 principal: [User],
887 resource: [Document]
888};
889 ",
890 Extensions::all_available(),
891 )
892 .unwrap()
893 .0;
894 let validator = Validator::new(schema);
895
896 let entity_manifest = compute_entity_manifest(&validator, &pset).expect("Should succeed");
897 let expected = serde_json::json!(
898 {
899 "perAction": [
900 [
901 {
902 "principal": "User",
903 "action": {
904 "ty": "Action",
905 "eid": "Read"
906 },
907 "resource": "Document"
908 },
909 {
910 "trie": [
911 [
912 {
913 "var": "principal"
914 },
915 {
916 "children": [
917 [
918 "manager",
919 {
920 "children": [],
921 "ancestorsTrie": {
922 "trie": [
923 [
924 {
925 "var": "resource",
926 },
927 {
928 "children": [],
929 "isAncestor": true,
930 "ancestorsTrie": { "trie": [] }
931 }
932 ]
933 ]
934 },
935 "isAncestor": false
936 }
937 ]
938 ],
939 "ancestorsTrie": {
940 "trie": [
941 [
942 {
943 "var": "resource",
944 },
945 {
946 "children": [],
947 "isAncestor": true,
948 "ancestorsTrie": { "trie": [] }
949 }
950 ]
951 ]
952 },
953 "isAncestor": false
954 }
955 ]
956 ]
957 }
958 ]
959 ]
960 });
961 let expected_manifest =
962 EntityManifest::from_json_value(expected, validator.schema()).unwrap();
963 assert_eq!(entity_manifest, expected_manifest);
964 }
965
966 #[test]
967 fn test_entity_manifest_multiple_types() {
968 let mut pset = PolicySet::new();
969 let policy = parse_policy(
970 None,
971 r#"permit(principal, action, resource)
972when {
973 principal.name == "John"
974};"#,
975 )
976 .expect("should succeed");
977 pset.add(policy.into()).expect("should succeed");
978
979 let schema = ValidatorSchema::from_cedarschema_str(
980 "
981entity User = {
982 name: String,
983};
984
985entity OtherUserType = {
986 name: String,
987 irrelevant: String,
988};
989
990entity Document;
991
992action Read appliesTo {
993 principal: [User, OtherUserType],
994 resource: [Document]
995};
996 ",
997 Extensions::all_available(),
998 )
999 .unwrap()
1000 .0;
1001 let validator = Validator::new(schema);
1002
1003 let entity_manifest = compute_entity_manifest(&validator, &pset).expect("Should succeed");
1004 let expected = serde_json::json!(
1005 {
1006 "perAction": [
1007 [
1008 {
1009 "principal": "User",
1010 "action": {
1011 "ty": "Action",
1012 "eid": "Read"
1013 },
1014 "resource": "Document"
1015 },
1016 {
1017 "trie": [
1018 [
1019 {
1020 "var": "principal"
1021 },
1022 {
1023 "children": [
1024 [
1025 "name",
1026 {
1027 "children": [],
1028 "ancestorsTrie": { "trie": []},
1029 "isAncestor": false
1030 }
1031 ]
1032 ],
1033 "ancestorsTrie": { "trie": []},
1034 "isAncestor": false
1035 }
1036 ]
1037 ]
1038 }
1039 ],
1040 [
1041 {
1042 "principal": "OtherUserType",
1043 "action": {
1044 "ty": "Action",
1045 "eid": "Read"
1046 },
1047 "resource": "Document"
1048 },
1049 {
1050 "trie": [
1051 [
1052 {
1053 "var": "principal"
1054 },
1055 {
1056 "children": [
1057 [
1058 "name",
1059 {
1060 "children": [],
1061 "ancestorsTrie": { "trie": []},
1062 "isAncestor": false
1063 }
1064 ]
1065 ],
1066 "ancestorsTrie": { "trie": []},
1067 "isAncestor": false
1068 }
1069 ]
1070 ]
1071 }
1072 ]
1073 ]
1074 });
1075 let expected_manifest =
1076 EntityManifest::from_json_value(expected, validator.schema()).unwrap();
1077 assert_eq!(entity_manifest, expected_manifest);
1078 }
1079
1080 #[test]
1081 fn test_entity_manifest_multiple_branches() {
1082 let mut pset = PolicySet::new();
1083 let policy1 = parse_policy(
1084 None,
1085 r#"
1086permit(
1087 principal,
1088 action == Action::"Read",
1089 resource
1090)
1091when
1092{
1093 resource.readers.contains(principal)
1094};"#,
1095 )
1096 .unwrap();
1097 let policy2 = parse_policy(
1098 Some(PolicyID::from_string("Policy2")),
1099 r#"permit(
1100 principal,
1101 action == Action::"Read",
1102 resource
1103)
1104when
1105{
1106 resource.metadata.owner == principal
1107};"#,
1108 )
1109 .unwrap();
1110 pset.add(policy1.into()).expect("should succeed");
1111 pset.add(policy2.into()).expect("should succeed");
1112
1113 let schema = ValidatorSchema::from_cedarschema_str(
1114 "
1115entity User;
1116
1117entity Metadata = {
1118 owner: User,
1119 time: String,
1120};
1121
1122entity Document = {
1123 metadata: Metadata,
1124 readers: Set<User>,
1125};
1126
1127action Read appliesTo {
1128 principal: [User],
1129 resource: [Document]
1130};
1131 ",
1132 Extensions::all_available(),
1133 )
1134 .unwrap()
1135 .0;
1136 let validator = Validator::new(schema);
1137
1138 let entity_manifest = compute_entity_manifest(&validator, &pset).expect("Should succeed");
1139 let expected = serde_json::json!(
1140 {
1141 "perAction": [
1142 [
1143 {
1144 "principal": "User",
1145 "action": {
1146 "ty": "Action",
1147 "eid": "Read"
1148 },
1149 "resource": "Document"
1150 },
1151 {
1152 "trie": [
1153 [
1154 {
1155 "var": "resource"
1156 },
1157 {
1158 "children": [
1159 [
1160 "metadata",
1161 {
1162 "children": [
1163 [
1164 "owner",
1165 {
1166 "children": [],
1167 "ancestorsTrie": { "trie": []},
1168 "isAncestor": false
1169 }
1170 ]
1171 ],
1172 "ancestorsTrie": { "trie": []},
1173 "isAncestor": false
1174 }
1175 ],
1176 [
1177 "readers",
1178 {
1179 "children": [],
1180 "ancestorsTrie": { "trie": []},
1181 "isAncestor": false
1182 }
1183 ]
1184 ],
1185 "ancestorsTrie": { "trie": []},
1186 "isAncestor": false
1187 }
1188 ],
1189 ]
1190 }
1191 ]
1192 ]
1193 });
1194 let expected_manifest =
1195 EntityManifest::from_json_value(expected, validator.schema()).unwrap();
1196 assert_eq!(entity_manifest, expected_manifest);
1197 }
1198
1199 #[test]
1200 fn test_entity_manifest_struct_equality() {
1201 let mut pset = PolicySet::new();
1202 let policy = parse_policy(
1205 None,
1206 r#"permit(principal, action, resource)
1207when {
1208 principal.metadata.nickname == "timmy" && principal.metadata == {
1209 "friends": [ "oliver" ],
1210 "nickname": "timmy"
1211 }
1212};"#,
1213 )
1214 .expect("should succeed");
1215 pset.add(policy.into()).expect("should succeed");
1216
1217 let schema = ValidatorSchema::from_cedarschema_str(
1218 "
1219entity User = {
1220 name: String,
1221 metadata: {
1222 friends: Set<String>,
1223 nickname: String,
1224 },
1225};
1226
1227entity Document;
1228
1229action BeSad appliesTo {
1230 principal: [User],
1231 resource: [Document]
1232};
1233 ",
1234 Extensions::all_available(),
1235 )
1236 .unwrap()
1237 .0;
1238 let validator = Validator::new(schema);
1239
1240 let entity_manifest = compute_entity_manifest(&validator, &pset).expect("Should succeed");
1241 let expected = serde_json::json!(
1242 {
1243 "perAction": [
1244 [
1245 {
1246 "principal": "User",
1247 "action": {
1248 "ty": "Action",
1249 "eid": "BeSad"
1250 },
1251 "resource": "Document"
1252 },
1253 {
1254 "trie": [
1255 [
1256 {
1257 "var": "principal"
1258 },
1259 {
1260 "children": [
1261 [
1262 "metadata",
1263 {
1264 "children": [
1265 [
1266 "nickname",
1267 {
1268 "children": [],
1269 "ancestorsTrie": { "trie": []},
1270 "isAncestor": false
1271 }
1272 ],
1273 [
1274 "friends",
1275 {
1276 "children": [],
1277 "ancestorsTrie": { "trie": []},
1278 "isAncestor": false
1279 }
1280 ]
1281 ],
1282 "ancestorsTrie": { "trie": []},
1283 "isAncestor": false
1284 }
1285 ]
1286 ],
1287 "ancestorsTrie": { "trie": []},
1288 "isAncestor": false
1289 }
1290 ]
1291 ]
1292 }
1293 ]
1294 ]
1295 });
1296 let expected_manifest =
1297 EntityManifest::from_json_value(expected, validator.schema()).unwrap();
1298 assert_eq!(entity_manifest, expected_manifest);
1299 }
1300
1301 #[test]
1302 fn test_entity_manifest_struct_equality_left_right_different() {
1303 let mut pset = PolicySet::new();
1304 let policy = parse_policy(
1307 None,
1308 r#"permit(principal, action, resource)
1309when {
1310 principal.metadata == resource.metadata
1311};"#,
1312 )
1313 .expect("should succeed");
1314 pset.add(policy.into()).expect("should succeed");
1315
1316 let schema = ValidatorSchema::from_cedarschema_str(
1317 "
1318entity User = {
1319 name: String,
1320 metadata: {
1321 friends: Set<String>,
1322 nickname: String,
1323 },
1324};
1325
1326entity Document;
1327
1328action Hello appliesTo {
1329 principal: [User],
1330 resource: [User]
1331};
1332 ",
1333 Extensions::all_available(),
1334 )
1335 .unwrap()
1336 .0;
1337 let validator = Validator::new(schema);
1338
1339 let entity_manifest = compute_entity_manifest(&validator, &pset).expect("Should succeed");
1340 let expected = serde_json::json!(
1341 {
1342 "perAction": [
1343 [
1344 {
1345 "principal": "User",
1346 "action": {
1347 "ty": "Action",
1348 "eid": "Hello"
1349 },
1350 "resource": "User"
1351 },
1352 {
1353 "trie": [
1354 [
1355 {
1356 "var": "resource"
1357 },
1358 {
1359 "children": [
1360 [
1361 "metadata",
1362 {
1363 "children": [
1364 [
1365 "friends",
1366 {
1367 "children": [],
1368 "ancestorsTrie": { "trie": []},
1369 "isAncestor": false
1370 }
1371 ],
1372 [
1373 "nickname",
1374 {
1375 "children": [],
1376 "ancestorsTrie": { "trie": []},
1377 "isAncestor": false
1378 }
1379 ]
1380 ],
1381 "ancestorsTrie": { "trie": []},
1382 "isAncestor": false
1383 }
1384 ]
1385 ],
1386 "ancestorsTrie": { "trie": []},
1387 "isAncestor": false
1388 }
1389 ],
1390 [
1391 {
1392 "var": "principal"
1393 },
1394 {
1395 "children": [
1396 [
1397 "metadata",
1398 {
1399 "children": [
1400 [
1401 "nickname",
1402 {
1403 "children": [],
1404 "ancestorsTrie": { "trie": []},
1405 "isAncestor": false
1406 }
1407 ],
1408 [
1409 "friends",
1410 {
1411 "children": [],
1412 "ancestorsTrie": { "trie": []},
1413 "isAncestor": false
1414 }
1415 ]
1416 ],
1417 "ancestorsTrie": { "trie": []},
1418 "isAncestor": false
1419 }
1420 ]
1421 ],
1422 "ancestorsTrie": { "trie": []},
1423 "isAncestor": false
1424 }
1425 ]
1426 ]
1427 }
1428 ]
1429 ]
1430 });
1431 let expected_manifest =
1432 EntityManifest::from_json_value(expected, validator.schema()).unwrap();
1433 assert_eq!(entity_manifest, expected_manifest);
1434 }
1435
1436 #[test]
1437 fn test_entity_manifest_with_if() {
1438 let mut pset = PolicySet::new();
1439
1440 let validator = Validator::new(document_fields_schema());
1441
1442 let policy = parse_policy(
1443 None,
1444 r#"permit(principal, action, resource)
1445when {
1446 if principal.name == "John"
1447 then resource.owner.name == User::"oliver".name
1448 else resource.viewer == User::"oliver"
1449};"#,
1450 )
1451 .expect("should succeed");
1452 pset.add(policy.into()).expect("should succeed");
1453
1454 let entity_manifest = compute_entity_manifest(&validator, &pset).expect("Should succeed");
1455 let expected = serde_json::json! ( {
1456 "perAction": [
1457 [
1458 {
1459 "principal": "User",
1460 "action": {
1461 "ty": "Action",
1462 "eid": "Read"
1463 },
1464 "resource": "Document"
1465 },
1466 {
1467 "trie": [
1468 [
1469 {
1470 "var": "principal"
1471 },
1472 {
1473 "children": [
1474 [
1475 "name",
1476 {
1477 "children": [],
1478 "ancestorsTrie": { "trie": []},
1479 "isAncestor": false
1480 }
1481 ]
1482 ],
1483 "ancestorsTrie": { "trie": []},
1484 "isAncestor": false
1485 }
1486 ],
1487 [
1488 {
1489 "literal": {
1490 "ty": "User",
1491 "eid": "oliver"
1492 }
1493 },
1494 {
1495 "children": [
1496 [
1497 "name",
1498 {
1499 "children": [],
1500 "ancestorsTrie": { "trie": []},
1501 "isAncestor": false
1502 }
1503 ]
1504 ],
1505 "ancestorsTrie": { "trie": []},
1506 "isAncestor": false
1507 }
1508 ],
1509 [
1510 {
1511 "var": "resource"
1512 },
1513 {
1514 "children": [
1515 [
1516 "viewer",
1517 {
1518 "children": [],
1519 "ancestorsTrie": { "trie": []},
1520 "isAncestor": false
1521 }
1522 ],
1523 [
1524 "owner",
1525 {
1526 "children": [
1527 [
1528 "name",
1529 {
1530 "children": [],
1531 "ancestorsTrie": { "trie": []},
1532 "isAncestor": false
1533 }
1534 ]
1535 ],
1536 "ancestorsTrie": { "trie": []},
1537 "isAncestor": false
1538 }
1539 ]
1540 ],
1541 "ancestorsTrie": { "trie": []},
1542 "isAncestor": false
1543 }
1544 ]
1545 ]
1546 }
1547 ]
1548 ]
1549 }
1550 );
1551 let expected_manifest =
1552 EntityManifest::from_json_value(expected, validator.schema()).unwrap();
1553 assert_eq!(entity_manifest, expected_manifest);
1554 }
1555
1556 #[test]
1557 fn test_entity_manifest_if_literal_record() {
1558 let mut pset = PolicySet::new();
1559
1560 let validator = Validator::new(document_fields_schema());
1561
1562 let policy = parse_policy(
1563 None,
1564 r#"permit(principal, action, resource)
1565when {
1566 {
1567 "myfield":
1568 {
1569 "secondfield":
1570 if principal.name == "yihong"
1571 then principal
1572 else resource.owner,
1573 "ignored but still important due to errors":
1574 resource.viewer
1575 }
1576 }["myfield"]["secondfield"].name == "pavel"
1577};"#,
1578 )
1579 .expect("should succeed");
1580 pset.add(policy.into()).expect("should succeed");
1581
1582 let entity_manifest = compute_entity_manifest(&validator, &pset).expect("Should succeed");
1583 let expected = serde_json::json! ( {
1584 "perAction": [
1585 [
1586 {
1587 "principal": "User",
1588 "action": {
1589 "ty": "Action",
1590 "eid": "Read"
1591 },
1592 "resource": "Document"
1593 },
1594 {
1595 "trie": [
1596 [
1597 {
1598 "var": "principal"
1599 },
1600 {
1601 "children": [
1602 [
1603 "name",
1604 {
1605 "children": [],
1606 "ancestorsTrie": { "trie": []},
1607 "isAncestor": false
1608 }
1609 ]
1610 ],
1611 "ancestorsTrie": { "trie": []},
1612 "isAncestor": false
1613 }
1614 ],
1615 [
1616 {
1617 "var": "resource"
1618 },
1619 {
1620 "children": [
1621 [
1622 "viewer",
1623 {
1624 "children": [],
1625 "ancestorsTrie": { "trie": []},
1626 "isAncestor": false
1627 }
1628 ],
1629 [
1630 "owner",
1631 {
1632 "children": [
1633 [
1634 "name",
1635 {
1636 "children": [],
1637 "ancestorsTrie": { "trie": []},
1638 "isAncestor": false
1639 }
1640 ]
1641 ],
1642 "ancestorsTrie": { "trie": []},
1643 "isAncestor": false
1644 }
1645 ]
1646 ],
1647 "ancestorsTrie": { "trie": []},
1648 "isAncestor": false
1649 }
1650 ]
1651 ]
1652 }
1653 ]
1654 ]
1655 }
1656 );
1657 let expected_manifest =
1658 EntityManifest::from_json_value(expected, validator.schema()).unwrap();
1659 assert_eq!(entity_manifest, expected_manifest);
1660 }
1661
1662 #[test]
1663 fn test_entity_manifest_action_in() {
1664 let mut pset = PolicySet::new();
1665 let policy = parse_policy(
1666 None,
1667 r#"permit(principal, action in Action::"parent", resource);"#,
1668 )
1669 .unwrap();
1670 pset.add(policy.into()).unwrap();
1671
1672 let schema = ValidatorSchema::from_cedarschema_str(
1673 "entity e; action action in parent appliesTo { principal: e, resource: e}; action parent;",
1674 Extensions::all_available(),
1675 )
1676 .unwrap()
1677 .0;
1678 let validator = Validator::new(schema);
1679
1680 let entity_manifest = compute_entity_manifest(&validator, &pset).expect("Should succeed");
1681 let expected = EntityManifest::from_json_value(
1682 serde_json::json!({
1683 "perAction": [
1684 [
1685 {
1686 "principal": "e",
1687 "action": {
1688 "ty": "Action",
1689 "eid": "action"
1690 },
1691 "resource": "e"
1692 },
1693 {
1694 "trie": [
1695 [
1696 {
1697 "var": "action"
1698 },
1699 {
1700 "children": [ ],
1701 "ancestorsTrie": {
1702 "trie": [
1703 [
1704 { "literal": {"ty": "Action", "eid": "parent"} },
1705 {
1706 "children": [],
1707 "isAncestor": true,
1708 "ancestorsTrie": { "trie": [] }
1709 }
1710 ]
1711 ]
1712 },
1713 "isAncestor": false
1714 }
1715 ],
1716 ]
1717 }
1718 ]
1719 ]
1720 }),
1721 validator.schema(),
1722 )
1723 .unwrap();
1724 assert_eq!(entity_manifest, expected);
1725 }
1726}