From 0e2c345270043c811638d8338db9ca7a7859071c Mon Sep 17 00:00:00 2001 From: Jamie Winder Date: Wed, 28 May 2025 19:50:13 +0100 Subject: [PATCH 1/3] cedar policy to 4.4.1 --- src/CedarDotNetFfi/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CedarDotNetFfi/Cargo.toml b/src/CedarDotNetFfi/Cargo.toml index 58fe2d1..b61db1e 100644 --- a/src/CedarDotNetFfi/Cargo.toml +++ b/src/CedarDotNetFfi/Cargo.toml @@ -10,7 +10,7 @@ crate-type=["cdylib"] serde_json = "1.0.140" [dependencies.cedar-policy] -version = "4.4.0" +version = "4.4.1" git = "https://github.com/cedar-policy/cedar" branch = "main" features = ["partial-eval"] From 230becfe6a6da435821097a1bb19dd7f7013a313 Mon Sep 17 00:00:00 2001 From: Jamie Winder Date: Wed, 30 Jul 2025 20:01:26 +0100 Subject: [PATCH 2/3] update to 4.5.0, add policy_set_to_text_to_parts --- src/CedarDotNet/CedarFunctions.cs | 26 ++++++ src/CedarDotNet/Interop/CedarFfi.cs | 3 + .../Models/CedarJsonSerializerContext.cs | 1 + .../Models/PolicySetTextToPartsAnswer.cs | 34 +++++++ src/CedarDotNetFfi/Cargo.lock | 93 ++++++++++--------- src/CedarDotNetFfi/Cargo.toml | 4 +- src/CedarDotNetFfi/src/lib.rs | 14 ++- .../CedarDotNet.UnitTests.csproj | 4 +- ...edarFunctionsTests.PolicySetTextToParts.cs | 46 +++++++++ .../Dtos/TestJsonSerializedContext.cs | 2 +- 10 files changed, 179 insertions(+), 48 deletions(-) create mode 100644 src/CedarDotNet/Models/PolicySetTextToPartsAnswer.cs create mode 100644 tests/CedarDotNet.UnitTests/CedarFunctionsTests.PolicySetTextToParts.cs diff --git a/src/CedarDotNet/CedarFunctions.cs b/src/CedarDotNet/CedarFunctions.cs index de417cd..c02de67 100644 --- a/src/CedarDotNet/CedarFunctions.cs +++ b/src/CedarDotNet/CedarFunctions.cs @@ -1,5 +1,7 @@ using CedarDotNet.Interop; using CedarDotNet.Models; +using System.Runtime.InteropServices; +using System.Text.Json; namespace CedarDotNet; @@ -86,6 +88,30 @@ public static IAuthorizationAnswer IsAuthorized( inputTypeInfo: CedarJsonSerializerContext.Default.AuthorizationCall, outputTypeInfo: CedarJsonSerializerContext.Default.IAuthorizationAnswer); + /// + /// Parses a policy set into its constituent policies. + /// + /// The policy set text. + /// The answer. + public static IPolicySetTextToPartsAnswer PolicySetTextToParts( + string policySetText) + { + var result = CedarFfi.PolicySetTextToParts(policySetText); + + try + { + var resultJson = Marshal.PtrToStringUTF8(result)!; + + return JsonSerializer.Deserialize( + json: resultJson, + jsonTypeInfo: CedarJsonSerializerContext.Default.IPolicySetTextToPartsAnswer)!; + } + finally + { + CedarFfi.FreeString(result); + } + } + /// /// Gets the language version. /// diff --git a/src/CedarDotNet/Interop/CedarFfi.cs b/src/CedarDotNet/Interop/CedarFfi.cs index 5bc9410..5901778 100644 --- a/src/CedarDotNet/Interop/CedarFfi.cs +++ b/src/CedarDotNet/Interop/CedarFfi.cs @@ -28,6 +28,9 @@ internal static partial class CedarFfi [LibraryImport(CedarNativeLibrary.Name, EntryPoint = "validate", StringMarshalling = StringMarshalling.Utf8)] public static partial IntPtr Validate(string call); + [LibraryImport(CedarNativeLibrary.Name, EntryPoint = "policy_set_text_to_parts", StringMarshalling = StringMarshalling.Utf8)] + public static partial IntPtr PolicySetTextToParts(string call); + [LibraryImport(CedarNativeLibrary.Name, EntryPoint = "get_lang_version", StringMarshalling = StringMarshalling.Utf8)] public static partial IntPtr GetLangVersion(); diff --git a/src/CedarDotNet/Models/CedarJsonSerializerContext.cs b/src/CedarDotNet/Models/CedarJsonSerializerContext.cs index 947fa99..6fba56d 100644 --- a/src/CedarDotNet/Models/CedarJsonSerializerContext.cs +++ b/src/CedarDotNet/Models/CedarJsonSerializerContext.cs @@ -10,6 +10,7 @@ namespace CedarDotNet.Models; [JsonSerializable(typeof(IAuthorizationAnswer))] [JsonSerializable(typeof(ICheckParseAnswer))] [JsonSerializable(typeof(IFormattingAnswer))] +[JsonSerializable(typeof(IPolicySetTextToPartsAnswer))] [JsonSourceGenerationOptions(UseStringEnumConverter = true)] internal sealed partial class CedarJsonSerializerContext : JsonSerializerContext; \ No newline at end of file diff --git a/src/CedarDotNet/Models/PolicySetTextToPartsAnswer.cs b/src/CedarDotNet/Models/PolicySetTextToPartsAnswer.cs new file mode 100644 index 0000000..52e10d1 --- /dev/null +++ b/src/CedarDotNet/Models/PolicySetTextToPartsAnswer.cs @@ -0,0 +1,34 @@ +using System.Text.Json.Serialization; + +namespace CedarDotNet.Models; + +/// +/// The marker interface for 'policy set text to parts' answer. +/// +[JsonPolymorphic(TypeDiscriminatorPropertyName = "type")] +[JsonDerivedType(typeof(PolicySetTextToPartsAnswerSuccess), "success")] +[JsonDerivedType(typeof(PolicySetTextToPartsAnswerFailure), "failure")] +public interface IPolicySetTextToPartsAnswer; + +/// +/// A successful 'policy set text to parts' answer. +/// +public sealed record class PolicySetTextToPartsAnswerSuccess + : IPolicySetTextToPartsAnswer +{ + [JsonPropertyName("policies")] + public required IReadOnlyCollection Policies { get; init; } + + [JsonPropertyName("policy_templates")] + public required IReadOnlyCollection PolicyTemplates { get; init; } +} + +/// +/// A failed 'policy set text to parts' answer. +/// +public sealed record class PolicySetTextToPartsAnswerFailure + : IPolicySetTextToPartsAnswer +{ + [JsonPropertyName("errors")] + public required IReadOnlyCollection Errors { get; init; } +} \ No newline at end of file diff --git a/src/CedarDotNetFfi/Cargo.lock b/src/CedarDotNetFfi/Cargo.lock index d2fe441..d206143 100644 --- a/src/CedarDotNetFfi/Cargo.lock +++ b/src/CedarDotNetFfi/Cargo.lock @@ -123,12 +123,11 @@ dependencies = [ [[package]] name = "cedar-policy" -version = "4.4.0" -source = "git+https://github.com/cedar-policy/cedar?branch=main#d95df22e17434b4c0cf85d4751ab0660637c24c6" +version = "4.5.0" +source = "git+https://github.com/cedar-policy/cedar?branch=main#55249a83fb1157d6aeba41838916b880d6794f97" dependencies = [ "cedar-policy-core", "cedar-policy-formatter", - "cedar-policy-validator", "itertools", "lalrpop-util", "lazy_static", @@ -145,8 +144,8 @@ dependencies = [ [[package]] name = "cedar-policy-core" -version = "4.4.0" -source = "git+https://github.com/cedar-policy/cedar?branch=main#d95df22e17434b4c0cf85d4751ab0660637c24c6" +version = "4.5.0" +source = "git+https://github.com/cedar-policy/cedar?branch=main#55249a83fb1157d6aeba41838916b880d6794f97" dependencies = [ "chrono", "educe", @@ -166,12 +165,13 @@ dependencies = [ "smol_str", "stacker", "thiserror", + "unicode-security", ] [[package]] name = "cedar-policy-formatter" -version = "4.4.0" -source = "git+https://github.com/cedar-policy/cedar?branch=main#d95df22e17434b4c0cf85d4751ab0660637c24c6" +version = "4.5.0" +source = "git+https://github.com/cedar-policy/cedar?branch=main#55249a83fb1157d6aeba41838916b880d6794f97" dependencies = [ "cedar-policy-core", "itertools", @@ -183,29 +183,6 @@ dependencies = [ "smol_str", ] -[[package]] -name = "cedar-policy-validator" -version = "4.4.0" -source = "git+https://github.com/cedar-policy/cedar?branch=main#d95df22e17434b4c0cf85d4751ab0660637c24c6" -dependencies = [ - "cedar-policy-core", - "educe", - "itertools", - "lalrpop", - "lalrpop-util", - "lazy_static", - "miette", - "nonempty", - "ref-cast", - "serde", - "serde_json", - "serde_with", - "smol_str", - "stacker", - "thiserror", - "unicode-security", -] - [[package]] name = "cfg-if" version = "1.0.0" @@ -311,6 +288,12 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + [[package]] name = "educe" version = "0.6.0" @@ -501,9 +484,9 @@ dependencies = [ [[package]] name = "lalrpop" -version = "0.22.1" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7047a26de42016abf8f181b46b398aef0b77ad46711df41847f6ed869a2a1d5b" +checksum = "ba4ebbd48ce411c1d10fb35185f5a51a7bfa3d8b24b4e330d30c9e3a34129501" dependencies = [ "ascii-canvas", "bit-set", @@ -523,9 +506,9 @@ dependencies = [ [[package]] name = "lalrpop-util" -version = "0.22.1" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8d05b3fe34b8bd562c338db725dfa9beb9451a48f65f129ccb9538b48d2c93b" +checksum = "b5baa5e9ff84f1aefd264e6869907646538a52147a755d494517a8007fb48733" dependencies = [ "regex-automata", "rustversion", @@ -630,9 +613,9 @@ checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" [[package]] name = "nonempty" -version = "0.10.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "303e8749c804ccd6ca3b428de7fe0d86cb86bc7606bc15291f100fd487960bb8" +checksum = "9737e026353e5cd0736f98eddae28665118eb6f6600902a7f50db585621fecb6" dependencies = [ "serde", ] @@ -853,6 +836,30 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -887,9 +894,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.140" +version = "1.0.141" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" dependencies = [ "indexmap 2.9.0", "itoa", @@ -900,15 +907,17 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.12.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa" +checksum = "f2c45cd61fefa9db6f254525d46e392b852e0e61d9a1fd36e5bd183450a556d5" dependencies = [ "base64", "chrono", "hex", "indexmap 1.9.3", "indexmap 2.9.0", + "schemars 0.9.0", + "schemars 1.0.4", "serde", "serde_derive", "serde_json", @@ -918,9 +927,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.12.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e" +checksum = "de90945e6565ce0d9a25098082ed4ee4002e047cb59892c318d66821e14bb30f" dependencies = [ "darling", "proc-macro2", diff --git a/src/CedarDotNetFfi/Cargo.toml b/src/CedarDotNetFfi/Cargo.toml index b61db1e..d935d28 100644 --- a/src/CedarDotNetFfi/Cargo.toml +++ b/src/CedarDotNetFfi/Cargo.toml @@ -7,10 +7,10 @@ edition = "2024" crate-type=["cdylib"] [dependencies] -serde_json = "1.0.140" +serde_json = "1.0.141" [dependencies.cedar-policy] -version = "4.4.1" +version = "4.5.0" git = "https://github.com/cedar-policy/cedar" branch = "main" features = ["partial-eval"] diff --git a/src/CedarDotNetFfi/src/lib.rs b/src/CedarDotNetFfi/src/lib.rs index 56e0a08..1199803 100644 --- a/src/CedarDotNetFfi/src/lib.rs +++ b/src/CedarDotNetFfi/src/lib.rs @@ -17,7 +17,8 @@ use cedar_policy::ffi::{ is_authorized_partial_json_str, validate_json_str, get_lang_version as internal_get_lang_version, - get_sdk_version as internal_get_sdk_version + get_sdk_version as internal_get_sdk_version, + policy_set_text_to_parts as internal_policy_set_text_to_parts }; #[unsafe(no_mangle)] @@ -153,4 +154,15 @@ pub fn load_policy_set(text: *const c_char) -> *const c_char { let result_json_str = serde_json::to_string(&arr).unwrap(); CString::new(result_json_str.to_string()).unwrap().into_raw() +} + +#[unsafe(no_mangle)] +pub fn policy_set_text_to_parts(text: *const c_char) -> *const c_char { + let text_str = unsafe { CStr::from_ptr(text).to_str().unwrap() }; + + let parts = internal_policy_set_text_to_parts(text_str); + + let result_json_str = serde_json::to_string(&parts).expect("Could not serialize parts to JSON"); + + CString::new(result_json_str).unwrap().into_raw() } \ No newline at end of file diff --git a/tests/CedarDotNet.UnitTests/CedarDotNet.UnitTests.csproj b/tests/CedarDotNet.UnitTests/CedarDotNet.UnitTests.csproj index 423ade6..a4cc99d 100644 --- a/tests/CedarDotNet.UnitTests/CedarDotNet.UnitTests.csproj +++ b/tests/CedarDotNet.UnitTests/CedarDotNet.UnitTests.csproj @@ -13,10 +13,10 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/tests/CedarDotNet.UnitTests/CedarFunctionsTests.PolicySetTextToParts.cs b/tests/CedarDotNet.UnitTests/CedarFunctionsTests.PolicySetTextToParts.cs new file mode 100644 index 0000000..b242c67 --- /dev/null +++ b/tests/CedarDotNet.UnitTests/CedarFunctionsTests.PolicySetTextToParts.cs @@ -0,0 +1,46 @@ +using CedarDotNet.Models; + +namespace CedarDotNet.UnitTests; + +public partial class CedarFunctionTests +{ + [Fact] + public void PolicySetTextToParts_ValidPolicySet_ReturnsPoliciesAndTemplates() + { + // Arrange + var policySetText = """ + permit(principal, action, resource) + when { principal has "Email" && principal.Email == "a@a.com" }; + + permit(principal in UserGroup::"DeathRowRecords", action == Action::"pop", resource); + + permit(principal in ?principal, action, resource); + """; + + // Act + var result = CedarFunctions.PolicySetTextToParts(policySetText); + + // Assert + Assert.IsType(result); + + var success = (PolicySetTextToPartsAnswerSuccess)result; + + Assert.Equal(2, success.Policies.Count); + Assert.Single(success.PolicyTemplates); + } + + [Fact] + public void PolicySetTextToParts_InvalidPolicySet_ReturnsFailure() + { + // Arrange + var policySetText = """ + invalid + """; + + // Act + var result = CedarFunctions.PolicySetTextToParts(policySetText); + + // Assert + Assert.IsType(result); + } +} diff --git a/tests/CedarDotNet.UnitTests/IntegrationTests/Dtos/TestJsonSerializedContext.cs b/tests/CedarDotNet.UnitTests/IntegrationTests/Dtos/TestJsonSerializedContext.cs index 8a8e851..fb096c3 100644 --- a/tests/CedarDotNet.UnitTests/IntegrationTests/Dtos/TestJsonSerializedContext.cs +++ b/tests/CedarDotNet.UnitTests/IntegrationTests/Dtos/TestJsonSerializedContext.cs @@ -6,6 +6,6 @@ namespace CedarDotNet.UnitTests.IntegrationTests.Dtos; [JsonSerializable(typeof(TestScenarioDto))] [JsonSerializable(typeof(IReadOnlyCollection))] [JsonSourceGenerationOptions( - PropertyNamingPolicy = JsonKnownNamingPolicy.SnakeCaseLower,UseStringEnumConverter = true)] + PropertyNamingPolicy = JsonKnownNamingPolicy.SnakeCaseLower, UseStringEnumConverter = true)] internal sealed partial class TestJsonSerializedContext : JsonSerializerContext; \ No newline at end of file From 138d05d2b459053ddd420455cb8302a364ccfb39 Mon Sep 17 00:00:00 2001 From: Jamie Winder Date: Wed, 30 Jul 2025 20:07:58 +0100 Subject: [PATCH 3/3] add xmldoc --- src/CedarDotNet/Models/PolicySetTextToPartsAnswer.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/CedarDotNet/Models/PolicySetTextToPartsAnswer.cs b/src/CedarDotNet/Models/PolicySetTextToPartsAnswer.cs index 52e10d1..7c352db 100644 --- a/src/CedarDotNet/Models/PolicySetTextToPartsAnswer.cs +++ b/src/CedarDotNet/Models/PolicySetTextToPartsAnswer.cs @@ -16,9 +16,15 @@ public interface IPolicySetTextToPartsAnswer; public sealed record class PolicySetTextToPartsAnswerSuccess : IPolicySetTextToPartsAnswer { + /// + /// The policies. + /// [JsonPropertyName("policies")] public required IReadOnlyCollection Policies { get; init; } + /// + /// The policy templates. + /// [JsonPropertyName("policy_templates")] public required IReadOnlyCollection PolicyTemplates { get; init; } }