1mod err;
20pub use err::*;
21mod expr;
22pub use expr::*;
23mod head_constraints;
24pub use head_constraints::*;
25mod utils;
26
27use crate::ast;
28use crate::entities::EntityUidJSON;
29use crate::parser::cst;
30use crate::parser::err::{ParseError, ParseErrors};
31use crate::parser::ASTNode;
32use serde::{Deserialize, Serialize};
33use smol_str::SmolStr;
34use std::collections::HashMap;
35
36#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
38#[serde(deny_unknown_fields)]
39pub struct Policy {
40 effect: ast::Effect,
42 principal: PrincipalConstraint,
44 action: ActionConstraint,
46 resource: ResourceConstraint,
48 conditions: Vec<Clause>,
50 #[serde(default)]
52 #[serde(skip_serializing_if = "HashMap::is_empty")]
53 annotations: HashMap<ast::Id, SmolStr>,
54}
55
56#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
58#[serde(deny_unknown_fields)]
59#[serde(tag = "kind", content = "body")]
60pub enum Clause {
61 #[serde(rename = "when")]
63 When(Expr),
64 #[serde(rename = "unless")]
66 Unless(Expr),
67}
68
69impl Policy {
70 pub fn link(
75 self,
76 vals: &HashMap<ast::SlotId, EntityUidJSON>,
77 ) -> Result<Self, InstantiationError> {
78 Ok(Policy {
79 effect: self.effect,
80 principal: self.principal.instantiate(vals)?,
81 action: self.action.instantiate(vals)?,
82 resource: self.resource.instantiate(vals)?,
83 conditions: self
84 .conditions
85 .into_iter()
86 .map(|clause| clause.instantiate(vals))
87 .collect::<Result<Vec<_>, _>>()?,
88 annotations: self.annotations,
89 })
90 }
91}
92
93impl Clause {
94 pub fn instantiate(
98 self,
99 _vals: &HashMap<ast::SlotId, EntityUidJSON>,
100 ) -> Result<Self, InstantiationError> {
101 Ok(self)
103 }
104}
105
106impl TryFrom<cst::Policy> for Policy {
107 type Error = ParseErrors;
108 fn try_from(policy: cst::Policy) -> Result<Policy, ParseErrors> {
109 let mut errs = vec![];
110 let effect = policy.effect.to_effect(&mut errs);
111 let (principal, action, resource) = policy.extract_head(&mut errs);
112 if errs.is_empty() {
113 let effect = effect.expect("should be Some if errs.is_empty()");
114 let principal = principal.expect("should be Some if errs.is_empty()");
115 let action = action.expect("should be Some if errs.is_empty()");
116 let resource = resource.expect("should be Some if errs.is_empty()");
117 let conditions = policy
118 .conds
119 .into_iter()
120 .map(|node| {
121 let (cond, _) = node.into_inner();
122 let cond =
123 cond.ok_or_else(|| ParseError::ToAST("policy cond was empty".to_string()))?;
124 cond.try_into()
125 })
126 .collect::<Result<Vec<_>, ParseErrors>>()?;
127 let annotations = policy.annotations.into_iter().map(|node| {
128 let mut errs = vec![];
129 let kv = node.to_kv_pair(&mut errs);
130 match (errs.is_empty(), kv) {
131 (true, Some((k, v))) => Ok((k, v)),
132 (false, _) => Err(ParseErrors(errs)),
133 (true, None) => Err(ParseError::ToAST("internal invariant violation: expected there to be an error if data is None here".to_string()).into()),
134 }
135 }).collect::<Result<_, ParseErrors>>()?;
136 Ok(Policy {
137 effect,
138 principal: principal.into(),
139 action: action.into(),
140 resource: resource.into(),
141 conditions,
142 annotations,
143 })
144 } else {
145 Err(ParseErrors(errs))
146 }
147 }
148}
149
150impl TryFrom<cst::Cond> for Clause {
151 type Error = ParseErrors;
152 fn try_from(cond: cst::Cond) -> Result<Clause, ParseErrors> {
153 let mut errs = vec![];
154 let expr: Result<Expr, ParseErrors> = match cond.expr {
155 None => Err(ParseError::ToAST("clause should not be empty".to_string()).into()),
156 Some(ASTNode { node, .. }) => match node {
157 Some(e) => e.try_into(),
158 None => Err(ParseError::ToAST("data should not be empty".to_string()).into()),
159 },
160 };
161 let expr = match expr {
162 Ok(expr) => Some(expr),
163 Err(expr_errs) => {
164 errs.extend(expr_errs.0.into_iter());
165 None
166 }
167 };
168
169 let is_when = cond.cond.to_cond_is_when(&mut errs);
170
171 if errs.is_empty() {
172 let expr = expr.expect("should be Some since errs.is_empty()");
173 Ok(match is_when {
174 None => unreachable!("should have had an err in this case"),
175 Some(true) => Clause::When(expr),
176 Some(false) => Clause::Unless(expr),
177 })
178 } else {
179 Err(ParseErrors(errs))
180 }
181 }
182}
183
184impl Policy {
185 pub fn try_into_ast_policy(
190 self,
191 id: Option<ast::PolicyID>,
192 ) -> Result<ast::Policy, EstToAstError> {
193 let template: ast::Template = self.try_into_ast_template(id)?;
194 ast::StaticPolicy::try_from(template)
195 .map(Into::into)
196 .map_err(Into::into)
197 }
198
199 pub fn try_into_ast_template(
204 self,
205 id: Option<ast::PolicyID>,
206 ) -> Result<ast::Template, EstToAstError> {
207 let conditions = match self.conditions.len() {
208 0 => ast::Expr::val(true),
209 _ => {
210 let mut conditions = self.conditions.into_iter().map(ast::Expr::try_from);
211 let first = conditions
212 .next()
213 .expect("already checked there is at least 1")?;
214 ast::ExprBuilder::with_data(())
215 .and_nary(first, conditions.collect::<Result<Vec<_>, _>>()?)
216 }
217 };
218 Ok(ast::Template::new(
219 id.unwrap_or(ast::PolicyID::from_string("JSON policy")),
220 self.annotations.into_iter().collect(),
221 self.effect,
222 self.principal.try_into()?,
223 self.action.try_into()?,
224 self.resource.try_into()?,
225 conditions,
226 ))
227 }
228}
229
230impl TryFrom<Clause> for ast::Expr {
231 type Error = EstToAstError;
232 fn try_from(clause: Clause) -> Result<ast::Expr, EstToAstError> {
233 match clause {
234 Clause::When(expr) => expr.try_into(),
235 Clause::Unless(expr) => Ok(ast::Expr::not(expr.try_into()?)),
236 }
237 }
238}
239
240impl From<ast::Policy> for Policy {
242 fn from(ast: ast::Policy) -> Policy {
243 Policy {
244 effect: ast.effect(),
245 principal: ast.principal_constraint().into(),
246 action: ast.action_constraint().clone().into(),
247 resource: ast.resource_constraint().into(),
248 conditions: vec![ast.non_head_constraints().clone().into()],
249 annotations: ast
250 .annotations()
251 .map(|(k, v)| (k.clone(), v.clone()))
252 .collect(),
253 }
254 }
255}
256
257impl From<ast::Template> for Policy {
259 fn from(ast: ast::Template) -> Policy {
260 Policy {
261 effect: ast.effect(),
262 principal: ast.principal_constraint().clone().into(),
263 action: ast.action_constraint().clone().into(),
264 resource: ast.resource_constraint().clone().into(),
265 conditions: vec![ast.non_head_constraints().clone().into()],
266 annotations: ast
267 .annotations()
268 .map(|(k, v)| (k.clone(), v.clone()))
269 .collect(),
270 }
271 }
272}
273
274impl From<ast::Expr> for Clause {
275 fn from(expr: ast::Expr) -> Clause {
276 Clause::When(expr.into())
277 }
278}
279
280#[cfg(test)]
281mod test {
282 use super::*;
283 use crate::parser;
284 use cool_asserts::assert_matches;
285 use serde_json::json;
286
287 fn est_roundtrip(est: Policy) -> Policy {
289 let json = serde_json::to_value(est).expect("failed to serialize to JSON");
290 serde_json::from_value(json.clone()).unwrap_or_else(|e| {
291 panic!(
292 "failed to deserialize from JSON: {e}\n\nJSON was:\n{}",
293 serde_json::to_string_pretty(&json).expect("failed to convert JSON to string")
294 )
295 })
296 }
297
298 fn ast_roundtrip(est: Policy) -> Policy {
300 let ast = est
301 .try_into_ast_policy(None)
302 .expect("Failed to convert to AST");
303 ast.try_into().expect("Failed to convert to EST")
304 }
305
306 fn ast_roundtrip_template(est: Policy) -> Policy {
308 let ast = est
309 .try_into_ast_template(None)
310 .expect("Failed to convert to AST");
311 ast.try_into().expect("Failed to convert to EST")
312 }
313
314 fn circular_roundtrip(est: Policy) -> Policy {
316 let ast = est
317 .try_into_ast_policy(None)
318 .expect("Failed to convert to AST");
319 let text = ast.to_string();
320 let cst = parser::text_to_cst::parse_policy(&text)
321 .expect("Failed to convert to CST")
322 .node
323 .expect("Node should not be empty");
324 cst.try_into().expect("Failed to convert to EST")
325 }
326
327 fn circular_roundtrip_template(est: Policy) -> Policy {
329 let ast = est
330 .try_into_ast_template(None)
331 .expect("Failed to convert to AST");
332 let text = ast.to_string();
333 let cst = parser::text_to_cst::parse_policy(&text)
334 .expect("Failed to convert to CST")
335 .node
336 .expect("Node should not be empty");
337 cst.try_into().expect("Failed to convert to EST")
338 }
339
340 #[test]
341 fn empty_policy() {
342 let policy = "permit(principal, action, resource);";
343 let cst = parser::text_to_cst::parse_policy(policy)
344 .unwrap()
345 .node
346 .unwrap();
347 let est: Policy = cst.try_into().unwrap();
348 let expected_json = json!(
349 {
350 "effect": "permit",
351 "principal": {
352 "op": "All",
353 },
354 "action": {
355 "op": "All",
356 },
357 "resource": {
358 "op": "All",
359 },
360 "conditions": [],
361 }
362 );
363 assert_eq!(
364 serde_json::to_value(&est).unwrap(),
365 expected_json,
366 "\nExpected:\n{}\n\nActual:\n{}\n\n",
367 serde_json::to_string_pretty(&expected_json).unwrap(),
368 serde_json::to_string_pretty(&est).unwrap()
369 );
370 let old_est = est.clone();
371 let est = est_roundtrip(est);
372 assert_eq!(&old_est, &est);
373
374 let expected_json_after_roundtrip = json!(
377 {
378 "effect": "permit",
379 "principal": {
380 "op": "All",
381 },
382 "action": {
383 "op": "All",
384 },
385 "resource": {
386 "op": "All",
387 },
388 "conditions": [
389 {
390 "kind": "when",
391 "body": {
392 "Value": true
393 }
394 }
395 ],
396 }
397 );
398 let roundtripped = serde_json::to_value(ast_roundtrip(est.clone())).unwrap();
399 assert_eq!(
400 roundtripped,
401 expected_json_after_roundtrip,
402 "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
403 serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
404 serde_json::to_string_pretty(&roundtripped).unwrap()
405 );
406 let roundtripped = serde_json::to_value(circular_roundtrip(est)).unwrap();
407 assert_eq!(
408 roundtripped,
409 expected_json_after_roundtrip,
410 "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
411 serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
412 serde_json::to_string_pretty(&roundtripped).unwrap()
413 );
414 }
415
416 #[test]
417 fn annotated_policy() {
418 let policy = r#"
419 @foo("bar")
420 @this1is2a3valid_identifier("any arbitrary ! string \" is @ allowed in 🦀 here_")
421 permit(principal, action, resource);
422 "#;
423 let cst = parser::text_to_cst::parse_policy(policy)
424 .unwrap()
425 .node
426 .unwrap();
427 let est: Policy = cst.try_into().unwrap();
428 let expected_json = json!(
429 {
430 "effect": "permit",
431 "principal": {
432 "op": "All",
433 },
434 "action": {
435 "op": "All",
436 },
437 "resource": {
438 "op": "All",
439 },
440 "conditions": [],
441 "annotations": {
442 "foo": "bar",
443 "this1is2a3valid_identifier": "any arbitrary ! string \" is @ allowed in 🦀 here_",
444 }
445 }
446 );
447 assert_eq!(
448 serde_json::to_value(&est).unwrap(),
449 expected_json,
450 "\nExpected:\n{}\n\nActual:\n{}\n\n",
451 serde_json::to_string_pretty(&expected_json).unwrap(),
452 serde_json::to_string_pretty(&est).unwrap()
453 );
454 let old_est = est.clone();
455 let est = est_roundtrip(est);
456 assert_eq!(&old_est, &est);
457
458 let expected_json_after_roundtrip = json!(
461 {
462 "effect": "permit",
463 "principal": {
464 "op": "All",
465 },
466 "action": {
467 "op": "All",
468 },
469 "resource": {
470 "op": "All",
471 },
472 "conditions": [
473 {
474 "kind": "when",
475 "body": {
476 "Value": true
477 }
478 }
479 ],
480 "annotations": {
481 "foo": "bar",
482 "this1is2a3valid_identifier": "any arbitrary ! string \" is @ allowed in 🦀 here_",
483 }
484 }
485 );
486 let roundtripped = serde_json::to_value(ast_roundtrip(est.clone())).unwrap();
487 assert_eq!(
488 roundtripped,
489 expected_json_after_roundtrip,
490 "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
491 serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
492 serde_json::to_string_pretty(&roundtripped).unwrap()
493 );
494 let roundtripped = serde_json::to_value(circular_roundtrip(est)).unwrap();
495 assert_eq!(
496 roundtripped,
497 expected_json_after_roundtrip,
498 "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
499 serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
500 serde_json::to_string_pretty(&roundtripped).unwrap()
501 );
502 }
503
504 #[test]
505 fn rbac_policy() {
506 let policy = r#"
507 permit(
508 principal == User::"12UA45",
509 action == Action::"view",
510 resource in Folder::"abc"
511 );
512 "#;
513 let cst = parser::text_to_cst::parse_policy(policy)
514 .unwrap()
515 .node
516 .unwrap();
517 let est: Policy = cst.try_into().unwrap();
518 let expected_json = json!(
519 {
520 "effect": "permit",
521 "principal": {
522 "op": "==",
523 "entity": { "type": "User", "id": "12UA45" },
524 },
525 "action": {
526 "op": "==",
527 "entity": { "type": "Action", "id": "view" },
528 },
529 "resource": {
530 "op": "in",
531 "entity": { "type": "Folder", "id": "abc" },
532 },
533 "conditions": []
534 }
535 );
536 assert_eq!(
537 serde_json::to_value(&est).unwrap(),
538 expected_json,
539 "\nExpected:\n{}\n\nActual:\n{}\n\n",
540 serde_json::to_string_pretty(&expected_json).unwrap(),
541 serde_json::to_string_pretty(&est).unwrap()
542 );
543 let old_est = est.clone();
544 let est = est_roundtrip(est);
545 assert_eq!(&old_est, &est);
546
547 let expected_json_after_roundtrip = json!(
550 {
551 "effect": "permit",
552 "principal": {
553 "op": "==",
554 "entity": { "type": "User", "id": "12UA45" },
555 },
556 "action": {
557 "op": "==",
558 "entity": { "type": "Action", "id": "view" },
559 },
560 "resource": {
561 "op": "in",
562 "entity": { "type": "Folder", "id": "abc" },
563 },
564 "conditions": [
565 {
566 "kind": "when",
567 "body": {
568 "Value": true
569 }
570 }
571 ]
572 }
573 );
574 let roundtripped = serde_json::to_value(ast_roundtrip(est.clone())).unwrap();
575 assert_eq!(
576 roundtripped,
577 expected_json_after_roundtrip,
578 "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
579 serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
580 serde_json::to_string_pretty(&roundtripped).unwrap()
581 );
582 let roundtripped = serde_json::to_value(circular_roundtrip(est)).unwrap();
583 assert_eq!(
584 roundtripped,
585 expected_json_after_roundtrip,
586 "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
587 serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
588 serde_json::to_string_pretty(&roundtripped).unwrap()
589 );
590 }
591
592 #[test]
593 fn rbac_template() {
594 let template = r#"
595 permit(
596 principal == ?principal,
597 action == Action::"view",
598 resource in ?resource
599 );
600 "#;
601 let cst = parser::text_to_cst::parse_policy(template)
602 .unwrap()
603 .node
604 .unwrap();
605 let est: Policy = cst.try_into().unwrap();
606 let expected_json = json!(
607 {
608 "effect": "permit",
609 "principal": {
610 "op": "==",
611 "slot": "?principal",
612 },
613 "action": {
614 "op": "==",
615 "entity": { "type": "Action", "id": "view" },
616 },
617 "resource": {
618 "op": "in",
619 "slot": "?resource",
620 },
621 "conditions": []
622 }
623 );
624 assert_eq!(
625 serde_json::to_value(&est).unwrap(),
626 expected_json,
627 "\nExpected:\n{}\n\nActual:\n{}\n\n",
628 serde_json::to_string_pretty(&expected_json).unwrap(),
629 serde_json::to_string_pretty(&est).unwrap()
630 );
631 let old_est = est.clone();
632 let est = est_roundtrip(est);
633 assert_eq!(&old_est, &est);
634
635 let expected_json_after_roundtrip = json!(
638 {
639 "effect": "permit",
640 "principal": {
641 "op": "==",
642 "slot": "?principal",
643 },
644 "action": {
645 "op": "==",
646 "entity": { "type": "Action", "id": "view" },
647 },
648 "resource": {
649 "op": "in",
650 "slot": "?resource",
651 },
652 "conditions": [
653 {
654 "kind": "when",
655 "body": {
656 "Value": true
657 }
658 }
659 ]
660 }
661 );
662 let roundtripped = serde_json::to_value(ast_roundtrip_template(est.clone())).unwrap();
663 assert_eq!(
664 roundtripped,
665 expected_json_after_roundtrip,
666 "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
667 serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
668 serde_json::to_string_pretty(&roundtripped).unwrap()
669 );
670 let roundtripped = serde_json::to_value(circular_roundtrip_template(est)).unwrap();
671 assert_eq!(
672 roundtripped,
673 expected_json_after_roundtrip,
674 "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
675 serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
676 serde_json::to_string_pretty(&roundtripped).unwrap()
677 );
678 }
679
680 #[test]
681 fn abac_policy() {
682 let policy = r#"
683 permit(
684 principal == User::"12UA45",
685 action == Action::"view",
686 resource in Folder::"abc"
687 ) when {
688 context.tls_version == "1.3"
689 };
690 "#;
691 let cst = parser::text_to_cst::parse_policy(policy)
692 .unwrap()
693 .node
694 .unwrap();
695 let est: Policy = cst.try_into().unwrap();
696 let expected_json = json!(
697 {
698 "effect": "permit",
699 "principal": {
700 "op": "==",
701 "entity": { "type": "User", "id": "12UA45" },
702 },
703 "action": {
704 "op": "==",
705 "entity": { "type": "Action", "id": "view" },
706 },
707 "resource": {
708 "op": "in",
709 "entity": { "type": "Folder", "id": "abc" },
710 },
711 "conditions": [
712 {
713 "kind": "when",
714 "body": {
715 "==": {
716 "left": {
717 ".": {
718 "left": { "Var": "context" },
719 "attr": "tls_version",
720 },
721 },
722 "right": {
723 "Value": "1.3"
724 }
725 }
726 }
727 }
728 ]
729 }
730 );
731 assert_eq!(
732 serde_json::to_value(&est).unwrap(),
733 expected_json,
734 "\nExpected:\n{}\n\nActual:\n{}\n\n",
735 serde_json::to_string_pretty(&expected_json).unwrap(),
736 serde_json::to_string_pretty(&est).unwrap()
737 );
738 let old_est = est.clone();
739 let est = est_roundtrip(est);
740 assert_eq!(&old_est, &est);
741
742 assert_eq!(ast_roundtrip(est.clone()), est);
743 assert_eq!(circular_roundtrip(est.clone()), est);
744 }
745
746 #[test]
747 fn action_list() {
748 let policy = r#"
749 permit(
750 principal == User::"12UA45",
751 action in [Action::"read", Action::"write"],
752 resource
753 );
754 "#;
755 let cst = parser::text_to_cst::parse_policy(policy)
756 .unwrap()
757 .node
758 .unwrap();
759 let est: Policy = cst.try_into().unwrap();
760 let expected_json = json!(
761 {
762 "effect": "permit",
763 "principal": {
764 "op": "==",
765 "entity": { "type": "User", "id": "12UA45" },
766 },
767 "action": {
768 "op": "in",
769 "entities": [
770 { "type": "Action", "id": "read" },
771 { "type": "Action", "id": "write" },
772 ]
773 },
774 "resource": {
775 "op": "All",
776 },
777 "conditions": []
778 }
779 );
780 assert_eq!(
781 serde_json::to_value(&est).unwrap(),
782 expected_json,
783 "\nExpected:\n{}\n\nActual:\n{}\n\n",
784 serde_json::to_string_pretty(&expected_json).unwrap(),
785 serde_json::to_string_pretty(&est).unwrap()
786 );
787 let old_est = est.clone();
788 let est = est_roundtrip(est);
789 assert_eq!(&old_est, &est);
790
791 let expected_json_after_roundtrip = json!(
794 {
795 "effect": "permit",
796 "principal": {
797 "op": "==",
798 "entity": { "type": "User", "id": "12UA45" },
799 },
800 "action": {
801 "op": "in",
802 "entities": [
803 { "type": "Action", "id": "read" },
804 { "type": "Action", "id": "write" },
805 ]
806 },
807 "resource": {
808 "op": "All",
809 },
810 "conditions": [
811 {
812 "kind": "when",
813 "body": {
814 "Value": true
815 }
816 }
817 ]
818 }
819 );
820 let roundtripped = serde_json::to_value(ast_roundtrip(est.clone())).unwrap();
821 assert_eq!(
822 roundtripped,
823 expected_json_after_roundtrip,
824 "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
825 serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
826 serde_json::to_string_pretty(&roundtripped).unwrap()
827 );
828 let roundtripped = serde_json::to_value(circular_roundtrip(est)).unwrap();
829 assert_eq!(
830 roundtripped,
831 expected_json_after_roundtrip,
832 "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
833 serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
834 serde_json::to_string_pretty(&roundtripped).unwrap()
835 );
836 }
837
838 #[test]
839 fn num_literals() {
840 let policy = r#"
841 permit(principal, action, resource)
842 when { 1 == 2 };
843 "#;
844 let cst = parser::text_to_cst::parse_policy(policy)
845 .unwrap()
846 .node
847 .unwrap();
848 let est: Policy = cst.try_into().unwrap();
849 let expected_json = json!(
850 {
851 "effect": "permit",
852 "principal": {
853 "op": "All",
854 },
855 "action": {
856 "op": "All",
857 },
858 "resource": {
859 "op": "All",
860 },
861 "conditions": [
862 {
863 "kind": "when",
864 "body": {
865 "==": {
866 "left": {
867 "Value": 1
868 },
869 "right": {
870 "Value": 2
871 }
872 }
873 }
874 }
875 ]
876 }
877 );
878 assert_eq!(
879 serde_json::to_value(&est).unwrap(),
880 expected_json,
881 "\nExpected:\n{}\n\nActual:\n{}\n\n",
882 serde_json::to_string_pretty(&expected_json).unwrap(),
883 serde_json::to_string_pretty(&est).unwrap()
884 );
885 let old_est = est.clone();
886 let est = est_roundtrip(est);
887 assert_eq!(&old_est, &est);
888
889 assert_eq!(ast_roundtrip(est.clone()), est);
890 assert_eq!(circular_roundtrip(est.clone()), est);
891 }
892
893 #[test]
894 fn entity_literals() {
895 let policy = r#"
896 permit(principal, action, resource)
897 when { User::"alice" == Namespace::Type::"foo" };
898 "#;
899 let cst = parser::text_to_cst::parse_policy(policy)
900 .unwrap()
901 .node
902 .unwrap();
903 let est: Policy = cst.try_into().unwrap();
904 let expected_json = json!(
905 {
906 "effect": "permit",
907 "principal": {
908 "op": "All",
909 },
910 "action": {
911 "op": "All",
912 },
913 "resource": {
914 "op": "All",
915 },
916 "conditions": [
917 {
918 "kind": "when",
919 "body": {
920 "==": {
921 "left": {
922 "Value": {
923 "__entity": {
924 "type": "User",
925 "id": "alice"
926 }
927 }
928 },
929 "right": {
930 "Value": {
931 "__entity": {
932 "type": "Namespace::Type",
933 "id": "foo"
934 }
935 }
936 }
937 }
938 }
939 }
940 ]
941 }
942 );
943 assert_eq!(
944 serde_json::to_value(&est).unwrap(),
945 expected_json,
946 "\nExpected:\n{}\n\nActual:\n{}\n\n",
947 serde_json::to_string_pretty(&expected_json).unwrap(),
948 serde_json::to_string_pretty(&est).unwrap()
949 );
950 let old_est = est.clone();
951 let est = est_roundtrip(est);
952 assert_eq!(&old_est, &est);
953
954 assert_eq!(ast_roundtrip(est.clone()), est);
955 assert_eq!(circular_roundtrip(est.clone()), est);
956 }
957
958 #[test]
959 fn bool_literals() {
960 let policy = r#"
961 permit(principal, action, resource)
962 when { false == true };
963 "#;
964 let cst = parser::text_to_cst::parse_policy(policy)
965 .unwrap()
966 .node
967 .unwrap();
968 let est: Policy = cst.try_into().unwrap();
969 let expected_json = json!(
970 {
971 "effect": "permit",
972 "principal": {
973 "op": "All",
974 },
975 "action": {
976 "op": "All",
977 },
978 "resource": {
979 "op": "All",
980 },
981 "conditions": [
982 {
983 "kind": "when",
984 "body": {
985 "==": {
986 "left": {
987 "Value": false
988 },
989 "right": {
990 "Value": true
991 }
992 }
993 }
994 }
995 ]
996 }
997 );
998 assert_eq!(
999 serde_json::to_value(&est).unwrap(),
1000 expected_json,
1001 "\nExpected:\n{}\n\nActual:\n{}\n\n",
1002 serde_json::to_string_pretty(&expected_json).unwrap(),
1003 serde_json::to_string_pretty(&est).unwrap()
1004 );
1005 let old_est = est.clone();
1006 let est = est_roundtrip(est);
1007 assert_eq!(&old_est, &est);
1008
1009 assert_eq!(ast_roundtrip(est.clone()), est);
1010 assert_eq!(circular_roundtrip(est.clone()), est);
1011 }
1012
1013 #[test]
1014 fn string_literals() {
1015 let policy = r#"
1016 permit(principal, action, resource)
1017 when { "spam" == "eggs" };
1018 "#;
1019 let cst = parser::text_to_cst::parse_policy(policy)
1020 .unwrap()
1021 .node
1022 .unwrap();
1023 let est: Policy = cst.try_into().unwrap();
1024 let expected_json = json!(
1025 {
1026 "effect": "permit",
1027 "principal": {
1028 "op": "All",
1029 },
1030 "action": {
1031 "op": "All",
1032 },
1033 "resource": {
1034 "op": "All",
1035 },
1036 "conditions": [
1037 {
1038 "kind": "when",
1039 "body": {
1040 "==": {
1041 "left": {
1042 "Value": "spam"
1043 },
1044 "right": {
1045 "Value": "eggs"
1046 }
1047 }
1048 }
1049 }
1050 ]
1051 }
1052 );
1053 assert_eq!(
1054 serde_json::to_value(&est).unwrap(),
1055 expected_json,
1056 "\nExpected:\n{}\n\nActual:\n{}\n\n",
1057 serde_json::to_string_pretty(&expected_json).unwrap(),
1058 serde_json::to_string_pretty(&est).unwrap()
1059 );
1060 let old_est = est.clone();
1061 let est = est_roundtrip(est);
1062 assert_eq!(&old_est, &est);
1063
1064 assert_eq!(ast_roundtrip(est.clone()), est);
1065 assert_eq!(circular_roundtrip(est.clone()), est);
1066 }
1067
1068 #[test]
1069 fn set_literals() {
1070 let policy = r#"
1071 permit(principal, action, resource)
1072 when { [1, 2, "foo"] == [4, 5, "spam"] };
1073 "#;
1074 let cst = parser::text_to_cst::parse_policy(policy)
1075 .unwrap()
1076 .node
1077 .unwrap();
1078 let est: Policy = cst.try_into().unwrap();
1079 let expected_json = json!(
1080 {
1081 "effect": "permit",
1082 "principal": {
1083 "op": "All",
1084 },
1085 "action": {
1086 "op": "All",
1087 },
1088 "resource": {
1089 "op": "All",
1090 },
1091 "conditions": [
1092 {
1093 "kind": "when",
1094 "body": {
1095 "==": {
1096 "left": {
1097 "Set": [
1098 { "Value": 1 },
1099 { "Value": 2 },
1100 { "Value": "foo" },
1101 ]
1102 },
1103 "right": {
1104 "Set": [
1105 { "Value": 4 },
1106 { "Value": 5 },
1107 { "Value": "spam" },
1108 ]
1109 }
1110 }
1111 }
1112 }
1113 ]
1114 }
1115 );
1116 assert_eq!(
1117 serde_json::to_value(&est).unwrap(),
1118 expected_json,
1119 "\nExpected:\n{}\n\nActual:\n{}\n\n",
1120 serde_json::to_string_pretty(&expected_json).unwrap(),
1121 serde_json::to_string_pretty(&est).unwrap()
1122 );
1123 let old_est = est.clone();
1124 let est = est_roundtrip(est);
1125 assert_eq!(&old_est, &est);
1126
1127 assert_eq!(ast_roundtrip(est.clone()), est);
1128 assert_eq!(circular_roundtrip(est.clone()), est);
1129 }
1130
1131 #[test]
1132 fn record_literals() {
1133 let policy = r#"
1134 permit(principal, action, resource)
1135 when { {foo: "spam", bar: false} == {} };
1136 "#;
1137 let cst = parser::text_to_cst::parse_policy(policy)
1138 .unwrap()
1139 .node
1140 .unwrap();
1141 let est: Policy = cst.try_into().unwrap();
1142 let expected_json = json!(
1143 {
1144 "effect": "permit",
1145 "principal": {
1146 "op": "All",
1147 },
1148 "action": {
1149 "op": "All",
1150 },
1151 "resource": {
1152 "op": "All",
1153 },
1154 "conditions": [
1155 {
1156 "kind": "when",
1157 "body": {
1158 "==": {
1159 "left": {
1160 "Record": {
1161 "foo": { "Value": "spam" },
1162 "bar": { "Value": false },
1163 }
1164 },
1165 "right": {
1166 "Record": {}
1167 }
1168 }
1169 }
1170 }
1171 ]
1172 }
1173 );
1174 assert_eq!(
1175 serde_json::to_value(&est).unwrap(),
1176 expected_json,
1177 "\nExpected:\n{}\n\nActual:\n{}\n\n",
1178 serde_json::to_string_pretty(&expected_json).unwrap(),
1179 serde_json::to_string_pretty(&est).unwrap()
1180 );
1181 let old_est = est.clone();
1182 let est = est_roundtrip(est);
1183 assert_eq!(&old_est, &est);
1184
1185 assert_eq!(ast_roundtrip(est.clone()), est);
1186 assert_eq!(circular_roundtrip(est.clone()), est);
1187 }
1188
1189 #[test]
1190 fn policy_variables() {
1191 let policy = r#"
1192 permit(principal, action, resource)
1193 when { principal == action && resource == context };
1194 "#;
1195 let cst = parser::text_to_cst::parse_policy(policy)
1196 .unwrap()
1197 .node
1198 .unwrap();
1199 let est: Policy = cst.try_into().unwrap();
1200 let expected_json = json!(
1201 {
1202 "effect": "permit",
1203 "principal": {
1204 "op": "All",
1205 },
1206 "action": {
1207 "op": "All",
1208 },
1209 "resource": {
1210 "op": "All",
1211 },
1212 "conditions": [
1213 {
1214 "kind": "when",
1215 "body": {
1216 "&&": {
1217 "left": {
1218 "==": {
1219 "left": {
1220 "Var": "principal"
1221 },
1222 "right": {
1223 "Var": "action"
1224 }
1225 }
1226 },
1227 "right": {
1228 "==": {
1229 "left": {
1230 "Var": "resource"
1231 },
1232 "right": {
1233 "Var": "context"
1234 }
1235 }
1236 }
1237 }
1238 }
1239 }
1240 ]
1241 }
1242 );
1243 assert_eq!(
1244 serde_json::to_value(&est).unwrap(),
1245 expected_json,
1246 "\nExpected:\n{}\n\nActual:\n{}\n\n",
1247 serde_json::to_string_pretty(&expected_json).unwrap(),
1248 serde_json::to_string_pretty(&est).unwrap()
1249 );
1250 let old_est = est.clone();
1251 let est = est_roundtrip(est);
1252 assert_eq!(&old_est, &est);
1253
1254 assert_eq!(ast_roundtrip(est.clone()), est);
1255 assert_eq!(circular_roundtrip(est.clone()), est);
1256 }
1257
1258 #[test]
1259 fn not() {
1260 let policy = r#"
1261 permit(principal, action, resource)
1262 when { !context.foo && principal != context.bar };
1263 "#;
1264 let cst = parser::text_to_cst::parse_policy(policy)
1265 .unwrap()
1266 .node
1267 .unwrap();
1268 let est: Policy = cst.try_into().unwrap();
1269 let expected_json = json!(
1270 {
1271 "effect": "permit",
1272 "principal": {
1273 "op": "All",
1274 },
1275 "action": {
1276 "op": "All",
1277 },
1278 "resource": {
1279 "op": "All",
1280 },
1281 "conditions": [
1282 {
1283 "kind": "when",
1284 "body": {
1285 "&&": {
1286 "left": {
1287 "!": {
1288 "arg": {
1289 ".": {
1290 "left": {
1291 "Var": "context"
1292 },
1293 "attr": "foo"
1294 }
1295 }
1296 }
1297 },
1298 "right": {
1299 "!=": {
1300 "left": {
1301 "Var": "principal"
1302 },
1303 "right": {
1304 ".": {
1305 "left": {
1306 "Var": "context"
1307 },
1308 "attr": "bar"
1309 }
1310 }
1311 }
1312 }
1313 }
1314 }
1315 }
1316 ]
1317 }
1318 );
1319 assert_eq!(
1320 serde_json::to_value(&est).unwrap(),
1321 expected_json,
1322 "\nExpected:\n{}\n\nActual:\n{}\n\n",
1323 serde_json::to_string_pretty(&expected_json).unwrap(),
1324 serde_json::to_string_pretty(&est).unwrap()
1325 );
1326 let old_est = est.clone();
1327 let est = est_roundtrip(est);
1328 assert_eq!(&old_est, &est);
1329
1330 let expected_json_after_roundtrip = json!(
1333 {
1334 "effect": "permit",
1335 "principal": {
1336 "op": "All",
1337 },
1338 "action": {
1339 "op": "All",
1340 },
1341 "resource": {
1342 "op": "All",
1343 },
1344 "conditions": [
1345 {
1346 "kind": "when",
1347 "body": {
1348 "&&": {
1349 "left": {
1350 "!": {
1351 "arg": {
1352 ".": {
1353 "left": {
1354 "Var": "context"
1355 },
1356 "attr": "foo"
1357 }
1358 }
1359 }
1360 },
1361 "right": {
1362 "!": {
1363 "arg": {
1364 "==": {
1365 "left": {
1366 "Var": "principal"
1367 },
1368 "right": {
1369 ".": {
1370 "left": {
1371 "Var": "context"
1372 },
1373 "attr": "bar"
1374 }
1375 }
1376 }
1377 }
1378 }
1379 }
1380 }
1381 }
1382 }
1383 ]
1384 }
1385 );
1386 let roundtripped = serde_json::to_value(ast_roundtrip(est.clone())).unwrap();
1387 assert_eq!(
1388 roundtripped,
1389 expected_json_after_roundtrip,
1390 "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
1391 serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
1392 serde_json::to_string_pretty(&roundtripped).unwrap()
1393 );
1394 let roundtripped = serde_json::to_value(circular_roundtrip(est)).unwrap();
1395 assert_eq!(
1396 roundtripped,
1397 expected_json_after_roundtrip,
1398 "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
1399 serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
1400 serde_json::to_string_pretty(&roundtripped).unwrap()
1401 );
1402 }
1403
1404 #[test]
1405 fn hierarchy_in() {
1406 let policy = r#"
1407 permit(principal, action, resource)
1408 when { resource in principal.department };
1409 "#;
1410 let cst = parser::text_to_cst::parse_policy(policy)
1411 .unwrap()
1412 .node
1413 .unwrap();
1414 let est: Policy = cst.try_into().unwrap();
1415 let expected_json = json!(
1416 {
1417 "effect": "permit",
1418 "principal": {
1419 "op": "All",
1420 },
1421 "action": {
1422 "op": "All",
1423 },
1424 "resource": {
1425 "op": "All",
1426 },
1427 "conditions": [
1428 {
1429 "kind": "when",
1430 "body": {
1431 "in": {
1432 "left": {
1433 "Var": "resource"
1434 },
1435 "right": {
1436 ".": {
1437 "left": {
1438 "Var": "principal"
1439 },
1440 "attr": "department"
1441 }
1442 }
1443 }
1444 }
1445 }
1446 ]
1447 }
1448 );
1449 assert_eq!(
1450 serde_json::to_value(&est).unwrap(),
1451 expected_json,
1452 "\nExpected:\n{}\n\nActual:\n{}\n\n",
1453 serde_json::to_string_pretty(&expected_json).unwrap(),
1454 serde_json::to_string_pretty(&est).unwrap()
1455 );
1456 let old_est = est.clone();
1457 let est = est_roundtrip(est);
1458 assert_eq!(&old_est, &est);
1459
1460 assert_eq!(ast_roundtrip(est.clone()), est);
1461 assert_eq!(circular_roundtrip(est.clone()), est);
1462 }
1463
1464 #[test]
1465 fn neg_less_and_greater() {
1466 let policy = r#"
1467 permit(principal, action, resource)
1468 when { -3 < 2 && 4 > -(23 - 1) || 0 <= 0 && 7 >= 1};
1469 "#;
1470 let cst = parser::text_to_cst::parse_policy(policy)
1471 .unwrap()
1472 .node
1473 .unwrap();
1474 let est: Policy = cst.try_into().unwrap();
1475 let expected_json = json!(
1476 {
1477 "effect": "permit",
1478 "principal": {
1479 "op": "All",
1480 },
1481 "action": {
1482 "op": "All",
1483 },
1484 "resource": {
1485 "op": "All",
1486 },
1487 "conditions": [
1488 {
1489 "kind": "when",
1490 "body": {
1491 "||": {
1492 "left": {
1493 "&&": {
1494 "left": {
1495 "<": {
1496 "left": {
1497 "Value": -3
1498 },
1499 "right": {
1500 "Value": 2
1501 }
1502 }
1503 },
1504 "right": {
1505 ">": {
1506 "left": {
1507 "Value": 4
1508 },
1509 "right": {
1510 "neg": {
1511 "arg": {
1512 "-": {
1513 "left": {
1514 "Value": 23
1515 },
1516 "right": {
1517 "Value": 1
1518 }
1519 }
1520 }
1521 }
1522 }
1523 }
1524 }
1525 }
1526 },
1527 "right": {
1528 "&&": {
1529 "left": {
1530 "<=": {
1531 "left": {
1532 "Value": 0
1533 },
1534 "right": {
1535 "Value": 0
1536 }
1537 }
1538 },
1539 "right": {
1540 ">=": {
1541 "left": {
1542 "Value": 7
1543 },
1544 "right": {
1545 "Value": 1
1546 }
1547 }
1548 }
1549 }
1550 }
1551 }
1552 }
1553 }
1554 ]
1555 }
1556 );
1557 assert_eq!(
1558 serde_json::to_value(&est).unwrap(),
1559 expected_json,
1560 "\nExpected:\n{}\n\nActual:\n{}\n\n",
1561 serde_json::to_string_pretty(&expected_json).unwrap(),
1562 serde_json::to_string_pretty(&est).unwrap()
1563 );
1564 let old_est = est.clone();
1565 let est = est_roundtrip(est);
1566 assert_eq!(&old_est, &est);
1567
1568 let expected_json_after_roundtrip = json!(
1571 {
1572 "effect": "permit",
1573 "principal": {
1574 "op": "All",
1575 },
1576 "action": {
1577 "op": "All",
1578 },
1579 "resource": {
1580 "op": "All",
1581 },
1582 "conditions": [
1583 {
1584 "kind": "when",
1585 "body": {
1586 "||": {
1587 "left": {
1588 "&&": {
1589 "left": {
1590 "<": {
1591 "left": {
1592 "Value": -3
1593 },
1594 "right": {
1595 "Value": 2
1596 }
1597 }
1598 },
1599 "right": {
1600 "<": {
1601 "left": {
1602 "neg": {
1603 "arg": {
1604 "-": {
1605 "left": {
1606 "Value": 23
1607 },
1608 "right": {
1609 "Value": 1
1610 }
1611 }
1612 }
1613 }
1614 },
1615 "right": {
1616 "Value": 4
1617 }
1618 }
1619 }
1620 }
1621 },
1622 "right": {
1623 "&&": {
1624 "left": {
1625 "<=": {
1626 "left": {
1627 "Value": 0
1628 },
1629 "right": {
1630 "Value": 0
1631 }
1632 }
1633 },
1634 "right": {
1635 "<=": {
1636 "left": {
1637 "Value": 1
1638 },
1639 "right": {
1640 "Value": 7
1641 }
1642 }
1643 }
1644 }
1645 }
1646 }
1647 }
1648 }
1649 ]
1650 }
1651 );
1652 let roundtripped = serde_json::to_value(ast_roundtrip(est.clone())).unwrap();
1653 assert_eq!(
1654 roundtripped,
1655 expected_json_after_roundtrip,
1656 "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
1657 serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
1658 serde_json::to_string_pretty(&roundtripped).unwrap()
1659 );
1660 let roundtripped = serde_json::to_value(circular_roundtrip(est)).unwrap();
1661 assert_eq!(
1662 roundtripped,
1663 expected_json_after_roundtrip,
1664 "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
1665 serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
1666 serde_json::to_string_pretty(&roundtripped).unwrap()
1667 );
1668 }
1669
1670 #[test]
1671 fn add_sub_and_mul() {
1672 let policy = r#"
1673 permit(principal, action, resource)
1674 when { 2 + 3 - principal.numFoos * (-10) == 7 };
1675 "#;
1676 let cst = parser::text_to_cst::parse_policy(policy)
1677 .unwrap()
1678 .node
1679 .unwrap();
1680 let est: Policy = cst.try_into().unwrap();
1681 let expected_json = json!(
1682 {
1683 "effect": "permit",
1684 "principal": {
1685 "op": "All",
1686 },
1687 "action": {
1688 "op": "All",
1689 },
1690 "resource": {
1691 "op": "All",
1692 },
1693 "conditions": [
1694 {
1695 "kind": "when",
1696 "body": {
1697 "==": {
1698 "left": {
1699 "-": {
1700 "left": {
1701 "+": {
1702 "left": {
1703 "Value": 2
1704 },
1705 "right": {
1706 "Value": 3
1707 }
1708 }
1709 },
1710 "right": {
1711 "*": {
1712 "left": {
1713 ".": {
1714 "left": {
1715 "Var": "principal"
1716 },
1717 "attr": "numFoos"
1718 }
1719 },
1720 "right": {
1721 "Value": -10
1722 }
1723 }
1724 }
1725 }
1726 },
1727 "right": {
1728 "Value": 7
1729 }
1730 }
1731 }
1732 }
1733 ]
1734 }
1735 );
1736 assert_eq!(
1737 serde_json::to_value(&est).unwrap(),
1738 expected_json,
1739 "\nExpected:\n{}\n\nActual:\n{}\n\n",
1740 serde_json::to_string_pretty(&expected_json).unwrap(),
1741 serde_json::to_string_pretty(&est).unwrap()
1742 );
1743 let old_est = est.clone();
1744 let est = est_roundtrip(est);
1745 assert_eq!(&old_est, &est);
1746
1747 assert_eq!(ast_roundtrip(est.clone()), est);
1748 assert_eq!(circular_roundtrip(est.clone()), est);
1749 }
1750
1751 #[test]
1752 fn contains_all_any() {
1753 let policy = r#"
1754 permit(principal, action, resource)
1755 when {
1756 principal.owners.contains("foo")
1757 && principal.owners.containsAny([1, Linux::Group::"sudoers"])
1758 && [2+3, "spam"].containsAll(resource.foos)
1759 };
1760 "#;
1761 let cst = parser::text_to_cst::parse_policy(policy)
1762 .unwrap()
1763 .node
1764 .unwrap();
1765 let est: Policy = cst.try_into().unwrap();
1766 let expected_json = json!(
1767 {
1768 "effect": "permit",
1769 "principal": {
1770 "op": "All",
1771 },
1772 "action": {
1773 "op": "All",
1774 },
1775 "resource": {
1776 "op": "All",
1777 },
1778 "conditions": [
1779 {
1780 "kind": "when",
1781 "body": {
1782 "&&": {
1783 "left": {
1784 "&&": {
1785 "left": {
1786 "contains": {
1787 "left": {
1788 ".": {
1789 "left": {
1790 "Var": "principal"
1791 },
1792 "attr": "owners"
1793 }
1794 },
1795 "right": {
1796 "Value": "foo"
1797 }
1798 }
1799 },
1800 "right": {
1801 "containsAny": {
1802 "left": {
1803 ".": {
1804 "left": {
1805 "Var": "principal"
1806 },
1807 "attr": "owners"
1808 }
1809 },
1810 "right": {
1811 "Set": [
1812 { "Value": 1 },
1813 { "Value": {
1814 "__entity": {
1815 "type": "Linux::Group",
1816 "id": "sudoers"
1817 }
1818 } }
1819 ]
1820 }
1821 }
1822 }
1823 }
1824 },
1825 "right": {
1826 "containsAll": {
1827 "left": {
1828 "Set": [
1829 { "+": {
1830 "left": {
1831 "Value": 2
1832 },
1833 "right": {
1834 "Value": 3
1835 }
1836 } },
1837 { "Value": "spam" },
1838 ]
1839 },
1840 "right": {
1841 ".": {
1842 "left": {
1843 "Var": "resource"
1844 },
1845 "attr": "foos"
1846 }
1847 }
1848 }
1849 }
1850 }
1851 }
1852 }
1853 ]
1854 }
1855 );
1856 assert_eq!(
1857 serde_json::to_value(&est).unwrap(),
1858 expected_json,
1859 "\nExpected:\n{}\n\nActual:\n{}\n\n",
1860 serde_json::to_string_pretty(&expected_json).unwrap(),
1861 serde_json::to_string_pretty(&est).unwrap()
1862 );
1863 let old_est = est.clone();
1864 let est = est_roundtrip(est);
1865 assert_eq!(&old_est, &est);
1866
1867 assert_eq!(ast_roundtrip(est.clone()), est);
1868 assert_eq!(circular_roundtrip(est.clone()), est);
1869 }
1870
1871 #[test]
1872 fn has_like_and_if() {
1873 let policy = r#"
1874 permit(principal, action, resource)
1875 when {
1876 if context.foo
1877 then principal has "-78/%$!"
1878 else resource.email like "*@amazon.com"
1879 };
1880 "#;
1881 let cst = parser::text_to_cst::parse_policy(policy)
1882 .unwrap()
1883 .node
1884 .unwrap();
1885 let est: Policy = cst.try_into().unwrap();
1886 let expected_json = json!(
1887 {
1888 "effect": "permit",
1889 "principal": {
1890 "op": "All",
1891 },
1892 "action": {
1893 "op": "All",
1894 },
1895 "resource": {
1896 "op": "All",
1897 },
1898 "conditions": [
1899 {
1900 "kind": "when",
1901 "body": {
1902 "if-then-else": {
1903 "if": {
1904 ".": {
1905 "left": {
1906 "Var": "context"
1907 },
1908 "attr": "foo"
1909 }
1910 },
1911 "then": {
1912 "has": {
1913 "left": {
1914 "Var": "principal"
1915 },
1916 "attr": "-78/%$!"
1917 }
1918 },
1919 "else": {
1920 "like": {
1921 "left": {
1922 ".": {
1923 "left": {
1924 "Var": "resource"
1925 },
1926 "attr": "email"
1927 }
1928 },
1929 "pattern": "*@amazon.com"
1930 }
1931 }
1932 }
1933 }
1934 }
1935 ]
1936 }
1937 );
1938 assert_eq!(
1939 serde_json::to_value(&est).unwrap(),
1940 expected_json,
1941 "\nExpected:\n{}\n\nActual:\n{}\n\n",
1942 serde_json::to_string_pretty(&expected_json).unwrap(),
1943 serde_json::to_string_pretty(&est).unwrap()
1944 );
1945 let old_est = est.clone();
1946 let est = est_roundtrip(est);
1947 assert_eq!(&old_est, &est);
1948
1949 assert_eq!(ast_roundtrip(est.clone()), est);
1950 assert_eq!(circular_roundtrip(est.clone()), est);
1951 }
1952
1953 #[test]
1954 fn decimal() {
1955 let policy = r#"
1956 permit(principal, action, resource)
1957 when {
1958 context.confidenceScore.greaterThan(decimal("10.0"))
1959 };
1960 "#;
1961 let cst = parser::text_to_cst::parse_policy(policy)
1962 .unwrap()
1963 .node
1964 .unwrap();
1965 let est: Policy = cst.try_into().unwrap();
1966 let expected_json = json!(
1967 {
1968 "effect": "permit",
1969 "principal": {
1970 "op": "All",
1971 },
1972 "action": {
1973 "op": "All",
1974 },
1975 "resource": {
1976 "op": "All",
1977 },
1978 "conditions": [
1979 {
1980 "kind": "when",
1981 "body": {
1982 "greaterThan": [
1983 {
1984 ".": {
1985 "left": {
1986 "Var": "context"
1987 },
1988 "attr": "confidenceScore"
1989 }
1990 },
1991 {
1992 "decimal": [
1993 {
1994 "Value": "10.0"
1995 }
1996 ]
1997 }
1998 ]
1999 }
2000 }
2001 ]
2002 }
2003 );
2004 assert_eq!(
2005 serde_json::to_value(&est).unwrap(),
2006 expected_json,
2007 "\nExpected:\n{}\n\nActual:\n{}\n\n",
2008 serde_json::to_string_pretty(&expected_json).unwrap(),
2009 serde_json::to_string_pretty(&est).unwrap()
2010 );
2011 let old_est = est.clone();
2012 let est = est_roundtrip(est);
2013 assert_eq!(&old_est, &est);
2014
2015 assert_eq!(ast_roundtrip(est.clone()), est);
2016 assert_eq!(circular_roundtrip(est.clone()), est);
2017 }
2018
2019 #[test]
2020 fn ip() {
2021 let policy = r#"
2022 permit(principal, action, resource)
2023 when {
2024 context.source_ip.isInRange(ip("222.222.222.0/24"))
2025 };
2026 "#;
2027 let cst = parser::text_to_cst::parse_policy(policy)
2028 .unwrap()
2029 .node
2030 .unwrap();
2031 let est: Policy = cst.try_into().unwrap();
2032 let expected_json = json!(
2033 {
2034 "effect": "permit",
2035 "principal": {
2036 "op": "All",
2037 },
2038 "action": {
2039 "op": "All",
2040 },
2041 "resource": {
2042 "op": "All",
2043 },
2044 "conditions": [
2045 {
2046 "kind": "when",
2047 "body": {
2048 "isInRange": [
2049 {
2050 ".": {
2051 "left": {
2052 "Var": "context"
2053 },
2054 "attr": "source_ip"
2055 }
2056 },
2057 {
2058 "ip": [
2059 {
2060 "Value": "222.222.222.0/24"
2061 }
2062 ]
2063 }
2064 ]
2065 }
2066 }
2067 ]
2068 }
2069 );
2070 assert_eq!(
2071 serde_json::to_value(&est).unwrap(),
2072 expected_json,
2073 "\nExpected:\n{}\n\nActual:\n{}\n\n",
2074 serde_json::to_string_pretty(&expected_json).unwrap(),
2075 serde_json::to_string_pretty(&est).unwrap()
2076 );
2077 let old_est = est.clone();
2078 let est = est_roundtrip(est);
2079 assert_eq!(&old_est, &est);
2080
2081 assert_eq!(ast_roundtrip(est.clone()), est);
2082 assert_eq!(circular_roundtrip(est.clone()), est);
2083 }
2084
2085 #[test]
2086 fn multiple_clauses() {
2087 let policy = r#"
2088 permit(principal, action, resource)
2089 when { context.foo }
2090 unless { context.bar }
2091 when { principal.eggs };
2092 "#;
2093 let cst = parser::text_to_cst::parse_policy(policy)
2094 .unwrap()
2095 .node
2096 .unwrap();
2097 let est: Policy = cst.try_into().unwrap();
2098 let expected_json = json!(
2099 {
2100 "effect": "permit",
2101 "principal": {
2102 "op": "All",
2103 },
2104 "action": {
2105 "op": "All",
2106 },
2107 "resource": {
2108 "op": "All",
2109 },
2110 "conditions": [
2111 {
2112 "kind": "when",
2113 "body": {
2114 ".": {
2115 "left": {
2116 "Var": "context"
2117 },
2118 "attr": "foo"
2119 }
2120 }
2121 },
2122 {
2123 "kind": "unless",
2124 "body": {
2125 ".": {
2126 "left": {
2127 "Var": "context"
2128 },
2129 "attr": "bar"
2130 }
2131 }
2132 },
2133 {
2134 "kind": "when",
2135 "body": {
2136 ".": {
2137 "left": {
2138 "Var": "principal"
2139 },
2140 "attr": "eggs"
2141 }
2142 }
2143 }
2144 ]
2145 }
2146 );
2147 assert_eq!(
2148 serde_json::to_value(&est).unwrap(),
2149 expected_json,
2150 "\nExpected:\n{}\n\nActual:\n{}\n\n",
2151 serde_json::to_string_pretty(&expected_json).unwrap(),
2152 serde_json::to_string_pretty(&est).unwrap()
2153 );
2154 let old_est = est.clone();
2155 let est = est_roundtrip(est);
2156 assert_eq!(&old_est, &est);
2157
2158 let expected_json_after_roundtrip = json!(
2161 {
2162 "effect": "permit",
2163 "principal": {
2164 "op": "All",
2165 },
2166 "action": {
2167 "op": "All",
2168 },
2169 "resource": {
2170 "op": "All",
2171 },
2172 "conditions": [
2173 {
2174 "kind": "when",
2175 "body": {
2176 "&&": {
2177 "left": {
2178 "&&": {
2179 "left": {
2180 ".": {
2181 "left": {
2182 "Var": "context"
2183 },
2184 "attr": "foo"
2185 }
2186 },
2187 "right": {
2188 "!": {
2189 "arg": {
2190 ".": {
2191 "left": {
2192 "Var": "context"
2193 },
2194 "attr": "bar"
2195 }
2196 }
2197 }
2198 }
2199 }
2200 },
2201 "right": {
2202 ".": {
2203 "left": {
2204 "Var": "principal"
2205 },
2206 "attr": "eggs"
2207 }
2208 }
2209 }
2210 }
2211 }
2212 ]
2213 }
2214 );
2215 let roundtripped = serde_json::to_value(ast_roundtrip(est.clone())).unwrap();
2216 assert_eq!(
2217 roundtripped,
2218 expected_json_after_roundtrip,
2219 "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
2220 serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
2221 serde_json::to_string_pretty(&roundtripped).unwrap()
2222 );
2223 let roundtripped = serde_json::to_value(circular_roundtrip(est)).unwrap();
2224 assert_eq!(
2225 roundtripped,
2226 expected_json_after_roundtrip,
2227 "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
2228 serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
2229 serde_json::to_string_pretty(&roundtripped).unwrap()
2230 );
2231 }
2232
2233 #[test]
2234 fn instantiate() {
2235 let template = r#"
2236 permit(
2237 principal == ?principal,
2238 action == Action::"view",
2239 resource in ?resource
2240 ) when {
2241 principal in resource.owners
2242 };
2243 "#;
2244 let cst = parser::text_to_cst::parse_policy(template)
2245 .unwrap()
2246 .node
2247 .unwrap();
2248 let est: Policy = cst.try_into().unwrap();
2249 let err = est
2250 .clone()
2251 .link(&HashMap::from_iter([]))
2252 .expect_err("didn't fill all the slots");
2253 assert_eq!(
2254 err,
2255 InstantiationError::MissedSlot {
2256 slot: ast::SlotId::principal()
2257 }
2258 );
2259 let err = est
2260 .clone()
2261 .link(&HashMap::from_iter([(
2262 ast::SlotId::principal(),
2263 EntityUidJSON::new("XYZCorp::User", "12UA45"),
2264 )]))
2265 .expect_err("didn't fill all the slots");
2266 assert_eq!(
2267 err,
2268 InstantiationError::MissedSlot {
2269 slot: ast::SlotId::resource()
2270 }
2271 );
2272 let linked = est
2273 .link(&HashMap::from_iter([
2274 (
2275 ast::SlotId::principal(),
2276 EntityUidJSON::new("XYZCorp::User", "12UA45"),
2277 ),
2278 (ast::SlotId::resource(), EntityUidJSON::new("Folder", "abc")),
2279 ]))
2280 .expect("did fill all the slots");
2281 let expected_json = json!(
2282 {
2283 "effect": "permit",
2284 "principal": {
2285 "op": "==",
2286 "entity": { "type": "XYZCorp::User", "id": "12UA45" },
2287 },
2288 "action": {
2289 "op": "==",
2290 "entity": { "type": "Action", "id": "view" },
2291 },
2292 "resource": {
2293 "op": "in",
2294 "entity": { "type": "Folder", "id": "abc" },
2295 },
2296 "conditions": [
2297 {
2298 "kind": "when",
2299 "body": {
2300 "in": {
2301 "left": {
2302 "Var": "principal"
2303 },
2304 "right": {
2305 ".": {
2306 "left": {
2307 "Var": "resource"
2308 },
2309 "attr": "owners"
2310 }
2311 }
2312 }
2313 }
2314 }
2315 ],
2316 }
2317 );
2318 let linked_json = serde_json::to_value(linked).unwrap();
2319 assert_eq!(
2320 linked_json,
2321 expected_json,
2322 "\nExpected:\n{}\n\nActual:\n{}\n\n",
2323 serde_json::to_string_pretty(&expected_json).unwrap(),
2324 serde_json::to_string_pretty(&linked_json).unwrap(),
2325 );
2326 }
2327
2328 #[test]
2329 fn eid_with_nulls() {
2330 let policy = r#"
2331 permit(
2332 principal == a::"\0\0\0J",
2333 action == Action::"view",
2334 resource
2335 );
2336 "#;
2337 let cst = parser::text_to_cst::parse_policy(policy)
2338 .unwrap()
2339 .node
2340 .unwrap();
2341 let est: Policy = cst.try_into().unwrap();
2342 let expected_json = json!(
2343 {
2344 "effect": "permit",
2345 "principal": {
2346 "op": "==",
2347 "entity": {
2348 "type": "a",
2349 "id": "\0\0\0J",
2350 }
2351 },
2352 "action": {
2353 "op": "==",
2354 "entity": {
2355 "type": "Action",
2356 "id": "view",
2357 }
2358 },
2359 "resource": {
2360 "op": "All"
2361 },
2362 "conditions": []
2363 }
2364 );
2365 assert_eq!(
2366 serde_json::to_value(&est).unwrap(),
2367 expected_json,
2368 "\nExpected:\n{}\n\nActual:\n{}\n\n",
2369 serde_json::to_string_pretty(&expected_json).unwrap(),
2370 serde_json::to_string_pretty(&est).unwrap()
2371 );
2372 let old_est = est.clone();
2373 let est = est_roundtrip(est);
2374 assert_eq!(&old_est, &est);
2375
2376 let expected_json_after_roundtrip = json!(
2379 {
2380 "effect": "permit",
2381 "principal": {
2382 "op": "==",
2383 "entity": {
2384 "type": "a",
2385 "id": "\0\0\0J",
2386 }
2387 },
2388 "action": {
2389 "op": "==",
2390 "entity": {
2391 "type": "Action",
2392 "id": "view",
2393 }
2394 },
2395 "resource": {
2396 "op": "All"
2397 },
2398 "conditions": [
2399 {
2400 "kind": "when",
2401 "body": {
2402 "Value": true
2403 }
2404 }
2405 ]
2406 }
2407 );
2408 let roundtripped = serde_json::to_value(ast_roundtrip(est.clone())).unwrap();
2409 assert_eq!(
2410 roundtripped,
2411 expected_json_after_roundtrip,
2412 "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
2413 serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
2414 serde_json::to_string_pretty(&roundtripped).unwrap()
2415 );
2416 let roundtripped = serde_json::to_value(circular_roundtrip(est)).unwrap();
2417 assert_eq!(
2418 roundtripped,
2419 expected_json_after_roundtrip,
2420 "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
2421 serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
2422 serde_json::to_string_pretty(&roundtripped).unwrap()
2423 );
2424 }
2425
2426 #[test]
2427 fn invalid_json_ests() {
2428 let bad = json!(
2429 {
2430 "effect": "Permit",
2431 "principal": {
2432 "op": "All"
2433 },
2434 "action": {
2435 "op": "All"
2436 },
2437 "resource": {
2438 "op": "All"
2439 },
2440 "conditions": []
2441 }
2442 );
2443 let est: Result<Policy, _> = serde_json::from_value(bad);
2444 assert_matches!(est, Err(_)); let bad = json!(
2447 {
2448 "effect": "permit",
2449 "principal": {
2450 "op": "All"
2451 },
2452 "action": {
2453 "op": "All"
2454 },
2455 "resource": {
2456 "op": "All"
2457 },
2458 "conditions": [
2459 {
2460 "kind": "when",
2461 "body": {}
2462 }
2463 ]
2464 }
2465 );
2466 let est: Policy = serde_json::from_value(bad).unwrap();
2467 let ast: Result<ast::Policy, _> = est.try_into_ast_policy(None);
2468 assert_matches!(ast, Err(EstToAstError::MissingOperator));
2469
2470 let bad = json!(
2471 {
2472 "effect": "permit",
2473 "principal": {
2474 "op": "All"
2475 },
2476 "action": {
2477 "op": "All"
2478 },
2479 "resource": {
2480 "op": "All"
2481 },
2482 "conditions": [
2483 {
2484 "kind": "when",
2485 "body": {
2486 "+": {
2487 "left": {
2488 "Value": 3
2489 },
2490 "right": {
2491 "Value": 4
2492 }
2493 },
2494 "-": {
2495 "left": {
2496 "Value": 2
2497 },
2498 "right": {
2499 "Value": 8
2500 }
2501 }
2502 }
2503 }
2504 ]
2505 }
2506 );
2507 let est: Result<Policy, _> = serde_json::from_value(bad);
2508 assert_matches!(est, Err(_)); let template = json!(
2511 {
2512 "effect": "permit",
2513 "principal": {
2514 "op": "==",
2515 "slot": "?principal",
2516 },
2517 "action": {
2518 "op": "All"
2519 },
2520 "resource": {
2521 "op": "All"
2522 },
2523 "conditions": []
2524 }
2525 );
2526 let est: Policy = serde_json::from_value(template).unwrap();
2527 let ast: Result<ast::Policy, _> = est.try_into_ast_policy(None);
2528 assert_matches!(
2529 ast,
2530 Err(EstToAstError::TemplateToPolicy(ast::ContainsSlot::Named(_)))
2531 );
2532 }
2533}