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
594#[derive(Debug, Error)]
596pub enum SchemaError {
597 #[error("JSON Schema file could not be parsed: {0}")]
599 ParseJson(serde_json::Error),
600 #[error("Transitive closure error on action hierarchy: {0}")]
603 ActionTransitiveClosureError(String),
604 #[error("Transitive closure error on entity hierarchy: {0}")]
607 EntityTransitiveClosureError(String),
608 #[error("Unsupported feature used in schema: {0}")]
611 UnsupportedSchemaFeature(String),
612 #[error("Undeclared entity types: {0:?}")]
615 UndeclaredEntityTypes(HashSet<String>),
616 #[error("Undeclared actions: {0:?}")]
618 UndeclaredActions(HashSet<String>),
619 #[error("Undeclared common types: {0:?}")]
621 UndeclaredCommonType(HashSet<String>),
622 #[error("Duplicate entity type {0}")]
625 DuplicateEntityType(String),
626 #[error("Duplicate action {0}")]
629 DuplicateAction(String),
630 #[error("Duplicate common type {0}")]
633 DuplicateCommonType(String),
634 #[error("Cycle in action hierarchy")]
636 CycleInActionHierarchy,
637 #[error("Parse error in entity type: {0}")]
639 EntityTypeParse(ParseErrors),
640 #[error("Parse error in namespace identifier: {0}")]
642 NamespaceParse(ParseErrors),
643 #[error("Parse error in common type identifier: {0}")]
645 CommonTypeParseError(ParseErrors),
646 #[error("Parse error in extension type: {0}")]
648 ExtensionTypeParse(ParseErrors),
649 #[error("Entity type `Action` declared in `entityTypes` list.")]
654 ActionEntityTypeDeclared,
655 #[error("Actions declared with `attributes`: [{}]", .0.iter().map(String::as_str).join(", "))]
658 ActionEntityAttributes(Vec<String>),
659 #[error("Action context or entity type shape is not a record")]
662 ContextOrShapeNotRecord,
663 #[error("Action attribute is an empty set")]
665 ActionEntityAttributeEmptySet,
666 #[error(
668 "Action has an attribute of unsupported type (escaped expression, entity or extension)"
669 )]
670 ActionEntityAttributeUnsupportedType,
671}
672
673#[doc(hidden)]
674impl From<cedar_policy_validator::SchemaError> for SchemaError {
675 fn from(value: cedar_policy_validator::SchemaError) -> Self {
676 match value {
677 cedar_policy_validator::SchemaError::ParseFileFormat(e) => Self::ParseJson(e),
678 cedar_policy_validator::SchemaError::ActionTransitiveClosureError(e) => {
679 Self::ActionTransitiveClosureError(e.to_string())
680 }
681 cedar_policy_validator::SchemaError::EntityTransitiveClosureError(e) => {
682 Self::EntityTransitiveClosureError(e.to_string())
683 }
684 cedar_policy_validator::SchemaError::UnsupportedSchemaFeature(e) => {
685 Self::UnsupportedSchemaFeature(e.to_string())
686 }
687 cedar_policy_validator::SchemaError::UndeclaredEntityTypes(e) => {
688 Self::UndeclaredEntityTypes(e)
689 }
690 cedar_policy_validator::SchemaError::UndeclaredActions(e) => Self::UndeclaredActions(e),
691 cedar_policy_validator::SchemaError::UndeclaredCommonType(c) => {
692 Self::UndeclaredCommonType(c)
693 }
694 cedar_policy_validator::SchemaError::DuplicateEntityType(e) => {
695 Self::DuplicateEntityType(e)
696 }
697 cedar_policy_validator::SchemaError::DuplicateAction(e) => Self::DuplicateAction(e),
698 cedar_policy_validator::SchemaError::DuplicateCommonType(c) => {
699 Self::DuplicateCommonType(c)
700 }
701 cedar_policy_validator::SchemaError::CycleInActionHierarchy => {
702 Self::CycleInActionHierarchy
703 }
704 cedar_policy_validator::SchemaError::EntityTypeParseError(e) => {
705 Self::EntityTypeParse(ParseErrors(e))
706 }
707 cedar_policy_validator::SchemaError::NamespaceParseError(e) => {
708 Self::NamespaceParse(ParseErrors(e))
709 }
710 cedar_policy_validator::SchemaError::CommonTypeParseError(e) => {
711 Self::CommonTypeParseError(ParseErrors(e))
712 }
713 cedar_policy_validator::SchemaError::ExtensionTypeParseError(e) => {
714 Self::ExtensionTypeParse(ParseErrors(e))
715 }
716 cedar_policy_validator::SchemaError::ActionEntityTypeDeclared => {
717 Self::ActionEntityTypeDeclared
718 }
719 cedar_policy_validator::SchemaError::ActionEntityAttributes(e) => {
720 Self::ActionEntityAttributes(e)
721 }
722 cedar_policy_validator::SchemaError::ContextOrShapeNotRecord
723 | cedar_policy_validator::SchemaError::ActionEntityAttributeEmptySet
724 | cedar_policy_validator::SchemaError::ActionEntityAttributeUnsupportedType => {
725 Self::ContextOrShapeNotRecord
726 }
727 }
728 }
729}
730
731#[derive(Debug)]
736pub struct ValidationResult<'a> {
737 validation_errors: Vec<ValidationError<'a>>,
738}
739
740impl<'a> ValidationResult<'a> {
741 pub fn validation_passed(&self) -> bool {
743 self.validation_errors.is_empty()
744 }
745
746 pub fn validation_errors(&self) -> impl Iterator<Item = &ValidationError<'a>> {
748 self.validation_errors.iter()
749 }
750}
751
752impl<'a> From<cedar_policy_validator::ValidationResult<'a>> for ValidationResult<'a> {
753 fn from(r: cedar_policy_validator::ValidationResult<'a>) -> Self {
754 Self {
755 validation_errors: r
756 .into_validation_errors()
757 .map(ValidationError::from)
758 .collect(),
759 }
760 }
761}
762
763#[derive(Debug, Error)]
768pub struct ValidationError<'a> {
769 location: SourceLocation<'a>,
770 error_kind: ValidationErrorKind,
771}
772
773impl<'a> ValidationError<'a> {
774 pub fn error_kind(&self) -> &ValidationErrorKind {
776 &self.error_kind
777 }
778
779 pub fn location(&self) -> &SourceLocation<'a> {
781 &self.location
782 }
783}
784
785impl<'a> From<cedar_policy_validator::ValidationError<'a>> for ValidationError<'a> {
786 fn from(err: cedar_policy_validator::ValidationError<'a>) -> Self {
787 let (location, error_kind) = err.into_location_and_error_kind();
788 Self {
789 location: SourceLocation::from(location),
790 error_kind,
791 }
792 }
793}
794
795impl<'a> std::fmt::Display for ValidationError<'a> {
796 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
797 write!(f, "Validation error on policy {}", self.location.policy_id)?;
798 if let (Some(range_start), Some(range_end)) =
799 (self.location().range_start(), self.location().range_end())
800 {
801 write!(f, " at offset {range_start}-{range_end}")?;
802 }
803 write!(f, ": {}", self.error_kind())
804 }
805}
806
807#[derive(Debug, Clone, Eq, PartialEq)]
809pub struct SourceLocation<'a> {
810 policy_id: &'a PolicyId,
811 source_range: Option<SourceInfo>,
812}
813
814impl<'a> SourceLocation<'a> {
815 pub fn policy_id(&self) -> &'a PolicyId {
817 self.policy_id
818 }
819
820 pub fn range_start(&self) -> Option<usize> {
823 self.source_range.as_ref().map(SourceInfo::range_start)
824 }
825
826 pub fn range_end(&self) -> Option<usize> {
829 self.source_range.as_ref().map(SourceInfo::range_end)
830 }
831}
832
833impl<'a> From<cedar_policy_validator::SourceLocation<'a>> for SourceLocation<'a> {
834 fn from(loc: cedar_policy_validator::SourceLocation<'a>) -> SourceLocation<'a> {
835 let policy_id: &'a PolicyId = PolicyId::ref_cast(loc.policy_id());
836 let source_range = loc.into_source_info();
837 Self {
838 policy_id,
839 source_range,
840 }
841 }
842}
843
844pub fn confusable_string_checker<'a>(
846 templates: impl Iterator<Item = &'a Template>,
847) -> impl Iterator<Item = ValidationWarning<'a>> {
848 cedar_policy_validator::confusable_string_checks(templates.map(|t| &t.ast))
849 .map(std::convert::Into::into)
850}
851
852#[derive(Debug, Error)]
853#[error("Warning on policy {}: {}", .location.policy_id, .kind)]
854pub struct ValidationWarning<'a> {
856 location: SourceLocation<'a>,
857 kind: ValidationWarningKind,
858}
859
860impl<'a> ValidationWarning<'a> {
861 pub fn warning_kind(&self) -> &ValidationWarningKind {
863 &self.kind
864 }
865
866 pub fn location(&self) -> &SourceLocation<'a> {
868 &self.location
869 }
870}
871
872#[doc(hidden)]
873impl<'a> From<cedar_policy_validator::ValidationWarning<'a>> for ValidationWarning<'a> {
874 fn from(w: cedar_policy_validator::ValidationWarning<'a>) -> Self {
875 let (loc, kind) = w.to_kind_and_location();
876 ValidationWarning {
877 location: SourceLocation {
878 policy_id: PolicyId::ref_cast(loc),
879 source_range: None,
880 },
881 kind,
882 }
883 }
884}
885
886#[repr(transparent)]
888#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, RefCast)]
889pub struct EntityId(ast::Eid);
890
891impl FromStr for EntityId {
892 type Err = ParseErrors;
893 fn from_str(eid_str: &str) -> Result<Self, Self::Err> {
894 Ok(Self(ast::Eid::new(eid_str)))
895 }
896}
897
898impl AsRef<str> for EntityId {
899 fn as_ref(&self) -> &str {
900 self.0.as_ref()
901 }
902}
903
904impl std::fmt::Display for EntityId {
908 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
909 write!(f, "{}", self.0)
910 }
911}
912
913#[repr(transparent)]
915#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, RefCast)]
916pub struct EntityTypeName(ast::Name);
917
918impl FromStr for EntityTypeName {
919 type Err = ParseErrors;
920
921 fn from_str(namespace_type_str: &str) -> Result<Self, Self::Err> {
922 match ast::Name::from_str(namespace_type_str) {
923 Ok(name) => Ok(Self(name)),
924 Err(errs) => Err(ParseErrors(errs)),
925 }
926 }
927}
928
929impl std::fmt::Display for EntityTypeName {
930 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
931 write!(f, "{}", self.0)
932 }
933}
934
935#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
937pub struct EntityNamespace(ast::Name);
938
939impl FromStr for EntityNamespace {
940 type Err = ParseErrors;
941
942 fn from_str(namespace_str: &str) -> Result<Self, Self::Err> {
943 Ok(Self(
944 ast::Name::from_str(namespace_str).map_err(ParseErrors)?,
945 ))
946 }
947}
948
949impl std::fmt::Display for EntityNamespace {
950 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
951 write!(f, "{}", self.0)
952 }
953}
954
955#[repr(transparent)]
957#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, RefCast)]
958pub struct EntityUid(ast::EntityUID);
959
960impl EntityUid {
961 pub fn type_name(&self) -> &EntityTypeName {
963 match self.0.entity_type() {
964 ast::EntityType::Unspecified => panic!("Impossible to have an unspecified entity"),
965 ast::EntityType::Concrete(name) => EntityTypeName::ref_cast(name),
966 }
967 }
968
969 pub fn id(&self) -> &EntityId {
971 EntityId::ref_cast(self.0.eid())
972 }
973
974 pub fn from_type_name_and_id(name: EntityTypeName, id: EntityId) -> Self {
976 Self(ast::EntityUID::from_components(name.0, id.0))
977 }
978
979 pub fn from_json(json: serde_json::Value) -> Result<Self, impl std::error::Error> {
986 let parsed: entities::EntityUidJSON = serde_json::from_value(json)?;
987 Ok::<Self, entities::JsonDeserializationError>(Self(
988 parsed.into_euid(|| JsonDeserializationErrorContext::EntityUid)?,
989 ))
990 }
991
992 #[cfg(test)]
994 pub(crate) fn from_strs(typename: &str, id: &str) -> Self {
995 Self::from_type_name_and_id(
996 EntityTypeName::from_str(typename).unwrap(),
997 EntityId::from_str(id).unwrap(),
998 )
999 }
1000}
1001
1002impl FromStr for EntityUid {
1003 type Err = ParseErrors;
1004
1005 fn from_str(uid: &str) -> Result<Self, Self::Err> {
1012 parser::parse_euid(uid).map(EntityUid).map_err(ParseErrors)
1013 }
1014}
1015
1016impl std::fmt::Display for EntityUid {
1017 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1018 write!(f, "{}", self.0)
1019 }
1020}
1021
1022#[derive(Error, Debug)]
1024#[non_exhaustive]
1025pub enum PolicySetError {
1026 #[error("Collision in template or policy id")]
1028 AlreadyDefined,
1029 #[error("Unable to link template: {0}")]
1031 LinkingError(#[from] ast::LinkingError),
1032 #[error("Expected static policy, but a template-linked policy was provided")]
1034 ExpectedStatic,
1035}
1036
1037impl From<ast::PolicySetError> for PolicySetError {
1038 fn from(e: ast::PolicySetError) -> Self {
1039 match e {
1040 ast::PolicySetError::Occupied => Self::AlreadyDefined,
1041 }
1042 }
1043}
1044
1045impl From<ast::ContainsSlot> for PolicySetError {
1046 fn from(_: ast::ContainsSlot) -> Self {
1047 Self::ExpectedStatic
1048 }
1049}
1050
1051#[derive(Debug, Clone, Default)]
1053pub struct PolicySet {
1054 pub(crate) ast: ast::PolicySet,
1057 policies: HashMap<PolicyId, Policy>,
1059 templates: HashMap<PolicyId, Template>,
1061}
1062
1063impl PartialEq for PolicySet {
1064 fn eq(&self, other: &Self) -> bool {
1065 self.ast.eq(&other.ast)
1067 }
1068}
1069impl Eq for PolicySet {}
1070
1071impl FromStr for PolicySet {
1072 type Err = ParseErrors;
1073
1074 fn from_str(policies: &str) -> Result<Self, Self::Err> {
1081 let (ests, pset) = parser::parse_policyset_to_ests_and_pset(policies)?;
1082 let policies = pset.policies().map(|p|
1083 (
1084 PolicyId(p.id().clone()),
1085 Policy { est: ests.get(p.id()).expect("internal invariant violation: policy id exists in asts but not ests").clone(), ast: p.clone() }
1086 )
1087 ).collect();
1088 let templates = pset.templates().map(|t|
1089 (
1090 PolicyId(t.id().clone()),
1091 Template { est: ests.get(t.id()).expect("internal invariant violation: template id exists in asts but not ests").clone(), ast: t.clone() }
1092 )
1093 ).collect();
1094 Ok(Self {
1095 ast: pset,
1096 policies,
1097 templates,
1098 })
1099 }
1100}
1101
1102impl PolicySet {
1103 pub fn new() -> Self {
1105 Self {
1106 ast: ast::PolicySet::new(),
1107 policies: HashMap::new(),
1108 templates: HashMap::new(),
1109 }
1110 }
1111
1112 pub fn from_policies(
1114 policies: impl IntoIterator<Item = Policy>,
1115 ) -> Result<Self, PolicySetError> {
1116 let mut set = Self::new();
1117 for policy in policies {
1118 set.add(policy)?;
1119 }
1120 Ok(set)
1121 }
1122
1123 pub fn add(&mut self, policy: Policy) -> Result<(), PolicySetError> {
1127 if policy.is_static() {
1128 let id = PolicyId(policy.ast.id().clone());
1129 self.ast.add(policy.ast.clone())?;
1130 self.policies.insert(id, policy);
1131 Ok(())
1132 } else {
1133 Err(PolicySetError::ExpectedStatic)
1134 }
1135 }
1136
1137 pub fn add_template(&mut self, template: Template) -> Result<(), PolicySetError> {
1139 let id = PolicyId(template.ast.id().clone());
1140 self.ast.add_template(template.ast.clone())?;
1141 self.templates.insert(id, template);
1142 Ok(())
1143 }
1144
1145 pub fn policies(&self) -> impl Iterator<Item = &Policy> {
1149 self.policies.values()
1150 }
1151
1152 pub fn templates(&self) -> impl Iterator<Item = &Template> {
1154 self.templates.values()
1155 }
1156
1157 pub fn template(&self, id: &PolicyId) -> Option<&Template> {
1159 self.templates.get(id)
1160 }
1161
1162 pub fn policy(&self, id: &PolicyId) -> Option<&Policy> {
1164 self.policies.get(id)
1165 }
1166
1167 pub fn annotation<'a>(&'a self, id: &PolicyId, key: impl AsRef<str>) -> Option<&'a str> {
1169 self.ast
1170 .get(&id.0)?
1171 .annotation(&key.as_ref().parse().ok()?)
1172 .map(smol_str::SmolStr::as_str)
1173 }
1174
1175 pub fn template_annotation(&self, id: &PolicyId, key: impl AsRef<str>) -> Option<String> {
1177 self.ast
1178 .get_template(&id.0)?
1179 .annotation(&key.as_ref().parse().ok()?)
1180 .map(smol_str::SmolStr::to_string)
1181 }
1182
1183 pub fn is_empty(&self) -> bool {
1185 debug_assert_eq!(
1186 self.ast.is_empty(),
1187 self.policies.is_empty() && self.templates.is_empty()
1188 );
1189 self.ast.is_empty()
1190 }
1191
1192 #[allow(clippy::needless_pass_by_value)]
1198 pub fn link(
1199 &mut self,
1200 template_id: PolicyId,
1201 new_id: PolicyId,
1202 vals: HashMap<SlotId, EntityUid>,
1203 ) -> Result<(), PolicySetError> {
1204 let unwrapped: HashMap<ast::SlotId, ast::EntityUID> = vals
1205 .into_iter()
1206 .map(|(key, value)| (key.into(), value.0))
1207 .collect();
1208 let est_vals = unwrapped.iter().map(|(k, v)| (*k, v.into())).collect();
1209 self.ast
1210 .link(template_id.0.clone(), new_id.0.clone(), unwrapped)
1211 .map_err(PolicySetError::LinkingError)?;
1212 let linked_ast = self
1213 .ast
1214 .get(&new_id.0)
1215 .expect("instantiate() didn't fail above, so this shouldn't fail")
1216 .clone();
1217 let lined_est = self
1219 .templates
1220 .get(&template_id)
1221 .expect("instantiate() didn't fail above, so this shouldn't fail")
1222 .clone()
1223 .est
1224 .link(&est_vals)
1225 .expect("instantiate() didn't fail above, so this shouldn't fail");
1226 self.policies.insert(
1227 new_id,
1228 Policy {
1229 ast: linked_ast,
1230 est: lined_est,
1231 },
1232 );
1233 Ok(())
1234 }
1235
1236 fn from_ast(ast: ast::PolicySet) -> Self {
1242 let policies = ast
1243 .policies()
1244 .map(|p| (PolicyId(p.id().clone()), Policy::from_ast(p.clone())))
1245 .collect();
1246 let templates = ast
1247 .templates()
1248 .map(|t| (PolicyId(t.id().clone()), Template::from_ast(t.clone())))
1249 .collect();
1250 Self {
1251 ast,
1252 policies,
1253 templates,
1254 }
1255 }
1256}
1257
1258impl std::fmt::Display for PolicySet {
1259 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1260 write!(f, "{}", self.ast)
1261 }
1262}
1263
1264#[derive(Debug, Clone)]
1266pub struct Template {
1267 ast: ast::Template,
1270 est: est::Policy,
1279}
1280
1281impl PartialEq for Template {
1282 fn eq(&self, other: &Self) -> bool {
1283 self.ast.eq(&other.ast)
1285 }
1286}
1287impl Eq for Template {}
1288
1289impl Template {
1290 pub fn parse(id: Option<String>, src: impl AsRef<str>) -> Result<Self, ParseErrors> {
1294 let (est, ast) = parser::parse_policy_template_to_est_and_ast(id, src.as_ref())?;
1295 Ok(Self { ast, est })
1296 }
1297
1298 pub fn id(&self) -> &PolicyId {
1300 PolicyId::ref_cast(self.ast.id())
1301 }
1302
1303 #[must_use]
1305 pub fn new_id(&self, id: PolicyId) -> Self {
1306 Self {
1307 ast: self.ast.new_id(id.0),
1308 est: self.est.clone(),
1309 }
1310 }
1311
1312 pub fn effect(&self) -> Effect {
1314 self.ast.effect()
1315 }
1316
1317 pub fn annotation(&self, key: impl AsRef<str>) -> Option<&str> {
1319 self.ast
1320 .annotation(&key.as_ref().parse().ok()?)
1321 .map(smol_str::SmolStr::as_str)
1322 }
1323
1324 pub fn annotations(&self) -> impl Iterator<Item = (&str, &str)> {
1326 self.ast
1327 .annotations()
1328 .map(|(k, v)| (k.as_ref(), v.as_str()))
1329 }
1330
1331 pub fn slots(&self) -> impl Iterator<Item = &SlotId> {
1333 self.ast.slots().map(SlotId::ref_cast)
1334 }
1335
1336 pub fn principal_constraint(&self) -> TemplatePrincipalConstraint {
1338 match self.ast.principal_constraint().as_inner() {
1339 ast::PrincipalOrResourceConstraint::Any => TemplatePrincipalConstraint::Any,
1340 ast::PrincipalOrResourceConstraint::In(eref) => {
1341 TemplatePrincipalConstraint::In(match eref {
1342 ast::EntityReference::EUID(e) => Some(EntityUid(e.as_ref().clone())),
1343 ast::EntityReference::Slot => None,
1344 })
1345 }
1346 ast::PrincipalOrResourceConstraint::Eq(eref) => {
1347 TemplatePrincipalConstraint::Eq(match eref {
1348 ast::EntityReference::EUID(e) => Some(EntityUid(e.as_ref().clone())),
1349 ast::EntityReference::Slot => None,
1350 })
1351 }
1352 }
1353 }
1354
1355 pub fn action_constraint(&self) -> ActionConstraint {
1357 match self.ast.action_constraint() {
1359 ast::ActionConstraint::Any => ActionConstraint::Any,
1360 ast::ActionConstraint::In(ids) => ActionConstraint::In(
1361 ids.iter()
1362 .map(|id| EntityUid(id.as_ref().clone()))
1363 .collect(),
1364 ),
1365 ast::ActionConstraint::Eq(id) => ActionConstraint::Eq(EntityUid(id.as_ref().clone())),
1366 }
1367 }
1368
1369 pub fn resource_constraint(&self) -> TemplateResourceConstraint {
1371 match self.ast.resource_constraint().as_inner() {
1372 ast::PrincipalOrResourceConstraint::Any => TemplateResourceConstraint::Any,
1373 ast::PrincipalOrResourceConstraint::In(eref) => {
1374 TemplateResourceConstraint::In(match eref {
1375 ast::EntityReference::EUID(e) => Some(EntityUid(e.as_ref().clone())),
1376 ast::EntityReference::Slot => None,
1377 })
1378 }
1379 ast::PrincipalOrResourceConstraint::Eq(eref) => {
1380 TemplateResourceConstraint::Eq(match eref {
1381 ast::EntityReference::EUID(e) => Some(EntityUid(e.as_ref().clone())),
1382 ast::EntityReference::Slot => None,
1383 })
1384 }
1385 }
1386 }
1387
1388 #[allow(dead_code)] fn from_json(
1394 id: Option<PolicyId>,
1395 json: serde_json::Value,
1396 ) -> Result<Self, cedar_policy_core::est::EstToAstError> {
1397 let est: est::Policy =
1398 serde_json::from_value(json).map_err(JsonDeserializationError::Serde)?;
1399 Ok(Self {
1400 ast: est.clone().try_into_ast_template(id.map(|id| id.0))?,
1401 est,
1402 })
1403 }
1404
1405 #[allow(dead_code)] fn to_json(&self) -> Result<serde_json::Value, impl std::error::Error> {
1408 serde_json::to_value(&self.est)
1409 }
1410
1411 fn from_ast(ast: ast::Template) -> Self {
1417 let est = ast.clone().into();
1418 Self { ast, est }
1419 }
1420}
1421
1422impl FromStr for Template {
1423 type Err = ParseErrors;
1424
1425 fn from_str(src: &str) -> Result<Self, Self::Err> {
1426 Self::parse(None, src)
1427 }
1428}
1429
1430#[derive(Debug, Clone, PartialEq, Eq)]
1432pub enum PrincipalConstraint {
1433 Any,
1435 In(EntityUid),
1437 Eq(EntityUid),
1439}
1440
1441#[derive(Debug, Clone, PartialEq, Eq)]
1443pub enum TemplatePrincipalConstraint {
1444 Any,
1446 In(Option<EntityUid>),
1449 Eq(Option<EntityUid>),
1452}
1453
1454impl TemplatePrincipalConstraint {
1455 pub fn has_slot(&self) -> bool {
1457 match self {
1458 Self::Any => false,
1459 Self::In(o) | Self::Eq(o) => o.is_none(),
1460 }
1461 }
1462}
1463
1464#[derive(Debug, Clone, PartialEq, Eq)]
1466pub enum ActionConstraint {
1467 Any,
1469 In(Vec<EntityUid>),
1471 Eq(EntityUid),
1473}
1474
1475#[derive(Debug, Clone, PartialEq, Eq)]
1477pub enum ResourceConstraint {
1478 Any,
1480 In(EntityUid),
1482 Eq(EntityUid),
1484}
1485
1486#[derive(Debug, Clone, PartialEq, Eq)]
1488pub enum TemplateResourceConstraint {
1489 Any,
1491 In(Option<EntityUid>),
1494 Eq(Option<EntityUid>),
1497}
1498
1499impl TemplateResourceConstraint {
1500 pub fn has_slot(&self) -> bool {
1502 match self {
1503 Self::Any => false,
1504 Self::In(o) | Self::Eq(o) => o.is_none(),
1505 }
1506 }
1507}
1508
1509#[repr(transparent)]
1511#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize, RefCast)]
1512pub struct PolicyId(ast::PolicyID);
1513
1514impl FromStr for PolicyId {
1515 type Err = ParseErrors;
1516
1517 fn from_str(id: &str) -> Result<Self, Self::Err> {
1519 Ok(Self(ast::PolicyID::from_string(id)))
1520 }
1521}
1522
1523impl std::fmt::Display for PolicyId {
1524 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1525 write!(f, "{}", self.0)
1526 }
1527}
1528
1529#[derive(Debug, Clone)]
1531pub struct Policy {
1532 ast: ast::Policy,
1535 est: est::Policy,
1541}
1542
1543impl PartialEq for Policy {
1544 fn eq(&self, other: &Self) -> bool {
1545 self.ast.eq(&other.ast)
1547 }
1548}
1549impl Eq for Policy {}
1550
1551impl Policy {
1552 pub fn template_id(&self) -> Option<&PolicyId> {
1555 if self.is_static() {
1556 None
1557 } else {
1558 Some(PolicyId::ref_cast(self.ast.template().id()))
1559 }
1560 }
1561
1562 pub fn effect(&self) -> Effect {
1564 self.ast.effect()
1565 }
1566
1567 pub fn annotation(&self, key: impl AsRef<str>) -> Option<&str> {
1569 self.ast
1570 .annotation(&key.as_ref().parse().ok()?)
1571 .map(smol_str::SmolStr::as_str)
1572 }
1573
1574 pub fn annotations(&self) -> impl Iterator<Item = (&str, &str)> {
1576 self.ast
1577 .annotations()
1578 .map(|(k, v)| (k.as_ref(), v.as_str()))
1579 }
1580
1581 pub fn id(&self) -> &PolicyId {
1583 PolicyId::ref_cast(self.ast.id())
1584 }
1585
1586 #[must_use]
1588 pub fn new_id(&self, id: PolicyId) -> Self {
1589 Self {
1590 ast: self.ast.new_id(id.0),
1591 est: self.est.clone(),
1592 }
1593 }
1594
1595 pub fn is_static(&self) -> bool {
1597 self.ast.is_static()
1598 }
1599
1600 pub fn principal_constraint(&self) -> PrincipalConstraint {
1602 let slot_id = ast::SlotId::principal();
1603 match self.ast.template().principal_constraint().as_inner() {
1604 ast::PrincipalOrResourceConstraint::Any => PrincipalConstraint::Any,
1605 ast::PrincipalOrResourceConstraint::In(eref) => {
1606 PrincipalConstraint::In(self.convert_entity_reference(eref, slot_id).clone())
1607 }
1608 ast::PrincipalOrResourceConstraint::Eq(eref) => {
1609 PrincipalConstraint::Eq(self.convert_entity_reference(eref, slot_id).clone())
1610 }
1611 }
1612 }
1613
1614 pub fn action_constraint(&self) -> ActionConstraint {
1616 match self.ast.template().action_constraint() {
1618 ast::ActionConstraint::Any => ActionConstraint::Any,
1619 ast::ActionConstraint::In(ids) => ActionConstraint::In(
1620 ids.iter()
1621 .map(|euid| EntityUid::ref_cast(euid.as_ref()))
1622 .cloned()
1623 .collect(),
1624 ),
1625 ast::ActionConstraint::Eq(id) => ActionConstraint::Eq(EntityUid::ref_cast(id).clone()),
1626 }
1627 }
1628
1629 pub fn resource_constraint(&self) -> ResourceConstraint {
1631 let slot_id = ast::SlotId::resource();
1632 match self.ast.template().resource_constraint().as_inner() {
1633 ast::PrincipalOrResourceConstraint::Any => ResourceConstraint::Any,
1634 ast::PrincipalOrResourceConstraint::In(eref) => {
1635 ResourceConstraint::In(self.convert_entity_reference(eref, slot_id).clone())
1636 }
1637 ast::PrincipalOrResourceConstraint::Eq(eref) => {
1638 ResourceConstraint::Eq(self.convert_entity_reference(eref, slot_id).clone())
1639 }
1640 }
1641 }
1642
1643 fn convert_entity_reference<'a>(
1644 &'a self,
1645 r: &'a ast::EntityReference,
1646 slot: ast::SlotId,
1647 ) -> &'a EntityUid {
1648 match r {
1649 ast::EntityReference::EUID(euid) => EntityUid::ref_cast(euid),
1650 ast::EntityReference::Slot => EntityUid::ref_cast(self.ast.env().get(&slot).unwrap()),
1652 }
1653 }
1654
1655 pub fn parse(id: Option<String>, policy_src: impl AsRef<str>) -> Result<Self, ParseErrors> {
1660 let (est, inline_ast) = parser::parse_policy_to_est_and_ast(id, policy_src.as_ref())?;
1661 let (_, ast) = ast::Template::link_static_policy(inline_ast);
1662 Ok(Self { ast, est })
1663 }
1664
1665 pub fn from_json(
1670 id: Option<PolicyId>,
1671 json: serde_json::Value,
1672 ) -> Result<Self, cedar_policy_core::est::EstToAstError> {
1673 let est: est::Policy =
1674 serde_json::from_value(json).map_err(JsonDeserializationError::Serde)?;
1675 Ok(Self {
1676 ast: est.clone().try_into_ast_policy(id.map(|id| id.0))?,
1677 est,
1678 })
1679 }
1680
1681 pub fn to_json(&self) -> Result<serde_json::Value, impl std::error::Error> {
1683 serde_json::to_value(&self.est)
1684 }
1685
1686 fn from_ast(ast: ast::Policy) -> Self {
1692 let est = ast.clone().into();
1693 Self { ast, est }
1694 }
1695}
1696
1697impl std::fmt::Display for Policy {
1698 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1699 self.ast.fmt(f)
1700 }
1701}
1702
1703impl FromStr for Policy {
1704 type Err = ParseErrors;
1705 fn from_str(policy: &str) -> Result<Self, Self::Err> {
1713 Self::parse(None, policy)
1714 }
1715}
1716
1717#[repr(transparent)]
1719#[derive(Debug, Clone, RefCast)]
1720pub struct Expression(ast::Expr);
1721
1722impl Expression {
1723 pub fn new_string(value: String) -> Self {
1725 Self(ast::Expr::val(value))
1726 }
1727
1728 pub fn new_bool(value: bool) -> Self {
1730 Self(ast::Expr::val(value))
1731 }
1732
1733 pub fn new_long(value: i64) -> Self {
1735 Self(ast::Expr::val(value))
1736 }
1737
1738 pub fn new_record(fields: impl IntoIterator<Item = (String, Self)>) -> Self {
1740 Self(ast::Expr::record(
1741 fields.into_iter().map(|(k, v)| (SmolStr::from(k), v.0)),
1742 ))
1743 }
1744
1745 pub fn new_set(values: impl IntoIterator<Item = Self>) -> Self {
1747 Self(ast::Expr::set(values.into_iter().map(|v| v.0)))
1748 }
1749}
1750
1751impl FromStr for Expression {
1752 type Err = ParseErrors;
1753
1754 fn from_str(expression: &str) -> Result<Self, Self::Err> {
1756 parser::parse_expr(expression)
1757 .map_err(ParseErrors)
1758 .map(Expression)
1759 }
1760}
1761
1762#[repr(transparent)]
1778#[derive(Debug, Clone, RefCast)]
1779pub struct RestrictedExpression(ast::RestrictedExpr);
1780
1781impl RestrictedExpression {
1782 pub fn new_string(value: String) -> Self {
1784 Self(ast::RestrictedExpr::val(value))
1785 }
1786
1787 pub fn new_bool(value: bool) -> Self {
1789 Self(ast::RestrictedExpr::val(value))
1790 }
1791
1792 pub fn new_long(value: i64) -> Self {
1794 Self(ast::RestrictedExpr::val(value))
1795 }
1796
1797 pub fn new_record(fields: impl IntoIterator<Item = (String, Self)>) -> Self {
1799 Self(ast::RestrictedExpr::record(
1800 fields.into_iter().map(|(k, v)| (SmolStr::from(k), v.0)),
1801 ))
1802 }
1803
1804 pub fn new_set(values: impl IntoIterator<Item = Self>) -> Self {
1806 Self(ast::RestrictedExpr::set(values.into_iter().map(|v| v.0)))
1807 }
1808}
1809
1810impl FromStr for RestrictedExpression {
1811 type Err = ParseErrors;
1812
1813 fn from_str(expression: &str) -> Result<Self, Self::Err> {
1815 parser::parse_restrictedexpr(expression)
1816 .map_err(ParseErrors)
1817 .map(RestrictedExpression)
1818 }
1819}
1820
1821#[repr(transparent)]
1823#[derive(Debug, RefCast)]
1824pub struct Request(pub(crate) ast::Request);
1825
1826impl Request {
1827 pub fn new(
1837 principal: Option<EntityUid>,
1838 action: Option<EntityUid>,
1839 resource: Option<EntityUid>,
1840 context: Context,
1841 ) -> Self {
1842 let p = match principal {
1843 Some(p) => p.0,
1844 None => ast::EntityUID::unspecified_from_eid(ast::Eid::new("principal")),
1845 };
1846 let a = match action {
1847 Some(a) => a.0,
1848 None => ast::EntityUID::unspecified_from_eid(ast::Eid::new("action")),
1849 };
1850 let r = match resource {
1851 Some(r) => r.0,
1852 None => ast::EntityUID::unspecified_from_eid(ast::Eid::new("resource")),
1853 };
1854 Self(ast::Request::new(p, a, r, context.0))
1855 }
1856
1857 pub fn principal(&self) -> Option<&EntityUid> {
1859 match self.0.principal() {
1860 ast::EntityUIDEntry::Concrete(euid) => Some(EntityUid::ref_cast(euid.as_ref())),
1861 ast::EntityUIDEntry::Unknown => None,
1862 }
1863 }
1864
1865 pub fn action(&self) -> Option<&EntityUid> {
1867 match self.0.action() {
1868 ast::EntityUIDEntry::Concrete(euid) => Some(EntityUid::ref_cast(euid.as_ref())),
1869 ast::EntityUIDEntry::Unknown => None,
1870 }
1871 }
1872
1873 pub fn resource(&self) -> Option<&EntityUid> {
1875 match self.0.resource() {
1876 ast::EntityUIDEntry::Concrete(euid) => Some(EntityUid::ref_cast(euid.as_ref())),
1877 ast::EntityUIDEntry::Unknown => None,
1878 }
1879 }
1880}
1881
1882#[repr(transparent)]
1884#[derive(Debug, Clone, RefCast)]
1885pub struct Context(ast::Context);
1886
1887impl Context {
1888 pub fn empty() -> Self {
1890 Self(ast::Context::empty())
1891 }
1892
1893 pub fn from_pairs(pairs: impl IntoIterator<Item = (String, RestrictedExpression)>) -> Self {
1897 Self(ast::Context::from_pairs(
1898 pairs.into_iter().map(|(k, v)| (SmolStr::from(k), v.0)),
1899 ))
1900 }
1901
1902 pub fn from_json_str(
1913 json: &str,
1914 schema: Option<(&Schema, &EntityUid)>,
1915 ) -> Result<Self, ContextJsonError> {
1916 let schema = schema
1917 .map(|(s, uid)| Self::get_context_schema(s, uid))
1918 .transpose()?;
1919 let context =
1920 entities::ContextJsonParser::new(schema.as_ref(), Extensions::all_available())
1921 .from_json_str(json)?;
1922 Ok(Self(context))
1923 }
1924
1925 pub fn from_json_value(
1936 json: serde_json::Value,
1937 schema: Option<(&Schema, &EntityUid)>,
1938 ) -> Result<Self, ContextJsonError> {
1939 let schema = schema
1940 .map(|(s, uid)| Self::get_context_schema(s, uid))
1941 .transpose()?;
1942 let context =
1943 entities::ContextJsonParser::new(schema.as_ref(), Extensions::all_available())
1944 .from_json_value(json)?;
1945 Ok(Self(context))
1946 }
1947
1948 pub fn from_json_file(
1959 json: impl std::io::Read,
1960 schema: Option<(&Schema, &EntityUid)>,
1961 ) -> Result<Self, ContextJsonError> {
1962 let schema = schema
1963 .map(|(s, uid)| Self::get_context_schema(s, uid))
1964 .transpose()?;
1965 let context =
1966 entities::ContextJsonParser::new(schema.as_ref(), Extensions::all_available())
1967 .from_json_file(json)?;
1968 Ok(Self(context))
1969 }
1970
1971 fn get_context_schema(
1973 schema: &Schema,
1974 action: &EntityUid,
1975 ) -> Result<impl ContextSchema, ContextJsonError> {
1976 schema
1977 .0
1978 .get_context_schema(&action.0)
1979 .ok_or_else(|| ContextJsonError::ActionDoesNotExist {
1980 action: action.clone(),
1981 })
1982 }
1983}
1984
1985#[derive(Debug, Error)]
1987pub enum ContextJsonError {
1988 #[error(transparent)]
1990 JsonDeserializationError(#[from] JsonDeserializationError),
1991 #[error("Action {action} doesn't exist in the supplied schema")]
1993 ActionDoesNotExist {
1994 action: EntityUid,
1996 },
1997}
1998
1999impl std::fmt::Display for Request {
2000 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2001 write!(f, "{}", self.0)
2002 }
2003}
2004
2005#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
2007pub enum EvalResult {
2008 Bool(bool),
2010 Long(i64),
2012 String(String),
2014 EntityUid(EntityUid),
2016 Set(Set),
2018 Record(Record),
2020 ExtensionValue(String),
2022 }
2024
2025#[derive(Debug, Eq, PartialEq, PartialOrd, Ord)]
2027pub struct Set(BTreeSet<EvalResult>);
2028
2029impl Set {
2030 pub fn iter(&self) -> impl Iterator<Item = &EvalResult> {
2032 self.0.iter()
2033 }
2034
2035 pub fn contains(&self, elem: &EvalResult) -> bool {
2037 self.0.contains(elem)
2038 }
2039
2040 pub fn len(&self) -> usize {
2042 self.0.len()
2043 }
2044
2045 pub fn is_empty(&self) -> bool {
2047 self.0.is_empty()
2048 }
2049}
2050
2051#[derive(Debug, Eq, PartialEq, PartialOrd, Ord)]
2053pub struct Record(BTreeMap<String, EvalResult>);
2054
2055impl Record {
2056 pub fn iter(&self) -> impl Iterator<Item = (&String, &EvalResult)> {
2058 self.0.iter()
2059 }
2060
2061 pub fn contains_attribute(&self, key: impl AsRef<str>) -> bool {
2063 self.0.contains_key(key.as_ref())
2064 }
2065
2066 pub fn get(&self, key: impl AsRef<str>) -> Option<&EvalResult> {
2068 self.0.get(key.as_ref())
2069 }
2070
2071 pub fn len(&self) -> usize {
2073 self.0.len()
2074 }
2075
2076 pub fn is_empty(&self) -> bool {
2078 self.0.is_empty()
2079 }
2080}
2081
2082#[doc(hidden)]
2083impl From<ast::Value> for EvalResult {
2084 fn from(v: ast::Value) -> Self {
2085 match v {
2086 ast::Value::Lit(ast::Literal::Bool(b)) => Self::Bool(b),
2087 ast::Value::Lit(ast::Literal::Long(i)) => Self::Long(i),
2088 ast::Value::Lit(ast::Literal::String(s)) => Self::String(s.to_string()),
2089 ast::Value::Lit(ast::Literal::EntityUID(e)) => {
2090 Self::EntityUid(EntityUid(ast::EntityUID::clone(&e)))
2091 }
2092 ast::Value::Set(s) => Self::Set(Set(s
2093 .authoritative
2094 .iter()
2095 .map(|v| v.clone().into())
2096 .collect())),
2097 ast::Value::Record(r) => Self::Record(Record(
2098 r.iter()
2099 .map(|(k, v)| (k.to_string(), v.clone().into()))
2100 .collect(),
2101 )),
2102 ast::Value::ExtensionValue(v) => Self::ExtensionValue(v.to_string()),
2103 }
2104 }
2105}
2106impl std::fmt::Display for EvalResult {
2107 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2108 match self {
2109 Self::Bool(b) => write!(f, "{b}"),
2110 Self::Long(l) => write!(f, "{l}"),
2111 Self::String(s) => write!(f, "\"{}\"", s.escape_debug()),
2112 Self::EntityUid(uid) => write!(f, "{uid}"),
2113 Self::Set(s) => {
2114 write!(f, "[")?;
2115 for (i, ev) in s.iter().enumerate() {
2116 write!(f, "{ev}")?;
2117 if (i + 1) < s.len() {
2118 write!(f, ", ")?;
2119 }
2120 }
2121 write!(f, "]")?;
2122 Ok(())
2123 }
2124 Self::Record(r) => {
2125 write!(f, "{{")?;
2126 for (i, (k, v)) in r.iter().enumerate() {
2127 write!(f, "\"{}\": {v}", k.escape_debug())?;
2128 if (i + 1) < r.len() {
2129 write!(f, ", ")?;
2130 }
2131 }
2132 write!(f, "}}")?;
2133 Ok(())
2134 }
2135 Self::ExtensionValue(s) => write!(f, "{s}"),
2136 }
2137 }
2138}
2139
2140pub fn eval_expression(
2144 request: &Request,
2145 entities: &Entities,
2146 expr: &Expression,
2147) -> Result<EvalResult, EvaluationError> {
2148 let all_ext = Extensions::all_available();
2149 let eval = Evaluator::new(&request.0, &entities.0, &all_ext)
2150 .map_err(|e| EvaluationError::StringMessage(e.to_string()))?;
2151 Ok(EvalResult::from(
2152 eval.interpret(&expr.0, &ast::SlotEnv::new())
2154 .map_err(|e| EvaluationError::StringMessage(e.to_string()))?,
2155 ))
2156}
2157
2158#[cfg(test)]
2159mod test {
2160 use std::collections::HashSet;
2161
2162 use crate::{PolicyId, PolicySet, ResidualResponse};
2163
2164 #[test]
2165 fn test_pe_response_constructor() {
2166 let p: PolicySet = "permit(principal, action, resource);".parse().unwrap();
2167 let reason: HashSet<PolicyId> = std::iter::once("id1".parse().unwrap()).collect();
2168 let errors: HashSet<String> = std::iter::once("error".to_string()).collect();
2169 let a = ResidualResponse::new(p.clone(), reason.clone(), errors.clone());
2170 assert_eq!(a.diagnostics().errors, errors);
2171 assert_eq!(a.diagnostics().reason, reason);
2172 assert_eq!(a.residuals(), &p);
2173 }
2174}
2175
2176#[cfg(test)]
2177mod entity_uid_tests {
2178 use super::*;
2179
2180 #[test]
2182 fn entity_uid_from_parts() {
2183 let entity_id = EntityId::from_str("bobby").expect("failed at constructing EntityId");
2184 let entity_type_name = EntityTypeName::from_str("Chess::Master")
2185 .expect("failed at constructing EntityTypeName");
2186 let euid = EntityUid::from_type_name_and_id(entity_type_name, entity_id);
2187 assert_eq!(euid.id().as_ref(), "bobby");
2188 assert_eq!(euid.type_name().to_string(), "Chess::Master");
2189 }
2190
2191 #[test]
2193 fn entity_uid_with_escape() {
2194 let entity_id = EntityId::from_str(r#"bobby\'s sister:\nVeronica"#)
2196 .expect("failed at constructing EntityId");
2197 let entity_type_name = EntityTypeName::from_str("Hockey::Master")
2198 .expect("failed at constructing EntityTypeName");
2199 let euid = EntityUid::from_type_name_and_id(entity_type_name, entity_id);
2200 assert_eq!(euid.id().as_ref(), r#"bobby\'s sister:\nVeronica"#);
2203 assert_eq!(euid.type_name().to_string(), "Hockey::Master");
2204 }
2205
2206 #[test]
2208 fn entity_uid_with_backslashes() {
2209 let entity_id =
2211 EntityId::from_str(r#"\ \a \b \' \" \\"#).expect("failed at constructing EntityId");
2212 let entity_type_name =
2213 EntityTypeName::from_str("Test::User").expect("failed at constructing EntityTypeName");
2214 let euid = EntityUid::from_type_name_and_id(entity_type_name, entity_id);
2215 assert_eq!(euid.id().as_ref(), r#"\ \a \b \' \" \\"#);
2217 assert_eq!(euid.type_name().to_string(), "Test::User");
2218 }
2219
2220 #[test]
2222 fn entity_uid_with_quotes() {
2223 let euid: EntityUid = EntityUid::from_type_name_and_id(
2224 EntityTypeName::from_str("Test::User").unwrap(),
2225 EntityId::from_str(r#"b'ob"by\'s sis\"ter"#).unwrap(),
2226 );
2227 assert_eq!(euid.id().as_ref(), r#"b'ob"by\'s sis\"ter"#);
2230 assert_eq!(euid.type_name().to_string(), r#"Test::User"#);
2231 }
2232
2233 #[test]
2234 fn malformed_entity_type_name_should_fail() {
2235 let result = EntityTypeName::from_str("I'm an invalid name");
2236
2237 assert!(matches!(result, Err(ParseErrors(_))));
2238 let error = result.err().unwrap();
2239 assert!(error.to_string().contains("Unrecognized token `'`"));
2240 }
2241
2242 #[test]
2244 fn parse_euid() {
2245 let parsed_eid: EntityUid = r#"Test::User::"bobby""#.parse().expect("Failed to parse");
2246 assert_eq!(parsed_eid.id().as_ref(), r#"bobby"#);
2247 assert_eq!(parsed_eid.type_name().to_string(), r#"Test::User"#);
2248 }
2249
2250 #[test]
2252 fn parse_euid_with_escape() {
2253 let parsed_eid: EntityUid = r#"Test::User::"b\'ob\"by""#.parse().expect("Failed to parse");
2255 assert_eq!(parsed_eid.id().as_ref(), r#"b'ob"by"#);
2258 assert_eq!(parsed_eid.type_name().to_string(), r#"Test::User"#);
2259 }
2260
2261 #[test]
2263 fn parse_euid_single_quotes() {
2264 let parsed_eid: EntityUid = r#"Test::User::"b'obby\'s sister""#
2266 .parse()
2267 .expect("Failed to parse");
2268 assert_eq!(parsed_eid.id().as_ref(), r#"b'obby's sister"#);
2271 assert_eq!(parsed_eid.type_name().to_string(), r#"Test::User"#);
2272 }
2273
2274 #[test]
2276 fn euid_roundtrip() {
2277 let parsed_euid: EntityUid = r#"Test::User::"b'ob""#.parse().expect("Failed to parse");
2278 assert_eq!(parsed_euid.id().as_ref(), r#"b'ob"#);
2279 let reparsed: EntityUid = format!("{parsed_euid}")
2280 .parse()
2281 .expect("failed to roundtrip");
2282 assert_eq!(reparsed.id().as_ref(), r#"b'ob"#);
2283 }
2284}
2285
2286#[cfg(test)]
2287mod head_constraints_tests {
2288 use super::*;
2289
2290 #[test]
2291 fn principal_constraint_inline() {
2292 let p = Policy::from_str("permit(principal,action,resource);").unwrap();
2293 assert_eq!(p.principal_constraint(), PrincipalConstraint::Any);
2294 let euid = EntityUid::from_strs("T", "a");
2295 assert_eq!(euid.id().as_ref(), "a");
2296 assert_eq!(
2297 euid.type_name(),
2298 &EntityTypeName::from_str("T").expect("Failed to parse EntityTypeName")
2299 );
2300 let p =
2301 Policy::from_str("permit(principal == T::\"a\",action,resource == T::\"b\");").unwrap();
2302 assert_eq!(
2303 p.principal_constraint(),
2304 PrincipalConstraint::Eq(euid.clone())
2305 );
2306 let p = Policy::from_str("permit(principal in T::\"a\",action,resource);").unwrap();
2307 assert_eq!(p.principal_constraint(), PrincipalConstraint::In(euid));
2308 }
2309
2310 #[test]
2311 fn action_constraint_inline() {
2312 let p = Policy::from_str("permit(principal,action,resource);").unwrap();
2313 assert_eq!(p.action_constraint(), ActionConstraint::Any);
2314 let euid = EntityUid::from_strs("NN::N::Action", "a");
2315 assert_eq!(
2316 euid.type_name(),
2317 &EntityTypeName::from_str("NN::N::Action").expect("Failed to parse EntityTypeName")
2318 );
2319 let p = Policy::from_str(
2320 "permit(principal == T::\"b\",action == NN::N::Action::\"a\",resource == T::\"c\");",
2321 )
2322 .unwrap();
2323 assert_eq!(p.action_constraint(), ActionConstraint::Eq(euid.clone()));
2324 let p = Policy::from_str("permit(principal,action in [NN::N::Action::\"a\"],resource);")
2325 .unwrap();
2326 assert_eq!(p.action_constraint(), ActionConstraint::In(vec![euid]));
2327 }
2328
2329 #[test]
2330 fn resource_constraint_inline() {
2331 let p = Policy::from_str("permit(principal,action,resource);").unwrap();
2332 assert_eq!(p.resource_constraint(), ResourceConstraint::Any);
2333 let euid = EntityUid::from_strs("NN::N::T", "a");
2334 assert_eq!(
2335 euid.type_name(),
2336 &EntityTypeName::from_str("NN::N::T").expect("Failed to parse EntityTypeName")
2337 );
2338 let p =
2339 Policy::from_str("permit(principal == T::\"b\",action,resource == NN::N::T::\"a\");")
2340 .unwrap();
2341 assert_eq!(
2342 p.resource_constraint(),
2343 ResourceConstraint::Eq(euid.clone())
2344 );
2345 let p = Policy::from_str("permit(principal,action,resource in NN::N::T::\"a\");").unwrap();
2346 assert_eq!(p.resource_constraint(), ResourceConstraint::In(euid));
2347 }
2348
2349 #[test]
2350 fn principal_constraint_link() {
2351 let p = link("permit(principal,action,resource);", HashMap::new());
2352 assert_eq!(p.principal_constraint(), PrincipalConstraint::Any);
2353 let euid = EntityUid::from_strs("T", "a");
2354 let p = link(
2355 "permit(principal == T::\"a\",action,resource);",
2356 HashMap::new(),
2357 );
2358 assert_eq!(
2359 p.principal_constraint(),
2360 PrincipalConstraint::Eq(euid.clone())
2361 );
2362 let p = link(
2363 "permit(principal in T::\"a\",action,resource);",
2364 HashMap::new(),
2365 );
2366 assert_eq!(
2367 p.principal_constraint(),
2368 PrincipalConstraint::In(euid.clone())
2369 );
2370 let map: HashMap<SlotId, EntityUid> =
2371 std::iter::once((SlotId::principal(), euid.clone())).collect();
2372 let p = link(
2373 "permit(principal in ?principal,action,resource);",
2374 map.clone(),
2375 );
2376 assert_eq!(
2377 p.principal_constraint(),
2378 PrincipalConstraint::In(euid.clone())
2379 );
2380 let p = link("permit(principal == ?principal,action,resource);", map);
2381 assert_eq!(p.principal_constraint(), PrincipalConstraint::Eq(euid));
2382 }
2383
2384 #[test]
2385 fn action_constraint_link() {
2386 let p = link("permit(principal,action,resource);", HashMap::new());
2387 assert_eq!(p.action_constraint(), ActionConstraint::Any);
2388 let euid = EntityUid::from_strs("Action", "a");
2389 let p = link(
2390 "permit(principal,action == Action::\"a\",resource);",
2391 HashMap::new(),
2392 );
2393 assert_eq!(p.action_constraint(), ActionConstraint::Eq(euid.clone()));
2394 let p = link(
2395 "permit(principal,action in [Action::\"a\",Action::\"b\"],resource);",
2396 HashMap::new(),
2397 );
2398 assert_eq!(
2399 p.action_constraint(),
2400 ActionConstraint::In(vec![euid, EntityUid::from_strs("Action", "b"),])
2401 );
2402 }
2403
2404 #[test]
2405 fn resource_constraint_link() {
2406 let p = link("permit(principal,action,resource);", HashMap::new());
2407 assert_eq!(p.resource_constraint(), ResourceConstraint::Any);
2408 let euid = EntityUid::from_strs("T", "a");
2409 let p = link(
2410 "permit(principal,action,resource == T::\"a\");",
2411 HashMap::new(),
2412 );
2413 assert_eq!(
2414 p.resource_constraint(),
2415 ResourceConstraint::Eq(euid.clone())
2416 );
2417 let p = link(
2418 "permit(principal,action,resource in T::\"a\");",
2419 HashMap::new(),
2420 );
2421 assert_eq!(
2422 p.resource_constraint(),
2423 ResourceConstraint::In(euid.clone())
2424 );
2425 let map: HashMap<SlotId, EntityUid> =
2426 std::iter::once((SlotId::resource(), euid.clone())).collect();
2427 let p = link(
2428 "permit(principal,action,resource in ?resource);",
2429 map.clone(),
2430 );
2431 assert_eq!(
2432 p.resource_constraint(),
2433 ResourceConstraint::In(euid.clone())
2434 );
2435 let p = link("permit(principal,action,resource == ?resource);", map);
2436 assert_eq!(p.resource_constraint(), ResourceConstraint::Eq(euid));
2437 }
2438
2439 fn link(src: &str, values: HashMap<SlotId, EntityUid>) -> Policy {
2440 let mut pset = PolicySet::new();
2441 let template = Template::parse(Some("Id".to_string()), src).unwrap();
2442
2443 pset.add_template(template).unwrap();
2444
2445 let link_id = PolicyId::from_str("link").unwrap();
2446 pset.link(PolicyId::from_str("Id").unwrap(), link_id.clone(), values)
2447 .unwrap();
2448 pset.policy(&link_id).unwrap().clone()
2449 }
2450}
2451
2452#[cfg(test)]
2454mod policy_set_tests {
2455 use super::*;
2456 use ast::LinkingError;
2457
2458 #[test]
2459 fn link_conflicts() {
2460 let mut pset = PolicySet::new();
2461 let p1 = Policy::parse(Some("id".into()), "permit(principal,action,resource);")
2462 .expect("Failed to parse");
2463 pset.add(p1).expect("Failed to add");
2464 let template = Template::parse(
2465 Some("t".into()),
2466 "permit(principal == ?principal, action, resource);",
2467 )
2468 .expect("Failed to parse");
2469 pset.add_template(template).expect("Add failed");
2470
2471 let env: HashMap<SlotId, EntityUid> =
2472 std::iter::once((SlotId::principal(), EntityUid::from_strs("Test", "test"))).collect();
2473
2474 let r = pset.link(
2475 PolicyId::from_str("t").unwrap(),
2476 PolicyId::from_str("id").unwrap(),
2477 env,
2478 );
2479
2480 match r {
2481 Ok(_) => panic!("Should have failed due to conflict"),
2482 Err(PolicySetError::LinkingError(LinkingError::PolicyIdConflict)) => (),
2483 Err(e) => panic!("Incorrect error: {e}"),
2484 };
2485 }
2486
2487 #[test]
2488 fn policyset_add() {
2489 let mut pset = PolicySet::new();
2490 let static_policy = Policy::parse(Some("id".into()), "permit(principal,action,resource);")
2491 .expect("Failed to parse");
2492 pset.add(static_policy).expect("Failed to add");
2493
2494 let template = Template::parse(
2495 Some("t".into()),
2496 "permit(principal == ?principal, action, resource);",
2497 )
2498 .expect("Failed to parse");
2499 pset.add_template(template).expect("Failed to add");
2500
2501 let env1: HashMap<SlotId, EntityUid> =
2502 std::iter::once((SlotId::principal(), EntityUid::from_strs("Test", "test1"))).collect();
2503 pset.link(
2504 PolicyId::from_str("t").unwrap(),
2505 PolicyId::from_str("link").unwrap(),
2506 env1,
2507 )
2508 .expect("Failed to link");
2509
2510 let env2: HashMap<SlotId, EntityUid> =
2511 std::iter::once((SlotId::principal(), EntityUid::from_strs("Test", "test2"))).collect();
2512
2513 let err = pset
2514 .link(
2515 PolicyId::from_str("t").unwrap(),
2516 PolicyId::from_str("link").unwrap(),
2517 env2.clone(),
2518 )
2519 .expect_err("Should have failed due to conflict with existing link id");
2520 match err {
2521 PolicySetError::LinkingError(_) => (),
2522 e => panic!("Wrong error: {e}"),
2523 }
2524
2525 pset.link(
2526 PolicyId::from_str("t").unwrap(),
2527 PolicyId::from_str("link2").unwrap(),
2528 env2,
2529 )
2530 .expect("Failed to link");
2531
2532 let template2 = Template::parse(
2533 Some("t".into()),
2534 "forbid(principal, action, resource == ?resource);",
2535 )
2536 .expect("Failed to parse");
2537 pset.add_template(template2)
2538 .expect_err("should have failed due to conflict on template id");
2539 let template2 = Template::parse(
2540 Some("t2".into()),
2541 "forbid(principal, action, resource == ?resource);",
2542 )
2543 .expect("Failed to parse");
2544 pset.add_template(template2)
2545 .expect("Failed to add template");
2546 let env3: HashMap<SlotId, EntityUid> =
2547 std::iter::once((SlotId::resource(), EntityUid::from_strs("Test", "test3"))).collect();
2548
2549 pset.link(
2550 PolicyId::from_str("t").unwrap(),
2551 PolicyId::from_str("unique3").unwrap(),
2552 env3.clone(),
2553 )
2554 .expect_err("should have failed due to conflict on template id");
2555
2556 pset.link(
2557 PolicyId::from_str("t2").unwrap(),
2558 PolicyId::from_str("unique3").unwrap(),
2559 env3,
2560 )
2561 .expect("should succeed with unique ids");
2562 }
2563
2564 #[test]
2565 fn pset_requests() {
2566 let template = Template::parse(
2567 Some("template".into()),
2568 "permit(principal == ?principal, action, resource);",
2569 )
2570 .expect("Template Parse Failure");
2571 let static_policy = Policy::parse(
2572 Some("static".into()),
2573 "permit(principal, action, resource);",
2574 )
2575 .expect("Static parse failure");
2576 let mut pset = PolicySet::new();
2577 pset.add_template(template).unwrap();
2578 pset.add(static_policy).unwrap();
2579 pset.link(
2580 PolicyId::from_str("template").unwrap(),
2581 PolicyId::from_str("linked").unwrap(),
2582 std::iter::once((SlotId::principal(), EntityUid::from_strs("Test", "test"))).collect(),
2583 )
2584 .expect("Link failure");
2585
2586 assert_eq!(pset.templates().count(), 1);
2587 assert_eq!(pset.policies().count(), 2);
2588 assert_eq!(pset.policies().filter(|p| p.is_static()).count(), 1);
2589
2590 assert_eq!(
2591 pset.template(&"template".parse().unwrap())
2592 .expect("lookup failed")
2593 .id(),
2594 &"template".parse().unwrap()
2595 );
2596 assert_eq!(
2597 pset.policy(&"static".parse().unwrap())
2598 .expect("lookup failed")
2599 .id(),
2600 &"static".parse().unwrap()
2601 );
2602 assert_eq!(
2603 pset.policy(&"linked".parse().unwrap())
2604 .expect("lookup failed")
2605 .id(),
2606 &"linked".parse().unwrap()
2607 );
2608 }
2609}
2610
2611#[cfg(test)]
2612mod schema_tests {
2613 use super::*;
2614 use cool_asserts::assert_matches;
2615 use serde_json::json;
2616
2617 #[test]
2619 fn valid_schema() {
2620 let _ = Schema::from_json_value(json!(
2621 { "": {
2622 "entityTypes": {
2623 "Photo": {
2624 "memberOfTypes": [ "Album" ],
2625 "shape": {
2626 "type": "Record",
2627 "attributes": {
2628 "foo": {
2629 "type": "Boolean",
2630 "required": false
2631 }
2632 }
2633 }
2634 },
2635 "Album": {
2636 "memberOfTypes": [ ],
2637 "shape": {
2638 "type": "Record",
2639 "attributes": {
2640 "foo": {
2641 "type": "Boolean",
2642 "required": false
2643 }
2644 }
2645 }
2646 }
2647 },
2648 "actions": {
2649 "view": {
2650 "appliesTo": {
2651 "principalTypes": ["Photo", "Album"],
2652 "resourceTypes": ["Photo"]
2653 }
2654 }
2655 }
2656 }}))
2657 .expect("schema should be valid");
2658 }
2659
2660 #[test]
2662 fn invalid_schema() {
2663 assert_matches!(
2664 Schema::from_json_value(json!(
2665 r#""{"": {
2668 "entityTypes": {
2669 "Photo": {
2670 "memberOfTypes": [ "Album" ],
2671 "shape": {
2672 "type": "Record",
2673 "attributes": {
2674 "foo": {
2675 "type": "Boolean",
2676 "required": false
2677 }
2678 }
2679 }
2680 },
2681 "Album": {
2682 "memberOfTypes": [ ],
2683 "shape": {
2684 "type": "Record",
2685 "attributes": {
2686 "foo": {
2687 "type": "Boolean",
2688 "required": false
2689 }
2690 }
2691 }
2692 },
2693 "Photo": {
2694 "memberOfTypes": [ "Album" ],
2695 "shape": {
2696 "type": "Record",
2697 "attributes": {
2698 "foo": {
2699 "type": "Boolean",
2700 "required": false
2701 }
2702 }
2703 }
2704 }
2705 },
2706 "actions": {
2707 "view": {
2708 "appliesTo": {
2709 "principalTypes": ["Photo", "Album"],
2710 "resourceTypes": ["Photo"]
2711 }
2712 }
2713 }
2714 }}"#
2715 )),
2716 Err(SchemaError::ParseJson(_))
2717 );
2718 }
2719}
2720
2721#[cfg(test)]
2722mod ancestors_tests {
2723 use super::*;
2724
2725 #[test]
2726 fn test_ancestors() {
2727 let a_euid: EntityUid = EntityUid::from_strs("test", "A");
2728 let b_euid: EntityUid = EntityUid::from_strs("test", "b");
2729 let c_euid: EntityUid = EntityUid::from_strs("test", "C");
2730 let a = Entity::new(a_euid.clone(), HashMap::new(), HashSet::new());
2731 let b = Entity::new(
2732 b_euid.clone(),
2733 HashMap::new(),
2734 std::iter::once(a_euid.clone()).collect(),
2735 );
2736 let c = Entity::new(
2737 c_euid.clone(),
2738 HashMap::new(),
2739 std::iter::once(b_euid.clone()).collect(),
2740 );
2741 let es = Entities::from_entities([a, b, c]).unwrap();
2742 let ans = es.ancestors(&c_euid).unwrap().collect::<HashSet<_>>();
2743 assert_eq!(ans.len(), 2);
2744 assert!(ans.contains(&b_euid));
2745 assert!(ans.contains(&a_euid));
2746 }
2747}
2748
2749#[cfg(test)]
2754mod schema_based_parsing_tests {
2755 use super::*;
2756 use cool_asserts::assert_matches;
2757 use serde_json::json;
2758
2759 #[test]
2761 #[allow(clippy::too_many_lines)]
2762 #[allow(clippy::cognitive_complexity)]
2763 fn attr_types() {
2764 let schema = Schema::from_json_value(json!(
2765 {"": {
2766 "entityTypes": {
2767 "Employee": {
2768 "memberOfTypes": [],
2769 "shape": {
2770 "type": "Record",
2771 "attributes": {
2772 "isFullTime": { "type": "Boolean" },
2773 "numDirectReports": { "type": "Long" },
2774 "department": { "type": "String" },
2775 "manager": { "type": "Entity", "name": "Employee" },
2776 "hr_contacts": { "type": "Set", "element": {
2777 "type": "Entity", "name": "HR" } },
2778 "json_blob": { "type": "Record", "attributes": {
2779 "inner1": { "type": "Boolean" },
2780 "inner2": { "type": "String" },
2781 "inner3": { "type": "Record", "attributes": {
2782 "innerinner": { "type": "Entity", "name": "Employee" }
2783 }}
2784 }},
2785 "home_ip": { "type": "Extension", "name": "ipaddr" },
2786 "work_ip": { "type": "Extension", "name": "ipaddr" },
2787 "trust_score": { "type": "Extension", "name": "decimal" },
2788 "tricky": { "type": "Record", "attributes": {
2789 "type": { "type": "String" },
2790 "id": { "type": "String" }
2791 }}
2792 }
2793 }
2794 },
2795 "HR": {
2796 "memberOfTypes": []
2797 }
2798 },
2799 "actions": {
2800 "view": { }
2801 }
2802 }}
2803 ))
2804 .expect("should be a valid schema");
2805
2806 let entitiesjson = json!(
2807 [
2808 {
2809 "uid": { "type": "Employee", "id": "12UA45" },
2810 "attrs": {
2811 "isFullTime": true,
2812 "numDirectReports": 3,
2813 "department": "Sales",
2814 "manager": { "type": "Employee", "id": "34FB87" },
2815 "hr_contacts": [
2816 { "type": "HR", "id": "aaaaa" },
2817 { "type": "HR", "id": "bbbbb" }
2818 ],
2819 "json_blob": {
2820 "inner1": false,
2821 "inner2": "-*/",
2822 "inner3": { "innerinner": { "type": "Employee", "id": "09AE76" }},
2823 },
2824 "home_ip": "222.222.222.101",
2825 "work_ip": { "fn": "ip", "arg": "2.2.2.0/24" },
2826 "trust_score": "5.7",
2827 "tricky": { "type": "Employee", "id": "34FB87" }
2828 },
2829 "parents": []
2830 }
2831 ]
2832 );
2833 let parsed = Entities::from_json_value(entitiesjson.clone(), None)
2837 .expect("Should parse without error");
2838 assert_eq!(parsed.iter().count(), 1);
2839 let parsed = parsed
2840 .get(&EntityUid::from_strs("Employee", "12UA45"))
2841 .expect("that should be the employee id");
2842 assert_eq!(
2843 parsed.attr("home_ip"),
2844 Some(Ok(EvalResult::String("222.222.222.101".into())))
2845 );
2846 assert_eq!(
2847 parsed.attr("trust_score"),
2848 Some(Ok(EvalResult::String("5.7".into())))
2849 );
2850 assert!(matches!(
2851 parsed.attr("manager"),
2852 Some(Ok(EvalResult::Record(_)))
2853 ));
2854 assert!(matches!(
2855 parsed.attr("work_ip"),
2856 Some(Ok(EvalResult::Record(_)))
2857 ));
2858 {
2859 let Some(Ok(EvalResult::Set(set))) = parsed.attr("hr_contacts") else { panic!("expected hr_contacts attr to exist and be a Set") };
2860 let contact = set.iter().next().expect("should be at least one contact");
2861 assert!(matches!(contact, EvalResult::Record(_)));
2862 };
2863 {
2864 let Some(Ok(EvalResult::Record(rec))) = parsed.attr("json_blob") else { panic!("expected json_blob attr to exist and be a Record") };
2865 let inner3 = rec.get("inner3").expect("expected inner3 attr to exist");
2866 let EvalResult::Record(rec) = inner3 else { panic!("expected inner3 to be a Record") };
2867 let innerinner = rec
2868 .get("innerinner")
2869 .expect("expected innerinner attr to exist");
2870 assert!(matches!(innerinner, EvalResult::Record(_)));
2871 };
2872 let parsed = Entities::from_json_value(entitiesjson, Some(&schema))
2874 .expect("Should parse without error");
2875 assert_eq!(parsed.iter().count(), 1);
2876 let parsed = parsed
2877 .get(&EntityUid::from_strs("Employee", "12UA45"))
2878 .expect("that should be the employee id");
2879 assert_eq!(parsed.attr("isFullTime"), Some(Ok(EvalResult::Bool(true))));
2880 assert_eq!(
2881 parsed.attr("numDirectReports"),
2882 Some(Ok(EvalResult::Long(3)))
2883 );
2884 assert_eq!(
2885 parsed.attr("department"),
2886 Some(Ok(EvalResult::String("Sales".into())))
2887 );
2888 assert_eq!(
2889 parsed.attr("manager"),
2890 Some(Ok(EvalResult::EntityUid(EntityUid::from_strs(
2891 "Employee", "34FB87"
2892 ))))
2893 );
2894 {
2895 let Some(Ok(EvalResult::Set(set))) = parsed.attr("hr_contacts") else { panic!("expected hr_contacts attr to exist and be a Set") };
2896 let contact = set.iter().next().expect("should be at least one contact");
2897 assert!(matches!(contact, EvalResult::EntityUid(_)));
2898 };
2899 {
2900 let Some(Ok(EvalResult::Record(rec))) = parsed.attr("json_blob") else { panic!("expected json_blob attr to exist and be a Record") };
2901 let inner3 = rec.get("inner3").expect("expected inner3 attr to exist");
2902 let EvalResult::Record(rec) = inner3 else { panic!("expected inner3 to be a Record") };
2903 let innerinner = rec
2904 .get("innerinner")
2905 .expect("expected innerinner attr to exist");
2906 assert!(matches!(innerinner, EvalResult::EntityUid(_)));
2907 };
2908 assert_eq!(
2909 parsed.attr("home_ip"),
2910 Some(Ok(EvalResult::ExtensionValue("222.222.222.101/32".into())))
2911 );
2912 assert_eq!(
2913 parsed.attr("work_ip"),
2914 Some(Ok(EvalResult::ExtensionValue("2.2.2.0/24".into())))
2915 );
2916 assert_eq!(
2917 parsed.attr("trust_score"),
2918 Some(Ok(EvalResult::ExtensionValue("5.7000".into())))
2919 );
2920
2921 let entitiesjson = json!(
2923 [
2924 {
2925 "uid": { "type": "Employee", "id": "12UA45" },
2926 "attrs": {
2927 "isFullTime": true,
2928 "numDirectReports": "3",
2929 "department": "Sales",
2930 "manager": { "type": "Employee", "id": "34FB87" },
2931 "hr_contacts": [
2932 { "type": "HR", "id": "aaaaa" },
2933 { "type": "HR", "id": "bbbbb" }
2934 ],
2935 "json_blob": {
2936 "inner1": false,
2937 "inner2": "-*/",
2938 "inner3": { "innerinner": { "type": "Employee", "id": "09AE76" }},
2939 },
2940 "home_ip": "222.222.222.101",
2941 "work_ip": { "fn": "ip", "arg": "2.2.2.0/24" },
2942 "trust_score": "5.7",
2943 "tricky": { "type": "Employee", "id": "34FB87" }
2944 },
2945 "parents": []
2946 }
2947 ]
2948 );
2949 let err = Entities::from_json_value(entitiesjson, Some(&schema))
2950 .expect_err("should fail due to type mismatch on numDirectReports");
2951 assert!(
2952 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"#),
2953 "actual error message was {err}"
2954 );
2955
2956 let entitiesjson = json!(
2958 [
2959 {
2960 "uid": { "type": "Employee", "id": "12UA45" },
2961 "attrs": {
2962 "isFullTime": true,
2963 "numDirectReports": 3,
2964 "department": "Sales",
2965 "manager": "34FB87",
2966 "hr_contacts": [
2967 { "type": "HR", "id": "aaaaa" },
2968 { "type": "HR", "id": "bbbbb" }
2969 ],
2970 "json_blob": {
2971 "inner1": false,
2972 "inner2": "-*/",
2973 "inner3": { "innerinner": { "type": "Employee", "id": "09AE76" }},
2974 },
2975 "home_ip": "222.222.222.101",
2976 "work_ip": { "fn": "ip", "arg": "2.2.2.0/24" },
2977 "trust_score": "5.7",
2978 "tricky": { "type": "Employee", "id": "34FB87" }
2979 },
2980 "parents": []
2981 }
2982 ]
2983 );
2984 let err = Entities::from_json_value(entitiesjson, Some(&schema))
2985 .expect_err("should fail due to type mismatch on manager");
2986 assert!(
2987 err.to_string()
2988 .contains(r#"In attribute "manager" on Employee::"12UA45", expected a literal entity reference, but got "34FB87""#),
2989 "actual error message was {err}"
2990 );
2991
2992 let entitiesjson = json!(
2994 [
2995 {
2996 "uid": { "type": "Employee", "id": "12UA45" },
2997 "attrs": {
2998 "isFullTime": true,
2999 "numDirectReports": 3,
3000 "department": "Sales",
3001 "manager": { "type": "Employee", "id": "34FB87" },
3002 "hr_contacts": { "type": "HR", "id": "aaaaa" },
3003 "json_blob": {
3004 "inner1": false,
3005 "inner2": "-*/",
3006 "inner3": { "innerinner": { "type": "Employee", "id": "09AE76" }},
3007 },
3008 "home_ip": "222.222.222.101",
3009 "work_ip": { "fn": "ip", "arg": "2.2.2.0/24" },
3010 "trust_score": "5.7",
3011 "tricky": { "type": "Employee", "id": "34FB87" }
3012 },
3013 "parents": []
3014 }
3015 ]
3016 );
3017 let err = Entities::from_json_value(entitiesjson, Some(&schema))
3018 .expect_err("should fail due to type mismatch on hr_contacts");
3019 assert!(
3020 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: ("#),
3021 "actual error message was {err}"
3022 );
3023
3024 let entitiesjson = json!(
3026 [
3027 {
3028 "uid": { "type": "Employee", "id": "12UA45" },
3029 "attrs": {
3030 "isFullTime": true,
3031 "numDirectReports": 3,
3032 "department": "Sales",
3033 "manager": { "type": "HR", "id": "34FB87" },
3034 "hr_contacts": [
3035 { "type": "HR", "id": "aaaaa" },
3036 { "type": "HR", "id": "bbbbb" }
3037 ],
3038 "json_blob": {
3039 "inner1": false,
3040 "inner2": "-*/",
3041 "inner3": { "innerinner": { "type": "Employee", "id": "09AE76" }},
3042 },
3043 "home_ip": "222.222.222.101",
3044 "work_ip": { "fn": "ip", "arg": "2.2.2.0/24" },
3045 "trust_score": "5.7",
3046 "tricky": { "type": "Employee", "id": "34FB87" }
3047 },
3048 "parents": []
3049 }
3050 ]
3051 );
3052 let err = Entities::from_json_value(entitiesjson, Some(&schema))
3053 .expect_err("should fail due to type mismatch on manager");
3054 assert!(
3055 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)"#),
3056 "actual error message was {err}"
3057 );
3058
3059 let entitiesjson = json!(
3062 [
3063 {
3064 "uid": { "type": "Employee", "id": "12UA45" },
3065 "attrs": {
3066 "isFullTime": true,
3067 "numDirectReports": 3,
3068 "department": "Sales",
3069 "manager": { "type": "Employee", "id": "34FB87" },
3070 "hr_contacts": [
3071 { "type": "HR", "id": "aaaaa" },
3072 { "type": "HR", "id": "bbbbb" }
3073 ],
3074 "json_blob": {
3075 "inner1": false,
3076 "inner2": "-*/",
3077 "inner3": { "innerinner": { "type": "Employee", "id": "09AE76" }},
3078 },
3079 "home_ip": { "fn": "decimal", "arg": "3.33" },
3080 "work_ip": { "fn": "ip", "arg": "2.2.2.0/24" },
3081 "trust_score": "5.7",
3082 "tricky": { "type": "Employee", "id": "34FB87" }
3083 },
3084 "parents": []
3085 }
3086 ]
3087 );
3088 let err = Entities::from_json_value(entitiesjson, Some(&schema))
3089 .expect_err("should fail due to type mismatch on home_ip");
3090 assert!(
3091 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"#),
3092 "actual error message was {err}"
3093 );
3094
3095 let entitiesjson = json!(
3097 [
3098 {
3099 "uid": { "type": "Employee", "id": "12UA45" },
3100 "attrs": {
3101 "isFullTime": true,
3102 "numDirectReports": 3,
3103 "department": "Sales",
3104 "manager": { "type": "Employee", "id": "34FB87" },
3105 "hr_contacts": [
3106 { "type": "HR", "id": "aaaaa" },
3107 { "type": "HR", "id": "bbbbb" }
3108 ],
3109 "json_blob": {
3110 "inner1": false,
3111 "inner3": { "innerinner": { "type": "Employee", "id": "09AE76" }},
3112 },
3113 "home_ip": "222.222.222.101",
3114 "work_ip": { "fn": "ip", "arg": "2.2.2.0/24" },
3115 "trust_score": "5.7",
3116 "tricky": { "type": "Employee", "id": "34FB87" }
3117 },
3118 "parents": []
3119 }
3120 ]
3121 );
3122 let err = Entities::from_json_value(entitiesjson, Some(&schema))
3123 .expect_err("should fail due to missing attribute \"inner2\"");
3124 assert!(
3125 err.to_string().contains(r#"In attribute "json_blob" on Employee::"12UA45", expected the record to have an attribute "inner2", but it didn't"#),
3126 "actual error message was {err}"
3127 );
3128
3129 let entitiesjson = json!(
3131 [
3132 {
3133 "uid": { "type": "Employee", "id": "12UA45" },
3134 "attrs": {
3135 "isFullTime": true,
3136 "numDirectReports": 3,
3137 "department": "Sales",
3138 "manager": { "type": "Employee", "id": "34FB87" },
3139 "hr_contacts": [
3140 { "type": "HR", "id": "aaaaa" },
3141 { "type": "HR", "id": "bbbbb" }
3142 ],
3143 "json_blob": {
3144 "inner1": 33,
3145 "inner2": "-*/",
3146 "inner3": { "innerinner": { "type": "Employee", "id": "09AE76" }},
3147 },
3148 "home_ip": "222.222.222.101",
3149 "work_ip": { "fn": "ip", "arg": "2.2.2.0/24" },
3150 "trust_score": "5.7",
3151 "tricky": { "type": "Employee", "id": "34FB87" }
3152 },
3153 "parents": []
3154 }
3155 ]
3156 );
3157 let err = Entities::from_json_value(entitiesjson, Some(&schema))
3158 .expect_err("should fail due to type mismatch on attribute \"inner1\"");
3159 assert!(
3160 err.to_string().contains(r#"In attribute "json_blob" on Employee::"12UA45", type mismatch: attribute was expected to have type record with attributes: "#),
3161 "actual error message was {err}"
3162 );
3163
3164 let entitiesjson = json!(
3165 [
3166 {
3167 "uid": { "__entity": { "type": "Employee", "id": "12UA45" } },
3168 "attrs": {
3169 "isFullTime": true,
3170 "numDirectReports": 3,
3171 "department": "Sales",
3172 "manager": { "__entity": { "type": "Employee", "id": "34FB87" } },
3173 "hr_contacts": [
3174 { "type": "HR", "id": "aaaaa" },
3175 { "type": "HR", "id": "bbbbb" }
3176 ],
3177 "json_blob": {
3178 "inner1": false,
3179 "inner2": "-*/",
3180 "inner3": { "innerinner": { "type": "Employee", "id": "09AE76" }},
3181 },
3182 "home_ip": { "__extn": { "fn": "ip", "arg": "222.222.222.101" } },
3183 "work_ip": { "__extn": { "fn": "ip", "arg": "2.2.2.0/24" } },
3184 "trust_score": { "__extn": { "fn": "decimal", "arg": "5.7" } },
3185 "tricky": { "type": "Employee", "id": "34FB87" }
3186 },
3187 "parents": []
3188 }
3189 ]
3190 );
3191 let _ = Entities::from_json_value(entitiesjson, Some(&schema))
3192 .expect("this version with explicit __entity and __extn escapes should also pass");
3193 }
3194
3195 #[test]
3197 fn namespaces() {
3198 let schema = Schema::from_str(
3199 r#"
3200 {"XYZCorp": {
3201 "entityTypes": {
3202 "Employee": {
3203 "memberOfTypes": [],
3204 "shape": {
3205 "type": "Record",
3206 "attributes": {
3207 "isFullTime": { "type": "Boolean" },
3208 "department": { "type": "String" },
3209 "manager": {
3210 "type": "Entity",
3211 "name": "XYZCorp::Employee"
3212 }
3213 }
3214 }
3215 }
3216 },
3217 "actions": {
3218 "view": {}
3219 }
3220 }}
3221 "#,
3222 )
3223 .expect("should be a valid schema");
3224
3225 let entitiesjson = json!(
3226 [
3227 {
3228 "uid": { "type": "XYZCorp::Employee", "id": "12UA45" },
3229 "attrs": {
3230 "isFullTime": true,
3231 "department": "Sales",
3232 "manager": { "type": "XYZCorp::Employee", "id": "34FB87" }
3233 },
3234 "parents": []
3235 }
3236 ]
3237 );
3238 let parsed = Entities::from_json_value(entitiesjson, Some(&schema))
3239 .expect("Should parse without error");
3240 assert_eq!(parsed.iter().count(), 1);
3241 let parsed = parsed
3242 .get(&EntityUid::from_strs("XYZCorp::Employee", "12UA45"))
3243 .expect("that should be the employee type and id");
3244 assert_eq!(parsed.attr("isFullTime"), Some(Ok(EvalResult::Bool(true))));
3245 assert_eq!(
3246 parsed.attr("department"),
3247 Some(Ok(EvalResult::String("Sales".into())))
3248 );
3249 assert_eq!(
3250 parsed.attr("manager"),
3251 Some(Ok(EvalResult::EntityUid(EntityUid::from_strs(
3252 "XYZCorp::Employee",
3253 "34FB87"
3254 ))))
3255 );
3256
3257 let entitiesjson = json!(
3258 [
3259 {
3260 "uid": { "type": "XYZCorp::Employee", "id": "12UA45" },
3261 "attrs": {
3262 "isFullTime": true,
3263 "department": "Sales",
3264 "manager": { "type": "Employee", "id": "34FB87" }
3265 },
3266 "parents": []
3267 }
3268 ]
3269 );
3270 let err = Entities::from_json_value(entitiesjson, Some(&schema))
3271 .expect_err("should fail due to manager being wrong entity type (missing namespace)");
3272 assert!(
3273 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)"#),
3274 "actual error message was {err}"
3275 );
3276 }
3277
3278 #[test]
3280 fn optional_attrs() {
3281 let schema = Schema::from_str(
3282 r#"
3283 {"": {
3284 "entityTypes": {
3285 "Employee": {
3286 "memberOfTypes": [],
3287 "shape": {
3288 "type": "Record",
3289 "attributes": {
3290 "isFullTime": { "type": "Boolean" },
3291 "department": { "type": "String", "required": false },
3292 "manager": { "type": "Entity", "name": "Employee" }
3293 }
3294 }
3295 }
3296 },
3297 "actions": {
3298 "view": {}
3299 }
3300 }}
3301 "#,
3302 )
3303 .expect("should be a valid schema");
3304
3305 let entitiesjson = json!(
3307 [
3308 {
3309 "uid": { "type": "Employee", "id": "12UA45" },
3310 "attrs": {
3311 "isFullTime": true,
3312 "department": "Sales",
3313 "manager": { "type": "Employee", "id": "34FB87" }
3314 },
3315 "parents": []
3316 }
3317 ]
3318 );
3319 let parsed = Entities::from_json_value(entitiesjson, Some(&schema))
3320 .expect("Should parse without error");
3321 assert_eq!(parsed.iter().count(), 1);
3322
3323 let entitiesjson = json!(
3325 [
3326 {
3327 "uid": { "type": "Employee", "id": "12UA45" },
3328 "attrs": {
3329 "isFullTime": true,
3330 "manager": { "type": "Employee", "id": "34FB87" }
3331 },
3332 "parents": []
3333 }
3334 ]
3335 );
3336 let parsed = Entities::from_json_value(entitiesjson, Some(&schema))
3337 .expect("Should parse without error");
3338 assert_eq!(parsed.iter().count(), 1);
3339 }
3340
3341 #[test]
3343 #[should_panic(
3344 expected = "UnsupportedSchemaFeature(\"Records and entities with additional attributes are not yet implemented.\")"
3345 )]
3346 fn open_entities() {
3347 let schema = Schema::from_str(
3348 r#"
3349 {"": {
3350 "entityTypes": {
3351 "Employee": {
3352 "memberOfTypes": [],
3353 "shape": {
3354 "type": "Record",
3355 "attributes": {
3356 "isFullTime": { "type": "Boolean" },
3357 "department": { "type": "String", "required": false },
3358 "manager": { "type": "Entity", "name": "Employee" }
3359 },
3360 "additionalAttributes": true
3361 }
3362 }
3363 },
3364 "actions": {
3365 "view": {}
3366 }
3367 }}
3368 "#,
3369 )
3370 .expect("should be a valid schema");
3371
3372 let entitiesjson = json!(
3374 [
3375 {
3376 "uid": { "type": "Employee", "id": "12UA45" },
3377 "attrs": {
3378 "isFullTime": true,
3379 "department": "Sales",
3380 "manager": { "type": "Employee", "id": "34FB87" }
3381 },
3382 "parents": []
3383 }
3384 ]
3385 );
3386 let parsed = Entities::from_json_value(entitiesjson, Some(&schema))
3387 .expect("Should parse without error");
3388 assert_eq!(parsed.iter().count(), 1);
3389
3390 let entitiesjson = json!(
3392 [
3393 {
3394 "uid": { "type": "Employee", "id": "12UA45" },
3395 "attrs": {
3396 "isFullTime": true,
3397 "foobar": 234,
3398 "manager": { "type": "Employee", "id": "34FB87" }
3399 },
3400 "parents": []
3401 }
3402 ]
3403 );
3404 let parsed = Entities::from_json_value(entitiesjson, Some(&schema))
3405 .expect("Should parse without error");
3406 assert_eq!(parsed.iter().count(), 1);
3407 }
3408
3409 #[test]
3410 fn schema_sanity_check() {
3411 let src = "{ , .. }";
3412 assert_matches!(Schema::from_str(src), Err(super::SchemaError::ParseJson(_)));
3413 }
3414
3415 #[test]
3416 fn template_constraint_sanity_checks() {
3417 assert!(!TemplatePrincipalConstraint::Any.has_slot());
3418 assert!(!TemplatePrincipalConstraint::In(Some(EntityUid::from_strs("a", "a"))).has_slot());
3419 assert!(!TemplatePrincipalConstraint::Eq(Some(EntityUid::from_strs("a", "a"))).has_slot());
3420 assert!(TemplatePrincipalConstraint::In(None).has_slot());
3421 assert!(TemplatePrincipalConstraint::Eq(None).has_slot());
3422 assert!(!TemplateResourceConstraint::Any.has_slot());
3423 assert!(!TemplateResourceConstraint::In(Some(EntityUid::from_strs("a", "a"))).has_slot());
3424 assert!(!TemplateResourceConstraint::Eq(Some(EntityUid::from_strs("a", "a"))).has_slot());
3425 assert!(TemplateResourceConstraint::In(None).has_slot());
3426 assert!(TemplateResourceConstraint::Eq(None).has_slot());
3427 }
3428
3429 #[test]
3430 fn template_principal_constraints() {
3431 let src = r#"
3432 permit(principal, action, resource);
3433 "#;
3434 let t = Template::parse(None, src).unwrap();
3435 assert_eq!(t.principal_constraint(), TemplatePrincipalConstraint::Any);
3436
3437 let src = r#"
3438 permit(principal == ?principal, action, resource);
3439 "#;
3440 let t = Template::parse(None, src).unwrap();
3441 assert_eq!(
3442 t.principal_constraint(),
3443 TemplatePrincipalConstraint::Eq(None)
3444 );
3445
3446 let src = r#"
3447 permit(principal == A::"a", action, resource);
3448 "#;
3449 let t = Template::parse(None, src).unwrap();
3450 assert_eq!(
3451 t.principal_constraint(),
3452 TemplatePrincipalConstraint::Eq(Some(EntityUid::from_strs("A", "a")))
3453 );
3454
3455 let src = r#"
3456 permit(principal in ?principal, action, resource);
3457 "#;
3458 let t = Template::parse(None, src).unwrap();
3459 assert_eq!(
3460 t.principal_constraint(),
3461 TemplatePrincipalConstraint::In(None)
3462 );
3463
3464 let src = r#"
3465 permit(principal in A::"a", action, resource);
3466 "#;
3467 let t = Template::parse(None, src).unwrap();
3468 assert_eq!(
3469 t.principal_constraint(),
3470 TemplatePrincipalConstraint::In(Some(EntityUid::from_strs("A", "a")))
3471 );
3472 }
3473
3474 #[test]
3475 fn template_action_constraints() {
3476 let src = r#"
3477 permit(principal, action, resource);
3478 "#;
3479 let t = Template::parse(None, src).unwrap();
3480 assert_eq!(t.action_constraint(), ActionConstraint::Any);
3481
3482 let src = r#"
3483 permit(principal, action == Action::"A", resource);
3484 "#;
3485 let t = Template::parse(None, src).unwrap();
3486 assert_eq!(
3487 t.action_constraint(),
3488 ActionConstraint::Eq(EntityUid::from_strs("Action", "A"))
3489 );
3490
3491 let src = r#"
3492 permit(principal, action in [Action::"A", Action::"B"], resource);
3493 "#;
3494 let t = Template::parse(None, src).unwrap();
3495 assert_eq!(
3496 t.action_constraint(),
3497 ActionConstraint::In(vec![
3498 EntityUid::from_strs("Action", "A"),
3499 EntityUid::from_strs("Action", "B")
3500 ])
3501 );
3502 }
3503
3504 #[test]
3505 fn template_resource_constraints() {
3506 let src = r#"
3507 permit(principal, action, resource);
3508 "#;
3509 let t = Template::parse(None, src).unwrap();
3510 assert_eq!(t.resource_constraint(), TemplateResourceConstraint::Any);
3511
3512 let src = r#"
3513 permit(principal, action, resource == ?resource);
3514 "#;
3515 let t = Template::parse(None, src).unwrap();
3516 assert_eq!(
3517 t.resource_constraint(),
3518 TemplateResourceConstraint::Eq(None)
3519 );
3520
3521 let src = r#"
3522 permit(principal, action, resource == A::"a");
3523 "#;
3524 let t = Template::parse(None, src).unwrap();
3525 assert_eq!(
3526 t.resource_constraint(),
3527 TemplateResourceConstraint::Eq(Some(EntityUid::from_strs("A", "a")))
3528 );
3529
3530 let src = r#"
3531 permit(principal, action, resource in ?resource);
3532 "#;
3533 let t = Template::parse(None, src).unwrap();
3534 assert_eq!(
3535 t.resource_constraint(),
3536 TemplateResourceConstraint::In(None)
3537 );
3538
3539 let src = r#"
3540 permit(principal, action, resource in A::"a");
3541 "#;
3542 let t = Template::parse(None, src).unwrap();
3543 assert_eq!(
3544 t.resource_constraint(),
3545 TemplateResourceConstraint::In(Some(EntityUid::from_strs("A", "a")))
3546 );
3547 }
3548
3549 #[test]
3550 fn schema_namespace() {
3551 let fragment: SchemaFragment = r#"
3552 {
3553 "Foo::Bar": {
3554 "entityTypes": {},
3555 "actions": {}
3556 }
3557 }
3558 "#
3559 .parse()
3560 .unwrap();
3561 let namespaces = fragment.namespaces().next().unwrap();
3562 assert_eq!(
3563 namespaces.map(|ns| ns.to_string()),
3564 Some("Foo::Bar".to_string())
3565 );
3566 let _schema: Schema = fragment.try_into().expect("Should convert to schema");
3567
3568 let fragment: SchemaFragment = r#"
3569 {
3570 "": {
3571 "entityTypes": {},
3572 "actions": {}
3573 }
3574 }
3575 "#
3576 .parse()
3577 .unwrap();
3578 let namespaces = fragment.namespaces().next().unwrap();
3579 assert_eq!(namespaces, None);
3580 let _schema: Schema = fragment.try_into().expect("Should convert to schema");
3581 }
3582
3583 #[test]
3584 fn load_multiple_namespaces() {
3585 let fragment = SchemaFragment::from_json_value(json!({
3586 "Foo::Bar": {
3587 "entityTypes": {
3588 "Baz": {
3589 "memberOfTypes": ["Bar::Foo::Baz"]
3590 }
3591 },
3592 "actions": {}
3593 },
3594 "Bar::Foo": {
3595 "entityTypes": {
3596 "Baz": {
3597 "memberOfTypes": ["Foo::Bar::Baz"]
3598 }
3599 },
3600 "actions": {}
3601 }
3602 }))
3603 .unwrap();
3604
3605 let schema = Schema::from_schema_fragments([fragment]).unwrap();
3606
3607 assert!(schema
3608 .0
3609 .get_entity_type(&"Foo::Bar::Baz".parse().unwrap())
3610 .is_some());
3611 assert!(schema
3612 .0
3613 .get_entity_type(&"Bar::Foo::Baz".parse().unwrap())
3614 .is_some());
3615 }
3616}