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