diff --git a/biscuit-auth/examples/testcases.rs b/biscuit-auth/examples/testcases.rs index 0c875ef3..50ea7d3d 100644 --- a/biscuit-auth/examples/testcases.rs +++ b/biscuit-auth/examples/testcases.rs @@ -146,6 +146,8 @@ fn main() { add_test_result(&mut results, null(&target, &root, test)); + add_test_result(&mut results, heterogeneous_equal(&target, &root, test)); + if json { let s = serde_json::to_string_pretty(&TestCases { root_private_key: hex::encode(root.private().to_bytes()), @@ -1277,9 +1279,9 @@ fn expressions(target: &str, root: &KeyPair, test: bool) -> TestResult { check if false || true; //boolean parens check if (true || false) && true; - // boolean equality - check if true == true; - check if false == false; + // boolean strict equality + check if true === true; + check if false === false; //integer less than check if 1 < 2; @@ -1291,10 +1293,10 @@ fn expressions(target: &str, root: &KeyPair, test: bool) -> TestResult { //integer greater or equal check if 2 >= 1; check if 2 >= 2; - //integer equal - check if 3 == 3; + //integer strict equal + check if 3 === 3; //integer add sub mul div - check if 1 + 2 * 3 - 4 /2 == 5; + check if 1 + 2 * 3 - 4 /2 === 5; // string prefix and suffix check if "hello world".starts_with("hello") && "hello world".ends_with("world"); @@ -1303,13 +1305,13 @@ fn expressions(target: &str, root: &KeyPair, test: bool) -> TestResult { // string contains check if "aaabde".contains("abd"); // string concatenation - check if "aaabde" == "aaa" + "b" + "de"; - // string equal - check if "abcD12" == "abcD12"; + check if "aaabde" === "aaa" + "b" + "de"; + // string strict equal + check if "abcD12" === "abcD12"; // string length - check if "abcD12".length() == 6; + check if "abcD12".length() === 6; // string length (non-ascii) - check if "é".length() == 2; + check if "é".length() === 2; //date less than check if 2019-12-04T09:46:41+00:00 < 2020-12-04T09:46:41+00:00; @@ -1321,11 +1323,11 @@ fn expressions(target: &str, root: &KeyPair, test: bool) -> TestResult { //date greater or equal check if 2020-12-04T09:46:41+00:00 >= 2019-12-04T09:46:41+00:00; check if 2020-12-04T09:46:41+00:00 >= 2020-12-04T09:46:41+00:00; - //date equal - check if 2020-12-04T09:46:41+00:00 == 2020-12-04T09:46:41+00:00; + //date strict equal + check if 2020-12-04T09:46:41+00:00 === 2020-12-04T09:46:41+00:00; - //bytes equal - check if hex:12ab == hex:12ab; + //bytes strict equal + check if hex:12ab === hex:12ab; // set contains check if [1, 2].contains(2); @@ -1334,16 +1336,16 @@ fn expressions(target: &str, root: &KeyPair, test: bool) -> TestResult { check if ["abc", "def"].contains("abc"); check if [hex:12ab, hex:34de].contains(hex:34de); check if [1, 2].contains([2]); - // set equal - check if [1, 2] == [1, 2]; + // set strict equal + check if [1, 2] === [1, 2]; // set intersection - check if [1, 2].intersection([2, 3]) == [2]; + check if [1, 2].intersection([2, 3]) === [2]; // set union - check if [1, 2].union([2, 3]) == [1, 2, 3]; + check if [1, 2].union([2, 3]) === [1, 2, 3]; // chained method calls check if [1, 2, 3].intersection([1, 2]).contains(1); // chained method calls with unary method - check if [1, 2, 3].intersection([1, 2]).length() == 2; + check if [1, 2, 3].intersection([1, 2]).length() === 2; "#) .build_with_rng(&root, SymbolTable::default(), &mut rng) .unwrap(); @@ -1924,18 +1926,18 @@ fn expressions_v4(target: &str, root: &KeyPair, test: bool) -> TestResult { let biscuit = biscuit!( r#" - //integer not equal - check if 1 != 3; + //integer not strict equal + check if 1 !== 3; //integer bitwise and or xor - check if 1 | 2 ^ 3 == 0; - // string not equal - check if "abcD12x" != "abcD12"; - //date not equal - check if 2022-12-04T09:46:41+00:00 != 2020-12-04T09:46:41+00:00; - //bytes not equal - check if hex:12abcd != hex:12ab; - // set not equal - check if [1, 4] != [1, 2]; + check if 1 | 2 ^ 3 === 0; + // string not strict equal + check if "abcD12x" !== "abcD12"; + //date not strict equal + check if 2022-12-04T09:46:41+00:00 !== 2020-12-04T09:46:41+00:00; + //bytes not strict equal + check if hex:12abcd !== hex:12ab; + // set not strict equal + check if [1, 4] !== [1, 2]; "# ) .build_with_rng(&root, SymbolTable::default(), &mut rng) @@ -2033,6 +2035,46 @@ fn null(target: &str, root: &KeyPair, test: bool) -> TestResult { } } +fn heterogeneous_equal(target: &str, root: &KeyPair, test: bool) -> TestResult { + let mut rng: StdRng = SeedableRng::seed_from_u64(1234); + let title = "test heterogeneous equal".to_string(); + let filename = "test031_heterogeneous_equal".to_string(); + let token; + + let biscuit = biscuit!( + r#" + check if fact(1, $value), 1 == $value; + check if fact2(1, $value), 1 != $value; + "# + ) + .build_with_rng(&root, SymbolTable::default(), &mut rng) + .unwrap(); + token = print_blocks(&biscuit); + + let data = write_or_load_testcase(target, &filename, root, &biscuit, test); + + let mut validations = BTreeMap::new(); + validations.insert( + "authorized same type".to_string(), + validate_token(root, &data[..], "fact(1, 1); fact2(1, 2); allow if true"), + ); + validations.insert( + "unauthorized failed logic different type".to_string(), + validate_token( + root, + &data[..], + "fact(1, true); fact2(1, false); allow if true", + ), + ); + + TestResult { + title, + filename, + token, + validations, + } +} + fn print_blocks(token: &Biscuit) -> Vec { let mut v = Vec::new(); diff --git a/biscuit-auth/samples/README.md b/biscuit-auth/samples/README.md index 029ead92..141a045f 100644 --- a/biscuit-auth/samples/README.md +++ b/biscuit-auth/samples/README.md @@ -1224,42 +1224,42 @@ check if !false; check if !false && true; check if false || true; check if (true || false) && true; -check if true == true; -check if false == false; +check if true === true; +check if false === false; check if 1 < 2; check if 2 > 1; check if 1 <= 2; check if 1 <= 1; check if 2 >= 1; check if 2 >= 2; -check if 3 == 3; -check if 1 + 2 * 3 - 4 / 2 == 5; +check if 3 === 3; +check if 1 + 2 * 3 - 4 / 2 === 5; check if "hello world".starts_with("hello") && "hello world".ends_with("world"); check if "aaabde".matches("a*c?.e"); check if "aaabde".contains("abd"); -check if "aaabde" == "aaa" + "b" + "de"; -check if "abcD12" == "abcD12"; -check if "abcD12".length() == 6; -check if "é".length() == 2; +check if "aaabde" === "aaa" + "b" + "de"; +check if "abcD12" === "abcD12"; +check if "abcD12".length() === 6; +check if "é".length() === 2; check if 2019-12-04T09:46:41Z < 2020-12-04T09:46:41Z; check if 2020-12-04T09:46:41Z > 2019-12-04T09:46:41Z; check if 2019-12-04T09:46:41Z <= 2020-12-04T09:46:41Z; check if 2020-12-04T09:46:41Z >= 2020-12-04T09:46:41Z; check if 2020-12-04T09:46:41Z >= 2019-12-04T09:46:41Z; check if 2020-12-04T09:46:41Z >= 2020-12-04T09:46:41Z; -check if 2020-12-04T09:46:41Z == 2020-12-04T09:46:41Z; -check if hex:12ab == hex:12ab; +check if 2020-12-04T09:46:41Z === 2020-12-04T09:46:41Z; +check if hex:12ab === hex:12ab; check if [1, 2].contains(2); check if [2019-12-04T09:46:41Z, 2020-12-04T09:46:41Z].contains(2020-12-04T09:46:41Z); check if [false, true].contains(true); check if ["abc", "def"].contains("abc"); check if [hex:12ab, hex:34de].contains(hex:34de); check if [1, 2].contains([2]); -check if [1, 2] == [1, 2]; -check if [1, 2].intersection([2, 3]) == [2]; -check if [1, 2].union([2, 3]) == [1, 2, 3]; +check if [1, 2] === [1, 2]; +check if [1, 2].intersection([2, 3]) === [2]; +check if [1, 2].union([2, 3]) === [1, 2, 3]; check if [1, 2, 3].intersection([1, 2]).contains(1); -check if [1, 2, 3].intersection([1, 2]).length() == 2; +check if [1, 2, 3].intersection([1, 2]).length() === 2; ``` ### validation @@ -1285,15 +1285,15 @@ World { checks: [ "check if !false", "check if !false && true", - "check if \"aaabde\" == \"aaa\" + \"b\" + \"de\"", + "check if \"aaabde\" === \"aaa\" + \"b\" + \"de\"", "check if \"aaabde\".contains(\"abd\")", "check if \"aaabde\".matches(\"a*c?.e\")", - "check if \"abcD12\" == \"abcD12\"", - "check if \"abcD12\".length() == 6", + "check if \"abcD12\" === \"abcD12\"", + "check if \"abcD12\".length() === 6", "check if \"hello world\".starts_with(\"hello\") && \"hello world\".ends_with(\"world\")", - "check if \"é\".length() == 2", + "check if \"é\".length() === 2", "check if (true || false) && true", - "check if 1 + 2 * 3 - 4 / 2 == 5", + "check if 1 + 2 * 3 - 4 / 2 === 5", "check if 1 < 2", "check if 1 <= 1", "check if 1 <= 2", @@ -1302,28 +1302,28 @@ World { "check if 2 >= 2", "check if 2019-12-04T09:46:41Z < 2020-12-04T09:46:41Z", "check if 2019-12-04T09:46:41Z <= 2020-12-04T09:46:41Z", - "check if 2020-12-04T09:46:41Z == 2020-12-04T09:46:41Z", + "check if 2020-12-04T09:46:41Z === 2020-12-04T09:46:41Z", "check if 2020-12-04T09:46:41Z > 2019-12-04T09:46:41Z", "check if 2020-12-04T09:46:41Z >= 2019-12-04T09:46:41Z", "check if 2020-12-04T09:46:41Z >= 2020-12-04T09:46:41Z", "check if 2020-12-04T09:46:41Z >= 2020-12-04T09:46:41Z", - "check if 3 == 3", + "check if 3 === 3", "check if [\"abc\", \"def\"].contains(\"abc\")", "check if [1, 2, 3].intersection([1, 2]).contains(1)", - "check if [1, 2, 3].intersection([1, 2]).length() == 2", - "check if [1, 2] == [1, 2]", + "check if [1, 2, 3].intersection([1, 2]).length() === 2", + "check if [1, 2] === [1, 2]", "check if [1, 2].contains(2)", "check if [1, 2].contains([2])", - "check if [1, 2].intersection([2, 3]) == [2]", - "check if [1, 2].union([2, 3]) == [1, 2, 3]", + "check if [1, 2].intersection([2, 3]) === [2]", + "check if [1, 2].union([2, 3]) === [1, 2, 3]", "check if [2019-12-04T09:46:41Z, 2020-12-04T09:46:41Z].contains(2020-12-04T09:46:41Z)", "check if [false, true].contains(true)", "check if [hex:12ab, hex:34de].contains(hex:34de)", - "check if false == false", + "check if false === false", "check if false || true", - "check if hex:12ab == hex:12ab", + "check if hex:12ab === hex:12ab", "check if true", - "check if true == true", + "check if true === true", ], }, ] @@ -2276,7 +2276,7 @@ allow if true; ``` revocation ids: -- `3346a22aae0abfc1ffa526f02f7650e90af909e5e519989026441e78cdc245b7fd126503cfdc8831325fc04307edc65238db319724477915f7040a2f6a719a05` +- `a57be539aae237040fe6c2c28c4263516147c9f0d1d7ba88a385f1574f504c544164a2c747efd8b30eaab9d351c383cc1875642f173546d5f4b53b2220c87a0a` authorizer world: ``` @@ -2315,12 +2315,12 @@ symbols: ["abcD12x", "abcD12"] public keys: [] ``` -check if 1 != 3; -check if 1 | 2 ^ 3 == 0; -check if "abcD12x" != "abcD12"; -check if 2022-12-04T09:46:41Z != 2020-12-04T09:46:41Z; -check if hex:12abcd != hex:12ab; -check if [1, 4] != [1, 2]; +check if 1 !== 3; +check if 1 | 2 ^ 3 === 0; +check if "abcD12x" !== "abcD12"; +check if 2022-12-04T09:46:41Z !== 2020-12-04T09:46:41Z; +check if hex:12abcd !== hex:12ab; +check if [1, 4] !== [1, 2]; ``` ### validation @@ -2344,12 +2344,12 @@ World { 0, ), checks: [ - "check if \"abcD12x\" != \"abcD12\"", - "check if 1 != 3", - "check if 1 | 2 ^ 3 == 0", - "check if 2022-12-04T09:46:41Z != 2020-12-04T09:46:41Z", - "check if [1, 4] != [1, 2]", - "check if hex:12abcd != hex:12ab", + "check if \"abcD12x\" !== \"abcD12\"", + "check if 1 !== 3", + "check if 1 | 2 ^ 3 === 0", + "check if 2022-12-04T09:46:41Z !== 2020-12-04T09:46:41Z", + "check if [1, 4] !== [1, 2]", + "check if hex:12abcd !== hex:12ab", ], }, ] @@ -2489,7 +2489,7 @@ allow if true; ``` revocation ids: -- `bbf3ad51a70e935126b334f37be2bf66e90162353c19c524c0d3579ee71034996872b8433b132e6e0b519d371b0ab20481d58c4619183e8997c3744786e8e003` +- `35d99762ee4343b245d66b719f7ad6180c76dd899c39e4072cf61dcf8673e7510374922457ce260b8c576431e894e38c7c0bacd3e5cae2bfc63e3286d2078d02` authorizer world: ``` @@ -2533,7 +2533,7 @@ allow if true; ``` revocation ids: -- `bbf3ad51a70e935126b334f37be2bf66e90162353c19c524c0d3579ee71034996872b8433b132e6e0b519d371b0ab20481d58c4619183e8997c3744786e8e003` +- `35d99762ee4343b245d66b719f7ad6180c76dd899c39e4072cf61dcf8673e7510374922457ce260b8c576431e894e38c7c0bacd3e5cae2bfc63e3286d2078d02` authorizer world: ``` @@ -2577,7 +2577,7 @@ allow if true; ``` revocation ids: -- `bbf3ad51a70e935126b334f37be2bf66e90162353c19c524c0d3579ee71034996872b8433b132e6e0b519d371b0ab20481d58c4619183e8997c3744786e8e003` +- `35d99762ee4343b245d66b719f7ad6180c76dd899c39e4072cf61dcf8673e7510374922457ce260b8c576431e894e38c7c0bacd3e5cae2bfc63e3286d2078d02` authorizer world: ``` @@ -2621,7 +2621,7 @@ allow if true; ``` revocation ids: -- `bbf3ad51a70e935126b334f37be2bf66e90162353c19c524c0d3579ee71034996872b8433b132e6e0b519d371b0ab20481d58c4619183e8997c3744786e8e003` +- `35d99762ee4343b245d66b719f7ad6180c76dd899c39e4072cf61dcf8673e7510374922457ce260b8c576431e894e38c7c0bacd3e5cae2bfc63e3286d2078d02` authorizer world: ``` @@ -2656,3 +2656,112 @@ World { result: `Err(FailedLogic(Unauthorized { policy: Allow(0), checks: [Block(FailedBlockCheck { block_id: 0, check_id: 0, rule: "check if fact(null, $value), $value == null" }), Block(FailedBlockCheck { block_id: 0, check_id: 1, rule: "reject if fact(null, $value), $value != null" })] }))` + +------------------------------ + +## test heterogeneous equal: test031_heterogeneous_equal.bc +### token + +authority: +symbols: ["fact", "value", "fact2"] + +public keys: [] + +``` +check if fact(1, $value), 1 == $value; +check if fact2(1, $value), 1 != $value; +``` + +### validation for "authorized same type" + +authorizer code: +``` +fact(1, 1); +fact2(1, 2); + +allow if true; +``` + +revocation ids: +- `4af31ee86f7afbf20c7fe7e664943b2b3895693b85b3c580f3accc3bbdf8d4b1909f84787e1189c0711fd850a7cc330021e8bbe127ae3a4b36624ff7e487170e` + +authorizer world: +``` +World { + facts: [ + Facts { + origin: { + None, + }, + facts: [ + "fact(1, 1)", + "fact2(1, 2)", + ], + }, +] + rules: [] + checks: [ + Checks { + origin: Some( + 0, + ), + checks: [ + "check if fact(1, $value), 1 == $value", + "check if fact2(1, $value), 1 != $value", + ], + }, +] + policies: [ + "allow if true", +] +} +``` + +result: `Ok(0)` +### validation for "unauthorized failed logic different type" + +authorizer code: +``` +fact(1, true); +fact2(1, false); + +allow if true; +``` + +revocation ids: +- `4af31ee86f7afbf20c7fe7e664943b2b3895693b85b3c580f3accc3bbdf8d4b1909f84787e1189c0711fd850a7cc330021e8bbe127ae3a4b36624ff7e487170e` + +authorizer world: +``` +World { + facts: [ + Facts { + origin: { + None, + }, + facts: [ + "fact(1, true)", + "fact2(1, false)", + ], + }, +] + rules: [] + checks: [ + Checks { + origin: Some( + 0, + ), + checks: [ + "check if fact(1, $value), 1 == $value", + "check if fact2(1, $value), 1 != $value", + ], + }, +] + policies: [ + "allow if true", +] +} +``` + +result: `Err(FailedLogic(Unauthorized { policy: Allow(0), checks: [Block(FailedBlockCheck { block_id: 0, check_id: 0, rule: "check if fact(1, $value), 1 == $value" })] }))` + diff --git a/biscuit-auth/samples/samples.json b/biscuit-auth/samples/samples.json index 54a1ee9e..b2fbf2bd 100644 --- a/biscuit-auth/samples/samples.json +++ b/biscuit-auth/samples/samples.json @@ -1245,7 +1245,7 @@ ], "public_keys": [], "external_key": null, - "code": "check if true;\ncheck if !false;\ncheck if !false && true;\ncheck if false || true;\ncheck if (true || false) && true;\ncheck if true == true;\ncheck if false == false;\ncheck if 1 < 2;\ncheck if 2 > 1;\ncheck if 1 <= 2;\ncheck if 1 <= 1;\ncheck if 2 >= 1;\ncheck if 2 >= 2;\ncheck if 3 == 3;\ncheck if 1 + 2 * 3 - 4 / 2 == 5;\ncheck if \"hello world\".starts_with(\"hello\") && \"hello world\".ends_with(\"world\");\ncheck if \"aaabde\".matches(\"a*c?.e\");\ncheck if \"aaabde\".contains(\"abd\");\ncheck if \"aaabde\" == \"aaa\" + \"b\" + \"de\";\ncheck if \"abcD12\" == \"abcD12\";\ncheck if \"abcD12\".length() == 6;\ncheck if \"é\".length() == 2;\ncheck if 2019-12-04T09:46:41Z < 2020-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z > 2019-12-04T09:46:41Z;\ncheck if 2019-12-04T09:46:41Z <= 2020-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z >= 2020-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z >= 2019-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z >= 2020-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z == 2020-12-04T09:46:41Z;\ncheck if hex:12ab == hex:12ab;\ncheck if [1, 2].contains(2);\ncheck if [2019-12-04T09:46:41Z, 2020-12-04T09:46:41Z].contains(2020-12-04T09:46:41Z);\ncheck if [false, true].contains(true);\ncheck if [\"abc\", \"def\"].contains(\"abc\");\ncheck if [hex:12ab, hex:34de].contains(hex:34de);\ncheck if [1, 2].contains([2]);\ncheck if [1, 2] == [1, 2];\ncheck if [1, 2].intersection([2, 3]) == [2];\ncheck if [1, 2].union([2, 3]) == [1, 2, 3];\ncheck if [1, 2, 3].intersection([1, 2]).contains(1);\ncheck if [1, 2, 3].intersection([1, 2]).length() == 2;\n" + "code": "check if true;\ncheck if !false;\ncheck if !false && true;\ncheck if false || true;\ncheck if (true || false) && true;\ncheck if true === true;\ncheck if false === false;\ncheck if 1 < 2;\ncheck if 2 > 1;\ncheck if 1 <= 2;\ncheck if 1 <= 1;\ncheck if 2 >= 1;\ncheck if 2 >= 2;\ncheck if 3 === 3;\ncheck if 1 + 2 * 3 - 4 / 2 === 5;\ncheck if \"hello world\".starts_with(\"hello\") && \"hello world\".ends_with(\"world\");\ncheck if \"aaabde\".matches(\"a*c?.e\");\ncheck if \"aaabde\".contains(\"abd\");\ncheck if \"aaabde\" === \"aaa\" + \"b\" + \"de\";\ncheck if \"abcD12\" === \"abcD12\";\ncheck if \"abcD12\".length() === 6;\ncheck if \"é\".length() === 2;\ncheck if 2019-12-04T09:46:41Z < 2020-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z > 2019-12-04T09:46:41Z;\ncheck if 2019-12-04T09:46:41Z <= 2020-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z >= 2020-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z >= 2019-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z >= 2020-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z === 2020-12-04T09:46:41Z;\ncheck if hex:12ab === hex:12ab;\ncheck if [1, 2].contains(2);\ncheck if [2019-12-04T09:46:41Z, 2020-12-04T09:46:41Z].contains(2020-12-04T09:46:41Z);\ncheck if [false, true].contains(true);\ncheck if [\"abc\", \"def\"].contains(\"abc\");\ncheck if [hex:12ab, hex:34de].contains(hex:34de);\ncheck if [1, 2].contains([2]);\ncheck if [1, 2] === [1, 2];\ncheck if [1, 2].intersection([2, 3]) === [2];\ncheck if [1, 2].union([2, 3]) === [1, 2, 3];\ncheck if [1, 2, 3].intersection([1, 2]).contains(1);\ncheck if [1, 2, 3].intersection([1, 2]).length() === 2;\n" } ], "validations": { @@ -1259,15 +1259,15 @@ "checks": [ "check if !false", "check if !false && true", - "check if \"aaabde\" == \"aaa\" + \"b\" + \"de\"", + "check if \"aaabde\" === \"aaa\" + \"b\" + \"de\"", "check if \"aaabde\".contains(\"abd\")", "check if \"aaabde\".matches(\"a*c?.e\")", - "check if \"abcD12\" == \"abcD12\"", - "check if \"abcD12\".length() == 6", + "check if \"abcD12\" === \"abcD12\"", + "check if \"abcD12\".length() === 6", "check if \"hello world\".starts_with(\"hello\") && \"hello world\".ends_with(\"world\")", - "check if \"é\".length() == 2", + "check if \"é\".length() === 2", "check if (true || false) && true", - "check if 1 + 2 * 3 - 4 / 2 == 5", + "check if 1 + 2 * 3 - 4 / 2 === 5", "check if 1 < 2", "check if 1 <= 1", "check if 1 <= 2", @@ -1276,28 +1276,28 @@ "check if 2 >= 2", "check if 2019-12-04T09:46:41Z < 2020-12-04T09:46:41Z", "check if 2019-12-04T09:46:41Z <= 2020-12-04T09:46:41Z", - "check if 2020-12-04T09:46:41Z == 2020-12-04T09:46:41Z", + "check if 2020-12-04T09:46:41Z === 2020-12-04T09:46:41Z", "check if 2020-12-04T09:46:41Z > 2019-12-04T09:46:41Z", "check if 2020-12-04T09:46:41Z >= 2019-12-04T09:46:41Z", "check if 2020-12-04T09:46:41Z >= 2020-12-04T09:46:41Z", "check if 2020-12-04T09:46:41Z >= 2020-12-04T09:46:41Z", - "check if 3 == 3", + "check if 3 === 3", "check if [\"abc\", \"def\"].contains(\"abc\")", "check if [1, 2, 3].intersection([1, 2]).contains(1)", - "check if [1, 2, 3].intersection([1, 2]).length() == 2", - "check if [1, 2] == [1, 2]", + "check if [1, 2, 3].intersection([1, 2]).length() === 2", + "check if [1, 2] === [1, 2]", "check if [1, 2].contains(2)", "check if [1, 2].contains([2])", - "check if [1, 2].intersection([2, 3]) == [2]", - "check if [1, 2].union([2, 3]) == [1, 2, 3]", + "check if [1, 2].intersection([2, 3]) === [2]", + "check if [1, 2].union([2, 3]) === [1, 2, 3]", "check if [2019-12-04T09:46:41Z, 2020-12-04T09:46:41Z].contains(2020-12-04T09:46:41Z)", "check if [false, true].contains(true)", "check if [hex:12ab, hex:34de].contains(hex:34de)", - "check if false == false", + "check if false === false", "check if false || true", - "check if hex:12ab == hex:12ab", + "check if hex:12ab === hex:12ab", "check if true", - "check if true == true" + "check if true === true" ] } ], @@ -2127,7 +2127,7 @@ }, "authorizer_code": "allow if true;\n", "revocation_ids": [ - "3346a22aae0abfc1ffa526f02f7650e90af909e5e519989026441e78cdc245b7fd126503cfdc8831325fc04307edc65238db319724477915f7040a2f6a719a05" + "a57be539aae237040fe6c2c28c4263516147c9f0d1d7ba88a385f1574f504c544164a2c747efd8b30eaab9d351c383cc1875642f173546d5f4b53b2220c87a0a" ] } } @@ -2143,7 +2143,7 @@ ], "public_keys": [], "external_key": null, - "code": "check if 1 != 3;\ncheck if 1 | 2 ^ 3 == 0;\ncheck if \"abcD12x\" != \"abcD12\";\ncheck if 2022-12-04T09:46:41Z != 2020-12-04T09:46:41Z;\ncheck if hex:12abcd != hex:12ab;\ncheck if [1, 4] != [1, 2];\n" + "code": "check if 1 !== 3;\ncheck if 1 | 2 ^ 3 === 0;\ncheck if \"abcD12x\" !== \"abcD12\";\ncheck if 2022-12-04T09:46:41Z !== 2020-12-04T09:46:41Z;\ncheck if hex:12abcd !== hex:12ab;\ncheck if [1, 4] !== [1, 2];\n" } ], "validations": { @@ -2155,12 +2155,12 @@ { "origin": 0, "checks": [ - "check if \"abcD12x\" != \"abcD12\"", - "check if 1 != 3", - "check if 1 | 2 ^ 3 == 0", - "check if 2022-12-04T09:46:41Z != 2020-12-04T09:46:41Z", - "check if [1, 4] != [1, 2]", - "check if hex:12abcd != hex:12ab" + "check if \"abcD12x\" !== \"abcD12\"", + "check if 1 !== 3", + "check if 1 | 2 ^ 3 === 0", + "check if 2022-12-04T09:46:41Z !== 2020-12-04T09:46:41Z", + "check if [1, 4] !== [1, 2]", + "check if hex:12abcd !== hex:12ab" ] } ], @@ -2323,7 +2323,7 @@ }, "authorizer_code": "fact(null, null);\n\nallow if true;\n", "revocation_ids": [ - "bbf3ad51a70e935126b334f37be2bf66e90162353c19c524c0d3579ee71034996872b8433b132e6e0b519d371b0ab20481d58c4619183e8997c3744786e8e003" + "35d99762ee4343b245d66b719f7ad6180c76dd899c39e4072cf61dcf8673e7510374922457ce260b8c576431e894e38c7c0bacd3e5cae2bfc63e3286d2078d02" ] }, "rejection1": { @@ -2381,7 +2381,7 @@ }, "authorizer_code": "fact(null, 1);\n\nallow if true;\n", "revocation_ids": [ - "bbf3ad51a70e935126b334f37be2bf66e90162353c19c524c0d3579ee71034996872b8433b132e6e0b519d371b0ab20481d58c4619183e8997c3744786e8e003" + "35d99762ee4343b245d66b719f7ad6180c76dd899c39e4072cf61dcf8673e7510374922457ce260b8c576431e894e38c7c0bacd3e5cae2bfc63e3286d2078d02" ] }, "rejection2": { @@ -2439,7 +2439,7 @@ }, "authorizer_code": "fact(null, true);\n\nallow if true;\n", "revocation_ids": [ - "bbf3ad51a70e935126b334f37be2bf66e90162353c19c524c0d3579ee71034996872b8433b132e6e0b519d371b0ab20481d58c4619183e8997c3744786e8e003" + "35d99762ee4343b245d66b719f7ad6180c76dd899c39e4072cf61dcf8673e7510374922457ce260b8c576431e894e38c7c0bacd3e5cae2bfc63e3286d2078d02" ] }, "rejection3": { @@ -2497,7 +2497,112 @@ }, "authorizer_code": "fact(null, \"abcd\");\n\nallow if true;\n", "revocation_ids": [ - "bbf3ad51a70e935126b334f37be2bf66e90162353c19c524c0d3579ee71034996872b8433b132e6e0b519d371b0ab20481d58c4619183e8997c3744786e8e003" + "35d99762ee4343b245d66b719f7ad6180c76dd899c39e4072cf61dcf8673e7510374922457ce260b8c576431e894e38c7c0bacd3e5cae2bfc63e3286d2078d02" + ] + } + } + }, + { + "title": "test heterogeneous equal", + "filename": "test031_heterogeneous_equal.bc", + "token": [ + { + "symbols": [ + "fact", + "value", + "fact2" + ], + "public_keys": [], + "external_key": null, + "code": "check if fact(1, $value), 1 == $value;\ncheck if fact2(1, $value), 1 != $value;\n" + } + ], + "validations": { + "authorized same type": { + "world": { + "facts": [ + { + "origin": [ + null + ], + "facts": [ + "fact(1, 1)", + "fact2(1, 2)" + ] + } + ], + "rules": [], + "checks": [ + { + "origin": 0, + "checks": [ + "check if fact(1, $value), 1 == $value", + "check if fact2(1, $value), 1 != $value" + ] + } + ], + "policies": [ + "allow if true" + ] + }, + "result": { + "Ok": 0 + }, + "authorizer_code": "fact(1, 1);\nfact2(1, 2);\n\nallow if true;\n", + "revocation_ids": [ + "4af31ee86f7afbf20c7fe7e664943b2b3895693b85b3c580f3accc3bbdf8d4b1909f84787e1189c0711fd850a7cc330021e8bbe127ae3a4b36624ff7e487170e" + ] + }, + "unauthorized failed logic different type": { + "world": { + "facts": [ + { + "origin": [ + null + ], + "facts": [ + "fact(1, true)", + "fact2(1, false)" + ] + } + ], + "rules": [], + "checks": [ + { + "origin": 0, + "checks": [ + "check if fact(1, $value), 1 == $value", + "check if fact2(1, $value), 1 != $value" + ] + } + ], + "policies": [ + "allow if true" + ] + }, + "result": { + "Err": { + "FailedLogic": { + "Unauthorized": { + "policy": { + "Allow": 0 + }, + "checks": [ + { + "Block": { + "block_id": 0, + "check_id": 0, + "rule": "check if fact(1, $value), 1 == $value" + } + } + ] + } + } + } + }, + "authorizer_code": "fact(1, true);\nfact2(1, false);\n\nallow if true;\n", + "revocation_ids": [ + "4af31ee86f7afbf20c7fe7e664943b2b3895693b85b3c580f3accc3bbdf8d4b1909f84787e1189c0711fd850a7cc330021e8bbe127ae3a4b36624ff7e487170e" ] } } diff --git a/biscuit-auth/samples/test027_integer_wraparound.bc b/biscuit-auth/samples/test027_integer_wraparound.bc index 50aa63b9..5f0ff2e7 100644 Binary files a/biscuit-auth/samples/test027_integer_wraparound.bc and b/biscuit-auth/samples/test027_integer_wraparound.bc differ diff --git a/biscuit-auth/samples/test030_null.bc b/biscuit-auth/samples/test030_null.bc index 2337a111..bca87e04 100644 Binary files a/biscuit-auth/samples/test030_null.bc and b/biscuit-auth/samples/test030_null.bc differ diff --git a/biscuit-auth/samples/test031_heterogeneous_equal.bc b/biscuit-auth/samples/test031_heterogeneous_equal.bc new file mode 100644 index 00000000..a4b3b91e Binary files /dev/null and b/biscuit-auth/samples/test031_heterogeneous_equal.bc differ diff --git a/biscuit-auth/src/datalog/expression.rs b/biscuit-auth/src/datalog/expression.rs index f5fc6ed3..5dce998a 100644 --- a/biscuit-auth/src/datalog/expression.rs +++ b/biscuit-auth/src/datalog/expression.rs @@ -80,6 +80,8 @@ pub enum Binary { BitwiseOr, BitwiseXor, NotEqual, + HeterogeneousEqual, + HeterogeneousNotEqual, } impl Binary { @@ -95,8 +97,14 @@ impl Binary { (Binary::GreaterThan, Term::Integer(i), Term::Integer(j)) => Ok(Term::Bool(i > j)), (Binary::LessOrEqual, Term::Integer(i), Term::Integer(j)) => Ok(Term::Bool(i <= j)), (Binary::GreaterOrEqual, Term::Integer(i), Term::Integer(j)) => Ok(Term::Bool(i >= j)), - (Binary::Equal, Term::Integer(i), Term::Integer(j)) => Ok(Term::Bool(i == j)), - (Binary::NotEqual, Term::Integer(i), Term::Integer(j)) => Ok(Term::Bool(i != j)), + (Binary::Equal | Binary::HeterogeneousEqual, Term::Integer(i), Term::Integer(j)) => { + Ok(Term::Bool(i == j)) + } + ( + Binary::NotEqual | Binary::HeterogeneousNotEqual, + Term::Integer(i), + Term::Integer(j), + ) => Ok(Term::Bool(i != j)), (Binary::Add, Term::Integer(i), Term::Integer(j)) => i .checked_add(j) .map(Term::Integer) @@ -159,26 +167,42 @@ impl Binary { _ => Err(error::Expression::UnknownSymbol(s1)), } } - (Binary::Equal, Term::Str(i), Term::Str(j)) => Ok(Term::Bool(i == j)), - (Binary::NotEqual, Term::Str(i), Term::Str(j)) => Ok(Term::Bool(i != j)), + (Binary::Equal | Binary::HeterogeneousEqual, Term::Str(i), Term::Str(j)) => { + Ok(Term::Bool(i == j)) + } + (Binary::NotEqual | Binary::HeterogeneousNotEqual, Term::Str(i), Term::Str(j)) => { + Ok(Term::Bool(i != j)) + } // date (Binary::LessThan, Term::Date(i), Term::Date(j)) => Ok(Term::Bool(i < j)), (Binary::GreaterThan, Term::Date(i), Term::Date(j)) => Ok(Term::Bool(i > j)), (Binary::LessOrEqual, Term::Date(i), Term::Date(j)) => Ok(Term::Bool(i <= j)), (Binary::GreaterOrEqual, Term::Date(i), Term::Date(j)) => Ok(Term::Bool(i >= j)), - (Binary::Equal, Term::Date(i), Term::Date(j)) => Ok(Term::Bool(i == j)), - (Binary::NotEqual, Term::Date(i), Term::Date(j)) => Ok(Term::Bool(i != j)), + (Binary::Equal | Binary::HeterogeneousEqual, Term::Date(i), Term::Date(j)) => { + Ok(Term::Bool(i == j)) + } + (Binary::NotEqual | Binary::HeterogeneousNotEqual, Term::Date(i), Term::Date(j)) => { + Ok(Term::Bool(i != j)) + } // symbol // byte array - (Binary::Equal, Term::Bytes(i), Term::Bytes(j)) => Ok(Term::Bool(i == j)), - (Binary::NotEqual, Term::Bytes(i), Term::Bytes(j)) => Ok(Term::Bool(i != j)), + (Binary::Equal | Binary::HeterogeneousEqual, Term::Bytes(i), Term::Bytes(j)) => { + Ok(Term::Bool(i == j)) + } + (Binary::NotEqual | Binary::HeterogeneousNotEqual, Term::Bytes(i), Term::Bytes(j)) => { + Ok(Term::Bool(i != j)) + } // set - (Binary::Equal, Term::Set(set), Term::Set(s)) => Ok(Term::Bool(set == s)), - (Binary::NotEqual, Term::Set(set), Term::Set(s)) => Ok(Term::Bool(set != s)), + (Binary::Equal | Binary::HeterogeneousEqual, Term::Set(set), Term::Set(s)) => { + Ok(Term::Bool(set == s)) + } // Strict equal support heterogeneous equal for Set to avoid introducing a breaking change + (Binary::NotEqual | Binary::HeterogeneousNotEqual, Term::Set(set), Term::Set(s)) => { + Ok(Term::Bool(set != s)) + } // Strict not equal support heterogeneous not equal for Set to avoid introducing a breaking change (Binary::Intersection, Term::Set(set), Term::Set(s)) => { Ok(Term::Set(set.intersection(&s).cloned().collect())) } @@ -205,16 +229,31 @@ impl Binary { // boolean (Binary::And, Term::Bool(i), Term::Bool(j)) => Ok(Term::Bool(i & j)), (Binary::Or, Term::Bool(i), Term::Bool(j)) => Ok(Term::Bool(i | j)), - (Binary::Equal, Term::Bool(i), Term::Bool(j)) => Ok(Term::Bool(i == j)), - (Binary::NotEqual, Term::Bool(i), Term::Bool(j)) => Ok(Term::Bool(i != j)), + (Binary::Equal | Binary::HeterogeneousEqual, Term::Bool(i), Term::Bool(j)) => { + Ok(Term::Bool(i == j)) + } + (Binary::NotEqual | Binary::HeterogeneousNotEqual, Term::Bool(i), Term::Bool(j)) => { + Ok(Term::Bool(i != j)) + } // null - (Binary::Equal, Term::Null, Term::Null) => Ok(Term::Bool(true)), - (Binary::Equal, Term::Null, _) => Ok(Term::Bool(false)), - (Binary::Equal, _, Term::Null) => Ok(Term::Bool(false)), - (Binary::NotEqual, Term::Null, Term::Null) => Ok(Term::Bool(false)), - (Binary::NotEqual, Term::Null, _) => Ok(Term::Bool(true)), - (Binary::NotEqual, _, Term::Null) => Ok(Term::Bool(true)), + (Binary::Equal | Binary::HeterogeneousEqual, Term::Null, Term::Null) => { + Ok(Term::Bool(true)) + } + (Binary::HeterogeneousEqual, Term::Null, _) => Ok(Term::Bool(false)), + (Binary::HeterogeneousEqual, _, Term::Null) => Ok(Term::Bool(false)), + (Binary::NotEqual | Binary::HeterogeneousNotEqual, Term::Null, Term::Null) => { + Ok(Term::Bool(false)) + } + (Binary::HeterogeneousNotEqual, Term::Null, _) => { + Ok(Term::Bool(true)) + } + (Binary::HeterogeneousNotEqual, _, Term::Null) => { + Ok(Term::Bool(true)) + } + + (Binary::HeterogeneousEqual, _, _) => Ok(Term::Bool(false)), + (Binary::HeterogeneousNotEqual, _, _) => Ok(Term::Bool(true)), _ => { //println!("unexpected value type on the stack"); @@ -229,8 +268,10 @@ impl Binary { Binary::GreaterThan => format!("{} > {}", left, right), Binary::LessOrEqual => format!("{} <= {}", left, right), Binary::GreaterOrEqual => format!("{} >= {}", left, right), - Binary::Equal => format!("{} == {}", left, right), - Binary::NotEqual => format!("{} != {}", left, right), + Binary::Equal => format!("{} === {}", left, right), + Binary::HeterogeneousEqual => format!("{} == {}", left, right), + Binary::NotEqual => format!("{} !== {}", left, right), + Binary::HeterogeneousNotEqual => format!("{} != {}", left, right), Binary::Contains => format!("{}.contains({})", left, right), Binary::Prefix => format!("{}.starts_with({})", left, right), Binary::Suffix => format!("{}.ends_with({})", left, right), @@ -324,6 +365,8 @@ impl Expression { #[cfg(test)] mod tests { + use std::collections::BTreeSet; + use super::*; use crate::datalog::{SymbolTable, TemporarySymbolTable}; @@ -482,81 +525,155 @@ mod tests { fn null_equal() { let symbols = SymbolTable::new(); let mut tmp_symbols = TemporarySymbolTable::new(&symbols); - - let ops = vec![ - Op::Value(Term::Null), - Op::Value(Term::Null), + let values: HashMap = HashMap::new(); + let operands = vec![Op::Value(Term::Null), Op::Value(Term::Null)]; + let operators = vec![ Op::Binary(Binary::Equal), + Op::Binary(Binary::HeterogeneousEqual), ]; - let values: HashMap = HashMap::new(); - - println!("ops: {:?}", ops); + for op in operators { + let mut ops = operands.clone(); + ops.push(op); + println!("ops: {:?}", ops); - let e = Expression { ops }; - println!("print: {}", e.print(&symbols).unwrap()); + let e = Expression { ops }; + println!("print: {}", e.print(&symbols).unwrap()); - let res = e.evaluate(&values, &mut tmp_symbols); - assert_eq!(res, Ok(Term::Bool(true))); + let res = e.evaluate(&values, &mut tmp_symbols); + assert_eq!(res, Ok(Term::Bool(true))); + } } #[test] fn null_not_equal() { let symbols = SymbolTable::new(); let mut tmp_symbols = TemporarySymbolTable::new(&symbols); - - let ops = vec![ - Op::Value(Term::Null), - Op::Value(Term::Null), + let values: HashMap = HashMap::new(); + let operands = vec![Op::Value(Term::Null), Op::Value(Term::Null)]; + let operators = vec![ Op::Binary(Binary::NotEqual), + Op::Binary(Binary::HeterogeneousNotEqual), ]; - let values: HashMap = HashMap::new(); - - println!("ops: {:?}", ops); + for op in operators { + let mut ops = operands.clone(); + ops.push(op); + println!("ops: {:?}", ops); - let e = Expression { ops }; - println!("print: {}", e.print(&symbols).unwrap()); + let e = Expression { ops }; + println!("print: {}", e.print(&symbols).unwrap()); - let res = e.evaluate(&values, &mut tmp_symbols); - assert_eq!(res, Ok(Term::Bool(false))); + let res = e.evaluate(&values, &mut tmp_symbols); + assert_eq!(res, Ok(Term::Bool(false))); + } } #[test] fn null_heterogeneous() { let symbols = SymbolTable::new(); let mut tmp_symbols = TemporarySymbolTable::new(&symbols); - - let ops = vec![ - Op::Value(Term::Null), - Op::Value(Term::Integer(1)), - Op::Binary(Binary::Equal), - ]; - let values: HashMap = HashMap::new(); + let operands = vec![Op::Value(Term::Null), Op::Value(Term::Integer(1))]; + let operators = HashMap::from([ + (Op::Binary(Binary::HeterogeneousNotEqual), true), + (Op::Binary(Binary::HeterogeneousEqual), false), + ]); + + for (op, result) in operators { + let mut ops = operands.clone(); + ops.push(op); + println!("ops: {:?}", ops); - println!("ops: {:?}", ops); - - let e = Expression { ops }; - println!("print: {}", e.print(&symbols).unwrap()); + let e = Expression { ops }; + println!("print: {}", e.print(&symbols).unwrap()); - let res = e.evaluate(&values, &mut tmp_symbols); - assert_eq!(res, Ok(Term::Bool(false))); + let res = e.evaluate(&values, &mut tmp_symbols); + assert_eq!(res, Ok(Term::Bool(result))); + } + } - let ops = vec![ - Op::Value(Term::Null), - Op::Value(Term::Integer(1)), - Op::Binary(Binary::NotEqual), + #[test] + fn equal_heterogeneous() { + let symbols = SymbolTable::new(); + let mut tmp_symbols = TemporarySymbolTable::new(&symbols); + let values: HashMap = HashMap::new(); + let operands_samples = [ + vec![Op::Value(Term::Bool(true)), Op::Value(Term::Integer(1))], + vec![Op::Value(Term::Bool(true)), Op::Value(Term::Str(1))], + vec![Op::Value(Term::Integer(1)), Op::Value(Term::Str(1))], + vec![ + Op::Value(Term::Set(BTreeSet::from([Term::Integer(1)]))), + Op::Value(Term::Set(BTreeSet::from([Term::Str(1)]))), + ], + vec![ + Op::Value(Term::Bytes(Vec::new())), + Op::Value(Term::Integer(1)), + ], + vec![ + Op::Value(Term::Bytes(Vec::new())), + Op::Value(Term::Str(1025)), + ], + vec![Op::Value(Term::Date(12)), Op::Value(Term::Integer(1))], ]; + let operators = HashMap::from([ + (Op::Binary(Binary::HeterogeneousNotEqual), true), + (Op::Binary(Binary::HeterogeneousEqual), false), + ]); + + for operands in operands_samples { + let operands_reversed: Vec<_> = operands.iter().cloned().rev().collect(); + for operand in [operands, operands_reversed] { + for (op, result) in &operators { + let mut ops = operand.clone(); + ops.push(op.clone()); + println!("ops: {:?}", ops); + + let e = Expression { ops }; + println!("print: {}", e.print(&symbols).unwrap()); + + let res = e.evaluate(&values, &mut tmp_symbols); + assert_eq!(res, Ok(Term::Bool(*result))); + } + } + } + } + #[test] + fn strict_equal_heterogeneous() { + let symbols = SymbolTable::new(); + let mut tmp_symbols = TemporarySymbolTable::new(&symbols); let values: HashMap = HashMap::new(); + let operands_samples = [ + vec![Op::Value(Term::Bool(true)), Op::Value(Term::Integer(1))], + vec![Op::Value(Term::Bool(true)), Op::Value(Term::Str(1))], + vec![Op::Value(Term::Integer(1)), Op::Value(Term::Str(1))], + vec![ + Op::Value(Term::Bytes(Vec::new())), + Op::Value(Term::Integer(1)), + ], + vec![ + Op::Value(Term::Bytes(Vec::new())), + Op::Value(Term::Str(1025)), + ], + vec![Op::Value(Term::Date(12)), Op::Value(Term::Integer(1))], + ]; + let operators = vec![Op::Binary(Binary::NotEqual), Op::Binary(Binary::Equal)]; - println!("ops: {:?}", ops); + for operands in operands_samples { + let operands_reversed: Vec<_> = operands.iter().cloned().rev().collect(); + for operand in [operands, operands_reversed] { + for op in &operators { + let mut ops = operand.clone(); + ops.push(op.clone()); + println!("ops: {:?}", ops); - let e = Expression { ops }; - println!("print: {}", e.print(&symbols).unwrap()); + let e = Expression { ops }; + println!("print: {}", e.print(&symbols).unwrap()); - let res = e.evaluate(&values, &mut tmp_symbols); - assert_eq!(res, Ok(Term::Bool(true))); + e.evaluate(&values, &mut tmp_symbols).unwrap_err(); + } + } + } } } diff --git a/biscuit-auth/src/datalog/mod.rs b/biscuit-auth/src/datalog/mod.rs index 982ad78e..1ad5ab37 100644 --- a/biscuit-auth/src/datalog/mod.rs +++ b/biscuit-auth/src/datalog/mod.rs @@ -965,12 +965,13 @@ pub fn contains_v4_op(expressions: &[Expression]) -> bool { fn contains_v5_op(expressions: &[Expression]) -> bool { expressions.iter().any(|expression| { - expression.ops.iter().any(|op| { - if let Op::Value(term) = op { - contains_v5_term(term) - } else { - false - } + expression.ops.iter().any(|op| match op { + Op::Value(term) => contains_v5_term(term), + Op::Binary(binary) => match binary { + Binary::HeterogeneousEqual | Binary::HeterogeneousNotEqual => true, + _ => false, + }, + _ => false, }) }) } diff --git a/biscuit-auth/src/format/convert.rs b/biscuit-auth/src/format/convert.rs index 3d62c811..57402e52 100644 --- a/biscuit-auth/src/format/convert.rs +++ b/biscuit-auth/src/format/convert.rs @@ -633,6 +633,8 @@ pub mod v2 { Binary::BitwiseOr => Kind::BitwiseOr, Binary::BitwiseXor => Kind::BitwiseXor, Binary::NotEqual => Kind::NotEqual, + Binary::HeterogeneousEqual => Kind::HeterogeneousEqual, + Binary::HeterogeneousNotEqual => Kind::HeterogeneousNotEqual, } as i32, }) } @@ -687,6 +689,12 @@ pub mod v2 { Some(op_binary::Kind::BitwiseOr) => Op::Binary(Binary::BitwiseOr), Some(op_binary::Kind::BitwiseXor) => Op::Binary(Binary::BitwiseXor), Some(op_binary::Kind::NotEqual) => Op::Binary(Binary::NotEqual), + Some(op_binary::Kind::HeterogeneousEqual) => { + Op::Binary(Binary::HeterogeneousEqual) + } + Some(op_binary::Kind::HeterogeneousNotEqual) => { + Op::Binary(Binary::HeterogeneousNotEqual) + } None => { return Err(error::Format::DeserializationError( "deserialization error: binary operation is empty".to_string(), diff --git a/biscuit-auth/src/format/schema.proto b/biscuit-auth/src/format/schema.proto index 6bb4fcd8..88d0d3b0 100644 --- a/biscuit-auth/src/format/schema.proto +++ b/biscuit-auth/src/format/schema.proto @@ -151,6 +151,8 @@ message OpBinary { BitwiseOr = 18; BitwiseXor = 19; NotEqual = 20; + HeterogeneousEqual = 21; + HeterogeneousNotEqual = 22; } required Kind kind = 1; diff --git a/biscuit-auth/src/format/schema.rs b/biscuit-auth/src/format/schema.rs index 3d581c4e..bc59730f 100644 --- a/biscuit-auth/src/format/schema.rs +++ b/biscuit-auth/src/format/schema.rs @@ -237,6 +237,8 @@ pub mod op_binary { BitwiseOr = 18, BitwiseXor = 19, NotEqual = 20, + HeterogeneousEqual = 21, + HeterogeneousNotEqual = 22, } } #[derive(Clone, PartialEq, ::prost::Message)] diff --git a/biscuit-auth/src/parser.rs b/biscuit-auth/src/parser.rs index 1e42dc25..bf0efd5c 100644 --- a/biscuit-auth/src/parser.rs +++ b/biscuit-auth/src/parser.rs @@ -483,7 +483,7 @@ mod tests { ops: vec![ Op::Value(int(1)), Op::Value(int(2)), - Op::Binary(Binary::Equal), + Op::Binary(Binary::HeterogeneousEqual), ], }], )], @@ -641,7 +641,7 @@ mod tests { ops: vec![ Op::Value(int(1)), Op::Value(int(2)), - Op::Binary(Binary::Equal), + Op::Binary(Binary::HeterogeneousEqual), ], }], )], diff --git a/biscuit-auth/src/token/builder.rs b/biscuit-auth/src/token/builder.rs index 88779063..2ed0fb49 100644 --- a/biscuit-auth/src/token/builder.rs +++ b/biscuit-auth/src/token/builder.rs @@ -974,6 +974,8 @@ impl From for Binary { biscuit_parser::builder::Binary::BitwiseOr => Binary::BitwiseOr, biscuit_parser::builder::Binary::BitwiseXor => Binary::BitwiseXor, biscuit_parser::builder::Binary::NotEqual => Binary::NotEqual, + biscuit_parser::builder::Binary::HeterogeneousEqual => Binary::HeterogeneousEqual, + biscuit_parser::builder::Binary::HeterogeneousNotEqual => Binary::HeterogeneousNotEqual, } } } diff --git a/biscuit-parser/src/builder.rs b/biscuit-parser/src/builder.rs index f024fadc..9fd85673 100644 --- a/biscuit-parser/src/builder.rs +++ b/biscuit-parser/src/builder.rs @@ -217,6 +217,8 @@ pub enum Binary { BitwiseOr, BitwiseXor, NotEqual, + HeterogeneousEqual, + HeterogeneousNotEqual, } #[cfg(feature = "datalog-macro")] @@ -266,6 +268,12 @@ impl ToTokens for Binary { Binary::BitwiseOr => quote! { ::biscuit_auth::datalog::Binary::BitwiseOr }, Binary::BitwiseXor => quote! { ::biscuit_auth::datalog::Binary::BitwiseXor }, Binary::NotEqual => quote! { ::biscuit_auth::datalog::Binary::NotEqual }, + Binary::HeterogeneousEqual => { + quote! { ::biscuit_auth::datalog::Binary::HeterogeneousEqual} + } + Binary::HeterogeneousNotEqual => { + quote! { ::biscuit_auth::datalog::Binary::HeterogeneousNotEqual} + } }); } } diff --git a/biscuit-parser/src/parser.rs b/biscuit-parser/src/parser.rs index 9b9c96c0..6176ed39 100644 --- a/biscuit-parser/src/parser.rs +++ b/biscuit-parser/src/parser.rs @@ -456,8 +456,10 @@ fn binary_op_2(i: &str) -> IResult<&str, builder::Binary, Error> { value(Binary::GreaterOrEqual, tag(">=")), value(Binary::LessThan, tag("<")), value(Binary::GreaterThan, tag(">")), - value(Binary::Equal, tag("==")), - value(Binary::NotEqual, tag("!=")), + value(Binary::Equal, tag("===")), + value(Binary::NotEqual, tag("!==")), + value(Binary::HeterogeneousEqual, tag("==")), + value(Binary::HeterogeneousNotEqual, tag("!=")), ))(i) } @@ -1322,6 +1324,18 @@ mod tests { )) ); + assert_eq!( + super::expr("$0 === 1").map(|(i, o)| (i, o.opcodes())), + Ok(( + "", + vec![ + Op::Value(var("0")), + Op::Value(int(1)), + Op::Binary(Binary::Equal), + ], + )) + ); + assert_eq!( super::expr("$0 == 1").map(|(i, o)| (i, o.opcodes())), Ok(( @@ -1329,6 +1343,43 @@ mod tests { vec![ Op::Value(var("0")), Op::Value(int(1)), + Op::Binary(Binary::HeterogeneousEqual), + ], + )) + ); + + assert_eq!( + super::expr("$0 !== 1").map(|(i, o)| (i, o.opcodes())), + Ok(( + "", + vec![ + Op::Value(var("0")), + Op::Value(int(1)), + Op::Binary(Binary::NotEqual), + ], + )) + ); + + assert_eq!( + super::expr("$0 != 1").map(|(i, o)| (i, o.opcodes())), + Ok(( + "", + vec![ + Op::Value(var("0")), + Op::Value(int(1)), + Op::Binary(Binary::HeterogeneousNotEqual), + ], + )) + ); + + assert_eq!( + super::expr("$0.length() === $1").map(|(i, o)| (i, o.opcodes())), + Ok(( + "", + vec![ + Op::Value(var("0")), + Op::Unary(Unary::Length), + Op::Value(var("1")), Op::Binary(Binary::Equal), ], )) @@ -1342,6 +1393,45 @@ mod tests { Op::Value(var("0")), Op::Unary(Unary::Length), Op::Value(var("1")), + Op::Binary(Binary::HeterogeneousEqual), + ], + )) + ); + + assert_eq!( + super::expr("$0.length() !== $1").map(|(i, o)| (i, o.opcodes())), + Ok(( + "", + vec![ + Op::Value(var("0")), + Op::Unary(Unary::Length), + Op::Value(var("1")), + Op::Binary(Binary::NotEqual), + ], + )) + ); + + assert_eq!( + super::expr("$0.length() != $1").map(|(i, o)| (i, o.opcodes())), + Ok(( + "", + vec![ + Op::Value(var("0")), + Op::Unary(Unary::Length), + Op::Value(var("1")), + Op::Binary(Binary::HeterogeneousNotEqual), + ], + )) + ); + + assert_eq!( + super::expr("!$0 === $1").map(|(i, o)| (i, o.opcodes())), + Ok(( + "", + vec![ + Op::Value(var("0")), + Op::Unary(Unary::Negate), + Op::Value(var("1")), Op::Binary(Binary::Equal), ], )) @@ -1355,7 +1445,33 @@ mod tests { Op::Value(var("0")), Op::Unary(Unary::Negate), Op::Value(var("1")), - Op::Binary(Binary::Equal), + Op::Binary(Binary::HeterogeneousEqual), + ], + )) + ); + + assert_eq!( + super::expr("!$0 !== $1").map(|(i, o)| (i, o.opcodes())), + Ok(( + "", + vec![ + Op::Value(var("0")), + Op::Unary(Unary::Negate), + Op::Value(var("1")), + Op::Binary(Binary::NotEqual), + ], + )) + ); + + assert_eq!( + super::expr("!$0 != $1").map(|(i, o)| (i, o.opcodes())), + Ok(( + "", + vec![ + Op::Value(var("0")), + Op::Unary(Unary::Negate), + Op::Value(var("1")), + Op::Binary(Binary::HeterogeneousNotEqual), ], )) ); @@ -1388,7 +1504,7 @@ mod tests { ); assert_eq!( - super::expr("(1 > 2) == 3").map(|(i, o)| (i, o.opcodes())), + super::expr("(1 > 2) === 3").map(|(i, o)| (i, o.opcodes())), Ok(( "", vec![ @@ -1402,6 +1518,51 @@ mod tests { )) ); + assert_eq!( + super::expr("(1 > 2) == 3").map(|(i, o)| (i, o.opcodes())), + Ok(( + "", + vec![ + Op::Value(int(1)), + Op::Value(int(2)), + Op::Binary(Binary::GreaterThan), + Op::Unary(Unary::Parens), + Op::Value(int(3)), + Op::Binary(Binary::HeterogeneousEqual), + ] + )) + ); + + assert_eq!( + super::expr("(1 > 2) !== 3").map(|(i, o)| (i, o.opcodes())), + Ok(( + "", + vec![ + Op::Value(int(1)), + Op::Value(int(2)), + Op::Binary(Binary::GreaterThan), + Op::Unary(Unary::Parens), + Op::Value(int(3)), + Op::Binary(Binary::NotEqual), + ] + )) + ); + + assert_eq!( + super::expr("(1 > 2) != 3").map(|(i, o)| (i, o.opcodes())), + Ok(( + "", + vec![ + Op::Value(int(1)), + Op::Value(int(2)), + Op::Binary(Binary::GreaterThan), + Op::Unary(Unary::Parens), + Op::Value(int(3)), + Op::Binary(Binary::HeterogeneousNotEqual), + ] + )) + ); + assert_eq!( super::expr("1 > 2 + 3").map(|(i, o)| (i, o.opcodes())), Ok(( @@ -1455,7 +1616,7 @@ mod tests { ); assert_eq!( - super::expr("$0 == \"abc\"").map(|(i, o)| (i, o.opcodes())), + super::expr("$0 === \"abc\"").map(|(i, o)| (i, o.opcodes())), Ok(( "", vec![ @@ -1466,6 +1627,42 @@ mod tests { )) ); + assert_eq!( + super::expr("$0 == \"abc\"").map(|(i, o)| (i, o.opcodes())), + Ok(( + "", + vec![ + Op::Value(var("0")), + Op::Value(string("abc")), + Op::Binary(Binary::HeterogeneousEqual), + ], + )) + ); + + assert_eq!( + super::expr("$0 !== \"abc\"").map(|(i, o)| (i, o.opcodes())), + Ok(( + "", + vec![ + Op::Value(var("0")), + Op::Value(string("abc")), + Op::Binary(Binary::NotEqual), + ], + )) + ); + + assert_eq!( + super::expr("$0 != \"abc\"").map(|(i, o)| (i, o.opcodes())), + Ok(( + "", + vec![ + Op::Value(var("0")), + Op::Value(string("abc")), + Op::Binary(Binary::HeterogeneousNotEqual), + ], + )) + ); + assert_eq!( super::expr("$0.ends_with(\"abc\")").map(|(i, o)| (i, o.opcodes())), Ok((