cedar_policy_core/
est.rs

1/*
2 * Copyright 2022-2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17//! This module contains the External Syntax Tree (EST)
18
19mod 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/// Serde JSON structure for policies and templates in the EST format
37#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
38#[serde(deny_unknown_fields)]
39pub struct Policy {
40    /// `Effect` of the policy or template
41    effect: ast::Effect,
42    /// Principal head constraint
43    principal: PrincipalConstraint,
44    /// Action head constraint
45    action: ActionConstraint,
46    /// Resource head constraint
47    resource: ResourceConstraint,
48    /// `when` and/or `unless` clauses
49    conditions: Vec<Clause>,
50    /// annotations
51    #[serde(default)]
52    #[serde(skip_serializing_if = "HashMap::is_empty")]
53    annotations: HashMap<ast::Id, SmolStr>,
54}
55
56/// Serde JSON structure for a `when` or `unless` clause in the EST format
57#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
58#[serde(deny_unknown_fields)]
59#[serde(tag = "kind", content = "body")]
60pub enum Clause {
61    /// A `when` clause
62    #[serde(rename = "when")]
63    When(Expr),
64    /// An `unless` clause
65    #[serde(rename = "unless")]
66    Unless(Expr),
67}
68
69impl Policy {
70    /// Fill in any slots in the policy using the values in `vals`. Throws an
71    /// error if `vals` doesn't contain a necessary mapping, but does not throw
72    /// an error if `vals` contains unused mappings -- and in particular if
73    /// `self` is an inline policy (in which case it is returned unchanged).
74    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    /// Fill in any slots in the clause using the values in `vals`. Throws an
95    /// error if `vals` doesn't contain a necessary mapping, but does not throw
96    /// an error if `vals` contains unused mappings.
97    pub fn instantiate(
98        self,
99        _vals: &HashMap<ast::SlotId, EntityUidJSON>,
100    ) -> Result<Self, InstantiationError> {
101        // currently, slots are not allowed in clauses
102        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    /// Try to convert EST Policy into AST Policy.
186    ///
187    /// This process requires a policy ID. If not supplied, this method will
188    /// fill it in as "JSON policy".
189    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    /// Try to convert EST Policy into AST Template.
200    ///
201    /// This process requires a policy ID. If not supplied, this method will
202    /// fill it in as "JSON policy".
203    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
240/// Convert AST to EST
241impl 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
257/// Convert AST to EST
258impl 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    // helper function to just do EST data structure --> JSON --> EST data structure
288    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    /// helper function to take EST-->AST-->EST for inline policies
299    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    /// helper function to take EST-->AST-->EST for templates
307    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    /// helper function to take EST-->AST-->text-->CST-->EST for inline policies
315    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    /// helper function to take EST-->AST-->text-->CST-->EST for templates
328    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        // during the lossy transform to AST, the only difference for this policy is that
375        // a `when { true }` is added
376        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        // during the lossy transform to AST, the only difference for this policy is that
459        // a `when { true }` is added
460        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        // during the lossy transform to AST, the only difference for this policy is that
548        // a `when { true }` is added
549        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        // during the lossy transform to AST, the only difference for this policy is that
636        // a `when { true }` is added
637        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        // during the lossy transform to AST, the only difference for this policy is that
792        // a `when { true }` is added
793        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        // during the lossy transform to AST, the only difference for this policy is that
1331        // `!=` is expanded to `!(==)`
1332        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        // during the lossy transform to AST, the `>` and `>=` ops are desugared to `<` and
1569        // `<=` ops with the operands flipped
1570        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        // during the lossy transform to AST, the multiple clauses on this policy are
2159        // combined into a single `when` clause
2160        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        // during the lossy transform to AST, the only difference for this policy is that
2377        // a `when { true }` is added
2378        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(_)); // `Permit` cannot be capitalized
2445
2446        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(_)); // two expressions in body, not connected
2509
2510        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}