cedar_policy_validator/schema/
action.rs1use cedar_policy_core::{
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::{
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 pub fn new(
72 name: EntityUID,
73 principal_entity_types: impl IntoIterator<Item = ast::EntityType>,
74 resource_entity_types: impl IntoIterator<Item = ast::EntityType>,
75 descendants: impl IntoIterator<Item = EntityUID>,
76 context: Type,
77 attribute_types: Attributes,
78 attributes: BTreeMap<SmolStr, PartialValue>,
79 loc: Option<Loc>,
80 ) -> Self {
81 Self {
82 name,
83 applies_to: ValidatorApplySpec::new(
84 principal_entity_types.into_iter().collect(),
85 resource_entity_types.into_iter().collect(),
86 ),
87 descendants: descendants.into_iter().collect(),
88 context,
89 attribute_types,
90 attributes,
91 loc,
92 }
93 }
94
95 pub fn name(&self) -> &EntityUID {
97 &self.name
98 }
99
100 pub fn loc(&self) -> Option<&Loc> {
102 self.loc.as_ref()
103 }
104
105 pub fn descendants(&self) -> impl Iterator<Item = &EntityUID> {
107 self.descendants.iter()
108 }
109
110 pub fn context(&self) -> &Type {
112 &self.context
113 }
114
115 pub fn principals(&self) -> impl Iterator<Item = &EntityType> {
117 self.applies_to.principal_apply_spec.iter()
118 }
119
120 pub fn resources(&self) -> impl Iterator<Item = &EntityType> {
122 self.applies_to.resource_apply_spec.iter()
123 }
124
125 pub fn context_type(&self) -> &Type {
129 &self.context
130 }
131
132 pub fn applies_to_principals(&self) -> impl Iterator<Item = &ast::EntityType> {
134 self.applies_to.applicable_principal_types()
135 }
136
137 pub fn applies_to_resources(&self) -> impl Iterator<Item = &ast::EntityType> {
139 self.applies_to.applicable_resource_types()
140 }
141
142 pub fn is_applicable_principal_type(&self, ty: &ast::EntityType) -> bool {
144 self.applies_to.is_applicable_principal_type(ty)
145 }
146
147 pub fn is_applicable_resource_type(&self, ty: &ast::EntityType) -> bool {
149 self.applies_to.is_applicable_resource_type(ty)
150 }
151
152 pub fn attribute_types(&self) -> &Attributes {
154 &self.attribute_types
155 }
156
157 pub fn attributes(&self) -> impl Iterator<Item = (&SmolStr, &PartialValue)> {
159 self.attributes.iter()
160 }
161}
162
163impl TCNode<EntityUID> for ValidatorActionId {
164 fn get_key(&self) -> EntityUID {
165 self.name.clone()
166 }
167
168 fn add_edge_to(&mut self, k: EntityUID) {
169 self.descendants.insert(k);
170 }
171
172 fn out_edges(&self) -> Box<dyn Iterator<Item = &EntityUID> + '_> {
173 Box::new(self.descendants.iter())
174 }
175
176 fn has_edge_to(&self, e: &EntityUID) -> bool {
177 self.descendants.contains(e)
178 }
179}
180
181#[derive(Clone, Debug)]
192pub(crate) struct ValidatorApplySpec<N> {
193 principal_apply_spec: HashSet<N>,
195
196 resource_apply_spec: HashSet<N>,
198}
199
200impl<N> ValidatorApplySpec<N> {
201 pub fn new(principal_apply_spec: HashSet<N>, resource_apply_spec: HashSet<N>) -> Self {
204 Self {
205 principal_apply_spec,
206 resource_apply_spec,
207 }
208 }
209}
210
211impl ValidatorApplySpec<ast::EntityType> {
212 pub fn is_applicable_principal_type(&self, ty: &ast::EntityType) -> bool {
214 self.principal_apply_spec.contains(ty)
215 }
216
217 pub fn applicable_principal_types(&self) -> impl Iterator<Item = &ast::EntityType> {
219 self.principal_apply_spec.iter()
220 }
221
222 pub fn is_applicable_resource_type(&self, ty: &ast::EntityType) -> bool {
224 self.resource_apply_spec.contains(ty)
225 }
226
227 pub fn applicable_resource_types(&self) -> impl Iterator<Item = &ast::EntityType> {
229 self.resource_apply_spec.iter()
230 }
231}
232
233impl ValidatorApplySpec<ConditionalName> {
234 pub fn fully_qualify_type_references(
242 self,
243 all_defs: &AllDefs,
244 ) -> Result<ValidatorApplySpec<ast::EntityType>, crate::schema::SchemaError> {
245 let principal_apply_spec = self
246 .principal_apply_spec
247 .into_iter()
248 .map(|cname| {
249 let internal_name = cname.resolve(all_defs)?;
250 internal_name_to_entity_type(internal_name).map_err(Into::into)
251 })
252 .partition_nonempty();
253 let resource_apply_spec = self
254 .resource_apply_spec
255 .into_iter()
256 .map(|cname| {
257 let internal_name = cname.resolve(all_defs)?;
258 internal_name_to_entity_type(internal_name).map_err(Into::into)
259 })
260 .partition_nonempty();
261
262 match (principal_apply_spec, resource_apply_spec) {
263 (Ok(principal_apply_spec), Ok(resource_apply_spec)) => Ok(ValidatorApplySpec {
264 principal_apply_spec,
265 resource_apply_spec,
266 }),
267 (Ok(_), Err(errs)) => Err(SchemaError::join_nonempty(errs)),
268 (Err(resource_errs), Ok(_)) => Err(SchemaError::join_nonempty(resource_errs)),
269 (Err(principal_errs), Err(resource_errs)) => {
270 let mut errs = principal_errs;
271 errs.extend(resource_errs);
272 Err(SchemaError::join_nonempty(errs))
273 }
274 }
275 }
276}
277
278#[cfg(test)]
279mod test {
280 use super::*;
281
282 fn make_action() -> ValidatorActionId {
283 ValidatorActionId {
284 name: r#"Action::"foo""#.parse().unwrap(),
285 applies_to: ValidatorApplySpec {
286 principal_apply_spec: HashSet::from([
287 "User".parse().unwrap(),
289 "User".parse().unwrap(),
290 ]),
291 resource_apply_spec: HashSet::from([
292 "App".parse().unwrap(),
293 "File".parse().unwrap(),
294 ]),
295 },
296 descendants: HashSet::new(),
297 context: Type::any_record(),
298 attribute_types: Attributes::default(),
299 attributes: BTreeMap::default(),
300 loc: None,
301 }
302 }
303
304 #[test]
305 fn test_resources() {
306 let a = make_action();
307 let got = a.resources().cloned().collect::<HashSet<EntityType>>();
308 let expected = HashSet::from(["App".parse().unwrap(), "File".parse().unwrap()]);
309 assert_eq!(got, expected);
310 }
311
312 #[test]
313 fn test_principals() {
314 let a = make_action();
315 let got = a.principals().cloned().collect::<Vec<EntityType>>();
316 let expected: [EntityType; 1] = ["User".parse().unwrap()];
317 assert_eq!(got, &expected);
318 }
319}