cedar_policy_validator/schema/
action.rs1use cedar_policy_core::{
20 ast::{self, EntityType, EntityUID, PartialValueSerializedAsExpr},
21 transitive_closure::TCNode,
22};
23use itertools::Itertools;
24use nonempty::NonEmpty;
25use serde::Serialize;
26use smol_str::SmolStr;
27use std::collections::{BTreeMap, HashSet};
28
29use super::internal_name_to_entity_type;
30use crate::{
31 schema::{AllDefs, SchemaError},
32 types::{Attributes, Type},
33 ConditionalName,
34};
35
36#[derive(Clone, Debug, Serialize)]
40#[serde(rename_all = "camelCase")]
41pub struct ValidatorActionId {
42 pub(crate) name: EntityUID,
44
45 pub(crate) applies_to: ValidatorApplySpec<ast::EntityType>,
47
48 pub(crate) descendants: HashSet<EntityUID>,
53
54 pub(crate) context: Type,
56
57 pub(crate) attribute_types: Attributes,
59
60 pub(crate) attributes: BTreeMap<SmolStr, PartialValueSerializedAsExpr>,
67}
68
69impl ValidatorActionId {
70 pub fn principals(&self) -> impl Iterator<Item = &EntityType> {
72 self.applies_to.principal_apply_spec.iter()
73 }
74
75 pub fn resources(&self) -> impl Iterator<Item = &EntityType> {
77 self.applies_to.resource_apply_spec.iter()
78 }
79
80 pub fn context_type(&self) -> &Type {
84 &self.context
85 }
86
87 pub fn applies_to_principals(&self) -> impl Iterator<Item = &ast::EntityType> {
89 self.applies_to.applicable_principal_types()
90 }
91
92 pub fn applies_to_resources(&self) -> impl Iterator<Item = &ast::EntityType> {
94 self.applies_to.applicable_resource_types()
95 }
96
97 pub fn is_applicable_principal_type(&self, ty: &ast::EntityType) -> bool {
99 self.applies_to.is_applicable_principal_type(ty)
100 }
101
102 pub fn is_applicable_resource_type(&self, ty: &ast::EntityType) -> bool {
104 self.applies_to.is_applicable_resource_type(ty)
105 }
106}
107
108impl TCNode<EntityUID> for ValidatorActionId {
109 fn get_key(&self) -> EntityUID {
110 self.name.clone()
111 }
112
113 fn add_edge_to(&mut self, k: EntityUID) {
114 self.descendants.insert(k);
115 }
116
117 fn out_edges(&self) -> Box<dyn Iterator<Item = &EntityUID> + '_> {
118 Box::new(self.descendants.iter())
119 }
120
121 fn has_edge_to(&self, e: &EntityUID) -> bool {
122 self.descendants.contains(e)
123 }
124}
125
126#[derive(Clone, Debug, Serialize)]
137#[serde(rename_all = "camelCase")]
138pub(crate) struct ValidatorApplySpec<N> {
139 principal_apply_spec: HashSet<N>,
141
142 resource_apply_spec: HashSet<N>,
144}
145
146impl<N> ValidatorApplySpec<N> {
147 pub fn new(principal_apply_spec: HashSet<N>, resource_apply_spec: HashSet<N>) -> Self {
150 Self {
151 principal_apply_spec,
152 resource_apply_spec,
153 }
154 }
155}
156
157impl ValidatorApplySpec<ast::EntityType> {
158 pub fn is_applicable_principal_type(&self, ty: &ast::EntityType) -> bool {
160 self.principal_apply_spec.contains(ty)
161 }
162
163 pub fn applicable_principal_types(&self) -> impl Iterator<Item = &ast::EntityType> {
165 self.principal_apply_spec.iter()
166 }
167
168 pub fn is_applicable_resource_type(&self, ty: &ast::EntityType) -> bool {
170 self.resource_apply_spec.contains(ty)
171 }
172
173 pub fn applicable_resource_types(&self) -> impl Iterator<Item = &ast::EntityType> {
175 self.resource_apply_spec.iter()
176 }
177}
178
179impl ValidatorApplySpec<ConditionalName> {
180 pub fn fully_qualify_type_references(
188 self,
189 all_defs: &AllDefs,
190 ) -> Result<ValidatorApplySpec<ast::EntityType>, crate::schema::SchemaError> {
191 let (principal_apply_spec, principal_errs) = self
192 .principal_apply_spec
193 .into_iter()
194 .map(|cname| {
195 let internal_name = cname.resolve(all_defs)?.clone();
196 internal_name_to_entity_type(internal_name).map_err(Into::into)
197 })
198 .partition_result::<_, Vec<SchemaError>, _, _>();
199 let (resource_apply_spec, resource_errs) = self
200 .resource_apply_spec
201 .into_iter()
202 .map(|cname| {
203 let internal_name = cname.resolve(all_defs)?.clone();
204 internal_name_to_entity_type(internal_name).map_err(Into::into)
205 })
206 .partition_result::<_, Vec<SchemaError>, _, _>();
207 match (
208 NonEmpty::from_vec(principal_errs),
209 NonEmpty::from_vec(resource_errs),
210 ) {
211 (None, None) => Ok(ValidatorApplySpec {
212 principal_apply_spec,
213 resource_apply_spec,
214 }),
215 (Some(principal_errs), None) => Err(SchemaError::join_nonempty(principal_errs)),
216 (None, Some(resource_errs)) => Err(SchemaError::join_nonempty(resource_errs)),
217 (Some(principal_errs), Some(resource_errs)) => {
218 let mut errs = principal_errs;
219 errs.extend(resource_errs);
220 Err(SchemaError::join_nonempty(errs))
221 }
222 }
223 }
224}
225
226#[cfg(test)]
227mod test {
228 use super::*;
229
230 fn make_action() -> ValidatorActionId {
231 ValidatorActionId {
232 name: r#"Action::"foo""#.parse().unwrap(),
233 applies_to: ValidatorApplySpec {
234 principal_apply_spec: HashSet::from([
235 "User".parse().unwrap(),
237 "User".parse().unwrap(),
238 ]),
239 resource_apply_spec: HashSet::from([
240 "App".parse().unwrap(),
241 "File".parse().unwrap(),
242 ]),
243 },
244 descendants: HashSet::new(),
245 context: Type::any_record(),
246 attribute_types: Attributes::default(),
247 attributes: BTreeMap::default(),
248 }
249 }
250
251 #[test]
252 fn test_resources() {
253 let a = make_action();
254 let got = a.resources().cloned().collect::<HashSet<EntityType>>();
255 let expected = HashSet::from(["App".parse().unwrap(), "File".parse().unwrap()]);
256 assert_eq!(got, expected);
257 }
258
259 #[test]
260 fn test_principals() {
261 let a = make_action();
262 let got = a.principals().cloned().collect::<Vec<EntityType>>();
263 let expected: [EntityType; 1] = ["User".parse().unwrap()];
264 assert_eq!(got, &expected);
265 }
266}