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