cedar_policy_core/ast/
request.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 crate::ast::{BorrowedRestrictedExpr, EntityUID, ExprKind, RestrictedExpr};
18use crate::entities::{ContextJsonParser, JsonDeserializationError, NullContextSchema};
19use crate::extensions::Extensions;
20use serde::{Deserialize, Serialize};
21use smol_str::SmolStr;
22use std::sync::Arc;
23
24use super::{Expr, Literal, PartialValue, Value, Var};
25
26/// Represents the request tuple <P, A, R, C> (see the Cedar design doc).
27#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct Request {
29    /// Principal associated with the request
30    /// None means that variable will result in a residual for partial evaluation.
31    pub(crate) principal: EntityUIDEntry,
32
33    /// Action associated with the request
34    /// None means that variable will result in a residual for partial evaluation.
35    pub(crate) action: EntityUIDEntry,
36
37    /// Resource associated with the request
38    /// None means that variable will result in a residual for partial evaluation.
39    pub(crate) resource: EntityUIDEntry,
40
41    /// Context associated with the request
42    /// None means that variable will result in a residual for partial evaluation.
43    pub(crate) context: Option<Context>,
44}
45
46/// An entry in a  request for a Entity UID.
47/// It may either be a concrete EUID
48/// or an unknown in the case of partial evaluation
49#[derive(Debug, Clone, Serialize, Deserialize)]
50pub enum EntityUIDEntry {
51    /// A concrete (but perhaps unspecified) EntityUID
52    Concrete(Arc<EntityUID>),
53    /// An EntityUID left as unknown for partial evaluation
54    Unknown,
55}
56
57impl EntityUIDEntry {
58    /// Evaluate the entry to either:
59    /// A value, if the entry is concrete
60    /// An unknown corresponding to the passed `var`
61    pub fn evaluate(&self, var: Var) -> PartialValue {
62        match self {
63            EntityUIDEntry::Concrete(euid) => Value::Lit(Literal::EntityUID(euid.clone())).into(),
64            EntityUIDEntry::Unknown => Expr::unknown(var.to_string()).into(),
65        }
66    }
67
68    /// Create an entry with a concrete EntityUID
69    pub fn concrete(euid: EntityUID) -> Self {
70        Self::Concrete(Arc::new(euid))
71    }
72}
73
74impl Request {
75    /// Default constructor
76    pub fn new(
77        principal: EntityUID,
78        action: EntityUID,
79        resource: EntityUID,
80        context: Context,
81    ) -> Self {
82        Self {
83            principal: EntityUIDEntry::concrete(principal),
84            action: EntityUIDEntry::concrete(action),
85            resource: EntityUIDEntry::concrete(resource),
86            context: Some(context),
87        }
88    }
89
90    /// Create a new request with potentially unknown (for partial eval) head variables
91    pub fn new_with_unknowns(
92        principal: EntityUIDEntry,
93        action: EntityUIDEntry,
94        resource: EntityUIDEntry,
95        context: Option<Context>,
96    ) -> Self {
97        Self {
98            principal,
99            action,
100            resource,
101            context,
102        }
103    }
104
105    /// Get the principal associated with the request
106    pub fn principal(&self) -> &EntityUIDEntry {
107        &self.principal
108    }
109
110    /// Get the action associated with the request
111    pub fn action(&self) -> &EntityUIDEntry {
112        &self.action
113    }
114
115    /// Get the resource associated with the request
116    pub fn resource(&self) -> &EntityUIDEntry {
117        &self.resource
118    }
119
120    /// Get the context associated with the request
121    /// Returning `None` means the variable is unknown, and will result in a residual expression
122    pub fn context(&self) -> Option<&Context> {
123        self.context.as_ref()
124    }
125}
126
127impl std::fmt::Display for Request {
128    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
129        let display_euid = |maybe_euid: &EntityUIDEntry| match maybe_euid {
130            EntityUIDEntry::Concrete(euid) => format!("{euid}"),
131            EntityUIDEntry::Unknown => "unknown".to_string(),
132        };
133        write!(
134            f,
135            "request with principal {}, action {}, resource {}, and context {}",
136            display_euid(&self.principal),
137            display_euid(&self.action),
138            display_euid(&self.resource),
139            match &self.context {
140                Some(x) => format!("{x}"),
141                None => "unknown".to_string(),
142            }
143        )
144    }
145}
146
147/// `Context` field of a `Request`
148#[derive(Debug, Clone, Serialize, Deserialize)]
149pub struct Context {
150    /// an `Expr::Record` that qualifies as a "restricted expression"
151    #[serde(flatten)]
152    context: RestrictedExpr,
153}
154
155impl Context {
156    /// Create an empty `Context`
157    pub fn empty() -> Self {
158        Self::from_pairs([])
159    }
160
161    /// Create a `Context` from a `RestrictedExpr`, which must be a `Record`
162    pub fn from_expr(expr: RestrictedExpr) -> Self {
163        debug_assert!(matches!(expr.expr_kind(), ExprKind::Record { .. }));
164        Self { context: expr }
165    }
166
167    /// Create a `Context` from a map of key to `RestrictedExpr`, or a Vec of
168    /// `(key, RestrictedExpr)` pairs, or any other iterator of `(key, RestrictedExpr)` pairs
169    pub fn from_pairs(pairs: impl IntoIterator<Item = (SmolStr, RestrictedExpr)>) -> Self {
170        Self {
171            context: RestrictedExpr::record(pairs),
172        }
173    }
174
175    /// Create a `Context` from a string containing JSON (which must be a JSON
176    /// object, not any other JSON type, or you will get an error here).
177    /// JSON here must use the `__entity` and `__extn` escapes for entity
178    /// references, extension values, etc.
179    ///
180    /// For schema-based parsing, use `ContextJsonParser`.
181    pub fn from_json_str(json: &str) -> Result<Self, JsonDeserializationError> {
182        ContextJsonParser::new(None::<&NullContextSchema>, Extensions::all_available())
183            .from_json_str(json)
184    }
185
186    /// Create a `Context` from a `serde_json::Value` (which must be a JSON
187    /// object, not any other JSON type, or you will get an error here).
188    /// JSON here must use the `__entity` and `__extn` escapes for entity
189    /// references, extension values, etc.
190    ///
191    /// For schema-based parsing, use `ContextJsonParser`.
192    pub fn from_json_value(json: serde_json::Value) -> Result<Self, JsonDeserializationError> {
193        ContextJsonParser::new(None::<&NullContextSchema>, Extensions::all_available())
194            .from_json_value(json)
195    }
196
197    /// Create a `Context` from a JSON file.  The JSON file must contain a JSON
198    /// object, not any other JSON type, or you will get an error here.
199    /// JSON here must use the `__entity` and `__extn` escapes for entity
200    /// references, extension values, etc.
201    ///
202    /// For schema-based parsing, use `ContextJsonParser`.
203    pub fn from_json_file(json: impl std::io::Read) -> Result<Self, JsonDeserializationError> {
204        ContextJsonParser::new(None::<&NullContextSchema>, Extensions::all_available())
205            .from_json_file(json)
206    }
207
208    /// Iterate over the (key, value) pairs in the `Context`
209    pub fn iter(&self) -> impl Iterator<Item = (&str, BorrowedRestrictedExpr<'_>)> {
210        match self.context.as_ref().expr_kind() {
211            ExprKind::Record { pairs } => pairs
212                .iter()
213                .map(|(k, v)| (k.as_str(), BorrowedRestrictedExpr::new_unchecked(v))), // given that the invariant holds for `self.context`, it will hold here
214            e => panic!("internal invariant violation: expected Expr::Record, got {e:?}"),
215        }
216    }
217}
218
219impl AsRef<RestrictedExpr> for Context {
220    fn as_ref(&self) -> &RestrictedExpr {
221        &self.context
222    }
223}
224
225impl std::default::Default for Context {
226    fn default() -> Context {
227        Context::empty()
228    }
229}
230
231impl std::fmt::Display for Context {
232    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
233        write!(f, "{}", self.context)
234    }
235}