1use crate::{PolicyId, SchemaWarning, SlotId};
19use miette::miette;
20use miette::WrapErr;
21use serde::{Deserialize, Serialize};
22use std::collections::BTreeSet;
23use std::{collections::HashMap, str::FromStr};
24
25pub use cedar_policy_core::jsonvalue::JsonValueWithNoDuplicateKeys;
29
30#[cfg(feature = "wasm")]
31extern crate tsify;
32
33#[derive(Debug, PartialEq, Eq, Clone, Hash, Deserialize, Serialize, Default)]
35#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
36#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
37#[serde(rename_all = "camelCase")]
38#[serde(deny_unknown_fields)]
39pub struct DetailedError {
40 pub message: String,
43 pub help: Option<String>,
45 pub code: Option<String>,
47 pub url: Option<String>,
49 pub severity: Option<Severity>,
51 #[serde(default)]
53 pub source_locations: Vec<SourceLabel>,
54 #[serde(default)]
56 pub related: Vec<DetailedError>,
57}
58
59impl FromStr for DetailedError {
60 type Err = std::convert::Infallible;
61
62 fn from_str(s: &str) -> Result<Self, Self::Err> {
63 Ok(DetailedError {
64 message: s.to_string(),
65 help: None,
66 code: None,
67 url: None,
68 severity: None,
69 source_locations: Vec::new(),
70 related: Vec::new(),
71 })
72 }
73}
74
75#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash, Deserialize, Serialize)]
79#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
80#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
81#[serde(rename_all = "camelCase")]
82pub enum Severity {
83 Advice,
85 Warning,
87 Error,
89}
90
91impl From<miette::Severity> for Severity {
92 fn from(severity: miette::Severity) -> Self {
93 match severity {
94 miette::Severity::Advice => Self::Advice,
95 miette::Severity::Warning => Self::Warning,
96 miette::Severity::Error => Self::Error,
97 }
98 }
99}
100
101#[derive(Debug, PartialEq, Eq, Clone, Hash, Deserialize, Serialize)]
103#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
104#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
105#[serde(rename_all = "camelCase")]
106#[serde(deny_unknown_fields)]
107pub struct SourceLabel {
108 pub label: Option<String>,
110 #[serde(flatten)]
112 pub loc: SourceLocation,
113}
114
115#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash, Deserialize, Serialize)]
117#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
118#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
119#[serde(rename_all = "camelCase")]
120#[serde(deny_unknown_fields)]
121pub struct SourceLocation {
122 pub start: usize,
124 pub end: usize,
126}
127
128impl From<miette::LabeledSpan> for SourceLabel {
129 fn from(span: miette::LabeledSpan) -> Self {
130 Self {
131 label: span.label().map(ToString::to_string),
132 loc: SourceLocation {
133 start: span.offset(),
134 end: span.offset() + span.len(),
135 },
136 }
137 }
138}
139
140impl<'a, E: miette::Diagnostic + ?Sized> From<&'a E> for DetailedError {
141 fn from(diag: &'a E) -> Self {
142 Self {
143 message: {
144 let mut s = diag.to_string();
145 let mut source = diag.source();
146 while let Some(e) = source {
147 s.push_str(": ");
148 s.push_str(&e.to_string());
149 source = e.source();
150 }
151 s
152 },
153 help: diag.help().map(|h| h.to_string()),
154 code: diag.code().map(|c| c.to_string()),
155 url: diag.url().map(|u| u.to_string()),
156 severity: diag.severity().map(Into::into),
157 source_locations: diag
158 .labels()
159 .map(|labels| labels.map(Into::into).collect())
160 .unwrap_or_default(),
161 related: diag
162 .related()
163 .map(|errs| errs.map(std::convert::Into::into).collect())
164 .unwrap_or_default(),
165 }
166 }
167}
168
169impl From<miette::Report> for DetailedError {
170 fn from(report: miette::Report) -> Self {
171 let diag: &dyn miette::Diagnostic = report.as_ref();
172 diag.into()
173 }
174}
175
176#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
179#[repr(transparent)]
180#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
181#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
182pub struct EntityUid(
183 #[cfg_attr(feature = "wasm", tsify(type = "EntityUidJson"))] JsonValueWithNoDuplicateKeys,
184);
185
186impl EntityUid {
187 pub fn parse(self, category: Option<&str>) -> Result<crate::EntityUid, miette::Report> {
196 crate::EntityUid::from_json(self.0.into())
197 .wrap_err_with(|| format!("failed to parse {}", category.unwrap_or("entity uid")))
198 }
199}
200
201#[doc(hidden)]
202impl From<serde_json::Value> for EntityUid {
203 fn from(json: serde_json::Value) -> Self {
204 Self(json.into())
205 }
206}
207
208#[derive(Debug, Serialize, Deserialize)]
212#[repr(transparent)]
213#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
214#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
215pub struct Context(
216 #[cfg_attr(feature = "wasm", tsify(type = "Record<string, CedarValueJson>"))]
217 JsonValueWithNoDuplicateKeys,
218);
219
220impl Context {
221 pub fn parse(
228 self,
229 schema_ref: Option<&crate::Schema>,
230 action_ref: Option<&crate::EntityUid>,
231 ) -> Result<crate::Context, miette::Report> {
232 crate::Context::from_json_value(
233 self.0.into(),
234 match (schema_ref, action_ref) {
235 (Some(s), Some(a)) => Some((s, a)),
236 _ => None,
237 },
238 )
239 .map_err(Into::into)
240 }
241}
242
243#[doc(hidden)]
244impl From<serde_json::Value> for Context {
245 fn from(json: serde_json::Value) -> Self {
246 Self(json.into())
247 }
248}
249
250#[derive(Debug, Serialize, Deserialize)]
254#[repr(transparent)]
255#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
256#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
257pub struct Entities(
258 #[cfg_attr(feature = "wasm", tsify(type = "Array<EntityJson>"))] JsonValueWithNoDuplicateKeys,
259);
260
261impl Entities {
262 pub fn parse(
269 self,
270 opt_schema: Option<&crate::Schema>,
271 ) -> Result<crate::Entities, miette::Report> {
272 crate::Entities::from_json_value(self.0.into(), opt_schema).map_err(Into::into)
273 }
274}
275
276#[doc(hidden)]
277impl From<serde_json::Value> for Entities {
278 fn from(json: serde_json::Value) -> Self {
279 Self(json.into())
280 }
281}
282
283#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
285#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
286#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
287#[serde(untagged)]
288#[serde(
289 expecting = "expected a static policy in the Cedar or JSON policy format (with no duplicate keys)"
290)]
291pub enum Policy {
292 Cedar(String),
294 Json(#[cfg_attr(feature = "wasm", tsify(type = "PolicyJson"))] JsonValueWithNoDuplicateKeys),
296}
297
298impl Policy {
299 pub(super) fn parse(self, id: Option<PolicyId>) -> Result<crate::Policy, miette::Report> {
303 let msg = id
304 .clone()
305 .map_or(String::new(), |id| format!(" with id `{id}`"));
306 match self {
307 Self::Cedar(str) => crate::Policy::parse(id, str)
308 .wrap_err(format!("failed to parse policy{msg} from string")),
309 Self::Json(json) => crate::Policy::from_json(id, json.into())
310 .wrap_err(format!("failed to parse policy{msg} from JSON")),
311 }
312 }
313
314 pub fn get_valid_request_envs(
321 self,
322 s: Schema,
323 ) -> Result<
324 (
325 impl Iterator<Item = String>,
326 impl Iterator<Item = String>,
327 impl Iterator<Item = String>,
328 ),
329 miette::Report,
330 > {
331 let t = self.parse(None)?;
332 let (s, _) = s.parse()?;
333 let mut principals = BTreeSet::new();
334 let mut actions = BTreeSet::new();
335 let mut resources = BTreeSet::new();
336 for env in t.get_valid_request_envs(&s) {
337 principals.insert(env.principal.to_string());
338 actions.insert(env.action.to_string());
339 resources.insert(env.resource.to_string());
340 }
341 Ok((
342 principals.into_iter(),
343 actions.into_iter(),
344 resources.into_iter(),
345 ))
346 }
347}
348
349#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
351#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
352#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
353#[serde(untagged)]
354#[serde(
355 expecting = "expected a policy template in the Cedar or JSON policy format (with no duplicate keys)"
356)]
357pub enum Template {
358 Cedar(String),
360 Json(#[cfg_attr(feature = "wasm", tsify(type = "PolicyJson"))] JsonValueWithNoDuplicateKeys),
362}
363
364impl Template {
365 pub(super) fn parse(self, id: Option<PolicyId>) -> Result<crate::Template, miette::Report> {
369 let msg = id
370 .clone()
371 .map(|id| format!(" with id `{id}`"))
372 .unwrap_or_default();
373 match self {
374 Self::Cedar(str) => crate::Template::parse(id, str)
375 .wrap_err(format!("failed to parse template{msg} from string")),
376 Self::Json(json) => crate::Template::from_json(id, json.into())
377 .wrap_err(format!("failed to parse template{msg} from JSON")),
378 }
379 }
380
381 pub(super) fn parse_and_add_to_set(
384 self,
385 id: Option<PolicyId>,
386 policies: &mut crate::PolicySet,
387 ) -> Result<(), miette::Report> {
388 let msg = id
389 .clone()
390 .map(|id| format!(" with id `{id}`"))
391 .unwrap_or_default();
392 let template = self.parse(id)?;
393 policies
394 .add_template(template)
395 .wrap_err(format!("failed to add template{msg} to policy set"))
396 }
397
398 pub fn get_valid_request_envs(
405 self,
406 s: Schema,
407 ) -> Result<
408 (
409 impl Iterator<Item = String>,
410 impl Iterator<Item = String>,
411 impl Iterator<Item = String>,
412 ),
413 miette::Report,
414 > {
415 let t = self.parse(None)?;
416 let (s, _) = s.parse()?;
417 let mut principals = BTreeSet::new();
418 let mut actions = BTreeSet::new();
419 let mut resources = BTreeSet::new();
420 for env in t.get_valid_request_envs(&s) {
421 principals.insert(env.principal.to_string());
422 actions.insert(env.action.to_string());
423 resources.insert(env.resource.to_string());
424 }
425 Ok((
426 principals.into_iter(),
427 actions.into_iter(),
428 resources.into_iter(),
429 ))
430 }
431}
432
433#[derive(Debug, Serialize, Deserialize)]
435#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
436#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
437#[serde(untagged)]
438#[serde(
439 expecting = "expected a static policy set represented by a string, JSON array, or JSON object (with no duplicate keys)"
440)]
441pub enum StaticPolicySet {
442 Concatenated(String),
445 Set(Vec<Policy>),
447 #[serde(with = "::serde_with::rust::maps_duplicate_key_is_error")]
449 Map(HashMap<PolicyId, Policy>),
450}
451
452impl StaticPolicySet {
453 pub(super) fn parse(self) -> Result<crate::PolicySet, Vec<miette::Report>> {
455 match self {
456 Self::Concatenated(str) => {
457 let policies = crate::PolicySet::from_str(&str)
458 .wrap_err("failed to parse policies from string")
459 .map_err(|e| vec![e])?;
460 if policies.templates().count() > 0 {
462 Err(vec![miette!("static policy set includes a template")])
463 } else {
464 Ok(policies)
465 }
466 }
467 Self::Set(set) => {
468 let mut errs = Vec::new();
469 let policies = set
470 .into_iter()
471 .map(|policy| policy.parse(None))
472 .filter_map(|r| r.map_err(|e| errs.push(e)).ok())
473 .collect::<Vec<_>>();
474 if errs.is_empty() {
475 crate::PolicySet::from_policies(policies).map_err(|e| vec![e.into()])
476 } else {
477 Err(errs)
478 }
479 }
480 Self::Map(map) => {
481 let mut errs = Vec::new();
482 let policies = map
483 .into_iter()
484 .map(|(id, policy)| policy.parse(Some(id)))
485 .filter_map(|r| r.map_err(|e| errs.push(e)).ok())
486 .collect::<Vec<_>>();
487 if errs.is_empty() {
488 crate::PolicySet::from_policies(policies).map_err(|e| vec![e.into()])
489 } else {
490 Err(errs)
491 }
492 }
493 }
494 }
495}
496
497impl Default for StaticPolicySet {
498 fn default() -> Self {
499 Self::Set(Vec::new())
500 }
501}
502
503#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
505#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
506#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
507#[serde(rename_all = "camelCase")]
508#[serde(deny_unknown_fields)]
509pub struct TemplateLink {
510 template_id: PolicyId,
512 new_id: PolicyId,
514 #[serde(with = "::serde_with::rust::maps_duplicate_key_is_error")]
516 values: HashMap<SlotId, EntityUid>,
517}
518
519impl TemplateLink {
520 pub(super) fn parse_and_add_to_set(
522 self,
523 policies: &mut crate::PolicySet,
524 ) -> Result<(), miette::Report> {
525 let values: HashMap<_, _> = self
526 .values
527 .into_iter()
528 .map(|(slot, euid)| euid.parse(None).map(|euid| (slot, euid)))
529 .collect::<Result<HashMap<_, _>, _>>()
530 .wrap_err("failed to parse link values")?;
531 policies
532 .link(self.template_id, self.new_id, values)
533 .map_err(miette::Report::new)
534 }
535}
536
537#[derive(Debug, Serialize, Deserialize)]
539#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
540#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
541#[serde(rename_all = "camelCase")]
542#[serde(deny_unknown_fields)]
543pub struct PolicySet {
544 #[serde(default)]
546 static_policies: StaticPolicySet,
547 #[serde(with = "::serde_with::rust::maps_duplicate_key_is_error")]
549 #[serde(default)]
550 templates: HashMap<PolicyId, Template>,
551 #[serde(default)]
553 template_links: Vec<TemplateLink>,
554}
555
556impl PolicySet {
557 pub fn parse(self) -> Result<crate::PolicySet, Vec<miette::Report>> {
564 let mut errs = Vec::new();
565 let mut policies = self.static_policies.parse().unwrap_or_else(|mut e| {
567 errs.append(&mut e);
568 crate::PolicySet::new()
569 });
570 self.templates.into_iter().for_each(|(id, template)| {
572 template
573 .parse_and_add_to_set(Some(id), &mut policies)
574 .unwrap_or_else(|e| errs.push(e));
575 });
576 self.template_links.into_iter().for_each(|link| {
578 link.parse_and_add_to_set(&mut policies)
579 .unwrap_or_else(|e| errs.push(e));
580 });
581 if !errs.is_empty() {
583 return Err(errs);
584 }
585 Ok(policies)
586 }
587}
588
589#[cfg(test)]
590impl PolicySet {
591 pub(super) fn new() -> Self {
593 Self {
594 static_policies: StaticPolicySet::Set(Vec::new()),
595 templates: HashMap::new(),
596 template_links: Vec::new(),
597 }
598 }
599}
600
601#[derive(Debug, Serialize, Deserialize)]
603#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
604#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
605#[serde(untagged)]
606#[serde(
607 expecting = "expected a schema in the Cedar or JSON policy format (with no duplicate keys)"
608)]
609pub enum Schema {
610 Cedar(String),
612 Json(
614 #[cfg_attr(feature = "wasm", tsify(type = "SchemaJson<string>"))]
615 JsonValueWithNoDuplicateKeys,
616 ),
617}
618
619impl Schema {
620 pub(super) fn parse(
622 self,
623 ) -> Result<(crate::Schema, Box<dyn Iterator<Item = SchemaWarning>>), miette::Report> {
624 let (schema_frag, warnings) = self.parse_schema_fragment()?;
625 Ok((schema_frag.try_into()?, warnings))
626 }
627
628 pub(super) fn parse_schema_fragment(
631 self,
632 ) -> Result<
633 (
634 crate::SchemaFragment,
635 Box<dyn Iterator<Item = SchemaWarning>>,
636 ),
637 miette::Report,
638 > {
639 match self {
640 Self::Cedar(str) => crate::SchemaFragment::from_cedarschema_str(&str)
641 .map(|(sch, warnings)| {
642 (
643 sch,
644 Box::new(warnings) as Box<dyn Iterator<Item = SchemaWarning>>,
645 )
646 })
647 .wrap_err("failed to parse schema from string"),
648 Self::Json(val) => crate::SchemaFragment::from_json_value(val.into())
649 .map(|sch| {
650 (
651 sch,
652 Box::new(std::iter::empty()) as Box<dyn Iterator<Item = SchemaWarning>>,
653 )
654 })
655 .wrap_err("failed to parse schema from JSON"),
656 }
657 }
658}
659
660pub(super) struct WithWarnings<T> {
661 pub t: T,
662 pub warnings: Vec<miette::Report>,
663}
664
665#[allow(clippy::panic, clippy::indexing_slicing)]
668#[allow(clippy::module_name_repetitions, clippy::missing_panics_doc)]
670#[cfg(test)]
671pub mod test_utils {
672 use super::*;
673
674 #[track_caller]
676 pub fn assert_error_matches(err: &DetailedError, msg: &str, help: Option<&str>) {
677 assert_eq!(err.message, msg, "did not see the expected error message");
678 assert_eq!(
679 err.help,
680 help.map(Into::into),
681 "did not see the expected help message"
682 );
683 }
684
685 #[track_caller]
687 pub fn assert_length_matches<T: std::fmt::Debug>(errs: &[T], n: usize) {
688 assert_eq!(
689 errs.len(),
690 n,
691 "expected {n} error(s) but saw {}",
692 errs.len()
693 );
694 }
695
696 #[track_caller]
699 pub fn assert_exactly_one_error(errs: &[DetailedError], msg: &str, help: Option<&str>) {
700 assert_length_matches(errs, 1);
701 assert_error_matches(&errs[0], msg, help);
702 }
703}
704
705#[allow(clippy::panic, clippy::indexing_slicing)]
707#[allow(clippy::too_many_lines)]
709#[cfg(test)]
710mod test {
711 use super::*;
712 use cedar_policy_core::test_utils::*;
713 use serde_json::json;
714 use test_utils::assert_length_matches;
715
716 #[test]
717 fn test_policy_parser() {
718 let policy_json = json!("permit(principal == User::\"alice\", action, resource);");
720 let policy: Policy =
721 serde_json::from_value(policy_json).expect("failed to parse from JSON");
722 policy.parse(None).expect("failed to convert to policy");
723
724 let policy_json = json!({
726 "effect": "permit",
727 "principal": {
728 "op": "==",
729 "entity": { "type": "User", "id": "alice" }
730 },
731 "action": {
732 "op": "All"
733 },
734 "resource": {
735 "op": "All"
736 },
737 "conditions": []
738 });
739 let policy: Policy =
740 serde_json::from_value(policy_json).expect("failed to parse from JSON");
741 policy.parse(None).expect("failed to convert to policy");
742
743 let src = "foo(principal == User::\"alice\", action, resource);";
745 let policy: Policy = serde_json::from_value(json!(src)).expect("failed to parse from JSON");
746 let err = policy
747 .parse(None)
748 .expect_err("should have failed to convert to policy");
749 expect_err(
750 src,
751 &err,
752 &ExpectedErrorMessageBuilder::error("failed to parse policy from string")
753 .source("invalid policy effect: foo")
754 .exactly_one_underline("foo")
755 .help("effect must be either `permit` or `forbid`")
756 .build(),
757 );
758
759 let src = "permit(principal == ?principal, action, resource);";
761 let policy: Policy =
762 serde_json::from_value(json!(src)).expect("failed to parse from string");
763 let err = policy
764 .parse(None)
765 .expect_err("should have failed to convert to policy");
766 expect_err(
767 src,
768 &err,
769 &ExpectedErrorMessageBuilder::error("failed to parse policy from string")
770 .source("expected a static policy, got a template containing the slot ?principal")
771 .exactly_one_underline("?principal")
772 .help("try removing the template slot(s) from this policy")
773 .build(),
774 );
775
776 let src = "permit(principal == User::\"alice\", action, resource); permit(principal == User::\"bob\", action, resource);";
778 let policy: Policy =
779 serde_json::from_value(json!(src)).expect("failed to parse from string");
780 let err = policy
781 .parse(None)
782 .expect_err("should have failed to convert to policy");
783 expect_err(
784 src,
785 &err,
786 &ExpectedErrorMessageBuilder::error("failed to parse policy from string")
787 .source("unexpected token `permit`")
788 .exactly_one_underline("permit")
789 .build(),
790 );
791
792 let policy_json_str = r#"{
795 "effect": "permit",
796 "effect": "forbid"
797 }"#;
798 let err = serde_json::from_str::<Policy>(policy_json_str)
799 .expect_err("should have failed to parse from JSON");
800 assert_eq!(
801 err.to_string(),
802 "expected a static policy in the Cedar or JSON policy format (with no duplicate keys)"
803 );
804 }
805
806 #[test]
807 fn test_template_parser() {
808 let template_json = json!("permit(principal == ?principal, action, resource);");
810 let template: Template =
811 serde_json::from_value(template_json).expect("failed to parse from JSON");
812 template.parse(None).expect("failed to convert to template");
813
814 let template_json = json!({
816 "effect": "permit",
817 "principal": {
818 "op": "==",
819 "slot": "?principal"
820 },
821 "action": {
822 "op": "All"
823 },
824 "resource": {
825 "op": "All"
826 },
827 "conditions": []
828 });
829 let template: Template =
830 serde_json::from_value(template_json).expect("failed to parse from JSON");
831 template.parse(None).expect("failed to convert to template");
832
833 let src = "permit(principal == ?foo, action, resource);";
835 let template: Template =
836 serde_json::from_value(json!(src)).expect("failed to parse from JSON");
837 let err = template
838 .parse(None)
839 .expect_err("should have failed to convert to template");
840 expect_err(
841 src,
842 &err,
843 &ExpectedErrorMessageBuilder::error("failed to parse template from string")
844 .source("expected an entity uid or matching template slot, found ?foo instead of ?principal")
845 .exactly_one_underline("?foo")
846 .build(),
847 );
848
849 let src = "permit(principal == User::\"alice\", action, resource);";
851 let template: Template =
852 serde_json::from_value(json!(src)).expect("failed to parse from JSON");
853 let err = template
854 .parse(None)
855 .expect_err("should have failed to convert to template");
856 expect_err(
857 src,
858 &err,
859 &ExpectedErrorMessageBuilder::error("failed to parse template from string")
860 .source("expected a template, got a static policy")
861 .help("a template should include slot(s) `?principal` or `?resource`")
862 .exactly_one_underline(src)
863 .build(),
864 );
865 }
866
867 #[test]
868 fn test_static_policy_set_parser() {
869 let policies_json = json!("permit(principal == User::\"alice\", action, resource);");
871 let policies: StaticPolicySet =
872 serde_json::from_value(policies_json).expect("failed to parse from JSON");
873 policies
874 .parse()
875 .expect("failed to convert to static policy set");
876
877 let policies_json = json!([
879 {
880 "effect": "permit",
881 "principal": {
882 "op": "==",
883 "entity": { "type": "User", "id": "alice" }
884 },
885 "action": {
886 "op": "All"
887 },
888 "resource": {
889 "op": "All"
890 },
891 "conditions": []
892 },
893 "permit(principal == User::\"bob\", action, resource);"
894 ]);
895 let policies: StaticPolicySet =
896 serde_json::from_value(policies_json).expect("failed to parse from JSON");
897 policies
898 .parse()
899 .expect("failed to convert to static policy set");
900
901 let policies_json = json!({
903 "policy0": {
904 "effect": "permit",
905 "principal": {
906 "op": "==",
907 "entity": { "type": "User", "id": "alice" }
908 },
909 "action": {
910 "op": "All"
911 },
912 "resource": {
913 "op": "All"
914 },
915 "conditions": []
916 },
917 "policy1": "permit(principal == User::\"bob\", action, resource);"
918 });
919 let policies: StaticPolicySet =
920 serde_json::from_value(policies_json).expect("failed to parse from JSON");
921 policies
922 .parse()
923 .expect("failed to convert to static policy set");
924
925 let policies_json = json!({
927 "policy0": "permit(principal == ?principal, action, resource);",
928 "policy1": "permit(principal == User::\"bob\", action, resource);"
929 });
930 let policies: StaticPolicySet =
931 serde_json::from_value(policies_json).expect("failed to parse from JSON");
932 let errs = policies
933 .parse()
934 .expect_err("should have failed to convert to static policy set");
935 assert_length_matches(&errs, 1);
936 expect_err(
937 "permit(principal == ?principal, action, resource);",
938 &errs[0],
939 &ExpectedErrorMessageBuilder::error(
940 "failed to parse policy with id `policy0` from string",
941 )
942 .source("expected a static policy, got a template containing the slot ?principal")
943 .exactly_one_underline("?principal")
944 .help("try removing the template slot(s) from this policy")
945 .build(),
946 );
947
948 let policies_json = json!(
950 "
951 permit(principal == User::\"alice\", action, resource);
952 permit(principal == ?principal, action, resource);
953 "
954 );
955 let policies: StaticPolicySet =
956 serde_json::from_value(policies_json).expect("failed to parse from JSON");
957 let errs = policies
958 .parse()
959 .expect_err("should have failed to convert to static policy set");
960 assert_length_matches(&errs, 1);
961 expect_err(
962 "permit(principal == ?principal, action, resource);",
963 &errs[0],
964 &ExpectedErrorMessageBuilder::error("static policy set includes a template").build(),
965 );
966
967 let policies_json = json!({
969 "policy0": "permit(principal == User::\"alice\", action, resource);",
970 "policy1": "permit(principal == User::\"bob\", action, resource); permit(principal, action, resource);"
971 });
972 let policies: StaticPolicySet =
973 serde_json::from_value(policies_json).expect("failed to parse from JSON");
974 let errs = policies
975 .parse()
976 .expect_err("should have failed to convert to static policy set");
977 assert_length_matches(&errs, 1);
978 expect_err(
979 "permit(principal == User::\"bob\", action, resource); permit(principal, action, resource);",
980 &errs[0],
981 &ExpectedErrorMessageBuilder::error(
982 "failed to parse policy with id `policy1` from string",
983 )
984 .source("unexpected token `permit`")
985 .exactly_one_underline("permit")
986 .build(),
987 );
988
989 let policies_json = json!({
991 "policy0": "permit(principal, action);",
992 "policy1": "forbid(principal, action);"
993 });
994 let policies: StaticPolicySet =
995 serde_json::from_value(policies_json).expect("failed to parse from JSON");
996 let errs = policies
997 .parse()
998 .expect_err("should have failed to convert to static policy set");
999 assert_length_matches(&errs, 2);
1000 for err in errs {
1001 if err
1003 .to_string()
1004 .contains("failed to parse policy with id `policy0`")
1005 {
1006 expect_err(
1007 "permit(principal, action);",
1008 &err,
1009 &ExpectedErrorMessageBuilder::error(
1010 "failed to parse policy with id `policy0` from string",
1011 )
1012 .source("this policy is missing the `resource` variable in the scope")
1013 .exactly_one_underline("")
1014 .help("policy scopes must contain a `principal`, `action`, and `resource` element in that order")
1015 .build(),
1016 );
1017 } else {
1018 expect_err(
1019 "forbid(principal, action);",
1020 &err,
1021 &ExpectedErrorMessageBuilder::error(
1022 "failed to parse policy with id `policy1` from string",
1023 )
1024 .source("this policy is missing the `resource` variable in the scope")
1025 .exactly_one_underline("")
1026 .help("policy scopes must contain a `principal`, `action`, and `resource` element in that order")
1027 .build(),
1028 );
1029 }
1030 }
1031 }
1032
1033 #[test]
1034 fn test_policy_set_parser() {
1035 let policies_json = json!({});
1037 let policies: PolicySet =
1038 serde_json::from_value(policies_json).expect("failed to parse from JSON");
1039 policies.parse().expect("failed to convert to policy set");
1040
1041 let policies_json = json!({
1043 "staticPolicies": [
1044 {
1045 "effect": "permit",
1046 "principal": {
1047 "op": "==",
1048 "entity": { "type": "User", "id": "alice" }
1049 },
1050 "action": {
1051 "op": "All"
1052 },
1053 "resource": {
1054 "op": "All"
1055 },
1056 "conditions": []
1057 },
1058 "permit(principal == User::\"bob\", action, resource);"
1059 ],
1060 "templates": {
1061 "ID0": "permit(principal == ?principal, action, resource);"
1062 },
1063 "templateLinks": [
1064 {
1065 "templateId": "ID0",
1066 "newId": "ID1",
1067 "values": { "?principal": { "type": "User", "id": "charlie" } }
1068 }
1069 ]
1070 });
1071 let policies: PolicySet =
1072 serde_json::from_value(policies_json).expect("failed to parse from JSON");
1073 policies.parse().expect("failed to convert to policy set");
1074
1075 let policies_json = json!({
1077 "staticPolicies": {
1078 "policy0": "permit(principal == User::\"alice\", action, resource);",
1079 "policy1": "permit(principal == User::\"bob\", action, resource);"
1080 },
1081 "templates": {
1082 "template": "permit(principal == ?principal, action, resource);"
1083 },
1084 "templateLinks": [
1085 {
1086 "templateId": "template",
1087 "newId": "policy0",
1088 "values": { "?principal": { "type": "User", "id": "charlie" } }
1089 }
1090 ]
1091 });
1092 let policies: PolicySet =
1093 serde_json::from_value(policies_json).expect("failed to parse from JSON");
1094 let errs = policies
1095 .parse()
1096 .expect_err("should have failed to convert to policy set");
1097 assert_length_matches(&errs, 1);
1098 expect_err(
1099 "",
1100 &errs[0],
1101 &ExpectedErrorMessageBuilder::error("unable to link template")
1102 .source("template-linked policy id `policy0` conflicts with an existing policy id")
1103 .build(),
1104 );
1105 }
1106
1107 #[test]
1108 fn policy_set_parser_is_compatible_with_est_parser() {
1109 let json = json!({
1111 "staticPolicies": {
1112 "policy1": {
1113 "effect": "permit",
1114 "principal": {
1115 "op": "==",
1116 "entity": { "type": "User", "id": "alice" }
1117 },
1118 "action": {
1119 "op": "==",
1120 "entity": { "type": "Action", "id": "view" }
1121 },
1122 "resource": {
1123 "op": "in",
1124 "entity": { "type": "Folder", "id": "foo" }
1125 },
1126 "conditions": []
1127 }
1128 },
1129 "templates": {
1130 "template": {
1131 "effect" : "permit",
1132 "principal" : {
1133 "op" : "==",
1134 "slot" : "?principal"
1135 },
1136 "action" : {
1137 "op" : "all"
1138 },
1139 "resource" : {
1140 "op" : "all",
1141 },
1142 "conditions": []
1143 }
1144 },
1145 "templateLinks" : [
1146 {
1147 "newId" : "link",
1148 "templateId" : "template",
1149 "values" : {
1150 "?principal" : { "type" : "User", "id" : "bob" }
1151 }
1152 }
1153 ]
1154 });
1155
1156 let ast_from_est = crate::PolicySet::from_json_value(json.clone())
1158 .expect("failed to convert to policy set");
1159
1160 let ffi_policy_set: PolicySet =
1162 serde_json::from_value(json).expect("failed to parse from JSON");
1163 let ast_from_ffi = ffi_policy_set
1164 .parse()
1165 .expect("failed to convert to policy set");
1166
1167 assert_eq!(ast_from_est, ast_from_ffi);
1169 }
1170
1171 #[test]
1172 fn test_schema_parser() {
1173 let schema_json = json!("entity User = {name: String};\nentity Photo;\naction viewPhoto appliesTo {principal: User, resource: Photo};");
1175 let schema: Schema =
1176 serde_json::from_value(schema_json).expect("failed to parse from JSON");
1177 let _ = schema.parse().expect("failed to convert to schema");
1178
1179 let schema_json = json!({
1181 "": {
1182 "entityTypes": {
1183 "User": {
1184 "shape": {
1185 "type": "Record",
1186 "attributes": {
1187 "name": {
1188 "type": "String"
1189 }
1190 }
1191 }
1192 },
1193 "Photo": {}
1194 },
1195 "actions": {
1196 "viewPhoto": {
1197 "appliesTo": {
1198 "principalTypes": [ "User" ],
1199 "resourceTypes": [ "Photo" ]
1200 }
1201 }
1202 }
1203 }
1204 });
1205 let schema: Schema =
1206 serde_json::from_value(schema_json).expect("failed to parse from JSON");
1207 let _ = schema.parse().expect("failed to convert to schema");
1208
1209 let src = "permit(principal == User::\"alice\", action, resource);";
1211 let schema: Schema = serde_json::from_value(json!(src)).expect("failed to parse from JSON");
1212 let err = schema
1213 .parse()
1214 .map(|(s, _)| s)
1215 .expect_err("should have failed to convert to schema");
1216 expect_err(
1217 src,
1218 &err,
1219 &ExpectedErrorMessageBuilder::error("failed to parse schema from string")
1220 .exactly_one_underline_with_label(
1221 "permit",
1222 "expected `@`, `action`, `entity`, `namespace`, or `type`",
1223 )
1224 .source("error parsing schema: unexpected token `permit`")
1225 .build(),
1226 );
1227 }
1228
1229 #[test]
1230 fn test_detailed_err_from_str() {
1231 let detailed_err = DetailedError::from_str("xxx");
1232 assert_eq!(detailed_err.unwrap().message, "xxx");
1233 }
1234}