|
16 | 16 | use crate::util::{AnalyzePolicyFindingsSer, OpenRequestEnv}; |
17 | 17 | use crate::{err::ExecError, util::RequestEnvSer}; |
18 | 18 | use cedar_lean_ffi::CedarLeanFfi; |
19 | | -use cedar_policy::{Effect, Policy, PolicyId, PolicySet, RequestEnv, Schema}; |
| 19 | +use cedar_policy::{Effect, Policy, PolicyId, PolicySet, RequestEnv, RestrictedExpression, Schema}; |
20 | 20 | use itertools::Itertools; |
| 21 | +use nonempty::NonEmpty; |
21 | 22 | use prettytable::{Attr, Cell, Row, Table}; |
22 | 23 | use serde::Serialize; |
23 | 24 | use std::{ |
@@ -491,10 +492,14 @@ fn vacuity_result( |
491 | 492 | /// Represents if policy1 is shadowed by policy2 or vice versa |
492 | 493 | #[derive(Clone, Copy, Debug, PartialEq)] |
493 | 494 | enum ShadowingResult { |
494 | | - Equivalent, // policies are non vacuous and allow the same requests |
495 | | - Policy1Shadows2, // policies are non vacuous and policy1 allows strictly more requests than policy2 |
496 | | - Policy2Shadows1, // policies are non vacuous and policy2 allows strictly more requests than policy1 |
497 | | - NoResult, // at least one policy is vacuous, or the policies allow incomparable sets of requests |
| 495 | + /// policies are non vacuous and allow the same requests |
| 496 | + Equivalent, |
| 497 | + /// policies are non vacuous and policy1 allows strictly more requests than policy2 |
| 498 | + Policy1Shadows2, |
| 499 | + /// policies are non vacuous and policy2 allows strictly more requests than policy1 |
| 500 | + Policy2Shadows1, |
| 501 | + /// at least one policy is vacuous, or the policies allow incomparable sets of requests |
| 502 | + NoResult, |
498 | 503 | } |
499 | 504 |
|
500 | 505 | /// Compute Redudant and Shadowed relationship between `policy1` and `policy2` (per environment) |
@@ -698,26 +703,142 @@ fn update_findings<T>( |
698 | 703 | } |
699 | 704 | } |
700 | 705 |
|
| 706 | +fn display_value(f: &mut std::fmt::Formatter<'_>, v: &cedar_lean_ffi::Value) -> std::fmt::Result { |
| 707 | + let rexpr = RestrictedExpression::try_from(v.clone()).unwrap(); |
| 708 | + let core_rexpr: &cedar_policy_core::ast::RestrictedExpr = rexpr.as_ref(); |
| 709 | + write!(f, "{core_rexpr}") |
| 710 | +} |
| 711 | + |
| 712 | +/// `prefix`: printed at the beginning of each line, including the first |
| 713 | +fn display_entity( |
| 714 | + f: &mut std::fmt::Formatter<'_>, |
| 715 | + uid: &cedar_lean_ffi::EntityUid, |
| 716 | + edata: &cedar_lean_ffi::EntityData, |
| 717 | + prefix: &str, |
| 718 | +) -> std::fmt::Result { |
| 719 | + write!(f, "{prefix}{uid}")?; |
| 720 | + if let Some(ancs) = NonEmpty::collect(edata.ancestors.iter()) { |
| 721 | + write!(f, " in [{ancs}]", ancs = ancs.iter().join(", "))?; |
| 722 | + } |
| 723 | + if let Some(attrs) = NonEmpty::collect(edata.attrs.iter()) { |
| 724 | + write!(f, " {{\n")?; |
| 725 | + for (k, v) in attrs.iter() { |
| 726 | + if cedar_policy_core::ast::is_normalized_ident(k) { |
| 727 | + write!(f, "{prefix} {k}: ")?; |
| 728 | + } else { |
| 729 | + write!(f, "{prefix} \"{}\": ", k.escape_debug())?; |
| 730 | + } |
| 731 | + display_value(f, v)?; |
| 732 | + write!(f, ",\n")?; |
| 733 | + } |
| 734 | + write!(f, "{prefix}}}")?; |
| 735 | + } |
| 736 | + if let Some(tags) = NonEmpty::collect(edata.tags.iter()) { |
| 737 | + write!(f, " tags {{\n")?; |
| 738 | + for (k, v) in tags.iter() { |
| 739 | + if cedar_policy_core::ast::is_normalized_ident(k) { |
| 740 | + write!(f, "{prefix} {k}: ")?; |
| 741 | + } else { |
| 742 | + write!(f, "{prefix} \"{}\": ", k.escape_debug())?; |
| 743 | + } |
| 744 | + display_value(f, v)?; |
| 745 | + write!(f, ",\n")?; |
| 746 | + } |
| 747 | + write!(f, "{prefix}}}")?; |
| 748 | + } |
| 749 | + Ok(()) |
| 750 | +} |
| 751 | + |
| 752 | +#[derive(Debug, Clone, Serialize)] |
| 753 | +#[serde(transparent)] |
| 754 | +struct ExampleEnv(cedar_lean_ffi::Env); |
| 755 | + |
| 756 | +impl std::fmt::Display for ExampleEnv { |
| 757 | + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| 758 | + write!( |
| 759 | + f, |
| 760 | + "principal: {p}, action: {a}, resource: {r}", |
| 761 | + p = self.0.request.principal, |
| 762 | + a = self.0.request.action, |
| 763 | + r = self.0.request.resource, |
| 764 | + )?; |
| 765 | + match self.0.request.context.len() { |
| 766 | + 0 => (), |
| 767 | + 1 => { |
| 768 | + let (k, v) = self.0.request.context.first().unwrap(); |
| 769 | + if cedar_policy_core::ast::is_normalized_ident(k) { |
| 770 | + write!(f, "\ncontext: {{ {k}: ")?; |
| 771 | + } else { |
| 772 | + write!(f, "\ncontext: {{ \"{}\": ", k.escape_debug())?; |
| 773 | + } |
| 774 | + display_value(f, v)?; |
| 775 | + write!(f, " }}")?; |
| 776 | + } |
| 777 | + _ => { |
| 778 | + writeln!(f, "\ncontext: {{")?; |
| 779 | + for (k, v) in self.0.request.context.iter() { |
| 780 | + if cedar_policy_core::ast::is_normalized_ident(k) { |
| 781 | + write!(f, " {k}: ")?; |
| 782 | + } else { |
| 783 | + write!(f, " \"{}\": ", k.escape_debug())?; |
| 784 | + } |
| 785 | + display_value(f, v)?; |
| 786 | + write!(f, ",\n")?; |
| 787 | + } |
| 788 | + write!(f, "}}")?; |
| 789 | + } |
| 790 | + } |
| 791 | + match self.0.entities.len() { |
| 792 | + 0 => (), |
| 793 | + _ => { |
| 794 | + writeln!(f, "\nentities: [")?; |
| 795 | + for (uid, edata) in self.0.entities.iter() { |
| 796 | + display_entity(f, uid, edata, " ")?; |
| 797 | + writeln!(f, ",")?; |
| 798 | + } |
| 799 | + write!(f, "]")?; |
| 800 | + } |
| 801 | + } |
| 802 | + writeln!(f) |
| 803 | + } |
| 804 | +} |
| 805 | + |
701 | 806 | #[derive(Debug, Clone, Serialize)] |
702 | 807 | enum PolicySetComparisonStatus { |
703 | | - MorePermissive, |
704 | | - LessPermissive, |
| 808 | + MorePermissive { |
| 809 | + /// Env where the first policyset allows and the second denies |
| 810 | + example: ExampleEnv, |
| 811 | + }, |
| 812 | + LessPermissive { |
| 813 | + /// Env where the first policyset denies and the second allows |
| 814 | + example: ExampleEnv, |
| 815 | + }, |
705 | 816 | Equivalent, |
706 | | - Incomparable, |
| 817 | + Incomparable { |
| 818 | + /// Env where the first policyset allows and the second denies |
| 819 | + more_permissive_example: ExampleEnv, |
| 820 | + /// Env where the first policyset denies and the second allows |
| 821 | + less_permissive_example: ExampleEnv, |
| 822 | + }, |
707 | 823 | } |
708 | 824 |
|
709 | 825 | impl PolicySetComparisonStatus { |
710 | 826 | fn print(self) -> String { |
711 | 827 | match self { |
712 | | - PolicySetComparisonStatus::MorePermissive => { |
713 | | - String::from("pset1 is more permissive than pset2") |
| 828 | + PolicySetComparisonStatus::MorePermissive { example } => { |
| 829 | + format!("pset1 is more permissive than pset2\nExample: {example}") |
714 | 830 | } |
715 | | - PolicySetComparisonStatus::LessPermissive => { |
716 | | - String::from("pset1 is less permissive than pset2") |
| 831 | + PolicySetComparisonStatus::LessPermissive { example } => { |
| 832 | + format!("pset1 is less permissive than pset2\nExample: {example}") |
717 | 833 | } |
718 | 834 | PolicySetComparisonStatus::Equivalent => String::from("pset1 is equivalent to pset2"), |
719 | | - PolicySetComparisonStatus::Incomparable => { |
720 | | - String::from("pset1 is incomparable with pset2") |
| 835 | + PolicySetComparisonStatus::Incomparable { |
| 836 | + more_permissive_example, |
| 837 | + less_permissive_example, |
| 838 | + } => { |
| 839 | + format!( |
| 840 | + "pset1 is incomparable with pset2\nExample where pset1 is more permissive: {more_permissive_example}\nExample where pset1 is less permissive: {less_permissive_example}" |
| 841 | + ) |
721 | 842 | } |
722 | 843 | } |
723 | 844 | } |
@@ -757,20 +878,27 @@ pub fn compare_policysets( |
757 | 878 | json_output: bool, |
758 | 879 | ) -> Result<(), ExecError> { |
759 | 880 | let req_envs = OpenRequestEnv::any().to_request_envs(&schema)?; |
760 | | - let lean_context = CedarLeanFfi::new(); |
761 | | - let schema = lean_context.load_lean_schema_object(&schema)?; |
| 881 | + let lean_ffi = CedarLeanFfi::new(); |
| 882 | + let schema = lean_ffi.load_lean_schema_object(&schema)?; |
762 | 883 | let comparison_results: Vec<PolicySetComparisonResult> = req_envs |
763 | 884 | .iter() |
764 | 885 | .map(|req_env| -> Result<PolicySetComparisonResult, ExecError> { |
765 | 886 | let fwd_implies = |
766 | | - lean_context.run_check_implies(&pset1, &pset2, schema.clone(), req_env)?; |
| 887 | + lean_ffi.run_check_implies_with_cex(&pset1, &pset2, schema.clone(), req_env)?; |
767 | 888 | let bwd_implies = |
768 | | - lean_context.run_check_implies(&pset2, &pset1, schema.clone(), req_env)?; |
| 889 | + lean_ffi.run_check_implies_with_cex(&pset2, &pset1, schema.clone(), req_env)?; |
769 | 890 | let status = match (fwd_implies, bwd_implies) { |
770 | | - (true, true) => PolicySetComparisonStatus::Equivalent, |
771 | | - (true, false) => PolicySetComparisonStatus::LessPermissive, |
772 | | - (false, true) => PolicySetComparisonStatus::MorePermissive, |
773 | | - (false, false) => PolicySetComparisonStatus::Incomparable, |
| 891 | + (None, None) => PolicySetComparisonStatus::Equivalent, |
| 892 | + (None, Some(cex)) => PolicySetComparisonStatus::LessPermissive { |
| 893 | + example: ExampleEnv(cex), |
| 894 | + }, |
| 895 | + (Some(cex), None) => PolicySetComparisonStatus::MorePermissive { |
| 896 | + example: ExampleEnv(cex), |
| 897 | + }, |
| 898 | + (Some(more_cex), Some(less_cex)) => PolicySetComparisonStatus::Incomparable { |
| 899 | + more_permissive_example: ExampleEnv(more_cex), |
| 900 | + less_permissive_example: ExampleEnv(less_cex), |
| 901 | + }, |
774 | 902 | }; |
775 | 903 | Ok(PolicySetComparisonResult { |
776 | 904 | req_env: RequestEnvSer::new(req_env), |
|
0 commit comments