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