1mod err;
20pub use err::*;
21mod expr;
22pub use expr::*;
23mod policy_set;
24pub use policy_set::*;
25mod scope_constraints;
26pub use scope_constraints::*;
27mod annotation;
28pub use annotation::*;
29
30use crate::ast::EntityUID;
31use crate::ast::{self, Annotation};
32use crate::entities::json::{err::JsonDeserializationError, EntityUidJson};
33use crate::expr_builder::ExprBuilder;
34use crate::parser::cst;
35use crate::parser::err::{parse_errors, ParseErrors, ToASTError, ToASTErrorKind};
36use crate::parser::util::{flatten_tuple_2, flatten_tuple_4};
37#[cfg(feature = "tolerant-ast")]
38use crate::parser::Loc;
39use serde::{Deserialize, Serialize};
40use serde_with::serde_as;
41use std::collections::{BTreeMap, HashMap};
42
43#[cfg(feature = "wasm")]
44extern crate tsify;
45
46#[serde_as]
53#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
54#[serde(deny_unknown_fields)]
55#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
56#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
57#[cfg_attr(feature = "wasm", serde(rename = "PolicyJson"))]
58pub struct Policy {
59 effect: ast::Effect,
61 principal: PrincipalConstraint,
63 action: ActionConstraint,
65 resource: ResourceConstraint,
67 conditions: Vec<Clause>,
69 #[serde(default)]
71 #[serde(skip_serializing_if = "Annotations::is_empty")]
72 annotations: Annotations,
73}
74
75#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
77#[serde(deny_unknown_fields)]
78#[serde(tag = "kind", content = "body")]
79#[serde(rename_all = "camelCase")]
80#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
81#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
82pub enum Clause {
83 When(Expr),
85 Unless(Expr),
87}
88
89impl Policy {
90 pub fn link(self, vals: &HashMap<ast::SlotId, EntityUidJson>) -> Result<Self, LinkingError> {
95 Ok(Policy {
96 effect: self.effect,
97 principal: self.principal.link(vals)?,
98 action: self.action.link(vals)?,
99 resource: self.resource.link(vals)?,
100 conditions: self
101 .conditions
102 .into_iter()
103 .map(|clause| clause.link(vals))
104 .collect::<Result<Vec<_>, _>>()?,
105 annotations: self.annotations,
106 })
107 }
108
109 pub fn sub_entity_literals(
111 self,
112 mapping: &BTreeMap<EntityUID, EntityUID>,
113 ) -> Result<Self, JsonDeserializationError> {
114 Ok(Policy {
115 effect: self.effect,
116 principal: self.principal.sub_entity_literals(mapping)?,
117 action: self.action.sub_entity_literals(mapping)?,
118 resource: self.resource.sub_entity_literals(mapping)?,
119 conditions: self
120 .conditions
121 .into_iter()
122 .map(|clause| clause.sub_entity_literals(mapping))
123 .collect::<Result<Vec<_>, _>>()?,
124 annotations: self.annotations,
125 })
126 }
127
128 pub fn is_template(&self) -> bool {
130 self.principal.has_slot()
131 || self.action.has_slot()
132 || self.resource.has_slot()
133 || self.conditions.iter().any(|c| c.has_slot())
134 }
135}
136
137impl Clause {
138 pub fn link(self, _vals: &HashMap<ast::SlotId, EntityUidJson>) -> Result<Self, LinkingError> {
142 Ok(self)
144 }
145
146 pub fn sub_entity_literals(
148 self,
149 mapping: &BTreeMap<EntityUID, EntityUID>,
150 ) -> Result<Self, JsonDeserializationError> {
151 use Clause::{Unless, When};
152 match self {
153 When(e) => Ok(When(e.sub_entity_literals(mapping)?)),
154 Unless(e) => Ok(Unless(e.sub_entity_literals(mapping)?)),
155 }
156 }
157
158 pub fn has_slot(&self) -> bool {
160 false
162 }
163}
164
165impl TryFrom<cst::Policy> for Policy {
166 type Error = ParseErrors;
167 fn try_from(policy: cst::Policy) -> Result<Policy, ParseErrors> {
168 let policy = match policy {
169 cst::Policy::Policy(policy_impl) => policy_impl,
170 #[cfg(feature = "tolerant-ast")]
171 cst::Policy::PolicyError => {
172 return Err(ParseErrors::singleton(ToASTError::new(
173 ToASTErrorKind::CSTErrorNode,
174 Some(Loc::new(0..1, "CSTErrorNode".into())),
176 )));
177 }
178 };
179 let maybe_effect = policy.effect.to_effect();
180 let maybe_scope = policy.extract_scope();
181 let maybe_annotations = policy.get_ast_annotations(|v, l| {
182 Some(Annotation {
183 val: v?,
184 loc: l.cloned(),
185 })
186 });
187 let maybe_conditions = ParseErrors::transpose(policy.conds.into_iter().map(|node| {
188 let (cond, loc) = node.into_inner();
189 let cond = cond.ok_or_else(|| {
190 ParseErrors::singleton(ToASTError::new(ToASTErrorKind::EmptyClause(None), loc))
191 })?;
192 cond.try_into()
193 }));
194
195 let (effect, annotations, (principal, action, resource), conditions) = flatten_tuple_4(
196 maybe_effect,
197 maybe_annotations,
198 maybe_scope,
199 maybe_conditions,
200 )?;
201 Ok(Policy {
202 effect,
203 principal: principal.into(),
204 action: action.into(),
205 resource: resource.into(),
206 conditions,
207 annotations: Annotations(annotations),
208 })
209 }
210}
211
212impl TryFrom<cst::Cond> for Clause {
213 type Error = ParseErrors;
214 fn try_from(cond: cst::Cond) -> Result<Clause, ParseErrors> {
215 let maybe_is_when = cond.cond.to_cond_is_when();
216 match cond.expr {
217 None => {
218 let maybe_ident = maybe_is_when.map(|is_when| {
219 cst::Ident::Ident(if is_when { "when" } else { "unless" }.into())
220 });
221 Err(cond
222 .cond
223 .to_ast_err(ToASTErrorKind::EmptyClause(maybe_ident.ok()))
224 .into())
225 }
226 Some(ref e) => {
227 let maybe_expr = e.try_into();
228 let (is_when, expr) = flatten_tuple_2(maybe_is_when, maybe_expr)?;
229 Ok(if is_when {
230 Clause::When(expr)
231 } else {
232 Clause::Unless(expr)
233 })
234 }
235 }
236 }
237}
238
239impl Policy {
240 pub fn try_into_ast_policy(
245 self,
246 id: Option<ast::PolicyID>,
247 ) -> Result<ast::Policy, FromJsonError> {
248 let template: ast::Template = self.try_into_ast_policy_or_template(id)?;
249 ast::StaticPolicy::try_from(template)
250 .map(Into::into)
251 .map_err(Into::into)
252 }
253
254 pub fn try_into_ast_template(
260 self,
261 id: Option<ast::PolicyID>,
262 ) -> Result<ast::Template, FromJsonError> {
263 let template: ast::Template = self.try_into_ast_policy_or_template(id)?;
264 if template.slots().count() == 0 {
265 Err(FromJsonError::PolicyToTemplate(
266 parse_errors::ExpectedTemplate::new(),
267 ))
268 } else {
269 Ok(template)
270 }
271 }
272
273 pub fn try_into_ast_policy_or_template(
279 self,
280 id: Option<ast::PolicyID>,
281 ) -> Result<ast::Template, FromJsonError> {
282 let id = id.unwrap_or_else(|| ast::PolicyID::from_string("JSON policy"));
283 let mut conds_rev_iter = self
286 .conditions
287 .into_iter()
288 .map(|cond| cond.try_into_ast(&id))
289 .rev()
290 .collect::<Result<Vec<_>, _>>()?
291 .into_iter();
292 let conditions = if let Some(last_expr) = conds_rev_iter.next() {
293 let builder = ast::ExprBuilder::with_data(());
294 Some(conds_rev_iter.fold(last_expr, |acc, prev| builder.clone().and(prev, acc)))
295 } else {
296 None
297 };
298 Ok(ast::Template::new(
299 id,
300 None,
301 self.annotations
302 .0
303 .into_iter()
304 .map(|(key, val)| {
305 (
306 key,
307 ast::Annotation::with_optional_value(val.map(|v| v.val), None),
308 )
309 })
310 .collect(),
311 self.effect,
312 self.principal.try_into()?,
313 self.action.try_into()?,
314 self.resource.try_into()?,
315 conditions,
316 ))
317 }
318}
319
320impl Clause {
321 fn filter_slots(e: ast::Expr, is_when: bool) -> Result<ast::Expr, FromJsonError> {
322 let first_slot = e.slots().next();
323 if let Some(slot) = first_slot {
324 Err(parse_errors::SlotsInConditionClause {
325 slot,
326 clause_type: if is_when { "when" } else { "unless" },
327 }
328 .into())
329 } else {
330 Ok(e)
331 }
332 }
333 fn try_into_ast(self, id: &ast::PolicyID) -> Result<ast::Expr, FromJsonError> {
335 match self {
336 Clause::When(expr) => Self::filter_slots(expr.try_into_ast(id)?, true),
337 Clause::Unless(expr) => {
338 Self::filter_slots(ast::Expr::not(expr.try_into_ast(id)?), false)
339 }
340 }
341 }
342}
343
344impl From<ast::Policy> for Policy {
346 fn from(ast: ast::Policy) -> Policy {
347 Policy {
348 effect: ast.effect(),
349 principal: ast.principal_constraint().into(),
350 action: ast.action_constraint().clone().into(),
351 resource: ast.resource_constraint().into(),
352 conditions: ast
353 .non_scope_constraints()
354 .map(|e| e.clone().into())
355 .as_slice()
356 .to_vec(),
357 annotations: Annotations(
358 ast.annotations()
359 .map(|(k, v)| (k.clone(), Some(v.clone())))
363 .collect(),
364 ),
365 }
366 }
367}
368
369impl From<ast::Template> for Policy {
371 fn from(ast: ast::Template) -> Policy {
372 Policy {
373 effect: ast.effect(),
374 principal: ast.principal_constraint().clone().into(),
375 action: ast.action_constraint().clone().into(),
376 resource: ast.resource_constraint().clone().into(),
377 conditions: ast
378 .non_scope_constraints()
379 .map(|e| e.clone().into())
380 .as_slice()
381 .to_vec(),
382 annotations: Annotations(
383 ast.annotations()
384 .map(|(k, v)| (k.clone(), Some(v.clone())))
388 .collect(),
389 ),
390 }
391 }
392}
393
394impl<T: Clone> From<ast::Expr<T>> for Clause {
395 fn from(expr: ast::Expr<T>) -> Clause {
396 Clause::When(expr.into_expr::<Builder>())
397 }
398}
399
400impl std::fmt::Display for Policy {
401 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
402 for (k, v) in self.annotations.0.iter() {
403 write!(f, "@{k}")?;
404 if let Some(v) = v {
405 write!(f, "({v})")?;
406 }
407 writeln!(f)?;
408 }
409 write!(
410 f,
411 "{}({}, {}, {})",
412 self.effect, self.principal, self.action, self.resource
413 )?;
414 for condition in &self.conditions {
415 write!(f, " {condition}")?;
416 }
417 write!(f, ";")
418 }
419}
420
421impl std::fmt::Display for Clause {
422 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
423 match self {
424 Self::When(expr) => write!(f, "when {{ {expr} }}"),
425 Self::Unless(expr) => write!(f, "unless {{ {expr} }}"),
426 }
427 }
428}
429
430#[allow(clippy::panic)]
432#[allow(clippy::indexing_slicing)]
434#[cfg(test)]
435mod test {
436 use super::*;
437 use crate::parser::{self, parse_policy_or_template_to_est};
438 use crate::test_utils::*;
439 use cool_asserts::assert_matches;
440 use serde_json::json;
441
442 #[track_caller]
445 fn est_roundtrip(est: Policy) -> Policy {
446 let json = serde_json::to_value(est).expect("failed to serialize to JSON");
447 serde_json::from_value(json.clone()).unwrap_or_else(|e| {
448 panic!(
449 "failed to deserialize from JSON: {e}\n\nJSON was:\n{}",
450 serde_json::to_string_pretty(&json).expect("failed to convert JSON to string")
451 )
452 })
453 }
454
455 #[track_caller]
458 fn text_roundtrip(est: &Policy) -> Policy {
459 let text = est.to_string();
460 let cst = parser::text_to_cst::parse_policy(&text)
461 .expect("Failed to convert to CST")
462 .node
463 .expect("Node should not be empty");
464 cst.try_into().expect("Failed to convert to EST")
465 }
466
467 #[track_caller]
470 fn ast_roundtrip(est: Policy) -> Policy {
471 let ast = est
472 .try_into_ast_policy(None)
473 .expect("Failed to convert to AST");
474 ast.into()
475 }
476
477 #[track_caller]
480 fn ast_roundtrip_template(est: Policy) -> Policy {
481 let ast = est
482 .try_into_ast_policy_or_template(None)
483 .expect("Failed to convert to AST");
484 ast.into()
485 }
486
487 #[track_caller]
490 fn circular_roundtrip(est: Policy) -> Policy {
491 let ast = est
492 .try_into_ast_policy(None)
493 .expect("Failed to convert to AST");
494 let text = ast.to_string();
495 let cst = parser::text_to_cst::parse_policy(&text)
496 .expect("Failed to convert to CST")
497 .node
498 .expect("Node should not be empty");
499 cst.try_into().expect("Failed to convert to EST")
500 }
501
502 #[track_caller]
505 fn circular_roundtrip_template(est: Policy) -> Policy {
506 let ast = est
507 .try_into_ast_policy_or_template(None)
508 .expect("Failed to convert to AST");
509 let text = ast.to_string();
510 let cst = parser::text_to_cst::parse_policy(&text)
511 .expect("Failed to convert to CST")
512 .node
513 .expect("Node should not be empty");
514 cst.try_into().expect("Failed to convert to EST")
515 }
516
517 #[test]
518 fn empty_policy() {
519 let policy = "permit(principal, action, resource);";
520 let cst = parser::text_to_cst::parse_policy(policy)
521 .unwrap()
522 .node
523 .unwrap();
524 let est: Policy = cst.try_into().unwrap();
525 let expected_json = json!(
526 {
527 "effect": "permit",
528 "principal": {
529 "op": "All",
530 },
531 "action": {
532 "op": "All",
533 },
534 "resource": {
535 "op": "All",
536 },
537 "conditions": [],
538 }
539 );
540 assert_eq!(
541 serde_json::to_value(&est).unwrap(),
542 expected_json,
543 "\nExpected:\n{}\n\nActual:\n{}\n\n",
544 serde_json::to_string_pretty(&expected_json).unwrap(),
545 serde_json::to_string_pretty(&est).unwrap()
546 );
547 let old_est = est.clone();
548 let roundtripped = est_roundtrip(est);
549 assert_eq!(&old_est, &roundtripped);
550 let est = text_roundtrip(&old_est);
551 assert_eq!(&old_est, &est);
552
553 let expected_json_after_roundtrip = json!(
554 {
555 "effect": "permit",
556 "principal": {
557 "op": "All",
558 },
559 "action": {
560 "op": "All",
561 },
562 "resource": {
563 "op": "All",
564 },
565 "conditions": [ ],
566 }
567 );
568 let roundtripped = serde_json::to_value(ast_roundtrip(est.clone())).unwrap();
569 assert_eq!(
570 roundtripped,
571 expected_json_after_roundtrip,
572 "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
573 serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
574 serde_json::to_string_pretty(&roundtripped).unwrap()
575 );
576 let roundtripped = serde_json::to_value(circular_roundtrip(est)).unwrap();
577 assert_eq!(
578 roundtripped,
579 expected_json_after_roundtrip,
580 "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
581 serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
582 serde_json::to_string_pretty(&roundtripped).unwrap()
583 );
584 }
585
586 #[test]
587 fn annotated_policy() {
588 let policy = r#"
589 @foo("bar")
590 @this1is2a3valid_identifier("any arbitrary ! string \" is @ allowed in 🦀 here_")
591 permit(principal, action, resource);
592 "#;
593 let cst = parser::text_to_cst::parse_policy(policy)
594 .unwrap()
595 .node
596 .unwrap();
597 let est: Policy = cst.try_into().unwrap();
598 let expected_json = json!(
599 {
600 "effect": "permit",
601 "principal": {
602 "op": "All",
603 },
604 "action": {
605 "op": "All",
606 },
607 "resource": {
608 "op": "All",
609 },
610 "conditions": [],
611 "annotations": {
612 "foo": "bar",
613 "this1is2a3valid_identifier": "any arbitrary ! string \" is @ allowed in 🦀 here_",
614 }
615 }
616 );
617 assert_eq!(
618 serde_json::to_value(&est).unwrap(),
619 expected_json,
620 "\nExpected:\n{}\n\nActual:\n{}\n\n",
621 serde_json::to_string_pretty(&expected_json).unwrap(),
622 serde_json::to_string_pretty(&est).unwrap()
623 );
624 let old_est = est.clone();
625 let roundtripped = est_roundtrip(est);
626 assert_eq!(&old_est, &roundtripped);
627 let est = text_roundtrip(&old_est);
628 assert_eq!(&old_est, &est);
629
630 let expected_json_after_roundtrip = json!(
631 {
632 "effect": "permit",
633 "principal": {
634 "op": "All",
635 },
636 "action": {
637 "op": "All",
638 },
639 "resource": {
640 "op": "All",
641 },
642 "conditions": [ ],
643 "annotations": {
644 "foo": "bar",
645 "this1is2a3valid_identifier": "any arbitrary ! string \" is @ allowed in 🦀 here_",
646 }
647 }
648 );
649 let roundtripped = serde_json::to_value(ast_roundtrip(est.clone())).unwrap();
650 assert_eq!(
651 roundtripped,
652 expected_json_after_roundtrip,
653 "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
654 serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
655 serde_json::to_string_pretty(&roundtripped).unwrap()
656 );
657 let roundtripped = serde_json::to_value(circular_roundtrip(est)).unwrap();
658 assert_eq!(
659 roundtripped,
660 expected_json_after_roundtrip,
661 "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
662 serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
663 serde_json::to_string_pretty(&roundtripped).unwrap()
664 );
665 }
666
667 #[test]
668 fn annotated_without_value_policy() {
669 let policy = r#"@foo permit(principal, action, resource);"#;
670 let cst = parser::text_to_cst::parse_policy(policy)
671 .unwrap()
672 .node
673 .unwrap();
674 let est: Policy = cst.try_into().unwrap();
675 let expected_json = json!(
676 {
677 "effect": "permit",
678 "principal": {
679 "op": "All",
680 },
681 "action": {
682 "op": "All",
683 },
684 "resource": {
685 "op": "All",
686 },
687 "conditions": [],
688 "annotations": { "foo": null, }
689 }
690 );
691 assert_eq!(
692 serde_json::to_value(&est).unwrap(),
693 expected_json,
694 "\nExpected:\n{}\n\nActual:\n{}\n\n",
695 serde_json::to_string_pretty(&expected_json).unwrap(),
696 serde_json::to_string_pretty(&est).unwrap()
697 );
698 let old_est = est.clone();
699 let roundtripped = est_roundtrip(est);
700 assert_eq!(&old_est, &roundtripped);
701 let est = text_roundtrip(&old_est);
702 assert_eq!(&old_est, &est);
703
704 let expected_json_after_roundtrip = json!(
706 {
707 "effect": "permit",
708 "principal": {
709 "op": "All",
710 },
711 "action": {
712 "op": "All",
713 },
714 "resource": {
715 "op": "All",
716 },
717 "conditions": [ ],
718 "annotations": { "foo": "", }
719 }
720 );
721 let roundtripped = serde_json::to_value(ast_roundtrip(est.clone())).unwrap();
722 assert_eq!(
723 roundtripped,
724 expected_json_after_roundtrip,
725 "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
726 serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
727 serde_json::to_string_pretty(&roundtripped).unwrap()
728 );
729 let roundtripped = serde_json::to_value(circular_roundtrip(est)).unwrap();
730 assert_eq!(
731 roundtripped,
732 expected_json_after_roundtrip,
733 "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
734 serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
735 serde_json::to_string_pretty(&roundtripped).unwrap()
736 );
737 }
738
739 #[test]
741 fn reserved_words_as_annotations() {
742 let policy = r#"
743 @if("this is the annotation for `if`")
744 @then("this is the annotation for `then`")
745 @else("this is the annotation for `else`")
746 @true("this is the annotation for `true`")
747 @false("this is the annotation for `false`")
748 @in("this is the annotation for `in`")
749 @is("this is the annotation for `is`")
750 @like("this is the annotation for `like`")
751 @has("this is the annotation for `has`")
752 @principal("this is the annotation for `principal`") // not reserved at time of this writing, but we test it anyway
753 permit(principal, action, resource) when { 2 == 2 };
754 "#;
755
756 let cst = parser::text_to_cst::parse_policy(policy)
757 .unwrap()
758 .node
759 .unwrap();
760 let est: Policy = cst.try_into().unwrap();
761 let expected_json = json!(
762 {
763 "effect": "permit",
764 "principal": {
765 "op": "All",
766 },
767 "action": {
768 "op": "All",
769 },
770 "resource": {
771 "op": "All",
772 },
773 "conditions": [
774 {
775 "kind": "when",
776 "body": {
777 "==": {
778 "left": { "Value": 2 },
779 "right": { "Value": 2 },
780 }
781 }
782 }
783 ],
784 "annotations": {
785 "if": "this is the annotation for `if`",
786 "then": "this is the annotation for `then`",
787 "else": "this is the annotation for `else`",
788 "true": "this is the annotation for `true`",
789 "false": "this is the annotation for `false`",
790 "in": "this is the annotation for `in`",
791 "is": "this is the annotation for `is`",
792 "like": "this is the annotation for `like`",
793 "has": "this is the annotation for `has`",
794 "principal": "this is the annotation for `principal`",
795 }
796 }
797 );
798 assert_eq!(
799 serde_json::to_value(&est).unwrap(),
800 expected_json,
801 "\nExpected:\n{}\n\nActual:\n{}\n\n",
802 serde_json::to_string_pretty(&expected_json).unwrap(),
803 serde_json::to_string_pretty(&est).unwrap()
804 );
805 let old_est = est.clone();
806 let roundtripped = est_roundtrip(est);
807 assert_eq!(&old_est, &roundtripped);
808 let est = text_roundtrip(&old_est);
809 assert_eq!(&old_est, &est);
810
811 assert_eq!(ast_roundtrip(est.clone()), est);
812 assert_eq!(circular_roundtrip(est.clone()), est);
813 }
814
815 #[test]
816 fn annotation_errors() {
817 let policy = r#"
818 @foo("1")
819 @foo("2")
820 permit(principal, action, resource);
821 "#;
822 let cst = parser::text_to_cst::parse_policy(policy)
823 .unwrap()
824 .node
825 .unwrap();
826 assert_matches!(Policy::try_from(cst), Err(e) => {
827 parser::test_utils::expect_exactly_one_error(policy, &e, &ExpectedErrorMessageBuilder::error("duplicate annotation: @foo").exactly_one_underline(r#"@foo("2")"#).build());
828 });
829
830 let policy = r#"
831 @foo("1")
832 @foo("1")
833 permit(principal, action, resource);
834 "#;
835 let cst = parser::text_to_cst::parse_policy(policy)
836 .unwrap()
837 .node
838 .unwrap();
839 assert_matches!(Policy::try_from(cst), Err(e) => {
840 parser::test_utils::expect_exactly_one_error(policy, &e, &ExpectedErrorMessageBuilder::error("duplicate annotation: @foo").exactly_one_underline(r#"@foo("1")"#).build());
841 });
842
843 let policy = r#"
844 @foo("1")
845 @bar("yellow")
846 @foo("abc")
847 @hello("goodbye")
848 @bar("123")
849 @foo("def")
850 permit(principal, action, resource);
851 "#;
852 let cst = parser::text_to_cst::parse_policy(policy)
853 .unwrap()
854 .node
855 .unwrap();
856 assert_matches!(Policy::try_from(cst), Err(e) => {
857 assert_eq!(e.len(), 3); parser::test_utils::expect_some_error_matches(policy, &e, &ExpectedErrorMessageBuilder::error("duplicate annotation: @foo").exactly_one_underline(r#"@foo("abc")"#).build());
859 parser::test_utils::expect_some_error_matches(policy, &e, &ExpectedErrorMessageBuilder::error("duplicate annotation: @foo").exactly_one_underline(r#"@foo("def")"#).build());
860 parser::test_utils::expect_some_error_matches(policy, &e, &ExpectedErrorMessageBuilder::error("duplicate annotation: @bar").exactly_one_underline(r#"@bar("123")"#).build());
861 });
862
863 let est = r#"
871 {
872 "effect": "permit",
873 "principal": {
874 "op": "All"
875 },
876 "action": {
877 "op": "All"
878 },
879 "resource": {
880 "op": "All"
881 },
882 "conditions": [],
883 "annotations": {
884 "foo": "1",
885 "foo": "2"
886 }
887 }
888 "#;
889 assert_matches!(serde_json::from_str::<Policy>(est), Err(e) => {
890 assert_eq!(e.to_string(), "invalid entry: found duplicate key at line 17 column 17");
891 });
892 }
893
894 #[test]
895 fn rbac_policy() {
896 let policy = r#"
897 permit(
898 principal == User::"12UA45",
899 action == Action::"view",
900 resource in Folder::"abc"
901 );
902 "#;
903 let cst = parser::text_to_cst::parse_policy(policy)
904 .unwrap()
905 .node
906 .unwrap();
907 let est: Policy = cst.try_into().unwrap();
908 let expected_json = json!(
909 {
910 "effect": "permit",
911 "principal": {
912 "op": "==",
913 "entity": { "type": "User", "id": "12UA45" },
914 },
915 "action": {
916 "op": "==",
917 "entity": { "type": "Action", "id": "view" },
918 },
919 "resource": {
920 "op": "in",
921 "entity": { "type": "Folder", "id": "abc" },
922 },
923 "conditions": []
924 }
925 );
926 assert_eq!(
927 serde_json::to_value(&est).unwrap(),
928 expected_json,
929 "\nExpected:\n{}\n\nActual:\n{}\n\n",
930 serde_json::to_string_pretty(&expected_json).unwrap(),
931 serde_json::to_string_pretty(&est).unwrap()
932 );
933 let old_est = est.clone();
934 let roundtripped = est_roundtrip(est);
935 assert_eq!(&old_est, &roundtripped);
936 let est = text_roundtrip(&old_est);
937 assert_eq!(&old_est, &est);
938
939 let expected_json_after_roundtrip = json!(
940 {
941 "effect": "permit",
942 "principal": {
943 "op": "==",
944 "entity": { "type": "User", "id": "12UA45" },
945 },
946 "action": {
947 "op": "==",
948 "entity": { "type": "Action", "id": "view" },
949 },
950 "resource": {
951 "op": "in",
952 "entity": { "type": "Folder", "id": "abc" },
953 },
954 "conditions": [ ]
955 }
956 );
957 let roundtripped = serde_json::to_value(ast_roundtrip(est.clone())).unwrap();
958 assert_eq!(
959 roundtripped,
960 expected_json_after_roundtrip,
961 "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
962 serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
963 serde_json::to_string_pretty(&roundtripped).unwrap()
964 );
965 let roundtripped = serde_json::to_value(circular_roundtrip(est)).unwrap();
966 assert_eq!(
967 roundtripped,
968 expected_json_after_roundtrip,
969 "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
970 serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
971 serde_json::to_string_pretty(&roundtripped).unwrap()
972 );
973 }
974
975 #[test]
976 fn rbac_template() {
977 let template = r#"
978 permit(
979 principal == ?principal,
980 action == Action::"view",
981 resource in ?resource
982 );
983 "#;
984 let cst = parser::text_to_cst::parse_policy(template)
985 .unwrap()
986 .node
987 .unwrap();
988 let est: Policy = cst.try_into().unwrap();
989 let expected_json = json!(
990 {
991 "effect": "permit",
992 "principal": {
993 "op": "==",
994 "slot": "?principal",
995 },
996 "action": {
997 "op": "==",
998 "entity": { "type": "Action", "id": "view" },
999 },
1000 "resource": {
1001 "op": "in",
1002 "slot": "?resource",
1003 },
1004 "conditions": []
1005 }
1006 );
1007 assert_eq!(
1008 serde_json::to_value(&est).unwrap(),
1009 expected_json,
1010 "\nExpected:\n{}\n\nActual:\n{}\n\n",
1011 serde_json::to_string_pretty(&expected_json).unwrap(),
1012 serde_json::to_string_pretty(&est).unwrap()
1013 );
1014 let old_est = est.clone();
1015 let roundtripped = est_roundtrip(est);
1016 assert_eq!(&old_est, &roundtripped);
1017 let est = text_roundtrip(&old_est);
1018 assert_eq!(&old_est, &est);
1019
1020 let expected_json_after_roundtrip = json!(
1021 {
1022 "effect": "permit",
1023 "principal": {
1024 "op": "==",
1025 "slot": "?principal",
1026 },
1027 "action": {
1028 "op": "==",
1029 "entity": { "type": "Action", "id": "view" },
1030 },
1031 "resource": {
1032 "op": "in",
1033 "slot": "?resource",
1034 },
1035 "conditions": [ ]
1036 }
1037 );
1038 let roundtripped = serde_json::to_value(ast_roundtrip_template(est.clone())).unwrap();
1039 assert_eq!(
1040 roundtripped,
1041 expected_json_after_roundtrip,
1042 "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
1043 serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
1044 serde_json::to_string_pretty(&roundtripped).unwrap()
1045 );
1046 let roundtripped = serde_json::to_value(circular_roundtrip_template(est)).unwrap();
1047 assert_eq!(
1048 roundtripped,
1049 expected_json_after_roundtrip,
1050 "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
1051 serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
1052 serde_json::to_string_pretty(&roundtripped).unwrap()
1053 );
1054 }
1055
1056 #[test]
1057 fn abac_policy() {
1058 let policy = r#"
1059 permit(
1060 principal == User::"12UA45",
1061 action == Action::"view",
1062 resource in Folder::"abc"
1063 ) when {
1064 context.tls_version == "1.3"
1065 };
1066 "#;
1067 let cst = parser::text_to_cst::parse_policy(policy)
1068 .unwrap()
1069 .node
1070 .unwrap();
1071 let est: Policy = cst.try_into().unwrap();
1072 let expected_json = json!(
1073 {
1074 "effect": "permit",
1075 "principal": {
1076 "op": "==",
1077 "entity": { "type": "User", "id": "12UA45" },
1078 },
1079 "action": {
1080 "op": "==",
1081 "entity": { "type": "Action", "id": "view" },
1082 },
1083 "resource": {
1084 "op": "in",
1085 "entity": { "type": "Folder", "id": "abc" },
1086 },
1087 "conditions": [
1088 {
1089 "kind": "when",
1090 "body": {
1091 "==": {
1092 "left": {
1093 ".": {
1094 "left": { "Var": "context" },
1095 "attr": "tls_version",
1096 },
1097 },
1098 "right": {
1099 "Value": "1.3"
1100 }
1101 }
1102 }
1103 }
1104 ]
1105 }
1106 );
1107 assert_eq!(
1108 serde_json::to_value(&est).unwrap(),
1109 expected_json,
1110 "\nExpected:\n{}\n\nActual:\n{}\n\n",
1111 serde_json::to_string_pretty(&expected_json).unwrap(),
1112 serde_json::to_string_pretty(&est).unwrap()
1113 );
1114 let old_est = est.clone();
1115 let roundtripped = est_roundtrip(est);
1116 assert_eq!(&old_est, &roundtripped);
1117 let est = text_roundtrip(&old_est);
1118 assert_eq!(&old_est, &est);
1119
1120 assert_eq!(ast_roundtrip(est.clone()), est);
1121 assert_eq!(circular_roundtrip(est.clone()), est);
1122 }
1123
1124 #[test]
1125 fn action_list() {
1126 let policy = r#"
1127 permit(
1128 principal == User::"12UA45",
1129 action in [Action::"read", Action::"write"],
1130 resource
1131 );
1132 "#;
1133 let cst = parser::text_to_cst::parse_policy(policy)
1134 .unwrap()
1135 .node
1136 .unwrap();
1137 let est: Policy = cst.try_into().unwrap();
1138 let expected_json = json!(
1139 {
1140 "effect": "permit",
1141 "principal": {
1142 "op": "==",
1143 "entity": { "type": "User", "id": "12UA45" },
1144 },
1145 "action": {
1146 "op": "in",
1147 "entities": [
1148 { "type": "Action", "id": "read" },
1149 { "type": "Action", "id": "write" },
1150 ]
1151 },
1152 "resource": {
1153 "op": "All",
1154 },
1155 "conditions": []
1156 }
1157 );
1158 assert_eq!(
1159 serde_json::to_value(&est).unwrap(),
1160 expected_json,
1161 "\nExpected:\n{}\n\nActual:\n{}\n\n",
1162 serde_json::to_string_pretty(&expected_json).unwrap(),
1163 serde_json::to_string_pretty(&est).unwrap()
1164 );
1165 let old_est = est.clone();
1166 let roundtripped = est_roundtrip(est);
1167 assert_eq!(&old_est, &roundtripped);
1168 let est = text_roundtrip(&old_est);
1169 assert_eq!(&old_est, &est);
1170
1171 let expected_json_after_roundtrip = json!(
1172 {
1173 "effect": "permit",
1174 "principal": {
1175 "op": "==",
1176 "entity": { "type": "User", "id": "12UA45" },
1177 },
1178 "action": {
1179 "op": "in",
1180 "entities": [
1181 { "type": "Action", "id": "read" },
1182 { "type": "Action", "id": "write" },
1183 ]
1184 },
1185 "resource": {
1186 "op": "All",
1187 },
1188 "conditions": [ ]
1189 }
1190 );
1191 let roundtripped = serde_json::to_value(ast_roundtrip(est.clone())).unwrap();
1192 assert_eq!(
1193 roundtripped,
1194 expected_json_after_roundtrip,
1195 "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
1196 serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
1197 serde_json::to_string_pretty(&roundtripped).unwrap()
1198 );
1199 let roundtripped = serde_json::to_value(circular_roundtrip(est)).unwrap();
1200 assert_eq!(
1201 roundtripped,
1202 expected_json_after_roundtrip,
1203 "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
1204 serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
1205 serde_json::to_string_pretty(&roundtripped).unwrap()
1206 );
1207 }
1208
1209 #[test]
1210 fn num_literals() {
1211 let policy = r#"
1212 permit(principal, action, resource)
1213 when { 1 == 2 };
1214 "#;
1215 let cst = parser::text_to_cst::parse_policy(policy)
1216 .unwrap()
1217 .node
1218 .unwrap();
1219 let est: Policy = cst.try_into().unwrap();
1220 let expected_json = json!(
1221 {
1222 "effect": "permit",
1223 "principal": {
1224 "op": "All",
1225 },
1226 "action": {
1227 "op": "All",
1228 },
1229 "resource": {
1230 "op": "All",
1231 },
1232 "conditions": [
1233 {
1234 "kind": "when",
1235 "body": {
1236 "==": {
1237 "left": {
1238 "Value": 1
1239 },
1240 "right": {
1241 "Value": 2
1242 }
1243 }
1244 }
1245 }
1246 ]
1247 }
1248 );
1249 assert_eq!(
1250 serde_json::to_value(&est).unwrap(),
1251 expected_json,
1252 "\nExpected:\n{}\n\nActual:\n{}\n\n",
1253 serde_json::to_string_pretty(&expected_json).unwrap(),
1254 serde_json::to_string_pretty(&est).unwrap()
1255 );
1256 let old_est = est.clone();
1257 let roundtripped = est_roundtrip(est);
1258 assert_eq!(&old_est, &roundtripped);
1259 let est = text_roundtrip(&old_est);
1260 assert_eq!(&old_est, &est);
1261
1262 assert_eq!(ast_roundtrip(est.clone()), est);
1263 assert_eq!(circular_roundtrip(est.clone()), est);
1264 }
1265
1266 #[test]
1267 fn entity_literals() {
1268 let policy = r#"
1269 permit(principal, action, resource)
1270 when { User::"alice" == Namespace::Type::"foo" };
1271 "#;
1272 let cst = parser::text_to_cst::parse_policy(policy)
1273 .unwrap()
1274 .node
1275 .unwrap();
1276 let est: Policy = cst.try_into().unwrap();
1277 let expected_json = json!(
1278 {
1279 "effect": "permit",
1280 "principal": {
1281 "op": "All",
1282 },
1283 "action": {
1284 "op": "All",
1285 },
1286 "resource": {
1287 "op": "All",
1288 },
1289 "conditions": [
1290 {
1291 "kind": "when",
1292 "body": {
1293 "==": {
1294 "left": {
1295 "Value": {
1296 "__entity": {
1297 "type": "User",
1298 "id": "alice"
1299 }
1300 }
1301 },
1302 "right": {
1303 "Value": {
1304 "__entity": {
1305 "type": "Namespace::Type",
1306 "id": "foo"
1307 }
1308 }
1309 }
1310 }
1311 }
1312 }
1313 ]
1314 }
1315 );
1316 assert_eq!(
1317 serde_json::to_value(&est).unwrap(),
1318 expected_json,
1319 "\nExpected:\n{}\n\nActual:\n{}\n\n",
1320 serde_json::to_string_pretty(&expected_json).unwrap(),
1321 serde_json::to_string_pretty(&est).unwrap()
1322 );
1323 let old_est = est.clone();
1324 let roundtripped = est_roundtrip(est);
1325 assert_eq!(&old_est, &roundtripped);
1326 let est = text_roundtrip(&old_est);
1327 assert_eq!(&old_est, &est);
1328
1329 assert_eq!(ast_roundtrip(est.clone()), est);
1330 assert_eq!(circular_roundtrip(est.clone()), est);
1331 }
1332
1333 #[test]
1334 fn bool_literals() {
1335 let policy = r#"
1336 permit(principal, action, resource)
1337 when { false == true };
1338 "#;
1339 let cst = parser::text_to_cst::parse_policy(policy)
1340 .unwrap()
1341 .node
1342 .unwrap();
1343 let est: Policy = cst.try_into().unwrap();
1344 let expected_json = json!(
1345 {
1346 "effect": "permit",
1347 "principal": {
1348 "op": "All",
1349 },
1350 "action": {
1351 "op": "All",
1352 },
1353 "resource": {
1354 "op": "All",
1355 },
1356 "conditions": [
1357 {
1358 "kind": "when",
1359 "body": {
1360 "==": {
1361 "left": {
1362 "Value": false
1363 },
1364 "right": {
1365 "Value": true
1366 }
1367 }
1368 }
1369 }
1370 ]
1371 }
1372 );
1373 assert_eq!(
1374 serde_json::to_value(&est).unwrap(),
1375 expected_json,
1376 "\nExpected:\n{}\n\nActual:\n{}\n\n",
1377 serde_json::to_string_pretty(&expected_json).unwrap(),
1378 serde_json::to_string_pretty(&est).unwrap()
1379 );
1380 let old_est = est.clone();
1381 let roundtripped = est_roundtrip(est);
1382 assert_eq!(&old_est, &roundtripped);
1383 let est = text_roundtrip(&old_est);
1384 assert_eq!(&old_est, &est);
1385
1386 assert_eq!(ast_roundtrip(est.clone()), est);
1387 assert_eq!(circular_roundtrip(est.clone()), est);
1388 }
1389
1390 #[test]
1391 fn string_literals() {
1392 let policy = r#"
1393 permit(principal, action, resource)
1394 when { "spam" == "eggs" };
1395 "#;
1396 let cst = parser::text_to_cst::parse_policy(policy)
1397 .unwrap()
1398 .node
1399 .unwrap();
1400 let est: Policy = cst.try_into().unwrap();
1401 let expected_json = json!(
1402 {
1403 "effect": "permit",
1404 "principal": {
1405 "op": "All",
1406 },
1407 "action": {
1408 "op": "All",
1409 },
1410 "resource": {
1411 "op": "All",
1412 },
1413 "conditions": [
1414 {
1415 "kind": "when",
1416 "body": {
1417 "==": {
1418 "left": {
1419 "Value": "spam"
1420 },
1421 "right": {
1422 "Value": "eggs"
1423 }
1424 }
1425 }
1426 }
1427 ]
1428 }
1429 );
1430 assert_eq!(
1431 serde_json::to_value(&est).unwrap(),
1432 expected_json,
1433 "\nExpected:\n{}\n\nActual:\n{}\n\n",
1434 serde_json::to_string_pretty(&expected_json).unwrap(),
1435 serde_json::to_string_pretty(&est).unwrap()
1436 );
1437 let old_est = est.clone();
1438 let roundtripped = est_roundtrip(est);
1439 assert_eq!(&old_est, &roundtripped);
1440 let est = text_roundtrip(&old_est);
1441 assert_eq!(&old_est, &est);
1442
1443 assert_eq!(ast_roundtrip(est.clone()), est);
1444 assert_eq!(circular_roundtrip(est.clone()), est);
1445 }
1446
1447 #[test]
1448 fn set_literals() {
1449 let policy = r#"
1450 permit(principal, action, resource)
1451 when { [1, 2, "foo"] == [4, 5, "spam"] };
1452 "#;
1453 let cst = parser::text_to_cst::parse_policy(policy)
1454 .unwrap()
1455 .node
1456 .unwrap();
1457 let est: Policy = cst.try_into().unwrap();
1458 let expected_json = json!(
1459 {
1460 "effect": "permit",
1461 "principal": {
1462 "op": "All",
1463 },
1464 "action": {
1465 "op": "All",
1466 },
1467 "resource": {
1468 "op": "All",
1469 },
1470 "conditions": [
1471 {
1472 "kind": "when",
1473 "body": {
1474 "==": {
1475 "left": {
1476 "Set": [
1477 { "Value": 1 },
1478 { "Value": 2 },
1479 { "Value": "foo" },
1480 ]
1481 },
1482 "right": {
1483 "Set": [
1484 { "Value": 4 },
1485 { "Value": 5 },
1486 { "Value": "spam" },
1487 ]
1488 }
1489 }
1490 }
1491 }
1492 ]
1493 }
1494 );
1495 assert_eq!(
1496 serde_json::to_value(&est).unwrap(),
1497 expected_json,
1498 "\nExpected:\n{}\n\nActual:\n{}\n\n",
1499 serde_json::to_string_pretty(&expected_json).unwrap(),
1500 serde_json::to_string_pretty(&est).unwrap()
1501 );
1502 let old_est = est.clone();
1503 let roundtripped = est_roundtrip(est);
1504 assert_eq!(&old_est, &roundtripped);
1505 let est = text_roundtrip(&old_est);
1506 assert_eq!(&old_est, &est);
1507
1508 assert_eq!(ast_roundtrip(est.clone()), est);
1509 assert_eq!(circular_roundtrip(est.clone()), est);
1510 }
1511
1512 #[test]
1513 fn record_literals() {
1514 let policy = r#"
1515 permit(principal, action, resource)
1516 when { {foo: "spam", bar: false} == {} };
1517 "#;
1518 let cst = parser::text_to_cst::parse_policy(policy)
1519 .unwrap()
1520 .node
1521 .unwrap();
1522 let est: Policy = cst.try_into().unwrap();
1523 let expected_json = json!(
1524 {
1525 "effect": "permit",
1526 "principal": {
1527 "op": "All",
1528 },
1529 "action": {
1530 "op": "All",
1531 },
1532 "resource": {
1533 "op": "All",
1534 },
1535 "conditions": [
1536 {
1537 "kind": "when",
1538 "body": {
1539 "==": {
1540 "left": {
1541 "Record": {
1542 "foo": { "Value": "spam" },
1543 "bar": { "Value": false },
1544 }
1545 },
1546 "right": {
1547 "Record": {}
1548 }
1549 }
1550 }
1551 }
1552 ]
1553 }
1554 );
1555 assert_eq!(
1556 serde_json::to_value(&est).unwrap(),
1557 expected_json,
1558 "\nExpected:\n{}\n\nActual:\n{}\n\n",
1559 serde_json::to_string_pretty(&expected_json).unwrap(),
1560 serde_json::to_string_pretty(&est).unwrap()
1561 );
1562 let old_est = est.clone();
1563 let roundtripped = est_roundtrip(est);
1564 assert_eq!(&old_est, &roundtripped);
1565 let est = text_roundtrip(&old_est);
1566 assert_eq!(&old_est, &est);
1567
1568 assert_eq!(ast_roundtrip(est.clone()), est);
1569 assert_eq!(circular_roundtrip(est.clone()), est);
1570 }
1571
1572 #[test]
1573 fn policy_variables() {
1574 let policy = r#"
1575 permit(principal, action, resource)
1576 when { principal == action && resource == context };
1577 "#;
1578 let cst = parser::text_to_cst::parse_policy(policy)
1579 .unwrap()
1580 .node
1581 .unwrap();
1582 let est: Policy = cst.try_into().unwrap();
1583 let expected_json = json!(
1584 {
1585 "effect": "permit",
1586 "principal": {
1587 "op": "All",
1588 },
1589 "action": {
1590 "op": "All",
1591 },
1592 "resource": {
1593 "op": "All",
1594 },
1595 "conditions": [
1596 {
1597 "kind": "when",
1598 "body": {
1599 "&&": {
1600 "left": {
1601 "==": {
1602 "left": {
1603 "Var": "principal"
1604 },
1605 "right": {
1606 "Var": "action"
1607 }
1608 }
1609 },
1610 "right": {
1611 "==": {
1612 "left": {
1613 "Var": "resource"
1614 },
1615 "right": {
1616 "Var": "context"
1617 }
1618 }
1619 }
1620 }
1621 }
1622 }
1623 ]
1624 }
1625 );
1626 assert_eq!(
1627 serde_json::to_value(&est).unwrap(),
1628 expected_json,
1629 "\nExpected:\n{}\n\nActual:\n{}\n\n",
1630 serde_json::to_string_pretty(&expected_json).unwrap(),
1631 serde_json::to_string_pretty(&est).unwrap()
1632 );
1633 let old_est = est.clone();
1634 let roundtripped = est_roundtrip(est);
1635 assert_eq!(&old_est, &roundtripped);
1636 let est = text_roundtrip(&old_est);
1637 assert_eq!(&old_est, &est);
1638
1639 assert_eq!(ast_roundtrip(est.clone()), est);
1640 assert_eq!(circular_roundtrip(est.clone()), est);
1641 }
1642
1643 #[test]
1644 fn not() {
1645 let policy = r#"
1646 permit(principal, action, resource)
1647 when { !context.foo && principal != context.bar };
1648 "#;
1649 let cst = parser::text_to_cst::parse_policy(policy)
1650 .unwrap()
1651 .node
1652 .unwrap();
1653 let est: Policy = cst.try_into().unwrap();
1654 let expected_json = json!(
1655 {
1656 "effect": "permit",
1657 "principal": {
1658 "op": "All",
1659 },
1660 "action": {
1661 "op": "All",
1662 },
1663 "resource": {
1664 "op": "All",
1665 },
1666 "conditions": [
1667 {
1668 "kind": "when",
1669 "body": {
1670 "&&": {
1671 "left": {
1672 "!": {
1673 "arg": {
1674 ".": {
1675 "left": {
1676 "Var": "context"
1677 },
1678 "attr": "foo"
1679 }
1680 }
1681 }
1682 },
1683 "right": {
1684 "!=": {
1685 "left": {
1686 "Var": "principal"
1687 },
1688 "right": {
1689 ".": {
1690 "left": {
1691 "Var": "context"
1692 },
1693 "attr": "bar"
1694 }
1695 }
1696 }
1697 }
1698 }
1699 }
1700 }
1701 ]
1702 }
1703 );
1704 assert_eq!(
1705 serde_json::to_value(&est).unwrap(),
1706 expected_json,
1707 "\nExpected:\n{}\n\nActual:\n{}\n\n",
1708 serde_json::to_string_pretty(&expected_json).unwrap(),
1709 serde_json::to_string_pretty(&est).unwrap()
1710 );
1711 let old_est = est.clone();
1712 let roundtripped = est_roundtrip(est);
1713 assert_eq!(&old_est, &roundtripped);
1714 let est = text_roundtrip(&old_est);
1715 assert_eq!(&old_est, &est);
1716
1717 let expected_json_after_roundtrip = json!(
1720 {
1721 "effect": "permit",
1722 "principal": {
1723 "op": "All",
1724 },
1725 "action": {
1726 "op": "All",
1727 },
1728 "resource": {
1729 "op": "All",
1730 },
1731 "conditions": [
1732 {
1733 "kind": "when",
1734 "body": {
1735 "&&": {
1736 "left": {
1737 "!": {
1738 "arg": {
1739 ".": {
1740 "left": {
1741 "Var": "context"
1742 },
1743 "attr": "foo"
1744 }
1745 }
1746 }
1747 },
1748 "right": {
1749 "!": {
1750 "arg": {
1751 "==": {
1752 "left": {
1753 "Var": "principal"
1754 },
1755 "right": {
1756 ".": {
1757 "left": {
1758 "Var": "context"
1759 },
1760 "attr": "bar"
1761 }
1762 }
1763 }
1764 }
1765 }
1766 }
1767 }
1768 }
1769 }
1770 ]
1771 }
1772 );
1773 let roundtripped = serde_json::to_value(ast_roundtrip(est.clone())).unwrap();
1774 assert_eq!(
1775 roundtripped,
1776 expected_json_after_roundtrip,
1777 "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
1778 serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
1779 serde_json::to_string_pretty(&roundtripped).unwrap()
1780 );
1781 let roundtripped = serde_json::to_value(circular_roundtrip(est)).unwrap();
1782 assert_eq!(
1783 roundtripped,
1784 expected_json_after_roundtrip,
1785 "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
1786 serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
1787 serde_json::to_string_pretty(&roundtripped).unwrap()
1788 );
1789 }
1790
1791 #[test]
1792 fn hierarchy_in() {
1793 let policy = r#"
1794 permit(principal, action, resource)
1795 when { resource in principal.department };
1796 "#;
1797 let cst = parser::text_to_cst::parse_policy(policy)
1798 .unwrap()
1799 .node
1800 .unwrap();
1801 let est: Policy = cst.try_into().unwrap();
1802 let expected_json = json!(
1803 {
1804 "effect": "permit",
1805 "principal": {
1806 "op": "All",
1807 },
1808 "action": {
1809 "op": "All",
1810 },
1811 "resource": {
1812 "op": "All",
1813 },
1814 "conditions": [
1815 {
1816 "kind": "when",
1817 "body": {
1818 "in": {
1819 "left": {
1820 "Var": "resource"
1821 },
1822 "right": {
1823 ".": {
1824 "left": {
1825 "Var": "principal"
1826 },
1827 "attr": "department"
1828 }
1829 }
1830 }
1831 }
1832 }
1833 ]
1834 }
1835 );
1836 assert_eq!(
1837 serde_json::to_value(&est).unwrap(),
1838 expected_json,
1839 "\nExpected:\n{}\n\nActual:\n{}\n\n",
1840 serde_json::to_string_pretty(&expected_json).unwrap(),
1841 serde_json::to_string_pretty(&est).unwrap()
1842 );
1843 let old_est = est.clone();
1844 let roundtripped = est_roundtrip(est);
1845 assert_eq!(&old_est, &roundtripped);
1846 let est = text_roundtrip(&old_est);
1847 assert_eq!(&old_est, &est);
1848
1849 assert_eq!(ast_roundtrip(est.clone()), est);
1850 assert_eq!(circular_roundtrip(est.clone()), est);
1851 }
1852
1853 #[test]
1854 fn nested_records() {
1855 let policy = r#"
1856 permit(principal, action, resource)
1857 when { context.something1.something2.something3 };
1858 "#;
1859 let cst = parser::text_to_cst::parse_policy(policy)
1860 .unwrap()
1861 .node
1862 .unwrap();
1863 let est: Policy = cst.try_into().unwrap();
1864 let expected_json = json!(
1865 {
1866 "effect": "permit",
1867 "principal": {
1868 "op": "All",
1869 },
1870 "action": {
1871 "op": "All",
1872 },
1873 "resource": {
1874 "op": "All",
1875 },
1876 "conditions": [
1877 {
1878 "kind": "when",
1879 "body": {
1880 ".": {
1881 "left": {
1882 ".": {
1883 "left": {
1884 ".": {
1885 "left": {
1886 "Var": "context"
1887 },
1888 "attr": "something1"
1889 }
1890 },
1891 "attr": "something2"
1892 }
1893 },
1894 "attr": "something3"
1895 }
1896 }
1897 }
1898 ]
1899 }
1900 );
1901 assert_eq!(
1902 serde_json::to_value(&est).unwrap(),
1903 expected_json,
1904 "\nExpected:\n{}\n\nActual:\n{}\n\n",
1905 serde_json::to_string_pretty(&expected_json).unwrap(),
1906 serde_json::to_string_pretty(&est).unwrap()
1907 );
1908 let old_est = est.clone();
1909 let roundtripped = est_roundtrip(est);
1910 assert_eq!(&old_est, &roundtripped);
1911 let est = text_roundtrip(&old_est);
1912 assert_eq!(&old_est, &est);
1913
1914 assert_eq!(ast_roundtrip(est.clone()), est);
1915 assert_eq!(circular_roundtrip(est.clone()), est);
1916 }
1917
1918 #[test]
1919 fn neg_less_and_greater() {
1920 let policy = r#"
1921 permit(principal, action, resource)
1922 when { -3 < 2 && 4 > -(23 - 1) || 0 <= 0 && 7 >= 1};
1923 "#;
1924 let cst = parser::text_to_cst::parse_policy(policy)
1925 .unwrap()
1926 .node
1927 .unwrap();
1928 let est: Policy = cst.try_into().unwrap();
1929 let expected_json = json!(
1930 {
1931 "effect": "permit",
1932 "principal": {
1933 "op": "All",
1934 },
1935 "action": {
1936 "op": "All",
1937 },
1938 "resource": {
1939 "op": "All",
1940 },
1941 "conditions": [
1942 {
1943 "kind": "when",
1944 "body": {
1945 "||": {
1946 "left": {
1947 "&&": {
1948 "left": {
1949 "<": {
1950 "left": {
1951 "Value": -3
1952 },
1953 "right": {
1954 "Value": 2
1955 }
1956 }
1957 },
1958 "right": {
1959 ">": {
1960 "left": {
1961 "Value": 4
1962 },
1963 "right": {
1964 "neg": {
1965 "arg": {
1966 "-": {
1967 "left": {
1968 "Value": 23
1969 },
1970 "right": {
1971 "Value": 1
1972 }
1973 }
1974 }
1975 }
1976 }
1977 }
1978 }
1979 }
1980 },
1981 "right": {
1982 "&&": {
1983 "left": {
1984 "<=": {
1985 "left": {
1986 "Value": 0
1987 },
1988 "right": {
1989 "Value": 0
1990 }
1991 }
1992 },
1993 "right": {
1994 ">=": {
1995 "left": {
1996 "Value": 7
1997 },
1998 "right": {
1999 "Value": 1
2000 }
2001 }
2002 }
2003 }
2004 }
2005 }
2006 }
2007 }
2008 ]
2009 }
2010 );
2011 assert_eq!(
2012 serde_json::to_value(&est).unwrap(),
2013 expected_json,
2014 "\nExpected:\n{}\n\nActual:\n{}\n\n",
2015 serde_json::to_string_pretty(&expected_json).unwrap(),
2016 serde_json::to_string_pretty(&est).unwrap()
2017 );
2018 let old_est = est.clone();
2019 let roundtripped = est_roundtrip(est);
2020 assert_eq!(&old_est, &roundtripped);
2021 let est = text_roundtrip(&old_est);
2022 assert_eq!(&old_est, &est);
2023
2024 let expected_json_after_roundtrip = json!(
2027 {
2028 "effect": "permit",
2029 "principal": {
2030 "op": "All",
2031 },
2032 "action": {
2033 "op": "All",
2034 },
2035 "resource": {
2036 "op": "All",
2037 },
2038 "conditions": [
2039 {
2040 "kind": "when",
2041 "body": {
2042 "||": {
2043 "left": {
2044 "&&": {
2045 "left": {
2046 "<": {
2047 "left": {
2048 "Value": -3
2049 },
2050 "right": {
2051 "Value": 2
2052 }
2053 }
2054 },
2055 "right": {
2056 "!": {
2057 "arg":{
2058 "<=": {
2059 "left": {
2060 "Value": 4
2061 },
2062 "right": {
2063 "neg": {
2064 "arg": {
2065 "-": {
2066 "left": {
2067 "Value": 23
2068 },
2069 "right": {
2070 "Value": 1
2071 }
2072 }
2073 }
2074 }
2075 }
2076 }
2077 }
2078 }
2079 }
2080 }
2081 },
2082 "right": {
2083 "&&": {
2084 "left": {
2085 "<=": {
2086 "left": {
2087 "Value": 0
2088 },
2089 "right": {
2090 "Value": 0
2091 }
2092 }
2093 },
2094 "right": {
2095 "!": {
2096 "arg": {
2097 "<": {
2098 "left": {
2099 "Value": 7
2100 },
2101 "right": {
2102 "Value": 1
2103 }
2104 }
2105 }
2106 }
2107 }
2108 }
2109 }
2110 }
2111 }
2112 }
2113 ]
2114 }
2115 );
2116 let roundtripped = serde_json::to_value(ast_roundtrip(est.clone())).unwrap();
2117 assert_eq!(
2118 roundtripped,
2119 expected_json_after_roundtrip,
2120 "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
2121 serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
2122 serde_json::to_string_pretty(&roundtripped).unwrap()
2123 );
2124 let roundtripped = serde_json::to_value(circular_roundtrip(est)).unwrap();
2125 assert_eq!(
2126 roundtripped,
2127 expected_json_after_roundtrip,
2128 "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
2129 serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
2130 serde_json::to_string_pretty(&roundtripped).unwrap()
2131 );
2132 }
2133
2134 #[test]
2135 fn add_sub_and_mul() {
2136 let policy = r#"
2137 permit(principal, action, resource)
2138 when { 2 + 3 - principal.numFoos * (-10) == 7 };
2139 "#;
2140 let cst = parser::text_to_cst::parse_policy(policy)
2141 .unwrap()
2142 .node
2143 .unwrap();
2144 let est: Policy = cst.try_into().unwrap();
2145 let expected_json = json!(
2146 {
2147 "effect": "permit",
2148 "principal": {
2149 "op": "All",
2150 },
2151 "action": {
2152 "op": "All",
2153 },
2154 "resource": {
2155 "op": "All",
2156 },
2157 "conditions": [
2158 {
2159 "kind": "when",
2160 "body": {
2161 "==": {
2162 "left": {
2163 "-": {
2164 "left": {
2165 "+": {
2166 "left": {
2167 "Value": 2
2168 },
2169 "right": {
2170 "Value": 3
2171 }
2172 }
2173 },
2174 "right": {
2175 "*": {
2176 "left": {
2177 ".": {
2178 "left": {
2179 "Var": "principal"
2180 },
2181 "attr": "numFoos"
2182 }
2183 },
2184 "right": {
2185 "Value": -10
2186 }
2187 }
2188 }
2189 }
2190 },
2191 "right": {
2192 "Value": 7
2193 }
2194 }
2195 }
2196 }
2197 ]
2198 }
2199 );
2200 assert_eq!(
2201 serde_json::to_value(&est).unwrap(),
2202 expected_json,
2203 "\nExpected:\n{}\n\nActual:\n{}\n\n",
2204 serde_json::to_string_pretty(&expected_json).unwrap(),
2205 serde_json::to_string_pretty(&est).unwrap()
2206 );
2207 let old_est = est.clone();
2208 let roundtripped = est_roundtrip(est);
2209 assert_eq!(&old_est, &roundtripped);
2210 let est = text_roundtrip(&old_est);
2211 assert_eq!(&old_est, &est);
2212
2213 assert_eq!(ast_roundtrip(est.clone()), est);
2214 assert_eq!(circular_roundtrip(est.clone()), est);
2215 }
2216
2217 #[test]
2218 fn contains_all_any() {
2219 let policy = r#"
2220 permit(principal, action, resource)
2221 when {
2222 principal.owners.contains("foo")
2223 && principal.owners.containsAny([1, Linux::Group::"sudoers"])
2224 && [2+3, "spam"].containsAll(resource.foos)
2225 && context.violations.isEmpty()
2226 };
2227 "#;
2228 let cst = parser::text_to_cst::parse_policy(policy)
2229 .unwrap()
2230 .node
2231 .unwrap();
2232 let est: Policy = cst.try_into().unwrap();
2233 let expected_json = json!(
2234 {
2235 "effect": "permit",
2236 "principal": {
2237 "op": "All",
2238 },
2239 "action": {
2240 "op": "All",
2241 },
2242 "resource": {
2243 "op": "All",
2244 },
2245 "conditions": [
2246 {
2247 "kind": "when",
2248 "body": {
2249 "&&": {
2250 "left": {
2251 "&&": {
2252 "left": {
2253 "&&": {
2254 "left": {
2255 "contains": {
2256 "left": {
2257 ".": {
2258 "left": {
2259 "Var": "principal"
2260 },
2261 "attr": "owners"
2262 }
2263 },
2264 "right": {
2265 "Value": "foo"
2266 }
2267 }
2268 },
2269 "right": {
2270 "containsAny": {
2271 "left": {
2272 ".": {
2273 "left": {
2274 "Var": "principal"
2275 },
2276 "attr": "owners"
2277 }
2278 },
2279 "right": {
2280 "Set": [
2281 { "Value": 1 },
2282 { "Value": {
2283 "__entity": {
2284 "type": "Linux::Group",
2285 "id": "sudoers"
2286 }
2287 } }
2288 ]
2289 }
2290 }
2291 }
2292 }
2293 },
2294 "right": {
2295 "containsAll": {
2296 "left": {
2297 "Set": [
2298 { "+": {
2299 "left": {
2300 "Value": 2
2301 },
2302 "right": {
2303 "Value": 3
2304 }
2305 } },
2306 { "Value": "spam" },
2307 ]
2308 },
2309 "right": {
2310 ".": {
2311 "left": {
2312 "Var": "resource"
2313 },
2314 "attr": "foos"
2315 }
2316 }
2317 }
2318 }
2319 }
2320 },
2321 "right": {
2322 "isEmpty": {
2323 "arg": {
2324 ".": {
2325 "left": {
2326 "Var": "context"
2327 },
2328 "attr": "violations"
2329 }
2330 }
2331 }
2332 }
2333 }
2334 }
2335 }
2336 ]
2337 }
2338 );
2339 assert_eq!(
2340 serde_json::to_value(&est).unwrap(),
2341 expected_json,
2342 "\nExpected:\n{}\n\nActual:\n{}\n\n",
2343 serde_json::to_string_pretty(&expected_json).unwrap(),
2344 serde_json::to_string_pretty(&est).unwrap()
2345 );
2346 let old_est = est.clone();
2347 let roundtripped = est_roundtrip(est);
2348 assert_eq!(&old_est, &roundtripped);
2349 let est = text_roundtrip(&old_est);
2350 assert_eq!(&old_est, &est);
2351
2352 assert_eq!(ast_roundtrip(est.clone()), est);
2353 assert_eq!(circular_roundtrip(est.clone()), est);
2354 }
2355
2356 #[test]
2357 fn entity_tags() {
2358 let policy = r#"
2359 permit(principal, action, resource)
2360 when {
2361 resource.hasTag("writeable")
2362 && resource.getTag("writeable").contains(principal.group)
2363 && principal.hasTag(context.foo)
2364 && principal.getTag(context.foo) == 72
2365 };
2366 "#;
2367 let cst = parser::text_to_cst::parse_policy(policy)
2368 .unwrap()
2369 .node
2370 .unwrap();
2371 let est: Policy = cst.try_into().unwrap();
2372 let expected_json = json!(
2373 {
2374 "effect": "permit",
2375 "principal": {
2376 "op": "All",
2377 },
2378 "action": {
2379 "op": "All",
2380 },
2381 "resource": {
2382 "op": "All",
2383 },
2384 "conditions": [
2385 {
2386 "kind": "when",
2387 "body": {
2388 "&&": {
2389 "left": {
2390 "&&": {
2391 "left": {
2392 "&&": {
2393 "left": {
2394 "hasTag": {
2395 "left": {
2396 "Var": "resource"
2397 },
2398 "right": {
2399 "Value": "writeable"
2400 }
2401 }
2402 },
2403 "right": {
2404 "contains": {
2405 "left": {
2406 "getTag": {
2407 "left": {
2408 "Var": "resource"
2409 },
2410 "right": {
2411 "Value": "writeable"
2412 }
2413 }
2414 },
2415 "right": {
2416 ".": {
2417 "left": {
2418 "Var": "principal"
2419 },
2420 "attr": "group"
2421 }
2422 }
2423 }
2424 }
2425 }
2426 },
2427 "right": {
2428 "hasTag": {
2429 "left": {
2430 "Var": "principal"
2431 },
2432 "right": {
2433 ".": {
2434 "left": {
2435 "Var": "context",
2436 },
2437 "attr": "foo"
2438 }
2439 }
2440 }
2441 },
2442 }
2443 },
2444 "right": {
2445 "==": {
2446 "left": {
2447 "getTag": {
2448 "left": {
2449 "Var": "principal"
2450 },
2451 "right": {
2452 ".": {
2453 "left": {
2454 "Var": "context",
2455 },
2456 "attr": "foo"
2457 }
2458 }
2459 }
2460 },
2461 "right": {
2462 "Value": 72
2463 }
2464 }
2465 }
2466 }
2467 }
2468 }
2469 ]
2470 }
2471 );
2472 assert_eq!(
2473 serde_json::to_value(&est).unwrap(),
2474 expected_json,
2475 "\nExpected:\n{}\n\nActual:\n{}\n\n",
2476 serde_json::to_string_pretty(&expected_json).unwrap(),
2477 serde_json::to_string_pretty(&est).unwrap()
2478 );
2479 let old_est = est.clone();
2480 let roundtripped = est_roundtrip(est);
2481 assert_eq!(&old_est, &roundtripped);
2482 let est = text_roundtrip(&old_est);
2483 assert_eq!(&old_est, &est);
2484
2485 assert_eq!(ast_roundtrip(est.clone()), est);
2486 assert_eq!(circular_roundtrip(est.clone()), est);
2487 }
2488
2489 #[test]
2490 fn like_special_patterns() {
2491 let policy = r#"
2492 permit(principal, action, resource)
2493 when {
2494
2495 "" like "ḛ̶͑͝x̶͔͛a̵̰̯͛m̴͉̋́p̷̠͂l̵͇̍̔ȩ̶̣͝"
2496 };
2497 "#;
2498 let cst = parser::text_to_cst::parse_policy(policy)
2499 .unwrap()
2500 .node
2501 .unwrap();
2502 let est: Policy = cst.try_into().unwrap();
2503 let expected_json = json!(
2504 {
2505 "effect": "permit",
2506 "principal": {
2507 "op": "All"
2508 },
2509 "action": {
2510 "op": "All"
2511 },
2512 "resource": {
2513 "op": "All"
2514 },
2515 "conditions": [
2516 {
2517 "kind": "when",
2518 "body": {
2519 "like": {
2520 "left": {
2521 "Value": ""
2522 },
2523 "pattern": [
2524 {
2525 "Literal": "e"
2526 },
2527 {
2528 "Literal": "̶"
2529 },
2530 {
2531 "Literal": "͑"
2532 },
2533 {
2534 "Literal": "͝"
2535 },
2536 {
2537 "Literal": "̰"
2538 },
2539 {
2540 "Literal": "x"
2541 },
2542 {
2543 "Literal": "̶"
2544 },
2545 {
2546 "Literal": "͛"
2547 },
2548 {
2549 "Literal": "͔"
2550 },
2551 {
2552 "Literal": "a"
2553 },
2554 {
2555 "Literal": "̵"
2556 },
2557 {
2558 "Literal": "͛"
2559 },
2560 {
2561 "Literal": "̰"
2562 },
2563 {
2564 "Literal": "̯"
2565 },
2566 {
2567 "Literal": "m"
2568 },
2569 {
2570 "Literal": "̴"
2571 },
2572 {
2573 "Literal": "̋"
2574 },
2575 {
2576 "Literal": "́"
2577 },
2578 {
2579 "Literal": "͉"
2580 },
2581 {
2582 "Literal": "p"
2583 },
2584 {
2585 "Literal": "̷"
2586 },
2587 {
2588 "Literal": "͂"
2589 },
2590 {
2591 "Literal": "̠"
2592 },
2593 {
2594 "Literal": "l"
2595 },
2596 {
2597 "Literal": "̵"
2598 },
2599 {
2600 "Literal": "̍"
2601 },
2602 {
2603 "Literal": "̔"
2604 },
2605 {
2606 "Literal": "͇"
2607 },
2608 {
2609 "Literal": "e"
2610 },
2611 {
2612 "Literal": "̶"
2613 },
2614 {
2615 "Literal": "͝"
2616 },
2617 {
2618 "Literal": "̧"
2619 },
2620 {
2621 "Literal": "̣"
2622 }
2623 ]
2624 }
2625 }
2626 }
2627 ]
2628 });
2629 assert_eq!(
2630 serde_json::to_value(&est).unwrap(),
2631 expected_json,
2632 "\nExpected:\n{}\n\nActual:\n{}\n\n",
2633 serde_json::to_string_pretty(&expected_json).unwrap(),
2634 serde_json::to_string_pretty(&est).unwrap()
2635 );
2636 let old_est = est.clone();
2637 let roundtripped = est_roundtrip(est);
2638 assert_eq!(&old_est, &roundtripped);
2639 let est = text_roundtrip(&old_est);
2640 assert_eq!(&old_est, &est);
2641
2642 assert_eq!(ast_roundtrip(est.clone()), est);
2643 assert_eq!(circular_roundtrip(est.clone()), est);
2644
2645 let alternative_json = json!(
2646 {
2647 "effect": "permit",
2648 "principal": {
2649 "op": "All"
2650 },
2651 "action": {
2652 "op": "All"
2653 },
2654 "resource": {
2655 "op": "All"
2656 },
2657 "conditions": [
2658 {
2659 "kind": "when",
2660 "body": {
2661 "like": {
2662 "left": {
2663 "Value": ""
2664 },
2665 "pattern": [
2666 {
2667 "Literal": "ḛ̶͑͝x̶͔͛a̵̰̯͛m̴͉̋́p̷̠͂l̵͇̍̔ȩ̶̣͝"
2668 }
2669 ]
2670 }
2671 }
2672 }
2673 ]
2674 }
2675 );
2676 let est1: Policy = serde_json::from_value(expected_json).unwrap();
2677 let est2: Policy = serde_json::from_value(alternative_json).unwrap();
2678 let ast1 = est1.try_into_ast_policy(None).unwrap();
2679 let ast2 = est2.try_into_ast_policy(None).unwrap();
2680 assert_eq!(ast1, ast2);
2681 }
2682
2683 #[test]
2684 fn has_like_and_if() {
2685 let policy = r#"
2686 permit(principal, action, resource)
2687 when {
2688 if context.foo
2689 then principal has "-78/%$!"
2690 else resource.email like "*@amazon.com"
2691 };
2692 "#;
2693 let cst = parser::text_to_cst::parse_policy(policy)
2694 .unwrap()
2695 .node
2696 .unwrap();
2697 let est: Policy = cst.try_into().unwrap();
2698 let expected_json = json!(
2699 {
2700 "effect": "permit",
2701 "principal": {
2702 "op": "All",
2703 },
2704 "action": {
2705 "op": "All",
2706 },
2707 "resource": {
2708 "op": "All",
2709 },
2710 "conditions": [
2711 {
2712 "kind": "when",
2713 "body": {
2714 "if-then-else": {
2715 "if": {
2716 ".": {
2717 "left": {
2718 "Var": "context"
2719 },
2720 "attr": "foo"
2721 }
2722 },
2723 "then": {
2724 "has": {
2725 "left": {
2726 "Var": "principal"
2727 },
2728 "attr": "-78/%$!"
2729 }
2730 },
2731 "else": {
2732 "like": {
2733 "left": {
2734 ".": {
2735 "left": {
2736 "Var": "resource"
2737 },
2738 "attr": "email"
2739 }
2740 },
2741 "pattern": [
2742 "Wildcard",
2743 {
2744 "Literal": "@"
2745 },
2746 {
2747 "Literal": "a"
2748 },
2749 {
2750 "Literal": "m"
2751 },
2752 {
2753 "Literal": "a"
2754 },
2755 {
2756 "Literal": "z"
2757 },
2758 {
2759 "Literal": "o"
2760 },
2761 {
2762 "Literal": "n"
2763 },
2764 {
2765 "Literal": "."
2766 },
2767 {
2768 "Literal": "c"
2769 },
2770 {
2771 "Literal": "o"
2772 },
2773 {
2774 "Literal": "m"
2775 }
2776 ]
2777 }
2778 }
2779 }
2780 }
2781 }
2782 ]
2783 }
2784 );
2785 assert_eq!(
2786 serde_json::to_value(&est).unwrap(),
2787 expected_json,
2788 "\nExpected:\n{}\n\nActual:\n{}\n\n",
2789 serde_json::to_string_pretty(&expected_json).unwrap(),
2790 serde_json::to_string_pretty(&est).unwrap()
2791 );
2792 let old_est = est.clone();
2793 let roundtripped = est_roundtrip(est);
2794 assert_eq!(&old_est, &roundtripped);
2795 let est = text_roundtrip(&old_est);
2796 assert_eq!(&old_est, &est);
2797
2798 assert_eq!(ast_roundtrip(est.clone()), est);
2799 assert_eq!(circular_roundtrip(est.clone()), est);
2800 }
2801
2802 #[test]
2803 fn decimal() {
2804 let policy = r#"
2805 permit(principal, action, resource)
2806 when {
2807 context.confidenceScore.greaterThan(decimal("10.0"))
2808 };
2809 "#;
2810 let cst = parser::text_to_cst::parse_policy(policy)
2811 .unwrap()
2812 .node
2813 .unwrap();
2814 let est: Policy = cst.try_into().unwrap();
2815 let expected_json = json!(
2816 {
2817 "effect": "permit",
2818 "principal": {
2819 "op": "All",
2820 },
2821 "action": {
2822 "op": "All",
2823 },
2824 "resource": {
2825 "op": "All",
2826 },
2827 "conditions": [
2828 {
2829 "kind": "when",
2830 "body": {
2831 "greaterThan": [
2832 {
2833 ".": {
2834 "left": {
2835 "Var": "context"
2836 },
2837 "attr": "confidenceScore"
2838 }
2839 },
2840 {
2841 "decimal": [
2842 {
2843 "Value": "10.0"
2844 }
2845 ]
2846 }
2847 ]
2848 }
2849 }
2850 ]
2851 }
2852 );
2853 assert_eq!(
2854 serde_json::to_value(&est).unwrap(),
2855 expected_json,
2856 "\nExpected:\n{}\n\nActual:\n{}\n\n",
2857 serde_json::to_string_pretty(&expected_json).unwrap(),
2858 serde_json::to_string_pretty(&est).unwrap()
2859 );
2860 let old_est = est.clone();
2861 let roundtripped = est_roundtrip(est);
2862 assert_eq!(&old_est, &roundtripped);
2863 let est = text_roundtrip(&old_est);
2864 assert_eq!(&old_est, &est);
2865
2866 assert_eq!(ast_roundtrip(est.clone()), est);
2867 assert_eq!(circular_roundtrip(est.clone()), est);
2868 }
2869
2870 #[test]
2871 fn ip() {
2872 let policy = r#"
2873 permit(principal, action, resource)
2874 when {
2875 context.source_ip.isInRange(ip("222.222.222.0/24"))
2876 };
2877 "#;
2878 let cst = parser::text_to_cst::parse_policy(policy)
2879 .unwrap()
2880 .node
2881 .unwrap();
2882 let est: Policy = cst.try_into().unwrap();
2883 let expected_json = json!(
2884 {
2885 "effect": "permit",
2886 "principal": {
2887 "op": "All",
2888 },
2889 "action": {
2890 "op": "All",
2891 },
2892 "resource": {
2893 "op": "All",
2894 },
2895 "conditions": [
2896 {
2897 "kind": "when",
2898 "body": {
2899 "isInRange": [
2900 {
2901 ".": {
2902 "left": {
2903 "Var": "context"
2904 },
2905 "attr": "source_ip"
2906 }
2907 },
2908 {
2909 "ip": [
2910 {
2911 "Value": "222.222.222.0/24"
2912 }
2913 ]
2914 }
2915 ]
2916 }
2917 }
2918 ]
2919 }
2920 );
2921 assert_eq!(
2922 serde_json::to_value(&est).unwrap(),
2923 expected_json,
2924 "\nExpected:\n{}\n\nActual:\n{}\n\n",
2925 serde_json::to_string_pretty(&expected_json).unwrap(),
2926 serde_json::to_string_pretty(&est).unwrap()
2927 );
2928 let old_est = est.clone();
2929 let roundtripped = est_roundtrip(est);
2930 assert_eq!(&old_est, &roundtripped);
2931 let est = text_roundtrip(&old_est);
2932 assert_eq!(&old_est, &est);
2933
2934 assert_eq!(ast_roundtrip(est.clone()), est);
2935 assert_eq!(circular_roundtrip(est.clone()), est);
2936 }
2937
2938 #[test]
2939 fn negative_numbers() {
2940 let policy = r#"
2941 permit(principal, action, resource)
2942 when { -1 };
2943 "#;
2944 let cst = parser::text_to_cst::parse_policy(policy)
2945 .unwrap()
2946 .node
2947 .unwrap();
2948 let est: Policy = cst.try_into().unwrap();
2949 let expected_json = json!(
2950 {
2951 "effect": "permit",
2952 "principal": {
2953 "op": "All",
2954 },
2955 "action": {
2956 "op": "All",
2957 },
2958 "resource": {
2959 "op": "All",
2960 },
2961 "conditions": [
2962 {
2963 "kind": "when",
2964 "body": {
2965 "Value": -1
2966 }
2967 }]});
2968 assert_eq!(
2969 serde_json::to_value(&est).unwrap(),
2970 expected_json,
2971 "\nExpected:\n{}\n\nActual:\n{}\n\n",
2972 serde_json::to_string_pretty(&expected_json).unwrap(),
2973 serde_json::to_string_pretty(&est).unwrap()
2974 );
2975 let policy = r#"
2976 permit(principal, action, resource)
2977 when { -(1) };
2978 "#;
2979 let cst = parser::text_to_cst::parse_policy(policy)
2980 .unwrap()
2981 .node
2982 .unwrap();
2983 let est: Policy = cst.try_into().unwrap();
2984 let expected_json = json!(
2985 {
2986 "effect": "permit",
2987 "principal": {
2988 "op": "All",
2989 },
2990 "action": {
2991 "op": "All",
2992 },
2993 "resource": {
2994 "op": "All",
2995 },
2996 "conditions": [
2997 {
2998 "kind": "when",
2999 "body": {
3000 "neg": {
3001 "arg": {
3002 "Value": 1
3003 }
3004 }
3005 }
3006 }]});
3007 assert_eq!(
3008 serde_json::to_value(&est).unwrap(),
3009 expected_json,
3010 "\nExpected:\n{}\n\nActual:\n{}\n\n",
3011 serde_json::to_string_pretty(&expected_json).unwrap(),
3012 serde_json::to_string_pretty(&est).unwrap()
3013 );
3014 }
3015
3016 #[test]
3017 fn string_escapes() {
3018 let est = parse_policy_or_template_to_est(
3019 r#"permit(principal, action, resource) when { "\n" };"#,
3020 )
3021 .unwrap();
3022 let new_est = text_roundtrip(&est);
3023 assert_eq!(est, new_est);
3024 }
3025
3026 #[test]
3027 fn eid_escapes() {
3028 let est = parse_policy_or_template_to_est(
3029 r#"permit(principal, action, resource) when { Foo::"\n" };"#,
3030 )
3031 .unwrap();
3032 let new_est = text_roundtrip(&est);
3033 assert_eq!(est, new_est);
3034 }
3035
3036 #[test]
3037 fn multiple_clauses() {
3038 let policy = r#"
3039 permit(principal, action, resource)
3040 when { context.foo }
3041 unless { context.bar }
3042 when { principal.eggs };
3043 "#;
3044 let cst = parser::text_to_cst::parse_policy(policy)
3045 .unwrap()
3046 .node
3047 .unwrap();
3048 let est: Policy = cst.try_into().unwrap();
3049 let expected_json = json!(
3050 {
3051 "effect": "permit",
3052 "principal": {
3053 "op": "All",
3054 },
3055 "action": {
3056 "op": "All",
3057 },
3058 "resource": {
3059 "op": "All",
3060 },
3061 "conditions": [
3062 {
3063 "kind": "when",
3064 "body": {
3065 ".": {
3066 "left": {
3067 "Var": "context"
3068 },
3069 "attr": "foo"
3070 }
3071 }
3072 },
3073 {
3074 "kind": "unless",
3075 "body": {
3076 ".": {
3077 "left": {
3078 "Var": "context"
3079 },
3080 "attr": "bar"
3081 }
3082 }
3083 },
3084 {
3085 "kind": "when",
3086 "body": {
3087 ".": {
3088 "left": {
3089 "Var": "principal"
3090 },
3091 "attr": "eggs"
3092 }
3093 }
3094 }
3095 ]
3096 }
3097 );
3098 assert_eq!(
3099 serde_json::to_value(&est).unwrap(),
3100 expected_json,
3101 "\nExpected:\n{}\n\nActual:\n{}\n\n",
3102 serde_json::to_string_pretty(&expected_json).unwrap(),
3103 serde_json::to_string_pretty(&est).unwrap()
3104 );
3105 let old_est = est.clone();
3106 let roundtripped = est_roundtrip(est);
3107 assert_eq!(&old_est, &roundtripped);
3108 let est = text_roundtrip(&old_est);
3109 assert_eq!(&old_est, &est);
3110
3111 let expected_json_after_roundtrip = json!(
3114 {
3115 "effect": "permit",
3116 "principal": {
3117 "op": "All",
3118 },
3119 "action": {
3120 "op": "All",
3121 },
3122 "resource": {
3123 "op": "All",
3124 },
3125 "conditions": [
3126 {
3127 "kind": "when",
3128 "body": {
3129 "&&": {
3130 "left": {
3131 ".": {
3132 "left": {
3133 "Var": "context"
3134 },
3135 "attr": "foo"
3136 }
3137 },
3138 "right": {
3139 "&&": {
3140 "left": {
3141 "!": {
3142 "arg": {
3143 ".": {
3144 "left": {
3145 "Var": "context"
3146 },
3147 "attr": "bar"
3148 }
3149 }
3150 }
3151 },
3152 "right": {
3153 ".": {
3154 "left": {
3155 "Var": "principal"
3156 },
3157 "attr": "eggs"
3158 }
3159 }
3160 }
3161 }
3162 }
3163 }
3164 }
3165 ]
3166 }
3167 );
3168 let roundtripped = serde_json::to_value(ast_roundtrip(est.clone())).unwrap();
3169 assert_eq!(
3170 roundtripped,
3171 expected_json_after_roundtrip,
3172 "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
3173 serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
3174 serde_json::to_string_pretty(&roundtripped).unwrap()
3175 );
3176 let roundtripped = serde_json::to_value(circular_roundtrip(est)).unwrap();
3177 assert_eq!(
3178 roundtripped,
3179 expected_json_after_roundtrip,
3180 "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
3181 serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
3182 serde_json::to_string_pretty(&roundtripped).unwrap()
3183 );
3184 }
3185
3186 #[test]
3187 fn link() {
3188 let template = r#"
3189 permit(
3190 principal == ?principal,
3191 action == Action::"view",
3192 resource in ?resource
3193 ) when {
3194 principal in resource.owners
3195 };
3196 "#;
3197 let cst = parser::text_to_cst::parse_policy(template)
3198 .unwrap()
3199 .node
3200 .unwrap();
3201 let est: Policy = cst.try_into().unwrap();
3202 let err = est
3203 .clone()
3204 .link(&HashMap::from_iter([]))
3205 .expect_err("didn't fill all the slots");
3206 expect_err(
3207 "",
3208 &miette::Report::new(err),
3209 &ExpectedErrorMessageBuilder::error(
3210 "failed to link template: no value provided for `?principal`",
3211 )
3212 .build(),
3213 );
3214 let err = est
3215 .clone()
3216 .link(&HashMap::from_iter([(
3217 ast::SlotId::principal(),
3218 EntityUidJson::new("XYZCorp::User", "12UA45"),
3219 )]))
3220 .expect_err("didn't fill all the slots");
3221 expect_err(
3222 "",
3223 &miette::Report::new(err),
3224 &ExpectedErrorMessageBuilder::error(
3225 "failed to link template: no value provided for `?resource`",
3226 )
3227 .build(),
3228 );
3229 let linked = est
3230 .link(&HashMap::from_iter([
3231 (
3232 ast::SlotId::principal(),
3233 EntityUidJson::new("XYZCorp::User", "12UA45"),
3234 ),
3235 (ast::SlotId::resource(), EntityUidJson::new("Folder", "abc")),
3236 ]))
3237 .expect("did fill all the slots");
3238 let expected_json = json!(
3239 {
3240 "effect": "permit",
3241 "principal": {
3242 "op": "==",
3243 "entity": { "type": "XYZCorp::User", "id": "12UA45" },
3244 },
3245 "action": {
3246 "op": "==",
3247 "entity": { "type": "Action", "id": "view" },
3248 },
3249 "resource": {
3250 "op": "in",
3251 "entity": { "type": "Folder", "id": "abc" },
3252 },
3253 "conditions": [
3254 {
3255 "kind": "when",
3256 "body": {
3257 "in": {
3258 "left": {
3259 "Var": "principal"
3260 },
3261 "right": {
3262 ".": {
3263 "left": {
3264 "Var": "resource"
3265 },
3266 "attr": "owners"
3267 }
3268 }
3269 }
3270 }
3271 }
3272 ],
3273 }
3274 );
3275 let linked_json = serde_json::to_value(linked).unwrap();
3276 assert_eq!(
3277 linked_json,
3278 expected_json,
3279 "\nExpected:\n{}\n\nActual:\n{}\n\n",
3280 serde_json::to_string_pretty(&expected_json).unwrap(),
3281 serde_json::to_string_pretty(&linked_json).unwrap(),
3282 );
3283 }
3284
3285 #[test]
3286 fn ast_to_est_with_linked_template_using_is_in_operator_1() {
3287 let template = r#"
3288 permit(
3289 principal is XYZCorp::User in ?principal,
3290 action == Action::"view",
3291 resource in ?resource
3292 ) when {
3293 principal in resource.owners
3294 };
3295 "#;
3296
3297 let cst = parser::text_to_cst::parse_policy(template).unwrap();
3298 let ast: ast::Template = cst
3299 .to_policy_template(ast::PolicyID::from_string("test"))
3300 .unwrap();
3301
3302 let policy = ast::Template::link(
3303 std::sync::Arc::new(ast),
3304 ast::PolicyID::from_string("test"),
3305 HashMap::from_iter([
3306 (
3307 ast::SlotId::principal(),
3308 r#"XYZCorp::User::"12UA45""#.parse().unwrap(),
3309 ),
3310 (ast::SlotId::resource(), r#"Folder::"abc""#.parse().unwrap()),
3311 ]),
3312 )
3313 .unwrap();
3314
3315 let est: Policy = policy.into();
3316
3317 let expected_json = json!(
3318 {
3319 "effect": "permit",
3320 "principal": {
3321 "op": "is",
3322 "entity_type": "XYZCorp::User",
3323 "in": { "entity": { "type": "XYZCorp::User", "id": "12UA45" } },
3324 },
3325 "action": {
3326 "op": "==",
3327 "entity": { "type": "Action", "id": "view" },
3328 },
3329 "resource": {
3330 "op": "in",
3331 "entity": { "type": "Folder", "id": "abc" },
3332 },
3333 "conditions": [
3334 {
3335 "kind": "when",
3336 "body": {
3337 "in": {
3338 "left": {
3339 "Var": "principal"
3340 },
3341 "right": {
3342 ".": {
3343 "left": {
3344 "Var": "resource"
3345 },
3346 "attr": "owners"
3347 }
3348 }
3349 }
3350 }
3351 }
3352 ],
3353 }
3354 );
3355 let linked_json = serde_json::to_value(est).unwrap();
3356 assert_eq!(
3357 linked_json,
3358 expected_json,
3359 "\nExpected:\n{}\n\nActual:\n{}\n\n",
3360 serde_json::to_string_pretty(&expected_json).unwrap(),
3361 serde_json::to_string_pretty(&linked_json).unwrap(),
3362 );
3363 }
3364
3365 #[test]
3366 fn ast_to_est_with_linked_template_using_is_in_operator_2() {
3367 let template = r#"
3368 permit(
3369 principal is User in ?principal,
3370 action,
3371 resource is Doc in ?resource
3372 );
3373 "#;
3374
3375 let cst = parser::text_to_cst::parse_policy(template).unwrap();
3376 let ast: ast::Template = cst
3377 .to_policy_template(ast::PolicyID::from_string("test"))
3378 .unwrap();
3379
3380 let policy = ast::Template::link(
3381 std::sync::Arc::new(ast),
3382 ast::PolicyID::from_string("test"),
3383 HashMap::from_iter([
3384 (
3385 ast::SlotId::principal(),
3386 r#"User::"alice""#.parse().unwrap(),
3387 ),
3388 (ast::SlotId::resource(), r#"Doc::"abc""#.parse().unwrap()),
3389 ]),
3390 )
3391 .unwrap();
3392
3393 let est: Policy = policy.into();
3394
3395 let expected_json = json!(
3396 {
3397 "effect": "permit",
3398 "principal": {
3399 "op": "is",
3400 "entity_type": "User",
3401 "in": { "entity": { "type": "User", "id": "alice" } }
3402 },
3403 "action": {
3404 "op": "All"
3405 },
3406 "resource": {
3407 "op": "is",
3408 "entity_type": "Doc",
3409 "in": { "entity": { "type": "Doc", "id": "abc" } }
3410 },
3411 "conditions": [ ],
3412 }
3413 );
3414 let linked_json = serde_json::to_value(est).unwrap();
3415 assert_eq!(
3416 linked_json,
3417 expected_json,
3418 "\nExpected:\n{}\n\nActual:\n{}\n\n",
3419 serde_json::to_string_pretty(&expected_json).unwrap(),
3420 serde_json::to_string_pretty(&linked_json).unwrap(),
3421 );
3422 }
3423
3424 #[test]
3425 fn eid_with_nulls() {
3426 let policy = r#"
3427 permit(
3428 principal == a::"\0\0\0J",
3429 action == Action::"view",
3430 resource
3431 );
3432 "#;
3433 let cst = parser::text_to_cst::parse_policy(policy)
3434 .unwrap()
3435 .node
3436 .unwrap();
3437 let est: Policy = cst.try_into().unwrap();
3438 let expected_json = json!(
3439 {
3440 "effect": "permit",
3441 "principal": {
3442 "op": "==",
3443 "entity": {
3444 "type": "a",
3445 "id": "\0\0\0J",
3446 }
3447 },
3448 "action": {
3449 "op": "==",
3450 "entity": {
3451 "type": "Action",
3452 "id": "view",
3453 }
3454 },
3455 "resource": {
3456 "op": "All"
3457 },
3458 "conditions": []
3459 }
3460 );
3461 assert_eq!(
3462 serde_json::to_value(&est).unwrap(),
3463 expected_json,
3464 "\nExpected:\n{}\n\nActual:\n{}\n\n",
3465 serde_json::to_string_pretty(&expected_json).unwrap(),
3466 serde_json::to_string_pretty(&est).unwrap()
3467 );
3468 let old_est = est.clone();
3469 let roundtripped = est_roundtrip(est);
3470 assert_eq!(&old_est, &roundtripped);
3471 let est = text_roundtrip(&old_est);
3472 assert_eq!(&old_est, &est);
3473
3474 let expected_json_after_roundtrip = json!(
3475 {
3476 "effect": "permit",
3477 "principal": {
3478 "op": "==",
3479 "entity": {
3480 "type": "a",
3481 "id": "\0\0\0J",
3482 }
3483 },
3484 "action": {
3485 "op": "==",
3486 "entity": {
3487 "type": "Action",
3488 "id": "view",
3489 }
3490 },
3491 "resource": {
3492 "op": "All"
3493 },
3494 "conditions": [ ]
3495 }
3496 );
3497 let roundtripped = serde_json::to_value(ast_roundtrip(est.clone())).unwrap();
3498 assert_eq!(
3499 roundtripped,
3500 expected_json_after_roundtrip,
3501 "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
3502 serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
3503 serde_json::to_string_pretty(&roundtripped).unwrap()
3504 );
3505 let roundtripped = serde_json::to_value(circular_roundtrip(est)).unwrap();
3506 assert_eq!(
3507 roundtripped,
3508 expected_json_after_roundtrip,
3509 "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
3510 serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
3511 serde_json::to_string_pretty(&roundtripped).unwrap()
3512 );
3513 }
3514
3515 #[test]
3516 fn invalid_json_ests() {
3517 let bad = json!(
3518 {
3519 "effect": "Permit",
3520 "principal": {
3521 "op": "All"
3522 },
3523 "action": {
3524 "op": "All"
3525 },
3526 "resource": {
3527 "op": "All"
3528 },
3529 "conditions": []
3530 }
3531 );
3532 let est: Result<Policy, _> = serde_json::from_value(bad);
3533 assert_matches!(est, Err(_)); let bad = json!(
3536 {
3537 "effect": "permit",
3538 "principal": {
3539 "op": "All"
3540 },
3541 "action": {
3542 "op": "All"
3543 },
3544 "resource": {
3545 "op": "All"
3546 },
3547 "conditions": [
3548 {
3549 "kind": "when",
3550 "body": {}
3551 }
3552 ]
3553 }
3554 );
3555 assert_matches!(serde_json::from_value::<Policy>(bad), Err(e) => {
3556 assert_eq!(e.to_string(), "empty map is not a valid expression");
3557 });
3558
3559 let bad = json!(
3560 {
3561 "effect": "permit",
3562 "principal": {
3563 "op": "All"
3564 },
3565 "action": {
3566 "op": "All"
3567 },
3568 "resource": {
3569 "op": "All"
3570 },
3571 "conditions": [
3572 {
3573 "kind": "when",
3574 "body": {
3575 "+": {
3576 "left": {
3577 "Value": 3
3578 },
3579 "right": {
3580 "Value": 4
3581 }
3582 },
3583 "-": {
3584 "left": {
3585 "Value": 8
3586 },
3587 "right": {
3588 "Value": 2
3589 }
3590 },
3591 }
3592 }
3593 ]
3594 }
3595 );
3596 assert_matches!(serde_json::from_value::<Policy>(bad), Err(e) => {
3597 assert_eq!(e.to_string(), "JSON object representing an `Expr` should have only one key, but found two keys: `+` and `-`");
3598 });
3599
3600 let bad = json!(
3601 {
3602 "effect": "permit",
3603 "principal": {
3604 "op": "All"
3605 },
3606 "action": {
3607 "op": "All"
3608 },
3609 "resource": {
3610 "op": "All"
3611 },
3612 "conditions": [
3613 {
3614 "kind": "when",
3615 "body": {
3616 "+": {
3617 "left": {
3618 "Value": 3
3619 },
3620 "right": {
3621 "Value": 4
3622 }
3623 },
3624 "-": {
3625 "left": {
3626 "Value": 2
3627 },
3628 "right": {
3629 "Value": 8
3630 }
3631 }
3632 }
3633 }
3634 ]
3635 }
3636 );
3637 let est: Result<Policy, _> = serde_json::from_value(bad);
3638 assert_matches!(est, Err(_)); let template = json!(
3641 {
3642 "effect": "permit",
3643 "principal": {
3644 "op": "==",
3645 "slot": "?principal",
3646 },
3647 "action": {
3648 "op": "All"
3649 },
3650 "resource": {
3651 "op": "All"
3652 },
3653 "conditions": []
3654 }
3655 );
3656 let est: Policy = serde_json::from_value(template).unwrap();
3657 let ast: Result<ast::Policy, _> = est.try_into_ast_policy(None);
3658 assert_matches!(
3659 ast,
3660 Err(e) => {
3661 expect_err(
3662 "",
3663 &miette::Report::new(e),
3664 &ExpectedErrorMessageBuilder::error(r#"expected a static policy, got a template containing the slot ?principal"#)
3665 .help("try removing the template slot(s) from this policy")
3666 .build()
3667 );
3668 }
3669 );
3670 }
3671
3672 #[test]
3673 fn record_duplicate_key() {
3674 let bad = r#"
3675 {
3676 "effect": "permit",
3677 "principal": { "op": "All" },
3678 "action": { "op": "All" },
3679 "resource": { "op": "All" },
3680 "conditions": [
3681 {
3682 "kind": "when",
3683 "body": {
3684 "Record": {
3685 "foo": {"Value": 0},
3686 "foo": {"Value": 1}
3687 }
3688 }
3689 }
3690 ]
3691 }
3692 "#;
3693 let est: Result<Policy, _> = serde_json::from_str(bad);
3694 assert_matches!(est, Err(_));
3695 }
3696
3697 #[test]
3698 fn value_record_duplicate_key() {
3699 let bad = r#"
3700 {
3701 "effect": "permit",
3702 "principal": { "op": "All" },
3703 "action": { "op": "All" },
3704 "resource": { "op": "All" },
3705 "conditions": [
3706 {
3707 "kind": "when",
3708 "body": {
3709 "Value": {
3710 "foo": 0,
3711 "foo": 1
3712 }
3713 }
3714 }
3715 ]
3716 }
3717 "#;
3718 let est: Result<Policy, _> = serde_json::from_str(bad);
3719 assert_matches!(est, Err(_));
3720 }
3721
3722 #[test]
3723 fn duplicate_annotations() {
3724 let bad = r#"
3725 {
3726 "effect": "permit",
3727 "principal": { "op": "All" },
3728 "action": { "op": "All" },
3729 "resource": { "op": "All" },
3730 "conditions": [],
3731 "annotations": {
3732 "foo": "bar",
3733 "foo": "baz"
3734 }
3735 }
3736 "#;
3737 let est: Result<Policy, _> = serde_json::from_str(bad);
3738 assert_matches!(est, Err(_));
3739 }
3740
3741 #[test]
3742 fn extension_duplicate_keys() {
3743 let bad = r#"
3744 {
3745 "effect": "permit",
3746 "principal": { "op": "All" },
3747 "action": { "op": "All" },
3748 "resource": { "op": "All" },
3749 "conditions": [
3750 {
3751 "kind": "when",
3752 "body": {
3753 "ip": [
3754 {
3755 "Value": "222.222.222.0/24"
3756 }
3757 ],
3758 "ip": [
3759 {
3760 "Value": "111.111.111.0/24"
3761 }
3762 ]
3763 }
3764 }
3765 ]
3766 }
3767 "#;
3768 let est: Result<Policy, _> = serde_json::from_str(bad);
3769 assert_matches!(est, Err(_));
3770 }
3771
3772 mod is_type {
3773 use cool_asserts::assert_panics;
3774
3775 use super::*;
3776
3777 #[test]
3778 fn principal() {
3779 let policy = r"permit(principal is User, action, resource);";
3780 let cst = parser::text_to_cst::parse_policy(policy)
3781 .unwrap()
3782 .node
3783 .unwrap();
3784 let est: Policy = cst.try_into().unwrap();
3785 let expected_json = json!(
3786 {
3787 "effect": "permit",
3788 "principal": {
3789 "op": "is",
3790 "entity_type": "User"
3791 },
3792 "action": {
3793 "op": "All",
3794 },
3795 "resource": {
3796 "op": "All",
3797 },
3798 "conditions": [ ]
3799 }
3800 );
3801 assert_eq!(
3802 serde_json::to_value(&est).unwrap(),
3803 expected_json,
3804 "\nExpected:\n{}\n\nActual:\n{}\n\n",
3805 serde_json::to_string_pretty(&expected_json).unwrap(),
3806 serde_json::to_string_pretty(&est).unwrap()
3807 );
3808 let old_est = est.clone();
3809 let roundtripped = est_roundtrip(est);
3810 assert_eq!(&old_est, &roundtripped);
3811 let est = text_roundtrip(&old_est);
3812 assert_eq!(&old_est, &est);
3813
3814 let expected_json_after_roundtrip = json!(
3815 {
3816 "effect": "permit",
3817 "principal": {
3818 "op": "is",
3819 "entity_type": "User"
3820 },
3821 "action": {
3822 "op": "All",
3823 },
3824 "resource": {
3825 "op": "All",
3826 },
3827 "conditions": [ ],
3828 }
3829 );
3830 let roundtripped = serde_json::to_value(ast_roundtrip(est.clone())).unwrap();
3831 assert_eq!(
3832 roundtripped,
3833 expected_json_after_roundtrip,
3834 "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
3835 serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
3836 serde_json::to_string_pretty(&roundtripped).unwrap()
3837 );
3838 let roundtripped = serde_json::to_value(circular_roundtrip(est)).unwrap();
3839 assert_eq!(
3840 roundtripped,
3841 expected_json_after_roundtrip,
3842 "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
3843 serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
3844 serde_json::to_string_pretty(&roundtripped).unwrap()
3845 );
3846 }
3847
3848 #[test]
3849 fn resource() {
3850 let policy = r"permit(principal, action, resource is Log);";
3851 let cst = parser::text_to_cst::parse_policy(policy)
3852 .unwrap()
3853 .node
3854 .unwrap();
3855 let est: Policy = cst.try_into().unwrap();
3856 let expected_json = json!(
3857 {
3858 "effect": "permit",
3859 "principal": {
3860 "op": "All",
3861 },
3862 "action": {
3863 "op": "All",
3864 },
3865 "resource": {
3866 "op": "is",
3867 "entity_type": "Log"
3868 },
3869 "conditions": [ ]
3870 }
3871 );
3872 assert_eq!(
3873 serde_json::to_value(&est).unwrap(),
3874 expected_json,
3875 "\nExpected:\n{}\n\nActual:\n{}\n\n",
3876 serde_json::to_string_pretty(&expected_json).unwrap(),
3877 serde_json::to_string_pretty(&est).unwrap()
3878 );
3879 let old_est = est.clone();
3880 let roundtripped = est_roundtrip(est);
3881 assert_eq!(&old_est, &roundtripped);
3882 let est = text_roundtrip(&old_est);
3883 assert_eq!(&old_est, &est);
3884
3885 let expected_json_after_roundtrip = json!(
3886 {
3887 "effect": "permit",
3888 "principal": {
3889 "op": "All",
3890 },
3891 "action": {
3892 "op": "All",
3893 },
3894 "resource": {
3895 "op": "is",
3896 "entity_type": "Log"
3897 },
3898 "conditions": [ ],
3899 }
3900 );
3901 let roundtripped = serde_json::to_value(ast_roundtrip(est.clone())).unwrap();
3902 assert_eq!(
3903 roundtripped,
3904 expected_json_after_roundtrip,
3905 "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
3906 serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
3907 serde_json::to_string_pretty(&roundtripped).unwrap()
3908 );
3909 let roundtripped = serde_json::to_value(circular_roundtrip(est)).unwrap();
3910 assert_eq!(
3911 roundtripped,
3912 expected_json_after_roundtrip,
3913 "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
3914 serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
3915 serde_json::to_string_pretty(&roundtripped).unwrap()
3916 );
3917 }
3918
3919 #[test]
3920 fn principal_in_entity() {
3921 let policy = r#"permit(principal is User in Group::"admin", action, resource);"#;
3922 let cst = parser::text_to_cst::parse_policy(policy)
3923 .unwrap()
3924 .node
3925 .unwrap();
3926 let est: Policy = cst.try_into().unwrap();
3927 let expected_json = json!(
3928 {
3929 "effect": "permit",
3930 "principal": {
3931 "op": "is",
3932 "entity_type": "User",
3933 "in": { "entity": { "type": "Group", "id": "admin" } }
3934 },
3935 "action": {
3936 "op": "All",
3937 },
3938 "resource": {
3939 "op": "All",
3940 },
3941 "conditions": [ ]
3942 }
3943 );
3944 assert_eq!(
3945 serde_json::to_value(&est).unwrap(),
3946 expected_json,
3947 "\nExpected:\n{}\n\nActual:\n{}\n\n",
3948 serde_json::to_string_pretty(&expected_json).unwrap(),
3949 serde_json::to_string_pretty(&est).unwrap()
3950 );
3951 let old_est = est.clone();
3952 let roundtripped = est_roundtrip(est);
3953 assert_eq!(&old_est, &roundtripped);
3954 let est = text_roundtrip(&old_est);
3955 assert_eq!(&old_est, &est);
3956
3957 let expected_json_after_roundtrip = json!(
3958 {
3959 "effect": "permit",
3960 "principal": {
3961 "op": "is",
3962 "entity_type": "User",
3963 "in": { "entity": { "type": "Group", "id": "admin" } }
3964 },
3965 "action": {
3966 "op": "All",
3967 },
3968 "resource": {
3969 "op": "All",
3970 },
3971 "conditions": [ ],
3972 }
3973 );
3974 let roundtripped = serde_json::to_value(ast_roundtrip(est.clone())).unwrap();
3975 assert_eq!(
3976 roundtripped,
3977 expected_json_after_roundtrip,
3978 "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
3979 serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
3980 serde_json::to_string_pretty(&roundtripped).unwrap()
3981 );
3982 let roundtripped = serde_json::to_value(circular_roundtrip(est)).unwrap();
3983 assert_eq!(
3984 roundtripped,
3985 expected_json_after_roundtrip,
3986 "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
3987 serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
3988 serde_json::to_string_pretty(&roundtripped).unwrap()
3989 );
3990 }
3991
3992 #[test]
3993 fn principal_in_slot() {
3994 let policy = r#"permit(principal is User in ?principal, action, resource);"#;
3995 let cst = parser::text_to_cst::parse_policy(policy)
3996 .unwrap()
3997 .node
3998 .unwrap();
3999 let est: Policy = cst.try_into().unwrap();
4000 let expected_json = json!(
4001 {
4002 "effect": "permit",
4003 "principal": {
4004 "op": "is",
4005 "entity_type": "User",
4006 "in": { "slot": "?principal" }
4007 },
4008 "action": {
4009 "op": "All",
4010 },
4011 "resource": {
4012 "op": "All",
4013 },
4014 "conditions": [ ]
4015 }
4016 );
4017 assert_eq!(
4018 serde_json::to_value(&est).unwrap(),
4019 expected_json,
4020 "\nExpected:\n{}\n\nActual:\n{}\n\n",
4021 serde_json::to_string_pretty(&expected_json).unwrap(),
4022 serde_json::to_string_pretty(&est).unwrap()
4023 );
4024 let old_est = est.clone();
4025 let roundtripped = est_roundtrip(est);
4026 assert_eq!(&old_est, &roundtripped);
4027 let est = text_roundtrip(&old_est);
4028 assert_eq!(&old_est, &est);
4029
4030 let expected_json_after_roundtrip = json!(
4031 {
4032 "effect": "permit",
4033 "principal": {
4034 "op": "is",
4035 "entity_type": "User",
4036 "in": { "slot": "?principal" }
4037 },
4038 "action": {
4039 "op": "All",
4040 },
4041 "resource": {
4042 "op": "All",
4043 },
4044 "conditions": [ ],
4045 }
4046 );
4047 let roundtripped = serde_json::to_value(ast_roundtrip_template(est.clone())).unwrap();
4048 assert_eq!(
4049 roundtripped,
4050 expected_json_after_roundtrip,
4051 "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
4052 serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
4053 serde_json::to_string_pretty(&roundtripped).unwrap()
4054 );
4055 let roundtripped = serde_json::to_value(circular_roundtrip_template(est)).unwrap();
4056 assert_eq!(
4057 roundtripped,
4058 expected_json_after_roundtrip,
4059 "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
4060 serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
4061 serde_json::to_string_pretty(&roundtripped).unwrap()
4062 );
4063 }
4064
4065 #[test]
4066 fn condition() {
4067 let policy = r#"
4068 permit(principal, action, resource)
4069 when { principal is User };"#;
4070 let cst = parser::text_to_cst::parse_policy(policy)
4071 .unwrap()
4072 .node
4073 .unwrap();
4074 let est: Policy = cst.try_into().unwrap();
4075 let expected_json = json!(
4076 {
4077 "effect": "permit",
4078 "principal": {
4079 "op": "All",
4080 },
4081 "action": {
4082 "op": "All",
4083 },
4084 "resource": {
4085 "op": "All",
4086 },
4087 "conditions": [
4088 {
4089 "kind": "when",
4090 "body": {
4091 "is": {
4092 "left": {
4093 "Var": "principal"
4094 },
4095 "entity_type": "User",
4096 }
4097 }
4098 }
4099 ]
4100 }
4101 );
4102 assert_eq!(
4103 serde_json::to_value(&est).unwrap(),
4104 expected_json,
4105 "\nExpected:\n{}\n\nActual:\n{}\n\n",
4106 serde_json::to_string_pretty(&expected_json).unwrap(),
4107 serde_json::to_string_pretty(&est).unwrap()
4108 );
4109 let old_est = est.clone();
4110 let roundtripped = est_roundtrip(est);
4111 assert_eq!(&old_est, &roundtripped);
4112 let est = text_roundtrip(&old_est);
4113 assert_eq!(&old_est, &est);
4114
4115 assert_eq!(ast_roundtrip(est.clone()), est);
4116 assert_eq!(circular_roundtrip(est.clone()), est);
4117 }
4118
4119 #[test]
4120 fn condition_in() {
4121 let policy = r#"
4122 permit(principal, action, resource)
4123 when { principal is User in 1 };"#;
4124 let cst = parser::text_to_cst::parse_policy(policy)
4125 .unwrap()
4126 .node
4127 .unwrap();
4128 let est: Policy = cst.try_into().unwrap();
4129 let expected_json = json!(
4130 {
4131 "effect": "permit",
4132 "principal": {
4133 "op": "All",
4134 },
4135 "action": {
4136 "op": "All",
4137 },
4138 "resource": {
4139 "op": "All",
4140 },
4141 "conditions": [
4142 {
4143 "kind": "when",
4144 "body": {
4145 "is": {
4146 "left": { "Var": "principal" },
4147 "entity_type": "User",
4148 "in": {"Value": 1}
4149 }
4150 }
4151 }
4152 ]
4153 }
4154 );
4155 assert_eq!(
4156 serde_json::to_value(&est).unwrap(),
4157 expected_json,
4158 "\nExpected:\n{}\n\nActual:\n{}\n\n",
4159 serde_json::to_string_pretty(&expected_json).unwrap(),
4160 serde_json::to_string_pretty(&est).unwrap()
4161 );
4162 let old_est = est.clone();
4163 let roundtripped = est_roundtrip(est);
4164 assert_eq!(&old_est, &roundtripped);
4165 let est = text_roundtrip(&old_est);
4166 assert_eq!(&old_est, &est);
4167
4168 let expected_json_after_roundtrip = json!(
4169 {
4170 "effect": "permit",
4171 "principal": {
4172 "op": "All",
4173 },
4174 "action": {
4175 "op": "All",
4176 },
4177 "resource": {
4178 "op": "All",
4179 },
4180 "conditions": [
4181 {
4182 "kind": "when",
4183 "body": {
4184 "&&": {
4185 "left": {
4186 "is": {
4187 "left": { "Var": "principal" },
4188 "entity_type": "User",
4189 }
4190 },
4191 "right": {
4192 "in": {
4193 "left": { "Var": "principal" },
4194 "right": { "Value": 1}
4195 }
4196 }
4197 }
4198 }
4199 }
4200 ],
4201 }
4202 );
4203 let roundtripped = serde_json::to_value(ast_roundtrip_template(est.clone())).unwrap();
4204 assert_eq!(
4205 roundtripped,
4206 expected_json_after_roundtrip,
4207 "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
4208 serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
4209 serde_json::to_string_pretty(&roundtripped).unwrap()
4210 );
4211 let roundtripped = serde_json::to_value(circular_roundtrip_template(est)).unwrap();
4212 assert_eq!(
4213 roundtripped,
4214 expected_json_after_roundtrip,
4215 "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
4216 serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
4217 serde_json::to_string_pretty(&roundtripped).unwrap()
4218 );
4219 }
4220
4221 #[test]
4222 fn invalid() {
4223 let bad = json!(
4224 {
4225 "effect": "permit",
4226 "principal": {
4227 "op": "is"
4228 },
4229 "action": {
4230 "op": "All"
4231 },
4232 "resource": {
4233 "op": "All"
4234 },
4235 "conditions": []
4236 }
4237 );
4238 assert_panics!(
4239 serde_json::from_value::<Policy>(bad).unwrap(),
4240 includes("missing field `entity_type`"),
4241 );
4242
4243 let bad = json!(
4244 {
4245 "effect": "permit",
4246 "principal": {
4247 "op": "is",
4248 "entity_type": "!"
4249 },
4250 "action": {
4251 "op": "All"
4252 },
4253 "resource": {
4254 "op": "All"
4255 },
4256 "conditions": []
4257 }
4258 );
4259 assert_matches!(
4260 serde_json::from_value::<Policy>(bad)
4261 .unwrap()
4262 .try_into_ast_policy(None),
4263 Err(e) => {
4264 expect_err(
4265 "!",
4266 &miette::Report::new(e),
4267 &ExpectedErrorMessageBuilder::error(r#"invalid entity type: unexpected token `!`"#)
4268 .exactly_one_underline_with_label("!", "expected identifier")
4269 .build()
4270 );
4271 }
4272 );
4273
4274 let bad = json!(
4275 {
4276 "effect": "permit",
4277 "principal": {
4278 "op": "is",
4279 "entity_type": "User",
4280 "==": {"entity": { "type": "User", "id": "alice"}}
4281 },
4282 "action": {
4283 "op": "All"
4284 },
4285 "resource": {
4286 "op": "All"
4287 },
4288 "conditions": []
4289 }
4290 );
4291 assert_panics!(
4292 serde_json::from_value::<Policy>(bad).unwrap(),
4293 includes("unknown field `==`, expected `entity_type` or `in`"),
4294 );
4295
4296 let bad = json!(
4297 {
4298 "effect": "permit",
4299 "principal": {
4300 "op": "All",
4301 },
4302 "action": {
4303 "op": "is",
4304 "entity_type": "Action"
4305 },
4306 "resource": {
4307 "op": "All"
4308 },
4309 "conditions": []
4310 }
4311 );
4312 assert_panics!(
4313 serde_json::from_value::<Policy>(bad).unwrap(),
4314 includes("unknown variant `is`, expected one of `All`, `all`, `==`, `in`"),
4315 );
4316 }
4317
4318 #[test]
4319 fn link() {
4320 let template = r#"
4321 permit(
4322 principal is User in ?principal,
4323 action,
4324 resource is Doc in ?resource
4325 );
4326 "#;
4327 let cst = parser::text_to_cst::parse_policy(template)
4328 .unwrap()
4329 .node
4330 .unwrap();
4331 let est: Policy = cst.try_into().unwrap();
4332 let err = est.clone().link(&HashMap::from_iter([]));
4333 assert_matches!(
4334 err,
4335 Err(e) => {
4336 expect_err(
4337 "",
4338 &miette::Report::new(e),
4339 &ExpectedErrorMessageBuilder::error("failed to link template: no value provided for `?principal`")
4340 .build()
4341 );
4342 }
4343 );
4344 let err = est.clone().link(&HashMap::from_iter([(
4345 ast::SlotId::principal(),
4346 EntityUidJson::new("User", "alice"),
4347 )]));
4348 assert_matches!(
4349 err,
4350 Err(e) => {
4351 expect_err(
4352 "",
4353 &miette::Report::new(e),
4354 &ExpectedErrorMessageBuilder::error("failed to link template: no value provided for `?resource`")
4355 .build()
4356 );
4357 }
4358 );
4359 let linked = est
4360 .link(&HashMap::from_iter([
4361 (
4362 ast::SlotId::principal(),
4363 EntityUidJson::new("User", "alice"),
4364 ),
4365 (ast::SlotId::resource(), EntityUidJson::new("Folder", "abc")),
4366 ]))
4367 .expect("did fill all the slots");
4368 let expected_json = json!(
4369 {
4370 "effect": "permit",
4371 "principal": {
4372 "op": "is",
4373 "entity_type": "User",
4374 "in": { "entity": { "type": "User", "id": "alice" } }
4375 },
4376 "action": {
4377 "op": "All"
4378 },
4379 "resource": {
4380 "op": "is",
4381 "entity_type": "Doc",
4382 "in": { "entity": { "type": "Folder", "id": "abc" } }
4383 },
4384 "conditions": [ ],
4385 }
4386 );
4387 let linked_json = serde_json::to_value(linked).unwrap();
4388 assert_eq!(
4389 linked_json,
4390 expected_json,
4391 "\nExpected:\n{}\n\nActual:\n{}\n\n",
4392 serde_json::to_string_pretty(&expected_json).unwrap(),
4393 serde_json::to_string_pretty(&linked_json).unwrap(),
4394 );
4395 }
4396
4397 #[test]
4398 fn link_no_slot() {
4399 let template = r#"permit(principal is User, action, resource is Doc);"#;
4400 let cst = parser::text_to_cst::parse_policy(template)
4401 .unwrap()
4402 .node
4403 .unwrap();
4404 let est: Policy = cst.try_into().unwrap();
4405 let linked = est.link(&HashMap::new()).unwrap();
4406 let expected_json = json!(
4407 {
4408 "effect": "permit",
4409 "principal": {
4410 "op": "is",
4411 "entity_type": "User",
4412 },
4413 "action": {
4414 "op": "All"
4415 },
4416 "resource": {
4417 "op": "is",
4418 "entity_type": "Doc",
4419 },
4420 "conditions": [ ],
4421 }
4422 );
4423 let linked_json = serde_json::to_value(linked).unwrap();
4424 assert_eq!(
4425 linked_json,
4426 expected_json,
4427 "\nExpected:\n{}\n\nActual:\n{}\n\n",
4428 serde_json::to_string_pretty(&expected_json).unwrap(),
4429 serde_json::to_string_pretty(&linked_json).unwrap(),
4430 );
4431 }
4432 }
4433
4434 mod reserved_names {
4435 use cool_asserts::assert_matches;
4436
4437 use crate::{entities::json::err::JsonDeserializationError, est::FromJsonError};
4438
4439 use super::Policy;
4440 #[test]
4441 fn entity_type() {
4442 let policy: Policy = serde_json::from_value(serde_json::json!(
4443 {
4444 "effect": "permit",
4445 "principal": {
4446 "op": "is",
4447 "entity_type": "__cedar",
4448 },
4449 "action": {
4450 "op": "All"
4451 },
4452 "resource": {
4453 "op": "All",
4454 },
4455 "conditions": [ ],
4456 }
4457 ))
4458 .unwrap();
4459 assert_matches!(
4460 policy.try_into_ast_policy(None),
4461 Err(FromJsonError::InvalidEntityType(_))
4462 );
4463
4464 let policy: Policy = serde_json::from_value(serde_json::json!(
4465 {
4466 "effect": "permit",
4467 "principal": {
4468 "op": "All",
4469 },
4470 "action": {
4471 "op": "All"
4472 },
4473 "resource": {
4474 "op": "All",
4475 },
4476 "conditions": [ {
4477 "kind": "when",
4478 "body": {
4479 "is": {
4480 "left": { "Var": "principal" },
4481 "entity_type": "__cedar",
4482 }
4483 }
4484 } ],
4485 }
4486 ))
4487 .unwrap();
4488 assert_matches!(
4489 policy.try_into_ast_policy(None),
4490 Err(FromJsonError::InvalidEntityType(_))
4491 );
4492 }
4493 #[test]
4494 fn entities() {
4495 let policy: Policy = serde_json::from_value(serde_json::json!(
4496 {
4497 "effect": "permit",
4498 "principal": {
4499 "op": "All"
4500 },
4501 "action": {
4502 "op": "All"
4503 },
4504 "resource": {
4505 "op": "All",
4506 },
4507 "conditions": [
4508 {
4509 "kind": "when",
4510 "body": {
4511 "==": {
4512 "left": {
4513 "Var": "principal"
4514 },
4515 "right": {
4516 "Value": {
4517 "__entity": { "type": "__cedar", "id": "" }
4518 }
4519 }
4520 }
4521 }
4522 }
4523 ],
4524 }
4525 ))
4526 .unwrap();
4527 assert_matches!(
4528 policy.try_into_ast_policy(None),
4529 Err(FromJsonError::JsonDeserializationError(
4530 JsonDeserializationError::ParseEscape(_)
4531 ))
4532 );
4533 let policy: Policy = serde_json::from_value(serde_json::json!(
4534 {
4535 "effect": "permit",
4536 "principal": {
4537 "op": "==",
4538 "entity": { "type": "__cedar", "id": "12UA45" }
4539 },
4540 "action": {
4541 "op": "All"
4542 },
4543 "resource": {
4544 "op": "All",
4545 },
4546 "conditions": [
4547 ],
4548 }
4549 ))
4550 .unwrap();
4551 assert_matches!(
4552 policy.try_into_ast_policy(None),
4553 Err(FromJsonError::JsonDeserializationError(
4554 JsonDeserializationError::ParseEscape(_)
4555 ))
4556 );
4557
4558 let policy: Policy = serde_json::from_value(serde_json::json!(
4559 {
4560 "effect": "permit",
4561 "principal": {
4562 "op": "All"
4563 },
4564 "action": {
4565 "op": "All"
4566 },
4567 "resource": {
4568 "op": "==",
4569 "entity": { "type": "__cedar", "id": "12UA45" }
4570 },
4571 "conditions": [
4572 ],
4573 }
4574 ))
4575 .unwrap();
4576 assert_matches!(
4577 policy.try_into_ast_policy(None),
4578 Err(FromJsonError::JsonDeserializationError(
4579 JsonDeserializationError::ParseEscape(_)
4580 ))
4581 );
4582
4583 let policy: Policy = serde_json::from_value(serde_json::json!(
4584 {
4585 "effect": "permit",
4586 "principal": {
4587 "op": "All"
4588 },
4589 "action": {
4590 "op": "==",
4591 "entity": { "type": "__cedar::Action", "id": "12UA45" }
4592 },
4593 "resource": {
4594 "op": "All"
4595 },
4596 "conditions": [
4597 ],
4598 }
4599 ))
4600 .unwrap();
4601 assert_matches!(
4602 policy.try_into_ast_policy(None),
4603 Err(FromJsonError::JsonDeserializationError(
4604 JsonDeserializationError::ParseEscape(_)
4605 ))
4606 );
4607 }
4608 }
4609
4610 #[test]
4611 fn extended_has() {
4612 let policy_text = r#"
4613 permit(principal, action, resource) when
4614 { principal has a.b.c };"#;
4615 let cst = parser::text_to_cst::parse_policy(policy_text).unwrap();
4616 let est: Policy = cst.node.unwrap().try_into().unwrap();
4617 assert_eq!(
4618 est,
4619 serde_json::from_value(json!({
4620 "effect": "permit",
4621 "principal": { "op": "All" },
4622 "action": { "op": "All" },
4623 "resource": { "op": "All" },
4624 "conditions": [
4625 {
4626 "kind": "when",
4627 "body": {
4628 "&&": {
4629 "left": {
4630 "&&": {
4631 "left": {
4632 "has": {
4633 "left": {
4634 "Var": "principal",
4635 },
4636 "attr": "a"
4637 }
4638 },
4639 "right": {
4640 "has": {
4641 "left": {
4642 ".": {
4643 "left": {
4644 "Var": "principal",
4645 },
4646 "attr": "a",
4647 },
4648 },
4649 "attr": "b"
4650 }
4651 },
4652 }
4653 },
4654 "right": {
4655 "has": {
4656 "left": {
4657 ".": {
4658 "left": {
4659 ".": {
4660 "left": {
4661 "Var": "principal",
4662 },
4663 "attr": "a"
4664 }
4665 },
4666 "attr": "b",
4667 }
4668 },
4669 "attr": "c",
4670 }
4671 },
4672 },
4673 },
4674 }
4675 ]
4676 }))
4677 .unwrap()
4678 );
4679 }
4680
4681 #[test]
4682 fn is_template_principal_slot() {
4683 let template = r#"
4684 permit(
4685 principal == ?principal,
4686 action == Action::"view",
4687 resource
4688 );
4689 "#;
4690 let cst = parser::text_to_cst::parse_policy(template)
4691 .unwrap()
4692 .node
4693 .unwrap();
4694 let est: Policy = cst.try_into().unwrap();
4695 assert!(
4696 est.is_template(),
4697 "Policy with principal slot not marked as template"
4698 );
4699 }
4700
4701 #[test]
4702 fn is_template_resource_slot() {
4703 let template = r#"
4704 permit(
4705 principal,
4706 action == Action::"view",
4707 resource in ?resource
4708 );
4709 "#;
4710 let cst = parser::text_to_cst::parse_policy(template)
4711 .unwrap()
4712 .node
4713 .unwrap();
4714 let est: Policy = cst.try_into().unwrap();
4715 assert!(
4716 est.is_template(),
4717 "Policy with resource slot not marked as template"
4718 );
4719 }
4720
4721 #[test]
4722 fn is_template_static_policy() {
4723 let template = r#"
4724 permit(
4725 principal,
4726 action == Action::"view",
4727 resource
4728 );
4729 "#;
4730 let cst = parser::text_to_cst::parse_policy(template)
4731 .unwrap()
4732 .node
4733 .unwrap();
4734 let est: Policy = cst.try_into().unwrap();
4735 assert!(!est.is_template(), "Static policy marked as template");
4736 }
4737
4738 #[test]
4739 fn is_template_static_policy_with_condition() {
4740 let template = r#"
4741 permit(
4742 principal,
4743 action == Action::"view",
4744 resource
4745 ) when {
4746 principal in resource.owners
4747 };
4748 "#;
4749 let cst = parser::text_to_cst::parse_policy(template)
4750 .unwrap()
4751 .node
4752 .unwrap();
4753 let est: Policy = cst.try_into().unwrap();
4754 assert!(!est.is_template(), "Static policy marked as template");
4755 }
4756
4757 #[test]
4758 fn conditions_right_associative() {
4759 let json = json!(
4760 {
4761 "effect": "permit",
4762 "principal": {
4763 "op": "All",
4764 },
4765 "action": {
4766 "op": "All",
4767 },
4768 "resource": {
4769 "op": "All",
4770 },
4771 "conditions": [
4772 {
4773 "kind": "when",
4774 "body": {
4775 "==": {
4776 "left": { "Value": 1 },
4777 "right": { "Value": 2 },
4778 }
4779 }
4780 },
4781 {
4782 "kind": "when",
4783 "body": {
4784 "==": {
4785 "left": { "Value": 3 },
4786 "right": { "Value": 4 },
4787 }
4788 }
4789 },
4790 {
4791 "kind": "when",
4792 "body": {
4793 "==": {
4794 "left": { "Value": 5 },
4795 "right": { "Value": 6 },
4796 }
4797 }
4798 }
4799 ],
4800 }
4801 );
4802 let est: Policy =
4803 serde_json::from_value(json).expect("Expected valid JSON to parse to EST");
4804 let ast = est
4805 .try_into_ast_policy_or_template(Some(ast::PolicyID::from_string("id")))
4806 .expect("Expected EST -> AST conversion to succeed");
4807 assert_eq!(
4808 ToString::to_string(&ast.non_scope_constraints().unwrap()),
4809 "(1 == 2) && ((3 == 4) && (5 == 6))"
4810 );
4811 }
4812}
4813
4814#[cfg(test)]
4815mod issue_891 {
4816 use crate::est;
4817 use cool_asserts::assert_matches;
4818 use serde_json::json;
4819
4820 fn est_json_with_body(body: &serde_json::Value) -> serde_json::Value {
4821 json!(
4822 {
4823 "effect": "permit",
4824 "principal": { "op": "All" },
4825 "action": { "op": "All" },
4826 "resource": { "op": "All" },
4827 "conditions": [
4828 {
4829 "kind": "when",
4830 "body": body,
4831 }
4832 ]
4833 }
4834 )
4835 }
4836
4837 #[test]
4838 fn invalid_extension_func() {
4839 let src = est_json_with_body(&json!( { "ow4": [ { "Var": "principal" } ] }));
4840 assert_matches!(serde_json::from_value::<est::Policy>(src), Err(e) => {
4841 assert!(e.to_string().starts_with("unknown variant `ow4`, expected one of `Value`, `Var`, "), "e was: {e}");
4842 });
4843
4844 let src = est_json_with_body(&json!(
4845 {
4846 "==": {
4847 "left": {"Var": "principal"},
4848 "right": {
4849 "ownerOrEqual": [
4850 {"Var": "resource"},
4851 {"decimal": [{ "Value": "0.75" }]}
4852 ]
4853 }
4854 }
4855 }
4856 ));
4857 assert_matches!(serde_json::from_value::<est::Policy>(src), Err(e) => {
4858 assert!(e.to_string().starts_with("unknown variant `ownerOrEqual`, expected one of `Value`, `Var`, "), "e was: {e}");
4859 });
4860
4861 let src = est_json_with_body(&json!(
4862 {
4863 "==": {
4864 "left": {"Var": "principal"},
4865 "right": {
4866 "resorThanOrEqual": [
4867 {"decimal": [{ "Value": "0.75" }]}
4868 ]
4869 }
4870 }
4871 }
4872 ));
4873 assert_matches!(serde_json::from_value::<est::Policy>(src), Err(e) => {
4874 assert!(e.to_string().starts_with("unknown variant `resorThanOrEqual`, expected one of `Value`, `Var`, "), "e was: {e}");
4875 });
4876 }
4877}
4878
4879#[cfg(test)]
4880mod issue_925 {
4881 use crate::{
4882 est,
4883 test_utils::{expect_err, ExpectedErrorMessageBuilder},
4884 };
4885 use cool_asserts::assert_matches;
4886 use serde_json::json;
4887
4888 #[test]
4889 fn invalid_action_type() {
4890 let src = json!(
4891 {
4892 "effect": "permit",
4893 "principal": {
4894 "op": "All"
4895 },
4896 "action": {
4897 "op": "==",
4898 "entity": {
4899 "type": "NotAction",
4900 "id": "view",
4901 }
4902 },
4903 "resource": {
4904 "op": "All"
4905 },
4906 "conditions": []
4907 }
4908 );
4909 let est: est::Policy = serde_json::from_value(src.clone()).unwrap();
4910 assert_matches!(
4911 est.try_into_ast_policy(None),
4912 Err(e) => {
4913 expect_err(
4914 &src,
4915 &miette::Report::new(e),
4916 &ExpectedErrorMessageBuilder::error(r#"expected an entity uid with type `Action` but got `NotAction::"view"`"#)
4917 .help("action entities must have type `Action`, optionally in a namespace")
4918 .build()
4919 );
4920 }
4921 );
4922
4923 let src = json!(
4924 {
4925 "effect": "permit",
4926 "principal": {
4927 "op": "All"
4928 },
4929 "action": {
4930 "op": "in",
4931 "entity": {
4932 "type": "NotAction",
4933 "id": "view",
4934 }
4935 },
4936 "resource": {
4937 "op": "All"
4938 },
4939 "conditions": []
4940 }
4941 );
4942 let est: est::Policy = serde_json::from_value(src.clone()).unwrap();
4943 assert_matches!(
4944 est.try_into_ast_policy(None),
4945 Err(e) => {
4946 expect_err(
4947 &src,
4948 &miette::Report::new(e),
4949 &ExpectedErrorMessageBuilder::error(r#"expected an entity uid with type `Action` but got `NotAction::"view"`"#)
4950 .help("action entities must have type `Action`, optionally in a namespace")
4951 .build()
4952 );
4953 }
4954 );
4955
4956 let src = json!(
4957 {
4958 "effect": "permit",
4959 "principal": {
4960 "op": "All"
4961 },
4962 "action": {
4963 "op": "in",
4964 "entities": [
4965 {
4966 "type": "NotAction",
4967 "id": "view",
4968 },
4969 {
4970 "type": "Other",
4971 "id": "edit",
4972 }
4973 ]
4974 },
4975 "resource": {
4976 "op": "All"
4977 },
4978 "conditions": []
4979 }
4980 );
4981 let est: est::Policy = serde_json::from_value(src.clone()).unwrap();
4982 assert_matches!(
4983 est.try_into_ast_policy(None),
4984 Err(e) => {
4985 expect_err(
4986 &src,
4987 &miette::Report::new(e),
4988 &ExpectedErrorMessageBuilder::error(r#"expected entity uids with type `Action` but got `NotAction::"view"` and `Other::"edit"`"#)
4989 .help("action entities must have type `Action`, optionally in a namespace")
4990 .build()
4991 );
4992 }
4993 );
4994 }
4995}
4996
4997#[cfg(test)]
4998mod issue_994 {
4999 use crate::{
5000 entities::json::err::JsonDeserializationError,
5001 est,
5002 test_utils::{expect_err, ExpectedErrorMessageBuilder},
5003 };
5004 use cool_asserts::assert_matches;
5005 use serde_json::json;
5006
5007 #[test]
5008 fn empty_annotation() {
5009 let src = json!(
5010 {
5011 "annotations": {"": ""},
5012 "effect": "permit",
5013 "principal": { "op": "All" },
5014 "action": { "op": "All" },
5015 "resource": { "op": "All" },
5016 "conditions": []
5017 }
5018 );
5019 assert_matches!(
5020 serde_json::from_value::<est::Policy>(src.clone())
5021 .map_err(|e| JsonDeserializationError::Serde(e.into())),
5022 Err(e) => {
5023 expect_err(
5024 &src,
5025 &miette::Report::new(e),
5026 &ExpectedErrorMessageBuilder::error(r#"invalid id ``: unexpected end of input"#)
5027 .build()
5028 );
5029 }
5030 );
5031 }
5032
5033 #[test]
5034 fn annotation_with_space() {
5035 let src = json!(
5036 {
5037 "annotations": {"has a space": ""},
5038 "effect": "permit",
5039 "principal": { "op": "All" },
5040 "action": { "op": "All" },
5041 "resource": { "op": "All" },
5042 "conditions": []
5043 }
5044 );
5045 assert_matches!(
5046 serde_json::from_value::<est::Policy>(src.clone())
5047 .map_err(|e| JsonDeserializationError::Serde(e.into())),
5048 Err(e) => {
5049 expect_err(
5050 &src,
5051 &miette::Report::new(e),
5052 &ExpectedErrorMessageBuilder::error(r#"invalid id `has a space`: unexpected token `a`"#)
5053 .build()
5054 );
5055 }
5056 );
5057 }
5058
5059 #[test]
5060 fn special_char() {
5061 let src = json!(
5062 {
5063 "annotations": {"@": ""},
5064 "effect": "permit",
5065 "principal": { "op": "All" },
5066 "action": { "op": "All" },
5067 "resource": { "op": "All" },
5068 "conditions": []
5069 }
5070 );
5071 assert_matches!(
5072 serde_json::from_value::<est::Policy>(src.clone())
5073 .map_err(|e| JsonDeserializationError::Serde(e.into())),
5074 Err(e) => {
5075 expect_err(
5076 &src,
5077 &miette::Report::new(e),
5078 &ExpectedErrorMessageBuilder::error(r#"invalid id `@`: unexpected token `@`"#)
5079 .build()
5080 );
5081 }
5082 );
5083 }
5084}
5085
5086#[cfg(feature = "partial-eval")]
5087#[cfg(test)]
5088mod issue_1061 {
5089 use crate::{est, parser};
5090 use serde_json::json;
5091
5092 #[test]
5093 fn function_with_name_unknown() {
5094 let src = json!(
5095 {
5096 "effect": "permit",
5097 "principal": {
5098 "op": "All"
5099 },
5100 "action": {
5101 "op": "All"
5102 },
5103 "resource": {
5104 "op": "All"
5105 },
5106 "conditions": [
5107 {
5108 "kind": "when",
5109 "body": {
5110 "unknown": [
5111 {"Value": ""}
5112 ]
5113 }
5114 }
5115 ]
5116 }
5117 );
5118 let est =
5119 serde_json::from_value::<est::Policy>(src).expect("Failed to deserialize policy JSON");
5120 let ast_from_est = est
5121 .try_into_ast_policy(None)
5122 .expect("Failed to convert EST to AST");
5123 let ast_from_cedar = parser::parse_policy_or_template(None, &ast_from_est.to_string())
5124 .expect("Failed to parse policy template");
5125
5126 assert!(ast_from_est
5127 .non_scope_constraints()
5128 .unwrap()
5129 .eq_shape(ast_from_cedar.non_scope_constraints().unwrap()));
5130 }
5131}