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