cedar_policy_core/validator/schema/
action.rs1use crate::{
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::validator::{
29 partition_nonempty::PartitionNonEmpty,
30 schema::{AllDefs, SchemaError},
31 types::{Attributes, Type},
32 ConditionalName,
33};
34
35#[derive(Clone, Debug)]
39pub struct ValidatorActionId {
40 pub(crate) name: EntityUID,
42
43 pub(crate) applies_to: ValidatorApplySpec<ast::EntityType>,
45
46 pub(crate) descendants: HashSet<EntityUID>,
51
52 pub(crate) context: Type,
54
55 pub(crate) attribute_types: Attributes,
57
58 pub(crate) attributes: BTreeMap<SmolStr, PartialValue>,
62 pub(crate) loc: Option<Loc>,
64}
65
66impl ValidatorActionId {
67 #[allow(clippy::too_many_arguments)]
72 pub fn new(
73 name: EntityUID,
74 principal_entity_types: impl IntoIterator<Item = ast::EntityType>,
75 resource_entity_types: impl IntoIterator<Item = ast::EntityType>,
76 descendants: impl IntoIterator<Item = EntityUID>,
77 context: Type,
78 attribute_types: Attributes,
79 attributes: BTreeMap<SmolStr, PartialValue>,
80 loc: Option<Loc>,
81 ) -> Self {
82 Self {
83 name,
84 applies_to: ValidatorApplySpec::new(
85 principal_entity_types.into_iter().collect(),
86 resource_entity_types.into_iter().collect(),
87 ),
88 descendants: descendants.into_iter().collect(),
89 context,
90 attribute_types,
91 attributes,
92 loc,
93 }
94 }
95
96 pub fn name(&self) -> &EntityUID {
98 &self.name
99 }
100
101 pub fn loc(&self) -> Option<&Loc> {
103 self.loc.as_ref()
104 }
105
106 pub fn descendants(&self) -> impl Iterator<Item = &EntityUID> {
108 self.descendants.iter()
109 }
110
111 pub fn context(&self) -> &Type {
113 &self.context
114 }
115
116 pub fn principals(&self) -> impl Iterator<Item = &EntityType> {
118 self.applies_to.principal_apply_spec.iter()
119 }
120
121 pub fn resources(&self) -> impl Iterator<Item = &EntityType> {
123 self.applies_to.resource_apply_spec.iter()
124 }
125
126 pub fn context_type(&self) -> &Type {
130 &self.context
131 }
132
133 pub fn applies_to_principals(&self) -> impl Iterator<Item = &ast::EntityType> {
135 self.applies_to.applicable_principal_types()
136 }
137
138 pub fn applies_to_resources(&self) -> impl Iterator<Item = &ast::EntityType> {
140 self.applies_to.applicable_resource_types()
141 }
142
143 pub fn is_applicable_principal_type(&self, ty: &ast::EntityType) -> bool {
145 self.applies_to.is_applicable_principal_type(ty)
146 }
147
148 pub fn is_applicable_resource_type(&self, ty: &ast::EntityType) -> bool {
150 self.applies_to.is_applicable_resource_type(ty)
151 }
152
153 pub fn attribute_types(&self) -> &Attributes {
155 &self.attribute_types
156 }
157
158 pub fn attributes(&self) -> impl Iterator<Item = (&SmolStr, &PartialValue)> {
160 self.attributes.iter()
161 }
162}
163
164impl TCNode<EntityUID> for ValidatorActionId {
165 fn get_key(&self) -> EntityUID {
166 self.name.clone()
167 }
168
169 fn add_edge_to(&mut self, k: EntityUID) {
170 self.descendants.insert(k);
171 }
172
173 fn out_edges(&self) -> Box<dyn Iterator<Item = &EntityUID> + '_> {
174 Box::new(self.descendants.iter())
175 }
176
177 fn has_edge_to(&self, e: &EntityUID) -> bool {
178 self.descendants.contains(e)
179 }
180
181 fn reset_edges(&mut self) {}
183}
184
185#[derive(Clone, Debug)]
196pub(crate) struct ValidatorApplySpec<N> {
197 principal_apply_spec: HashSet<N>,
199
200 resource_apply_spec: HashSet<N>,
202}
203
204impl<N> ValidatorApplySpec<N> {
205 pub fn new(principal_apply_spec: HashSet<N>, resource_apply_spec: HashSet<N>) -> Self {
208 Self {
209 principal_apply_spec,
210 resource_apply_spec,
211 }
212 }
213}
214
215impl ValidatorApplySpec<ast::EntityType> {
216 pub fn is_applicable_principal_type(&self, ty: &ast::EntityType) -> bool {
218 self.principal_apply_spec.contains(ty)
219 }
220
221 pub fn applicable_principal_types(&self) -> impl Iterator<Item = &ast::EntityType> {
223 self.principal_apply_spec.iter()
224 }
225
226 pub fn is_applicable_resource_type(&self, ty: &ast::EntityType) -> bool {
228 self.resource_apply_spec.contains(ty)
229 }
230
231 pub fn applicable_resource_types(&self) -> impl Iterator<Item = &ast::EntityType> {
233 self.resource_apply_spec.iter()
234 }
235}
236
237impl ValidatorApplySpec<ConditionalName> {
238 pub fn fully_qualify_type_references(
246 self,
247 all_defs: &AllDefs,
248 ) -> Result<ValidatorApplySpec<ast::EntityType>, crate::validator::schema::SchemaError> {
249 let principal_apply_spec = self
250 .principal_apply_spec
251 .into_iter()
252 .map(|cname| {
253 let internal_name = cname.resolve(all_defs)?;
254 internal_name_to_entity_type(internal_name).map_err(Into::into)
255 })
256 .partition_nonempty();
257 let resource_apply_spec = self
258 .resource_apply_spec
259 .into_iter()
260 .map(|cname| {
261 let internal_name = cname.resolve(all_defs)?;
262 internal_name_to_entity_type(internal_name).map_err(Into::into)
263 })
264 .partition_nonempty();
265
266 match (principal_apply_spec, resource_apply_spec) {
267 (Ok(principal_apply_spec), Ok(resource_apply_spec)) => Ok(ValidatorApplySpec {
268 principal_apply_spec,
269 resource_apply_spec,
270 }),
271 (Ok(_), Err(errs)) => Err(SchemaError::join_nonempty(errs)),
272 (Err(resource_errs), Ok(_)) => Err(SchemaError::join_nonempty(resource_errs)),
273 (Err(principal_errs), Err(resource_errs)) => {
274 let mut errs = principal_errs;
275 errs.extend(resource_errs);
276 Err(SchemaError::join_nonempty(errs))
277 }
278 }
279 }
280}
281
282#[cfg(test)]
283mod test {
284 use super::*;
285
286 fn make_action() -> ValidatorActionId {
287 ValidatorActionId {
288 name: r#"Action::"foo""#.parse().unwrap(),
289 applies_to: ValidatorApplySpec {
290 principal_apply_spec: HashSet::from([
291 "User".parse().unwrap(),
293 "User".parse().unwrap(),
294 ]),
295 resource_apply_spec: HashSet::from([
296 "App".parse().unwrap(),
297 "File".parse().unwrap(),
298 ]),
299 },
300 descendants: HashSet::new(),
301 context: Type::any_record(),
302 attribute_types: Attributes::default(),
303 attributes: BTreeMap::default(),
304 loc: None,
305 }
306 }
307
308 #[test]
309 fn test_resources() {
310 let a = make_action();
311 let got = a.resources().cloned().collect::<HashSet<EntityType>>();
312 let expected = HashSet::from(["App".parse().unwrap(), "File".parse().unwrap()]);
313 assert_eq!(got, expected);
314 }
315
316 #[test]
317 fn test_principals() {
318 let a = make_action();
319 let got = a.principals().cloned().collect::<Vec<EntityType>>();
320 let expected: [EntityType; 1] = ["User".parse().unwrap()];
321 assert_eq!(got, &expected);
322 }
323}