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