Skip to main content

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