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
261#[repr(transparent)]
263#[derive(Debug, RefCast)]
264pub struct Authorizer(authorizer::Authorizer);
265
266impl Default for Authorizer {
267 fn default() -> Self {
268 Self::new()
269 }
270}
271
272impl Authorizer {
273 pub fn new() -> Self {
275 Self(authorizer::Authorizer::new())
276 }
277
278 pub fn is_authorized(&self, r: &Request, p: &PolicySet, e: &Entities) -> Response {
284 self.0.is_authorized(&r.0, &p.ast, &e.0).into()
285 }
286
287 pub fn is_authorized_partial(
292 &self,
293 query: &Request,
294 policy_set: &PolicySet,
295 entities: &Entities,
296 ) -> PartialResponse {
297 let response = self
298 .0
299 .is_authorized_core(&query.0, &policy_set.ast, &entities.0);
300 match response {
301 authorizer::ResponseKind::FullyEvaluated(a) => PartialResponse::Concrete(Response {
302 decision: a.decision,
303 diagnostics: Diagnostics {
304 reason: a.diagnostics.reason.into_iter().map(PolicyId).collect(),
305 errors: a.diagnostics.errors.into_iter().collect(),
306 },
307 }),
308 authorizer::ResponseKind::Partial(p) => PartialResponse::Residual(ResidualResponse {
309 residuals: PolicySet::from_ast(p.residuals),
310 diagnostics: Diagnostics {
311 reason: p.diagnostics.reason.into_iter().map(PolicyId).collect(),
312 errors: p.diagnostics.errors.into_iter().collect(),
313 },
314 }),
315 }
316 }
317}
318
319#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
321pub struct Response {
322 decision: Decision,
324 diagnostics: Diagnostics,
326}
327
328#[derive(Debug, PartialEq, Clone)]
331pub enum PartialResponse {
332 Concrete(Response),
334 Residual(ResidualResponse),
336}
337
338#[derive(Debug, PartialEq, Eq, Clone)]
340pub struct ResidualResponse {
341 residuals: PolicySet,
343 diagnostics: Diagnostics,
345}
346
347#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
349pub struct Diagnostics {
350 reason: HashSet<PolicyId>,
353 errors: HashSet<String>,
355}
356
357impl Diagnostics {
358 pub fn reason(&self) -> impl Iterator<Item = &PolicyId> {
360 self.reason.iter()
361 }
362
363 pub fn errors(&self) -> impl Iterator<Item = EvaluationError> + '_ {
365 self.errors
366 .iter()
367 .cloned()
368 .map(EvaluationError::StringMessage)
369 }
370}
371
372impl Response {
373 pub fn new(decision: Decision, reason: HashSet<PolicyId>, errors: HashSet<String>) -> Self {
375 Self {
376 decision,
377 diagnostics: Diagnostics { reason, errors },
378 }
379 }
380
381 pub fn decision(&self) -> Decision {
383 self.decision
384 }
385
386 pub fn diagnostics(&self) -> &Diagnostics {
388 &self.diagnostics
389 }
390}
391
392impl From<authorizer::Response> for Response {
393 fn from(a: authorizer::Response) -> Self {
394 Self {
395 decision: a.decision,
396 diagnostics: Diagnostics {
397 reason: a.diagnostics.reason.into_iter().map(PolicyId).collect(),
398 errors: a.diagnostics.errors.into_iter().collect(),
399 },
400 }
401 }
402}
403
404impl ResidualResponse {
405 pub fn new(residuals: PolicySet, reason: HashSet<PolicyId>, errors: HashSet<String>) -> Self {
407 Self {
408 residuals,
409 diagnostics: Diagnostics { reason, errors },
410 }
411 }
412
413 pub fn residuals(&self) -> &PolicySet {
415 &self.residuals
416 }
417
418 pub fn diagnostics(&self) -> &Diagnostics {
420 &self.diagnostics
421 }
422}
423
424#[derive(Debug, Clone, PartialEq, Eq, Error)]
427pub enum EvaluationError {
428 #[error("{0}")]
431 StringMessage(String),
432}
433
434#[derive(Default, Eq, PartialEq, Copy, Clone, Debug)]
436#[non_exhaustive]
437pub enum ValidationMode {
438 #[default]
441 Strict,
442 Permissive,
444}
445
446impl From<ValidationMode> for cedar_policy_validator::ValidationMode {
447 fn from(mode: ValidationMode) -> Self {
448 match mode {
449 ValidationMode::Strict => Self::Strict,
450 ValidationMode::Permissive => Self::Permissive,
451 }
452 }
453}
454
455#[repr(transparent)]
457#[derive(Debug, RefCast)]
458pub struct Validator(cedar_policy_validator::Validator);
459
460impl Validator {
461 pub fn new(schema: Schema) -> Self {
464 Self(cedar_policy_validator::Validator::new(schema.0))
465 }
466
467 pub fn validate<'a>(
475 &'a self,
476 pset: &'a PolicySet,
477 mode: ValidationMode,
478 ) -> ValidationResult<'a> {
479 ValidationResult::from(self.0.validate(&pset.ast, mode.into()))
480 }
481}
482
483#[derive(Debug)]
486pub struct SchemaFragment(cedar_policy_validator::ValidatorSchemaFragment);
487
488impl SchemaFragment {
489 pub fn namespaces(&self) -> impl Iterator<Item = Option<EntityNamespace>> + '_ {
493 self.0
494 .namespaces()
495 .map(|ns| ns.as_ref().map(|ns| EntityNamespace(ns.clone())))
496 }
497
498 pub fn from_json_value(json: serde_json::Value) -> Result<Self, SchemaError> {
501 Ok(Self(
502 cedar_policy_validator::SchemaFragment::from_json_value(json)?.try_into()?,
503 ))
504 }
505
506 pub fn from_file(file: impl std::io::Read) -> Result<Self, SchemaError> {
508 Ok(Self(
509 cedar_policy_validator::SchemaFragment::from_file(file)?.try_into()?,
510 ))
511 }
512}
513
514impl TryInto<Schema> for SchemaFragment {
515 type Error = SchemaError;
516
517 fn try_into(self) -> Result<Schema, Self::Error> {
521 Ok(Schema(
522 cedar_policy_validator::ValidatorSchema::from_schema_fragments([self.0])?,
523 ))
524 }
525}
526
527impl FromStr for SchemaFragment {
528 type Err = SchemaError;
529 fn from_str(src: &str) -> Result<Self, Self::Err> {
536 Ok(Self(
537 serde_json::from_str::<cedar_policy_validator::SchemaFragment>(src)
538 .map_err(cedar_policy_validator::SchemaError::from)?
539 .try_into()?,
540 ))
541 }
542}
543
544#[repr(transparent)]
546#[derive(Debug, Clone, RefCast)]
547pub struct Schema(pub(crate) cedar_policy_validator::ValidatorSchema);
548
549impl FromStr for Schema {
550 type Err = SchemaError;
551
552 fn from_str(schema_src: &str) -> Result<Self, Self::Err> {
559 Ok(Self(schema_src.parse()?))
560 }
561}
562
563impl Schema {
564 pub fn from_schema_fragments(
569 fragments: impl IntoIterator<Item = SchemaFragment>,
570 ) -> Result<Self, SchemaError> {
571 Ok(Self(
572 cedar_policy_validator::ValidatorSchema::from_schema_fragments(
573 fragments.into_iter().map(|f| f.0),
574 )?,
575 ))
576 }
577
578 pub fn from_json_value(json: serde_json::Value) -> Result<Self, SchemaError> {
581 Ok(Self(
582 cedar_policy_validator::ValidatorSchema::from_json_value(json)?,
583 ))
584 }
585
586 pub fn from_file(file: impl std::io::Read) -> Result<Self, SchemaError> {
588 Ok(Self(cedar_policy_validator::ValidatorSchema::from_file(
589 file,
590 )?))
591 }
592
593 pub fn action_entities(&self) -> Result<Entities, entities::EntitiesError> {
596 Ok(Entities(self.0.action_entities()?))
597 }
598}
599
600#[derive(Debug, Error)]
602pub enum SchemaError {
603 #[error("JSON Schema file could not be parsed: {0}")]
605 ParseJson(serde_json::Error),
606 #[error("Transitive closure error on action hierarchy: {0}")]
609 ActionTransitiveClosureError(String),
610 #[error("Transitive closure error on entity hierarchy: {0}")]
613 EntityTransitiveClosureError(String),
614 #[error("Unsupported feature used in schema: {0}")]
617 UnsupportedSchemaFeature(String),
618 #[error("Undeclared entity types: {0:?}")]
621 UndeclaredEntityTypes(HashSet<String>),
622 #[error("Undeclared actions: {0:?}")]
624 UndeclaredActions(HashSet<String>),
625 #[error("Undeclared common types: {0:?}")]
627 UndeclaredCommonType(HashSet<String>),
628 #[error("Duplicate entity type {0}")]
631 DuplicateEntityType(String),
632 #[error("Duplicate action {0}")]
635 DuplicateAction(String),
636 #[error("Duplicate common type {0}")]
639 DuplicateCommonType(String),
640 #[error("Cycle in action hierarchy")]
642 CycleInActionHierarchy,
643 #[error("Parse error in entity type: {0}")]
645 EntityTypeParse(ParseErrors),
646 #[error("Parse error in namespace identifier: {0}")]
648 NamespaceParse(ParseErrors),
649 #[error("Parse error in common type identifier: {0}")]
651 CommonTypeParseError(ParseErrors),
652 #[error("Parse error in extension type: {0}")]
654 ExtensionTypeParse(ParseErrors),
655 #[error("Entity type `Action` declared in `entityTypes` list.")]
660 ActionEntityTypeDeclared,
661 #[error("Actions declared with `attributes`: [{}]", .0.iter().map(String::as_str).join(", "))]
664 ActionEntityAttributes(Vec<String>),
665 #[error("Action context or entity type shape is not a record")]
668 ContextOrShapeNotRecord,
669 #[error("Action attribute is an empty set")]
671 ActionEntityAttributeEmptySet,
672 #[error(
674 "Action has an attribute of unsupported type (escaped expression, entity or extension)"
675 )]
676 ActionEntityAttributeUnsupportedType,
677}
678
679#[doc(hidden)]
680impl From<cedar_policy_validator::SchemaError> for SchemaError {
681 fn from(value: cedar_policy_validator::SchemaError) -> Self {
682 match value {
683 cedar_policy_validator::SchemaError::ParseFileFormat(e) => Self::ParseJson(e),
684 cedar_policy_validator::SchemaError::ActionTransitiveClosureError(e) => {
685 Self::ActionTransitiveClosureError(e.to_string())
686 }
687 cedar_policy_validator::SchemaError::EntityTransitiveClosureError(e) => {
688 Self::EntityTransitiveClosureError(e.to_string())
689 }
690 cedar_policy_validator::SchemaError::UnsupportedSchemaFeature(e) => {
691 Self::UnsupportedSchemaFeature(e.to_string())
692 }
693 cedar_policy_validator::SchemaError::UndeclaredEntityTypes(e) => {
694 Self::UndeclaredEntityTypes(e)
695 }
696 cedar_policy_validator::SchemaError::UndeclaredActions(e) => Self::UndeclaredActions(e),
697 cedar_policy_validator::SchemaError::UndeclaredCommonType(c) => {
698 Self::UndeclaredCommonType(c)
699 }
700 cedar_policy_validator::SchemaError::DuplicateEntityType(e) => {
701 Self::DuplicateEntityType(e)
702 }
703 cedar_policy_validator::SchemaError::DuplicateAction(e) => Self::DuplicateAction(e),
704 cedar_policy_validator::SchemaError::DuplicateCommonType(c) => {
705 Self::DuplicateCommonType(c)
706 }
707 cedar_policy_validator::SchemaError::CycleInActionHierarchy => {
708 Self::CycleInActionHierarchy
709 }
710 cedar_policy_validator::SchemaError::EntityTypeParseError(e) => {
711 Self::EntityTypeParse(ParseErrors(e))
712 }
713 cedar_policy_validator::SchemaError::NamespaceParseError(e) => {
714 Self::NamespaceParse(ParseErrors(e))
715 }
716 cedar_policy_validator::SchemaError::CommonTypeParseError(e) => {
717 Self::CommonTypeParseError(ParseErrors(e))
718 }
719 cedar_policy_validator::SchemaError::ExtensionTypeParseError(e) => {
720 Self::ExtensionTypeParse(ParseErrors(e))
721 }
722 cedar_policy_validator::SchemaError::ActionEntityTypeDeclared => {
723 Self::ActionEntityTypeDeclared
724 }
725 cedar_policy_validator::SchemaError::ActionEntityAttributes(e) => {
726 Self::ActionEntityAttributes(e)
727 }
728 cedar_policy_validator::SchemaError::ContextOrShapeNotRecord
729 | cedar_policy_validator::SchemaError::ActionEntityAttributeEmptySet
730 | cedar_policy_validator::SchemaError::ActionEntityAttributeUnsupportedType => {
731 Self::ContextOrShapeNotRecord
732 }
733 }
734 }
735}
736
737#[derive(Debug)]
742pub struct ValidationResult<'a> {
743 validation_errors: Vec<ValidationError<'a>>,
744}
745
746impl<'a> ValidationResult<'a> {
747 pub fn validation_passed(&self) -> bool {
749 self.validation_errors.is_empty()
750 }
751
752 pub fn validation_errors(&self) -> impl Iterator<Item = &ValidationError<'a>> {
754 self.validation_errors.iter()
755 }
756}
757
758impl<'a> From<cedar_policy_validator::ValidationResult<'a>> for ValidationResult<'a> {
759 fn from(r: cedar_policy_validator::ValidationResult<'a>) -> Self {
760 Self {
761 validation_errors: r
762 .into_validation_errors()
763 .map(ValidationError::from)
764 .collect(),
765 }
766 }
767}
768
769#[derive(Debug, Error)]
774pub struct ValidationError<'a> {
775 location: SourceLocation<'a>,
776 error_kind: ValidationErrorKind,
777}
778
779impl<'a> ValidationError<'a> {
780 pub fn error_kind(&self) -> &ValidationErrorKind {
782 &self.error_kind
783 }
784
785 pub fn location(&self) -> &SourceLocation<'a> {
787 &self.location
788 }
789}
790
791impl<'a> From<cedar_policy_validator::ValidationError<'a>> for ValidationError<'a> {
792 fn from(err: cedar_policy_validator::ValidationError<'a>) -> Self {
793 let (location, error_kind) = err.into_location_and_error_kind();
794 Self {
795 location: SourceLocation::from(location),
796 error_kind,
797 }
798 }
799}
800
801impl<'a> std::fmt::Display for ValidationError<'a> {
802 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
803 write!(f, "Validation error on policy {}", self.location.policy_id)?;
804 if let (Some(range_start), Some(range_end)) =
805 (self.location().range_start(), self.location().range_end())
806 {
807 write!(f, " at offset {range_start}-{range_end}")?;
808 }
809 write!(f, ": {}", self.error_kind())
810 }
811}
812
813#[derive(Debug, Clone, Eq, PartialEq)]
815pub struct SourceLocation<'a> {
816 policy_id: &'a PolicyId,
817 source_range: Option<SourceInfo>,
818}
819
820impl<'a> SourceLocation<'a> {
821 pub fn policy_id(&self) -> &'a PolicyId {
823 self.policy_id
824 }
825
826 pub fn range_start(&self) -> Option<usize> {
829 self.source_range.as_ref().map(SourceInfo::range_start)
830 }
831
832 pub fn range_end(&self) -> Option<usize> {
835 self.source_range.as_ref().map(SourceInfo::range_end)
836 }
837}
838
839impl<'a> From<cedar_policy_validator::SourceLocation<'a>> for SourceLocation<'a> {
840 fn from(loc: cedar_policy_validator::SourceLocation<'a>) -> SourceLocation<'a> {
841 let policy_id: &'a PolicyId = PolicyId::ref_cast(loc.policy_id());
842 let source_range = loc.into_source_info();
843 Self {
844 policy_id,
845 source_range,
846 }
847 }
848}
849
850pub fn confusable_string_checker<'a>(
852 templates: impl Iterator<Item = &'a Template>,
853) -> impl Iterator<Item = ValidationWarning<'a>> {
854 cedar_policy_validator::confusable_string_checks(templates.map(|t| &t.ast))
855 .map(std::convert::Into::into)
856}
857
858#[derive(Debug, Error)]
859#[error("Warning on policy {}: {}", .location.policy_id, .kind)]
860pub struct ValidationWarning<'a> {
862 location: SourceLocation<'a>,
863 kind: ValidationWarningKind,
864}
865
866impl<'a> ValidationWarning<'a> {
867 pub fn warning_kind(&self) -> &ValidationWarningKind {
869 &self.kind
870 }
871
872 pub fn location(&self) -> &SourceLocation<'a> {
874 &self.location
875 }
876}
877
878#[doc(hidden)]
879impl<'a> From<cedar_policy_validator::ValidationWarning<'a>> for ValidationWarning<'a> {
880 fn from(w: cedar_policy_validator::ValidationWarning<'a>) -> Self {
881 let (loc, kind) = w.to_kind_and_location();
882 ValidationWarning {
883 location: SourceLocation {
884 policy_id: PolicyId::ref_cast(loc),
885 source_range: None,
886 },
887 kind,
888 }
889 }
890}
891
892#[repr(transparent)]
894#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, RefCast)]
895pub struct EntityId(ast::Eid);
896
897impl FromStr for EntityId {
898 type Err = ParseErrors;
899 fn from_str(eid_str: &str) -> Result<Self, Self::Err> {
900 Ok(Self(ast::Eid::new(eid_str)))
901 }
902}
903
904impl AsRef<str> for EntityId {
905 fn as_ref(&self) -> &str {
906 self.0.as_ref()
907 }
908}
909
910impl std::fmt::Display for EntityId {
914 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
915 write!(f, "{}", self.0)
916 }
917}
918
919#[repr(transparent)]
921#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, RefCast)]
922pub struct EntityTypeName(ast::Name);
923
924impl FromStr for EntityTypeName {
925 type Err = ParseErrors;
926
927 fn from_str(namespace_type_str: &str) -> Result<Self, Self::Err> {
928 match ast::Name::from_str(namespace_type_str) {
929 Ok(name) => Ok(Self(name)),
930 Err(errs) => Err(ParseErrors(errs)),
931 }
932 }
933}
934
935impl std::fmt::Display for EntityTypeName {
936 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
937 write!(f, "{}", self.0)
938 }
939}
940
941#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
943pub struct EntityNamespace(ast::Name);
944
945impl FromStr for EntityNamespace {
946 type Err = ParseErrors;
947
948 fn from_str(namespace_str: &str) -> Result<Self, Self::Err> {
949 Ok(Self(
950 ast::Name::from_str(namespace_str).map_err(ParseErrors)?,
951 ))
952 }
953}
954
955impl std::fmt::Display for EntityNamespace {
956 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
957 write!(f, "{}", self.0)
958 }
959}
960
961#[repr(transparent)]
963#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, RefCast)]
964pub struct EntityUid(ast::EntityUID);
965
966impl EntityUid {
967 pub fn type_name(&self) -> &EntityTypeName {
969 match self.0.entity_type() {
970 ast::EntityType::Unspecified => panic!("Impossible to have an unspecified entity"),
971 ast::EntityType::Concrete(name) => EntityTypeName::ref_cast(name),
972 }
973 }
974
975 pub fn id(&self) -> &EntityId {
977 EntityId::ref_cast(self.0.eid())
978 }
979
980 pub fn from_type_name_and_id(name: EntityTypeName, id: EntityId) -> Self {
982 Self(ast::EntityUID::from_components(name.0, id.0))
983 }
984
985 pub fn from_json(json: serde_json::Value) -> Result<Self, impl std::error::Error> {
992 let parsed: entities::EntityUidJSON = serde_json::from_value(json)?;
993 Ok::<Self, entities::JsonDeserializationError>(Self(
994 parsed.into_euid(|| JsonDeserializationErrorContext::EntityUid)?,
995 ))
996 }
997
998 #[cfg(test)]
1000 pub(crate) fn from_strs(typename: &str, id: &str) -> Self {
1001 Self::from_type_name_and_id(
1002 EntityTypeName::from_str(typename).unwrap(),
1003 EntityId::from_str(id).unwrap(),
1004 )
1005 }
1006}
1007
1008impl FromStr for EntityUid {
1009 type Err = ParseErrors;
1010
1011 fn from_str(uid: &str) -> Result<Self, Self::Err> {
1018 parser::parse_euid(uid).map(EntityUid).map_err(ParseErrors)
1019 }
1020}
1021
1022impl std::fmt::Display for EntityUid {
1023 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1024 write!(f, "{}", self.0)
1025 }
1026}
1027
1028#[derive(Error, Debug)]
1030#[non_exhaustive]
1031pub enum PolicySetError {
1032 #[error("Collision in template or policy id")]
1034 AlreadyDefined,
1035 #[error("Unable to link template: {0}")]
1037 LinkingError(#[from] ast::LinkingError),
1038 #[error("Expected static policy, but a template-linked policy was provided")]
1040 ExpectedStatic,
1041}
1042
1043impl From<ast::PolicySetError> for PolicySetError {
1044 fn from(e: ast::PolicySetError) -> Self {
1045 match e {
1046 ast::PolicySetError::Occupied => Self::AlreadyDefined,
1047 }
1048 }
1049}
1050
1051impl From<ast::ContainsSlot> for PolicySetError {
1052 fn from(_: ast::ContainsSlot) -> Self {
1053 Self::ExpectedStatic
1054 }
1055}
1056
1057#[derive(Debug, Clone, Default)]
1059pub struct PolicySet {
1060 pub(crate) ast: ast::PolicySet,
1063 policies: HashMap<PolicyId, Policy>,
1065 templates: HashMap<PolicyId, Template>,
1067}
1068
1069impl PartialEq for PolicySet {
1070 fn eq(&self, other: &Self) -> bool {
1071 self.ast.eq(&other.ast)
1073 }
1074}
1075impl Eq for PolicySet {}
1076
1077impl FromStr for PolicySet {
1078 type Err = ParseErrors;
1079
1080 fn from_str(policies: &str) -> Result<Self, Self::Err> {
1087 let (ests, pset) = parser::parse_policyset_to_ests_and_pset(policies)?;
1088 let policies = pset.policies().map(|p|
1089 (
1090 PolicyId(p.id().clone()),
1091 Policy { est: ests.get(p.id()).expect("internal invariant violation: policy id exists in asts but not ests").clone(), ast: p.clone() }
1092 )
1093 ).collect();
1094 let templates = pset.templates().map(|t|
1095 (
1096 PolicyId(t.id().clone()),
1097 Template { est: ests.get(t.id()).expect("internal invariant violation: template id exists in asts but not ests").clone(), ast: t.clone() }
1098 )
1099 ).collect();
1100 Ok(Self {
1101 ast: pset,
1102 policies,
1103 templates,
1104 })
1105 }
1106}
1107
1108impl PolicySet {
1109 pub fn new() -> Self {
1111 Self {
1112 ast: ast::PolicySet::new(),
1113 policies: HashMap::new(),
1114 templates: HashMap::new(),
1115 }
1116 }
1117
1118 pub fn from_policies(
1120 policies: impl IntoIterator<Item = Policy>,
1121 ) -> Result<Self, PolicySetError> {
1122 let mut set = Self::new();
1123 for policy in policies {
1124 set.add(policy)?;
1125 }
1126 Ok(set)
1127 }
1128
1129 pub fn add(&mut self, policy: Policy) -> Result<(), PolicySetError> {
1133 if policy.is_static() {
1134 let id = PolicyId(policy.ast.id().clone());
1135 self.ast.add(policy.ast.clone())?;
1136 self.policies.insert(id, policy);
1137 Ok(())
1138 } else {
1139 Err(PolicySetError::ExpectedStatic)
1140 }
1141 }
1142
1143 pub fn add_template(&mut self, template: Template) -> Result<(), PolicySetError> {
1145 let id = PolicyId(template.ast.id().clone());
1146 self.ast.add_template(template.ast.clone())?;
1147 self.templates.insert(id, template);
1148 Ok(())
1149 }
1150
1151 pub fn policies(&self) -> impl Iterator<Item = &Policy> {
1155 self.policies.values()
1156 }
1157
1158 pub fn templates(&self) -> impl Iterator<Item = &Template> {
1160 self.templates.values()
1161 }
1162
1163 pub fn template(&self, id: &PolicyId) -> Option<&Template> {
1165 self.templates.get(id)
1166 }
1167
1168 pub fn policy(&self, id: &PolicyId) -> Option<&Policy> {
1170 self.policies.get(id)
1171 }
1172
1173 pub fn annotation<'a>(&'a self, id: &PolicyId, key: impl AsRef<str>) -> Option<&'a str> {
1175 self.ast
1176 .get(&id.0)?
1177 .annotation(&key.as_ref().parse().ok()?)
1178 .map(smol_str::SmolStr::as_str)
1179 }
1180
1181 pub fn template_annotation(&self, id: &PolicyId, key: impl AsRef<str>) -> Option<String> {
1183 self.ast
1184 .get_template(&id.0)?
1185 .annotation(&key.as_ref().parse().ok()?)
1186 .map(smol_str::SmolStr::to_string)
1187 }
1188
1189 pub fn is_empty(&self) -> bool {
1191 debug_assert_eq!(
1192 self.ast.is_empty(),
1193 self.policies.is_empty() && self.templates.is_empty()
1194 );
1195 self.ast.is_empty()
1196 }
1197
1198 #[allow(clippy::needless_pass_by_value)]
1204 pub fn link(
1205 &mut self,
1206 template_id: PolicyId,
1207 new_id: PolicyId,
1208 vals: HashMap<SlotId, EntityUid>,
1209 ) -> Result<(), PolicySetError> {
1210 let unwrapped: HashMap<ast::SlotId, ast::EntityUID> = vals
1211 .into_iter()
1212 .map(|(key, value)| (key.into(), value.0))
1213 .collect();
1214 let est_vals = unwrapped.iter().map(|(k, v)| (*k, v.into())).collect();
1215 self.ast
1216 .link(template_id.0.clone(), new_id.0.clone(), unwrapped)
1217 .map_err(PolicySetError::LinkingError)?;
1218 let linked_ast = self
1219 .ast
1220 .get(&new_id.0)
1221 .expect("instantiate() didn't fail above, so this shouldn't fail")
1222 .clone();
1223 let lined_est = self
1225 .templates
1226 .get(&template_id)
1227 .expect("instantiate() didn't fail above, so this shouldn't fail")
1228 .clone()
1229 .est
1230 .link(&est_vals)
1231 .expect("instantiate() didn't fail above, so this shouldn't fail");
1232 self.policies.insert(
1233 new_id,
1234 Policy {
1235 ast: linked_ast,
1236 est: lined_est,
1237 },
1238 );
1239 Ok(())
1240 }
1241
1242 fn from_ast(ast: ast::PolicySet) -> Self {
1248 let policies = ast
1249 .policies()
1250 .map(|p| (PolicyId(p.id().clone()), Policy::from_ast(p.clone())))
1251 .collect();
1252 let templates = ast
1253 .templates()
1254 .map(|t| (PolicyId(t.id().clone()), Template::from_ast(t.clone())))
1255 .collect();
1256 Self {
1257 ast,
1258 policies,
1259 templates,
1260 }
1261 }
1262}
1263
1264impl std::fmt::Display for PolicySet {
1265 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1266 write!(f, "{}", self.ast)
1267 }
1268}
1269
1270#[derive(Debug, Clone)]
1272pub struct Template {
1273 ast: ast::Template,
1276 est: est::Policy,
1285}
1286
1287impl PartialEq for Template {
1288 fn eq(&self, other: &Self) -> bool {
1289 self.ast.eq(&other.ast)
1291 }
1292}
1293impl Eq for Template {}
1294
1295impl Template {
1296 pub fn parse(id: Option<String>, src: impl AsRef<str>) -> Result<Self, ParseErrors> {
1300 let (est, ast) = parser::parse_policy_template_to_est_and_ast(id, src.as_ref())?;
1301 Ok(Self { ast, est })
1302 }
1303
1304 pub fn id(&self) -> &PolicyId {
1306 PolicyId::ref_cast(self.ast.id())
1307 }
1308
1309 #[must_use]
1311 pub fn new_id(&self, id: PolicyId) -> Self {
1312 Self {
1313 ast: self.ast.new_id(id.0),
1314 est: self.est.clone(),
1315 }
1316 }
1317
1318 pub fn effect(&self) -> Effect {
1320 self.ast.effect()
1321 }
1322
1323 pub fn annotation(&self, key: impl AsRef<str>) -> Option<&str> {
1325 self.ast
1326 .annotation(&key.as_ref().parse().ok()?)
1327 .map(smol_str::SmolStr::as_str)
1328 }
1329
1330 pub fn annotations(&self) -> impl Iterator<Item = (&str, &str)> {
1332 self.ast
1333 .annotations()
1334 .map(|(k, v)| (k.as_ref(), v.as_str()))
1335 }
1336
1337 pub fn slots(&self) -> impl Iterator<Item = &SlotId> {
1339 self.ast.slots().map(SlotId::ref_cast)
1340 }
1341
1342 pub fn principal_constraint(&self) -> TemplatePrincipalConstraint {
1344 match self.ast.principal_constraint().as_inner() {
1345 ast::PrincipalOrResourceConstraint::Any => TemplatePrincipalConstraint::Any,
1346 ast::PrincipalOrResourceConstraint::In(eref) => {
1347 TemplatePrincipalConstraint::In(match eref {
1348 ast::EntityReference::EUID(e) => Some(EntityUid(e.as_ref().clone())),
1349 ast::EntityReference::Slot => None,
1350 })
1351 }
1352 ast::PrincipalOrResourceConstraint::Eq(eref) => {
1353 TemplatePrincipalConstraint::Eq(match eref {
1354 ast::EntityReference::EUID(e) => Some(EntityUid(e.as_ref().clone())),
1355 ast::EntityReference::Slot => None,
1356 })
1357 }
1358 }
1359 }
1360
1361 pub fn action_constraint(&self) -> ActionConstraint {
1363 match self.ast.action_constraint() {
1365 ast::ActionConstraint::Any => ActionConstraint::Any,
1366 ast::ActionConstraint::In(ids) => ActionConstraint::In(
1367 ids.iter()
1368 .map(|id| EntityUid(id.as_ref().clone()))
1369 .collect(),
1370 ),
1371 ast::ActionConstraint::Eq(id) => ActionConstraint::Eq(EntityUid(id.as_ref().clone())),
1372 }
1373 }
1374
1375 pub fn resource_constraint(&self) -> TemplateResourceConstraint {
1377 match self.ast.resource_constraint().as_inner() {
1378 ast::PrincipalOrResourceConstraint::Any => TemplateResourceConstraint::Any,
1379 ast::PrincipalOrResourceConstraint::In(eref) => {
1380 TemplateResourceConstraint::In(match eref {
1381 ast::EntityReference::EUID(e) => Some(EntityUid(e.as_ref().clone())),
1382 ast::EntityReference::Slot => None,
1383 })
1384 }
1385 ast::PrincipalOrResourceConstraint::Eq(eref) => {
1386 TemplateResourceConstraint::Eq(match eref {
1387 ast::EntityReference::EUID(e) => Some(EntityUid(e.as_ref().clone())),
1388 ast::EntityReference::Slot => None,
1389 })
1390 }
1391 }
1392 }
1393
1394 #[allow(dead_code)] fn from_json(
1400 id: Option<PolicyId>,
1401 json: serde_json::Value,
1402 ) -> Result<Self, cedar_policy_core::est::EstToAstError> {
1403 let est: est::Policy =
1404 serde_json::from_value(json).map_err(JsonDeserializationError::Serde)?;
1405 Ok(Self {
1406 ast: est.clone().try_into_ast_template(id.map(|id| id.0))?,
1407 est,
1408 })
1409 }
1410
1411 #[allow(dead_code)] fn to_json(&self) -> Result<serde_json::Value, impl std::error::Error> {
1414 serde_json::to_value(&self.est)
1415 }
1416
1417 fn from_ast(ast: ast::Template) -> Self {
1423 let est = ast.clone().into();
1424 Self { ast, est }
1425 }
1426}
1427
1428impl FromStr for Template {
1429 type Err = ParseErrors;
1430
1431 fn from_str(src: &str) -> Result<Self, Self::Err> {
1432 Self::parse(None, src)
1433 }
1434}
1435
1436#[derive(Debug, Clone, PartialEq, Eq)]
1438pub enum PrincipalConstraint {
1439 Any,
1441 In(EntityUid),
1443 Eq(EntityUid),
1445}
1446
1447#[derive(Debug, Clone, PartialEq, Eq)]
1449pub enum TemplatePrincipalConstraint {
1450 Any,
1452 In(Option<EntityUid>),
1455 Eq(Option<EntityUid>),
1458}
1459
1460impl TemplatePrincipalConstraint {
1461 pub fn has_slot(&self) -> bool {
1463 match self {
1464 Self::Any => false,
1465 Self::In(o) | Self::Eq(o) => o.is_none(),
1466 }
1467 }
1468}
1469
1470#[derive(Debug, Clone, PartialEq, Eq)]
1472pub enum ActionConstraint {
1473 Any,
1475 In(Vec<EntityUid>),
1477 Eq(EntityUid),
1479}
1480
1481#[derive(Debug, Clone, PartialEq, Eq)]
1483pub enum ResourceConstraint {
1484 Any,
1486 In(EntityUid),
1488 Eq(EntityUid),
1490}
1491
1492#[derive(Debug, Clone, PartialEq, Eq)]
1494pub enum TemplateResourceConstraint {
1495 Any,
1497 In(Option<EntityUid>),
1500 Eq(Option<EntityUid>),
1503}
1504
1505impl TemplateResourceConstraint {
1506 pub fn has_slot(&self) -> bool {
1508 match self {
1509 Self::Any => false,
1510 Self::In(o) | Self::Eq(o) => o.is_none(),
1511 }
1512 }
1513}
1514
1515#[repr(transparent)]
1517#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize, RefCast)]
1518pub struct PolicyId(ast::PolicyID);
1519
1520impl FromStr for PolicyId {
1521 type Err = ParseErrors;
1522
1523 fn from_str(id: &str) -> Result<Self, Self::Err> {
1525 Ok(Self(ast::PolicyID::from_string(id)))
1526 }
1527}
1528
1529impl std::fmt::Display for PolicyId {
1530 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1531 write!(f, "{}", self.0)
1532 }
1533}
1534
1535#[derive(Debug, Clone)]
1537pub struct Policy {
1538 ast: ast::Policy,
1541 est: est::Policy,
1547}
1548
1549impl PartialEq for Policy {
1550 fn eq(&self, other: &Self) -> bool {
1551 self.ast.eq(&other.ast)
1553 }
1554}
1555impl Eq for Policy {}
1556
1557impl Policy {
1558 pub fn template_id(&self) -> Option<&PolicyId> {
1561 if self.is_static() {
1562 None
1563 } else {
1564 Some(PolicyId::ref_cast(self.ast.template().id()))
1565 }
1566 }
1567
1568 pub fn effect(&self) -> Effect {
1570 self.ast.effect()
1571 }
1572
1573 pub fn annotation(&self, key: impl AsRef<str>) -> Option<&str> {
1575 self.ast
1576 .annotation(&key.as_ref().parse().ok()?)
1577 .map(smol_str::SmolStr::as_str)
1578 }
1579
1580 pub fn annotations(&self) -> impl Iterator<Item = (&str, &str)> {
1582 self.ast
1583 .annotations()
1584 .map(|(k, v)| (k.as_ref(), v.as_str()))
1585 }
1586
1587 pub fn id(&self) -> &PolicyId {
1589 PolicyId::ref_cast(self.ast.id())
1590 }
1591
1592 #[must_use]
1594 pub fn new_id(&self, id: PolicyId) -> Self {
1595 Self {
1596 ast: self.ast.new_id(id.0),
1597 est: self.est.clone(),
1598 }
1599 }
1600
1601 pub fn is_static(&self) -> bool {
1603 self.ast.is_static()
1604 }
1605
1606 pub fn principal_constraint(&self) -> PrincipalConstraint {
1608 let slot_id = ast::SlotId::principal();
1609 match self.ast.template().principal_constraint().as_inner() {
1610 ast::PrincipalOrResourceConstraint::Any => PrincipalConstraint::Any,
1611 ast::PrincipalOrResourceConstraint::In(eref) => {
1612 PrincipalConstraint::In(self.convert_entity_reference(eref, slot_id).clone())
1613 }
1614 ast::PrincipalOrResourceConstraint::Eq(eref) => {
1615 PrincipalConstraint::Eq(self.convert_entity_reference(eref, slot_id).clone())
1616 }
1617 }
1618 }
1619
1620 pub fn action_constraint(&self) -> ActionConstraint {
1622 match self.ast.template().action_constraint() {
1624 ast::ActionConstraint::Any => ActionConstraint::Any,
1625 ast::ActionConstraint::In(ids) => ActionConstraint::In(
1626 ids.iter()
1627 .map(|euid| EntityUid::ref_cast(euid.as_ref()))
1628 .cloned()
1629 .collect(),
1630 ),
1631 ast::ActionConstraint::Eq(id) => ActionConstraint::Eq(EntityUid::ref_cast(id).clone()),
1632 }
1633 }
1634
1635 pub fn resource_constraint(&self) -> ResourceConstraint {
1637 let slot_id = ast::SlotId::resource();
1638 match self.ast.template().resource_constraint().as_inner() {
1639 ast::PrincipalOrResourceConstraint::Any => ResourceConstraint::Any,
1640 ast::PrincipalOrResourceConstraint::In(eref) => {
1641 ResourceConstraint::In(self.convert_entity_reference(eref, slot_id).clone())
1642 }
1643 ast::PrincipalOrResourceConstraint::Eq(eref) => {
1644 ResourceConstraint::Eq(self.convert_entity_reference(eref, slot_id).clone())
1645 }
1646 }
1647 }
1648
1649 fn convert_entity_reference<'a>(
1650 &'a self,
1651 r: &'a ast::EntityReference,
1652 slot: ast::SlotId,
1653 ) -> &'a EntityUid {
1654 match r {
1655 ast::EntityReference::EUID(euid) => EntityUid::ref_cast(euid),
1656 ast::EntityReference::Slot => EntityUid::ref_cast(self.ast.env().get(&slot).unwrap()),
1658 }
1659 }
1660
1661 pub fn parse(id: Option<String>, policy_src: impl AsRef<str>) -> Result<Self, ParseErrors> {
1666 let (est, inline_ast) = parser::parse_policy_to_est_and_ast(id, policy_src.as_ref())?;
1667 let (_, ast) = ast::Template::link_static_policy(inline_ast);
1668 Ok(Self { ast, est })
1669 }
1670
1671 pub fn from_json(
1676 id: Option<PolicyId>,
1677 json: serde_json::Value,
1678 ) -> Result<Self, cedar_policy_core::est::EstToAstError> {
1679 let est: est::Policy =
1680 serde_json::from_value(json).map_err(JsonDeserializationError::Serde)?;
1681 Ok(Self {
1682 ast: est.clone().try_into_ast_policy(id.map(|id| id.0))?,
1683 est,
1684 })
1685 }
1686
1687 pub fn to_json(&self) -> Result<serde_json::Value, impl std::error::Error> {
1689 serde_json::to_value(&self.est)
1690 }
1691
1692 fn from_ast(ast: ast::Policy) -> Self {
1698 let est = ast.clone().into();
1699 Self { ast, est }
1700 }
1701}
1702
1703impl std::fmt::Display for Policy {
1704 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1705 self.ast.fmt(f)
1706 }
1707}
1708
1709impl FromStr for Policy {
1710 type Err = ParseErrors;
1711 fn from_str(policy: &str) -> Result<Self, Self::Err> {
1719 Self::parse(None, policy)
1720 }
1721}
1722
1723#[repr(transparent)]
1725#[derive(Debug, Clone, RefCast)]
1726pub struct Expression(ast::Expr);
1727
1728impl Expression {
1729 pub fn new_string(value: String) -> Self {
1731 Self(ast::Expr::val(value))
1732 }
1733
1734 pub fn new_bool(value: bool) -> Self {
1736 Self(ast::Expr::val(value))
1737 }
1738
1739 pub fn new_long(value: i64) -> Self {
1741 Self(ast::Expr::val(value))
1742 }
1743
1744 pub fn new_record(fields: impl IntoIterator<Item = (String, Self)>) -> Self {
1746 Self(ast::Expr::record(
1747 fields.into_iter().map(|(k, v)| (SmolStr::from(k), v.0)),
1748 ))
1749 }
1750
1751 pub fn new_set(values: impl IntoIterator<Item = Self>) -> Self {
1753 Self(ast::Expr::set(values.into_iter().map(|v| v.0)))
1754 }
1755}
1756
1757impl FromStr for Expression {
1758 type Err = ParseErrors;
1759
1760 fn from_str(expression: &str) -> Result<Self, Self::Err> {
1762 parser::parse_expr(expression)
1763 .map_err(ParseErrors)
1764 .map(Expression)
1765 }
1766}
1767
1768#[repr(transparent)]
1784#[derive(Debug, Clone, RefCast)]
1785pub struct RestrictedExpression(ast::RestrictedExpr);
1786
1787impl RestrictedExpression {
1788 pub fn new_string(value: String) -> Self {
1790 Self(ast::RestrictedExpr::val(value))
1791 }
1792
1793 pub fn new_bool(value: bool) -> Self {
1795 Self(ast::RestrictedExpr::val(value))
1796 }
1797
1798 pub fn new_long(value: i64) -> Self {
1800 Self(ast::RestrictedExpr::val(value))
1801 }
1802
1803 pub fn new_record(fields: impl IntoIterator<Item = (String, Self)>) -> Self {
1805 Self(ast::RestrictedExpr::record(
1806 fields.into_iter().map(|(k, v)| (SmolStr::from(k), v.0)),
1807 ))
1808 }
1809
1810 pub fn new_set(values: impl IntoIterator<Item = Self>) -> Self {
1812 Self(ast::RestrictedExpr::set(values.into_iter().map(|v| v.0)))
1813 }
1814}
1815
1816impl FromStr for RestrictedExpression {
1817 type Err = ParseErrors;
1818
1819 fn from_str(expression: &str) -> Result<Self, Self::Err> {
1821 parser::parse_restrictedexpr(expression)
1822 .map_err(ParseErrors)
1823 .map(RestrictedExpression)
1824 }
1825}
1826
1827#[repr(transparent)]
1829#[derive(Debug, RefCast)]
1830pub struct Request(pub(crate) ast::Request);
1831
1832impl Request {
1833 pub fn new(
1843 principal: Option<EntityUid>,
1844 action: Option<EntityUid>,
1845 resource: Option<EntityUid>,
1846 context: Context,
1847 ) -> Self {
1848 let p = match principal {
1849 Some(p) => p.0,
1850 None => ast::EntityUID::unspecified_from_eid(ast::Eid::new("principal")),
1851 };
1852 let a = match action {
1853 Some(a) => a.0,
1854 None => ast::EntityUID::unspecified_from_eid(ast::Eid::new("action")),
1855 };
1856 let r = match resource {
1857 Some(r) => r.0,
1858 None => ast::EntityUID::unspecified_from_eid(ast::Eid::new("resource")),
1859 };
1860 Self(ast::Request::new(p, a, r, context.0))
1861 }
1862
1863 pub fn principal(&self) -> Option<&EntityUid> {
1865 match self.0.principal() {
1866 ast::EntityUIDEntry::Concrete(euid) => Some(EntityUid::ref_cast(euid.as_ref())),
1867 ast::EntityUIDEntry::Unknown => None,
1868 }
1869 }
1870
1871 pub fn action(&self) -> Option<&EntityUid> {
1873 match self.0.action() {
1874 ast::EntityUIDEntry::Concrete(euid) => Some(EntityUid::ref_cast(euid.as_ref())),
1875 ast::EntityUIDEntry::Unknown => None,
1876 }
1877 }
1878
1879 pub fn resource(&self) -> Option<&EntityUid> {
1881 match self.0.resource() {
1882 ast::EntityUIDEntry::Concrete(euid) => Some(EntityUid::ref_cast(euid.as_ref())),
1883 ast::EntityUIDEntry::Unknown => None,
1884 }
1885 }
1886}
1887
1888#[repr(transparent)]
1890#[derive(Debug, Clone, RefCast)]
1891pub struct Context(ast::Context);
1892
1893impl Context {
1894 pub fn empty() -> Self {
1896 Self(ast::Context::empty())
1897 }
1898
1899 pub fn from_pairs(pairs: impl IntoIterator<Item = (String, RestrictedExpression)>) -> Self {
1903 Self(ast::Context::from_pairs(
1904 pairs.into_iter().map(|(k, v)| (SmolStr::from(k), v.0)),
1905 ))
1906 }
1907
1908 pub fn from_json_str(
1919 json: &str,
1920 schema: Option<(&Schema, &EntityUid)>,
1921 ) -> Result<Self, ContextJsonError> {
1922 let schema = schema
1923 .map(|(s, uid)| Self::get_context_schema(s, uid))
1924 .transpose()?;
1925 let context =
1926 entities::ContextJsonParser::new(schema.as_ref(), Extensions::all_available())
1927 .from_json_str(json)?;
1928 Ok(Self(context))
1929 }
1930
1931 pub fn from_json_value(
1942 json: serde_json::Value,
1943 schema: Option<(&Schema, &EntityUid)>,
1944 ) -> Result<Self, ContextJsonError> {
1945 let schema = schema
1946 .map(|(s, uid)| Self::get_context_schema(s, uid))
1947 .transpose()?;
1948 let context =
1949 entities::ContextJsonParser::new(schema.as_ref(), Extensions::all_available())
1950 .from_json_value(json)?;
1951 Ok(Self(context))
1952 }
1953
1954 pub fn from_json_file(
1965 json: impl std::io::Read,
1966 schema: Option<(&Schema, &EntityUid)>,
1967 ) -> Result<Self, ContextJsonError> {
1968 let schema = schema
1969 .map(|(s, uid)| Self::get_context_schema(s, uid))
1970 .transpose()?;
1971 let context =
1972 entities::ContextJsonParser::new(schema.as_ref(), Extensions::all_available())
1973 .from_json_file(json)?;
1974 Ok(Self(context))
1975 }
1976
1977 fn get_context_schema(
1979 schema: &Schema,
1980 action: &EntityUid,
1981 ) -> Result<impl ContextSchema, ContextJsonError> {
1982 schema
1983 .0
1984 .get_context_schema(&action.0)
1985 .ok_or_else(|| ContextJsonError::ActionDoesNotExist {
1986 action: action.clone(),
1987 })
1988 }
1989}
1990
1991#[derive(Debug, Error)]
1993pub enum ContextJsonError {
1994 #[error(transparent)]
1996 JsonDeserializationError(#[from] JsonDeserializationError),
1997 #[error("Action {action} doesn't exist in the supplied schema")]
1999 ActionDoesNotExist {
2000 action: EntityUid,
2002 },
2003}
2004
2005impl std::fmt::Display for Request {
2006 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2007 write!(f, "{}", self.0)
2008 }
2009}
2010
2011#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
2013pub enum EvalResult {
2014 Bool(bool),
2016 Long(i64),
2018 String(String),
2020 EntityUid(EntityUid),
2022 Set(Set),
2024 Record(Record),
2026 ExtensionValue(String),
2028 }
2030
2031#[derive(Debug, Eq, PartialEq, PartialOrd, Ord)]
2033pub struct Set(BTreeSet<EvalResult>);
2034
2035impl Set {
2036 pub fn iter(&self) -> impl Iterator<Item = &EvalResult> {
2038 self.0.iter()
2039 }
2040
2041 pub fn contains(&self, elem: &EvalResult) -> bool {
2043 self.0.contains(elem)
2044 }
2045
2046 pub fn len(&self) -> usize {
2048 self.0.len()
2049 }
2050
2051 pub fn is_empty(&self) -> bool {
2053 self.0.is_empty()
2054 }
2055}
2056
2057#[derive(Debug, Eq, PartialEq, PartialOrd, Ord)]
2059pub struct Record(BTreeMap<String, EvalResult>);
2060
2061impl Record {
2062 pub fn iter(&self) -> impl Iterator<Item = (&String, &EvalResult)> {
2064 self.0.iter()
2065 }
2066
2067 pub fn contains_attribute(&self, key: impl AsRef<str>) -> bool {
2069 self.0.contains_key(key.as_ref())
2070 }
2071
2072 pub fn get(&self, key: impl AsRef<str>) -> Option<&EvalResult> {
2074 self.0.get(key.as_ref())
2075 }
2076
2077 pub fn len(&self) -> usize {
2079 self.0.len()
2080 }
2081
2082 pub fn is_empty(&self) -> bool {
2084 self.0.is_empty()
2085 }
2086}
2087
2088#[doc(hidden)]
2089impl From<ast::Value> for EvalResult {
2090 fn from(v: ast::Value) -> Self {
2091 match v {
2092 ast::Value::Lit(ast::Literal::Bool(b)) => Self::Bool(b),
2093 ast::Value::Lit(ast::Literal::Long(i)) => Self::Long(i),
2094 ast::Value::Lit(ast::Literal::String(s)) => Self::String(s.to_string()),
2095 ast::Value::Lit(ast::Literal::EntityUID(e)) => {
2096 Self::EntityUid(EntityUid(ast::EntityUID::clone(&e)))
2097 }
2098 ast::Value::Set(s) => Self::Set(Set(s
2099 .authoritative
2100 .iter()
2101 .map(|v| v.clone().into())
2102 .collect())),
2103 ast::Value::Record(r) => Self::Record(Record(
2104 r.iter()
2105 .map(|(k, v)| (k.to_string(), v.clone().into()))
2106 .collect(),
2107 )),
2108 ast::Value::ExtensionValue(v) => Self::ExtensionValue(v.to_string()),
2109 }
2110 }
2111}
2112impl std::fmt::Display for EvalResult {
2113 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2114 match self {
2115 Self::Bool(b) => write!(f, "{b}"),
2116 Self::Long(l) => write!(f, "{l}"),
2117 Self::String(s) => write!(f, "\"{}\"", s.escape_debug()),
2118 Self::EntityUid(uid) => write!(f, "{uid}"),
2119 Self::Set(s) => {
2120 write!(f, "[")?;
2121 for (i, ev) in s.iter().enumerate() {
2122 write!(f, "{ev}")?;
2123 if (i + 1) < s.len() {
2124 write!(f, ", ")?;
2125 }
2126 }
2127 write!(f, "]")?;
2128 Ok(())
2129 }
2130 Self::Record(r) => {
2131 write!(f, "{{")?;
2132 for (i, (k, v)) in r.iter().enumerate() {
2133 write!(f, "\"{}\": {v}", k.escape_debug())?;
2134 if (i + 1) < r.len() {
2135 write!(f, ", ")?;
2136 }
2137 }
2138 write!(f, "}}")?;
2139 Ok(())
2140 }
2141 Self::ExtensionValue(s) => write!(f, "{s}"),
2142 }
2143 }
2144}
2145
2146pub fn eval_expression(
2150 request: &Request,
2151 entities: &Entities,
2152 expr: &Expression,
2153) -> Result<EvalResult, EvaluationError> {
2154 let all_ext = Extensions::all_available();
2155 let eval = Evaluator::new(&request.0, &entities.0, &all_ext)
2156 .map_err(|e| EvaluationError::StringMessage(e.to_string()))?;
2157 Ok(EvalResult::from(
2158 eval.interpret(&expr.0, &ast::SlotEnv::new())
2160 .map_err(|e| EvaluationError::StringMessage(e.to_string()))?,
2161 ))
2162}
2163
2164#[cfg(test)]
2165mod test {
2166 use std::collections::HashSet;
2167
2168 use crate::{PolicyId, PolicySet, ResidualResponse};
2169
2170 #[test]
2171 fn test_pe_response_constructor() {
2172 let p: PolicySet = "permit(principal, action, resource);".parse().unwrap();
2173 let reason: HashSet<PolicyId> = std::iter::once("id1".parse().unwrap()).collect();
2174 let errors: HashSet<String> = std::iter::once("error".to_string()).collect();
2175 let a = ResidualResponse::new(p.clone(), reason.clone(), errors.clone());
2176 assert_eq!(a.diagnostics().errors, errors);
2177 assert_eq!(a.diagnostics().reason, reason);
2178 assert_eq!(a.residuals(), &p);
2179 }
2180}
2181
2182#[cfg(test)]
2183mod entity_uid_tests {
2184 use super::*;
2185
2186 #[test]
2188 fn entity_uid_from_parts() {
2189 let entity_id = EntityId::from_str("bobby").expect("failed at constructing EntityId");
2190 let entity_type_name = EntityTypeName::from_str("Chess::Master")
2191 .expect("failed at constructing EntityTypeName");
2192 let euid = EntityUid::from_type_name_and_id(entity_type_name, entity_id);
2193 assert_eq!(euid.id().as_ref(), "bobby");
2194 assert_eq!(euid.type_name().to_string(), "Chess::Master");
2195 }
2196
2197 #[test]
2199 fn entity_uid_with_escape() {
2200 let entity_id = EntityId::from_str(r#"bobby\'s sister:\nVeronica"#)
2202 .expect("failed at constructing EntityId");
2203 let entity_type_name = EntityTypeName::from_str("Hockey::Master")
2204 .expect("failed at constructing EntityTypeName");
2205 let euid = EntityUid::from_type_name_and_id(entity_type_name, entity_id);
2206 assert_eq!(euid.id().as_ref(), r#"bobby\'s sister:\nVeronica"#);
2209 assert_eq!(euid.type_name().to_string(), "Hockey::Master");
2210 }
2211
2212 #[test]
2214 fn entity_uid_with_backslashes() {
2215 let entity_id =
2217 EntityId::from_str(r#"\ \a \b \' \" \\"#).expect("failed at constructing EntityId");
2218 let entity_type_name =
2219 EntityTypeName::from_str("Test::User").expect("failed at constructing EntityTypeName");
2220 let euid = EntityUid::from_type_name_and_id(entity_type_name, entity_id);
2221 assert_eq!(euid.id().as_ref(), r#"\ \a \b \' \" \\"#);
2223 assert_eq!(euid.type_name().to_string(), "Test::User");
2224 }
2225
2226 #[test]
2228 fn entity_uid_with_quotes() {
2229 let euid: EntityUid = EntityUid::from_type_name_and_id(
2230 EntityTypeName::from_str("Test::User").unwrap(),
2231 EntityId::from_str(r#"b'ob"by\'s sis\"ter"#).unwrap(),
2232 );
2233 assert_eq!(euid.id().as_ref(), r#"b'ob"by\'s sis\"ter"#);
2236 assert_eq!(euid.type_name().to_string(), r#"Test::User"#);
2237 }
2238
2239 #[test]
2240 fn malformed_entity_type_name_should_fail() {
2241 let result = EntityTypeName::from_str("I'm an invalid name");
2242
2243 assert!(matches!(result, Err(ParseErrors(_))));
2244 let error = result.err().unwrap();
2245 assert!(error.to_string().contains("Unrecognized token `'`"));
2246 }
2247
2248 #[test]
2250 fn parse_euid() {
2251 let parsed_eid: EntityUid = r#"Test::User::"bobby""#.parse().expect("Failed to parse");
2252 assert_eq!(parsed_eid.id().as_ref(), r#"bobby"#);
2253 assert_eq!(parsed_eid.type_name().to_string(), r#"Test::User"#);
2254 }
2255
2256 #[test]
2258 fn parse_euid_with_escape() {
2259 let parsed_eid: EntityUid = r#"Test::User::"b\'ob\"by""#.parse().expect("Failed to parse");
2261 assert_eq!(parsed_eid.id().as_ref(), r#"b'ob"by"#);
2264 assert_eq!(parsed_eid.type_name().to_string(), r#"Test::User"#);
2265 }
2266
2267 #[test]
2269 fn parse_euid_single_quotes() {
2270 let parsed_eid: EntityUid = r#"Test::User::"b'obby\'s sister""#
2272 .parse()
2273 .expect("Failed to parse");
2274 assert_eq!(parsed_eid.id().as_ref(), r#"b'obby's sister"#);
2277 assert_eq!(parsed_eid.type_name().to_string(), r#"Test::User"#);
2278 }
2279
2280 #[test]
2282 fn euid_roundtrip() {
2283 let parsed_euid: EntityUid = r#"Test::User::"b'ob""#.parse().expect("Failed to parse");
2284 assert_eq!(parsed_euid.id().as_ref(), r#"b'ob"#);
2285 let reparsed: EntityUid = format!("{parsed_euid}")
2286 .parse()
2287 .expect("failed to roundtrip");
2288 assert_eq!(reparsed.id().as_ref(), r#"b'ob"#);
2289 }
2290}
2291
2292#[cfg(test)]
2293mod head_constraints_tests {
2294 use super::*;
2295
2296 #[test]
2297 fn principal_constraint_inline() {
2298 let p = Policy::from_str("permit(principal,action,resource);").unwrap();
2299 assert_eq!(p.principal_constraint(), PrincipalConstraint::Any);
2300 let euid = EntityUid::from_strs("T", "a");
2301 assert_eq!(euid.id().as_ref(), "a");
2302 assert_eq!(
2303 euid.type_name(),
2304 &EntityTypeName::from_str("T").expect("Failed to parse EntityTypeName")
2305 );
2306 let p =
2307 Policy::from_str("permit(principal == T::\"a\",action,resource == T::\"b\");").unwrap();
2308 assert_eq!(
2309 p.principal_constraint(),
2310 PrincipalConstraint::Eq(euid.clone())
2311 );
2312 let p = Policy::from_str("permit(principal in T::\"a\",action,resource);").unwrap();
2313 assert_eq!(p.principal_constraint(), PrincipalConstraint::In(euid));
2314 }
2315
2316 #[test]
2317 fn action_constraint_inline() {
2318 let p = Policy::from_str("permit(principal,action,resource);").unwrap();
2319 assert_eq!(p.action_constraint(), ActionConstraint::Any);
2320 let euid = EntityUid::from_strs("NN::N::Action", "a");
2321 assert_eq!(
2322 euid.type_name(),
2323 &EntityTypeName::from_str("NN::N::Action").expect("Failed to parse EntityTypeName")
2324 );
2325 let p = Policy::from_str(
2326 "permit(principal == T::\"b\",action == NN::N::Action::\"a\",resource == T::\"c\");",
2327 )
2328 .unwrap();
2329 assert_eq!(p.action_constraint(), ActionConstraint::Eq(euid.clone()));
2330 let p = Policy::from_str("permit(principal,action in [NN::N::Action::\"a\"],resource);")
2331 .unwrap();
2332 assert_eq!(p.action_constraint(), ActionConstraint::In(vec![euid]));
2333 }
2334
2335 #[test]
2336 fn resource_constraint_inline() {
2337 let p = Policy::from_str("permit(principal,action,resource);").unwrap();
2338 assert_eq!(p.resource_constraint(), ResourceConstraint::Any);
2339 let euid = EntityUid::from_strs("NN::N::T", "a");
2340 assert_eq!(
2341 euid.type_name(),
2342 &EntityTypeName::from_str("NN::N::T").expect("Failed to parse EntityTypeName")
2343 );
2344 let p =
2345 Policy::from_str("permit(principal == T::\"b\",action,resource == NN::N::T::\"a\");")
2346 .unwrap();
2347 assert_eq!(
2348 p.resource_constraint(),
2349 ResourceConstraint::Eq(euid.clone())
2350 );
2351 let p = Policy::from_str("permit(principal,action,resource in NN::N::T::\"a\");").unwrap();
2352 assert_eq!(p.resource_constraint(), ResourceConstraint::In(euid));
2353 }
2354
2355 #[test]
2356 fn principal_constraint_link() {
2357 let p = link("permit(principal,action,resource);", HashMap::new());
2358 assert_eq!(p.principal_constraint(), PrincipalConstraint::Any);
2359 let euid = EntityUid::from_strs("T", "a");
2360 let p = link(
2361 "permit(principal == T::\"a\",action,resource);",
2362 HashMap::new(),
2363 );
2364 assert_eq!(
2365 p.principal_constraint(),
2366 PrincipalConstraint::Eq(euid.clone())
2367 );
2368 let p = link(
2369 "permit(principal in T::\"a\",action,resource);",
2370 HashMap::new(),
2371 );
2372 assert_eq!(
2373 p.principal_constraint(),
2374 PrincipalConstraint::In(euid.clone())
2375 );
2376 let map: HashMap<SlotId, EntityUid> =
2377 std::iter::once((SlotId::principal(), euid.clone())).collect();
2378 let p = link(
2379 "permit(principal in ?principal,action,resource);",
2380 map.clone(),
2381 );
2382 assert_eq!(
2383 p.principal_constraint(),
2384 PrincipalConstraint::In(euid.clone())
2385 );
2386 let p = link("permit(principal == ?principal,action,resource);", map);
2387 assert_eq!(p.principal_constraint(), PrincipalConstraint::Eq(euid));
2388 }
2389
2390 #[test]
2391 fn action_constraint_link() {
2392 let p = link("permit(principal,action,resource);", HashMap::new());
2393 assert_eq!(p.action_constraint(), ActionConstraint::Any);
2394 let euid = EntityUid::from_strs("Action", "a");
2395 let p = link(
2396 "permit(principal,action == Action::\"a\",resource);",
2397 HashMap::new(),
2398 );
2399 assert_eq!(p.action_constraint(), ActionConstraint::Eq(euid.clone()));
2400 let p = link(
2401 "permit(principal,action in [Action::\"a\",Action::\"b\"],resource);",
2402 HashMap::new(),
2403 );
2404 assert_eq!(
2405 p.action_constraint(),
2406 ActionConstraint::In(vec![euid, EntityUid::from_strs("Action", "b"),])
2407 );
2408 }
2409
2410 #[test]
2411 fn resource_constraint_link() {
2412 let p = link("permit(principal,action,resource);", HashMap::new());
2413 assert_eq!(p.resource_constraint(), ResourceConstraint::Any);
2414 let euid = EntityUid::from_strs("T", "a");
2415 let p = link(
2416 "permit(principal,action,resource == T::\"a\");",
2417 HashMap::new(),
2418 );
2419 assert_eq!(
2420 p.resource_constraint(),
2421 ResourceConstraint::Eq(euid.clone())
2422 );
2423 let p = link(
2424 "permit(principal,action,resource in T::\"a\");",
2425 HashMap::new(),
2426 );
2427 assert_eq!(
2428 p.resource_constraint(),
2429 ResourceConstraint::In(euid.clone())
2430 );
2431 let map: HashMap<SlotId, EntityUid> =
2432 std::iter::once((SlotId::resource(), euid.clone())).collect();
2433 let p = link(
2434 "permit(principal,action,resource in ?resource);",
2435 map.clone(),
2436 );
2437 assert_eq!(
2438 p.resource_constraint(),
2439 ResourceConstraint::In(euid.clone())
2440 );
2441 let p = link("permit(principal,action,resource == ?resource);", map);
2442 assert_eq!(p.resource_constraint(), ResourceConstraint::Eq(euid));
2443 }
2444
2445 fn link(src: &str, values: HashMap<SlotId, EntityUid>) -> Policy {
2446 let mut pset = PolicySet::new();
2447 let template = Template::parse(Some("Id".to_string()), src).unwrap();
2448
2449 pset.add_template(template).unwrap();
2450
2451 let link_id = PolicyId::from_str("link").unwrap();
2452 pset.link(PolicyId::from_str("Id").unwrap(), link_id.clone(), values)
2453 .unwrap();
2454 pset.policy(&link_id).unwrap().clone()
2455 }
2456}
2457
2458#[cfg(test)]
2460mod policy_set_tests {
2461 use super::*;
2462 use ast::LinkingError;
2463
2464 #[test]
2465 fn link_conflicts() {
2466 let mut pset = PolicySet::new();
2467 let p1 = Policy::parse(Some("id".into()), "permit(principal,action,resource);")
2468 .expect("Failed to parse");
2469 pset.add(p1).expect("Failed to add");
2470 let template = Template::parse(
2471 Some("t".into()),
2472 "permit(principal == ?principal, action, resource);",
2473 )
2474 .expect("Failed to parse");
2475 pset.add_template(template).expect("Add failed");
2476
2477 let env: HashMap<SlotId, EntityUid> =
2478 std::iter::once((SlotId::principal(), EntityUid::from_strs("Test", "test"))).collect();
2479
2480 let r = pset.link(
2481 PolicyId::from_str("t").unwrap(),
2482 PolicyId::from_str("id").unwrap(),
2483 env,
2484 );
2485
2486 match r {
2487 Ok(_) => panic!("Should have failed due to conflict"),
2488 Err(PolicySetError::LinkingError(LinkingError::PolicyIdConflict)) => (),
2489 Err(e) => panic!("Incorrect error: {e}"),
2490 };
2491 }
2492
2493 #[test]
2494 fn policyset_add() {
2495 let mut pset = PolicySet::new();
2496 let static_policy = Policy::parse(Some("id".into()), "permit(principal,action,resource);")
2497 .expect("Failed to parse");
2498 pset.add(static_policy).expect("Failed to add");
2499
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("Failed to add");
2506
2507 let env1: HashMap<SlotId, EntityUid> =
2508 std::iter::once((SlotId::principal(), EntityUid::from_strs("Test", "test1"))).collect();
2509 pset.link(
2510 PolicyId::from_str("t").unwrap(),
2511 PolicyId::from_str("link").unwrap(),
2512 env1,
2513 )
2514 .expect("Failed to link");
2515
2516 let env2: HashMap<SlotId, EntityUid> =
2517 std::iter::once((SlotId::principal(), EntityUid::from_strs("Test", "test2"))).collect();
2518
2519 let err = pset
2520 .link(
2521 PolicyId::from_str("t").unwrap(),
2522 PolicyId::from_str("link").unwrap(),
2523 env2.clone(),
2524 )
2525 .expect_err("Should have failed due to conflict with existing link id");
2526 match err {
2527 PolicySetError::LinkingError(_) => (),
2528 e => panic!("Wrong error: {e}"),
2529 }
2530
2531 pset.link(
2532 PolicyId::from_str("t").unwrap(),
2533 PolicyId::from_str("link2").unwrap(),
2534 env2,
2535 )
2536 .expect("Failed to link");
2537
2538 let template2 = Template::parse(
2539 Some("t".into()),
2540 "forbid(principal, action, resource == ?resource);",
2541 )
2542 .expect("Failed to parse");
2543 pset.add_template(template2)
2544 .expect_err("should have failed due to conflict on template id");
2545 let template2 = Template::parse(
2546 Some("t2".into()),
2547 "forbid(principal, action, resource == ?resource);",
2548 )
2549 .expect("Failed to parse");
2550 pset.add_template(template2)
2551 .expect("Failed to add template");
2552 let env3: HashMap<SlotId, EntityUid> =
2553 std::iter::once((SlotId::resource(), EntityUid::from_strs("Test", "test3"))).collect();
2554
2555 pset.link(
2556 PolicyId::from_str("t").unwrap(),
2557 PolicyId::from_str("unique3").unwrap(),
2558 env3.clone(),
2559 )
2560 .expect_err("should have failed due to conflict on template id");
2561
2562 pset.link(
2563 PolicyId::from_str("t2").unwrap(),
2564 PolicyId::from_str("unique3").unwrap(),
2565 env3,
2566 )
2567 .expect("should succeed with unique ids");
2568 }
2569
2570 #[test]
2571 fn pset_requests() {
2572 let template = Template::parse(
2573 Some("template".into()),
2574 "permit(principal == ?principal, action, resource);",
2575 )
2576 .expect("Template Parse Failure");
2577 let static_policy = Policy::parse(
2578 Some("static".into()),
2579 "permit(principal, action, resource);",
2580 )
2581 .expect("Static parse failure");
2582 let mut pset = PolicySet::new();
2583 pset.add_template(template).unwrap();
2584 pset.add(static_policy).unwrap();
2585 pset.link(
2586 PolicyId::from_str("template").unwrap(),
2587 PolicyId::from_str("linked").unwrap(),
2588 std::iter::once((SlotId::principal(), EntityUid::from_strs("Test", "test"))).collect(),
2589 )
2590 .expect("Link failure");
2591
2592 assert_eq!(pset.templates().count(), 1);
2593 assert_eq!(pset.policies().count(), 2);
2594 assert_eq!(pset.policies().filter(|p| p.is_static()).count(), 1);
2595
2596 assert_eq!(
2597 pset.template(&"template".parse().unwrap())
2598 .expect("lookup failed")
2599 .id(),
2600 &"template".parse().unwrap()
2601 );
2602 assert_eq!(
2603 pset.policy(&"static".parse().unwrap())
2604 .expect("lookup failed")
2605 .id(),
2606 &"static".parse().unwrap()
2607 );
2608 assert_eq!(
2609 pset.policy(&"linked".parse().unwrap())
2610 .expect("lookup failed")
2611 .id(),
2612 &"linked".parse().unwrap()
2613 );
2614 }
2615}
2616
2617#[cfg(test)]
2618mod schema_tests {
2619 use super::*;
2620 use cool_asserts::assert_matches;
2621 use serde_json::json;
2622
2623 #[test]
2625 fn valid_schema() {
2626 let _ = Schema::from_json_value(json!(
2627 { "": {
2628 "entityTypes": {
2629 "Photo": {
2630 "memberOfTypes": [ "Album" ],
2631 "shape": {
2632 "type": "Record",
2633 "attributes": {
2634 "foo": {
2635 "type": "Boolean",
2636 "required": false
2637 }
2638 }
2639 }
2640 },
2641 "Album": {
2642 "memberOfTypes": [ ],
2643 "shape": {
2644 "type": "Record",
2645 "attributes": {
2646 "foo": {
2647 "type": "Boolean",
2648 "required": false
2649 }
2650 }
2651 }
2652 }
2653 },
2654 "actions": {
2655 "view": {
2656 "appliesTo": {
2657 "principalTypes": ["Photo", "Album"],
2658 "resourceTypes": ["Photo"]
2659 }
2660 }
2661 }
2662 }}))
2663 .expect("schema should be valid");
2664 }
2665
2666 #[test]
2668 fn invalid_schema() {
2669 assert_matches!(
2670 Schema::from_json_value(json!(
2671 r#""{"": {
2674 "entityTypes": {
2675 "Photo": {
2676 "memberOfTypes": [ "Album" ],
2677 "shape": {
2678 "type": "Record",
2679 "attributes": {
2680 "foo": {
2681 "type": "Boolean",
2682 "required": false
2683 }
2684 }
2685 }
2686 },
2687 "Album": {
2688 "memberOfTypes": [ ],
2689 "shape": {
2690 "type": "Record",
2691 "attributes": {
2692 "foo": {
2693 "type": "Boolean",
2694 "required": false
2695 }
2696 }
2697 }
2698 },
2699 "Photo": {
2700 "memberOfTypes": [ "Album" ],
2701 "shape": {
2702 "type": "Record",
2703 "attributes": {
2704 "foo": {
2705 "type": "Boolean",
2706 "required": false
2707 }
2708 }
2709 }
2710 }
2711 },
2712 "actions": {
2713 "view": {
2714 "appliesTo": {
2715 "principalTypes": ["Photo", "Album"],
2716 "resourceTypes": ["Photo"]
2717 }
2718 }
2719 }
2720 }}"#
2721 )),
2722 Err(SchemaError::ParseJson(_))
2723 );
2724 }
2725}
2726
2727#[cfg(test)]
2728mod ancestors_tests {
2729 use super::*;
2730
2731 #[test]
2732 fn test_ancestors() {
2733 let a_euid: EntityUid = EntityUid::from_strs("test", "A");
2734 let b_euid: EntityUid = EntityUid::from_strs("test", "b");
2735 let c_euid: EntityUid = EntityUid::from_strs("test", "C");
2736 let a = Entity::new(a_euid.clone(), HashMap::new(), HashSet::new());
2737 let b = Entity::new(
2738 b_euid.clone(),
2739 HashMap::new(),
2740 std::iter::once(a_euid.clone()).collect(),
2741 );
2742 let c = Entity::new(
2743 c_euid.clone(),
2744 HashMap::new(),
2745 std::iter::once(b_euid.clone()).collect(),
2746 );
2747 let es = Entities::from_entities([a, b, c]).unwrap();
2748 let ans = es.ancestors(&c_euid).unwrap().collect::<HashSet<_>>();
2749 assert_eq!(ans.len(), 2);
2750 assert!(ans.contains(&b_euid));
2751 assert!(ans.contains(&a_euid));
2752 }
2753}
2754
2755#[cfg(test)]
2760mod schema_based_parsing_tests {
2761 use std::assert_eq;
2762
2763 use super::*;
2764 use cedar_policy_core::ast::EntityUID;
2765 use cool_asserts::assert_matches;
2766 use serde_json::json;
2767
2768 #[test]
2770 #[allow(clippy::too_many_lines)]
2771 #[allow(clippy::cognitive_complexity)]
2772 fn attr_types() {
2773 let schema = Schema::from_json_value(json!(
2774 {"": {
2775 "entityTypes": {
2776 "Employee": {
2777 "memberOfTypes": [],
2778 "shape": {
2779 "type": "Record",
2780 "attributes": {
2781 "isFullTime": { "type": "Boolean" },
2782 "numDirectReports": { "type": "Long" },
2783 "department": { "type": "String" },
2784 "manager": { "type": "Entity", "name": "Employee" },
2785 "hr_contacts": { "type": "Set", "element": {
2786 "type": "Entity", "name": "HR" } },
2787 "json_blob": { "type": "Record", "attributes": {
2788 "inner1": { "type": "Boolean" },
2789 "inner2": { "type": "String" },
2790 "inner3": { "type": "Record", "attributes": {
2791 "innerinner": { "type": "Entity", "name": "Employee" }
2792 }}
2793 }},
2794 "home_ip": { "type": "Extension", "name": "ipaddr" },
2795 "work_ip": { "type": "Extension", "name": "ipaddr" },
2796 "trust_score": { "type": "Extension", "name": "decimal" },
2797 "tricky": { "type": "Record", "attributes": {
2798 "type": { "type": "String" },
2799 "id": { "type": "String" }
2800 }}
2801 }
2802 }
2803 },
2804 "HR": {
2805 "memberOfTypes": []
2806 }
2807 },
2808 "actions": {
2809 "view": { }
2810 }
2811 }}
2812 ))
2813 .expect("should be a valid schema");
2814
2815 let entitiesjson = json!(
2816 [
2817 {
2818 "uid": { "type": "Employee", "id": "12UA45" },
2819 "attrs": {
2820 "isFullTime": true,
2821 "numDirectReports": 3,
2822 "department": "Sales",
2823 "manager": { "type": "Employee", "id": "34FB87" },
2824 "hr_contacts": [
2825 { "type": "HR", "id": "aaaaa" },
2826 { "type": "HR", "id": "bbbbb" }
2827 ],
2828 "json_blob": {
2829 "inner1": false,
2830 "inner2": "-*/",
2831 "inner3": { "innerinner": { "type": "Employee", "id": "09AE76" }},
2832 },
2833 "home_ip": "222.222.222.101",
2834 "work_ip": { "fn": "ip", "arg": "2.2.2.0/24" },
2835 "trust_score": "5.7",
2836 "tricky": { "type": "Employee", "id": "34FB87" }
2837 },
2838 "parents": []
2839 }
2840 ]
2841 );
2842 let parsed = Entities::from_json_value(entitiesjson.clone(), None)
2846 .expect("Should parse without error");
2847 assert_eq!(parsed.iter().count(), 1);
2848 let parsed = parsed
2849 .get(&EntityUid::from_strs("Employee", "12UA45"))
2850 .expect("that should be the employee id");
2851 assert_eq!(
2852 parsed.attr("home_ip"),
2853 Some(Ok(EvalResult::String("222.222.222.101".into())))
2854 );
2855 assert_eq!(
2856 parsed.attr("trust_score"),
2857 Some(Ok(EvalResult::String("5.7".into())))
2858 );
2859 assert!(matches!(
2860 parsed.attr("manager"),
2861 Some(Ok(EvalResult::Record(_)))
2862 ));
2863 assert!(matches!(
2864 parsed.attr("work_ip"),
2865 Some(Ok(EvalResult::Record(_)))
2866 ));
2867 {
2868 let Some(Ok(EvalResult::Set(set))) = parsed.attr("hr_contacts") else { panic!("expected hr_contacts attr to exist and be a Set") };
2869 let contact = set.iter().next().expect("should be at least one contact");
2870 assert!(matches!(contact, EvalResult::Record(_)));
2871 };
2872 {
2873 let Some(Ok(EvalResult::Record(rec))) = parsed.attr("json_blob") else { panic!("expected json_blob attr to exist and be a Record") };
2874 let inner3 = rec.get("inner3").expect("expected inner3 attr to exist");
2875 let EvalResult::Record(rec) = inner3 else { panic!("expected inner3 to be a Record") };
2876 let innerinner = rec
2877 .get("innerinner")
2878 .expect("expected innerinner attr to exist");
2879 assert!(matches!(innerinner, EvalResult::Record(_)));
2880 };
2881 let parsed = Entities::from_json_value(entitiesjson, Some(&schema))
2883 .expect("Should parse without error");
2884 assert_eq!(parsed.iter().count(), 1);
2885 let parsed = parsed
2886 .get(&EntityUid::from_strs("Employee", "12UA45"))
2887 .expect("that should be the employee id");
2888 assert_eq!(parsed.attr("isFullTime"), Some(Ok(EvalResult::Bool(true))));
2889 assert_eq!(
2890 parsed.attr("numDirectReports"),
2891 Some(Ok(EvalResult::Long(3)))
2892 );
2893 assert_eq!(
2894 parsed.attr("department"),
2895 Some(Ok(EvalResult::String("Sales".into())))
2896 );
2897 assert_eq!(
2898 parsed.attr("manager"),
2899 Some(Ok(EvalResult::EntityUid(EntityUid::from_strs(
2900 "Employee", "34FB87"
2901 ))))
2902 );
2903 {
2904 let Some(Ok(EvalResult::Set(set))) = parsed.attr("hr_contacts") else { panic!("expected hr_contacts attr to exist and be a Set") };
2905 let contact = set.iter().next().expect("should be at least one contact");
2906 assert!(matches!(contact, EvalResult::EntityUid(_)));
2907 };
2908 {
2909 let Some(Ok(EvalResult::Record(rec))) = parsed.attr("json_blob") else { panic!("expected json_blob attr to exist and be a Record") };
2910 let inner3 = rec.get("inner3").expect("expected inner3 attr to exist");
2911 let EvalResult::Record(rec) = inner3 else { panic!("expected inner3 to be a Record") };
2912 let innerinner = rec
2913 .get("innerinner")
2914 .expect("expected innerinner attr to exist");
2915 assert!(matches!(innerinner, EvalResult::EntityUid(_)));
2916 };
2917 assert_eq!(
2918 parsed.attr("home_ip"),
2919 Some(Ok(EvalResult::ExtensionValue("222.222.222.101/32".into())))
2920 );
2921 assert_eq!(
2922 parsed.attr("work_ip"),
2923 Some(Ok(EvalResult::ExtensionValue("2.2.2.0/24".into())))
2924 );
2925 assert_eq!(
2926 parsed.attr("trust_score"),
2927 Some(Ok(EvalResult::ExtensionValue("5.7000".into())))
2928 );
2929
2930 let entitiesjson = json!(
2932 [
2933 {
2934 "uid": { "type": "Employee", "id": "12UA45" },
2935 "attrs": {
2936 "isFullTime": true,
2937 "numDirectReports": "3",
2938 "department": "Sales",
2939 "manager": { "type": "Employee", "id": "34FB87" },
2940 "hr_contacts": [
2941 { "type": "HR", "id": "aaaaa" },
2942 { "type": "HR", "id": "bbbbb" }
2943 ],
2944 "json_blob": {
2945 "inner1": false,
2946 "inner2": "-*/",
2947 "inner3": { "innerinner": { "type": "Employee", "id": "09AE76" }},
2948 },
2949 "home_ip": "222.222.222.101",
2950 "work_ip": { "fn": "ip", "arg": "2.2.2.0/24" },
2951 "trust_score": "5.7",
2952 "tricky": { "type": "Employee", "id": "34FB87" }
2953 },
2954 "parents": []
2955 }
2956 ]
2957 );
2958 let err = Entities::from_json_value(entitiesjson, Some(&schema))
2959 .expect_err("should fail due to type mismatch on numDirectReports");
2960 assert!(
2961 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"#),
2962 "actual error message was {err}"
2963 );
2964
2965 let entitiesjson = json!(
2967 [
2968 {
2969 "uid": { "type": "Employee", "id": "12UA45" },
2970 "attrs": {
2971 "isFullTime": true,
2972 "numDirectReports": 3,
2973 "department": "Sales",
2974 "manager": "34FB87",
2975 "hr_contacts": [
2976 { "type": "HR", "id": "aaaaa" },
2977 { "type": "HR", "id": "bbbbb" }
2978 ],
2979 "json_blob": {
2980 "inner1": false,
2981 "inner2": "-*/",
2982 "inner3": { "innerinner": { "type": "Employee", "id": "09AE76" }},
2983 },
2984 "home_ip": "222.222.222.101",
2985 "work_ip": { "fn": "ip", "arg": "2.2.2.0/24" },
2986 "trust_score": "5.7",
2987 "tricky": { "type": "Employee", "id": "34FB87" }
2988 },
2989 "parents": []
2990 }
2991 ]
2992 );
2993 let err = Entities::from_json_value(entitiesjson, Some(&schema))
2994 .expect_err("should fail due to type mismatch on manager");
2995 assert!(
2996 err.to_string()
2997 .contains(r#"In attribute "manager" on Employee::"12UA45", expected a literal entity reference, but got "34FB87""#),
2998 "actual error message was {err}"
2999 );
3000
3001 let entitiesjson = json!(
3003 [
3004 {
3005 "uid": { "type": "Employee", "id": "12UA45" },
3006 "attrs": {
3007 "isFullTime": true,
3008 "numDirectReports": 3,
3009 "department": "Sales",
3010 "manager": { "type": "Employee", "id": "34FB87" },
3011 "hr_contacts": { "type": "HR", "id": "aaaaa" },
3012 "json_blob": {
3013 "inner1": false,
3014 "inner2": "-*/",
3015 "inner3": { "innerinner": { "type": "Employee", "id": "09AE76" }},
3016 },
3017 "home_ip": "222.222.222.101",
3018 "work_ip": { "fn": "ip", "arg": "2.2.2.0/24" },
3019 "trust_score": "5.7",
3020 "tricky": { "type": "Employee", "id": "34FB87" }
3021 },
3022 "parents": []
3023 }
3024 ]
3025 );
3026 let err = Entities::from_json_value(entitiesjson, Some(&schema))
3027 .expect_err("should fail due to type mismatch on hr_contacts");
3028 assert!(
3029 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: ("#),
3030 "actual error message was {err}"
3031 );
3032
3033 let entitiesjson = json!(
3035 [
3036 {
3037 "uid": { "type": "Employee", "id": "12UA45" },
3038 "attrs": {
3039 "isFullTime": true,
3040 "numDirectReports": 3,
3041 "department": "Sales",
3042 "manager": { "type": "HR", "id": "34FB87" },
3043 "hr_contacts": [
3044 { "type": "HR", "id": "aaaaa" },
3045 { "type": "HR", "id": "bbbbb" }
3046 ],
3047 "json_blob": {
3048 "inner1": false,
3049 "inner2": "-*/",
3050 "inner3": { "innerinner": { "type": "Employee", "id": "09AE76" }},
3051 },
3052 "home_ip": "222.222.222.101",
3053 "work_ip": { "fn": "ip", "arg": "2.2.2.0/24" },
3054 "trust_score": "5.7",
3055 "tricky": { "type": "Employee", "id": "34FB87" }
3056 },
3057 "parents": []
3058 }
3059 ]
3060 );
3061 let err = Entities::from_json_value(entitiesjson, Some(&schema))
3062 .expect_err("should fail due to type mismatch on manager");
3063 assert!(
3064 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)"#),
3065 "actual error message was {err}"
3066 );
3067
3068 let entitiesjson = json!(
3071 [
3072 {
3073 "uid": { "type": "Employee", "id": "12UA45" },
3074 "attrs": {
3075 "isFullTime": true,
3076 "numDirectReports": 3,
3077 "department": "Sales",
3078 "manager": { "type": "Employee", "id": "34FB87" },
3079 "hr_contacts": [
3080 { "type": "HR", "id": "aaaaa" },
3081 { "type": "HR", "id": "bbbbb" }
3082 ],
3083 "json_blob": {
3084 "inner1": false,
3085 "inner2": "-*/",
3086 "inner3": { "innerinner": { "type": "Employee", "id": "09AE76" }},
3087 },
3088 "home_ip": { "fn": "decimal", "arg": "3.33" },
3089 "work_ip": { "fn": "ip", "arg": "2.2.2.0/24" },
3090 "trust_score": "5.7",
3091 "tricky": { "type": "Employee", "id": "34FB87" }
3092 },
3093 "parents": []
3094 }
3095 ]
3096 );
3097 let err = Entities::from_json_value(entitiesjson, Some(&schema))
3098 .expect_err("should fail due to type mismatch on home_ip");
3099 assert!(
3100 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"#),
3101 "actual error message was {err}"
3102 );
3103
3104 let entitiesjson = json!(
3106 [
3107 {
3108 "uid": { "type": "Employee", "id": "12UA45" },
3109 "attrs": {
3110 "isFullTime": true,
3111 "numDirectReports": 3,
3112 "department": "Sales",
3113 "manager": { "type": "Employee", "id": "34FB87" },
3114 "hr_contacts": [
3115 { "type": "HR", "id": "aaaaa" },
3116 { "type": "HR", "id": "bbbbb" }
3117 ],
3118 "json_blob": {
3119 "inner1": false,
3120 "inner3": { "innerinner": { "type": "Employee", "id": "09AE76" }},
3121 },
3122 "home_ip": "222.222.222.101",
3123 "work_ip": { "fn": "ip", "arg": "2.2.2.0/24" },
3124 "trust_score": "5.7",
3125 "tricky": { "type": "Employee", "id": "34FB87" }
3126 },
3127 "parents": []
3128 }
3129 ]
3130 );
3131 let err = Entities::from_json_value(entitiesjson, Some(&schema))
3132 .expect_err("should fail due to missing attribute \"inner2\"");
3133 assert!(
3134 err.to_string().contains(r#"In attribute "json_blob" on Employee::"12UA45", expected the record to have an attribute "inner2", but it didn't"#),
3135 "actual error message was {err}"
3136 );
3137
3138 let entitiesjson = json!(
3140 [
3141 {
3142 "uid": { "type": "Employee", "id": "12UA45" },
3143 "attrs": {
3144 "isFullTime": true,
3145 "numDirectReports": 3,
3146 "department": "Sales",
3147 "manager": { "type": "Employee", "id": "34FB87" },
3148 "hr_contacts": [
3149 { "type": "HR", "id": "aaaaa" },
3150 { "type": "HR", "id": "bbbbb" }
3151 ],
3152 "json_blob": {
3153 "inner1": 33,
3154 "inner2": "-*/",
3155 "inner3": { "innerinner": { "type": "Employee", "id": "09AE76" }},
3156 },
3157 "home_ip": "222.222.222.101",
3158 "work_ip": { "fn": "ip", "arg": "2.2.2.0/24" },
3159 "trust_score": "5.7",
3160 "tricky": { "type": "Employee", "id": "34FB87" }
3161 },
3162 "parents": []
3163 }
3164 ]
3165 );
3166 let err = Entities::from_json_value(entitiesjson, Some(&schema))
3167 .expect_err("should fail due to type mismatch on attribute \"inner1\"");
3168 assert!(
3169 err.to_string().contains(r#"In attribute "json_blob" on Employee::"12UA45", type mismatch: attribute was expected to have type record with attributes: "#),
3170 "actual error message was {err}"
3171 );
3172
3173 let entitiesjson = json!(
3174 [
3175 {
3176 "uid": { "__entity": { "type": "Employee", "id": "12UA45" } },
3177 "attrs": {
3178 "isFullTime": true,
3179 "numDirectReports": 3,
3180 "department": "Sales",
3181 "manager": { "__entity": { "type": "Employee", "id": "34FB87" } },
3182 "hr_contacts": [
3183 { "type": "HR", "id": "aaaaa" },
3184 { "type": "HR", "id": "bbbbb" }
3185 ],
3186 "json_blob": {
3187 "inner1": false,
3188 "inner2": "-*/",
3189 "inner3": { "innerinner": { "type": "Employee", "id": "09AE76" }},
3190 },
3191 "home_ip": { "__extn": { "fn": "ip", "arg": "222.222.222.101" } },
3192 "work_ip": { "__extn": { "fn": "ip", "arg": "2.2.2.0/24" } },
3193 "trust_score": { "__extn": { "fn": "decimal", "arg": "5.7" } },
3194 "tricky": { "type": "Employee", "id": "34FB87" }
3195 },
3196 "parents": []
3197 }
3198 ]
3199 );
3200 let _ = Entities::from_json_value(entitiesjson, Some(&schema))
3201 .expect("this version with explicit __entity and __extn escapes should also pass");
3202 }
3203
3204 #[test]
3206 fn namespaces() {
3207 let schema = Schema::from_str(
3208 r#"
3209 {"XYZCorp": {
3210 "entityTypes": {
3211 "Employee": {
3212 "memberOfTypes": [],
3213 "shape": {
3214 "type": "Record",
3215 "attributes": {
3216 "isFullTime": { "type": "Boolean" },
3217 "department": { "type": "String" },
3218 "manager": {
3219 "type": "Entity",
3220 "name": "XYZCorp::Employee"
3221 }
3222 }
3223 }
3224 }
3225 },
3226 "actions": {
3227 "view": {}
3228 }
3229 }}
3230 "#,
3231 )
3232 .expect("should be a valid schema");
3233
3234 let entitiesjson = json!(
3235 [
3236 {
3237 "uid": { "type": "XYZCorp::Employee", "id": "12UA45" },
3238 "attrs": {
3239 "isFullTime": true,
3240 "department": "Sales",
3241 "manager": { "type": "XYZCorp::Employee", "id": "34FB87" }
3242 },
3243 "parents": []
3244 }
3245 ]
3246 );
3247 let parsed = Entities::from_json_value(entitiesjson, Some(&schema))
3248 .expect("Should parse without error");
3249 assert_eq!(parsed.iter().count(), 1);
3250 let parsed = parsed
3251 .get(&EntityUid::from_strs("XYZCorp::Employee", "12UA45"))
3252 .expect("that should be the employee type and id");
3253 assert_eq!(parsed.attr("isFullTime"), Some(Ok(EvalResult::Bool(true))));
3254 assert_eq!(
3255 parsed.attr("department"),
3256 Some(Ok(EvalResult::String("Sales".into())))
3257 );
3258 assert_eq!(
3259 parsed.attr("manager"),
3260 Some(Ok(EvalResult::EntityUid(EntityUid::from_strs(
3261 "XYZCorp::Employee",
3262 "34FB87"
3263 ))))
3264 );
3265
3266 let entitiesjson = json!(
3267 [
3268 {
3269 "uid": { "type": "XYZCorp::Employee", "id": "12UA45" },
3270 "attrs": {
3271 "isFullTime": true,
3272 "department": "Sales",
3273 "manager": { "type": "Employee", "id": "34FB87" }
3274 },
3275 "parents": []
3276 }
3277 ]
3278 );
3279 let err = Entities::from_json_value(entitiesjson, Some(&schema))
3280 .expect_err("should fail due to manager being wrong entity type (missing namespace)");
3281 assert!(
3282 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)"#),
3283 "actual error message was {err}"
3284 );
3285 }
3286
3287 #[test]
3289 fn optional_attrs() {
3290 let schema = Schema::from_str(
3291 r#"
3292 {"": {
3293 "entityTypes": {
3294 "Employee": {
3295 "memberOfTypes": [],
3296 "shape": {
3297 "type": "Record",
3298 "attributes": {
3299 "isFullTime": { "type": "Boolean" },
3300 "department": { "type": "String", "required": false },
3301 "manager": { "type": "Entity", "name": "Employee" }
3302 }
3303 }
3304 }
3305 },
3306 "actions": {
3307 "view": {}
3308 }
3309 }}
3310 "#,
3311 )
3312 .expect("should be a valid schema");
3313
3314 let entitiesjson = json!(
3316 [
3317 {
3318 "uid": { "type": "Employee", "id": "12UA45" },
3319 "attrs": {
3320 "isFullTime": true,
3321 "department": "Sales",
3322 "manager": { "type": "Employee", "id": "34FB87" }
3323 },
3324 "parents": []
3325 }
3326 ]
3327 );
3328 let parsed = Entities::from_json_value(entitiesjson, Some(&schema))
3329 .expect("Should parse without error");
3330 assert_eq!(parsed.iter().count(), 1);
3331
3332 let entitiesjson = json!(
3334 [
3335 {
3336 "uid": { "type": "Employee", "id": "12UA45" },
3337 "attrs": {
3338 "isFullTime": true,
3339 "manager": { "type": "Employee", "id": "34FB87" }
3340 },
3341 "parents": []
3342 }
3343 ]
3344 );
3345 let parsed = Entities::from_json_value(entitiesjson, Some(&schema))
3346 .expect("Should parse without error");
3347 assert_eq!(parsed.iter().count(), 1);
3348 }
3349
3350 #[test]
3352 #[should_panic(
3353 expected = "UnsupportedSchemaFeature(\"Records and entities with additional attributes are not yet implemented.\")"
3354 )]
3355 fn open_entities() {
3356 let schema = Schema::from_str(
3357 r#"
3358 {"": {
3359 "entityTypes": {
3360 "Employee": {
3361 "memberOfTypes": [],
3362 "shape": {
3363 "type": "Record",
3364 "attributes": {
3365 "isFullTime": { "type": "Boolean" },
3366 "department": { "type": "String", "required": false },
3367 "manager": { "type": "Entity", "name": "Employee" }
3368 },
3369 "additionalAttributes": true
3370 }
3371 }
3372 },
3373 "actions": {
3374 "view": {}
3375 }
3376 }}
3377 "#,
3378 )
3379 .expect("should be a valid schema");
3380
3381 let entitiesjson = json!(
3383 [
3384 {
3385 "uid": { "type": "Employee", "id": "12UA45" },
3386 "attrs": {
3387 "isFullTime": true,
3388 "department": "Sales",
3389 "manager": { "type": "Employee", "id": "34FB87" }
3390 },
3391 "parents": []
3392 }
3393 ]
3394 );
3395 let parsed = Entities::from_json_value(entitiesjson, Some(&schema))
3396 .expect("Should parse without error");
3397 assert_eq!(parsed.iter().count(), 1);
3398
3399 let entitiesjson = json!(
3401 [
3402 {
3403 "uid": { "type": "Employee", "id": "12UA45" },
3404 "attrs": {
3405 "isFullTime": true,
3406 "foobar": 234,
3407 "manager": { "type": "Employee", "id": "34FB87" }
3408 },
3409 "parents": []
3410 }
3411 ]
3412 );
3413 let parsed = Entities::from_json_value(entitiesjson, Some(&schema))
3414 .expect("Should parse without error");
3415 assert_eq!(parsed.iter().count(), 1);
3416 }
3417
3418 #[test]
3419 fn schema_sanity_check() {
3420 let src = "{ , .. }";
3421 assert_matches!(Schema::from_str(src), Err(super::SchemaError::ParseJson(_)));
3422 }
3423
3424 #[test]
3425 fn template_constraint_sanity_checks() {
3426 assert!(!TemplatePrincipalConstraint::Any.has_slot());
3427 assert!(!TemplatePrincipalConstraint::In(Some(EntityUid::from_strs("a", "a"))).has_slot());
3428 assert!(!TemplatePrincipalConstraint::Eq(Some(EntityUid::from_strs("a", "a"))).has_slot());
3429 assert!(TemplatePrincipalConstraint::In(None).has_slot());
3430 assert!(TemplatePrincipalConstraint::Eq(None).has_slot());
3431 assert!(!TemplateResourceConstraint::Any.has_slot());
3432 assert!(!TemplateResourceConstraint::In(Some(EntityUid::from_strs("a", "a"))).has_slot());
3433 assert!(!TemplateResourceConstraint::Eq(Some(EntityUid::from_strs("a", "a"))).has_slot());
3434 assert!(TemplateResourceConstraint::In(None).has_slot());
3435 assert!(TemplateResourceConstraint::Eq(None).has_slot());
3436 }
3437
3438 #[test]
3439 fn template_principal_constraints() {
3440 let src = r#"
3441 permit(principal, action, resource);
3442 "#;
3443 let t = Template::parse(None, src).unwrap();
3444 assert_eq!(t.principal_constraint(), TemplatePrincipalConstraint::Any);
3445
3446 let src = r#"
3447 permit(principal == ?principal, action, resource);
3448 "#;
3449 let t = Template::parse(None, src).unwrap();
3450 assert_eq!(
3451 t.principal_constraint(),
3452 TemplatePrincipalConstraint::Eq(None)
3453 );
3454
3455 let src = r#"
3456 permit(principal == A::"a", action, resource);
3457 "#;
3458 let t = Template::parse(None, src).unwrap();
3459 assert_eq!(
3460 t.principal_constraint(),
3461 TemplatePrincipalConstraint::Eq(Some(EntityUid::from_strs("A", "a")))
3462 );
3463
3464 let src = r#"
3465 permit(principal in ?principal, action, resource);
3466 "#;
3467 let t = Template::parse(None, src).unwrap();
3468 assert_eq!(
3469 t.principal_constraint(),
3470 TemplatePrincipalConstraint::In(None)
3471 );
3472
3473 let src = r#"
3474 permit(principal in A::"a", action, resource);
3475 "#;
3476 let t = Template::parse(None, src).unwrap();
3477 assert_eq!(
3478 t.principal_constraint(),
3479 TemplatePrincipalConstraint::In(Some(EntityUid::from_strs("A", "a")))
3480 );
3481 }
3482
3483 #[test]
3484 fn template_action_constraints() {
3485 let src = r#"
3486 permit(principal, action, resource);
3487 "#;
3488 let t = Template::parse(None, src).unwrap();
3489 assert_eq!(t.action_constraint(), ActionConstraint::Any);
3490
3491 let src = r#"
3492 permit(principal, action == Action::"A", resource);
3493 "#;
3494 let t = Template::parse(None, src).unwrap();
3495 assert_eq!(
3496 t.action_constraint(),
3497 ActionConstraint::Eq(EntityUid::from_strs("Action", "A"))
3498 );
3499
3500 let src = r#"
3501 permit(principal, action in [Action::"A", Action::"B"], resource);
3502 "#;
3503 let t = Template::parse(None, src).unwrap();
3504 assert_eq!(
3505 t.action_constraint(),
3506 ActionConstraint::In(vec![
3507 EntityUid::from_strs("Action", "A"),
3508 EntityUid::from_strs("Action", "B")
3509 ])
3510 );
3511 }
3512
3513 #[test]
3514 fn template_resource_constraints() {
3515 let src = r#"
3516 permit(principal, action, resource);
3517 "#;
3518 let t = Template::parse(None, src).unwrap();
3519 assert_eq!(t.resource_constraint(), TemplateResourceConstraint::Any);
3520
3521 let src = r#"
3522 permit(principal, action, resource == ?resource);
3523 "#;
3524 let t = Template::parse(None, src).unwrap();
3525 assert_eq!(
3526 t.resource_constraint(),
3527 TemplateResourceConstraint::Eq(None)
3528 );
3529
3530 let src = r#"
3531 permit(principal, action, resource == A::"a");
3532 "#;
3533 let t = Template::parse(None, src).unwrap();
3534 assert_eq!(
3535 t.resource_constraint(),
3536 TemplateResourceConstraint::Eq(Some(EntityUid::from_strs("A", "a")))
3537 );
3538
3539 let src = r#"
3540 permit(principal, action, resource in ?resource);
3541 "#;
3542 let t = Template::parse(None, src).unwrap();
3543 assert_eq!(
3544 t.resource_constraint(),
3545 TemplateResourceConstraint::In(None)
3546 );
3547
3548 let src = r#"
3549 permit(principal, action, resource in A::"a");
3550 "#;
3551 let t = Template::parse(None, src).unwrap();
3552 assert_eq!(
3553 t.resource_constraint(),
3554 TemplateResourceConstraint::In(Some(EntityUid::from_strs("A", "a")))
3555 );
3556 }
3557
3558 #[test]
3559 fn schema_namespace() {
3560 let fragment: SchemaFragment = r#"
3561 {
3562 "Foo::Bar": {
3563 "entityTypes": {},
3564 "actions": {}
3565 }
3566 }
3567 "#
3568 .parse()
3569 .unwrap();
3570 let namespaces = fragment.namespaces().next().unwrap();
3571 assert_eq!(
3572 namespaces.map(|ns| ns.to_string()),
3573 Some("Foo::Bar".to_string())
3574 );
3575 let _schema: Schema = fragment.try_into().expect("Should convert to schema");
3576
3577 let fragment: SchemaFragment = r#"
3578 {
3579 "": {
3580 "entityTypes": {},
3581 "actions": {}
3582 }
3583 }
3584 "#
3585 .parse()
3586 .unwrap();
3587 let namespaces = fragment.namespaces().next().unwrap();
3588 assert_eq!(namespaces, None);
3589 let _schema: Schema = fragment.try_into().expect("Should convert to schema");
3590 }
3591
3592 #[test]
3593 fn load_multiple_namespaces() {
3594 let fragment = SchemaFragment::from_json_value(json!({
3595 "Foo::Bar": {
3596 "entityTypes": {
3597 "Baz": {
3598 "memberOfTypes": ["Bar::Foo::Baz"]
3599 }
3600 },
3601 "actions": {}
3602 },
3603 "Bar::Foo": {
3604 "entityTypes": {
3605 "Baz": {
3606 "memberOfTypes": ["Foo::Bar::Baz"]
3607 }
3608 },
3609 "actions": {}
3610 }
3611 }))
3612 .unwrap();
3613
3614 let schema = Schema::from_schema_fragments([fragment]).unwrap();
3615
3616 assert!(schema
3617 .0
3618 .get_entity_type(&"Foo::Bar::Baz".parse().unwrap())
3619 .is_some());
3620 assert!(schema
3621 .0
3622 .get_entity_type(&"Bar::Foo::Baz".parse().unwrap())
3623 .is_some());
3624 }
3625
3626 #[test]
3627 fn get_attributes_from_schema() {
3628 let fragment: SchemaFragment = SchemaFragment::from_json_value(json!({
3629 "": {
3630 "entityTypes": {},
3631 "actions": {
3632 "A": {},
3633 "B": {
3634 "memberOf": [{"id": "A"}]
3635 },
3636 "C": {
3637 "memberOf": [{"id": "A"}]
3638 },
3639 "D": {
3640 "memberOf": [{"id": "B"}, {"id": "C"}]
3641 },
3642 "E": {
3643 "memberOf": [{"id": "D"}]
3644 }
3645 }
3646 }}))
3647 .unwrap();
3648 let schema = Schema::from_schema_fragments([fragment]).unwrap();
3649 let action_entities = schema.action_entities().unwrap();
3650
3651 let a_euid = EntityUid::from_strs("Action", "A");
3652 let b_euid = EntityUid::from_strs("Action", "B");
3653 let c_euid = EntityUid::from_strs("Action", "C");
3654 let d_euid = EntityUid::from_strs("Action", "D");
3655 let e_euid = EntityUid::from_strs("Action", "E");
3656 assert_eq!(
3657 action_entities,
3658 Entities::from_entities([
3659 Entity::new(a_euid.clone(), HashMap::new(), HashSet::new()),
3660 Entity::new(
3661 b_euid.clone(),
3662 HashMap::new(),
3663 HashSet::from([a_euid.clone()])
3664 ),
3665 Entity::new(
3666 c_euid.clone(),
3667 HashMap::new(),
3668 HashSet::from([a_euid.clone()])
3669 ),
3670 Entity::new(
3671 d_euid.clone(),
3672 HashMap::new(),
3673 HashSet::from([a_euid.clone(), b_euid.clone(), c_euid.clone()])
3674 ),
3675 Entity::new(
3676 e_euid.clone(),
3677 HashMap::new(),
3678 HashSet::from([a_euid, b_euid, c_euid, d_euid])
3679 ),
3680 ])
3681 .unwrap()
3682 )
3683 }
3684}