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