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