cedar_policy_core/validator/schema/
action.rs

1/*
2 * Copyright Cedar Contributors
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
17//! This module contains the definition of `ValidatorActionId` and the types it relies on
18
19use crate::{
20    ast::{self, EntityType, EntityUID, PartialValue},
21    parser::Loc,
22    transitive_closure::TCNode,
23};
24use smol_str::SmolStr;
25use std::collections::{BTreeMap, HashSet};
26
27use super::internal_name_to_entity_type;
28use crate::validator::{
29    partition_nonempty::PartitionNonEmpty,
30    schema::{AllDefs, SchemaError},
31    types::{Attributes, Type},
32    ConditionalName,
33};
34
35/// Contains information about actions used by the validator.  The contents of
36/// the struct are the same as the schema entity type structure, but the
37/// `member_of` relation is reversed to instead be `descendants`.
38#[derive(Clone, Debug)]
39pub struct ValidatorActionId {
40    /// The name of the action.
41    pub(crate) name: EntityUID,
42
43    /// The principals and resources that the action can be applied to.
44    pub(crate) applies_to: ValidatorApplySpec<ast::EntityType>,
45
46    /// The set of actions that are members of this action. When this
47    /// structure is initially constructed, the field will contain direct
48    /// children, but it will be updated to contain the closure of all
49    /// descendants before it is used in any validation.
50    pub(crate) descendants: HashSet<EntityUID>,
51
52    /// The type of the context record associated with this action.
53    pub(crate) context: Type,
54
55    /// The attribute types for this action, used for typechecking.
56    pub(crate) attribute_types: Attributes,
57
58    /// The actual attribute value for this action, used to construct an
59    /// `Entity` for this action. Could also be used for more precise
60    /// typechecking by partial evaluation.
61    pub(crate) attributes: BTreeMap<SmolStr, PartialValue>,
62    /// Source location - if available
63    pub(crate) loc: Option<Loc>,
64}
65
66impl ValidatorActionId {
67    /// Construct a new `ValidatorActionId`.
68    ///
69    /// This constructor assumes that `descendants` has TC already computed.
70    /// That is, caller is responsible for TC.
71    #[allow(clippy::too_many_arguments)]
72    pub fn new(
73        name: EntityUID,
74        principal_entity_types: impl IntoIterator<Item = ast::EntityType>,
75        resource_entity_types: impl IntoIterator<Item = ast::EntityType>,
76        descendants: impl IntoIterator<Item = EntityUID>,
77        context: Type,
78        attribute_types: Attributes,
79        attributes: BTreeMap<SmolStr, PartialValue>,
80        loc: Option<Loc>,
81    ) -> Self {
82        Self {
83            name,
84            applies_to: ValidatorApplySpec::new(
85                principal_entity_types.into_iter().collect(),
86                resource_entity_types.into_iter().collect(),
87            ),
88            descendants: descendants.into_iter().collect(),
89            context,
90            attribute_types,
91            attributes,
92            loc,
93        }
94    }
95
96    /// The name of the action
97    pub fn name(&self) -> &EntityUID {
98        &self.name
99    }
100
101    /// The source location if available
102    pub fn loc(&self) -> Option<&Loc> {
103        self.loc.as_ref()
104    }
105
106    /// Iterator over the actions that are members of this action
107    pub fn descendants(&self) -> impl Iterator<Item = &EntityUID> {
108        self.descendants.iter()
109    }
110
111    /// Context type for this action
112    pub fn context(&self) -> &Type {
113        &self.context
114    }
115
116    /// Returns an iterator over all the principals that this action applies to
117    pub fn principals(&self) -> impl Iterator<Item = &EntityType> {
118        self.applies_to.principal_apply_spec.iter()
119    }
120
121    /// Returns an iterator over all the resources that this action applies to
122    pub fn resources(&self) -> impl Iterator<Item = &EntityType> {
123        self.applies_to.resource_apply_spec.iter()
124    }
125
126    /// The `Type` that this action requires for its context.
127    ///
128    /// This always returns a closed record type.
129    pub fn context_type(&self) -> &Type {
130        &self.context
131    }
132
133    /// The [`ast::EntityType`]s that can be the `principal` for this action.
134    pub fn applies_to_principals(&self) -> impl Iterator<Item = &ast::EntityType> {
135        self.applies_to.applicable_principal_types()
136    }
137
138    /// The [`ast::EntityType`]s that can be the `resource` for this action.
139    pub fn applies_to_resources(&self) -> impl Iterator<Item = &ast::EntityType> {
140        self.applies_to.applicable_resource_types()
141    }
142
143    /// Is the given principal type applicable for this spec?
144    pub fn is_applicable_principal_type(&self, ty: &ast::EntityType) -> bool {
145        self.applies_to.is_applicable_principal_type(ty)
146    }
147
148    /// Is the given resource type applicable for this spec?
149    pub fn is_applicable_resource_type(&self, ty: &ast::EntityType) -> bool {
150        self.applies_to.is_applicable_resource_type(ty)
151    }
152
153    /// Attribute types for this action
154    pub fn attribute_types(&self) -> &Attributes {
155        &self.attribute_types
156    }
157
158    /// Attribute values for this action
159    pub fn attributes(&self) -> impl Iterator<Item = (&SmolStr, &PartialValue)> {
160        self.attributes.iter()
161    }
162}
163
164impl TCNode<EntityUID> for ValidatorActionId {
165    fn get_key(&self) -> EntityUID {
166        self.name.clone()
167    }
168
169    fn add_edge_to(&mut self, k: EntityUID) {
170        self.descendants.insert(k);
171    }
172
173    fn out_edges(&self) -> Box<dyn Iterator<Item = &EntityUID> + '_> {
174        Box::new(self.descendants.iter())
175    }
176
177    fn has_edge_to(&self, e: &EntityUID) -> bool {
178        self.descendants.contains(e)
179    }
180
181    // No-op as schema based TCs do not update
182    fn reset_edges(&mut self) {}
183}
184
185/// The principals and resources that an action can be applied to.
186///
187/// The parameter `N` represents the type of entity type names stored in this
188/// [`ValidatorApplySpec`]. For instance, this could be [`crate::validator::RawName`],
189/// [`crate::validator::ConditionalName`], or [`InternalName`], depending on whether the
190/// names have been resolved into fully-qualified names yet.
191/// (It could also in principle be [`ast::EntityType`], which like
192/// [`InternalName`] and [`Name`] always represents a fully-qualified name, but
193/// as of this writing we always use [`Name`] or [`InternalName`] for the
194/// parameter here when we want to indicate names have been fully qualified.)
195#[derive(Clone, Debug)]
196pub(crate) struct ValidatorApplySpec<N> {
197    /// The principal entity types the action can be applied to.
198    principal_apply_spec: HashSet<N>,
199
200    /// The resource entity types the action can be applied to.
201    resource_apply_spec: HashSet<N>,
202}
203
204impl<N> ValidatorApplySpec<N> {
205    /// Create an apply spec for an action that can only be applied to some
206    /// specific entities.
207    pub fn new(principal_apply_spec: HashSet<N>, resource_apply_spec: HashSet<N>) -> Self {
208        Self {
209            principal_apply_spec,
210            resource_apply_spec,
211        }
212    }
213}
214
215impl ValidatorApplySpec<ast::EntityType> {
216    /// Is the given principal type applicable for this spec?
217    pub fn is_applicable_principal_type(&self, ty: &ast::EntityType) -> bool {
218        self.principal_apply_spec.contains(ty)
219    }
220
221    /// Get the applicable principal types for this spec.
222    pub fn applicable_principal_types(&self) -> impl Iterator<Item = &ast::EntityType> {
223        self.principal_apply_spec.iter()
224    }
225
226    /// Is the given resource type applicable for this spec?
227    pub fn is_applicable_resource_type(&self, ty: &ast::EntityType) -> bool {
228        self.resource_apply_spec.contains(ty)
229    }
230
231    /// Get the applicable resource types for this spec.
232    pub fn applicable_resource_types(&self) -> impl Iterator<Item = &ast::EntityType> {
233        self.resource_apply_spec.iter()
234    }
235}
236
237impl ValidatorApplySpec<ConditionalName> {
238    /// Convert this [`ValidatorApplySpec<ConditionalName>`] into a
239    /// [`ValidatorApplySpec<ast::EntityType>`] by fully-qualifying all
240    /// typenames that appear anywhere in any definitions, and checking that
241    /// none of these typenames contain `__cedar`.
242    ///
243    /// `all_defs` needs to contain the full set of all fully-qualified typenames
244    /// and actions that are defined in the schema (in all schema fragments).
245    pub fn fully_qualify_type_references(
246        self,
247        all_defs: &AllDefs,
248    ) -> Result<ValidatorApplySpec<ast::EntityType>, crate::validator::schema::SchemaError> {
249        let principal_apply_spec = self
250            .principal_apply_spec
251            .into_iter()
252            .map(|cname| {
253                let internal_name = cname.resolve(all_defs)?;
254                internal_name_to_entity_type(internal_name).map_err(Into::into)
255            })
256            .partition_nonempty();
257        let resource_apply_spec = self
258            .resource_apply_spec
259            .into_iter()
260            .map(|cname| {
261                let internal_name = cname.resolve(all_defs)?;
262                internal_name_to_entity_type(internal_name).map_err(Into::into)
263            })
264            .partition_nonempty();
265
266        match (principal_apply_spec, resource_apply_spec) {
267            (Ok(principal_apply_spec), Ok(resource_apply_spec)) => Ok(ValidatorApplySpec {
268                principal_apply_spec,
269                resource_apply_spec,
270            }),
271            (Ok(_), Err(errs)) => Err(SchemaError::join_nonempty(errs)),
272            (Err(resource_errs), Ok(_)) => Err(SchemaError::join_nonempty(resource_errs)),
273            (Err(principal_errs), Err(resource_errs)) => {
274                let mut errs = principal_errs;
275                errs.extend(resource_errs);
276                Err(SchemaError::join_nonempty(errs))
277            }
278        }
279    }
280}
281
282#[cfg(test)]
283mod test {
284    use super::*;
285
286    fn make_action() -> ValidatorActionId {
287        ValidatorActionId {
288            name: r#"Action::"foo""#.parse().unwrap(),
289            applies_to: ValidatorApplySpec {
290                principal_apply_spec: HashSet::from([
291                    // Make sure duplicates are handled as expected
292                    "User".parse().unwrap(),
293                    "User".parse().unwrap(),
294                ]),
295                resource_apply_spec: HashSet::from([
296                    "App".parse().unwrap(),
297                    "File".parse().unwrap(),
298                ]),
299            },
300            descendants: HashSet::new(),
301            context: Type::any_record(),
302            attribute_types: Attributes::default(),
303            attributes: BTreeMap::default(),
304            loc: None,
305        }
306    }
307
308    #[test]
309    fn test_resources() {
310        let a = make_action();
311        let got = a.resources().cloned().collect::<HashSet<EntityType>>();
312        let expected = HashSet::from(["App".parse().unwrap(), "File".parse().unwrap()]);
313        assert_eq!(got, expected);
314    }
315
316    #[test]
317    fn test_principals() {
318        let a = make_action();
319        let got = a.principals().cloned().collect::<Vec<EntityType>>();
320        let expected: [EntityType; 1] = ["User".parse().unwrap()];
321        assert_eq!(got, &expected);
322    }
323}