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