cedar_policy_core/
parser.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 parser for the Cedar language.
18
19/// Concrete Syntax Tree def used as parser first pass
20pub mod cst;
21/// Step two: convert CST to package AST
22mod cst_to_ast;
23/// error handling utilities
24pub mod err;
25/// implementations for formatting, like `Display`
26mod fmt;
27/// Metadata wrapper for CST Nodes
28mod node;
29pub use node::{ASTNode, SourceInfo};
30/// Step one: Convert text to CST
31pub mod text_to_cst;
32/// Utility functions to unescape string literals
33pub(crate) mod unescape;
34
35use smol_str::SmolStr;
36use std::collections::HashMap;
37
38use crate::ast;
39use crate::est;
40
41/// simple main function for parsing policies
42/// generates numbered ids
43pub fn parse_policyset(text: &str) -> Result<ast::PolicySet, Vec<err::ParseError>> {
44    let mut errs = Vec::new();
45    text_to_cst::parse_policies(text)?
46        .to_policyset(&mut errs)
47        .ok_or(errs)
48}
49
50/// Like `parse_policyset()`, but also returns the (lossless) ESTs -- that is,
51/// the ESTs of the original policies without any of the lossy transforms
52/// involved in converting to AST.
53pub fn parse_policyset_to_ests_and_pset(
54    text: &str,
55) -> Result<(HashMap<ast::PolicyID, est::Policy>, ast::PolicySet), err::ParseErrors> {
56    let mut errs = Vec::new();
57    let cst = text_to_cst::parse_policies(text).map_err(err::ParseErrors)?;
58    let pset = cst.to_policyset(&mut errs);
59    if pset.is_none() {
60        return Err(err::ParseErrors(errs));
61    }
62    let ests = cst
63        .with_generated_policyids()
64        .expect("shouldn't be None since parse_policies() didn't return Err")
65        .map(|(id, policy)| match &policy.node {
66            Some(p) => Ok(Some((id, p.clone().try_into()?))),
67            None => Ok(None),
68        })
69        .collect::<Result<Option<HashMap<ast::PolicyID, est::Policy>>, err::ParseErrors>>()?;
70    match (errs.is_empty(), ests, pset) {
71        (true, Some(ests), Some(pset)) => Ok((ests, pset)),
72        (_, _, _) => Err(err::ParseErrors(errs)),
73    }
74}
75
76/// Simple main function for parsing a policy template.
77/// If `id` is Some, then the resulting template will have that `id`.
78/// If the `id` is None, the parser will use "policy0".
79pub fn parse_policy_template(
80    id: Option<String>,
81    text: &str,
82) -> Result<ast::Template, Vec<err::ParseError>> {
83    let mut errs = Vec::new();
84    let id = match id {
85        Some(id) => ast::PolicyID::from_string(id),
86        None => ast::PolicyID::from_string("policy0"),
87    };
88    let r = text_to_cst::parse_policy(text)?.to_policy_template(id, &mut errs);
89    if errs.is_empty() {
90        r.ok_or(errs).map(ast::Template::from)
91    } else {
92        Err(errs)
93    }
94}
95
96/// Like `parse_policy_template()`, but also returns the (lossless) EST -- that
97/// is, the EST of the original template without any of the lossy transforms
98/// involved in converting to AST.
99pub fn parse_policy_template_to_est_and_ast(
100    id: Option<String>,
101    text: &str,
102) -> Result<(est::Policy, ast::Template), err::ParseErrors> {
103    let mut errs = Vec::new();
104    let id = match id {
105        Some(id) => ast::PolicyID::from_string(id),
106        None => ast::PolicyID::from_string("policy0"),
107    };
108    let cst = text_to_cst::parse_policy(text).map_err(err::ParseErrors)?;
109    let ast = cst
110        .to_policy_template(id, &mut errs)
111        .ok_or_else(|| err::ParseErrors(errs.clone()))?;
112    let est = cst.node.map(TryInto::try_into).transpose()?;
113    match (errs.is_empty(), est) {
114        (true, Some(est)) => Ok((est, ast)),
115        (_, _) => Err(err::ParseErrors(errs)),
116    }
117}
118
119/// simple main function for parsing a policy.
120/// If `id` is Some, then the resulting policy will have that `id`.
121/// If the `id` is None, the parser will use "policy0".
122pub fn parse_policy(
123    id: Option<String>,
124    text: &str,
125) -> Result<ast::StaticPolicy, Vec<err::ParseError>> {
126    let mut errs = Vec::new();
127    let id = match id {
128        Some(id) => ast::PolicyID::from_string(id),
129        None => ast::PolicyID::from_string("policy0"),
130    };
131    let r = text_to_cst::parse_policy(text)?.to_policy(id, &mut errs);
132
133    if errs.is_empty() {
134        r.ok_or(errs)
135    } else {
136        Err(errs)
137    }
138}
139
140/// Like `parse_policy()`, but also returns the (lossless) EST -- that is, the
141/// EST of the original policy without any of the lossy transforms involved in
142/// converting to AST.
143pub fn parse_policy_to_est_and_ast(
144    id: Option<String>,
145    text: &str,
146) -> Result<(est::Policy, ast::StaticPolicy), err::ParseErrors> {
147    let mut errs = Vec::new();
148    let id = match id {
149        Some(id) => ast::PolicyID::from_string(id),
150        None => ast::PolicyID::from_string("policy0"),
151    };
152    let cst = text_to_cst::parse_policy(text).map_err(err::ParseErrors)?;
153    let ast = cst
154        .to_policy(id, &mut errs)
155        .ok_or_else(|| err::ParseErrors(errs.clone()))?;
156
157    let est = cst.node.map(TryInto::try_into).transpose()?;
158    match (errs.is_empty(), est) {
159        (true, Some(est)) => Ok((est, ast)),
160        (_, _) => Err(err::ParseErrors(errs)),
161    }
162}
163
164/// parse an Expr
165pub fn parse_expr(ptext: &str) -> Result<ast::Expr, Vec<err::ParseError>> {
166    let mut errs = Vec::new();
167    text_to_cst::parse_expr(ptext)?
168        .to_expr(&mut errs)
169        .ok_or(errs)
170}
171
172/// parse a RestrictedExpr
173pub fn parse_restrictedexpr(ptext: &str) -> Result<ast::RestrictedExpr, Vec<err::ParseError>> {
174    parse_expr(ptext)
175        .and_then(|expr| ast::RestrictedExpr::new(expr).map_err(|err| vec![err.into()]))
176}
177
178/// parse an EntityUID
179pub fn parse_euid(euid: &str) -> Result<ast::EntityUID, Vec<err::ParseError>> {
180    let mut errs = Vec::new();
181    text_to_cst::parse_ref(euid)?.to_ref(&mut errs).ok_or(errs)
182}
183
184/// parse a Name
185pub fn parse_name(name: &str) -> Result<ast::Name, Vec<err::ParseError>> {
186    let mut errs = Vec::new();
187    text_to_cst::parse_name(name)?
188        .to_name(&mut errs)
189        .ok_or(errs)
190}
191
192/// Parse a namespace
193pub fn parse_namespace(name: &str) -> Result<Vec<ast::Id>, Vec<err::ParseError>> {
194    // A namespace parses identically to a `Name`, except all of the parsed
195    // `Id`s are part of the namespace path. To parse a namespace, we parse it
196    // as a `Name`, before combining the `id` and `path` into the full namespace.
197    Ok(if name.is_empty() {
198        Vec::new()
199    } else {
200        let name = parse_name(name)?;
201        let once = std::iter::once(name.id);
202        let namespace_path = name.path.as_ref().iter().cloned();
203        namespace_path.chain(once).collect()
204    })
205}
206
207/// parse a string into an ast::Literal (does not support expressions)
208pub fn parse_literal(val: &str) -> Result<ast::Literal, Vec<err::ParseError>> {
209    let mut errs = Vec::new();
210    match text_to_cst::parse_primary(val)?
211        .to_expr(&mut errs)
212        .ok_or(errs)?
213        .into_expr_kind()
214    {
215        ast::ExprKind::Lit(v) => Ok(v),
216        _ => Err(vec![err::ParseError::ToAST(
217            "text is not a literal".to_string(),
218        )]),
219    }
220}
221
222/// parse a string into an internal Cedar string
223///
224/// This performs unescaping and validation, returning
225/// a String suitable for an attr, eid, or literal.
226///
227/// Quote handling is as if the input is surrounded by
228/// double quotes ("{val}").
229///
230/// It does not return a string suitable for a pattern. Use the
231/// full expression parser for those.
232pub fn parse_internal_string(val: &str) -> Result<SmolStr, Vec<err::ParseError>> {
233    let mut errs = Vec::new();
234    // we need to add quotes for this to be a valid string literal
235    text_to_cst::parse_primary(&format!(r#""{val}""#))?
236        .to_string_literal(&mut errs)
237        .ok_or(errs)
238}
239
240/// parse an identifier
241pub fn parse_ident(id: &str) -> Result<ast::Id, Vec<err::ParseError>> {
242    let mut errs = Vec::new();
243    text_to_cst::parse_ident(id)?
244        .to_valid_ident(&mut errs)
245        .ok_or(errs)
246}
247
248/// parse into a `Request`
249pub fn parse_request(
250    principal: impl AsRef<str>,      // should be a "Type::EID" string
251    action: impl AsRef<str>,         // should be a "Type::EID" string
252    resource: impl AsRef<str>,       // should be a "Type::EID" string
253    context_json: serde_json::Value, // JSON object mapping Strings to ast::RestrictedExpr
254) -> Result<ast::Request, Vec<err::ParseError>> {
255    let mut errs = vec![];
256    // Parse principal, action, resource
257    let mut parse_par = |s, name| {
258        parse_euid(s)
259            .map_err(|e| {
260                errs.push(err::ParseError::WithContext {
261                    context: format!("trying to parse {}", name),
262                    errs: e,
263                })
264            })
265            .ok()
266    };
267
268    let (principal, action, resource) = (
269        parse_par(principal.as_ref(), "principal"),
270        parse_par(action.as_ref(), "action"),
271        parse_par(resource.as_ref(), "resource"),
272    );
273
274    let context = match ast::Context::from_json_value(context_json) {
275        Ok(ctx) => Some(ctx),
276        Err(e) => {
277            errs.push(err::ParseError::ToAST(format!(
278                "failed to parse context JSON: {}",
279                err::ParseErrors(vec![err::ParseError::ToAST(e.to_string())])
280            )));
281            None
282        }
283    };
284    match (principal, action, resource, errs.as_slice()) {
285        (Some(p), Some(a), Some(r), &[]) => Ok(ast::Request {
286            principal: ast::EntityUIDEntry::concrete(p),
287            action: ast::EntityUIDEntry::concrete(a),
288            resource: ast::EntityUIDEntry::concrete(r),
289            context,
290        }),
291        _ => Err(errs),
292    }
293}
294
295#[cfg(test)]
296mod test {
297    use std::collections::HashSet;
298
299    use itertools::Itertools;
300
301    use crate::ast::{test_generators::*, Template};
302
303    use super::*;
304
305    #[test]
306    fn test_template_parsing() {
307        for template in all_templates().map(Template::from) {
308            let id = template.id();
309            let src = format!("{template}");
310            let parsed = parse_policy_template(Some(id.to_string()), &src);
311            match parsed {
312                Ok(p) => {
313                    assert_eq!(
314                        p.slots().collect::<HashSet<_>>(),
315                        template.slots().collect::<HashSet<_>>()
316                    );
317                    assert_eq!(p.id(), template.id());
318                    assert_eq!(p.effect(), template.effect());
319                    assert_eq!(p.principal_constraint(), template.principal_constraint());
320                    assert_eq!(p.action_constraint(), template.action_constraint());
321                    assert_eq!(p.resource_constraint(), template.resource_constraint());
322                    assert!(
323                        p.non_head_constraints()
324                            .eq_shape(template.non_head_constraints()),
325                        "{:?} and {:?} should have the same shape.",
326                        p.non_head_constraints(),
327                        template.non_head_constraints()
328                    );
329                }
330                Err(e) => panic!(
331                    "Failed to parse {src}, {}",
332                    e.into_iter().map(|e| format!("{e}")).join("\n")
333                ),
334            }
335        }
336    }
337
338    #[test]
339    fn test_error_out() {
340        let errors = parse_policyset(
341            r#"
342            permit(principal:p,action:a,resource:r)
343            when{w or if c but not z} // expr error
344            unless{u if c else d or f} // expr error
345            advice{"doit"};
346
347            permit(principality in Group::"jane_friends", // policy error
348            action in [PhotoOp::"view", PhotoOp::"comment"],
349            resource in Album::"jane_trips");
350
351            forbid(principal, action, resource)
352            when   { "private" in resource.tags }
353            unless { resource in principal.account };
354        "#,
355        )
356        .expect_err("multiple errors above");
357        println!("{:?}", errors);
358        assert!(errors.len() >= 3);
359    }
360}
361
362#[cfg(test)]
363mod eval_tests {
364    use super::*;
365    use crate::evaluator as eval;
366    use crate::extensions::Extensions;
367
368    #[test]
369    fn interpret_exprs() {
370        let request = eval::test::basic_request();
371        let entities = eval::test::basic_entities();
372        let exts = Extensions::none();
373        let evaluator = eval::Evaluator::new(&request, &entities, &exts).unwrap();
374
375        // bools
376        let expr = parse_expr("false").expect("parse fail");
377        assert_eq!(
378            evaluator
379                .interpret_inline_policy(&expr)
380                .expect("interpret fail"),
381            ast::Value::Lit(ast::Literal::Bool(false))
382        );
383        let expr = parse_expr("true && true").expect("parse fail");
384        assert_eq!(
385            evaluator
386                .interpret_inline_policy(&expr)
387                .expect("interpret fail"),
388            ast::Value::Lit(ast::Literal::Bool(true))
389        );
390        let expr = parse_expr("!true || false && !true").expect("parse fail");
391        assert_eq!(
392            evaluator
393                .interpret_inline_policy(&expr)
394                .expect("interpret fail"),
395            ast::Value::Lit(ast::Literal::Bool(false))
396        );
397        let expr = parse_expr("!!!!true").expect("parse fail");
398        assert_eq!(
399            evaluator
400                .interpret_inline_policy(&expr)
401                .expect("interpret fail"),
402            ast::Value::Lit(ast::Literal::Bool(true))
403        );
404
405        let expr = parse_expr(
406            r#"
407        if false || true != 4 then
408            600
409        else
410            -200
411        "#,
412        )
413        .expect("parse fail");
414        assert_eq!(
415            evaluator
416                .interpret_inline_policy(&expr)
417                .expect("interpret fail"),
418            ast::Value::Lit(ast::Literal::Long(600))
419        );
420    }
421
422    #[test]
423    fn interpret_membership() {
424        let request = eval::test::basic_request();
425        let entities = eval::test::rich_entities();
426        let exts = Extensions::none();
427        let evaluator = eval::Evaluator::new(&request, &entities, &exts).unwrap();
428
429        let expr = parse_expr(
430            r#"
431
432        test_entity_type::"child" in
433            test_entity_type::"unrelated"
434
435        "#,
436        )
437        .expect("parse fail");
438        assert_eq!(
439            evaluator
440                .interpret_inline_policy(&expr)
441                .expect("interpret fail"),
442            ast::Value::Lit(ast::Literal::Bool(false))
443        );
444        let expr = parse_expr(
445            r#"
446
447        test_entity_type::"child" in
448            test_entity_type::"child"
449
450        "#,
451        )
452        .expect("parse fail");
453        assert_eq!(
454            evaluator
455                .interpret_inline_policy(&expr)
456                .expect("interpret fail"),
457            ast::Value::Lit(ast::Literal::Bool(true))
458        );
459        let expr = parse_expr(
460            r#"
461
462        other_type::"other_child" in
463            test_entity_type::"parent"
464
465        "#,
466        )
467        .expect("parse fail");
468        assert_eq!(
469            evaluator
470                .interpret_inline_policy(&expr)
471                .expect("interpret fail"),
472            ast::Value::Lit(ast::Literal::Bool(true))
473        );
474        let expr = parse_expr(
475            r#"
476
477        test_entity_type::"child" in
478            test_entity_type::"grandparent"
479
480        "#,
481        )
482        .expect("parse fail");
483        assert_eq!(
484            evaluator
485                .interpret_inline_policy(&expr)
486                .expect("interpret fail"),
487            ast::Value::Lit(ast::Literal::Bool(true))
488        );
489    }
490
491    #[test]
492    fn interpret_relation() {
493        let request = eval::test::basic_request();
494        let entities = eval::test::basic_entities();
495        let exts = Extensions::none();
496        let evaluator = eval::Evaluator::new(&request, &entities, &exts).unwrap();
497
498        let expr = parse_expr(
499            r#"
500
501            3 < 2 || 2 > 3
502
503        "#,
504        )
505        .expect("parse fail");
506        assert_eq!(
507            evaluator
508                .interpret_inline_policy(&expr)
509                .expect("interpret fail"),
510            ast::Value::Lit(ast::Literal::Bool(false))
511        );
512        let expr = parse_expr(
513            r#"
514
515            7 <= 7 && 4 != 5
516
517        "#,
518        )
519        .expect("parse fail");
520        assert_eq!(
521            evaluator
522                .interpret_inline_policy(&expr)
523                .expect("interpret fail"),
524            ast::Value::Lit(ast::Literal::Bool(true))
525        );
526    }
527}
528
529#[cfg(test)]
530mod parse_tests {
531    use super::*;
532
533    #[test]
534    fn parse_exists() {
535        let result = parse_policyset(
536            r#"
537            permit(principal, action, resource)
538            when{ true };
539        "#,
540        );
541        assert!(!result.expect("parse error").is_empty());
542    }
543
544    #[test]
545    fn test_parse_namespace() -> Result<(), Vec<err::ParseError>> {
546        assert_eq!(Vec::<ast::Id>::new(), parse_namespace("")?);
547        assert_eq!(vec!["Foo".parse::<ast::Id>()?], parse_namespace("Foo")?);
548        assert_eq!(
549            vec!["Foo".parse::<ast::Id>()?, "Bar".parse::<ast::Id>()?],
550            parse_namespace("Foo::Bar")?
551        );
552        assert_eq!(
553            vec![
554                "Foo".parse::<ast::Id>()?,
555                "Bar".parse::<ast::Id>()?,
556                "Baz".parse::<ast::Id>()?
557            ],
558            parse_namespace("Foo::Bar::Baz")?
559        );
560        Ok(())
561    }
562
563    #[test]
564    fn test_parse_string() {
565        // test idempotence
566        assert_eq!(
567            ast::Eid::new(parse_internal_string(r#"a\nblock\nid"#).expect("should parse"))
568                .to_string(),
569            r#"a\nblock\nid"#,
570        );
571        parse_internal_string(r#"oh, no, a '! "#).expect("single quote should be fine");
572        parse_internal_string(r#"oh, no, a "! "#).expect_err("double quote not allowed");
573        parse_internal_string(r#"oh, no, a \"! and a \'! "#).expect("escaped quotes should parse");
574    }
575}