1use std::collections::{HashMap, HashSet};
5
6use cedar_policy_core::{
7 ast::{
8 Eid, EntityAttrEvaluationError, EntityType, EntityUID, Id, Name,
9 PartialValueSerializedAsExpr,
10 },
11 entities::{CedarValueJson, JsonDeserializationErrorContext},
12 evaluator::RestrictedEvaluator,
13 extensions::Extensions,
14 parser::err::ParseErrors,
15 FromNormalizedStr,
16};
17use smol_str::SmolStr;
18
19use super::ValidatorApplySpec;
20use crate::types::OpenTag;
21use crate::{
22 err::*,
23 schema_file_format,
24 types::{AttributeType, Attributes, Type},
25 ActionBehavior, ActionEntityUID, ActionType, NamespaceDefinition, SchemaType,
26 SchemaTypeVariant, TypeOfAttribute, SCHEMA_TYPE_VARIANT_TAGS,
27};
28
29pub(crate) static ACTION_ENTITY_TYPE: &str = "Action";
35
36#[test]
37fn action_entity_type_parses() {
38 Id::from_normalized_str(ACTION_ENTITY_TYPE).unwrap();
39}
40
41pub(crate) fn is_action_entity_type(ty: &Name) -> bool {
45 ty.basename().as_ref() == ACTION_ENTITY_TYPE
46}
47
48#[derive(Debug)]
56pub struct ValidatorNamespaceDef {
57 namespace: Option<Name>,
62 pub(super) type_defs: TypeDefs,
65 pub(super) entity_types: EntityTypesDef,
67 pub(super) actions: ActionsDef,
69}
70
71#[derive(Debug)]
74pub struct TypeDefs {
75 pub(super) type_defs: HashMap<Name, Type>,
76}
77
78#[derive(Debug)]
81pub struct EntityTypesDef {
82 pub(super) entity_types: HashMap<Name, EntityTypeFragment>,
83}
84
85#[derive(Debug)]
89pub struct EntityTypeFragment {
90 pub(super) attributes: WithUnresolvedTypeDefs<Type>,
95 pub(super) parents: HashSet<Name>,
100}
101
102#[derive(Debug)]
105pub struct ActionsDef {
106 pub(super) actions: HashMap<EntityUID, ActionFragment>,
107}
108
109#[derive(Debug)]
110pub struct ActionFragment {
111 pub(super) context: WithUnresolvedTypeDefs<Type>,
115 pub(super) applies_to: ValidatorApplySpec,
117 pub(super) parents: HashSet<EntityUID>,
119 pub(super) attribute_types: Attributes,
121 pub(super) attributes: HashMap<SmolStr, PartialValueSerializedAsExpr>,
125}
126
127type ResolveFunc<T> = dyn FnOnce(&HashMap<Name, Type>) -> Result<T>;
128pub enum WithUnresolvedTypeDefs<T> {
131 WithUnresolved(Box<ResolveFunc<T>>),
132 WithoutUnresolved(T),
133}
134
135impl<T: 'static> WithUnresolvedTypeDefs<T> {
136 pub fn new(f: impl FnOnce(&HashMap<Name, Type>) -> Result<T> + 'static) -> Self {
137 Self::WithUnresolved(Box::new(f))
138 }
139
140 pub fn map<U: 'static>(self, f: impl FnOnce(T) -> U + 'static) -> WithUnresolvedTypeDefs<U> {
141 match self {
142 Self::WithUnresolved(_) => {
143 WithUnresolvedTypeDefs::new(|type_defs| self.resolve_type_defs(type_defs).map(f))
144 }
145 Self::WithoutUnresolved(v) => WithUnresolvedTypeDefs::WithoutUnresolved(f(v)),
146 }
147 }
148
149 pub fn resolve_type_defs(self, type_defs: &HashMap<Name, Type>) -> Result<T> {
152 match self {
153 WithUnresolvedTypeDefs::WithUnresolved(f) => f(type_defs),
154 WithUnresolvedTypeDefs::WithoutUnresolved(v) => Ok(v),
155 }
156 }
157}
158
159impl<T: 'static> From<T> for WithUnresolvedTypeDefs<T> {
160 fn from(value: T) -> Self {
161 Self::WithoutUnresolved(value)
162 }
163}
164
165impl<T: std::fmt::Debug> std::fmt::Debug for WithUnresolvedTypeDefs<T> {
166 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
167 match self {
168 WithUnresolvedTypeDefs::WithUnresolved(_) => f.debug_tuple("WithUnresolved").finish(),
169 WithUnresolvedTypeDefs::WithoutUnresolved(v) => {
170 f.debug_tuple("WithoutUnresolved").field(v).finish()
171 }
172 }
173 }
174}
175
176impl TryInto<ValidatorNamespaceDef> for NamespaceDefinition {
177 type Error = SchemaError;
178
179 fn try_into(self) -> Result<ValidatorNamespaceDef> {
180 ValidatorNamespaceDef::from_namespace_definition(
181 None,
182 self,
183 ActionBehavior::default(),
184 Extensions::all_available(),
185 )
186 }
187}
188
189impl ValidatorNamespaceDef {
190 pub fn from_namespace_definition(
194 namespace: Option<SmolStr>,
195 namespace_def: NamespaceDefinition,
196 action_behavior: ActionBehavior,
197 extensions: Extensions<'_>,
198 ) -> Result<ValidatorNamespaceDef> {
199 let mut e_types_ids: HashSet<SmolStr> = HashSet::new();
201 for name in namespace_def.entity_types.keys() {
202 if !e_types_ids.insert(name.clone()) {
203 return Err(SchemaError::DuplicateEntityType(name.to_string()));
205 }
206 }
207 let mut a_name_eids: HashSet<SmolStr> = HashSet::new();
208 for name in namespace_def.actions.keys() {
209 if !a_name_eids.insert(name.clone()) {
210 return Err(SchemaError::DuplicateAction(name.to_string()));
212 }
213 }
214
215 let schema_namespace = match namespace.as_deref() {
216 None => None,
217 Some("") => None, Some(ns) => Some(Name::from_normalized_str(ns).map_err(SchemaError::ParseNamespace)?),
219 };
220
221 Self::check_action_behavior(&namespace_def, action_behavior)?;
224
225 let type_defs =
228 Self::build_type_defs(namespace_def.common_types, schema_namespace.as_ref())?;
229 let actions =
230 Self::build_action_ids(namespace_def.actions, schema_namespace.as_ref(), extensions)?;
231 let entity_types =
232 Self::build_entity_types(namespace_def.entity_types, schema_namespace.as_ref())?;
233
234 Ok(ValidatorNamespaceDef {
235 namespace: schema_namespace,
236 type_defs,
237 entity_types,
238 actions,
239 })
240 }
241
242 fn is_builtin_type_name(name: &SmolStr) -> bool {
243 SCHEMA_TYPE_VARIANT_TAGS
244 .iter()
245 .any(|type_name| name == type_name)
246 }
247
248 fn build_type_defs(
249 schema_file_type_def: HashMap<SmolStr, SchemaType>,
250 schema_namespace: Option<&Name>,
251 ) -> Result<TypeDefs> {
252 let type_defs = schema_file_type_def
253 .into_iter()
254 .map(|(name_str, schema_ty)| -> Result<_> {
255 if Self::is_builtin_type_name(&name_str) {
256 return Err(SchemaError::DuplicateCommonType(name_str.to_string()));
257 }
258 let name = Self::parse_unqualified_name_with_namespace(
259 &name_str,
260 schema_namespace.cloned(),
261 )
262 .map_err(SchemaError::ParseCommonType)?;
263 let ty = Self::try_schema_type_into_validator_type(schema_namespace, schema_ty)?
264 .resolve_type_defs(&HashMap::new())?;
265 Ok((name, ty))
266 })
267 .collect::<Result<HashMap<_, _>>>()?;
268 Ok(TypeDefs { type_defs })
269 }
270
271 fn build_entity_types(
275 schema_files_types: HashMap<SmolStr, schema_file_format::EntityType>,
276 schema_namespace: Option<&Name>,
277 ) -> Result<EntityTypesDef> {
278 Ok(EntityTypesDef {
279 entity_types: schema_files_types
280 .into_iter()
281 .map(|(name_str, entity_type)| -> Result<_> {
282 let name = Self::parse_unqualified_name_with_namespace(
283 &name_str,
284 schema_namespace.cloned(),
285 )
286 .map_err(SchemaError::ParseEntityType)?;
287
288 let parents = entity_type
289 .member_of_types
290 .iter()
291 .map(|parent| -> Result<_> {
292 Self::parse_possibly_qualified_name_with_default_namespace(
293 parent,
294 schema_namespace,
295 )
296 .map_err(SchemaError::ParseEntityType)
297 })
298 .collect::<Result<HashSet<_>>>()?;
299
300 let attributes = Self::try_schema_type_into_validator_type(
301 schema_namespace,
302 entity_type.shape.into_inner(),
303 )?;
304
305 Ok((
306 name,
307 EntityTypeFragment {
308 attributes,
309 parents,
310 },
311 ))
312 })
313 .collect::<Result<HashMap<_, _>>>()?,
314 })
315 }
316
317 fn jsonval_to_type_helper(v: &CedarValueJson, action_id: &EntityUID) -> Result<Type> {
323 match v {
324 CedarValueJson::Bool(_) => Ok(Type::primitive_boolean()),
325 CedarValueJson::Long(_) => Ok(Type::primitive_long()),
326 CedarValueJson::String(_) => Ok(Type::primitive_string()),
327 CedarValueJson::Record(r) => {
328 let mut required_attrs: HashMap<SmolStr, Type> = HashMap::new();
329 for (k, v_prime) in r {
330 let t = Self::jsonval_to_type_helper(v_prime, action_id);
331 match t {
332 Ok(ty) => required_attrs.insert(k.clone(), ty),
333 Err(e) => return Err(e),
334 };
335 }
336 Ok(Type::record_with_required_attributes(
337 required_attrs,
338 OpenTag::ClosedAttributes,
339 ))
340 }
341 CedarValueJson::Set(v) => match v.get(0) {
342 None => Err(SchemaError::ActionAttributesContainEmptySet(
344 action_id.clone(),
345 )),
346 Some(element) => {
347 let element_type = Self::jsonval_to_type_helper(element, action_id);
348 match element_type {
349 Ok(t) => Ok(Type::Set {
350 element_type: Some(Box::new(t)),
351 }),
352 Err(_) => element_type,
353 }
354 }
355 },
356 CedarValueJson::EntityEscape { __entity: _ } => {
357 Err(SchemaError::UnsupportedActionAttribute(
358 action_id.clone(),
359 "entity escape (`__entity`)".to_owned(),
360 ))
361 }
362 CedarValueJson::ExprEscape { __expr: _ } => {
363 Err(SchemaError::UnsupportedActionAttribute(
364 action_id.clone(),
365 "expression escape (`__expr`)".to_owned(),
366 ))
367 }
368 CedarValueJson::ExtnEscape { __extn: _ } => {
369 Err(SchemaError::UnsupportedActionAttribute(
370 action_id.clone(),
371 "extension function escape (`__extn`)".to_owned(),
372 ))
373 }
374 }
375 }
376
377 fn convert_attr_jsonval_map_to_attributes(
379 m: HashMap<SmolStr, CedarValueJson>,
380 action_id: &EntityUID,
381 extensions: Extensions<'_>,
382 ) -> Result<(Attributes, HashMap<SmolStr, PartialValueSerializedAsExpr>)> {
383 let mut attr_types: HashMap<SmolStr, Type> = HashMap::new();
384 let mut attr_values: HashMap<SmolStr, PartialValueSerializedAsExpr> = HashMap::new();
385 let evaluator = RestrictedEvaluator::new(&extensions);
386
387 for (k, v) in m {
388 let t = Self::jsonval_to_type_helper(&v, action_id);
389 match t {
390 Ok(ty) => attr_types.insert(k.clone(), ty),
391 Err(e) => return Err(e),
392 };
393
394 #[allow(clippy::expect_used)]
404 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`");
405 let pv = evaluator
406 .partial_interpret(e.as_borrowed())
407 .map_err(|err| {
408 SchemaError::ActionAttrEval(EntityAttrEvaluationError {
409 uid: action_id.clone(),
410 attr: k.clone(),
411 err,
412 })
413 })?;
414 attr_values.insert(k.clone(), pv.into());
415 }
416 Ok((
417 Attributes::with_required_attributes(attr_types),
418 attr_values,
419 ))
420 }
421
422 fn build_action_ids(
426 schema_file_actions: HashMap<SmolStr, ActionType>,
427 schema_namespace: Option<&Name>,
428 extensions: Extensions<'_>,
429 ) -> Result<ActionsDef> {
430 Ok(ActionsDef {
431 actions: schema_file_actions
432 .into_iter()
433 .map(|(action_id_str, action_type)| -> Result<_> {
434 let action_id = Self::parse_action_id_with_namespace(
435 &ActionEntityUID::default_type(action_id_str),
436 schema_namespace,
437 )?;
438
439 let (principal_types, resource_types, context) = action_type
440 .applies_to
441 .map(|applies_to| {
442 (
443 applies_to.principal_types,
444 applies_to.resource_types,
445 applies_to.context,
446 )
447 })
448 .unwrap_or_default();
449
450 let applies_to = ValidatorApplySpec::new(
454 Self::parse_apply_spec_type_list(principal_types, schema_namespace)?,
455 Self::parse_apply_spec_type_list(resource_types, schema_namespace)?,
456 );
457
458 let context = Self::try_schema_type_into_validator_type(
459 schema_namespace,
460 context.into_inner(),
461 )?;
462
463 let parents = action_type
464 .member_of
465 .unwrap_or_default()
466 .iter()
467 .map(|parent| -> Result<_> {
468 Self::parse_action_id_with_namespace(parent, schema_namespace)
469 })
470 .collect::<Result<HashSet<_>>>()?;
471
472 let (attribute_types, attributes) =
473 Self::convert_attr_jsonval_map_to_attributes(
474 action_type.attributes.unwrap_or_default(),
475 &action_id,
476 extensions,
477 )?;
478
479 Ok((
480 action_id,
481 ActionFragment {
482 context,
483 applies_to,
484 parents,
485 attribute_types,
486 attributes,
487 },
488 ))
489 })
490 .collect::<Result<HashMap<_, _>>>()?,
491 })
492 }
493
494 fn check_action_behavior(
500 schema_file: &NamespaceDefinition,
501 action_behavior: ActionBehavior,
502 ) -> Result<()> {
503 if schema_file
504 .entity_types
505 .iter()
506 .any(|(name, _)| name == ACTION_ENTITY_TYPE)
510 {
511 return Err(SchemaError::ActionEntityTypeDeclared);
512 }
513 if action_behavior == ActionBehavior::ProhibitAttributes {
514 let mut actions_with_attributes: Vec<String> = Vec::new();
515 for (name, a) in &schema_file.actions {
516 if a.attributes.is_some() {
517 actions_with_attributes.push(name.to_string());
518 }
519 }
520 if !actions_with_attributes.is_empty() {
521 return Err(SchemaError::UnsupportedFeature(
522 UnsupportedFeature::ActionAttributes(actions_with_attributes),
523 ));
524 }
525 }
526
527 Ok(())
528 }
529
530 fn parse_record_attributes(
535 schema_namespace: Option<&Name>,
536 attrs: impl IntoIterator<Item = (SmolStr, TypeOfAttribute)>,
537 ) -> Result<WithUnresolvedTypeDefs<Attributes>> {
538 let attrs_with_type_defs = attrs
539 .into_iter()
540 .map(|(attr, ty)| -> Result<_> {
541 Ok((
542 attr,
543 (
544 Self::try_schema_type_into_validator_type(schema_namespace, ty.ty)?,
545 ty.required,
546 ),
547 ))
548 })
549 .collect::<Result<Vec<_>>>()?;
550 Ok(WithUnresolvedTypeDefs::new(|typ_defs| {
551 attrs_with_type_defs
552 .into_iter()
553 .map(|(s, (attr_ty, is_req))| {
554 attr_ty
555 .resolve_type_defs(typ_defs)
556 .map(|ty| (s, AttributeType::new(ty, is_req)))
557 })
558 .collect::<Result<Vec<_>>>()
559 .map(Attributes::with_attributes)
560 }))
561 }
562
563 fn parse_apply_spec_type_list(
568 types: Option<Vec<SmolStr>>,
569 namespace: Option<&Name>,
570 ) -> Result<HashSet<EntityType>> {
571 types
572 .map(|types| {
573 types
574 .iter()
575 .map(|ty_str| {
579 Ok(EntityType::Specified(
580 Self::parse_possibly_qualified_name_with_default_namespace(
581 ty_str, namespace,
582 )
583 .map_err(SchemaError::ParseEntityType)?,
584 ))
585 })
586 .collect::<Result<HashSet<_>>>()
588 })
589 .unwrap_or_else(|| Ok(HashSet::from([EntityType::Unspecified])))
590 }
591
592 pub(crate) fn parse_possibly_qualified_name_with_default_namespace(
597 name_str: &SmolStr,
598 default_namespace: Option<&Name>,
599 ) -> std::result::Result<Name, ParseErrors> {
600 let name = Name::from_normalized_str(name_str)?;
601
602 let qualified_name = if name.namespace_components().next().is_some() {
603 name
605 } else {
606 match default_namespace {
609 Some(namespace) => {
610 Name::type_in_namespace(name.basename().clone(), namespace.clone())
611 }
612 None => name,
613 }
614 };
615
616 Ok(qualified_name)
617 }
618
619 fn parse_unqualified_name_with_namespace(
623 type_name: impl AsRef<str>,
624 namespace: Option<Name>,
625 ) -> std::result::Result<Name, ParseErrors> {
626 let type_name = Id::from_normalized_str(type_name.as_ref())?;
627 match namespace {
628 Some(namespace) => Ok(Name::type_in_namespace(type_name, namespace)),
629 None => Ok(Name::unqualified_name(type_name)),
630 }
631 }
632
633 fn parse_action_id_with_namespace(
639 action_id: &ActionEntityUID,
640 namespace: Option<&Name>,
641 ) -> Result<EntityUID> {
642 let namespaced_action_type = if let Some(action_ty) = &action_id.ty {
643 Self::parse_possibly_qualified_name_with_default_namespace(action_ty, namespace)
644 .map_err(SchemaError::ParseEntityType)?
645 } else {
646 #[allow(clippy::expect_used)]
648 let id = Id::from_normalized_str(ACTION_ENTITY_TYPE).expect(
649 "Expected that the constant ACTION_ENTITY_TYPE would be a valid entity type.",
650 );
651 match namespace {
652 Some(namespace) => Name::type_in_namespace(id, namespace.clone()),
653 None => Name::unqualified_name(id),
654 }
655 };
656 Ok(EntityUID::from_components(
657 namespaced_action_type,
658 Eid::new(action_id.id.clone()),
659 ))
660 }
661
662 pub(crate) fn try_schema_type_into_validator_type(
668 default_namespace: Option<&Name>,
669 schema_ty: SchemaType,
670 ) -> Result<WithUnresolvedTypeDefs<Type>> {
671 match schema_ty {
672 SchemaType::Type(SchemaTypeVariant::String) => Ok(Type::primitive_string().into()),
673 SchemaType::Type(SchemaTypeVariant::Long) => Ok(Type::primitive_long().into()),
674 SchemaType::Type(SchemaTypeVariant::Boolean) => Ok(Type::primitive_boolean().into()),
675 SchemaType::Type(SchemaTypeVariant::Set { element }) => Ok(
676 Self::try_schema_type_into_validator_type(default_namespace, *element)?
677 .map(Type::set),
678 ),
679 SchemaType::Type(SchemaTypeVariant::Record {
680 attributes,
681 additional_attributes,
682 }) => {
683 if cfg!(not(feature = "partial-validate")) && additional_attributes {
684 Err(SchemaError::UnsupportedFeature(
685 UnsupportedFeature::OpenRecordsAndEntities,
686 ))
687 } else {
688 Ok(
689 Self::parse_record_attributes(default_namespace, attributes)?.map(
690 move |attrs| {
691 Type::record_with_attributes(
692 attrs,
693 if additional_attributes {
694 OpenTag::OpenAttributes
695 } else {
696 OpenTag::ClosedAttributes
697 },
698 )
699 },
700 ),
701 )
702 }
703 }
704 SchemaType::Type(SchemaTypeVariant::Entity { name }) => {
705 let entity_type_name = Self::parse_possibly_qualified_name_with_default_namespace(
706 &name,
707 default_namespace,
708 )
709 .map_err(SchemaError::ParseEntityType)?;
710 Ok(Type::named_entity_reference(entity_type_name).into())
711 }
712 SchemaType::Type(SchemaTypeVariant::Extension { name }) => {
713 let extension_type_name =
714 Name::from_normalized_str(&name).map_err(SchemaError::ParseExtensionType)?;
715 Ok(Type::extension(extension_type_name).into())
716 }
717 SchemaType::TypeDef { type_name } => {
718 let defined_type_name = Self::parse_possibly_qualified_name_with_default_namespace(
719 &type_name,
720 default_namespace,
721 )
722 .map_err(SchemaError::ParseCommonType)?;
723 Ok(WithUnresolvedTypeDefs::new(move |typ_defs| {
724 typ_defs.get(&defined_type_name).cloned().ok_or(
725 SchemaError::UndeclaredCommonTypes(HashSet::from([type_name.to_string()])),
726 )
727 }))
728 }
729 }
730 }
731
732 pub fn namespace(&self) -> &Option<Name> {
734 &self.namespace
735 }
736}