1use std::collections::{BTreeMap, HashMap, HashSet};
21
22use cedar_policy_core::{
23 ast::{
24 Eid, EntityAttrEvaluationError, EntityType, EntityUID, Id, Name,
25 PartialValueSerializedAsExpr,
26 },
27 entities::{CedarValueJson, JsonDeserializationErrorContext},
28 evaluator::RestrictedEvaluator,
29 extensions::Extensions,
30 FromNormalizedStr,
31};
32use smol_str::{SmolStr, ToSmolStr};
33
34use super::ValidatorApplySpec;
35use crate::{
36 err::*,
37 is_builtin_type_name, schema_file_format,
38 types::{AttributeType, Attributes, OpenTag, Type},
39 ActionBehavior, ActionEntityUID, ActionType, NamespaceDefinition, SchemaType,
40 SchemaTypeVariant, TypeOfAttribute,
41};
42pub(crate) static ACTION_ENTITY_TYPE: &str = "Action";
48
49#[test]
50fn action_entity_type_parses() {
51 Id::from_normalized_str(ACTION_ENTITY_TYPE).unwrap();
52}
53
54pub(crate) fn is_action_entity_type(ty: &Name) -> bool {
58 ty.basename().as_ref() == ACTION_ENTITY_TYPE
59}
60
61#[derive(Debug)]
69pub struct ValidatorNamespaceDef {
70 namespace: Option<Name>,
75 pub(super) type_defs: TypeDefs,
78 pub(super) entity_types: EntityTypesDef,
80 pub(super) actions: ActionsDef,
82}
83
84#[derive(Debug)]
88pub struct TypeDefs {
89 pub(super) type_defs: HashMap<Name, SchemaType>,
90}
91
92#[derive(Debug)]
95pub struct EntityTypesDef {
96 pub(super) entity_types: HashMap<Name, EntityTypeFragment>,
97}
98
99#[derive(Debug)]
103pub struct EntityTypeFragment {
104 pub(super) attributes: WithUnresolvedTypeDefs<Type>,
109 pub(super) parents: HashSet<Name>,
114}
115
116#[derive(Debug)]
119pub struct ActionsDef {
120 pub(super) actions: HashMap<EntityUID, ActionFragment>,
121}
122
123#[derive(Debug)]
124pub struct ActionFragment {
125 pub(super) context: WithUnresolvedTypeDefs<Type>,
129 pub(super) applies_to: ValidatorApplySpec,
131 pub(super) parents: HashSet<EntityUID>,
133 pub(super) attribute_types: Attributes,
135 pub(super) attributes: BTreeMap<SmolStr, PartialValueSerializedAsExpr>,
139}
140
141type ResolveFunc<T> = dyn FnOnce(&HashMap<Name, Type>) -> Result<T>;
142pub enum WithUnresolvedTypeDefs<T> {
145 WithUnresolved(Box<ResolveFunc<T>>),
146 WithoutUnresolved(T),
147}
148
149impl<T: 'static> WithUnresolvedTypeDefs<T> {
150 pub fn new(f: impl FnOnce(&HashMap<Name, Type>) -> Result<T> + 'static) -> Self {
151 Self::WithUnresolved(Box::new(f))
152 }
153
154 pub fn map<U: 'static>(self, f: impl FnOnce(T) -> U + 'static) -> WithUnresolvedTypeDefs<U> {
155 match self {
156 Self::WithUnresolved(_) => {
157 WithUnresolvedTypeDefs::new(|type_defs| self.resolve_type_defs(type_defs).map(f))
158 }
159 Self::WithoutUnresolved(v) => WithUnresolvedTypeDefs::WithoutUnresolved(f(v)),
160 }
161 }
162
163 pub fn resolve_type_defs(self, type_defs: &HashMap<Name, Type>) -> Result<T> {
166 match self {
167 WithUnresolvedTypeDefs::WithUnresolved(f) => f(type_defs),
168 WithUnresolvedTypeDefs::WithoutUnresolved(v) => Ok(v),
169 }
170 }
171}
172
173impl<T: 'static> From<T> for WithUnresolvedTypeDefs<T> {
174 fn from(value: T) -> Self {
175 Self::WithoutUnresolved(value)
176 }
177}
178
179impl<T: std::fmt::Debug> std::fmt::Debug for WithUnresolvedTypeDefs<T> {
180 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
181 match self {
182 WithUnresolvedTypeDefs::WithUnresolved(_) => f.debug_tuple("WithUnresolved").finish(),
183 WithUnresolvedTypeDefs::WithoutUnresolved(v) => {
184 f.debug_tuple("WithoutUnresolved").field(v).finish()
185 }
186 }
187 }
188}
189
190impl TryInto<ValidatorNamespaceDef> for NamespaceDefinition {
191 type Error = SchemaError;
192
193 fn try_into(self) -> Result<ValidatorNamespaceDef> {
194 ValidatorNamespaceDef::from_namespace_definition(
195 None,
196 self,
197 ActionBehavior::default(),
198 Extensions::all_available(),
199 )
200 }
201}
202
203impl ValidatorNamespaceDef {
204 pub fn from_namespace_definition(
208 namespace: Option<Name>,
209 namespace_def: NamespaceDefinition,
210 action_behavior: ActionBehavior,
211 extensions: Extensions<'_>,
212 ) -> Result<ValidatorNamespaceDef> {
213 let mut e_types_ids: HashSet<Id> = HashSet::new();
215 for name in namespace_def.entity_types.keys() {
216 if !e_types_ids.insert(name.clone()) {
217 return Err(SchemaError::DuplicateEntityType(name.to_string()));
219 }
220 }
221 let mut a_name_eids: HashSet<SmolStr> = HashSet::new();
222 for name in namespace_def.actions.keys() {
223 if !a_name_eids.insert(name.clone()) {
224 return Err(SchemaError::DuplicateAction(name.to_string()));
226 }
227 }
228
229 Self::check_action_behavior(&namespace_def, action_behavior)?;
232
233 let type_defs = Self::build_type_defs(namespace_def.common_types, namespace.as_ref())?;
236 let actions =
237 Self::build_action_ids(namespace_def.actions, namespace.as_ref(), extensions)?;
238 let entity_types =
239 Self::build_entity_types(namespace_def.entity_types, namespace.as_ref())?;
240
241 Ok(ValidatorNamespaceDef {
242 namespace,
243 type_defs,
244 entity_types,
245 actions,
246 })
247 }
248
249 fn build_type_defs(
250 schema_file_type_def: HashMap<Id, SchemaType>,
251 schema_namespace: Option<&Name>,
252 ) -> Result<TypeDefs> {
253 let type_defs = schema_file_type_def
254 .into_iter()
255 .map(|(name, schema_ty)| -> Result<_> {
256 let name_str = name.clone().into_smolstr();
257 if is_builtin_type_name(&name_str) {
258 return Err(SchemaError::DuplicateCommonType(name_str.to_string()));
259 }
260 let name =
261 Name::from(name).prefix_namespace_if_unqualified(schema_namespace.cloned());
262 Ok((
263 name,
264 schema_ty
265 .prefix_common_type_references_with_namespace(schema_namespace.cloned()),
266 ))
267 })
268 .collect::<Result<HashMap<_, _>>>()?;
269 Ok(TypeDefs { type_defs })
270 }
271
272 fn build_entity_types(
276 schema_files_types: HashMap<Id, schema_file_format::EntityType>,
277 schema_namespace: Option<&Name>,
278 ) -> Result<EntityTypesDef> {
279 Ok(EntityTypesDef {
280 entity_types: schema_files_types
281 .into_iter()
282 .map(|(id, entity_type)| -> Result<_> {
283 let name =
284 Name::from(id).prefix_namespace_if_unqualified(schema_namespace.cloned());
285
286 let parents = entity_type
287 .member_of_types
288 .into_iter()
289 .map(|ty| ty.prefix_namespace_if_unqualified(schema_namespace.cloned()))
290 .collect();
291
292 let attributes = Self::try_schema_type_into_validator_type(
293 schema_namespace,
294 entity_type.shape.into_inner(),
295 )?;
296
297 Ok((
298 name,
299 EntityTypeFragment {
300 attributes,
301 parents,
302 },
303 ))
304 })
305 .collect::<Result<HashMap<_, _>>>()?,
306 })
307 }
308
309 fn jsonval_to_type_helper(v: &CedarValueJson, action_id: &EntityUID) -> Result<Type> {
315 match v {
316 CedarValueJson::Bool(_) => Ok(Type::primitive_boolean()),
317 CedarValueJson::Long(_) => Ok(Type::primitive_long()),
318 CedarValueJson::String(_) => Ok(Type::primitive_string()),
319 CedarValueJson::Record(r) => {
320 let mut required_attrs: HashMap<SmolStr, Type> = HashMap::new();
321 for (k, v_prime) in r {
322 let t = Self::jsonval_to_type_helper(v_prime, action_id);
323 match t {
324 Ok(ty) => required_attrs.insert(k.clone(), ty),
325 Err(e) => return Err(e),
326 };
327 }
328 Ok(Type::record_with_required_attributes(
329 required_attrs,
330 OpenTag::ClosedAttributes,
331 ))
332 }
333 CedarValueJson::Set(v) => match v.first() {
334 None => Err(SchemaError::ActionAttributesContainEmptySet(
336 action_id.clone(),
337 )),
338 Some(element) => {
339 let element_type = Self::jsonval_to_type_helper(element, action_id);
340 match element_type {
341 Ok(t) => Ok(Type::Set {
342 element_type: Some(Box::new(t)),
343 }),
344 Err(_) => element_type,
345 }
346 }
347 },
348 CedarValueJson::EntityEscape { __entity: _ } => {
349 Err(SchemaError::UnsupportedActionAttribute(
350 action_id.clone(),
351 "entity escape (`__entity`)".to_owned(),
352 ))
353 }
354 CedarValueJson::ExprEscape { __expr: _ } => {
355 Err(SchemaError::UnsupportedActionAttribute(
356 action_id.clone(),
357 "expression escape (`__expr`)".to_owned(),
358 ))
359 }
360 CedarValueJson::ExtnEscape { __extn: _ } => {
361 Err(SchemaError::UnsupportedActionAttribute(
362 action_id.clone(),
363 "extension function escape (`__extn`)".to_owned(),
364 ))
365 }
366 CedarValueJson::Null => Err(SchemaError::UnsupportedActionAttribute(
367 action_id.clone(),
368 "null".to_owned(),
369 )),
370 }
371 }
372
373 fn convert_attr_jsonval_map_to_attributes(
375 m: HashMap<SmolStr, CedarValueJson>,
376 action_id: &EntityUID,
377 extensions: Extensions<'_>,
378 ) -> Result<(Attributes, BTreeMap<SmolStr, PartialValueSerializedAsExpr>)> {
379 let mut attr_types: HashMap<SmolStr, Type> = HashMap::with_capacity(m.len());
380 let mut attr_values: BTreeMap<SmolStr, PartialValueSerializedAsExpr> = BTreeMap::new();
381 let evaluator = RestrictedEvaluator::new(&extensions);
382
383 for (k, v) in m {
384 let t = Self::jsonval_to_type_helper(&v, action_id);
385 match t {
386 Ok(ty) => attr_types.insert(k.clone(), ty),
387 Err(e) => return Err(e),
388 };
389
390 #[allow(clippy::expect_used)]
400 let e = v.into_expr(|| JsonDeserializationErrorContext::EntityAttribute { uid: action_id.clone(), attr: k.clone() }).expect("`Self::jsonval_to_type_helper` will always return `Err` for a `CedarValueJson` that might make `into_expr` return `Err`");
401 let pv = evaluator
402 .partial_interpret(e.as_borrowed())
403 .map_err(|err| {
404 SchemaError::ActionAttrEval(EntityAttrEvaluationError {
405 uid: action_id.clone(),
406 attr: k.clone(),
407 err,
408 })
409 })?;
410 attr_values.insert(k.clone(), pv.into());
411 }
412 Ok((
413 Attributes::with_required_attributes(attr_types),
414 attr_values,
415 ))
416 }
417
418 fn build_action_ids(
422 schema_file_actions: HashMap<SmolStr, ActionType>,
423 schema_namespace: Option<&Name>,
424 extensions: Extensions<'_>,
425 ) -> Result<ActionsDef> {
426 Ok(ActionsDef {
427 actions: schema_file_actions
428 .into_iter()
429 .map(|(action_id_str, action_type)| -> Result<_> {
430 let action_id = Self::parse_action_id_with_namespace(
431 &ActionEntityUID::default_type(action_id_str),
432 schema_namespace,
433 );
434
435 let (principal_types, resource_types, context) = action_type
436 .applies_to
437 .map(|applies_to| {
438 (
439 applies_to.principal_types,
440 applies_to.resource_types,
441 applies_to.context,
442 )
443 })
444 .unwrap_or_default();
445
446 let applies_to = ValidatorApplySpec::new(
450 Self::parse_apply_spec_type_list(principal_types, schema_namespace),
451 Self::parse_apply_spec_type_list(resource_types, schema_namespace),
452 );
453
454 let context = Self::try_schema_type_into_validator_type(
455 schema_namespace,
456 context.into_inner(),
457 )?;
458
459 let parents = action_type
460 .member_of
461 .unwrap_or_default()
462 .iter()
463 .map(|parent| {
464 Self::parse_action_id_with_namespace(parent, schema_namespace)
465 })
466 .collect::<HashSet<_>>();
467
468 let (attribute_types, attributes) =
469 Self::convert_attr_jsonval_map_to_attributes(
470 action_type.attributes.unwrap_or_default(),
471 &action_id,
472 extensions,
473 )?;
474
475 Ok((
476 action_id,
477 ActionFragment {
478 context,
479 applies_to,
480 parents,
481 attribute_types,
482 attributes,
483 },
484 ))
485 })
486 .collect::<Result<HashMap<_, _>>>()?,
487 })
488 }
489
490 fn check_action_behavior(
496 schema_file: &NamespaceDefinition,
497 action_behavior: ActionBehavior,
498 ) -> Result<()> {
499 if schema_file
500 .entity_types
501 .iter()
502 .any(|(name, _)| name.to_smolstr() == ACTION_ENTITY_TYPE)
506 {
507 return Err(SchemaError::ActionEntityTypeDeclared);
508 }
509 if action_behavior == ActionBehavior::ProhibitAttributes {
510 let mut actions_with_attributes: Vec<String> = Vec::new();
511 for (name, a) in &schema_file.actions {
512 if a.attributes.is_some() {
513 actions_with_attributes.push(name.to_string());
514 }
515 }
516 if !actions_with_attributes.is_empty() {
517 return Err(SchemaError::UnsupportedFeature(
518 UnsupportedFeature::ActionAttributes(actions_with_attributes),
519 ));
520 }
521 }
522
523 Ok(())
524 }
525
526 fn parse_record_attributes(
531 schema_namespace: Option<&Name>,
532 attrs: impl IntoIterator<Item = (SmolStr, TypeOfAttribute)>,
533 ) -> Result<WithUnresolvedTypeDefs<Attributes>> {
534 let attrs_with_type_defs = attrs
535 .into_iter()
536 .map(|(attr, ty)| -> Result<_> {
537 Ok((
538 attr,
539 (
540 Self::try_schema_type_into_validator_type(schema_namespace, ty.ty)?,
541 ty.required,
542 ),
543 ))
544 })
545 .collect::<Result<Vec<_>>>()?;
546 Ok(WithUnresolvedTypeDefs::new(|typ_defs| {
547 attrs_with_type_defs
548 .into_iter()
549 .map(|(s, (attr_ty, is_req))| {
550 attr_ty
551 .resolve_type_defs(typ_defs)
552 .map(|ty| (s, AttributeType::new(ty, is_req)))
553 })
554 .collect::<Result<Vec<_>>>()
555 .map(Attributes::with_attributes)
556 }))
557 }
558
559 fn parse_apply_spec_type_list(
564 types: Option<Vec<Name>>,
565 namespace: Option<&Name>,
566 ) -> HashSet<EntityType> {
567 types
568 .map(|types| {
569 types
570 .iter()
571 .map(|ty| {
575 EntityType::Specified(
576 ty.prefix_namespace_if_unqualified(namespace.cloned()),
577 )
578 })
579 .collect::<HashSet<_>>()
581 })
582 .unwrap_or_else(|| HashSet::from([EntityType::Unspecified]))
583 }
584
585 fn parse_action_id_with_namespace(
591 action_id: &ActionEntityUID,
592 namespace: Option<&Name>,
593 ) -> EntityUID {
594 let namespaced_action_type = if let Some(action_ty) = &action_id.ty {
595 action_ty.prefix_namespace_if_unqualified(namespace.cloned())
596 } else {
597 #[allow(clippy::expect_used)]
599 let id = Id::from_normalized_str(ACTION_ENTITY_TYPE).expect(
600 "Expected that the constant ACTION_ENTITY_TYPE would be a valid entity type.",
601 );
602 match namespace {
603 Some(namespace) => Name::type_in_namespace(id, namespace.clone(), None),
604 None => Name::unqualified_name(id),
605 }
606 };
607 EntityUID::from_components(namespaced_action_type, Eid::new(action_id.id.clone()), None)
608 }
609
610 pub(crate) fn try_schema_type_into_validator_type(
616 default_namespace: Option<&Name>,
617 schema_ty: SchemaType,
618 ) -> Result<WithUnresolvedTypeDefs<Type>> {
619 match schema_ty {
620 SchemaType::Type(SchemaTypeVariant::String) => Ok(Type::primitive_string().into()),
621 SchemaType::Type(SchemaTypeVariant::Long) => Ok(Type::primitive_long().into()),
622 SchemaType::Type(SchemaTypeVariant::Boolean) => Ok(Type::primitive_boolean().into()),
623 SchemaType::Type(SchemaTypeVariant::Set { element }) => Ok(
624 Self::try_schema_type_into_validator_type(default_namespace, *element)?
625 .map(Type::set),
626 ),
627 SchemaType::Type(SchemaTypeVariant::Record {
628 attributes,
629 additional_attributes,
630 }) => {
631 if cfg!(not(feature = "partial-validate")) && additional_attributes {
632 Err(SchemaError::UnsupportedFeature(
633 UnsupportedFeature::OpenRecordsAndEntities,
634 ))
635 } else {
636 Ok(
637 Self::parse_record_attributes(default_namespace, attributes)?.map(
638 move |attrs| {
639 Type::record_with_attributes(
640 attrs,
641 if additional_attributes {
642 OpenTag::OpenAttributes
643 } else {
644 OpenTag::ClosedAttributes
645 },
646 )
647 },
648 ),
649 )
650 }
651 }
652 SchemaType::Type(SchemaTypeVariant::Entity { name }) => {
653 Ok(Type::named_entity_reference(
654 name.prefix_namespace_if_unqualified(default_namespace.cloned()),
655 )
656 .into())
657 }
658 SchemaType::Type(SchemaTypeVariant::Extension { name }) => {
659 let extension_type_name = Name::unqualified_name(name);
660 Ok(Type::extension(extension_type_name).into())
661 }
662 SchemaType::TypeDef { type_name } => {
663 let defined_type_name =
664 type_name.prefix_namespace_if_unqualified(default_namespace.cloned());
665 Ok(WithUnresolvedTypeDefs::new(move |typ_defs| {
666 typ_defs.get(&defined_type_name).cloned().ok_or(
667 SchemaError::UndeclaredCommonTypes(HashSet::from([
668 defined_type_name.to_string()
669 ])),
670 )
671 }))
672 }
673 }
674 }
675
676 pub fn namespace(&self) -> &Option<Name> {
678 &self.namespace
679 }
680}