cedar_policy_core/validator/schema/
action.rs1use 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#[derive(Educe, Clone, Debug)]
39#[educe(PartialEq, Eq)]
40pub struct ValidatorActionId {
41 pub(crate) name: EntityUID,
43
44 pub(crate) applies_to: ValidatorApplySpec<ast::EntityType>,
46
47 pub(crate) descendants: HashSet<EntityUID>,
52
53 pub(crate) context: Type,
55
56 #[educe(PartialEq(ignore))]
58 pub(crate) loc: Option<Loc>,
59}
60
61impl ValidatorActionId {
62 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 pub fn name(&self) -> &EntityUID {
88 &self.name
89 }
90
91 pub fn loc(&self) -> Option<&Loc> {
93 self.loc.as_ref()
94 }
95
96 pub fn descendants(&self) -> impl Iterator<Item = &EntityUID> {
98 self.descendants.iter()
99 }
100
101 pub fn context(&self) -> &Type {
103 &self.context
104 }
105
106 pub fn principals(&self) -> impl Iterator<Item = &EntityType> {
108 self.applies_to.principal_apply_spec.iter()
109 }
110
111 pub fn resources(&self) -> impl Iterator<Item = &EntityType> {
113 self.applies_to.resource_apply_spec.iter()
114 }
115
116 pub fn context_type(&self) -> &Type {
120 &self.context
121 }
122
123 pub fn applies_to_principals(&self) -> impl Iterator<Item = &ast::EntityType> {
125 self.applies_to.applicable_principal_types()
126 }
127
128 pub fn applies_to_resources(&self) -> impl Iterator<Item = &ast::EntityType> {
130 self.applies_to.applicable_resource_types()
131 }
132
133 pub fn is_applicable_principal_type(&self, ty: &ast::EntityType) -> bool {
135 self.applies_to.is_applicable_principal_type(ty)
136 }
137
138 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 fn reset_edges(&mut self) {}
163}
164
165#[derive(Clone, Debug)]
176pub(crate) struct ValidatorApplySpec<N> {
177 principal_apply_spec: HashSet<N>,
179
180 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 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 pub fn is_applicable_principal_type(&self, ty: &ast::EntityType) -> bool {
207 self.principal_apply_spec.contains(ty)
208 }
209
210 pub fn applicable_principal_types(&self) -> impl Iterator<Item = &ast::EntityType> {
212 self.principal_apply_spec.iter()
213 }
214
215 pub fn is_applicable_resource_type(&self, ty: &ast::EntityType) -> bool {
217 self.resource_apply_spec.contains(ty)
218 }
219
220 pub fn applicable_resource_types(&self) -> impl Iterator<Item = &ast::EntityType> {
222 self.resource_apply_spec.iter()
223 }
224}
225
226impl ValidatorApplySpec<ConditionalName> {
227 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 "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}