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