cedar_policy_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 cedar_policy_core::{
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::{
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    pub fn new(
72        name: EntityUID,
73        principal_entity_types: impl IntoIterator<Item = ast::EntityType>,
74        resource_entity_types: impl IntoIterator<Item = ast::EntityType>,
75        descendants: impl IntoIterator<Item = EntityUID>,
76        context: Type,
77        attribute_types: Attributes,
78        attributes: BTreeMap<SmolStr, PartialValue>,
79        loc: Option<Loc>,
80    ) -> Self {
81        Self {
82            name,
83            applies_to: ValidatorApplySpec::new(
84                principal_entity_types.into_iter().collect(),
85                resource_entity_types.into_iter().collect(),
86            ),
87            descendants: descendants.into_iter().collect(),
88            context,
89            attribute_types,
90            attributes,
91            loc,
92        }
93    }
94
95    /// The name of the action
96    pub fn name(&self) -> &EntityUID {
97        &self.name
98    }
99
100    /// The source location if available
101    pub fn loc(&self) -> Option<&Loc> {
102        self.loc.as_ref()
103    }
104
105    /// Iterator over the actions that are members of this action
106    pub fn descendants(&self) -> impl Iterator<Item = &EntityUID> {
107        self.descendants.iter()
108    }
109
110    /// Context type for this action
111    pub fn context(&self) -> &Type {
112        &self.context
113    }
114
115    /// Returns an iterator over all the principals that this action applies to
116    pub fn principals(&self) -> impl Iterator<Item = &EntityType> {
117        self.applies_to.principal_apply_spec.iter()
118    }
119
120    /// Returns an iterator over all the resources that this action applies to
121    pub fn resources(&self) -> impl Iterator<Item = &EntityType> {
122        self.applies_to.resource_apply_spec.iter()
123    }
124
125    /// The `Type` that this action requires for its context.
126    ///
127    /// This always returns a closed record type.
128    pub fn context_type(&self) -> &Type {
129        &self.context
130    }
131
132    /// The [`ast::EntityType`]s that can be the `principal` for this action.
133    pub fn applies_to_principals(&self) -> impl Iterator<Item = &ast::EntityType> {
134        self.applies_to.applicable_principal_types()
135    }
136
137    /// The [`ast::EntityType`]s that can be the `resource` for this action.
138    pub fn applies_to_resources(&self) -> impl Iterator<Item = &ast::EntityType> {
139        self.applies_to.applicable_resource_types()
140    }
141
142    /// Is the given principal type applicable for this spec?
143    pub fn is_applicable_principal_type(&self, ty: &ast::EntityType) -> bool {
144        self.applies_to.is_applicable_principal_type(ty)
145    }
146
147    /// Is the given resource type applicable for this spec?
148    pub fn is_applicable_resource_type(&self, ty: &ast::EntityType) -> bool {
149        self.applies_to.is_applicable_resource_type(ty)
150    }
151
152    /// Attribute types for this action
153    pub fn attribute_types(&self) -> &Attributes {
154        &self.attribute_types
155    }
156
157    /// Attribute values for this action
158    pub fn attributes(&self) -> impl Iterator<Item = (&SmolStr, &PartialValue)> {
159        self.attributes.iter()
160    }
161}
162
163impl TCNode<EntityUID> for ValidatorActionId {
164    fn get_key(&self) -> EntityUID {
165        self.name.clone()
166    }
167
168    fn add_edge_to(&mut self, k: EntityUID) {
169        self.descendants.insert(k);
170    }
171
172    fn out_edges(&self) -> Box<dyn Iterator<Item = &EntityUID> + '_> {
173        Box::new(self.descendants.iter())
174    }
175
176    fn has_edge_to(&self, e: &EntityUID) -> bool {
177        self.descendants.contains(e)
178    }
179}
180
181/// The principals and resources that an action can be applied to.
182///
183/// The parameter `N` represents the type of entity type names stored in this
184/// [`ValidatorApplySpec`]. For instance, this could be [`crate::RawName`],
185/// [`crate::ConditionalName`], or [`InternalName`], depending on whether the
186/// names have been resolved into fully-qualified names yet.
187/// (It could also in principle be [`ast::EntityType`], which like
188/// [`InternalName`] and [`Name`] always represents a fully-qualified name, but
189/// as of this writing we always use [`Name`] or [`InternalName`] for the
190/// parameter here when we want to indicate names have been fully qualified.)
191#[derive(Clone, Debug)]
192pub(crate) struct ValidatorApplySpec<N> {
193    /// The principal entity types the action can be applied to.
194    principal_apply_spec: HashSet<N>,
195
196    /// The resource entity types the action can be applied to.
197    resource_apply_spec: HashSet<N>,
198}
199
200impl<N> ValidatorApplySpec<N> {
201    /// Create an apply spec for an action that can only be applied to some
202    /// specific entities.
203    pub fn new(principal_apply_spec: HashSet<N>, resource_apply_spec: HashSet<N>) -> Self {
204        Self {
205            principal_apply_spec,
206            resource_apply_spec,
207        }
208    }
209}
210
211impl ValidatorApplySpec<ast::EntityType> {
212    /// Is the given principal type applicable for this spec?
213    pub fn is_applicable_principal_type(&self, ty: &ast::EntityType) -> bool {
214        self.principal_apply_spec.contains(ty)
215    }
216
217    /// Get the applicable principal types for this spec.
218    pub fn applicable_principal_types(&self) -> impl Iterator<Item = &ast::EntityType> {
219        self.principal_apply_spec.iter()
220    }
221
222    /// Is the given resource type applicable for this spec?
223    pub fn is_applicable_resource_type(&self, ty: &ast::EntityType) -> bool {
224        self.resource_apply_spec.contains(ty)
225    }
226
227    /// Get the applicable resource types for this spec.
228    pub fn applicable_resource_types(&self) -> impl Iterator<Item = &ast::EntityType> {
229        self.resource_apply_spec.iter()
230    }
231}
232
233impl ValidatorApplySpec<ConditionalName> {
234    /// Convert this [`ValidatorApplySpec<ConditionalName>`] into a
235    /// [`ValidatorApplySpec<ast::EntityType>`] by fully-qualifying all
236    /// typenames that appear anywhere in any definitions, and checking that
237    /// none of these typenames contain `__cedar`.
238    ///
239    /// `all_defs` needs to contain the full set of all fully-qualified typenames
240    /// and actions that are defined in the schema (in all schema fragments).
241    pub fn fully_qualify_type_references(
242        self,
243        all_defs: &AllDefs,
244    ) -> Result<ValidatorApplySpec<ast::EntityType>, crate::schema::SchemaError> {
245        let principal_apply_spec = self
246            .principal_apply_spec
247            .into_iter()
248            .map(|cname| {
249                let internal_name = cname.resolve(all_defs)?;
250                internal_name_to_entity_type(internal_name).map_err(Into::into)
251            })
252            .partition_nonempty();
253        let resource_apply_spec = self
254            .resource_apply_spec
255            .into_iter()
256            .map(|cname| {
257                let internal_name = cname.resolve(all_defs)?;
258                internal_name_to_entity_type(internal_name).map_err(Into::into)
259            })
260            .partition_nonempty();
261
262        match (principal_apply_spec, resource_apply_spec) {
263            (Ok(principal_apply_spec), Ok(resource_apply_spec)) => Ok(ValidatorApplySpec {
264                principal_apply_spec,
265                resource_apply_spec,
266            }),
267            (Ok(_), Err(errs)) => Err(SchemaError::join_nonempty(errs)),
268            (Err(resource_errs), Ok(_)) => Err(SchemaError::join_nonempty(resource_errs)),
269            (Err(principal_errs), Err(resource_errs)) => {
270                let mut errs = principal_errs;
271                errs.extend(resource_errs);
272                Err(SchemaError::join_nonempty(errs))
273            }
274        }
275    }
276}
277
278#[cfg(test)]
279mod test {
280    use super::*;
281
282    fn make_action() -> ValidatorActionId {
283        ValidatorActionId {
284            name: r#"Action::"foo""#.parse().unwrap(),
285            applies_to: ValidatorApplySpec {
286                principal_apply_spec: HashSet::from([
287                    // Make sure duplicates are handled as expected
288                    "User".parse().unwrap(),
289                    "User".parse().unwrap(),
290                ]),
291                resource_apply_spec: HashSet::from([
292                    "App".parse().unwrap(),
293                    "File".parse().unwrap(),
294                ]),
295            },
296            descendants: HashSet::new(),
297            context: Type::any_record(),
298            attribute_types: Attributes::default(),
299            attributes: BTreeMap::default(),
300            loc: None,
301        }
302    }
303
304    #[test]
305    fn test_resources() {
306        let a = make_action();
307        let got = a.resources().cloned().collect::<HashSet<EntityType>>();
308        let expected = HashSet::from(["App".parse().unwrap(), "File".parse().unwrap()]);
309        assert_eq!(got, expected);
310    }
311
312    #[test]
313    fn test_principals() {
314        let a = make_action();
315        let got = a.principals().cloned().collect::<Vec<EntityType>>();
316        let expected: [EntityType; 1] = ["User".parse().unwrap()];
317        assert_eq!(got, &expected);
318    }
319}