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 new(
75 name: EntityUID,
76 principal_entity_types: impl IntoIterator<Item = ast::EntityType>,
77 resource_entity_types: impl IntoIterator<Item = ast::EntityType>,
78 descendants: impl IntoIterator<Item = EntityUID>,
79 context: Type,
80 attribute_types: Attributes,
81 attributes: BTreeMap<SmolStr, PartialValueSerializedAsExpr>,
82 ) -> Self {
83 Self {
84 name,
85 applies_to: ValidatorApplySpec::new(
86 principal_entity_types.into_iter().collect(),
87 resource_entity_types.into_iter().collect(),
88 ),
89 descendants: descendants.into_iter().collect(),
90 context,
91 attribute_types,
92 attributes,
93 }
94 }
95
96 pub fn name(&self) -> &EntityUID {
98 &self.name
99 }
100
101 pub fn descendants(&self) -> impl Iterator<Item = &EntityUID> {
103 self.descendants.iter()
104 }
105
106 pub fn context(&self) -> &Type {
108 &self.context
109 }
110
111 pub fn principals(&self) -> impl Iterator<Item = &EntityType> {
113 self.applies_to.principal_apply_spec.iter()
114 }
115
116 pub fn resources(&self) -> impl Iterator<Item = &EntityType> {
118 self.applies_to.resource_apply_spec.iter()
119 }
120
121 pub fn context_type(&self) -> &Type {
125 &self.context
126 }
127
128 pub fn applies_to_principals(&self) -> impl Iterator<Item = &ast::EntityType> {
130 self.applies_to.applicable_principal_types()
131 }
132
133 pub fn applies_to_resources(&self) -> impl Iterator<Item = &ast::EntityType> {
135 self.applies_to.applicable_resource_types()
136 }
137
138 pub fn is_applicable_principal_type(&self, ty: &ast::EntityType) -> bool {
140 self.applies_to.is_applicable_principal_type(ty)
141 }
142
143 pub fn is_applicable_resource_type(&self, ty: &ast::EntityType) -> bool {
145 self.applies_to.is_applicable_resource_type(ty)
146 }
147
148 pub fn attribute_types(&self) -> &Attributes {
150 &self.attribute_types
151 }
152
153 pub fn attributes(&self) -> impl Iterator<Item = (&SmolStr, &PartialValueSerializedAsExpr)> {
155 self.attributes.iter()
156 }
157}
158
159impl TCNode<EntityUID> for ValidatorActionId {
160 fn get_key(&self) -> EntityUID {
161 self.name.clone()
162 }
163
164 fn add_edge_to(&mut self, k: EntityUID) {
165 self.descendants.insert(k);
166 }
167
168 fn out_edges(&self) -> Box<dyn Iterator<Item = &EntityUID> + '_> {
169 Box::new(self.descendants.iter())
170 }
171
172 fn has_edge_to(&self, e: &EntityUID) -> bool {
173 self.descendants.contains(e)
174 }
175}
176
177#[derive(Clone, Debug, Serialize)]
188#[serde(rename_all = "camelCase")]
189pub(crate) struct ValidatorApplySpec<N> {
190 principal_apply_spec: HashSet<N>,
192
193 resource_apply_spec: HashSet<N>,
195}
196
197impl<N> ValidatorApplySpec<N> {
198 pub fn new(principal_apply_spec: HashSet<N>, resource_apply_spec: HashSet<N>) -> Self {
201 Self {
202 principal_apply_spec,
203 resource_apply_spec,
204 }
205 }
206}
207
208impl ValidatorApplySpec<ast::EntityType> {
209 pub fn is_applicable_principal_type(&self, ty: &ast::EntityType) -> bool {
211 self.principal_apply_spec.contains(ty)
212 }
213
214 pub fn applicable_principal_types(&self) -> impl Iterator<Item = &ast::EntityType> {
216 self.principal_apply_spec.iter()
217 }
218
219 pub fn is_applicable_resource_type(&self, ty: &ast::EntityType) -> bool {
221 self.resource_apply_spec.contains(ty)
222 }
223
224 pub fn applicable_resource_types(&self) -> impl Iterator<Item = &ast::EntityType> {
226 self.resource_apply_spec.iter()
227 }
228}
229
230impl ValidatorApplySpec<ConditionalName> {
231 pub fn fully_qualify_type_references(
239 self,
240 all_defs: &AllDefs,
241 ) -> Result<ValidatorApplySpec<ast::EntityType>, crate::schema::SchemaError> {
242 let (principal_apply_spec, principal_errs) = self
243 .principal_apply_spec
244 .into_iter()
245 .map(|cname| {
246 let internal_name = cname.resolve(all_defs)?;
247 internal_name_to_entity_type(internal_name).map_err(Into::into)
248 })
249 .partition_result::<_, Vec<SchemaError>, _, _>();
250 let (resource_apply_spec, resource_errs) = self
251 .resource_apply_spec
252 .into_iter()
253 .map(|cname| {
254 let internal_name = cname.resolve(all_defs)?;
255 internal_name_to_entity_type(internal_name).map_err(Into::into)
256 })
257 .partition_result::<_, Vec<SchemaError>, _, _>();
258 match (
259 NonEmpty::from_vec(principal_errs),
260 NonEmpty::from_vec(resource_errs),
261 ) {
262 (None, None) => Ok(ValidatorApplySpec {
263 principal_apply_spec,
264 resource_apply_spec,
265 }),
266 (Some(principal_errs), None) => Err(SchemaError::join_nonempty(principal_errs)),
267 (None, Some(resource_errs)) => Err(SchemaError::join_nonempty(resource_errs)),
268 (Some(principal_errs), Some(resource_errs)) => {
269 let mut errs = principal_errs;
270 errs.extend(resource_errs);
271 Err(SchemaError::join_nonempty(errs))
272 }
273 }
274 }
275}
276
277#[cfg(test)]
278mod test {
279 use super::*;
280
281 fn make_action() -> ValidatorActionId {
282 ValidatorActionId {
283 name: r#"Action::"foo""#.parse().unwrap(),
284 applies_to: ValidatorApplySpec {
285 principal_apply_spec: HashSet::from([
286 "User".parse().unwrap(),
288 "User".parse().unwrap(),
289 ]),
290 resource_apply_spec: HashSet::from([
291 "App".parse().unwrap(),
292 "File".parse().unwrap(),
293 ]),
294 },
295 descendants: HashSet::new(),
296 context: Type::any_record(),
297 attribute_types: Attributes::default(),
298 attributes: BTreeMap::default(),
299 }
300 }
301
302 #[test]
303 fn test_resources() {
304 let a = make_action();
305 let got = a.resources().cloned().collect::<HashSet<EntityType>>();
306 let expected = HashSet::from(["App".parse().unwrap(), "File".parse().unwrap()]);
307 assert_eq!(got, expected);
308 }
309
310 #[test]
311 fn test_principals() {
312 let a = make_action();
313 let got = a.principals().cloned().collect::<Vec<EntityType>>();
314 let expected: [EntityType; 1] = ["User".parse().unwrap()];
315 assert_eq!(got, &expected);
316 }
317}