cedar_policy_core/ast/restricted_expr.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
17use super::{Expr, ExprKind, Literal, Name};
18use crate::entities::JsonSerializationError;
19use serde::{Deserialize, Serialize};
20use smol_str::SmolStr;
21use std::hash::{Hash, Hasher};
22use std::ops::Deref;
23use thiserror::Error;
24
25/// A few places in Core use these "restricted expressions" (for lack of a
26/// better term) which are in some sense the minimal subset of `Expr` required
27/// to express all possible `Value`s.
28///
29/// Specifically, "restricted" expressions are
30/// defined as expressions containing only the following:
31/// - bool, int, and string literals
32/// - literal EntityUIDs such as User::"alice"
33/// - extension function calls, where the arguments must be other things
34/// on this list
35/// - set and record literals, where the values must be other things on
36/// this list
37///
38/// That means the following are not allowed in "restricted" expressions:
39/// - `principal`, `action`, `resource`, `context`
40/// - builtin operators and functions, including `.`, `in`, `has`, `like`,
41/// `.contains()`
42/// - if-then-else expressions
43///
44/// These restrictions represent the expressions that are allowed to appear as
45/// attribute values in `Slice` and `Context`.
46#[derive(Deserialize, Serialize, Hash, Debug, Clone, PartialEq, Eq)]
47#[serde(transparent)]
48pub struct RestrictedExpr(Expr);
49
50impl RestrictedExpr {
51 /// Create a new `RestrictedExpr` from an `Expr`.
52 ///
53 /// This function is "safe" in the sense that it will verify that the
54 /// provided `expr` does indeed qualify as a "restricted" expression,
55 /// returning an error if not.
56 ///
57 /// Note this check requires recursively walking the AST. For a version of
58 /// this function that doesn't perform this check, see `new_unchecked()`
59 /// below.
60 pub fn new(expr: Expr) -> Result<Self, RestrictedExpressionError> {
61 is_restricted(&expr)?;
62 Ok(Self(expr))
63 }
64
65 /// Create a new `RestrictedExpr` from an `Expr`, where the caller is
66 /// responsible for ensuring that the `Expr` is a valid "restricted
67 /// expression". If it is not, internal invariants will be violated, which
68 /// may lead to other errors later, panics, or even incorrect results.
69 ///
70 /// For a "safer" version of this function that returns an error for invalid
71 /// inputs, see `new()` above.
72 pub fn new_unchecked(expr: Expr) -> Self {
73 // in debug builds, this does the check anyway, panicking if it fails
74 if cfg!(debug_assertions) {
75 Self::new(expr).unwrap()
76 } else {
77 Self(expr)
78 }
79 }
80
81 /// Create a `RestrictedExpr` that's just a single `Literal`.
82 ///
83 /// Note that you can pass this a `Literal`, an `i64`, a `String`, etc.
84 pub fn val(v: impl Into<Literal>) -> Self {
85 // All literals are valid restricted-exprs
86 Self::new_unchecked(Expr::val(v))
87 }
88
89 /// Create a `RestrictedExpr` which evaluates to a Set of the given `RestrictedExpr`s
90 pub fn set(exprs: impl IntoIterator<Item = RestrictedExpr>) -> Self {
91 // Set expressions are valid restricted-exprs if their elements are; and
92 // we know the elements are because we require `RestrictedExpr`s in the
93 // parameter
94 Self::new_unchecked(Expr::set(exprs.into_iter().map(Into::into)))
95 }
96
97 /// Create a `RestrictedExpr` which evaluates to a Record with the given (key, value) pairs.
98 pub fn record(pairs: impl IntoIterator<Item = (SmolStr, RestrictedExpr)>) -> Self {
99 // Record expressions are valid restricted-exprs if their elements are;
100 // and we know the elements are because we require `RestrictedExpr`s in
101 // the parameter
102 Self::new_unchecked(Expr::record(pairs.into_iter().map(|(k, v)| (k, v.into()))))
103 }
104
105 /// Create a `RestrictedExpr` which calls the given extension function
106 pub fn call_extension_fn(function_name: Name, args: Vec<RestrictedExpr>) -> Self {
107 // Extension-function calls are valid restricted-exprs if their
108 // arguments are; and we know the arguments are because we require
109 // `RestrictedExpr`s in the parameter
110 Self::new_unchecked(Expr::call_extension_fn(
111 function_name,
112 args.into_iter().map(Into::into).collect(),
113 ))
114 }
115}
116
117/// While `RestrictedExpr` wraps an _owned_ `Expr`, `BorrowedRestrictedExpr`
118/// wraps a _borrowed_ `Expr`, with the same invariants.
119#[derive(Serialize, Hash, Debug, Clone, PartialEq, Eq)]
120pub struct BorrowedRestrictedExpr<'a>(&'a Expr);
121
122impl<'a> BorrowedRestrictedExpr<'a> {
123 /// Create a new `BorrowedRestrictedExpr` from an `&Expr`.
124 ///
125 /// This function is "safe" in the sense that it will verify that the
126 /// provided `expr` does indeed qualify as a "restricted" expression,
127 /// returning an error if not.
128 ///
129 /// Note this check requires recursively walking the AST. For a version of
130 /// this function that doesn't perform this check, see `new_unchecked()`
131 /// below.
132 pub fn new(expr: &'a Expr) -> Result<Self, RestrictedExpressionError> {
133 is_restricted(expr)?;
134 Ok(Self(expr))
135 }
136
137 /// Create a new `BorrowedRestrictedExpr` from an `&Expr`, where the caller
138 /// is responsible for ensuring that the `Expr` is a valid "restricted
139 /// expression". If it is not, internal invariants will be violated, which
140 /// may lead to other errors later, panics, or even incorrect results.
141 ///
142 /// For a "safer" version of this function that returns an error for invalid
143 /// inputs, see `new()` above.
144 pub fn new_unchecked(expr: &'a Expr) -> Self {
145 // in debug builds, this does the check anyway, panicking if it fails
146 if cfg!(debug_assertions) {
147 Self::new(expr).unwrap()
148 } else {
149 Self(expr)
150 }
151 }
152
153 /// Write a BorrowedRestrictedExpr in "natural JSON" format.
154 ///
155 /// Used to output the context as a map from Strings to JSON Values
156 pub fn to_natural_json(self) -> Result<serde_json::Value, JsonSerializationError> {
157 Ok(serde_json::to_value(
158 crate::entities::JSONValue::from_expr(self)?,
159 )?)
160 }
161}
162
163/// Helper function: does the given `Expr` qualify as a "restricted" expression.
164///
165/// Returns `Ok(())` if yes, or a `RestrictedExpressionError` if no.
166fn is_restricted(expr: &Expr) -> Result<(), RestrictedExpressionError> {
167 match expr.expr_kind() {
168 ExprKind::Lit(_) => Ok(()),
169 ExprKind::Unknown { .. } => Ok(()),
170 ExprKind::Var(_) => Err(RestrictedExpressionError::InvalidRestrictedExpression {
171 feature: expr.to_string(),
172 }),
173 ExprKind::Slot(_) => Err(RestrictedExpressionError::InvalidRestrictedExpression {
174 feature: "template slots".into(),
175 }),
176 ExprKind::If { .. } => Err(RestrictedExpressionError::InvalidRestrictedExpression {
177 feature: "if-then-else".into(),
178 }),
179 ExprKind::And { .. } => Err(RestrictedExpressionError::InvalidRestrictedExpression {
180 feature: "&&".into(),
181 }),
182 ExprKind::Or { .. } => Err(RestrictedExpressionError::InvalidRestrictedExpression {
183 feature: "||".into(),
184 }),
185 ExprKind::UnaryApp { op, .. } => {
186 Err(RestrictedExpressionError::InvalidRestrictedExpression {
187 feature: op.to_string(),
188 })
189 }
190 ExprKind::BinaryApp { op, .. } => {
191 Err(RestrictedExpressionError::InvalidRestrictedExpression {
192 feature: op.to_string(),
193 })
194 }
195 ExprKind::MulByConst { .. } => {
196 Err(RestrictedExpressionError::InvalidRestrictedExpression {
197 feature: "multiplication".into(),
198 })
199 }
200 ExprKind::GetAttr { .. } => Err(RestrictedExpressionError::InvalidRestrictedExpression {
201 feature: "get-attribute".into(),
202 }),
203 ExprKind::HasAttr { .. } => Err(RestrictedExpressionError::InvalidRestrictedExpression {
204 feature: "'has'".into(),
205 }),
206 ExprKind::Like { .. } => Err(RestrictedExpressionError::InvalidRestrictedExpression {
207 feature: "'like'".into(),
208 }),
209 ExprKind::ExtensionFunctionApp { args, .. } => args.iter().try_for_each(is_restricted),
210 ExprKind::Set(exprs) => exprs.iter().try_for_each(is_restricted),
211 ExprKind::Record { pairs } => pairs.iter().map(|(_, v)| v).try_for_each(is_restricted),
212 }
213}
214
215// converting into Expr is always safe; restricted exprs are always valid Exprs
216impl From<RestrictedExpr> for Expr {
217 fn from(r: RestrictedExpr) -> Expr {
218 r.0
219 }
220}
221
222impl AsRef<Expr> for RestrictedExpr {
223 fn as_ref(&self) -> &Expr {
224 &self.0
225 }
226}
227
228impl Deref for RestrictedExpr {
229 type Target = Expr;
230 fn deref(&self) -> &Expr {
231 self.as_ref()
232 }
233}
234
235impl std::fmt::Display for RestrictedExpr {
236 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
237 write!(f, "{}", &self.0)
238 }
239}
240
241// converting into Expr is always safe; restricted exprs are always valid Exprs
242impl<'a> From<BorrowedRestrictedExpr<'a>> for &'a Expr {
243 fn from(r: BorrowedRestrictedExpr<'a>) -> &'a Expr {
244 r.0
245 }
246}
247
248impl<'a> AsRef<Expr> for BorrowedRestrictedExpr<'a> {
249 fn as_ref(&self) -> &Expr {
250 self.0
251 }
252}
253
254impl RestrictedExpr {
255 /// Turn an `&RestrictedExpr` into a `BorrowedRestrictedExpr`
256 pub fn as_borrowed(&self) -> BorrowedRestrictedExpr<'_> {
257 BorrowedRestrictedExpr::new_unchecked(self.as_ref())
258 }
259}
260
261impl<'a> Deref for BorrowedRestrictedExpr<'a> {
262 type Target = Expr;
263 fn deref(&self) -> &Expr {
264 self.as_ref()
265 }
266}
267
268impl<'a> std::fmt::Display for BorrowedRestrictedExpr<'a> {
269 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
270 write!(f, "{}", &self.0)
271 }
272}
273
274/// Like `ExprShapeOnly`, but for restricted expressions.
275///
276/// A newtype wrapper around (borrowed) restricted expressions that provides
277/// `Eq` and `Hash` implementations that ignore any source information or other
278/// generic data used to annotate the expression.
279#[derive(Eq, Debug, Clone)]
280pub struct RestrictedExprShapeOnly<'a>(BorrowedRestrictedExpr<'a>);
281
282impl<'a> RestrictedExprShapeOnly<'a> {
283 /// Construct a `RestrictedExprShapeOnly` from a `BorrowedRestrictedExpr`.
284 /// The `BorrowedRestrictedExpr` is not modified, but any comparisons on the
285 /// resulting `RestrictedExprShapeOnly` will ignore source information and
286 /// generic data.
287 pub fn new(e: BorrowedRestrictedExpr<'a>) -> RestrictedExprShapeOnly<'a> {
288 RestrictedExprShapeOnly(e)
289 }
290}
291
292impl<'a> PartialEq for RestrictedExprShapeOnly<'a> {
293 fn eq(&self, other: &Self) -> bool {
294 self.0.eq_shape(&other.0)
295 }
296}
297
298impl<'a> Hash for RestrictedExprShapeOnly<'a> {
299 fn hash<H: Hasher>(&self, state: &mut H) {
300 self.0.hash_shape(state);
301 }
302}
303
304/// Errors generated in the restricted_expr module
305#[derive(Debug, Clone, PartialEq, Hash, Error)]
306pub enum RestrictedExpressionError {
307 /// A "restricted" expression contained a feature that is not allowed
308 /// in "restricted" expressions. The `feature` is just a string description
309 /// of the feature that is not allowed.
310 #[error("not allowed to use {feature} in a restricted expression")]
311 InvalidRestrictedExpression {
312 /// what disallowed feature appeared in the expression
313 feature: String,
314 },
315}