1#![allow(
19 clippy::missing_panics_doc,
20 clippy::missing_errors_doc,
21 clippy::similar_names
22)]
23pub use ast::Effect;
24pub use authorizer::Decision;
25use cedar_policy_core::ast;
26use cedar_policy_core::authorizer;
27use cedar_policy_core::entities;
28use cedar_policy_core::entities::JsonDeserializationErrorContext;
29use cedar_policy_core::entities::{ContextSchema, Dereference, JsonDeserializationError};
30use cedar_policy_core::est;
31use cedar_policy_core::evaluator::{Evaluator, RestrictedEvaluator};
32pub use cedar_policy_core::extensions;
33use cedar_policy_core::extensions::Extensions;
34use cedar_policy_core::parser;
35pub use cedar_policy_core::parser::err::ParseErrors;
36use cedar_policy_core::parser::SourceInfo;
37pub use cedar_policy_validator::{TypeErrorKind, ValidationErrorKind, ValidationWarningKind};
38use itertools::Itertools;
39use ref_cast::RefCast;
40use serde::{Deserialize, Serialize};
41use smol_str::SmolStr;
42use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
43use std::str::FromStr;
44use thiserror::Error;
45
46#[repr(transparent)]
48#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Hash, RefCast)]
49pub struct SlotId(ast::SlotId);
50
51impl SlotId {
52 pub fn principal() -> Self {
54 Self(ast::SlotId::principal())
55 }
56
57 pub fn resource() -> Self {
59 Self(ast::SlotId::resource())
60 }
61}
62
63impl std::fmt::Display for SlotId {
64 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65 write!(f, "{}", self.0)
66 }
67}
68
69impl From<ast::SlotId> for SlotId {
70 fn from(a: ast::SlotId) -> Self {
71 Self(a)
72 }
73}
74
75impl From<SlotId> for ast::SlotId {
76 fn from(s: SlotId) -> Self {
77 s.0
78 }
79}
80
81#[repr(transparent)]
83#[derive(Debug, Clone, PartialEq, Eq, RefCast)]
84pub struct Entity(ast::Entity);
85
86impl Entity {
87 pub fn new(
92 uid: EntityUid,
93 attrs: HashMap<String, RestrictedExpression>,
94 parents: HashSet<EntityUid>,
95 ) -> Self {
96 Self(ast::Entity::new(
99 uid.0,
100 attrs
101 .into_iter()
102 .map(|(k, v)| (SmolStr::from(k), v.0))
103 .collect(),
104 parents.into_iter().map(|uid| uid.0).collect(),
105 ))
106 }
107
108 pub fn with_uid(uid: EntityUid) -> Self {
110 Self(ast::Entity::with_uid(uid.0))
111 }
112
113 pub fn uid(&self) -> EntityUid {
115 EntityUid(self.0.uid())
116 }
117
118 pub fn attr(&self, attr: &str) -> Option<Result<EvalResult, EvaluationError>> {
122 let expr = self.0.get(attr)?;
123 let all_ext = Extensions::all_available();
124 let evaluator = RestrictedEvaluator::new(&all_ext);
125 Some(
126 evaluator
127 .interpret(expr.as_borrowed())
128 .map(EvalResult::from)
129 .map_err(|e| EvaluationError::StringMessage(e.to_string())),
130 )
131 }
132}
133
134impl std::fmt::Display for Entity {
135 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
136 write!(f, "{}", self.0)
137 }
138}
139
140#[repr(transparent)]
143#[derive(Debug, Clone, Default, PartialEq, Eq, RefCast)]
144pub struct Entities(pub(crate) entities::Entities);
145
146impl Entities {
147 pub fn empty() -> Self {
149 Self(entities::Entities::new())
150 }
151
152 pub fn get(&self, uid: &EntityUid) -> Option<&Entity> {
154 match self.0.entity(&uid.0) {
155 Dereference::Residual(_) | Dereference::NoSuchEntity => None,
156 Dereference::Data(e) => Some(Entity::ref_cast(e)),
157 }
158 }
159
160 #[must_use]
164 pub fn partial(self) -> Self {
165 Self(self.0.partial())
166 }
167
168 pub fn iter(&self) -> impl Iterator<Item = &Entity> {
170 self.0.iter().map(Entity::ref_cast)
171 }
172
173 pub fn from_entities(
176 entities: impl IntoIterator<Item = Entity>,
177 ) -> Result<Self, entities::EntitiesError> {
178 entities::Entities::from_entities(
179 entities.into_iter().map(|e| e.0),
180 entities::TCComputation::ComputeNow,
181 )
182 .map(Entities)
183 }
184
185 pub fn from_json_str(
191 json: &str,
192 schema: Option<&Schema>,
193 ) -> Result<Self, entities::EntitiesError> {
194 let eparser = entities::EntityJsonParser::new(
195 schema.map(|s| &s.0),
196 Extensions::all_available(),
197 entities::TCComputation::ComputeNow,
198 );
199 eparser.from_json_str(json).map(Entities)
200 }
201
202 pub fn from_json_value(
209 json: serde_json::Value,
210 schema: Option<&Schema>,
211 ) -> Result<Self, entities::EntitiesError> {
212 let eparser = entities::EntityJsonParser::new(
213 schema.map(|s| &s.0),
214 Extensions::all_available(),
215 entities::TCComputation::ComputeNow,
216 );
217 eparser.from_json_value(json).map(Entities)
218 }
219
220 pub fn from_json_file(
227 json: impl std::io::Read,
228 schema: Option<&Schema>,
229 ) -> Result<Self, entities::EntitiesError> {
230 let eparser = entities::EntityJsonParser::new(
231 schema.map(|s| &s.0),
232 Extensions::all_available(),
233 entities::TCComputation::ComputeNow,
234 );
235 eparser.from_json_file(json).map(Entities)
236 }
237
238 pub fn is_ancestor_of(&self, a: &EntityUid, b: &EntityUid) -> bool {
241 match self.0.entity(&b.0) {
242 Dereference::Data(b) => b.is_descendant_of(&a.0),
243 _ => a == b, }
245 }
246
247 pub fn ancestors<'a>(
250 &'a self,
251 euid: &EntityUid,
252 ) -> Option<impl Iterator<Item = &'a EntityUid>> {
253 let entity = match self.0.entity(&euid.0) {
254 Dereference::Residual(_) | Dereference::NoSuchEntity => None,
255 Dereference::Data(e) => Some(e),
256 }?;
257 Some(entity.ancestors().map(EntityUid::ref_cast))
258 }
259
260 pub fn write_to_json(
268 &self,
269 f: impl std::io::Write,
270 ) -> std::result::Result<(), entities::EntitiesError> {
271 self.0.write_to_json(f)
272 }
273}
274
275#[repr(transparent)]
277#[derive(Debug, RefCast)]
278pub struct Authorizer(authorizer::Authorizer);
279
280impl Default for Authorizer {
281 fn default() -> Self {
282 Self::new()
283 }
284}
285
286impl Authorizer {
287 pub fn new() -> Self {
289 Self(authorizer::Authorizer::new())
290 }
291
292 pub fn is_authorized(&self, r: &Request, p: &PolicySet, e: &Entities) -> Response {
298 self.0.is_authorized(&r.0, &p.ast, &e.0).into()
299 }
300
301 pub fn is_authorized_partial(
306 &self,
307 query: &Request,
308 policy_set: &PolicySet,
309 entities: &Entities,
310 ) -> PartialResponse {
311 let response = self
312 .0
313 .is_authorized_core(&query.0, &policy_set.ast, &entities.0);
314 match response {
315 authorizer::ResponseKind::FullyEvaluated(a) => PartialResponse::Concrete(Response {
316 decision: a.decision,
317 diagnostics: Diagnostics {
318 reason: a.diagnostics.reason.into_iter().map(PolicyId).collect(),
319 errors: a.diagnostics.errors.into_iter().collect(),
320 },
321 }),
322 authorizer::ResponseKind::Partial(p) => PartialResponse::Residual(ResidualResponse {
323 residuals: PolicySet::from_ast(p.residuals),
324 diagnostics: Diagnostics {
325 reason: p.diagnostics.reason.into_iter().map(PolicyId).collect(),
326 errors: p.diagnostics.errors.into_iter().collect(),
327 },
328 }),
329 }
330 }
331}
332
333#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
335pub struct Response {
336 decision: Decision,
338 diagnostics: Diagnostics,
340}
341
342#[derive(Debug, PartialEq, Clone)]
345pub enum PartialResponse {
346 Concrete(Response),
348 Residual(ResidualResponse),
350}
351
352#[derive(Debug, PartialEq, Eq, Clone)]
354pub struct ResidualResponse {
355 residuals: PolicySet,
357 diagnostics: Diagnostics,
359}
360
361#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
363pub struct Diagnostics {
364 reason: HashSet<PolicyId>,
367 errors: HashSet<String>,
369}
370
371impl Diagnostics {
372 pub fn reason(&self) -> impl Iterator<Item = &PolicyId> {
374 self.reason.iter()
375 }
376
377 pub fn errors(&self) -> impl Iterator<Item = EvaluationError> + '_ {
379 self.errors
380 .iter()
381 .cloned()
382 .map(EvaluationError::StringMessage)
383 }
384}
385
386impl Response {
387 pub fn new(decision: Decision, reason: HashSet<PolicyId>, errors: HashSet<String>) -> Self {
389 Self {
390 decision,
391 diagnostics: Diagnostics { reason, errors },
392 }
393 }
394
395 pub fn decision(&self) -> Decision {
397 self.decision
398 }
399
400 pub fn diagnostics(&self) -> &Diagnostics {
402 &self.diagnostics
403 }
404}
405
406impl From<authorizer::Response> for Response {
407 fn from(a: authorizer::Response) -> Self {
408 Self {
409 decision: a.decision,
410 diagnostics: Diagnostics {
411 reason: a.diagnostics.reason.into_iter().map(PolicyId).collect(),
412 errors: a.diagnostics.errors.into_iter().collect(),
413 },
414 }
415 }
416}
417
418impl ResidualResponse {
419 pub fn new(residuals: PolicySet, reason: HashSet<PolicyId>, errors: HashSet<String>) -> Self {
421 Self {
422 residuals,
423 diagnostics: Diagnostics { reason, errors },
424 }
425 }
426
427 pub fn residuals(&self) -> &PolicySet {
429 &self.residuals
430 }
431
432 pub fn diagnostics(&self) -> &Diagnostics {
434 &self.diagnostics
435 }
436}
437
438#[derive(Debug, Clone, PartialEq, Eq, Error)]
441pub enum EvaluationError {
442 #[error("{0}")]
445 StringMessage(String),
446}
447
448#[derive(Default, Eq, PartialEq, Copy, Clone, Debug)]
450#[non_exhaustive]
451pub enum ValidationMode {
452 #[default]
455 Strict,
456 Permissive,
458}
459
460impl From<ValidationMode> for cedar_policy_validator::ValidationMode {
461 fn from(mode: ValidationMode) -> Self {
462 match mode {
463 ValidationMode::Strict => Self::Strict,
464 ValidationMode::Permissive => Self::Permissive,
465 }
466 }
467}
468
469#[repr(transparent)]
471#[derive(Debug, RefCast)]
472pub struct Validator(cedar_policy_validator::Validator);
473
474impl Validator {
475 pub fn new(schema: Schema) -> Self {
478 Self(cedar_policy_validator::Validator::new(schema.0))
479 }
480
481 pub fn validate<'a>(
489 &'a self,
490 pset: &'a PolicySet,
491 mode: ValidationMode,
492 ) -> ValidationResult<'a> {
493 ValidationResult::from(self.0.validate(&pset.ast, mode.into()))
494 }
495}
496
497#[derive(Debug)]
500pub struct SchemaFragment(cedar_policy_validator::ValidatorSchemaFragment);
501
502impl SchemaFragment {
503 pub fn namespaces(&self) -> impl Iterator<Item = Option<EntityNamespace>> + '_ {
507 self.0
508 .namespaces()
509 .map(|ns| ns.as_ref().map(|ns| EntityNamespace(ns.clone())))
510 }
511
512 pub fn from_json_value(json: serde_json::Value) -> Result<Self, SchemaError> {
515 Ok(Self(
516 cedar_policy_validator::SchemaFragment::from_json_value(json)?.try_into()?,
517 ))
518 }
519
520 pub fn from_file(file: impl std::io::Read) -> Result<Self, SchemaError> {
522 Ok(Self(
523 cedar_policy_validator::SchemaFragment::from_file(file)?.try_into()?,
524 ))
525 }
526}
527
528impl TryInto<Schema> for SchemaFragment {
529 type Error = SchemaError;
530
531 fn try_into(self) -> Result<Schema, Self::Error> {
535 Ok(Schema(
536 cedar_policy_validator::ValidatorSchema::from_schema_fragments([self.0])?,
537 ))
538 }
539}
540
541impl FromStr for SchemaFragment {
542 type Err = SchemaError;
543 fn from_str(src: &str) -> Result<Self, Self::Err> {
550 Ok(Self(
551 serde_json::from_str::<cedar_policy_validator::SchemaFragment>(src)
552 .map_err(cedar_policy_validator::SchemaError::from)?
553 .try_into()?,
554 ))
555 }
556}
557
558#[repr(transparent)]
560#[derive(Debug, Clone, RefCast)]
561pub struct Schema(pub(crate) cedar_policy_validator::ValidatorSchema);
562
563impl FromStr for Schema {
564 type Err = SchemaError;
565
566 fn from_str(schema_src: &str) -> Result<Self, Self::Err> {
573 Ok(Self(schema_src.parse()?))
574 }
575}
576
577impl Schema {
578 pub fn from_schema_fragments(
583 fragments: impl IntoIterator<Item = SchemaFragment>,
584 ) -> Result<Self, SchemaError> {
585 Ok(Self(
586 cedar_policy_validator::ValidatorSchema::from_schema_fragments(
587 fragments.into_iter().map(|f| f.0),
588 )?,
589 ))
590 }
591
592 pub fn from_json_value(json: serde_json::Value) -> Result<Self, SchemaError> {
595 Ok(Self(
596 cedar_policy_validator::ValidatorSchema::from_json_value(json)?,
597 ))
598 }
599
600 pub fn from_file(file: impl std::io::Read) -> Result<Self, SchemaError> {
602 Ok(Self(cedar_policy_validator::ValidatorSchema::from_file(
603 file,
604 )?))
605 }
606
607 pub fn action_entities(&self) -> Result<Entities, entities::EntitiesError> {
610 Ok(Entities(self.0.action_entities()?))
611 }
612}
613
614#[derive(Debug, Error)]
616pub enum SchemaError {
617 #[error("JSON Schema file could not be parsed: {0}")]
619 ParseJson(serde_json::Error),
620 #[error("Transitive closure error on action hierarchy: {0}")]
623 ActionTransitiveClosureError(String),
624 #[error("Transitive closure error on entity hierarchy: {0}")]
627 EntityTransitiveClosureError(String),
628 #[error("Unsupported feature used in schema: {0}")]
631 UnsupportedSchemaFeature(String),
632 #[error("Undeclared entity types: {0:?}")]
635 UndeclaredEntityTypes(HashSet<String>),
636 #[error("Undeclared actions: {0:?}")]
638 UndeclaredActions(HashSet<String>),
639 #[error("Undeclared common types: {0:?}")]
641 UndeclaredCommonType(HashSet<String>),
642 #[error("Duplicate entity type {0}")]
645 DuplicateEntityType(String),
646 #[error("Duplicate action {0}")]
649 DuplicateAction(String),
650 #[error("Duplicate common type {0}")]
653 DuplicateCommonType(String),
654 #[error("Cycle in action hierarchy")]
656 CycleInActionHierarchy,
657 #[error("Parse error in entity type: {0}")]
659 EntityTypeParse(ParseErrors),
660 #[error("Parse error in namespace identifier: {0}")]
662 NamespaceParse(ParseErrors),
663 #[error("Parse error in common type identifier: {0}")]
665 CommonTypeParseError(ParseErrors),
666 #[error("Parse error in extension type: {0}")]
668 ExtensionTypeParse(ParseErrors),
669 #[error("Entity type `Action` declared in `entityTypes` list.")]
674 ActionEntityTypeDeclared,
675 #[error("Actions declared with `attributes`: [{}]", .0.iter().map(String::as_str).join(", "))]
678 ActionEntityAttributes(Vec<String>),
679 #[error("Action context or entity type shape is not a record")]
682 ContextOrShapeNotRecord,
683 #[error("Action attribute is an empty set")]
685 ActionEntityAttributeEmptySet,
686 #[error(
688 "Action has an attribute of unsupported type (escaped expression, entity or extension)"
689 )]
690 ActionEntityAttributeUnsupportedType,
691}
692
693#[doc(hidden)]
694impl From<cedar_policy_validator::SchemaError> for SchemaError {
695 fn from(value: cedar_policy_validator::SchemaError) -> Self {
696 match value {
697 cedar_policy_validator::SchemaError::ParseFileFormat(e) => Self::ParseJson(e),
698 cedar_policy_validator::SchemaError::ActionTransitiveClosureError(e) => {
699 Self::ActionTransitiveClosureError(e.to_string())
700 }
701 cedar_policy_validator::SchemaError::EntityTransitiveClosureError(e) => {
702 Self::EntityTransitiveClosureError(e.to_string())
703 }
704 cedar_policy_validator::SchemaError::UnsupportedSchemaFeature(e) => {
705 Self::UnsupportedSchemaFeature(e.to_string())
706 }
707 cedar_policy_validator::SchemaError::UndeclaredEntityTypes(e) => {
708 Self::UndeclaredEntityTypes(e)
709 }
710 cedar_policy_validator::SchemaError::UndeclaredActions(e) => Self::UndeclaredActions(e),
711 cedar_policy_validator::SchemaError::UndeclaredCommonType(c) => {
712 Self::UndeclaredCommonType(c)
713 }
714 cedar_policy_validator::SchemaError::DuplicateEntityType(e) => {
715 Self::DuplicateEntityType(e)
716 }
717 cedar_policy_validator::SchemaError::DuplicateAction(e) => Self::DuplicateAction(e),
718 cedar_policy_validator::SchemaError::DuplicateCommonType(c) => {
719 Self::DuplicateCommonType(c)
720 }
721 cedar_policy_validator::SchemaError::CycleInActionHierarchy => {
722 Self::CycleInActionHierarchy
723 }
724 cedar_policy_validator::SchemaError::EntityTypeParseError(e) => {
725 Self::EntityTypeParse(ParseErrors(e))
726 }
727 cedar_policy_validator::SchemaError::NamespaceParseError(e) => {
728 Self::NamespaceParse(ParseErrors(e))
729 }
730 cedar_policy_validator::SchemaError::CommonTypeParseError(e) => {
731 Self::CommonTypeParseError(ParseErrors(e))
732 }
733 cedar_policy_validator::SchemaError::ExtensionTypeParseError(e) => {
734 Self::ExtensionTypeParse(ParseErrors(e))
735 }
736 cedar_policy_validator::SchemaError::ActionEntityTypeDeclared => {
737 Self::ActionEntityTypeDeclared
738 }
739 cedar_policy_validator::SchemaError::ActionEntityAttributes(e) => {
740 Self::ActionEntityAttributes(e)
741 }
742 cedar_policy_validator::SchemaError::ContextOrShapeNotRecord
743 | cedar_policy_validator::SchemaError::ActionEntityAttributeEmptySet
744 | cedar_policy_validator::SchemaError::ActionEntityAttributeUnsupportedType => {
745 Self::ContextOrShapeNotRecord
746 }
747 }
748 }
749}
750
751#[derive(Debug)]
756pub struct ValidationResult<'a> {
757 validation_errors: Vec<ValidationError<'a>>,
758}
759
760impl<'a> ValidationResult<'a> {
761 pub fn validation_passed(&self) -> bool {
763 self.validation_errors.is_empty()
764 }
765
766 pub fn validation_errors(&self) -> impl Iterator<Item = &ValidationError<'a>> {
768 self.validation_errors.iter()
769 }
770}
771
772impl<'a> From<cedar_policy_validator::ValidationResult<'a>> for ValidationResult<'a> {
773 fn from(r: cedar_policy_validator::ValidationResult<'a>) -> Self {
774 Self {
775 validation_errors: r
776 .into_validation_errors()
777 .map(ValidationError::from)
778 .collect(),
779 }
780 }
781}
782
783#[derive(Debug, Error)]
788pub struct ValidationError<'a> {
789 location: SourceLocation<'a>,
790 error_kind: ValidationErrorKind,
791}
792
793impl<'a> ValidationError<'a> {
794 pub fn error_kind(&self) -> &ValidationErrorKind {
796 &self.error_kind
797 }
798
799 pub fn location(&self) -> &SourceLocation<'a> {
801 &self.location
802 }
803}
804
805impl<'a> From<cedar_policy_validator::ValidationError<'a>> for ValidationError<'a> {
806 fn from(err: cedar_policy_validator::ValidationError<'a>) -> Self {
807 let (location, error_kind) = err.into_location_and_error_kind();
808 Self {
809 location: SourceLocation::from(location),
810 error_kind,
811 }
812 }
813}
814
815impl<'a> std::fmt::Display for ValidationError<'a> {
816 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
817 write!(f, "Validation error on policy {}", self.location.policy_id)?;
818 if let (Some(range_start), Some(range_end)) =
819 (self.location().range_start(), self.location().range_end())
820 {
821 write!(f, " at offset {range_start}-{range_end}")?;
822 }
823 write!(f, ": {}", self.error_kind())
824 }
825}
826
827#[derive(Debug, Clone, Eq, PartialEq)]
829pub struct SourceLocation<'a> {
830 policy_id: &'a PolicyId,
831 source_range: Option<SourceInfo>,
832}
833
834impl<'a> SourceLocation<'a> {
835 pub fn policy_id(&self) -> &'a PolicyId {
837 self.policy_id
838 }
839
840 pub fn range_start(&self) -> Option<usize> {
843 self.source_range.as_ref().map(SourceInfo::range_start)
844 }
845
846 pub fn range_end(&self) -> Option<usize> {
849 self.source_range.as_ref().map(SourceInfo::range_end)
850 }
851}
852
853impl<'a> From<cedar_policy_validator::SourceLocation<'a>> for SourceLocation<'a> {
854 fn from(loc: cedar_policy_validator::SourceLocation<'a>) -> SourceLocation<'a> {
855 let policy_id: &'a PolicyId = PolicyId::ref_cast(loc.policy_id());
856 let source_range = loc.into_source_info();
857 Self {
858 policy_id,
859 source_range,
860 }
861 }
862}
863
864pub fn confusable_string_checker<'a>(
866 templates: impl Iterator<Item = &'a Template>,
867) -> impl Iterator<Item = ValidationWarning<'a>> {
868 cedar_policy_validator::confusable_string_checks(templates.map(|t| &t.ast))
869 .map(std::convert::Into::into)
870}
871
872#[derive(Debug, Error)]
873#[error("Warning on policy {}: {}", .location.policy_id, .kind)]
874pub struct ValidationWarning<'a> {
876 location: SourceLocation<'a>,
877 kind: ValidationWarningKind,
878}
879
880impl<'a> ValidationWarning<'a> {
881 pub fn warning_kind(&self) -> &ValidationWarningKind {
883 &self.kind
884 }
885
886 pub fn location(&self) -> &SourceLocation<'a> {
888 &self.location
889 }
890}
891
892#[doc(hidden)]
893impl<'a> From<cedar_policy_validator::ValidationWarning<'a>> for ValidationWarning<'a> {
894 fn from(w: cedar_policy_validator::ValidationWarning<'a>) -> Self {
895 let (loc, kind) = w.to_kind_and_location();
896 ValidationWarning {
897 location: SourceLocation {
898 policy_id: PolicyId::ref_cast(loc),
899 source_range: None,
900 },
901 kind,
902 }
903 }
904}
905
906#[repr(transparent)]
908#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, RefCast)]
909pub struct EntityId(ast::Eid);
910
911impl FromStr for EntityId {
912 type Err = ParseErrors;
913 fn from_str(eid_str: &str) -> Result<Self, Self::Err> {
914 Ok(Self(ast::Eid::new(eid_str)))
915 }
916}
917
918impl AsRef<str> for EntityId {
919 fn as_ref(&self) -> &str {
920 self.0.as_ref()
921 }
922}
923
924impl std::fmt::Display for EntityId {
928 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
929 write!(f, "{}", self.0)
930 }
931}
932
933#[repr(transparent)]
935#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, RefCast)]
936pub struct EntityTypeName(ast::Name);
937
938impl FromStr for EntityTypeName {
939 type Err = ParseErrors;
940
941 fn from_str(namespace_type_str: &str) -> Result<Self, Self::Err> {
942 match ast::Name::from_str(namespace_type_str) {
943 Ok(name) => Ok(Self(name)),
944 Err(errs) => Err(ParseErrors(errs)),
945 }
946 }
947}
948
949impl std::fmt::Display for EntityTypeName {
950 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
951 write!(f, "{}", self.0)
952 }
953}
954
955#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
957pub struct EntityNamespace(ast::Name);
958
959impl FromStr for EntityNamespace {
960 type Err = ParseErrors;
961
962 fn from_str(namespace_str: &str) -> Result<Self, Self::Err> {
963 Ok(Self(
964 ast::Name::from_str(namespace_str).map_err(ParseErrors)?,
965 ))
966 }
967}
968
969impl std::fmt::Display for EntityNamespace {
970 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
971 write!(f, "{}", self.0)
972 }
973}
974
975#[repr(transparent)]
977#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, RefCast)]
978pub struct EntityUid(ast::EntityUID);
979
980impl EntityUid {
981 pub fn type_name(&self) -> &EntityTypeName {
983 match self.0.entity_type() {
984 ast::EntityType::Unspecified => panic!("Impossible to have an unspecified entity"),
985 ast::EntityType::Concrete(name) => EntityTypeName::ref_cast(name),
986 }
987 }
988
989 pub fn id(&self) -> &EntityId {
991 EntityId::ref_cast(self.0.eid())
992 }
993
994 pub fn from_type_name_and_id(name: EntityTypeName, id: EntityId) -> Self {
996 Self(ast::EntityUID::from_components(name.0, id.0))
997 }
998
999 pub fn from_json(json: serde_json::Value) -> Result<Self, impl std::error::Error> {
1006 let parsed: entities::EntityUidJSON = serde_json::from_value(json)?;
1007 Ok::<Self, entities::JsonDeserializationError>(Self(
1008 parsed.into_euid(|| JsonDeserializationErrorContext::EntityUid)?,
1009 ))
1010 }
1011
1012 #[cfg(test)]
1014 pub(crate) fn from_strs(typename: &str, id: &str) -> Self {
1015 Self::from_type_name_and_id(
1016 EntityTypeName::from_str(typename).unwrap(),
1017 EntityId::from_str(id).unwrap(),
1018 )
1019 }
1020}
1021
1022impl FromStr for EntityUid {
1023 type Err = ParseErrors;
1024
1025 fn from_str(uid: &str) -> Result<Self, Self::Err> {
1032 parser::parse_euid(uid).map(EntityUid).map_err(ParseErrors)
1033 }
1034}
1035
1036impl std::fmt::Display for EntityUid {
1037 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1038 write!(f, "{}", self.0)
1039 }
1040}
1041
1042#[derive(Error, Debug)]
1044#[non_exhaustive]
1045pub enum PolicySetError {
1046 #[error("Collision in template or policy id")]
1048 AlreadyDefined,
1049 #[error("Unable to link template: {0}")]
1051 LinkingError(#[from] ast::LinkingError),
1052 #[error("Expected static policy, but a template-linked policy was provided")]
1054 ExpectedStatic,
1055 #[error("expected a template, but a static policy was provided")]
1057 ExpectedTemplate,
1058}
1059
1060impl From<ast::PolicySetError> for PolicySetError {
1061 fn from(e: ast::PolicySetError) -> Self {
1062 match e {
1063 ast::PolicySetError::Occupied => Self::AlreadyDefined,
1064 }
1065 }
1066}
1067
1068impl From<ast::ContainsSlot> for PolicySetError {
1069 fn from(_: ast::ContainsSlot) -> Self {
1070 Self::ExpectedStatic
1071 }
1072}
1073
1074#[derive(Debug, Clone, Default)]
1076pub struct PolicySet {
1077 pub(crate) ast: ast::PolicySet,
1080 policies: HashMap<PolicyId, Policy>,
1082 templates: HashMap<PolicyId, Template>,
1084}
1085
1086impl PartialEq for PolicySet {
1087 fn eq(&self, other: &Self) -> bool {
1088 self.ast.eq(&other.ast)
1090 }
1091}
1092impl Eq for PolicySet {}
1093
1094impl FromStr for PolicySet {
1095 type Err = ParseErrors;
1096
1097 fn from_str(policies: &str) -> Result<Self, Self::Err> {
1104 let (ests, pset) = parser::parse_policyset_to_ests_and_pset(policies)?;
1105 let policies = pset.policies().map(|p|
1106 (
1107 PolicyId(p.id().clone()),
1108 Policy { est: ests.get(p.id()).expect("internal invariant violation: policy id exists in asts but not ests").clone(), ast: p.clone() }
1109 )
1110 ).collect();
1111 let templates = pset.templates().map(|t|
1112 (
1113 PolicyId(t.id().clone()),
1114 Template { est: ests.get(t.id()).expect("internal invariant violation: template id exists in asts but not ests").clone(), ast: t.clone() }
1115 )
1116 ).collect();
1117 Ok(Self {
1118 ast: pset,
1119 policies,
1120 templates,
1121 })
1122 }
1123}
1124
1125impl PolicySet {
1126 pub fn new() -> Self {
1128 Self {
1129 ast: ast::PolicySet::new(),
1130 policies: HashMap::new(),
1131 templates: HashMap::new(),
1132 }
1133 }
1134
1135 pub fn from_policies(
1137 policies: impl IntoIterator<Item = Policy>,
1138 ) -> Result<Self, PolicySetError> {
1139 let mut set = Self::new();
1140 for policy in policies {
1141 set.add(policy)?;
1142 }
1143 Ok(set)
1144 }
1145
1146 pub fn add(&mut self, policy: Policy) -> Result<(), PolicySetError> {
1150 if policy.is_static() {
1151 let id = PolicyId(policy.ast.id().clone());
1152 self.ast.add(policy.ast.clone())?;
1153 self.policies.insert(id, policy);
1154 Ok(())
1155 } else {
1156 Err(PolicySetError::ExpectedStatic)
1157 }
1158 }
1159
1160 pub fn add_template(&mut self, template: Template) -> Result<(), PolicySetError> {
1162 let id = PolicyId(template.ast.id().clone());
1163 self.ast.add_template(template.ast.clone())?;
1164 self.templates.insert(id, template);
1165 Ok(())
1166 }
1167
1168 pub fn policies(&self) -> impl Iterator<Item = &Policy> {
1172 self.policies.values()
1173 }
1174
1175 pub fn templates(&self) -> impl Iterator<Item = &Template> {
1177 self.templates.values()
1178 }
1179
1180 pub fn template(&self, id: &PolicyId) -> Option<&Template> {
1182 self.templates.get(id)
1183 }
1184
1185 pub fn policy(&self, id: &PolicyId) -> Option<&Policy> {
1187 self.policies.get(id)
1188 }
1189
1190 pub fn annotation<'a>(&'a self, id: &PolicyId, key: impl AsRef<str>) -> Option<&'a str> {
1192 self.ast
1193 .get(&id.0)?
1194 .annotation(&key.as_ref().parse().ok()?)
1195 .map(smol_str::SmolStr::as_str)
1196 }
1197
1198 pub fn template_annotation(&self, id: &PolicyId, key: impl AsRef<str>) -> Option<String> {
1200 self.ast
1201 .get_template(&id.0)?
1202 .annotation(&key.as_ref().parse().ok()?)
1203 .map(smol_str::SmolStr::to_string)
1204 }
1205
1206 pub fn is_empty(&self) -> bool {
1208 debug_assert_eq!(
1209 self.ast.is_empty(),
1210 self.policies.is_empty() && self.templates.is_empty()
1211 );
1212 self.ast.is_empty()
1213 }
1214
1215 #[allow(clippy::needless_pass_by_value)]
1224 pub fn link(
1225 &mut self,
1226 template_id: PolicyId,
1227 new_id: PolicyId,
1228 vals: HashMap<SlotId, EntityUid>,
1229 ) -> Result<(), PolicySetError> {
1230 let unwrapped: HashMap<ast::SlotId, ast::EntityUID> = vals
1231 .into_iter()
1232 .map(|(key, value)| (key.into(), value.0))
1233 .collect();
1234 let est_vals = unwrapped.iter().map(|(k, v)| (*k, v.into())).collect();
1235 self.ast
1236 .link(template_id.0.clone(), new_id.0.clone(), unwrapped)
1237 .map_err(PolicySetError::LinkingError)?;
1238 let linked_ast = self
1239 .ast
1240 .get(&new_id.0)
1241 .expect("instantiate() didn't fail above, so this shouldn't fail")
1242 .clone();
1243 let lined_est = self
1245 .templates
1246 .get(&template_id)
1247 .ok_or(PolicySetError::ExpectedTemplate)?
1253 .clone()
1254 .est
1255 .link(&est_vals)
1256 .expect("ast.link() didn't fail above, so this shouldn't fail");
1261 self.policies.insert(
1262 new_id,
1263 Policy {
1264 ast: linked_ast,
1265 est: lined_est,
1266 },
1267 );
1268 Ok(())
1269 }
1270
1271 fn from_ast(ast: ast::PolicySet) -> Self {
1277 let policies = ast
1278 .policies()
1279 .map(|p| (PolicyId(p.id().clone()), Policy::from_ast(p.clone())))
1280 .collect();
1281 let templates = ast
1282 .templates()
1283 .map(|t| (PolicyId(t.id().clone()), Template::from_ast(t.clone())))
1284 .collect();
1285 Self {
1286 ast,
1287 policies,
1288 templates,
1289 }
1290 }
1291}
1292
1293impl std::fmt::Display for PolicySet {
1294 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1295 write!(f, "{}", self.ast)
1296 }
1297}
1298
1299#[derive(Debug, Clone)]
1301pub struct Template {
1302 ast: ast::Template,
1305 est: est::Policy,
1314}
1315
1316impl PartialEq for Template {
1317 fn eq(&self, other: &Self) -> bool {
1318 self.ast.eq(&other.ast)
1320 }
1321}
1322impl Eq for Template {}
1323
1324impl Template {
1325 pub fn parse(id: Option<String>, src: impl AsRef<str>) -> Result<Self, ParseErrors> {
1329 let (est, ast) = parser::parse_policy_template_to_est_and_ast(id, src.as_ref())?;
1330 Ok(Self { ast, est })
1331 }
1332
1333 pub fn id(&self) -> &PolicyId {
1335 PolicyId::ref_cast(self.ast.id())
1336 }
1337
1338 #[must_use]
1340 pub fn new_id(&self, id: PolicyId) -> Self {
1341 Self {
1342 ast: self.ast.new_id(id.0),
1343 est: self.est.clone(),
1344 }
1345 }
1346
1347 pub fn effect(&self) -> Effect {
1349 self.ast.effect()
1350 }
1351
1352 pub fn annotation(&self, key: impl AsRef<str>) -> Option<&str> {
1354 self.ast
1355 .annotation(&key.as_ref().parse().ok()?)
1356 .map(smol_str::SmolStr::as_str)
1357 }
1358
1359 pub fn annotations(&self) -> impl Iterator<Item = (&str, &str)> {
1361 self.ast
1362 .annotations()
1363 .map(|(k, v)| (k.as_ref(), v.as_str()))
1364 }
1365
1366 pub fn slots(&self) -> impl Iterator<Item = &SlotId> {
1368 self.ast.slots().map(SlotId::ref_cast)
1369 }
1370
1371 pub fn principal_constraint(&self) -> TemplatePrincipalConstraint {
1373 match self.ast.principal_constraint().as_inner() {
1374 ast::PrincipalOrResourceConstraint::Any => TemplatePrincipalConstraint::Any,
1375 ast::PrincipalOrResourceConstraint::In(eref) => {
1376 TemplatePrincipalConstraint::In(match eref {
1377 ast::EntityReference::EUID(e) => Some(EntityUid(e.as_ref().clone())),
1378 ast::EntityReference::Slot => None,
1379 })
1380 }
1381 ast::PrincipalOrResourceConstraint::Eq(eref) => {
1382 TemplatePrincipalConstraint::Eq(match eref {
1383 ast::EntityReference::EUID(e) => Some(EntityUid(e.as_ref().clone())),
1384 ast::EntityReference::Slot => None,
1385 })
1386 }
1387 }
1388 }
1389
1390 pub fn action_constraint(&self) -> ActionConstraint {
1392 match self.ast.action_constraint() {
1394 ast::ActionConstraint::Any => ActionConstraint::Any,
1395 ast::ActionConstraint::In(ids) => ActionConstraint::In(
1396 ids.iter()
1397 .map(|id| EntityUid(id.as_ref().clone()))
1398 .collect(),
1399 ),
1400 ast::ActionConstraint::Eq(id) => ActionConstraint::Eq(EntityUid(id.as_ref().clone())),
1401 }
1402 }
1403
1404 pub fn resource_constraint(&self) -> TemplateResourceConstraint {
1406 match self.ast.resource_constraint().as_inner() {
1407 ast::PrincipalOrResourceConstraint::Any => TemplateResourceConstraint::Any,
1408 ast::PrincipalOrResourceConstraint::In(eref) => {
1409 TemplateResourceConstraint::In(match eref {
1410 ast::EntityReference::EUID(e) => Some(EntityUid(e.as_ref().clone())),
1411 ast::EntityReference::Slot => None,
1412 })
1413 }
1414 ast::PrincipalOrResourceConstraint::Eq(eref) => {
1415 TemplateResourceConstraint::Eq(match eref {
1416 ast::EntityReference::EUID(e) => Some(EntityUid(e.as_ref().clone())),
1417 ast::EntityReference::Slot => None,
1418 })
1419 }
1420 }
1421 }
1422
1423 #[allow(dead_code)] fn from_json(
1429 id: Option<PolicyId>,
1430 json: serde_json::Value,
1431 ) -> Result<Self, cedar_policy_core::est::EstToAstError> {
1432 let est: est::Policy =
1433 serde_json::from_value(json).map_err(JsonDeserializationError::Serde)?;
1434 Ok(Self {
1435 ast: est.clone().try_into_ast_template(id.map(|id| id.0))?,
1436 est,
1437 })
1438 }
1439
1440 #[allow(dead_code)] fn to_json(&self) -> Result<serde_json::Value, impl std::error::Error> {
1443 serde_json::to_value(&self.est)
1444 }
1445
1446 fn from_ast(ast: ast::Template) -> Self {
1452 let est = ast.clone().into();
1453 Self { ast, est }
1454 }
1455}
1456
1457impl FromStr for Template {
1458 type Err = ParseErrors;
1459
1460 fn from_str(src: &str) -> Result<Self, Self::Err> {
1461 Self::parse(None, src)
1462 }
1463}
1464
1465#[derive(Debug, Clone, PartialEq, Eq)]
1467pub enum PrincipalConstraint {
1468 Any,
1470 In(EntityUid),
1472 Eq(EntityUid),
1474}
1475
1476#[derive(Debug, Clone, PartialEq, Eq)]
1478pub enum TemplatePrincipalConstraint {
1479 Any,
1481 In(Option<EntityUid>),
1484 Eq(Option<EntityUid>),
1487}
1488
1489impl TemplatePrincipalConstraint {
1490 pub fn has_slot(&self) -> bool {
1492 match self {
1493 Self::Any => false,
1494 Self::In(o) | Self::Eq(o) => o.is_none(),
1495 }
1496 }
1497}
1498
1499#[derive(Debug, Clone, PartialEq, Eq)]
1501pub enum ActionConstraint {
1502 Any,
1504 In(Vec<EntityUid>),
1506 Eq(EntityUid),
1508}
1509
1510#[derive(Debug, Clone, PartialEq, Eq)]
1512pub enum ResourceConstraint {
1513 Any,
1515 In(EntityUid),
1517 Eq(EntityUid),
1519}
1520
1521#[derive(Debug, Clone, PartialEq, Eq)]
1523pub enum TemplateResourceConstraint {
1524 Any,
1526 In(Option<EntityUid>),
1529 Eq(Option<EntityUid>),
1532}
1533
1534impl TemplateResourceConstraint {
1535 pub fn has_slot(&self) -> bool {
1537 match self {
1538 Self::Any => false,
1539 Self::In(o) | Self::Eq(o) => o.is_none(),
1540 }
1541 }
1542}
1543
1544#[repr(transparent)]
1546#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize, RefCast)]
1547pub struct PolicyId(ast::PolicyID);
1548
1549impl FromStr for PolicyId {
1550 type Err = ParseErrors;
1551
1552 fn from_str(id: &str) -> Result<Self, Self::Err> {
1554 Ok(Self(ast::PolicyID::from_string(id)))
1555 }
1556}
1557
1558impl std::fmt::Display for PolicyId {
1559 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1560 write!(f, "{}", self.0)
1561 }
1562}
1563
1564#[derive(Debug, Clone)]
1566pub struct Policy {
1567 ast: ast::Policy,
1570 est: est::Policy,
1576}
1577
1578impl PartialEq for Policy {
1579 fn eq(&self, other: &Self) -> bool {
1580 self.ast.eq(&other.ast)
1582 }
1583}
1584impl Eq for Policy {}
1585
1586impl Policy {
1587 pub fn template_id(&self) -> Option<&PolicyId> {
1590 if self.is_static() {
1591 None
1592 } else {
1593 Some(PolicyId::ref_cast(self.ast.template().id()))
1594 }
1595 }
1596
1597 pub fn effect(&self) -> Effect {
1599 self.ast.effect()
1600 }
1601
1602 pub fn annotation(&self, key: impl AsRef<str>) -> Option<&str> {
1604 self.ast
1605 .annotation(&key.as_ref().parse().ok()?)
1606 .map(smol_str::SmolStr::as_str)
1607 }
1608
1609 pub fn annotations(&self) -> impl Iterator<Item = (&str, &str)> {
1611 self.ast
1612 .annotations()
1613 .map(|(k, v)| (k.as_ref(), v.as_str()))
1614 }
1615
1616 pub fn id(&self) -> &PolicyId {
1618 PolicyId::ref_cast(self.ast.id())
1619 }
1620
1621 #[must_use]
1623 pub fn new_id(&self, id: PolicyId) -> Self {
1624 Self {
1625 ast: self.ast.new_id(id.0),
1626 est: self.est.clone(),
1627 }
1628 }
1629
1630 pub fn is_static(&self) -> bool {
1632 self.ast.is_static()
1633 }
1634
1635 pub fn principal_constraint(&self) -> PrincipalConstraint {
1637 let slot_id = ast::SlotId::principal();
1638 match self.ast.template().principal_constraint().as_inner() {
1639 ast::PrincipalOrResourceConstraint::Any => PrincipalConstraint::Any,
1640 ast::PrincipalOrResourceConstraint::In(eref) => {
1641 PrincipalConstraint::In(self.convert_entity_reference(eref, slot_id).clone())
1642 }
1643 ast::PrincipalOrResourceConstraint::Eq(eref) => {
1644 PrincipalConstraint::Eq(self.convert_entity_reference(eref, slot_id).clone())
1645 }
1646 }
1647 }
1648
1649 pub fn action_constraint(&self) -> ActionConstraint {
1651 match self.ast.template().action_constraint() {
1653 ast::ActionConstraint::Any => ActionConstraint::Any,
1654 ast::ActionConstraint::In(ids) => ActionConstraint::In(
1655 ids.iter()
1656 .map(|euid| EntityUid::ref_cast(euid.as_ref()))
1657 .cloned()
1658 .collect(),
1659 ),
1660 ast::ActionConstraint::Eq(id) => ActionConstraint::Eq(EntityUid::ref_cast(id).clone()),
1661 }
1662 }
1663
1664 pub fn resource_constraint(&self) -> ResourceConstraint {
1666 let slot_id = ast::SlotId::resource();
1667 match self.ast.template().resource_constraint().as_inner() {
1668 ast::PrincipalOrResourceConstraint::Any => ResourceConstraint::Any,
1669 ast::PrincipalOrResourceConstraint::In(eref) => {
1670 ResourceConstraint::In(self.convert_entity_reference(eref, slot_id).clone())
1671 }
1672 ast::PrincipalOrResourceConstraint::Eq(eref) => {
1673 ResourceConstraint::Eq(self.convert_entity_reference(eref, slot_id).clone())
1674 }
1675 }
1676 }
1677
1678 fn convert_entity_reference<'a>(
1679 &'a self,
1680 r: &'a ast::EntityReference,
1681 slot: ast::SlotId,
1682 ) -> &'a EntityUid {
1683 match r {
1684 ast::EntityReference::EUID(euid) => EntityUid::ref_cast(euid),
1685 ast::EntityReference::Slot => EntityUid::ref_cast(self.ast.env().get(&slot).unwrap()),
1687 }
1688 }
1689
1690 pub fn parse(id: Option<String>, policy_src: impl AsRef<str>) -> Result<Self, ParseErrors> {
1695 let (est, inline_ast) = parser::parse_policy_to_est_and_ast(id, policy_src.as_ref())?;
1696 let (_, ast) = ast::Template::link_static_policy(inline_ast);
1697 Ok(Self { ast, est })
1698 }
1699
1700 pub fn from_json(
1705 id: Option<PolicyId>,
1706 json: serde_json::Value,
1707 ) -> Result<Self, cedar_policy_core::est::EstToAstError> {
1708 let est: est::Policy =
1709 serde_json::from_value(json).map_err(JsonDeserializationError::Serde)?;
1710 Ok(Self {
1711 ast: est.clone().try_into_ast_policy(id.map(|id| id.0))?,
1712 est,
1713 })
1714 }
1715
1716 pub fn to_json(&self) -> Result<serde_json::Value, impl std::error::Error> {
1718 serde_json::to_value(&self.est)
1719 }
1720
1721 fn from_ast(ast: ast::Policy) -> Self {
1727 let est = ast.clone().into();
1728 Self { ast, est }
1729 }
1730}
1731
1732impl std::fmt::Display for Policy {
1733 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1734 self.ast.fmt(f)
1735 }
1736}
1737
1738impl FromStr for Policy {
1739 type Err = ParseErrors;
1740 fn from_str(policy: &str) -> Result<Self, Self::Err> {
1748 Self::parse(None, policy)
1749 }
1750}
1751
1752#[repr(transparent)]
1754#[derive(Debug, Clone, RefCast)]
1755pub struct Expression(ast::Expr);
1756
1757impl Expression {
1758 pub fn new_string(value: String) -> Self {
1760 Self(ast::Expr::val(value))
1761 }
1762
1763 pub fn new_bool(value: bool) -> Self {
1765 Self(ast::Expr::val(value))
1766 }
1767
1768 pub fn new_long(value: i64) -> Self {
1770 Self(ast::Expr::val(value))
1771 }
1772
1773 pub fn new_record(fields: impl IntoIterator<Item = (String, Self)>) -> Self {
1775 Self(ast::Expr::record(
1776 fields.into_iter().map(|(k, v)| (SmolStr::from(k), v.0)),
1777 ))
1778 }
1779
1780 pub fn new_set(values: impl IntoIterator<Item = Self>) -> Self {
1782 Self(ast::Expr::set(values.into_iter().map(|v| v.0)))
1783 }
1784}
1785
1786impl FromStr for Expression {
1787 type Err = ParseErrors;
1788
1789 fn from_str(expression: &str) -> Result<Self, Self::Err> {
1791 parser::parse_expr(expression)
1792 .map_err(ParseErrors)
1793 .map(Expression)
1794 }
1795}
1796
1797#[repr(transparent)]
1813#[derive(Debug, Clone, RefCast)]
1814pub struct RestrictedExpression(ast::RestrictedExpr);
1815
1816impl RestrictedExpression {
1817 pub fn new_string(value: String) -> Self {
1819 Self(ast::RestrictedExpr::val(value))
1820 }
1821
1822 pub fn new_bool(value: bool) -> Self {
1824 Self(ast::RestrictedExpr::val(value))
1825 }
1826
1827 pub fn new_long(value: i64) -> Self {
1829 Self(ast::RestrictedExpr::val(value))
1830 }
1831
1832 pub fn new_record(fields: impl IntoIterator<Item = (String, Self)>) -> Self {
1834 Self(ast::RestrictedExpr::record(
1835 fields.into_iter().map(|(k, v)| (SmolStr::from(k), v.0)),
1836 ))
1837 }
1838
1839 pub fn new_set(values: impl IntoIterator<Item = Self>) -> Self {
1841 Self(ast::RestrictedExpr::set(values.into_iter().map(|v| v.0)))
1842 }
1843}
1844
1845impl FromStr for RestrictedExpression {
1846 type Err = ParseErrors;
1847
1848 fn from_str(expression: &str) -> Result<Self, Self::Err> {
1850 parser::parse_restrictedexpr(expression)
1851 .map_err(ParseErrors)
1852 .map(RestrictedExpression)
1853 }
1854}
1855
1856#[repr(transparent)]
1858#[derive(Debug, RefCast)]
1859pub struct Request(pub(crate) ast::Request);
1860
1861impl Request {
1862 pub fn new(
1872 principal: Option<EntityUid>,
1873 action: Option<EntityUid>,
1874 resource: Option<EntityUid>,
1875 context: Context,
1876 ) -> Self {
1877 let p = match principal {
1878 Some(p) => p.0,
1879 None => ast::EntityUID::unspecified_from_eid(ast::Eid::new("principal")),
1880 };
1881 let a = match action {
1882 Some(a) => a.0,
1883 None => ast::EntityUID::unspecified_from_eid(ast::Eid::new("action")),
1884 };
1885 let r = match resource {
1886 Some(r) => r.0,
1887 None => ast::EntityUID::unspecified_from_eid(ast::Eid::new("resource")),
1888 };
1889 Self(ast::Request::new(p, a, r, context.0))
1890 }
1891
1892 pub fn principal(&self) -> Option<&EntityUid> {
1894 match self.0.principal() {
1895 ast::EntityUIDEntry::Concrete(euid) => Some(EntityUid::ref_cast(euid.as_ref())),
1896 ast::EntityUIDEntry::Unknown => None,
1897 }
1898 }
1899
1900 pub fn action(&self) -> Option<&EntityUid> {
1902 match self.0.action() {
1903 ast::EntityUIDEntry::Concrete(euid) => Some(EntityUid::ref_cast(euid.as_ref())),
1904 ast::EntityUIDEntry::Unknown => None,
1905 }
1906 }
1907
1908 pub fn resource(&self) -> Option<&EntityUid> {
1910 match self.0.resource() {
1911 ast::EntityUIDEntry::Concrete(euid) => Some(EntityUid::ref_cast(euid.as_ref())),
1912 ast::EntityUIDEntry::Unknown => None,
1913 }
1914 }
1915}
1916
1917#[repr(transparent)]
1919#[derive(Debug, Clone, RefCast)]
1920pub struct Context(ast::Context);
1921
1922impl Context {
1923 pub fn empty() -> Self {
1925 Self(ast::Context::empty())
1926 }
1927
1928 pub fn from_pairs(pairs: impl IntoIterator<Item = (String, RestrictedExpression)>) -> Self {
1932 Self(ast::Context::from_pairs(
1933 pairs.into_iter().map(|(k, v)| (SmolStr::from(k), v.0)),
1934 ))
1935 }
1936
1937 pub fn from_json_str(
1948 json: &str,
1949 schema: Option<(&Schema, &EntityUid)>,
1950 ) -> Result<Self, ContextJsonError> {
1951 let schema = schema
1952 .map(|(s, uid)| Self::get_context_schema(s, uid))
1953 .transpose()?;
1954 let context =
1955 entities::ContextJsonParser::new(schema.as_ref(), Extensions::all_available())
1956 .from_json_str(json)?;
1957 Ok(Self(context))
1958 }
1959
1960 pub fn from_json_value(
1971 json: serde_json::Value,
1972 schema: Option<(&Schema, &EntityUid)>,
1973 ) -> Result<Self, ContextJsonError> {
1974 let schema = schema
1975 .map(|(s, uid)| Self::get_context_schema(s, uid))
1976 .transpose()?;
1977 let context =
1978 entities::ContextJsonParser::new(schema.as_ref(), Extensions::all_available())
1979 .from_json_value(json)?;
1980 Ok(Self(context))
1981 }
1982
1983 pub fn from_json_file(
1994 json: impl std::io::Read,
1995 schema: Option<(&Schema, &EntityUid)>,
1996 ) -> Result<Self, ContextJsonError> {
1997 let schema = schema
1998 .map(|(s, uid)| Self::get_context_schema(s, uid))
1999 .transpose()?;
2000 let context =
2001 entities::ContextJsonParser::new(schema.as_ref(), Extensions::all_available())
2002 .from_json_file(json)?;
2003 Ok(Self(context))
2004 }
2005
2006 fn get_context_schema(
2008 schema: &Schema,
2009 action: &EntityUid,
2010 ) -> Result<impl ContextSchema, ContextJsonError> {
2011 schema
2012 .0
2013 .get_context_schema(&action.0)
2014 .ok_or_else(|| ContextJsonError::ActionDoesNotExist {
2015 action: action.clone(),
2016 })
2017 }
2018}
2019
2020#[derive(Debug, Error)]
2022pub enum ContextJsonError {
2023 #[error(transparent)]
2025 JsonDeserializationError(#[from] JsonDeserializationError),
2026 #[error("Action {action} doesn't exist in the supplied schema")]
2028 ActionDoesNotExist {
2029 action: EntityUid,
2031 },
2032}
2033
2034impl std::fmt::Display for Request {
2035 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2036 write!(f, "{}", self.0)
2037 }
2038}
2039
2040#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
2042pub enum EvalResult {
2043 Bool(bool),
2045 Long(i64),
2047 String(String),
2049 EntityUid(EntityUid),
2051 Set(Set),
2053 Record(Record),
2055 ExtensionValue(String),
2057 }
2059
2060#[derive(Debug, Eq, PartialEq, PartialOrd, Ord)]
2062pub struct Set(BTreeSet<EvalResult>);
2063
2064impl Set {
2065 pub fn iter(&self) -> impl Iterator<Item = &EvalResult> {
2067 self.0.iter()
2068 }
2069
2070 pub fn contains(&self, elem: &EvalResult) -> bool {
2072 self.0.contains(elem)
2073 }
2074
2075 pub fn len(&self) -> usize {
2077 self.0.len()
2078 }
2079
2080 pub fn is_empty(&self) -> bool {
2082 self.0.is_empty()
2083 }
2084}
2085
2086#[derive(Debug, Eq, PartialEq, PartialOrd, Ord)]
2088pub struct Record(BTreeMap<String, EvalResult>);
2089
2090impl Record {
2091 pub fn iter(&self) -> impl Iterator<Item = (&String, &EvalResult)> {
2093 self.0.iter()
2094 }
2095
2096 pub fn contains_attribute(&self, key: impl AsRef<str>) -> bool {
2098 self.0.contains_key(key.as_ref())
2099 }
2100
2101 pub fn get(&self, key: impl AsRef<str>) -> Option<&EvalResult> {
2103 self.0.get(key.as_ref())
2104 }
2105
2106 pub fn len(&self) -> usize {
2108 self.0.len()
2109 }
2110
2111 pub fn is_empty(&self) -> bool {
2113 self.0.is_empty()
2114 }
2115}
2116
2117#[doc(hidden)]
2118impl From<ast::Value> for EvalResult {
2119 fn from(v: ast::Value) -> Self {
2120 match v {
2121 ast::Value::Lit(ast::Literal::Bool(b)) => Self::Bool(b),
2122 ast::Value::Lit(ast::Literal::Long(i)) => Self::Long(i),
2123 ast::Value::Lit(ast::Literal::String(s)) => Self::String(s.to_string()),
2124 ast::Value::Lit(ast::Literal::EntityUID(e)) => {
2125 Self::EntityUid(EntityUid(ast::EntityUID::clone(&e)))
2126 }
2127 ast::Value::Set(s) => Self::Set(Set(s
2128 .authoritative
2129 .iter()
2130 .map(|v| v.clone().into())
2131 .collect())),
2132 ast::Value::Record(r) => Self::Record(Record(
2133 r.iter()
2134 .map(|(k, v)| (k.to_string(), v.clone().into()))
2135 .collect(),
2136 )),
2137 ast::Value::ExtensionValue(v) => Self::ExtensionValue(v.to_string()),
2138 }
2139 }
2140}
2141impl std::fmt::Display for EvalResult {
2142 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2143 match self {
2144 Self::Bool(b) => write!(f, "{b}"),
2145 Self::Long(l) => write!(f, "{l}"),
2146 Self::String(s) => write!(f, "\"{}\"", s.escape_debug()),
2147 Self::EntityUid(uid) => write!(f, "{uid}"),
2148 Self::Set(s) => {
2149 write!(f, "[")?;
2150 for (i, ev) in s.iter().enumerate() {
2151 write!(f, "{ev}")?;
2152 if (i + 1) < s.len() {
2153 write!(f, ", ")?;
2154 }
2155 }
2156 write!(f, "]")?;
2157 Ok(())
2158 }
2159 Self::Record(r) => {
2160 write!(f, "{{")?;
2161 for (i, (k, v)) in r.iter().enumerate() {
2162 write!(f, "\"{}\": {v}", k.escape_debug())?;
2163 if (i + 1) < r.len() {
2164 write!(f, ", ")?;
2165 }
2166 }
2167 write!(f, "}}")?;
2168 Ok(())
2169 }
2170 Self::ExtensionValue(s) => write!(f, "{s}"),
2171 }
2172 }
2173}
2174
2175pub fn eval_expression(
2179 request: &Request,
2180 entities: &Entities,
2181 expr: &Expression,
2182) -> Result<EvalResult, EvaluationError> {
2183 let all_ext = Extensions::all_available();
2184 let eval = Evaluator::new(&request.0, &entities.0, &all_ext)
2185 .map_err(|e| EvaluationError::StringMessage(e.to_string()))?;
2186 Ok(EvalResult::from(
2187 eval.interpret(&expr.0, &ast::SlotEnv::new())
2189 .map_err(|e| EvaluationError::StringMessage(e.to_string()))?,
2190 ))
2191}
2192
2193#[cfg(test)]
2194mod test {
2195 use std::collections::HashSet;
2196
2197 use crate::{PolicyId, PolicySet, ResidualResponse};
2198
2199 #[test]
2200 fn test_pe_response_constructor() {
2201 let p: PolicySet = "permit(principal, action, resource);".parse().unwrap();
2202 let reason: HashSet<PolicyId> = std::iter::once("id1".parse().unwrap()).collect();
2203 let errors: HashSet<String> = std::iter::once("error".to_string()).collect();
2204 let a = ResidualResponse::new(p.clone(), reason.clone(), errors.clone());
2205 assert_eq!(a.diagnostics().errors, errors);
2206 assert_eq!(a.diagnostics().reason, reason);
2207 assert_eq!(a.residuals(), &p);
2208 }
2209}
2210
2211#[cfg(test)]
2212mod entity_uid_tests {
2213 use super::*;
2214
2215 #[test]
2217 fn entity_uid_from_parts() {
2218 let entity_id = EntityId::from_str("bobby").expect("failed at constructing EntityId");
2219 let entity_type_name = EntityTypeName::from_str("Chess::Master")
2220 .expect("failed at constructing EntityTypeName");
2221 let euid = EntityUid::from_type_name_and_id(entity_type_name, entity_id);
2222 assert_eq!(euid.id().as_ref(), "bobby");
2223 assert_eq!(euid.type_name().to_string(), "Chess::Master");
2224 }
2225
2226 #[test]
2228 fn entity_uid_with_escape() {
2229 let entity_id = EntityId::from_str(r#"bobby\'s sister:\nVeronica"#)
2231 .expect("failed at constructing EntityId");
2232 let entity_type_name = EntityTypeName::from_str("Hockey::Master")
2233 .expect("failed at constructing EntityTypeName");
2234 let euid = EntityUid::from_type_name_and_id(entity_type_name, entity_id);
2235 assert_eq!(euid.id().as_ref(), r#"bobby\'s sister:\nVeronica"#);
2238 assert_eq!(euid.type_name().to_string(), "Hockey::Master");
2239 }
2240
2241 #[test]
2243 fn entity_uid_with_backslashes() {
2244 let entity_id =
2246 EntityId::from_str(r#"\ \a \b \' \" \\"#).expect("failed at constructing EntityId");
2247 let entity_type_name =
2248 EntityTypeName::from_str("Test::User").expect("failed at constructing EntityTypeName");
2249 let euid = EntityUid::from_type_name_and_id(entity_type_name, entity_id);
2250 assert_eq!(euid.id().as_ref(), r#"\ \a \b \' \" \\"#);
2252 assert_eq!(euid.type_name().to_string(), "Test::User");
2253 }
2254
2255 #[test]
2257 fn entity_uid_with_quotes() {
2258 let euid: EntityUid = EntityUid::from_type_name_and_id(
2259 EntityTypeName::from_str("Test::User").unwrap(),
2260 EntityId::from_str(r#"b'ob"by\'s sis\"ter"#).unwrap(),
2261 );
2262 assert_eq!(euid.id().as_ref(), r#"b'ob"by\'s sis\"ter"#);
2265 assert_eq!(euid.type_name().to_string(), r#"Test::User"#);
2266 }
2267
2268 #[test]
2269 fn malformed_entity_type_name_should_fail() {
2270 let result = EntityTypeName::from_str("I'm an invalid name");
2271
2272 assert!(matches!(result, Err(ParseErrors(_))));
2273 let error = result.err().unwrap();
2274 assert!(error.to_string().contains("Unrecognized token `'`"));
2275 }
2276
2277 #[test]
2279 fn parse_euid() {
2280 let parsed_eid: EntityUid = r#"Test::User::"bobby""#.parse().expect("Failed to parse");
2281 assert_eq!(parsed_eid.id().as_ref(), r#"bobby"#);
2282 assert_eq!(parsed_eid.type_name().to_string(), r#"Test::User"#);
2283 }
2284
2285 #[test]
2287 fn parse_euid_with_escape() {
2288 let parsed_eid: EntityUid = r#"Test::User::"b\'ob\"by""#.parse().expect("Failed to parse");
2290 assert_eq!(parsed_eid.id().as_ref(), r#"b'ob"by"#);
2293 assert_eq!(parsed_eid.type_name().to_string(), r#"Test::User"#);
2294 }
2295
2296 #[test]
2298 fn parse_euid_single_quotes() {
2299 let parsed_eid: EntityUid = r#"Test::User::"b'obby\'s sister""#
2301 .parse()
2302 .expect("Failed to parse");
2303 assert_eq!(parsed_eid.id().as_ref(), r#"b'obby's sister"#);
2306 assert_eq!(parsed_eid.type_name().to_string(), r#"Test::User"#);
2307 }
2308
2309 #[test]
2311 fn euid_roundtrip() {
2312 let parsed_euid: EntityUid = r#"Test::User::"b'ob""#.parse().expect("Failed to parse");
2313 assert_eq!(parsed_euid.id().as_ref(), r#"b'ob"#);
2314 let reparsed: EntityUid = format!("{parsed_euid}")
2315 .parse()
2316 .expect("failed to roundtrip");
2317 assert_eq!(reparsed.id().as_ref(), r#"b'ob"#);
2318 }
2319}
2320
2321#[cfg(test)]
2322mod head_constraints_tests {
2323 use super::*;
2324
2325 #[test]
2326 fn principal_constraint_inline() {
2327 let p = Policy::from_str("permit(principal,action,resource);").unwrap();
2328 assert_eq!(p.principal_constraint(), PrincipalConstraint::Any);
2329 let euid = EntityUid::from_strs("T", "a");
2330 assert_eq!(euid.id().as_ref(), "a");
2331 assert_eq!(
2332 euid.type_name(),
2333 &EntityTypeName::from_str("T").expect("Failed to parse EntityTypeName")
2334 );
2335 let p =
2336 Policy::from_str("permit(principal == T::\"a\",action,resource == T::\"b\");").unwrap();
2337 assert_eq!(
2338 p.principal_constraint(),
2339 PrincipalConstraint::Eq(euid.clone())
2340 );
2341 let p = Policy::from_str("permit(principal in T::\"a\",action,resource);").unwrap();
2342 assert_eq!(p.principal_constraint(), PrincipalConstraint::In(euid));
2343 }
2344
2345 #[test]
2346 fn action_constraint_inline() {
2347 let p = Policy::from_str("permit(principal,action,resource);").unwrap();
2348 assert_eq!(p.action_constraint(), ActionConstraint::Any);
2349 let euid = EntityUid::from_strs("NN::N::Action", "a");
2350 assert_eq!(
2351 euid.type_name(),
2352 &EntityTypeName::from_str("NN::N::Action").expect("Failed to parse EntityTypeName")
2353 );
2354 let p = Policy::from_str(
2355 "permit(principal == T::\"b\",action == NN::N::Action::\"a\",resource == T::\"c\");",
2356 )
2357 .unwrap();
2358 assert_eq!(p.action_constraint(), ActionConstraint::Eq(euid.clone()));
2359 let p = Policy::from_str("permit(principal,action in [NN::N::Action::\"a\"],resource);")
2360 .unwrap();
2361 assert_eq!(p.action_constraint(), ActionConstraint::In(vec![euid]));
2362 }
2363
2364 #[test]
2365 fn resource_constraint_inline() {
2366 let p = Policy::from_str("permit(principal,action,resource);").unwrap();
2367 assert_eq!(p.resource_constraint(), ResourceConstraint::Any);
2368 let euid = EntityUid::from_strs("NN::N::T", "a");
2369 assert_eq!(
2370 euid.type_name(),
2371 &EntityTypeName::from_str("NN::N::T").expect("Failed to parse EntityTypeName")
2372 );
2373 let p =
2374 Policy::from_str("permit(principal == T::\"b\",action,resource == NN::N::T::\"a\");")
2375 .unwrap();
2376 assert_eq!(
2377 p.resource_constraint(),
2378 ResourceConstraint::Eq(euid.clone())
2379 );
2380 let p = Policy::from_str("permit(principal,action,resource in NN::N::T::\"a\");").unwrap();
2381 assert_eq!(p.resource_constraint(), ResourceConstraint::In(euid));
2382 }
2383
2384 #[test]
2385 fn principal_constraint_link() {
2386 let p = link("permit(principal,action,resource);", HashMap::new());
2387 assert_eq!(p.principal_constraint(), PrincipalConstraint::Any);
2388 let euid = EntityUid::from_strs("T", "a");
2389 let p = link(
2390 "permit(principal == T::\"a\",action,resource);",
2391 HashMap::new(),
2392 );
2393 assert_eq!(
2394 p.principal_constraint(),
2395 PrincipalConstraint::Eq(euid.clone())
2396 );
2397 let p = link(
2398 "permit(principal in T::\"a\",action,resource);",
2399 HashMap::new(),
2400 );
2401 assert_eq!(
2402 p.principal_constraint(),
2403 PrincipalConstraint::In(euid.clone())
2404 );
2405 let map: HashMap<SlotId, EntityUid> =
2406 std::iter::once((SlotId::principal(), euid.clone())).collect();
2407 let p = link(
2408 "permit(principal in ?principal,action,resource);",
2409 map.clone(),
2410 );
2411 assert_eq!(
2412 p.principal_constraint(),
2413 PrincipalConstraint::In(euid.clone())
2414 );
2415 let p = link("permit(principal == ?principal,action,resource);", map);
2416 assert_eq!(p.principal_constraint(), PrincipalConstraint::Eq(euid));
2417 }
2418
2419 #[test]
2420 fn action_constraint_link() {
2421 let p = link("permit(principal,action,resource);", HashMap::new());
2422 assert_eq!(p.action_constraint(), ActionConstraint::Any);
2423 let euid = EntityUid::from_strs("Action", "a");
2424 let p = link(
2425 "permit(principal,action == Action::\"a\",resource);",
2426 HashMap::new(),
2427 );
2428 assert_eq!(p.action_constraint(), ActionConstraint::Eq(euid.clone()));
2429 let p = link(
2430 "permit(principal,action in [Action::\"a\",Action::\"b\"],resource);",
2431 HashMap::new(),
2432 );
2433 assert_eq!(
2434 p.action_constraint(),
2435 ActionConstraint::In(vec![euid, EntityUid::from_strs("Action", "b"),])
2436 );
2437 }
2438
2439 #[test]
2440 fn resource_constraint_link() {
2441 let p = link("permit(principal,action,resource);", HashMap::new());
2442 assert_eq!(p.resource_constraint(), ResourceConstraint::Any);
2443 let euid = EntityUid::from_strs("T", "a");
2444 let p = link(
2445 "permit(principal,action,resource == T::\"a\");",
2446 HashMap::new(),
2447 );
2448 assert_eq!(
2449 p.resource_constraint(),
2450 ResourceConstraint::Eq(euid.clone())
2451 );
2452 let p = link(
2453 "permit(principal,action,resource in T::\"a\");",
2454 HashMap::new(),
2455 );
2456 assert_eq!(
2457 p.resource_constraint(),
2458 ResourceConstraint::In(euid.clone())
2459 );
2460 let map: HashMap<SlotId, EntityUid> =
2461 std::iter::once((SlotId::resource(), euid.clone())).collect();
2462 let p = link(
2463 "permit(principal,action,resource in ?resource);",
2464 map.clone(),
2465 );
2466 assert_eq!(
2467 p.resource_constraint(),
2468 ResourceConstraint::In(euid.clone())
2469 );
2470 let p = link("permit(principal,action,resource == ?resource);", map);
2471 assert_eq!(p.resource_constraint(), ResourceConstraint::Eq(euid));
2472 }
2473
2474 fn link(src: &str, values: HashMap<SlotId, EntityUid>) -> Policy {
2475 let mut pset = PolicySet::new();
2476 let template = Template::parse(Some("Id".to_string()), src).unwrap();
2477
2478 pset.add_template(template).unwrap();
2479
2480 let link_id = PolicyId::from_str("link").unwrap();
2481 pset.link(PolicyId::from_str("Id").unwrap(), link_id.clone(), values)
2482 .unwrap();
2483 pset.policy(&link_id).unwrap().clone()
2484 }
2485}
2486
2487#[cfg(test)]
2489mod policy_set_tests {
2490 use super::*;
2491 use ast::LinkingError;
2492 use cool_asserts::assert_matches;
2493
2494 #[test]
2495 fn link_conflicts() {
2496 let mut pset = PolicySet::new();
2497 let p1 = Policy::parse(Some("id".into()), "permit(principal,action,resource);")
2498 .expect("Failed to parse");
2499 pset.add(p1).expect("Failed to add");
2500 let template = Template::parse(
2501 Some("t".into()),
2502 "permit(principal == ?principal, action, resource);",
2503 )
2504 .expect("Failed to parse");
2505 pset.add_template(template).expect("Add failed");
2506
2507 let env: HashMap<SlotId, EntityUid> =
2508 std::iter::once((SlotId::principal(), EntityUid::from_strs("Test", "test"))).collect();
2509
2510 let r = pset.link(
2511 PolicyId::from_str("t").unwrap(),
2512 PolicyId::from_str("id").unwrap(),
2513 env,
2514 );
2515
2516 match r {
2517 Ok(_) => panic!("Should have failed due to conflict"),
2518 Err(PolicySetError::LinkingError(LinkingError::PolicyIdConflict)) => (),
2519 Err(e) => panic!("Incorrect error: {e}"),
2520 };
2521 }
2522
2523 #[test]
2524 fn policyset_add() {
2525 let mut pset = PolicySet::new();
2526 let static_policy = Policy::parse(Some("id".into()), "permit(principal,action,resource);")
2527 .expect("Failed to parse");
2528 pset.add(static_policy).expect("Failed to add");
2529
2530 let template = Template::parse(
2531 Some("t".into()),
2532 "permit(principal == ?principal, action, resource);",
2533 )
2534 .expect("Failed to parse");
2535 pset.add_template(template).expect("Failed to add");
2536
2537 let env1: HashMap<SlotId, EntityUid> =
2538 std::iter::once((SlotId::principal(), EntityUid::from_strs("Test", "test1"))).collect();
2539 pset.link(
2540 PolicyId::from_str("t").unwrap(),
2541 PolicyId::from_str("link").unwrap(),
2542 env1,
2543 )
2544 .expect("Failed to link");
2545
2546 let env2: HashMap<SlotId, EntityUid> =
2547 std::iter::once((SlotId::principal(), EntityUid::from_strs("Test", "test2"))).collect();
2548
2549 let err = pset
2550 .link(
2551 PolicyId::from_str("t").unwrap(),
2552 PolicyId::from_str("link").unwrap(),
2553 env2.clone(),
2554 )
2555 .expect_err("Should have failed due to conflict with existing link id");
2556 match err {
2557 PolicySetError::LinkingError(_) => (),
2558 e => panic!("Wrong error: {e}"),
2559 }
2560
2561 pset.link(
2562 PolicyId::from_str("t").unwrap(),
2563 PolicyId::from_str("link2").unwrap(),
2564 env2,
2565 )
2566 .expect("Failed to link");
2567
2568 let template2 = Template::parse(
2569 Some("t".into()),
2570 "forbid(principal, action, resource == ?resource);",
2571 )
2572 .expect("Failed to parse");
2573 pset.add_template(template2)
2574 .expect_err("should have failed due to conflict on template id");
2575 let template2 = Template::parse(
2576 Some("t2".into()),
2577 "forbid(principal, action, resource == ?resource);",
2578 )
2579 .expect("Failed to parse");
2580 pset.add_template(template2)
2581 .expect("Failed to add template");
2582 let env3: HashMap<SlotId, EntityUid> =
2583 std::iter::once((SlotId::resource(), EntityUid::from_strs("Test", "test3"))).collect();
2584
2585 pset.link(
2586 PolicyId::from_str("t").unwrap(),
2587 PolicyId::from_str("unique3").unwrap(),
2588 env3.clone(),
2589 )
2590 .expect_err("should have failed due to conflict on template id");
2591
2592 pset.link(
2593 PolicyId::from_str("t2").unwrap(),
2594 PolicyId::from_str("unique3").unwrap(),
2595 env3,
2596 )
2597 .expect("should succeed with unique ids");
2598 }
2599
2600 #[test]
2601 fn pset_requests() {
2602 let template = Template::parse(
2603 Some("template".into()),
2604 "permit(principal == ?principal, action, resource);",
2605 )
2606 .expect("Template Parse Failure");
2607 let static_policy = Policy::parse(
2608 Some("static".into()),
2609 "permit(principal, action, resource);",
2610 )
2611 .expect("Static parse failure");
2612 let mut pset = PolicySet::new();
2613 pset.add_template(template).unwrap();
2614 pset.add(static_policy).unwrap();
2615 pset.link(
2616 PolicyId::from_str("template").unwrap(),
2617 PolicyId::from_str("linked").unwrap(),
2618 std::iter::once((SlotId::principal(), EntityUid::from_strs("Test", "test"))).collect(),
2619 )
2620 .expect("Link failure");
2621
2622 assert_eq!(pset.templates().count(), 1);
2623 assert_eq!(pset.policies().count(), 2);
2624 assert_eq!(pset.policies().filter(|p| p.is_static()).count(), 1);
2625
2626 assert_eq!(
2627 pset.template(&"template".parse().unwrap())
2628 .expect("lookup failed")
2629 .id(),
2630 &"template".parse().unwrap()
2631 );
2632 assert_eq!(
2633 pset.policy(&"static".parse().unwrap())
2634 .expect("lookup failed")
2635 .id(),
2636 &"static".parse().unwrap()
2637 );
2638 assert_eq!(
2639 pset.policy(&"linked".parse().unwrap())
2640 .expect("lookup failed")
2641 .id(),
2642 &"linked".parse().unwrap()
2643 );
2644 }
2645
2646 #[test]
2647 fn link_static_policy() {
2648 let static_policy = Policy::parse(
2651 Some("static".into()),
2652 "permit(principal, action, resource);",
2653 )
2654 .expect("Static parse failure");
2655 let mut pset = PolicySet::new();
2656 pset.add(static_policy).unwrap();
2657
2658 let result = pset.link(
2659 PolicyId::from_str("static").unwrap(),
2660 PolicyId::from_str("linked").unwrap(),
2661 HashMap::new(),
2662 );
2663 assert_matches!(result, Err(PolicySetError::ExpectedTemplate));
2664 }
2665
2666 #[test]
2667 fn link_linked_policy() {
2668 let template = Template::parse(
2669 Some("template".into()),
2670 "permit(principal == ?principal, action, resource);",
2671 )
2672 .expect("Template Parse Failure");
2673 let mut pset = PolicySet::new();
2674 pset.add_template(template).unwrap();
2675
2676 pset.link(
2677 PolicyId::from_str("template").unwrap(),
2678 PolicyId::from_str("linked").unwrap(),
2679 std::iter::once((SlotId::principal(), EntityUid::from_strs("Test", "test"))).collect(),
2680 )
2681 .unwrap();
2682
2683 let result = pset.link(
2684 PolicyId::from_str("linked").unwrap(),
2685 PolicyId::from_str("linked2").unwrap(),
2686 HashMap::new(),
2687 );
2688 assert_matches!(
2689 result,
2690 Err(PolicySetError::LinkingError(LinkingError::NoSuchTemplate(
2691 _
2692 )))
2693 );
2694 }
2695}
2696
2697#[cfg(test)]
2698mod schema_tests {
2699 use super::*;
2700 use cool_asserts::assert_matches;
2701 use serde_json::json;
2702
2703 #[test]
2705 fn valid_schema() {
2706 let _ = Schema::from_json_value(json!(
2707 { "": {
2708 "entityTypes": {
2709 "Photo": {
2710 "memberOfTypes": [ "Album" ],
2711 "shape": {
2712 "type": "Record",
2713 "attributes": {
2714 "foo": {
2715 "type": "Boolean",
2716 "required": false
2717 }
2718 }
2719 }
2720 },
2721 "Album": {
2722 "memberOfTypes": [ ],
2723 "shape": {
2724 "type": "Record",
2725 "attributes": {
2726 "foo": {
2727 "type": "Boolean",
2728 "required": false
2729 }
2730 }
2731 }
2732 }
2733 },
2734 "actions": {
2735 "view": {
2736 "appliesTo": {
2737 "principalTypes": ["Photo", "Album"],
2738 "resourceTypes": ["Photo"]
2739 }
2740 }
2741 }
2742 }}))
2743 .expect("schema should be valid");
2744 }
2745
2746 #[test]
2748 fn invalid_schema() {
2749 assert_matches!(
2750 Schema::from_json_value(json!(
2751 r#""{"": {
2754 "entityTypes": {
2755 "Photo": {
2756 "memberOfTypes": [ "Album" ],
2757 "shape": {
2758 "type": "Record",
2759 "attributes": {
2760 "foo": {
2761 "type": "Boolean",
2762 "required": false
2763 }
2764 }
2765 }
2766 },
2767 "Album": {
2768 "memberOfTypes": [ ],
2769 "shape": {
2770 "type": "Record",
2771 "attributes": {
2772 "foo": {
2773 "type": "Boolean",
2774 "required": false
2775 }
2776 }
2777 }
2778 },
2779 "Photo": {
2780 "memberOfTypes": [ "Album" ],
2781 "shape": {
2782 "type": "Record",
2783 "attributes": {
2784 "foo": {
2785 "type": "Boolean",
2786 "required": false
2787 }
2788 }
2789 }
2790 }
2791 },
2792 "actions": {
2793 "view": {
2794 "appliesTo": {
2795 "principalTypes": ["Photo", "Album"],
2796 "resourceTypes": ["Photo"]
2797 }
2798 }
2799 }
2800 }}"#
2801 )),
2802 Err(SchemaError::ParseJson(_))
2803 );
2804 }
2805}
2806
2807#[cfg(test)]
2808mod ancestors_tests {
2809 use super::*;
2810
2811 #[test]
2812 fn test_ancestors() {
2813 let a_euid: EntityUid = EntityUid::from_strs("test", "A");
2814 let b_euid: EntityUid = EntityUid::from_strs("test", "b");
2815 let c_euid: EntityUid = EntityUid::from_strs("test", "C");
2816 let a = Entity::new(a_euid.clone(), HashMap::new(), HashSet::new());
2817 let b = Entity::new(
2818 b_euid.clone(),
2819 HashMap::new(),
2820 std::iter::once(a_euid.clone()).collect(),
2821 );
2822 let c = Entity::new(
2823 c_euid.clone(),
2824 HashMap::new(),
2825 std::iter::once(b_euid.clone()).collect(),
2826 );
2827 let es = Entities::from_entities([a, b, c]).unwrap();
2828 let ans = es.ancestors(&c_euid).unwrap().collect::<HashSet<_>>();
2829 assert_eq!(ans.len(), 2);
2830 assert!(ans.contains(&b_euid));
2831 assert!(ans.contains(&a_euid));
2832 }
2833}
2834
2835#[cfg(test)]
2840mod schema_based_parsing_tests {
2841 use std::assert_eq;
2842
2843 use super::*;
2844 use cedar_policy_core::ast::EntityUID;
2845 use cool_asserts::assert_matches;
2846 use serde_json::json;
2847
2848 #[test]
2850 #[allow(clippy::too_many_lines)]
2851 #[allow(clippy::cognitive_complexity)]
2852 fn attr_types() {
2853 let schema = Schema::from_json_value(json!(
2854 {"": {
2855 "entityTypes": {
2856 "Employee": {
2857 "memberOfTypes": [],
2858 "shape": {
2859 "type": "Record",
2860 "attributes": {
2861 "isFullTime": { "type": "Boolean" },
2862 "numDirectReports": { "type": "Long" },
2863 "department": { "type": "String" },
2864 "manager": { "type": "Entity", "name": "Employee" },
2865 "hr_contacts": { "type": "Set", "element": {
2866 "type": "Entity", "name": "HR" } },
2867 "json_blob": { "type": "Record", "attributes": {
2868 "inner1": { "type": "Boolean" },
2869 "inner2": { "type": "String" },
2870 "inner3": { "type": "Record", "attributes": {
2871 "innerinner": { "type": "Entity", "name": "Employee" }
2872 }}
2873 }},
2874 "home_ip": { "type": "Extension", "name": "ipaddr" },
2875 "work_ip": { "type": "Extension", "name": "ipaddr" },
2876 "trust_score": { "type": "Extension", "name": "decimal" },
2877 "tricky": { "type": "Record", "attributes": {
2878 "type": { "type": "String" },
2879 "id": { "type": "String" }
2880 }}
2881 }
2882 }
2883 },
2884 "HR": {
2885 "memberOfTypes": []
2886 }
2887 },
2888 "actions": {
2889 "view": { }
2890 }
2891 }}
2892 ))
2893 .expect("should be a valid schema");
2894
2895 let entitiesjson = json!(
2896 [
2897 {
2898 "uid": { "type": "Employee", "id": "12UA45" },
2899 "attrs": {
2900 "isFullTime": true,
2901 "numDirectReports": 3,
2902 "department": "Sales",
2903 "manager": { "type": "Employee", "id": "34FB87" },
2904 "hr_contacts": [
2905 { "type": "HR", "id": "aaaaa" },
2906 { "type": "HR", "id": "bbbbb" }
2907 ],
2908 "json_blob": {
2909 "inner1": false,
2910 "inner2": "-*/",
2911 "inner3": { "innerinner": { "type": "Employee", "id": "09AE76" }},
2912 },
2913 "home_ip": "222.222.222.101",
2914 "work_ip": { "fn": "ip", "arg": "2.2.2.0/24" },
2915 "trust_score": "5.7",
2916 "tricky": { "type": "Employee", "id": "34FB87" }
2917 },
2918 "parents": []
2919 }
2920 ]
2921 );
2922 let parsed = Entities::from_json_value(entitiesjson.clone(), None)
2926 .expect("Should parse without error");
2927 assert_eq!(parsed.iter().count(), 1);
2928 let parsed = parsed
2929 .get(&EntityUid::from_strs("Employee", "12UA45"))
2930 .expect("that should be the employee id");
2931 assert_eq!(
2932 parsed.attr("home_ip"),
2933 Some(Ok(EvalResult::String("222.222.222.101".into())))
2934 );
2935 assert_eq!(
2936 parsed.attr("trust_score"),
2937 Some(Ok(EvalResult::String("5.7".into())))
2938 );
2939 assert!(matches!(
2940 parsed.attr("manager"),
2941 Some(Ok(EvalResult::Record(_)))
2942 ));
2943 assert!(matches!(
2944 parsed.attr("work_ip"),
2945 Some(Ok(EvalResult::Record(_)))
2946 ));
2947 {
2948 let Some(Ok(EvalResult::Set(set))) = parsed.attr("hr_contacts") else { panic!("expected hr_contacts attr to exist and be a Set") };
2949 let contact = set.iter().next().expect("should be at least one contact");
2950 assert!(matches!(contact, EvalResult::Record(_)));
2951 };
2952 {
2953 let Some(Ok(EvalResult::Record(rec))) = parsed.attr("json_blob") else { panic!("expected json_blob attr to exist and be a Record") };
2954 let inner3 = rec.get("inner3").expect("expected inner3 attr to exist");
2955 let EvalResult::Record(rec) = inner3 else { panic!("expected inner3 to be a Record") };
2956 let innerinner = rec
2957 .get("innerinner")
2958 .expect("expected innerinner attr to exist");
2959 assert!(matches!(innerinner, EvalResult::Record(_)));
2960 };
2961 let parsed = Entities::from_json_value(entitiesjson, Some(&schema))
2963 .expect("Should parse without error");
2964 assert_eq!(parsed.iter().count(), 1);
2965 let parsed = parsed
2966 .get(&EntityUid::from_strs("Employee", "12UA45"))
2967 .expect("that should be the employee id");
2968 assert_eq!(parsed.attr("isFullTime"), Some(Ok(EvalResult::Bool(true))));
2969 assert_eq!(
2970 parsed.attr("numDirectReports"),
2971 Some(Ok(EvalResult::Long(3)))
2972 );
2973 assert_eq!(
2974 parsed.attr("department"),
2975 Some(Ok(EvalResult::String("Sales".into())))
2976 );
2977 assert_eq!(
2978 parsed.attr("manager"),
2979 Some(Ok(EvalResult::EntityUid(EntityUid::from_strs(
2980 "Employee", "34FB87"
2981 ))))
2982 );
2983 {
2984 let Some(Ok(EvalResult::Set(set))) = parsed.attr("hr_contacts") else { panic!("expected hr_contacts attr to exist and be a Set") };
2985 let contact = set.iter().next().expect("should be at least one contact");
2986 assert!(matches!(contact, EvalResult::EntityUid(_)));
2987 };
2988 {
2989 let Some(Ok(EvalResult::Record(rec))) = parsed.attr("json_blob") else { panic!("expected json_blob attr to exist and be a Record") };
2990 let inner3 = rec.get("inner3").expect("expected inner3 attr to exist");
2991 let EvalResult::Record(rec) = inner3 else { panic!("expected inner3 to be a Record") };
2992 let innerinner = rec
2993 .get("innerinner")
2994 .expect("expected innerinner attr to exist");
2995 assert!(matches!(innerinner, EvalResult::EntityUid(_)));
2996 };
2997 assert_eq!(
2998 parsed.attr("home_ip"),
2999 Some(Ok(EvalResult::ExtensionValue("222.222.222.101/32".into())))
3000 );
3001 assert_eq!(
3002 parsed.attr("work_ip"),
3003 Some(Ok(EvalResult::ExtensionValue("2.2.2.0/24".into())))
3004 );
3005 assert_eq!(
3006 parsed.attr("trust_score"),
3007 Some(Ok(EvalResult::ExtensionValue("5.7000".into())))
3008 );
3009
3010 let entitiesjson = json!(
3012 [
3013 {
3014 "uid": { "type": "Employee", "id": "12UA45" },
3015 "attrs": {
3016 "isFullTime": true,
3017 "numDirectReports": "3",
3018 "department": "Sales",
3019 "manager": { "type": "Employee", "id": "34FB87" },
3020 "hr_contacts": [
3021 { "type": "HR", "id": "aaaaa" },
3022 { "type": "HR", "id": "bbbbb" }
3023 ],
3024 "json_blob": {
3025 "inner1": false,
3026 "inner2": "-*/",
3027 "inner3": { "innerinner": { "type": "Employee", "id": "09AE76" }},
3028 },
3029 "home_ip": "222.222.222.101",
3030 "work_ip": { "fn": "ip", "arg": "2.2.2.0/24" },
3031 "trust_score": "5.7",
3032 "tricky": { "type": "Employee", "id": "34FB87" }
3033 },
3034 "parents": []
3035 }
3036 ]
3037 );
3038 let err = Entities::from_json_value(entitiesjson, Some(&schema))
3039 .expect_err("should fail due to type mismatch on numDirectReports");
3040 assert!(
3041 err.to_string().contains(r#"In attribute "numDirectReports" on Employee::"12UA45", type mismatch: attribute was expected to have type long, but actually has type string"#),
3042 "actual error message was {err}"
3043 );
3044
3045 let entitiesjson = json!(
3047 [
3048 {
3049 "uid": { "type": "Employee", "id": "12UA45" },
3050 "attrs": {
3051 "isFullTime": true,
3052 "numDirectReports": 3,
3053 "department": "Sales",
3054 "manager": "34FB87",
3055 "hr_contacts": [
3056 { "type": "HR", "id": "aaaaa" },
3057 { "type": "HR", "id": "bbbbb" }
3058 ],
3059 "json_blob": {
3060 "inner1": false,
3061 "inner2": "-*/",
3062 "inner3": { "innerinner": { "type": "Employee", "id": "09AE76" }},
3063 },
3064 "home_ip": "222.222.222.101",
3065 "work_ip": { "fn": "ip", "arg": "2.2.2.0/24" },
3066 "trust_score": "5.7",
3067 "tricky": { "type": "Employee", "id": "34FB87" }
3068 },
3069 "parents": []
3070 }
3071 ]
3072 );
3073 let err = Entities::from_json_value(entitiesjson, Some(&schema))
3074 .expect_err("should fail due to type mismatch on manager");
3075 assert!(
3076 err.to_string()
3077 .contains(r#"In attribute "manager" on Employee::"12UA45", expected a literal entity reference, but got "34FB87""#),
3078 "actual error message was {err}"
3079 );
3080
3081 let entitiesjson = json!(
3083 [
3084 {
3085 "uid": { "type": "Employee", "id": "12UA45" },
3086 "attrs": {
3087 "isFullTime": true,
3088 "numDirectReports": 3,
3089 "department": "Sales",
3090 "manager": { "type": "Employee", "id": "34FB87" },
3091 "hr_contacts": { "type": "HR", "id": "aaaaa" },
3092 "json_blob": {
3093 "inner1": false,
3094 "inner2": "-*/",
3095 "inner3": { "innerinner": { "type": "Employee", "id": "09AE76" }},
3096 },
3097 "home_ip": "222.222.222.101",
3098 "work_ip": { "fn": "ip", "arg": "2.2.2.0/24" },
3099 "trust_score": "5.7",
3100 "tricky": { "type": "Employee", "id": "34FB87" }
3101 },
3102 "parents": []
3103 }
3104 ]
3105 );
3106 let err = Entities::from_json_value(entitiesjson, Some(&schema))
3107 .expect_err("should fail due to type mismatch on hr_contacts");
3108 assert!(
3109 err.to_string().contains(r#"In attribute "hr_contacts" on Employee::"12UA45", type mismatch: attribute was expected to have type (set of (entity of type HR)), but actually has type record with attributes: ("#),
3110 "actual error message was {err}"
3111 );
3112
3113 let entitiesjson = json!(
3115 [
3116 {
3117 "uid": { "type": "Employee", "id": "12UA45" },
3118 "attrs": {
3119 "isFullTime": true,
3120 "numDirectReports": 3,
3121 "department": "Sales",
3122 "manager": { "type": "HR", "id": "34FB87" },
3123 "hr_contacts": [
3124 { "type": "HR", "id": "aaaaa" },
3125 { "type": "HR", "id": "bbbbb" }
3126 ],
3127 "json_blob": {
3128 "inner1": false,
3129 "inner2": "-*/",
3130 "inner3": { "innerinner": { "type": "Employee", "id": "09AE76" }},
3131 },
3132 "home_ip": "222.222.222.101",
3133 "work_ip": { "fn": "ip", "arg": "2.2.2.0/24" },
3134 "trust_score": "5.7",
3135 "tricky": { "type": "Employee", "id": "34FB87" }
3136 },
3137 "parents": []
3138 }
3139 ]
3140 );
3141 let err = Entities::from_json_value(entitiesjson, Some(&schema))
3142 .expect_err("should fail due to type mismatch on manager");
3143 assert!(
3144 err.to_string().contains(r#"In attribute "manager" on Employee::"12UA45", type mismatch: attribute was expected to have type (entity of type Employee), but actually has type (entity of type HR)"#),
3145 "actual error message was {err}"
3146 );
3147
3148 let entitiesjson = json!(
3151 [
3152 {
3153 "uid": { "type": "Employee", "id": "12UA45" },
3154 "attrs": {
3155 "isFullTime": true,
3156 "numDirectReports": 3,
3157 "department": "Sales",
3158 "manager": { "type": "Employee", "id": "34FB87" },
3159 "hr_contacts": [
3160 { "type": "HR", "id": "aaaaa" },
3161 { "type": "HR", "id": "bbbbb" }
3162 ],
3163 "json_blob": {
3164 "inner1": false,
3165 "inner2": "-*/",
3166 "inner3": { "innerinner": { "type": "Employee", "id": "09AE76" }},
3167 },
3168 "home_ip": { "fn": "decimal", "arg": "3.33" },
3169 "work_ip": { "fn": "ip", "arg": "2.2.2.0/24" },
3170 "trust_score": "5.7",
3171 "tricky": { "type": "Employee", "id": "34FB87" }
3172 },
3173 "parents": []
3174 }
3175 ]
3176 );
3177 let err = Entities::from_json_value(entitiesjson, Some(&schema))
3178 .expect_err("should fail due to type mismatch on home_ip");
3179 assert!(
3180 err.to_string().contains(r#"In attribute "home_ip" on Employee::"12UA45", type mismatch: attribute was expected to have type ipaddr, but actually has type decimal"#),
3181 "actual error message was {err}"
3182 );
3183
3184 let entitiesjson = json!(
3186 [
3187 {
3188 "uid": { "type": "Employee", "id": "12UA45" },
3189 "attrs": {
3190 "isFullTime": true,
3191 "numDirectReports": 3,
3192 "department": "Sales",
3193 "manager": { "type": "Employee", "id": "34FB87" },
3194 "hr_contacts": [
3195 { "type": "HR", "id": "aaaaa" },
3196 { "type": "HR", "id": "bbbbb" }
3197 ],
3198 "json_blob": {
3199 "inner1": false,
3200 "inner3": { "innerinner": { "type": "Employee", "id": "09AE76" }},
3201 },
3202 "home_ip": "222.222.222.101",
3203 "work_ip": { "fn": "ip", "arg": "2.2.2.0/24" },
3204 "trust_score": "5.7",
3205 "tricky": { "type": "Employee", "id": "34FB87" }
3206 },
3207 "parents": []
3208 }
3209 ]
3210 );
3211 let err = Entities::from_json_value(entitiesjson, Some(&schema))
3212 .expect_err("should fail due to missing attribute \"inner2\"");
3213 assert!(
3214 err.to_string().contains(r#"In attribute "json_blob" on Employee::"12UA45", expected the record to have an attribute "inner2", but it didn't"#),
3215 "actual error message was {err}"
3216 );
3217
3218 let entitiesjson = json!(
3220 [
3221 {
3222 "uid": { "type": "Employee", "id": "12UA45" },
3223 "attrs": {
3224 "isFullTime": true,
3225 "numDirectReports": 3,
3226 "department": "Sales",
3227 "manager": { "type": "Employee", "id": "34FB87" },
3228 "hr_contacts": [
3229 { "type": "HR", "id": "aaaaa" },
3230 { "type": "HR", "id": "bbbbb" }
3231 ],
3232 "json_blob": {
3233 "inner1": 33,
3234 "inner2": "-*/",
3235 "inner3": { "innerinner": { "type": "Employee", "id": "09AE76" }},
3236 },
3237 "home_ip": "222.222.222.101",
3238 "work_ip": { "fn": "ip", "arg": "2.2.2.0/24" },
3239 "trust_score": "5.7",
3240 "tricky": { "type": "Employee", "id": "34FB87" }
3241 },
3242 "parents": []
3243 }
3244 ]
3245 );
3246 let err = Entities::from_json_value(entitiesjson, Some(&schema))
3247 .expect_err("should fail due to type mismatch on attribute \"inner1\"");
3248 assert!(
3249 err.to_string().contains(r#"In attribute "json_blob" on Employee::"12UA45", type mismatch: attribute was expected to have type record with attributes: "#),
3250 "actual error message was {err}"
3251 );
3252
3253 let entitiesjson = json!(
3254 [
3255 {
3256 "uid": { "__entity": { "type": "Employee", "id": "12UA45" } },
3257 "attrs": {
3258 "isFullTime": true,
3259 "numDirectReports": 3,
3260 "department": "Sales",
3261 "manager": { "__entity": { "type": "Employee", "id": "34FB87" } },
3262 "hr_contacts": [
3263 { "type": "HR", "id": "aaaaa" },
3264 { "type": "HR", "id": "bbbbb" }
3265 ],
3266 "json_blob": {
3267 "inner1": false,
3268 "inner2": "-*/",
3269 "inner3": { "innerinner": { "type": "Employee", "id": "09AE76" }},
3270 },
3271 "home_ip": { "__extn": { "fn": "ip", "arg": "222.222.222.101" } },
3272 "work_ip": { "__extn": { "fn": "ip", "arg": "2.2.2.0/24" } },
3273 "trust_score": { "__extn": { "fn": "decimal", "arg": "5.7" } },
3274 "tricky": { "type": "Employee", "id": "34FB87" }
3275 },
3276 "parents": []
3277 }
3278 ]
3279 );
3280 let _ = Entities::from_json_value(entitiesjson, Some(&schema))
3281 .expect("this version with explicit __entity and __extn escapes should also pass");
3282 }
3283
3284 #[test]
3286 fn namespaces() {
3287 let schema = Schema::from_str(
3288 r#"
3289 {"XYZCorp": {
3290 "entityTypes": {
3291 "Employee": {
3292 "memberOfTypes": [],
3293 "shape": {
3294 "type": "Record",
3295 "attributes": {
3296 "isFullTime": { "type": "Boolean" },
3297 "department": { "type": "String" },
3298 "manager": {
3299 "type": "Entity",
3300 "name": "XYZCorp::Employee"
3301 }
3302 }
3303 }
3304 }
3305 },
3306 "actions": {
3307 "view": {}
3308 }
3309 }}
3310 "#,
3311 )
3312 .expect("should be a valid schema");
3313
3314 let entitiesjson = json!(
3315 [
3316 {
3317 "uid": { "type": "XYZCorp::Employee", "id": "12UA45" },
3318 "attrs": {
3319 "isFullTime": true,
3320 "department": "Sales",
3321 "manager": { "type": "XYZCorp::Employee", "id": "34FB87" }
3322 },
3323 "parents": []
3324 }
3325 ]
3326 );
3327 let parsed = Entities::from_json_value(entitiesjson, Some(&schema))
3328 .expect("Should parse without error");
3329 assert_eq!(parsed.iter().count(), 1);
3330 let parsed = parsed
3331 .get(&EntityUid::from_strs("XYZCorp::Employee", "12UA45"))
3332 .expect("that should be the employee type and id");
3333 assert_eq!(parsed.attr("isFullTime"), Some(Ok(EvalResult::Bool(true))));
3334 assert_eq!(
3335 parsed.attr("department"),
3336 Some(Ok(EvalResult::String("Sales".into())))
3337 );
3338 assert_eq!(
3339 parsed.attr("manager"),
3340 Some(Ok(EvalResult::EntityUid(EntityUid::from_strs(
3341 "XYZCorp::Employee",
3342 "34FB87"
3343 ))))
3344 );
3345
3346 let entitiesjson = json!(
3347 [
3348 {
3349 "uid": { "type": "XYZCorp::Employee", "id": "12UA45" },
3350 "attrs": {
3351 "isFullTime": true,
3352 "department": "Sales",
3353 "manager": { "type": "Employee", "id": "34FB87" }
3354 },
3355 "parents": []
3356 }
3357 ]
3358 );
3359 let err = Entities::from_json_value(entitiesjson, Some(&schema))
3360 .expect_err("should fail due to manager being wrong entity type (missing namespace)");
3361 assert!(
3362 err.to_string().contains(r#"In attribute "manager" on XYZCorp::Employee::"12UA45", type mismatch: attribute was expected to have type (entity of type XYZCorp::Employee), but actually has type (entity of type Employee)"#),
3363 "actual error message was {err}"
3364 );
3365 }
3366
3367 #[test]
3369 fn optional_attrs() {
3370 let schema = Schema::from_str(
3371 r#"
3372 {"": {
3373 "entityTypes": {
3374 "Employee": {
3375 "memberOfTypes": [],
3376 "shape": {
3377 "type": "Record",
3378 "attributes": {
3379 "isFullTime": { "type": "Boolean" },
3380 "department": { "type": "String", "required": false },
3381 "manager": { "type": "Entity", "name": "Employee" }
3382 }
3383 }
3384 }
3385 },
3386 "actions": {
3387 "view": {}
3388 }
3389 }}
3390 "#,
3391 )
3392 .expect("should be a valid schema");
3393
3394 let entitiesjson = json!(
3396 [
3397 {
3398 "uid": { "type": "Employee", "id": "12UA45" },
3399 "attrs": {
3400 "isFullTime": true,
3401 "department": "Sales",
3402 "manager": { "type": "Employee", "id": "34FB87" }
3403 },
3404 "parents": []
3405 }
3406 ]
3407 );
3408 let parsed = Entities::from_json_value(entitiesjson, Some(&schema))
3409 .expect("Should parse without error");
3410 assert_eq!(parsed.iter().count(), 1);
3411
3412 let entitiesjson = json!(
3414 [
3415 {
3416 "uid": { "type": "Employee", "id": "12UA45" },
3417 "attrs": {
3418 "isFullTime": true,
3419 "manager": { "type": "Employee", "id": "34FB87" }
3420 },
3421 "parents": []
3422 }
3423 ]
3424 );
3425 let parsed = Entities::from_json_value(entitiesjson, Some(&schema))
3426 .expect("Should parse without error");
3427 assert_eq!(parsed.iter().count(), 1);
3428 }
3429
3430 #[test]
3432 #[should_panic(
3433 expected = "UnsupportedSchemaFeature(\"Records and entities with additional attributes are not yet implemented.\")"
3434 )]
3435 fn open_entities() {
3436 let schema = Schema::from_str(
3437 r#"
3438 {"": {
3439 "entityTypes": {
3440 "Employee": {
3441 "memberOfTypes": [],
3442 "shape": {
3443 "type": "Record",
3444 "attributes": {
3445 "isFullTime": { "type": "Boolean" },
3446 "department": { "type": "String", "required": false },
3447 "manager": { "type": "Entity", "name": "Employee" }
3448 },
3449 "additionalAttributes": true
3450 }
3451 }
3452 },
3453 "actions": {
3454 "view": {}
3455 }
3456 }}
3457 "#,
3458 )
3459 .expect("should be a valid schema");
3460
3461 let entitiesjson = json!(
3463 [
3464 {
3465 "uid": { "type": "Employee", "id": "12UA45" },
3466 "attrs": {
3467 "isFullTime": true,
3468 "department": "Sales",
3469 "manager": { "type": "Employee", "id": "34FB87" }
3470 },
3471 "parents": []
3472 }
3473 ]
3474 );
3475 let parsed = Entities::from_json_value(entitiesjson, Some(&schema))
3476 .expect("Should parse without error");
3477 assert_eq!(parsed.iter().count(), 1);
3478
3479 let entitiesjson = json!(
3481 [
3482 {
3483 "uid": { "type": "Employee", "id": "12UA45" },
3484 "attrs": {
3485 "isFullTime": true,
3486 "foobar": 234,
3487 "manager": { "type": "Employee", "id": "34FB87" }
3488 },
3489 "parents": []
3490 }
3491 ]
3492 );
3493 let parsed = Entities::from_json_value(entitiesjson, Some(&schema))
3494 .expect("Should parse without error");
3495 assert_eq!(parsed.iter().count(), 1);
3496 }
3497
3498 #[test]
3499 fn schema_sanity_check() {
3500 let src = "{ , .. }";
3501 assert_matches!(Schema::from_str(src), Err(super::SchemaError::ParseJson(_)));
3502 }
3503
3504 #[test]
3505 fn template_constraint_sanity_checks() {
3506 assert!(!TemplatePrincipalConstraint::Any.has_slot());
3507 assert!(!TemplatePrincipalConstraint::In(Some(EntityUid::from_strs("a", "a"))).has_slot());
3508 assert!(!TemplatePrincipalConstraint::Eq(Some(EntityUid::from_strs("a", "a"))).has_slot());
3509 assert!(TemplatePrincipalConstraint::In(None).has_slot());
3510 assert!(TemplatePrincipalConstraint::Eq(None).has_slot());
3511 assert!(!TemplateResourceConstraint::Any.has_slot());
3512 assert!(!TemplateResourceConstraint::In(Some(EntityUid::from_strs("a", "a"))).has_slot());
3513 assert!(!TemplateResourceConstraint::Eq(Some(EntityUid::from_strs("a", "a"))).has_slot());
3514 assert!(TemplateResourceConstraint::In(None).has_slot());
3515 assert!(TemplateResourceConstraint::Eq(None).has_slot());
3516 }
3517
3518 #[test]
3519 fn template_principal_constraints() {
3520 let src = r#"
3521 permit(principal, action, resource);
3522 "#;
3523 let t = Template::parse(None, src).unwrap();
3524 assert_eq!(t.principal_constraint(), TemplatePrincipalConstraint::Any);
3525
3526 let src = r#"
3527 permit(principal == ?principal, action, resource);
3528 "#;
3529 let t = Template::parse(None, src).unwrap();
3530 assert_eq!(
3531 t.principal_constraint(),
3532 TemplatePrincipalConstraint::Eq(None)
3533 );
3534
3535 let src = r#"
3536 permit(principal == A::"a", action, resource);
3537 "#;
3538 let t = Template::parse(None, src).unwrap();
3539 assert_eq!(
3540 t.principal_constraint(),
3541 TemplatePrincipalConstraint::Eq(Some(EntityUid::from_strs("A", "a")))
3542 );
3543
3544 let src = r#"
3545 permit(principal in ?principal, action, resource);
3546 "#;
3547 let t = Template::parse(None, src).unwrap();
3548 assert_eq!(
3549 t.principal_constraint(),
3550 TemplatePrincipalConstraint::In(None)
3551 );
3552
3553 let src = r#"
3554 permit(principal in A::"a", action, resource);
3555 "#;
3556 let t = Template::parse(None, src).unwrap();
3557 assert_eq!(
3558 t.principal_constraint(),
3559 TemplatePrincipalConstraint::In(Some(EntityUid::from_strs("A", "a")))
3560 );
3561 }
3562
3563 #[test]
3564 fn template_action_constraints() {
3565 let src = r#"
3566 permit(principal, action, resource);
3567 "#;
3568 let t = Template::parse(None, src).unwrap();
3569 assert_eq!(t.action_constraint(), ActionConstraint::Any);
3570
3571 let src = r#"
3572 permit(principal, action == Action::"A", resource);
3573 "#;
3574 let t = Template::parse(None, src).unwrap();
3575 assert_eq!(
3576 t.action_constraint(),
3577 ActionConstraint::Eq(EntityUid::from_strs("Action", "A"))
3578 );
3579
3580 let src = r#"
3581 permit(principal, action in [Action::"A", Action::"B"], resource);
3582 "#;
3583 let t = Template::parse(None, src).unwrap();
3584 assert_eq!(
3585 t.action_constraint(),
3586 ActionConstraint::In(vec![
3587 EntityUid::from_strs("Action", "A"),
3588 EntityUid::from_strs("Action", "B")
3589 ])
3590 );
3591 }
3592
3593 #[test]
3594 fn template_resource_constraints() {
3595 let src = r#"
3596 permit(principal, action, resource);
3597 "#;
3598 let t = Template::parse(None, src).unwrap();
3599 assert_eq!(t.resource_constraint(), TemplateResourceConstraint::Any);
3600
3601 let src = r#"
3602 permit(principal, action, resource == ?resource);
3603 "#;
3604 let t = Template::parse(None, src).unwrap();
3605 assert_eq!(
3606 t.resource_constraint(),
3607 TemplateResourceConstraint::Eq(None)
3608 );
3609
3610 let src = r#"
3611 permit(principal, action, resource == A::"a");
3612 "#;
3613 let t = Template::parse(None, src).unwrap();
3614 assert_eq!(
3615 t.resource_constraint(),
3616 TemplateResourceConstraint::Eq(Some(EntityUid::from_strs("A", "a")))
3617 );
3618
3619 let src = r#"
3620 permit(principal, action, resource in ?resource);
3621 "#;
3622 let t = Template::parse(None, src).unwrap();
3623 assert_eq!(
3624 t.resource_constraint(),
3625 TemplateResourceConstraint::In(None)
3626 );
3627
3628 let src = r#"
3629 permit(principal, action, resource in A::"a");
3630 "#;
3631 let t = Template::parse(None, src).unwrap();
3632 assert_eq!(
3633 t.resource_constraint(),
3634 TemplateResourceConstraint::In(Some(EntityUid::from_strs("A", "a")))
3635 );
3636 }
3637
3638 #[test]
3639 fn schema_namespace() {
3640 let fragment: SchemaFragment = r#"
3641 {
3642 "Foo::Bar": {
3643 "entityTypes": {},
3644 "actions": {}
3645 }
3646 }
3647 "#
3648 .parse()
3649 .unwrap();
3650 let namespaces = fragment.namespaces().next().unwrap();
3651 assert_eq!(
3652 namespaces.map(|ns| ns.to_string()),
3653 Some("Foo::Bar".to_string())
3654 );
3655 let _schema: Schema = fragment.try_into().expect("Should convert to schema");
3656
3657 let fragment: SchemaFragment = r#"
3658 {
3659 "": {
3660 "entityTypes": {},
3661 "actions": {}
3662 }
3663 }
3664 "#
3665 .parse()
3666 .unwrap();
3667 let namespaces = fragment.namespaces().next().unwrap();
3668 assert_eq!(namespaces, None);
3669 let _schema: Schema = fragment.try_into().expect("Should convert to schema");
3670 }
3671
3672 #[test]
3673 fn load_multiple_namespaces() {
3674 let fragment = SchemaFragment::from_json_value(json!({
3675 "Foo::Bar": {
3676 "entityTypes": {
3677 "Baz": {
3678 "memberOfTypes": ["Bar::Foo::Baz"]
3679 }
3680 },
3681 "actions": {}
3682 },
3683 "Bar::Foo": {
3684 "entityTypes": {
3685 "Baz": {
3686 "memberOfTypes": ["Foo::Bar::Baz"]
3687 }
3688 },
3689 "actions": {}
3690 }
3691 }))
3692 .unwrap();
3693
3694 let schema = Schema::from_schema_fragments([fragment]).unwrap();
3695
3696 assert!(schema
3697 .0
3698 .get_entity_type(&"Foo::Bar::Baz".parse().unwrap())
3699 .is_some());
3700 assert!(schema
3701 .0
3702 .get_entity_type(&"Bar::Foo::Baz".parse().unwrap())
3703 .is_some());
3704 }
3705
3706 #[test]
3707 fn get_attributes_from_schema() {
3708 let fragment: SchemaFragment = SchemaFragment::from_json_value(json!({
3709 "": {
3710 "entityTypes": {},
3711 "actions": {
3712 "A": {},
3713 "B": {
3714 "memberOf": [{"id": "A"}]
3715 },
3716 "C": {
3717 "memberOf": [{"id": "A"}]
3718 },
3719 "D": {
3720 "memberOf": [{"id": "B"}, {"id": "C"}]
3721 },
3722 "E": {
3723 "memberOf": [{"id": "D"}]
3724 }
3725 }
3726 }}))
3727 .unwrap();
3728 let schema = Schema::from_schema_fragments([fragment]).unwrap();
3729 let action_entities = schema.action_entities().unwrap();
3730
3731 let a_euid = EntityUid::from_strs("Action", "A");
3732 let b_euid = EntityUid::from_strs("Action", "B");
3733 let c_euid = EntityUid::from_strs("Action", "C");
3734 let d_euid = EntityUid::from_strs("Action", "D");
3735 let e_euid = EntityUid::from_strs("Action", "E");
3736 assert_eq!(
3737 action_entities,
3738 Entities::from_entities([
3739 Entity::new(a_euid.clone(), HashMap::new(), HashSet::new()),
3740 Entity::new(
3741 b_euid.clone(),
3742 HashMap::new(),
3743 HashSet::from([a_euid.clone()])
3744 ),
3745 Entity::new(
3746 c_euid.clone(),
3747 HashMap::new(),
3748 HashSet::from([a_euid.clone()])
3749 ),
3750 Entity::new(
3751 d_euid.clone(),
3752 HashMap::new(),
3753 HashSet::from([a_euid.clone(), b_euid.clone(), c_euid.clone()])
3754 ),
3755 Entity::new(
3756 e_euid.clone(),
3757 HashMap::new(),
3758 HashSet::from([a_euid, b_euid, c_euid, d_euid])
3759 ),
3760 ])
3761 .unwrap()
3762 )
3763 }
3764}