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