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(super) 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 #[cfg(test)]
569 pub(super) fn new() -> Self {
570 Self {
571 static_policies: StaticPolicySet::Set(Vec::new()),
572 templates: HashMap::new(),
573 template_links: Vec::new(),
574 }
575 }
576}
577
578#[derive(Debug, Serialize, Deserialize)]
580#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
581#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
582#[serde(untagged)]
583#[serde(
584 expecting = "expected a schema in the Cedar or JSON policy format (with no duplicate keys)"
585)]
586pub enum Schema {
587 Cedar(String),
589 Json(
591 #[cfg_attr(feature = "wasm", tsify(type = "SchemaJson<string>"))]
592 JsonValueWithNoDuplicateKeys,
593 ),
594}
595
596impl Schema {
597 pub(super) fn parse(
599 self,
600 ) -> Result<(crate::Schema, Box<dyn Iterator<Item = SchemaWarning>>), miette::Report> {
601 let (schema_frag, warnings) = self.parse_schema_fragment()?;
602 Ok((schema_frag.try_into()?, warnings))
603 }
604
605 pub(super) fn parse_schema_fragment(
608 self,
609 ) -> Result<
610 (
611 crate::SchemaFragment,
612 Box<dyn Iterator<Item = SchemaWarning>>,
613 ),
614 miette::Report,
615 > {
616 match self {
617 Self::Cedar(str) => crate::SchemaFragment::from_cedarschema_str(&str)
618 .map(|(sch, warnings)| {
619 (
620 sch,
621 Box::new(warnings) as Box<dyn Iterator<Item = SchemaWarning>>,
622 )
623 })
624 .wrap_err("failed to parse schema from string"),
625 Self::Json(val) => crate::SchemaFragment::from_json_value(val.into())
626 .map(|sch| {
627 (
628 sch,
629 Box::new(std::iter::empty()) as Box<dyn Iterator<Item = SchemaWarning>>,
630 )
631 })
632 .wrap_err("failed to parse schema from JSON"),
633 }
634 }
635}
636
637pub(super) struct WithWarnings<T> {
638 pub t: T,
639 pub warnings: Vec<miette::Report>,
640}
641
642#[allow(clippy::panic, clippy::indexing_slicing)]
645#[allow(clippy::module_name_repetitions, clippy::missing_panics_doc)]
647#[cfg(test)]
648pub mod test_utils {
649 use super::*;
650
651 #[track_caller]
653 pub fn assert_error_matches(err: &DetailedError, msg: &str, help: Option<&str>) {
654 assert_eq!(err.message, msg, "did not see the expected error message");
655 assert_eq!(
656 err.help,
657 help.map(Into::into),
658 "did not see the expected help message"
659 );
660 }
661
662 #[track_caller]
664 pub fn assert_length_matches<T: std::fmt::Debug>(errs: &[T], n: usize) {
665 assert_eq!(
666 errs.len(),
667 n,
668 "expected {n} error(s) but saw {}",
669 errs.len()
670 );
671 }
672
673 #[track_caller]
676 pub fn assert_exactly_one_error(errs: &[DetailedError], msg: &str, help: Option<&str>) {
677 assert_length_matches(errs, 1);
678 assert_error_matches(&errs[0], msg, help);
679 }
680}
681
682#[allow(clippy::panic, clippy::indexing_slicing)]
684#[allow(clippy::too_many_lines)]
686#[cfg(test)]
687mod test {
688 use super::*;
689 use cedar_policy_core::test_utils::*;
690 use serde_json::json;
691 use test_utils::assert_length_matches;
692
693 #[test]
694 fn test_policy_parser() {
695 let policy_json = json!("permit(principal == User::\"alice\", action, resource);");
697 let policy: Policy =
698 serde_json::from_value(policy_json).expect("failed to parse from JSON");
699 policy.parse(None).expect("failed to convert to policy");
700
701 let policy_json = json!({
703 "effect": "permit",
704 "principal": {
705 "op": "==",
706 "entity": { "type": "User", "id": "alice" }
707 },
708 "action": {
709 "op": "All"
710 },
711 "resource": {
712 "op": "All"
713 },
714 "conditions": []
715 });
716 let policy: Policy =
717 serde_json::from_value(policy_json).expect("failed to parse from JSON");
718 policy.parse(None).expect("failed to convert to policy");
719
720 let src = "foo(principal == User::\"alice\", action, resource);";
722 let policy: Policy = serde_json::from_value(json!(src)).expect("failed to parse from JSON");
723 let err = policy
724 .parse(None)
725 .expect_err("should have failed to convert to policy");
726 expect_err(
727 src,
728 &err,
729 &ExpectedErrorMessageBuilder::error("failed to parse policy from string")
730 .source("invalid policy effect: foo")
731 .exactly_one_underline("foo")
732 .help("effect must be either `permit` or `forbid`")
733 .build(),
734 );
735
736 let src = "permit(principal == ?principal, action, resource);";
738 let policy: Policy =
739 serde_json::from_value(json!(src)).expect("failed to parse from string");
740 let err = policy
741 .parse(None)
742 .expect_err("should have failed to convert to policy");
743 expect_err(
744 src,
745 &err,
746 &ExpectedErrorMessageBuilder::error("failed to parse policy from string")
747 .source("expected a static policy, got a template containing the slot ?principal")
748 .exactly_one_underline("?principal")
749 .help("try removing the template slot(s) from this policy")
750 .build(),
751 );
752
753 let src = "permit(principal == User::\"alice\", action, resource); permit(principal == User::\"bob\", action, resource);";
755 let policy: Policy =
756 serde_json::from_value(json!(src)).expect("failed to parse from string");
757 let err = policy
758 .parse(None)
759 .expect_err("should have failed to convert to policy");
760 expect_err(
761 src,
762 &err,
763 &ExpectedErrorMessageBuilder::error("failed to parse policy from string")
764 .source("unexpected token `permit`")
765 .exactly_one_underline("permit")
766 .build(),
767 );
768
769 let policy_json_str = r#"{
772 "effect": "permit",
773 "effect": "forbid"
774 }"#;
775 let err = serde_json::from_str::<Policy>(policy_json_str)
776 .expect_err("should have failed to parse from JSON");
777 assert_eq!(
778 err.to_string(),
779 "expected a static policy in the Cedar or JSON policy format (with no duplicate keys)"
780 );
781 }
782
783 #[test]
784 fn test_template_parser() {
785 let template_json = json!("permit(principal == ?principal, action, resource);");
787 let template: Template =
788 serde_json::from_value(template_json).expect("failed to parse from JSON");
789 template.parse(None).expect("failed to convert to template");
790
791 let template_json = json!({
793 "effect": "permit",
794 "principal": {
795 "op": "==",
796 "slot": "?principal"
797 },
798 "action": {
799 "op": "All"
800 },
801 "resource": {
802 "op": "All"
803 },
804 "conditions": []
805 });
806 let template: Template =
807 serde_json::from_value(template_json).expect("failed to parse from JSON");
808 template.parse(None).expect("failed to convert to template");
809
810 let src = "permit(principal == ?foo, action, resource);";
812 let template: Template =
813 serde_json::from_value(json!(src)).expect("failed to parse from JSON");
814 let err = template
815 .parse(None)
816 .expect_err("should have failed to convert to template");
817 expect_err(
818 src,
819 &err,
820 &ExpectedErrorMessageBuilder::error("failed to parse template from string")
821 .source("expected an entity uid or matching template slot, found ?foo instead of ?principal")
822 .exactly_one_underline("?foo")
823 .build(),
824 );
825
826 let src = "permit(principal == User::\"alice\", action, resource);";
828 let template: Template =
829 serde_json::from_value(json!(src)).expect("failed to parse from JSON");
830 let err = template
831 .parse(None)
832 .expect_err("should have failed to convert to template");
833 expect_err(
834 src,
835 &err,
836 &ExpectedErrorMessageBuilder::error("failed to parse template from string")
837 .source("expected a template, got a static policy")
838 .help("a template should include slot(s) `?principal` or `?resource`")
839 .exactly_one_underline(src)
840 .build(),
841 );
842 }
843
844 #[test]
845 fn test_static_policy_set_parser() {
846 let policies_json = json!("permit(principal == User::\"alice\", action, resource);");
848 let policies: StaticPolicySet =
849 serde_json::from_value(policies_json).expect("failed to parse from JSON");
850 policies
851 .parse()
852 .expect("failed to convert to static policy set");
853
854 let policies_json = json!([
856 {
857 "effect": "permit",
858 "principal": {
859 "op": "==",
860 "entity": { "type": "User", "id": "alice" }
861 },
862 "action": {
863 "op": "All"
864 },
865 "resource": {
866 "op": "All"
867 },
868 "conditions": []
869 },
870 "permit(principal == User::\"bob\", action, resource);"
871 ]);
872 let policies: StaticPolicySet =
873 serde_json::from_value(policies_json).expect("failed to parse from JSON");
874 policies
875 .parse()
876 .expect("failed to convert to static policy set");
877
878 let policies_json = json!({
880 "policy0": {
881 "effect": "permit",
882 "principal": {
883 "op": "==",
884 "entity": { "type": "User", "id": "alice" }
885 },
886 "action": {
887 "op": "All"
888 },
889 "resource": {
890 "op": "All"
891 },
892 "conditions": []
893 },
894 "policy1": "permit(principal == User::\"bob\", action, resource);"
895 });
896 let policies: StaticPolicySet =
897 serde_json::from_value(policies_json).expect("failed to parse from JSON");
898 policies
899 .parse()
900 .expect("failed to convert to static policy set");
901
902 let policies_json = json!({
904 "policy0": "permit(principal == ?principal, action, resource);",
905 "policy1": "permit(principal == User::\"bob\", action, resource);"
906 });
907 let policies: StaticPolicySet =
908 serde_json::from_value(policies_json).expect("failed to parse from JSON");
909 let errs = policies
910 .parse()
911 .expect_err("should have failed to convert to static policy set");
912 assert_length_matches(&errs, 1);
913 expect_err(
914 "permit(principal == ?principal, action, resource);",
915 &errs[0],
916 &ExpectedErrorMessageBuilder::error(
917 "failed to parse policy with id `policy0` from string",
918 )
919 .source("expected a static policy, got a template containing the slot ?principal")
920 .exactly_one_underline("?principal")
921 .help("try removing the template slot(s) from this policy")
922 .build(),
923 );
924
925 let policies_json = json!(
927 "
928 permit(principal == User::\"alice\", action, resource);
929 permit(principal == ?principal, action, resource);
930 "
931 );
932 let policies: StaticPolicySet =
933 serde_json::from_value(policies_json).expect("failed to parse from JSON");
934 let errs = policies
935 .parse()
936 .expect_err("should have failed to convert to static policy set");
937 assert_length_matches(&errs, 1);
938 expect_err(
939 "permit(principal == ?principal, action, resource);",
940 &errs[0],
941 &ExpectedErrorMessageBuilder::error("static policy set includes a template").build(),
942 );
943
944 let policies_json = json!({
946 "policy0": "permit(principal == User::\"alice\", action, resource);",
947 "policy1": "permit(principal == User::\"bob\", action, resource); permit(principal, action, resource);"
948 });
949 let policies: StaticPolicySet =
950 serde_json::from_value(policies_json).expect("failed to parse from JSON");
951 let errs = policies
952 .parse()
953 .expect_err("should have failed to convert to static policy set");
954 assert_length_matches(&errs, 1);
955 expect_err(
956 "permit(principal == User::\"bob\", action, resource); permit(principal, action, resource);",
957 &errs[0],
958 &ExpectedErrorMessageBuilder::error(
959 "failed to parse policy with id `policy1` from string",
960 )
961 .source("unexpected token `permit`")
962 .exactly_one_underline("permit")
963 .build(),
964 );
965
966 let policies_json = json!({
968 "policy0": "permit(principal, action);",
969 "policy1": "forbid(principal, action);"
970 });
971 let policies: StaticPolicySet =
972 serde_json::from_value(policies_json).expect("failed to parse from JSON");
973 let errs = policies
974 .parse()
975 .expect_err("should have failed to convert to static policy set");
976 assert_length_matches(&errs, 2);
977 for err in errs {
978 if err
980 .to_string()
981 .contains("failed to parse policy with id `policy0`")
982 {
983 expect_err(
984 "permit(principal, action);",
985 &err,
986 &ExpectedErrorMessageBuilder::error(
987 "failed to parse policy with id `policy0` from string",
988 )
989 .source("this policy is missing the `resource` variable in the scope")
990 .exactly_one_underline("")
991 .help("policy scopes must contain a `principal`, `action`, and `resource` element in that order")
992 .build(),
993 );
994 } else {
995 expect_err(
996 "forbid(principal, action);",
997 &err,
998 &ExpectedErrorMessageBuilder::error(
999 "failed to parse policy with id `policy1` from string",
1000 )
1001 .source("this policy is missing the `resource` variable in the scope")
1002 .exactly_one_underline("")
1003 .help("policy scopes must contain a `principal`, `action`, and `resource` element in that order")
1004 .build(),
1005 );
1006 }
1007 }
1008 }
1009
1010 #[test]
1011 fn test_policy_set_parser() {
1012 let policies_json = json!({});
1014 let policies: PolicySet =
1015 serde_json::from_value(policies_json).expect("failed to parse from JSON");
1016 policies.parse().expect("failed to convert to policy set");
1017
1018 let policies_json = json!({
1020 "staticPolicies": [
1021 {
1022 "effect": "permit",
1023 "principal": {
1024 "op": "==",
1025 "entity": { "type": "User", "id": "alice" }
1026 },
1027 "action": {
1028 "op": "All"
1029 },
1030 "resource": {
1031 "op": "All"
1032 },
1033 "conditions": []
1034 },
1035 "permit(principal == User::\"bob\", action, resource);"
1036 ],
1037 "templates": {
1038 "ID0": "permit(principal == ?principal, action, resource);"
1039 },
1040 "templateLinks": [
1041 {
1042 "templateId": "ID0",
1043 "newId": "ID1",
1044 "values": { "?principal": { "type": "User", "id": "charlie" } }
1045 }
1046 ]
1047 });
1048 let policies: PolicySet =
1049 serde_json::from_value(policies_json).expect("failed to parse from JSON");
1050 policies.parse().expect("failed to convert to policy set");
1051
1052 let policies_json = json!({
1054 "staticPolicies": {
1055 "policy0": "permit(principal == User::\"alice\", action, resource);",
1056 "policy1": "permit(principal == User::\"bob\", action, resource);"
1057 },
1058 "templates": {
1059 "template": "permit(principal == ?principal, action, resource);"
1060 },
1061 "templateLinks": [
1062 {
1063 "templateId": "template",
1064 "newId": "policy0",
1065 "values": { "?principal": { "type": "User", "id": "charlie" } }
1066 }
1067 ]
1068 });
1069 let policies: PolicySet =
1070 serde_json::from_value(policies_json).expect("failed to parse from JSON");
1071 let errs = policies
1072 .parse()
1073 .expect_err("should have failed to convert to policy set");
1074 assert_length_matches(&errs, 1);
1075 expect_err(
1076 "",
1077 &errs[0],
1078 &ExpectedErrorMessageBuilder::error("unable to link template")
1079 .source("template-linked policy id `policy0` conflicts with an existing policy id")
1080 .build(),
1081 );
1082 }
1083
1084 #[test]
1085 fn policy_set_parser_is_compatible_with_est_parser() {
1086 let json = json!({
1088 "staticPolicies": {
1089 "policy1": {
1090 "effect": "permit",
1091 "principal": {
1092 "op": "==",
1093 "entity": { "type": "User", "id": "alice" }
1094 },
1095 "action": {
1096 "op": "==",
1097 "entity": { "type": "Action", "id": "view" }
1098 },
1099 "resource": {
1100 "op": "in",
1101 "entity": { "type": "Folder", "id": "foo" }
1102 },
1103 "conditions": []
1104 }
1105 },
1106 "templates": {
1107 "template": {
1108 "effect" : "permit",
1109 "principal" : {
1110 "op" : "==",
1111 "slot" : "?principal"
1112 },
1113 "action" : {
1114 "op" : "all"
1115 },
1116 "resource" : {
1117 "op" : "all",
1118 },
1119 "conditions": []
1120 }
1121 },
1122 "templateLinks" : [
1123 {
1124 "newId" : "link",
1125 "templateId" : "template",
1126 "values" : {
1127 "?principal" : { "type" : "User", "id" : "bob" }
1128 }
1129 }
1130 ]
1131 });
1132
1133 let ast_from_est = crate::PolicySet::from_json_value(json.clone())
1135 .expect("failed to convert to policy set");
1136
1137 let ffi_policy_set: PolicySet =
1139 serde_json::from_value(json).expect("failed to parse from JSON");
1140 let ast_from_ffi = ffi_policy_set
1141 .parse()
1142 .expect("failed to convert to policy set");
1143
1144 assert_eq!(ast_from_est, ast_from_ffi);
1146 }
1147
1148 #[test]
1149 fn test_schema_parser() {
1150 let schema_json = json!("entity User = {name: String};\nentity Photo;\naction viewPhoto appliesTo {principal: User, resource: Photo};");
1152 let schema: Schema =
1153 serde_json::from_value(schema_json).expect("failed to parse from JSON");
1154 let _ = schema.parse().expect("failed to convert to schema");
1155
1156 let schema_json = json!({
1158 "": {
1159 "entityTypes": {
1160 "User": {
1161 "shape": {
1162 "type": "Record",
1163 "attributes": {
1164 "name": {
1165 "type": "String"
1166 }
1167 }
1168 }
1169 },
1170 "Photo": {}
1171 },
1172 "actions": {
1173 "viewPhoto": {
1174 "appliesTo": {
1175 "principalTypes": [ "User" ],
1176 "resourceTypes": [ "Photo" ]
1177 }
1178 }
1179 }
1180 }
1181 });
1182 let schema: Schema =
1183 serde_json::from_value(schema_json).expect("failed to parse from JSON");
1184 let _ = schema.parse().expect("failed to convert to schema");
1185
1186 let src = "permit(principal == User::\"alice\", action, resource);";
1188 let schema: Schema = serde_json::from_value(json!(src)).expect("failed to parse from JSON");
1189 let err = schema
1190 .parse()
1191 .map(|(s, _)| s)
1192 .expect_err("should have failed to convert to schema");
1193 expect_err(
1194 src,
1195 &err,
1196 &ExpectedErrorMessageBuilder::error("failed to parse schema from string")
1197 .exactly_one_underline_with_label(
1198 "permit",
1199 "expected `@`, `action`, `entity`, `namespace`, or `type`",
1200 )
1201 .source("error parsing schema: unexpected token `permit`")
1202 .build(),
1203 );
1204 }
1205}