cedar_policy_core/validator/schema/
action.rs1use crate::{
20 ast::{self, EntityType, EntityUID},
21 parser::Loc,
22 transitive_closure::TCNode,
23};
24use std::collections::HashSet;
25
26use super::internal_name_to_entity_type;
27use crate::validator::{
28 partition_nonempty::PartitionNonEmpty,
29 schema::{AllDefs, SchemaError},
30 types::Type,
31 ConditionalName,
32};
33
34#[derive(Clone, Debug)]
38pub struct ValidatorActionId {
39 pub(crate) name: EntityUID,
41
42 pub(crate) applies_to: ValidatorApplySpec<ast::EntityType>,
44
45 pub(crate) descendants: HashSet<EntityUID>,
50
51 pub(crate) context: Type,
53
54 pub(crate) loc: Option<Loc>,
56}
57
58impl ValidatorActionId {
59 #[allow(clippy::too_many_arguments)]
64 pub fn new(
65 name: EntityUID,
66 principal_entity_types: impl IntoIterator<Item = ast::EntityType>,
67 resource_entity_types: impl IntoIterator<Item = ast::EntityType>,
68 descendants: impl IntoIterator<Item = EntityUID>,
69 context: Type,
70 loc: Option<Loc>,
71 ) -> Self {
72 Self {
73 name,
74 applies_to: ValidatorApplySpec::new(
75 principal_entity_types.into_iter().collect(),
76 resource_entity_types.into_iter().collect(),
77 ),
78 descendants: descendants.into_iter().collect(),
79 context,
80 loc,
81 }
82 }
83
84 pub fn name(&self) -> &EntityUID {
86 &self.name
87 }
88
89 pub fn loc(&self) -> Option<&Loc> {
91 self.loc.as_ref()
92 }
93
94 pub fn descendants(&self) -> impl Iterator<Item = &EntityUID> {
96 self.descendants.iter()
97 }
98
99 pub fn context(&self) -> &Type {
101 &self.context
102 }
103
104 pub fn principals(&self) -> impl Iterator<Item = &EntityType> {
106 self.applies_to.principal_apply_spec.iter()
107 }
108
109 pub fn resources(&self) -> impl Iterator<Item = &EntityType> {
111 self.applies_to.resource_apply_spec.iter()
112 }
113
114 pub fn context_type(&self) -> &Type {
118 &self.context
119 }
120
121 pub fn applies_to_principals(&self) -> impl Iterator<Item = &ast::EntityType> {
123 self.applies_to.applicable_principal_types()
124 }
125
126 pub fn applies_to_resources(&self) -> impl Iterator<Item = &ast::EntityType> {
128 self.applies_to.applicable_resource_types()
129 }
130
131 pub fn is_applicable_principal_type(&self, ty: &ast::EntityType) -> bool {
133 self.applies_to.is_applicable_principal_type(ty)
134 }
135
136 pub fn is_applicable_resource_type(&self, ty: &ast::EntityType) -> bool {
138 self.applies_to.is_applicable_resource_type(ty)
139 }
140}
141
142impl TCNode<EntityUID> for ValidatorActionId {
143 fn get_key(&self) -> EntityUID {
144 self.name.clone()
145 }
146
147 fn add_edge_to(&mut self, k: EntityUID) {
148 self.descendants.insert(k);
149 }
150
151 fn out_edges(&self) -> Box<dyn Iterator<Item = &EntityUID> + '_> {
152 Box::new(self.descendants.iter())
153 }
154
155 fn has_edge_to(&self, e: &EntityUID) -> bool {
156 self.descendants.contains(e)
157 }
158
159 fn reset_edges(&mut self) {}
161}
162
163#[derive(Clone, Debug)]
174pub(crate) struct ValidatorApplySpec<N> {
175 principal_apply_spec: HashSet<N>,
177
178 resource_apply_spec: HashSet<N>,
180}
181
182impl<N> ValidatorApplySpec<N> {
183 pub fn new(principal_apply_spec: HashSet<N>, resource_apply_spec: HashSet<N>) -> Self {
186 Self {
187 principal_apply_spec,
188 resource_apply_spec,
189 }
190 }
191}
192
193impl ValidatorApplySpec<ast::EntityType> {
194 pub fn is_applicable_principal_type(&self, ty: &ast::EntityType) -> bool {
196 self.principal_apply_spec.contains(ty)
197 }
198
199 pub fn applicable_principal_types(&self) -> impl Iterator<Item = &ast::EntityType> {
201 self.principal_apply_spec.iter()
202 }
203
204 pub fn is_applicable_resource_type(&self, ty: &ast::EntityType) -> bool {
206 self.resource_apply_spec.contains(ty)
207 }
208
209 pub fn applicable_resource_types(&self) -> impl Iterator<Item = &ast::EntityType> {
211 self.resource_apply_spec.iter()
212 }
213}
214
215impl ValidatorApplySpec<ConditionalName> {
216 pub fn fully_qualify_type_references(
224 self,
225 all_defs: &AllDefs,
226 ) -> Result<ValidatorApplySpec<ast::EntityType>, crate::validator::schema::SchemaError> {
227 let principal_apply_spec = self
228 .principal_apply_spec
229 .into_iter()
230 .map(|cname| {
231 let internal_name = cname.resolve(all_defs)?;
232 internal_name_to_entity_type(internal_name).map_err(Into::into)
233 })
234 .partition_nonempty();
235 let resource_apply_spec = self
236 .resource_apply_spec
237 .into_iter()
238 .map(|cname| {
239 let internal_name = cname.resolve(all_defs)?;
240 internal_name_to_entity_type(internal_name).map_err(Into::into)
241 })
242 .partition_nonempty();
243
244 match (principal_apply_spec, resource_apply_spec) {
245 (Ok(principal_apply_spec), Ok(resource_apply_spec)) => Ok(ValidatorApplySpec {
246 principal_apply_spec,
247 resource_apply_spec,
248 }),
249 (Ok(_), Err(errs)) => Err(SchemaError::join_nonempty(errs)),
250 (Err(resource_errs), Ok(_)) => Err(SchemaError::join_nonempty(resource_errs)),
251 (Err(principal_errs), Err(resource_errs)) => {
252 let mut errs = principal_errs;
253 errs.extend(resource_errs);
254 Err(SchemaError::join_nonempty(errs))
255 }
256 }
257 }
258}
259
260#[cfg(test)]
261mod test {
262 use super::*;
263
264 fn make_action() -> ValidatorActionId {
265 ValidatorActionId {
266 name: r#"Action::"foo""#.parse().unwrap(),
267 applies_to: ValidatorApplySpec {
268 principal_apply_spec: HashSet::from([
269 "User".parse().unwrap(),
271 "User".parse().unwrap(),
272 ]),
273 resource_apply_spec: HashSet::from([
274 "App".parse().unwrap(),
275 "File".parse().unwrap(),
276 ]),
277 },
278 descendants: HashSet::new(),
279 context: Type::any_record(),
280 loc: None,
281 }
282 }
283
284 #[test]
285 fn test_resources() {
286 let a = make_action();
287 let got = a.resources().cloned().collect::<HashSet<EntityType>>();
288 let expected = HashSet::from(["App".parse().unwrap(), "File".parse().unwrap()]);
289 assert_eq!(got, expected);
290 }
291
292 #[test]
293 fn test_principals() {
294 let a = make_action();
295 let got = a.principals().cloned().collect::<Vec<EntityType>>();
296 let expected: [EntityType; 1] = ["User".parse().unwrap()];
297 assert_eq!(got, &expected);
298 }
299}