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