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::{EntityType, EntityUID, PartialValueSerializedAsExpr},
21    transitive_closure::TCNode,
22};
23use serde::Serialize;
24use smol_str::SmolStr;
25use std::collections::{BTreeMap, HashSet};
26
27use crate::types::{Attributes, Type};
28
29/// Contains information about actions used by the validator.  The contents of
30/// the struct are the same as the schema entity type structure, but the
31/// `member_of` relation is reversed to instead be `descendants`.
32#[derive(Clone, Debug, Serialize)]
33pub struct ValidatorActionId {
34    /// The name of the action.
35    pub(crate) name: EntityUID,
36
37    /// The principals and resources that the action can be applied to.
38    #[serde(rename = "appliesTo")]
39    pub(crate) applies_to: ValidatorApplySpec,
40
41    /// The set of actions that can be members of this action. When this
42    /// structure is initially constructed, the field will contain direct
43    /// children, but it will be updated to contain the closure of all
44    /// descendants before it is used in any validation.
45    pub(crate) descendants: HashSet<EntityUID>,
46
47    /// The type of the context record associated with this action.
48    pub(crate) context: Type,
49
50    /// The attribute types for this action, used for typechecking.
51    pub(crate) attribute_types: Attributes,
52
53    /// The actual attribute value for this action, used to construct an
54    /// `Entity` for this action. Could also be used for more precise
55    /// typechecking by partial evaluation.
56    ///
57    /// Attributes are serialized as `RestrictedExpr`s, so that roundtripping
58    /// works seamlessly.
59    pub(crate) attributes: BTreeMap<SmolStr, PartialValueSerializedAsExpr>,
60}
61
62impl ValidatorActionId {
63    /// Returns an iterator over all the principals that this action applies to
64    pub fn principals(&self) -> impl Iterator<Item = &EntityType> {
65        self.applies_to.principal_apply_spec.iter()
66    }
67
68    /// Returns an iterator over all the resources that this action applies to
69    pub fn resources(&self) -> impl Iterator<Item = &EntityType> {
70        self.applies_to.resource_apply_spec.iter()
71    }
72
73    /// The `Type` that this action requires for its context.
74    ///
75    /// This always returns a closed record type.
76    pub fn context_type(&self) -> Type {
77        self.context.clone()
78    }
79
80    /// The `EntityType`s that can be the `principal` for this action.
81    pub fn applies_to_principals(&self) -> impl Iterator<Item = &EntityType> {
82        self.applies_to.principal_apply_spec.iter()
83    }
84
85    /// The `EntityType`s that can be the `resource` for this action.
86    pub fn applies_to_resources(&self) -> impl Iterator<Item = &EntityType> {
87        self.applies_to.resource_apply_spec.iter()
88    }
89}
90
91impl TCNode<EntityUID> for ValidatorActionId {
92    fn get_key(&self) -> EntityUID {
93        self.name.clone()
94    }
95
96    fn add_edge_to(&mut self, k: EntityUID) {
97        self.descendants.insert(k);
98    }
99
100    fn out_edges(&self) -> Box<dyn Iterator<Item = &EntityUID> + '_> {
101        Box::new(self.descendants.iter())
102    }
103
104    fn has_edge_to(&self, e: &EntityUID) -> bool {
105        self.descendants.contains(e)
106    }
107}
108
109/// The principals and resources that an action can be applied to.
110#[derive(Clone, Debug, Serialize)]
111pub(crate) struct ValidatorApplySpec {
112    /// The principal entity types the action can be applied to. This set may
113    /// be a singleton set containing the unspecified entity type when the
114    /// `principalTypes` list is omitted in the schema. A non-singleton set
115    /// shouldn't contain the unspecified entity type, but (policy) validation
116    /// will give the same success/failure result as when it is the only element
117    /// of the set, perhaps with extra type errors.
118    #[serde(rename = "principalApplySpec")]
119    principal_apply_spec: HashSet<EntityType>,
120
121    /// The resource entity types the action can be applied to. See comments on
122    /// `principal_apply_spec` about the unspecified entity type.
123    #[serde(rename = "resourceApplySpec")]
124    resource_apply_spec: HashSet<EntityType>,
125}
126
127impl ValidatorApplySpec {
128    /// Create an apply spec for an action that can only be applied to some
129    /// specific entities.
130    pub fn new(
131        principal_apply_spec: HashSet<EntityType>,
132        resource_apply_spec: HashSet<EntityType>,
133    ) -> Self {
134        Self {
135            principal_apply_spec,
136            resource_apply_spec,
137        }
138    }
139
140    /// Is the given principal type applicable for this spec?
141    pub fn is_applicable_principal_type(&self, ty: &EntityType) -> bool {
142        self.principal_apply_spec.contains(ty)
143    }
144
145    /// Get the applicable principal types for this spec.
146    pub fn applicable_principal_types(&self) -> impl Iterator<Item = &EntityType> {
147        self.principal_apply_spec.iter()
148    }
149
150    /// Is the given resource type applicable for this spec?
151    pub fn is_applicable_resource_type(&self, ty: &EntityType) -> bool {
152        self.resource_apply_spec.contains(ty)
153    }
154
155    /// Get the applicable resource types for this spec.
156    pub fn applicable_resource_types(&self) -> impl Iterator<Item = &EntityType> {
157        self.resource_apply_spec.iter()
158    }
159}
160
161#[cfg(test)]
162mod test {
163    use super::*;
164
165    fn make_action() -> ValidatorActionId {
166        ValidatorActionId {
167            name: r#"Action::"foo""#.parse().unwrap(),
168            applies_to: ValidatorApplySpec {
169                principal_apply_spec: HashSet::from([
170                    // Make sure duplicates are handled as expected
171                    EntityType::Specified("User".parse().unwrap()),
172                    EntityType::Specified("User".parse().unwrap()),
173                ]),
174                resource_apply_spec: HashSet::from([
175                    EntityType::Specified("App".parse().unwrap()),
176                    EntityType::Specified("File".parse().unwrap()),
177                ]),
178            },
179            descendants: HashSet::new(),
180            context: Type::any_record(),
181            attribute_types: Attributes::default(),
182            attributes: BTreeMap::default(),
183        }
184    }
185
186    #[test]
187    fn test_resources() {
188        let a = make_action();
189        let got = a.resources().cloned().collect::<HashSet<EntityType>>();
190        let expected = HashSet::from([
191            EntityType::Specified("App".parse().unwrap()),
192            EntityType::Specified("File".parse().unwrap()),
193        ]);
194        assert_eq!(got, expected);
195    }
196
197    #[test]
198    fn test_principals() {
199        let a = make_action();
200        let got = a.principals().cloned().collect::<Vec<EntityType>>();
201        let expected: [EntityType; 1] = [EntityType::Specified("User".parse().unwrap())];
202        assert_eq!(got, &expected);
203    }
204}