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