cedar_policy_core/ast/
entity.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::*;
18use crate::parser::err::ParseError;
19use crate::transitive_closure::TCNode;
20use itertools::Itertools;
21use serde::{Deserialize, Serialize};
22use smol_str::SmolStr;
23use std::collections::{HashMap, HashSet};
24
25/// We support two types of entities. The first is a nominal type (e.g., User, Action)
26/// and the second is an unspecified type, which is used (internally) to represent cases
27/// where the input request does not provide a principal, action, and/or resource.
28#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone, Hash, PartialOrd, Ord)]
29#[cfg_attr(fuzzing, derive(arbitrary::Arbitrary))]
30pub enum EntityType {
31    /// Concrete nominal type
32    Concrete(Name),
33    /// Unspecified
34    Unspecified,
35}
36
37// Note: the characters '<' and '>' are not allowed in `Name`s, so the display for
38// `Unspecified` never conflicts with `Concrete(name)`.
39impl std::fmt::Display for EntityType {
40    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41        match self {
42            Self::Unspecified => write!(f, "<Unspecified>"),
43            Self::Concrete(name) => write!(f, "{}", name),
44        }
45    }
46}
47
48/// Unique ID for an entity. These represent entities in the AST.
49#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone, Hash, PartialOrd, Ord)]
50#[cfg_attr(fuzzing, derive(arbitrary::Arbitrary))]
51pub struct EntityUID {
52    /// Typename of the entity
53    ty: EntityType,
54    /// EID of the entity
55    eid: Eid,
56}
57
58impl StaticallyTyped for EntityUID {
59    fn type_of(&self) -> Type {
60        Type::Entity {
61            ty: self.ty.clone(),
62        }
63    }
64}
65
66impl EntityUID {
67    /// Create an `EntityUID` with the given string as its EID.
68    /// Useful for testing.
69    #[cfg(test)]
70    pub(crate) fn with_eid(eid: &str) -> Self {
71        Self {
72            ty: Self::test_entity_type(),
73            eid: Eid(eid.into()),
74        }
75    }
76    // by default, Coverlay does not track coverage for lines after a line
77    // containing #[cfg(test)].
78    // we use the following sentinel to "turn back on" coverage tracking for
79    // remaining lines of this file, until the next #[cfg(test)]
80    // GRCOV_BEGIN_COVERAGE
81
82    /// The type of entities created with the above `with_eid()`.
83    #[cfg(test)]
84    pub(crate) fn test_entity_type() -> EntityType {
85        let name = Name::parse_unqualified_name("test_entity_type")
86            .expect("test_entity_type should be a valid identifier");
87        EntityType::Concrete(name)
88    }
89    // by default, Coverlay does not track coverage for lines after a line
90    // containing #[cfg(test)].
91    // we use the following sentinel to "turn back on" coverage tracking for
92    // remaining lines of this file, until the next #[cfg(test)]
93    // GRCOV_BEGIN_COVERAGE
94
95    /// Create an `EntityUID` with the given (unqualified) typename, and the given string as its EID.
96    pub fn with_eid_and_type(typename: &str, eid: &str) -> Result<Self, Vec<ParseError>> {
97        Ok(Self {
98            ty: EntityType::Concrete(Name::parse_unqualified_name(typename)?),
99            eid: Eid(eid.into()),
100        })
101    }
102
103    /// Split into the `EntityType` representing the entity type, and the `Eid`
104    /// representing its name
105    pub fn components(self) -> (EntityType, Eid) {
106        (self.ty, self.eid)
107    }
108
109    /// Create a nominally-typed `EntityUID` with the given typename and EID
110    pub fn from_components(name: Name, eid: Eid) -> Self {
111        Self {
112            ty: EntityType::Concrete(name),
113            eid,
114        }
115    }
116
117    /// Create an unspecified `EntityUID` with the given EID
118    pub fn unspecified_from_eid(eid: Eid) -> Self {
119        Self {
120            ty: EntityType::Unspecified,
121            eid,
122        }
123    }
124
125    /// Get the type component.
126    pub fn entity_type(&self) -> &EntityType {
127        &self.ty
128    }
129
130    /// Get the Eid component.
131    pub fn eid(&self) -> &Eid {
132        &self.eid
133    }
134}
135
136impl std::fmt::Display for EntityUID {
137    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
138        write!(f, "{}::\"{}\"", self.entity_type(), self.eid)
139    }
140}
141
142// allow `.parse()` on a string to make an `EntityUID`
143impl std::str::FromStr for EntityUID {
144    type Err = Vec<ParseError>;
145
146    fn from_str(s: &str) -> Result<Self, Vec<ParseError>> {
147        crate::parser::parse_euid(s)
148    }
149}
150
151/// EID type is just a SmolStr for now
152#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone, Hash, PartialOrd, Ord)]
153pub struct Eid(SmolStr);
154
155impl Eid {
156    /// Construct an Eid
157    pub fn new(eid: impl Into<SmolStr>) -> Self {
158        Eid(eid.into())
159    }
160}
161
162impl AsRef<SmolStr> for Eid {
163    fn as_ref(&self) -> &SmolStr {
164        &self.0
165    }
166}
167
168impl AsRef<str> for Eid {
169    fn as_ref(&self) -> &str {
170        &self.0
171    }
172}
173
174#[cfg(fuzzing)]
175impl<'a> arbitrary::Arbitrary<'a> for Eid {
176    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
177        let x: String = u.arbitrary()?;
178        Ok(Self(x.into()))
179    }
180}
181
182impl std::fmt::Display for Eid {
183    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
184        write!(f, "{}", self.0.escape_debug())
185    }
186}
187
188/// Entity datatype
189#[derive(Serialize, Deserialize, Debug, Clone)]
190pub struct Entity {
191    /// UID
192    uid: EntityUID,
193
194    /// Internal HashMap of attributes.
195    ///
196    /// In the serialized form of `Entity`, attribute values appear as
197    /// `RestrictedExpr`s.
198    attrs: HashMap<SmolStr, RestrictedExpr>,
199
200    /// Set of ancestors of this `Entity` (i.e., all direct and transitive
201    /// parents), as UIDs
202    ancestors: HashSet<EntityUID>,
203}
204
205impl Entity {
206    /// Create a new `Entity` with this UID, attributes, and ancestors
207    pub fn new(
208        uid: EntityUID,
209        attrs: HashMap<SmolStr, RestrictedExpr>,
210        ancestors: HashSet<EntityUID>,
211    ) -> Self {
212        Entity {
213            uid,
214            attrs,
215            ancestors,
216        }
217    }
218
219    /// Get the UID of this entity
220    pub fn uid(&self) -> EntityUID {
221        self.uid.clone()
222    }
223
224    /// Get the value for the given attribute, or `None` if not present
225    pub fn get(&self, attr: &str) -> Option<&RestrictedExpr> {
226        self.attrs.get(attr)
227    }
228
229    /// Is this `Entity` a descendant of `e` in the entity hierarchy?
230    pub fn is_descendant_of(&self, e: &EntityUID) -> bool {
231        self.ancestors.contains(e)
232    }
233
234    /// Iterate over this entity's ancestors
235    pub fn ancestors(&self) -> impl Iterator<Item = &EntityUID> {
236        self.ancestors.iter()
237    }
238
239    /// Create an `Entity` with the given UID, no attributes, and no parents.
240    pub fn with_uid(uid: EntityUID) -> Self {
241        Self {
242            uid,
243            attrs: HashMap::new(),
244            ancestors: HashSet::new(),
245        }
246    }
247
248    /// Read-only access the internal `attrs` map of String to RestrictedExpr.
249    /// This function is available only inside Core.
250    pub(crate) fn attrs(&self) -> &HashMap<SmolStr, RestrictedExpr> {
251        &self.attrs
252    }
253
254    /// Set the given attribute to the given value.
255    // Only used for convenience in some tests and when fuzzing
256    #[cfg(any(test, fuzzing))]
257    pub fn set_attr(&mut self, attr: SmolStr, val: RestrictedExpr) {
258        self.attrs.insert(attr, val);
259    }
260
261    /// Mark the given `UID` as an ancestor of this `Entity`.
262    // When fuzzing, `add_ancestor()` is fully `pub`.
263    #[cfg(not(fuzzing))]
264    pub(crate) fn add_ancestor(&mut self, uid: EntityUID) {
265        self.ancestors.insert(uid);
266    }
267    /// Mark the given `UID` as an ancestor of this `Entity`
268    #[cfg(fuzzing)]
269    pub fn add_ancestor(&mut self, uid: EntityUID) {
270        self.ancestors.insert(uid);
271    }
272}
273
274impl PartialEq for Entity {
275    fn eq(&self, other: &Self) -> bool {
276        self.uid() == other.uid()
277    }
278}
279
280impl Eq for Entity {}
281
282impl StaticallyTyped for Entity {
283    fn type_of(&self) -> Type {
284        self.uid.type_of()
285    }
286}
287
288impl TCNode<EntityUID> for Entity {
289    fn get_key(&self) -> EntityUID {
290        self.uid()
291    }
292
293    fn add_edge_to(&mut self, k: EntityUID) {
294        self.add_ancestor(k)
295    }
296
297    fn out_edges(&self) -> Box<dyn Iterator<Item = &EntityUID> + '_> {
298        Box::new(self.ancestors())
299    }
300
301    fn has_edge_to(&self, e: &EntityUID) -> bool {
302        self.is_descendant_of(e)
303    }
304}
305
306impl std::fmt::Display for Entity {
307    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
308        write!(
309            f,
310            "{}:\n  attrs:{}\n  ancestors:{}",
311            self.uid,
312            self.attrs
313                .iter()
314                .map(|(k, v)| format!("{}: {}", k, v))
315                .join("; "),
316            self.ancestors.iter().join(", ")
317        )
318    }
319}
320
321#[cfg(test)]
322mod test {
323    use super::*;
324
325    #[test]
326    fn display() {
327        let e = EntityUID::with_eid("eid");
328        assert_eq!(format!("{e}"), "test_entity_type::\"eid\"");
329    }
330
331    #[test]
332    fn test_euid_equality() {
333        let e1 = EntityUID::with_eid("foo");
334        let e2 = EntityUID::from_components(
335            Name::parse_unqualified_name("test_entity_type").expect("should be a valid identifier"),
336            Eid("foo".into()),
337        );
338        let e3 = EntityUID::unspecified_from_eid(Eid("foo".into()));
339        let e4 = EntityUID::unspecified_from_eid(Eid("bar".into()));
340        let e5 = EntityUID::from_components(
341            Name::parse_unqualified_name("Unspecified").expect("should be a valid identifier"),
342            Eid("foo".into()),
343        );
344
345        // an EUID is equal to itself
346        assert_eq!(e1, e1);
347        assert_eq!(e2, e2);
348        assert_eq!(e3, e3);
349
350        // constructing with `with_euid` or `from_components` is the same
351        assert_eq!(e1, e2);
352
353        // other pairs are not equal
354        assert!(e1 != e3);
355        assert!(e1 != e4);
356        assert!(e1 != e5);
357        assert!(e3 != e4);
358        assert!(e3 != e5);
359        assert!(e4 != e5);
360
361        // e3 and e5 are displayed differently
362        assert!(format!("{e3}") != format!("{e5}"));
363    }
364}