Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for closures #202

Merged
merged 23 commits into from
Jun 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
b0a92fa
WIP closures
divarvel Dec 29, 2023
3486159
closures: remove recursion
divarvel Dec 30, 2023
ccc8612
wip support for Binary::Any (recursive calls)
divarvel Jan 1, 2024
91c852a
dedicated pre-aggregated "closure" op
divarvel Jan 2, 2024
fec7ef8
closures: support for any/all, recursive ops, protobuf encoding
divarvel Jan 2, 2024
0f6c3ad
parser support for closures and lazy boolean ops
divarvel Jan 2, 2024
0d029b0
wip: nested closures
divarvel Jan 3, 2024
1fb6e21
Remove self-modifying ops stack
divarvel Jan 3, 2024
8d2371d
Merge branch 'v5' into closures
Geal May 22, 2024
025ba46
remove unused sample
Geal May 22, 2024
2f2c3d8
Merge branch 'v5' into closures
Geal May 23, 2024
9876514
chore: remove leftover printlns
divarvel May 23, 2024
8433c34
fix: don't print legacy && / || the same way
divarvel May 23, 2024
ac30afa
fix: detect shadowed variables in closure parameters
divarvel May 24, 2024
89cbdb5
fix: unused variables
divarvel May 24, 2024
e98cb78
more tests for closures
divarvel May 24, 2024
de7d207
fix: remove debug output from samples README
divarvel May 24, 2024
61e253a
Merge branch 'v5' into closures
Geal May 25, 2024
70e65e7
Merge remote-tracking branch 'origin/v5' into closures
divarvel May 26, 2024
85d7d51
feat: v5 block detection for closures and new operators
divarvel May 26, 2024
159998b
trigger shadowing detection
divarvel May 26, 2024
fab2f24
improve coverage for closures handling
divarvel May 26, 2024
6fe9bfd
Merge remote-tracking branch 'origin/v5' into closures
divarvel May 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 65 additions & 13 deletions biscuit-auth/examples/testcases.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,8 @@ fn run(target: String, root_key: Option<String>, test: bool, json: bool) {

add_test_result(&mut results, heterogeneous_equal(&target, &root, test));

add_test_result(&mut results, closures(&target, &root, test));

if json {
let s = serde_json::to_string_pretty(&TestCases {
root_private_key: hex::encode(root.private().to_bytes()),
Expand Down Expand Up @@ -1277,12 +1279,6 @@ fn expressions(target: &str, root: &KeyPair, test: bool) -> TestResult {
check if true;
//boolean false and negation
check if !false;
//boolean and
check if !false && true;
//boolean or
check if false || true;
//boolean parens
check if (true || false) && true;
// boolean strict equality
check if true === true;
check if false === false;
Expand All @@ -1303,7 +1299,7 @@ fn expressions(target: &str, root: &KeyPair, test: bool) -> TestResult {
check if 1 + 2 * 3 - 4 /2 === 5;

// string prefix and suffix
check if "hello world".starts_with("hello") && "hello world".ends_with("world");
check if "hello world".starts_with("hello"), "hello world".ends_with("world");
// string regex
check if "aaabde".matches("a*c?.e");
// string contains
Expand Down Expand Up @@ -1893,12 +1889,9 @@ fn integer_wraparound(target: &str, root: &KeyPair, test: bool) -> TestResult {

let biscuit = biscuit!(
r#"
// integer overflows must abort evaluating the whole expression
// todo update this test when integer overflows abort
// the whole datalog evaluation
check if true || 10000000000 * 10000000000 != 0;
check if true || 9223372036854775807 + 1 != 0;
check if true || -9223372036854775808 - 1 != 0;
check if 10000000000 * 10000000000 != 0;
check if 9223372036854775807 + 1 != 0;
check if -9223372036854775808 - 1 != 0;
"#
)
.build_with_rng(&root, SymbolTable::default(), &mut rng)
Expand Down Expand Up @@ -2079,6 +2072,65 @@ fn heterogeneous_equal(target: &str, root: &KeyPair, test: bool) -> TestResult {
}
}

fn closures(target: &str, root: &KeyPair, test: bool) -> TestResult {
let mut rng: StdRng = SeedableRng::seed_from_u64(1234);
let title = "test laziness and closures".to_string();
let filename = "test032_laziness_closures".to_string();
let token;

let biscuit = biscuit!(
r#"
// boolean and
check if !false && true;
// boolean or
check if false || true;
// boolean parens
check if (true || false) && true;
// boolean and laziness
check if !(false && "x".intersection("x"));
// boolean or laziness
check if true || "x".intersection("x");
// all
check if [1,2,3].all($p -> $p > 0);
// all
check if ![1,2,3].all($p -> $p == 2);
// any
check if [1,2,3].any($p -> $p > 2);
// any
check if ![1,2,3].any($p -> $p > 3);
// nested closures
check if [1,2,3].any($p -> $p > 1 && [3,4,5].any($q -> $p == $q));
"#
)
.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(
"".to_string(),
validate_token(root, &data[..], "allow if true"),
);
validations.insert(
"shadowing".to_string(),
validate_token(
root,
&data[..],
"allow if [true].any($p -> [true].all($p -> $p))",
),
);

TestResult {
title,
filename,
token,
validations,
}
}

fn print_blocks(token: &Biscuit) -> Vec<BlockContent> {
let mut v = Vec::new();

Expand Down
133 changes: 117 additions & 16 deletions biscuit-auth/samples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1221,9 +1221,6 @@ public keys: []
```
check if true;
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 1 < 2;
Expand All @@ -1234,7 +1231,7 @@ check if 2 >= 1;
check if 2 >= 2;
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 "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";
Expand Down Expand Up @@ -1270,7 +1267,7 @@ allow if true;
```

revocation ids:
- `3d5b23b502b3dd920bfb68b9039164d1563bb8927210166fa5c17f41b76b31bb957bc2ed3318452958f658baa2d398fe4cf25c58a27e6c8bc42c9702c8aa1b0c`
- `d0420227266e3583a42dfaa0e38550d99f681d150dd18856f3af9a697bc9c5c8bf06b4b0fe5b9df0377d1b963574e2fd210a0a76a8b0756a65f640c602bebd07`

authorizer world:
```
Expand All @@ -1284,15 +1281,13 @@ World {
),
checks: [
"check if !false",
"check if !false && true",
"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 \"hello world\".starts_with(\"hello\") && \"hello world\".ends_with(\"world\")",
"check if \"hello world\".starts_with(\"hello\"), \"hello world\".ends_with(\"world\")",
"check if \"é\".length() === 2",
"check if (true || false) && true",
"check if 1 + 2 * 3 - 4 / 2 === 5",
"check if 1 < 2",
"check if 1 <= 1",
Expand Down Expand Up @@ -1320,7 +1315,6 @@ World {
"check if [false, true].contains(true)",
"check if [hex:12ab, hex:34de].contains(hex:34de)",
"check if false === false",
"check if false || true",
"check if hex:12ab === hex:12ab",
"check if true",
"check if true === true",
Expand Down Expand Up @@ -2263,9 +2257,9 @@ symbols: []
public keys: []

```
check if true || 10000000000 * 10000000000 != 0;
check if true || 9223372036854775807 + 1 != 0;
check if true || -9223372036854775808 - 1 != 0;
check if 10000000000 * 10000000000 != 0;
check if 9223372036854775807 + 1 != 0;
check if -9223372036854775808 - 1 != 0;
```

### validation
Expand All @@ -2276,7 +2270,7 @@ allow if true;
```

revocation ids:
- `a57be539aae237040fe6c2c28c4263516147c9f0d1d7ba88a385f1574f504c544164a2c747efd8b30eaab9d351c383cc1875642f173546d5f4b53b2220c87a0a`
- `365092619226161cf3973343f02c829fe05ab2b0d01f09555272348c9fcce041846be6159badd643aee108c9ce735ca8d12a009979c46b6e2c46e7999824c008`

authorizer world:
```
Expand All @@ -2289,9 +2283,9 @@ World {
0,
),
checks: [
"check if true || -9223372036854775808 - 1 != 0",
"check if true || 10000000000 * 10000000000 != 0",
"check if true || 9223372036854775807 + 1 != 0",
"check if -9223372036854775808 - 1 != 0",
"check if 10000000000 * 10000000000 != 0",
"check if 9223372036854775807 + 1 != 0",
],
},
]
Expand Down Expand Up @@ -2765,3 +2759,110 @@ World {

result: `Err(FailedLogic(Unauthorized { policy: Allow(0), checks: [Block(FailedBlockCheck { block_id: 0, check_id: 0, rule: "check if fact(1, $value), 1 == $value" })] }))`


------------------------------

## test laziness and closures: test032_laziness_closures.bc
### token

authority:
symbols: ["x", "p", "q"]

public keys: []

```
check if !false && true;
check if false || true;
check if (true || false) && true;
check if !(false && "x".intersection("x"));
check if true || "x".intersection("x");
check if [1, 2, 3].all($p -> $p > 0);
check if ![1, 2, 3].all($p -> $p == 2);
check if [1, 2, 3].any($p -> $p > 2);
check if ![1, 2, 3].any($p -> $p > 3);
check if [1, 2, 3].any($p -> $p > 1 && [3, 4, 5].any($q -> $p == $q));
```

### validation

authorizer code:
```
allow if true;
```

revocation ids:
- `65e4da4fa213559d3b1097424504d2c9daeb28b4db51c49254852b6f57dc55e200f2f977b459f0c35e17c3c06394bfcaf5db7106e23bb2a623f48c4b84649a0b`

authorizer world:
```
World {
facts: []
rules: []
checks: [
Checks {
origin: Some(
0,
),
checks: [
"check if !(false && \"x\".intersection(\"x\"))",
"check if ![1, 2, 3].all($p -> $p == 2)",
"check if ![1, 2, 3].any($p -> $p > 3)",
"check if !false && true",
"check if (true || false) && true",
"check if [1, 2, 3].all($p -> $p > 0)",
"check if [1, 2, 3].any($p -> $p > 1 && [3, 4, 5].any($q -> $p == $q))",
"check if [1, 2, 3].any($p -> $p > 2)",
"check if false || true",
"check if true || \"x\".intersection(\"x\")",
],
},
]
policies: [
"allow if true",
]
}
```

result: `Ok(0)`
### validation for "shadowing"

authorizer code:
```
allow if [true].any($p -> [true].all($p -> $p));
```

revocation ids:
- `65e4da4fa213559d3b1097424504d2c9daeb28b4db51c49254852b6f57dc55e200f2f977b459f0c35e17c3c06394bfcaf5db7106e23bb2a623f48c4b84649a0b`

authorizer world:
```
World {
facts: []
rules: []
checks: [
Checks {
origin: Some(
0,
),
checks: [
"check if !(false && \"x\".intersection(\"x\"))",
"check if ![1, 2, 3].all($p -> $p == 2)",
"check if ![1, 2, 3].any($p -> $p > 3)",
"check if !false && true",
"check if (true || false) && true",
"check if [1, 2, 3].all($p -> $p > 0)",
"check if [1, 2, 3].any($p -> $p > 1 && [3, 4, 5].any($q -> $p == $q))",
"check if [1, 2, 3].any($p -> $p > 2)",
"check if false || true",
"check if true || \"x\".intersection(\"x\")",
],
},
]
policies: [
"allow if [true].any($p -> [true].all($p -> $p))",
]
}
```

result: `Err(Execution(ShadowedVariable))`

Loading
Loading