diff --git a/Cargo.toml b/Cargo.toml index 9dcf7dc8..45780548 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,2 +1,3 @@ [workspace] members = ["biscuit-auth", "biscuit-quote", "biscuit-parser", "biscuit-capi"] +resolver = "2" diff --git a/biscuit-auth/Cargo.toml b/biscuit-auth/Cargo.toml index 334c7d68..01a92e67 100644 --- a/biscuit-auth/Cargo.toml +++ b/biscuit-auth/Cargo.toml @@ -33,7 +33,7 @@ prost-types = "0.10" regex = { version = "1.5", default-features = false, features = ["std"] } nom = { version = "7", default-features = false, features = ["std"] } hex = "0.4" -zeroize = { version = "1", default-features = false } +zeroize = { version = "1.5", default-features = false } thiserror = "1" rand = { version = "0.8" } wasm-bindgen = { version = "0.2", optional = true } @@ -48,7 +48,11 @@ biscuit-quote = { version = "0.2.2", optional = true, path = "../biscuit-quote" chrono = { version = "0.4.26", optional = true, default-features = false, features = [ "serde", ] } - +serde_json = "1.0.117" +ecdsa = { version = "0.16.9", features = ["signing", "verifying", "pem", "alloc", "pkcs8", "serde"] } +p256 = "0.13.2" +pkcs8 = "0.9.0" +elliptic-curve = { version = "0.13.8", features = ["pkcs8"] } [dev-dependencies] bencher = "0.1.5" diff --git a/biscuit-auth/benches/token.rs b/biscuit-auth/benches/token.rs index e68279dd..a4ca33fe 100644 --- a/biscuit-auth/benches/token.rs +++ b/biscuit-auth/benches/token.rs @@ -3,22 +3,25 @@ extern crate biscuit_auth as biscuit; use std::time::Duration; use biscuit::{ - builder::*, builder_ext::BuilderExt, datalog::SymbolTable, AuthorizerLimits, Biscuit, KeyPair, - UnverifiedBiscuit, + builder::*, + builder_ext::{AuthorizerExt, BuilderExt}, + datalog::SymbolTable, + AuthorizerLimits, Biscuit, KeyPair, UnverifiedBiscuit, }; use codspeed_bencher_compat::{benchmark_group, benchmark_main, Bencher}; use rand::rngs::OsRng; fn create_block_1(b: &mut Bencher) { let mut rng = OsRng; - let root = KeyPair::new_with_rng(&mut rng); - - let mut builder = Biscuit::builder(); - builder.add_fact(fact("right", &[string("file1"), string("read")])); - builder.add_fact(fact("right", &[string("file2"), string("read")])); - builder.add_fact(fact("right", &[string("file1"), string("write")])); - - let token = builder + let root = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); + + let token = Biscuit::builder() + .fact(fact("right", &[string("file1"), string("read")])) + .unwrap() + .fact(fact("right", &[string("file2"), string("read")])) + .unwrap() + .fact(fact("right", &[string("file1"), string("write")])) + .unwrap() .build_with_rng(&root, SymbolTable::default(), &mut rng) .unwrap(); let data = token.to_vec().unwrap(); @@ -26,36 +29,38 @@ fn create_block_1(b: &mut Bencher) { b.bytes = data.len() as u64; assert_eq!(b.bytes, 206); b.iter(|| { - let mut builder = Biscuit::builder(); - builder.add_fact(fact("right", &[string("file1"), string("read")])); - builder.add_fact(fact("right", &[string("file2"), string("read")])); - builder.add_fact(fact("right", &[string("file1"), string("write")])); - - let token = builder + let token = Biscuit::builder() + .fact(fact("right", &[string("file1"), string("read")])) + .unwrap() + .fact(fact("right", &[string("file2"), string("read")])) + .unwrap() + .fact(fact("right", &[string("file1"), string("write")])) + .unwrap() .build_with_rng(&root, SymbolTable::default(), &mut rng) .unwrap(); - let data = token.to_vec().unwrap(); + let _data = token.to_vec().unwrap(); }); } fn append_block_2(b: &mut Bencher) { let mut rng: OsRng = OsRng; - let root = KeyPair::new_with_rng(&mut rng); - let keypair2 = KeyPair::new_with_rng(&mut rng); - - let mut builder = Biscuit::builder(); - builder.add_fact(fact("right", &[string("file1"), string("read")])); - builder.add_fact(fact("right", &[string("file2"), string("read")])); - builder.add_fact(fact("right", &[string("file1"), string("write")])); - - let token = builder + let root = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); + let keypair2 = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); + + let token = Biscuit::builder() + .fact(fact("right", &[string("file1"), string("read")])) + .unwrap() + .fact(fact("right", &[string("file2"), string("read")])) + .unwrap() + .fact(fact("right", &[string("file1"), string("write")])) + .unwrap() .build_with_rng(&root, SymbolTable::default(), &mut rng) .unwrap(); let base_data = token.to_vec().unwrap(); - let mut block_builder = BlockBuilder::new(); - block_builder.check_resource("file1"); - block_builder.check_operation("read"); + let block_builder = BlockBuilder::new() + .check_resource("file1") + .check_operation("read"); let token2 = token.append_with_keypair(&keypair2, block_builder).unwrap(); let data = token2.to_vec().unwrap(); @@ -64,36 +69,37 @@ fn append_block_2(b: &mut Bencher) { assert_eq!(b.bytes, 189); b.iter(|| { let token = Biscuit::from(&base_data, &root.public()).unwrap(); - let mut block_builder = BlockBuilder::new(); - block_builder.check_resource("file1"); - block_builder.check_operation("read"); + let block_builder = BlockBuilder::new() + .check_resource("file1") + .check_operation("read"); let token2 = token.append_with_keypair(&keypair2, block_builder).unwrap(); - let data = token2.to_vec().unwrap(); + let _data = token2.to_vec().unwrap(); }); } fn append_block_5(b: &mut Bencher) { let mut rng: OsRng = OsRng; - let root = KeyPair::new_with_rng(&mut rng); - let keypair2 = KeyPair::new_with_rng(&mut rng); - let keypair3 = KeyPair::new_with_rng(&mut rng); - let keypair4 = KeyPair::new_with_rng(&mut rng); - let keypair5 = KeyPair::new_with_rng(&mut rng); - - let mut builder = Biscuit::builder(); - builder.add_fact(fact("right", &[string("file1"), string("read")])); - builder.add_fact(fact("right", &[string("file2"), string("read")])); - builder.add_fact(fact("right", &[string("file1"), string("write")])); - - let token = builder + let root = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); + let keypair2 = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); + let keypair3 = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); + let keypair4 = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); + let keypair5 = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); + + let token = Biscuit::builder() + .fact(fact("right", &[string("file1"), string("read")])) + .unwrap() + .fact(fact("right", &[string("file2"), string("read")])) + .unwrap() + .fact(fact("right", &[string("file1"), string("write")])) + .unwrap() .build_with_rng(&root, SymbolTable::default(), &mut rng) .unwrap(); let base_data = token.to_vec().unwrap(); - let mut block_builder = BlockBuilder::new(); - block_builder.check_resource("file1"); - block_builder.check_operation("read"); + let block_builder = BlockBuilder::new() + .check_resource("file1") + .check_operation("read"); let token2 = token.append_with_keypair(&keypair2, block_builder).unwrap(); let data = token2.to_vec().unwrap(); @@ -102,49 +108,56 @@ fn append_block_5(b: &mut Bencher) { assert_eq!(b.bytes, 189); b.iter(|| { let token2 = Biscuit::from(&data, &root.public()).unwrap(); - let mut b = BlockBuilder::new(); - b.check_resource("file1"); - b.check_operation("read"); + let block_builder = BlockBuilder::new() + .check_resource("file1") + .check_operation("read"); - let token3 = token2.append_with_keypair(&keypair3, b).unwrap(); + let token3 = token2 + .append_with_keypair(&keypair3, block_builder) + .unwrap(); let data = token3.to_vec().unwrap(); let token3 = Biscuit::from(&data, &root.public()).unwrap(); - let mut b = BlockBuilder::new(); - b.check_resource("file1"); - b.check_operation("read"); + let block_builder = BlockBuilder::new() + .check_resource("file1") + .check_operation("read"); - let token4 = token3.append_with_keypair(&keypair4, b).unwrap(); + let token4 = token3 + .append_with_keypair(&keypair4, block_builder) + .unwrap(); let data = token4.to_vec().unwrap(); let token4 = Biscuit::from(&data, &root.public()).unwrap(); - let mut b = BlockBuilder::new(); - b.check_resource("file1"); - b.check_operation("read"); + let block_builder = BlockBuilder::new() + .check_resource("file1") + .check_operation("read"); - let token5 = token4.append_with_keypair(&keypair5, b).unwrap(); - let data = token5.to_vec().unwrap(); + let token5 = token4 + .append_with_keypair(&keypair5, block_builder) + .unwrap(); + let _data = token5.to_vec().unwrap(); }); } fn unverified_append_block_2(b: &mut Bencher) { let mut rng: OsRng = OsRng; - let root = KeyPair::new_with_rng(&mut rng); - let keypair2 = KeyPair::new_with_rng(&mut rng); - - let mut builder = Biscuit::builder(); - builder.add_fact(fact("right", &[string("file1"), string("read")])); - builder.add_fact(fact("right", &[string("file2"), string("read")])); - builder.add_fact(fact("right", &[string("file1"), string("write")])); - - let token = builder + let root = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); + let keypair2 = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); + + let token = Biscuit::builder() + .fact(fact("right", &[string("file1"), string("read")])) + .unwrap() + .fact(fact("right", &[string("file2"), string("read")])) + .unwrap() + .fact(fact("right", &[string("file1"), string("write")])) + .unwrap() .build_with_rng(&root, SymbolTable::default(), &mut rng) .unwrap(); let base_data = token.to_vec().unwrap(); - let mut block_builder = BlockBuilder::new(); - block_builder.check_resource("file1"); - block_builder.check_operation("read"); + let block_builder = BlockBuilder::new() + .check_resource("file1") + .check_operation("read"); let token2 = token.append_with_keypair(&keypair2, block_builder).unwrap(); let data = token2.to_vec().unwrap(); @@ -153,36 +166,37 @@ fn unverified_append_block_2(b: &mut Bencher) { assert_eq!(b.bytes, 189); b.iter(|| { let token = UnverifiedBiscuit::from(&base_data).unwrap(); - let mut block_builder = BlockBuilder::new(); - block_builder.check_resource("file1"); - block_builder.check_operation("read"); + let block_builder = BlockBuilder::new() + .check_resource("file1") + .check_operation("read"); let token2 = token.append_with_keypair(&keypair2, block_builder).unwrap(); - let data = token2.to_vec().unwrap(); + let _data = token2.to_vec().unwrap(); }); } fn unverified_append_block_5(b: &mut Bencher) { let mut rng: OsRng = OsRng; - let root = KeyPair::new_with_rng(&mut rng); - let keypair2 = KeyPair::new_with_rng(&mut rng); - let keypair3 = KeyPair::new_with_rng(&mut rng); - let keypair4 = KeyPair::new_with_rng(&mut rng); - let keypair5 = KeyPair::new_with_rng(&mut rng); - - let mut builder = Biscuit::builder(); - builder.add_fact(fact("right", &[string("file1"), string("read")])); - builder.add_fact(fact("right", &[string("file2"), string("read")])); - builder.add_fact(fact("right", &[string("file1"), string("write")])); - - let token = builder + let root = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); + let keypair2 = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); + let keypair3 = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); + let keypair4 = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); + let keypair5 = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); + + let token = Biscuit::builder() + .fact(fact("right", &[string("file1"), string("read")])) + .unwrap() + .fact(fact("right", &[string("file2"), string("read")])) + .unwrap() + .fact(fact("right", &[string("file1"), string("write")])) + .unwrap() .build_with_rng(&root, SymbolTable::default(), &mut rng) .unwrap(); let base_data = token.to_vec().unwrap(); - let mut block_builder = BlockBuilder::new(); - block_builder.check_resource("file1"); - block_builder.check_operation("read"); + let block_builder = BlockBuilder::new() + .check_resource("file1") + .check_operation("read"); let token2 = token.append_with_keypair(&keypair2, block_builder).unwrap(); let data = token2.to_vec().unwrap(); @@ -191,60 +205,71 @@ fn unverified_append_block_5(b: &mut Bencher) { assert_eq!(b.bytes, 189); b.iter(|| { let token2 = UnverifiedBiscuit::from(&data).unwrap(); - let mut b = BlockBuilder::new(); - b.check_resource("file1"); - b.check_operation("read"); + let block_builder = BlockBuilder::new() + .check_resource("file1") + .check_operation("read"); - let token3 = token2.append_with_keypair(&keypair3, b).unwrap(); + let token3 = token2 + .append_with_keypair(&keypair3, block_builder) + .unwrap(); let data = token3.to_vec().unwrap(); let token3 = UnverifiedBiscuit::from(&data).unwrap(); - let mut b = BlockBuilder::new(); - b.check_resource("file1"); - b.check_operation("read"); + let block_builder = BlockBuilder::new() + .check_resource("file1") + .check_operation("read"); - let token4 = token3.append_with_keypair(&keypair4, b).unwrap(); + let token4 = token3 + .append_with_keypair(&keypair4, block_builder) + .unwrap(); let data = token4.to_vec().unwrap(); let token4 = UnverifiedBiscuit::from(&data).unwrap(); - let mut b = BlockBuilder::new(); - b.check_resource("file1"); - b.check_operation("read"); + let block_builder = BlockBuilder::new() + .check_resource("file1") + .check_operation("read"); - let token5 = token4.append_with_keypair(&keypair5, b).unwrap(); - let data = token5.to_vec().unwrap(); + let token5 = token4 + .append_with_keypair(&keypair5, block_builder) + .unwrap(); + let _data = token5.to_vec().unwrap(); }); } fn verify_block_2(b: &mut Bencher) { let mut rng: OsRng = OsRng; - let root = KeyPair::new_with_rng(&mut rng); - let keypair2 = KeyPair::new_with_rng(&mut rng); + let root = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); + let keypair2 = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); let data = { - let mut builder = Biscuit::builder(); - builder.add_fact(fact("right", &[string("file1"), string("read")])); - builder.add_fact(fact("right", &[string("file2"), string("read")])); - builder.add_fact(fact("right", &[string("file1"), string("write")])); - - let token = builder + let token = Biscuit::builder() + .fact(fact("right", &[string("file1"), string("read")])) + .unwrap() + .fact(fact("right", &[string("file2"), string("read")])) + .unwrap() + .fact(fact("right", &[string("file1"), string("write")])) + .unwrap() .build_with_rng(&root, SymbolTable::default(), &mut rng) .unwrap(); - let base_data = token.to_vec().unwrap(); + let _base_data = token.to_vec().unwrap(); - let mut block_builder = BlockBuilder::new(); - block_builder.check_resource("file1"); - block_builder.check_operation("read"); + let block_builder = BlockBuilder::new() + .check_resource("file1") + .check_operation("read"); let token2 = token.append_with_keypair(&keypair2, block_builder).unwrap(); token2.to_vec().unwrap() }; let token = Biscuit::from(&data, &root.public()).unwrap(); - let mut verifier = token.authorizer().unwrap(); - verifier.add_fact("resource(\"file1\")"); - verifier.add_fact("operation(\"read\")"); - verifier.allow(); + let mut verifier = AuthorizerBuilder::new() + .fact("resource(\"file1\")") + .unwrap() + .fact("operation(\"read\")") + .unwrap() + .allow_all() + .build(&token) + .unwrap(); verifier .authorize_with_limits(AuthorizerLimits { max_time: Duration::from_secs(10), @@ -254,10 +279,14 @@ fn verify_block_2(b: &mut Bencher) { b.bytes = data.len() as u64; b.iter(|| { let token = Biscuit::from(&data, &root.public()).unwrap(); - let mut verifier = token.authorizer().unwrap(); - verifier.add_fact("resource(\"file1\")"); - verifier.add_fact("operation(\"read\")"); - verifier.allow(); + let mut verifier = AuthorizerBuilder::new() + .fact("resource(\"file1\")") + .unwrap() + .fact("operation(\"read\")") + .unwrap() + .allow_all() + .build(&token) + .unwrap(); verifier .authorize_with_limits(AuthorizerLimits { max_time: Duration::from_secs(10), @@ -269,48 +298,49 @@ fn verify_block_2(b: &mut Bencher) { fn verify_block_5(b: &mut Bencher) { let mut rng: OsRng = OsRng; - let root = KeyPair::new_with_rng(&mut rng); - let keypair2 = KeyPair::new_with_rng(&mut rng); - let keypair3 = KeyPair::new_with_rng(&mut rng); - let keypair4 = KeyPair::new_with_rng(&mut rng); - let keypair5 = KeyPair::new_with_rng(&mut rng); + let root = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); + let keypair2 = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); + let keypair3 = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); + let keypair4 = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); + let keypair5 = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); let data = { - let mut builder = Biscuit::builder(); - builder.add_fact(fact("right", &[string("file1"), string("read")])); - builder.add_fact(fact("right", &[string("file2"), string("read")])); - builder.add_fact(fact("right", &[string("file1"), string("write")])); - - let token = builder + let token = Biscuit::builder() + .fact(fact("right", &[string("file1"), string("read")])) + .unwrap() + .fact(fact("right", &[string("file2"), string("read")])) + .unwrap() + .fact(fact("right", &[string("file1"), string("write")])) + .unwrap() .build_with_rng(&root, SymbolTable::default(), &mut rng) .unwrap(); - let base_data = token.to_vec().unwrap(); + let _base_data = token.to_vec().unwrap(); - let mut block_builder = BlockBuilder::new(); - block_builder.check_resource("file1"); - block_builder.check_operation("read"); + let block_builder = BlockBuilder::new() + .check_resource("file1") + .check_operation("read"); let token2 = token.append_with_keypair(&keypair2, block_builder).unwrap(); - let mut block_builder = BlockBuilder::new(); - block_builder.check_resource("file1"); - block_builder.check_operation("read"); + let block_builder = BlockBuilder::new() + .check_resource("file1") + .check_operation("read"); let token3 = token2 .append_with_keypair(&keypair3, block_builder) .unwrap(); - let mut block_builder = BlockBuilder::new(); - block_builder.check_resource("file1"); - block_builder.check_operation("read"); + let block_builder = BlockBuilder::new() + .check_resource("file1") + .check_operation("read"); let token4 = token3 .append_with_keypair(&keypair4, block_builder) .unwrap(); - let mut block_builder = BlockBuilder::new(); - block_builder.check_resource("file1"); - block_builder.check_operation("read"); + let block_builder = BlockBuilder::new() + .check_resource("file1") + .check_operation("read"); let token5 = token4 .append_with_keypair(&keypair5, block_builder) @@ -319,10 +349,14 @@ fn verify_block_5(b: &mut Bencher) { }; let token = Biscuit::from(&data, &root.public()).unwrap(); - let mut verifier = token.authorizer().unwrap(); - verifier.add_fact("resource(\"file1\")"); - verifier.add_fact("operation(\"read\")"); - verifier.allow(); + let mut verifier = AuthorizerBuilder::new() + .fact("resource(\"file1\")") + .unwrap() + .fact("operation(\"read\")") + .unwrap() + .allow_all() + .build(&token) + .unwrap(); verifier .authorize_with_limits(AuthorizerLimits { max_time: Duration::from_secs(10), @@ -333,10 +367,14 @@ fn verify_block_5(b: &mut Bencher) { b.bytes = data.len() as u64; b.iter(|| { let token = Biscuit::from(&data, &root.public()).unwrap(); - let mut verifier = token.authorizer().unwrap(); - verifier.add_fact("resource(\"file1\")"); - verifier.add_fact("operation(\"read\")"); - verifier.allow(); + let mut verifier = AuthorizerBuilder::new() + .fact("resource(\"file1\")") + .unwrap() + .fact("operation(\"read\")") + .unwrap() + .allow_all() + .build(&token) + .unwrap(); verifier .authorize_with_limits(AuthorizerLimits { max_time: Duration::from_secs(10), @@ -348,33 +386,38 @@ fn verify_block_5(b: &mut Bencher) { fn check_signature_2(b: &mut Bencher) { let mut rng: OsRng = OsRng; - let root = KeyPair::new_with_rng(&mut rng); - let keypair2 = KeyPair::new_with_rng(&mut rng); + let root = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); + let keypair2 = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); let data = { - let mut builder = Biscuit::builder(); - builder.add_fact(fact("right", &[string("file1"), string("read")])); - builder.add_fact(fact("right", &[string("file2"), string("read")])); - builder.add_fact(fact("right", &[string("file1"), string("write")])); - - let token = builder + let token = Biscuit::builder() + .fact(fact("right", &[string("file1"), string("read")])) + .unwrap() + .fact(fact("right", &[string("file2"), string("read")])) + .unwrap() + .fact(fact("right", &[string("file1"), string("write")])) + .unwrap() .build_with_rng(&root, SymbolTable::default(), &mut rng) .unwrap(); - let base_data = token.to_vec().unwrap(); + let _base_data = token.to_vec().unwrap(); - let mut block_builder = BlockBuilder::new(); - block_builder.check_resource("file1"); - block_builder.check_operation("read"); + let block_builder = BlockBuilder::new() + .check_resource("file1") + .check_operation("read"); let token2 = token.append_with_keypair(&keypair2, block_builder).unwrap(); token2.to_vec().unwrap() }; let token = Biscuit::from(&data, &root.public()).unwrap(); - let mut verifier = token.authorizer().unwrap(); - verifier.add_fact("resource(\"file1\")"); - verifier.add_fact("operation(\"read\")"); - verifier.allow(); + let mut verifier = AuthorizerBuilder::new() + .fact("resource(\"file1\")") + .unwrap() + .fact("operation(\"read\")") + .unwrap() + .allow_all() + .build(&token) + .unwrap(); verifier .authorize_with_limits(AuthorizerLimits { max_time: Duration::from_secs(10), @@ -384,53 +427,54 @@ fn check_signature_2(b: &mut Bencher) { b.bytes = data.len() as u64; b.iter(|| { - let token = Biscuit::from(&data, &root.public()).unwrap(); + let _token = Biscuit::from(&data, &root.public()).unwrap(); }); } fn check_signature_5(b: &mut Bencher) { let mut rng: OsRng = OsRng; - let root = KeyPair::new_with_rng(&mut rng); - let keypair2 = KeyPair::new_with_rng(&mut rng); - let keypair3 = KeyPair::new_with_rng(&mut rng); - let keypair4 = KeyPair::new_with_rng(&mut rng); - let keypair5 = KeyPair::new_with_rng(&mut rng); + let root = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); + let keypair2 = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); + let keypair3 = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); + let keypair4 = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); + let keypair5 = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); let data = { - let mut builder = Biscuit::builder(); - builder.add_fact(fact("right", &[string("file1"), string("read")])); - builder.add_fact(fact("right", &[string("file2"), string("read")])); - builder.add_fact(fact("right", &[string("file1"), string("write")])); - - let token = builder + let token = Biscuit::builder() + .fact(fact("right", &[string("file1"), string("read")])) + .unwrap() + .fact(fact("right", &[string("file2"), string("read")])) + .unwrap() + .fact(fact("right", &[string("file1"), string("write")])) + .unwrap() .build_with_rng(&root, SymbolTable::default(), &mut rng) .unwrap(); - let base_data = token.to_vec().unwrap(); + let _base_data = token.to_vec().unwrap(); - let mut block_builder = BlockBuilder::new(); - block_builder.check_resource("file1"); - block_builder.check_operation("read"); + let block_builder = BlockBuilder::new() + .check_resource("file1") + .check_operation("read"); let token2 = token.append_with_keypair(&keypair2, block_builder).unwrap(); - let mut block_builder = BlockBuilder::new(); - block_builder.check_resource("file1"); - block_builder.check_operation("read"); + let block_builder = BlockBuilder::new() + .check_resource("file1") + .check_operation("read"); let token3 = token2 .append_with_keypair(&keypair3, block_builder) .unwrap(); - let mut block_builder = BlockBuilder::new(); - block_builder.check_resource("file1"); - block_builder.check_operation("read"); + let block_builder = BlockBuilder::new() + .check_resource("file1") + .check_operation("read"); let token4 = token3 .append_with_keypair(&keypair4, block_builder) .unwrap(); - let mut block_builder = BlockBuilder::new(); - block_builder.check_resource("file1"); - block_builder.check_operation("read"); + let block_builder = BlockBuilder::new() + .check_resource("file1") + .check_operation("read"); let token5 = token4 .append_with_keypair(&keypair5, block_builder) @@ -439,10 +483,14 @@ fn check_signature_5(b: &mut Bencher) { }; let token = Biscuit::from(&data, &root.public()).unwrap(); - let mut verifier = token.authorizer().unwrap(); - verifier.add_fact("resource(\"file1\")"); - verifier.add_fact("operation(\"read\")"); - verifier.allow(); + let mut verifier = AuthorizerBuilder::new() + .fact("resource(\"file1\")") + .unwrap() + .fact("operation(\"read\")") + .unwrap() + .allow_all() + .build(&token) + .unwrap(); verifier .authorize_with_limits(AuthorizerLimits { max_time: Duration::from_secs(10), @@ -452,39 +500,44 @@ fn check_signature_5(b: &mut Bencher) { b.bytes = data.len() as u64; b.iter(|| { - let token = Biscuit::from(&data, &root.public()).unwrap(); + let _token = Biscuit::from(&data, &root.public()).unwrap(); }); } fn checks_block_2(b: &mut Bencher) { let mut rng: OsRng = OsRng; - let root = KeyPair::new_with_rng(&mut rng); - let keypair2 = KeyPair::new_with_rng(&mut rng); + let root = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); + let keypair2 = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); let data = { - let mut builder = Biscuit::builder(); - builder.add_fact(fact("right", &[string("file1"), string("read")])); - builder.add_fact(fact("right", &[string("file2"), string("read")])); - builder.add_fact(fact("right", &[string("file1"), string("write")])); - - let token = builder + let token = Biscuit::builder() + .fact(fact("right", &[string("file1"), string("read")])) + .unwrap() + .fact(fact("right", &[string("file2"), string("read")])) + .unwrap() + .fact(fact("right", &[string("file1"), string("write")])) + .unwrap() .build_with_rng(&root, SymbolTable::default(), &mut rng) .unwrap(); - let base_data = token.to_vec().unwrap(); + let _base_data = token.to_vec().unwrap(); - let mut block_builder = BlockBuilder::new(); - block_builder.check_resource("file1"); - block_builder.check_operation("read"); + let block_builder = BlockBuilder::new() + .check_resource("file1") + .check_operation("read"); let token2 = token.append_with_keypair(&keypair2, block_builder).unwrap(); token2.to_vec().unwrap() }; let token = Biscuit::from(&data, &root.public()).unwrap(); - let mut verifier = token.authorizer().unwrap(); - verifier.add_fact("resource(\"file1\")"); - verifier.add_fact("operation(\"read\")"); - verifier.allow(); + let mut verifier = AuthorizerBuilder::new() + .fact("resource(\"file1\")") + .unwrap() + .fact("operation(\"read\")") + .unwrap() + .allow_all() + .build(&token) + .unwrap(); verifier .authorize_with_limits(AuthorizerLimits { max_time: Duration::from_secs(10), @@ -495,10 +548,14 @@ fn checks_block_2(b: &mut Bencher) { let token = Biscuit::from(&data, &root.public()).unwrap(); b.bytes = data.len() as u64; b.iter(|| { - let mut verifier = token.authorizer().unwrap(); - verifier.add_fact("resource(\"file1\")"); - verifier.add_fact("operation(\"read\")"); - verifier.allow(); + let mut verifier = AuthorizerBuilder::new() + .fact("resource(\"file1\")") + .unwrap() + .fact("operation(\"read\")") + .unwrap() + .allow_all() + .build(&token) + .unwrap(); verifier .authorize_with_limits(AuthorizerLimits { max_time: Duration::from_secs(10), @@ -510,33 +567,38 @@ fn checks_block_2(b: &mut Bencher) { fn checks_block_create_verifier2(b: &mut Bencher) { let mut rng: OsRng = OsRng; - let root = KeyPair::new_with_rng(&mut rng); - let keypair2 = KeyPair::new_with_rng(&mut rng); + let root = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); + let keypair2 = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); let data = { - let mut builder = Biscuit::builder(); - builder.add_fact(fact("right", &[string("file1"), string("read")])); - builder.add_fact(fact("right", &[string("file2"), string("read")])); - builder.add_fact(fact("right", &[string("file1"), string("write")])); - - let token = builder + let token = Biscuit::builder() + .fact(fact("right", &[string("file1"), string("read")])) + .unwrap() + .fact(fact("right", &[string("file2"), string("read")])) + .unwrap() + .fact(fact("right", &[string("file1"), string("write")])) + .unwrap() .build_with_rng(&root, SymbolTable::default(), &mut rng) .unwrap(); - let base_data = token.to_vec().unwrap(); + let _base_data = token.to_vec().unwrap(); - let mut block_builder = BlockBuilder::new(); - block_builder.check_resource("file1"); - block_builder.check_operation("read"); + let block_builder = BlockBuilder::new() + .check_resource("file1") + .check_operation("read"); let token2 = token.append_with_keypair(&keypair2, block_builder).unwrap(); token2.to_vec().unwrap() }; let token = Biscuit::from(&data, &root.public()).unwrap(); - let mut verifier = token.authorizer().unwrap(); - verifier.add_fact("resource(\"file1\")"); - verifier.add_fact("operation(\"read\")"); - verifier.allow(); + let mut verifier = AuthorizerBuilder::new() + .fact("resource(\"file1\")") + .unwrap() + .fact("operation(\"read\")") + .unwrap() + .allow_all() + .build(&token) + .unwrap(); verifier .authorize_with_limits(AuthorizerLimits { max_time: Duration::from_secs(10), @@ -547,39 +609,44 @@ fn checks_block_create_verifier2(b: &mut Bencher) { let token = Biscuit::from(&data, &root.public()).unwrap(); b.bytes = data.len() as u64; b.iter(|| { - let mut verifier = token.authorizer().unwrap(); + let _verifier = token.authorizer().unwrap(); }); } fn checks_block_verify_only2(b: &mut Bencher) { let mut rng: OsRng = OsRng; - let root = KeyPair::new_with_rng(&mut rng); - let keypair2 = KeyPair::new_with_rng(&mut rng); + let root = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); + let keypair2 = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); let data = { - let mut builder = Biscuit::builder(); - builder.add_fact(fact("right", &[string("file1"), string("read")])); - builder.add_fact(fact("right", &[string("file2"), string("read")])); - builder.add_fact(fact("right", &[string("file1"), string("write")])); - - let token = builder + let token = Biscuit::builder() + .fact(fact("right", &[string("file1"), string("read")])) + .unwrap() + .fact(fact("right", &[string("file2"), string("read")])) + .unwrap() + .fact(fact("right", &[string("file1"), string("write")])) + .unwrap() .build_with_rng(&root, SymbolTable::default(), &mut rng) .unwrap(); - let base_data = token.to_vec().unwrap(); + let _base_data = token.to_vec().unwrap(); - let mut block_builder = BlockBuilder::new(); - block_builder.check_resource("file1"); - block_builder.check_operation("read"); + let block_builder = BlockBuilder::new() + .check_resource("file1") + .check_operation("read"); let token2 = token.append_with_keypair(&keypair2, block_builder).unwrap(); token2.to_vec().unwrap() }; let token = Biscuit::from(&data, &root.public()).unwrap(); - let mut verifier = token.authorizer().unwrap(); - verifier.add_fact("resource(\"file1\")"); - verifier.add_fact("operation(\"read\")"); - verifier.allow(); + let mut verifier = AuthorizerBuilder::new() + .fact("resource(\"file1\")") + .unwrap() + .fact("operation(\"read\")") + .unwrap() + .allow_all() + .build(&token) + .unwrap(); verifier .authorize_with_limits(AuthorizerLimits { max_time: Duration::from_secs(10), @@ -589,10 +656,14 @@ fn checks_block_verify_only2(b: &mut Bencher) { let token = Biscuit::from(&data, &root.public()).unwrap(); b.iter(|| { - let mut verifier = token.authorizer().unwrap(); - verifier.add_fact("resource(\"file1\")"); - verifier.add_fact("operation(\"read\")"); - verifier.allow(); + let mut verifier = AuthorizerBuilder::new() + .fact("resource(\"file1\")") + .unwrap() + .fact("operation(\"read\")") + .unwrap() + .allow_all() + .build(&token) + .unwrap(); verifier .authorize_with_limits(AuthorizerLimits { max_time: Duration::from_secs(10), diff --git a/biscuit-auth/examples/testcases.rs b/biscuit-auth/examples/testcases.rs index 0df792f9..f589b13f 100644 --- a/biscuit-auth/examples/testcases.rs +++ b/biscuit-auth/examples/testcases.rs @@ -7,12 +7,17 @@ use biscuit::datalog::SymbolTable; use biscuit::error; use biscuit::format::convert::v2 as convert; use biscuit::macros::*; -use biscuit::Authorizer; use biscuit::{builder::*, builder_ext::*, Biscuit}; use biscuit::{KeyPair, PrivateKey, PublicKey}; +use biscuit_auth::builder; +use biscuit_auth::builder::Algorithm; +use biscuit_auth::datalog::ExternFunc; +use biscuit_auth::datalog::RunLimits; use prost::Message; use rand::prelude::*; use serde::Serialize; +use std::collections::HashMap; +use std::sync::Arc; use std::{ collections::{BTreeMap, BTreeSet}, fs::File, @@ -73,13 +78,14 @@ fn main() { fn run(target: String, root_key: Option, test: bool, json: bool) { let root = if let Some(key) = root_key { - KeyPair::from(&PrivateKey::from_bytes_hex(&key).unwrap()) + KeyPair::from(&PrivateKey::from_bytes_hex(&key, Algorithm::Ed25519).unwrap()) } else { let mut rng: StdRng = SeedableRng::seed_from_u64(1234); - KeyPair::new_with_rng(&mut rng) + KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng) }; let mut results = Vec::new(); + add_test_result(&mut results, basic_token(&target, &root, test)); add_test_result(&mut results, different_root_key(&target, &root, test)); @@ -145,6 +151,24 @@ fn run(target: String, root_key: Option, test: bool, json: bool) { add_test_result(&mut results, expressions_v4(&target, &root, test)); + add_test_result(&mut results, reject_if(&target, &root, test)); + + add_test_result(&mut results, null(&target, &root, test)); + + add_test_result(&mut results, heterogeneous_equal(&target, &root, test)); + + add_test_result(&mut results, closures(&target, &root, test)); + + add_test_result(&mut results, type_of(&target, &root, test)); + + add_test_result(&mut results, array_map(&target, &root, test)); + + add_test_result(&mut results, ffi(&target, &root, test)); + + add_test_result(&mut results, secp256r1(&target, &root, test)); + + add_test_result(&mut results, secp256r1_third_party(&target, &root, test)); + if json { let s = serde_json::to_string_pretty(&TestCases { root_private_key: hex::encode(root.private().to_bytes()), @@ -190,6 +214,7 @@ struct BlockContent { pub public_keys: Vec, pub external_key: Option, pub code: String, + pub version: u32, } #[derive(Debug, Serialize)] @@ -220,6 +245,7 @@ impl TestResult { if let Some(key) = &block.external_key { writeln!(&mut s, "external signature by: {:?}\n", key); } + writeln!(&mut s, "block version: {}\n", block.version); writeln!(&mut s, "```\n{}```\n", block.code); } @@ -254,26 +280,26 @@ impl TestResult { #[derive(Debug, Serialize)] struct AuthorizerWorld { - pub facts: Vec, - pub rules: Vec, - pub checks: Vec, + pub facts: Vec, + pub rules: Vec, + pub checks: Vec, pub policies: Vec, } #[derive(Debug, Serialize, PartialEq, Eq, PartialOrd, Ord)] -struct AuthorizerFactSet { +struct Facts { origin: BTreeSet>, facts: Vec, } #[derive(Debug, Serialize, PartialEq, Eq, PartialOrd, Ord)] -struct AuthorizerRuleSet { +struct Rules { origin: Option, rules: Vec, } #[derive(Debug, Serialize, PartialEq, Eq, PartialOrd, Ord)] -struct AuthorizerCheckSet { +struct Checks { origin: Option, checks: Vec, } @@ -285,6 +311,22 @@ enum AuthorizerResult { } fn validate_token(root: &KeyPair, data: &[u8], authorizer_code: &str) -> Validation { + validate_token_with_limits_and_external_functions( + root, + data, + authorizer_code, + RunLimits::default(), + Default::default(), + ) +} + +fn validate_token_with_limits_and_external_functions( + root: &KeyPair, + data: &[u8], + authorizer_code: &str, + run_limits: RunLimits, + extern_funcs: HashMap, +) -> Validation { let token = match Biscuit::from(&data[..], &root.public()) { Ok(t) => t, Err(e) => { @@ -303,11 +345,13 @@ fn validate_token(root: &KeyPair, data: &[u8], authorizer_code: &str) -> Validat revocation_ids.push(hex::encode(&bytes)); } - let mut authorizer = Authorizer::new(); - authorizer.add_code(authorizer_code).unwrap(); - let authorizer_code = authorizer.dump_code(); + let builder = AuthorizerBuilder::new() + .set_extern_funcs(extern_funcs) + .code(authorizer_code) + .unwrap(); + let authorizer_code = builder.dump_code(); - match authorizer.add_token(&token) { + let mut authorizer = match builder.build(&token) { Ok(v) => v, Err(e) => { return Validation { @@ -319,7 +363,7 @@ fn validate_token(root: &KeyPair, data: &[u8], authorizer_code: &str) -> Validat } }; - let res = authorizer.authorize(); + let res = authorizer.authorize_with_limits(run_limits); //println!("authorizer world:\n{}", authorizer.print_world()); let (_, _, _, policies) = authorizer.dump(); let snapshot = authorizer.snapshot().unwrap(); @@ -347,7 +391,7 @@ fn validate_token(root: &KeyPair, data: &[u8], authorizer_code: &str) -> Validat } if !rules.is_empty() { rules.sort(); - authorizer_rules.push(AuthorizerRuleSet { + authorizer_rules.push(Rules { origin: Some(i), rules, }); @@ -361,7 +405,7 @@ fn validate_token(root: &KeyPair, data: &[u8], authorizer_code: &str) -> Validat } if !checks.is_empty() { checks.sort(); - authorizer_checks.push(AuthorizerCheckSet { + authorizer_checks.push(Checks { origin: Some(i), checks, }); @@ -376,7 +420,7 @@ fn validate_token(root: &KeyPair, data: &[u8], authorizer_code: &str) -> Validat } if !rules.is_empty() { rules.sort(); - authorizer_rules.push(AuthorizerRuleSet { + authorizer_rules.push(Rules { origin: Some(usize::MAX), rules, }); @@ -390,7 +434,7 @@ fn validate_token(root: &KeyPair, data: &[u8], authorizer_code: &str) -> Validat } if !checks.is_empty() { checks.sort(); - authorizer_checks.push(AuthorizerCheckSet { + authorizer_checks.push(Checks { origin: Some(usize::MAX), checks, }); @@ -415,7 +459,7 @@ fn validate_token(root: &KeyPair, data: &[u8], authorizer_code: &str) -> Validat } if !facts.is_empty() { facts.sort(); - authorizer_facts.push(AuthorizerFactSet { origin, facts }); + authorizer_facts.push(Facts { origin, facts }); } } authorizer_facts.sort(); @@ -497,7 +541,7 @@ fn basic_token(target: &str, root: &KeyPair, test: bool) -> TestResult { .build_with_rng(&root, SymbolTable::default(), &mut rng) .unwrap(); - let keypair2 = KeyPair::new_with_rng(&mut rng); + let keypair2 = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); let biscuit2 = biscuit1 .append_with_keypair( &keypair2, @@ -541,7 +585,7 @@ fn different_root_key(target: &str, root: &KeyPair, test: bool) -> TestResult { let filename = "test002_different_root_key".to_string(); let token; - let root2 = KeyPair::new_with_rng(&mut rng); + let root2 = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); let biscuit1 = biscuit!( r#" @@ -551,7 +595,7 @@ fn different_root_key(target: &str, root: &KeyPair, test: bool) -> TestResult { .build_with_rng(&root2, SymbolTable::default(), &mut rng) .unwrap(); - let keypair2 = KeyPair::new_with_rng(&mut rng); + let keypair2 = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); let biscuit2 = biscuit1 .append_with_keypair( &keypair2, @@ -604,7 +648,7 @@ fn invalid_signature_format(target: &str, root: &KeyPair, test: bool) -> TestRes .build_with_rng(&root, SymbolTable::default(), &mut rng) .unwrap(); - let keypair2 = KeyPair::new_with_rng(&mut rng); + let keypair2 = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); let biscuit2 = biscuit1 .append_with_keypair( &keypair2, @@ -656,7 +700,7 @@ fn random_block(target: &str, root: &KeyPair, test: bool) -> TestResult { .build_with_rng(&root, SymbolTable::default(), &mut rng) .unwrap(); - let keypair2 = KeyPair::new_with_rng(&mut rng); + let keypair2 = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); let biscuit2 = biscuit1 .append_with_keypair( &keypair2, @@ -711,7 +755,7 @@ fn invalid_signature(target: &str, root: &KeyPair, test: bool) -> TestResult { .build_with_rng(&root, SymbolTable::default(), &mut rng) .unwrap(); - let keypair2 = KeyPair::new_with_rng(&mut rng); + let keypair2 = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); let biscuit2 = biscuit1 .append_with_keypair( &keypair2, @@ -765,7 +809,7 @@ fn reordered_blocks(target: &str, root: &KeyPair, test: bool) -> TestResult { .build_with_rng(&root, SymbolTable::default(), &mut rng) .unwrap(); - let keypair2 = KeyPair::new_with_rng(&mut rng); + let keypair2 = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); let biscuit2 = biscuit1 .append_with_keypair( &keypair2, @@ -773,7 +817,7 @@ fn reordered_blocks(target: &str, root: &KeyPair, test: bool) -> TestResult { ) .unwrap(); - let keypair3 = KeyPair::new_with_rng(&mut rng); + let keypair3 = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); let biscuit3 = biscuit2 .append_with_keypair(&keypair3, block!(r#"check if resource("file1")"#)) .unwrap(); @@ -823,7 +867,7 @@ fn scoped_rules(target: &str, root: &KeyPair, test: bool) -> TestResult { .build_with_rng(&root, SymbolTable::default(), &mut rng) .unwrap(); - let keypair2 = KeyPair::new_with_rng(&mut rng); + let keypair2 = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); let biscuit2 = biscuit1 .append_with_keypair( &keypair2, @@ -836,11 +880,11 @@ fn scoped_rules(target: &str, root: &KeyPair, test: bool) -> TestResult { ) .unwrap(); - let mut block3 = BlockBuilder::new(); - - block3.add_fact(r#"owner("alice", "file2")"#).unwrap(); + let block3 = BlockBuilder::new() + .fact(r#"owner("alice", "file2")"#) + .unwrap(); - let keypair3 = KeyPair::new_with_rng(&mut rng); + let keypair3 = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); let biscuit3 = biscuit2.append_with_keypair(&keypair3, block3).unwrap(); token = print_blocks(&biscuit3); @@ -882,7 +926,7 @@ fn scoped_checks(target: &str, root: &KeyPair, test: bool) -> TestResult { .build_with_rng(&root, SymbolTable::default(), &mut rng) .unwrap(); - let keypair2 = KeyPair::new_with_rng(&mut rng); + let keypair2 = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); let biscuit2 = biscuit1 .append_with_keypair( &keypair2, @@ -890,7 +934,7 @@ fn scoped_checks(target: &str, root: &KeyPair, test: bool) -> TestResult { ) .unwrap(); - let keypair3 = KeyPair::new_with_rng(&mut rng); + let keypair3 = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); let biscuit3 = biscuit2 .append_with_keypair(&keypair3, block!(r#"right("file2", "read")"#)) .unwrap(); @@ -931,16 +975,15 @@ fn expired_token(target: &str, root: &KeyPair, test: bool) -> TestResult { .build_with_rng(&root, SymbolTable::default(), &mut rng) .unwrap(); - let mut block2 = block!(r#"check if resource("file1");"#); - - // January 1 2019 - block2.check_expiration_date( - UNIX_EPOCH - .checked_add(Duration::from_secs(49 * 365 * 24 * 3600)) - .unwrap(), - ); + let block2 = block!(r#"check if resource("file1");"#) + // January 1 2019 + .check_expiration_date( + UNIX_EPOCH + .checked_add(Duration::from_secs(49 * 365 * 24 * 3600)) + .unwrap(), + ); - let keypair2 = KeyPair::new_with_rng(&mut rng); + let keypair2 = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); let biscuit2 = biscuit1.append_with_keypair(&keypair2, block2).unwrap(); token = print_blocks(&biscuit2); @@ -983,7 +1026,7 @@ fn authorizer_scope(target: &str, root: &KeyPair, test: bool) -> TestResult { .build_with_rng(&root, SymbolTable::default(), &mut rng) .unwrap(); - let keypair2 = KeyPair::new_with_rng(&mut rng); + let keypair2 = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); let biscuit2 = biscuit1 .append_with_keypair(&keypair2, block!(r#"right("file2", "read")"#)) .unwrap(); @@ -1117,13 +1160,13 @@ fn block_rules(target: &str, root: &KeyPair, test: bool) -> TestResult { .build_with_rng(&root, SymbolTable::default(), &mut rng) .unwrap(); - let keypair2 = KeyPair::new_with_rng(&mut rng); + let keypair2 = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); let biscuit2 = biscuit1.append_with_keypair(&keypair2, block!(r#" // generate valid_date("file1") if before Thursday, December 31, 2030 12:59:59 PM UTC valid_date("file1") <- time($0), resource("file1"), $0 <= 2030-12-31T12:59:59Z; // generate a valid date fact for any file other than "file1" if before Friday, December 31, 1999 12:59:59 PM UTC - valid_date($1) <- time($0), resource($1), $0 <= 1999-12-31T12:59:59Z, !["file1"].contains($1); + valid_date($1) <- time($0), resource($1), $0 <= 1999-12-31T12:59:59Z, !{"file1"}.contains($1); check if valid_date($0), resource($0); "#)).unwrap(); @@ -1238,7 +1281,7 @@ fn check_head_name(target: &str, root: &KeyPair, test: bool) -> TestResult { .build_with_rng(&root, SymbolTable::default(), &mut rng) .unwrap(); - let keypair2 = KeyPair::new_with_rng(&mut rng); + let keypair2 = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); let biscuit2 = biscuit1 .append_with_keypair(&keypair2, block!(r#"query("test")"#)) .unwrap(); @@ -1270,15 +1313,9 @@ 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 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; @@ -1290,25 +1327,25 @@ 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"); + check if "hello world".starts_with("hello"), "hello world".ends_with("world"); // string regex check if "aaabde".matches("a*c?.e"); // 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; @@ -1320,29 +1357,32 @@ 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); - check if [2020-12-04T09:46:41+00:00, 2019-12-04T09:46:41+00:00].contains(2020-12-04T09:46:41+00:00); - check if [true, 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]); - // set equal - check if [1, 2] == [1, 2]; + check if {1, 2}.contains(2); + check if { 2020-12-04T09:46:41+00:00, 2019-12-04T09:46:41+00:00}.contains(2020-12-04T09:46:41+00:00); + check if {true, 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}); + // 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); + 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; + + // empty set literal + check if {,}.length() === 0; "#) .build_with_rng(&root, SymbolTable::default(), &mut rng) .unwrap(); @@ -1374,18 +1414,16 @@ fn unbound_variables_in_rule(target: &str, root: &KeyPair, test: bool) -> TestRe .build_with_rng(&root, SymbolTable::default(), &mut rng) .unwrap(); - let mut block2 = BlockBuilder::new(); - - // this one does not go through the parser because it checks for unused variables - block2 - .add_rule(rule( + let block2 = BlockBuilder::new() + // this one does not go through the parser because it checks for unused variables + .rule(rule( "operation", &[var("unbound"), string("read")], &[pred("operation", &[var("any1"), var("any2")])], )) .unwrap(); - let keypair2 = KeyPair::new_with_rng(&mut rng); + let keypair2 = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); let biscuit2 = biscuit1.append_with_keypair(&keypair2, block2).unwrap(); token = print_blocks(&biscuit2); @@ -1415,7 +1453,7 @@ fn generating_ambient_from_variables(target: &str, root: &KeyPair, test: bool) - .build_with_rng(&root, SymbolTable::default(), &mut rng) .unwrap(); - let keypair2 = KeyPair::new_with_rng(&mut rng); + let keypair2 = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); let biscuit2 = biscuit1 .append_with_keypair(&keypair2, block!(r#"operation("read") <- operation($any)"#)) .unwrap(); @@ -1452,7 +1490,7 @@ fn sealed_token(target: &str, root: &KeyPair, test: bool) -> TestResult { .build_with_rng(&root, SymbolTable::default(), &mut rng) .unwrap(); - let keypair2 = KeyPair::new_with_rng(&mut rng); + let keypair2 = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); let biscuit2 = biscuit1 .append_with_keypair( &keypair2, @@ -1579,12 +1617,12 @@ fn execution_scope(target: &str, root: &KeyPair, test: bool) -> TestResult { .build_with_rng(&root, SymbolTable::default(), &mut rng) .unwrap(); - let keypair2 = KeyPair::new_with_rng(&mut rng); + let keypair2 = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); let biscuit2 = biscuit1 .append_with_keypair(&keypair2, block!("block1_fact(1)")) .unwrap(); - let keypair3 = KeyPair::new_with_rng(&mut rng); + let keypair3 = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); let biscuit3 = biscuit2 .append_with_keypair( &keypair3, @@ -1621,10 +1659,11 @@ fn third_party(target: &str, root: &KeyPair, test: bool) -> TestResult { let token; // keep this to conserve the same RNG state - let _ = KeyPair::new_with_rng(&mut rng); + let _ = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); let external = KeyPair::from( &PrivateKey::from_bytes_hex( "12aca40167fbdd1a11037e9fd440e3d510d9d9dea70a6646aa4aaf84d718d75a", + Algorithm::Ed25519, ) .unwrap(), ); @@ -1652,7 +1691,7 @@ fn third_party(target: &str, root: &KeyPair, test: bool) -> TestResult { ), ) .unwrap(); - let keypair2 = KeyPair::new_with_rng(&mut rng); + let keypair2 = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); let biscuit2 = biscuit1 .append_third_party_with_keypair(external.public(), res, keypair2) .unwrap(); @@ -1683,7 +1722,7 @@ fn check_all(target: &str, root: &KeyPair, test: bool) -> TestResult { let biscuit1 = biscuit!( r#" - allowed_operations(["A", "B"]); + allowed_operations({"A", "B"}); check all operation($op), allowed_operations($allowed), $allowed.contains($op); "# ) @@ -1736,25 +1775,28 @@ fn public_keys_interning(target: &str, root: &KeyPair, test: bool) -> TestResult let token; // keep this to conserve the same RNG state - let _ = KeyPair::new_with_rng(&mut rng); - let _ = KeyPair::new_with_rng(&mut rng); - let _ = KeyPair::new_with_rng(&mut rng); + let _ = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); + let _ = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); + let _ = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); let external1 = KeyPair::from( &PrivateKey::from_bytes_hex( "12aca40167fbdd1a11037e9fd440e3d510d9d9dea70a6646aa4aaf84d718d75a", + Algorithm::Ed25519, ) .unwrap(), ); let external2 = KeyPair::from( &PrivateKey::from_bytes_hex( "018e3f6864a1c9ffc2e67939a835d41c808b0084b3d7babf9364f674db19eeb3", + Algorithm::Ed25519, ) .unwrap(), ); let external3 = KeyPair::from( &PrivateKey::from_bytes_hex( "88c637e4844fc3f52290889dc961cb15d809c994b5ef71990d6a2f989bd2f02c", + Algorithm::Ed25519, ) .unwrap(), ); @@ -1788,7 +1830,11 @@ fn public_keys_interning(target: &str, root: &KeyPair, test: bool) -> TestResult .unwrap(); let biscuit2 = biscuit1 - .append_third_party_with_keypair(external1.public(), res1, KeyPair::new_with_rng(&mut rng)) + .append_third_party_with_keypair( + external1.public(), + res1, + KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng), + ) .unwrap(); let req2 = biscuit2.third_party_request().unwrap(); @@ -1808,7 +1854,11 @@ fn public_keys_interning(target: &str, root: &KeyPair, test: bool) -> TestResult .unwrap(); let biscuit3 = biscuit2 - .append_third_party_with_keypair(external2.public(), res2, KeyPair::new_with_rng(&mut rng)) + .append_third_party_with_keypair( + external2.public(), + res2, + KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng), + ) .unwrap(); let req3 = biscuit3.third_party_request().unwrap(); @@ -1828,12 +1878,16 @@ fn public_keys_interning(target: &str, root: &KeyPair, test: bool) -> TestResult .unwrap(); let biscuit4 = biscuit3 - .append_third_party_with_keypair(external2.public(), res3, KeyPair::new_with_rng(&mut rng)) + .append_third_party_with_keypair( + external2.public(), + res3, + KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng), + ) .unwrap(); let biscuit5 = biscuit4 .append_with_keypair( - &KeyPair::new_with_rng(&mut rng), + &KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng), block!( r#" query(4); @@ -1886,12 +1940,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) @@ -1923,18 +1974,144 @@ 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) + .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"), + ); + + TestResult { + title, + filename, + token, + validations, + } +} + +fn reject_if(target: &str, root: &KeyPair, test: bool) -> TestResult { + let mut rng: StdRng = SeedableRng::seed_from_u64(1234); + let title = "test reject if".to_string(); + let filename = "test029_reject_if".to_string(); + let token; + + let biscuit = biscuit!(r#"reject if test($test), $test"#) + .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[..], "test(false); allow if true"), + ); + validations.insert( + "rejection".to_string(), + validate_token(root, &data[..], "test(true); allow if true"), + ); + + TestResult { + title, + filename, + token, + validations, + } +} + +fn null(target: &str, root: &KeyPair, test: bool) -> TestResult { + let mut rng: StdRng = SeedableRng::seed_from_u64(1234); + let title = "test null".to_string(); + let filename = "test030_null".to_string(); + let token; + + let biscuit = biscuit!( + r#" + check if fact(null, $value), $value == null; + reject if fact(null, $value), $value != null; + "# + ) + .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[..], "fact(null, null); allow if true"), + ); + validations.insert( + "rejection1".to_string(), + validate_token(root, &data[..], "fact(null, 1); allow if true"), + ); + validations.insert( + "rejection2".to_string(), + validate_token(root, &data[..], "fact(null, true); allow if true"), + ); + validations.insert( + "rejection3".to_string(), + validate_token(root, &data[..], "fact(null, \"abcd\"); allow if true"), + ); + + TestResult { + title, + filename, + token, + validations, + } +} + +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 true == true; + check if false == false; + check if false != true; + check if 1 != true; + check if 1 == 1; + check if 1 != 3; + check if 1 != true; + check if "abcD12" == "abcD12"; + check if "abcD12x" != "abcD12"; + check if "abcD12x" != true; + check if 2022-12-04T09:46:41+00:00 == 2022-12-04T09:46:41+00:00; + check if 2022-12-04T09:46:41+00:00 != 2020-12-04T09:46:41+00:00; + check if 2022-12-04T09:46:41+00:00 != true; + check if hex:12abcd == hex:12abcd; + check if hex:12abcd != hex:12ab; + check if hex:12abcd != true; + check if {1, 2} == {1, 2}; + check if {1, 4} != {1, 2}; + check if {1, 4} != true; + check if fact(1, $value), 1 == $value; + check if fact2(1, $value), 1 != $value; "# ) .build_with_rng(&root, SymbolTable::default(), &mut rng) @@ -1943,11 +2120,358 @@ fn expressions_v4(target: &str, root: &KeyPair, test: bool) -> TestResult { let data = write_or_load_testcase(target, &filename, root, &biscuit, test); + let mut validations = BTreeMap::new(); + validations.insert( + "".to_string(), + validate_token(root, &data[..], "fact(1,1); fact2(1,2); allow if true"), + ); + validations.insert( + "evaluate to false".to_string(), + validate_token( + root, + &data[..], + "fact(1,2); fact2(1,1); check if false != false; allow if true", + ), + ); + + TestResult { + title, + filename, + token, + validations, + } +} + +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 type_of(target: &str, root: &KeyPair, test: bool) -> TestResult { + let mut rng: StdRng = SeedableRng::seed_from_u64(1234); + let title = "test .type()".to_string(); + let filename = "test033_typeof".to_string(); + let token; + + let biscuit = biscuit!( + r#" + check if 1.type() == "integer"; + integer(1); + check if integer($t), $t.type() == "integer"; + check if "test".type() == "string"; + string("test"); + check if string($t), $t.type() == "string"; + check if (2023-12-28T00:00:00Z).type() == "date"; + date(2023-12-28T00:00:00Z); + check if date($t), $t.type() == "date"; + check if hex:aa.type() == "bytes"; + bytes(hex:aa); + check if bytes($t), $t.type() == "bytes"; + check if true.type() == "bool"; + bool(true); + check if bool($t), $t.type() == "bool"; + check if {true, false}.type() == "set"; + set({true, false}); + check if set($t), $t.type() == "set"; + check if null.type() == "null"; + null(null); + check if null($t), $t.type() == "null"; + array([1,2,3]); + check if array($t), $t.type() == "array"; + map({"a": true}); + check if map($t), $t.type() == "map"; + "# + ) + .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"), + ); + + TestResult { + title, + filename, + token, + validations, + } +} + +fn array_map(target: &str, root: &KeyPair, test: bool) -> TestResult { + let mut rng: StdRng = SeedableRng::seed_from_u64(1234); + let title = "test array and map operations".to_string(); + let filename = "test034_array_map".to_string(); + let token; + + let biscuit = biscuit!( + r#" + // array + check if [1, 2, 1].length() == 3; + check if ["a", "b"] != true; + check if ["a", "b"] != [1, 2, 3]; + check if ["a", "b"] == ["a", "b"]; + check if ["a", "b"] === ["a", "b"]; + check if ["a", "b"] !== ["a", "c"]; + check if ["a", "b", "c"].contains("c"); + check if [1, 2, 3].starts_with([1, 2]); + check if [4, 5, 6 ].ends_with([6]); + check if [1,2, "a"].get(2) == "a"; + check if [1, 2].get(3) == null; + check if [1,2,3].all($p -> $p > 0); + check if [1,2,3].any($p -> $p > 2); + // map + check if { "a": 1 , "b": 2, "c": 3, "d": 4}.length() == 4; + check if { 1: "a" , 2: "b"} != true; + check if { 1: "a" , 2: "b"} != { "a": 1 , "b": 2}; + check if { 1: "a" , 2: "b"} == { 2: "b", 1: "a" }; + check if { 1: "a" , 2: "b"} !== { "a": 1 , "b": 2}; + check if { 1: "a" , 2: "b"} === { 2: "b", 1: "a" }; + check if { "a": 1 , "b": 2, "c": 3, "d": 4}.contains("d"); + check if { "a": 1 , "b": 2, 1: "A" }.get("a") == 1; + check if { "a": 1 , "b": 2, 1: "A" }.get(1) == "A"; + check if { "a": 1 , "b": 2, 1: "A" }.get("c") == null; + check if { "a": 1 , "b": 2, 1: "A" }.get(2) == null; + check if { "a": 1 , "b": 2 }.all($kv -> $kv.get(0) != "c" && $kv.get(1) < 3 ); + check if { "a": 1 , "b": 2, 1: "A" }.any($kv -> $kv.get(0) == 1 && $kv.get(1) == "A" ); + // nesting + check if { "user": { "id": 1, "roles": ["admin"] } }.get("user").get("roles").contains("admin"); + "# + ) + .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"), + ); + + TestResult { + title, + filename, + token, + validations, + } +} + +fn ffi(target: &str, root: &KeyPair, test: bool) -> TestResult { + let mut rng: StdRng = SeedableRng::seed_from_u64(1234); + let title = "test ffi calls (v6 blocks)".to_string(); + let filename = "test035_ffi".to_string(); + let token; + + let biscuit = + biscuit!(r#"check if true.extern::test(), "a".extern::test("a") == "equal strings""#) + .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_with_limits_and_external_functions( + root, + &data[..], + "allow if true", + RunLimits::default(), + HashMap::from([( + "test".to_string(), + ExternFunc::new(Arc::new(|left, right| match (left, right) { + (t, None) => Ok(t), + (builder::Term::Str(left), Some(builder::Term::Str(right))) + if left == right => + { + Ok(builder::Term::Str("equal strings".to_string())) + } + (builder::Term::Str(_), Some(builder::Term::Str(_))) => { + Ok(builder::Term::Str("different strings".to_string())) + } + _ => Err("unsupported operands".to_string()), + })), + )]), + ), + ); + + TestResult { + title, + filename, + token, + validations, + } +} + +fn secp256r1(target: &str, root: &KeyPair, test: bool) -> TestResult { + let mut rng: StdRng = SeedableRng::seed_from_u64(1234); + let title = "ECDSA secp256r1 signatures".to_string(); + let filename = "test036_secp256r1".to_string(); + let token; + + let keypair2 = KeyPair::new_with_rng(Algorithm::Secp256r1, &mut rng); + let biscuit1 = biscuit!( + r#" + right("file1", "read"); + right("file2", "read"); + right("file1", "write"); + "# + ) + .build_with_key_pair(&root, SymbolTable::default(), &keypair2) + .unwrap(); + + let keypair3 = KeyPair::new_with_rng(Algorithm::Secp256r1, &mut rng); + let biscuit2 = biscuit1 + .append_with_keypair( + &keypair3, + block!( + r#" + check if resource($0), operation("read"), right($0, "read") + "# + ), + ) + .unwrap(); + + token = print_blocks(&biscuit2); + + let data = write_or_load_testcase(target, &filename, root, &biscuit2, test); + + let mut validations = BTreeMap::new(); + validations.insert( + "".to_string(), + validate_token( + root, + &data[..], + r#" + resource("file1"); + operation("read"); + allow if true; + "#, + ), + ); + + TestResult { + title, + filename, + token, + validations, + } +} + +fn secp256r1_third_party(target: &str, root: &KeyPair, test: bool) -> TestResult { + let mut rng: StdRng = SeedableRng::seed_from_u64(1234); + let title = "ECDSA secp256r1 signature on third-party block".to_string(); + let filename = "test037_secp256r1_third_party".to_string(); + let token; + + let external_keypair = KeyPair::new_with_rng(Algorithm::Secp256r1, &mut rng); + let keypair2 = KeyPair::new_with_rng(Algorithm::Secp256r1, &mut rng); + let biscuit1 = biscuit!( + r#" + right("file1", "read"); + right("file2", "read"); + right("file1", "write"); + check if from_third(true) trusting {external_pub}; + "#, + external_pub = external_keypair.public(), + ) + .build_with_key_pair(&root, SymbolTable::default(), &keypair2) + .unwrap(); + + let req = biscuit1.third_party_request().unwrap(); + let block = req + .create_block( + &external_keypair.private(), + block!( + r#" check if resource($0), operation("read"), right($0, "read"); from_third(true);"# + ), + ) + .unwrap(); + let keypair3 = KeyPair::new_with_rng(Algorithm::Secp256r1, &mut rng); + + let biscuit2 = biscuit1 + .append_third_party_with_keypair(external_keypair.public(), block, keypair3) + .unwrap(); + + token = print_blocks(&biscuit2); + + let data = write_or_load_testcase(target, &filename, root, &biscuit2, test); + + let mut validations = BTreeMap::new(); + validations.insert( + "".to_string(), + validate_token( + root, + &data[..], + r#" + resource("file1"); + operation("read"); + allow if true; + "#, + ), + ); TestResult { title, @@ -1972,6 +2496,7 @@ fn print_blocks(token: &Biscuit) -> Vec { .map(|k| k.print()) .collect(), external_key: token.block_external_key(i).unwrap().map(|k| k.print()), + version: token.block_version(i).unwrap(), }); } diff --git a/biscuit-auth/examples/third_party.rs b/biscuit-auth/examples/third_party.rs index 072f435f..dcd2bd68 100644 --- a/biscuit-auth/examples/third_party.rs +++ b/biscuit-auth/examples/third_party.rs @@ -1,22 +1,24 @@ -use biscuit_auth::{builder::BlockBuilder, datalog::SymbolTable, Biscuit, KeyPair}; +use std::time::Duration; + +use biscuit_auth::{ + builder::{Algorithm, AuthorizerBuilder, BlockBuilder}, + builder_ext::AuthorizerExt, + datalog::{RunLimits, SymbolTable}, + Biscuit, KeyPair, +}; use rand::{prelude::StdRng, SeedableRng}; fn main() { let mut rng: StdRng = SeedableRng::seed_from_u64(0); - let root = KeyPair::new_with_rng(&mut rng); - let external = KeyPair::new_with_rng(&mut rng); - - let mut builder = Biscuit::builder(); - + let root = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); + let external = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng); let external_pub = hex::encode(external.public().to_bytes()); - builder - .add_check( + let biscuit1 = Biscuit::builder() + .check( format!("check if external_fact(\"hello\") trusting ed25519/{external_pub}").as_str(), ) - .unwrap(); - - let biscuit1 = builder + .unwrap() .build_with_rng(&root, SymbolTable::default(), &mut rng) .unwrap(); @@ -25,21 +27,36 @@ fn main() { let serialized_req = biscuit1.third_party_request().unwrap().serialize().unwrap(); let req = biscuit_auth::ThirdPartyRequest::deserialize(&serialized_req).unwrap(); - let mut builder = BlockBuilder::new(); - builder.add_fact("external_fact(\"hello\")").unwrap(); + let builder = BlockBuilder::new() + .fact("external_fact(\"hello\")") + .unwrap(); let res = req.create_block(&external.private(), builder).unwrap(); let biscuit2 = biscuit1.append_third_party(external.public(), res).unwrap(); println!("biscuit2: {}", biscuit2); - let mut authorizer = biscuit1.authorizer().unwrap(); - authorizer.allow().unwrap(); + let mut authorizer = AuthorizerBuilder::new() + .allow_all() + .limits(RunLimits { + max_time: Duration::from_secs(10), + ..Default::default() + }) + .build(&biscuit1) + .unwrap(); + println!("authorize biscuit1:\n{:?}", authorizer.authorize()); println!("world:\n{}", authorizer.print_world()); - let mut authorizer = biscuit2.authorizer().unwrap(); - authorizer.allow().unwrap(); + let mut authorizer = AuthorizerBuilder::new() + .allow_all() + .limits(RunLimits { + max_time: Duration::from_secs(10), + ..Default::default() + }) + .build(&biscuit2) + .unwrap(); + println!("authorize biscuit2:\n{:?}", authorizer.authorize()); println!("world:\n{}", authorizer.print_world()); } diff --git a/biscuit-auth/examples/verifying_printer.rs b/biscuit-auth/examples/verifying_printer.rs index 79f307c3..033d0a8a 100644 --- a/biscuit-auth/examples/verifying_printer.rs +++ b/biscuit-auth/examples/verifying_printer.rs @@ -1,4 +1,4 @@ -use biscuit_auth::PublicKey; +use biscuit_auth::{builder::AuthorizerBuilder, builder_ext::AuthorizerExt, PublicKey}; fn main() { let mut args = std::env::args(); @@ -14,6 +14,7 @@ fn main() { let data = std::fs::read(target).unwrap(); let root = PublicKey::from_bytes( &hex::decode("acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189").unwrap(), + biscuit_auth::builder::Algorithm::Ed25519, ) .unwrap(); let token = biscuit_auth::Biscuit::from(&data[..], root).unwrap(); @@ -24,8 +25,7 @@ fn main() { } println!("token:\n{}", token); - let mut authorizer = token.authorizer().unwrap(); - authorizer.allow().unwrap(); + let mut authorizer = AuthorizerBuilder::new().allow_all().build(&token).unwrap(); println!("authorizer result: {:?}", authorizer.authorize()); println!("authorizer world:\n{}", authorizer.print_world()); diff --git a/biscuit-auth/samples/README.md b/biscuit-auth/samples/README.md index e5fbafe3..32040d15 100644 --- a/biscuit-auth/samples/README.md +++ b/biscuit-auth/samples/README.md @@ -13,6 +13,8 @@ symbols: ["file1", "file2"] public keys: [] +block version: 3 + ``` right("file1", "read"); right("file2", "read"); @@ -24,6 +26,8 @@ symbols: ["0"] public keys: [] +block version: 3 + ``` check if resource($0), operation("read"), right($0, "read"); ``` @@ -45,7 +49,7 @@ authorizer world: ``` World { facts: [ - AuthorizerFactSet { + Facts { origin: { None, }, @@ -53,7 +57,7 @@ World { "resource(\"file1\")", ], }, - AuthorizerFactSet { + Facts { origin: { Some( 0, @@ -68,7 +72,7 @@ World { ] rules: [] checks: [ - AuthorizerCheckSet { + Checks { origin: Some( 1, ), @@ -96,6 +100,8 @@ symbols: ["file1"] public keys: [] +block version: 3 + ``` right("file1", "read"); ``` @@ -105,6 +111,8 @@ symbols: ["0"] public keys: [] +block version: 3 + ``` check if resource($0), operation("read"), right($0, "read"); ``` @@ -124,6 +132,8 @@ symbols: ["file1", "file2"] public keys: [] +block version: 3 + ``` right("file1", "read"); right("file2", "read"); @@ -135,13 +145,15 @@ symbols: ["0"] public keys: [] +block version: 3 + ``` check if resource($0), operation("read"), right($0, "read"); ``` ### validation -result: `Err(Format(InvalidSignatureSize(16)))` +result: `Err(Format(BlockSignatureDeserializationError("block signature deserialization error: [117, 149, 161, 18, 161, 235, 91, 129, 166, 227, 152, 133, 46, 97, 24, 183]")))` ------------------------------ @@ -154,6 +166,8 @@ symbols: ["file1", "file2"] public keys: [] +block version: 3 + ``` right("file1", "read"); right("file2", "read"); @@ -165,6 +179,8 @@ symbols: ["0"] public keys: [] +block version: 3 + ``` check if resource($0), operation("read"), right($0, "read"); ``` @@ -184,6 +200,8 @@ symbols: ["file1", "file2"] public keys: [] +block version: 3 + ``` right("file1", "read"); right("file2", "read"); @@ -195,6 +213,8 @@ symbols: ["0"] public keys: [] +block version: 3 + ``` check if resource($0), operation("read"), right($0, "read"); ``` @@ -214,6 +234,8 @@ symbols: ["file1", "file2"] public keys: [] +block version: 3 + ``` right("file1", "read"); right("file2", "read"); @@ -225,6 +247,8 @@ symbols: ["0"] public keys: [] +block version: 3 + ``` check if resource($0), operation("read"), right($0, "read"); ``` @@ -234,6 +258,8 @@ symbols: [] public keys: [] +block version: 3 + ``` check if resource("file1"); ``` @@ -253,6 +279,8 @@ symbols: ["user_id", "alice", "file1"] public keys: [] +block version: 3 + ``` user_id("alice"); owner("alice", "file1"); @@ -263,6 +291,8 @@ symbols: ["0", "1"] public keys: [] +block version: 3 + ``` right($0, "read") <- resource($0), user_id($1), owner($1, $0); check if resource($0), operation("read"), right($0, "read"); @@ -273,6 +303,8 @@ symbols: ["file2"] public keys: [] +block version: 3 + ``` owner("alice", "file2"); ``` @@ -296,7 +328,7 @@ authorizer world: ``` World { facts: [ - AuthorizerFactSet { + Facts { origin: { None, }, @@ -305,7 +337,7 @@ World { "resource(\"file2\")", ], }, - AuthorizerFactSet { + Facts { origin: { Some( 0, @@ -316,7 +348,7 @@ World { "user_id(\"alice\")", ], }, - AuthorizerFactSet { + Facts { origin: { Some( 2, @@ -328,7 +360,7 @@ World { }, ] rules: [ - AuthorizerRuleSet { + Rules { origin: Some( 1, ), @@ -338,7 +370,7 @@ World { }, ] checks: [ - AuthorizerCheckSet { + Checks { origin: Some( 1, ), @@ -366,6 +398,8 @@ symbols: ["file1"] public keys: [] +block version: 3 + ``` right("file1", "read"); ``` @@ -375,6 +409,8 @@ symbols: ["0"] public keys: [] +block version: 3 + ``` check if resource($0), operation("read"), right($0, "read"); ``` @@ -384,6 +420,8 @@ symbols: ["file2"] public keys: [] +block version: 3 + ``` right("file2", "read"); ``` @@ -407,7 +445,7 @@ authorizer world: ``` World { facts: [ - AuthorizerFactSet { + Facts { origin: { None, }, @@ -416,7 +454,7 @@ World { "resource(\"file2\")", ], }, - AuthorizerFactSet { + Facts { origin: { Some( 0, @@ -426,7 +464,7 @@ World { "right(\"file1\", \"read\")", ], }, - AuthorizerFactSet { + Facts { origin: { Some( 2, @@ -439,7 +477,7 @@ World { ] rules: [] checks: [ - AuthorizerCheckSet { + Checks { origin: Some( 1, ), @@ -467,6 +505,8 @@ symbols: [] public keys: [] +block version: 3 + ``` ``` @@ -475,6 +515,8 @@ symbols: ["file1"] public keys: [] +block version: 3 + ``` check if resource("file1"); check if time($time), $time <= 2018-12-20T00:00:00Z; @@ -499,7 +541,7 @@ authorizer world: ``` World { facts: [ - AuthorizerFactSet { + Facts { origin: { None, }, @@ -512,7 +554,7 @@ World { ] rules: [] checks: [ - AuthorizerCheckSet { + Checks { origin: Some( 1, ), @@ -541,6 +583,8 @@ symbols: ["file1"] public keys: [] +block version: 3 + ``` right("file1", "read"); ``` @@ -550,6 +594,8 @@ symbols: ["file2"] public keys: [] +block version: 3 + ``` right("file2", "read"); ``` @@ -574,7 +620,7 @@ authorizer world: ``` World { facts: [ - AuthorizerFactSet { + Facts { origin: { None, }, @@ -583,7 +629,7 @@ World { "resource(\"file2\")", ], }, - AuthorizerFactSet { + Facts { origin: { Some( 0, @@ -593,7 +639,7 @@ World { "right(\"file1\", \"read\")", ], }, - AuthorizerFactSet { + Facts { origin: { Some( 1, @@ -606,7 +652,7 @@ World { ] rules: [] checks: [ - AuthorizerCheckSet { + Checks { origin: Some( 18446744073709551615, ), @@ -634,6 +680,8 @@ symbols: ["file1"] public keys: [] +block version: 3 + ``` right("file1", "read"); ``` @@ -657,7 +705,7 @@ authorizer world: ``` World { facts: [ - AuthorizerFactSet { + Facts { origin: { None, }, @@ -666,7 +714,7 @@ World { "resource(\"file2\")", ], }, - AuthorizerFactSet { + Facts { origin: { Some( 0, @@ -679,7 +727,7 @@ World { ] rules: [] checks: [ - AuthorizerCheckSet { + Checks { origin: Some( 18446744073709551615, ), @@ -707,6 +755,8 @@ symbols: ["file1"] public keys: [] +block version: 3 + ``` check if resource("file1"); ``` @@ -728,7 +778,7 @@ authorizer world: ``` World { facts: [ - AuthorizerFactSet { + Facts { origin: { None, }, @@ -740,7 +790,7 @@ World { ] rules: [] checks: [ - AuthorizerCheckSet { + Checks { origin: Some( 0, ), @@ -773,7 +823,7 @@ authorizer world: ``` World { facts: [ - AuthorizerFactSet { + Facts { origin: { None, }, @@ -785,7 +835,7 @@ World { ] rules: [] checks: [ - AuthorizerCheckSet { + Checks { origin: Some( 0, ), @@ -813,6 +863,8 @@ symbols: ["file1", "file2"] public keys: [] +block version: 3 + ``` right("file1", "read"); right("file2", "read"); @@ -823,9 +875,11 @@ symbols: ["valid_date", "0", "1"] public keys: [] +block version: 3 + ``` valid_date("file1") <- time($0), resource("file1"), $0 <= 2030-12-31T12:59:59Z; -valid_date($1) <- time($0), resource($1), $0 <= 1999-12-31T12:59:59Z, !["file1"].contains($1); +valid_date($1) <- time($0), resource($1), $0 <= 1999-12-31T12:59:59Z, !{"file1"}.contains($1); check if valid_date($0), resource($0); ``` @@ -847,7 +901,7 @@ authorizer world: ``` World { facts: [ - AuthorizerFactSet { + Facts { origin: { None, }, @@ -856,7 +910,7 @@ World { "time(2020-12-21T09:23:12Z)", ], }, - AuthorizerFactSet { + Facts { origin: { None, Some( @@ -867,7 +921,7 @@ World { "valid_date(\"file1\")", ], }, - AuthorizerFactSet { + Facts { origin: { Some( 0, @@ -880,18 +934,18 @@ World { }, ] rules: [ - AuthorizerRuleSet { + Rules { origin: Some( 1, ), rules: [ "valid_date(\"file1\") <- time($0), resource(\"file1\"), $0 <= 2030-12-31T12:59:59Z", - "valid_date($1) <- time($0), resource($1), $0 <= 1999-12-31T12:59:59Z, ![\"file1\"].contains($1)", + "valid_date($1) <- time($0), resource($1), $0 <= 1999-12-31T12:59:59Z, !{\"file1\"}.contains($1)", ], }, ] checks: [ - AuthorizerCheckSet { + Checks { origin: Some( 1, ), @@ -925,7 +979,7 @@ authorizer world: ``` World { facts: [ - AuthorizerFactSet { + Facts { origin: { None, }, @@ -934,7 +988,7 @@ World { "time(2020-12-21T09:23:12Z)", ], }, - AuthorizerFactSet { + Facts { origin: { Some( 0, @@ -947,18 +1001,18 @@ World { }, ] rules: [ - AuthorizerRuleSet { + Rules { origin: Some( 1, ), rules: [ "valid_date(\"file1\") <- time($0), resource(\"file1\"), $0 <= 2030-12-31T12:59:59Z", - "valid_date($1) <- time($0), resource($1), $0 <= 1999-12-31T12:59:59Z, ![\"file1\"].contains($1)", + "valid_date($1) <- time($0), resource($1), $0 <= 1999-12-31T12:59:59Z, !{\"file1\"}.contains($1)", ], }, ] checks: [ - AuthorizerCheckSet { + Checks { origin: Some( 1, ), @@ -986,6 +1040,8 @@ symbols: ["0", "file[0-9]+.txt"] public keys: [] +block version: 3 + ``` check if resource($0), $0.matches("file[0-9]+.txt"); ``` @@ -1006,7 +1062,7 @@ authorizer world: ``` World { facts: [ - AuthorizerFactSet { + Facts { origin: { None, }, @@ -1017,7 +1073,7 @@ World { ] rules: [] checks: [ - AuthorizerCheckSet { + Checks { origin: Some( 0, ), @@ -1049,7 +1105,7 @@ authorizer world: ``` World { facts: [ - AuthorizerFactSet { + Facts { origin: { None, }, @@ -1060,7 +1116,7 @@ World { ] rules: [] checks: [ - AuthorizerCheckSet { + Checks { origin: Some( 0, ), @@ -1088,6 +1144,8 @@ symbols: ["must_be_present", "hello"] public keys: [] +block version: 3 + ``` must_be_present("hello"); ``` @@ -1108,7 +1166,7 @@ authorizer world: ``` World { facts: [ - AuthorizerFactSet { + Facts { origin: { Some( 0, @@ -1121,7 +1179,7 @@ World { ] rules: [] checks: [ - AuthorizerCheckSet { + Checks { origin: Some( 18446744073709551615, ), @@ -1149,6 +1207,8 @@ symbols: ["hello"] public keys: [] +block version: 3 + ``` check if resource("hello"); ``` @@ -1158,6 +1218,8 @@ symbols: ["test"] public keys: [] +block version: 3 + ``` query("test"); ``` @@ -1177,7 +1239,7 @@ authorizer world: ``` World { facts: [ - AuthorizerFactSet { + Facts { origin: { Some( 1, @@ -1190,7 +1252,7 @@ World { ] rules: [] checks: [ - AuthorizerCheckSet { + Checks { origin: Some( 0, ), @@ -1218,48 +1280,48 @@ symbols: ["hello world", "hello", "world", "aaabde", "a*c?.e", "abd", "aaa", "b" public keys: [] +block version: 3 + ``` 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 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 "hello world".starts_with("hello") && "hello world".ends_with("world"); +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 [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, 3].intersection([1, 2]).contains(1); -check if [1, 2, 3].intersection([1, 2]).length() == 2; +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, 3}.intersection({1, 2}).contains(1); +check if {1, 2, 3}.intersection({1, 2}).length() === 2; +check if {,}.length() === 0; ``` ### validation @@ -1270,7 +1332,7 @@ allow if true; ``` revocation ids: -- `3d5b23b502b3dd920bfb68b9039164d1563bb8927210166fa5c17f41b76b31bb957bc2ed3318452958f658baa2d398fe4cf25c58a27e6c8bc42c9702c8aa1b0c` +- `fa358e4e3bea896415b1859e6cd347e64e1918fb86e31ae3fe208628321576a47f7a269760357e291c827ec9cbe322074f6860a546207a64e133c83a214bb505` authorizer world: ``` @@ -1278,22 +1340,20 @@ World { facts: [] rules: [] checks: [ - AuthorizerCheckSet { + Checks { origin: Some( 0, ), 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 \"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 \"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 1 + 2 * 3 - 4 / 2 === 5", "check if 1 < 2", "check if 1 <= 1", "check if 1 <= 2", @@ -1302,28 +1362,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 [\"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].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 [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 || true", - "check if hex:12ab == hex:12ab", + "check if 3 === 3", + "check if false === false", + "check if hex:12ab === hex:12ab", "check if true", - "check if true == true", + "check if true === true", + "check if {\"abc\", \"def\"}.contains(\"abc\")", + "check if {,}.length() === 0", + "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}.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 {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)", ], }, ] @@ -1346,6 +1406,8 @@ symbols: [] public keys: [] +block version: 3 + ``` check if operation("read"); ``` @@ -1355,6 +1417,8 @@ symbols: ["unbound", "any1", "any2"] public keys: [] +block version: 3 + ``` operation($unbound, "read") <- operation($any1, $any2); ``` @@ -1374,6 +1438,8 @@ symbols: [] public keys: [] +block version: 3 + ``` check if operation("read"); ``` @@ -1383,6 +1449,8 @@ symbols: ["any"] public keys: [] +block version: 3 + ``` operation("read") <- operation($any); ``` @@ -1404,7 +1472,7 @@ authorizer world: ``` World { facts: [ - AuthorizerFactSet { + Facts { origin: { None, }, @@ -1412,7 +1480,7 @@ World { "operation(\"write\")", ], }, - AuthorizerFactSet { + Facts { origin: { None, Some( @@ -1425,7 +1493,7 @@ World { }, ] rules: [ - AuthorizerRuleSet { + Rules { origin: Some( 1, ), @@ -1435,7 +1503,7 @@ World { }, ] checks: [ - AuthorizerCheckSet { + Checks { origin: Some( 0, ), @@ -1463,6 +1531,8 @@ symbols: ["file1", "file2"] public keys: [] +block version: 3 + ``` right("file1", "read"); right("file2", "read"); @@ -1474,6 +1544,8 @@ symbols: ["0"] public keys: [] +block version: 3 + ``` check if resource($0), operation("read"), right($0, "read"); ``` @@ -1496,7 +1568,7 @@ authorizer world: ``` World { facts: [ - AuthorizerFactSet { + Facts { origin: { None, }, @@ -1505,7 +1577,7 @@ World { "resource(\"file1\")", ], }, - AuthorizerFactSet { + Facts { origin: { Some( 0, @@ -1520,7 +1592,7 @@ World { ] rules: [] checks: [ - AuthorizerCheckSet { + Checks { origin: Some( 1, ), @@ -1548,6 +1620,8 @@ symbols: ["ns::fact_123", "hello é\t😁"] public keys: [] +block version: 3 + ``` ns::fact_123("hello é 😁"); ``` @@ -1568,7 +1642,7 @@ authorizer world: ``` World { facts: [ - AuthorizerFactSet { + Facts { origin: { Some( 0, @@ -1581,7 +1655,7 @@ World { ] rules: [] checks: [ - AuthorizerCheckSet { + Checks { origin: Some( 18446744073709551615, ), @@ -1609,6 +1683,8 @@ symbols: [] public keys: [] +block version: 3 + ``` read(0); write(1); @@ -1656,7 +1732,7 @@ authorizer world: ``` World { facts: [ - AuthorizerFactSet { + Facts { origin: { Some( 0, @@ -1696,7 +1772,7 @@ World { ] rules: [] checks: [ - AuthorizerCheckSet { + Checks { origin: Some( 18446744073709551615, ), @@ -1724,6 +1800,8 @@ symbols: ["authority_fact"] public keys: [] +block version: 3 + ``` authority_fact(1); ``` @@ -1733,6 +1811,8 @@ symbols: ["block1_fact"] public keys: [] +block version: 3 + ``` block1_fact(1); ``` @@ -1742,6 +1822,8 @@ symbols: ["var"] public keys: [] +block version: 3 + ``` check if authority_fact($var); check if block1_fact($var); @@ -1763,7 +1845,7 @@ authorizer world: ``` World { facts: [ - AuthorizerFactSet { + Facts { origin: { Some( 0, @@ -1773,7 +1855,7 @@ World { "authority_fact(1)", ], }, - AuthorizerFactSet { + Facts { origin: { Some( 1, @@ -1786,7 +1868,7 @@ World { ] rules: [] checks: [ - AuthorizerCheckSet { + Checks { origin: Some( 2, ), @@ -1815,6 +1897,8 @@ symbols: [] public keys: ["ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189"] +block version: 4 + ``` right("read"); check if group("admin") trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189; @@ -1827,6 +1911,8 @@ public keys: [] external signature by: "ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189" +block version: 5 + ``` group("admin"); check if right("read"); @@ -1841,13 +1927,13 @@ allow if true; revocation ids: - `470e4bf7aa2a01ab39c98150bd06aa15b4aa5d86509044a8809a8634cd8cf2b42269a51a774b65d10bac9369d013070b00187925196a8e680108473f11cf8f03` -- `342167bc54bc642b6718a276875e55b6d39e9b21e4ce13b926a3d398b6c057fc436385bf4c817a16f9ecdf0b0d950e8b8258a20aeb3fd8896c5e9c1f0a53da03` +- `901b2af4dacf33458d2d91ac484b60bad948e8d10faa9695b096054d5b46e832a977b60b17464cacf545ad0801f549ea454675f0ac88c413406925e2af83ff08` authorizer world: ``` World { facts: [ - AuthorizerFactSet { + Facts { origin: { Some( 0, @@ -1857,7 +1943,7 @@ World { "right(\"read\")", ], }, - AuthorizerFactSet { + Facts { origin: { Some( 1, @@ -1870,7 +1956,7 @@ World { ] rules: [] checks: [ - AuthorizerCheckSet { + Checks { origin: Some( 0, ), @@ -1878,7 +1964,7 @@ World { "check if group(\"admin\") trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189", ], }, - AuthorizerCheckSet { + Checks { origin: Some( 1, ), @@ -1906,8 +1992,10 @@ symbols: ["allowed_operations", "A", "B", "op", "allowed"] public keys: [] +block version: 4 + ``` -allowed_operations(["A", "B"]); +allowed_operations({"A", "B"}); check all operation($op), allowed_operations($allowed), $allowed.contains($op); ``` @@ -1928,7 +2016,7 @@ authorizer world: ``` World { facts: [ - AuthorizerFactSet { + Facts { origin: { None, }, @@ -1937,20 +2025,20 @@ World { "operation(\"B\")", ], }, - AuthorizerFactSet { + Facts { origin: { Some( 0, ), }, facts: [ - "allowed_operations([\"A\", \"B\"])", + "allowed_operations({\"A\", \"B\"})", ], }, ] rules: [] checks: [ - AuthorizerCheckSet { + Checks { origin: Some( 0, ), @@ -1983,7 +2071,7 @@ authorizer world: ``` World { facts: [ - AuthorizerFactSet { + Facts { origin: { None, }, @@ -1992,20 +2080,20 @@ World { "operation(\"invalid\")", ], }, - AuthorizerFactSet { + Facts { origin: { Some( 0, ), }, facts: [ - "allowed_operations([\"A\", \"B\"])", + "allowed_operations({\"A\", \"B\"})", ], }, ] rules: [] checks: [ - AuthorizerCheckSet { + Checks { origin: Some( 0, ), @@ -2033,6 +2121,8 @@ symbols: [] public keys: ["ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189"] +block version: 4 + ``` query(0); check if true trusting previous, ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189; @@ -2045,6 +2135,8 @@ public keys: ["ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7 external signature by: "ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189" +block version: 5 + ``` query(1); query(1, 2) <- query(1), query(2) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463; @@ -2059,6 +2151,8 @@ public keys: ["ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7 external signature by: "ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463" +block version: 5 + ``` query(2); check if query(2), query(3) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463; @@ -2072,6 +2166,8 @@ public keys: ["ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7 external signature by: "ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463" +block version: 5 + ``` query(3); check if query(2), query(3) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463; @@ -2083,6 +2179,8 @@ symbols: [] public keys: ["ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463", "ed25519/f98da8c1cf907856431bfc3dc87531e0eaadba90f919edc232405b85877ef136"] +block version: 4 + ``` query(4); check if query(2) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463; @@ -2103,16 +2201,16 @@ allow if true; revocation ids: - `3771cefe71beb21ead35a59c8116ee82627a5717c0295f35980662abccb159fe1b37848cb1818e548656bd4fd882d0094a2daab631c76b2b72e3a093914bfe04` -- `6528db2c9a561ada9086268549a600a8a52ff434ea8183812623eec0e9b6c5d3c41ab7868808623021d92294d583afdf92f4354bcdaa1bc50453e1b89afd630d` -- `5d5679fe69bfe74b7919323515e9ecba9d01422b16be9341b57f88e695b2bb0bd7966b781001d2b9e00ee618fdc239c96e17e32cb379f13f12d6bd7b1b47ad04` -- `c37bf24c063f0310eccab8864e48dbeffcdd7240b4f8d1e01eba4fc703e6c9082b845bb55543b10f008dc7f4e78540411912ac1f36fa2aa90011dca40f323b09` -- `3f675d6c364e06405d4868c904e40f3d81c32b083d91586db814d4cb4bf536b4ba209d82f11b4cb6da293b60b20d6122fc3e0e08e80c381dee83edd848211900` +- `7113d4dbb3b688b80e941f365a2c6342d480c77ed03937bccf85dc5cc3554c7517887b1b0c9021388a71e6ca9047aabaaad5ae5b511a2880902568444a98e50b` +- `d0e3fc4bbd1b7320022800af909585aa906f677c4ca79c275a10b6779f669384c464ee84a1b04f13877a25761a874748362c065f4d15a8cab5c5e16c34074403` +- `29b7e0a1f118a6185814a552660c516c43482044e280e7a8de85b8e7e54947e0ae82eb39d7b524d4b72cb9812a7a4b8871964f8f825b1c1ed85d344c05281d0d` +- `c0a505d4d921a8b2d0b885917d42e2bca87b5302d13249a61af6f3802af44d691c40a624f901d677724740cb974a188aeb1c3992c1565ac0fbec3aa4f68dac0a` authorizer world: ``` World { facts: [ - AuthorizerFactSet { + Facts { origin: { Some( 0, @@ -2122,7 +2220,7 @@ World { "query(0)", ], }, - AuthorizerFactSet { + Facts { origin: { Some( 1, @@ -2132,7 +2230,7 @@ World { "query(1)", ], }, - AuthorizerFactSet { + Facts { origin: { Some( 1, @@ -2145,7 +2243,7 @@ World { "query(1, 2)", ], }, - AuthorizerFactSet { + Facts { origin: { Some( 2, @@ -2155,7 +2253,7 @@ World { "query(2)", ], }, - AuthorizerFactSet { + Facts { origin: { Some( 3, @@ -2165,7 +2263,7 @@ World { "query(3)", ], }, - AuthorizerFactSet { + Facts { origin: { Some( 4, @@ -2177,7 +2275,7 @@ World { }, ] rules: [ - AuthorizerRuleSet { + Rules { origin: Some( 1, ), @@ -2187,7 +2285,7 @@ World { }, ] checks: [ - AuthorizerCheckSet { + Checks { origin: Some( 0, ), @@ -2195,7 +2293,7 @@ World { "check if true trusting previous, ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189", ], }, - AuthorizerCheckSet { + Checks { origin: Some( 1, ), @@ -2204,7 +2302,7 @@ World { "check if query(2), query(3) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463", ], }, - AuthorizerCheckSet { + Checks { origin: Some( 2, ), @@ -2213,7 +2311,7 @@ World { "check if query(2), query(3) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463", ], }, - AuthorizerCheckSet { + Checks { origin: Some( 3, ), @@ -2222,7 +2320,7 @@ World { "check if query(2), query(3) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463", ], }, - AuthorizerCheckSet { + Checks { origin: Some( 4, ), @@ -2231,7 +2329,7 @@ World { "check if query(4) trusting ed25519/f98da8c1cf907856431bfc3dc87531e0eaadba90f919edc232405b85877ef136", ], }, - AuthorizerCheckSet { + Checks { origin: Some( 18446744073709551615, ), @@ -2262,10 +2360,12 @@ symbols: [] public keys: [] +block version: 4 + ``` -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 @@ -2276,7 +2376,7 @@ allow if true; ``` revocation ids: -- `3346a22aae0abfc1ffa526f02f7650e90af909e5e519989026441e78cdc245b7fd126503cfdc8831325fc04307edc65238db319724477915f7040a2f6a719a05` +- `fb5e7ac2bb892f5cf2fb59677cfad1f96deabbc8e158e3fd1b5ee7c4b6949c999e2169187cbee53b943eebdadaaf68832747baa8cffa2ff9f78025a1f55f440c` authorizer world: ``` @@ -2284,14 +2384,14 @@ World { facts: [] rules: [] checks: [ - AuthorizerCheckSet { + Checks { origin: Some( 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", ], }, ] @@ -2314,13 +2414,15 @@ symbols: ["abcD12x", "abcD12"] public keys: [] +block version: 4 + ``` -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 @@ -2339,17 +2441,1063 @@ World { facts: [] rules: [] checks: [ - AuthorizerCheckSet { + Checks { origin: Some( 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 hex:12abcd !== hex:12ab", + "check if {1, 4} !== {1, 2}", + ], + }, +] + policies: [ + "allow if true", +] +} +``` + +result: `Ok(0)` + + +------------------------------ + +## test reject if: test029_reject_if.bc +### token + +authority: +symbols: ["test"] + +public keys: [] + +block version: 6 + +``` +reject if test($test), $test; +``` + +### validation + +authorizer code: +``` +test(false); + +allow if true; +``` + +revocation ids: +- `8d175329f7cf161f3cb5badc52f0e22e520956cdb565edbed963e9b047b20a314a7de1c9eba6b7bbf622636516ab3cc7f91572ae9461d3152825e0ece5127a0a` + +authorizer world: +``` +World { + facts: [ + Facts { + origin: { + None, + }, + facts: [ + "test(false)", + ], + }, +] + rules: [] + checks: [ + Checks { + origin: Some( + 0, + ), + checks: [ + "reject if test($test), $test", + ], + }, +] + policies: [ + "allow if true", +] +} +``` + +result: `Ok(0)` +### validation for "rejection" + +authorizer code: +``` +test(true); + +allow if true; +``` + +revocation ids: +- `8d175329f7cf161f3cb5badc52f0e22e520956cdb565edbed963e9b047b20a314a7de1c9eba6b7bbf622636516ab3cc7f91572ae9461d3152825e0ece5127a0a` + +authorizer world: +``` +World { + facts: [ + Facts { + origin: { + None, + }, + facts: [ + "test(true)", + ], + }, +] + rules: [] + checks: [ + Checks { + origin: Some( + 0, + ), + checks: [ + "reject if test($test), $test", + ], + }, +] + policies: [ + "allow if true", +] +} +``` + +result: `Err(FailedLogic(Unauthorized { policy: Allow(0), checks: [Block(FailedBlockCheck { block_id: 0, check_id: 0, rule: "reject if test($test), $test" })] }))` + + +------------------------------ + +## test null: test030_null.bc +### token + +authority: +symbols: ["fact", "value"] + +public keys: [] + +block version: 6 + +``` +check if fact(null, $value), $value == null; +reject if fact(null, $value), $value != null; +``` + +### validation + +authorizer code: +``` +fact(null, null); + +allow if true; +``` + +revocation ids: +- `fe50d65706a5945c76569d1ff2be8ece24276857631e96efa05959f73bb4ea8c772945738a01da77a1661aef2b8233b4f4e49ae220f2c81fd0b8da59c212750b` + +authorizer world: +``` +World { + facts: [ + Facts { + origin: { + None, + }, + facts: [ + "fact(null, null)", + ], + }, +] + rules: [] + checks: [ + Checks { + origin: Some( + 0, + ), + checks: [ + "check if fact(null, $value), $value == null", + "reject if fact(null, $value), $value != null", + ], + }, +] + policies: [ + "allow if true", +] +} +``` + +result: `Ok(0)` +### validation for "rejection1" + +authorizer code: +``` +fact(null, 1); + +allow if true; +``` + +revocation ids: +- `fe50d65706a5945c76569d1ff2be8ece24276857631e96efa05959f73bb4ea8c772945738a01da77a1661aef2b8233b4f4e49ae220f2c81fd0b8da59c212750b` + +authorizer world: +``` +World { + facts: [ + Facts { + origin: { + None, + }, + facts: [ + "fact(null, 1)", + ], + }, +] + rules: [] + checks: [ + Checks { + origin: Some( + 0, + ), + checks: [ + "check if fact(null, $value), $value == null", + "reject if fact(null, $value), $value != null", + ], + }, +] + policies: [ + "allow if true", +] +} +``` + +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" })] }))` +### validation for "rejection2" + +authorizer code: +``` +fact(null, true); + +allow if true; +``` + +revocation ids: +- `fe50d65706a5945c76569d1ff2be8ece24276857631e96efa05959f73bb4ea8c772945738a01da77a1661aef2b8233b4f4e49ae220f2c81fd0b8da59c212750b` + +authorizer world: +``` +World { + facts: [ + Facts { + origin: { + None, + }, + facts: [ + "fact(null, true)", + ], + }, +] + rules: [] + checks: [ + Checks { + origin: Some( + 0, + ), + checks: [ + "check if fact(null, $value), $value == null", + "reject if fact(null, $value), $value != null", + ], + }, +] + policies: [ + "allow if true", +] +} +``` + +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" })] }))` +### validation for "rejection3" + +authorizer code: +``` +fact(null, "abcd"); + +allow if true; +``` + +revocation ids: +- `fe50d65706a5945c76569d1ff2be8ece24276857631e96efa05959f73bb4ea8c772945738a01da77a1661aef2b8233b4f4e49ae220f2c81fd0b8da59c212750b` + +authorizer world: +``` +World { + facts: [ + Facts { + origin: { + None, + }, + facts: [ + "fact(null, \"abcd\")", + ], + }, +] + rules: [] + checks: [ + Checks { + origin: Some( + 0, + ), + checks: [ + "check if fact(null, $value), $value == null", + "reject if fact(null, $value), $value != null", + ], + }, +] + policies: [ + "allow if true", +] +} +``` + +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: ["abcD12", "abcD12x", "fact", "value", "fact2"] + +public keys: [] + +block version: 6 + +``` +check if true == true; +check if false == false; +check if false != true; +check if 1 != true; +check if 1 == 1; +check if 1 != 3; +check if 1 != true; +check if "abcD12" == "abcD12"; +check if "abcD12x" != "abcD12"; +check if "abcD12x" != true; +check if 2022-12-04T09:46:41Z == 2022-12-04T09:46:41Z; +check if 2022-12-04T09:46:41Z != 2020-12-04T09:46:41Z; +check if 2022-12-04T09:46:41Z != true; +check if hex:12abcd == hex:12abcd; +check if hex:12abcd != hex:12ab; +check if hex:12abcd != true; +check if {1, 2} == {1, 2}; +check if {1, 4} != {1, 2}; +check if {1, 4} != true; +check if fact(1, $value), 1 == $value; +check if fact2(1, $value), 1 != $value; +``` + +### validation + +authorizer code: +``` +fact(1, 1); +fact2(1, 2); + +allow if true; +``` + +revocation ids: +- `be50b2040f4b5fe278b87815910d249eeb9ca5238cae4ea538e22afda11f576e868cbfe7e6b0a03b02ae0f22239ec908947d4bad5a878e4b9f7bd7de73e5c90a` + +authorizer world: +``` +World { + facts: [ + Facts { + origin: { + None, + }, + facts: [ + "fact(1, 1)", + "fact2(1, 2)", + ], + }, +] + rules: [] + checks: [ + Checks { + origin: Some( + 0, + ), + checks: [ + "check if \"abcD12\" == \"abcD12\"", + "check if \"abcD12x\" != \"abcD12\"", + "check if \"abcD12x\" != true", + "check if 1 != 3", + "check if 1 != true", + "check if 1 != true", + "check if 1 == 1", + "check if 2022-12-04T09:46:41Z != 2020-12-04T09:46:41Z", + "check if 2022-12-04T09:46:41Z != true", + "check if 2022-12-04T09:46:41Z == 2022-12-04T09:46:41Z", + "check if fact(1, $value), 1 == $value", + "check if fact2(1, $value), 1 != $value", + "check if false != true", + "check if false == false", + "check if hex:12abcd != hex:12ab", + "check if hex:12abcd != true", + "check if hex:12abcd == hex:12abcd", + "check if true == true", + "check if {1, 2} == {1, 2}", + "check if {1, 4} != true", + "check if {1, 4} != {1, 2}", + ], + }, +] + policies: [ + "allow if true", +] +} +``` + +result: `Ok(0)` +### validation for "evaluate to false" + +authorizer code: +``` +fact(1, 2); +fact2(1, 1); + +check if false != false; + +allow if true; +``` + +revocation ids: +- `be50b2040f4b5fe278b87815910d249eeb9ca5238cae4ea538e22afda11f576e868cbfe7e6b0a03b02ae0f22239ec908947d4bad5a878e4b9f7bd7de73e5c90a` + +authorizer world: +``` +World { + facts: [ + Facts { + origin: { + None, + }, + facts: [ + "fact(1, 2)", + "fact2(1, 1)", + ], + }, +] + rules: [] + checks: [ + Checks { + origin: Some( + 0, + ), + checks: [ + "check if \"abcD12\" == \"abcD12\"", + "check if \"abcD12x\" != \"abcD12\"", + "check if \"abcD12x\" != true", + "check if 1 != 3", + "check if 1 != true", + "check if 1 != true", + "check if 1 == 1", + "check if 2022-12-04T09:46:41Z != 2020-12-04T09:46:41Z", + "check if 2022-12-04T09:46:41Z != true", + "check if 2022-12-04T09:46:41Z == 2022-12-04T09:46:41Z", + "check if fact(1, $value), 1 == $value", + "check if fact2(1, $value), 1 != $value", + "check if false != true", + "check if false == false", + "check if hex:12abcd != hex:12ab", + "check if hex:12abcd != true", + "check if hex:12abcd == hex:12abcd", + "check if true == true", + "check if {1, 2} == {1, 2}", + "check if {1, 4} != true", + "check if {1, 4} != {1, 2}", + ], + }, + Checks { + origin: Some( + 18446744073709551615, + ), + checks: [ + "check if false != false", + ], + }, +] + policies: [ + "allow if true", +] +} +``` + +result: `Err(FailedLogic(Unauthorized { policy: Allow(0), checks: [Authorizer(FailedAuthorizerCheck { check_id: 0, rule: "check if false != false" }), Block(FailedBlockCheck { block_id: 0, check_id: 19, rule: "check if fact(1, $value), 1 == $value" }), Block(FailedBlockCheck { block_id: 0, check_id: 20, rule: "check if fact2(1, $value), 1 != $value" })] }))` + + +------------------------------ + +## test laziness and closures: test032_laziness_closures.bc +### token + +authority: +symbols: ["x", "p", "q"] + +public keys: [] + +block version: 6 + +``` +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: +- `2cd348b6df5f08b900903fd8d3fbea0bb89b665c331a2aa2131e0b8ecb38b3550275d4ccd8db35da6c4433eed1d456cfb761e3fcc7845894d891e986ca044b02` + +authorizer world: +``` +World { + facts: [] + rules: [] + checks: [ + Checks { + origin: Some( + 0, + ), + checks: [ + "check if !(false && \"x\".intersection(\"x\"))", + "check if !false && true", + "check if !{1, 2, 3}.all($p -> $p == 2)", + "check if !{1, 2, 3}.any($p -> $p > 3)", + "check if (true || false) && true", + "check if false || true", + "check if true || \"x\".intersection(\"x\")", + "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)", + ], + }, +] + policies: [ + "allow if true", +] +} +``` + +result: `Ok(0)` +### validation for "shadowing" + +authorizer code: +``` +allow if [true].any($p -> [true].all($p -> $p)); +``` + +revocation ids: +- `2cd348b6df5f08b900903fd8d3fbea0bb89b665c331a2aa2131e0b8ecb38b3550275d4ccd8db35da6c4433eed1d456cfb761e3fcc7845894d891e986ca044b02` + +authorizer world: +``` +World { + facts: [] + rules: [] + checks: [ + Checks { + origin: Some( + 0, + ), + checks: [ + "check if !(false && \"x\".intersection(\"x\"))", + "check if !false && true", + "check if !{1, 2, 3}.all($p -> $p == 2)", + "check if !{1, 2, 3}.any($p -> $p > 3)", + "check if (true || false) && true", + "check if false || true", + "check if true || \"x\".intersection(\"x\")", + "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)", + ], + }, +] + policies: [ + "allow if [true].any($p -> [true].all($p -> $p))", +] +} +``` + +result: `Err(Execution(ShadowedVariable))` + + +------------------------------ + +## test .type(): test033_typeof.bc +### token + +authority: +symbols: ["integer", "string", "test", "date", "bytes", "bool", "set", "null", "array", "map", "a", "t"] + +public keys: [] + +block version: 6 + +``` +integer(1); +string("test"); +date(2023-12-28T00:00:00Z); +bytes(hex:aa); +bool(true); +set({false, true}); +null(null); +array([1, 2, 3]); +map({"a": true}); +check if 1.type() == "integer"; +check if integer($t), $t.type() == "integer"; +check if "test".type() == "string"; +check if string($t), $t.type() == "string"; +check if (2023-12-28T00:00:00Z).type() == "date"; +check if date($t), $t.type() == "date"; +check if hex:aa.type() == "bytes"; +check if bytes($t), $t.type() == "bytes"; +check if true.type() == "bool"; +check if bool($t), $t.type() == "bool"; +check if {false, true}.type() == "set"; +check if set($t), $t.type() == "set"; +check if null.type() == "null"; +check if null($t), $t.type() == "null"; +check if array($t), $t.type() == "array"; +check if map($t), $t.type() == "map"; +``` + +### validation + +authorizer code: +``` +allow if true; +``` + +revocation ids: +- `e60875c6ef7917c227a5e4b2cabfe250a85fa0598eb3cf7987ded0da2b69a559a1665bd312aeecde78e76aeb28ea1c1a03ec9b7dec8aeb519e7867ef8ff9b402` + +authorizer world: +``` +World { + facts: [ + Facts { + origin: { + Some( + 0, + ), + }, + facts: [ + "array([1, 2, 3])", + "bool(true)", + "bytes(hex:aa)", + "date(2023-12-28T00:00:00Z)", + "integer(1)", + "map({\"a\": true})", + "null(null)", + "set({false, true})", + "string(\"test\")", + ], + }, +] + rules: [] + checks: [ + Checks { + origin: Some( + 0, + ), + checks: [ + "check if \"test\".type() == \"string\"", + "check if (2023-12-28T00:00:00Z).type() == \"date\"", + "check if 1.type() == \"integer\"", + "check if array($t), $t.type() == \"array\"", + "check if bool($t), $t.type() == \"bool\"", + "check if bytes($t), $t.type() == \"bytes\"", + "check if date($t), $t.type() == \"date\"", + "check if hex:aa.type() == \"bytes\"", + "check if integer($t), $t.type() == \"integer\"", + "check if map($t), $t.type() == \"map\"", + "check if null($t), $t.type() == \"null\"", + "check if null.type() == \"null\"", + "check if set($t), $t.type() == \"set\"", + "check if string($t), $t.type() == \"string\"", + "check if true.type() == \"bool\"", + "check if {false, true}.type() == \"set\"", + ], + }, +] + policies: [ + "allow if true", +] +} +``` + +result: `Ok(0)` + + +------------------------------ + +## test array and map operations: test034_array_map.bc +### token + +authority: +symbols: ["a", "b", "c", "p", "d", "A", "kv", "id", "roles"] + +public keys: [] + +block version: 6 + +``` +check if [1, 2, 1].length() == 3; +check if ["a", "b"] != true; +check if ["a", "b"] != [1, 2, 3]; +check if ["a", "b"] == ["a", "b"]; +check if ["a", "b"] === ["a", "b"]; +check if ["a", "b"] !== ["a", "c"]; +check if ["a", "b", "c"].contains("c"); +check if [1, 2, 3].starts_with([1, 2]); +check if [4, 5, 6].ends_with([6]); +check if [1, 2, "a"].get(2) == "a"; +check if [1, 2].get(3) == null; +check if [1, 2, 3].all($p -> $p > 0); +check if [1, 2, 3].any($p -> $p > 2); +check if {"a": 1, "b": 2, "c": 3, "d": 4}.length() == 4; +check if {1: "a", 2: "b"} != true; +check if {1: "a", 2: "b"} != {"a": 1, "b": 2}; +check if {1: "a", 2: "b"} == {1: "a", 2: "b"}; +check if {1: "a", 2: "b"} !== {"a": 1, "b": 2}; +check if {1: "a", 2: "b"} === {1: "a", 2: "b"}; +check if {"a": 1, "b": 2, "c": 3, "d": 4}.contains("d"); +check if {1: "A", "a": 1, "b": 2}.get("a") == 1; +check if {1: "A", "a": 1, "b": 2}.get(1) == "A"; +check if {1: "A", "a": 1, "b": 2}.get("c") == null; +check if {1: "A", "a": 1, "b": 2}.get(2) == null; +check if {"a": 1, "b": 2}.all($kv -> $kv.get(0) != "c" && $kv.get(1) < 3); +check if {1: "A", "a": 1, "b": 2}.any($kv -> $kv.get(0) == 1 && $kv.get(1) == "A"); +check if {"user": {"id": 1, "roles": ["admin"]}}.get("user").get("roles").contains("admin"); +``` + +### validation + +authorizer code: +``` +allow if true; +``` + +revocation ids: +- `b22238a06ca9c015d3c49d4ebaa7e8ab6e0d69119b3264033618e726d62fc6f4757a7bebc25f255444aba39994554a62a53ecc13b68802efab8da85ace62390d` + +authorizer world: +``` +World { + facts: [] + rules: [] + checks: [ + Checks { + origin: Some( + 0, + ), + checks: [ + "check if [\"a\", \"b\", \"c\"].contains(\"c\")", + "check if [\"a\", \"b\"] != [1, 2, 3]", + "check if [\"a\", \"b\"] != true", + "check if [\"a\", \"b\"] !== [\"a\", \"c\"]", + "check if [\"a\", \"b\"] == [\"a\", \"b\"]", + "check if [\"a\", \"b\"] === [\"a\", \"b\"]", + "check if [1, 2, \"a\"].get(2) == \"a\"", + "check if [1, 2, 1].length() == 3", + "check if [1, 2, 3].all($p -> $p > 0)", + "check if [1, 2, 3].any($p -> $p > 2)", + "check if [1, 2, 3].starts_with([1, 2])", + "check if [1, 2].get(3) == null", + "check if [4, 5, 6].ends_with([6])", + "check if {\"a\": 1, \"b\": 2, \"c\": 3, \"d\": 4}.contains(\"d\")", + "check if {\"a\": 1, \"b\": 2, \"c\": 3, \"d\": 4}.length() == 4", + "check if {\"a\": 1, \"b\": 2}.all($kv -> $kv.get(0) != \"c\" && $kv.get(1) < 3)", + "check if {\"user\": {\"id\": 1, \"roles\": [\"admin\"]}}.get(\"user\").get(\"roles\").contains(\"admin\")", + "check if {1: \"A\", \"a\": 1, \"b\": 2}.any($kv -> $kv.get(0) == 1 && $kv.get(1) == \"A\")", + "check if {1: \"A\", \"a\": 1, \"b\": 2}.get(\"a\") == 1", + "check if {1: \"A\", \"a\": 1, \"b\": 2}.get(\"c\") == null", + "check if {1: \"A\", \"a\": 1, \"b\": 2}.get(1) == \"A\"", + "check if {1: \"A\", \"a\": 1, \"b\": 2}.get(2) == null", + "check if {1: \"a\", 2: \"b\"} != true", + "check if {1: \"a\", 2: \"b\"} != {\"a\": 1, \"b\": 2}", + "check if {1: \"a\", 2: \"b\"} !== {\"a\": 1, \"b\": 2}", + "check if {1: \"a\", 2: \"b\"} == {1: \"a\", 2: \"b\"}", + "check if {1: \"a\", 2: \"b\"} === {1: \"a\", 2: \"b\"}", + ], + }, +] + policies: [ + "allow if true", +] +} +``` + +result: `Ok(0)` + + +------------------------------ + +## test ffi calls (v6 blocks): test035_ffi.bc +### token + +authority: +symbols: ["test", "a", "equal strings"] + +public keys: [] + +block version: 6 + +``` +check if true.extern::test(), "a".extern::test("a") == "equal strings"; +``` + +### validation + +authorizer code: +``` +allow if true; +``` + +revocation ids: +- `d1719fd101c2695d2dac4df67569918363f691b6167670e1dbbf8026f639a7aa1ec2e13707f4d34cadbb2adce5c6e8a816577dd069a8717e0f5cb4ea3cec5b04` + +authorizer world: +``` +World { + facts: [] + rules: [] + checks: [ + Checks { + origin: Some( + 0, + ), + checks: [ + "check if true.extern::test(), \"a\".extern::test(\"a\") == \"equal strings\"", + ], + }, +] + policies: [ + "allow if true", +] +} +``` + +result: `Ok(0)` + + +------------------------------ + +## ECDSA secp256r1 signatures: test036_secp256r1.bc +### token + +authority: +symbols: ["file1", "file2"] + +public keys: [] + +block version: 3 + +``` +right("file1", "read"); +right("file2", "read"); +right("file1", "write"); +``` + +1: +symbols: ["0"] + +public keys: [] + +block version: 3 + +``` +check if resource($0), operation("read"), right($0, "read"); +``` + +### validation + +authorizer code: +``` +resource("file1"); +operation("read"); + +allow if true; +``` + +revocation ids: +- `628b9a6d74cc80b3ece50befd1f5f0f025c0a35d51708b2e77c11aed5f968b93b4096c87ed8169605716de934e155443f140334d71708fcc4247e5a0a518b30d` +- `3046022100b60674854a12814cc36c8aab9600c1d9f9d3160e2334b72c0feede5a56213ea5022100a4f4bbf2dc33b309267af39fce76612017ddb6171e9cd2a3aa8a853f45f1675f` + +authorizer world: +``` +World { + facts: [ + Facts { + origin: { + None, + }, + facts: [ + "operation(\"read\")", + "resource(\"file1\")", + ], + }, + Facts { + origin: { + Some( + 0, + ), + }, + facts: [ + "right(\"file1\", \"read\")", + "right(\"file1\", \"write\")", + "right(\"file2\", \"read\")", + ], + }, +] + rules: [] + checks: [ + Checks { + origin: Some( + 1, + ), + checks: [ + "check if resource($0), operation(\"read\"), right($0, \"read\")", + ], + }, +] + policies: [ + "allow if true", +] +} +``` + +result: `Ok(0)` + + +------------------------------ + +## ECDSA secp256r1 signature on third-party block: test037_secp256r1_third_party.bc +### token + +authority: +symbols: ["file1", "file2", "from_third"] + +public keys: ["secp256r1/025e918fd4463832aea2823dfd9716a36b4d9b1377bd53dd82ddf4c0bc75ed6bbf"] + +block version: 4 + +``` +right("file1", "read"); +right("file2", "read"); +right("file1", "write"); +check if from_third(true) trusting secp256r1/025e918fd4463832aea2823dfd9716a36b4d9b1377bd53dd82ddf4c0bc75ed6bbf; +``` + +1: +symbols: ["from_third", "0"] + +public keys: [] + +external signature by: "secp256r1/025e918fd4463832aea2823dfd9716a36b4d9b1377bd53dd82ddf4c0bc75ed6bbf" + +block version: 5 + +``` +from_third(true); +check if resource($0), operation("read"), right($0, "read"); +``` + +### validation + +authorizer code: +``` +resource("file1"); +operation("read"); + +allow if true; +``` + +revocation ids: +- `70f5402208516fd44cfc9df3dfcfc0a327ee9004f1801ed0a7abdcbbae923d566ddcd2d4a14f4622b35732c4e538af04075cc67ab0888fa2d8923cc668187f0f` +- `30450220793f95665d9af646339503a073670ea2c352459d2a2c2e14c57565f6c7eaf6bc022100cccadfc37e46755f52bb054ed206d7335067885df599a69431db40e33f33d4cf` + +authorizer world: +``` +World { + facts: [ + Facts { + origin: { + None, + }, + facts: [ + "operation(\"read\")", + "resource(\"file1\")", + ], + }, + Facts { + origin: { + Some( + 0, + ), + }, + facts: [ + "right(\"file1\", \"read\")", + "right(\"file1\", \"write\")", + "right(\"file2\", \"read\")", + ], + }, + Facts { + origin: { + Some( + 1, + ), + }, + facts: [ + "from_third(true)", + ], + }, +] + rules: [] + checks: [ + Checks { + origin: Some( + 0, + ), + checks: [ + "check if from_third(true) trusting secp256r1/025e918fd4463832aea2823dfd9716a36b4d9b1377bd53dd82ddf4c0bc75ed6bbf", + ], + }, + Checks { + origin: Some( + 1, + ), + checks: [ + "check if resource($0), operation(\"read\"), right($0, \"read\")", ], }, ] diff --git a/biscuit-auth/samples/samples.json b/biscuit-auth/samples/samples.json index d97790a7..ab47728d 100644 --- a/biscuit-auth/samples/samples.json +++ b/biscuit-auth/samples/samples.json @@ -13,7 +13,8 @@ ], "public_keys": [], "external_key": null, - "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\nright(\"file1\", \"write\");\n" + "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\nright(\"file1\", \"write\");\n", + "version": 3 }, { "symbols": [ @@ -21,7 +22,8 @@ ], "public_keys": [], "external_key": null, - "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n" + "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n", + "version": 3 } ], "validations": { @@ -98,7 +100,8 @@ ], "public_keys": [], "external_key": null, - "code": "right(\"file1\", \"read\");\n" + "code": "right(\"file1\", \"read\");\n", + "version": 3 }, { "symbols": [ @@ -106,7 +109,8 @@ ], "public_keys": [], "external_key": null, - "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n" + "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n", + "version": 3 } ], "validations": { @@ -137,7 +141,8 @@ ], "public_keys": [], "external_key": null, - "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\nright(\"file1\", \"write\");\n" + "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\nright(\"file1\", \"write\");\n", + "version": 3 }, { "symbols": [ @@ -145,7 +150,8 @@ ], "public_keys": [], "external_key": null, - "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n" + "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n", + "version": 3 } ], "validations": { @@ -154,7 +160,7 @@ "result": { "Err": { "Format": { - "InvalidSignatureSize": 16 + "BlockSignatureDeserializationError": "block signature deserialization error: [117, 149, 161, 18, 161, 235, 91, 129, 166, 227, 152, 133, 46, 97, 24, 183]" } } }, @@ -174,7 +180,8 @@ ], "public_keys": [], "external_key": null, - "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\nright(\"file1\", \"write\");\n" + "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\nright(\"file1\", \"write\");\n", + "version": 3 }, { "symbols": [ @@ -182,7 +189,8 @@ ], "public_keys": [], "external_key": null, - "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n" + "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n", + "version": 3 } ], "validations": { @@ -213,7 +221,8 @@ ], "public_keys": [], "external_key": null, - "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\nright(\"file1\", \"write\");\n" + "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\nright(\"file1\", \"write\");\n", + "version": 3 }, { "symbols": [ @@ -221,7 +230,8 @@ ], "public_keys": [], "external_key": null, - "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n" + "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n", + "version": 3 } ], "validations": { @@ -252,7 +262,8 @@ ], "public_keys": [], "external_key": null, - "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\nright(\"file1\", \"write\");\n" + "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\nright(\"file1\", \"write\");\n", + "version": 3 }, { "symbols": [ @@ -260,13 +271,15 @@ ], "public_keys": [], "external_key": null, - "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n" + "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n", + "version": 3 }, { "symbols": [], "public_keys": [], "external_key": null, - "code": "check if resource(\"file1\");\n" + "code": "check if resource(\"file1\");\n", + "version": 3 } ], "validations": { @@ -298,7 +311,8 @@ ], "public_keys": [], "external_key": null, - "code": "user_id(\"alice\");\nowner(\"alice\", \"file1\");\n" + "code": "user_id(\"alice\");\nowner(\"alice\", \"file1\");\n", + "version": 3 }, { "symbols": [ @@ -307,7 +321,8 @@ ], "public_keys": [], "external_key": null, - "code": "right($0, \"read\") <- resource($0), user_id($1), owner($1, $0);\ncheck if resource($0), operation(\"read\"), right($0, \"read\");\n" + "code": "right($0, \"read\") <- resource($0), user_id($1), owner($1, $0);\ncheck if resource($0), operation(\"read\"), right($0, \"read\");\n", + "version": 3 }, { "symbols": [ @@ -315,7 +330,8 @@ ], "public_keys": [], "external_key": null, - "code": "owner(\"alice\", \"file2\");\n" + "code": "owner(\"alice\", \"file2\");\n", + "version": 3 } ], "validations": { @@ -408,7 +424,8 @@ ], "public_keys": [], "external_key": null, - "code": "right(\"file1\", \"read\");\n" + "code": "right(\"file1\", \"read\");\n", + "version": 3 }, { "symbols": [ @@ -416,7 +433,8 @@ ], "public_keys": [], "external_key": null, - "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n" + "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n", + "version": 3 }, { "symbols": [ @@ -424,7 +442,8 @@ ], "public_keys": [], "external_key": null, - "code": "right(\"file2\", \"read\");\n" + "code": "right(\"file2\", \"read\");\n", + "version": 3 } ], "validations": { @@ -507,7 +526,8 @@ "symbols": [], "public_keys": [], "external_key": null, - "code": "" + "code": "", + "version": 3 }, { "symbols": [ @@ -515,7 +535,8 @@ ], "public_keys": [], "external_key": null, - "code": "check if resource(\"file1\");\ncheck if time($time), $time <= 2018-12-20T00:00:00Z;\n" + "code": "check if resource(\"file1\");\ncheck if time($time), $time <= 2018-12-20T00:00:00Z;\n", + "version": 3 } ], "validations": { @@ -585,7 +606,8 @@ ], "public_keys": [], "external_key": null, - "code": "right(\"file1\", \"read\");\n" + "code": "right(\"file1\", \"read\");\n", + "version": 3 }, { "symbols": [ @@ -593,7 +615,8 @@ ], "public_keys": [], "external_key": null, - "code": "right(\"file2\", \"read\");\n" + "code": "right(\"file2\", \"read\");\n", + "version": 3 } ], "validations": { @@ -676,7 +699,8 @@ ], "public_keys": [], "external_key": null, - "code": "right(\"file1\", \"read\");\n" + "code": "right(\"file1\", \"read\");\n", + "version": 3 } ], "validations": { @@ -750,7 +774,8 @@ ], "public_keys": [], "external_key": null, - "code": "check if resource(\"file1\");\n" + "code": "check if resource(\"file1\");\n", + "version": 3 } ], "validations": { @@ -852,7 +877,8 @@ ], "public_keys": [], "external_key": null, - "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\n" + "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\n", + "version": 3 }, { "symbols": [ @@ -862,7 +888,8 @@ ], "public_keys": [], "external_key": null, - "code": "valid_date(\"file1\") <- time($0), resource(\"file1\"), $0 <= 2030-12-31T12:59:59Z;\nvalid_date($1) <- time($0), resource($1), $0 <= 1999-12-31T12:59:59Z, ![\"file1\"].contains($1);\ncheck if valid_date($0), resource($0);\n" + "code": "valid_date(\"file1\") <- time($0), resource(\"file1\"), $0 <= 2030-12-31T12:59:59Z;\nvalid_date($1) <- time($0), resource($1), $0 <= 1999-12-31T12:59:59Z, !{\"file1\"}.contains($1);\ncheck if valid_date($0), resource($0);\n", + "version": 3 } ], "validations": { @@ -902,7 +929,7 @@ "origin": 1, "rules": [ "valid_date(\"file1\") <- time($0), resource(\"file1\"), $0 <= 2030-12-31T12:59:59Z", - "valid_date($1) <- time($0), resource($1), $0 <= 1999-12-31T12:59:59Z, ![\"file1\"].contains($1)" + "valid_date($1) <- time($0), resource($1), $0 <= 1999-12-31T12:59:59Z, !{\"file1\"}.contains($1)" ] } ], @@ -954,7 +981,7 @@ "origin": 1, "rules": [ "valid_date(\"file1\") <- time($0), resource(\"file1\"), $0 <= 2030-12-31T12:59:59Z", - "valid_date($1) <- time($0), resource($1), $0 <= 1999-12-31T12:59:59Z, ![\"file1\"].contains($1)" + "valid_date($1) <- time($0), resource($1), $0 <= 1999-12-31T12:59:59Z, !{\"file1\"}.contains($1)" ] } ], @@ -1009,7 +1036,8 @@ ], "public_keys": [], "external_key": null, - "code": "check if resource($0), $0.matches(\"file[0-9]+.txt\");\n" + "code": "check if resource($0), $0.matches(\"file[0-9]+.txt\");\n", + "version": 3 } ], "validations": { @@ -1109,7 +1137,8 @@ ], "public_keys": [], "external_key": null, - "code": "must_be_present(\"hello\");\n" + "code": "must_be_present(\"hello\");\n", + "version": 3 } ], "validations": { @@ -1158,7 +1187,8 @@ ], "public_keys": [], "external_key": null, - "code": "check if resource(\"hello\");\n" + "code": "check if resource(\"hello\");\n", + "version": 3 }, { "symbols": [ @@ -1166,7 +1196,8 @@ ], "public_keys": [], "external_key": null, - "code": "query(\"test\");\n" + "code": "query(\"test\");\n", + "version": 3 } ], "validations": { @@ -1245,7 +1276,8 @@ ], "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 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;\ncheck if {,}.length() === 0;\n", + "version": 3 } ], "validations": { @@ -1258,16 +1290,14 @@ "origin": 0, "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 \"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 \"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 1 + 2 * 3 - 4 / 2 === 5", "check if 1 < 2", "check if 1 <= 1", "check if 1 <= 2", @@ -1276,28 +1306,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 [\"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].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 [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 || true", - "check if hex:12ab == hex:12ab", + "check if 3 === 3", + "check if false === false", + "check if hex:12ab === hex:12ab", "check if true", - "check if true == true" + "check if true === true", + "check if {\"abc\", \"def\"}.contains(\"abc\")", + "check if {,}.length() === 0", + "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}.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 {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)" ] } ], @@ -1310,7 +1340,7 @@ }, "authorizer_code": "allow if true;\n", "revocation_ids": [ - "3d5b23b502b3dd920bfb68b9039164d1563bb8927210166fa5c17f41b76b31bb957bc2ed3318452958f658baa2d398fe4cf25c58a27e6c8bc42c9702c8aa1b0c" + "fa358e4e3bea896415b1859e6cd347e64e1918fb86e31ae3fe208628321576a47f7a269760357e291c827ec9cbe322074f6860a546207a64e133c83a214bb505" ] } } @@ -1323,7 +1353,8 @@ "symbols": [], "public_keys": [], "external_key": null, - "code": "check if operation(\"read\");\n" + "code": "check if operation(\"read\");\n", + "version": 3 }, { "symbols": [ @@ -1333,7 +1364,8 @@ ], "public_keys": [], "external_key": null, - "code": "operation($unbound, \"read\") <- operation($any1, $any2);\n" + "code": "operation($unbound, \"read\") <- operation($any1, $any2);\n", + "version": 3 } ], "validations": { @@ -1365,7 +1397,8 @@ "symbols": [], "public_keys": [], "external_key": null, - "code": "check if operation(\"read\");\n" + "code": "check if operation(\"read\");\n", + "version": 3 }, { "symbols": [ @@ -1373,7 +1406,8 @@ ], "public_keys": [], "external_key": null, - "code": "operation(\"read\") <- operation($any);\n" + "code": "operation(\"read\") <- operation($any);\n", + "version": 3 } ], "validations": { @@ -1457,7 +1491,8 @@ ], "public_keys": [], "external_key": null, - "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\nright(\"file1\", \"write\");\n" + "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\nright(\"file1\", \"write\");\n", + "version": 3 }, { "symbols": [ @@ -1465,7 +1500,8 @@ ], "public_keys": [], "external_key": null, - "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n" + "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n", + "version": 3 } ], "validations": { @@ -1527,7 +1563,8 @@ ], "public_keys": [], "external_key": null, - "code": "ns::fact_123(\"hello é\t😁\");\n" + "code": "ns::fact_123(\"hello é\t😁\");\n", + "version": 3 } ], "validations": { @@ -1574,7 +1611,8 @@ "symbols": [], "public_keys": [], "external_key": null, - "code": "read(0);\nwrite(1);\nresource(2);\noperation(3);\nright(4);\ntime(5);\nrole(6);\nowner(7);\ntenant(8);\nnamespace(9);\nuser(10);\nteam(11);\nservice(12);\nadmin(13);\nemail(14);\ngroup(15);\nmember(16);\nip_address(17);\nclient(18);\nclient_ip(19);\ndomain(20);\npath(21);\nversion(22);\ncluster(23);\nnode(24);\nhostname(25);\nnonce(26);\nquery(27);\n" + "code": "read(0);\nwrite(1);\nresource(2);\noperation(3);\nright(4);\ntime(5);\nrole(6);\nowner(7);\ntenant(8);\nnamespace(9);\nuser(10);\nteam(11);\nservice(12);\nadmin(13);\nemail(14);\ngroup(15);\nmember(16);\nip_address(17);\nclient(18);\nclient_ip(19);\ndomain(20);\npath(21);\nversion(22);\ncluster(23);\nnode(24);\nhostname(25);\nnonce(26);\nquery(27);\n", + "version": 3 } ], "validations": { @@ -1650,7 +1688,8 @@ ], "public_keys": [], "external_key": null, - "code": "authority_fact(1);\n" + "code": "authority_fact(1);\n", + "version": 3 }, { "symbols": [ @@ -1658,7 +1697,8 @@ ], "public_keys": [], "external_key": null, - "code": "block1_fact(1);\n" + "code": "block1_fact(1);\n", + "version": 3 }, { "symbols": [ @@ -1666,7 +1706,8 @@ ], "public_keys": [], "external_key": null, - "code": "check if authority_fact($var);\ncheck if block1_fact($var);\n" + "code": "check if authority_fact($var);\ncheck if block1_fact($var);\n", + "version": 3 } ], "validations": { @@ -1743,13 +1784,15 @@ "ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189" ], "external_key": null, - "code": "right(\"read\");\ncheck if group(\"admin\") trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189;\n" + "code": "right(\"read\");\ncheck if group(\"admin\") trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189;\n", + "version": 4 }, { "symbols": [], "public_keys": [], "external_key": "ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189", - "code": "group(\"admin\");\ncheck if right(\"read\");\n" + "code": "group(\"admin\");\ncheck if right(\"read\");\n", + "version": 5 } ], "validations": { @@ -1798,7 +1841,7 @@ "authorizer_code": "allow if true;\n", "revocation_ids": [ "470e4bf7aa2a01ab39c98150bd06aa15b4aa5d86509044a8809a8634cd8cf2b42269a51a774b65d10bac9369d013070b00187925196a8e680108473f11cf8f03", - "342167bc54bc642b6718a276875e55b6d39e9b21e4ce13b926a3d398b6c057fc436385bf4c817a16f9ecdf0b0d950e8b8258a20aeb3fd8896c5e9c1f0a53da03" + "901b2af4dacf33458d2d91ac484b60bad948e8d10faa9695b096054d5b46e832a977b60b17464cacf545ad0801f549ea454675f0ac88c413406925e2af83ff08" ] } } @@ -1817,7 +1860,8 @@ ], "public_keys": [], "external_key": null, - "code": "allowed_operations([\"A\", \"B\"]);\ncheck all operation($op), allowed_operations($allowed), $allowed.contains($op);\n" + "code": "allowed_operations({\"A\", \"B\"});\ncheck all operation($op), allowed_operations($allowed), $allowed.contains($op);\n", + "version": 4 } ], "validations": { @@ -1838,7 +1882,7 @@ 0 ], "facts": [ - "allowed_operations([\"A\", \"B\"])" + "allowed_operations({\"A\", \"B\"})" ] } ], @@ -1880,7 +1924,7 @@ 0 ], "facts": [ - "allowed_operations([\"A\", \"B\"])" + "allowed_operations({\"A\", \"B\"})" ] } ], @@ -1934,7 +1978,8 @@ "ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189" ], "external_key": null, - "code": "query(0);\ncheck if true trusting previous, ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189;\n" + "code": "query(0);\ncheck if true trusting previous, ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189;\n", + "version": 4 }, { "symbols": [], @@ -1943,7 +1988,8 @@ "ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189" ], "external_key": "ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189", - "code": "query(1);\nquery(1, 2) <- query(1), query(2) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463;\ncheck if query(2), query(3) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463;\ncheck if query(1) trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189;\n" + "code": "query(1);\nquery(1, 2) <- query(1), query(2) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463;\ncheck if query(2), query(3) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463;\ncheck if query(1) trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189;\n", + "version": 5 }, { "symbols": [], @@ -1952,7 +1998,8 @@ "ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189" ], "external_key": "ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463", - "code": "query(2);\ncheck if query(2), query(3) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463;\ncheck if query(1) trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189;\n" + "code": "query(2);\ncheck if query(2), query(3) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463;\ncheck if query(1) trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189;\n", + "version": 5 }, { "symbols": [], @@ -1961,7 +2008,8 @@ "ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189" ], "external_key": "ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463", - "code": "query(3);\ncheck if query(2), query(3) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463;\ncheck if query(1) trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189;\n" + "code": "query(3);\ncheck if query(2), query(3) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463;\ncheck if query(1) trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189;\n", + "version": 5 }, { "symbols": [], @@ -1970,7 +2018,8 @@ "ed25519/f98da8c1cf907856431bfc3dc87531e0eaadba90f919edc232405b85877ef136" ], "external_key": null, - "code": "query(4);\ncheck if query(2) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463;\ncheck if query(4) trusting ed25519/f98da8c1cf907856431bfc3dc87531e0eaadba90f919edc232405b85877ef136;\n" + "code": "query(4);\ncheck if query(2) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463;\ncheck if query(4) trusting ed25519/f98da8c1cf907856431bfc3dc87531e0eaadba90f919edc232405b85877ef136;\n", + "version": 4 } ], "validations": { @@ -2090,10 +2139,10 @@ "authorizer_code": "check if query(1, 2) trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189, ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463;\n\ndeny if query(3);\ndeny if query(1, 2);\ndeny if query(0) trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189;\nallow if true;\n", "revocation_ids": [ "3771cefe71beb21ead35a59c8116ee82627a5717c0295f35980662abccb159fe1b37848cb1818e548656bd4fd882d0094a2daab631c76b2b72e3a093914bfe04", - "6528db2c9a561ada9086268549a600a8a52ff434ea8183812623eec0e9b6c5d3c41ab7868808623021d92294d583afdf92f4354bcdaa1bc50453e1b89afd630d", - "5d5679fe69bfe74b7919323515e9ecba9d01422b16be9341b57f88e695b2bb0bd7966b781001d2b9e00ee618fdc239c96e17e32cb379f13f12d6bd7b1b47ad04", - "c37bf24c063f0310eccab8864e48dbeffcdd7240b4f8d1e01eba4fc703e6c9082b845bb55543b10f008dc7f4e78540411912ac1f36fa2aa90011dca40f323b09", - "3f675d6c364e06405d4868c904e40f3d81c32b083d91586db814d4cb4bf536b4ba209d82f11b4cb6da293b60b20d6122fc3e0e08e80c381dee83edd848211900" + "7113d4dbb3b688b80e941f365a2c6342d480c77ed03937bccf85dc5cc3554c7517887b1b0c9021388a71e6ca9047aabaaad5ae5b511a2880902568444a98e50b", + "d0e3fc4bbd1b7320022800af909585aa906f677c4ca79c275a10b6779f669384c464ee84a1b04f13877a25761a874748362c065f4d15a8cab5c5e16c34074403", + "29b7e0a1f118a6185814a552660c516c43482044e280e7a8de85b8e7e54947e0ae82eb39d7b524d4b72cb9812a7a4b8871964f8f825b1c1ed85d344c05281d0d", + "c0a505d4d921a8b2d0b885917d42e2bca87b5302d13249a61af6f3802af44d691c40a624f901d677724740cb974a188aeb1c3992c1565ac0fbec3aa4f68dac0a" ] } } @@ -2106,7 +2155,8 @@ "symbols": [], "public_keys": [], "external_key": null, - "code": "check if true || 10000000000 * 10000000000 != 0;\ncheck if true || 9223372036854775807 + 1 != 0;\ncheck if true || -9223372036854775808 - 1 != 0;\n" + "code": "check if 10000000000 * 10000000000 !== 0;\ncheck if 9223372036854775807 + 1 !== 0;\ncheck if -9223372036854775808 - 1 !== 0;\n", + "version": 4 } ], "validations": { @@ -2118,9 +2168,9 @@ { "origin": 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" ] } ], @@ -2135,7 +2185,7 @@ }, "authorizer_code": "allow if true;\n", "revocation_ids": [ - "3346a22aae0abfc1ffa526f02f7650e90af909e5e519989026441e78cdc245b7fd126503cfdc8831325fc04307edc65238db319724477915f7040a2f6a719a05" + "fb5e7ac2bb892f5cf2fb59677cfad1f96deabbc8e158e3fd1b5ee7c4b6949c999e2169187cbee53b943eebdadaaf68832747baa8cffa2ff9f78025a1f55f440c" ] } } @@ -2151,7 +2201,8 @@ ], "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", + "version": 4 } ], "validations": { @@ -2163,12 +2214,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 hex:12abcd !== hex:12ab", + "check if {1, 4} !== {1, 2}" ] } ], @@ -2185,6 +2236,948 @@ ] } } + }, + { + "title": "test reject if", + "filename": "test029_reject_if.bc", + "token": [ + { + "symbols": [ + "test" + ], + "public_keys": [], + "external_key": null, + "code": "reject if test($test), $test;\n", + "version": 6 + } + ], + "validations": { + "": { + "world": { + "facts": [ + { + "origin": [ + null + ], + "facts": [ + "test(false)" + ] + } + ], + "rules": [], + "checks": [ + { + "origin": 0, + "checks": [ + "reject if test($test), $test" + ] + } + ], + "policies": [ + "allow if true" + ] + }, + "result": { + "Ok": 0 + }, + "authorizer_code": "test(false);\n\nallow if true;\n", + "revocation_ids": [ + "8d175329f7cf161f3cb5badc52f0e22e520956cdb565edbed963e9b047b20a314a7de1c9eba6b7bbf622636516ab3cc7f91572ae9461d3152825e0ece5127a0a" + ] + }, + "rejection": { + "world": { + "facts": [ + { + "origin": [ + null + ], + "facts": [ + "test(true)" + ] + } + ], + "rules": [], + "checks": [ + { + "origin": 0, + "checks": [ + "reject if test($test), $test" + ] + } + ], + "policies": [ + "allow if true" + ] + }, + "result": { + "Err": { + "FailedLogic": { + "Unauthorized": { + "policy": { + "Allow": 0 + }, + "checks": [ + { + "Block": { + "block_id": 0, + "check_id": 0, + "rule": "reject if test($test), $test" + } + } + ] + } + } + } + }, + "authorizer_code": "test(true);\n\nallow if true;\n", + "revocation_ids": [ + "8d175329f7cf161f3cb5badc52f0e22e520956cdb565edbed963e9b047b20a314a7de1c9eba6b7bbf622636516ab3cc7f91572ae9461d3152825e0ece5127a0a" + ] + } + } + }, + { + "title": "test null", + "filename": "test030_null.bc", + "token": [ + { + "symbols": [ + "fact", + "value" + ], + "public_keys": [], + "external_key": null, + "code": "check if fact(null, $value), $value == null;\nreject if fact(null, $value), $value != null;\n", + "version": 6 + } + ], + "validations": { + "": { + "world": { + "facts": [ + { + "origin": [ + null + ], + "facts": [ + "fact(null, null)" + ] + } + ], + "rules": [], + "checks": [ + { + "origin": 0, + "checks": [ + "check if fact(null, $value), $value == null", + "reject if fact(null, $value), $value != null" + ] + } + ], + "policies": [ + "allow if true" + ] + }, + "result": { + "Ok": 0 + }, + "authorizer_code": "fact(null, null);\n\nallow if true;\n", + "revocation_ids": [ + "fe50d65706a5945c76569d1ff2be8ece24276857631e96efa05959f73bb4ea8c772945738a01da77a1661aef2b8233b4f4e49ae220f2c81fd0b8da59c212750b" + ] + }, + "rejection1": { + "world": { + "facts": [ + { + "origin": [ + null + ], + "facts": [ + "fact(null, 1)" + ] + } + ], + "rules": [], + "checks": [ + { + "origin": 0, + "checks": [ + "check if fact(null, $value), $value == null", + "reject if fact(null, $value), $value != null" + ] + } + ], + "policies": [ + "allow if true" + ] + }, + "result": { + "Err": { + "FailedLogic": { + "Unauthorized": { + "policy": { + "Allow": 0 + }, + "checks": [ + { + "Block": { + "block_id": 0, + "check_id": 0, + "rule": "check if fact(null, $value), $value == null" + } + }, + { + "Block": { + "block_id": 0, + "check_id": 1, + "rule": "reject if fact(null, $value), $value != null" + } + } + ] + } + } + } + }, + "authorizer_code": "fact(null, 1);\n\nallow if true;\n", + "revocation_ids": [ + "fe50d65706a5945c76569d1ff2be8ece24276857631e96efa05959f73bb4ea8c772945738a01da77a1661aef2b8233b4f4e49ae220f2c81fd0b8da59c212750b" + ] + }, + "rejection2": { + "world": { + "facts": [ + { + "origin": [ + null + ], + "facts": [ + "fact(null, true)" + ] + } + ], + "rules": [], + "checks": [ + { + "origin": 0, + "checks": [ + "check if fact(null, $value), $value == null", + "reject if fact(null, $value), $value != null" + ] + } + ], + "policies": [ + "allow if true" + ] + }, + "result": { + "Err": { + "FailedLogic": { + "Unauthorized": { + "policy": { + "Allow": 0 + }, + "checks": [ + { + "Block": { + "block_id": 0, + "check_id": 0, + "rule": "check if fact(null, $value), $value == null" + } + }, + { + "Block": { + "block_id": 0, + "check_id": 1, + "rule": "reject if fact(null, $value), $value != null" + } + } + ] + } + } + } + }, + "authorizer_code": "fact(null, true);\n\nallow if true;\n", + "revocation_ids": [ + "fe50d65706a5945c76569d1ff2be8ece24276857631e96efa05959f73bb4ea8c772945738a01da77a1661aef2b8233b4f4e49ae220f2c81fd0b8da59c212750b" + ] + }, + "rejection3": { + "world": { + "facts": [ + { + "origin": [ + null + ], + "facts": [ + "fact(null, \"abcd\")" + ] + } + ], + "rules": [], + "checks": [ + { + "origin": 0, + "checks": [ + "check if fact(null, $value), $value == null", + "reject if fact(null, $value), $value != null" + ] + } + ], + "policies": [ + "allow if true" + ] + }, + "result": { + "Err": { + "FailedLogic": { + "Unauthorized": { + "policy": { + "Allow": 0 + }, + "checks": [ + { + "Block": { + "block_id": 0, + "check_id": 0, + "rule": "check if fact(null, $value), $value == null" + } + }, + { + "Block": { + "block_id": 0, + "check_id": 1, + "rule": "reject if fact(null, $value), $value != null" + } + } + ] + } + } + } + }, + "authorizer_code": "fact(null, \"abcd\");\n\nallow if true;\n", + "revocation_ids": [ + "fe50d65706a5945c76569d1ff2be8ece24276857631e96efa05959f73bb4ea8c772945738a01da77a1661aef2b8233b4f4e49ae220f2c81fd0b8da59c212750b" + ] + } + } + }, + { + "title": "test heterogeneous equal", + "filename": "test031_heterogeneous_equal.bc", + "token": [ + { + "symbols": [ + "abcD12", + "abcD12x", + "fact", + "value", + "fact2" + ], + "public_keys": [], + "external_key": null, + "code": "check if true == true;\ncheck if false == false;\ncheck if false != true;\ncheck if 1 != true;\ncheck if 1 == 1;\ncheck if 1 != 3;\ncheck if 1 != true;\ncheck if \"abcD12\" == \"abcD12\";\ncheck if \"abcD12x\" != \"abcD12\";\ncheck if \"abcD12x\" != true;\ncheck if 2022-12-04T09:46:41Z == 2022-12-04T09:46:41Z;\ncheck if 2022-12-04T09:46:41Z != 2020-12-04T09:46:41Z;\ncheck if 2022-12-04T09:46:41Z != true;\ncheck if hex:12abcd == hex:12abcd;\ncheck if hex:12abcd != hex:12ab;\ncheck if hex:12abcd != true;\ncheck if {1, 2} == {1, 2};\ncheck if {1, 4} != {1, 2};\ncheck if {1, 4} != true;\ncheck if fact(1, $value), 1 == $value;\ncheck if fact2(1, $value), 1 != $value;\n", + "version": 6 + } + ], + "validations": { + "": { + "world": { + "facts": [ + { + "origin": [ + null + ], + "facts": [ + "fact(1, 1)", + "fact2(1, 2)" + ] + } + ], + "rules": [], + "checks": [ + { + "origin": 0, + "checks": [ + "check if \"abcD12\" == \"abcD12\"", + "check if \"abcD12x\" != \"abcD12\"", + "check if \"abcD12x\" != true", + "check if 1 != 3", + "check if 1 != true", + "check if 1 != true", + "check if 1 == 1", + "check if 2022-12-04T09:46:41Z != 2020-12-04T09:46:41Z", + "check if 2022-12-04T09:46:41Z != true", + "check if 2022-12-04T09:46:41Z == 2022-12-04T09:46:41Z", + "check if fact(1, $value), 1 == $value", + "check if fact2(1, $value), 1 != $value", + "check if false != true", + "check if false == false", + "check if hex:12abcd != hex:12ab", + "check if hex:12abcd != true", + "check if hex:12abcd == hex:12abcd", + "check if true == true", + "check if {1, 2} == {1, 2}", + "check if {1, 4} != true", + "check if {1, 4} != {1, 2}" + ] + } + ], + "policies": [ + "allow if true" + ] + }, + "result": { + "Ok": 0 + }, + "authorizer_code": "fact(1, 1);\nfact2(1, 2);\n\nallow if true;\n", + "revocation_ids": [ + "be50b2040f4b5fe278b87815910d249eeb9ca5238cae4ea538e22afda11f576e868cbfe7e6b0a03b02ae0f22239ec908947d4bad5a878e4b9f7bd7de73e5c90a" + ] + }, + "evaluate to false": { + "world": { + "facts": [ + { + "origin": [ + null + ], + "facts": [ + "fact(1, 2)", + "fact2(1, 1)" + ] + } + ], + "rules": [], + "checks": [ + { + "origin": 0, + "checks": [ + "check if \"abcD12\" == \"abcD12\"", + "check if \"abcD12x\" != \"abcD12\"", + "check if \"abcD12x\" != true", + "check if 1 != 3", + "check if 1 != true", + "check if 1 != true", + "check if 1 == 1", + "check if 2022-12-04T09:46:41Z != 2020-12-04T09:46:41Z", + "check if 2022-12-04T09:46:41Z != true", + "check if 2022-12-04T09:46:41Z == 2022-12-04T09:46:41Z", + "check if fact(1, $value), 1 == $value", + "check if fact2(1, $value), 1 != $value", + "check if false != true", + "check if false == false", + "check if hex:12abcd != hex:12ab", + "check if hex:12abcd != true", + "check if hex:12abcd == hex:12abcd", + "check if true == true", + "check if {1, 2} == {1, 2}", + "check if {1, 4} != true", + "check if {1, 4} != {1, 2}" + ] + }, + { + "origin": 18446744073709551615, + "checks": [ + "check if false != false" + ] + } + ], + "policies": [ + "allow if true" + ] + }, + "result": { + "Err": { + "FailedLogic": { + "Unauthorized": { + "policy": { + "Allow": 0 + }, + "checks": [ + { + "Authorizer": { + "check_id": 0, + "rule": "check if false != false" + } + }, + { + "Block": { + "block_id": 0, + "check_id": 19, + "rule": "check if fact(1, $value), 1 == $value" + } + }, + { + "Block": { + "block_id": 0, + "check_id": 20, + "rule": "check if fact2(1, $value), 1 != $value" + } + } + ] + } + } + } + }, + "authorizer_code": "fact(1, 2);\nfact2(1, 1);\n\ncheck if false != false;\n\nallow if true;\n", + "revocation_ids": [ + "be50b2040f4b5fe278b87815910d249eeb9ca5238cae4ea538e22afda11f576e868cbfe7e6b0a03b02ae0f22239ec908947d4bad5a878e4b9f7bd7de73e5c90a" + ] + } + } + }, + { + "title": "test laziness and closures", + "filename": "test032_laziness_closures.bc", + "token": [ + { + "symbols": [ + "x", + "p", + "q" + ], + "public_keys": [], + "external_key": null, + "code": "check if !false && true;\ncheck if false || true;\ncheck if (true || false) && true;\ncheck if !(false && \"x\".intersection(\"x\"));\ncheck if true || \"x\".intersection(\"x\");\ncheck if {1, 2, 3}.all($p -> $p > 0);\ncheck if !{1, 2, 3}.all($p -> $p == 2);\ncheck if {1, 2, 3}.any($p -> $p > 2);\ncheck if !{1, 2, 3}.any($p -> $p > 3);\ncheck if {1, 2, 3}.any($p -> $p > 1 && {3, 4, 5}.any($q -> $p == $q));\n", + "version": 6 + } + ], + "validations": { + "": { + "world": { + "facts": [], + "rules": [], + "checks": [ + { + "origin": 0, + "checks": [ + "check if !(false && \"x\".intersection(\"x\"))", + "check if !false && true", + "check if !{1, 2, 3}.all($p -> $p == 2)", + "check if !{1, 2, 3}.any($p -> $p > 3)", + "check if (true || false) && true", + "check if false || true", + "check if true || \"x\".intersection(\"x\")", + "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)" + ] + } + ], + "policies": [ + "allow if true" + ] + }, + "result": { + "Ok": 0 + }, + "authorizer_code": "allow if true;\n", + "revocation_ids": [ + "2cd348b6df5f08b900903fd8d3fbea0bb89b665c331a2aa2131e0b8ecb38b3550275d4ccd8db35da6c4433eed1d456cfb761e3fcc7845894d891e986ca044b02" + ] + }, + "shadowing": { + "world": { + "facts": [], + "rules": [], + "checks": [ + { + "origin": 0, + "checks": [ + "check if !(false && \"x\".intersection(\"x\"))", + "check if !false && true", + "check if !{1, 2, 3}.all($p -> $p == 2)", + "check if !{1, 2, 3}.any($p -> $p > 3)", + "check if (true || false) && true", + "check if false || true", + "check if true || \"x\".intersection(\"x\")", + "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)" + ] + } + ], + "policies": [ + "allow if [true].any($p -> [true].all($p -> $p))" + ] + }, + "result": { + "Err": { + "Execution": "ShadowedVariable" + } + }, + "authorizer_code": "allow if [true].any($p -> [true].all($p -> $p));\n", + "revocation_ids": [ + "2cd348b6df5f08b900903fd8d3fbea0bb89b665c331a2aa2131e0b8ecb38b3550275d4ccd8db35da6c4433eed1d456cfb761e3fcc7845894d891e986ca044b02" + ] + } + } + }, + { + "title": "test .type()", + "filename": "test033_typeof.bc", + "token": [ + { + "symbols": [ + "integer", + "string", + "test", + "date", + "bytes", + "bool", + "set", + "null", + "array", + "map", + "a", + "t" + ], + "public_keys": [], + "external_key": null, + "code": "integer(1);\nstring(\"test\");\ndate(2023-12-28T00:00:00Z);\nbytes(hex:aa);\nbool(true);\nset({false, true});\nnull(null);\narray([1, 2, 3]);\nmap({\"a\": true});\ncheck if 1.type() == \"integer\";\ncheck if integer($t), $t.type() == \"integer\";\ncheck if \"test\".type() == \"string\";\ncheck if string($t), $t.type() == \"string\";\ncheck if (2023-12-28T00:00:00Z).type() == \"date\";\ncheck if date($t), $t.type() == \"date\";\ncheck if hex:aa.type() == \"bytes\";\ncheck if bytes($t), $t.type() == \"bytes\";\ncheck if true.type() == \"bool\";\ncheck if bool($t), $t.type() == \"bool\";\ncheck if {false, true}.type() == \"set\";\ncheck if set($t), $t.type() == \"set\";\ncheck if null.type() == \"null\";\ncheck if null($t), $t.type() == \"null\";\ncheck if array($t), $t.type() == \"array\";\ncheck if map($t), $t.type() == \"map\";\n", + "version": 6 + } + ], + "validations": { + "": { + "world": { + "facts": [ + { + "origin": [ + 0 + ], + "facts": [ + "array([1, 2, 3])", + "bool(true)", + "bytes(hex:aa)", + "date(2023-12-28T00:00:00Z)", + "integer(1)", + "map({\"a\": true})", + "null(null)", + "set({false, true})", + "string(\"test\")" + ] + } + ], + "rules": [], + "checks": [ + { + "origin": 0, + "checks": [ + "check if \"test\".type() == \"string\"", + "check if (2023-12-28T00:00:00Z).type() == \"date\"", + "check if 1.type() == \"integer\"", + "check if array($t), $t.type() == \"array\"", + "check if bool($t), $t.type() == \"bool\"", + "check if bytes($t), $t.type() == \"bytes\"", + "check if date($t), $t.type() == \"date\"", + "check if hex:aa.type() == \"bytes\"", + "check if integer($t), $t.type() == \"integer\"", + "check if map($t), $t.type() == \"map\"", + "check if null($t), $t.type() == \"null\"", + "check if null.type() == \"null\"", + "check if set($t), $t.type() == \"set\"", + "check if string($t), $t.type() == \"string\"", + "check if true.type() == \"bool\"", + "check if {false, true}.type() == \"set\"" + ] + } + ], + "policies": [ + "allow if true" + ] + }, + "result": { + "Ok": 0 + }, + "authorizer_code": "allow if true;\n", + "revocation_ids": [ + "e60875c6ef7917c227a5e4b2cabfe250a85fa0598eb3cf7987ded0da2b69a559a1665bd312aeecde78e76aeb28ea1c1a03ec9b7dec8aeb519e7867ef8ff9b402" + ] + } + } + }, + { + "title": "test array and map operations", + "filename": "test034_array_map.bc", + "token": [ + { + "symbols": [ + "a", + "b", + "c", + "p", + "d", + "A", + "kv", + "id", + "roles" + ], + "public_keys": [], + "external_key": null, + "code": "check if [1, 2, 1].length() == 3;\ncheck if [\"a\", \"b\"] != true;\ncheck if [\"a\", \"b\"] != [1, 2, 3];\ncheck if [\"a\", \"b\"] == [\"a\", \"b\"];\ncheck if [\"a\", \"b\"] === [\"a\", \"b\"];\ncheck if [\"a\", \"b\"] !== [\"a\", \"c\"];\ncheck if [\"a\", \"b\", \"c\"].contains(\"c\");\ncheck if [1, 2, 3].starts_with([1, 2]);\ncheck if [4, 5, 6].ends_with([6]);\ncheck if [1, 2, \"a\"].get(2) == \"a\";\ncheck if [1, 2].get(3) == null;\ncheck if [1, 2, 3].all($p -> $p > 0);\ncheck if [1, 2, 3].any($p -> $p > 2);\ncheck if {\"a\": 1, \"b\": 2, \"c\": 3, \"d\": 4}.length() == 4;\ncheck if {1: \"a\", 2: \"b\"} != true;\ncheck if {1: \"a\", 2: \"b\"} != {\"a\": 1, \"b\": 2};\ncheck if {1: \"a\", 2: \"b\"} == {1: \"a\", 2: \"b\"};\ncheck if {1: \"a\", 2: \"b\"} !== {\"a\": 1, \"b\": 2};\ncheck if {1: \"a\", 2: \"b\"} === {1: \"a\", 2: \"b\"};\ncheck if {\"a\": 1, \"b\": 2, \"c\": 3, \"d\": 4}.contains(\"d\");\ncheck if {1: \"A\", \"a\": 1, \"b\": 2}.get(\"a\") == 1;\ncheck if {1: \"A\", \"a\": 1, \"b\": 2}.get(1) == \"A\";\ncheck if {1: \"A\", \"a\": 1, \"b\": 2}.get(\"c\") == null;\ncheck if {1: \"A\", \"a\": 1, \"b\": 2}.get(2) == null;\ncheck if {\"a\": 1, \"b\": 2}.all($kv -> $kv.get(0) != \"c\" && $kv.get(1) < 3);\ncheck if {1: \"A\", \"a\": 1, \"b\": 2}.any($kv -> $kv.get(0) == 1 && $kv.get(1) == \"A\");\ncheck if {\"user\": {\"id\": 1, \"roles\": [\"admin\"]}}.get(\"user\").get(\"roles\").contains(\"admin\");\n", + "version": 6 + } + ], + "validations": { + "": { + "world": { + "facts": [], + "rules": [], + "checks": [ + { + "origin": 0, + "checks": [ + "check if [\"a\", \"b\", \"c\"].contains(\"c\")", + "check if [\"a\", \"b\"] != [1, 2, 3]", + "check if [\"a\", \"b\"] != true", + "check if [\"a\", \"b\"] !== [\"a\", \"c\"]", + "check if [\"a\", \"b\"] == [\"a\", \"b\"]", + "check if [\"a\", \"b\"] === [\"a\", \"b\"]", + "check if [1, 2, \"a\"].get(2) == \"a\"", + "check if [1, 2, 1].length() == 3", + "check if [1, 2, 3].all($p -> $p > 0)", + "check if [1, 2, 3].any($p -> $p > 2)", + "check if [1, 2, 3].starts_with([1, 2])", + "check if [1, 2].get(3) == null", + "check if [4, 5, 6].ends_with([6])", + "check if {\"a\": 1, \"b\": 2, \"c\": 3, \"d\": 4}.contains(\"d\")", + "check if {\"a\": 1, \"b\": 2, \"c\": 3, \"d\": 4}.length() == 4", + "check if {\"a\": 1, \"b\": 2}.all($kv -> $kv.get(0) != \"c\" && $kv.get(1) < 3)", + "check if {\"user\": {\"id\": 1, \"roles\": [\"admin\"]}}.get(\"user\").get(\"roles\").contains(\"admin\")", + "check if {1: \"A\", \"a\": 1, \"b\": 2}.any($kv -> $kv.get(0) == 1 && $kv.get(1) == \"A\")", + "check if {1: \"A\", \"a\": 1, \"b\": 2}.get(\"a\") == 1", + "check if {1: \"A\", \"a\": 1, \"b\": 2}.get(\"c\") == null", + "check if {1: \"A\", \"a\": 1, \"b\": 2}.get(1) == \"A\"", + "check if {1: \"A\", \"a\": 1, \"b\": 2}.get(2) == null", + "check if {1: \"a\", 2: \"b\"} != true", + "check if {1: \"a\", 2: \"b\"} != {\"a\": 1, \"b\": 2}", + "check if {1: \"a\", 2: \"b\"} !== {\"a\": 1, \"b\": 2}", + "check if {1: \"a\", 2: \"b\"} == {1: \"a\", 2: \"b\"}", + "check if {1: \"a\", 2: \"b\"} === {1: \"a\", 2: \"b\"}" + ] + } + ], + "policies": [ + "allow if true" + ] + }, + "result": { + "Ok": 0 + }, + "authorizer_code": "allow if true;\n", + "revocation_ids": [ + "b22238a06ca9c015d3c49d4ebaa7e8ab6e0d69119b3264033618e726d62fc6f4757a7bebc25f255444aba39994554a62a53ecc13b68802efab8da85ace62390d" + ] + } + } + }, + { + "title": "test ffi calls (v6 blocks)", + "filename": "test035_ffi.bc", + "token": [ + { + "symbols": [ + "test", + "a", + "equal strings" + ], + "public_keys": [], + "external_key": null, + "code": "check if true.extern::test(), \"a\".extern::test(\"a\") == \"equal strings\";\n", + "version": 6 + } + ], + "validations": { + "": { + "world": { + "facts": [], + "rules": [], + "checks": [ + { + "origin": 0, + "checks": [ + "check if true.extern::test(), \"a\".extern::test(\"a\") == \"equal strings\"" + ] + } + ], + "policies": [ + "allow if true" + ] + }, + "result": { + "Ok": 0 + }, + "authorizer_code": "allow if true;\n", + "revocation_ids": [ + "d1719fd101c2695d2dac4df67569918363f691b6167670e1dbbf8026f639a7aa1ec2e13707f4d34cadbb2adce5c6e8a816577dd069a8717e0f5cb4ea3cec5b04" + ] + } + } + }, + { + "title": "ECDSA secp256r1 signatures", + "filename": "test036_secp256r1.bc", + "token": [ + { + "symbols": [ + "file1", + "file2" + ], + "public_keys": [], + "external_key": null, + "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\nright(\"file1\", \"write\");\n", + "version": 3 + }, + { + "symbols": [ + "0" + ], + "public_keys": [], + "external_key": null, + "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n", + "version": 3 + } + ], + "validations": { + "": { + "world": { + "facts": [ + { + "origin": [ + null + ], + "facts": [ + "operation(\"read\")", + "resource(\"file1\")" + ] + }, + { + "origin": [ + 0 + ], + "facts": [ + "right(\"file1\", \"read\")", + "right(\"file1\", \"write\")", + "right(\"file2\", \"read\")" + ] + } + ], + "rules": [], + "checks": [ + { + "origin": 1, + "checks": [ + "check if resource($0), operation(\"read\"), right($0, \"read\")" + ] + } + ], + "policies": [ + "allow if true" + ] + }, + "result": { + "Ok": 0 + }, + "authorizer_code": "resource(\"file1\");\noperation(\"read\");\n\nallow if true;\n", + "revocation_ids": [ + "628b9a6d74cc80b3ece50befd1f5f0f025c0a35d51708b2e77c11aed5f968b93b4096c87ed8169605716de934e155443f140334d71708fcc4247e5a0a518b30d", + "3046022100b60674854a12814cc36c8aab9600c1d9f9d3160e2334b72c0feede5a56213ea5022100a4f4bbf2dc33b309267af39fce76612017ddb6171e9cd2a3aa8a853f45f1675f" + ] + } + } + }, + { + "title": "ECDSA secp256r1 signature on third-party block", + "filename": "test037_secp256r1_third_party.bc", + "token": [ + { + "symbols": [ + "file1", + "file2", + "from_third" + ], + "public_keys": [ + "secp256r1/025e918fd4463832aea2823dfd9716a36b4d9b1377bd53dd82ddf4c0bc75ed6bbf" + ], + "external_key": null, + "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\nright(\"file1\", \"write\");\ncheck if from_third(true) trusting secp256r1/025e918fd4463832aea2823dfd9716a36b4d9b1377bd53dd82ddf4c0bc75ed6bbf;\n", + "version": 4 + }, + { + "symbols": [ + "from_third", + "0" + ], + "public_keys": [], + "external_key": "secp256r1/025e918fd4463832aea2823dfd9716a36b4d9b1377bd53dd82ddf4c0bc75ed6bbf", + "code": "from_third(true);\ncheck if resource($0), operation(\"read\"), right($0, \"read\");\n", + "version": 5 + } + ], + "validations": { + "": { + "world": { + "facts": [ + { + "origin": [ + null + ], + "facts": [ + "operation(\"read\")", + "resource(\"file1\")" + ] + }, + { + "origin": [ + 0 + ], + "facts": [ + "right(\"file1\", \"read\")", + "right(\"file1\", \"write\")", + "right(\"file2\", \"read\")" + ] + }, + { + "origin": [ + 1 + ], + "facts": [ + "from_third(true)" + ] + } + ], + "rules": [], + "checks": [ + { + "origin": 0, + "checks": [ + "check if from_third(true) trusting secp256r1/025e918fd4463832aea2823dfd9716a36b4d9b1377bd53dd82ddf4c0bc75ed6bbf" + ] + }, + { + "origin": 1, + "checks": [ + "check if resource($0), operation(\"read\"), right($0, \"read\")" + ] + } + ], + "policies": [ + "allow if true" + ] + }, + "result": { + "Ok": 0 + }, + "authorizer_code": "resource(\"file1\");\noperation(\"read\");\n\nallow if true;\n", + "revocation_ids": [ + "70f5402208516fd44cfc9df3dfcfc0a327ee9004f1801ed0a7abdcbbae923d566ddcd2d4a14f4622b35732c4e538af04075cc67ab0888fa2d8923cc668187f0f", + "30450220793f95665d9af646339503a073670ea2c352459d2a2c2e14c57565f6c7eaf6bc022100cccadfc37e46755f52bb054ed206d7335067885df599a69431db40e33f33d4cf" + ] + } + } } ] } diff --git a/biscuit-auth/samples/test017_expressions.bc b/biscuit-auth/samples/test017_expressions.bc index 1f3234c0..5b28d360 100644 Binary files a/biscuit-auth/samples/test017_expressions.bc and b/biscuit-auth/samples/test017_expressions.bc differ diff --git a/biscuit-auth/samples/test024_third_party.bc b/biscuit-auth/samples/test024_third_party.bc index d2aef528..8b7b8645 100644 Binary files a/biscuit-auth/samples/test024_third_party.bc and b/biscuit-auth/samples/test024_third_party.bc differ diff --git a/biscuit-auth/samples/test026_public_keys_interning.bc b/biscuit-auth/samples/test026_public_keys_interning.bc index 392d649c..89a87c7f 100644 Binary files a/biscuit-auth/samples/test026_public_keys_interning.bc and b/biscuit-auth/samples/test026_public_keys_interning.bc differ diff --git a/biscuit-auth/samples/test027_integer_wraparound.bc b/biscuit-auth/samples/test027_integer_wraparound.bc index 50aa63b9..463a6c53 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/test029_reject_if.bc b/biscuit-auth/samples/test029_reject_if.bc new file mode 100644 index 00000000..86ef918b Binary files /dev/null and b/biscuit-auth/samples/test029_reject_if.bc differ diff --git a/biscuit-auth/samples/test030_null.bc b/biscuit-auth/samples/test030_null.bc new file mode 100644 index 00000000..2868715d Binary files /dev/null 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..2f6cb41f Binary files /dev/null and b/biscuit-auth/samples/test031_heterogeneous_equal.bc differ diff --git a/biscuit-auth/samples/test032_laziness_closures.bc b/biscuit-auth/samples/test032_laziness_closures.bc new file mode 100644 index 00000000..9a98b876 Binary files /dev/null and b/biscuit-auth/samples/test032_laziness_closures.bc differ diff --git a/biscuit-auth/samples/test033_typeof.bc b/biscuit-auth/samples/test033_typeof.bc new file mode 100644 index 00000000..517ae4e7 Binary files /dev/null and b/biscuit-auth/samples/test033_typeof.bc differ diff --git a/biscuit-auth/samples/test034_array_map.bc b/biscuit-auth/samples/test034_array_map.bc new file mode 100644 index 00000000..9deb150a Binary files /dev/null and b/biscuit-auth/samples/test034_array_map.bc differ diff --git a/biscuit-auth/samples/test035_ffi.bc b/biscuit-auth/samples/test035_ffi.bc new file mode 100644 index 00000000..a65c1c4e Binary files /dev/null and b/biscuit-auth/samples/test035_ffi.bc differ diff --git a/biscuit-auth/samples/test036_secp256r1.bc b/biscuit-auth/samples/test036_secp256r1.bc new file mode 100644 index 00000000..8c4c772d Binary files /dev/null and b/biscuit-auth/samples/test036_secp256r1.bc differ diff --git a/biscuit-auth/samples/test037_secp256r1_third_party.bc b/biscuit-auth/samples/test037_secp256r1_third_party.bc new file mode 100644 index 00000000..d40456e7 Binary files /dev/null and b/biscuit-auth/samples/test037_secp256r1_third_party.bc differ diff --git a/biscuit-auth/src/bwk.rs b/biscuit-auth/src/bwk.rs index d9d244da..8e6cfc98 100644 --- a/biscuit-auth/src/bwk.rs +++ b/biscuit-auth/src/bwk.rs @@ -3,6 +3,7 @@ use std::convert::TryFrom; use chrono::{DateTime, FixedOffset}; use serde::{Deserialize, Serialize}; +use crate::builder::Algorithm; use crate::{error, PublicKey}; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] @@ -27,7 +28,7 @@ struct BiscuitWebKeyRepr { impl From for BiscuitWebKeyRepr { fn from(value: BiscuitWebKey) -> Self { BiscuitWebKeyRepr { - algorithm: "ed25519".to_string(), + algorithm: value.public_key.algorithm_string().to_string(), key_bytes: value.public_key.to_bytes_hex(), key_id: value.key_id, issuer: value.issuer, @@ -40,14 +41,8 @@ impl TryFrom for BiscuitWebKey { type Error = error::Format; fn try_from(value: BiscuitWebKeyRepr) -> Result { - if value.algorithm != "ed25519" { - return Err(error::Format::DeserializationError(format!( - "deserialization error: unexpected key algorithm {}", - value.algorithm - ))); - } - - let public_key = PublicKey::from_bytes_hex(&value.key_bytes)?; + let algorithm = Algorithm::try_from(value.algorithm.as_str())?; + let public_key = PublicKey::from_bytes_hex(&value.key_bytes, algorithm)?; Ok(BiscuitWebKey { public_key, @@ -79,7 +74,7 @@ mod tests { let parsed: BiscuitWebKey = serde_json::from_str(&serialized).unwrap(); assert_eq!(parsed, bwk); - let keypair = KeyPair::new(); + let keypair = KeyPair::new_with_algorithm(Algorithm::Secp256r1); let bwk = BiscuitWebKey { public_key: keypair.public(), key_id: 0, @@ -120,7 +115,8 @@ mod tests { .unwrap(), BiscuitWebKey { public_key: PublicKey::from_bytes_hex( - "63c7a8628c14b778a4b66a22e1f53dab4542423295b6fb5a52283da58bcf6d9a" + "63c7a8628c14b778a4b66a22e1f53dab4542423295b6fb5a52283da58bcf6d9a", + Algorithm::Ed25519 ) .unwrap(), key_id: 12, @@ -145,7 +141,8 @@ mod tests { .unwrap(), BiscuitWebKey { public_key: PublicKey::from_bytes_hex( - "63c7a8628c14b778a4b66a22e1f53dab4542423295b6fb5a52283da58bcf6d9a" + "63c7a8628c14b778a4b66a22e1f53dab4542423295b6fb5a52283da58bcf6d9a", + Algorithm::Ed25519 ) .unwrap(), key_id: 12, @@ -166,7 +163,8 @@ mod tests { .unwrap(), BiscuitWebKey { public_key: PublicKey::from_bytes_hex( - "63c7a8628c14b778a4b66a22e1f53dab4542423295b6fb5a52283da58bcf6d9a" + "63c7a8628c14b778a4b66a22e1f53dab4542423295b6fb5a52283da58bcf6d9a", + Algorithm::Ed25519 ) .unwrap(), key_id: 12, @@ -187,7 +185,8 @@ mod tests { .unwrap(), BiscuitWebKey { public_key: PublicKey::from_bytes_hex( - "63c7a8628c14b778a4b66a22e1f53dab4542423295b6fb5a52283da58bcf6d9a" + "63c7a8628c14b778a4b66a22e1f53dab4542423295b6fb5a52283da58bcf6d9a", + Algorithm::Ed25519 ) .unwrap(), key_id: u32::MAX, diff --git a/biscuit-auth/src/crypto/ed25519.rs b/biscuit-auth/src/crypto/ed25519.rs new file mode 100644 index 00000000..c7c8ac12 --- /dev/null +++ b/biscuit-auth/src/crypto/ed25519.rs @@ -0,0 +1,246 @@ +//! cryptographic operations +//! +//! Biscuit tokens are based on a chain of Ed25519 signatures. +//! This provides the fundamental operation for offline delegation: from a message +//! and a valid signature, it is possible to add a new message and produce a valid +//! signature for the whole. +//! +//! The implementation is based on [ed25519_dalek](https://github.com/dalek-cryptography/ed25519-dalek). +#![allow(non_snake_case)] +use crate::{error::Format, format::schema}; + +use super::error; +use super::Signature; +#[cfg(feature = "pem")] +use ed25519_dalek::pkcs8::DecodePrivateKey; +use ed25519_dalek::Signer; +use ed25519_dalek::*; +use rand_core::{CryptoRng, RngCore}; +use std::{convert::TryInto, hash::Hash, ops::Drop}; +use zeroize::Zeroize; + +/// pair of cryptographic keys used to sign a token's block +#[derive(Debug)] +pub struct KeyPair { + pub(super) kp: ed25519_dalek::SigningKey, +} + +impl KeyPair { + pub fn new() -> Self { + Self::new_with_rng(&mut rand::rngs::OsRng) + } + + pub fn new_with_rng(rng: &mut T) -> Self { + let kp = ed25519_dalek::SigningKey::generate(rng); + KeyPair { kp } + } + + pub fn from(key: &PrivateKey) -> Self { + KeyPair { + kp: ed25519_dalek::SigningKey::from_bytes(&key.0), + } + } + + /// deserializes from a byte array + pub fn from_bytes(bytes: &[u8]) -> Result { + let bytes: [u8; 32] = bytes + .try_into() + .map_err(|_| Format::InvalidKeySize(bytes.len()))?; + + Ok(KeyPair { + kp: ed25519_dalek::SigningKey::from_bytes(&bytes), + }) + } + + pub fn sign(&self, data: &[u8]) -> Result { + Ok(Signature( + self.kp + .try_sign(&data) + .map_err(|s| s.to_string()) + .map_err(error::Signature::InvalidSignatureGeneration) + .map_err(error::Format::Signature)? + .to_bytes() + .to_vec(), + )) + } + + pub fn private(&self) -> PrivateKey { + PrivateKey(self.kp.to_bytes()) + } + + pub fn public(&self) -> PublicKey { + PublicKey(self.kp.verifying_key()) + } + + pub fn algorithm(&self) -> crate::format::schema::public_key::Algorithm { + crate::format::schema::public_key::Algorithm::Ed25519 + } + + #[cfg(feature = "pem")] + pub fn from_private_key_der(bytes: &[u8]) -> Result { + let kp = SigningKey::from_pkcs8_der(bytes) + .map_err(|e| error::Format::InvalidKey(e.to_string()))?; + Ok(KeyPair { kp }) + } + + #[cfg(feature = "pem")] + pub fn from_private_key_pem(str: &str) -> Result { + let kp = SigningKey::from_pkcs8_pem(str) + .map_err(|e| error::Format::InvalidKey(e.to_string()))?; + Ok(KeyPair { kp }) + } +} + +impl std::default::Default for KeyPair { + fn default() -> Self { + Self::new() + } +} + +/// the private part of a [KeyPair] +#[derive(Debug)] +pub struct PrivateKey(pub(crate) ed25519_dalek::SecretKey); + +impl PrivateKey { + /// serializes to a byte array + pub fn to_bytes(&self) -> Vec { + self.0.to_vec() + } + + /// serializes to an hex-encoded string + pub fn to_bytes_hex(&self) -> String { + hex::encode(self.0) + } + + /// deserializes from a byte array + pub fn from_bytes(bytes: &[u8]) -> Result { + let bytes: [u8; 32] = bytes + .try_into() + .map_err(|_| Format::InvalidKeySize(bytes.len()))?; + Ok(PrivateKey(bytes)) + } + + /// deserializes from an hex-encoded string + pub fn from_bytes_hex(str: &str) -> Result { + let bytes = hex::decode(str).map_err(|e| error::Format::InvalidKey(e.to_string()))?; + Self::from_bytes(&bytes) + } + + /// returns the matching public key + pub fn public(&self) -> PublicKey { + PublicKey(SigningKey::from_bytes(&self.0).verifying_key()) + } + + pub fn algorithm(&self) -> crate::format::schema::public_key::Algorithm { + crate::format::schema::public_key::Algorithm::Ed25519 + } +} + +impl std::clone::Clone for PrivateKey { + fn clone(&self) -> Self { + PrivateKey::from_bytes(&self.to_bytes()).unwrap() + } +} + +impl Drop for PrivateKey { + fn drop(&mut self) { + self.0.zeroize(); + } +} + +/// the public part of a [KeyPair] +#[derive(Debug, Clone, Copy, Eq)] +pub struct PublicKey(ed25519_dalek::VerifyingKey); + +impl PublicKey { + /// serializes to a byte array + pub fn to_bytes(&self) -> [u8; 32] { + self.0.to_bytes() + } + + /// serializes to an hex-encoded string + pub fn to_bytes_hex(&self) -> String { + hex::encode(self.to_bytes()) + } + + /// deserializes from a byte array + pub fn from_bytes(bytes: &[u8]) -> Result { + let bytes: [u8; 32] = bytes + .try_into() + .map_err(|_| Format::InvalidKeySize(bytes.len()))?; + + ed25519_dalek::VerifyingKey::from_bytes(&bytes) + .map(PublicKey) + .map_err(|s| s.to_string()) + .map_err(Format::InvalidKey) + } + + /// deserializes from an hex-encoded string + pub fn from_bytes_hex(str: &str) -> Result { + let bytes = hex::decode(str).map_err(|e| error::Format::InvalidKey(e.to_string()))?; + Self::from_bytes(&bytes) + } + + pub fn from_proto(key: &schema::PublicKey) -> Result { + if key.algorithm != schema::public_key::Algorithm::Ed25519 as i32 { + return Err(error::Format::DeserializationError(format!( + "deserialization error: unexpected key algorithm {}", + key.algorithm + ))); + } + + PublicKey::from_bytes(&key.key) + } + + pub fn to_proto(&self) -> schema::PublicKey { + schema::PublicKey { + algorithm: schema::public_key::Algorithm::Ed25519 as i32, + key: self.to_bytes().to_vec(), + } + } + + pub fn verify_signature( + &self, + data: &[u8], + signature: &Signature, + ) -> Result<(), error::Format> { + let signature_bytes: [u8; 64] = signature.0.clone().try_into().map_err(|e| { + error::Format::BlockSignatureDeserializationError(format!( + "block signature deserialization error: {:?}", + e + )) + })?; + let sig = ed25519_dalek::Signature::from_bytes(&signature_bytes); + + self.0 + .verify_strict(&data, &sig) + .map_err(|s| s.to_string()) + .map_err(error::Signature::InvalidSignature) + .map_err(error::Format::Signature) + } + + pub fn algorithm(&self) -> crate::format::schema::public_key::Algorithm { + crate::format::schema::public_key::Algorithm::Ed25519 + } + + pub(crate) fn write(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "ed25519/{}", hex::encode(&self.to_bytes())) + } + + pub fn print(&self) -> String { + format!("ed25519/{}", hex::encode(&self.to_bytes())) + } +} + +impl PartialEq for PublicKey { + fn eq(&self, other: &Self) -> bool { + self.0.to_bytes() == other.0.to_bytes() + } +} + +impl Hash for PublicKey { + fn hash(&self, state: &mut H) { + (crate::format::schema::public_key::Algorithm::Ed25519 as i32).hash(state); + self.0.to_bytes().hash(state); + } +} diff --git a/biscuit-auth/src/crypto/mod.rs b/biscuit-auth/src/crypto/mod.rs index da7dd7d8..80ca01a9 100644 --- a/biscuit-auth/src/crypto/mod.rs +++ b/biscuit-auth/src/crypto/mod.rs @@ -7,62 +7,107 @@ //! //! The implementation is based on [ed25519_dalek](https://github.com/dalek-cryptography/ed25519-dalek). #![allow(non_snake_case)] -use crate::{error::Format, format::schema}; +use crate::builder::Algorithm; +use crate::format::schema; +use crate::format::ThirdPartyVerificationMode; use super::error; -#[cfg(feature = "pem")] -use ed25519_dalek::pkcs8::DecodePrivateKey; -use ed25519_dalek::*; +mod ed25519; +mod p256; use nom::Finish; use rand_core::{CryptoRng, RngCore}; -use std::{convert::TryInto, fmt::Display, hash::Hash, ops::Drop, str::FromStr}; -use zeroize::Zeroize; +use std::fmt; +use std::hash::Hash; +use std::str::FromStr; /// pair of cryptographic keys used to sign a token's block #[derive(Debug)] -pub struct KeyPair { - pub(crate) kp: ed25519_dalek::SigningKey, +pub enum KeyPair { + Ed25519(ed25519::KeyPair), + P256(p256::KeyPair), } impl KeyPair { + /// Create a new ed25519 keypair with the default OS RNG pub fn new() -> Self { - Self::new_with_rng(&mut rand::rngs::OsRng) + Self::new_with_rng(Algorithm::Ed25519, &mut rand::rngs::OsRng) } - pub fn new_with_rng(rng: &mut T) -> Self { - let kp = ed25519_dalek::SigningKey::generate(rng); + /// Create a new keypair with a chosen algorithm and the default OS RNG + pub fn new_with_algorithm(algorithm: Algorithm) -> Self { + Self::new_with_rng(algorithm, &mut rand::rngs::OsRng) + } - KeyPair { kp } + pub fn new_with_rng(algorithm: Algorithm, rng: &mut T) -> Self { + match algorithm { + Algorithm::Ed25519 => KeyPair::Ed25519(ed25519::KeyPair::new_with_rng(rng)), + Algorithm::Secp256r1 => KeyPair::P256(p256::KeyPair::new_with_rng(rng)), + } } pub fn from(key: &PrivateKey) -> Self { - KeyPair { - kp: ed25519_dalek::SigningKey::from_bytes(&key.0), + match key { + PrivateKey::Ed25519(key) => KeyPair::Ed25519(ed25519::KeyPair::from(key)), + PrivateKey::P256(key) => KeyPair::P256(p256::KeyPair::from(key)), + } + } + + /// deserializes from a byte array + pub fn from_bytes( + bytes: &[u8], + algorithm: schema::public_key::Algorithm, + ) -> Result { + match algorithm { + schema::public_key::Algorithm::Ed25519 => { + Ok(KeyPair::Ed25519(ed25519::KeyPair::from_bytes(bytes)?)) + } + schema::public_key::Algorithm::Secp256r1 => { + Ok(KeyPair::P256(p256::KeyPair::from_bytes(bytes)?)) + } + } + } + + pub fn sign(&self, data: &[u8]) -> Result { + match self { + KeyPair::Ed25519(key) => key.sign(data), + KeyPair::P256(key) => key.sign(data), } } #[cfg(feature = "pem")] pub fn from_private_key_der(bytes: &[u8]) -> Result { - let kp = SigningKey::from_pkcs8_der(bytes) - .map_err(|e| error::Format::InvalidKey(e.to_string()))?; - Ok(KeyPair { kp }) + ed25519::KeyPair::from_private_key_der(bytes) + .map(KeyPair::Ed25519) + .or_else(|_| p256::KeyPair::from_private_key_der(bytes).map(KeyPair::P256)) } #[cfg(feature = "pem")] pub fn from_private_key_pem(str: &str) -> Result { - let kp = SigningKey::from_pkcs8_pem(str) - .map_err(|e| error::Format::InvalidKey(e.to_string()))?; - Ok(KeyPair { kp }) + ed25519::KeyPair::from_private_key_pem(str) + .map(KeyPair::Ed25519) + .or_else(|_| p256::KeyPair::from_private_key_pem(str).map(KeyPair::P256)) } pub fn private(&self) -> PrivateKey { - let secret = self.kp.to_bytes(); - PrivateKey(secret) + match self { + KeyPair::Ed25519(key) => PrivateKey::Ed25519(key.private()), + KeyPair::P256(key) => PrivateKey::P256(key.private()), + } } pub fn public(&self) -> PublicKey { - PublicKey(self.kp.verifying_key()) + match self { + KeyPair::Ed25519(key) => PublicKey::Ed25519(key.public()), + KeyPair::P256(key) => PublicKey::P256(key.public()), + } + } + + pub fn algorithm(&self) -> crate::format::schema::public_key::Algorithm { + match self { + KeyPair::Ed25519(_) => crate::format::schema::public_key::Algorithm::Ed25519, + KeyPair::P256(_) => crate::format::schema::public_key::Algorithm::Secp256r1, + } } } @@ -73,13 +118,19 @@ impl std::default::Default for KeyPair { } /// the private part of a [KeyPair] -#[derive(Debug)] -pub struct PrivateKey(pub(crate) ed25519_dalek::SecretKey); +#[derive(Debug, Clone)] +pub enum PrivateKey { + Ed25519(ed25519::PrivateKey), + P256(p256::PrivateKey), +} impl PrivateKey { /// serializes to a byte array - pub fn to_bytes(&self) -> [u8; 32] { - self.0 + pub fn to_bytes(&self) -> zeroize::Zeroizing> { + match self { + PrivateKey::Ed25519(key) => zeroize::Zeroizing::new(key.to_bytes()), + PrivateKey::P256(key) => key.to_bytes(), + } } /// serializes to an hex-encoded string @@ -88,45 +139,49 @@ impl PrivateKey { } /// deserializes from a byte array - pub fn from_bytes(bytes: &[u8]) -> Result { - let bytes: [u8; 32] = bytes - .try_into() - .map_err(|_| Format::InvalidKeySize(bytes.len()))?; - Ok(PrivateKey(bytes)) + pub fn from_bytes(bytes: &[u8], algorithm: Algorithm) -> Result { + match algorithm { + Algorithm::Ed25519 => Ok(PrivateKey::Ed25519(ed25519::PrivateKey::from_bytes(bytes)?)), + Algorithm::Secp256r1 => Ok(PrivateKey::P256(p256::PrivateKey::from_bytes(bytes)?)), + } } /// deserializes from an hex-encoded string - pub fn from_bytes_hex(str: &str) -> Result { + pub fn from_bytes_hex(str: &str, algorithm: Algorithm) -> Result { let bytes = hex::decode(str).map_err(|e| error::Format::InvalidKey(e.to_string()))?; - Self::from_bytes(&bytes) + Self::from_bytes(&bytes, algorithm) } /// returns the matching public key pub fn public(&self) -> PublicKey { - PublicKey(SigningKey::from_bytes(&self.0).verifying_key()) - } -} - -impl std::clone::Clone for PrivateKey { - fn clone(&self) -> Self { - PrivateKey::from_bytes(&self.to_bytes()).unwrap() + match self { + PrivateKey::Ed25519(key) => PublicKey::Ed25519(key.public()), + PrivateKey::P256(key) => PublicKey::P256(key.public()), + } } -} -impl Drop for PrivateKey { - fn drop(&mut self) { - self.0.zeroize(); + pub fn algorithm(&self) -> crate::format::schema::public_key::Algorithm { + match self { + PrivateKey::Ed25519(_) => crate::format::schema::public_key::Algorithm::Ed25519, + PrivateKey::P256(_) => crate::format::schema::public_key::Algorithm::Secp256r1, + } } } /// the public part of a [KeyPair] -#[derive(Debug, Clone, Copy, Eq)] -pub struct PublicKey(pub(crate) ed25519_dalek::VerifyingKey); +#[derive(Debug, Clone, Copy, PartialEq, Hash, Eq)] +pub enum PublicKey { + Ed25519(ed25519::PublicKey), + P256(p256::PublicKey), +} impl PublicKey { /// serializes to a byte array - pub fn to_bytes(&self) -> [u8; 32] { - self.0.to_bytes() + pub fn to_bytes(&self) -> Vec { + match self { + PublicKey::Ed25519(key) => key.to_bytes().into(), + PublicKey::P256(key) => key.to_bytes(), + } } /// serializes to an hex-encoded string @@ -135,56 +190,101 @@ impl PublicKey { } /// deserializes from a byte array - pub fn from_bytes(bytes: &[u8]) -> Result { - let bytes: [u8; 32] = bytes - .try_into() - .map_err(|_| Format::InvalidKeySize(bytes.len()))?; - - ed25519_dalek::VerifyingKey::from_bytes(&bytes) - .map(PublicKey) - .map_err(|s| s.to_string()) - .map_err(Format::InvalidKey) + pub fn from_bytes(bytes: &[u8], algorithm: Algorithm) -> Result { + match algorithm { + Algorithm::Ed25519 => Ok(PublicKey::Ed25519(ed25519::PublicKey::from_bytes(bytes)?)), + Algorithm::Secp256r1 => Ok(PublicKey::P256(p256::PublicKey::from_bytes(bytes)?)), + } } /// deserializes from an hex-encoded string - pub fn from_bytes_hex(str: &str) -> Result { + pub fn from_bytes_hex(str: &str, algorithm: Algorithm) -> Result { let bytes = hex::decode(str).map_err(|e| error::Format::InvalidKey(e.to_string()))?; - Self::from_bytes(&bytes) + Self::from_bytes(&bytes, algorithm) } pub fn from_proto(key: &schema::PublicKey) -> Result { - if key.algorithm != schema::public_key::Algorithm::Ed25519 as i32 { - return Err(error::Format::DeserializationError(format!( + if key.algorithm == schema::public_key::Algorithm::Ed25519 as i32 { + Ok(PublicKey::Ed25519(ed25519::PublicKey::from_bytes( + &key.key, + )?)) + } else if key.algorithm == schema::public_key::Algorithm::Secp256r1 as i32 { + Ok(PublicKey::P256(p256::PublicKey::from_bytes(&key.key)?)) + } else { + Err(error::Format::DeserializationError(format!( "deserialization error: unexpected key algorithm {}", key.algorithm - ))); + ))) } - - PublicKey::from_bytes(&key.key) } pub fn to_proto(&self) -> schema::PublicKey { schema::PublicKey { - algorithm: schema::public_key::Algorithm::Ed25519 as i32, - key: self.to_bytes().to_vec(), + algorithm: self.algorithm() as i32, + key: self.to_bytes(), + } + } + + pub fn verify_signature( + &self, + data: &[u8], + signature: &Signature, + ) -> Result<(), error::Format> { + match self { + PublicKey::Ed25519(key) => key.verify_signature(data, signature), + PublicKey::P256(key) => key.verify_signature(data, signature), + } + } + + pub fn algorithm(&self) -> crate::format::schema::public_key::Algorithm { + match self { + PublicKey::Ed25519(_) => crate::format::schema::public_key::Algorithm::Ed25519, + PublicKey::P256(_) => crate::format::schema::public_key::Algorithm::Secp256r1, + } + } + + pub fn algorithm_string(&self) -> &str { + match self { + PublicKey::Ed25519(_) => "ed25519", + PublicKey::P256(_) => "secp256r1", + } + } + + pub(crate) fn write(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + PublicKey::Ed25519(key) => key.write(f), + PublicKey::P256(key) => key.write(f), } } pub fn print(&self) -> String { - self.to_string() + match self { + PublicKey::Ed25519(key) => key.print(), + PublicKey::P256(key) => key.print(), + } } } -impl PartialEq for PublicKey { - fn eq(&self, other: &Self) -> bool { - self.0.to_bytes() == other.0.to_bytes() +impl fmt::Display for PublicKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.write(f) } } -impl Hash for PublicKey { - fn hash(&self, state: &mut H) { - (crate::format::schema::public_key::Algorithm::Ed25519 as i32).hash(state); - self.0.to_bytes().hash(state); +#[derive(Clone, Debug)] +pub struct Signature(pub(crate) Vec); + +impl Signature { + pub fn from_bytes(data: &[u8]) -> Result { + Ok(Signature(data.to_owned())) + } + + pub(crate) fn from_vec(data: Vec) -> Self { + Signature(data) + } + + pub fn to_bytes(&self) -> &[u8] { + &self.0[..] } } @@ -192,16 +292,16 @@ impl FromStr for PublicKey { type Err = error::Token; fn from_str(s: &str) -> Result { - let (_, bytes) = biscuit_parser::parser::public_key(s) + let (_, public_key) = biscuit_parser::parser::public_key(s) .finish() .map_err(biscuit_parser::error::LanguageError::from)?; - Ok(PublicKey::from_bytes(&bytes)?) - } -} - -impl Display for PublicKey { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "ed25519/{}", hex::encode(self.to_bytes())) + Ok(PublicKey::from_bytes( + &public_key.key, + match public_key.algorithm { + biscuit_parser::builder::Algorithm::Ed25519 => Algorithm::Ed25519, + biscuit_parser::builder::Algorithm::Secp256r1 => Algorithm::Secp256r1, + }, + )?) } } @@ -209,287 +309,286 @@ impl Display for PublicKey { pub struct Block { pub(crate) data: Vec, pub(crate) next_key: PublicKey, - pub signature: ed25519_dalek::Signature, + pub signature: Signature, pub external_signature: Option, + pub version: u32, } #[derive(Clone, Debug)] pub struct ExternalSignature { pub(crate) public_key: PublicKey, - pub(crate) signature: ed25519_dalek::Signature, -} - -#[derive(Clone, Debug)] -pub struct Token { - pub root: PublicKey, - pub blocks: Vec, - pub next: TokenNext, + pub(crate) signature: Signature, } #[derive(Clone, Debug)] pub enum TokenNext { Secret(PrivateKey), - Seal(ed25519_dalek::Signature), + Seal(Signature), } -pub fn sign( +pub fn sign_authority_block( keypair: &KeyPair, next_key: &KeyPair, message: &[u8], + version: u32, ) -> Result { - //FIXME: replace with SHA512 hashing - let mut to_sign = message.to_vec(); - to_sign.extend(&(crate::format::schema::public_key::Algorithm::Ed25519 as i32).to_le_bytes()); - to_sign.extend(&next_key.public().to_bytes()); - - let signature = keypair - .kp - .try_sign(&to_sign) - .map_err(|s| s.to_string()) - .map_err(error::Signature::InvalidSignatureGeneration) - .map_err(error::Format::Signature)?; - - Ok(signature) -} - -pub fn verify_block_signature(block: &Block, public_key: &PublicKey) -> Result<(), error::Format> { - //FIXME: replace with SHA512 hashing - let mut to_verify = block.data.to_vec(); - - if let Some(signature) = block.external_signature.as_ref() { - to_verify.extend_from_slice(&signature.signature.to_bytes()); - } - to_verify.extend(&(crate::format::schema::public_key::Algorithm::Ed25519 as i32).to_le_bytes()); - to_verify.extend(&block.next_key.to_bytes()); - - public_key - .0 - .verify_strict(&to_verify, &block.signature) - .map_err(|s| s.to_string()) - .map_err(error::Signature::InvalidSignature) - .map_err(error::Format::Signature)?; - - if let Some(external_signature) = block.external_signature.as_ref() { - let mut to_verify = block.data.to_vec(); - to_verify - .extend(&(crate::format::schema::public_key::Algorithm::Ed25519 as i32).to_le_bytes()); - to_verify.extend(&public_key.to_bytes()); + let to_sign = match version { + 0 => generate_authority_block_signature_payload_v0(&message, &next_key.public()), + 1 => generate_authority_block_signature_payload_v1(&message, &next_key.public(), version), + _ => { + return Err(error::Format::DeserializationError(format!( + "unsupported block version: {}", + version + )) + .into()) + } + }; - external_signature - .public_key - .0 - .verify_strict(&to_verify, &external_signature.signature) - .map_err(|s| s.to_string()) - .map_err(error::Signature::InvalidSignature) - .map_err(error::Format::Signature)?; - } + let signature = keypair.sign(&to_sign)?; - Ok(()) + Ok(Signature(signature.to_bytes().to_vec())) } -impl Token { - #[allow(dead_code)] - pub fn new( - keypair: &KeyPair, - next_key: &KeyPair, - message: &[u8], - ) -> Result { - let signature = sign(keypair, next_key, message)?; - - let block = Block { - data: message.to_vec(), - next_key: next_key.public(), - signature, - external_signature: None, - }; - - Ok(Token { - root: keypair.public(), - blocks: vec![block], - next: TokenNext::Secret(next_key.private()), - }) - } - - #[allow(dead_code)] - pub fn append( - &self, - next_key: &KeyPair, - message: &[u8], - external_signature: Option, - ) -> Result { - let keypair = match self.next.keypair() { - Err(error::Token::AlreadySealed) => Err(error::Token::AppendOnSealed), - other => other, - }?; - - let signature = sign(&keypair, next_key, message)?; - - let block = Block { - data: message.to_vec(), - next_key: next_key.public(), - signature, +pub fn sign_block( + keypair: &KeyPair, + next_key: &KeyPair, + message: &[u8], + external_signature: Option<&ExternalSignature>, + previous_signature: &Signature, + version: u32, +) -> Result { + let to_sign = match version { + 0 => generate_block_signature_payload_v0(&message, &next_key.public(), external_signature), + 1 => generate_block_signature_payload_v1( + &message, + &next_key.public(), external_signature, - }; - - let mut t = Token { - root: self.root, - blocks: self.blocks.clone(), - next: TokenNext::Secret(next_key.private()), - }; + previous_signature, + version, + ), + _ => { + return Err(error::Format::DeserializationError(format!( + "unsupported block version: {}", + version + )) + .into()) + } + }; - t.blocks.push(block); + Ok(keypair.sign(&to_sign)?) +} - Ok(t) - } +pub fn verify_authority_block_signature( + block: &Block, + public_key: &PublicKey, +) -> Result<(), error::Format> { + let to_verify = match block.version { + 0 => generate_block_signature_payload_v0( + &block.data, + &block.next_key, + block.external_signature.as_ref(), + ), + 1 => generate_authority_block_signature_payload_v1( + &block.data, + &block.next_key, + block.version, + ), + _ => { + return Err(error::Format::DeserializationError(format!( + "unsupported block version: {}", + block.version + ))) + } + }; - #[allow(dead_code)] - pub fn verify(&self, root: PublicKey) -> Result<(), error::Token> { - //FIXME: try batched signature verification - let mut current_pub = root; + public_key.verify_signature(&to_verify, &block.signature) +} - for block in &self.blocks { - verify_block_signature(block, ¤t_pub)?; - current_pub = block.next_key; +pub fn verify_block_signature( + block: &Block, + public_key: &PublicKey, + previous_signature: &Signature, + verification_mode: ThirdPartyVerificationMode, +) -> Result<(), error::Format> { + let to_verify = match block.version { + 0 => generate_block_signature_payload_v0( + &block.data, + &block.next_key, + block.external_signature.as_ref(), + ), + 1 => generate_block_signature_payload_v1( + &block.data, + &block.next_key, + block.external_signature.as_ref(), + previous_signature, + block.version, + ), + _ => { + return Err(error::Format::DeserializationError(format!( + "unsupported block version: {}", + block.version + ))) } + }; - match &self.next { - TokenNext::Secret(private) => { - if current_pub != private.public() { - return Err(error::Format::Signature(error::Signature::InvalidSignature( - "the last public key does not match the private key".to_string(), - )) - .into()); - } - } - TokenNext::Seal(signature) => { - //FIXME: replace with SHA512 hashing - let mut to_verify = Vec::new(); - for block in &self.blocks { - to_verify.extend(&block.data); - to_verify.extend(&block.next_key.to_bytes()); - } - - current_pub - .0 - .verify_strict(&to_verify, signature) - .map_err(|s| s.to_string()) - .map_err(error::Signature::InvalidSignature) - .map_err(error::Format::Signature)?; - } - } + public_key.verify_signature(&to_verify, &block.signature)?; - Ok(()) + if let Some(external_signature) = block.external_signature.as_ref() { + verify_external_signature( + &block.data, + public_key, + previous_signature, + external_signature, + block.version, + verification_mode, + )?; } + + Ok(()) } -impl TokenNext { - pub fn keypair(&self) -> Result { - match &self { - TokenNext::Seal(_) => Err(error::Token::AlreadySealed), - TokenNext::Secret(private) => Ok(KeyPair::from(private)), +pub fn verify_external_signature( + payload: &[u8], + public_key: &PublicKey, + previous_signature: &Signature, + external_signature: &ExternalSignature, + version: u32, + verification_mode: ThirdPartyVerificationMode, +) -> Result<(), error::Format> { + let to_verify = match verification_mode { + ThirdPartyVerificationMode::UnsafeLegacy => { + generate_external_signature_payload_v0(payload, public_key) } - } - - pub fn is_sealed(&self) -> bool { - match &self { - TokenNext::Seal(_) => true, - TokenNext::Secret(_) => false, + ThirdPartyVerificationMode::PreviousSignatureHashing => { + generate_external_signature_payload_v1(payload, previous_signature.to_bytes(), version) } - } -} + }; -#[cfg(test)] -mod tests { - /* - use super::*; - use rand::prelude::*; - use rand_core::SeedableRng; - - #[test] - fn basic_signature() { - let mut rng: StdRng = SeedableRng::seed_from_u64(0); + external_signature + .public_key + .verify_signature(&to_verify, &external_signature.signature) +} - let message = b"hello world"; - let keypair = KeyPair::new_with_rng(&mut rng); +pub(crate) fn generate_authority_block_signature_payload_v0( + payload: &[u8], + next_key: &PublicKey, +) -> Vec { + let mut to_verify = payload.to_vec(); - let signature = keypair.sign(&mut rng, message); + to_verify.extend(&(next_key.algorithm() as i32).to_le_bytes()); + to_verify.extend(next_key.to_bytes()); + to_verify +} - assert!(verify(&keypair.public, message, &signature)); +pub(crate) fn generate_block_signature_payload_v0( + payload: &[u8], + next_key: &PublicKey, + external_signature: Option<&ExternalSignature>, +) -> Vec { + let mut to_verify = payload.to_vec(); - assert!(!verify(&keypair.public, b"AAAA", &signature)); + if let Some(signature) = external_signature.as_ref() { + to_verify.extend_from_slice(&signature.signature.to_bytes()); } + to_verify.extend(&(next_key.algorithm() as i32).to_le_bytes()); + to_verify.extend(next_key.to_bytes()); + to_verify +} - #[test] - fn three_messages() { - //let mut rng: OsRng = OsRng::new().unwrap(); - //keep the same values in tests - let mut rng: StdRng = SeedableRng::seed_from_u64(0); - - let message1 = b"hello"; - let keypair1 = KeyPair::new_with_rng(&mut rng); +pub(crate) fn generate_authority_block_signature_payload_v1( + payload: &[u8], + next_key: &PublicKey, + version: u32, +) -> Vec { + let mut to_verify = b"\0BLOCK\0\0VERSION\0".to_vec(); + to_verify.extend(version.to_le_bytes()); - let token1 = Token::new(&mut rng, &keypair1, &message1[..]); + to_verify.extend(b"\0PAYLOAD\0".to_vec()); + to_verify.extend(payload.to_vec()); - assert_eq!(token1.verify(), Ok(()), "cannot verify first token"); + to_verify.extend(b"\0ALGORITHM\0".to_vec()); + to_verify.extend(&(next_key.algorithm() as i32).to_le_bytes()); - println!("will derive a second token"); + to_verify.extend(b"\0NEXTKEY\0".to_vec()); + to_verify.extend(&next_key.to_bytes()); - let message2 = b"world"; - let keypair2 = KeyPair::new_with_rng(&mut rng); + to_verify +} - let token2 = token1.append(&mut rng, &keypair2, &message2[..]); +pub(crate) fn generate_block_signature_payload_v1( + payload: &[u8], + next_key: &PublicKey, + external_signature: Option<&ExternalSignature>, + previous_signature: &Signature, + version: u32, +) -> Vec { + let mut to_verify = b"\0BLOCK\0\0VERSION\0".to_vec(); + to_verify.extend(version.to_le_bytes()); - assert_eq!(token2.verify(), Ok(()), "cannot verify second token"); + to_verify.extend(b"\0PAYLOAD\0".to_vec()); + to_verify.extend(payload.to_vec()); - println!("will derive a third token"); + to_verify.extend(b"\0ALGORITHM\0".to_vec()); + to_verify.extend(&(next_key.algorithm() as i32).to_le_bytes()); - let message3 = b"!!!"; - let keypair3 = KeyPair::new_with_rng(&mut rng); + to_verify.extend(b"\0NEXTKEY\0".to_vec()); + to_verify.extend(&next_key.to_bytes()); - let token3 = token2.append(&mut rng, &keypair3, &message3[..]); + to_verify.extend(b"\0PREVSIG\0".to_vec()); + to_verify.extend(previous_signature.to_bytes()); - assert_eq!(token3.verify(), Ok(()), "cannot verify third token"); + if let Some(signature) = external_signature.as_ref() { + to_verify.extend(b"\0EXTERNALSIG\0".to_vec()); + to_verify.extend_from_slice(&signature.signature.to_bytes()); } - #[test] - fn change_message() { - //let mut rng: OsRng = OsRng::new().unwrap(); - //keep the same values in tests - let mut rng: StdRng = SeedableRng::seed_from_u64(0); - - let message1 = b"hello"; - let keypair1 = KeyPair::new_with_rng(&mut rng); - - let token1 = Token::new(&mut rng, &keypair1, &message1[..]); - - assert_eq!(token1.verify(), Ok(()), "cannot verify first token"); - - println!("will derive a second token"); + to_verify +} - let message2 = b"world"; - let keypair2 = KeyPair::new_with_rng(&mut rng); +fn generate_external_signature_payload_v0(payload: &[u8], previous_key: &PublicKey) -> Vec { + let mut to_verify = payload.to_vec(); + to_verify.extend(&(previous_key.algorithm() as i32).to_le_bytes()); + to_verify.extend(&previous_key.to_bytes()); - let mut token2 = token1.append(&mut rng, &keypair2, &message2[..]); + to_verify +} - token2.messages[1] = Vec::from(&b"you"[..]); +pub(crate) fn generate_external_signature_payload_v1( + payload: &[u8], + previous_signature: &[u8], + version: u32, +) -> Vec { + let mut to_verify = b"\0EXTERNAL\0\0VERSION\0".to_vec(); + to_verify.extend(version.to_le_bytes()); - assert_eq!( - token2.verify(), - Err(error::Signature::InvalidSignature), - "second token should not be valid" - ); + to_verify.extend(b"\0PAYLOAD\0".to_vec()); + to_verify.extend(payload.to_vec()); - println!("will derive a third token"); + to_verify.extend(b"\0PREVSIG\0".to_vec()); + to_verify.extend(previous_signature); + to_verify +} - let message3 = b"!!!"; - let keypair3 = KeyPair::new_with_rng(&mut rng); +pub(crate) fn generate_seal_signature_payload_v0(block: &Block) -> Vec { + let mut to_verify = block.data.to_vec(); + to_verify.extend(&(block.next_key.algorithm() as i32).to_le_bytes()); + to_verify.extend(&block.next_key.to_bytes()); + to_verify.extend(block.signature.to_bytes()); + to_verify +} - let token3 = token2.append(&mut rng, &keypair3, &message3[..]); +impl TokenNext { + pub fn keypair(&self) -> Result { + match &self { + TokenNext::Seal(_) => Err(error::Token::AlreadySealed), + TokenNext::Secret(private) => Ok(KeyPair::from(private)), + } + } - assert_eq!( - token3.verify(), - Err(error::Signature::InvalidSignature), - "cannot verify third token" - ); - }*/ + pub fn is_sealed(&self) -> bool { + match &self { + TokenNext::Seal(_) => true, + TokenNext::Secret(_) => false, + } + } } diff --git a/biscuit-auth/src/crypto/p256.rs b/biscuit-auth/src/crypto/p256.rs new file mode 100644 index 00000000..63a9506b --- /dev/null +++ b/biscuit-auth/src/crypto/p256.rs @@ -0,0 +1,270 @@ +#![allow(non_snake_case)] +use crate::{error::Format, format::schema}; + +use super::error; +use super::Signature; + +use p256::ecdsa::{signature::Signer, signature::Verifier, SigningKey, VerifyingKey}; +use p256::elliptic_curve::rand_core::{CryptoRng, OsRng, RngCore}; +use p256::NistP256; +use std::hash::Hash; + +/// pair of cryptographic keys used to sign a token's block +#[derive(Debug)] +pub struct KeyPair { + kp: SigningKey, +} + +impl KeyPair { + pub fn new() -> Self { + Self::new_with_rng(&mut OsRng) + } + + pub fn new_with_rng(rng: &mut T) -> Self { + let kp = SigningKey::random(rng); + + KeyPair { kp } + } + + pub fn from(key: &PrivateKey) -> Self { + KeyPair { kp: key.0.clone() } + } + + /// deserializes from a big endian byte array + pub fn from_bytes(bytes: &[u8]) -> Result { + // the version of generic-array used by p256 panics if the input length + // is incorrect (including when using `.try_into()`) + if bytes.len() != 32 { + return Err(Format::InvalidKeySize(bytes.len())); + } + let kp = SigningKey::from_bytes(bytes.into()) + .map_err(|s| s.to_string()) + .map_err(Format::InvalidKey)?; + + Ok(KeyPair { kp }) + } + + pub fn sign(&self, data: &[u8]) -> Result { + let signature: ecdsa::Signature = self + .kp + .try_sign(&data) + .map_err(|s| s.to_string()) + .map_err(error::Signature::InvalidSignatureGeneration) + .map_err(error::Format::Signature)?; + Ok(Signature(signature.to_der().as_bytes().to_owned())) + } + + pub fn private(&self) -> PrivateKey { + PrivateKey(self.kp.clone()) + } + + pub fn public(&self) -> PublicKey { + PublicKey(*self.kp.verifying_key()) + } + + pub fn algorithm(&self) -> crate::format::schema::public_key::Algorithm { + crate::format::schema::public_key::Algorithm::Secp256r1 + } + + #[cfg(feature = "pem")] + pub fn from_private_key_der(bytes: &[u8]) -> Result { + let kp = SigningKey::from_pkcs8_der(bytes) + .map_err(|e| error::Format::InvalidKey(e.to_string()))?; + Ok(KeyPair { kp }) + } + + #[cfg(feature = "pem")] + pub fn from_private_key_pem(str: &str) -> Result { + let kp = SigningKey::from_pkcs8_pem(str) + .map_err(|e| error::Format::InvalidKey(e.to_string()))?; + Ok(KeyPair { kp }) + } +} + +impl std::default::Default for KeyPair { + fn default() -> Self { + Self::new() + } +} + +/// the private part of a [KeyPair] +#[derive(Debug)] +pub struct PrivateKey(SigningKey); + +impl PrivateKey { + /// serializes to a big endian byte array + pub fn to_bytes(&self) -> zeroize::Zeroizing> { + let field_bytes = self.0.to_bytes(); + zeroize::Zeroizing::new(field_bytes.to_vec()) + } + + /// serializes to an hex-encoded string + pub fn to_bytes_hex(&self) -> String { + hex::encode(self.to_bytes()) + } + + /// deserializes from a big endian byte array + pub fn from_bytes(bytes: &[u8]) -> Result { + // the version of generic-array used by p256 panics if the input length + // is incorrect (including when using `.try_into()`) + if bytes.len() != 32 { + return Err(Format::InvalidKeySize(bytes.len())); + } + SigningKey::from_bytes(bytes.into()) + .map(PrivateKey) + .map_err(|s| s.to_string()) + .map_err(Format::InvalidKey) + } + + /// deserializes from an hex-encoded string + pub fn from_bytes_hex(str: &str) -> Result { + let bytes = hex::decode(str).map_err(|e| error::Format::InvalidKey(e.to_string()))?; + Self::from_bytes(&bytes) + } + + /// returns the matching public key + pub fn public(&self) -> PublicKey { + PublicKey(*(&self.0).verifying_key()) + } + + pub fn algorithm(&self) -> crate::format::schema::public_key::Algorithm { + crate::format::schema::public_key::Algorithm::Ed25519 + } +} + +impl std::clone::Clone for PrivateKey { + fn clone(&self) -> Self { + PrivateKey::from_bytes(&self.to_bytes()).unwrap() + } +} + +/// the public part of a [KeyPair] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct PublicKey(VerifyingKey); + +impl PublicKey { + /// serializes to a byte array + pub fn to_bytes(&self) -> Vec { + self.0.to_encoded_point(true).to_bytes().into() + } + + /// serializes to an hex-encoded string + pub fn to_bytes_hex(&self) -> String { + hex::encode(self.to_bytes()) + } + + /// deserializes from a byte array + pub fn from_bytes(bytes: &[u8]) -> Result { + let k = VerifyingKey::from_sec1_bytes(bytes) + .map_err(|s| s.to_string()) + .map_err(Format::InvalidKey)?; + + Ok(Self(k.into())) + } + + /// deserializes from an hex-encoded string + pub fn from_bytes_hex(str: &str) -> Result { + let bytes = hex::decode(str).map_err(|e| error::Format::InvalidKey(e.to_string()))?; + Self::from_bytes(&bytes) + } + + pub fn from_proto(key: &schema::PublicKey) -> Result { + if key.algorithm != schema::public_key::Algorithm::Ed25519 as i32 { + return Err(error::Format::DeserializationError(format!( + "deserialization error: unexpected key algorithm {}", + key.algorithm + ))); + } + + PublicKey::from_bytes(&key.key) + } + + pub fn to_proto(&self) -> schema::PublicKey { + schema::PublicKey { + algorithm: schema::public_key::Algorithm::Ed25519 as i32, + key: self.to_bytes().to_vec(), + } + } + + pub fn verify_signature( + &self, + data: &[u8], + signature: &Signature, + ) -> Result<(), error::Format> { + let sig = p256::ecdsa::Signature::from_der(&signature.0).map_err(|e| { + error::Format::BlockSignatureDeserializationError(format!( + "block signature deserialization error: {:?}", + e + )) + })?; + + self.0 + .verify(&data, &sig) + .map_err(|s| s.to_string()) + .map_err(error::Signature::InvalidSignature) + .map_err(error::Format::Signature) + } + + pub fn algorithm(&self) -> crate::format::schema::public_key::Algorithm { + crate::format::schema::public_key::Algorithm::Ed25519 + } + + pub(crate) fn write(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "secp256r1/{}", hex::encode(&self.to_bytes())) + } + pub fn print(&self) -> String { + format!("secp256r1/{}", hex::encode(&self.to_bytes())) + } +} + +impl Hash for PublicKey { + fn hash(&self, state: &mut H) { + (crate::format::schema::public_key::Algorithm::Ed25519 as i32).hash(state); + self.to_bytes().hash(state); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn serialization() { + let kp = KeyPair::new(); + let private = kp.private(); + let public = kp.public(); + let private_hex = private.to_bytes_hex(); + let public_hex = public.to_bytes_hex(); + + println!("private: {private_hex}"); + println!("public: {public_hex}"); + + let message = "hello world"; + let signature = kp.sign(message.as_bytes()).unwrap(); + println!("signature: {}", hex::encode(&signature.0)); + + let deserialized_priv = PrivateKey::from_bytes_hex(&private_hex).unwrap(); + let deserialized_pub = PublicKey::from_bytes_hex(&public_hex).unwrap(); + + assert_eq!(private.0.to_bytes(), deserialized_priv.0.to_bytes()); + assert_eq!(public, deserialized_pub); + + deserialized_pub + .verify_signature(message.as_bytes(), &signature) + .unwrap(); + //panic!(); + } + + #[test] + fn invalid_sizes() { + assert_eq!( + PrivateKey::from_bytes(&[0xaa]).unwrap_err(), + error::Format::InvalidKeySize(1) + ); + assert_eq!( + KeyPair::from_bytes(&[0xaa]).unwrap_err(), + error::Format::InvalidKeySize(1) + ); + PublicKey::from_bytes(&[0xaa]).unwrap_err(); + } +} diff --git a/biscuit-auth/src/datalog/expression.rs b/biscuit-auth/src/datalog/expression.rs index 8a5dd13a..ecb47a28 100644 --- a/biscuit-auth/src/datalog/expression.rs +++ b/biscuit-auth/src/datalog/expression.rs @@ -1,9 +1,55 @@ -use crate::error; +use crate::{builder, error}; -use super::Term; +use super::{MapKey, SymbolIndex, Term}; use super::{SymbolTable, TemporarySymbolTable}; use regex::Regex; -use std::collections::HashMap; +use std::sync::Arc; +use std::{ + collections::{HashMap, HashSet}, + convert::TryFrom, +}; + +#[derive(Clone)] +pub struct ExternFunc( + pub Arc< + dyn Fn(builder::Term, Option) -> Result + Send + Sync, + >, +); + +impl std::fmt::Debug for ExternFunc { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "") + } +} + +impl ExternFunc { + pub fn new( + f: Arc< + dyn Fn(builder::Term, Option) -> Result + + Send + + Sync, + >, + ) -> Self { + Self(f) + } + + pub fn call( + &self, + symbols: &mut TemporarySymbolTable, + name: &str, + left: Term, + right: Option, + ) -> Result { + let left = builder::Term::from_datalog(left, symbols)?; + let right = right + .map(|right| builder::Term::from_datalog(right, symbols)) + .transpose()?; + match self.0(left, right) { + Ok(t) => Ok(t.to_datalog(symbols)), + Err(e) => Err(error::Expression::ExternEvalError(name.to_string(), e)), + } + } +} #[derive(Debug, Clone, PartialEq, Hash, Eq)] pub struct Expression { @@ -15,6 +61,7 @@ pub enum Op { Value(Term), Unary(Unary), Binary(Binary), + Closure(Vec, Vec), } /// Unary operation code @@ -23,13 +70,16 @@ pub enum Unary { Negate, Parens, Length, + TypeOf, + Ffi(SymbolIndex), } impl Unary { fn evaluate( &self, value: Term, - symbols: &TemporarySymbolTable, + symbols: &mut TemporarySymbolTable, + extern_funcs: &HashMap, ) -> Result { match (self, value) { (Unary::Negate, Term::Bool(b)) => Ok(Term::Bool(!b)), @@ -40,6 +90,34 @@ impl Unary { .ok_or(error::Expression::UnknownSymbol(i)), (Unary::Length, Term::Bytes(s)) => Ok(Term::Integer(s.len() as i64)), (Unary::Length, Term::Set(s)) => Ok(Term::Integer(s.len() as i64)), + (Unary::Length, Term::Array(a)) => Ok(Term::Integer(a.len() as i64)), + (Unary::Length, Term::Map(m)) => Ok(Term::Integer(m.len() as i64)), + (Unary::TypeOf, t) => { + let type_string = match t { + Term::Variable(_) => return Err(error::Expression::InvalidType), + Term::Integer(_) => "integer", + Term::Str(_) => "string", + Term::Date(_) => "date", + Term::Bytes(_) => "bytes", + Term::Bool(_) => "bool", + Term::Set(_) => "set", + Term::Null => "null", + Term::Array(_) => "array", + Term::Map(_) => "map", + }; + let sym = symbols.insert(type_string); + Ok(Term::Str(sym)) + } + (Unary::Ffi(name), i) => { + let name = symbols + .get_symbol(*name) + .ok_or(error::Expression::UnknownSymbol(*name))? + .to_owned(); + let fun = extern_funcs + .get(&name) + .ok_or(error::Expression::UndefinedExtern(name.to_owned()))?; + fun.call(symbols, &name, i, None) + } _ => { //println!("unexpected value type on the stack"); Err(error::Expression::InvalidType) @@ -47,11 +125,15 @@ impl Unary { } } - pub fn print(&self, value: String, _symbols: &SymbolTable) -> String { + pub fn print(&self, value: String, symbols: &SymbolTable) -> String { match self { Unary::Negate => format!("!{}", value), Unary::Parens => format!("({})", value), Unary::Length => format!("{}.length()", value), + Unary::TypeOf => format!("{}.type()", value), + Unary::Ffi(name) => { + format!("{value}.extern::{}()", symbols.print_symbol_default(*name)) + } } } } @@ -80,14 +162,147 @@ pub enum Binary { BitwiseOr, BitwiseXor, NotEqual, + HeterogeneousEqual, + HeterogeneousNotEqual, + LazyAnd, + LazyOr, + All, + Any, + Get, + Ffi(SymbolIndex), } impl Binary { + fn evaluate_with_closure( + &self, + left: Term, + right: Vec, + params: &[u32], + values: &mut HashMap, + symbols: &mut TemporarySymbolTable, + extern_func: &HashMap, + ) -> Result { + match (self, left, params) { + // boolean + (Binary::LazyOr, Term::Bool(true), []) => Ok(Term::Bool(true)), + (Binary::LazyOr, Term::Bool(false), []) => { + let e = Expression { ops: right.clone() }; + e.evaluate(values, symbols, extern_func) + } + (Binary::LazyAnd, Term::Bool(false), []) => Ok(Term::Bool(false)), + (Binary::LazyAnd, Term::Bool(true), []) => { + let e = Expression { ops: right.clone() }; + e.evaluate(values, symbols, extern_func) + } + + // set + (Binary::All, Term::Set(set_values), [param]) => { + for value in set_values.iter() { + values.insert(*param, value.clone()); + let e = Expression { ops: right.clone() }; + let result = e.evaluate(values, symbols, extern_func); + values.remove(param); + match result? { + Term::Bool(true) => {} + Term::Bool(false) => return Ok(Term::Bool(false)), + _ => return Err(error::Expression::InvalidType), + }; + } + Ok(Term::Bool(true)) + } + (Binary::Any, Term::Set(set_values), [param]) => { + for value in set_values.iter() { + values.insert(*param, value.clone()); + let e = Expression { ops: right.clone() }; + let result = e.evaluate(values, symbols, extern_func); + values.remove(param); + match result? { + Term::Bool(false) => {} + Term::Bool(true) => return Ok(Term::Bool(true)), + _ => return Err(error::Expression::InvalidType), + }; + } + Ok(Term::Bool(false)) + } + + // array + (Binary::All, Term::Array(array), [param]) => { + for value in array.iter() { + values.insert(*param, value.clone()); + let e = Expression { ops: right.clone() }; + let result = e.evaluate(values, symbols, extern_func); + values.remove(param); + match result? { + Term::Bool(true) => {} + Term::Bool(false) => return Ok(Term::Bool(false)), + _ => return Err(error::Expression::InvalidType), + }; + } + Ok(Term::Bool(true)) + } + (Binary::Any, Term::Array(array), [param]) => { + for value in array.iter() { + values.insert(*param, value.clone()); + let e = Expression { ops: right.clone() }; + let result = e.evaluate(values, symbols, extern_func); + values.remove(param); + match result? { + Term::Bool(false) => {} + Term::Bool(true) => return Ok(Term::Bool(true)), + _ => return Err(error::Expression::InvalidType), + }; + } + Ok(Term::Bool(false)) + } + + //map + (Binary::All, Term::Map(map), [param]) => { + for (key, value) in map.iter() { + let key = match key { + MapKey::Integer(i) => Term::Integer(*i), + MapKey::Str(i) => Term::Str(*i), + }; + values.insert(*param, Term::Array(vec![key, value.clone()])); + + let e = Expression { ops: right.clone() }; + let result = e.evaluate(values, symbols, extern_func); + values.remove(param); + match result? { + Term::Bool(true) => {} + Term::Bool(false) => return Ok(Term::Bool(false)), + _ => return Err(error::Expression::InvalidType), + }; + } + Ok(Term::Bool(true)) + } + (Binary::Any, Term::Map(map), [param]) => { + for (key, value) in map.iter() { + let key = match key { + MapKey::Integer(i) => Term::Integer(*i), + MapKey::Str(i) => Term::Str(*i), + }; + values.insert(*param, Term::Array(vec![key, value.clone()])); + + let e = Expression { ops: right.clone() }; + let result = e.evaluate(values, symbols, extern_func); + values.remove(param); + match result? { + Term::Bool(false) => {} + Term::Bool(true) => return Ok(Term::Bool(true)), + _ => return Err(error::Expression::InvalidType), + }; + } + Ok(Term::Bool(false)) + } + (_, _, _) => Err(error::Expression::InvalidType), + } + } fn evaluate( &self, left: Term, right: Term, symbols: &mut TemporarySymbolTable, + extern_funcs: &HashMap, ) -> Result { match (self, left, right) { // integer @@ -95,8 +310,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 +380,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,8 +442,80 @@ 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 | 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)), + + // array + (Binary::Equal | Binary::HeterogeneousEqual, Term::Array(i), Term::Array(j)) => { + Ok(Term::Bool(i == j)) + } + (Binary::NotEqual | Binary::HeterogeneousNotEqual, Term::Array(i), Term::Array(j)) => { + Ok(Term::Bool(i != j)) + } + (Binary::Contains, Term::Array(i), j) => { + Ok(Term::Bool(i.iter().any(|elem| elem == &j))) + } + (Binary::Prefix, Term::Array(i), Term::Array(j)) => Ok(Term::Bool(i.starts_with(&j))), + (Binary::Suffix, Term::Array(i), Term::Array(j)) => Ok(Term::Bool(i.ends_with(&j))), + (Binary::Get, Term::Array(i), Term::Integer(index)) => Ok(TryFrom::try_from(index) + .ok() + .and_then(|index: usize| i.get(index).cloned()) + .unwrap_or(Term::Null)), + + // map + (Binary::Equal | Binary::HeterogeneousEqual, Term::Map(i), Term::Map(j)) => { + Ok(Term::Bool(i == j)) + } + (Binary::NotEqual | Binary::HeterogeneousNotEqual, Term::Map(i), Term::Map(j)) => { + Ok(Term::Bool(i != j)) + } + (Binary::Contains, Term::Map(i), j) => { + Ok(Term::Bool(i.iter().any(|elem| match (elem.0, &j) { + (super::MapKey::Integer(k), Term::Integer(l)) => k == l, + (super::MapKey::Str(k), Term::Str(l)) => k == l, + _ => false, + }))) + } + (Binary::Get, Term::Map(m), Term::Integer(i)) => match m.get(&MapKey::Integer(i)) { + Some(term) => Ok(term.clone()), + None => Ok(Term::Null), + }, + (Binary::Get, Term::Map(m), Term::Str(i)) => match m.get(&MapKey::Str(i)) { + Some(term) => Ok(term.clone()), + None => Ok(Term::Null), + }, + + // heterogeneous equals catch all + (Binary::HeterogeneousEqual, _, _) => Ok(Term::Bool(false)), + (Binary::HeterogeneousNotEqual, _, _) => Ok(Term::Bool(true)), + + // FFI + (Binary::Ffi(name), left, right) => { + let name = symbols + .get_symbol(*name) + .ok_or(error::Expression::UnknownSymbol(*name))? + .to_owned(); + let fun = extern_funcs + .get(&name) + .ok_or(error::Expression::UndefinedExtern(name.to_owned()))?; + fun.call(symbols, &name, left, Some(right)) + } _ => { //println!("unexpected value type on the stack"); @@ -215,14 +524,16 @@ impl Binary { } } - pub fn print(&self, left: String, right: String, _symbols: &SymbolTable) -> String { + pub fn print(&self, left: String, right: String, symbols: &SymbolTable) -> String { match self { Binary::LessThan => format!("{} < {}", left, right), 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), @@ -231,58 +542,110 @@ impl Binary { Binary::Sub => format!("{} - {}", left, right), Binary::Mul => format!("{} * {}", left, right), Binary::Div => format!("{} / {}", left, right), - Binary::And => format!("{} && {}", left, right), - Binary::Or => format!("{} || {}", left, right), + Binary::And => format!("{} &&! {}", left, right), + Binary::Or => format!("{} ||! {}", left, right), Binary::Intersection => format!("{}.intersection({})", left, right), Binary::Union => format!("{}.union({})", left, right), Binary::BitwiseAnd => format!("{} & {}", left, right), Binary::BitwiseOr => format!("{} | {}", left, right), Binary::BitwiseXor => format!("{} ^ {}", left, right), + Binary::LazyAnd => format!("{left} && {right}"), + Binary::LazyOr => format!("{left} || {right}"), + Binary::All => format!("{left}.all({right})"), + Binary::Any => format!("{left}.any({right})"), + Binary::Get => format!("{left}.get({right})"), + Binary::Ffi(name) => format!( + "{left}.extern::{}({right})", + symbols.print_symbol_default(*name) + ), } } } +#[derive(Clone, Debug)] +enum StackElem { + Closure(Vec, Vec), + Term(Term), +} + impl Expression { pub fn evaluate( &self, values: &HashMap, symbols: &mut TemporarySymbolTable, + extern_funcs: &HashMap, ) -> Result { - let mut stack: Vec = Vec::new(); + let mut stack: Vec = Vec::new(); for op in self.ops.iter() { - //println!("op: {:?}\t| stack: {:?}", op, stack); + // println!("op: {:?}\t| stack: {:?}", op, stack); + match op { Op::Value(Term::Variable(i)) => match values.get(i) { - Some(term) => stack.push(term.clone()), + Some(term) => stack.push(StackElem::Term(term.clone())), None => { //println!("unknown variable {}", i); return Err(error::Expression::UnknownVariable(*i)); } }, - Op::Value(term) => stack.push(term.clone()), - Op::Unary(unary) => match stack.pop() { - None => { - //println!("expected a value on the stack"); - return Err(error::Expression::InvalidStack); + Op::Value(term) => stack.push(StackElem::Term(term.clone())), + Op::Unary(unary) => { + match stack.pop() { + Some(StackElem::Term(term)) => stack.push(StackElem::Term( + unary.evaluate(term, symbols, extern_funcs)?, + )), + _ => { + return Err(error::Expression::InvalidStack); + } } - Some(term) => stack.push(unary.evaluate(term, symbols)?), - }, + } Op::Binary(binary) => match (stack.pop(), stack.pop()) { - (Some(right_term), Some(left_term)) => { - stack.push(binary.evaluate(left_term, right_term, symbols)?) + (Some(StackElem::Term(right_term)), Some(StackElem::Term(left_term))) => stack + .push(StackElem::Term(binary.evaluate( + left_term, + right_term, + symbols, + extern_funcs, + )?)), + ( + Some(StackElem::Closure(params, right_ops)), + Some(StackElem::Term(left_term)), + ) => { + if values + .keys() + .collect::>() + .intersection(¶ms.iter().collect()) + .next() + .is_some() + { + return Err(error::Expression::ShadowedVariable); + } + let mut values = values.clone(); + stack.push(StackElem::Term(binary.evaluate_with_closure( + left_term, + right_ops, + ¶ms, + &mut values, + symbols, + extern_funcs, + )?)) } _ => { - //println!("expected two values on the stack"); return Err(error::Expression::InvalidStack); } }, + Op::Closure(params, ops) => { + stack.push(StackElem::Closure(params.clone(), ops.clone())); + } } } if stack.len() == 1 { - Ok(stack.remove(0)) + match stack.remove(0) { + StackElem::Term(t) => Ok(t), + _ => Err(error::Expression::InvalidStack), + } } else { Err(error::Expression::InvalidStack) } @@ -303,6 +666,24 @@ impl Expression { (Some(right), Some(left)) => stack.push(binary.print(left, right, symbols)), _ => return None, }, + Op::Closure(params, ops) => { + let exp_body = Expression { ops: ops.clone() }; + let body = match exp_body.print(symbols) { + Some(c) => c, + _ => return None, + }; + + if params.is_empty() { + stack.push(body); + } else { + let param_group = params + .iter() + .map(|s| symbols.print_term(&Term::Variable(*s))) + .collect::>() + .join(", "); + stack.push(format!("{param_group} -> {body}")); + } + } } } @@ -316,8 +697,10 @@ impl Expression { #[cfg(test)] mod tests { + use std::collections::{BTreeMap, BTreeSet}; + use super::*; - use crate::datalog::{SymbolTable, TemporarySymbolTable}; + use crate::datalog::{MapKey, SymbolTable, TemporarySymbolTable}; #[test] fn negate() { @@ -342,7 +725,7 @@ mod tests { let e = Expression { ops }; println!("print: {}", e.print(&symbols).unwrap()); - let res = e.evaluate(&values, &mut tmp_symbols); + let res = e.evaluate(&values, &mut tmp_symbols, &Default::default()); assert_eq!(res, Ok(Term::Bool(true))); } @@ -372,7 +755,7 @@ mod tests { let e = Expression { ops }; println!("print: {}", e.print(&symbols).unwrap()); - let res = e.evaluate(&HashMap::new(), &mut tmp_symbols); + let res = e.evaluate(&HashMap::new(), &mut tmp_symbols, &Default::default()); assert_eq!(res, Ok(Term::Integer(expected))); } } @@ -389,7 +772,7 @@ mod tests { let values = HashMap::new(); let e = Expression { ops }; - let res = e.evaluate(&values, &mut tmp_symbols); + let res = e.evaluate(&values, &mut tmp_symbols, &Default::default()); assert_eq!(res, Err(error::Expression::DivideByZero)); let ops = vec![ @@ -400,7 +783,7 @@ mod tests { let values = HashMap::new(); let e = Expression { ops }; - let res = e.evaluate(&values, &mut tmp_symbols); + let res = e.evaluate(&values, &mut tmp_symbols, &Default::default()); assert_eq!(res, Err(error::Expression::Overflow)); let ops = vec![ @@ -411,7 +794,7 @@ mod tests { let values = HashMap::new(); let e = Expression { ops }; - let res = e.evaluate(&values, &mut tmp_symbols); + let res = e.evaluate(&values, &mut tmp_symbols, &Default::default()); assert_eq!(res, Err(error::Expression::Overflow)); let ops = vec![ @@ -422,7 +805,7 @@ mod tests { let values = HashMap::new(); let e = Expression { ops }; - let res = e.evaluate(&values, &mut tmp_symbols); + let res = e.evaluate(&values, &mut tmp_symbols, &Default::default()); assert_eq!(res, Err(error::Expression::Overflow)); } @@ -469,4 +852,981 @@ mod tests { assert_eq!(e3.print(&symbols).unwrap(), "1 + 2 < 3"); //panic!(); } + + #[test] + fn null_equal() { + let symbols = SymbolTable::new(); + let mut tmp_symbols = TemporarySymbolTable::new(&symbols); + 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), + ]; + + 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 res = e.evaluate(&values, &mut tmp_symbols, &Default::default()); + assert_eq!(res, Ok(Term::Bool(true))); + } + } + + #[test] + fn null_not_equal() { + let symbols = SymbolTable::new(); + let mut tmp_symbols = TemporarySymbolTable::new(&symbols); + 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), + ]; + + 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 res = e.evaluate(&values, &mut tmp_symbols, &Default::default()); + assert_eq!(res, Ok(Term::Bool(false))); + } + } + + #[test] + fn null_heterogeneous() { + let symbols = SymbolTable::new(); + let mut tmp_symbols = TemporarySymbolTable::new(&symbols); + 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); + + let e = Expression { ops }; + println!("print: {}", e.print(&symbols).unwrap()); + + let res = e.evaluate(&values, &mut tmp_symbols, &Default::default()); + assert_eq!(res, Ok(Term::Bool(result))); + } + } + + #[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, &Default::default()); + 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)]; + + 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()); + + e.evaluate(&values, &mut tmp_symbols, &Default::default()) + .unwrap_err(); + } + } + } + } + + #[test] + fn laziness() { + let symbols = SymbolTable::new(); + let mut symbols = TemporarySymbolTable::new(&symbols); + + let ops1 = vec![ + Op::Value(Term::Bool(false)), + Op::Closure( + vec![], + vec![ + Op::Value(Term::Bool(true)), + Op::Closure(vec![], vec![Op::Value(Term::Bool(true))]), + Op::Binary(Binary::LazyAnd), + ], + ), + Op::Binary(Binary::LazyOr), + ]; + let e2 = Expression { ops: ops1 }; + + let res2 = e2 + .evaluate(&HashMap::new(), &mut symbols, &Default::default()) + .unwrap(); + assert_eq!(res2, Term::Bool(true)); + } + + #[test] + fn any() { + let mut symbols = SymbolTable::new(); + let p = symbols.insert("param") as u32; + let mut tmp_symbols = TemporarySymbolTable::new(&symbols); + + let ops1 = vec![ + Op::Value(Term::Set([Term::Bool(false), Term::Bool(true)].into())), + Op::Closure(vec![p], vec![Op::Value(Term::Variable(p))]), + Op::Binary(Binary::Any), + ]; + let e1 = Expression { ops: ops1 }; + println!("{:?}", e1.print(&symbols)); + + let res1 = e1 + .evaluate(&HashMap::new(), &mut tmp_symbols, &Default::default()) + .unwrap(); + assert_eq!(res1, Term::Bool(true)); + + let ops2 = vec![ + Op::Value(Term::Set([Term::Integer(1), Term::Integer(2)].into())), + Op::Closure( + vec![p], + vec![ + Op::Value(Term::Variable(p)), + Op::Value(Term::Integer(0)), + Op::Binary(Binary::LessThan), + ], + ), + Op::Binary(Binary::Any), + ]; + let e2 = Expression { ops: ops2 }; + println!("{:?}", e2.print(&symbols)); + + let res2 = e2 + .evaluate(&HashMap::new(), &mut tmp_symbols, &Default::default()) + .unwrap(); + assert_eq!(res2, Term::Bool(false)); + + let ops3 = vec![ + Op::Value(Term::Set([Term::Integer(1), Term::Integer(2)].into())), + Op::Closure(vec![p], vec![Op::Value(Term::Integer(0))]), + Op::Binary(Binary::Any), + ]; + let e3 = Expression { ops: ops3 }; + println!("{:?}", e3.print(&symbols)); + + let err3 = e3 + .evaluate(&HashMap::new(), &mut tmp_symbols, &Default::default()) + .unwrap_err(); + assert_eq!(err3, error::Expression::InvalidType); + } + + #[test] + fn all() { + let mut symbols = SymbolTable::new(); + let p = symbols.insert("param") as u32; + let mut tmp_symbols = TemporarySymbolTable::new(&symbols); + + let ops1 = vec![ + Op::Value(Term::Set([Term::Integer(1), Term::Integer(2)].into())), + Op::Closure( + vec![p], + vec![ + Op::Value(Term::Variable(p)), + Op::Value(Term::Integer(0)), + Op::Binary(Binary::GreaterThan), + ], + ), + Op::Binary(Binary::All), + ]; + let e1 = Expression { ops: ops1 }; + println!("{:?}", e1.print(&symbols)); + + let res1 = e1 + .evaluate(&HashMap::new(), &mut tmp_symbols, &Default::default()) + .unwrap(); + assert_eq!(res1, Term::Bool(true)); + + let ops2 = vec![ + Op::Value(Term::Set([Term::Integer(1), Term::Integer(2)].into())), + Op::Closure( + vec![p], + vec![ + Op::Value(Term::Variable(p)), + Op::Value(Term::Integer(0)), + Op::Binary(Binary::LessThan), + ], + ), + Op::Binary(Binary::All), + ]; + let e2 = Expression { ops: ops2 }; + println!("{:?}", e2.print(&symbols)); + + let res2 = e2 + .evaluate(&HashMap::new(), &mut tmp_symbols, &Default::default()) + .unwrap(); + assert_eq!(res2, Term::Bool(false)); + + let ops3 = vec![ + Op::Value(Term::Set([Term::Integer(1), Term::Integer(2)].into())), + Op::Closure(vec![p], vec![Op::Value(Term::Integer(0))]), + Op::Binary(Binary::All), + ]; + let e3 = Expression { ops: ops3 }; + println!("{:?}", e3.print(&symbols)); + + let err3 = e3 + .evaluate(&HashMap::new(), &mut tmp_symbols, &Default::default()) + .unwrap_err(); + assert_eq!(err3, error::Expression::InvalidType); + } + + #[test] + fn nested_closures() { + let mut symbols = SymbolTable::new(); + let p = symbols.insert("p") as u32; + let q = symbols.insert("q") as u32; + let mut tmp_symbols = TemporarySymbolTable::new(&symbols); + + let ops1 = vec![ + Op::Value(Term::Set( + [Term::Integer(1), Term::Integer(2), Term::Integer(3)].into(), + )), + Op::Closure( + vec![p], + vec![ + Op::Value(Term::Variable(p)), + Op::Value(Term::Integer(1)), + Op::Binary(Binary::GreaterThan), + Op::Closure( + vec![], + vec![ + Op::Value(Term::Set( + [Term::Integer(3), Term::Integer(4), Term::Integer(5)].into(), + )), + Op::Closure( + vec![q], + vec![ + Op::Value(Term::Variable(p)), + Op::Value(Term::Variable(q)), + Op::Binary(Binary::Equal), + ], + ), + Op::Binary(Binary::Any), + ], + ), + Op::Binary(Binary::LazyAnd), + ], + ), + Op::Binary(Binary::Any), + ]; + let e1 = Expression { ops: ops1 }; + println!("{}", e1.print(&symbols).unwrap()); + + let res1 = e1 + .evaluate(&HashMap::new(), &mut tmp_symbols, &Default::default()) + .unwrap(); + assert_eq!(res1, Term::Bool(true)); + } + + #[test] + fn variable_shadowing() { + let mut symbols = SymbolTable::new(); + let p = symbols.insert("param") as u32; + let mut tmp_symbols = TemporarySymbolTable::new(&symbols); + + let ops1 = vec![ + Op::Value(Term::Set([Term::Integer(1), Term::Integer(2)].into())), + Op::Closure( + vec![p], + vec![ + Op::Value(Term::Variable(p)), + Op::Value(Term::Integer(0)), + Op::Binary(Binary::GreaterThan), + ], + ), + Op::Binary(Binary::All), + ]; + let e1 = Expression { ops: ops1 }; + println!("{:?}", e1.print(&symbols)); + + let mut values = HashMap::new(); + values.insert(p, Term::Null); + let res1 = e1.evaluate(&values, &mut tmp_symbols, &Default::default()); + assert_eq!(res1, Err(error::Expression::ShadowedVariable)); + + let mut symbols = SymbolTable::new(); + let p = symbols.insert("p") as u32; + let mut tmp_symbols = TemporarySymbolTable::new(&symbols); + + let ops2 = vec![ + Op::Value(Term::Set( + [Term::Integer(1), Term::Integer(2), Term::Integer(3)].into(), + )), + Op::Closure( + vec![p], + vec![ + Op::Value(Term::Variable(p)), + Op::Value(Term::Integer(1)), + Op::Binary(Binary::GreaterThan), + Op::Closure( + vec![], + vec![ + Op::Value(Term::Set( + [Term::Integer(3), Term::Integer(4), Term::Integer(5)].into(), + )), + Op::Closure( + vec![p], + vec![ + Op::Value(Term::Variable(p)), + Op::Value(Term::Variable(p)), + Op::Binary(Binary::Equal), + ], + ), + Op::Binary(Binary::Any), + ], + ), + Op::Binary(Binary::LazyAnd), + ], + ), + Op::Binary(Binary::Any), + ]; + let e2 = Expression { ops: ops2 }; + println!("{}", e2.print(&symbols).unwrap()); + + let res2 = e2.evaluate(&HashMap::new(), &mut tmp_symbols, &Default::default()); + assert_eq!(res2, Err(error::Expression::ShadowedVariable)); + } + + #[test] + fn array() { + let symbols = SymbolTable::new(); + let mut tmp_symbols = TemporarySymbolTable::new(&symbols); + let ops = vec![ + Op::Value(Term::Array(vec![Term::Integer(0), Term::Integer(1)])), + Op::Value(Term::Array(vec![Term::Integer(0), Term::Integer(1)])), + Op::Binary(Binary::Equal), + ]; + + let values = HashMap::new(); + let e = Expression { ops }; + let res = e.evaluate(&values, &mut tmp_symbols, &Default::default()); + assert_eq!(res, Ok(Term::Bool(true))); + + let ops = vec![ + Op::Value(Term::Array(vec![Term::Integer(0), Term::Integer(1)])), + Op::Value(Term::Array(vec![Term::Integer(0)])), + Op::Binary(Binary::Equal), + ]; + + let values = HashMap::new(); + let e = Expression { ops }; + let res = e.evaluate(&values, &mut tmp_symbols, &Default::default()); + assert_eq!(res, Ok(Term::Bool(false))); + + let ops = vec![ + Op::Value(Term::Array(vec![Term::Integer(0), Term::Integer(1)])), + Op::Value(Term::Integer(1)), + Op::Binary(Binary::Contains), + ]; + + let values = HashMap::new(); + let e = Expression { ops }; + let res = e.evaluate(&values, &mut tmp_symbols, &Default::default()); + assert_eq!(res, Ok(Term::Bool(true))); + + let ops = vec![ + Op::Value(Term::Array(vec![Term::Integer(0), Term::Integer(1)])), + Op::Value(Term::Integer(2)), + Op::Binary(Binary::Contains), + ]; + + let values = HashMap::new(); + let e = Expression { ops }; + let res = e.evaluate(&values, &mut tmp_symbols, &Default::default()); + assert_eq!(res, Ok(Term::Bool(false))); + + let ops = vec![ + Op::Value(Term::Array(vec![ + Term::Integer(0), + Term::Integer(1), + Term::Integer(2), + ])), + Op::Value(Term::Array(vec![Term::Integer(0), Term::Integer(1)])), + Op::Binary(Binary::Prefix), + ]; + + let values = HashMap::new(); + let e = Expression { ops }; + let res = e.evaluate(&values, &mut tmp_symbols, &Default::default()); + assert_eq!(res, Ok(Term::Bool(true))); + + let ops = vec![ + Op::Value(Term::Array(vec![ + Term::Integer(0), + Term::Integer(1), + Term::Integer(2), + ])), + Op::Value(Term::Array(vec![Term::Integer(2), Term::Integer(1)])), + Op::Binary(Binary::Prefix), + ]; + + let values = HashMap::new(); + let e = Expression { ops }; + let res = e.evaluate(&values, &mut tmp_symbols, &Default::default()); + assert_eq!(res, Ok(Term::Bool(false))); + + let ops = vec![ + Op::Value(Term::Array(vec![ + Term::Integer(0), + Term::Integer(1), + Term::Integer(2), + ])), + Op::Value(Term::Array(vec![Term::Integer(1), Term::Integer(2)])), + Op::Binary(Binary::Suffix), + ]; + + let values = HashMap::new(); + let e = Expression { ops }; + let res = e.evaluate(&values, &mut tmp_symbols, &Default::default()); + assert_eq!(res, Ok(Term::Bool(true))); + + let ops = vec![ + Op::Value(Term::Array(vec![ + Term::Integer(0), + Term::Integer(1), + Term::Integer(2), + ])), + Op::Value(Term::Array(vec![Term::Integer(0), Term::Integer(2)])), + Op::Binary(Binary::Suffix), + ]; + + let values = HashMap::new(); + let e = Expression { ops }; + let res = e.evaluate(&values, &mut tmp_symbols, &Default::default()); + assert_eq!(res, Ok(Term::Bool(false))); + + // get + let ops = vec![ + Op::Value(Term::Array(vec![ + Term::Integer(0), + Term::Integer(1), + Term::Integer(2), + ])), + Op::Value(Term::Integer(1)), + Op::Binary(Binary::Get), + ]; + + let values = HashMap::new(); + let e = Expression { ops }; + let res = e.evaluate(&values, &mut tmp_symbols, &Default::default()); + assert_eq!(res, Ok(Term::Integer(1))); + + // get out of bounds + let ops = vec![ + Op::Value(Term::Array(vec![ + Term::Integer(0), + Term::Integer(1), + Term::Integer(2), + ])), + Op::Value(Term::Integer(3)), + Op::Binary(Binary::Get), + ]; + + let values = HashMap::new(); + let e = Expression { ops }; + let res = e.evaluate(&values, &mut tmp_symbols, &Default::default()); + assert_eq!(res, Ok(Term::Null)); + + // all + let p = tmp_symbols.insert("param") as u32; + let ops1 = vec![ + Op::Value(Term::Array([Term::Integer(1), Term::Integer(2)].into())), + Op::Closure( + vec![p], + vec![ + Op::Value(Term::Variable(p)), + Op::Value(Term::Integer(0)), + Op::Binary(Binary::GreaterThan), + ], + ), + Op::Binary(Binary::All), + ]; + let e1 = Expression { ops: ops1 }; + println!("{:?}", e1.print(&symbols)); + + let res1 = e1 + .evaluate(&HashMap::new(), &mut tmp_symbols, &Default::default()) + .unwrap(); + assert_eq!(res1, Term::Bool(true)); + + // any + let ops1 = vec![ + Op::Value(Term::Array([Term::Integer(1), Term::Integer(2)].into())), + Op::Closure( + vec![p], + vec![ + Op::Value(Term::Variable(p)), + Op::Value(Term::Integer(0)), + Op::Binary(Binary::Equal), + ], + ), + Op::Binary(Binary::Any), + ]; + let e1 = Expression { ops: ops1 }; + println!("{:?}", e1.print(&symbols)); + + let res1 = e1 + .evaluate(&HashMap::new(), &mut tmp_symbols, &Default::default()) + .unwrap(); + assert_eq!(res1, Term::Bool(false)); + } + + #[test] + fn map() { + let mut symbols = SymbolTable::new(); + let p = symbols.insert("param") as u32; + let mut tmp_symbols = TemporarySymbolTable::new(&symbols); + + let ops = vec![ + Op::Value(Term::Map( + [ + (MapKey::Str(1), Term::Integer(0)), + (MapKey::Str(2), Term::Integer(1)), + ] + .iter() + .cloned() + .collect(), + )), + Op::Value(Term::Map( + [ + (MapKey::Str(2), Term::Integer(1)), + (MapKey::Str(1), Term::Integer(0)), + ] + .iter() + .cloned() + .collect(), + )), + Op::Binary(Binary::Equal), + ]; + + let values = HashMap::new(); + let e = Expression { ops }; + let res = e.evaluate(&values, &mut tmp_symbols, &Default::default()); + assert_eq!(res, Ok(Term::Bool(true))); + + let ops = vec![ + Op::Value(Term::Map( + [ + (MapKey::Str(1), Term::Integer(0)), + (MapKey::Str(2), Term::Integer(1)), + ] + .iter() + .cloned() + .collect(), + )), + Op::Value(Term::Map( + [(MapKey::Str(1), Term::Integer(0))] + .iter() + .cloned() + .collect(), + )), + Op::Binary(Binary::Equal), + ]; + + let values = HashMap::new(); + let e = Expression { ops }; + let res = e.evaluate(&values, &mut tmp_symbols, &Default::default()); + assert_eq!(res, Ok(Term::Bool(false))); + + let ops = vec![ + Op::Value(Term::Map( + [ + (MapKey::Str(1), Term::Integer(0)), + (MapKey::Str(2), Term::Integer(1)), + ] + .iter() + .cloned() + .collect(), + )), + Op::Value(Term::Str(1)), + Op::Binary(Binary::Contains), + ]; + + let values = HashMap::new(); + let e = Expression { ops }; + let res = e.evaluate(&values, &mut tmp_symbols, &Default::default()); + assert_eq!(res, Ok(Term::Bool(true))); + + let ops = vec![ + Op::Value(Term::Map( + [ + (MapKey::Str(1), Term::Integer(0)), + (MapKey::Str(2), Term::Integer(1)), + ] + .iter() + .cloned() + .collect(), + )), + Op::Value(Term::Integer(0)), + Op::Binary(Binary::Contains), + ]; + + let values = HashMap::new(); + let e = Expression { ops }; + let res = e.evaluate(&values, &mut tmp_symbols, &Default::default()); + assert_eq!(res, Ok(Term::Bool(false))); + + // get + let ops = vec![ + Op::Value(Term::Map( + [ + (MapKey::Str(1), Term::Integer(0)), + (MapKey::Integer(2), Term::Integer(1)), + ] + .iter() + .cloned() + .collect(), + )), + Op::Value(Term::Str(1)), + Op::Binary(Binary::Get), + ]; + + let values = HashMap::new(); + let e = Expression { ops }; + let res = e.evaluate(&values, &mut tmp_symbols, &Default::default()); + assert_eq!(res, Ok(Term::Integer(0))); + + let ops = vec![ + Op::Value(Term::Map( + [ + (MapKey::Str(1), Term::Integer(0)), + (MapKey::Integer(2), Term::Integer(1)), + ] + .iter() + .cloned() + .collect(), + )), + Op::Value(Term::Integer(2)), + Op::Binary(Binary::Get), + ]; + + let values = HashMap::new(); + let e = Expression { ops }; + let res = e.evaluate(&values, &mut tmp_symbols, &Default::default()); + assert_eq!(res, Ok(Term::Integer(1))); + + // get non existing key + let ops = vec![ + Op::Value(Term::Map( + [ + (MapKey::Str(1), Term::Integer(0)), + (MapKey::Str(2), Term::Integer(1)), + ] + .iter() + .cloned() + .collect(), + )), + Op::Value(Term::Integer(0)), + Op::Binary(Binary::Get), + ]; + + let values = HashMap::new(); + let e = Expression { ops }; + let res = e.evaluate(&values, &mut tmp_symbols, &Default::default()); + assert_eq!(res, Ok(Term::Null)); + + let ops = vec![ + Op::Value(Term::Map( + [ + (MapKey::Str(1), Term::Integer(0)), + (MapKey::Str(2), Term::Integer(1)), + ] + .iter() + .cloned() + .collect(), + )), + Op::Value(Term::Str(3)), + Op::Binary(Binary::Get), + ]; + + let values = HashMap::new(); + let e = Expression { ops }; + let res = e.evaluate(&values, &mut tmp_symbols, &Default::default()); + assert_eq!(res, Ok(Term::Null)); + + // all + let ops1 = vec![ + Op::Value(Term::Map( + [ + (MapKey::Str(1), Term::Integer(0)), + (MapKey::Str(2), Term::Integer(1)), + ] + .iter() + .cloned() + .collect(), + )), + Op::Closure( + vec![p], + vec![ + Op::Value(Term::Variable(p)), + Op::Value(Term::Integer(1)), + Op::Binary(Binary::Get), + Op::Value(Term::Integer(2)), + Op::Binary(Binary::LessThan), + ], + ), + Op::Binary(Binary::All), + ]; + let e1 = Expression { ops: ops1 }; + println!("{:?}", e1.print(&symbols)); + + let res1 = e1 + .evaluate(&HashMap::new(), &mut tmp_symbols, &Default::default()) + .unwrap(); + assert_eq!(res1, Term::Bool(true)); + + // any + let ops1 = vec![ + Op::Value(Term::Map( + [ + (MapKey::Str(1), Term::Integer(0)), + (MapKey::Str(2), Term::Integer(1)), + ] + .iter() + .cloned() + .collect(), + )), + Op::Closure( + vec![p], + vec![ + Op::Value(Term::Variable(p)), + Op::Value(Term::Integer(0)), + Op::Binary(Binary::Get), + Op::Value(Term::Str(1)), + Op::Binary(Binary::Equal), + ], + ), + Op::Binary(Binary::Any), + ]; + let e1 = Expression { ops: ops1 }; + println!("{:?}", e1.print(&symbols)); + + let res1 = e1 + .evaluate(&HashMap::new(), &mut tmp_symbols, &Default::default()) + .unwrap(); + assert_eq!(res1, Term::Bool(true)); + } + #[test] + fn ffi() { + let mut symbols = SymbolTable::new(); + let i = symbols.insert("test"); + let j = symbols.insert("TeSt"); + let test_bin = symbols.insert("test_bin"); + let test_un = symbols.insert("test_un"); + let test_closure = symbols.insert("test_closure"); + let test_fn = symbols.insert("test_fn"); + let id_fn = symbols.insert("id"); + let mut tmp_symbols = TemporarySymbolTable::new(&symbols); + let ops = vec![ + Op::Value(Term::Integer(60)), + Op::Value(Term::Integer(0)), + Op::Binary(Binary::Ffi(test_bin)), + Op::Value(Term::Str(i)), + Op::Value(Term::Str(j)), + Op::Binary(Binary::Ffi(test_bin)), + Op::Binary(Binary::And), + Op::Value(Term::Integer(42)), + Op::Unary(Unary::Ffi(test_un)), + Op::Binary(Binary::And), + Op::Value(Term::Integer(42)), + Op::Unary(Unary::Ffi(test_closure)), + Op::Binary(Binary::And), + Op::Value(Term::Str(i)), + Op::Unary(Unary::Ffi(test_closure)), + Op::Binary(Binary::And), + Op::Value(Term::Integer(42)), + Op::Unary(Unary::Ffi(test_fn)), + Op::Binary(Binary::And), + Op::Value(Term::Integer(42)), + Op::Unary(Unary::Ffi(id_fn)), + Op::Value(Term::Integer(42)), + Op::Binary(Binary::HeterogeneousEqual), + Op::Binary(Binary::And), + Op::Value(Term::Str(i)), + Op::Unary(Unary::Ffi(id_fn)), + Op::Value(Term::Str(i)), + Op::Binary(Binary::HeterogeneousEqual), + Op::Binary(Binary::And), + Op::Value(Term::Bool(true)), + Op::Unary(Unary::Ffi(id_fn)), + Op::Value(Term::Bool(true)), + Op::Binary(Binary::HeterogeneousEqual), + Op::Binary(Binary::And), + Op::Value(Term::Date(0)), + Op::Unary(Unary::Ffi(id_fn)), + Op::Value(Term::Date(0)), + Op::Binary(Binary::HeterogeneousEqual), + Op::Binary(Binary::And), + Op::Value(Term::Bytes(vec![42])), + Op::Unary(Unary::Ffi(id_fn)), + Op::Value(Term::Bytes(vec![42])), + Op::Binary(Binary::HeterogeneousEqual), + Op::Binary(Binary::And), + Op::Value(Term::Null), + Op::Unary(Unary::Ffi(id_fn)), + Op::Value(Term::Null), + Op::Binary(Binary::HeterogeneousEqual), + Op::Binary(Binary::And), + Op::Value(Term::Array(vec![Term::Null])), + Op::Unary(Unary::Ffi(id_fn)), + Op::Value(Term::Array(vec![Term::Null])), + Op::Binary(Binary::HeterogeneousEqual), + Op::Binary(Binary::And), + Op::Value(Term::Set(BTreeSet::from([Term::Null]))), + Op::Unary(Unary::Ffi(id_fn)), + Op::Value(Term::Set(BTreeSet::from([Term::Null]))), + Op::Binary(Binary::HeterogeneousEqual), + Op::Binary(Binary::And), + Op::Value(Term::Map(BTreeMap::from([ + (MapKey::Integer(42), Term::Null), + (MapKey::Str(i), Term::Null), + ]))), + Op::Unary(Unary::Ffi(id_fn)), + Op::Value(Term::Map(BTreeMap::from([ + (MapKey::Integer(42), Term::Null), + (MapKey::Str(i), Term::Null), + ]))), + Op::Binary(Binary::HeterogeneousEqual), + Op::Binary(Binary::And), + ]; + + let values = HashMap::new(); + let e = Expression { ops }; + let mut extern_funcs: HashMap = Default::default(); + extern_funcs.insert( + "test_bin".to_owned(), + ExternFunc::new(Arc::new(|left, right| match (left, right) { + (builder::Term::Integer(left), Some(builder::Term::Integer(right))) => { + println!("{left} {right}"); + Ok(builder::Term::Bool((left % 60) == (right % 60))) + } + (builder::Term::Str(left), Some(builder::Term::Str(right))) => { + println!("{left} {right}"); + Ok(builder::Term::Bool( + left.to_lowercase() == right.to_lowercase(), + )) + } + _ => Err("Expected two strings or two integers".to_string()), + })), + ); + extern_funcs.insert( + "test_un".to_owned(), + ExternFunc::new(Arc::new(|left, right| match (&left, &right) { + (builder::Term::Integer(left), None) => Ok(builder::boolean(*left == 42)), + _ => { + println!("{left:?}, {right:?}"); + Err("expecting a single integer".to_string()) + } + })), + ); + extern_funcs.insert( + "id".to_string(), + ExternFunc::new(Arc::new(|left, right| match (left, right) { + (a, None) => Ok(a), + _ => Err("expecting a single value".to_string()), + })), + ); + let closed_over_int = 42; + let closed_over_string = "test".to_string(); + extern_funcs.insert( + "test_closure".to_owned(), + ExternFunc::new(Arc::new(move |left, right| match (&left, &right) { + (builder::Term::Integer(left), None) => { + Ok(builder::boolean(*left == closed_over_int)) + } + (builder::Term::Str(left), None) => { + Ok(builder::boolean(left == &closed_over_string)) + } + _ => { + println!("{left:?}, {right:?}"); + Err("expecting a single integer".to_string()) + } + })), + ); + extern_funcs.insert("test_fn".to_owned(), ExternFunc::new(Arc::new(toto))); + let res = e.evaluate(&values, &mut tmp_symbols, &extern_funcs); + assert_eq!(res, Ok(Term::Bool(true))); + } + + fn toto(_left: builder::Term, _right: Option) -> Result { + Ok(builder::Term::Bool(true)) + } } diff --git a/biscuit-auth/src/datalog/mod.rs b/biscuit-auth/src/datalog/mod.rs index dc6fc3f4..6820f053 100644 --- a/biscuit-auth/src/datalog/mod.rs +++ b/biscuit-auth/src/datalog/mod.rs @@ -2,9 +2,9 @@ use crate::builder::{CheckKind, Convert}; use crate::error::Execution; use crate::time::Instant; -use crate::token::{Scope, MIN_SCHEMA_VERSION}; +use crate::token::{Scope, DATALOG_3_1, DATALOG_3_3, MIN_SCHEMA_VERSION}; use crate::{builder, error}; -use std::collections::{BTreeSet, HashMap, HashSet}; +use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use std::convert::AsRef; use std::fmt; use std::time::{Duration, SystemTime, UNIX_EPOCH}; @@ -25,6 +25,15 @@ pub enum Term { Bytes(Vec), Bool(bool), Set(BTreeSet), + Null, + Array(Vec), + Map(BTreeMap), +} + +#[derive(Debug, Clone, PartialEq, Hash, Eq, PartialOrd, Ord)] +pub enum MapKey { + Integer(i64), + Str(SymbolIndex), } impl From<&Term> for Term { @@ -37,6 +46,9 @@ impl From<&Term> for Term { Term::Bytes(ref b) => Term::Bytes(b.clone()), Term::Bool(ref b) => Term::Bool(*b), Term::Set(ref s) => Term::Set(s.clone()), + Term::Null => Term::Null, + Term::Array(ref a) => Term::Array(a.clone()), + Term::Map(m) => Term::Map(m.clone()), } } } @@ -126,6 +138,7 @@ impl Rule { facts: IT, rule_origin: usize, symbols: &'a SymbolTable, + extern_funcs: &'a HashMap, ) -> impl Iterator> + 'a where IT: Iterator + Clone + 'a, @@ -137,7 +150,7 @@ impl Rule { .map(move |(origin, variables)| { let mut temporary_symbols = TemporarySymbolTable::new(symbols); for e in self.expressions.iter() { - match e.evaluate(&variables, &mut temporary_symbols) { + match e.evaluate(&variables, &mut temporary_symbols, extern_funcs) { Ok(Term::Bool(true)) => {} Ok(Term::Bool(false)) => return Ok((origin, variables, false)), Ok(_) => return Err(error::Expression::InvalidType), @@ -165,14 +178,14 @@ impl Rule { _ => continue, }; } - + origin.insert(rule_origin); Some(Ok((origin, Fact { predicate: p }))) } else {None} }, Err(e) => Some(Err(e)) } - + }) } @@ -182,15 +195,16 @@ impl Rule { origin: usize, scope: &TrustedOrigins, symbols: &SymbolTable, + extern_funcs: &HashMap, ) -> Result { let fact_it = facts.iterator(scope); - let mut it = self.apply(fact_it, origin, symbols); + let mut it = self.apply(fact_it, origin, symbols, extern_funcs); let next = it.next(); match next { None => Ok(false), Some(Ok(_)) => Ok(true), - Some(Err(e)) => Err(Execution::Expression(e)) + Some(Err(e)) => Err(Execution::Expression(e)), } } @@ -199,6 +213,7 @@ impl Rule { facts: &FactSet, scope: &TrustedOrigins, symbols: &SymbolTable, + extern_funcs: &HashMap, ) -> Result { let fact_it = facts.iterator(scope); let variables = MatchedVariables::new(self.variables_set()); @@ -209,13 +224,15 @@ impl Rule { let mut temporary_symbols = TemporarySymbolTable::new(symbols); for e in self.expressions.iter() { - match e.evaluate(&variables, &mut temporary_symbols) { + match e.evaluate(&variables, &mut temporary_symbols, extern_funcs) { Ok(Term::Bool(true)) => {} Ok(Term::Bool(false)) => { //println!("expr returned {:?}", res); return Ok(false); - }, - Ok(_) => return Err(error::Execution::Expression(error::Expression::InvalidType)), + } + Ok(_) => { + return Err(error::Execution::Expression(error::Expression::InvalidType)) + } Err(e) => { return Err(error::Execution::Expression(e)); } @@ -558,7 +575,10 @@ pub fn match_preds(rule_pred: &Predicate, fact_pred: &Predicate) -> bool { (Term::Date(i), Term::Date(j)) => i == j, (Term::Bytes(i), Term::Bytes(j)) => i == j, (Term::Bool(i), Term::Bool(j)) => i == j, + (Term::Null, Term::Null) => true, (Term::Set(i), Term::Set(j)) => i == j, + (Term::Array(i), Term::Array(j)) => i == j, + (Term::Map(i), Term::Map(j)) => i == j, _ => false, }) } @@ -568,6 +588,7 @@ pub struct World { pub facts: FactSet, pub rules: RuleSet, pub iterations: u64, + pub extern_funcs: HashMap, } impl World { @@ -602,13 +623,12 @@ impl World { for (scope, rules) in self.rules.inner.iter() { let it = self.facts.iterator(scope); for (origin, rule) in rules { - for res in rule.apply(it.clone(), *origin, symbols) { + for res in rule.apply(it.clone(), *origin, symbols, &self.extern_funcs) { match res { - Ok((origin,fact)) => { + Ok((origin, fact)) => { new_facts.insert(&origin, fact); - - }, - Err(e) => { + } + Err(e) => { return Err(Execution::Expression(e)); } } @@ -625,7 +645,9 @@ impl World { index += 1; if index == limits.max_iterations { - break Err(Execution::RunLimit( crate::error::RunLimit::TooManyIterations)); + break Err(Execution::RunLimit( + crate::error::RunLimit::TooManyIterations, + )); } if self.facts.len() >= limits.max_facts as usize { @@ -676,13 +698,12 @@ impl World { let mut new_facts = FactSet::default(); let it = self.facts.iterator(scope); //new_facts.extend(rule.apply(it, origin, symbols)); - for res in rule.apply(it.clone(), origin, symbols) { + for res in rule.apply(it.clone(), origin, symbols, &self.extern_funcs) { match res { - Ok((origin,fact)) => { + Ok((origin, fact)) => { new_facts.insert(&origin, fact); - - }, - Err(e) => { + } + Err(e) => { return Err(Execution::Expression(e)); } } @@ -698,7 +719,7 @@ impl World { scope: &TrustedOrigins, symbols: &SymbolTable, ) -> Result { - rule.find_match(&self.facts, origin, scope, symbols) + rule.find_match(&self.facts, origin, scope, symbols, &self.extern_funcs) } pub fn query_match_all( @@ -707,12 +728,12 @@ impl World { scope: &TrustedOrigins, symbols: &SymbolTable, ) -> Result { - rule.check_match_all(&self.facts, scope, symbols) + rule.check_match_all(&self.facts, scope, symbols, &self.extern_funcs) } } /// runtime limits for the Datalog engine -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct RunLimits { /// maximum number of Datalog facts (memory usage) pub max_facts: u64, @@ -762,7 +783,7 @@ impl FactSet { pub fn iterator<'a>( &'a self, block_ids: &'a TrustedOrigins, - ) -> impl Iterator + Clone { + ) -> impl Iterator + Clone { self.inner .iter() .filter_map(move |(ids, facts)| { @@ -838,36 +859,43 @@ impl RuleSet { pub struct SchemaVersion { contains_scopes: bool, - contains_v4: bool, + contains_v3_1: bool, contains_check_all: bool, + contains_v3_3: bool, } impl SchemaVersion { pub fn version(&self) -> u32 { - if self.contains_scopes || self.contains_v4 || self.contains_check_all { - 4 + if self.contains_v3_3 { + DATALOG_3_3 + } else if self.contains_scopes || self.contains_v3_1 || self.contains_check_all { + DATALOG_3_1 } else { MIN_SCHEMA_VERSION } } pub fn check_compatibility(&self, version: u32) -> Result<(), error::Format> { - if version < 4 { + if version < DATALOG_3_1 { if self.contains_scopes { Err(error::Format::DeserializationError( - "v3 blocks must not have scopes".to_string(), + "scopes are only supported in datalog v3.1+".to_string(), )) - } else if self.contains_v4 { + } else if self.contains_v3_1 { Err(error::Format::DeserializationError( - "v3 blocks must not have v4 operators (bitwise operators or !=)".to_string(), + "bitwise operators and != are only supported in datalog v3.1+".to_string(), )) } else if self.contains_check_all { Err(error::Format::DeserializationError( - "v3 blocks must not have use all".to_string(), + "check all is only supported in datalog v3.1+".to_string(), )) } else { Ok(()) } + } else if version < DATALOG_3_3 && self.contains_v3_3 { + Err(error::Format::DeserializationError( + "maps, arrays, null, closures are only supported in datalog v3.3+".to_string(), + )) } else { Ok(()) } @@ -876,7 +904,7 @@ impl SchemaVersion { /// Determine the schema version given the elements of a block. pub fn get_schema_version( - _facts: &[Fact], + facts: &[Fact], rules: &[Rule], checks: &[Check], scopes: &[Scope], @@ -887,26 +915,55 @@ pub fn get_schema_version( .iter() .any(|c: &Check| c.queries.iter().any(|q| !q.scopes.is_empty())); - let contains_check_all = checks.iter().any(|c: &Check| c.kind == CheckKind::All); + let mut contains_check_all = false; + let mut contains_v3_3 = false; - let contains_v4 = rules.iter().any(|rule| contains_v4_op(&rule.expressions)) + for c in checks.iter() { + if c.kind == CheckKind::All { + contains_check_all = true; + } else if c.kind == CheckKind::Reject { + contains_v3_3 = true; + } + } + + let contains_v3_1 = rules.iter().any(|rule| contains_v3_1_op(&rule.expressions)) || checks.iter().any(|check| { check .queries .iter() - .any(|query| contains_v4_op(&query.expressions)) + .any(|query| contains_v3_1_op(&query.expressions)) + }); + + // null, heterogeneous equals, closures + if !contains_v3_3 { + contains_v3_3 = rules.iter().any(|rule| { + contains_v3_3_predicate(&rule.head) + || rule.body.iter().any(contains_v3_3_predicate) + || contains_v3_3_op(&rule.expressions) + }) || checks.iter().any(|check| { + check.queries.iter().any(|query| { + query.body.iter().any(contains_v3_3_predicate) + || contains_v3_3_op(&query.expressions) + }) }); + } + if !contains_v3_3 { + contains_v3_3 = facts + .iter() + .any(|fact| contains_v3_3_predicate(&fact.predicate)) + } SchemaVersion { contains_scopes, - contains_v4, + contains_v3_1, contains_check_all, + contains_v3_3, } } -/// Determine whether any of the expression contain a v4 operator. -/// Bitwise operators and != are only supported in biscuits v4+ -pub fn contains_v4_op(expressions: &[Expression]) -> bool { +/// Determine whether any of the expression contain a v3.1 operator. +/// Bitwise operators and != are only supported in biscuits v3.1+ +pub fn contains_v3_1_op(expressions: &[Expression]) -> bool { expressions.iter().any(|expression| { expression.ops.iter().any(|op| { if let Op::Binary(binary) = op { @@ -923,6 +980,38 @@ pub fn contains_v4_op(expressions: &[Expression]) -> bool { }) } +fn contains_v3_3_op(expressions: &[Expression]) -> bool { + expressions.iter().any(|expression| { + expression.ops.iter().any(|op| match op { + Op::Value(term) => contains_v3_3_term(term), + Op::Closure(_, _) => true, + Op::Unary(unary) => matches!(unary, Unary::TypeOf | Unary::Ffi(_)), + Op::Binary(binary) => matches!( + binary, + Binary::HeterogeneousEqual + | Binary::HeterogeneousNotEqual + | Binary::LazyAnd + | Binary::LazyOr + | Binary::All + | Binary::Any + | Binary::Ffi(_) + ), + }) + }) +} + +fn contains_v3_3_predicate(predicate: &Predicate) -> bool { + predicate.terms.iter().any(contains_v3_3_term) +} + +fn contains_v3_3_term(term: &Term) -> bool { + match term { + Term::Null => true, + Term::Set(s) => s.contains(&Term::Null), + _ => false, + } +} + #[cfg(test)] mod tests { use super::*; @@ -984,25 +1073,31 @@ mod tests { println!("adding r2: {}", syms.print_rule(&r2)); w.add_rule(0, &[0].iter().collect(), r2); - w.run_with_limits(&syms, RunLimits { - max_time: Duration::from_secs(10), - ..Default::default() - }).unwrap(); + w.run_with_limits( + &syms, + RunLimits { + max_time: Duration::from_secs(10), + ..Default::default() + }, + ) + .unwrap(); println!("parents:"); - let res = w.query_rule( - rule::( - parent, - &[var(&mut syms, "parent"), var(&mut syms, "child")], - &[pred( + let res = w + .query_rule( + rule::( parent, &[var(&mut syms, "parent"), var(&mut syms, "child")], - )], - ), - 0, - &[0].iter().collect(), - &syms, - ).unwrap(); + &[pred( + parent, + &[var(&mut syms, "parent"), var(&mut syms, "child")], + )], + ), + 0, + &[0].iter().collect(), + &syms, + ) + .unwrap(); for (origin, fact) in res.iterator(&[0].iter().collect()) { println!("\t{:?}\t{}", origin, syms.print_fact(fact)); @@ -1018,7 +1113,7 @@ mod tests { ), 0, &[0].iter().collect(), - &syms + &syms, ) ); println!( @@ -1034,24 +1129,26 @@ mod tests { ), 0, &[0].iter().collect(), - &syms + &syms, ) ); w.add_fact(&[0].iter().collect(), fact(parent, &[&c, &e])); w.run(&syms).unwrap(); - let res = w.query_rule( - rule::( - grandparent, - &[var(&mut syms, "grandparent"), var(&mut syms, "grandchild")], - &[pred( + let res = w + .query_rule( + rule::( grandparent, &[var(&mut syms, "grandparent"), var(&mut syms, "grandchild")], - )], - ), - 0, - &[0].iter().collect(), - &syms, - ).unwrap(); + &[pred( + grandparent, + &[var(&mut syms, "grandparent"), var(&mut syms, "grandchild")], + )], + ), + 0, + &[0].iter().collect(), + &syms, + ) + .unwrap(); println!("grandparents after inserting parent(C, E): {:?}", res); let res = res @@ -1105,26 +1202,28 @@ mod tests { w.add_fact(&[0].iter().collect(), fact(t2, &[&int(1), &bbb, &int(0)])); w.add_fact(&[0].iter().collect(), fact(t2, &[&int(2), &ccc, &int(1)])); - let res = w.query_rule( - rule( - join, - &[var(&mut syms, "left"), var(&mut syms, "right")], - &[ - pred(t1, &[var(&mut syms, "id"), var(&mut syms, "left")]), - pred( - t2, - &[ - var(&mut syms, "t2_id"), - var(&mut syms, "right"), - var(&mut syms, "id"), - ], - ), - ], - ), - 0, - &[0].iter().collect(), - &syms, - ).unwrap(); + let res = w + .query_rule( + rule( + join, + &[var(&mut syms, "left"), var(&mut syms, "right")], + &[ + pred(t1, &[var(&mut syms, "id"), var(&mut syms, "left")]), + pred( + t2, + &[ + var(&mut syms, "t2_id"), + var(&mut syms, "right"), + var(&mut syms, "id"), + ], + ), + ], + ), + 0, + &[0].iter().collect(), + &syms, + ) + .unwrap(); for (_, fact) in res.iter_all() { println!("\t{}", syms.print_fact(fact)); @@ -1145,33 +1244,35 @@ mod tests { assert_eq!(res2, compared); // test constraints - let res = w.query_rule( - expressed_rule( - join, - &[var(&mut syms, "left"), var(&mut syms, "right")], - &[ - pred(t1, &[var(&mut syms, "id"), var(&mut syms, "left")]), - pred( - t2, - &[ - var(&mut syms, "t2_id"), - var(&mut syms, "right"), - var(&mut syms, "id"), - ], - ), - ], - &[Expression { - ops: vec![ - Op::Value(var(&mut syms, "id")), - Op::Value(Term::Integer(1)), - Op::Binary(Binary::LessThan), + let res = w + .query_rule( + expressed_rule( + join, + &[var(&mut syms, "left"), var(&mut syms, "right")], + &[ + pred(t1, &[var(&mut syms, "id"), var(&mut syms, "left")]), + pred( + t2, + &[ + var(&mut syms, "t2_id"), + var(&mut syms, "right"), + var(&mut syms, "id"), + ], + ), ], - }], - ), - 0, - &[0].iter().collect(), - &syms, - ).unwrap(); + &[Expression { + ops: vec![ + Op::Value(var(&mut syms, "id")), + Op::Value(Term::Integer(1)), + Op::Binary(Binary::LessThan), + ], + }], + ), + 0, + &[0].iter().collect(), + &syms, + ) + .unwrap(); for (_, fact) in res.iter_all() { println!("\t{}", syms.print_fact(fact)); @@ -1255,8 +1356,9 @@ mod tests { ), 0, &[0].iter().collect(), - syms, - ).unwrap() + &syms, + ) + .unwrap() .iter_all() .map(|(_, fact)| fact.clone()) .collect() @@ -1407,35 +1509,37 @@ mod tests { w.add_fact(&[0].iter().collect(), fact(x, &[&abc, &int(0), &test])); w.add_fact(&[0].iter().collect(), fact(x, &[&def, &int(2), &hello])); - let res = w.query_rule( - expressed_rule( - int_set, - &[var(&mut syms, "sym"), var(&mut syms, "str")], - &[pred( - x, - &[ - var(&mut syms, "sym"), - var(&mut syms, "int"), - var(&mut syms, "str"), - ], - )], - &[Expression { - ops: vec![ - Op::Value(Term::Set( - [Term::Integer(0), Term::Integer(1)] - .iter() - .cloned() - .collect(), - )), - Op::Value(var(&mut syms, "int")), - Op::Binary(Binary::Contains), - ], - }], - ), - 0, - &[0].iter().collect(), - &syms, - ).unwrap(); + let res = w + .query_rule( + expressed_rule( + int_set, + &[var(&mut syms, "sym"), var(&mut syms, "str")], + &[pred( + x, + &[ + var(&mut syms, "sym"), + var(&mut syms, "int"), + var(&mut syms, "str"), + ], + )], + &[Expression { + ops: vec![ + Op::Value(Term::Set( + [Term::Integer(0), Term::Integer(1)] + .iter() + .cloned() + .collect(), + )), + Op::Value(var(&mut syms, "int")), + Op::Binary(Binary::Contains), + ], + }], + ), + 0, + &[0].iter().collect(), + &syms, + ) + .unwrap(); for (_, fact) in res.iter_all() { println!("\t{}", syms.print_fact(fact)); @@ -1454,37 +1558,39 @@ mod tests { let abc_sym_id = syms.add("abc"); let ghi_sym_id = syms.add("ghi"); - let res = w.query_rule( - expressed_rule( - symbol_set, - &[ - var(&mut syms, "symbol"), - var(&mut syms, "int"), - var(&mut syms, "str"), - ], - &[pred( - x, + let res = w + .query_rule( + expressed_rule( + symbol_set, &[ var(&mut syms, "symbol"), var(&mut syms, "int"), var(&mut syms, "str"), ], - )], - &[Expression { - ops: vec![ - Op::Value(Term::Set( - [abc_sym_id, ghi_sym_id].iter().cloned().collect(), - )), - Op::Value(var(&mut syms, "symbol")), - Op::Binary(Binary::Contains), - Op::Unary(Unary::Negate), - ], - }], - ), - 0, - &[0].iter().collect(), - &syms, - ).unwrap(); + &[pred( + x, + &[ + var(&mut syms, "symbol"), + var(&mut syms, "int"), + var(&mut syms, "str"), + ], + )], + &[Expression { + ops: vec![ + Op::Value(Term::Set( + [abc_sym_id, ghi_sym_id].iter().cloned().collect(), + )), + Op::Value(var(&mut syms, "symbol")), + Op::Binary(Binary::Contains), + Op::Unary(Unary::Negate), + ], + }], + ), + 0, + &[0].iter().collect(), + &syms, + ) + .unwrap(); for (_, fact) in res.iter_all() { println!("\t{}", syms.print_fact(fact)); @@ -1500,34 +1606,36 @@ mod tests { .collect::>(); assert_eq!(res2, compared); - let res = w.query_rule( - expressed_rule( - string_set, - &[ - var(&mut syms, "sym"), - var(&mut syms, "int"), - var(&mut syms, "str"), - ], - &[pred( - x, + let res = w + .query_rule( + expressed_rule( + string_set, &[ var(&mut syms, "sym"), var(&mut syms, "int"), var(&mut syms, "str"), ], - )], - &[Expression { - ops: vec![ - Op::Value(Term::Set([test.clone(), aaa].iter().cloned().collect())), - Op::Value(var(&mut syms, "str")), - Op::Binary(Binary::Contains), - ], - }], - ), - 0, - &[0].iter().collect(), - &syms, - ).unwrap(); + &[pred( + x, + &[ + var(&mut syms, "sym"), + var(&mut syms, "int"), + var(&mut syms, "str"), + ], + )], + &[Expression { + ops: vec![ + Op::Value(Term::Set([test.clone(), aaa].iter().cloned().collect())), + Op::Value(var(&mut syms, "str")), + Op::Binary(Binary::Contains), + ], + }], + ), + 0, + &[0].iter().collect(), + &syms, + ) + .unwrap(); for (_, fact) in res.iter_all() { println!("\t{}", syms.print_fact(fact)); @@ -1565,12 +1673,14 @@ mod tests { w.add_fact(&[0].iter().collect(), fact(right, &[&file2, &read])); w.add_fact(&[0].iter().collect(), fact(right, &[&file1, &write])); - let res = w.query_rule( - rule(check1, &[&file1], &[pred(resource, &[&file1])]), - 0, - &[0].iter().collect(), - &syms, - ).unwrap(); + let res = w + .query_rule( + rule(check1, &[&file1], &[pred(resource, &[&file1])]), + 0, + &[0].iter().collect(), + &syms, + ) + .unwrap(); for (_, fact) in res.iter_all() { println!("\t{}", syms.print_fact(fact)); @@ -1578,20 +1688,22 @@ mod tests { assert!(res.is_empty()); - let res = w.query_rule( - rule( - check2, - &[Term::Variable(0)], - &[ - pred(resource, &[&Term::Variable(0)]), - pred(operation, &[&read]), - pred(right, &[&Term::Variable(0), &read]), - ], - ), - 0, - &[0].iter().collect(), - &syms, - ).unwrap(); + let res = w + .query_rule( + rule( + check2, + &[Term::Variable(0)], + &[ + pred(resource, &[&Term::Variable(0)]), + pred(operation, &[&read]), + pred(right, &[&Term::Variable(0), &read]), + ], + ), + 0, + &[0].iter().collect(), + &syms, + ) + .unwrap(); for (_, fact) in res.iter_all() { println!("\t{}", syms.print_fact(fact)); diff --git a/biscuit-auth/src/datalog/symbol.rs b/biscuit-auth/src/datalog/symbol.rs index a0b41ac2..01c7af7c 100644 --- a/biscuit-auth/src/datalog/symbol.rs +++ b/biscuit-auth/src/datalog/symbol.rs @@ -198,12 +198,42 @@ impl SymbolTable { } } Term::Set(s) => { - let terms = s + if s.is_empty() { + "{,}".to_string() + } else { + let terms = s + .iter() + .map(|term| self.print_term(term)) + .collect::>(); + format!("{{{}}}", terms.join(", ")) + } + } + Term::Null => "null".to_string(), + Term::Array(a) => { + let terms = a .iter() .map(|term| self.print_term(term)) .collect::>(); format!("[{}]", terms.join(", ")) } + Term::Map(m) => { + let terms = m + .iter() + .map(|(key, term)| match key { + crate::datalog::MapKey::Integer(i) => { + format!("{}: {}", i, self.print_term(term)) + } + crate::datalog::MapKey::Str(s) => { + format!( + "\"{}\": {}", + self.print_symbol_default(*s as u64), + self.print_term(term) + ) + } + }) + .collect::>(); + format!("{{{}}}", terms.join(", ")) + } } } @@ -257,7 +287,7 @@ impl SymbolTable { crate::token::Scope::Previous => "previous".to_string(), crate::token::Scope::PublicKey(key_id) => { match self.public_keys.get_key(*key_id) { - Some(key) => format!("ed25519/{}", hex::encode(key.to_bytes())), + Some(key) => key.print(), None => "".to_string(), } } @@ -283,10 +313,11 @@ impl SymbolTable { .collect::>(); format!( - "check {} {}", + "{} {}", match c.kind { - crate::builder::CheckKind::One => "if", - crate::builder::CheckKind::All => "all", + crate::builder::CheckKind::One => "check if", + crate::builder::CheckKind::All => "check all", + crate::builder::CheckKind::Reject => "reject if", }, queries.join(" or ") ) diff --git a/biscuit-auth/src/error.rs b/biscuit-auth/src/error.rs index 35856121..4e61adbe 100644 --- a/biscuit-auth/src/error.rs +++ b/biscuit-auth/src/error.rs @@ -1,7 +1,10 @@ //! error types //! -use std::convert::{From, Infallible}; +use std::{ + convert::{From, Infallible}, + fmt::Display, +}; use thiserror::Error; /// the global error type for Biscuit @@ -16,7 +19,7 @@ pub enum Token { AppendOnSealed, #[error("tried to seal an already sealed token")] AlreadySealed, - #[error("authorization failed")] + #[error("authorization failed: {0}")] FailedLogic(Logic), #[error("error generating Datalog: {0}")] Language(biscuit_parser::error::LanguageError), @@ -168,9 +171,9 @@ pub enum Signature { #[derive(Error, Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde-error", derive(serde::Serialize, serde::Deserialize))] pub enum Logic { - #[error("a rule provided by a block is generating facts with the authority or ambient tag, or has head variables not used in its body")] + #[error("a rule provided by a block is producing a fact with unbound variables")] InvalidBlockRule(u32, String), - #[error("authorization failed")] + #[error("{policy}, and the following checks failed: {}", display_failed_checks(.checks))] Unauthorized { /// the policy that matched policy: MatchedPolicy, @@ -179,7 +182,7 @@ pub enum Logic { }, #[error("the authorizer already contains a token")] AuthorizerNotEmpty, - #[error("no matching policy was found")] + #[error("no matching policy was found, and the following checks failed: {}", display_failed_checks(.checks))] NoMatchingPolicy { /// list of checks that failed validation checks: Vec, @@ -189,9 +192,9 @@ pub enum Logic { #[derive(Error, Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde-error", derive(serde::Serialize, serde::Deserialize))] pub enum MatchedPolicy { - #[error("an allow policy matched")] + #[error("an allow policy matched (policy index: {0})")] Allow(usize), - #[error("a deny policy matched")] + #[error("a deny policy matched (policy index: {0})")] Deny(usize), } @@ -199,12 +202,19 @@ pub enum MatchedPolicy { #[derive(Error, Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde-error", derive(serde::Serialize, serde::Deserialize))] pub enum FailedCheck { - #[error("a check failed in a block")] + #[error("{0}")] Block(FailedBlockCheck), - #[error("a check provided by the authorizer failed")] + #[error("{0}")] Authorizer(FailedAuthorizerCheck), } +fn display_failed_checks(c: &[FailedCheck]) -> String { + c.iter() + .map(|c| c.to_string()) + .collect::>() + .join(", ") +} + #[derive(Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde-error", derive(serde::Serialize, serde::Deserialize))] pub struct FailedBlockCheck { @@ -214,6 +224,16 @@ pub struct FailedBlockCheck { pub rule: String, } +impl Display for FailedBlockCheck { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Check n°{} in block n°{}: {}", + self.check_id, self.block_id, self.rule + ) + } +} + #[derive(Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde-error", derive(serde::Serialize, serde::Deserialize))] pub struct FailedAuthorizerCheck { @@ -222,6 +242,12 @@ pub struct FailedAuthorizerCheck { pub rule: String, } +impl Display for FailedAuthorizerCheck { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Check n°{} in authorizer: {}", self.check_id, self.rule) + } +} + /// Datalog execution errors #[derive(Error, Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde-error", derive(serde::Serialize, serde::Deserialize))] @@ -248,6 +274,12 @@ pub enum Expression { DivideByZero, #[error("Wrong number of elements on stack")] InvalidStack, + #[error("Shadowed variable")] + ShadowedVariable, + #[error("Undefined extern func: {0}")] + UndefinedExtern(String), + #[error("Error while evaluating extern func {0}: {1}")] + ExternEvalError(String, String), } /// runtime limits errors @@ -277,5 +309,27 @@ mod tests { format!("{}", Token::Base64(Base64Error::InvalidLength)), "Cannot decode base64 token: Encoded text cannot have a 6-bit remainder." ); + + assert_eq!( + format!( + "{}", + Token::FailedLogic(Logic::Unauthorized { + policy: MatchedPolicy::Allow(0), + checks: vec![ + FailedCheck::Authorizer(FailedAuthorizerCheck { + check_id: 0, + rule: "check if false".to_string() + }), + FailedCheck::Block(FailedBlockCheck { + block_id: 0, + check_id: 0, + rule: "check if false".to_string() + }) + ] + }) + ) + .to_string(), + "authorization failed: an allow policy matched (policy index: 0), and the following checks failed: Check n°0 in authorizer: check if false, Check n°0 in block n°0: check if false" + ); } } diff --git a/biscuit-auth/src/format/convert.rs b/biscuit-auth/src/format/convert.rs index d1e2cef2..df8fa290 100644 --- a/biscuit-auth/src/format/convert.rs +++ b/biscuit-auth/src/format/convert.rs @@ -10,7 +10,7 @@ use crate::error; use crate::token::public_keys::PublicKeys; use crate::token::Scope; use crate::token::{authorizer::AuthorizerPolicies, Block}; -use crate::token::{MAX_SCHEMA_VERSION, MIN_SCHEMA_VERSION}; +use crate::token::{DATALOG_3_1, DATALOG_3_2, DATALOG_3_3, MAX_SCHEMA_VERSION, MIN_SCHEMA_VERSION}; pub fn token_block_to_proto_block(input: &Block) -> schema::Block { schema::Block { @@ -71,15 +71,27 @@ pub fn proto_block_to_token_block( rules.push(v2::proto_rule_to_token_rule(rule, version)?.0); } - if version == MIN_SCHEMA_VERSION && input.checks_v2.iter().any(|c| c.kind.is_some()) { - return Err(error::Format::DeserializationError( - "deserialization error: v3 blocks must not contain a check kind".to_string(), - )); + if version < MAX_SCHEMA_VERSION { + for c in input.checks_v2.iter() { + if version < DATALOG_3_1 && c.kind.is_some() { + return Err(error::Format::DeserializationError( + "deserialization error: check kinds are only supported on datalog v3.1+ blocks" + .to_string(), + )); + } else if version < DATALOG_3_3 && c.kind == Some(schema::check_v2::Kind::Reject as i32) + { + return Err(error::Format::DeserializationError( + "deserialization error: reject if is only supported in datalog v3.3+" + .to_string(), + )); + } + } } - if version != MAX_SCHEMA_VERSION && external_key.is_some() { + if version < DATALOG_3_2 && external_key.is_some() { return Err(error::Format::DeserializationError( - "deserialization error: third-party blocks must be v5".to_string(), + "deserialization error: third-party blocks are only supported in datalog v3.2+" + .to_string(), )); } @@ -308,8 +320,12 @@ pub mod v2 { use crate::builder::Convert; use crate::datalog::*; use crate::error; + use crate::format::schema::Empty; + use crate::format::schema::MapEntry; use crate::token::Scope; - use crate::token::MIN_SCHEMA_VERSION; + use crate::token::DATALOG_3_1; + + use std::collections::BTreeMap; use std::collections::BTreeSet; pub fn token_fact_to_proto_fact(input: &Fact) -> schema::FactV2 { @@ -332,6 +348,7 @@ pub mod v2 { kind: match input.kind { crate::token::builder::CheckKind::One => None, crate::token::builder::CheckKind::All => Some(Kind::All as i32), + crate::token::builder::CheckKind::Reject => Some(Kind::Reject as i32), }, } } @@ -349,6 +366,7 @@ pub mod v2 { let kind = match input.kind { None | Some(0) => crate::token::builder::CheckKind::One, Some(1) => crate::token::builder::CheckKind::All, + Some(2) => crate::token::builder::CheckKind::Reject, _ => { return Err(error::Format::DeserializationError( "deserialization error: invalid check kind".to_string(), @@ -444,9 +462,9 @@ pub mod v2 { expressions.push(proto_expression_to_token_expression(c)?); } - if version == MIN_SCHEMA_VERSION && !input.scope.is_empty() { + if version < DATALOG_3_1 && !input.scope.is_empty() { return Err(error::Format::DeserializationError( - "deserialization error: v3 blocks must not have scopes".to_string(), + "deserialization error: scopes are only supported in datalog v3.1+".to_string(), )); } @@ -514,6 +532,35 @@ pub mod v2 { set: s.iter().map(token_term_to_proto_id).collect(), })), }, + Term::Null => schema::TermV2 { + content: Some(Content::Null(Empty {})), + }, + Term::Array(a) => schema::TermV2 { + content: Some(Content::Array(schema::Array { + array: a.iter().map(token_term_to_proto_id).collect(), + })), + }, + Term::Map(m) => schema::TermV2 { + content: Some(Content::Map(schema::Map { + entries: m + .iter() + .map(|(key, term)| { + let key = match key { + MapKey::Integer(i) => schema::MapKey { + content: Some(schema::map_key::Content::Integer(*i)), + }, + MapKey::Str(s) => schema::MapKey { + content: Some(schema::map_key::Content::String(*s)), + }, + }; + schema::MapEntry { + key, + value: token_term_to_proto_id(term), + } + }) + .collect(), + })), + }, } } @@ -551,6 +598,9 @@ pub mod v2 { "deserialization error: sets cannot contain other sets".to_string(), )); } + Some(Content::Null(_)) => 8, + Some(Content::Array(_)) => 9, + Some(Content::Map(_)) => 10, None => { return Err(error::Format::DeserializationError( "deserialization error: ID content enum is empty".to_string(), @@ -574,121 +624,223 @@ pub mod v2 { Ok(Term::Set(set)) } - } - } + Some(Content::Null(_)) => Ok(Term::Null), + Some(Content::Array(a)) => { + let array = a + .array + .iter() + .map(proto_id_to_token_term) + .collect::>()?; + + Ok(Term::Array(array)) + } + Some(Content::Map(m)) => { + let mut map = BTreeMap::new(); - pub fn token_expression_to_proto_expression(input: &Expression) -> schema::ExpressionV2 { - schema::ExpressionV2 { - ops: input - .ops - .iter() - .map(|op| { - let content = match op { - Op::Value(i) => schema::op::Content::Value(token_term_to_proto_id(i)), - Op::Unary(u) => { - use schema::op_unary::Kind; - - schema::op::Content::Unary(schema::OpUnary { - kind: match u { - Unary::Negate => Kind::Negate, - Unary::Parens => Kind::Parens, - Unary::Length => Kind::Length, - } as i32, - }) - } - Op::Binary(b) => { - use schema::op_binary::Kind; - - schema::op::Content::Binary(schema::OpBinary { - kind: match b { - Binary::LessThan => Kind::LessThan, - Binary::GreaterThan => Kind::GreaterThan, - Binary::LessOrEqual => Kind::LessOrEqual, - Binary::GreaterOrEqual => Kind::GreaterOrEqual, - Binary::Equal => Kind::Equal, - Binary::Contains => Kind::Contains, - Binary::Prefix => Kind::Prefix, - Binary::Suffix => Kind::Suffix, - Binary::Regex => Kind::Regex, - Binary::Add => Kind::Add, - Binary::Sub => Kind::Sub, - Binary::Mul => Kind::Mul, - Binary::Div => Kind::Div, - Binary::And => Kind::And, - Binary::Or => Kind::Or, - Binary::Intersection => Kind::Intersection, - Binary::Union => Kind::Union, - Binary::BitwiseAnd => Kind::BitwiseAnd, - Binary::BitwiseOr => Kind::BitwiseOr, - Binary::BitwiseXor => Kind::BitwiseXor, - Binary::NotEqual => Kind::NotEqual, - } as i32, - }) + for MapEntry { key, value } in m.entries.iter() { + let key = match key.content { + Some(schema::map_key::Content::Integer(i)) => MapKey::Integer(i), + Some(schema::map_key::Content::String(s)) => MapKey::Str(s), + None => { + return Err(error::Format::DeserializationError( + "deserialization error: ID content enum is empty".to_string(), + )) } }; - schema::Op { - content: Some(content), - } + map.insert(key, proto_id_to_token_term(&value)?); + } + + Ok(Term::Map(map)) + } + } + } + + fn token_op_to_proto_op(op: &Op) -> schema::Op { + let content = match op { + Op::Value(i) => schema::op::Content::Value(token_term_to_proto_id(i)), + Op::Unary(u) => { + use schema::op_unary::Kind; + + schema::op::Content::Unary(schema::OpUnary { + kind: match u { + Unary::Negate => Kind::Negate, + Unary::Parens => Kind::Parens, + Unary::Length => Kind::Length, + Unary::TypeOf => Kind::TypeOf, + Unary::Ffi(_) => Kind::Ffi, + } as i32, + ffi_name: match u { + Unary::Ffi(name) => Some(name.to_owned()), + _ => None, + }, }) - .collect(), + } + Op::Binary(b) => { + use schema::op_binary::Kind; + + schema::op::Content::Binary(schema::OpBinary { + kind: match b { + Binary::LessThan => Kind::LessThan, + Binary::GreaterThan => Kind::GreaterThan, + Binary::LessOrEqual => Kind::LessOrEqual, + Binary::GreaterOrEqual => Kind::GreaterOrEqual, + Binary::Equal => Kind::Equal, + Binary::Contains => Kind::Contains, + Binary::Prefix => Kind::Prefix, + Binary::Suffix => Kind::Suffix, + Binary::Regex => Kind::Regex, + Binary::Add => Kind::Add, + Binary::Sub => Kind::Sub, + Binary::Mul => Kind::Mul, + Binary::Div => Kind::Div, + Binary::And => Kind::And, + Binary::Or => Kind::Or, + Binary::Intersection => Kind::Intersection, + Binary::Union => Kind::Union, + Binary::BitwiseAnd => Kind::BitwiseAnd, + Binary::BitwiseOr => Kind::BitwiseOr, + Binary::BitwiseXor => Kind::BitwiseXor, + Binary::NotEqual => Kind::NotEqual, + Binary::HeterogeneousEqual => Kind::HeterogeneousEqual, + Binary::HeterogeneousNotEqual => Kind::HeterogeneousNotEqual, + Binary::LazyAnd => Kind::LazyAnd, + Binary::LazyOr => Kind::LazyOr, + Binary::All => Kind::All, + Binary::Any => Kind::Any, + Binary::Get => Kind::Get, + Binary::Ffi(_) => Kind::Ffi, + } as i32, + ffi_name: match b { + Binary::Ffi(name) => Some(name.to_owned()), + _ => None, + }, + }) + } + Op::Closure(params, ops) => schema::op::Content::Closure(schema::OpClosure { + params: params.clone(), + ops: ops.iter().map(token_op_to_proto_op).collect(), + }), + }; + + schema::Op { + content: Some(content), } } - pub fn proto_expression_to_token_expression( - input: &schema::ExpressionV2, - ) -> Result { - use schema::{op, op_binary, op_unary}; - let mut ops = Vec::new(); + pub fn token_expression_to_proto_expression(input: &Expression) -> schema::ExpressionV2 { + schema::ExpressionV2 { + ops: input.ops.iter().map(token_op_to_proto_op).collect(), + } + } - for op in input.ops.iter() { - let translated = match op.content.as_ref() { - Some(op::Content::Value(id)) => Op::Value(proto_id_to_token_term(id)?), - Some(op::Content::Unary(u)) => match op_unary::Kind::from_i32(u.kind) { - Some(op_unary::Kind::Negate) => Op::Unary(Unary::Negate), - Some(op_unary::Kind::Parens) => Op::Unary(Unary::Parens), - Some(op_unary::Kind::Length) => Op::Unary(Unary::Length), - None => { + fn proto_op_to_token_op(op: &schema::Op) -> Result { + use schema::{op, op_binary, op_unary}; + Ok(match op.content.as_ref() { + Some(op::Content::Value(id)) => Op::Value(proto_id_to_token_term(id)?), + Some(op::Content::Unary(u)) => { + match (op_unary::Kind::from_i32(u.kind), u.ffi_name.as_ref()) { + (Some(op_unary::Kind::Negate), None) => Op::Unary(Unary::Negate), + (Some(op_unary::Kind::Parens), None) => Op::Unary(Unary::Parens), + (Some(op_unary::Kind::Length), None) => Op::Unary(Unary::Length), + (Some(op_unary::Kind::TypeOf), None) => Op::Unary(Unary::TypeOf), + (Some(op_unary::Kind::Ffi), Some(n)) => Op::Unary(Unary::Ffi(*n)), + (Some(op_unary::Kind::Ffi), None) => { + return Err(error::Format::DeserializationError( + "deserialization error: missing ffi name".to_string(), + )) + } + (Some(_), Some(_)) => { + return Err(error::Format::DeserializationError( + "deserialization error: ffi name set on a regular unary operation" + .to_string(), + )) + } + (None, _) => { return Err(error::Format::DeserializationError( "deserialization error: unary operation is empty".to_string(), )) } - }, - Some(op::Content::Binary(b)) => match op_binary::Kind::from_i32(b.kind) { - Some(op_binary::Kind::LessThan) => Op::Binary(Binary::LessThan), - Some(op_binary::Kind::GreaterThan) => Op::Binary(Binary::GreaterThan), - Some(op_binary::Kind::LessOrEqual) => Op::Binary(Binary::LessOrEqual), - Some(op_binary::Kind::GreaterOrEqual) => Op::Binary(Binary::GreaterOrEqual), - Some(op_binary::Kind::Equal) => Op::Binary(Binary::Equal), - Some(op_binary::Kind::Contains) => Op::Binary(Binary::Contains), - Some(op_binary::Kind::Prefix) => Op::Binary(Binary::Prefix), - Some(op_binary::Kind::Suffix) => Op::Binary(Binary::Suffix), - Some(op_binary::Kind::Regex) => Op::Binary(Binary::Regex), - Some(op_binary::Kind::Add) => Op::Binary(Binary::Add), - Some(op_binary::Kind::Sub) => Op::Binary(Binary::Sub), - Some(op_binary::Kind::Mul) => Op::Binary(Binary::Mul), - Some(op_binary::Kind::Div) => Op::Binary(Binary::Div), - Some(op_binary::Kind::And) => Op::Binary(Binary::And), - Some(op_binary::Kind::Or) => Op::Binary(Binary::Or), - Some(op_binary::Kind::Intersection) => Op::Binary(Binary::Intersection), - Some(op_binary::Kind::Union) => Op::Binary(Binary::Union), - Some(op_binary::Kind::BitwiseAnd) => Op::Binary(Binary::BitwiseAnd), - 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), - None => { + } + } + Some(op::Content::Binary(b)) => { + match (op_binary::Kind::from_i32(b.kind), b.ffi_name.as_ref()) { + (Some(op_binary::Kind::LessThan), None) => Op::Binary(Binary::LessThan), + (Some(op_binary::Kind::GreaterThan), None) => Op::Binary(Binary::GreaterThan), + (Some(op_binary::Kind::LessOrEqual), None) => Op::Binary(Binary::LessOrEqual), + (Some(op_binary::Kind::GreaterOrEqual), None) => { + Op::Binary(Binary::GreaterOrEqual) + } + (Some(op_binary::Kind::Equal), None) => Op::Binary(Binary::Equal), + (Some(op_binary::Kind::Contains), None) => Op::Binary(Binary::Contains), + (Some(op_binary::Kind::Prefix), None) => Op::Binary(Binary::Prefix), + (Some(op_binary::Kind::Suffix), None) => Op::Binary(Binary::Suffix), + (Some(op_binary::Kind::Regex), None) => Op::Binary(Binary::Regex), + (Some(op_binary::Kind::Add), None) => Op::Binary(Binary::Add), + (Some(op_binary::Kind::Sub), None) => Op::Binary(Binary::Sub), + (Some(op_binary::Kind::Mul), None) => Op::Binary(Binary::Mul), + (Some(op_binary::Kind::Div), None) => Op::Binary(Binary::Div), + (Some(op_binary::Kind::And), None) => Op::Binary(Binary::And), + (Some(op_binary::Kind::Or), None) => Op::Binary(Binary::Or), + (Some(op_binary::Kind::Intersection), None) => Op::Binary(Binary::Intersection), + (Some(op_binary::Kind::Union), None) => Op::Binary(Binary::Union), + (Some(op_binary::Kind::BitwiseAnd), None) => Op::Binary(Binary::BitwiseAnd), + (Some(op_binary::Kind::BitwiseOr), None) => Op::Binary(Binary::BitwiseOr), + (Some(op_binary::Kind::BitwiseXor), None) => Op::Binary(Binary::BitwiseXor), + (Some(op_binary::Kind::NotEqual), None) => Op::Binary(Binary::NotEqual), + (Some(op_binary::Kind::HeterogeneousEqual), None) => { + Op::Binary(Binary::HeterogeneousEqual) + } + (Some(op_binary::Kind::HeterogeneousNotEqual), None) => { + Op::Binary(Binary::HeterogeneousNotEqual) + } + (Some(op_binary::Kind::LazyAnd), None) => Op::Binary(Binary::LazyAnd), + (Some(op_binary::Kind::LazyOr), None) => Op::Binary(Binary::LazyOr), + (Some(op_binary::Kind::All), None) => Op::Binary(Binary::All), + (Some(op_binary::Kind::Any), None) => Op::Binary(Binary::Any), + (Some(op_binary::Kind::Get), None) => Op::Binary(Binary::Get), + (Some(op_binary::Kind::Ffi), Some(n)) => Op::Binary(Binary::Ffi(*n)), + (Some(op_binary::Kind::Ffi), None) => { + return Err(error::Format::DeserializationError( + "deserialization error: missing ffi name".to_string(), + )) + } + (Some(_), Some(_)) => { + return Err(error::Format::DeserializationError( + "deserialization error: ffi name set on a regular binary operation" + .to_string(), + )) + } + (None, _) => { return Err(error::Format::DeserializationError( "deserialization error: binary operation is empty".to_string(), )) } - }, - None => { - return Err(error::Format::DeserializationError( - "deserialization error: operation is empty".to_string(), - )) } - }; - ops.push(translated); + } + Some(op::Content::Closure(op_closure)) => Op::Closure( + op_closure.params.clone(), + op_closure + .ops + .iter() + .map(proto_op_to_token_op) + .collect::>()?, + ), + None => { + return Err(error::Format::DeserializationError( + "deserialization error: operation is empty".to_string(), + )) + } + }) + } + + pub fn proto_expression_to_token_expression( + input: &schema::ExpressionV2, + ) -> Result { + let mut ops = Vec::new(); + + for op in input.ops.iter() { + ops.push(proto_op_to_token_op(op)?); } Ok(Expression { ops }) diff --git a/biscuit-auth/src/format/mod.rs b/biscuit-auth/src/format/mod.rs index 90816111..33a795d8 100644 --- a/biscuit-auth/src/format/mod.rs +++ b/biscuit-auth/src/format/mod.rs @@ -6,15 +6,15 @@ //! - serialization of a wrapper structure containing serialized blocks and the signature use super::crypto::{self, KeyPair, PrivateKey, PublicKey, TokenNext}; -use ed25519_dalek::Signer; use prost::Message; use super::error; use super::token::Block; use crate::crypto::ExternalSignature; +use crate::crypto::Signature; use crate::datalog::SymbolTable; use crate::token::RootKeyProvider; -use std::convert::TryInto; +use crate::token::DATALOG_3_3; /// Structures generated from the Protobuf schema pub mod schema; /*{ @@ -25,6 +25,9 @@ pub mod convert; use self::convert::*; +pub(crate) const THIRD_PARTY_SIGNATURE_VERSION: u32 = 1; +pub(crate) const DATALOG_3_3_SIGNATURE_VERSION: u32 = 1; +pub(crate) const NON_ED25519_SIGNATURE_VERSION: u32 = 1; /// Intermediate structure for token serialization /// /// This structure contains the blocks serialized to byte arrays. Those arrays @@ -42,7 +45,10 @@ impl SerializedBiscuit { where KP: RootKeyProvider, { - let deser = SerializedBiscuit::deserialize(slice)?; + let deser = SerializedBiscuit::deserialize( + slice, + ThirdPartyVerificationMode::PreviousSignatureHashing, + )?; let root = key_provider.choose(deser.root_key_id)?; deser.verify(&root)?; @@ -50,18 +56,34 @@ impl SerializedBiscuit { Ok(deser) } - pub(crate) fn deserialize(slice: &[u8]) -> Result { + pub(crate) fn unsafe_from_slice( + slice: &[u8], + key_provider: KP, + ) -> Result + where + KP: RootKeyProvider, + { + let deser = + SerializedBiscuit::deserialize(slice, ThirdPartyVerificationMode::UnsafeLegacy)?; + + let root = key_provider.choose(deser.root_key_id)?; + deser.verify_inner(&root, ThirdPartyVerificationMode::UnsafeLegacy)?; + + Ok(deser) + } + + pub(crate) fn deserialize( + slice: &[u8], + verification_mode: ThirdPartyVerificationMode, + ) -> Result { let data = schema::Biscuit::decode(slice).map_err(|e| { error::Format::DeserializationError(format!("deserialization error: {:?}", e)) })?; let next_key = PublicKey::from_proto(&data.authority.next_key)?; + let mut next_key_algorithm = next_key.algorithm(); - let bytes: [u8; 64] = (&data.authority.signature[..]) - .try_into() - .map_err(|_| error::Format::InvalidSignatureSize(data.authority.signature.len()))?; - - let signature = ed25519_dalek::Signature::from_bytes(&bytes); + let signature = Signature::from_vec(data.authority.signature); if data.authority.external_signature.is_some() { return Err(error::Format::DeserializationError( @@ -74,26 +96,27 @@ impl SerializedBiscuit { next_key, signature, external_signature: None, + version: data.authority.version.unwrap_or_default(), }; let mut blocks = Vec::new(); - for block in &data.blocks { + for block in data.blocks { let next_key = PublicKey::from_proto(&block.next_key)?; + next_key_algorithm = next_key.algorithm(); - let bytes: [u8; 64] = (&block.signature[..]) - .try_into() - .map_err(|_| error::Format::InvalidSignatureSize(block.signature.len()))?; + let signature = Signature::from_vec(block.signature); - let signature = ed25519_dalek::Signature::from_bytes(&bytes); + let external_signature = if let Some(ex) = block.external_signature { + if verification_mode == ThirdPartyVerificationMode::PreviousSignatureHashing + && block.version != Some(THIRD_PARTY_SIGNATURE_VERSION) + { + return Err(error::Format::DeserializationError( + "Unsupported third party block version".to_string(), + )); + } - let external_signature = if let Some(ex) = block.external_signature.as_ref() { let public_key = PublicKey::from_proto(&ex.public_key)?; - - let bytes: [u8; 64] = (&ex.signature[..]) - .try_into() - .map_err(|_| error::Format::InvalidSignatureSize(ex.signature.len()))?; - - let signature = ed25519_dalek::Signature::from_bytes(&bytes); + let signature = Signature::from_vec(ex.signature); Some(ExternalSignature { public_key, @@ -108,6 +131,7 @@ impl SerializedBiscuit { next_key, signature, external_signature, + version: block.version.unwrap_or_default(), }); } @@ -118,13 +142,17 @@ impl SerializedBiscuit { )) } Some(schema::proof::Content::NextSecret(v)) => { - TokenNext::Secret(PrivateKey::from_bytes(&v)?) + let next_key_algorithm = match next_key_algorithm { + schema::public_key::Algorithm::Ed25519 => crate::builder::Algorithm::Ed25519, + schema::public_key::Algorithm::Secp256r1 => { + crate::builder::Algorithm::Secp256r1 + } + }; + TokenNext::Secret(PrivateKey::from_bytes(&v, next_key_algorithm)?) } Some(schema::proof::Content::FinalSignature(v)) => { - let bytes: [u8; 64] = (&v[..]) - .try_into() - .map_err(|_| error::Format::InvalidSignatureSize(v.len()))?; - let signature = ed25519_dalek::Signature::from_bytes(&bytes); + let signature = Signature::from_vec(v); + TokenNext::Seal(signature) } }; @@ -198,6 +226,11 @@ impl SerializedBiscuit { next_key: self.authority.next_key.to_proto(), signature: self.authority.signature.to_bytes().to_vec(), external_signature: None, + version: if self.authority.version > 0 { + Some(self.authority.version) + } else { + None + }, }; let mut blocks = Vec::new(); @@ -212,6 +245,11 @@ impl SerializedBiscuit { public_key: external_signature.public_key.to_proto(), } }), + version: if block.version > 0 { + Some(block.version) + } else { + None + }, }; blocks.push(b); @@ -255,6 +293,30 @@ impl SerializedBiscuit { root_keypair: &KeyPair, next_keypair: &KeyPair, authority: &Block, + ) -> Result { + let authority_signature_version = block_signature_version( + root_keypair, + next_keypair, + &None, + &Some(authority.version), + std::iter::empty(), + ); + Self::new_inner( + root_key_id, + root_keypair, + next_keypair, + authority, + authority_signature_version, + ) + } + + /// creates a new token + pub(crate) fn new_inner( + root_key_id: Option, + root_keypair: &KeyPair, + next_keypair: &KeyPair, + authority: &Block, + authority_signature_version: u32, ) -> Result { let mut v = Vec::new(); token_block_to_proto_block(authority) @@ -263,7 +325,12 @@ impl SerializedBiscuit { error::Format::SerializationError(format!("serialization error: {:?}", e)) })?; - let signature = crypto::sign(root_keypair, next_keypair, &v)?; + let signature = crypto::sign_authority_block( + root_keypair, + next_keypair, + &v, + authority_signature_version, + )?; Ok(SerializedBiscuit { root_key_id, @@ -272,6 +339,7 @@ impl SerializedBiscuit { next_key: next_keypair.public(), signature, external_signature: None, + version: authority_signature_version, }, blocks: vec![], proof: TokenNext::Secret(next_keypair.private()), @@ -293,11 +361,28 @@ impl SerializedBiscuit { .map_err(|e| { error::Format::SerializationError(format!("serialization error: {:?}", e)) })?; - if let Some(signature) = &external_signature { - v.extend_from_slice(&signature.signature.to_bytes()); - } - let signature = crypto::sign(&keypair, next_keypair, &v)?; + let signature_version = block_signature_version( + &keypair, + next_keypair, + &external_signature, + &Some(block.version), + // std::iter::once(self.authority.version) + // .chain(self.blocks.iter().map(|block| block.version)), + self.blocks + .iter() + .chain([&self.authority]) + .map(|block| block.version), + ); + + let signature = crypto::sign_block( + &keypair, + next_keypair, + &v, + external_signature.as_ref(), + &self.last_block().signature, + signature_version, + )?; // Add new block let mut blocks = self.blocks.clone(); @@ -306,6 +391,7 @@ impl SerializedBiscuit { next_key: next_keypair.public(), signature, external_signature, + version: signature_version, }); Ok(SerializedBiscuit { @@ -325,12 +411,25 @@ impl SerializedBiscuit { ) -> Result { let keypair = self.proof.keypair()?; - let mut v = block.clone(); - if let Some(signature) = &external_signature { - v.extend_from_slice(&signature.signature.to_bytes()); - } - - let signature = crypto::sign(&keypair, next_keypair, &v)?; + let signature_version = block_signature_version( + &keypair, + next_keypair, + &external_signature, + // The version block is not directly available, so we don’t take it into account here + // `append_serialized` is only used for third-party blocks anyway, so maybe we should make `external_signature` mandatory and not bother + &None, + std::iter::once(self.authority.version) + .chain(self.blocks.iter().map(|block| block.version)), + ); + + let signature = crypto::sign_block( + &keypair, + next_keypair, + &block, + external_signature.as_ref(), + &self.last_block().signature, + signature_version, + )?; // Add new block let mut blocks = self.blocks.clone(); @@ -339,6 +438,7 @@ impl SerializedBiscuit { next_key: next_keypair.public(), signature, external_signature, + version: signature_version, }); Ok(SerializedBiscuit { @@ -351,15 +451,38 @@ impl SerializedBiscuit { /// checks the signature on a deserialized token pub fn verify(&self, root: &PublicKey) -> Result<(), error::Format> { + self.verify_inner(root, ThirdPartyVerificationMode::PreviousSignatureHashing) + } + + pub(crate) fn verify_inner( + &self, + root: &PublicKey, + verification_mode: ThirdPartyVerificationMode, + ) -> Result<(), error::Format> { //FIXME: try batched signature verification let mut current_pub = root; + let mut previous_signature; - crypto::verify_block_signature(&self.authority, current_pub)?; + crypto::verify_authority_block_signature(&self.authority, current_pub)?; current_pub = &self.authority.next_key; + previous_signature = &self.authority.signature; for block in &self.blocks { - crypto::verify_block_signature(block, current_pub)?; + let verification_mode = match (block.version, verification_mode) { + (0, ThirdPartyVerificationMode::UnsafeLegacy) => { + ThirdPartyVerificationMode::UnsafeLegacy + } + _ => ThirdPartyVerificationMode::PreviousSignatureHashing, + }; + + crypto::verify_block_signature( + block, + current_pub, + previous_signature, + verification_mode, + )?; current_pub = &block.next_key; + previous_signature = &block.signature; } match &self.proof { @@ -374,26 +497,15 @@ impl SerializedBiscuit { } TokenNext::Seal(signature) => { //FIXME: replace with SHA512 hashing - let mut to_verify = Vec::new(); - let block = if self.blocks.is_empty() { &self.authority } else { &self.blocks[self.blocks.len() - 1] }; - to_verify.extend(&block.data); - to_verify.extend( - &(crate::format::schema::public_key::Algorithm::Ed25519 as i32).to_le_bytes(), - ); - to_verify.extend(&block.next_key.to_bytes()); - to_verify.extend(&block.signature.to_bytes()); - - current_pub - .0 - .verify_strict(&to_verify, signature) - .map_err(|s| s.to_string()) - .map_err(error::Signature::InvalidSignature) - .map_err(error::Format::Signature)?; + + let to_verify = crypto::generate_seal_signature_payload_v0(block); + + current_pub.verify_signature(&to_verify, &signature)?; } } @@ -404,24 +516,15 @@ impl SerializedBiscuit { let keypair = self.proof.keypair()?; //FIXME: replace with SHA512 hashing - let mut to_sign = Vec::new(); let block = if self.blocks.is_empty() { &self.authority } else { &self.blocks[self.blocks.len() - 1] }; - to_sign.extend(&block.data); - to_sign - .extend(&(crate::format::schema::public_key::Algorithm::Ed25519 as i32).to_le_bytes()); - to_sign.extend(&block.next_key.to_bytes()); - to_sign.extend(&block.signature.to_bytes()); - - let signature = keypair - .kp - .try_sign(&to_sign) - .map_err(|s| s.to_string()) - .map_err(error::Signature::InvalidSignatureGeneration) - .map_err(error::Format::Signature)?; + + let to_sign = crypto::generate_seal_signature_payload_v0(block); + + let signature = keypair.sign(&to_sign)?; Ok(SerializedBiscuit { root_key_id: self.root_key_id, @@ -430,12 +533,61 @@ impl SerializedBiscuit { proof: TokenNext::Seal(signature), }) } + + pub(crate) fn last_block(&self) -> &crypto::Block { + self.blocks.last().unwrap_or(&self.authority) + } +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub(crate) enum ThirdPartyVerificationMode { + UnsafeLegacy, + PreviousSignatureHashing, +} + +fn block_signature_version( + block_keypair: &KeyPair, + next_keypair: &KeyPair, + external_signature: &Option, + block_version: &Option, + previous_blocks_sig_versions: I, +) -> u32 +where + I: Iterator, +{ + if external_signature.is_some() { + return THIRD_PARTY_SIGNATURE_VERSION; + } + + match block_version { + Some(block_version) if *block_version >= DATALOG_3_3 => { + return DATALOG_3_3_SIGNATURE_VERSION; + } + _ => {} + } + + match (block_keypair, next_keypair) { + (KeyPair::Ed25519(_), KeyPair::Ed25519(_)) => {} + _ => { + return NON_ED25519_SIGNATURE_VERSION; + } + } + + previous_blocks_sig_versions.max().unwrap_or(0) } #[cfg(test)] mod tests { use std::io::Read; + use crate::{ + builder::Algorithm, + crypto::{ExternalSignature, Signature}, + format::block_signature_version, + token::{DATALOG_3_1, DATALOG_3_3}, + KeyPair, + }; + #[test] fn proto() { // somehow when building under cargo-tarpaulin, OUT_DIR is not set @@ -461,4 +613,88 @@ mod tests { panic!(); } } + + #[test] + fn test_block_signature_version() { + assert_eq!( + block_signature_version( + &KeyPair::new(), + &KeyPair::new(), + &None, + &Some(DATALOG_3_1), + std::iter::empty() + ), + 0, + "ed25519 everywhere, authority block, no new datalog features" + ); + assert_eq!( + block_signature_version( + &KeyPair::new_with_algorithm(Algorithm::Secp256r1), + &KeyPair::new_with_algorithm(Algorithm::Ed25519), + &None, + &Some(DATALOG_3_1), + std::iter::empty() + ), + 1, + "s256r1 root key, authority block, no new datalog features" + ); + assert_eq!( + block_signature_version( + &KeyPair::new_with_algorithm(Algorithm::Ed25519), + &KeyPair::new_with_algorithm(Algorithm::Secp256r1), + &None, + &Some(DATALOG_3_1), + std::iter::empty() + ), + 1, + "s256r1 next key, authority block, no new datalog features" + ); + assert_eq!( + block_signature_version( + &KeyPair::new_with_algorithm(Algorithm::Secp256r1), + &KeyPair::new_with_algorithm(Algorithm::Secp256r1), + &None, + &Some(DATALOG_3_1), + std::iter::empty() + ), + 1, + "s256r1 root & next key, authority block, no new datalog features" + ); + assert_eq!( + block_signature_version( + &KeyPair::new(), + &KeyPair::new(), + &Some(ExternalSignature { + public_key: KeyPair::new().public(), + signature: Signature::from_vec(Vec::new()) + }), + &Some(DATALOG_3_1), + std::iter::once(0) + ), + 1, + "ed25519 root & next key, third-party block, no new datalog features" + ); + assert_eq!( + block_signature_version( + &KeyPair::new(), + &KeyPair::new(), + &None, + &Some(DATALOG_3_3), + std::iter::empty() + ), + 1, + "ed25519 root & next key, first-party block, new datalog features" + ); + assert_eq!( + block_signature_version( + &KeyPair::new(), + &KeyPair::new(), + &None, + &Some(DATALOG_3_1), + std::iter::once(1) + ), + 1, + "ed25519 root & next key, first-party block, no new datalog features, previous v1 block" + ); + } } diff --git a/biscuit-auth/src/format/schema.proto b/biscuit-auth/src/format/schema.proto index bc6cb295..21a13edd 100644 --- a/biscuit-auth/src/format/schema.proto +++ b/biscuit-auth/src/format/schema.proto @@ -14,6 +14,7 @@ message SignedBlock { required PublicKey nextKey = 2; required bytes signature = 3; optional ExternalSignature externalSignature = 4; + optional uint32 version = 5; } message ExternalSignature { @@ -26,12 +27,12 @@ message PublicKey { enum Algorithm { Ed25519 = 0; + SECP256R1 = 1; } required bytes key = 2; } - message Proof { oneof Content { bytes nextSecret = 1; @@ -80,6 +81,7 @@ message CheckV2 { enum Kind { One = 0; All = 1; + Reject = 2; } } @@ -97,6 +99,9 @@ message TermV2 { bytes bytes = 5; bool bool = 6; TermSet set = 7; + Empty null = 8; + Array array = 9; + Map map = 10; } } @@ -104,6 +109,26 @@ message TermSet { repeated TermV2 set = 1; } +message Array { + repeated TermV2 array = 1; +} + +message Map { + repeated MapEntry entries = 1; +} + +message MapEntry { + required MapKey key = 1; + required TermV2 value = 2; +} + +message MapKey { + oneof Content { + int64 integer = 1; + uint64 string = 2; + } +} + message ExpressionV2 { repeated Op ops = 1; } @@ -113,6 +138,7 @@ message Op { TermV2 value = 1; OpUnary unary = 2; OpBinary Binary = 3; + OpClosure closure = 4; } } @@ -121,9 +147,12 @@ message OpUnary { Negate = 0; Parens = 1; Length = 2; + TypeOf = 3; + Ffi = 4; } required Kind kind = 1; + optional uint64 ffiName = 2; } message OpBinary { @@ -149,9 +178,23 @@ message OpBinary { BitwiseOr = 18; BitwiseXor = 19; NotEqual = 20; + HeterogeneousEqual = 21; + HeterogeneousNotEqual = 22; + LazyAnd = 23; + LazyOr = 24; + All = 25; + Any = 26; + Get = 27; + Ffi = 28; } required Kind kind = 1; + optional uint64 ffiName = 2; +} + +message OpClosure { + repeated uint32 params = 1; + repeated Op ops = 2; } message Policy { @@ -174,8 +217,10 @@ message AuthorizerPolicies { } message ThirdPartyBlockRequest { - required PublicKey previousKey = 1; - repeated PublicKey publicKeys = 2; + optional PublicKey legacyPreviousKey = 1; + repeated PublicKey legacyPublicKeys = 2; + required bytes previousSignature = 3; + } message ThirdPartyBlockContents { @@ -228,4 +273,4 @@ message SnapshotBlock { repeated CheckV2 checks_v2 = 5; repeated Scope scope = 6; optional PublicKey externalKey = 7; -} \ No newline at end of file +} diff --git a/biscuit-auth/src/format/schema.rs b/biscuit-auth/src/format/schema.rs index 869858f9..d5d2c3c4 100644 --- a/biscuit-auth/src/format/schema.rs +++ b/biscuit-auth/src/format/schema.rs @@ -19,6 +19,8 @@ pub struct SignedBlock { pub signature: ::prost::alloc::vec::Vec, #[prost(message, optional, tag="4")] pub external_signature: ::core::option::Option, + #[prost(uint32, optional, tag="5")] + pub version: ::core::option::Option, } #[derive(Clone, PartialEq, ::prost::Message)] pub struct ExternalSignature { @@ -40,6 +42,7 @@ pub mod public_key { #[repr(i32)] pub enum Algorithm { Ed25519 = 0, + Secp256r1 = 1, } } #[derive(Clone, PartialEq, ::prost::Message)] @@ -127,6 +130,7 @@ pub mod check_v2 { pub enum Kind { One = 0, All = 1, + Reject = 2, } } #[derive(Clone, PartialEq, ::prost::Message)] @@ -138,7 +142,7 @@ pub struct PredicateV2 { } #[derive(Clone, PartialEq, ::prost::Message)] pub struct TermV2 { - #[prost(oneof="term_v2::Content", tags="1, 2, 3, 4, 5, 6, 7")] + #[prost(oneof="term_v2::Content", tags="1, 2, 3, 4, 5, 6, 7, 8, 9, 10")] pub content: ::core::option::Option, } /// Nested message and enum types in `TermV2`. @@ -159,6 +163,12 @@ pub mod term_v2 { Bool(bool), #[prost(message, tag="7")] Set(super::TermSet), + #[prost(message, tag="8")] + Null(super::Empty), + #[prost(message, tag="9")] + Array(super::Array), + #[prost(message, tag="10")] + Map(super::Map), } } #[derive(Clone, PartialEq, ::prost::Message)] @@ -167,13 +177,45 @@ pub struct TermSet { pub set: ::prost::alloc::vec::Vec, } #[derive(Clone, PartialEq, ::prost::Message)] +pub struct Array { + #[prost(message, repeated, tag="1")] + pub array: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Map { + #[prost(message, repeated, tag="1")] + pub entries: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct MapEntry { + #[prost(message, required, tag="1")] + pub key: MapKey, + #[prost(message, required, tag="2")] + pub value: TermV2, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct MapKey { + #[prost(oneof="map_key::Content", tags="1, 2")] + pub content: ::core::option::Option, +} +/// Nested message and enum types in `MapKey`. +pub mod map_key { + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Content { + #[prost(int64, tag="1")] + Integer(i64), + #[prost(uint64, tag="2")] + String(u64), + } +} +#[derive(Clone, PartialEq, ::prost::Message)] pub struct ExpressionV2 { #[prost(message, repeated, tag="1")] pub ops: ::prost::alloc::vec::Vec, } #[derive(Clone, PartialEq, ::prost::Message)] pub struct Op { - #[prost(oneof="op::Content", tags="1, 2, 3")] + #[prost(oneof="op::Content", tags="1, 2, 3, 4")] pub content: ::core::option::Option, } /// Nested message and enum types in `Op`. @@ -186,12 +228,16 @@ pub mod op { Unary(super::OpUnary), #[prost(message, tag="3")] Binary(super::OpBinary), + #[prost(message, tag="4")] + Closure(super::OpClosure), } } #[derive(Clone, PartialEq, ::prost::Message)] pub struct OpUnary { #[prost(enumeration="op_unary::Kind", required, tag="1")] pub kind: i32, + #[prost(uint64, optional, tag="2")] + pub ffi_name: ::core::option::Option, } /// Nested message and enum types in `OpUnary`. pub mod op_unary { @@ -201,12 +247,16 @@ pub mod op_unary { Negate = 0, Parens = 1, Length = 2, + TypeOf = 3, + Ffi = 4, } } #[derive(Clone, PartialEq, ::prost::Message)] pub struct OpBinary { #[prost(enumeration="op_binary::Kind", required, tag="1")] pub kind: i32, + #[prost(uint64, optional, tag="2")] + pub ffi_name: ::core::option::Option, } /// Nested message and enum types in `OpBinary`. pub mod op_binary { @@ -234,9 +284,24 @@ pub mod op_binary { BitwiseOr = 18, BitwiseXor = 19, NotEqual = 20, + HeterogeneousEqual = 21, + HeterogeneousNotEqual = 22, + LazyAnd = 23, + LazyOr = 24, + All = 25, + Any = 26, + Get = 27, + Ffi = 28, } } #[derive(Clone, PartialEq, ::prost::Message)] +pub struct OpClosure { + #[prost(uint32, repeated, packed="false", tag="1")] + pub params: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="2")] + pub ops: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] pub struct Policy { #[prost(message, repeated, tag="1")] pub queries: ::prost::alloc::vec::Vec, @@ -269,10 +334,12 @@ pub struct AuthorizerPolicies { } #[derive(Clone, PartialEq, ::prost::Message)] pub struct ThirdPartyBlockRequest { - #[prost(message, required, tag="1")] - pub previous_key: PublicKey, + #[prost(message, optional, tag="1")] + pub legacy_previous_key: ::core::option::Option, #[prost(message, repeated, tag="2")] - pub public_keys: ::prost::alloc::vec::Vec, + pub legacy_public_keys: ::prost::alloc::vec::Vec, + #[prost(bytes="vec", required, tag="3")] + pub previous_signature: ::prost::alloc::vec::Vec, } #[derive(Clone, PartialEq, ::prost::Message)] pub struct ThirdPartyBlockContents { diff --git a/biscuit-auth/src/lib.rs b/biscuit-auth/src/lib.rs index dc6bd64a..41a657b6 100644 --- a/biscuit-auth/src/lib.rs +++ b/biscuit-auth/src/lib.rs @@ -88,7 +88,7 @@ //! // - one for /a/file1.txt and a write operation //! // - one for /a/file2.txt and a read operation //! -//! let v1 = authorizer!(r#" +//! let mut v1 = authorizer!(r#" //! resource("/a/file1.txt"); //! operation("read"); //! @@ -101,26 +101,29 @@ //! // explicit catch-all deny. here it is not necessary: if no policy //! // matches, a default deny applies //! deny if true; -//! "#); +//! "#) +//! .build(&biscuit2)?; //! //! let mut v2 = authorizer!(r#" //! resource("/a/file1.txt"); //! operation("write"); //! allow if right("/a/file1.txt", "write"); -//! "#); -//! +//! "#) +//! .build(&biscuit2)?; +//! //! let mut v3 = authorizer!(r#" //! resource("/a/file2.txt"); //! operation("read"); //! allow if right("/a/file2.txt", "read"); -//! "#); +//! "#) +//! .build(&biscuit2)?; //! //! // the token restricts to read operations: -//! assert!(biscuit2.authorize(&v1).is_ok()); +//! assert!(v1.authorize().is_ok()); //! // the second verifier requested a read operation -//! assert!(biscuit2.authorize(&v2).is_err()); +//! assert!(v2.authorize().is_err()); //! // the third verifier requests /a/file2.txt -//! assert!(biscuit2.authorize(&v3).is_err()); +//! assert!(v3.authorize().is_err()); //! //! Ok(()) //! } @@ -229,6 +232,7 @@ mod token; pub use crypto::{KeyPair, PrivateKey, PublicKey}; pub use token::authorizer::{Authorizer, AuthorizerLimits}; pub use token::builder; +pub use token::builder::{Algorithm, AuthorizerBuilder, BiscuitBuilder, BlockBuilder}; pub use token::builder_ext; pub use token::unverified::UnverifiedBiscuit; pub use token::Biscuit; diff --git a/biscuit-auth/src/macros.rs b/biscuit-auth/src/macros.rs index dfab972e..a511b6e8 100644 --- a/biscuit-auth/src/macros.rs +++ b/biscuit-auth/src/macros.rs @@ -25,7 +25,7 @@ //! expiration = SystemTime::now() + Duration::from_secs(86_400), //! )).expect("Failed to append block"); //! -//! new_biscuit.authorize(&authorizer!( +//! authorizer!( //! r#" //! time({now}); //! operation({operation}); @@ -41,7 +41,11 @@ //! operation = "read", //! resource = "file1", //! user_id = "1234", -//! )).expect("Failed to authorize biscuit"); +//! ) +//! .build(&new_biscuit) +//! .expect("failed to build the authorizer") +//! .authorize() +//! .expect("Failed to authorize biscuit"); //! ``` /// Create an `Authorizer` from a datalog string and optional parameters. @@ -77,8 +81,8 @@ pub use biscuit_quote::authorizer; /// now = SystemTime::now() /// ); /// -/// authorizer_merge!( -/// &mut b, +/// b = authorizer_merge!( +/// b, /// r#" /// allow if true; /// "# @@ -125,8 +129,8 @@ pub use biscuit_quote::biscuit; /// user_id = "1234" /// ); /// -/// biscuit_merge!( -/// &mut b, +/// b = biscuit_merge!( +/// b, /// r#" /// check if time($time), $time < {expiration} /// "#, @@ -170,8 +174,8 @@ pub use biscuit_quote::block; /// user_id = "1234" /// ); /// -/// block_merge!( -/// &mut b, +/// b = block_merge!( +/// b, /// r#" /// check if user($id); /// "# diff --git a/biscuit-auth/src/parser.rs b/biscuit-auth/src/parser.rs index 1e42dc25..77f0c3ef 100644 --- a/biscuit-auth/src/parser.rs +++ b/biscuit-auth/src/parser.rs @@ -31,6 +31,7 @@ mod tests { Value(builder::Term), Unary(builder::Op, Box), Binary(builder::Op, Box, Box), + Closure(Vec, Box), } impl Expr { @@ -52,6 +53,11 @@ mod tests { right.into_opcodes(v); v.push(op); } + Expr::Closure(params, body) => { + let mut ops = vec![]; + body.into_opcodes(&mut ops); + v.push(builder::Op::Closure(params, ops)); + } } } } @@ -68,6 +74,9 @@ mod tests { Box::new((*expr1).into()), Box::new((*expr2).into()), ), + biscuit_parser::parser::Expr::Closure(params, body) => { + Expr::Closure(params, Box::new((*body).into())) + } } } } @@ -319,22 +328,28 @@ mod tests { Ok(( " ", Expr::Binary( - Op::Binary(Binary::And), + Op::Binary(Binary::LazyAnd), Box::new(Expr::Binary( - Op::Binary(Binary::And), + Op::Binary(Binary::LazyAnd), Box::new(Expr::Binary( Op::Binary(Binary::LessThan), Box::new(Expr::Value(int(2))), Box::new(Expr::Value(var("test"))), )), - Box::new(Expr::Binary( - Op::Binary(Binary::Prefix), - Box::new(Expr::Value(var("var2"))), - Box::new(Expr::Value(string("test"))), - )), + Box::new(Expr::Closure( + vec![], + Box::new(Expr::Binary( + Op::Binary(Binary::Prefix), + Box::new(Expr::Value(var("var2"))), + Box::new(Expr::Value(string("test"))) + ),) + )) )), - Box::new(Expr::Value(Term::Bool(true))), - ) + Box::new(Expr::Closure( + vec![], + Box::new(Expr::Value(Term::Bool(true))) + )), + ), )) ); @@ -368,7 +383,11 @@ mod tests { println!("print: {}", e.print(&syms).unwrap()); let h = HashMap::new(); let result = e - .evaluate(&h, &mut TemporarySymbolTable::new(&syms)) + .evaluate( + &h, + &mut TemporarySymbolTable::new(&syms), + &Default::default(), + ) .unwrap(); println!("evaluates to: {:?}", result); @@ -399,7 +418,11 @@ mod tests { println!("print: {}", e.print(&syms).unwrap()); let h = HashMap::new(); let result = e - .evaluate(&h, &mut TemporarySymbolTable::new(&syms)) + .evaluate( + &h, + &mut TemporarySymbolTable::new(&syms), + &Default::default(), + ) .unwrap(); println!("evaluates to: {:?}", result); @@ -483,7 +506,7 @@ mod tests { ops: vec![ Op::Value(int(1)), Op::Value(int(2)), - Op::Binary(Binary::Equal), + Op::Binary(Binary::HeterogeneousEqual), ], }], )], @@ -641,7 +664,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/authorizer.rs b/biscuit-auth/src/token/authorizer.rs index 922beacd..040270a0 100644 --- a/biscuit-auth/src/token/authorizer.rs +++ b/biscuit-auth/src/token/authorizer.rs @@ -1,17 +1,11 @@ //! Authorizer structure and associated functions -use super::builder::{ - constrained_rule, date, fact, pred, rule, string, var, Binary, BlockBuilder, Check, Expression, - Fact, Op, Policy, PolicyKind, Rule, Scope, Term, -}; -use super::builder_ext::{AuthorizerExt, BuilderExt}; +use super::builder::{AuthorizerBuilder, BlockBuilder, Check, Fact, Policy, PolicyKind, Rule}; use super::{Biscuit, Block}; -use crate::builder::{self, CheckKind, Convert}; -use crate::crypto::PublicKey; -use crate::datalog::{self, Origin, RunLimits, SymbolTable, TrustedOrigins}; +use crate::builder::{CheckKind, Convert}; +use crate::datalog::{self, ExternFunc, Origin, RunLimits, TrustedOrigins}; use crate::error; use crate::time::Instant; use crate::token; -use biscuit_parser::parser::parse_source; use prost::Message; use std::collections::{BTreeMap, HashSet}; use std::time::Duration; @@ -20,7 +14,6 @@ use std::{ convert::{TryFrom, TryInto}, default::Default, fmt::Write, - time::SystemTime, }; mod snapshot; @@ -28,25 +21,36 @@ mod snapshot; /// used to check authorization policies on a token /// /// can be created from [Biscuit::authorizer] or [Authorizer::new] -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Authorizer { - authorizer_block_builder: BlockBuilder, - world: datalog::World, + pub(crate) authorizer_block_builder: BlockBuilder, + pub(crate) world: datalog::World, pub(crate) symbols: datalog::SymbolTable, - token_origins: TrustedOrigins, - policies: Vec, - blocks: Option>, - public_key_to_block_id: HashMap>, - limits: AuthorizerLimits, - execution_time: Duration, + pub(crate) token_origins: TrustedOrigins, + pub(crate) policies: Vec, + pub(crate) blocks: Option>, + pub(crate) public_key_to_block_id: HashMap>, + pub(crate) limits: AuthorizerLimits, + pub(crate) execution_time: Option, } impl Authorizer { - pub(crate) fn from_token(token: &Biscuit) -> Result { - let mut v = Authorizer::new(); - v.add_token(token)?; + pub fn run(&mut self) -> Result { + match self.execution_time { + Some(execution_time) => Ok(execution_time), + None => { + let start = Instant::now(); + self.world + .run_with_limits(&self.symbols, self.limits.clone())?; + let execution_time = start.elapsed(); + self.execution_time = Some(execution_time); + Ok(execution_time) + } + } + } - Ok(v) + pub(crate) fn from_token(token: &Biscuit) -> Result { + AuthorizerBuilder::new().build(token) } /// creates a new empty authorizer @@ -58,7 +62,7 @@ impl Authorizer { /// In the latter case, we can create an empty authorizer, load it /// with the facts, rules and checks, and each time a token must be checked, /// clone the authorizer and load the token with [`Authorizer::add_token`] - pub fn new() -> Self { + fn new() -> Self { let world = datalog::World::new(); let symbols = super::default_symbol_table(); let authorizer_block_builder = BlockBuilder::new(); @@ -72,7 +76,7 @@ impl Authorizer { blocks: None, public_key_to_block_id: HashMap::new(), limits: AuthorizerLimits::default(), - execution_time: Duration::default(), + execution_time: None, } } @@ -81,106 +85,6 @@ impl Authorizer { AuthorizerPolicies::deserialize(data)?.try_into() } - /// add a token to an empty authorizer - pub fn add_token(&mut self, token: &Biscuit) -> Result<(), error::Token> { - if self.blocks.is_some() { - return Err(error::Logic::AuthorizerNotEmpty.into()); - } - - for (i, block) in token.container.blocks.iter().enumerate() { - if let Some(sig) = block.external_signature.as_ref() { - let new_key_id = self.symbols.public_keys.insert(&sig.public_key); - - self.public_key_to_block_id - .entry(new_key_id as usize) - .or_default() - .push(i + 1); - } - } - - let mut blocks = Vec::new(); - - for i in 0..token.block_count() { - let mut block = token.block(i)?; - - self.load_and_translate_block(&mut block, i, &token.symbols)?; - - blocks.push(block); - } - - self.blocks = Some(blocks); - self.token_origins = TrustedOrigins::from_scopes( - &[token::Scope::Previous], - &TrustedOrigins::default(), - token.block_count(), - &self.public_key_to_block_id, - ); - - Ok(()) - } - - /// we need to modify the block loaded from the token, because the authorizer's and the token's symbol table can differ - fn load_and_translate_block( - &mut self, - block: &mut Block, - i: usize, - token_symbols: &SymbolTable, - ) -> Result<(), error::Token> { - // if it is a 3rd party block, it should not affect the main symbol table - let block_symbols = if i == 0 || block.external_key.is_none() { - token_symbols.clone() - } else { - block.symbols.clone() - }; - - let mut block_origin = Origin::default(); - block_origin.insert(i); - - for scope in block.scopes.iter_mut() { - *scope = builder::Scope::convert_from(scope, &block_symbols) - .map(|s| s.convert(&mut self.symbols))?; - } - - let block_trusted_origins = TrustedOrigins::from_scopes( - &block.scopes, - &TrustedOrigins::default(), - i, - &self.public_key_to_block_id, - ); - - for fact in block.facts.iter_mut() { - *fact = Fact::convert_from(fact, &block_symbols)?.convert(&mut self.symbols); - self.world.facts.insert(&block_origin, fact.clone()); - } - - for rule in block.rules.iter_mut() { - if let Err(_message) = rule.validate_variables(&block_symbols) { - return Err( - error::Logic::InvalidBlockRule(0, block_symbols.print_rule(rule)).into(), - ); - } - *rule = rule.translate(&block_symbols, &mut self.symbols)?; - - let rule_trusted_origins = TrustedOrigins::from_scopes( - &rule.scopes, - &block_trusted_origins, - i, - &self.public_key_to_block_id, - ); - - self.world - .rules - .insert(i, &rule_trusted_origins, rule.clone()); - } - - for check in block.checks.iter_mut() { - let c = Check::convert_from(check, &block_symbols)?; - *check = c.convert(&mut self.symbols); - } - - Ok(()) - } - /// serializes a authorizer's content /// /// you can use this to save a set of policies and load them quickly before @@ -201,188 +105,6 @@ impl Authorizer { }) } - /// Add the rules, facts, checks, and policies of another `Authorizer`. - /// If a token has already been added to `other`, it is not merged into `self`. - pub fn merge(&mut self, mut other: Authorizer) { - self.merge_block(other.authorizer_block_builder); - self.policies.append(&mut other.policies); - } - - /// Add the rules, facts, and checks of another `BlockBuilder`. - pub fn merge_block(&mut self, other: BlockBuilder) { - self.authorizer_block_builder.merge(other) - } - - pub fn add_fact>(&mut self, fact: F) -> Result<(), error::Token> - where - error::Token: From<>::Error>, - { - self.authorizer_block_builder.add_fact(fact) - } - - pub fn add_rule>(&mut self, rule: Ru) -> Result<(), error::Token> - where - error::Token: From<>::Error>, - { - self.authorizer_block_builder.add_rule(rule) - } - - pub fn add_check>(&mut self, check: C) -> Result<(), error::Token> - where - error::Token: From<>::Error>, - { - self.authorizer_block_builder.add_check(check) - } - - /// adds some datalog code to the authorizer - /// - /// ```rust - /// extern crate biscuit_auth as biscuit; - /// - /// use biscuit::Authorizer; - /// - /// let mut authorizer = Authorizer::new(); - /// - /// authorizer.add_code(r#" - /// resource("/file1.txt"); - /// - /// check if user(1234); - /// - /// // default allow - /// allow if true; - /// "#).expect("should parse correctly"); - /// ``` - pub fn add_code>(&mut self, source: T) -> Result<(), error::Token> { - self.add_code_with_params(source, HashMap::new(), HashMap::new()) - } - - pub fn add_code_with_params>( - &mut self, - source: T, - params: HashMap, - scope_params: HashMap, - ) -> Result<(), error::Token> { - let source = source.as_ref(); - - let source_result = parse_source(source).map_err(|e| { - let e2: biscuit_parser::error::LanguageError = e.into(); - e2 - })?; - - for (_, fact) in source_result.facts.into_iter() { - let mut fact: Fact = fact.into(); - for (name, value) in ¶ms { - let res = match fact.set(name, value) { - Ok(_) => Ok(()), - Err(error::Token::Language( - biscuit_parser::error::LanguageError::Parameters { - missing_parameters, .. - }, - )) if missing_parameters.is_empty() => Ok(()), - Err(e) => Err(e), - }; - res?; - } - fact.validate()?; - self.authorizer_block_builder.facts.push(fact); - } - - for (_, rule) in source_result.rules.into_iter() { - let mut rule: Rule = rule.into(); - for (name, value) in ¶ms { - let res = match rule.set(name, value) { - Ok(_) => Ok(()), - Err(error::Token::Language( - biscuit_parser::error::LanguageError::Parameters { - missing_parameters, .. - }, - )) if missing_parameters.is_empty() => Ok(()), - Err(e) => Err(e), - }; - res?; - } - for (name, value) in &scope_params { - let res = match rule.set_scope(name, *value) { - Ok(_) => Ok(()), - Err(error::Token::Language( - biscuit_parser::error::LanguageError::Parameters { - missing_parameters, .. - }, - )) if missing_parameters.is_empty() => Ok(()), - Err(e) => Err(e), - }; - res?; - } - rule.validate_parameters()?; - self.authorizer_block_builder.rules.push(rule); - } - - for (_, check) in source_result.checks.into_iter() { - let mut check: Check = check.into(); - for (name, value) in ¶ms { - let res = match check.set(name, value) { - Ok(_) => Ok(()), - Err(error::Token::Language( - biscuit_parser::error::LanguageError::Parameters { - missing_parameters, .. - }, - )) if missing_parameters.is_empty() => Ok(()), - Err(e) => Err(e), - }; - res?; - } - for (name, value) in &scope_params { - let res = match check.set_scope(name, *value) { - Ok(_) => Ok(()), - Err(error::Token::Language( - biscuit_parser::error::LanguageError::Parameters { - missing_parameters, .. - }, - )) if missing_parameters.is_empty() => Ok(()), - Err(e) => Err(e), - }; - res?; - } - check.validate_parameters()?; - self.authorizer_block_builder.checks.push(check); - } - for (_, policy) in source_result.policies.into_iter() { - let mut policy: Policy = policy.into(); - for (name, value) in ¶ms { - let res = match policy.set(name, value) { - Ok(_) => Ok(()), - Err(error::Token::Language( - biscuit_parser::error::LanguageError::Parameters { - missing_parameters, .. - }, - )) if missing_parameters.is_empty() => Ok(()), - Err(e) => Err(e), - }; - res?; - } - for (name, value) in &scope_params { - let res = match policy.set_scope(name, *value) { - Ok(_) => Ok(()), - Err(error::Token::Language( - biscuit_parser::error::LanguageError::Parameters { - missing_parameters, .. - }, - )) if missing_parameters.is_empty() => Ok(()), - Err(e) => Err(e), - }; - res?; - } - policy.validate_parameters()?; - self.policies.push(policy); - } - - Ok(()) - } - - pub fn add_scope(&mut self, scope: Scope) { - self.authorizer_block_builder.add_scope(scope); - } - /// Returns the runtime limits of the authorizer /// /// Those limits cover all the executions under the `authorize`, `query` and `query_all` methods @@ -390,11 +112,9 @@ impl Authorizer { &self.limits } - /// Sets the runtime limits of the authorizer - /// - /// Those limits cover all the executions under the `authorize`, `query` and `query_all` methods - pub fn set_limits(&mut self, limits: AuthorizerLimits) { - self.limits = limits; + /// Returns the currently registered external functions + pub fn external_funcs(&self) -> &HashMap { + &self.world.extern_funcs } /// run a query over the authorizer's Datalog engine to gather data @@ -403,10 +123,11 @@ impl Authorizer { /// # use biscuit_auth::KeyPair; /// # use biscuit_auth::Biscuit; /// let keypair = KeyPair::new(); - /// let mut builder = Biscuit::builder(); - /// builder.add_fact("user(\"John Doe\", 42)"); - /// - /// let biscuit = builder.build(&keypair).unwrap(); + /// let biscuit = Biscuit::builder() + /// .fact("user(\"John Doe\", 42)") + /// .expect("parse error") + /// .build(&keypair) + /// .unwrap(); /// /// let mut authorizer = biscuit.authorizer().unwrap(); /// let res: Vec<(String, i64)> = authorizer.query("data($name, $id) <- user($name, $id)").unwrap(); @@ -422,12 +143,13 @@ impl Authorizer { where error::Token: From<>::Error>, { + let execution_time = self.run()?; let mut limits = self.limits.clone(); limits.max_iterations -= self.world.iterations; - if self.execution_time >= limits.max_time { + if execution_time >= limits.max_time { return Err(error::Token::RunLimit(error::RunLimit::Timeout)); } - limits.max_time -= self.execution_time; + limits.max_time -= execution_time; self.query_with_limits(rule, limits) } @@ -445,11 +167,12 @@ impl Authorizer { where error::Token: From<>::Error>, { + let execution_time = self.run()?; let rule = rule.try_into()?.convert(&mut self.symbols); let start = Instant::now(); let result = self.query_inner(rule, limits); - self.execution_time += start.elapsed(); + self.execution_time = Some(start.elapsed() + execution_time); result } @@ -457,7 +180,7 @@ impl Authorizer { fn query_inner, E: Into>( &mut self, rule: datalog::Rule, - limits: AuthorizerLimits, + _limits: AuthorizerLimits, ) -> Result, error::Token> { let rule_trusted_origins = TrustedOrigins::from_scopes( &rule.scopes, @@ -469,7 +192,6 @@ impl Authorizer { &self.public_key_to_block_id, ); - self.world.run_with_limits(&self.symbols, limits)?; let res = self .world .query_rule(rule, usize::MAX, &rule_trusted_origins, &self.symbols)?; @@ -493,10 +215,11 @@ impl Authorizer { /// # use biscuit_auth::KeyPair; /// # use biscuit_auth::Biscuit; /// let keypair = KeyPair::new(); - /// let mut builder = Biscuit::builder(); - /// builder.add_fact("user(\"John Doe\", 42)"); - /// - /// let biscuit = builder.build(&keypair).unwrap(); + /// let biscuit = Biscuit::builder() + /// .fact("user(\"John Doe\", 42)") + /// .expect("parse error") + /// .build(&keypair) + /// .unwrap(); /// /// let mut authorizer = biscuit.authorizer().unwrap(); /// let res: Vec<(String, i64)> = authorizer.query_all("data($name, $id) <- user($name, $id)").unwrap(); @@ -511,12 +234,13 @@ impl Authorizer { where error::Token: From<>::Error>, { + let execution_time = self.run()?; let mut limits = self.limits.clone(); limits.max_iterations -= self.world.iterations; - if self.execution_time >= limits.max_time { + if execution_time >= limits.max_time { return Err(error::Token::RunLimit(error::RunLimit::Timeout)); } - limits.max_time -= self.execution_time; + limits.max_time -= execution_time; self.query_all_with_limits(rule, limits) } @@ -538,11 +262,12 @@ impl Authorizer { where error::Token: From<>::Error>, { + let execution_time = self.run()?; let rule = rule.try_into()?.convert(&mut self.symbols); let start = Instant::now(); let result = self.query_all_inner(rule, limits); - self.execution_time += start.elapsed(); + self.execution_time = Some(execution_time + start.elapsed()); result } @@ -550,10 +275,8 @@ impl Authorizer { fn query_all_inner, E: Into>( &mut self, rule: datalog::Rule, - limits: AuthorizerLimits, + _limits: AuthorizerLimits, ) -> Result, error::Token> { - self.world.run_with_limits(&self.symbols, limits)?; - let rule_trusted_origins = if rule.scopes.is_empty() { self.token_origins.clone() } else { @@ -583,36 +306,8 @@ impl Authorizer { .collect::, _>>() } - /// adds a fact with the current time - pub fn set_time(&mut self) { - let fact = fact("time", &[date(&SystemTime::now())]); - self.authorizer_block_builder.add_fact(fact).unwrap(); - } - - /// add a policy to the authorizer - pub fn add_policy>(&mut self, policy: P) -> Result<(), error::Token> - where - error::Token: From<

>::Error>, - { - let policy = policy.try_into()?; - policy.validate_parameters()?; - self.policies.push(policy); - Ok(()) - } - - /// todo remove, it's covered in BuilderExt - /// adds a `allow if true` policy - pub fn allow(&mut self) -> Result<(), error::Token> { - self.add_policy("allow if true") - } - - /// adds a `deny if true` policy - pub fn deny(&mut self) -> Result<(), error::Token> { - self.add_policy("deny if true") - } - /// returns the elapsed execution time - pub fn execution_time(&self) -> Duration { + pub fn execution_time(&self) -> Option { self.execution_time } @@ -631,17 +326,17 @@ impl Authorizer { /// on error, this can return a list of all the failed checks or deny policy /// on success, it returns the index of the policy that matched pub fn authorize(&mut self) -> Result { + let execution_time = self.run()?; let mut limits = self.limits.clone(); limits.max_iterations -= self.world.iterations; - if self.execution_time >= limits.max_time { + if execution_time >= limits.max_time { return Err(error::Token::RunLimit(error::RunLimit::Timeout)); } - limits.max_time -= self.execution_time; + limits.max_time -= execution_time; self.authorize_with_limits(limits) } - /// TODO: consume the input to prevent further direct use /// verifies the checks and policies /// /// on error, this can return a list of all the failed checks or deny policy @@ -651,17 +346,17 @@ impl Authorizer { &mut self, limits: AuthorizerLimits, ) -> Result { + let execution_time = self.run()?; let start = Instant::now(); let result = self.authorize_inner(limits); - self.execution_time += start.elapsed(); + self.execution_time = Some(execution_time + start.elapsed()); result } - fn authorize_inner(&mut self, mut limits: AuthorizerLimits) -> Result { + fn authorize_inner(&mut self, limits: AuthorizerLimits) -> Result { let start = Instant::now(); let time_limit = start + limits.max_time; - let mut current_iterations = self.world.iterations; let mut errors = vec![]; let mut policy_result: Option> = None; @@ -684,45 +379,6 @@ impl Authorizer { &self.public_key_to_block_id, ); - for fact in &self.authorizer_block_builder.facts { - self.world - .facts - .insert(&authorizer_origin, fact.convert(&mut self.symbols)); - } - - for rule in &self.authorizer_block_builder.rules { - let rule = rule.convert(&mut self.symbols); - - let rule_trusted_origins = TrustedOrigins::from_scopes( - &rule.scopes, - &authorizer_trusted_origins, - usize::MAX, - &self.public_key_to_block_id, - ); - - self.world - .rules - .insert(usize::MAX, &rule_trusted_origins, rule); - } - - limits.max_time = time_limit - Instant::now(); - self.world.run_with_limits(&self.symbols, limits.clone())?; - - let authorizer_scopes: Vec = self - .authorizer_block_builder - .scopes - .clone() - .iter() - .map(|s| s.convert(&mut self.symbols)) - .collect(); - - let authorizer_trusted_origins = TrustedOrigins::from_scopes( - &authorizer_scopes, - &TrustedOrigins::default(), - usize::MAX, - &self.public_key_to_block_id, - ); - for (i, check) in self.authorizer_block_builder.checks.iter().enumerate() { let c = check.convert(&mut self.symbols); let mut successful = false; @@ -746,6 +402,12 @@ impl Authorizer { self.world .query_match_all(query, &rule_trusted_origins, &self.symbols)? } + CheckKind::Reject => !self.world.query_match( + query, + usize::MAX, + &rule_trusted_origins, + &self.symbols, + )?, }; let now = Instant::now(); @@ -799,6 +461,12 @@ impl Authorizer { &rule_trusted_origins, &self.symbols, )?, + CheckKind::Reject => !self.world.query_match( + query.clone(), + 0, + &rule_trusted_origins, + &self.symbols, + )?, }; let now = Instant::now(); @@ -863,12 +531,6 @@ impl Authorizer { &self.public_key_to_block_id, ); - limits.max_time = time_limit - Instant::now(); - limits.max_iterations -= self.world.iterations - current_iterations; - current_iterations = self.world.iterations; - - self.world.run_with_limits(&self.symbols, limits.clone())?; - for (j, check) in block.checks.iter().enumerate() { let mut successful = false; @@ -892,6 +554,12 @@ impl Authorizer { &rule_trusted_origins, &self.symbols, )?, + CheckKind::Reject => !self.world.query_match( + query.clone(), + i + 1, + &rule_trusted_origins, + &self.symbols, + )?, }; let now = Instant::now(); @@ -951,23 +619,21 @@ impl Authorizer { } } - let mut facts = self + let facts = self .world .facts .iter_all() .map(|f| Fact::convert_from(f.1, &self.symbols)) .collect::, error::Format>>() .unwrap(); - facts.extend(self.authorizer_block_builder.facts.clone()); - let mut rules = self + let rules = self .world .rules .iter_all() .map(|r| Rule::convert_from(r.1, &self.symbols)) .collect::, error::Format>>() .unwrap(); - rules.extend(self.authorizer_block_builder.rules.clone()); (facts, rules, checks, self.policies.clone()) } @@ -975,6 +641,9 @@ impl Authorizer { pub fn dump_code(&self) -> String { let (facts, rules, checks, policies) = self.dump(); let mut f = String::new(); + + let mut facts = facts.into_iter().map(|f| f.to_string()).collect::>(); + facts.sort(); for fact in &facts { let _ = writeln!(f, "{fact};"); } @@ -1017,24 +686,6 @@ impl std::fmt::Display for Authorizer { all_facts.insert(origin, facts); } - let builder_facts = self - .authorizer_block_builder - .facts - .iter() - .map(|f| f.to_string()) - .collect::>(); - has_facts = has_facts || !builder_facts.is_empty(); - let mut authorizer_origin = Origin::default(); - authorizer_origin.insert(usize::MAX); - match all_facts.get_mut(&authorizer_origin) { - Some(e) => { - e.extend(builder_facts); - } - None => { - all_facts.insert(&authorizer_origin, builder_facts); - } - } - if has_facts { writeln!(f, "// Facts:")?; } @@ -1068,19 +719,6 @@ impl std::fmt::Display for Authorizer { } } - let builder_rules = self - .authorizer_block_builder - .rules - .iter() - .map(|rule| rule.to_string()) - .collect::>(); - has_rules = has_rules || !builder_rules.is_empty(); - - rules_map - .entry(usize::MAX) - .or_default() - .extend(builder_rules); - if has_rules { writeln!(f, "// Rules:")?; } @@ -1173,15 +811,16 @@ impl TryFrom for Authorizer { let mut authorizer = Self::new(); for fact in facts.into_iter() { - authorizer.authorizer_block_builder.add_fact(fact)?; + authorizer.authorizer_block_builder = authorizer.authorizer_block_builder.fact(fact)?; } for rule in rules.into_iter() { - authorizer.authorizer_block_builder.add_rule(rule)?; + authorizer.authorizer_block_builder = authorizer.authorizer_block_builder.rule(rule)?; } for check in checks.into_iter() { - authorizer.authorizer_block_builder.add_check(check)?; + authorizer.authorizer_block_builder = + authorizer.authorizer_block_builder.check(check)?; } for policy in policies { @@ -1230,114 +869,15 @@ impl AuthorizerPolicies { pub type AuthorizerLimits = RunLimits; -impl BuilderExt for Authorizer { - fn add_resource(&mut self, name: &str) { - let f = fact("resource", &[string(name)]); - self.add_fact(f).unwrap(); - } - fn check_resource(&mut self, name: &str) { - self.add_check(Check { - queries: vec![rule( - "resource_check", - &[string("resource_check")], - &[pred("resource", &[string(name)])], - )], - kind: CheckKind::One, - }) - .unwrap(); - } - fn add_operation(&mut self, name: &str) { - let f = fact("operation", &[string(name)]); - self.add_fact(f).unwrap(); - } - fn check_operation(&mut self, name: &str) { - self.add_check(Check { - queries: vec![rule( - "operation_check", - &[string("operation_check")], - &[pred("operation", &[string(name)])], - )], - kind: CheckKind::One, - }) - .unwrap(); - } - fn check_resource_prefix(&mut self, prefix: &str) { - let check = constrained_rule( - "prefix", - &[var("resource")], - &[pred("resource", &[var("resource")])], - &[Expression { - ops: vec![ - Op::Value(var("resource")), - Op::Value(string(prefix)), - Op::Binary(Binary::Prefix), - ], - }], - ); - - self.add_check(Check { - queries: vec![check], - kind: CheckKind::One, - }) - .unwrap(); - } - - fn check_resource_suffix(&mut self, suffix: &str) { - let check = constrained_rule( - "suffix", - &[var("resource")], - &[pred("resource", &[var("resource")])], - &[Expression { - ops: vec![ - Op::Value(var("resource")), - Op::Value(string(suffix)), - Op::Binary(Binary::Suffix), - ], - }], - ); - - self.add_check(Check { - queries: vec![check], - kind: CheckKind::One, - }) - .unwrap(); - } - - fn check_expiration_date(&mut self, exp: SystemTime) { - let check = constrained_rule( - "expiration", - &[var("time")], - &[pred("time", &[var("time")])], - &[Expression { - ops: vec![ - Op::Value(var("time")), - Op::Value(date(&exp)), - Op::Binary(Binary::LessOrEqual), - ], - }], - ); - - self.add_check(Check { - queries: vec![check], - kind: CheckKind::One, - }) - .unwrap(); - } -} - -impl AuthorizerExt for Authorizer { - fn add_allow_all(&mut self) { - self.add_policy("allow if true").unwrap(); - } - fn add_deny_all(&mut self) { - self.add_policy("deny if true").unwrap(); - } -} - #[cfg(test)] mod tests { use std::time::Duration; + use datalog::{SymbolTable, World}; + use token::builder::{self, load_and_translate_block, var}; + use token::{public_keys::PublicKeys, DATALOG_3_1}; + + use crate::PublicKey; use crate::{ builder::{BiscuitBuilder, BlockBuilder}, KeyPair, @@ -1347,8 +887,11 @@ mod tests { #[test] fn empty_authorizer() { - let mut authorizer = Authorizer::new(); - authorizer.add_policy("allow if true").unwrap(); + let mut authorizer = AuthorizerBuilder::new() + .policy("allow if true") + .unwrap() + .build_unauthenticated() + .unwrap(); assert_eq!( authorizer.authorize_with_limits(AuthorizerLimits { max_time: Duration::from_secs(10), @@ -1360,7 +903,6 @@ mod tests { #[test] fn parameter_substitution() { - let mut authorizer = Authorizer::new(); let mut params = HashMap::new(); params.insert("p1".to_string(), "value".into()); params.insert("p2".to_string(), 0i64.into()); @@ -1371,11 +913,12 @@ mod tests { PublicKey::from_bytes( &hex::decode("6e9e6d5a75cf0c0e87ec1256b4dfed0ca3ba452912d213fcc70f8516583db9db") .unwrap(), + crate::builder::Algorithm::Ed25519, ) .unwrap(), ); - authorizer - .add_code_with_params( + let _authorizer = AuthorizerBuilder::new() + .code_with_params( r#" fact({p1}, "value"); rule($var, {p2}) <- fact($var, {p2}); @@ -1385,75 +928,69 @@ mod tests { params, scope_params, ) + .unwrap() + .build_unauthenticated() .unwrap(); } #[test] fn forbid_unbound_parameters() { - let mut builder = Authorizer::new(); + let builder = AuthorizerBuilder::new(); let mut fact = Fact::try_from("fact({p1}, {p4})").unwrap(); fact.set("p1", "hello").unwrap(); - let res = builder.add_fact(fact); + let res = builder.clone().fact(fact); assert_eq!( - res, - Err(error::Token::Language( - biscuit_parser::error::LanguageError::Parameters { - missing_parameters: vec!["p4".to_string()], - unused_parameters: vec![], - } - )) + res.unwrap_err(), + error::Token::Language(biscuit_parser::error::LanguageError::Parameters { + missing_parameters: vec!["p4".to_string()], + unused_parameters: vec![], + }) ); let mut rule = Rule::try_from( "fact($var1, {p2}) <- f1($var1, $var3), f2({p2}, $var3, {p4}), $var3.starts_with({p2})", ) .unwrap(); rule.set("p2", "hello").unwrap(); - let res = builder.add_rule(rule); + let res = builder.clone().rule(rule); assert_eq!( - res, - Err(error::Token::Language( - biscuit_parser::error::LanguageError::Parameters { - missing_parameters: vec!["p4".to_string()], - unused_parameters: vec![], - } - )) + res.unwrap_err(), + error::Token::Language(biscuit_parser::error::LanguageError::Parameters { + missing_parameters: vec!["p4".to_string()], + unused_parameters: vec![], + }) ); let mut check = Check::try_from("check if {p4}, {p3}").unwrap(); check.set("p3", true).unwrap(); - let res = builder.add_check(check); + let res = builder.clone().check(check); assert_eq!( - res, - Err(error::Token::Language( - biscuit_parser::error::LanguageError::Parameters { - missing_parameters: vec!["p4".to_string()], - unused_parameters: vec![], - } - )) + res.unwrap_err(), + error::Token::Language(biscuit_parser::error::LanguageError::Parameters { + missing_parameters: vec!["p4".to_string()], + unused_parameters: vec![], + }) ); let mut policy = Policy::try_from("allow if {p4}, {p3}").unwrap(); policy.set("p3", true).unwrap(); - let res = builder.add_policy(policy); + let res = builder.clone().policy(policy); assert_eq!( - res, - Err(error::Token::Language( - biscuit_parser::error::LanguageError::Parameters { - missing_parameters: vec!["p4".to_string()], - unused_parameters: vec![], - } - )) + res.unwrap_err(), + error::Token::Language(biscuit_parser::error::LanguageError::Parameters { + missing_parameters: vec!["p4".to_string()], + unused_parameters: vec![], + }) ); } #[test] fn forbid_unbound_parameters_in_add_code() { - let mut builder = Authorizer::new(); + let builder = AuthorizerBuilder::new(); let mut params = HashMap::new(); params.insert("p1".to_string(), "hello".into()); params.insert("p2".to_string(), 1i64.into()); params.insert("p4".to_string(), "this will be ignored".into()); - let res = builder.add_code_with_params( + let res = builder.code_with_params( r#"fact({p1}, "value"); rule($head_var) <- f1($head_var), {p2} > 0; check if {p3}; @@ -1464,13 +1001,11 @@ mod tests { ); assert_eq!( - res, - Err(error::Token::Language( - biscuit_parser::error::LanguageError::Parameters { - missing_parameters: vec!["p3".to_string()], - unused_parameters: vec![], - } - )) + res.unwrap_err(), + error::Token::Language(biscuit_parser::error::LanguageError::Parameters { + missing_parameters: vec!["p3".to_string()], + unused_parameters: vec![], + }) ) } @@ -1479,10 +1014,11 @@ mod tests { use crate::Biscuit; use crate::KeyPair; let keypair = KeyPair::new(); - let mut builder = Biscuit::builder(); - builder.add_fact("user(\"John Doe\", 42)").unwrap(); - - let biscuit = builder.build(&keypair).unwrap(); + let biscuit = Biscuit::builder() + .fact("user(\"John Doe\", 42)") + .unwrap() + .build(&keypair) + .unwrap(); let mut authorizer = biscuit.authorizer().unwrap(); let res: Vec<(String, i64)> = authorizer @@ -1499,10 +1035,11 @@ mod tests { use crate::Biscuit; use crate::KeyPair; let keypair = KeyPair::new(); - let mut builder = Biscuit::builder(); - builder.add_fact("user(\"John Doe\")").unwrap(); - - let biscuit = builder.build(&keypair).unwrap(); + let biscuit = Biscuit::builder() + .fact("user(\"John Doe\")") + .unwrap() + .build(&keypair) + .unwrap(); let mut authorizer = biscuit.authorizer().unwrap(); let res: Vec<(String,)> = authorizer.query("data($name) <- user($name)").unwrap(); @@ -1516,26 +1053,25 @@ mod tests { let root = KeyPair::new(); let external = KeyPair::new(); - let mut builder = Biscuit::builder(); let mut scope_params = HashMap::new(); scope_params.insert("external_pub".to_string(), external.public()); - builder - .add_code_with_params( + + let biscuit1 = Biscuit::builder() + .code_with_params( r#"right("read"); - check if group("admin") trusting {external_pub}; - "#, + check if group("admin") trusting {external_pub}; + "#, HashMap::new(), scope_params, ) + .unwrap() + .build(&root) .unwrap(); - let biscuit1 = builder.build(&root).unwrap(); - let req = biscuit1.third_party_request().unwrap(); - let mut builder = BlockBuilder::new(); - builder - .add_code( + let builder = BlockBuilder::new() + .code( r#"group("admin"); check if right("read"); "#, @@ -1543,16 +1079,18 @@ mod tests { .unwrap(); let res = req.create_block(&external.private(), builder).unwrap(); let biscuit2 = biscuit1.append_third_party(external.public(), res).unwrap(); + let serialized = biscuit2.to_vec().unwrap(); + let biscuit2 = Biscuit::from(serialized, root.public()).unwrap(); - let mut authorizer = Authorizer::new(); + let builder = AuthorizerBuilder::new(); let external2 = KeyPair::new(); let mut scope_params = HashMap::new(); scope_params.insert("external".to_string(), external.public()); scope_params.insert("external2".to_string(), external2.public()); - authorizer - .add_code_with_params( + let mut authorizer = builder + .code_with_params( r#" // this rule trusts both the third-party block and the authority, and can access facts // from both @@ -1571,18 +1109,17 @@ mod tests { HashMap::new(), scope_params, ) + .unwrap() + .limits(AuthorizerLimits { + max_time: Duration::from_secs(10), //Set 10 seconds as the maximum time allowed for the authorization due to "cheap" worker on GitHub Actions + ..Default::default() + }) + .build(&biscuit2) .unwrap(); - authorizer.add_token(&biscuit2).unwrap(); - println!("token:\n{}", biscuit2); println!("world:\n{}", authorizer.print_world()); - authorizer.set_limits(AuthorizerLimits { - max_time: Duration::from_millis(10), //Set 10 milliseconds as the maximum time allowed for the authorization due to "cheap" worker on GitHub Actions - ..Default::default() - }); - let res = authorizer.authorize_with_limits(AuthorizerLimits { max_time: Duration::from_secs(10), ..Default::default() @@ -1696,21 +1233,20 @@ mod tests { fn authorizer_display_before_and_after_authorization() { let root = KeyPair::new(); - let mut token_builder = BiscuitBuilder::new(); - token_builder - .add_code( + let token = BiscuitBuilder::new() + .code( r#" authority_fact(true); authority_rule($v) <- authority_fact($v); check if authority_fact(true), authority_rule(true); "#, ) + .unwrap() + .build(&root) .unwrap(); - let token = token_builder.build(&root).unwrap(); - let mut authorizer = token.authorizer().unwrap(); - authorizer - .add_code( + let mut authorizer = AuthorizerBuilder::new() + .code( r#" authorizer_fact(true); authorizer_rule($v) <- authorizer_fact($v); @@ -1718,6 +1254,8 @@ mod tests { allow if true; "#, ) + .unwrap() + .build(&token) .unwrap(); let output_before_authorization = authorizer.to_string(); @@ -1772,4 +1310,67 @@ allow if true; let authorizer = Authorizer::new(); assert_eq!("", authorizer.to_string()) } + + #[test] + fn rule_validate_variables() { + let builder = AuthorizerBuilder::new(); + let mut syms = SymbolTable::new(); + let rule_name = syms.insert("test"); + let pred_name = syms.insert("pred"); + let rule = datalog::rule( + rule_name, + &[datalog::var(&mut syms, "unbound")], + &[datalog::pred(pred_name, &[datalog::var(&mut syms, "any")])], + ); + let mut block = Block { + symbols: syms.clone(), + facts: vec![], + rules: vec![rule], + checks: vec![], + context: None, + version: DATALOG_3_1, + external_key: None, + public_keys: PublicKeys::new(), + scopes: vec![], + }; + + // FIXME + assert_eq!( + /*builder + .load_and_translate_block(&mut block, 0, &syms)*/ + load_and_translate_block( + &mut block, + 0, + &syms, + &mut SymbolTable::new(), + &mut HashMap::new(), + &mut World::new(), + ) + .unwrap_err(), + error::Token::FailedLogic(error::Logic::InvalidBlockRule( + 0, + "test($unbound) <- pred($any)".to_string() + )) + ); + + // broken rules directly added to the authorizer currently don’t trigger any error, but silently fail to generate facts when they match + let mut authorizer = builder + .rule(builder::rule( + "test", + &[var("unbound")], + &[builder::pred("pred", &[builder::var("any")])], + )) + .unwrap() + .build_unauthenticated() + .unwrap(); + let res: Vec<(String,)> = authorizer + .query(builder::rule( + "output", + &[builder::string("x")], + &[builder::pred("test", &[builder::var("any")])], + )) + .unwrap(); + + assert_eq!(res, vec![]); + } } diff --git a/biscuit-auth/src/token/authorizer/snapshot.rs b/biscuit-auth/src/token/authorizer/snapshot.rs index 373aff9f..95b06d79 100644 --- a/biscuit-auth/src/token/authorizer/snapshot.rs +++ b/biscuit-auth/src/token/authorizer/snapshot.rs @@ -2,7 +2,7 @@ use prost::Message; use std::{collections::HashMap, time::Duration}; use crate::{ - builder::{BlockBuilder, Convert, Policy}, + builder::{load_and_translate_block, BlockBuilder, Convert, Policy}, datalog::{Origin, RunLimits, TrustedOrigins}, error, format::{ @@ -69,7 +69,8 @@ impl super::Authorizer { authorizer.authorizer_block_builder = authorizer_block_builder; authorizer.policies = policies; authorizer.limits = limits; - authorizer.execution_time = execution_time; + authorizer.execution_time = + Some(execution_time).filter(|_| execution_time > Duration::default()); let mut public_key_to_block_id: HashMap> = HashMap::new(); let mut blocks = Vec::new(); @@ -91,7 +92,14 @@ impl super::Authorizer { .push(i); } - authorizer.load_and_translate_block(&mut block, i, &token_symbols)?; + load_and_translate_block( + &mut block, + i, + &token_symbols, + &mut authorizer.symbols, + &mut public_key_to_block_id, + &mut authorizer.world, + )?; blocks.push(block); } @@ -107,6 +115,46 @@ impl super::Authorizer { authorizer.blocks = Some(blocks); } + let mut authorizer_origin = Origin::default(); + authorizer_origin.insert(usize::MAX); + + let authorizer_scopes: Vec = authorizer + .authorizer_block_builder + .scopes + .clone() + .iter() + .map(|s| s.convert(&mut authorizer.symbols)) + .collect(); + + let authorizer_trusted_origins = TrustedOrigins::from_scopes( + &authorizer_scopes, + &TrustedOrigins::default(), + usize::MAX, + &authorizer.public_key_to_block_id, + ); + for fact in &authorizer.authorizer_block_builder.facts { + authorizer + .world + .facts + .insert(&authorizer_origin, fact.convert(&mut authorizer.symbols)); + } + + for rule in &authorizer.authorizer_block_builder.rules { + let rule = rule.convert(&mut authorizer.symbols); + + let rule_trusted_origins = TrustedOrigins::from_scopes( + &rule.scopes, + &authorizer_trusted_origins, + usize::MAX, + &authorizer.public_key_to_block_id, + ); + + authorizer + .world + .rules + .insert(usize::MAX, &rule_trusted_origins, rule); + } + for GeneratedFacts { origins, facts } in world.generated_facts { let origin = proto_origin_to_authorizer_origin(&origins)?; @@ -200,7 +248,7 @@ impl super::Authorizer { Ok(schema::AuthorizerSnapshot { world, - execution_time: self.execution_time.as_nanos() as u64, + execution_time: self.execution_time.unwrap_or_default().as_nanos() as u64, limits: schema::RunLimits { max_facts: self.limits.max_facts, max_iterations: self.limits.max_iterations, @@ -224,7 +272,7 @@ impl super::Authorizer { } } -fn authorizer_origin_to_proto_origin(origin: &Origin) -> Vec { +pub(crate) fn authorizer_origin_to_proto_origin(origin: &Origin) -> Vec { origin .inner .iter() @@ -242,7 +290,9 @@ fn authorizer_origin_to_proto_origin(origin: &Origin) -> Vec { .collect() } -fn proto_origin_to_authorizer_origin(origins: &[schema::Origin]) -> Result { +pub(crate) fn proto_origin_to_authorizer_origin( + origins: &[schema::Origin], +) -> Result { let mut new_origin = Origin::default(); for origin in origins { @@ -261,3 +311,171 @@ fn proto_origin_to_authorizer_origin(origins: &[schema::Origin]) -> Result, - pub rules: Vec, - pub checks: Vec, - pub scopes: Vec, - pub context: Option, -} - -impl BlockBuilder { - pub fn new() -> BlockBuilder { - BlockBuilder::default() - } - - pub fn merge(&mut self, mut other: BlockBuilder) { - self.facts.append(&mut other.facts); - self.rules.append(&mut other.rules); - self.checks.append(&mut other.checks); - - if let Some(c) = other.context { - self.set_context(c); - } - } - - pub fn add_fact>(&mut self, fact: F) -> Result<(), error::Token> - where - error::Token: From<>::Error>, - { - let fact = fact.try_into()?; - fact.validate()?; - - self.facts.push(fact); - Ok(()) - } - - pub fn add_rule>(&mut self, rule: R) -> Result<(), error::Token> - where - error::Token: From<>::Error>, - { - let rule = rule.try_into()?; - rule.validate_parameters()?; - self.rules.push(rule); - Ok(()) - } - - pub fn add_check>(&mut self, check: C) -> Result<(), error::Token> - where - error::Token: From<>::Error>, - { - let check = check.try_into()?; - check.validate_parameters()?; - self.checks.push(check); - Ok(()) - } - - pub fn add_code>(&mut self, source: T) -> Result<(), error::Token> { - self.add_code_with_params(source, HashMap::new(), HashMap::new()) - } - - /// Add datalog code to the builder, performing parameter subsitution as required - /// Unknown parameters are ignored - pub fn add_code_with_params>( - &mut self, - source: T, - params: HashMap, - scope_params: HashMap, - ) -> Result<(), error::Token> { - let input = source.as_ref(); - - let source_result = parse_block_source(input).map_err(|e| { - let e2: biscuit_parser::error::LanguageError = e.into(); - e2 - })?; - - for (_, fact) in source_result.facts.into_iter() { - let mut fact: Fact = fact.into(); - for (name, value) in ¶ms { - let res = match fact.set(name, value) { - Ok(_) => Ok(()), - Err(error::Token::Language( - biscuit_parser::error::LanguageError::Parameters { - missing_parameters, .. - }, - )) if missing_parameters.is_empty() => Ok(()), - Err(e) => Err(e), - }; - res?; - } - fact.validate()?; - self.facts.push(fact); - } - - for (_, rule) in source_result.rules.into_iter() { - let mut rule: Rule = rule.into(); - for (name, value) in ¶ms { - let res = match rule.set(name, value) { - Ok(_) => Ok(()), - Err(error::Token::Language( - biscuit_parser::error::LanguageError::Parameters { - missing_parameters, .. - }, - )) if missing_parameters.is_empty() => Ok(()), - Err(e) => Err(e), - }; - res?; - } - for (name, value) in &scope_params { - let res = match rule.set_scope(name, *value) { - Ok(_) => Ok(()), - Err(error::Token::Language( - biscuit_parser::error::LanguageError::Parameters { - missing_parameters, .. - }, - )) if missing_parameters.is_empty() => Ok(()), - Err(e) => Err(e), - }; - res?; - } - rule.validate_parameters()?; - self.rules.push(rule); - } - - for (_, check) in source_result.checks.into_iter() { - let mut check: Check = check.into(); - for (name, value) in ¶ms { - let res = match check.set(name, value) { - Ok(_) => Ok(()), - Err(error::Token::Language( - biscuit_parser::error::LanguageError::Parameters { - missing_parameters, .. - }, - )) if missing_parameters.is_empty() => Ok(()), - Err(e) => Err(e), - }; - res?; - } - for (name, value) in &scope_params { - let res = match check.set_scope(name, *value) { - Ok(_) => Ok(()), - Err(error::Token::Language( - biscuit_parser::error::LanguageError::Parameters { - missing_parameters, .. - }, - )) if missing_parameters.is_empty() => Ok(()), - Err(e) => Err(e), - }; - res?; - } - check.validate_parameters()?; - self.checks.push(check); - } - - Ok(()) - } - - pub fn add_scope(&mut self, scope: Scope) { - self.scopes.push(scope); - } - - pub fn set_context(&mut self, context: String) { - self.context = Some(context); - } - - pub(crate) fn build(self, mut symbols: SymbolTable) -> Block { - let symbols_start = symbols.current_offset(); - let public_keys_start = symbols.public_keys.current_offset(); - - let mut facts = Vec::new(); - for fact in self.facts { - facts.push(fact.convert(&mut symbols)); - } - - let mut rules = Vec::new(); - for rule in &self.rules { - rules.push(rule.convert(&mut symbols)); - } - - let mut checks = Vec::new(); - for check in &self.checks { - checks.push(check.convert(&mut symbols)); - } - - let mut scopes = Vec::new(); - for scope in &self.scopes { - scopes.push(scope.convert(&mut symbols)); - } - - let new_syms = symbols.split_at(symbols_start); - let public_keys = symbols.public_keys.split_at(public_keys_start); - let schema_version = get_schema_version(&facts, &rules, &checks, &scopes); - - Block { - symbols: new_syms, - facts, - rules, - checks, - context: self.context, - version: schema_version.version(), - external_key: None, - public_keys, - scopes, - } - } - - pub(crate) fn convert_from( - block: &Block, - symbols: &SymbolTable, - ) -> Result { - Ok(BlockBuilder { - facts: block - .facts - .iter() - .map(|f| Fact::convert_from(f, symbols)) - .collect::, error::Format>>()?, - rules: block - .rules - .iter() - .map(|r| Rule::convert_from(r, symbols)) - .collect::, error::Format>>()?, - checks: block - .checks - .iter() - .map(|c| Check::convert_from(c, symbols)) - .collect::, error::Format>>()?, - scopes: block - .scopes - .iter() - .map(|s| Scope::convert_from(s, symbols)) - .collect::, error::Format>>()?, - context: block.context.clone(), - }) - } - - // still used in tests but does not make sense for the public API - #[cfg(test)] - pub(crate) fn check_right(&mut self, right: &str) { - let check = rule( - "check_right", - &[string(right)], - &[ - pred("resource", &[var("resource_name")]), - pred("operation", &[string(right)]), - pred("right", &[var("resource_name"), string(right)]), - ], - ); - - let _ = self.add_check(check); - } -} - -impl fmt::Display for BlockBuilder { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - for mut fact in self.facts.clone().into_iter() { - fact.apply_parameters(); - writeln!(f, "{};", &fact)?; - } - for mut rule in self.rules.clone().into_iter() { - rule.apply_parameters(); - writeln!(f, "{};", &rule)?; - } - for mut check in self.checks.clone().into_iter() { - check.apply_parameters(); - writeln!(f, "{};", &check)?; - } - Ok(()) - } -} - -/// creates a Biscuit -#[derive(Clone, Default)] -pub struct BiscuitBuilder { - inner: BlockBuilder, - root_key_id: Option, -} - -impl BiscuitBuilder { - pub fn new() -> BiscuitBuilder { - BiscuitBuilder { - inner: BlockBuilder::new(), - root_key_id: None, - } - } - - pub fn merge(&mut self, other: BlockBuilder) { - self.inner.merge(other) - } - - pub fn add_fact>(&mut self, fact: F) -> Result<(), error::Token> - where - error::Token: From<>::Error>, - { - self.inner.add_fact(fact) - } - - pub fn add_rule>(&mut self, rule: Ru) -> Result<(), error::Token> - where - error::Token: From<>::Error>, - { - self.inner.add_rule(rule) - } - - pub fn add_check>(&mut self, check: C) -> Result<(), error::Token> - where - error::Token: From<>::Error>, - { - self.inner.add_check(check) - } - - pub fn add_code>(&mut self, source: T) -> Result<(), error::Token> { - self.inner - .add_code_with_params(source, HashMap::new(), HashMap::new()) - } - - pub fn add_code_with_params>( - &mut self, - source: T, - params: HashMap, - scope_params: HashMap, - ) -> Result<(), error::Token> { - self.inner - .add_code_with_params(source, params, scope_params) - } - - pub fn add_scope(&mut self, scope: Scope) { - self.inner.add_scope(scope); - } - - #[cfg(test)] - pub(crate) fn add_right(&mut self, resource: &str, right: &str) { - let _ = self.add_fact(fact("right", &[string(resource), string(right)])); - } - - pub fn set_context(&mut self, context: String) { - self.inner.set_context(context); - } - - pub fn set_root_key_id(&mut self, root_key_id: u32) { - self.root_key_id = Some(root_key_id); - } - - /// returns all of the datalog loaded in the biscuit builder - pub fn dump(&self) -> (Vec, Vec, Vec) { - ( - self.inner.facts.clone(), - self.inner.rules.clone(), - self.inner.checks.clone(), - ) - } - - pub fn dump_code(&self) -> String { - let (facts, rules, checks) = self.dump(); - let mut f = String::new(); - for fact in facts { - let _ = writeln!(f, "{};", fact); - } - for rule in rules { - let _ = writeln!(f, "{};", rule); - } - for check in checks { - let _ = writeln!(f, "{};", check); - } - f - } - - pub fn build(self, root_key: &KeyPair) -> Result { - self.build_with_symbols(root_key, default_symbol_table()) - } - - pub fn build_with_symbols( - self, - root_key: &KeyPair, - symbols: SymbolTable, - ) -> Result { - self.build_with_rng(root_key, symbols, &mut rand::rngs::OsRng) - } - - pub fn build_with_rng( - self, - root: &KeyPair, - symbols: SymbolTable, - rng: &mut R, - ) -> Result { - let authority_block = self.inner.build(symbols.clone()); - Biscuit::new_with_rng(rng, self.root_key_id, root, symbols, authority_block) - } -} - -pub trait Convert: Sized { - fn convert(&self, symbols: &mut SymbolTable) -> T; - fn convert_from(f: &T, symbols: &SymbolTable) -> Result; - fn translate( - f: &T, - from_symbols: &SymbolTable, - to_symbols: &mut SymbolTable, - ) -> Result { - Ok(Self::convert_from(f, from_symbols)?.convert(to_symbols)) - } -} - -/// Builder for a Datalog value -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum Term { - Variable(String), - Integer(i64), - Str(String), - Date(u64), - Bytes(Vec), - Bool(bool), - Set(BTreeSet), - Parameter(String), -} - -impl Convert for Term { - fn convert(&self, symbols: &mut SymbolTable) -> datalog::Term { - match self { - Term::Variable(s) => datalog::Term::Variable(symbols.insert(s) as u32), - Term::Integer(i) => datalog::Term::Integer(*i), - Term::Str(s) => datalog::Term::Str(symbols.insert(s)), - Term::Date(d) => datalog::Term::Date(*d), - Term::Bytes(s) => datalog::Term::Bytes(s.clone()), - Term::Bool(b) => datalog::Term::Bool(*b), - Term::Set(s) => datalog::Term::Set(s.iter().map(|i| i.convert(symbols)).collect()), - // The error is caught in the `add_xxx` functions, so this should - // not happen™ - Term::Parameter(s) => panic!("Remaining parameter {}", &s), - } - } - - fn convert_from(f: &datalog::Term, symbols: &SymbolTable) -> Result { - Ok(match f { - datalog::Term::Variable(s) => Term::Variable(symbols.print_symbol(*s as u64)?), - datalog::Term::Integer(i) => Term::Integer(*i), - datalog::Term::Str(s) => Term::Str(symbols.print_symbol(*s)?), - datalog::Term::Date(d) => Term::Date(*d), - datalog::Term::Bytes(s) => Term::Bytes(s.clone()), - datalog::Term::Bool(b) => Term::Bool(*b), - datalog::Term::Set(s) => Term::Set( - s.iter() - .map(|i| Term::convert_from(i, symbols)) - .collect::, error::Format>>()?, - ), - }) - } -} - -impl From<&Term> for Term { - fn from(i: &Term) -> Self { - match i { - Term::Variable(ref v) => Term::Variable(v.clone()), - Term::Integer(ref i) => Term::Integer(*i), - Term::Str(ref s) => Term::Str(s.clone()), - Term::Date(ref d) => Term::Date(*d), - Term::Bytes(ref s) => Term::Bytes(s.clone()), - Term::Bool(b) => Term::Bool(*b), - Term::Set(ref s) => Term::Set(s.clone()), - Term::Parameter(ref p) => Term::Parameter(p.clone()), - } - } -} - -impl From for Term { - fn from(t: biscuit_parser::builder::Term) -> Self { - match t { - biscuit_parser::builder::Term::Variable(v) => Term::Variable(v), - biscuit_parser::builder::Term::Integer(i) => Term::Integer(i), - biscuit_parser::builder::Term::Str(s) => Term::Str(s), - biscuit_parser::builder::Term::Date(d) => Term::Date(d), - biscuit_parser::builder::Term::Bytes(s) => Term::Bytes(s), - biscuit_parser::builder::Term::Bool(b) => Term::Bool(b), - biscuit_parser::builder::Term::Set(s) => { - Term::Set(s.into_iter().map(|t| t.into()).collect()) - } - biscuit_parser::builder::Term::Parameter(ref p) => Term::Parameter(p.clone()), - } - } -} - -impl AsRef for Term { - fn as_ref(&self) -> &Term { - self - } -} - -impl fmt::Display for Term { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Term::Variable(i) => write!(f, "${}", i), - Term::Integer(i) => write!(f, "{}", i), - Term::Str(s) => write!(f, "\"{}\"", s), - Term::Date(d) => { - let date = time::OffsetDateTime::from_unix_timestamp(*d as i64) - .ok() - .and_then(|t| { - t.format(&time::format_description::well_known::Rfc3339) - .ok() - }) - .unwrap_or_else(|| "".to_string()); - - write!(f, "{}", date) - } - Term::Bytes(s) => write!(f, "hex:{}", hex::encode(s)), - Term::Bool(b) => { - if *b { - write!(f, "true") - } else { - write!(f, "false") - } - } - Term::Set(s) => { - let terms = s.iter().map(|term| term.to_string()).collect::>(); - write!(f, "[{}]", terms.join(", ")) - } - Term::Parameter(s) => { - write!(f, "{{{}}}", s) - } - } - } -} - -/// Builder for a block or rule scope -#[derive(Clone, Debug, Hash, PartialEq, Eq)] -pub enum Scope { - /// Trusts the first block, current block and the authorizer - Authority, - /// Trusts the current block and all previous ones - Previous, - /// Trusts the current block and any block signed by the public key - PublicKey(PublicKey), - /// Used for parameter substitution - Parameter(String), -} - -impl Convert for Scope { - fn convert(&self, symbols: &mut SymbolTable) -> super::Scope { - match self { - Scope::Authority => crate::token::Scope::Authority, - Scope::Previous => crate::token::Scope::Previous, - Scope::PublicKey(key) => { - crate::token::Scope::PublicKey(symbols.public_keys.insert(key)) - } - // The error is caught in the `add_xxx` functions, so this should - // not happen™ - Scope::Parameter(s) => panic!("Remaining parameter {}", &s), - } - } - - fn convert_from(scope: &super::Scope, symbols: &SymbolTable) -> Result { - Ok(match scope { - super::Scope::Authority => Scope::Authority, - super::Scope::Previous => Scope::Previous, - super::Scope::PublicKey(key_id) => Scope::PublicKey( - *symbols - .public_keys - .get_key(*key_id) - .ok_or(error::Format::UnknownExternalKey)?, - ), - }) - } -} - -impl fmt::Display for Scope { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Scope::Authority => write!(f, "authority"), - Scope::Previous => write!(f, "previous"), - Scope::PublicKey(pk) => write!(f, "ed25519/{}", hex::encode(pk.to_bytes())), - Scope::Parameter(s) => { - write!(f, "{{{}}}", s) - } - } - } -} - -impl From for Scope { - fn from(scope: biscuit_parser::builder::Scope) -> Self { - match scope { - biscuit_parser::builder::Scope::Authority => Scope::Authority, - biscuit_parser::builder::Scope::Previous => Scope::Previous, - biscuit_parser::builder::Scope::PublicKey(pk) => { - Scope::PublicKey(PublicKey::from_bytes(&pk).expect("invalid public key")) - } - biscuit_parser::builder::Scope::Parameter(s) => Scope::Parameter(s), - } - } -} - -/// Builder for a Datalog dicate, used in facts and rules -#[derive(Debug, Clone, PartialEq, Hash, Eq)] -pub struct Predicate { - pub name: String, - pub terms: Vec, -} - -impl Predicate { - pub fn new>>(name: String, terms: T) -> Predicate { - Predicate { - name, - terms: terms.into(), - } - } -} - -impl Convert for Predicate { - fn convert(&self, symbols: &mut SymbolTable) -> datalog::Predicate { - let name = symbols.insert(&self.name); - let mut terms = vec![]; - - for term in self.terms.iter() { - terms.push(term.convert(symbols)); - } - - datalog::Predicate { name, terms } - } - - fn convert_from(p: &datalog::Predicate, symbols: &SymbolTable) -> Result { - Ok(Predicate { - name: symbols.print_symbol(p.name)?, - terms: p - .terms - .iter() - .map(|term| Term::convert_from(term, symbols)) - .collect::, error::Format>>()?, - }) - } -} - -impl AsRef for Predicate { - fn as_ref(&self) -> &Predicate { - self - } -} - -impl fmt::Display for Predicate { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}(", self.name)?; - - if !self.terms.is_empty() { - write!(f, "{}", self.terms[0])?; - - if self.terms.len() > 1 { - for i in 1..self.terms.len() { - write!(f, ", {}", self.terms[i])?; - } - } - } - write!(f, ")") - } -} - -impl From for Predicate { - fn from(p: biscuit_parser::builder::Predicate) -> Self { - Predicate { - name: p.name, - terms: p.terms.into_iter().map(|t| t.into()).collect(), - } - } -} - -/// Builder for a Datalog fact -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Fact { - pub predicate: Predicate, - pub parameters: Option>>, -} - -impl Fact { - pub fn new>>(name: String, terms: T) -> Fact { - let mut parameters = HashMap::new(); - let terms: Vec = terms.into(); - - for term in &terms { - if let Term::Parameter(name) = &term { - parameters.insert(name.to_string(), None); - } - } - Fact { - predicate: Predicate::new(name, terms), - parameters: Some(parameters), - } - } - - pub fn validate(&self) -> Result<(), error::Token> { - match &self.parameters { - None => Ok(()), - Some(parameters) => { - let invalid_parameters = parameters - .iter() - .filter_map( - |(name, opt_term)| { - if opt_term.is_none() { - Some(name) - } else { - None - } - }, - ) - .map(|name| name.to_string()) - .collect::>(); - - if invalid_parameters.is_empty() { - Ok(()) - } else { - Err(error::Token::Language( - biscuit_parser::error::LanguageError::Parameters { - missing_parameters: invalid_parameters, - unused_parameters: vec![], - }, - )) - } - } - } - } - - /// replace a parameter with the term argument - pub fn set>(&mut self, name: &str, term: T) -> Result<(), error::Token> { - if let Some(parameters) = self.parameters.as_mut() { - match parameters.get_mut(name) { - None => Err(error::Token::Language( - biscuit_parser::error::LanguageError::Parameters { - missing_parameters: vec![], - unused_parameters: vec![name.to_string()], - }, - )), - Some(v) => { - *v = Some(term.into()); - Ok(()) - } - } - } else { - Err(error::Token::Language( - biscuit_parser::error::LanguageError::Parameters { - missing_parameters: vec![], - unused_parameters: vec![name.to_string()], - }, - )) - } - } - - /// replace a parameter with the term argument, without raising an error - /// if the parameter is not present in the fact description - pub fn set_lenient>(&mut self, name: &str, term: T) -> Result<(), error::Token> { - if let Some(parameters) = self.parameters.as_mut() { - match parameters.get_mut(name) { - None => Ok(()), - Some(v) => { - *v = Some(term.into()); - Ok(()) - } - } - } else { - Err(error::Token::Language( - biscuit_parser::error::LanguageError::Parameters { - missing_parameters: vec![], - unused_parameters: vec![name.to_string()], - }, - )) - } - } - - #[cfg(feature = "datalog-macro")] - pub fn set_macro_param( - &mut self, - name: &str, - param: T, - ) -> Result<(), error::Token> { - match param.to_any_param() { - AnyParam::Term(t) => self.set_lenient(name, t), - AnyParam::PublicKey(_) => Ok(()), - } - } - - fn apply_parameters(&mut self) { - if let Some(parameters) = self.parameters.clone() { - self.predicate.terms = self - .predicate - .terms - .drain(..) - .map(|t| { - if let Term::Parameter(name) = &t { - if let Some(Some(term)) = parameters.get(name) { - return term.clone(); - } - } - t - }) - .collect(); - } - } -} - -impl Convert for Fact { - fn convert(&self, symbols: &mut SymbolTable) -> datalog::Fact { - let mut fact = self.clone(); - fact.apply_parameters(); - - datalog::Fact { - predicate: fact.predicate.convert(symbols), - } - } - - fn convert_from(f: &datalog::Fact, symbols: &SymbolTable) -> Result { - Ok(Fact { - predicate: Predicate::convert_from(&f.predicate, symbols)?, - parameters: None, - }) - } -} - -impl fmt::Display for Fact { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut fact = self.clone(); - fact.apply_parameters(); - - fact.predicate.fmt(f) - } -} - -impl From for Fact { - fn from(f: biscuit_parser::builder::Fact) -> Self { - Fact { - predicate: f.predicate.into(), - // pub parameters: Option>>, - parameters: f.parameters.map(|h| { - h.into_iter() - .map(|(k, v)| (k, v.map(|term| term.into()))) - .collect() - }), - } - } -} - -/// Builder for a Datalog expression -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Expression { - pub ops: Vec, -} -// todo track parameters - -impl Convert for Expression { - fn convert(&self, symbols: &mut SymbolTable) -> datalog::Expression { - datalog::Expression { - ops: self.ops.iter().map(|op| op.convert(symbols)).collect(), - } - } - - fn convert_from(e: &datalog::Expression, symbols: &SymbolTable) -> Result { - Ok(Expression { - ops: e - .ops - .iter() - .map(|op| Op::convert_from(op, symbols)) - .collect::, error::Format>>()?, - }) - } -} - -impl AsRef for Expression { - fn as_ref(&self) -> &Expression { - self - } -} - -impl fmt::Display for Expression { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut syms = super::default_symbol_table(); - let expr = self.convert(&mut syms); - let s = expr.print(&syms).unwrap(); - write!(f, "{}", s) - } -} - -impl From for Expression { - fn from(e: biscuit_parser::builder::Expression) -> Self { - Expression { - ops: e.ops.into_iter().map(|op| op.into()).collect(), - } - } -} - -/// Builder for an expression operation -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum Op { - Value(Term), - Unary(Unary), - Binary(Binary), -} - -impl Convert for Op { - fn convert(&self, symbols: &mut SymbolTable) -> datalog::Op { - match self { - Op::Value(t) => datalog::Op::Value(t.convert(symbols)), - Op::Unary(u) => datalog::Op::Unary(u.clone()), - Op::Binary(b) => datalog::Op::Binary(b.clone()), - } - } - - fn convert_from(op: &datalog::Op, symbols: &SymbolTable) -> Result { - Ok(match op { - datalog::Op::Value(t) => Op::Value(Term::convert_from(t, symbols)?), - datalog::Op::Unary(u) => Op::Unary(u.clone()), - datalog::Op::Binary(b) => Op::Binary(b.clone()), - }) - } -} - -impl From for Op { - fn from(op: biscuit_parser::builder::Op) -> Self { - match op { - biscuit_parser::builder::Op::Value(t) => Op::Value(t.into()), - biscuit_parser::builder::Op::Unary(u) => Op::Unary(u.into()), - biscuit_parser::builder::Op::Binary(b) => Op::Binary(b.into()), - } - } -} - -impl From for Unary { - fn from(unary: biscuit_parser::builder::Unary) -> Self { - match unary { - biscuit_parser::builder::Unary::Negate => Unary::Negate, - biscuit_parser::builder::Unary::Parens => Unary::Parens, - biscuit_parser::builder::Unary::Length => Unary::Length, - } - } -} - -impl From for Binary { - fn from(binary: biscuit_parser::builder::Binary) -> Self { - match binary { - biscuit_parser::builder::Binary::LessThan => Binary::LessThan, - biscuit_parser::builder::Binary::GreaterThan => Binary::GreaterThan, - biscuit_parser::builder::Binary::LessOrEqual => Binary::LessOrEqual, - biscuit_parser::builder::Binary::GreaterOrEqual => Binary::GreaterOrEqual, - biscuit_parser::builder::Binary::Equal => Binary::Equal, - biscuit_parser::builder::Binary::Contains => Binary::Contains, - biscuit_parser::builder::Binary::Prefix => Binary::Prefix, - biscuit_parser::builder::Binary::Suffix => Binary::Suffix, - biscuit_parser::builder::Binary::Regex => Binary::Regex, - biscuit_parser::builder::Binary::Add => Binary::Add, - biscuit_parser::builder::Binary::Sub => Binary::Sub, - biscuit_parser::builder::Binary::Mul => Binary::Mul, - biscuit_parser::builder::Binary::Div => Binary::Div, - biscuit_parser::builder::Binary::And => Binary::And, - biscuit_parser::builder::Binary::Or => Binary::Or, - biscuit_parser::builder::Binary::Intersection => Binary::Intersection, - biscuit_parser::builder::Binary::Union => Binary::Union, - biscuit_parser::builder::Binary::BitwiseAnd => Binary::BitwiseAnd, - biscuit_parser::builder::Binary::BitwiseOr => Binary::BitwiseOr, - biscuit_parser::builder::Binary::BitwiseXor => Binary::BitwiseXor, - biscuit_parser::builder::Binary::NotEqual => Binary::NotEqual, - } - } -} - -/// Builder for a Datalog rule -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Rule { - pub head: Predicate, - pub body: Vec, - pub expressions: Vec, - pub parameters: Option>>, - pub scopes: Vec, - pub scope_parameters: Option>>, -} - -impl Rule { - pub fn new( - head: Predicate, - body: Vec, - expressions: Vec, - scopes: Vec, - ) -> Rule { - let mut parameters = HashMap::new(); - let mut scope_parameters = HashMap::new(); - for term in &head.terms { - if let Term::Parameter(name) = &term { - parameters.insert(name.to_string(), None); - } - } - - for predicate in &body { - for term in &predicate.terms { - if let Term::Parameter(name) = &term { - parameters.insert(name.to_string(), None); - } - } - } - - for expression in &expressions { - for op in &expression.ops { - if let Op::Value(Term::Parameter(name)) = &op { - parameters.insert(name.to_string(), None); - } - } - } - - for scope in &scopes { - if let Scope::Parameter(name) = &scope { - scope_parameters.insert(name.to_string(), None); - } - } - - Rule { - head, - body, - expressions, - parameters: Some(parameters), - scopes, - scope_parameters: Some(scope_parameters), - } - } - - pub fn validate_parameters(&self) -> Result<(), error::Token> { - let mut invalid_parameters = match &self.parameters { - None => vec![], - Some(parameters) => parameters - .iter() - .filter_map( - |(name, opt_term)| { - if opt_term.is_none() { - Some(name) - } else { - None - } - }, - ) - .map(|name| name.to_string()) - .collect::>(), - }; - let mut invalid_scope_parameters = match &self.scope_parameters { - None => vec![], - Some(parameters) => parameters - .iter() - .filter_map( - |(name, opt_key)| { - if opt_key.is_none() { - Some(name) - } else { - None - } - }, - ) - .map(|name| name.to_string()) - .collect::>(), - }; - let mut all_invalid_parameters = vec![]; - all_invalid_parameters.append(&mut invalid_parameters); - all_invalid_parameters.append(&mut invalid_scope_parameters); - - if all_invalid_parameters.is_empty() { - Ok(()) - } else { - Err(error::Token::Language( - biscuit_parser::error::LanguageError::Parameters { - missing_parameters: all_invalid_parameters, - unused_parameters: vec![], - }, - )) - } - } - - pub fn validate_variables(&self) -> Result<(), String> { - let mut head_variables: std::collections::HashSet = self - .head - .terms - .iter() - .filter_map(|term| match term { - Term::Variable(s) => Some(s.to_string()), - _ => None, - }) - .collect(); - - for predicate in self.body.iter() { - for term in predicate.terms.iter() { - if let Term::Variable(v) = term { - head_variables.remove(v); - if head_variables.is_empty() { - return Ok(()); - } - } - } - } - - if head_variables.is_empty() { - Ok(()) - } else { - Err(format!( - "rule head contains variables that are not used in predicates of the rule's body: {}", - head_variables - .iter() - .map(|s| format!("${}", s)) - .collect::>() - .join(", ") - )) - } - } - - /// replace a parameter with the term argument - pub fn set>(&mut self, name: &str, term: T) -> Result<(), error::Token> { - if let Some(parameters) = self.parameters.as_mut() { - match parameters.get_mut(name) { - None => Err(error::Token::Language( - biscuit_parser::error::LanguageError::Parameters { - missing_parameters: vec![], - unused_parameters: vec![name.to_string()], - }, - )), - Some(v) => { - *v = Some(term.into()); - Ok(()) - } - } - } else { - Err(error::Token::Language( - biscuit_parser::error::LanguageError::Parameters { - missing_parameters: vec![], - unused_parameters: vec![name.to_string()], - }, - )) - } - } - - /// replace a parameter with the term argument, without raising an error if the - /// parameter is not present in the rule - pub fn set_lenient>(&mut self, name: &str, term: T) -> Result<(), error::Token> { - if let Some(parameters) = self.parameters.as_mut() { - match parameters.get_mut(name) { - None => Ok(()), - Some(v) => { - *v = Some(term.into()); - Ok(()) - } - } - } else { - Err(error::Token::Language( - biscuit_parser::error::LanguageError::Parameters { - missing_parameters: vec![], - unused_parameters: vec![name.to_string()], - }, - )) - } - } - - /// replace a scope parameter with the pubkey argument - pub fn set_scope(&mut self, name: &str, pubkey: PublicKey) -> Result<(), error::Token> { - if let Some(parameters) = self.scope_parameters.as_mut() { - match parameters.get_mut(name) { - None => Err(error::Token::Language( - biscuit_parser::error::LanguageError::Parameters { - missing_parameters: vec![], - unused_parameters: vec![name.to_string()], - }, - )), - Some(v) => { - *v = Some(pubkey); - Ok(()) - } - } - } else { - Err(error::Token::Language( - biscuit_parser::error::LanguageError::Parameters { - missing_parameters: vec![], - unused_parameters: vec![name.to_string()], - }, - )) - } - } - - /// replace a scope parameter with the public key argument, without raising an error if the - /// parameter is not present in the rule scope - pub fn set_scope_lenient(&mut self, name: &str, pubkey: PublicKey) -> Result<(), error::Token> { - if let Some(parameters) = self.scope_parameters.as_mut() { - match parameters.get_mut(name) { - None => Ok(()), - Some(v) => { - *v = Some(pubkey); - Ok(()) - } - } - } else { - Err(error::Token::Language( - biscuit_parser::error::LanguageError::Parameters { - missing_parameters: vec![], - unused_parameters: vec![name.to_string()], - }, - )) - } - } - - #[cfg(feature = "datalog-macro")] - pub fn set_macro_param( - &mut self, - name: &str, - param: T, - ) -> Result<(), error::Token> { - match param.to_any_param() { - AnyParam::Term(t) => self.set_lenient(name, t), - AnyParam::PublicKey(pubkey) => self.set_scope_lenient(name, pubkey), - } - } - - fn apply_parameters(&mut self) { - if let Some(parameters) = self.parameters.clone() { - self.head.terms = self - .head - .terms - .drain(..) - .map(|t| { - if let Term::Parameter(name) = &t { - if let Some(Some(term)) = parameters.get(name) { - return term.clone(); - } - } - t - }) - .collect(); - - for predicate in &mut self.body { - predicate.terms = predicate - .terms - .drain(..) - .map(|t| { - if let Term::Parameter(name) = &t { - if let Some(Some(term)) = parameters.get(name) { - return term.clone(); - } - } - t - }) - .collect(); - } - - for expression in &mut self.expressions { - expression.ops = expression - .ops - .drain(..) - .map(|op| { - if let Op::Value(Term::Parameter(name)) = &op { - if let Some(Some(term)) = parameters.get(name) { - return Op::Value(term.clone()); - } - } - op - }) - .collect(); - } - } - - if let Some(parameters) = self.scope_parameters.clone() { - self.scopes = self - .scopes - .drain(..) - .map(|scope| { - if let Scope::Parameter(name) = &scope { - if let Some(Some(pubkey)) = parameters.get(name) { - return Scope::PublicKey(*pubkey); - } - } - scope - }) - .collect(); - } - } -} - -impl Convert for Rule { - fn convert(&self, symbols: &mut SymbolTable) -> datalog::Rule { - let mut r = self.clone(); - r.apply_parameters(); - - let head = r.head.convert(symbols); - let mut body = vec![]; - let mut expressions = vec![]; - let mut scopes = vec![]; - - for p in r.body.iter() { - body.push(p.convert(symbols)); - } - - for c in r.expressions.iter() { - expressions.push(c.convert(symbols)); - } - - for scope in r.scopes.iter() { - scopes.push(match scope { - Scope::Authority => crate::token::Scope::Authority, - Scope::Previous => crate::token::Scope::Previous, - Scope::PublicKey(key) => { - crate::token::Scope::PublicKey(symbols.public_keys.insert(key)) - } - // The error is caught in the `add_xxx` functions, so this should - // not happen™ - Scope::Parameter(s) => panic!("Remaining parameter {}", &s), - }) - } - datalog::Rule { - head, - body, - expressions, - scopes, - } - } - - fn convert_from(r: &datalog::Rule, symbols: &SymbolTable) -> Result { - Ok(Rule { - head: Predicate::convert_from(&r.head, symbols)?, - body: r - .body - .iter() - .map(|p| Predicate::convert_from(p, symbols)) - .collect::, error::Format>>()?, - expressions: r - .expressions - .iter() - .map(|c| Expression::convert_from(c, symbols)) - .collect::, error::Format>>()?, - parameters: None, - scopes: r - .scopes - .iter() - .map(|scope| Scope::convert_from(scope, symbols)) - .collect::, error::Format>>()?, - scope_parameters: None, - }) - } -} - -fn display_rule_body(r: &Rule, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut rule = r.clone(); - rule.apply_parameters(); - if !rule.body.is_empty() { - write!(f, "{}", rule.body[0])?; - - if rule.body.len() > 1 { - for i in 1..rule.body.len() { - write!(f, ", {}", rule.body[i])?; - } - } - } - - if !rule.expressions.is_empty() { - if !rule.body.is_empty() { - write!(f, ", ")?; - } - - write!(f, "{}", rule.expressions[0])?; - - if rule.expressions.len() > 1 { - for i in 1..rule.expressions.len() { - write!(f, ", {}", rule.expressions[i])?; - } - } - } - - if !rule.scopes.is_empty() { - write!(f, " trusting {}", rule.scopes[0])?; - if rule.scopes.len() > 1 { - for i in 1..rule.scopes.len() { - write!(f, ", {}", rule.scopes[i])?; - } - } - } - - Ok(()) -} - -impl fmt::Display for Rule { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut r = self.clone(); - r.apply_parameters(); - - write!(f, "{} <- ", r.head)?; - - display_rule_body(&r, f) - } -} - -impl From for Rule { - fn from(r: biscuit_parser::builder::Rule) -> Self { - Rule { - head: r.head.into(), - body: r.body.into_iter().map(|p| p.into()).collect(), - expressions: r.expressions.into_iter().map(|e| e.into()).collect(), - parameters: r.parameters.map(|h| { - h.into_iter() - .map(|(k, v)| (k, v.map(|term| term.into()))) - .collect() - }), - scopes: r.scopes.into_iter().map(|s| s.into()).collect(), - scope_parameters: r.scope_parameters.map(|h| { - h.into_iter() - .map(|(k, v)| { - ( - k, - v.map(|bytes| { - PublicKey::from_bytes(&bytes).expect("invalid public key") - }), - ) - }) - .collect() - }), - } - } -} - -/// Builder for a Biscuit check -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Check { - pub queries: Vec, - pub kind: CheckKind, -} - -/// Builder for a Biscuit check -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum CheckKind { - One, - All, -} - -impl Check { - /// replace a parameter with the term argument - pub fn set>(&mut self, name: &str, term: T) -> Result<(), error::Token> { - let term = term.into(); - self.set_inner(name, term) - } - - fn set_inner(&mut self, name: &str, term: Term) -> Result<(), error::Token> { - let mut found = false; - for query in &mut self.queries { - if query.set(name, term.clone()).is_ok() { - found = true; - } - } - - if found { - Ok(()) - } else { - Err(error::Token::Language( - biscuit_parser::error::LanguageError::Parameters { - missing_parameters: vec![], - unused_parameters: vec![name.to_string()], - }, - )) - } - } - - /// replace a scope parameter with the pubkey argument - pub fn set_scope(&mut self, name: &str, pubkey: PublicKey) -> Result<(), error::Token> { - let mut found = false; - for query in &mut self.queries { - if query.set_scope(name, pubkey).is_ok() { - found = true; - } - } - - if found { - Ok(()) - } else { - Err(error::Token::Language( - biscuit_parser::error::LanguageError::Parameters { - missing_parameters: vec![], - unused_parameters: vec![name.to_string()], - }, - )) - } - } - - /// replace a parameter with the term argument, without raising an error if the - /// parameter is not present in the check - pub fn set_lenient>(&mut self, name: &str, term: T) -> Result<(), error::Token> { - let term = term.into(); - for query in &mut self.queries { - query.set_lenient(name, term.clone())?; - } - Ok(()) - } - - /// replace a scope parameter with the term argument, without raising an error if the - /// parameter is not present in the check - pub fn set_scope_lenient(&mut self, name: &str, pubkey: PublicKey) -> Result<(), error::Token> { - for query in &mut self.queries { - query.set_scope_lenient(name, pubkey)?; - } - Ok(()) - } - - #[cfg(feature = "datalog-macro")] - pub fn set_macro_param( - &mut self, - name: &str, - param: T, - ) -> Result<(), error::Token> { - match param.to_any_param() { - AnyParam::Term(t) => self.set_lenient(name, t), - AnyParam::PublicKey(p) => self.set_scope_lenient(name, p), - } - } - - pub fn validate_parameters(&self) -> Result<(), error::Token> { - for rule in &self.queries { - rule.validate_parameters()?; - } - - Ok(()) - } - - fn apply_parameters(&mut self) { - for rule in self.queries.iter_mut() { - rule.apply_parameters(); - } - } -} - -impl Convert for Check { - fn convert(&self, symbols: &mut SymbolTable) -> datalog::Check { - let mut queries = vec![]; - for q in self.queries.iter() { - queries.push(q.convert(symbols)); - } - - datalog::Check { - queries, - kind: self.kind.clone(), - } - } - - fn convert_from(r: &datalog::Check, symbols: &SymbolTable) -> Result { - let mut queries = vec![]; - for q in r.queries.iter() { - queries.push(Rule::convert_from(q, symbols)?); - } - - Ok(Check { - queries, - kind: r.kind.clone(), - }) - } -} - -impl TryFrom for Check { - type Error = error::Token; - - fn try_from(value: Rule) -> Result { - Ok(Check { - queries: vec![value], - kind: CheckKind::One, - }) - } -} - -impl TryFrom<&[Rule]> for Check { - type Error = error::Token; - - fn try_from(values: &[Rule]) -> Result { - Ok(Check { - queries: values.to_vec(), - kind: CheckKind::One, - }) - } -} - -impl fmt::Display for Check { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self.kind { - CheckKind::One => write!(f, "check if ")?, - CheckKind::All => write!(f, "check all ")?, - }; - if !self.queries.is_empty() { - let mut q0 = self.queries[0].clone(); - q0.apply_parameters(); - display_rule_body(&q0, f)?; - - if self.queries.len() > 1 { - for i in 1..self.queries.len() { - write!(f, " or ")?; - let mut qn = self.queries[i].clone(); - qn.apply_parameters(); - display_rule_body(&qn, f)?; - } - } - } - - Ok(()) - } -} - -impl From for Check { - fn from(c: biscuit_parser::builder::Check) -> Self { - Check { - queries: c.queries.into_iter().map(|q| q.into()).collect(), - kind: match c.kind { - biscuit_parser::builder::CheckKind::One => CheckKind::One, - biscuit_parser::builder::CheckKind::All => CheckKind::All, - }, - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum PolicyKind { - Allow, - Deny, -} - -/// Builder for a Biscuit policy -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Policy { - pub queries: Vec, - pub kind: PolicyKind, -} - -impl Policy { - /// replace a parameter with the term argument - pub fn set>(&mut self, name: &str, term: T) -> Result<(), error::Token> { - let term = term.into(); - self.set_inner(name, term) - } - - pub fn set_inner(&mut self, name: &str, term: Term) -> Result<(), error::Token> { - let mut found = false; - for query in &mut self.queries { - if query.set(name, term.clone()).is_ok() { - found = true; - } - } - - if found { - Ok(()) - } else { - Err(error::Token::Language( - biscuit_parser::error::LanguageError::Parameters { - missing_parameters: vec![], - unused_parameters: vec![name.to_string()], - }, - )) - } - } - - /// replace a scope parameter with the pubkey argument - pub fn set_scope(&mut self, name: &str, pubkey: PublicKey) -> Result<(), error::Token> { - let mut found = false; - for query in &mut self.queries { - if query.set_scope(name, pubkey).is_ok() { - found = true; - } - } - - if found { - Ok(()) - } else { - Err(error::Token::Language( - biscuit_parser::error::LanguageError::Parameters { - missing_parameters: vec![], - unused_parameters: vec![name.to_string()], - }, - )) - } - } - - /// replace a parameter with the term argument, ignoring unknown parameters - pub fn set_lenient>(&mut self, name: &str, term: T) -> Result<(), error::Token> { - let term = term.into(); - for query in &mut self.queries { - query.set_lenient(name, term.clone())?; - } - Ok(()) - } - - /// replace a scope parameter with the pubkey argument, ignoring unknown parameters - pub fn set_scope_lenient(&mut self, name: &str, pubkey: PublicKey) -> Result<(), error::Token> { - for query in &mut self.queries { - query.set_scope_lenient(name, pubkey)?; - } - Ok(()) - } - - #[cfg(feature = "datalog-macro")] - pub fn set_macro_param( - &mut self, - name: &str, - param: T, - ) -> Result<(), error::Token> { - match param.to_any_param() { - AnyParam::Term(t) => self.set_lenient(name, t), - AnyParam::PublicKey(p) => self.set_scope_lenient(name, p), - } - } - - pub fn validate_parameters(&self) -> Result<(), error::Token> { - for query in &self.queries { - query.validate_parameters()?; - } - - Ok(()) - } -} - -impl fmt::Display for Policy { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if !self.queries.is_empty() { - match self.kind { - PolicyKind::Allow => write!(f, "allow if ")?, - PolicyKind::Deny => write!(f, "deny if ")?, - } - - if !self.queries.is_empty() { - display_rule_body(&self.queries[0], f)?; +use std::{ + collections::BTreeSet, + time::{SystemTime, UNIX_EPOCH}, +}; - if self.queries.len() > 1 { - for i in 1..self.queries.len() { - write!(f, " or ")?; - display_rule_body(&self.queries[i], f)?; - } - } - } - } else { - match self.kind { - PolicyKind::Allow => write!(f, "allow")?, - PolicyKind::Deny => write!(f, "deny")?, - } - } +// reexport those because the builder uses the same definitions +use super::Block; +use crate::crypto::PublicKey; +use crate::datalog::SymbolTable; +pub use crate::datalog::{ + Binary as DatalogBinary, Expression as DatalogExpression, Op as DatalogOp, + Unary as DatalogUnary, +}; +use crate::error; - Ok(()) - } -} +mod algorithm; +mod authorizer; +mod biscuit; +mod block; +mod check; +mod expression; +mod fact; +mod policy; +mod predicate; +mod rule; +mod scope; +mod term; + +pub use algorithm::*; +pub use authorizer::*; +pub use biscuit::*; +pub use block::*; +pub use check::*; +pub use expression::*; +pub use fact::*; +pub use policy::*; +pub use predicate::*; +pub use rule::*; +pub use scope::*; +pub use term::*; -impl From for Policy { - fn from(p: biscuit_parser::builder::Policy) -> Self { - Policy { - queries: p.queries.into_iter().map(|q| q.into()).collect(), - kind: match p.kind { - biscuit_parser::builder::PolicyKind::Allow => PolicyKind::Allow, - biscuit_parser::builder::PolicyKind::Deny => PolicyKind::Deny, - }, - } +pub trait Convert: Sized { + fn convert(&self, symbols: &mut SymbolTable) -> T; + fn convert_from(f: &T, symbols: &SymbolTable) -> Result; + fn translate( + f: &T, + from_symbols: &SymbolTable, + to_symbols: &mut SymbolTable, + ) -> Result { + Ok(Self::convert_from(f, from_symbols)?.convert(to_symbols)) } } @@ -1873,244 +169,6 @@ pub trait ToAnyParam { fn to_any_param(&self) -> AnyParam; } -impl From for Term { - fn from(i: i64) -> Self { - Term::Integer(i) - } -} - -#[cfg(feature = "datalog-macro")] -impl ToAnyParam for i64 { - fn to_any_param(&self) -> AnyParam { - AnyParam::Term((*self).into()) - } -} - -impl TryFrom for i64 { - type Error = error::Token; - fn try_from(value: Term) -> Result { - match value { - Term::Integer(i) => Ok(i), - _ => Err(error::Token::ConversionError(format!( - "expected integer, got {:?}", - value - ))), - } - } -} - -impl From for Term { - fn from(b: bool) -> Self { - Term::Bool(b) - } -} - -#[cfg(feature = "datalog-macro")] -impl ToAnyParam for bool { - fn to_any_param(&self) -> AnyParam { - AnyParam::Term((*self).into()) - } -} - -impl TryFrom for bool { - type Error = error::Token; - fn try_from(value: Term) -> Result { - match value { - Term::Bool(b) => Ok(b), - _ => Err(error::Token::ConversionError(format!( - "expected boolean, got {:?}", - value - ))), - } - } -} - -impl From for Term { - fn from(s: String) -> Self { - Term::Str(s) - } -} - -#[cfg(feature = "datalog-macro")] -impl ToAnyParam for String { - fn to_any_param(&self) -> AnyParam { - AnyParam::Term((self.clone()).into()) - } -} - -impl From<&str> for Term { - fn from(s: &str) -> Self { - Term::Str(s.into()) - } -} - -#[cfg(feature = "datalog-macro")] -impl ToAnyParam for &str { - fn to_any_param(&self) -> AnyParam { - AnyParam::Term(self.to_string().into()) - } -} - -impl TryFrom for String { - type Error = error::Token; - fn try_from(value: Term) -> Result { - match value { - Term::Str(s) => Ok(s), - _ => Err(error::Token::ConversionError(format!( - "expected string or symbol, got {:?}", - value - ))), - } - } -} - -impl From> for Term { - fn from(v: Vec) -> Self { - Term::Bytes(v) - } -} - -#[cfg(feature = "datalog-macro")] -impl ToAnyParam for Vec { - fn to_any_param(&self) -> AnyParam { - AnyParam::Term((self.clone()).into()) - } -} - -impl TryFrom for Vec { - type Error = error::Token; - fn try_from(value: Term) -> Result { - match value { - Term::Bytes(b) => Ok(b), - _ => Err(error::Token::ConversionError(format!( - "expected byte array, got {:?}", - value - ))), - } - } -} - -impl From<&[u8]> for Term { - fn from(v: &[u8]) -> Self { - Term::Bytes(v.into()) - } -} - -#[cfg(feature = "datalog-macro")] -impl ToAnyParam for [u8] { - fn to_any_param(&self) -> AnyParam { - AnyParam::Term(self.into()) - } -} - -#[cfg(feature = "uuid")] -impl ToAnyParam for uuid::Uuid { - fn to_any_param(&self) -> AnyParam { - AnyParam::Term(Term::Bytes(self.as_bytes().to_vec())) - } -} - -impl From for Term { - fn from(t: SystemTime) -> Self { - let dur = t.duration_since(UNIX_EPOCH).unwrap(); - Term::Date(dur.as_secs()) - } -} - -#[cfg(feature = "datalog-macro")] -impl ToAnyParam for SystemTime { - fn to_any_param(&self) -> AnyParam { - AnyParam::Term((*self).into()) - } -} - -impl TryFrom for SystemTime { - type Error = error::Token; - fn try_from(value: Term) -> Result { - match value { - Term::Date(d) => Ok(UNIX_EPOCH + Duration::from_secs(d)), - _ => Err(error::Token::ConversionError(format!( - "expected date, got {:?}", - value - ))), - } - } -} - -impl From> for Term { - fn from(value: BTreeSet) -> Term { - set(value) - } -} - -#[cfg(feature = "datalog-macro")] -impl ToAnyParam for BTreeSet { - fn to_any_param(&self) -> AnyParam { - AnyParam::Term((self.clone()).into()) - } -} - -impl> TryFrom for BTreeSet { - type Error = error::Token; - fn try_from(value: Term) -> Result { - match value { - Term::Set(d) => d.iter().cloned().map(TryFrom::try_from).collect(), - _ => Err(error::Token::ConversionError(format!( - "expected set, got {:?}", - value - ))), - } - } -} - -macro_rules! tuple_try_from( - ($ty1:ident, $ty2:ident, $($ty:ident),*) => ( - tuple_try_from!(__impl $ty1, $ty2; $($ty),*); - ); - (__impl $($ty: ident),+; $ty1:ident, $($ty2:ident),*) => ( - tuple_try_from_impl!($($ty),+); - tuple_try_from!(__impl $($ty),+ , $ty1; $($ty2),*); - ); - (__impl $($ty: ident),+; $ty1:ident) => ( - tuple_try_from_impl!($($ty),+); - tuple_try_from_impl!($($ty),+, $ty1); - ); - ); - -impl> TryFrom for (A,) { - type Error = error::Token; - fn try_from(fact: Fact) -> Result { - let mut terms = fact.predicate.terms; - let mut it = terms.drain(..); - - Ok((it - .next() - .ok_or_else(|| error::Token::ConversionError("not enough terms in fact".to_string())) - .and_then(A::try_from)?,)) - } -} - -macro_rules! tuple_try_from_impl( - ($($ty: ident),+) => ( - impl<$($ty: TryFrom),+> TryFrom for ($($ty),+) { - type Error = error::Token; - fn try_from(fact: Fact) -> Result { - let mut terms = fact.predicate.terms; - let mut it = terms.drain(..); - - Ok(( - $( - it.next().ok_or(error::Token::ConversionError("not enough terms in fact".to_string())).and_then($ty::try_from)? - ),+ - )) - - } - } - ); - ); - -tuple_try_from!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U); - #[cfg(feature = "datalog-macro")] impl ToAnyParam for PublicKey { fn to_any_param(&self) -> AnyParam { @@ -2118,219 +176,10 @@ impl ToAnyParam for PublicKey { } } -impl TryFrom<&str> for Fact { - type Error = error::Token; - - fn try_from(value: &str) -> Result { - Ok(biscuit_parser::parser::fact(value) - .finish() - .map(|(_, o)| o.into()) - .map_err(biscuit_parser::error::LanguageError::from)?) - } -} - -impl TryFrom<&str> for Rule { - type Error = error::Token; - - fn try_from(value: &str) -> Result { - Ok(biscuit_parser::parser::rule(value) - .finish() - .map(|(_, o)| o.into()) - .map_err(biscuit_parser::error::LanguageError::from)?) - } -} - -impl FromStr for Fact { - type Err = error::Token; - - fn from_str(s: &str) -> Result { - Ok(biscuit_parser::parser::fact(s) - .finish() - .map(|(_, o)| o.into()) - .map_err(biscuit_parser::error::LanguageError::from)?) - } -} - -impl FromStr for Rule { - type Err = error::Token; - - fn from_str(s: &str) -> Result { - Ok(biscuit_parser::parser::rule(s) - .finish() - .map(|(_, o)| o.into()) - .map_err(biscuit_parser::error::LanguageError::from)?) - } -} - -impl TryFrom<&str> for Check { - type Error = error::Token; - - fn try_from(value: &str) -> Result { - Ok(biscuit_parser::parser::check(value) - .finish() - .map(|(_, o)| o.into()) - .map_err(biscuit_parser::error::LanguageError::from)?) - } -} - -impl FromStr for Check { - type Err = error::Token; - - fn from_str(s: &str) -> Result { - Ok(biscuit_parser::parser::check(s) - .finish() - .map(|(_, o)| o.into()) - .map_err(biscuit_parser::error::LanguageError::from)?) - } -} - -impl TryFrom<&str> for Policy { - type Error = error::Token; - - fn try_from(value: &str) -> Result { - Ok(biscuit_parser::parser::policy(value) - .finish() - .map(|(_, o)| o.into()) - .map_err(biscuit_parser::error::LanguageError::from)?) - } -} - -impl FromStr for Policy { - type Err = error::Token; - - fn from_str(s: &str) -> Result { - Ok(biscuit_parser::parser::policy(s) - .finish() - .map(|(_, o)| o.into()) - .map_err(biscuit_parser::error::LanguageError::from)?) - } -} - -impl BuilderExt for BlockBuilder { - fn add_resource(&mut self, name: &str) { - self.facts.push(fact("resource", &[string(name)])); - } - fn check_resource(&mut self, name: &str) { - self.checks.push(Check { - queries: vec![rule( - "resource_check", - &[string("resource_check")], - &[pred("resource", &[string(name)])], - )], - kind: CheckKind::One, - }); - } - fn add_operation(&mut self, name: &str) { - self.facts.push(fact("operation", &[string(name)])); - } - fn check_operation(&mut self, name: &str) { - self.checks.push(Check { - queries: vec![rule( - "operation_check", - &[string("operation_check")], - &[pred("operation", &[string(name)])], - )], - kind: CheckKind::One, - }); - } - fn check_resource_prefix(&mut self, prefix: &str) { - let check = constrained_rule( - "prefix", - &[var("resource")], - &[pred("resource", &[var("resource")])], - &[Expression { - ops: vec![ - Op::Value(var("resource")), - Op::Value(string(prefix)), - Op::Binary(Binary::Prefix), - ], - }], - ); - - self.checks.push(Check { - queries: vec![check], - kind: CheckKind::One, - }); - } - - fn check_resource_suffix(&mut self, suffix: &str) { - let check = constrained_rule( - "suffix", - &[var("resource")], - &[pred("resource", &[var("resource")])], - &[Expression { - ops: vec![ - Op::Value(var("resource")), - Op::Value(string(suffix)), - Op::Binary(Binary::Suffix), - ], - }], - ); - - self.checks.push(Check { - queries: vec![check], - kind: CheckKind::One, - }); - } - - fn check_expiration_date(&mut self, exp: SystemTime) { - let empty: Vec = Vec::new(); - let check = constrained_rule( - "query", - &empty, - &[pred("time", &[var("time")])], - &[Expression { - ops: vec![ - Op::Value(var("time")), - Op::Value(date(&exp)), - Op::Binary(Binary::LessOrEqual), - ], - }], - ); - - self.checks.push(Check { - queries: vec![check], - kind: CheckKind::One, - }); - } -} - -impl fmt::Display for BiscuitBuilder { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self.root_key_id { - None => writeln!(f, "// no root key id set")?, - Some(id) => writeln!(f, "// root key id: {}", id)?, - } - self.inner.fmt(f) - } -} - -impl BuilderExt for BiscuitBuilder { - fn add_resource(&mut self, name: &str) { - self.inner.add_resource(name); - } - fn check_resource(&mut self, name: &str) { - self.inner.check_resource(name); - } - fn check_resource_prefix(&mut self, prefix: &str) { - self.inner.check_resource_prefix(prefix); - } - fn check_resource_suffix(&mut self, suffix: &str) { - self.inner.check_resource_suffix(suffix); - } - fn add_operation(&mut self, name: &str) { - self.inner.add_operation(name); - } - fn check_operation(&mut self, name: &str) { - self.inner.check_operation(name); - } - fn check_expiration_date(&mut self, date: SystemTime) { - self.inner.check_expiration_date(date); - } -} - #[cfg(test)] mod tests { + use std::{collections::HashMap, convert::TryFrom}; + use super::*; #[test] @@ -2348,7 +197,21 @@ mod tests { rule.set("p5", term_set).unwrap(); let s = rule.to_string(); - assert_eq!(s, "fact($var1, \"hello\", [0]) <- f1($var1, $var3), f2(\"hello\", $var3, 1), $var3.starts_with(\"hello\")"); + assert_eq!(s, "fact($var1, \"hello\", {0}) <- f1($var1, $var3), f2(\"hello\", $var3, 1), $var3.starts_with(\"hello\")"); + } + + #[test] + fn set_closure_parameters() { + let mut rule = Rule::try_from("fact(true) <- false || {p1}").unwrap(); + rule.set_lenient("p1", true).unwrap(); + println!("{rule:?}"); + let s = rule.to_string(); + assert_eq!(s, "fact(true) <- false || true"); + + let mut rule = Rule::try_from("fact(true) <- false || {p1}").unwrap(); + rule.set("p1", true).unwrap(); + let s = rule.to_string(); + assert_eq!(s, "fact(true) <- false || true"); } #[test] @@ -2356,6 +219,7 @@ mod tests { let pubkey = PublicKey::from_bytes( &hex::decode("6e9e6d5a75cf0c0e87ec1256b4dfed0ca3ba452912d213fcc70f8516583db9db") .unwrap(), + Algorithm::Ed25519, ) .unwrap(); let mut rule = Rule::try_from( @@ -2382,12 +246,13 @@ mod tests { let pubkey = PublicKey::from_bytes( &hex::decode("6e9e6d5a75cf0c0e87ec1256b4dfed0ca3ba452912d213fcc70f8516583db9db") .unwrap(), + Algorithm::Ed25519, ) .unwrap(); let mut scope_params = HashMap::new(); scope_params.insert("pk".to_string(), pubkey); - builder - .add_code_with_params( + builder = builder + .code_with_params( r#"fact({p1}, "value"); rule($head_var) <- f1($head_var), {p2} > 0 trusting {pk}; check if {p3} trusting {pk}; @@ -2407,57 +272,51 @@ check if true trusting ed25519/6e9e6d5a75cf0c0e87ec1256b4dfed0ca3ba452912d213fcc #[test] fn forbid_unbound_parameters() { - let mut builder = BlockBuilder::new(); + let builder = BlockBuilder::new(); let mut fact = Fact::try_from("fact({p1}, {p4})").unwrap(); fact.set("p1", "hello").unwrap(); - let res = builder.add_fact(fact); + let res = builder.clone().fact(fact); assert_eq!( - res, - Err(error::Token::Language( - biscuit_parser::error::LanguageError::Parameters { - missing_parameters: vec!["p4".to_string()], - unused_parameters: vec![], - } - )) + res.unwrap_err(), + error::Token::Language(biscuit_parser::error::LanguageError::Parameters { + missing_parameters: vec!["p4".to_string()], + unused_parameters: vec![], + }) ); let mut rule = Rule::try_from( "fact($var1, {p2}) <- f1($var1, $var3), f2({p2}, $var3, {p4}), $var3.starts_with({p2})", ) .unwrap(); rule.set("p2", "hello").unwrap(); - let res = builder.add_rule(rule); + let res = builder.clone().rule(rule); assert_eq!( - res, - Err(error::Token::Language( - biscuit_parser::error::LanguageError::Parameters { - missing_parameters: vec!["p4".to_string()], - unused_parameters: vec![], - } - )) + res.unwrap_err(), + error::Token::Language(biscuit_parser::error::LanguageError::Parameters { + missing_parameters: vec!["p4".to_string()], + unused_parameters: vec![], + }) ); let mut check = Check::try_from("check if {p4}, {p3}").unwrap(); check.set("p3", true).unwrap(); - let res = builder.add_check(check); + let res = builder.clone().check(check); assert_eq!( - res, - Err(error::Token::Language( - biscuit_parser::error::LanguageError::Parameters { - missing_parameters: vec!["p4".to_string()], - unused_parameters: vec![], - } - )) + res.unwrap_err(), + error::Token::Language(biscuit_parser::error::LanguageError::Parameters { + missing_parameters: vec!["p4".to_string()], + unused_parameters: vec![], + }) ); } #[test] fn forbid_unbound_parameters_in_set_code() { - let mut builder = BlockBuilder::new(); + let builder = BlockBuilder::new(); let mut params = HashMap::new(); params.insert("p1".to_string(), "hello".into()); params.insert("p2".to_string(), 1i64.into()); params.insert("p4".to_string(), "this will be ignored".into()); - let res = builder.add_code_with_params( + let res = builder.code_with_params( r#"fact({p1}, "value"); rule($head_var) <- f1($head_var), {p2} > 0; check if {p3}; @@ -2467,13 +326,15 @@ check if true trusting ed25519/6e9e6d5a75cf0c0e87ec1256b4dfed0ca3ba452912d213fcc ); assert_eq!( - res, - Err(error::Token::Language( - biscuit_parser::error::LanguageError::Parameters { - missing_parameters: vec!["p3".to_string()], - unused_parameters: vec![], - } - )) - ) + res.unwrap_err(), + error::Token::Language(biscuit_parser::error::LanguageError::Parameters { + missing_parameters: vec!["p3".to_string()], + unused_parameters: vec![], + }) + ); + } + #[test] + fn empty_set_display() { + assert_eq!(Term::Set(BTreeSet::new()).to_string(), "{,}"); } } diff --git a/biscuit-auth/src/token/builder/algorithm.rs b/biscuit-auth/src/token/builder/algorithm.rs new file mode 100644 index 00000000..018e6959 --- /dev/null +++ b/biscuit-auth/src/token/builder/algorithm.rs @@ -0,0 +1,85 @@ +use core::fmt::Display; +use std::{ + convert::{TryFrom, TryInto}, + str::FromStr, +}; + +use crate::error; + +#[derive(Debug, Copy, Clone, PartialEq, Hash, Eq)] +pub enum Algorithm { + Ed25519, + Secp256r1, +} + +impl Default for Algorithm { + fn default() -> Self { + Self::Ed25519 + } +} + +impl Display for Algorithm { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Algorithm::Ed25519 => write!(f, "ed25519"), + Algorithm::Secp256r1 => write!(f, "secp256r1"), + } + } +} +impl FromStr for Algorithm { + type Err = error::Format; + + fn from_str(s: &str) -> Result { + s.try_into() + } +} + +impl TryFrom<&str> for Algorithm { + type Error = error::Format; + fn try_from(value: &str) -> Result { + match value { + "ed25519" => Ok(Algorithm::Ed25519), + "secp256r1" => Ok(Algorithm::Secp256r1), + _ => Err(error::Format::DeserializationError(format!( + "deserialization error: unexpected key algorithm {}", + value + ))), + } + } +} + +impl From for Algorithm { + fn from(value: biscuit_parser::builder::Algorithm) -> Algorithm { + match value { + biscuit_parser::builder::Algorithm::Ed25519 => Algorithm::Ed25519, + biscuit_parser::builder::Algorithm::Secp256r1 => Algorithm::Secp256r1, + } + } +} + +impl From for biscuit_parser::builder::Algorithm { + fn from(value: Algorithm) -> biscuit_parser::builder::Algorithm { + match value { + Algorithm::Ed25519 => biscuit_parser::builder::Algorithm::Ed25519, + Algorithm::Secp256r1 => biscuit_parser::builder::Algorithm::Secp256r1, + } + } +} + +impl From for Algorithm { + fn from(value: crate::format::schema::public_key::Algorithm) -> Algorithm { + match value { + crate::format::schema::public_key::Algorithm::Ed25519 => Algorithm::Ed25519, + crate::format::schema::public_key::Algorithm::Secp256r1 => Algorithm::Secp256r1, + } + } +} + +impl From for crate::format::schema::public_key::Algorithm { + fn from(value: Algorithm) -> crate::format::schema::public_key::Algorithm { + match value { + Algorithm::Ed25519 => crate::format::schema::public_key::Algorithm::Ed25519, + Algorithm::Secp256r1 => crate::format::schema::public_key::Algorithm::Secp256r1, + } + } +} diff --git a/biscuit-auth/src/token/builder/authorizer.rs b/biscuit-auth/src/token/builder/authorizer.rs new file mode 100644 index 00000000..9a9121f2 --- /dev/null +++ b/biscuit-auth/src/token/builder/authorizer.rs @@ -0,0 +1,661 @@ +use std::{ + collections::HashMap, + convert::TryInto, + fmt::Write, + time::{Duration, SystemTime}, +}; + +use biscuit_parser::parser::parse_source; +use prost::Message; + +use crate::{ + builder::Convert, + builder_ext::{AuthorizerExt, BuilderExt}, + datalog::{ExternFunc, Origin, RunLimits, SymbolTable, TrustedOrigins, World}, + error, + format::{ + convert::{ + proto_snapshot_block_to_token_block, token_block_to_proto_snapshot_block, + v2::{policy_to_proto_policy, proto_policy_to_policy}, + }, + schema, + }, + token::{self, default_symbol_table, Block, MAX_SCHEMA_VERSION, MIN_SCHEMA_VERSION}, + Authorizer, AuthorizerLimits, Biscuit, PublicKey, +}; + +use super::{date, fact, BlockBuilder, Check, Fact, Policy, Rule, Scope, Term}; + +#[derive(Clone, Debug, Default)] +pub struct AuthorizerBuilder { + authorizer_block_builder: BlockBuilder, + policies: Vec, + extern_funcs: HashMap, + pub(crate) limits: AuthorizerLimits, +} + +impl AuthorizerBuilder { + pub fn new() -> AuthorizerBuilder { + AuthorizerBuilder::default() + } + + pub fn fact>(mut self, fact: F) -> Result + where + error::Token: From<>::Error>, + { + self.authorizer_block_builder = self.authorizer_block_builder.fact(fact)?; + Ok(self) + } + + pub fn rule>(mut self, rule: R) -> Result + where + error::Token: From<>::Error>, + { + self.authorizer_block_builder = self.authorizer_block_builder.rule(rule)?; + Ok(self) + } + + pub fn check>(mut self, check: C) -> Result + where + error::Token: From<>::Error>, + { + self.authorizer_block_builder = self.authorizer_block_builder.check(check)?; + Ok(self) + } + + /// adds some datalog code to the authorizer + /// + /// ```rust + /// extern crate biscuit_auth as biscuit; + /// + /// use biscuit::builder::AuthorizerBuilder; + /// + /// let mut authorizer = AuthorizerBuilder::new() + /// .code(r#" + /// resource("/file1.txt"); + /// + /// check if user(1234); + /// + /// // default allow + /// allow if true; + /// "#) + /// .expect("should parse correctly") + /// .build_unauthenticated(); + /// ``` + pub fn code>(self, source: T) -> Result { + self.code_with_params(source, HashMap::new(), HashMap::new()) + } + + /// Add datalog code to the builder, performing parameter subsitution as required + /// Unknown parameters are ignored + pub fn code_with_params>( + mut self, + source: T, + params: HashMap, + scope_params: HashMap, + ) -> Result { + let source = source.as_ref(); + + let source_result = parse_source(source).map_err(|e| { + let e2: biscuit_parser::error::LanguageError = e.into(); + e2 + })?; + + for (_, fact) in source_result.facts.into_iter() { + let mut fact: Fact = fact.into(); + for (name, value) in ¶ms { + let res = match fact.set(name, value) { + Ok(_) => Ok(()), + Err(error::Token::Language( + biscuit_parser::error::LanguageError::Parameters { + missing_parameters, .. + }, + )) if missing_parameters.is_empty() => Ok(()), + Err(e) => Err(e), + }; + res?; + } + fact.validate()?; + self.authorizer_block_builder.facts.push(fact); + } + + for (_, rule) in source_result.rules.into_iter() { + let mut rule: Rule = rule.into(); + for (name, value) in ¶ms { + let res = match rule.set(name, value) { + Ok(_) => Ok(()), + Err(error::Token::Language( + biscuit_parser::error::LanguageError::Parameters { + missing_parameters, .. + }, + )) if missing_parameters.is_empty() => Ok(()), + Err(e) => Err(e), + }; + res?; + } + for (name, value) in &scope_params { + let res = match rule.set_scope(name, *value) { + Ok(_) => Ok(()), + Err(error::Token::Language( + biscuit_parser::error::LanguageError::Parameters { + missing_parameters, .. + }, + )) if missing_parameters.is_empty() => Ok(()), + Err(e) => Err(e), + }; + res?; + } + rule.validate_parameters()?; + self.authorizer_block_builder.rules.push(rule); + } + + for (_, check) in source_result.checks.into_iter() { + let mut check: Check = check.into(); + for (name, value) in ¶ms { + let res = match check.set(name, value) { + Ok(_) => Ok(()), + Err(error::Token::Language( + biscuit_parser::error::LanguageError::Parameters { + missing_parameters, .. + }, + )) if missing_parameters.is_empty() => Ok(()), + Err(e) => Err(e), + }; + res?; + } + for (name, value) in &scope_params { + let res = match check.set_scope(name, *value) { + Ok(_) => Ok(()), + Err(error::Token::Language( + biscuit_parser::error::LanguageError::Parameters { + missing_parameters, .. + }, + )) if missing_parameters.is_empty() => Ok(()), + Err(e) => Err(e), + }; + res?; + } + check.validate_parameters()?; + self.authorizer_block_builder.checks.push(check); + } + for (_, policy) in source_result.policies.into_iter() { + let mut policy: Policy = policy.into(); + for (name, value) in ¶ms { + let res = match policy.set(name, value) { + Ok(_) => Ok(()), + Err(error::Token::Language( + biscuit_parser::error::LanguageError::Parameters { + missing_parameters, .. + }, + )) if missing_parameters.is_empty() => Ok(()), + Err(e) => Err(e), + }; + res?; + } + for (name, value) in &scope_params { + let res = match policy.set_scope(name, *value) { + Ok(_) => Ok(()), + Err(error::Token::Language( + biscuit_parser::error::LanguageError::Parameters { + missing_parameters, .. + }, + )) if missing_parameters.is_empty() => Ok(()), + Err(e) => Err(e), + }; + res?; + } + policy.validate_parameters()?; + self.policies.push(policy); + } + + Ok(self) + } + + pub fn scope(mut self, scope: Scope) -> Self { + self.authorizer_block_builder = self.authorizer_block_builder.scope(scope); + self + } + + /// add a policy to the authorizer + pub fn policy>(mut self, policy: P) -> Result + where + error::Token: From<

>::Error>, + { + let policy = policy.try_into()?; + policy.validate_parameters()?; + self.policies.push(policy); + Ok(self) + } + + /// adds a fact with the current time + pub fn time(mut self) -> Self { + let fact = fact("time", &[date(&SystemTime::now())]); + self.authorizer_block_builder = self.authorizer_block_builder.fact(fact).unwrap(); + self + } + + /// Sets the runtime limits of the authorizer + /// + /// Those limits cover all the executions under the `authorize`, `query` and `query_all` methods + pub fn limits(mut self, limits: AuthorizerLimits) -> Self { + self.limits = limits; + self + } + + /// Replaces the registered external functions + pub fn set_extern_funcs(mut self, extern_funcs: HashMap) -> Self { + self.extern_funcs = extern_funcs; + self + } + + /// Registers the provided external functions (possibly replacing already registered functions) + pub fn register_extern_funcs(mut self, extern_funcs: HashMap) -> Self { + self.extern_funcs.extend(extern_funcs); + self + } + + /// Registers the provided external function (possibly replacing an already registered function) + pub fn register_extern_func(mut self, name: String, func: ExternFunc) -> Self { + self.extern_funcs.insert(name, func); + self + } + + pub fn dump_code(&self) -> String { + let mut f = String::new(); + for fact in &self.authorizer_block_builder.facts { + let _ = writeln!(f, "{fact};"); + } + if !self.authorizer_block_builder.facts.is_empty() { + let _ = writeln!(f); + } + + for rule in &self.authorizer_block_builder.rules { + let _ = writeln!(f, "{rule};"); + } + if !self.authorizer_block_builder.rules.is_empty() { + let _ = writeln!(f); + } + + for check in &self.authorizer_block_builder.checks { + let _ = writeln!(f, "{check};"); + } + if !self.authorizer_block_builder.checks.is_empty() { + let _ = writeln!(f); + } + + for policy in &self.policies { + let _ = writeln!(f, "{policy};"); + } + f + } + + /// builds the authorizer from a token + pub fn build(self, token: &Biscuit) -> Result { + self.build_inner(Some(token)) + } + + /// builds the authorizer without a token + pub fn build_unauthenticated(self) -> Result { + self.build_inner(None) + } + + fn build_inner(self, token: Option<&Biscuit>) -> Result { + let mut world = World::new(); + world.extern_funcs = self.extern_funcs; + + let mut symbols = SymbolTable::new(); + let mut public_key_to_block_id: HashMap> = HashMap::new(); + let mut token_origins = TrustedOrigins::default(); + let mut blocks: Option> = None; + + // load the token if present + if let Some(token) = token { + for (i, block) in token.container.blocks.iter().enumerate() { + if let Some(sig) = block.external_signature.as_ref() { + let new_key_id = symbols.public_keys.insert(&sig.public_key); + + public_key_to_block_id + .entry(new_key_id as usize) + .or_default() + .push(i + 1); + } + } + + blocks = Some( + token + .blocks() + .enumerate() + .map(|(i, block)| { + block.and_then(|mut b| { + load_and_translate_block( + &mut b, + i, + &token.symbols, + &mut symbols, + &mut public_key_to_block_id, + &mut world, + )?; + Ok(b) + }) + }) + .collect::, _>>()?, + ); + + token_origins = TrustedOrigins::from_scopes( + &[token::Scope::Previous], + &TrustedOrigins::default(), + token.block_count(), + &public_key_to_block_id, + ); + } + let mut authorizer_origin = Origin::default(); + authorizer_origin.insert(usize::MAX); + + let authorizer_scopes: Vec = self + .authorizer_block_builder + .scopes + .clone() + .iter() + .map(|s| s.convert(&mut symbols)) + .collect(); + + let authorizer_trusted_origins = TrustedOrigins::from_scopes( + &authorizer_scopes, + &TrustedOrigins::default(), + usize::MAX, + &public_key_to_block_id, + ); + for fact in &self.authorizer_block_builder.facts { + world + .facts + .insert(&authorizer_origin, fact.convert(&mut symbols)); + } + + for rule in &self.authorizer_block_builder.rules { + let rule = rule.convert(&mut symbols); + + let rule_trusted_origins = TrustedOrigins::from_scopes( + &rule.scopes, + &authorizer_trusted_origins, + usize::MAX, + &public_key_to_block_id, + ); + + world.rules.insert(usize::MAX, &rule_trusted_origins, rule); + } + + /* + let start = Instant::now(); + world.run_with_limits(&symbols, self.limits.clone())?; + let execution_time = start.elapsed(); + */ + + Ok(Authorizer { + authorizer_block_builder: self.authorizer_block_builder, + world, + symbols, + token_origins, + policies: self.policies, + blocks, + public_key_to_block_id, + limits: self.limits, + execution_time: None, + }) + } +} + +/// we need to modify the block loaded from the token, because the authorizer's and the token's symbol table can differ +pub(crate) fn load_and_translate_block( + block: &mut Block, + i: usize, + token_symbols: &SymbolTable, + authorizer_symbols: &mut SymbolTable, + public_key_to_block_id: &mut HashMap>, + world: &mut World, +) -> Result<(), error::Token> { + // if it is a 3rd party block, it should not affect the main symbol table + let block_symbols = if i == 0 || block.external_key.is_none() { + token_symbols.clone() + } else { + block.symbols.clone() + }; + + let mut block_origin = Origin::default(); + block_origin.insert(i); + + for scope in block.scopes.iter_mut() { + *scope = crate::token::builder::Scope::convert_from(scope, &block_symbols) + .map(|s| s.convert(authorizer_symbols))?; + } + + let block_trusted_origins = TrustedOrigins::from_scopes( + &block.scopes, + &TrustedOrigins::default(), + i, + public_key_to_block_id, + ); + + for fact in block.facts.iter_mut() { + *fact = Fact::convert_from(fact, &block_symbols)?.convert(authorizer_symbols); + world.facts.insert(&block_origin, fact.clone()); + } + + for rule in block.rules.iter_mut() { + if let Err(_message) = rule.validate_variables(&block_symbols) { + return Err(error::Logic::InvalidBlockRule(0, block_symbols.print_rule(rule)).into()); + } + *rule = rule.translate(&block_symbols, authorizer_symbols)?; + + let rule_trusted_origins = TrustedOrigins::from_scopes( + &rule.scopes, + &block_trusted_origins, + i, + public_key_to_block_id, + ); + + world.rules.insert(i, &rule_trusted_origins, rule.clone()); + } + + for check in block.checks.iter_mut() { + let c = Check::convert_from(check, &block_symbols)?; + *check = c.convert(authorizer_symbols); + } + + Ok(()) +} + +impl BuilderExt for AuthorizerBuilder { + fn resource(mut self, name: &str) -> Self { + self.authorizer_block_builder = self.authorizer_block_builder.resource(name); + self + } + fn check_resource(mut self, name: &str) -> Self { + self.authorizer_block_builder = self.authorizer_block_builder.check_resource(name); + self + } + fn operation(mut self, name: &str) -> Self { + self.authorizer_block_builder = self.authorizer_block_builder.operation(name); + self + } + fn check_operation(mut self, name: &str) -> Self { + self.authorizer_block_builder = self.authorizer_block_builder.check_operation(name); + self + } + fn check_resource_prefix(mut self, prefix: &str) -> Self { + self.authorizer_block_builder = self.authorizer_block_builder.check_resource_prefix(prefix); + self + } + + fn check_resource_suffix(mut self, suffix: &str) -> Self { + self.authorizer_block_builder = self.authorizer_block_builder.check_resource_suffix(suffix); + self + } + + fn check_expiration_date(mut self, exp: SystemTime) -> Self { + self.authorizer_block_builder = self.authorizer_block_builder.check_expiration_date(exp); + self + } +} + +impl AuthorizerExt for AuthorizerBuilder { + fn allow_all(self) -> Self { + self.policy("allow if true").unwrap() + } + fn deny_all(self) -> Self { + self.policy("deny if true").unwrap() + } +} + +impl AuthorizerBuilder { + pub fn from_snapshot(input: schema::AuthorizerSnapshot) -> Result { + let schema::AuthorizerSnapshot { + limits, + execution_time, + world, + } = input; + + let limits = RunLimits { + max_facts: limits.max_facts, + max_iterations: limits.max_iterations, + max_time: Duration::from_nanos(limits.max_time), + }; + + let version = world.version.unwrap_or(0); + if !(MIN_SCHEMA_VERSION..=MAX_SCHEMA_VERSION).contains(&version) { + return Err(error::Format::Version { + minimum: crate::token::MIN_SCHEMA_VERSION, + maximum: crate::token::MAX_SCHEMA_VERSION, + actual: version, + } + .into()); + } + + if !world.blocks.is_empty() { + return Err(error::Format::DeserializationError( + "cannot deserialize an AuthorizerBuilder fro a snapshot with blocks".to_string(), + ) + .into()); + } + + if !world.generated_facts.is_empty() { + return Err(error::Format::DeserializationError( + "cannot deserialize an AuthorizerBuilder from a snapshot with generated facts" + .to_string(), + ) + .into()); + } + + if world.iterations != 0 { + return Err(error::Format::DeserializationError( + "cannot deserialize an AuthorizerBuilder from a snapshot with non-zero iterations" + .to_string(), + ) + .into()); + } + + if execution_time != 0 { + return Err(error::Format::DeserializationError( + "cannot deserialize an AuthorizerBuilder from a snapshot with non-zero execution time".to_string(), + ) + .into()); + } + + let mut symbols = default_symbol_table(); + for symbol in world.symbols { + symbols.insert(&symbol); + } + for public_key in world.public_keys { + symbols + .public_keys + .insert(&PublicKey::from_proto(&public_key)?); + } + + let authorizer_block = proto_snapshot_block_to_token_block(&world.authorizer_block)?; + + let authorizer_block_builder = BlockBuilder::convert_from(&authorizer_block, &symbols)?; + let policies = world + .authorizer_policies + .iter() + .map(|policy| proto_policy_to_policy(policy, &symbols, version)) + .collect::, error::Format>>()?; + + let mut authorizer = AuthorizerBuilder::new(); + authorizer.authorizer_block_builder = authorizer_block_builder; + authorizer.policies = policies; + authorizer.limits = limits; + + Ok(authorizer) + } + + pub fn from_raw_snapshot(input: &[u8]) -> Result { + let snapshot = schema::AuthorizerSnapshot::decode(input).map_err(|e| { + error::Format::DeserializationError(format!("deserialization error: {:?}", e)) + })?; + Self::from_snapshot(snapshot) + } + + pub fn from_base64_snapshot(input: &str) -> Result { + let bytes = base64::decode_config(input, base64::URL_SAFE)?; + Self::from_raw_snapshot(&bytes) + } + + pub fn snapshot(&self) -> Result { + let mut symbols = default_symbol_table(); + + let authorizer_policies = self + .policies + .iter() + .map(|policy| policy_to_proto_policy(policy, &mut symbols)) + .collect(); + + let authorizer_block = self.authorizer_block_builder.clone().build(symbols.clone()); + symbols.extend(&authorizer_block.symbols)?; + symbols.public_keys.extend(&authorizer_block.public_keys)?; + + let authorizer_block = token_block_to_proto_snapshot_block(&authorizer_block); + + let blocks = vec![]; + + let generated_facts = vec![]; + + let world = schema::AuthorizerWorld { + version: Some(MAX_SCHEMA_VERSION), + symbols: symbols.strings(), + public_keys: symbols + .public_keys + .into_inner() + .into_iter() + .map(|key| key.to_proto()) + .collect(), + blocks, + authorizer_block, + authorizer_policies, + generated_facts, + iterations: 0, + }; + + Ok(schema::AuthorizerSnapshot { + world, + execution_time: 0u64, + limits: schema::RunLimits { + max_facts: self.limits.max_facts, + max_iterations: self.limits.max_iterations, + max_time: self.limits.max_time.as_nanos() as u64, + }, + }) + } + + pub fn to_raw_snapshot(&self) -> Result, error::Format> { + let snapshot = self.snapshot()?; + let mut bytes = Vec::new(); + snapshot.encode(&mut bytes).map_err(|e| { + error::Format::SerializationError(format!("serialization error: {:?}", e)) + })?; + Ok(bytes) + } + + pub fn to_base64_snapshot(&self) -> Result { + let snapshot_bytes = self.to_raw_snapshot()?; + Ok(base64::encode_config(snapshot_bytes, base64::URL_SAFE)) + } +} diff --git a/biscuit-auth/src/token/builder/biscuit.rs b/biscuit-auth/src/token/builder/biscuit.rs new file mode 100644 index 00000000..4bd769f4 --- /dev/null +++ b/biscuit-auth/src/token/builder/biscuit.rs @@ -0,0 +1,195 @@ +use super::{BlockBuilder, Check, Fact, Rule, Scope, Term}; +use crate::builder_ext::BuilderExt; +use crate::crypto::PublicKey; +use crate::datalog::SymbolTable; +use crate::token::default_symbol_table; +use crate::{error, Biscuit, KeyPair}; +use rand::{CryptoRng, RngCore}; + +use std::fmt; +use std::time::SystemTime; +use std::{collections::HashMap, convert::TryInto, fmt::Write}; + +/// creates a Biscuit +#[derive(Clone, Default)] +pub struct BiscuitBuilder { + inner: BlockBuilder, + root_key_id: Option, +} + +impl BiscuitBuilder { + pub fn new() -> BiscuitBuilder { + BiscuitBuilder { + inner: BlockBuilder::new(), + root_key_id: None, + } + } + + pub fn merge(mut self, other: BlockBuilder) -> Self { + self.inner = self.inner.merge(other); + self + } + + pub fn fact>(mut self, fact: F) -> Result + where + error::Token: From<>::Error>, + { + self.inner = self.inner.fact(fact)?; + Ok(self) + } + + pub fn rule>(mut self, rule: Ru) -> Result + where + error::Token: From<>::Error>, + { + self.inner = self.inner.rule(rule)?; + Ok(self) + } + + pub fn check>(mut self, check: C) -> Result + where + error::Token: From<>::Error>, + { + self.inner = self.inner.check(check)?; + Ok(self) + } + + pub fn code>(mut self, source: T) -> Result { + self.inner = self + .inner + .code_with_params(source, HashMap::new(), HashMap::new())?; + Ok(self) + } + + pub fn code_with_params>( + mut self, + source: T, + params: HashMap, + scope_params: HashMap, + ) -> Result { + self.inner = self.inner.code_with_params(source, params, scope_params)?; + Ok(self) + } + + pub fn scope(mut self, scope: Scope) -> Self { + self.inner = self.inner.scope(scope); + self + } + + #[cfg(test)] + pub(crate) fn right(self, resource: &str, right: &str) -> Self { + use crate::builder::fact; + + use super::string; + + self.fact(fact("right", &[string(resource), string(right)])) + .unwrap() + } + + pub fn context(mut self, context: String) -> Self { + self.inner = self.inner.context(context); + self + } + + pub fn root_key_id(mut self, root_key_id: u32) -> Self { + self.root_key_id = Some(root_key_id); + self + } + + /// returns all of the datalog loaded in the biscuit builder + pub fn dump(&self) -> (Vec, Vec, Vec) { + ( + self.inner.facts.clone(), + self.inner.rules.clone(), + self.inner.checks.clone(), + ) + } + + pub fn dump_code(&self) -> String { + let (facts, rules, checks) = self.dump(); + let mut f = String::new(); + for fact in facts { + let _ = writeln!(f, "{};", fact); + } + for rule in rules { + let _ = writeln!(f, "{};", rule); + } + for check in checks { + let _ = writeln!(f, "{};", check); + } + f + } + + pub fn build(self, root_key: &KeyPair) -> Result { + self.build_with_symbols(root_key, default_symbol_table()) + } + + pub fn build_with_symbols( + self, + root_key: &KeyPair, + symbols: SymbolTable, + ) -> Result { + self.build_with_rng(root_key, symbols, &mut rand::rngs::OsRng) + } + + pub fn build_with_rng( + self, + root: &KeyPair, + symbols: SymbolTable, + rng: &mut R, + ) -> Result { + let authority_block = self.inner.build(symbols.clone()); + Biscuit::new_with_rng(rng, self.root_key_id, root, symbols, authority_block) + } + + pub fn build_with_key_pair( + self, + root: &KeyPair, + symbols: SymbolTable, + next: &KeyPair, + ) -> Result { + let authority_block = self.inner.build(symbols.clone()); + Biscuit::new_with_key_pair(self.root_key_id, root, next, symbols, authority_block) + } +} + +impl fmt::Display for BiscuitBuilder { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.root_key_id { + None => writeln!(f, "// no root key id set")?, + Some(id) => writeln!(f, "// root key id: {}", id)?, + } + self.inner.fmt(f) + } +} + +impl BuilderExt for BiscuitBuilder { + fn resource(mut self, name: &str) -> Self { + self.inner = self.inner.resource(name); + self + } + fn check_resource(mut self, name: &str) -> Self { + self.inner = self.inner.check_resource(name); + self + } + fn check_resource_prefix(mut self, prefix: &str) -> Self { + self.inner = self.inner.check_resource_prefix(prefix); + self + } + fn check_resource_suffix(mut self, suffix: &str) -> Self { + self.inner = self.inner.check_resource_suffix(suffix); + self + } + fn operation(mut self, name: &str) -> Self { + self.inner = self.inner.operation(name); + self + } + fn check_operation(mut self, name: &str) -> Self { + self.inner = self.inner.check_operation(name); + self + } + fn check_expiration_date(mut self, date: SystemTime) -> Self { + self.inner = self.inner.check_expiration_date(date); + self + } +} diff --git a/biscuit-auth/src/token/builder/block.rs b/biscuit-auth/src/token/builder/block.rs new file mode 100644 index 00000000..d0bf0459 --- /dev/null +++ b/biscuit-auth/src/token/builder/block.rs @@ -0,0 +1,384 @@ +use super::{ + constrained_rule, date, fact, pred, rule, string, var, Binary, Block, Check, CheckKind, + Convert, Expression, Fact, Op, Rule, Scope, Term, +}; +use crate::builder_ext::BuilderExt; +use crate::crypto::PublicKey; +use crate::datalog::{get_schema_version, SymbolTable}; +use crate::error; +use biscuit_parser::parser::parse_block_source; + +use std::time::SystemTime; +use std::{collections::HashMap, convert::TryInto, fmt}; + +/// creates a Block content to append to an existing token +#[derive(Clone, Debug, Default)] +pub struct BlockBuilder { + pub facts: Vec, + pub rules: Vec, + pub checks: Vec, + pub scopes: Vec, + pub context: Option, +} + +impl BlockBuilder { + pub fn new() -> BlockBuilder { + BlockBuilder::default() + } + + pub fn merge(mut self, mut other: BlockBuilder) -> Self { + self.facts.append(&mut other.facts); + self.rules.append(&mut other.rules); + self.checks.append(&mut other.checks); + + if let Some(c) = other.context { + self.context = Some(c); + } + self + } + + pub fn fact>(mut self, fact: F) -> Result + where + error::Token: From<>::Error>, + { + let fact = fact.try_into()?; + fact.validate()?; + + self.facts.push(fact); + Ok(self) + } + + pub fn rule>(mut self, rule: R) -> Result + where + error::Token: From<>::Error>, + { + let rule = rule.try_into()?; + rule.validate_parameters()?; + self.rules.push(rule); + Ok(self) + } + + pub fn check>(mut self, check: C) -> Result + where + error::Token: From<>::Error>, + { + let check = check.try_into()?; + check.validate_parameters()?; + self.checks.push(check); + Ok(self) + } + + pub fn code>(self, source: T) -> Result { + self.code_with_params(source, HashMap::new(), HashMap::new()) + } + + /// Add datalog code to the builder, performing parameter subsitution as required + /// Unknown parameters are ignored + pub fn code_with_params>( + mut self, + source: T, + params: HashMap, + scope_params: HashMap, + ) -> Result { + let input = source.as_ref(); + + let source_result = parse_block_source(input).map_err(|e| { + let e2: biscuit_parser::error::LanguageError = e.into(); + e2 + })?; + + for (_, fact) in source_result.facts.into_iter() { + let mut fact: Fact = fact.into(); + for (name, value) in ¶ms { + let res = match fact.set(name, value) { + Ok(_) => Ok(()), + Err(error::Token::Language( + biscuit_parser::error::LanguageError::Parameters { + missing_parameters, .. + }, + )) if missing_parameters.is_empty() => Ok(()), + Err(e) => Err(e), + }; + res?; + } + fact.validate()?; + self.facts.push(fact); + } + + for (_, rule) in source_result.rules.into_iter() { + let mut rule: Rule = rule.into(); + for (name, value) in ¶ms { + let res = match rule.set(name, value) { + Ok(_) => Ok(()), + Err(error::Token::Language( + biscuit_parser::error::LanguageError::Parameters { + missing_parameters, .. + }, + )) if missing_parameters.is_empty() => Ok(()), + Err(e) => Err(e), + }; + res?; + } + for (name, value) in &scope_params { + let res = match rule.set_scope(name, *value) { + Ok(_) => Ok(()), + Err(error::Token::Language( + biscuit_parser::error::LanguageError::Parameters { + missing_parameters, .. + }, + )) if missing_parameters.is_empty() => Ok(()), + Err(e) => Err(e), + }; + res?; + } + rule.validate_parameters()?; + self.rules.push(rule); + } + + for (_, check) in source_result.checks.into_iter() { + let mut check: Check = check.into(); + for (name, value) in ¶ms { + let res = match check.set(name, value) { + Ok(_) => Ok(()), + Err(error::Token::Language( + biscuit_parser::error::LanguageError::Parameters { + missing_parameters, .. + }, + )) if missing_parameters.is_empty() => Ok(()), + Err(e) => Err(e), + }; + res?; + } + for (name, value) in &scope_params { + let res = match check.set_scope(name, *value) { + Ok(_) => Ok(()), + Err(error::Token::Language( + biscuit_parser::error::LanguageError::Parameters { + missing_parameters, .. + }, + )) if missing_parameters.is_empty() => Ok(()), + Err(e) => Err(e), + }; + res?; + } + check.validate_parameters()?; + self.checks.push(check); + } + + Ok(self) + } + + pub fn scope(mut self, scope: Scope) -> Self { + self.scopes.push(scope); + self + } + + pub fn context(mut self, context: String) -> Self { + self.context = Some(context); + self + } + + pub(crate) fn build(self, mut symbols: SymbolTable) -> Block { + let symbols_start = symbols.current_offset(); + let public_keys_start = symbols.public_keys.current_offset(); + + let mut facts = Vec::new(); + for fact in self.facts { + facts.push(fact.convert(&mut symbols)); + } + + let mut rules = Vec::new(); + for rule in &self.rules { + rules.push(rule.convert(&mut symbols)); + } + + let mut checks = Vec::new(); + for check in &self.checks { + checks.push(check.convert(&mut symbols)); + } + + let mut scopes = Vec::new(); + for scope in &self.scopes { + scopes.push(scope.convert(&mut symbols)); + } + + let new_syms = symbols.split_at(symbols_start); + let public_keys = symbols.public_keys.split_at(public_keys_start); + let schema_version = get_schema_version(&facts, &rules, &checks, &scopes); + + Block { + symbols: new_syms, + facts, + rules, + checks, + context: self.context, + version: schema_version.version(), + external_key: None, + public_keys, + scopes, + } + } + + pub(crate) fn convert_from( + block: &Block, + symbols: &SymbolTable, + ) -> Result { + Ok(BlockBuilder { + facts: block + .facts + .iter() + .map(|f| Fact::convert_from(f, symbols)) + .collect::, error::Format>>()?, + rules: block + .rules + .iter() + .map(|r| Rule::convert_from(r, symbols)) + .collect::, error::Format>>()?, + checks: block + .checks + .iter() + .map(|c| Check::convert_from(c, symbols)) + .collect::, error::Format>>()?, + scopes: block + .scopes + .iter() + .map(|s| Scope::convert_from(s, symbols)) + .collect::, error::Format>>()?, + context: block.context.clone(), + }) + } + + // still used in tests but does not make sense for the public API + #[cfg(test)] + pub(crate) fn check_right(self, right: &str) -> Result { + use crate::builder::{pred, string, var}; + + use super::rule; + + let term = string(right); + let check = rule( + "check_right", + &[string(right)], + &[ + pred("resource", &[var("resource_name")]), + pred("operation", &[term]), + pred("right", &[var("resource_name"), string(right)]), + ], + ); + + self.check(check) + } +} + +impl fmt::Display for BlockBuilder { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for mut fact in self.facts.clone().into_iter() { + fact.apply_parameters(); + writeln!(f, "{};", &fact)?; + } + for mut rule in self.rules.clone().into_iter() { + rule.apply_parameters(); + writeln!(f, "{};", &rule)?; + } + for mut check in self.checks.clone().into_iter() { + check.apply_parameters(); + writeln!(f, "{};", &check)?; + } + Ok(()) + } +} + +impl BuilderExt for BlockBuilder { + fn resource(mut self, name: &str) -> Self { + self.facts.push(fact("resource", &[string(name)])); + self + } + fn check_resource(mut self, name: &str) -> Self { + self.checks.push(Check { + queries: vec![rule( + "resource_check", + &[string("resource_check")], + &[pred("resource", &[string(name)])], + )], + kind: CheckKind::One, + }); + self + } + fn operation(mut self, name: &str) -> Self { + self.facts.push(fact("operation", &[string(name)])); + self + } + fn check_operation(mut self, name: &str) -> Self { + self.checks.push(Check { + queries: vec![rule( + "operation_check", + &[string("operation_check")], + &[pred("operation", &[string(name)])], + )], + kind: CheckKind::One, + }); + self + } + fn check_resource_prefix(mut self, prefix: &str) -> Self { + let check = constrained_rule( + "prefix", + &[var("resource")], + &[pred("resource", &[var("resource")])], + &[Expression { + ops: vec![ + Op::Value(var("resource")), + Op::Value(string(prefix)), + Op::Binary(Binary::Prefix), + ], + }], + ); + + self.checks.push(Check { + queries: vec![check], + kind: CheckKind::One, + }); + self + } + + fn check_resource_suffix(mut self, suffix: &str) -> Self { + let check = constrained_rule( + "suffix", + &[var("resource")], + &[pred("resource", &[var("resource")])], + &[Expression { + ops: vec![ + Op::Value(var("resource")), + Op::Value(string(suffix)), + Op::Binary(Binary::Suffix), + ], + }], + ); + + self.checks.push(Check { + queries: vec![check], + kind: CheckKind::One, + }); + self + } + + fn check_expiration_date(mut self, exp: SystemTime) -> Self { + let empty: Vec = Vec::new(); + let ops = vec![ + Op::Value(var("time")), + Op::Value(date(&exp)), + Op::Binary(Binary::LessOrEqual), + ]; + let check = constrained_rule( + "query", + &empty, + &[pred("time", &[var("time")])], + &[Expression { ops }], + ); + + self.checks.push(Check { + queries: vec![check], + kind: CheckKind::One, + }); + self + } +} diff --git a/biscuit-auth/src/token/builder/check.rs b/biscuit-auth/src/token/builder/check.rs new file mode 100644 index 00000000..a2feee3d --- /dev/null +++ b/biscuit-auth/src/token/builder/check.rs @@ -0,0 +1,231 @@ +use std::{convert::TryFrom, fmt, str::FromStr}; + +use nom::Finish; + +use crate::{ + datalog::{self, SymbolTable}, + error, PublicKey, +}; + +use super::{display_rule_body, Convert, Rule, Term, ToAnyParam}; + +/// Builder for a Biscuit check +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Check { + pub queries: Vec, + pub kind: CheckKind, +} + +/// Builder for a Biscuit check +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum CheckKind { + One, + All, + Reject, +} + +impl Check { + /// replace a parameter with the term argument + pub fn set>(&mut self, name: &str, term: T) -> Result<(), error::Token> { + let term = term.into(); + self.set_inner(name, term) + } + + fn set_inner(&mut self, name: &str, term: Term) -> Result<(), error::Token> { + let mut found = false; + for query in &mut self.queries { + if query.set(name, term.clone()).is_ok() { + found = true; + } + } + + if found { + Ok(()) + } else { + Err(error::Token::Language( + biscuit_parser::error::LanguageError::Parameters { + missing_parameters: vec![], + unused_parameters: vec![name.to_string()], + }, + )) + } + } + + /// replace a scope parameter with the pubkey argument + pub fn set_scope(&mut self, name: &str, pubkey: PublicKey) -> Result<(), error::Token> { + let mut found = false; + for query in &mut self.queries { + if query.set_scope(name, pubkey).is_ok() { + found = true; + } + } + + if found { + Ok(()) + } else { + Err(error::Token::Language( + biscuit_parser::error::LanguageError::Parameters { + missing_parameters: vec![], + unused_parameters: vec![name.to_string()], + }, + )) + } + } + + /// replace a parameter with the term argument, without raising an error if the + /// parameter is not present in the check + pub fn set_lenient>(&mut self, name: &str, term: T) -> Result<(), error::Token> { + let term = term.into(); + for query in &mut self.queries { + query.set_lenient(name, term.clone())?; + } + Ok(()) + } + + /// replace a scope parameter with the term argument, without raising an error if the + /// parameter is not present in the check + pub fn set_scope_lenient(&mut self, name: &str, pubkey: PublicKey) -> Result<(), error::Token> { + for query in &mut self.queries { + query.set_scope_lenient(name, pubkey)?; + } + Ok(()) + } + + #[cfg(feature = "datalog-macro")] + pub fn set_macro_param( + &mut self, + name: &str, + param: T, + ) -> Result<(), error::Token> { + use super::AnyParam; + + match param.to_any_param() { + AnyParam::Term(t) => self.set_lenient(name, t), + AnyParam::PublicKey(p) => self.set_scope_lenient(name, p), + } + } + + pub fn validate_parameters(&self) -> Result<(), error::Token> { + for rule in &self.queries { + rule.validate_parameters()?; + } + + Ok(()) + } + + pub(super) fn apply_parameters(&mut self) { + for rule in self.queries.iter_mut() { + rule.apply_parameters(); + } + } +} + +impl Convert for Check { + fn convert(&self, symbols: &mut SymbolTable) -> datalog::Check { + let mut queries = vec![]; + for q in self.queries.iter() { + queries.push(q.convert(symbols)); + } + + datalog::Check { + queries, + kind: self.kind.clone(), + } + } + + fn convert_from(r: &datalog::Check, symbols: &SymbolTable) -> Result { + let mut queries = vec![]; + for q in r.queries.iter() { + queries.push(Rule::convert_from(q, symbols)?); + } + + Ok(Check { + queries, + kind: r.kind.clone(), + }) + } +} + +impl TryFrom for Check { + type Error = error::Token; + + fn try_from(value: Rule) -> Result { + Ok(Check { + queries: vec![value], + kind: CheckKind::One, + }) + } +} + +impl TryFrom<&[Rule]> for Check { + type Error = error::Token; + + fn try_from(values: &[Rule]) -> Result { + Ok(Check { + queries: values.to_vec(), + kind: CheckKind::One, + }) + } +} + +impl fmt::Display for Check { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.kind { + CheckKind::One => write!(f, "check if ")?, + CheckKind::All => write!(f, "check all ")?, + CheckKind::Reject => write!(f, "reject if ")?, + }; + + if !self.queries.is_empty() { + let mut q0 = self.queries[0].clone(); + q0.apply_parameters(); + display_rule_body(&q0, f)?; + + if self.queries.len() > 1 { + for i in 1..self.queries.len() { + write!(f, " or ")?; + let mut qn = self.queries[i].clone(); + qn.apply_parameters(); + display_rule_body(&qn, f)?; + } + } + } + + Ok(()) + } +} + +impl From for Check { + fn from(c: biscuit_parser::builder::Check) -> Self { + Check { + queries: c.queries.into_iter().map(|q| q.into()).collect(), + kind: match c.kind { + biscuit_parser::builder::CheckKind::One => CheckKind::One, + biscuit_parser::builder::CheckKind::All => CheckKind::All, + biscuit_parser::builder::CheckKind::Reject => CheckKind::Reject, + }, + } + } +} + +impl TryFrom<&str> for Check { + type Error = error::Token; + + fn try_from(value: &str) -> Result { + Ok(biscuit_parser::parser::check(value) + .finish() + .map(|(_, o)| o.into()) + .map_err(biscuit_parser::error::LanguageError::from)?) + } +} + +impl FromStr for Check { + type Err = error::Token; + + fn from_str(s: &str) -> Result { + Ok(biscuit_parser::parser::check(s) + .finish() + .map(|(_, o)| o.into()) + .map_err(biscuit_parser::error::LanguageError::from)?) + } +} diff --git a/biscuit-auth/src/token/builder/expression.rs b/biscuit-auth/src/token/builder/expression.rs new file mode 100644 index 00000000..a4fe2fa9 --- /dev/null +++ b/biscuit-auth/src/token/builder/expression.rs @@ -0,0 +1,330 @@ +use std::{collections::HashMap, fmt}; + +use crate::{ + datalog::{self, SymbolTable}, + error, + token::default_symbol_table, +}; + +use super::{Convert, Term}; + +/// Builder for a unary operation +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum Unary { + Negate, + Parens, + Length, + TypeOf, + Ffi(String), +} + +/// Builder for a binary operation +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum Binary { + LessThan, + GreaterThan, + LessOrEqual, + GreaterOrEqual, + Equal, + Contains, + Prefix, + Suffix, + Regex, + Add, + Sub, + Mul, + Div, + And, + Or, + Intersection, + Union, + BitwiseAnd, + BitwiseOr, + BitwiseXor, + NotEqual, + HeterogeneousEqual, + HeterogeneousNotEqual, + LazyAnd, + LazyOr, + All, + Any, + Get, + Ffi(String), +} + +/// Builder for a Datalog expression +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Expression { + pub ops: Vec, +} +// todo track parameters + +impl Convert for Expression { + fn convert(&self, symbols: &mut SymbolTable) -> datalog::Expression { + datalog::Expression { + ops: self.ops.iter().map(|op| op.convert(symbols)).collect(), + } + } + + fn convert_from(e: &datalog::Expression, symbols: &SymbolTable) -> Result { + Ok(Expression { + ops: e + .ops + .iter() + .map(|op| Op::convert_from(op, symbols)) + .collect::, error::Format>>()?, + }) + } +} + +impl AsRef for Expression { + fn as_ref(&self) -> &Expression { + self + } +} + +impl fmt::Display for Expression { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut syms = default_symbol_table(); + let expr = self.convert(&mut syms); + let s = expr.print(&syms).unwrap(); + write!(f, "{}", s) + } +} + +impl From for Expression { + fn from(e: biscuit_parser::builder::Expression) -> Self { + Expression { + ops: e.ops.into_iter().map(|op| op.into()).collect(), + } + } +} + +/// Builder for an expression operation +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Op { + Value(Term), + Unary(Unary), + Binary(Binary), + Closure(Vec, Vec), +} + +impl Op { + pub(super) fn collect_parameters(&self, parameters: &mut HashMap>) { + match self { + Op::Value(Term::Parameter(ref name)) => { + parameters.insert(name.to_owned(), None); + } + Op::Closure(_, ops) => { + for op in ops { + op.collect_parameters(parameters); + } + } + _ => {} + } + } + + pub(super) fn apply_parameters(self, parameters: &HashMap>) -> Self { + match self { + Op::Value(Term::Parameter(ref name)) => { + if let Some(Some(t)) = parameters.get(name) { + Op::Value(t.clone()) + } else { + self + } + } + Op::Value(_) => self, + Op::Unary(_) => self, + Op::Binary(_) => self, + Op::Closure(args, mut ops) => Op::Closure( + args, + ops.drain(..) + .map(|op| op.apply_parameters(parameters)) + .collect(), + ), + } + } +} + +impl Convert for Op { + fn convert(&self, symbols: &mut SymbolTable) -> datalog::Op { + match self { + Op::Value(t) => datalog::Op::Value(t.convert(symbols)), + Op::Unary(u) => datalog::Op::Unary(u.convert(symbols)), + Op::Binary(b) => datalog::Op::Binary(b.convert(symbols)), + Op::Closure(ps, os) => datalog::Op::Closure( + ps.iter().map(|p| symbols.insert(p) as u32).collect(), + os.iter().map(|o| o.convert(symbols)).collect(), + ), + } + } + + fn convert_from(op: &datalog::Op, symbols: &SymbolTable) -> Result { + Ok(match op { + datalog::Op::Value(t) => Op::Value(Term::convert_from(t, symbols)?), + datalog::Op::Unary(u) => Op::Unary(Unary::convert_from(u, symbols)?), + datalog::Op::Binary(b) => Op::Binary(Binary::convert_from(b, symbols)?), + datalog::Op::Closure(ps, os) => Op::Closure( + ps.iter() + .map(|p| symbols.print_symbol(*p as u64)) + .collect::>()?, + os.iter() + .map(|o| Op::convert_from(o, symbols)) + .collect::>()?, + ), + }) + } +} + +impl From for Op { + fn from(op: biscuit_parser::builder::Op) -> Self { + match op { + biscuit_parser::builder::Op::Value(t) => Op::Value(t.into()), + biscuit_parser::builder::Op::Unary(u) => Op::Unary(u.into()), + biscuit_parser::builder::Op::Binary(b) => Op::Binary(b.into()), + biscuit_parser::builder::Op::Closure(ps, os) => { + Op::Closure(ps, os.into_iter().map(|o| o.into()).collect()) + } + } + } +} + +impl Convert for Unary { + fn convert(&self, symbols: &mut SymbolTable) -> datalog::Unary { + match self { + Unary::Negate => datalog::Unary::Negate, + Unary::Parens => datalog::Unary::Parens, + Unary::Length => datalog::Unary::Length, + Unary::TypeOf => datalog::Unary::TypeOf, + Unary::Ffi(n) => datalog::Unary::Ffi(symbols.insert(n)), + } + } + + fn convert_from(f: &datalog::Unary, symbols: &SymbolTable) -> Result { + match f { + datalog::Unary::Negate => Ok(Unary::Negate), + datalog::Unary::Parens => Ok(Unary::Parens), + datalog::Unary::Length => Ok(Unary::Length), + datalog::Unary::TypeOf => Ok(Unary::TypeOf), + datalog::Unary::Ffi(i) => Ok(Unary::Ffi(symbols.print_symbol(*i)?)), + } + } +} + +impl From for Unary { + fn from(unary: biscuit_parser::builder::Unary) -> Self { + match unary { + biscuit_parser::builder::Unary::Negate => Unary::Negate, + biscuit_parser::builder::Unary::Parens => Unary::Parens, + biscuit_parser::builder::Unary::Length => Unary::Length, + biscuit_parser::builder::Unary::TypeOf => Unary::TypeOf, + biscuit_parser::builder::Unary::Ffi(name) => Unary::Ffi(name), + } + } +} + +impl Convert for Binary { + fn convert(&self, symbols: &mut SymbolTable) -> datalog::Binary { + match self { + Binary::LessThan => datalog::Binary::LessThan, + Binary::GreaterThan => datalog::Binary::GreaterThan, + Binary::LessOrEqual => datalog::Binary::LessOrEqual, + Binary::GreaterOrEqual => datalog::Binary::GreaterOrEqual, + Binary::Equal => datalog::Binary::Equal, + Binary::Contains => datalog::Binary::Contains, + Binary::Prefix => datalog::Binary::Prefix, + Binary::Suffix => datalog::Binary::Suffix, + Binary::Regex => datalog::Binary::Regex, + Binary::Add => datalog::Binary::Add, + Binary::Sub => datalog::Binary::Sub, + Binary::Mul => datalog::Binary::Mul, + Binary::Div => datalog::Binary::Div, + Binary::And => datalog::Binary::And, + Binary::Or => datalog::Binary::Or, + Binary::Intersection => datalog::Binary::Intersection, + Binary::Union => datalog::Binary::Union, + Binary::BitwiseAnd => datalog::Binary::BitwiseAnd, + Binary::BitwiseOr => datalog::Binary::BitwiseOr, + Binary::BitwiseXor => datalog::Binary::BitwiseXor, + Binary::NotEqual => datalog::Binary::NotEqual, + Binary::HeterogeneousEqual => datalog::Binary::HeterogeneousEqual, + Binary::HeterogeneousNotEqual => datalog::Binary::HeterogeneousNotEqual, + Binary::LazyAnd => datalog::Binary::LazyAnd, + Binary::LazyOr => datalog::Binary::LazyOr, + Binary::All => datalog::Binary::All, + Binary::Any => datalog::Binary::Any, + Binary::Get => datalog::Binary::Get, + Binary::Ffi(n) => datalog::Binary::Ffi(symbols.insert(n)), + } + } + + fn convert_from(f: &datalog::Binary, symbols: &SymbolTable) -> Result { + match f { + datalog::Binary::LessThan => Ok(Binary::LessThan), + datalog::Binary::GreaterThan => Ok(Binary::GreaterThan), + datalog::Binary::LessOrEqual => Ok(Binary::LessOrEqual), + datalog::Binary::GreaterOrEqual => Ok(Binary::GreaterOrEqual), + datalog::Binary::Equal => Ok(Binary::Equal), + datalog::Binary::Contains => Ok(Binary::Contains), + datalog::Binary::Prefix => Ok(Binary::Prefix), + datalog::Binary::Suffix => Ok(Binary::Suffix), + datalog::Binary::Regex => Ok(Binary::Regex), + datalog::Binary::Add => Ok(Binary::Add), + datalog::Binary::Sub => Ok(Binary::Sub), + datalog::Binary::Mul => Ok(Binary::Mul), + datalog::Binary::Div => Ok(Binary::Div), + datalog::Binary::And => Ok(Binary::And), + datalog::Binary::Or => Ok(Binary::Or), + datalog::Binary::Intersection => Ok(Binary::Intersection), + datalog::Binary::Union => Ok(Binary::Union), + datalog::Binary::BitwiseAnd => Ok(Binary::BitwiseAnd), + datalog::Binary::BitwiseOr => Ok(Binary::BitwiseOr), + datalog::Binary::BitwiseXor => Ok(Binary::BitwiseXor), + datalog::Binary::NotEqual => Ok(Binary::NotEqual), + datalog::Binary::HeterogeneousEqual => Ok(Binary::HeterogeneousEqual), + datalog::Binary::HeterogeneousNotEqual => Ok(Binary::HeterogeneousNotEqual), + datalog::Binary::LazyAnd => Ok(Binary::LazyAnd), + datalog::Binary::LazyOr => Ok(Binary::LazyOr), + datalog::Binary::All => Ok(Binary::All), + datalog::Binary::Any => Ok(Binary::Any), + datalog::Binary::Get => Ok(Binary::Get), + datalog::Binary::Ffi(i) => Ok(Binary::Ffi(symbols.print_symbol(*i)?)), + } + } +} + +impl From for Binary { + fn from(binary: biscuit_parser::builder::Binary) -> Self { + match binary { + biscuit_parser::builder::Binary::LessThan => Binary::LessThan, + biscuit_parser::builder::Binary::GreaterThan => Binary::GreaterThan, + biscuit_parser::builder::Binary::LessOrEqual => Binary::LessOrEqual, + biscuit_parser::builder::Binary::GreaterOrEqual => Binary::GreaterOrEqual, + biscuit_parser::builder::Binary::Equal => Binary::Equal, + biscuit_parser::builder::Binary::Contains => Binary::Contains, + biscuit_parser::builder::Binary::Prefix => Binary::Prefix, + biscuit_parser::builder::Binary::Suffix => Binary::Suffix, + biscuit_parser::builder::Binary::Regex => Binary::Regex, + biscuit_parser::builder::Binary::Add => Binary::Add, + biscuit_parser::builder::Binary::Sub => Binary::Sub, + biscuit_parser::builder::Binary::Mul => Binary::Mul, + biscuit_parser::builder::Binary::Div => Binary::Div, + biscuit_parser::builder::Binary::And => Binary::And, + biscuit_parser::builder::Binary::Or => Binary::Or, + biscuit_parser::builder::Binary::Intersection => Binary::Intersection, + biscuit_parser::builder::Binary::Union => Binary::Union, + biscuit_parser::builder::Binary::BitwiseAnd => Binary::BitwiseAnd, + 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, + biscuit_parser::builder::Binary::LazyAnd => Binary::LazyAnd, + biscuit_parser::builder::Binary::LazyOr => Binary::LazyOr, + biscuit_parser::builder::Binary::All => Binary::All, + biscuit_parser::builder::Binary::Any => Binary::Any, + biscuit_parser::builder::Binary::Get => Binary::Get, + biscuit_parser::builder::Binary::Ffi(name) => Binary::Ffi(name), + } + } +} diff --git a/biscuit-auth/src/token/builder/fact.rs b/biscuit-auth/src/token/builder/fact.rs new file mode 100644 index 00000000..7e5d9bf6 --- /dev/null +++ b/biscuit-auth/src/token/builder/fact.rs @@ -0,0 +1,198 @@ +use std::{collections::HashMap, convert::TryFrom, fmt, str::FromStr}; + +use nom::Finish; + +use crate::{ + datalog::{self, SymbolTable}, + error, +}; + +use super::{Convert, Predicate, Term, ToAnyParam}; + +/// Builder for a Datalog fact +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Fact { + pub predicate: Predicate, + pub parameters: Option>>, +} + +impl Fact { + pub fn new>>(name: String, terms: T) -> Fact { + let mut parameters = HashMap::new(); + let terms: Vec = terms.into(); + + for term in &terms { + term.extract_parameters(&mut parameters); + } + Fact { + predicate: Predicate::new(name, terms), + parameters: Some(parameters), + } + } + + pub fn validate(&self) -> Result<(), error::Token> { + match &self.parameters { + None => Ok(()), + Some(parameters) => { + let invalid_parameters = parameters + .iter() + .filter_map( + |(name, opt_term)| { + if opt_term.is_none() { + Some(name) + } else { + None + } + }, + ) + .map(|name| name.to_string()) + .collect::>(); + + if invalid_parameters.is_empty() { + Ok(()) + } else { + Err(error::Token::Language( + biscuit_parser::error::LanguageError::Parameters { + missing_parameters: invalid_parameters, + unused_parameters: vec![], + }, + )) + } + } + } + } + + /// replace a parameter with the term argument + pub fn set>(&mut self, name: &str, term: T) -> Result<(), error::Token> { + if let Some(parameters) = self.parameters.as_mut() { + match parameters.get_mut(name) { + None => Err(error::Token::Language( + biscuit_parser::error::LanguageError::Parameters { + missing_parameters: vec![], + unused_parameters: vec![name.to_string()], + }, + )), + Some(v) => { + *v = Some(term.into()); + Ok(()) + } + } + } else { + Err(error::Token::Language( + biscuit_parser::error::LanguageError::Parameters { + missing_parameters: vec![], + unused_parameters: vec![name.to_string()], + }, + )) + } + } + + /// replace a parameter with the term argument, without raising an error + /// if the parameter is not present in the fact description + pub fn set_lenient>(&mut self, name: &str, term: T) -> Result<(), error::Token> { + if let Some(parameters) = self.parameters.as_mut() { + match parameters.get_mut(name) { + None => Ok(()), + Some(v) => { + *v = Some(term.into()); + Ok(()) + } + } + } else { + Err(error::Token::Language( + biscuit_parser::error::LanguageError::Parameters { + missing_parameters: vec![], + unused_parameters: vec![name.to_string()], + }, + )) + } + } + + #[cfg(feature = "datalog-macro")] + pub fn set_macro_param( + &mut self, + name: &str, + param: T, + ) -> Result<(), error::Token> { + use super::AnyParam; + + match param.to_any_param() { + AnyParam::Term(t) => self.set_lenient(name, t), + AnyParam::PublicKey(_) => Ok(()), + } + } + + pub(super) fn apply_parameters(&mut self) { + if let Some(parameters) = self.parameters.clone() { + self.predicate.terms = self + .predicate + .terms + .drain(..) + .map(|t| t.apply_parameters(¶meters)) + .collect(); + } + } +} + +impl Convert for Fact { + fn convert(&self, symbols: &mut SymbolTable) -> datalog::Fact { + let mut fact = self.clone(); + fact.apply_parameters(); + + datalog::Fact { + predicate: fact.predicate.convert(symbols), + } + } + + fn convert_from(f: &datalog::Fact, symbols: &SymbolTable) -> Result { + Ok(Fact { + predicate: Predicate::convert_from(&f.predicate, symbols)?, + parameters: None, + }) + } +} + +impl fmt::Display for Fact { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut fact = self.clone(); + fact.apply_parameters(); + + fact.predicate.fmt(f) + } +} + +impl From for Fact { + fn from(f: biscuit_parser::builder::Fact) -> Self { + Fact { + predicate: f.predicate.into(), + // pub parameters: Option>>, + parameters: f.parameters.map(|h| { + h.into_iter() + .map(|(k, v)| (k, v.map(|term| term.into()))) + .collect() + }), + } + } +} + +impl TryFrom<&str> for Fact { + type Error = error::Token; + + fn try_from(value: &str) -> Result { + Ok(biscuit_parser::parser::fact(value) + .finish() + .map(|(_, o)| o.into()) + .map_err(biscuit_parser::error::LanguageError::from)?) + } +} + +impl FromStr for Fact { + type Err = error::Token; + + fn from_str(s: &str) -> Result { + Ok(biscuit_parser::parser::fact(s) + .finish() + .map(|(_, o)| o.into()) + .map_err(biscuit_parser::error::LanguageError::from)?) + } +} diff --git a/biscuit-auth/src/token/builder/policy.rs b/biscuit-auth/src/token/builder/policy.rs new file mode 100644 index 00000000..29faa844 --- /dev/null +++ b/biscuit-auth/src/token/builder/policy.rs @@ -0,0 +1,171 @@ +use std::{convert::TryFrom, fmt, str::FromStr}; + +use nom::Finish; + +use crate::{error, PublicKey}; + +use super::{display_rule_body, Rule, Term, ToAnyParam}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum PolicyKind { + Allow, + Deny, +} + +/// Builder for a Biscuit policy +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Policy { + pub queries: Vec, + pub kind: PolicyKind, +} + +impl Policy { + /// replace a parameter with the term argument + pub fn set>(&mut self, name: &str, term: T) -> Result<(), error::Token> { + let term = term.into(); + self.set_inner(name, term) + } + + pub fn set_inner(&mut self, name: &str, term: Term) -> Result<(), error::Token> { + let mut found = false; + for query in &mut self.queries { + if query.set(name, term.clone()).is_ok() { + found = true; + } + } + + if found { + Ok(()) + } else { + Err(error::Token::Language( + biscuit_parser::error::LanguageError::Parameters { + missing_parameters: vec![], + unused_parameters: vec![name.to_string()], + }, + )) + } + } + + /// replace a scope parameter with the pubkey argument + pub fn set_scope(&mut self, name: &str, pubkey: PublicKey) -> Result<(), error::Token> { + let mut found = false; + for query in &mut self.queries { + if query.set_scope(name, pubkey).is_ok() { + found = true; + } + } + + if found { + Ok(()) + } else { + Err(error::Token::Language( + biscuit_parser::error::LanguageError::Parameters { + missing_parameters: vec![], + unused_parameters: vec![name.to_string()], + }, + )) + } + } + + /// replace a parameter with the term argument, ignoring unknown parameters + pub fn set_lenient>(&mut self, name: &str, term: T) -> Result<(), error::Token> { + let term = term.into(); + for query in &mut self.queries { + query.set_lenient(name, term.clone())?; + } + Ok(()) + } + + /// replace a scope parameter with the pubkey argument, ignoring unknown parameters + pub fn set_scope_lenient(&mut self, name: &str, pubkey: PublicKey) -> Result<(), error::Token> { + for query in &mut self.queries { + query.set_scope_lenient(name, pubkey)?; + } + Ok(()) + } + + #[cfg(feature = "datalog-macro")] + pub fn set_macro_param( + &mut self, + name: &str, + param: T, + ) -> Result<(), error::Token> { + use super::AnyParam; + + match param.to_any_param() { + AnyParam::Term(t) => self.set_lenient(name, t), + AnyParam::PublicKey(p) => self.set_scope_lenient(name, p), + } + } + + pub fn validate_parameters(&self) -> Result<(), error::Token> { + for query in &self.queries { + query.validate_parameters()?; + } + + Ok(()) + } +} + +impl fmt::Display for Policy { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if !self.queries.is_empty() { + match self.kind { + PolicyKind::Allow => write!(f, "allow if ")?, + PolicyKind::Deny => write!(f, "deny if ")?, + } + + if !self.queries.is_empty() { + display_rule_body(&self.queries[0], f)?; + + if self.queries.len() > 1 { + for i in 1..self.queries.len() { + write!(f, " or ")?; + display_rule_body(&self.queries[i], f)?; + } + } + } + } else { + match self.kind { + PolicyKind::Allow => write!(f, "allow")?, + PolicyKind::Deny => write!(f, "deny")?, + } + } + + Ok(()) + } +} + +impl From for Policy { + fn from(p: biscuit_parser::builder::Policy) -> Self { + Policy { + queries: p.queries.into_iter().map(|q| q.into()).collect(), + kind: match p.kind { + biscuit_parser::builder::PolicyKind::Allow => PolicyKind::Allow, + biscuit_parser::builder::PolicyKind::Deny => PolicyKind::Deny, + }, + } + } +} + +impl TryFrom<&str> for Policy { + type Error = error::Token; + + fn try_from(value: &str) -> Result { + Ok(biscuit_parser::parser::policy(value) + .finish() + .map(|(_, o)| o.into()) + .map_err(biscuit_parser::error::LanguageError::from)?) + } +} + +impl FromStr for Policy { + type Err = error::Token; + + fn from_str(s: &str) -> Result { + Ok(biscuit_parser::parser::policy(s) + .finish() + .map(|(_, o)| o.into()) + .map_err(biscuit_parser::error::LanguageError::from)?) + } +} diff --git a/biscuit-auth/src/token/builder/predicate.rs b/biscuit-auth/src/token/builder/predicate.rs new file mode 100644 index 00000000..12bd00d1 --- /dev/null +++ b/biscuit-auth/src/token/builder/predicate.rs @@ -0,0 +1,80 @@ +use std::fmt; + +use crate::{ + datalog::{self, SymbolTable}, + error, +}; + +use super::{Convert, Term}; + +/// Builder for a Datalog predicate, used in facts and rules +#[derive(Debug, Clone, PartialEq, Hash, Eq)] +pub struct Predicate { + pub name: String, + pub terms: Vec, +} + +impl Predicate { + pub fn new>>(name: String, terms: T) -> Predicate { + Predicate { + name, + terms: terms.into(), + } + } +} + +impl Convert for Predicate { + fn convert(&self, symbols: &mut SymbolTable) -> datalog::Predicate { + let name = symbols.insert(&self.name); + let mut terms = vec![]; + + for term in self.terms.iter() { + terms.push(term.convert(symbols)); + } + + datalog::Predicate { name, terms } + } + + fn convert_from(p: &datalog::Predicate, symbols: &SymbolTable) -> Result { + Ok(Predicate { + name: symbols.print_symbol(p.name)?, + terms: p + .terms + .iter() + .map(|term| Term::convert_from(term, symbols)) + .collect::, error::Format>>()?, + }) + } +} + +impl AsRef for Predicate { + fn as_ref(&self) -> &Predicate { + self + } +} + +impl fmt::Display for Predicate { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}(", self.name)?; + + if !self.terms.is_empty() { + write!(f, "{}", self.terms[0])?; + + if self.terms.len() > 1 { + for i in 1..self.terms.len() { + write!(f, ", {}", self.terms[i])?; + } + } + } + write!(f, ")") + } +} + +impl From for Predicate { + fn from(p: biscuit_parser::builder::Predicate) -> Self { + Predicate { + name: p.name, + terms: p.terms.into_iter().map(|t| t.into()).collect(), + } + } +} diff --git a/biscuit-auth/src/token/builder/rule.rs b/biscuit-auth/src/token/builder/rule.rs new file mode 100644 index 00000000..e33d017f --- /dev/null +++ b/biscuit-auth/src/token/builder/rule.rs @@ -0,0 +1,473 @@ +use std::{collections::HashMap, convert::TryFrom, fmt, str::FromStr}; + +use nom::Finish; + +use crate::{ + datalog::{self, SymbolTable}, + error, PublicKey, +}; + +use super::{Convert, Expression, Predicate, Scope, Term, ToAnyParam}; + +/// Builder for a Datalog rule +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Rule { + pub head: Predicate, + pub body: Vec, + pub expressions: Vec, + pub parameters: Option>>, + pub scopes: Vec, + pub scope_parameters: Option>>, +} + +impl Rule { + pub fn new( + head: Predicate, + body: Vec, + expressions: Vec, + scopes: Vec, + ) -> Rule { + let mut parameters = HashMap::new(); + let mut scope_parameters = HashMap::new(); + for term in &head.terms { + term.extract_parameters(&mut parameters); + } + + for predicate in &body { + for term in &predicate.terms { + term.extract_parameters(&mut parameters); + } + } + + for expression in &expressions { + for op in &expression.ops { + op.collect_parameters(&mut parameters); + } + } + + for scope in &scopes { + if let Scope::Parameter(name) = &scope { + scope_parameters.insert(name.to_string(), None); + } + } + + Rule { + head, + body, + expressions, + parameters: Some(parameters), + scopes, + scope_parameters: Some(scope_parameters), + } + } + + pub fn validate_parameters(&self) -> Result<(), error::Token> { + let mut invalid_parameters = match &self.parameters { + None => vec![], + Some(parameters) => parameters + .iter() + .filter_map( + |(name, opt_term)| { + if opt_term.is_none() { + Some(name) + } else { + None + } + }, + ) + .map(|name| name.to_string()) + .collect::>(), + }; + let mut invalid_scope_parameters = match &self.scope_parameters { + None => vec![], + Some(parameters) => parameters + .iter() + .filter_map( + |(name, opt_key)| { + if opt_key.is_none() { + Some(name) + } else { + None + } + }, + ) + .map(|name| name.to_string()) + .collect::>(), + }; + let mut all_invalid_parameters = vec![]; + all_invalid_parameters.append(&mut invalid_parameters); + all_invalid_parameters.append(&mut invalid_scope_parameters); + + if all_invalid_parameters.is_empty() { + Ok(()) + } else { + Err(error::Token::Language( + biscuit_parser::error::LanguageError::Parameters { + missing_parameters: all_invalid_parameters, + unused_parameters: vec![], + }, + )) + } + } + + pub fn validate_variables(&self) -> Result<(), String> { + let mut head_variables: std::collections::HashSet = self + .head + .terms + .iter() + .filter_map(|term| match term { + Term::Variable(s) => Some(s.to_string()), + _ => None, + }) + .collect(); + + for predicate in self.body.iter() { + for term in predicate.terms.iter() { + if let Term::Variable(v) = term { + head_variables.remove(v); + if head_variables.is_empty() { + return Ok(()); + } + } + } + } + + if head_variables.is_empty() { + Ok(()) + } else { + Err(format!( + "rule head contains variables that are not used in predicates of the rule's body: {}", + head_variables + .iter() + .map(|s| format!("${}", s)) + .collect::>() + .join(", ") + )) + } + } + + /// replace a parameter with the term argument + pub fn set>(&mut self, name: &str, term: T) -> Result<(), error::Token> { + if let Some(parameters) = self.parameters.as_mut() { + match parameters.get_mut(name) { + None => Err(error::Token::Language( + biscuit_parser::error::LanguageError::Parameters { + missing_parameters: vec![], + unused_parameters: vec![name.to_string()], + }, + )), + Some(v) => { + *v = Some(term.into()); + Ok(()) + } + } + } else { + Err(error::Token::Language( + biscuit_parser::error::LanguageError::Parameters { + missing_parameters: vec![], + unused_parameters: vec![name.to_string()], + }, + )) + } + } + + /// replace a parameter with the term argument, without raising an error if the + /// parameter is not present in the rule + pub fn set_lenient>(&mut self, name: &str, term: T) -> Result<(), error::Token> { + if let Some(parameters) = self.parameters.as_mut() { + match parameters.get_mut(name) { + None => Ok(()), + Some(v) => { + *v = Some(term.into()); + Ok(()) + } + } + } else { + Err(error::Token::Language( + biscuit_parser::error::LanguageError::Parameters { + missing_parameters: vec![], + unused_parameters: vec![name.to_string()], + }, + )) + } + } + + /// replace a scope parameter with the pubkey argument + pub fn set_scope(&mut self, name: &str, pubkey: PublicKey) -> Result<(), error::Token> { + if let Some(parameters) = self.scope_parameters.as_mut() { + match parameters.get_mut(name) { + None => Err(error::Token::Language( + biscuit_parser::error::LanguageError::Parameters { + missing_parameters: vec![], + unused_parameters: vec![name.to_string()], + }, + )), + Some(v) => { + *v = Some(pubkey); + Ok(()) + } + } + } else { + Err(error::Token::Language( + biscuit_parser::error::LanguageError::Parameters { + missing_parameters: vec![], + unused_parameters: vec![name.to_string()], + }, + )) + } + } + + /// replace a scope parameter with the public key argument, without raising an error if the + /// parameter is not present in the rule scope + pub fn set_scope_lenient(&mut self, name: &str, pubkey: PublicKey) -> Result<(), error::Token> { + if let Some(parameters) = self.scope_parameters.as_mut() { + match parameters.get_mut(name) { + None => Ok(()), + Some(v) => { + *v = Some(pubkey); + Ok(()) + } + } + } else { + Err(error::Token::Language( + biscuit_parser::error::LanguageError::Parameters { + missing_parameters: vec![], + unused_parameters: vec![name.to_string()], + }, + )) + } + } + + #[cfg(feature = "datalog-macro")] + pub fn set_macro_param( + &mut self, + name: &str, + param: T, + ) -> Result<(), error::Token> { + use super::AnyParam; + + match param.to_any_param() { + AnyParam::Term(t) => self.set_lenient(name, t), + AnyParam::PublicKey(pubkey) => self.set_scope_lenient(name, pubkey), + } + } + + pub(super) fn apply_parameters(&mut self) { + if let Some(parameters) = self.parameters.clone() { + self.head.terms = self + .head + .terms + .drain(..) + .map(|t| { + if let Term::Parameter(name) = &t { + if let Some(Some(term)) = parameters.get(name) { + return term.clone(); + } + } + t + }) + .collect(); + + for predicate in &mut self.body { + predicate.terms = predicate + .terms + .drain(..) + .map(|t| { + if let Term::Parameter(name) = &t { + if let Some(Some(term)) = parameters.get(name) { + return term.clone(); + } + } + t + }) + .collect(); + } + + for expression in &mut self.expressions { + expression.ops = expression + .ops + .drain(..) + .map(|op| op.apply_parameters(¶meters)) + .collect(); + } + } + + if let Some(parameters) = self.scope_parameters.clone() { + self.scopes = self + .scopes + .drain(..) + .map(|scope| { + if let Scope::Parameter(name) = &scope { + if let Some(Some(pubkey)) = parameters.get(name) { + return Scope::PublicKey(*pubkey); + } + } + scope + }) + .collect(); + } + } +} + +impl Convert for Rule { + fn convert(&self, symbols: &mut SymbolTable) -> datalog::Rule { + let mut r = self.clone(); + r.apply_parameters(); + + let head = r.head.convert(symbols); + let mut body = vec![]; + let mut expressions = vec![]; + let mut scopes = vec![]; + + for p in r.body.iter() { + body.push(p.convert(symbols)); + } + + for c in r.expressions.iter() { + expressions.push(c.convert(symbols)); + } + + for scope in r.scopes.iter() { + scopes.push(match scope { + Scope::Authority => crate::token::Scope::Authority, + Scope::Previous => crate::token::Scope::Previous, + Scope::PublicKey(key) => { + crate::token::Scope::PublicKey(symbols.public_keys.insert(key)) + } + // The error is caught in the `add_xxx` functions, so this should + // not happen™ + Scope::Parameter(s) => panic!("Remaining parameter {}", &s), + }) + } + datalog::Rule { + head, + body, + expressions, + scopes, + } + } + + fn convert_from(r: &datalog::Rule, symbols: &SymbolTable) -> Result { + Ok(Rule { + head: Predicate::convert_from(&r.head, symbols)?, + body: r + .body + .iter() + .map(|p| Predicate::convert_from(p, symbols)) + .collect::, error::Format>>()?, + expressions: r + .expressions + .iter() + .map(|c| Expression::convert_from(c, symbols)) + .collect::, error::Format>>()?, + parameters: None, + scopes: r + .scopes + .iter() + .map(|scope| Scope::convert_from(scope, symbols)) + .collect::, error::Format>>()?, + scope_parameters: None, + }) + } +} + +pub(super) fn display_rule_body(r: &Rule, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut rule = r.clone(); + rule.apply_parameters(); + if !rule.body.is_empty() { + write!(f, "{}", rule.body[0])?; + + if rule.body.len() > 1 { + for i in 1..rule.body.len() { + write!(f, ", {}", rule.body[i])?; + } + } + } + + if !rule.expressions.is_empty() { + if !rule.body.is_empty() { + write!(f, ", ")?; + } + + write!(f, "{}", rule.expressions[0])?; + + if rule.expressions.len() > 1 { + for i in 1..rule.expressions.len() { + write!(f, ", {}", rule.expressions[i])?; + } + } + } + + if !rule.scopes.is_empty() { + write!(f, " trusting {}", rule.scopes[0])?; + if rule.scopes.len() > 1 { + for i in 1..rule.scopes.len() { + write!(f, ", {}", rule.scopes[i])?; + } + } + } + + Ok(()) +} + +impl fmt::Display for Rule { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut r = self.clone(); + r.apply_parameters(); + + write!(f, "{} <- ", r.head)?; + + display_rule_body(&r, f) + } +} + +impl From for Rule { + fn from(r: biscuit_parser::builder::Rule) -> Self { + Rule { + head: r.head.into(), + body: r.body.into_iter().map(|p| p.into()).collect(), + expressions: r.expressions.into_iter().map(|e| e.into()).collect(), + parameters: r.parameters.map(|h| { + h.into_iter() + .map(|(k, v)| (k, v.map(|term| term.into()))) + .collect() + }), + scopes: r.scopes.into_iter().map(|s| s.into()).collect(), + scope_parameters: r.scope_parameters.map(|h| { + h.into_iter() + .map(|(k, v)| { + ( + k, + v.map(|pk| { + PublicKey::from_bytes(&pk.key, pk.algorithm.into()) + .expect("invalid public key") + }), + ) + }) + .collect() + }), + } + } +} + +impl TryFrom<&str> for Rule { + type Error = error::Token; + + fn try_from(value: &str) -> Result { + Ok(biscuit_parser::parser::rule(value) + .finish() + .map(|(_, o)| o.into()) + .map_err(biscuit_parser::error::LanguageError::from)?) + } +} + +impl FromStr for Rule { + type Err = error::Token; + + fn from_str(s: &str) -> Result { + Ok(biscuit_parser::parser::rule(s) + .finish() + .map(|(_, o)| o.into()) + .map_err(biscuit_parser::error::LanguageError::from)?) + } +} diff --git a/biscuit-auth/src/token/builder/scope.rs b/biscuit-auth/src/token/builder/scope.rs new file mode 100644 index 00000000..b97361b1 --- /dev/null +++ b/biscuit-auth/src/token/builder/scope.rs @@ -0,0 +1,75 @@ +use std::fmt; + +use crate::{datalog::SymbolTable, error, PublicKey}; + +use super::Convert; + +/// Builder for a block or rule scope +#[derive(Clone, Debug, Hash, PartialEq, Eq)] +pub enum Scope { + /// Trusts the first block, current block and the authorizer + Authority, + /// Trusts the current block and all previous ones + Previous, + /// Trusts the current block and any block signed by the public key + PublicKey(PublicKey), + /// Used for parameter substitution + Parameter(String), +} + +impl Convert for Scope { + fn convert(&self, symbols: &mut SymbolTable) -> crate::token::Scope { + match self { + Scope::Authority => crate::token::Scope::Authority, + Scope::Previous => crate::token::Scope::Previous, + Scope::PublicKey(key) => { + crate::token::Scope::PublicKey(symbols.public_keys.insert(key)) + } + // The error is caught in the `add_xxx` functions, so this should + // not happen™ + Scope::Parameter(s) => panic!("Remaining parameter {}", &s), + } + } + + fn convert_from( + scope: &crate::token::Scope, + symbols: &SymbolTable, + ) -> Result { + Ok(match scope { + crate::token::Scope::Authority => Scope::Authority, + crate::token::Scope::Previous => Scope::Previous, + crate::token::Scope::PublicKey(key_id) => Scope::PublicKey( + *symbols + .public_keys + .get_key(*key_id) + .ok_or(error::Format::UnknownExternalKey)?, + ), + }) + } +} + +impl fmt::Display for Scope { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Scope::Authority => write!(f, "authority"), + Scope::Previous => write!(f, "previous"), + Scope::PublicKey(pk) => pk.write(f), + Scope::Parameter(s) => { + write!(f, "{{{}}}", s) + } + } + } +} + +impl From for Scope { + fn from(scope: biscuit_parser::builder::Scope) -> Self { + match scope { + biscuit_parser::builder::Scope::Authority => Scope::Authority, + biscuit_parser::builder::Scope::Previous => Scope::Previous, + biscuit_parser::builder::Scope::PublicKey(pk) => Scope::PublicKey( + PublicKey::from_bytes(&pk.key, pk.algorithm.into()).expect("invalid public key"), + ), + biscuit_parser::builder::Scope::Parameter(s) => Scope::Parameter(s), + } + } +} diff --git a/biscuit-auth/src/token/builder/term.rs b/biscuit-auth/src/token/builder/term.rs new file mode 100644 index 00000000..a8459e4c --- /dev/null +++ b/biscuit-auth/src/token/builder/term.rs @@ -0,0 +1,666 @@ +use std::{ + collections::{BTreeMap, BTreeSet, HashMap}, + convert::{TryFrom, TryInto}, + fmt, + time::{Duration, SystemTime, UNIX_EPOCH}, +}; + +use crate::{ + datalog::{self, SymbolTable, TemporarySymbolTable}, + error, +}; + +#[cfg(feature = "datalog-macro")] +use super::AnyParam; +use super::{set, Convert, Fact, ToAnyParam}; + +/// Builder for a Datalog value +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum Term { + Variable(String), + Integer(i64), + Str(String), + Date(u64), + Bytes(Vec), + Bool(bool), + Set(BTreeSet), + Parameter(String), + Null, + Array(Vec), + Map(BTreeMap), +} + +impl Term { + pub(super) fn extract_parameters(&self, parameters: &mut HashMap>) { + match self { + Term::Parameter(name) => { + parameters.insert(name.to_string(), None); + } + Term::Set(s) => { + for term in s { + term.extract_parameters(parameters); + } + } + Term::Array(a) => { + for term in a { + term.extract_parameters(parameters); + } + } + Term::Map(m) => { + for (key, term) in m { + if let MapKey::Parameter(name) = key { + parameters.insert(name.to_string(), None); + } + term.extract_parameters(parameters); + } + } + _ => {} + } + } + + pub(super) fn apply_parameters(self, parameters: &HashMap>) -> Term { + match self { + Term::Parameter(name) => { + if let Some(Some(term)) = parameters.get(&name) { + term.clone() + } else { + Term::Parameter(name) + } + } + Term::Map(m) => Term::Map( + m.into_iter() + .map(|(key, term)| { + ( + match key { + MapKey::Parameter(name) => { + if let Some(Some(key_term)) = parameters.get(&name) { + match key_term { + Term::Integer(i) => MapKey::Integer(*i), + Term::Str(s) => MapKey::Str(s.clone()), + //FIXME: we should return an error + _ => MapKey::Parameter(name), + } + } else { + MapKey::Parameter(name) + } + } + _ => key, + }, + term.apply_parameters(parameters), + ) + }) + .collect(), + ), + Term::Array(array) => Term::Array( + array + .into_iter() + .map(|term| term.apply_parameters(parameters)) + .collect(), + ), + Term::Set(set) => Term::Set( + set.into_iter() + .map(|term| term.apply_parameters(parameters)) + .collect(), + ), + _ => self, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum MapKey { + Integer(i64), + Str(String), + Parameter(String), +} + +impl Term { + pub fn to_datalog(self, symbols: &mut TemporarySymbolTable) -> datalog::Term { + match self { + Term::Variable(s) => datalog::Term::Variable(symbols.insert(&s) as u32), + Term::Integer(i) => datalog::Term::Integer(i), + Term::Str(s) => datalog::Term::Str(symbols.insert(&s)), + Term::Date(d) => datalog::Term::Date(d), + Term::Bytes(s) => datalog::Term::Bytes(s), + Term::Bool(b) => datalog::Term::Bool(b), + Term::Set(s) => { + datalog::Term::Set(s.into_iter().map(|i| i.to_datalog(symbols)).collect()) + } + Term::Null => datalog::Term::Null, + Term::Array(a) => { + datalog::Term::Array(a.into_iter().map(|i| i.to_datalog(symbols)).collect()) + } + Term::Map(m) => datalog::Term::Map( + m.into_iter() + .map(|(k, i)| { + ( + match k { + MapKey::Integer(i) => datalog::MapKey::Integer(i), + MapKey::Str(s) => datalog::MapKey::Str(symbols.insert(&s)), + // The error is caught in the `add_xxx` functions, so this should + // not happen™ + MapKey::Parameter(s) => panic!("Remaining parameter {}", &s), + }, + i.to_datalog(symbols), + ) + }) + .collect(), + ), + // The error is caught in the `add_xxx` functions, so this should + // not happen™ + Term::Parameter(s) => panic!("Remaining parameter {}", &s), + } + } + + pub fn from_datalog( + term: datalog::Term, + symbols: &TemporarySymbolTable, + ) -> Result { + Ok(match term { + datalog::Term::Variable(s) => Term::Variable( + symbols + .get_symbol(s as u64) + .ok_or(error::Expression::UnknownVariable(s))? + .to_string(), + ), + datalog::Term::Integer(i) => Term::Integer(i), + datalog::Term::Str(s) => Term::Str( + symbols + .get_symbol(s) + .ok_or(error::Expression::UnknownSymbol(s))? + .to_string(), + ), + datalog::Term::Date(d) => Term::Date(d), + datalog::Term::Bytes(s) => Term::Bytes(s), + datalog::Term::Bool(b) => Term::Bool(b), + datalog::Term::Set(s) => Term::Set( + s.into_iter() + .map(|i| Self::from_datalog(i, symbols)) + .collect::>()?, + ), + datalog::Term::Null => Term::Null, + datalog::Term::Array(a) => Term::Array( + a.into_iter() + .map(|i| Self::from_datalog(i, symbols)) + .collect::>()?, + ), + datalog::Term::Map(m) => Term::Map( + m.into_iter() + .map(|(k, i)| { + Ok(( + match k { + datalog::MapKey::Integer(i) => MapKey::Integer(i), + datalog::MapKey::Str(s) => MapKey::Str( + symbols + .get_symbol(s) + .ok_or(error::Expression::UnknownSymbol(s))? + .to_string(), + ), + }, + Self::from_datalog(i, symbols)?, + )) + }) + .collect::>()?, + ), + }) + } +} + +impl Convert for Term { + fn convert(&self, symbols: &mut SymbolTable) -> datalog::Term { + match self { + Term::Variable(s) => datalog::Term::Variable(symbols.insert(s) as u32), + Term::Integer(i) => datalog::Term::Integer(*i), + Term::Str(s) => datalog::Term::Str(symbols.insert(s)), + Term::Date(d) => datalog::Term::Date(*d), + Term::Bytes(s) => datalog::Term::Bytes(s.clone()), + Term::Bool(b) => datalog::Term::Bool(*b), + Term::Set(s) => datalog::Term::Set(s.iter().map(|i| i.convert(symbols)).collect()), + Term::Null => datalog::Term::Null, + // The error is caught in the `add_xxx` functions, so this should + // not happen™ + Term::Parameter(s) => panic!("Remaining parameter {}", &s), + Term::Array(a) => datalog::Term::Array(a.iter().map(|i| i.convert(symbols)).collect()), + Term::Map(m) => datalog::Term::Map( + m.iter() + .map(|(key, term)| { + let key = match key { + MapKey::Integer(i) => datalog::MapKey::Integer(*i), + MapKey::Str(s) => datalog::MapKey::Str(symbols.insert(s)), + MapKey::Parameter(s) => panic!("Remaining parameter {}", &s), + }; + + (key, term.convert(symbols)) + }) + .collect(), + ), + } + } + + fn convert_from(f: &datalog::Term, symbols: &SymbolTable) -> Result { + Ok(match f { + datalog::Term::Variable(s) => Term::Variable(symbols.print_symbol(*s as u64)?), + datalog::Term::Integer(i) => Term::Integer(*i), + datalog::Term::Str(s) => Term::Str(symbols.print_symbol(*s)?), + datalog::Term::Date(d) => Term::Date(*d), + datalog::Term::Bytes(s) => Term::Bytes(s.clone()), + datalog::Term::Bool(b) => Term::Bool(*b), + datalog::Term::Set(s) => Term::Set( + s.iter() + .map(|i| Term::convert_from(i, symbols)) + .collect::, error::Format>>()?, + ), + datalog::Term::Null => Term::Null, + datalog::Term::Array(a) => Term::Array( + a.iter() + .map(|i| Term::convert_from(i, symbols)) + .collect::, error::Format>>()?, + ), + datalog::Term::Map(m) => Term::Map( + m.iter() + .map(|(key, term)| { + let key = match key { + datalog::MapKey::Integer(i) => Ok(MapKey::Integer(*i)), + datalog::MapKey::Str(s) => symbols.print_symbol(*s).map(MapKey::Str), + }; + + key.and_then(|k| Term::convert_from(term, symbols).map(|term| (k, term))) + }) + .collect::, error::Format>>()?, + ), + }) + } +} + +impl From<&Term> for Term { + fn from(i: &Term) -> Self { + match i { + Term::Variable(ref v) => Term::Variable(v.clone()), + Term::Integer(ref i) => Term::Integer(*i), + Term::Str(ref s) => Term::Str(s.clone()), + Term::Date(ref d) => Term::Date(*d), + Term::Bytes(ref s) => Term::Bytes(s.clone()), + Term::Bool(b) => Term::Bool(*b), + Term::Set(ref s) => Term::Set(s.clone()), + Term::Parameter(ref p) => Term::Parameter(p.clone()), + Term::Null => Term::Null, + Term::Array(ref a) => Term::Array(a.clone()), + Term::Map(m) => Term::Map(m.clone()), + } + } +} + +impl From for Term { + fn from(t: biscuit_parser::builder::Term) -> Self { + match t { + biscuit_parser::builder::Term::Variable(v) => Term::Variable(v), + biscuit_parser::builder::Term::Integer(i) => Term::Integer(i), + biscuit_parser::builder::Term::Str(s) => Term::Str(s), + biscuit_parser::builder::Term::Date(d) => Term::Date(d), + biscuit_parser::builder::Term::Bytes(s) => Term::Bytes(s), + biscuit_parser::builder::Term::Bool(b) => Term::Bool(b), + biscuit_parser::builder::Term::Set(s) => { + Term::Set(s.into_iter().map(|t| t.into()).collect()) + } + biscuit_parser::builder::Term::Null => Term::Null, + biscuit_parser::builder::Term::Parameter(ref p) => Term::Parameter(p.clone()), + biscuit_parser::builder::Term::Array(a) => { + Term::Array(a.into_iter().map(|t| t.into()).collect()) + } + biscuit_parser::builder::Term::Map(a) => Term::Map( + a.into_iter() + .map(|(key, term)| { + ( + match key { + biscuit_parser::builder::MapKey::Parameter(s) => { + MapKey::Parameter(s) + } + biscuit_parser::builder::MapKey::Integer(i) => MapKey::Integer(i), + biscuit_parser::builder::MapKey::Str(s) => MapKey::Str(s), + }, + term.into(), + ) + }) + .collect(), + ), + } + } +} + +impl AsRef for Term { + fn as_ref(&self) -> &Term { + self + } +} + +impl fmt::Display for Term { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Term::Variable(i) => write!(f, "${}", i), + Term::Integer(i) => write!(f, "{}", i), + Term::Str(s) => write!(f, "\"{}\"", s), + Term::Date(d) => { + let date = time::OffsetDateTime::from_unix_timestamp(*d as i64) + .ok() + .and_then(|t| { + t.format(&time::format_description::well_known::Rfc3339) + .ok() + }) + .unwrap_or_else(|| "".to_string()); + + write!(f, "{}", date) + } + Term::Bytes(s) => write!(f, "hex:{}", hex::encode(s)), + Term::Bool(b) => { + if *b { + write!(f, "true") + } else { + write!(f, "false") + } + } + Term::Set(s) => { + if s.is_empty() { + write!(f, "{{,}}") + } else { + let terms = s.iter().map(|term| term.to_string()).collect::>(); + write!(f, "{{{}}}", terms.join(", ")) + } + } + Term::Parameter(s) => { + write!(f, "{{{}}}", s) + } + Term::Null => write!(f, "null"), + Term::Array(a) => { + let terms = a.iter().map(|term| term.to_string()).collect::>(); + write!(f, "[{}]", terms.join(", ")) + } + Term::Map(m) => { + let terms = m + .iter() + .map(|(key, term)| match key { + MapKey::Integer(i) => format!("{i}: {}", term), + MapKey::Str(s) => format!("\"{s}\": {}", term), + MapKey::Parameter(s) => format!("{{{s}}}: {}", term), + }) + .collect::>(); + write!(f, "{{{}}}", terms.join(", ")) + } + } + } +} + +#[cfg(feature = "datalog-macro")] +impl ToAnyParam for Term { + fn to_any_param(&self) -> AnyParam { + AnyParam::Term(self.clone()) + } +} + +impl From for Term { + fn from(i: i64) -> Self { + Term::Integer(i) + } +} + +#[cfg(feature = "datalog-macro")] +impl ToAnyParam for i64 { + fn to_any_param(&self) -> AnyParam { + AnyParam::Term((*self).into()) + } +} + +impl TryFrom for i64 { + type Error = error::Token; + fn try_from(value: Term) -> Result { + match value { + Term::Integer(i) => Ok(i), + _ => Err(error::Token::ConversionError(format!( + "expected integer, got {:?}", + value + ))), + } + } +} + +impl From for Term { + fn from(b: bool) -> Self { + Term::Bool(b) + } +} + +#[cfg(feature = "datalog-macro")] +impl ToAnyParam for bool { + fn to_any_param(&self) -> AnyParam { + AnyParam::Term((*self).into()) + } +} + +impl TryFrom for bool { + type Error = error::Token; + fn try_from(value: Term) -> Result { + match value { + Term::Bool(b) => Ok(b), + _ => Err(error::Token::ConversionError(format!( + "expected boolean, got {:?}", + value + ))), + } + } +} + +impl From for Term { + fn from(s: String) -> Self { + Term::Str(s) + } +} + +#[cfg(feature = "datalog-macro")] +impl ToAnyParam for String { + fn to_any_param(&self) -> AnyParam { + AnyParam::Term((self.clone()).into()) + } +} + +impl From<&str> for Term { + fn from(s: &str) -> Self { + Term::Str(s.into()) + } +} + +#[cfg(feature = "datalog-macro")] +impl ToAnyParam for &str { + fn to_any_param(&self) -> AnyParam { + AnyParam::Term(self.to_string().into()) + } +} + +impl TryFrom for String { + type Error = error::Token; + fn try_from(value: Term) -> Result { + match value { + Term::Str(s) => Ok(s), + _ => Err(error::Token::ConversionError(format!( + "expected string or symbol, got {:?}", + value + ))), + } + } +} + +impl From> for Term { + fn from(v: Vec) -> Self { + Term::Bytes(v) + } +} + +#[cfg(feature = "datalog-macro")] +impl ToAnyParam for Vec { + fn to_any_param(&self) -> AnyParam { + AnyParam::Term((self.clone()).into()) + } +} + +impl TryFrom for Vec { + type Error = error::Token; + fn try_from(value: Term) -> Result { + match value { + Term::Bytes(b) => Ok(b), + _ => Err(error::Token::ConversionError(format!( + "expected byte array, got {:?}", + value + ))), + } + } +} + +impl From<&[u8]> for Term { + fn from(v: &[u8]) -> Self { + Term::Bytes(v.into()) + } +} + +#[cfg(feature = "datalog-macro")] +impl ToAnyParam for [u8] { + fn to_any_param(&self) -> AnyParam { + AnyParam::Term(self.into()) + } +} + +#[cfg(feature = "uuid")] +impl ToAnyParam for uuid::Uuid { + fn to_any_param(&self) -> AnyParam { + AnyParam::Term(Term::Bytes(self.as_bytes().to_vec())) + } +} + +impl From for Term { + fn from(t: SystemTime) -> Self { + let dur = t.duration_since(UNIX_EPOCH).unwrap(); + Term::Date(dur.as_secs()) + } +} + +#[cfg(feature = "datalog-macro")] +impl ToAnyParam for SystemTime { + fn to_any_param(&self) -> AnyParam { + AnyParam::Term((*self).into()) + } +} + +impl TryFrom for SystemTime { + type Error = error::Token; + fn try_from(value: Term) -> Result { + match value { + Term::Date(d) => Ok(UNIX_EPOCH + Duration::from_secs(d)), + _ => Err(error::Token::ConversionError(format!( + "expected date, got {:?}", + value + ))), + } + } +} + +impl From> for Term { + fn from(value: BTreeSet) -> Term { + set(value) + } +} + +#[cfg(feature = "datalog-macro")] +impl ToAnyParam for BTreeSet { + fn to_any_param(&self) -> AnyParam { + AnyParam::Term((self.clone()).into()) + } +} + +impl> TryFrom for BTreeSet { + type Error = error::Token; + fn try_from(value: Term) -> Result { + match value { + Term::Set(d) => d.iter().cloned().map(TryFrom::try_from).collect(), + _ => Err(error::Token::ConversionError(format!( + "expected set, got {:?}", + value + ))), + } + } +} + +// TODO: From and ToAnyParam for arrays and maps +impl TryFrom for Term { + type Error = &'static str; + + fn try_from(value: serde_json::Value) -> Result { + match value { + serde_json::Value::Null => Ok(Term::Null), + serde_json::Value::Bool(b) => Ok(Term::Bool(b)), + serde_json::Value::Number(i) => match i.as_i64() { + Some(i) => Ok(Term::Integer(i)), + None => Err("Biscuit values do not support floating point numbers"), + }, + serde_json::Value::String(s) => Ok(Term::Str(s)), + serde_json::Value::Array(array) => Ok(Term::Array( + array + .into_iter() + .map(|v| v.try_into()) + .collect::>()?, + )), + serde_json::Value::Object(o) => Ok(Term::Map( + o.into_iter() + .map(|(key, value)| { + let value: Term = value.try_into()?; + Ok::<_, &'static str>((MapKey::Str(key), value)) + }) + .collect::>()?, + )), + } + } +} + +macro_rules! tuple_try_from( + ($ty1:ident, $ty2:ident, $($ty:ident),*) => ( + tuple_try_from!(__impl $ty1, $ty2; $($ty),*); + ); + (__impl $($ty: ident),+; $ty1:ident, $($ty2:ident),*) => ( + tuple_try_from_impl!($($ty),+); + tuple_try_from!(__impl $($ty),+ , $ty1; $($ty2),*); + ); + (__impl $($ty: ident),+; $ty1:ident) => ( + tuple_try_from_impl!($($ty),+); + tuple_try_from_impl!($($ty),+, $ty1); + ); + ); + +impl> TryFrom for (A,) { + type Error = error::Token; + fn try_from(fact: Fact) -> Result { + let mut terms = fact.predicate.terms; + let mut it = terms.drain(..); + + Ok((it + .next() + .ok_or_else(|| error::Token::ConversionError("not enough terms in fact".to_string())) + .and_then(A::try_from)?,)) + } +} + +macro_rules! tuple_try_from_impl( + ($($ty: ident),+) => ( + impl<$($ty: TryFrom),+> TryFrom for ($($ty),+) { + type Error = error::Token; + fn try_from(fact: Fact) -> Result { + let mut terms = fact.predicate.terms; + let mut it = terms.drain(..); + + Ok(( + $( + it.next().ok_or(error::Token::ConversionError("not enough terms in fact".to_string())).and_then($ty::try_from)? + ),+ + )) + + } + } + ); + ); + +tuple_try_from!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U); diff --git a/biscuit-auth/src/token/builder_ext.rs b/biscuit-auth/src/token/builder_ext.rs index 021cb44a..93ca14d8 100644 --- a/biscuit-auth/src/token/builder_ext.rs +++ b/biscuit-auth/src/token/builder_ext.rs @@ -1,16 +1,16 @@ use std::time::SystemTime; pub trait BuilderExt { - fn add_resource(&mut self, name: &str); - fn check_resource(&mut self, name: &str); - fn check_resource_prefix(&mut self, prefix: &str); - fn check_resource_suffix(&mut self, suffix: &str); - fn add_operation(&mut self, name: &str); - fn check_operation(&mut self, name: &str); - fn check_expiration_date(&mut self, date: SystemTime); + fn resource(self, name: &str) -> Self; + fn check_resource(self, name: &str) -> Self; + fn check_resource_prefix(self, prefix: &str) -> Self; + fn check_resource_suffix(self, suffix: &str) -> Self; + fn operation(self, name: &str) -> Self; + fn check_operation(self, name: &str) -> Self; + fn check_expiration_date(self, date: SystemTime) -> Self; } pub trait AuthorizerExt { - fn add_allow_all(&mut self); - fn add_deny_all(&mut self); + fn allow_all(self) -> Self; + fn deny_all(self) -> Self; } diff --git a/biscuit-auth/src/token/mod.rs b/biscuit-auth/src/token/mod.rs index fe1b089e..367a8db4 100644 --- a/biscuit-auth/src/token/mod.rs +++ b/biscuit-auth/src/token/mod.rs @@ -1,20 +1,20 @@ //! main structures to interact with Biscuit tokens -use std::convert::TryInto; use std::fmt::Display; +use std::iter::once; -use self::public_keys::PublicKeys; - -use super::crypto::{KeyPair, PublicKey}; -use super::datalog::SymbolTable; -use super::error; -use super::format::SerializedBiscuit; use builder::{BiscuitBuilder, BlockBuilder}; use prost::Message; use rand_core::{CryptoRng, RngCore}; +use self::public_keys::PublicKeys; +use super::crypto::{KeyPair, PublicKey, Signature}; +use super::datalog::SymbolTable; +use super::error; +use super::format::SerializedBiscuit; use crate::crypto::{self}; use crate::format::convert::proto_block_to_token_block; use crate::format::schema::{self, ThirdPartyBlockContents}; +use crate::format::{ThirdPartyVerificationMode, THIRD_PARTY_SIGNATURE_VERSION}; use authorizer::Authorizer; pub mod authorizer; @@ -24,14 +24,19 @@ pub mod builder_ext; pub(crate) mod public_keys; pub(crate) mod third_party; pub mod unverified; - pub use block::Block; pub use third_party::*; /// minimum supported version of the serialization format pub const MIN_SCHEMA_VERSION: u32 = 3; /// maximum supported version of the serialization format -pub const MAX_SCHEMA_VERSION: u32 = 5; +pub const MAX_SCHEMA_VERSION: u32 = 6; +/// starting version for datalog 3.1 features (check all, bitwise operators, !=, …) +pub const DATALOG_3_1: u32 = 4; +/// starting version for 3rd party blocks (datalog 3.2) +pub const DATALOG_3_2: u32 = 5; +/// starting version for datalog 3.3 features (reject if, closures, array/map, null, external functions, …) +pub const DATALOG_3_3: u32 = 6; /// some symbols are predefined and available in every implementation, to avoid /// transmitting them with every token @@ -49,25 +54,26 @@ pub fn default_symbol_table() -> SymbolTable { /// /// use biscuit::{KeyPair, Biscuit, builder::*, builder_ext::*}; /// -/// fn main() { +/// fn main() -> Result<(), biscuit::error::Token> { /// let root = KeyPair::new(); /// /// // first we define the authority block for global data, /// // like access rights /// // data from the authority block cannot be created in any other block -/// let mut builder = Biscuit::builder(); -/// builder.add_fact(fact("right", &[string("/a/file1.txt"), string("read")])); +/// let token1 = Biscuit::builder() +/// .fact(fact("right", &[string("/a/file1.txt"), string("read")]))? /// -/// // facts and rules can also be parsed from a string -/// builder.add_fact("right(\"/a/file1.txt\", \"read\")").expect("parse error"); -/// -/// let token1 = builder.build(&root).unwrap(); +/// // facts and rules can also be parsed from a string +/// .fact("right(\"/a/file1.txt\", \"read\")")? +/// .build(&root)?; /// /// // we can create a new block builder from that token -/// let mut builder2 = BlockBuilder::new(); -/// builder2.check_operation("read"); +/// let builder2 = BlockBuilder::new() +/// .check_operation("read"); +/// +/// let token2 = token1.append(builder2)?; /// -/// let token2 = token1.append(builder2).unwrap(); +/// Ok(()) /// } /// ``` #[derive(Clone, Debug)] @@ -105,6 +111,23 @@ impl Biscuit { Biscuit::from_base64_with_symbols(slice, key_provider, default_symbol_table()) } + /// deserializes a token and validates the signature using the root public key + /// + /// This allows the deprecated 3rd party block format + pub fn unsafe_deprecated_deserialize( + slice: T, + key_provider: KP, + ) -> Result + where + T: AsRef<[u8]>, + KP: RootKeyProvider, + { + let container = SerializedBiscuit::unsafe_from_slice(slice.as_ref(), key_provider) + .map_err(error::Token::Format)?; + + Biscuit::from_serialized_container(container, default_symbol_table()) + } + /// serializes the token pub fn to_vec(&self) -> Result, error::Token> { self.container.to_vec().map_err(error::Token::Format) @@ -135,24 +158,17 @@ impl Biscuit { Ok(token) } - /// creates a authorizer from this token + /// creates an authorizer from this token pub fn authorizer(&self) -> Result { Authorizer::from_token(self) } - /// runs authorization with the provided authorizer - pub fn authorize(&self, authorizer: &Authorizer) -> Result { - let mut a = authorizer.clone(); - a.add_token(self)?; - a.authorize() - } - /// adds a new block to the token /// /// since the public key is integrated into the token, the keypair can be /// discarded right after calling this function pub fn append(&self, block_builder: BlockBuilder) -> Result { - let keypair = KeyPair::new_with_rng(&mut rand::rngs::OsRng); + let keypair = KeyPair::new_with_rng(builder::Algorithm::Ed25519, &mut rand::rngs::OsRng); self.append_with_keypair(&keypair, block_builder) } @@ -221,6 +237,11 @@ impl Biscuit { }) } + /// gets the datalog version for a given block + pub fn block_version(&self, index: usize) -> Result { + self.block(index).map(|block| block.version) + } + /// creates a new token, using a provided CSPRNG /// /// the public part of the root keypair must be used for verification @@ -228,6 +249,25 @@ impl Biscuit { rng: &mut T, root_key_id: Option, root: &KeyPair, + symbols: SymbolTable, + authority: Block, + ) -> Result { + Self::new_with_key_pair( + root_key_id, + root, + &KeyPair::new_with_rng(builder::Algorithm::Ed25519, rng), + symbols, + authority, + ) + } + + /// creates a new token, using provided keypairs (the root keypair, and the keypair used to sign the next block) + /// + /// the public part of the root keypair must be used for verification + pub(crate) fn new_with_key_pair( + root_key_id: Option, + root: &KeyPair, + next_keypair: &KeyPair, mut symbols: SymbolTable, authority: Block, ) -> Result { @@ -239,8 +279,7 @@ impl Biscuit { let blocks = vec![]; - let next_keypair = KeyPair::new_with_rng(rng); - let container = SerializedBiscuit::new(root_key_id, root, &next_keypair, &authority)?; + let container = SerializedBiscuit::new(root_key_id, root, next_keypair, &authority)?; symbols.public_keys.extend(&authority.public_keys)?; @@ -368,7 +407,8 @@ impl Biscuit { external_key: PublicKey, response: ThirdPartyBlock, ) -> Result { - let next_keypair = KeyPair::new_with_rng(&mut rand::rngs::OsRng); + let next_keypair = + KeyPair::new_with_rng(builder::Algorithm::Ed25519, &mut rand::rngs::OsRng); self.append_third_party_with_keypair(external_key, response, next_keypair) } @@ -383,37 +423,42 @@ impl Biscuit { external_signature, } = response.0; - if external_signature.public_key.algorithm != schema::public_key::Algorithm::Ed25519 as i32 - { + let provided_key = PublicKey::from_proto(&external_signature.public_key)?; + if external_key != provided_key { return Err(error::Token::Format(error::Format::DeserializationError( format!( - "deserialization error: unexpected key algorithm {}", - external_signature.public_key.algorithm + "deserialization error: unexpected key {}", + provided_key.print() ), ))); } - let bytes: [u8; 64] = (&external_signature.signature[..]) - .try_into() - .map_err(|_| error::Format::InvalidSignatureSize(external_signature.signature.len()))?; - let signature = ed25519_dalek::Signature::from_bytes(&bytes); + let signature = Signature::from_vec(external_signature.signature); + let previous_key = self .container .blocks .last() .unwrap_or(&self.container.authority) .next_key; - let mut to_verify = payload.clone(); - to_verify - .extend(&(crate::format::schema::public_key::Algorithm::Ed25519 as i32).to_le_bytes()); - to_verify.extend(&previous_key.to_bytes()); - - external_key - .0 - .verify_strict(&to_verify, &signature) - .map_err(|s| s.to_string()) - .map_err(error::Signature::InvalidSignature) - .map_err(error::Format::Signature)?; + + let external_signature = crypto::ExternalSignature { + public_key: external_key, + signature, + }; + crypto::verify_external_signature( + &payload, + &previous_key, + &self + .container + .blocks + .last() + .unwrap_or(&self.container.authority) + .signature, + &external_signature, + THIRD_PARTY_SIGNATURE_VERSION, + ThirdPartyVerificationMode::PreviousSignatureHashing, + )?; let block = schema::Block::decode(&payload[..]).map_err(|e| { error::Token::Format(error::Format::DeserializationError(format!( @@ -422,11 +467,6 @@ impl Biscuit { ))) })?; - let external_signature = crypto::ExternalSignature { - public_key: external_key, - signature, - }; - let symbols = self.symbols.clone(); let mut blocks = self.blocks.clone(); @@ -530,6 +570,32 @@ impl Biscuit { Ok(block) } + + pub(crate) fn blocks(&self) -> impl Iterator> + use<'_> { + once( + proto_block_to_token_block( + &self.authority, + self.container + .authority + .external_signature + .as_ref() + .map(|ex| ex.public_key), + ) + .map_err(error::Token::Format), + ) + .chain(self.blocks.iter().zip(self.container.blocks.iter()).map( + |(block, container)| { + proto_block_to_token_block( + block, + container + .external_signature + .as_ref() + .map(|ex| ex.public_key), + ) + .map_err(error::Token::Format) + }, + )) + } } impl Display for Biscuit { @@ -665,23 +731,25 @@ mod tests { use super::*; use crate::builder::CheckKind; use crate::crypto::KeyPair; - use crate::{error::*, AuthorizerLimits}; + use crate::{error::*, AuthorizerLimits, UnverifiedBiscuit}; + use builder::AuthorizerBuilder; + use builder_ext::AuthorizerExt; use rand::prelude::*; use std::time::{Duration, SystemTime}; #[test] fn basic() { let mut rng: StdRng = SeedableRng::seed_from_u64(0); - let root = KeyPair::new_with_rng(&mut rng); + let root = KeyPair::new_with_rng(builder::Algorithm::Ed25519, &mut rng); let serialized1 = { - let mut builder = Biscuit::builder(); - - builder.add_fact("right(\"file1\", \"read\")").unwrap(); - builder.add_fact("right(\"file2\", \"read\")").unwrap(); - builder.add_fact("right(\"file1\", \"write\")").unwrap(); - - let biscuit1 = builder + let biscuit1 = Biscuit::builder() + .fact("right(\"file1\", \"read\")") + .unwrap() + .fact("right(\"file2\", \"read\")") + .unwrap() + .fact("right(\"file1\", \"write\")") + .unwrap() .build_with_rng(&root, default_symbol_table(), &mut rng) .unwrap(); @@ -727,10 +795,8 @@ mod tests { let biscuit1_deser = Biscuit::from(&serialized1, root.public()).unwrap(); // new check: can only have read access1 - let mut block2 = BlockBuilder::new(); - - block2 - .add_check(rule( + let block2 = BlockBuilder::new() + .check(rule( "check1", &[var("resource")], &[ @@ -741,7 +807,7 @@ mod tests { )) .unwrap(); - let keypair2 = KeyPair::new_with_rng(&mut rng); + let keypair2 = KeyPair::new_with_rng(builder::Algorithm::Ed25519, &mut rng); let biscuit2 = biscuit1_deser .append_with_keypair(&keypair2, block2) .unwrap(); @@ -758,17 +824,15 @@ mod tests { let biscuit2_deser = Biscuit::from(&serialized2, root.public()).unwrap(); // new check: can only access file1 - let mut block3 = BlockBuilder::new(); - - block3 - .add_check(rule( + let block3 = BlockBuilder::new() + .check(rule( "check2", &[string("file1")], &[pred("resource", &[string("file1")])], )) .unwrap(); - let keypair3 = KeyPair::new_with_rng(&mut rng); + let keypair3 = KeyPair::new_with_rng(builder::Algorithm::Ed25519, &mut rng); let biscuit3 = biscuit2_deser .append_with_keypair(&keypair3, block3) .unwrap(); @@ -783,7 +847,7 @@ mod tests { let final_token = Biscuit::from(&serialized3, root.public()).unwrap(); println!("final token:\n{}", final_token); { - let mut authorizer = final_token.authorizer().unwrap(); + let mut builder = AuthorizerBuilder::new(); let mut facts = vec![ fact("resource", &[string("file1")]), @@ -791,11 +855,12 @@ mod tests { ]; for fact in facts.drain(..) { - authorizer.add_fact(fact).unwrap(); + builder = builder.fact(fact).unwrap(); } //println!("final token: {:#?}", final_token); - authorizer.allow().unwrap(); + + let mut authorizer = builder.allow_all().build(&final_token).unwrap(); let res = authorizer.authorize(); println!("res1: {:?}", res); @@ -803,7 +868,7 @@ mod tests { } { - let mut authorizer = final_token.authorizer().unwrap(); + let mut builder = AuthorizerBuilder::new(); let mut facts = vec![ fact("resource", &[string("file2")]), @@ -811,10 +876,11 @@ mod tests { ]; for fact in facts.drain(..) { - authorizer.add_fact(fact).unwrap(); + builder = builder.fact(fact).unwrap(); } + builder = builder.allow_all(); - authorizer.allow().unwrap(); + let mut authorizer = builder.build(&final_token).unwrap(); let res = authorizer.authorize_with_limits(AuthorizerLimits { max_time: Duration::from_secs(10), @@ -835,35 +901,36 @@ mod tests { #[test] fn folders() { let mut rng: StdRng = SeedableRng::seed_from_u64(0); - let root = KeyPair::new_with_rng(&mut rng); - - let mut builder = Biscuit::builder(); - - builder.add_right("/folder1/file1", "read"); - builder.add_right("/folder1/file1", "write"); - builder.add_right("/folder1/file2", "read"); - builder.add_right("/folder1/file2", "write"); - builder.add_right("/folder2/file3", "read"); - - let biscuit1 = builder + let root = KeyPair::new_with_rng(builder::Algorithm::Ed25519, &mut rng); + + let biscuit1 = Biscuit::builder() + .right("/folder1/file1", "read") + .right("/folder1/file1", "write") + .right("/folder1/file2", "read") + .right("/folder1/file2", "write") + .right("/folder2/file3", "read") .build_with_rng(&root, default_symbol_table(), &mut rng) .unwrap(); println!("biscuit1 (authority): {}", biscuit1); - let mut block2 = BlockBuilder::new(); - - block2.check_resource_prefix("/folder1/"); - block2.check_right("read"); + let block2 = BlockBuilder::new() + .check_resource_prefix("/folder1/") + .check_right("read") + .unwrap(); - let keypair2 = KeyPair::new_with_rng(&mut rng); + let keypair2 = KeyPair::new_with_rng(builder::Algorithm::Ed25519, &mut rng); let biscuit2 = biscuit1.append_with_keypair(&keypair2, block2).unwrap(); { - let mut authorizer = biscuit2.authorizer().unwrap(); - authorizer.add_fact("resource(\"/folder1/file1\")").unwrap(); - authorizer.add_fact("operation(\"read\")").unwrap(); - authorizer.allow().unwrap(); + let mut authorizer = AuthorizerBuilder::new() + .fact("resource(\"/folder1/file1\")") + .unwrap() + .fact("operation(\"read\")") + .unwrap() + .allow_all() + .build(&biscuit2) + .unwrap(); let res = authorizer.authorize_with_limits(AuthorizerLimits { max_time: Duration::from_secs(10), @@ -875,10 +942,14 @@ mod tests { } { - let mut authorizer = biscuit2.authorizer().unwrap(); - authorizer.add_fact("resource(\"/folder2/file3\")").unwrap(); - authorizer.add_fact("operation(\"read\")").unwrap(); - authorizer.allow().unwrap(); + let mut authorizer = AuthorizerBuilder::new() + .fact("resource(\"/folder2/file3\")") + .unwrap() + .fact("operation(\"read\")") + .unwrap() + .allow_all() + .build(&biscuit2) + .unwrap(); let res = authorizer.authorize_with_limits(AuthorizerLimits { max_time: Duration::from_secs(10), @@ -901,9 +972,13 @@ mod tests { } { - let mut authorizer = biscuit2.authorizer().unwrap(); - authorizer.add_fact("resource(\"/folder2/file1\")").unwrap(); - authorizer.add_fact("operation(\"write\")").unwrap(); + let mut authorizer = AuthorizerBuilder::new() + .fact("resource(\"/folder2/file1\")") + .unwrap() + .fact("operation(\"write\")") + .unwrap() + .build(&biscuit2) + .unwrap(); let res = authorizer.authorize(); println!("res3: {:?}", res); @@ -919,33 +994,34 @@ mod tests { #[test] fn constraints() { let mut rng: StdRng = SeedableRng::seed_from_u64(0); - let root = KeyPair::new_with_rng(&mut rng); - - let mut builder = Biscuit::builder(); + let root = KeyPair::new_with_rng(builder::Algorithm::Ed25519, &mut rng); - builder.add_right("file1", "read"); - builder.add_right("file2", "read"); - - let biscuit1 = builder + let biscuit1 = Biscuit::builder() + .right("file1", "read") + .right("file2", "read") .build_with_rng(&root, default_symbol_table(), &mut rng) .unwrap(); println!("biscuit1 (authority): {}", biscuit1); - let mut block2 = BlockBuilder::new(); - - block2.check_expiration_date(SystemTime::now() + Duration::from_secs(30)); - block2.add_fact("key(1234)").unwrap(); + let block2 = BlockBuilder::new() + .check_expiration_date(SystemTime::now() + Duration::from_secs(30)) + .fact("key(1234)") + .unwrap(); - let keypair2 = KeyPair::new_with_rng(&mut rng); + let keypair2 = KeyPair::new_with_rng(builder::Algorithm::Ed25519, &mut rng); let biscuit2 = biscuit1.append_with_keypair(&keypair2, block2).unwrap(); { - let mut authorizer = biscuit2.authorizer().unwrap(); - authorizer.add_fact("resource(\"file1\")").unwrap(); - authorizer.add_fact("operation(\"read\")").unwrap(); - authorizer.set_time(); - authorizer.allow().unwrap(); + let mut authorizer = AuthorizerBuilder::new() + .fact("resource(\"file1\")") + .unwrap() + .fact("operation(\"read\")") + .unwrap() + .time() + .allow_all() + .build(&biscuit2) + .unwrap(); let res = authorizer.authorize_with_limits(AuthorizerLimits { max_time: Duration::from_secs(10), @@ -957,11 +1033,15 @@ mod tests { { println!("biscuit2: {}", biscuit2); - let mut authorizer = biscuit2.authorizer().unwrap(); - authorizer.add_fact("resource(\"file1\")").unwrap(); - authorizer.add_fact("operation(\"read\")").unwrap(); - authorizer.set_time(); - authorizer.allow().unwrap(); + let mut authorizer = AuthorizerBuilder::new() + .fact("resource(\"file1\")") + .unwrap() + .fact("operation(\"read\")") + .unwrap() + .time() + .allow_all() + .build(&biscuit2) + .unwrap(); let res = authorizer.authorize_with_limits(AuthorizerLimits { max_time: Duration::from_secs(10), @@ -978,36 +1058,37 @@ mod tests { #[test] fn sealed_token() { let mut rng: StdRng = SeedableRng::seed_from_u64(0); - let root = KeyPair::new_with_rng(&mut rng); - let mut builder = Biscuit::builder(); - - builder.add_right("/folder1/file1", "read"); - builder.add_right("/folder1/file1", "write"); - builder.add_right("/folder1/file2", "read"); - builder.add_right("/folder1/file2", "write"); - builder.add_right("/folder2/file3", "read"); - - let biscuit1 = builder + let root = KeyPair::new_with_rng(builder::Algorithm::Ed25519, &mut rng); + let biscuit1 = Biscuit::builder() + .right("/folder1/file1", "read") + .right("/folder1/file1", "write") + .right("/folder1/file2", "read") + .right("/folder1/file2", "write") + .right("/folder2/file3", "read") .build_with_rng(&root, default_symbol_table(), &mut rng) .unwrap(); println!("biscuit1 (authority): {}", biscuit1); - let mut block2 = BlockBuilder::new(); - - block2.check_resource_prefix("/folder1/"); - block2.check_right("read"); + let block2 = BlockBuilder::new() + .check_resource_prefix("/folder1/") + .check_right("read") + .unwrap(); - let keypair2 = KeyPair::new_with_rng(&mut rng); + let keypair2 = KeyPair::new_with_rng(builder::Algorithm::Ed25519, &mut rng); let biscuit2 = biscuit1.append_with_keypair(&keypair2, block2).unwrap(); //println!("biscuit2:\n{:#?}", biscuit2); //panic!(); { - let mut authorizer = biscuit2.authorizer().unwrap(); - authorizer.add_fact("resource(\"/folder1/file1\")").unwrap(); - authorizer.add_fact("operation(\"read\")").unwrap(); - authorizer.allow().unwrap(); + let mut authorizer = AuthorizerBuilder::new() + .fact("resource(\"/folder1/file1\")") + .unwrap() + .fact("operation(\"read\")") + .unwrap() + .allow_all() + .build(&biscuit2) + .unwrap(); let res = authorizer.authorize_with_limits(AuthorizerLimits { max_time: Duration::from_secs(10), @@ -1026,10 +1107,14 @@ mod tests { let biscuit3 = Biscuit::from(sealed, root.public()).unwrap(); { - let mut authorizer = biscuit3.authorizer().unwrap(); - authorizer.add_fact("resource(\"/folder1/file1\")").unwrap(); - authorizer.add_fact("operation(\"read\")").unwrap(); - authorizer.allow().unwrap(); + let mut authorizer = AuthorizerBuilder::new() + .fact("resource(\"/folder1/file1\")") + .unwrap() + .fact("operation(\"read\")") + .unwrap() + .allow_all() + .build(&biscuit3) + .unwrap(); let res = authorizer.authorize(); println!("res1: {:?}", res); @@ -1042,36 +1127,31 @@ mod tests { use crate::token::builder::*; let mut rng: StdRng = SeedableRng::seed_from_u64(1234); - let root = KeyPair::new_with_rng(&mut rng); - - let mut builder = Biscuit::builder(); - - builder - .add_fact(fact("right", &[string("file1"), string("read")])) - .unwrap(); - builder - .add_fact(fact("right", &[string("file2"), string("read")])) - .unwrap(); - builder - .add_fact(fact("right", &[string("file1"), string("write")])) - .unwrap(); - - let biscuit1 = builder + let root = KeyPair::new_with_rng(builder::Algorithm::Ed25519, &mut rng); + + let biscuit1 = Biscuit::builder() + .fact(fact("right", &[string("file1"), string("read")])) + .unwrap() + .fact(fact("right", &[string("file2"), string("read")])) + .unwrap() + .fact(fact("right", &[string("file1"), string("write")])) + .unwrap() .build_with_rng(&root, default_symbol_table(), &mut rng) .unwrap(); println!("{}", biscuit1); - let mut v = biscuit1.authorizer().expect("omg authorizer"); - - v.add_check(rule( - "right", - &[string("right")], - &[pred("right", &[string("file2"), string("write")])], - )) - .unwrap(); + let mut authorizer = AuthorizerBuilder::new() + .check(rule( + "right", + &[string("right")], + &[pred("right", &[string("file2"), string("write")])], + )) + .unwrap() + .build(&biscuit1) + .unwrap(); //assert!(v.verify().is_err()); - let res = v.authorize_with_limits(AuthorizerLimits { + let res = authorizer.authorize_with_limits(AuthorizerLimits { max_time: Duration::from_secs(10), ..Default::default() }); @@ -1090,42 +1170,44 @@ mod tests { #[test] fn authorizer_queries() { let mut rng: StdRng = SeedableRng::seed_from_u64(0); - let root = KeyPair::new_with_rng(&mut rng); - - let mut builder = Biscuit::builder(); - - builder.add_right("file1", "read"); - builder.add_right("file2", "read"); - builder.add_fact("key(0000)").unwrap(); + let root = KeyPair::new_with_rng(builder::Algorithm::Ed25519, &mut rng); - let biscuit1 = builder + let biscuit1 = Biscuit::builder() + .right("file1", "read") + .right("file2", "read") + .fact("key(0000)") + .unwrap() .build_with_rng(&root, default_symbol_table(), &mut rng) .unwrap(); println!("biscuit1 (authority): {}", biscuit1); - let mut block2 = BlockBuilder::new(); - - block2.check_expiration_date(SystemTime::now() + Duration::from_secs(30)); - block2.add_fact("key(1234)").unwrap(); + let block2 = BlockBuilder::new() + .check_expiration_date(SystemTime::now() + Duration::from_secs(30)) + .fact("key(1234)") + .unwrap(); - let keypair2 = KeyPair::new_with_rng(&mut rng); + let keypair2 = KeyPair::new_with_rng(builder::Algorithm::Ed25519, &mut rng); let biscuit2 = biscuit1.append_with_keypair(&keypair2, block2).unwrap(); - let mut block3 = BlockBuilder::new(); - - block3.check_expiration_date(SystemTime::now() + Duration::from_secs(10)); - block3.add_fact("key(5678)").unwrap(); + let block3 = BlockBuilder::new() + .check_expiration_date(SystemTime::now() + Duration::from_secs(10)) + .fact("key(5678)") + .unwrap(); - let keypair3 = KeyPair::new_with_rng(&mut rng); + let keypair3 = KeyPair::new_with_rng(builder::Algorithm::Ed25519, &mut rng); let biscuit3 = biscuit2.append_with_keypair(&keypair3, block3).unwrap(); { println!("biscuit3: {}", biscuit3); - let mut authorizer = biscuit3.authorizer().unwrap(); - authorizer.add_fact("resource(\"file1\")").unwrap(); - authorizer.add_fact("operation(\"read\")").unwrap(); - authorizer.set_time(); + let mut authorizer = AuthorizerBuilder::new() + .fact("resource(\"file1\")") + .unwrap() + .fact("operation(\"read\")") + .unwrap() + .time() + .build(&biscuit3) + .unwrap(); // test that cloning correctly embeds the first block's facts let mut other_authorizer = authorizer.clone(); @@ -1178,39 +1260,39 @@ mod tests { #[test] fn check_head_name() { let mut rng: StdRng = SeedableRng::seed_from_u64(0); - let root = KeyPair::new_with_rng(&mut rng); + let root = KeyPair::new_with_rng(builder::Algorithm::Ed25519, &mut rng); - let mut builder = Biscuit::builder(); - - builder - .add_check(check( + let biscuit1 = Biscuit::builder() + .check(check( &[pred("resource", &[string("hello")])], CheckKind::One, )) - .unwrap(); - - let biscuit1 = builder + .unwrap() .build_with_rng(&root, default_symbol_table(), &mut rng) .unwrap(); println!("biscuit1 (authority): {}", biscuit1); // new check: can only have read access1 - let mut block2 = BlockBuilder::new(); - block2.add_fact(fact("check1", &[string("test")])).unwrap(); + let block2 = BlockBuilder::new() + .fact(fact("check1", &[string("test")])) + .unwrap(); - let keypair2 = KeyPair::new_with_rng(&mut rng); + let keypair2 = KeyPair::new_with_rng(builder::Algorithm::Ed25519, &mut rng); let biscuit2 = biscuit1.append_with_keypair(&keypair2, block2).unwrap(); println!("biscuit2: {}", biscuit2); //println!("generated biscuit token 2: {} bytes\n{}", serialized2.len(), serialized2.to_hex(16)); { - let mut authorizer = biscuit2.authorizer().unwrap(); - authorizer.add_fact("resource(\"file1\")").unwrap(); - authorizer.add_fact("operation(\"read\")").unwrap(); - println!("symbols before time: {:?}", authorizer.symbols); - authorizer.set_time(); + let mut authorizer = AuthorizerBuilder::new() + .fact("resource(\"file1\")") + .unwrap() + .fact("operation(\"read\")") + .unwrap() + .time() + .build(&biscuit2) + .unwrap(); println!("world:\n{}", authorizer.print_world()); println!("symbols: {:?}", authorizer.symbols); @@ -1282,28 +1364,32 @@ mod tests { #[test] fn bytes_constraints() { let mut rng: StdRng = SeedableRng::seed_from_u64(0); - let root = KeyPair::new_with_rng(&mut rng); + let root = KeyPair::new_with_rng(builder::Algorithm::Ed25519, &mut rng); - let mut builder = Biscuit::builder(); - builder.add_fact("bytes(hex:0102AB)").unwrap(); - let biscuit1 = builder + let biscuit1 = Biscuit::builder() + .fact("bytes(hex:0102AB)") + .unwrap() .build_with_rng(&root, default_symbol_table(), &mut rng) .unwrap(); println!("biscuit1 (authority): {}", biscuit1); - let mut block2 = BlockBuilder::new(); - block2 - .add_rule("has_bytes($0) <- bytes($0), [ hex:00000000, hex:0102AB ].contains($0)") + let block2 = BlockBuilder::new() + .rule("has_bytes($0) <- bytes($0), { hex:00000000, hex:0102AB }.contains($0)") .unwrap(); - let keypair2 = KeyPair::new_with_rng(&mut rng); + let keypair2 = KeyPair::new_with_rng(builder::Algorithm::Ed25519, &mut rng); let biscuit2 = biscuit1.append_with_keypair(&keypair2, block2).unwrap(); - let mut authorizer = biscuit2.authorizer().unwrap(); - authorizer - .add_check("check if bytes($0), [ hex:00000000, hex:0102AB ].contains($0)") + let mut authorizer = AuthorizerBuilder::new() + .check("check if bytes($0), { hex:00000000, hex:0102AB }.contains($0)") + .unwrap() + .allow_all() + .limits(AuthorizerLimits { + max_time: Duration::from_secs(10), + ..Default::default() + }) + .build(&biscuit2) .unwrap(); - authorizer.allow().unwrap(); let res = authorizer.authorize_with_limits(AuthorizerLimits { max_time: Duration::from_secs(10), @@ -1328,23 +1414,18 @@ mod tests { #[test] fn block1_generates_authority_or_ambient() { let mut rng: StdRng = SeedableRng::seed_from_u64(0); - let root = KeyPair::new_with_rng(&mut rng); + let root = KeyPair::new_with_rng(builder::Algorithm::Ed25519, &mut rng); let serialized1 = { - let mut builder = Biscuit::builder(); - - builder - .add_fact("right(\"/folder1/file1\", \"read\")") - .unwrap(); - builder - .add_fact("right(\"/folder1/file1\", \"write\")") - .unwrap(); - builder - .add_fact("right(\"/folder2/file1\", \"read\")") - .unwrap(); - builder.add_check("check if operation(\"read\")").unwrap(); - - let biscuit1 = builder + let biscuit1 = Biscuit::builder() + .fact("right(\"/folder1/file1\", \"read\")") + .unwrap() + .fact("right(\"/folder1/file1\", \"write\")") + .unwrap() + .fact("right(\"/folder2/file1\", \"read\")") + .unwrap() + .check("check if operation(\"read\")") + .unwrap() .build_with_rng(&root, default_symbol_table(), &mut rng) .unwrap(); @@ -1361,23 +1442,21 @@ mod tests { let biscuit1_deser = Biscuit::from(&serialized1, |_| Ok(root.public())).unwrap(); // new check: can only have read access1 - let mut block2 = BlockBuilder::new(); + let block2 = BlockBuilder::new() // Bypass `check if operation("read")` from authority block - block2 - .add_rule("operation(\"read\") <- operation($any)") - .unwrap(); + .rule("operation(\"read\") <- operation($any)") + .unwrap() // Bypass `check if resource($file), $file.starts_with("/folder1/")` from block #1 - block2 - .add_rule("resource(\"/folder1/\") <- resource($any)") - .unwrap(); + .rule("resource(\"/folder1/\") <- resource($any)") + .unwrap() // Add missing rights - block2.add_rule("right($file, $right) <- right($any1, $any2), resource($file), operation($right)") + .rule("right($file, $right) <- right($any1, $any2), resource($file), operation($right)") .unwrap(); - let keypair2 = KeyPair::new_with_rng(&mut rng); + let keypair2 = KeyPair::new_with_rng(builder::Algorithm::Ed25519, &mut rng); let biscuit2 = biscuit1_deser .append_with_keypair(&keypair2, block2) .unwrap(); @@ -1393,13 +1472,16 @@ mod tests { let final_token = Biscuit::from(&serialized2, root.public()).unwrap(); println!("final token:\n{}", final_token); - let mut authorizer = final_token.authorizer().unwrap(); - authorizer.add_fact("resource(\"/folder2/file1\")").unwrap(); - authorizer.add_fact("operation(\"write\")").unwrap(); - authorizer - .add_policy("allow if resource($file), operation($op), right($file, $op)") + let mut authorizer = AuthorizerBuilder::new() + .fact("resource(\"/folder2/file1\")") + .unwrap() + .fact("operation(\"write\")") + .unwrap() + .policy("allow if resource($file), operation($op), right($file, $op)") + .unwrap() + .deny_all() + .build(&final_token) .unwrap(); - authorizer.deny().unwrap(); let res = authorizer.authorize_with_limits(crate::token::authorizer::AuthorizerLimits { max_time: Duration::from_secs(1), @@ -1414,35 +1496,34 @@ mod tests { #[test] fn check_all() { let mut rng: StdRng = SeedableRng::seed_from_u64(0); - let root = KeyPair::new_with_rng(&mut rng); + let root = KeyPair::new_with_rng(builder::Algorithm::Ed25519, &mut rng); - let mut builder = Biscuit::builder(); - - builder.add_check("check if fact($v), $v < 1").unwrap(); - - let biscuit1 = builder + let biscuit1 = Biscuit::builder() + .check("check if fact($v), $v < 1") + .unwrap() .build_with_rng(&root, default_symbol_table(), &mut rng) .unwrap(); println!("biscuit1 (authority): {}", biscuit1); - let mut builder = Biscuit::builder(); - - builder.add_check("check all fact($v), $v < 1").unwrap(); - - let biscuit2 = builder + let biscuit2 = Biscuit::builder() + .check("check all fact($v), $v < 1") + .unwrap() .build_with_rng(&root, default_symbol_table(), &mut rng) .unwrap(); println!("biscuit2 (authority): {}", biscuit2); { - let mut authorizer = biscuit1.authorizer().unwrap(); - authorizer.add_fact("fact(0)").unwrap(); - authorizer.add_fact("fact(1)").unwrap(); - - //println!("final token: {:#?}", final_token); - authorizer.allow().unwrap(); + let mut authorizer = AuthorizerBuilder::new() + .fact("fact(0)") + .unwrap() + .fact("fact(1)") + .unwrap() + //println!("final token: {:#?}", final_token); + .allow_all() + .build(&biscuit1) + .unwrap(); let res = authorizer.authorize_with_limits(AuthorizerLimits { max_time: Duration::from_secs(10), @@ -1453,12 +1534,15 @@ mod tests { } { - let mut authorizer = biscuit2.authorizer().unwrap(); - authorizer.add_fact("fact(0)").unwrap(); - authorizer.add_fact("fact(1)").unwrap(); - - //println!("final token: {:#?}", final_token); - authorizer.allow().unwrap(); + let mut authorizer = AuthorizerBuilder::new() + .fact("fact(0)") + .unwrap() + .fact("fact(1)") + .unwrap() + //println!("final token: {:#?}", final_token); + .allow_all() + .build(&biscuit2) + .unwrap(); let res = authorizer.authorize_with_limits(AuthorizerLimits { max_time: Duration::from_secs(10), @@ -1479,4 +1563,83 @@ mod tests { ); } } + + // check that we can still allow the verification of the old 3rd party block signature + #[test] + fn third_party_unsafe_deserialize() { + // this is a token generated with the old third party signature, that does not include the previous block's signature + let token_bytes = include_bytes!("../../tests/fixtures/unsafe_third_party.bc"); + let _ = UnverifiedBiscuit::unsafe_deprecated_deserialize(token_bytes).unwrap(); + assert_eq!( + UnverifiedBiscuit::from(token_bytes).unwrap_err(), + error::Token::Format(error::Format::DeserializationError( + "Unsupported third party block version".to_string() + )) + ); + + let root_key = PublicKey::from_bytes_hex( + "1055c750b1a1505937af1537c626ba3263995c33a64758aaafb1275b0312e284", + builder::Algorithm::Ed25519, + ) + .unwrap(); + let _ = Biscuit::unsafe_deprecated_deserialize(token_bytes, root_key).unwrap(); + assert_eq!( + Biscuit::from(token_bytes, root_key).unwrap_err(), + error::Token::Format(error::Format::DeserializationError( + "Unsupported third party block version".to_string() + )) + ); + } + + // tests that the authority block signature version 1 works + #[test] + fn authority_signature_v1() { + let mut rng: StdRng = SeedableRng::seed_from_u64(0); + let root = KeyPair::new_with_rng(builder::Algorithm::Ed25519, &mut rng); + + let authority_block = Block { + symbols: default_symbol_table(), + facts: vec![], + rules: vec![], + checks: vec![], + context: None, + version: 0, + external_key: None, + public_keys: PublicKeys::new(), + scopes: vec![], + }; + + let next_keypair = KeyPair::new_with_rng(builder::Algorithm::Ed25519, &mut rng); + let token = + SerializedBiscuit::new_inner(None, &root, &next_keypair, &authority_block, 1).unwrap(); + let serialized = token.to_vec().unwrap(); + + let _ = Biscuit::from(&serialized, root.public()).unwrap(); + } + + #[test] + fn verified_unverified_consistency() { + let mut rng: StdRng = SeedableRng::seed_from_u64(0); + let root = KeyPair::new_with_rng(builder::Algorithm::Ed25519, &mut rng); + let biscuit1 = Biscuit::builder() + .fact("right(\"file1\", \"read\")") + .unwrap() + .fact("right(\"file2\", \"read\")") + .unwrap() + .fact("right(\"file1\", \"write\")") + .unwrap() + .build_with_rng(&root, default_symbol_table(), &mut rng) + .unwrap(); + + println!("biscuit1 (authority): {}", biscuit1); + + let serialized = biscuit1.to_vec().unwrap(); + + let parsed = UnverifiedBiscuit::from(serialized).unwrap(); + + for i in 0..parsed.block_count() { + assert_eq!(parsed.print_block_source(i), biscuit1.print_block_source(i)); + assert_eq!(parsed.block_version(i), biscuit1.block_version(i)); + } + } } diff --git a/biscuit-auth/src/token/third_party.rs b/biscuit-auth/src/token/third_party.rs index c6e300bf..64b43d22 100644 --- a/biscuit-auth/src/token/third_party.rs +++ b/biscuit-auth/src/token/third_party.rs @@ -1,19 +1,22 @@ -use ed25519_dalek::Signer; +use std::cmp::max; + use prost::Message; use crate::{ builder::BlockBuilder, - crypto::PublicKey, + crypto::generate_external_signature_payload_v1, datalog::SymbolTable, error, format::{convert::token_block_to_proto_block, schema, SerializedBiscuit}, KeyPair, PrivateKey, }; +use super::THIRD_PARTY_SIGNATURE_VERSION; + /// Third party block request -#[derive(Debug)] +#[derive(PartialEq, Debug)] pub struct ThirdPartyRequest { - pub(crate) previous_key: PublicKey, + pub(crate) previous_signature: Vec, } impl ThirdPartyRequest { @@ -24,21 +27,23 @@ impl ThirdPartyRequest { return Err(error::Token::AppendOnSealed); } - let previous_key = container + let previous_signature = container .blocks .last() .unwrap_or(&container.authority) - .next_key; - - Ok(ThirdPartyRequest { previous_key }) + .signature + .to_bytes() + .to_vec(); + Ok(ThirdPartyRequest { previous_signature }) } pub fn serialize(&self) -> Result, error::Token> { - let previous_key = self.previous_key.to_proto(); + let previous_signature = self.previous_signature.clone(); let request = schema::ThirdPartyBlockRequest { - previous_key, - public_keys: Vec::new(), + legacy_previous_key: None, + legacy_public_keys: Vec::new(), + previous_signature, }; let mut v = Vec::new(); @@ -59,15 +64,21 @@ impl ThirdPartyRequest { error::Format::DeserializationError(format!("deserialization error: {:?}", e)) })?; - let previous_key = PublicKey::from_proto(&data.previous_key)?; - - if !data.public_keys.is_empty() { + if !data.legacy_public_keys.is_empty() { return Err(error::Token::Format(error::Format::DeserializationError( "public keys were provided in third-party block request".to_owned(), ))); } - Ok(ThirdPartyRequest { previous_key }) + if data.legacy_previous_key.is_some() { + return Err(error::Token::Format(error::Format::DeserializationError( + "previous public key was provided in third-party block request".to_owned(), + ))); + } + + let previous_signature = data.previous_signature.to_vec(); + + Ok(ThirdPartyRequest { previous_signature }) } pub fn deserialize_base64(slice: T) -> Result @@ -86,26 +97,23 @@ impl ThirdPartyRequest { ) -> Result { let symbols = SymbolTable::new(); let mut block = block_builder.build(symbols); - block.version = super::MAX_SCHEMA_VERSION; + block.version = max(super::DATALOG_3_2, block.version); - let mut v = Vec::new(); + let mut payload = Vec::new(); token_block_to_proto_block(&block) - .encode(&mut v) + .encode(&mut payload) .map_err(|e| { error::Format::SerializationError(format!("serialization error: {:?}", e)) })?; - let payload = v.clone(); - v.extend(&(crate::format::schema::public_key::Algorithm::Ed25519 as i32).to_le_bytes()); - v.extend(self.previous_key.to_bytes()); + let signed_payload = generate_external_signature_payload_v1( + &payload, + &self.previous_signature, + THIRD_PARTY_SIGNATURE_VERSION, + ); let keypair = KeyPair::from(private_key); - let signature = keypair - .kp - .try_sign(&v) - .map_err(|s| s.to_string()) - .map_err(error::Signature::InvalidSignatureGeneration) - .map_err(error::Format::Signature)?; + let signature = keypair.sign(&signed_payload)?; let public_key = keypair.public(); let content = schema::ThirdPartyBlockContents { @@ -142,3 +150,28 @@ impl ThirdPartyBlock { Ok(base64::encode_config(self.serialize()?, base64::URL_SAFE)) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn third_party_request_roundtrip() { + let mut rng: rand::rngs::StdRng = rand::SeedableRng::seed_from_u64(0); + let root = KeyPair::new_with_rng(crate::builder::Algorithm::Ed25519, &mut rng); + let biscuit1 = crate::Biscuit::builder() + .fact("right(\"file1\", \"read\")") + .unwrap() + .fact("right(\"file2\", \"read\")") + .unwrap() + .fact("right(\"file1\", \"write\")") + .unwrap() + .build_with_rng(&root, crate::token::default_symbol_table(), &mut rng) + .unwrap(); + let req = biscuit1.third_party_request().unwrap(); + let serialized_req = req.serialize().unwrap(); + let parsed_req = ThirdPartyRequest::deserialize(&serialized_req).unwrap(); + + assert_eq!(req, parsed_req); + } +} diff --git a/biscuit-auth/src/token/unverified.rs b/biscuit-auth/src/token/unverified.rs index 28500b3f..20d07bae 100644 --- a/biscuit-auth/src/token/unverified.rs +++ b/biscuit-auth/src/token/unverified.rs @@ -1,17 +1,19 @@ -use std::convert::TryInto; +use prost::Message; use super::{default_symbol_table, Biscuit, Block}; use crate::{ builder::BlockBuilder, - crypto, - crypto::PublicKey, + crypto::{self, PublicKey, Signature}, datalog::SymbolTable, error, - format::{convert::proto_block_to_token_block, schema, SerializedBiscuit}, + format::{ + convert::proto_block_to_token_block, + schema::{self, public_key::Algorithm}, + SerializedBiscuit, + }, token::{ThirdPartyBlockContents, ThirdPartyRequest}, KeyPair, RootKeyProvider, }; -use prost::Message; /// A token that was parsed without cryptographic signature verification /// @@ -37,6 +39,29 @@ impl UnverifiedBiscuit { Self::from_with_symbols(slice.as_ref(), default_symbol_table()) } + /// deserializes a token from raw bytes + /// + /// This allows the deprecated 3rd party block format + pub fn unsafe_deprecated_deserialize(slice: T) -> Result + where + T: AsRef<[u8]>, + { + let container = SerializedBiscuit::deserialize( + slice.as_ref(), + crate::format::ThirdPartyVerificationMode::UnsafeLegacy, + )?; + let mut symbols = default_symbol_table(); + + let (authority, blocks) = container.extract_blocks(&mut symbols)?; + + Ok(UnverifiedBiscuit { + authority, + blocks, + symbols, + container, + }) + } + /// deserializes a token from base64 pub fn from_base64(slice: T) -> Result where @@ -76,7 +101,8 @@ impl UnverifiedBiscuit { /// since the public key is integrated into the token, the keypair can be /// discarded right after calling this function pub fn append(&self, block_builder: BlockBuilder) -> Result { - let keypair = KeyPair::new_with_rng(&mut rand::rngs::OsRng); + let keypair = + KeyPair::new_with_rng(super::builder::Algorithm::Ed25519, &mut rand::rngs::OsRng); self.append_with_keypair(&keypair, block_builder) } @@ -95,7 +121,10 @@ impl UnverifiedBiscuit { /// deserializes from raw bytes with a custom symbol table pub fn from_with_symbols(slice: &[u8], mut symbols: SymbolTable) -> Result { - let container = SerializedBiscuit::deserialize(slice)?; + let container = SerializedBiscuit::deserialize( + slice, + crate::format::ThirdPartyVerificationMode::PreviousSignatureHashing, + )?; let (authority, blocks) = container.extract_blocks(&mut symbols)?; @@ -219,6 +248,11 @@ impl UnverifiedBiscuit { }) } + /// gets the datalog version for a given block + pub fn block_version(&self, index: usize) -> Result { + self.block(index).map(|block| block.version) + } + pub(crate) fn block(&self, index: usize) -> Result { let mut block = if index == 0 { proto_block_to_token_block( @@ -268,8 +302,16 @@ impl UnverifiedBiscuit { } pub fn append_third_party(&self, slice: &[u8]) -> Result { - let next_keypair = KeyPair::new_with_rng(&mut rand::rngs::OsRng); + let next_keypair = + KeyPair::new_with_rng(super::builder::Algorithm::Ed25519, &mut rand::rngs::OsRng); + self.append_third_party_with_keypair(slice, next_keypair) + } + pub fn append_third_party_with_keypair( + &self, + slice: &[u8], + next_keypair: KeyPair, + ) -> Result { let ThirdPartyBlockContents { payload, external_signature, @@ -277,38 +319,23 @@ impl UnverifiedBiscuit { error::Format::DeserializationError(format!("deserialization error: {:?}", e)) })?; - if external_signature.public_key.algorithm != schema::public_key::Algorithm::Ed25519 as i32 - { - return Err(error::Token::Format(error::Format::DeserializationError( - format!( - "deserialization error: unexpected key algorithm {}", - external_signature.public_key.algorithm - ), - ))); - } - let external_key = - PublicKey::from_bytes(&external_signature.public_key.key).map_err(|e| { - error::Format::BlockSignatureDeserializationError(format!( - "block external public key deserialization error: {:?}", - e - )) + let algorithm = + Algorithm::from_i32(external_signature.public_key.algorithm).ok_or_else(|| { + error::Format::DeserializationError( + "deserialization error: invalid external key algorithm".to_string(), + ) })?; - - let bytes: [u8; 64] = (&external_signature.signature[..]) - .try_into() - .map_err(|_| error::Format::InvalidSignatureSize(external_signature.signature.len()))?; - - let signature = ed25519_dalek::Signature::from_bytes(&bytes); - let previous_key = self - .container - .blocks - .last() - .unwrap_or(&self.container.authority) - .next_key; - let mut to_verify = payload.clone(); - to_verify - .extend(&(crate::format::schema::public_key::Algorithm::Ed25519 as i32).to_le_bytes()); - to_verify.extend(&previous_key.to_bytes()); + let external_key = + PublicKey::from_bytes(&external_signature.public_key.key, algorithm.into()).map_err( + |e| { + error::Format::BlockSignatureDeserializationError(format!( + "block external public key deserialization error: {:?}", + e + )) + }, + )?; + + let signature = Signature::from_vec(external_signature.signature); let block = schema::Block::decode(&payload[..]).map_err(|e| { error::Token::Format(error::Format::DeserializationError(format!( diff --git a/biscuit-auth/tests/fixtures/unsafe_third_party.bc b/biscuit-auth/tests/fixtures/unsafe_third_party.bc new file mode 100644 index 00000000..7bca415c Binary files /dev/null and b/biscuit-auth/tests/fixtures/unsafe_third_party.bc differ diff --git a/biscuit-auth/tests/macros.rs b/biscuit-auth/tests/macros.rs index 49244655..bb5498af 100644 --- a/biscuit-auth/tests/macros.rs +++ b/biscuit-auth/tests/macros.rs @@ -1,33 +1,47 @@ -use biscuit_auth::builder; +use biscuit_auth::{builder, datalog::RunLimits, KeyPair}; use biscuit_quote::{ authorizer, authorizer_merge, biscuit, biscuit_merge, block, block_merge, check, fact, policy, rule, }; -use std::collections::BTreeSet; +use serde_json::json; +use std::{collections::BTreeSet, convert::TryInto, time::Duration}; #[test] fn block_macro() { let mut term_set = BTreeSet::new(); term_set.insert(builder::int(0i64)); let my_key = "my_value"; + let array_param = 2; + let mapkey = "hello"; + let mut b = block!( - r#"fact("test", hex:aabbcc, [true], {my_key}, {term_set}); - rule($0, true) <- fact($0, $1, $2, {my_key}); + r#"fact("test", hex:aabbcc, [1, {array_param}], {my_key}, {term_set}, {"a": 1, 2 : "abcd", {mapkey}: 0 }); + rule($0, true) <- fact($0, $1, $2, {my_key}), true || false; check if {my_key}.starts_with("my"); + check if {true,false}.any($p -> true); "#, ); let is_true = true; - block_merge!(&mut b, r#"appended({is_true});"#); + b = block_merge!(b, r#"appended({is_true});"#); assert_eq!( b.to_string(), - r#"fact("test", hex:aabbcc, [true], "my_value", [0]); + r#"fact("test", hex:aabbcc, [1, 2], "my_value", {0}, {2: "abcd", "a": 1, "hello": 0}); appended(true); -rule($0, true) <- fact($0, $1, $2, "my_value"); +rule($0, true) <- fact($0, $1, $2, "my_value"), true || false; check if "my_value".starts_with("my"); +check if {false, true}.any($p -> true); "#, ); + + let b = block!(r#"check if "test".extern::toto() && "test".extern::test("test");"#); + + assert_eq!( + b.to_string(), + r#"check if "test".extern::toto() && "test".extern::test("test"); +"# + ); } #[test] @@ -54,17 +68,26 @@ fn authorizer_macro() { ); let is_true = true; - authorizer_merge!( - &mut b, + b = authorizer_merge!( + b, r#"appended({is_true}); allow if true; "# ); + let mut authorizer = b + .limits(RunLimits { + max_time: Duration::from_secs(10), + ..Default::default() + }) + .build_unauthenticated() + .unwrap(); + authorizer.run().unwrap(); assert_eq!( - b.dump_code(), - r#"fact("test", hex:aabbcc, [true], "my_value"); -appended(true); + authorizer.dump_code(), + r#"appended(true); +fact("test", hex:aabbcc, [true], "my_value"); +rule("test", true); rule($0, true) <- fact($0, $1, $2, "my_value"); @@ -78,7 +101,13 @@ allow if true; #[test] fn authorizer_macro_trailing_comma() { - let a = authorizer!(r#"fact("test", {my_key});"#, my_key = "my_value",); + let a = authorizer!(r#"fact("test", {my_key});"#, my_key = "my_value",) + .limits(RunLimits { + max_time: Duration::from_secs(10), + ..Default::default() + }) + .build_unauthenticated() + .unwrap(); assert_eq!( a.dump_code(), r#"fact("test", "my_value"); @@ -92,6 +121,7 @@ fn biscuit_macro() { use biscuit_auth::PublicKey; let pubkey = PublicKey::from_bytes( &hex::decode("6e9e6d5a75cf0c0e87ec1256b4dfed0ca3ba452912d213fcc70f8516583db9db").unwrap(), + biscuit_auth::builder::Algorithm::Ed25519, ) .unwrap(); @@ -104,12 +134,11 @@ fn biscuit_macro() { check if true trusting ed25519/6e9e6d5a75cf0c0e87ec1256b4dfed0ca3ba452912d213fcc70f8516583db9db; "#, my_key_bytes = s.into_bytes(), - ); - b.set_root_key_id(2); + ).root_key_id(2); let is_true = true; - biscuit_merge!( - &mut b, + b = biscuit_merge!( + b, r#"appended({is_true}); check if true; "# @@ -154,6 +183,7 @@ fn rule_macro() { use biscuit_auth::PublicKey; let pubkey = PublicKey::from_bytes( &hex::decode("6e9e6d5a75cf0c0e87ec1256b4dfed0ca3ba452912d213fcc70f8516583db9db").unwrap(), + biscuit_auth::builder::Algorithm::Ed25519, ) .unwrap(); let mut term_set = BTreeSet::new(); @@ -165,7 +195,7 @@ fn rule_macro() { assert_eq!( r.to_string(), - r#"rule($0, true) <- fact($0, $1, $2, "my_value", [0]) trusting ed25519/6e9e6d5a75cf0c0e87ec1256b4dfed0ca3ba452912d213fcc70f8516583db9db"#, + r#"rule($0, true) <- fact($0, $1, $2, "my_value", {0}) trusting ed25519/6e9e6d5a75cf0c0e87ec1256b4dfed0ca3ba452912d213fcc70f8516583db9db"#, ); } @@ -175,7 +205,7 @@ fn fact_macro() { term_set.insert(builder::int(0i64)); let f = fact!(r#"fact({my_key}, {term_set})"#, my_key = "my_value",); - assert_eq!(f.to_string(), r#"fact("my_value", [0])"#,); + assert_eq!(f.to_string(), r#"fact("my_value", {0})"#,); } #[test] @@ -183,6 +213,7 @@ fn check_macro() { use biscuit_auth::PublicKey; let pubkey = PublicKey::from_bytes( &hex::decode("6e9e6d5a75cf0c0e87ec1256b4dfed0ca3ba452912d213fcc70f8516583db9db").unwrap(), + biscuit_auth::builder::Algorithm::Ed25519, ) .unwrap(); let mut term_set = BTreeSet::new(); @@ -194,7 +225,7 @@ fn check_macro() { assert_eq!( c.to_string(), - r#"check if fact("my_value", [0]) trusting ed25519/6e9e6d5a75cf0c0e87ec1256b4dfed0ca3ba452912d213fcc70f8516583db9db"#, + r#"check if fact("my_value", {0}) trusting ed25519/6e9e6d5a75cf0c0e87ec1256b4dfed0ca3ba452912d213fcc70f8516583db9db"#, ); } @@ -203,6 +234,7 @@ fn policy_macro() { use biscuit_auth::PublicKey; let pubkey = PublicKey::from_bytes( &hex::decode("6e9e6d5a75cf0c0e87ec1256b4dfed0ca3ba452912d213fcc70f8516583db9db").unwrap(), + biscuit_auth::builder::Algorithm::Ed25519, ) .unwrap(); let mut term_set = BTreeSet::new(); @@ -214,6 +246,67 @@ fn policy_macro() { assert_eq!( p.to_string(), - r#"allow if fact("my_value", [0]) trusting ed25519/6e9e6d5a75cf0c0e87ec1256b4dfed0ca3ba452912d213fcc70f8516583db9db"#, + r#"allow if fact("my_value", {0}) trusting ed25519/6e9e6d5a75cf0c0e87ec1256b4dfed0ca3ba452912d213fcc70f8516583db9db"#, + ); +} + +#[test] +fn json() { + let key_pair = KeyPair::new(); + let biscuit = biscuit!(r#"user(123)"#).build(&key_pair).unwrap(); + + let value: serde_json::Value = json!( + { + "id": 123, + "roles": ["admin"] + } + ); + let json_value: biscuit_auth::builder::Term = value.try_into().unwrap(); + + let mut authorizer = authorizer!( + r#" + user_roles({json_value}); + allow if + user($id), + user_roles($value), + $value.get("id") == $id, + $value.get("roles").contains("admin");"# + ) + .limits(RunLimits { + max_time: Duration::from_secs(10), + ..Default::default() + }) + .build(&biscuit) + .unwrap(); + assert_eq!( + authorizer + .authorize_with_limits(RunLimits { + max_time: Duration::from_secs(1), + ..Default::default() + }) + .unwrap(), + 0 + ); +} + +#[test] +fn ecdsa() { + use biscuit_auth::PublicKey; + + let pubkey = PublicKey::from_bytes( + &hex::decode("0245dd01132962da3812911b746b080aed714873c1812e7cefacf13e3880712da0").unwrap(), + biscuit_auth::builder::Algorithm::Secp256r1, + ) + .unwrap(); + let mut term_set = BTreeSet::new(); + term_set.insert(builder::int(0i64)); + let r = rule!( + r#"rule($0, true) <- fact($0, $1, $2, {my_key}, {term_set}) trusting {pubkey}"#, + my_key = "my_value", + ); + + assert_eq!( + r.to_string(), + r#"rule($0, true) <- fact($0, $1, $2, "my_value", {0}) trusting secp256r1/0245dd01132962da3812911b746b080aed714873c1812e7cefacf13e3880712da0"#, ); } diff --git a/biscuit-auth/tests/rights.rs b/biscuit-auth/tests/rights.rs index 39950811..a470ee64 100644 --- a/biscuit-auth/tests/rights.rs +++ b/biscuit-auth/tests/rights.rs @@ -9,42 +9,44 @@ use rand::{prelude::StdRng, SeedableRng}; fn main() { let mut rng: StdRng = SeedableRng::seed_from_u64(1234); - let root = KeyPair::new_with_rng(&mut rng); + let root = KeyPair::new_with_rng(builder::Algorithm::Ed25519, &mut rng); - let mut builder = Biscuit::builder(); - - builder.add_fact(fact( - "right", - &[string("authority"), string("file1"), string("read")], - )); - builder.add_fact(fact( - "right", - &[string("authority"), string("file2"), string("read")], - )); - builder.add_fact(fact( - "right", - &[string("authority"), string("file1"), string("write")], - )); - - let biscuit1 = builder + let biscuit1 = Biscuit::builder() + .fact(fact( + "right", + &[string("authority"), string("file1"), string("read")], + )) + .unwrap() + .fact(fact( + "right", + &[string("authority"), string("file2"), string("read")], + )) + .unwrap() + .fact(fact( + "right", + &[string("authority"), string("file1"), string("write")], + )) + .unwrap() .build_with_rng(&root, SymbolTable::default(), &mut rng) .unwrap(); println!("{}", biscuit1); - let mut v = biscuit1.authorizer().expect("omg verifier"); + let mut v = AuthorizerBuilder::new() + .check(rule( + "right", + &[string("right")], + &[pred( + "right", + &[string("authority"), string("file2"), string("write")], + )], + )) + .unwrap() + .build(&biscuit1) + .unwrap(); //v.add_resource("file2"); //v.add_operation("read"); //v.add_operation("write"); - v.add_check(rule( - "right", - &[string("right")], - &[pred( - "right", - &[string("authority"), string("file2"), string("write")], - )], - )); - let res = v.authorize(); println!("{:#?}", res); panic!() diff --git a/biscuit-capi/src/lib.rs b/biscuit-capi/src/lib.rs index 23a0b290..2900c26c 100644 --- a/biscuit-capi/src/lib.rs +++ b/biscuit-capi/src/lib.rs @@ -292,14 +292,22 @@ pub extern "C" fn error_check_is_authorizer(check_index: u64) -> bool { pub struct Biscuit(biscuit_auth::Biscuit); pub struct KeyPair(biscuit_auth::KeyPair); pub struct PublicKey(biscuit_auth::PublicKey); -pub struct BiscuitBuilder(biscuit_auth::builder::BiscuitBuilder); -pub struct BlockBuilder(biscuit_auth::builder::BlockBuilder); +pub struct BiscuitBuilder(Option); +pub struct BlockBuilder(Option); pub struct Authorizer(biscuit_auth::Authorizer); +pub struct AuthorizerBuilder(Option); + +#[repr(C)] +pub enum SignatureAlgorithm { + Ed25519, + Secp256r1, +} #[no_mangle] pub unsafe extern "C" fn key_pair_new<'a>( seed_ptr: *const u8, seed_len: usize, + algorithm: SignatureAlgorithm, ) -> Option> { let slice = std::slice::from_raw_parts(seed_ptr, seed_len); if slice.len() != 32 { @@ -311,9 +319,13 @@ pub unsafe extern "C" fn key_pair_new<'a>( seed.copy_from_slice(slice); let mut rng: StdRng = SeedableRng::from_seed(seed); + let algorithm = match algorithm { + SignatureAlgorithm::Ed25519 => biscuit_auth::builder::Algorithm::Ed25519, + SignatureAlgorithm::Secp256r1 => biscuit_auth::builder::Algorithm::Secp256r1, + }; Some(Box::new(KeyPair(biscuit_auth::KeyPair::new_with_rng( - &mut rng, + algorithm, &mut rng, )))) } @@ -344,10 +356,20 @@ pub unsafe extern "C" fn key_pair_serialize(kp: Option<&KeyPair>, buffer_ptr: *m /// expects a 32 byte buffer #[no_mangle] -pub unsafe extern "C" fn key_pair_deserialize(buffer_ptr: *mut u8) -> Option> { +pub unsafe extern "C" fn key_pair_deserialize( + buffer_ptr: *mut u8, + algorithm: SignatureAlgorithm, +) -> Option> { let input_slice = std::slice::from_raw_parts_mut(buffer_ptr, 32); - match biscuit_auth::PrivateKey::from_bytes(input_slice).ok() { + let algorithm = match algorithm { + SignatureAlgorithm::Ed25519 => biscuit_auth::format::schema::public_key::Algorithm::Ed25519, + SignatureAlgorithm::Secp256r1 => { + biscuit_auth::format::schema::public_key::Algorithm::Secp256r1 + } + }; + + match biscuit_auth::PrivateKey::from_bytes(input_slice, algorithm.into()).ok() { None => { update_last_error(Error::InvalidArgument); None @@ -379,10 +401,17 @@ pub unsafe extern "C" fn public_key_serialize( /// expects a 32 byte buffer #[no_mangle] -pub unsafe extern "C" fn public_key_deserialize(buffer_ptr: *mut u8) -> Option> { +pub unsafe extern "C" fn public_key_deserialize( + buffer_ptr: *mut u8, + algorithm: SignatureAlgorithm, +) -> Option> { let input_slice = std::slice::from_raw_parts_mut(buffer_ptr, 32); + let algorithm = match algorithm { + SignatureAlgorithm::Ed25519 => biscuit_auth::builder::Algorithm::Ed25519, + SignatureAlgorithm::Secp256r1 => biscuit_auth::builder::Algorithm::Secp256r1, + }; - match biscuit_auth::PublicKey::from_bytes(input_slice).ok() { + match biscuit_auth::PublicKey::from_bytes(input_slice, algorithm).ok() { None => { update_last_error(Error::InvalidArgument); None @@ -394,9 +423,45 @@ pub unsafe extern "C" fn public_key_deserialize(buffer_ptr: *mut u8) -> Option>) {} +impl BiscuitBuilder { + fn set_context(&mut self, context: &str) { + let mut inner = self.0.take().unwrap(); + inner = inner.context(context.to_string()); + self.0 = Some(inner); + } + + fn set_root_key_id(&mut self, root_key_id: u32) { + let mut inner = self.0.take().unwrap(); + inner = inner.root_key_id(root_key_id); + self.0 = Some(inner); + } + + fn add_fact(&mut self, fact: &str) -> Result<(), biscuit_auth::error::Token> { + let mut inner = self.0.take().unwrap(); + inner = inner.fact(fact)?; + self.0 = Some(inner); + Ok(()) + } + + fn add_rule(&mut self, rule: &str) -> Result<(), biscuit_auth::error::Token> { + let mut inner = self.0.take().unwrap(); + inner = inner.rule(rule)?; + self.0 = Some(inner); + Ok(()) + } + + fn add_check(&mut self, check: &str) -> Result<(), biscuit_auth::error::Token> { + let mut inner = self.0.take().unwrap(); + inner = inner.check(check)?; + self.0 = Some(inner); + Ok(()) + } +} #[no_mangle] pub unsafe extern "C" fn biscuit_builder() -> Option> { - Some(Box::new(BiscuitBuilder(biscuit_auth::Biscuit::builder()))) + Some(Box::new(BiscuitBuilder(Some( + biscuit_auth::Biscuit::builder(), + )))) } #[no_mangle] @@ -418,7 +483,7 @@ pub unsafe extern "C" fn biscuit_builder_set_context( false } Ok(context) => { - builder.0.set_context(context.to_string()); + builder.set_context(context); true } } @@ -435,7 +500,7 @@ pub unsafe extern "C" fn biscuit_builder_set_root_key_id( } let builder = builder.unwrap(); - builder.0.set_root_key_id(root_key_id); + builder.set_root_key_id(root_key_id); true } @@ -458,7 +523,6 @@ pub unsafe extern "C" fn biscuit_builder_add_fact( } builder - .0 .add_fact(s.unwrap()) .map_err(|e| { update_last_error(Error::Biscuit(e)); @@ -485,7 +549,6 @@ pub unsafe extern "C" fn biscuit_builder_add_rule( } builder - .0 .add_rule(s.unwrap()) .map_err(|e| { update_last_error(Error::Biscuit(e)); @@ -512,14 +575,15 @@ pub unsafe extern "C" fn biscuit_builder_add_check( } builder - .0 .add_check(s.unwrap()) .map_err(|e| { update_last_error(Error::Biscuit(e)); }) .is_ok() } - +/// Build a biscuit token from a builder +/// +/// The builder will be freed automatically when the biscuit is returned #[no_mangle] pub unsafe extern "C" fn biscuit_builder_build( builder: Option<&BiscuitBuilder>, @@ -549,6 +613,7 @@ pub unsafe extern "C" fn biscuit_builder_build( (*builder) .0 .clone() + .expect("builder is none") .build_with_rng(&key_pair.0, SymbolTable::default(), &mut rng) .map(Biscuit) .map(Box::new) @@ -734,9 +799,40 @@ pub unsafe extern "C" fn biscuit_block_context( } } +impl BlockBuilder { + fn set_context(&mut self, context: &str) { + let mut inner = self.0.take().unwrap(); + inner = inner.context(context.to_string()); + self.0 = Some(inner); + } + + fn add_fact(&mut self, fact: &str) -> Result<(), biscuit_auth::error::Token> { + let mut inner = self.0.take().unwrap(); + inner = inner.fact(fact)?; + self.0 = Some(inner); + Ok(()) + } + + fn add_rule(&mut self, rule: &str) -> Result<(), biscuit_auth::error::Token> { + let mut inner = self.0.take().unwrap(); + inner = inner.rule(rule)?; + self.0 = Some(inner); + Ok(()) + } + + fn add_check(&mut self, check: &str) -> Result<(), biscuit_auth::error::Token> { + let mut inner = self.0.take().unwrap(); + inner = inner.check(check)?; + self.0 = Some(inner); + Ok(()) + } +} + #[no_mangle] pub unsafe extern "C" fn create_block() -> Box { - Box::new(BlockBuilder(biscuit_auth::builder::BlockBuilder::new())) + Box::new(BlockBuilder(Some( + biscuit_auth::builder::BlockBuilder::new(), + ))) } #[no_mangle] @@ -762,7 +858,7 @@ pub unsafe extern "C" fn biscuit_append_block( match biscuit .0 - .append_with_keypair(&key_pair.0, builder.0.clone()) + .append_with_keypair(&key_pair.0, builder.0.clone().expect("builder is none")) { Ok(token) => Some(Box::new(Biscuit(token))), Err(e) => { @@ -806,7 +902,7 @@ pub unsafe extern "C" fn block_builder_set_context( false } Ok(context) => { - builder.0.set_context(context.to_string()); + builder.set_context(context); true } } @@ -831,7 +927,6 @@ pub unsafe extern "C" fn block_builder_add_fact( } builder - .0 .add_fact(s.unwrap()) .map_err(|e| { update_last_error(Error::Biscuit(e)); @@ -858,7 +953,6 @@ pub unsafe extern "C" fn block_builder_add_rule( } builder - .0 .add_rule(s.unwrap()) .map_err(|e| { update_last_error(Error::Biscuit(e)); @@ -885,7 +979,6 @@ pub unsafe extern "C" fn block_builder_add_check( } builder - .0 .add_check(s.unwrap()) .map_err(|e| { update_last_error(Error::Biscuit(e)); @@ -896,16 +989,53 @@ pub unsafe extern "C" fn block_builder_add_check( #[no_mangle] pub unsafe extern "C" fn block_builder_free(_builder: Option>) {} +impl AuthorizerBuilder { + fn add_fact(&mut self, fact: &str) -> Result<(), biscuit_auth::error::Token> { + let mut inner = self.0.take().unwrap(); + inner = inner.fact(fact)?; + self.0 = Some(inner); + Ok(()) + } + + fn add_rule(&mut self, rule: &str) -> Result<(), biscuit_auth::error::Token> { + let mut inner = self.0.take().unwrap(); + inner = inner.rule(rule)?; + self.0 = Some(inner); + Ok(()) + } + + fn add_check(&mut self, check: &str) -> Result<(), biscuit_auth::error::Token> { + let mut inner = self.0.take().unwrap(); + inner = inner.check(check)?; + self.0 = Some(inner); + Ok(()) + } + + fn add_policy(&mut self, policy: &str) -> Result<(), biscuit_auth::error::Token> { + let mut inner = self.0.take().unwrap(); + inner = inner.policy(policy)?; + self.0 = Some(inner); + Ok(()) + } +} + +#[no_mangle] +pub unsafe extern "C" fn authorizer_builder() -> Option> { + Some(Box::new(AuthorizerBuilder(Some( + biscuit_auth::builder::AuthorizerBuilder::new(), + )))) +} + #[no_mangle] -pub unsafe extern "C" fn authorizer_add_fact( - authorizer: Option<&mut Authorizer>, +pub unsafe extern "C" fn authorizer_builder_add_fact( + builder: Option<&mut AuthorizerBuilder>, fact: *const c_char, ) -> bool { - if authorizer.is_none() { + if builder.is_none() { update_last_error(Error::InvalidArgument); return false; } - let authorizer = authorizer.unwrap(); + let builder = builder.unwrap(); let fact = CStr::from_ptr(fact); let s = fact.to_str(); @@ -914,8 +1044,7 @@ pub unsafe extern "C" fn authorizer_add_fact( return false; } - authorizer - .0 + builder .add_fact(s.unwrap()) .map_err(|e| { update_last_error(Error::Biscuit(e)); @@ -924,15 +1053,15 @@ pub unsafe extern "C" fn authorizer_add_fact( } #[no_mangle] -pub unsafe extern "C" fn authorizer_add_rule( - authorizer: Option<&mut Authorizer>, +pub unsafe extern "C" fn authorizer_builder_add_rule( + builder: Option<&mut AuthorizerBuilder>, rule: *const c_char, ) -> bool { - if authorizer.is_none() { + if builder.is_none() { update_last_error(Error::InvalidArgument); return false; } - let authorizer = authorizer.unwrap(); + let builder = builder.unwrap(); let rule = CStr::from_ptr(rule); let s = rule.to_str(); @@ -941,8 +1070,7 @@ pub unsafe extern "C" fn authorizer_add_rule( return false; } - authorizer - .0 + builder .add_rule(s.unwrap()) .map_err(|e| { update_last_error(Error::Biscuit(e)); @@ -951,15 +1079,15 @@ pub unsafe extern "C" fn authorizer_add_rule( } #[no_mangle] -pub unsafe extern "C" fn authorizer_add_check( - authorizer: Option<&mut Authorizer>, +pub unsafe extern "C" fn authorizer_builder_add_check( + builder: Option<&mut AuthorizerBuilder>, check: *const c_char, ) -> bool { - if authorizer.is_none() { + if builder.is_none() { update_last_error(Error::InvalidArgument); return false; } - let authorizer = authorizer.unwrap(); + let builder = builder.unwrap(); let check = CStr::from_ptr(check); let s = check.to_str(); @@ -968,8 +1096,7 @@ pub unsafe extern "C" fn authorizer_add_check( return false; } - authorizer - .0 + builder .add_check(s.unwrap()) .map_err(|e| { update_last_error(Error::Biscuit(e)); @@ -978,15 +1105,15 @@ pub unsafe extern "C" fn authorizer_add_check( } #[no_mangle] -pub unsafe extern "C" fn authorizer_add_policy( - authorizer: Option<&mut Authorizer>, +pub unsafe extern "C" fn authorizer_builder_add_policy( + builder: Option<&mut AuthorizerBuilder>, policy: *const c_char, ) -> bool { - if authorizer.is_none() { + if builder.is_none() { update_last_error(Error::InvalidArgument); return false; } - let authorizer = authorizer.unwrap(); + let builder = builder.unwrap(); let policy = CStr::from_ptr(policy); let s = policy.to_str(); @@ -995,8 +1122,7 @@ pub unsafe extern "C" fn authorizer_add_policy( return false; } - authorizer - .0 + builder .add_policy(s.unwrap()) .map_err(|e| { update_last_error(Error::Biscuit(e)); @@ -1004,6 +1130,54 @@ pub unsafe extern "C" fn authorizer_add_policy( .is_ok() } +/// Build an authorizer +/// +/// The builder will be freed automatically when the authorizer is returned +#[no_mangle] +pub unsafe extern "C" fn authorizer_builder_build( + builder: Option>, + token: &Biscuit, +) -> Option> { + if builder.is_none() { + update_last_error(Error::InvalidArgument); + } + let builder = builder.unwrap(); + builder + .0 + .clone() + .take() + .unwrap() + .build(&token.0) + .map(Authorizer) + .map(Box::new) + .ok() +} + +/// Build an authorizer without a token +/// +/// The builder will be freed automatically when the authorizer is returned +#[no_mangle] +pub unsafe extern "C" fn authorizer_builder_build_unauthenticated( + builder: Option>, +) -> Option> { + if builder.is_none() { + update_last_error(Error::InvalidArgument); + } + let builder = builder.unwrap(); + builder + .0 + .clone() + .take() + .unwrap() + .build_unauthenticated() + .map(Authorizer) + .map(Box::new) + .ok() +} + +#[no_mangle] +pub unsafe extern "C" fn authorizer_builder_free(_builder: Option>) {} + #[no_mangle] pub unsafe extern "C" fn authorizer_authorize(authorizer: Option<&mut Authorizer>) -> bool { if authorizer.is_none() { diff --git a/biscuit-capi/tests/capi.rs b/biscuit-capi/tests/capi.rs index af7f615a..ec1a387e 100644 --- a/biscuit-capi/tests/capi.rs +++ b/biscuit-capi/tests/capi.rs @@ -14,7 +14,7 @@ mod capi { int main() { char *seed = "abcdefghabcdefghabcdefghabcdefgh"; - KeyPair * root_kp = key_pair_new((const uint8_t *) seed, strlen(seed)); + KeyPair * root_kp = key_pair_new((const uint8_t *) seed, strlen(seed), 0); printf("key_pair creation error? %s\n", error_message()); PublicKey* root = key_pair_public(root_kp); @@ -34,18 +34,22 @@ mod capi { char *seed2 = "ijklmnopijklmnopijklmnopijklmnop"; - KeyPair * kp2 = key_pair_new((const uint8_t *) seed2, strlen(seed2)); + KeyPair * kp2 = key_pair_new((const uint8_t *) seed2, strlen(seed2), 0); Biscuit* b2 = biscuit_append_block(biscuit, bb, kp2); printf("biscuit append error? %s\n", error_message()); - Authorizer * authorizer = biscuit_authorizer(b2); - printf("authorizer creation error? %s\n", error_message()); - authorizer_add_check(authorizer, "check if right(\"efgh\")"); - printf("authorizer add check error? %s\n", error_message()); + AuthorizerBuilder * ab = authorizer_builder(); + printf("authorizer builder creation error? %s\n", error_message()); + + authorizer_builder_add_check(ab, "check if right(\"efgh\")"); + printf("authorizer builder add check error? %s\n", error_message()); - authorizer_add_policy(authorizer, "allow if true"); - printf("authorizer add policy error? %s\n", error_message()); + authorizer_builder_add_policy(ab, "allow if true"); + printf("authorizer builder add policy error? %s\n", error_message()); + + Authorizer * authorizer = authorizer_builder_build(ab, b2); + printf("authorizer creation error? %s\n", error_message()); if(!authorizer_authorize(authorizer)) { printf("authorizer error(code = %d): %s\n", error_kind(), error_message()); @@ -111,10 +115,11 @@ builder add authority error? (null) biscuit creation error? (null) builder add check error? (null) biscuit append error? (null) +authorizer builder creation error? (null) +authorizer builder add check error? (null) +authorizer builder add policy error? (null) authorizer creation error? (null) -authorizer add check error? (null) -authorizer add policy error? (null) -authorizer error(code = 21): authorization failed +authorizer error(code = 21): authorization failed: an allow policy matched (policy index: 0), and the following checks failed: Check n°0 in authorizer: check if right("efgh"), Check n°0 in block n°1: check if operation("read") failed checks (2): Authorizer check 0: check if right("efgh") Block 1, check 0: check if operation("read") @@ -157,7 +162,7 @@ biscuit block 0 context: (null) uint8_t * pub_buf = malloc(32); - KeyPair * kp = key_pair_new((const uint8_t *) seed, strlen(seed)); + KeyPair * kp = key_pair_new((const uint8_t *) seed, strlen(seed), 0); printf("key_pair creation error? %s\n", error_message()); PublicKey* pubkey = key_pair_public(kp); diff --git a/biscuit-parser/src/builder.rs b/biscuit-parser/src/builder.rs index 1065b4c5..27c9de07 100644 --- a/biscuit-parser/src/builder.rs +++ b/biscuit-parser/src/builder.rs @@ -1,6 +1,6 @@ //! helper functions and structure to create tokens and blocks use std::{ - collections::{BTreeSet, HashMap, HashSet}, + collections::{BTreeMap, BTreeSet, HashMap, HashSet}, time::{SystemTime, UNIX_EPOCH}, }; @@ -18,6 +18,45 @@ pub enum Term { Bool(bool), Set(BTreeSet), Parameter(String), + Null, + Array(Vec), + Map(BTreeMap), +} + +impl Term { + fn extract_parameters(&self, parameters: &mut HashMap>) { + match self { + Term::Parameter(name) => { + parameters.insert(name.to_string(), None); + } + Term::Set(s) => { + for term in s { + term.extract_parameters(parameters); + } + } + Term::Array(a) => { + for term in a { + term.extract_parameters(parameters); + } + } + Term::Map(m) => { + for (key, term) in m { + if let MapKey::Parameter(name) = key { + parameters.insert(name.to_string(), None); + } + term.extract_parameters(parameters); + } + } + _ => {} + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum MapKey { + Parameter(String), + Integer(i64), + Str(String), } impl From<&Term> for Term { @@ -31,6 +70,9 @@ impl From<&Term> for Term { Term::Bool(b) => Term::Bool(*b), Term::Set(ref s) => Term::Set(s.clone()), Term::Parameter(ref p) => Term::Parameter(p.clone()), + Term::Null => Term::Null, + Term::Array(ref a) => Term::Array(a.clone()), + Term::Map(ref m) => Term::Map(m.clone()), } } } @@ -55,13 +97,51 @@ impl ToTokens for Term { Term::Set(v) => { quote! {{ use std::iter::FromIterator; - ::biscuit_auth::builder::Term::Set(::std::collections::BTreeSet::from_iter(<[::biscuit_auth::builder::Term]>::into_vec(Box::new([ #(#v),*])))) + ::biscuit_auth::builder::Term::Set(::std::collections::BTreeSet::from_iter(<[::biscuit_auth::builder::Term]>::into_vec(Box::new([ #(#v),*])))) + }} + } + Term::Null => quote! { ::biscuit_auth::builder::Term::Null }, + Term::Array(v) => { + quote! {{ + use std::iter::FromIterator; + ::biscuit_auth::builder::Term::Array(::std::vec::Vec::from_iter(<[::biscuit_auth::builder::Term]>::into_vec( Box::new([ #(#v),*])))) + }} + } + Term::Map(m) => { + let it = m.iter().map(|(key, term)| MapEntry {key, term }); + quote! {{ + use std::iter::FromIterator; + ::biscuit_auth::builder::Term::Map(::std::collections::BTreeMap::from_iter(<[(::biscuit_auth::builder::MapKey,::biscuit_auth::builder::Term)]>::into_vec(Box::new([ #(#it),*])))) }} } }) } } +#[cfg(feature = "datalog-macro")] +struct MapEntry<'a> { + key: &'a MapKey, + term: &'a Term, +} + +#[cfg(feature = "datalog-macro")] +impl<'a> ToTokens for MapEntry<'a> { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let term = self.term; + tokens.extend(match self.key { + MapKey::Parameter(p) => { + quote! { (::biscuit_auth::builder::MapKey::Parameter(#p.to_string()) , #term )} + } + MapKey::Integer(i) => { + quote! { (::biscuit_auth::builder::MapKey::Integer(#i) , #term )} + } + MapKey::Str(s) => { + quote! { (::biscuit_auth::builder::MapKey::Str(#s.to_string()) , #term )} + } + }); + } +} + #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub enum Scope { Authority, @@ -77,10 +157,15 @@ impl ToTokens for Scope { Scope::Authority => quote! { ::biscuit_auth::builder::Scope::Authority}, Scope::Previous => quote! { ::biscuit_auth::builder::Scope::Previous}, Scope::PublicKey(pk) => { - let bytes = pk.iter(); - quote! { ::biscuit_auth::builder::Scope::PublicKey( - ::biscuit_auth::PublicKey::from_bytes(&[#(#bytes),*]).unwrap() - )} + let bytes = pk.key.iter(); + match pk.algorithm { + Algorithm::Ed25519 => quote! { ::biscuit_auth::builder::Scope::PublicKey( + ::biscuit_auth::PublicKey::from_bytes(&[#(#bytes),*], ::biscuit_auth::builder::Algorithm::Ed25519).unwrap() + )}, + Algorithm::Secp256r1 => quote! { ::biscuit_auth::builder::Scope::PublicKey( + ::biscuit_auth::PublicKey::from_bytes(&[#(#bytes),*], ::biscuit_auth::builder::Algorithm::Secp256r1).unwrap() + )}, + } } Scope::Parameter(v) => { quote! { ::biscuit_auth::builder::Scope::Parameter(#v.to_string())} @@ -132,9 +217,7 @@ impl Fact { let terms: Vec = terms.into(); for term in &terms { - if let Term::Parameter(name) = &term { - parameters.insert(name.to_string(), None); - } + term.extract_parameters(&mut parameters); } Fact { predicate: Predicate::new(name, terms), @@ -181,6 +264,23 @@ pub enum Op { Value(Term), Unary(Unary), Binary(Binary), + Closure(Vec, Vec), +} + +impl Op { + fn collect_parameters(&self, parameters: &mut HashMap>) { + match self { + Op::Value(term) => { + term.extract_parameters(parameters); + } + Op::Closure(_, ops) => { + for op in ops { + op.collect_parameters(parameters); + } + } + _ => {} + } + } } #[derive(Debug, Clone, PartialEq, Eq)] @@ -188,6 +288,8 @@ pub enum Unary { Negate, Parens, Length, + TypeOf, + Ffi(String), } #[derive(Debug, Clone, PartialEq, Eq)] @@ -213,6 +315,14 @@ pub enum Binary { BitwiseOr, BitwiseXor, NotEqual, + HeterogeneousEqual, + HeterogeneousNotEqual, + LazyAnd, + LazyOr, + All, + Any, + Get, + Ffi(String), } #[cfg(feature = "datalog-macro")] @@ -222,6 +332,12 @@ impl ToTokens for Op { Op::Value(t) => quote! { ::biscuit_auth::builder::Op::Value(#t) }, Op::Unary(u) => quote! { ::biscuit_auth::builder::Op::Unary(#u) }, Op::Binary(b) => quote! { ::biscuit_auth::builder::Op::Binary(#b) }, + Op::Closure(params, os) => quote! { + ::biscuit_auth::builder::Op::Closure( + <[String]>::into_vec(Box::new([#(#params.to_string()),*])), + <[::biscuit_auth::builder::Op]>::into_vec(Box::new([#(#os),*])) + ) + }, }); } } @@ -230,9 +346,11 @@ impl ToTokens for Op { impl ToTokens for Unary { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { tokens.extend(match self { - Unary::Negate => quote! {::biscuit_auth::datalog::Unary::Negate }, - Unary::Parens => quote! {::biscuit_auth::datalog::Unary::Parens }, - Unary::Length => quote! {::biscuit_auth::datalog::Unary::Length }, + Unary::Negate => quote! {::biscuit_auth::builder::Unary::Negate }, + Unary::Parens => quote! {::biscuit_auth::builder::Unary::Parens }, + Unary::Length => quote! {::biscuit_auth::builder::Unary::Length }, + Unary::TypeOf => quote! {::biscuit_auth::builder::Unary::TypeOf }, + Unary::Ffi(name) => quote! {::biscuit_auth::builder::Unary::Ffi(#name.to_string()) }, }); } } @@ -241,32 +359,54 @@ impl ToTokens for Unary { impl ToTokens for Binary { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { tokens.extend(match self { - Binary::LessThan => quote! { ::biscuit_auth::datalog::Binary::LessThan }, - Binary::GreaterThan => quote! { ::biscuit_auth::datalog::Binary::GreaterThan }, - Binary::LessOrEqual => quote! { ::biscuit_auth::datalog::Binary::LessOrEqual }, - Binary::GreaterOrEqual => quote! { ::biscuit_auth::datalog::Binary::GreaterOrEqual }, - Binary::Equal => quote! { ::biscuit_auth::datalog::Binary::Equal }, - Binary::Contains => quote! { ::biscuit_auth::datalog::Binary::Contains }, - Binary::Prefix => quote! { ::biscuit_auth::datalog::Binary::Prefix }, - Binary::Suffix => quote! { ::biscuit_auth::datalog::Binary::Suffix }, - Binary::Regex => quote! { ::biscuit_auth::datalog::Binary::Regex }, - Binary::Add => quote! { ::biscuit_auth::datalog::Binary::Add }, - Binary::Sub => quote! { ::biscuit_auth::datalog::Binary::Sub }, - Binary::Mul => quote! { ::biscuit_auth::datalog::Binary::Mul }, - Binary::Div => quote! { ::biscuit_auth::datalog::Binary::Div }, - Binary::And => quote! { ::biscuit_auth::datalog::Binary::And }, - Binary::Or => quote! { ::biscuit_auth::datalog::Binary::Or }, - Binary::Intersection => quote! { ::biscuit_auth::datalog::Binary::Intersection }, - Binary::Union => quote! { ::biscuit_auth::datalog::Binary::Union }, - Binary::BitwiseAnd => quote! { ::biscuit_auth::datalog::Binary::BitwiseAnd }, - 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::LessThan => quote! { ::biscuit_auth::builder::Binary::LessThan }, + Binary::GreaterThan => quote! { ::biscuit_auth::builder::Binary::GreaterThan }, + Binary::LessOrEqual => quote! { ::biscuit_auth::builder::Binary::LessOrEqual }, + Binary::GreaterOrEqual => quote! { ::biscuit_auth::builder::Binary::GreaterOrEqual }, + Binary::Equal => quote! { ::biscuit_auth::builder::Binary::Equal }, + Binary::Contains => quote! { ::biscuit_auth::builder::Binary::Contains }, + Binary::Prefix => quote! { ::biscuit_auth::builder::Binary::Prefix }, + Binary::Suffix => quote! { ::biscuit_auth::builder::Binary::Suffix }, + Binary::Regex => quote! { ::biscuit_auth::builder::Binary::Regex }, + Binary::Add => quote! { ::biscuit_auth::builder::Binary::Add }, + Binary::Sub => quote! { ::biscuit_auth::builder::Binary::Sub }, + Binary::Mul => quote! { ::biscuit_auth::builder::Binary::Mul }, + Binary::Div => quote! { ::biscuit_auth::builder::Binary::Div }, + Binary::And => quote! { ::biscuit_auth::builder::Binary::And }, + Binary::Or => quote! { ::biscuit_auth::builder::Binary::Or }, + Binary::Intersection => quote! { ::biscuit_auth::builder::Binary::Intersection }, + Binary::Union => quote! { ::biscuit_auth::builder::Binary::Union }, + Binary::BitwiseAnd => quote! { ::biscuit_auth::builder::Binary::BitwiseAnd }, + Binary::BitwiseOr => quote! { ::biscuit_auth::builder::Binary::BitwiseOr }, + Binary::BitwiseXor => quote! { ::biscuit_auth::builder::Binary::BitwiseXor }, + Binary::NotEqual => quote! { ::biscuit_auth::builder::Binary::NotEqual }, + Binary::HeterogeneousEqual => { + quote! { ::biscuit_auth::builder::Binary::HeterogeneousEqual} + } + Binary::HeterogeneousNotEqual => { + quote! { ::biscuit_auth::builder::Binary::HeterogeneousNotEqual} + } + Binary::LazyAnd => quote! { ::biscuit_auth::builder::Binary::LazyAnd }, + Binary::LazyOr => quote! { ::biscuit_auth::builder::Binary::LazyOr }, + Binary::All => quote! { ::biscuit_auth::builder::Binary::All }, + Binary::Any => quote! { ::biscuit_auth::builder::Binary::Any }, + Binary::Get => quote! { ::biscuit_auth::builder::Binary::Get }, + Binary::Ffi(name) => quote! {::biscuit_auth::builder::Binary::Ffi(#name.to_string()) }, }); } } -pub type PublicKey = Vec; +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub struct PublicKey { + pub key: Vec, + pub algorithm: Algorithm, +} + +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub enum Algorithm { + Ed25519, + Secp256r1, +} /// Builder for a Datalog rule #[derive(Debug, Clone, PartialEq, Eq)] @@ -290,24 +430,18 @@ impl Rule { let mut scope_parameters = HashMap::new(); for term in &head.terms { - if let Term::Parameter(name) = &term { - parameters.insert(name.to_string(), None); - } + term.extract_parameters(&mut parameters); } for predicate in &body { for term in &predicate.terms { - if let Term::Parameter(name) = &term { - parameters.insert(name.to_string(), None); - } + term.extract_parameters(&mut parameters); } } for expression in &expressions { for op in &expression.ops { - if let Op::Value(Term::Parameter(name)) = &op { - parameters.insert(name.to_string(), None); - } + op.collect_parameters(&mut parameters); } } @@ -398,6 +532,7 @@ pub struct Check { pub enum CheckKind { One, All, + Reject, } #[cfg(feature = "datalog-macro")] @@ -424,6 +559,9 @@ impl ToTokens for CheckKind { CheckKind::All => quote! { ::biscuit_auth::builder::CheckKind::All }, + CheckKind::Reject => quote! { + ::biscuit_auth::builder::CheckKind::Reject + }, }); } } @@ -484,30 +622,26 @@ pub fn pred>(name: &str, terms: &[I]) -> Predicate { } /// creates a rule -pub fn rule, P: AsRef>( - head_name: &str, - head_terms: &[T], - predicates: &[P], -) -> Rule { +pub fn rule>(head_name: &str, head_terms: &[T], predicates: &[Predicate]) -> Rule { Rule::new( pred(head_name, head_terms), - predicates.iter().map(|p| p.as_ref().clone()).collect(), + predicates.to_vec(), Vec::new(), vec![], ) } /// creates a rule with constraints -pub fn constrained_rule, P: AsRef, E: AsRef>( +pub fn constrained_rule>( head_name: &str, head_terms: &[T], - predicates: &[P], - expressions: &[E], + predicates: &[Predicate], + expressions: &[Expression], ) -> Rule { Rule::new( pred(head_name, head_terms), - predicates.iter().map(|p| p.as_ref().clone()).collect(), - expressions.iter().map(|c| c.as_ref().clone()).collect(), + predicates.to_vec(), + expressions.to_vec(), vec![], ) } @@ -569,6 +703,21 @@ pub fn set(s: BTreeSet) -> Term { Term::Set(s) } +/// creates a null +pub fn null() -> Term { + Term::Null +} + +/// creates an array +pub fn array(a: Vec) -> Term { + Term::Array(a) +} + +/// creates a map +pub fn map(m: BTreeMap) -> Term { + Term::Map(m) +} + /// creates a parameter pub fn parameter(p: &str) -> Term { Term::Parameter(p.to_string()) diff --git a/biscuit-parser/src/parser.rs b/biscuit-parser/src/parser.rs index 0f5554ca..d17d02b1 100644 --- a/biscuit-parser/src/parser.rs +++ b/biscuit-parser/src/parser.rs @@ -1,18 +1,21 @@ -use crate::builder::{self, CheckKind}; +use crate::builder::{self, CheckKind, PublicKey}; use nom::{ branch::alt, bytes::complete::{escaped_transform, tag, tag_no_case, take_until, take_while, take_while1}, character::{ - complete::{char, digit1, multispace0 as space0}, - is_alphanumeric, + complete::{char, digit1, multispace0 as space0, satisfy}, + is_alphabetic, is_alphanumeric, }, combinator::{consumed, cut, eof, map, map_res, opt, recognize, value}, error::{ErrorKind, FromExternalError, ParseError}, multi::{many0, separated_list0, separated_list1}, - sequence::{delimited, pair, preceded, terminated, tuple}, - IResult, Offset, + sequence::{delimited, pair, preceded, separated_pair, terminated, tuple}, + IResult, Offset, Parser, +}; +use std::{ + collections::{BTreeMap, BTreeSet}, + convert::TryInto, }; -use std::{collections::BTreeSet, convert::TryInto}; use thiserror::Error; /// parse a Datalog fact @@ -70,6 +73,7 @@ fn check_inner(i: &str) -> IResult<&str, builder::Check, Error> { let (i, kind) = alt(( map(tag_no_case("check if"), |_| CheckKind::One), map(tag_no_case("check all"), |_| CheckKind::All), + map(tag_no_case("reject if"), |_| CheckKind::Reject), ))(i)?; let (i, queries) = cut(check_body)(i)?; @@ -198,78 +202,6 @@ pub fn rule_inner(i: &str) -> IResult<&str, builder::Rule, Error> { Ok((i, rule)) } -/* -impl TryFrom<&str> for builder::Fact { - type Error = error::Token; - - fn try_from(value: &str) -> Result { - Ok(fact(value).finish().map(|(_, o)| o)?) - } -} - -impl TryFrom<&str> for builder::Rule { - type Error = error::Token; - - fn try_from(value: &str) -> Result { - Ok(rule(value).finish().map(|(_, o)| o)?) - } -} - -impl FromStr for builder::Fact { - type Err = error::Token; - - fn from_str(s: &str) -> Result { - Ok(fact(s).finish().map(|(_, o)| o)?) - } -} - -impl FromStr for builder::Rule { - type Err = error::Token; - - fn from_str(s: &str) -> Result { - Ok(rule(s).finish().map(|(_, o)| o)?) - } -} - -impl TryFrom<&str> for builder::Check { - type Error = error::Token; - - fn try_from(value: &str) -> Result { - Ok(check(value).finish().map(|(_, o)| o)?) - } -} - -impl FromStr for builder::Check { - type Err = error::Token; - - fn from_str(s: &str) -> Result { - Ok(check(s).finish().map(|(_, o)| o)?) - } -} - -impl TryFrom<&str> for builder::Policy { - type Error = error::Token; - - fn try_from(value: &str) -> Result { - Ok(policy(value).finish().map(|(_, o)| o)?) - } -} - -impl FromStr for builder::Policy { - type Err = error::Token; - - fn from_str(s: &str) -> Result { - Ok(policy(s).finish().map(|(_, o)| o)?) - } -} - -impl FromStr for builder::Predicate { - type Err = error::Token; - - fn from_str(s: &str) -> Result { - Ok(predicate(s).finish().map(|(_, o)| o)?) - } -}*/ fn predicate(i: &str) -> IResult<&str, builder::Predicate, Error> { let (i, _) = space0(i)?; @@ -379,7 +311,16 @@ fn scope(i: &str) -> IResult<&str, builder::Scope, Error> { } pub fn public_key(i: &str) -> IResult<&str, builder::PublicKey, Error> { - preceded(tag("ed25519/"), parse_hex)(i) + alt(( + preceded(tag("ed25519/"), parse_hex).map(|key| PublicKey { + key, + algorithm: builder::Algorithm::Ed25519, + }), + preceded(tag("secp256r1/"), parse_hex).map(|key| PublicKey { + key, + algorithm: builder::Algorithm::Secp256r1, + }), + ))(i) } #[derive(Debug, PartialEq)] @@ -387,6 +328,7 @@ pub enum Expr { Value(builder::Term), Unary(builder::Op, Box), Binary(builder::Op, Box, Box), + Closure(Vec, Box), } impl Expr { @@ -408,6 +350,11 @@ impl Expr { right.into_opcodes(v); v.push(op); } + Expr::Closure(params, expr) => { + let mut ops = vec![]; + expr.into_opcodes(&mut ops); + v.push(builder::Op::Closure(params, ops)) + } } } } @@ -440,12 +387,12 @@ fn unary_parens(i: &str) -> IResult<&str, Expr, Error> { fn binary_op_0(i: &str) -> IResult<&str, builder::Binary, Error> { use builder::Binary; - value(Binary::Or, tag("||"))(i) + value(Binary::LazyOr, tag("||"))(i) } fn binary_op_1(i: &str) -> IResult<&str, builder::Binary, Error> { use builder::Binary; - value(Binary::And, tag("&&"))(i) + value(Binary::LazyAnd, tag("&&"))(i) } fn binary_op_2(i: &str) -> IResult<&str, builder::Binary, Error> { @@ -455,8 +402,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) } @@ -485,6 +434,16 @@ fn binary_op_7(i: &str) -> IResult<&str, builder::Binary, Error> { alt((value(Binary::Mul, tag("*")), value(Binary::Div, tag("/"))))(i) } +fn extern_un(i: &str) -> IResult<&str, builder::Unary, Error> { + let (i, func) = preceded(tag("extern::"), name)(i)?; + Ok((i, builder::Unary::Ffi(func.to_string()))) +} + +fn extern_bin(i: &str) -> IResult<&str, builder::Binary, Error> { + let (i, func) = preceded(tag("extern::"), name)(i)?; + Ok((i, builder::Binary::Ffi(func.to_string()))) +} + fn binary_op_8(i: &str) -> IResult<&str, builder::Binary, Error> { use builder::Binary; @@ -495,6 +454,10 @@ fn binary_op_8(i: &str) -> IResult<&str, builder::Binary, Error> { value(Binary::Regex, tag("matches")), value(Binary::Intersection, tag("intersection")), value(Binary::Union, tag("union")), + value(Binary::All, tag("all")), + value(Binary::Any, tag("any")), + value(Binary::Get, tag("get")), + extern_bin, ))(i) } @@ -507,7 +470,14 @@ fn expr_term(i: &str) -> IResult<&str, Expr, Error> { fn fold_exprs(initial: Expr, remainder: Vec<(builder::Binary, Expr)>) -> Expr { remainder.into_iter().fold(initial, |acc, pair| { let (op, expr) = pair; - Expr::Binary(builder::Op::Binary(op), Box::new(acc), Box::new(expr)) + match op { + builder::Binary::LazyAnd | builder::Binary::LazyOr => Expr::Binary( + builder::Op::Binary(op), + Box::new(acc), + Box::new(Expr::Closure(vec![], Box::new(expr))), + ), + _ => Expr::Binary(builder::Op::Binary(op), Box::new(acc), Box::new(expr)), + } }) } @@ -634,10 +604,24 @@ fn expr9(i: &str) -> IResult<&str, Expr, Error> { let bin_result = binary_method(i); let un_result = unary_method(i); match (bin_result, un_result) { - (Ok((i, (op, arg))), _) => { + (Ok((i, (op, params, arg))), _) => { input = i; - initial = - Expr::Binary(builder::Op::Binary(op), Box::new(initial), Box::new(arg)); + match params { + Some(params) => { + initial = Expr::Binary( + builder::Op::Binary(op), + Box::new(initial), + Box::new(Expr::Closure(params, Box::new(arg))), + ); + } + None => { + initial = Expr::Binary( + builder::Op::Binary(op), + Box::new(initial), + Box::new(arg), + ); + } + } } (_, Ok((i, op))) => { input = i; @@ -652,22 +636,40 @@ fn expr9(i: &str) -> IResult<&str, Expr, Error> { } } -fn binary_method(i: &str) -> IResult<&str, (builder::Binary, Expr), Error> { +fn binary_method(i: &str) -> IResult<&str, (builder::Binary, Option>, Expr), Error> { let (i, op) = binary_op_8(i)?; let (i, _) = char('(')(i)?; let (i, _) = space0(i)?; // we only support a single argument for now - let (i, arg) = expr(i)?; - let (i, _) = space0(i)?; - let (i, _) = char(')')(i)?; + match op { + builder::Binary::All | builder::Binary::Any => { + let (i, param) = preceded(char('$'), name)(i)?; + let (i, _) = space0(i)?; + let (i, _) = tag("->")(i)?; + let (i, _) = space0(i)?; + let (i, arg) = expr(i)?; + let (i, _) = space0(i)?; + let (i, _) = char(')')(i)?; + Ok((i, (op, Some(vec![param.to_owned()]), arg))) + } + _ => { + let (i, arg) = expr(i)?; + let (i, _) = space0(i)?; + let (i, _) = char(')')(i)?; - Ok((i, (op, arg))) + Ok((i, (op, None, arg))) + } + } } fn unary_method(i: &str) -> IResult<&str, builder::Unary, Error> { use builder::Unary; - let (i, op) = value(Unary::Length, tag("length"))(i)?; + let (i, op) = alt(( + value(Unary::Length, tag("length")), + value(Unary::TypeOf, tag("type")), + extern_un, + ))(i)?; let (i, _) = char('(')(i)?; let (i, _) = space0(i)?; @@ -682,6 +684,21 @@ fn name(i: &str) -> IResult<&str, &str, Error> { reduce(take_while1(is_name_char), " ,:(\n;")(i) } +fn parameter_name(i: &str) -> IResult<&str, &str, Error> { + let is_name_char = |c: char| is_alphanumeric(c as u8) || c == '_' || c == ':'; + + error( + recognize(preceded( + satisfy(|c: char| is_alphabetic(c as u8)), + take_while(is_name_char), + )), + |_| { + "invalid parameter name: it must start with an alphabetic character, followed by alphanumeric characters, underscores or colons".to_string() + }, + " ,:(\n;", + )(i) +} + fn printable(i: &str) -> IResult<&str, &str, Error> { take_while1(|c: char| c != '\\' && c != '"')(i) } @@ -720,7 +737,9 @@ fn integer(i: &str) -> IResult<&str, builder::Term, Error> { fn parse_date(i: &str) -> IResult<&str, u64, Error> { map_res( map_res( - take_while1(|c: char| c != ',' && c != ' ' && c != ')' && c != ']' && c != ';'), + take_while1(|c: char| { + c != ',' && c != ' ' && c != ')' && c != ']' && c != ';' && c != '}' + }), |s| time::OffsetDateTime::parse(s, &time::format_description::well_known::Rfc3339), ), |t| t.unix_timestamp().try_into(), @@ -754,7 +773,10 @@ fn variable(i: &str) -> IResult<&str, builder::Term, Error> { } fn parameter(i: &str) -> IResult<&str, builder::Term, Error> { - map(delimited(char('{'), name, char('}')), builder::parameter)(i) + map( + delimited(char('{'), parameter_name, char('}')), + builder::parameter, + )(i) } fn parse_bool(i: &str) -> IResult<&str, bool, Error> { @@ -765,10 +787,21 @@ fn boolean(i: &str) -> IResult<&str, builder::Term, Error> { parse_bool(i).map(|(i, b)| (i, builder::boolean(b))) } +fn null(i: &str) -> IResult<&str, builder::Term, Error> { + tag("null")(i).map(|(i, _)| (i, builder::null())) +} + fn set(i: &str) -> IResult<&str, builder::Term, Error> { - //println!("set:\t{}", i); - let (i, _) = preceded(space0, char('['))(i)?; - let (i, mut list) = cut(separated_list0(preceded(space0, char(',')), term_in_set))(i)?; + alt((empty_set, non_empty_set))(i) +} + +fn empty_set(i: &str) -> IResult<&str, builder::Term, Error> { + tag("{,}")(i).map(|(i, _)| (i, builder::set(BTreeSet::new()))) +} + +fn non_empty_set(i: &str) -> IResult<&str, builder::Term, Error> { + let (i, _) = preceded(space0, char('{'))(i)?; + let (i, mut list) = cut(separated_list1(preceded(space0, char(',')), term_in_set))(i)?; let mut set = BTreeSet::new(); @@ -795,6 +828,9 @@ fn set(i: &str) -> IResult<&str, builder::Term, Error> { })) } builder::Term::Parameter(_) => 7, + builder::Term::Null => 8, + builder::Term::Array(_) => 9, + builder::Term::Map(_) => 10, }; if let Some(k) = kind { @@ -812,16 +848,55 @@ fn set(i: &str) -> IResult<&str, builder::Term, Error> { set.insert(term); } - let (i, _) = preceded(space0, char(']'))(i)?; + let (i, _) = preceded(space0, char('}'))(i)?; Ok((i, builder::set(set))) } +fn array(i: &str) -> IResult<&str, builder::Term, Error> { + let (i, _) = preceded(space0, char('['))(i)?; + let (i, array) = cut(separated_list0(preceded(space0, char(',')), term_in_fact))(i)?; + let (i, _) = preceded(space0, char(']'))(i)?; + + Ok((i, builder::array(array))) +} + +fn parse_map(i: &str) -> IResult<&str, builder::Term, Error> { + let (i, _) = preceded(space0, char('{'))(i)?; + let (i, mut list) = cut(separated_list0( + preceded(space0, char(',')), + separated_pair(map_key, preceded(space0, char(':')), term_in_fact), + ))(i)?; + + let mut map = BTreeMap::new(); + + for (key, term) in list.drain(..) { + map.insert(key, term); + } + + let (i, _) = preceded(space0, char('}'))(i)?; + + Ok((i, builder::map(map))) +} + +fn map_key(i: &str) -> IResult<&str, builder::MapKey, Error> { + preceded( + space0, + alt(( + map(delimited(char('{'), parameter_name, char('}')), |s| { + builder::MapKey::Parameter(s.to_string()) + }), + map(parse_string, |s| builder::MapKey::Str(s.to_string())), + map(parse_integer, builder::MapKey::Integer), + )), + )(i) +} + fn term(i: &str) -> IResult<&str, builder::Term, Error> { preceded( space0, alt(( - parameter, string, date, variable, integer, bytes, boolean, set, + parameter, string, date, variable, integer, bytes, boolean, null, array, parse_map, set, )), )(i) } @@ -830,7 +905,9 @@ fn term_in_fact(i: &str) -> IResult<&str, builder::Term, Error> { preceded( space0, error( - alt((parameter, string, date, integer, bytes, boolean, set)), + alt(( + parameter, string, date, integer, bytes, boolean, null, set, array, parse_map, + )), |input| match input.chars().next() { None | Some(',') | Some(')') => "missing term".to_string(), Some('$') => "variables are not allowed in facts".to_string(), @@ -845,13 +922,15 @@ fn term_in_set(i: &str) -> IResult<&str, builder::Term, Error> { preceded( space0, error( - alt((parameter, string, date, integer, bytes, boolean)), + alt(( + parameter, string, date, integer, bytes, boolean, null, parse_map, + )), |input| match input.chars().next() { - None | Some(',') | Some(']') => "missing term".to_string(), + None | Some(',') | Some('}') => "missing term".to_string(), Some('$') => "variables are not allowed in sets".to_string(), _ => "expected a valid term".to_string(), }, - " ,]\n;", + " ,}\n;", ), )(i) } @@ -1184,7 +1263,12 @@ where #[cfg(test)] mod tests { - use crate::builder::{self, Unary}; + use nom::error::ErrorKind; + + use crate::{ + builder::{self, array, int, var, Binary, CheckKind, Op, Unary}, + parser::Error, + }; #[test] fn name() { @@ -1232,6 +1316,17 @@ mod tests { super::parameter("{param}"), Ok(("", builder::parameter("param"))) ); + + assert_eq!( + super::parameter("{1param}"), + Err(nom::Err::Error(crate::parser::Error { + input: "1param}", + code: nom::error::ErrorKind::Satisfy, + message: Some("invalid parameter name: it must start with an alphabetic character, followed by alphanumeric characters, underscores or colons".to_string()) + })) + ); + + assert_eq!(super::parameter("{p}"), Ok(("", builder::parameter("p")))); } #[test] @@ -1316,6 +1411,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(( @@ -1323,6 +1430,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), ], )) @@ -1336,6 +1480,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), ], )) @@ -1349,7 +1532,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), ], )) ); @@ -1361,8 +1570,8 @@ mod tests { vec![ Op::Value(boolean(false)), Op::Unary(Unary::Negate), - Op::Value(boolean(true)), - Op::Binary(Binary::And), + Op::Closure(vec![], vec![Op::Value(boolean(true)),]), + Op::Binary(Binary::LazyAnd), ], )) ); @@ -1373,16 +1582,21 @@ mod tests { "", vec![ Op::Value(boolean(true)), - Op::Value(boolean(true)), - Op::Value(boolean(true)), - Op::Binary(Binary::And), - Op::Binary(Binary::Or), + Op::Closure( + vec![], + vec![ + Op::Value(boolean(true)), + Op::Closure(vec![], vec![Op::Value(boolean(true)),]), + Op::Binary(Binary::LazyAnd), + ] + ), + Op::Binary(Binary::LazyOr), ], )) ); 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![ @@ -1396,6 +1610,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(( @@ -1424,7 +1683,7 @@ mod tests { let h = [int(1), int(2)].iter().cloned().collect::>(); assert_eq!( - super::expr("[1, 2].contains($0)").map(|(i, o)| (i, o.opcodes())), + super::expr("{1, 2}.contains($0)").map(|(i, o)| (i, o.opcodes())), Ok(( "", vec![ @@ -1436,7 +1695,7 @@ mod tests { ); assert_eq!( - super::expr("![1, 2].contains($0)").map(|(i, o)| (i, o.opcodes())), + super::expr("!{ 1, 2}.contains($0)").map(|(i, o)| (i, o.opcodes())), Ok(( "", vec![ @@ -1448,8 +1707,27 @@ mod tests { )) ); + let h = [ + builder::Term::Date(1575452801), + builder::Term::Date(1607075201), + ] + .iter() + .cloned() + .collect::>(); assert_eq!( - super::expr("$0 == \"abc\"").map(|(i, o)| (i, o.opcodes())), + super::expr("{2020-12-04T09:46:41+00:00, 2019-12-04T09:46:41+00:00}.contains(2020-12-04T09:46:41+00:00)").map(|(i, o)| (i, o.opcodes())), + Ok(( + "", + vec![ + Op::Value(set(h)), + Op::Value(builder::Term::Date(1607075201)), + Op::Binary(Binary::Contains), + ], + )) + ); + + assert_eq!( + super::expr("$0 === \"abc\"").map(|(i, o)| (i, o.opcodes())), Ok(( "", vec![ @@ -1460,6 +1738,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(( @@ -1501,7 +1815,7 @@ mod tests { .cloned() .collect::>(); assert_eq!( - super::expr("[\"abc\", \"def\"].contains($0)").map(|(i, o)| (i, o.opcodes())), + super::expr("{\"abc\", \"def\"}.contains($0)").map(|(i, o)| (i, o.opcodes())), Ok(( "", vec![ @@ -1513,7 +1827,7 @@ mod tests { ); assert_eq!( - super::expr("![\"abc\", \"def\"].contains($0)").map(|(i, o)| (i, o.opcodes())), + super::expr("!{\"abc\", \"def\"}.contains($0)").map(|(i, o)| (i, o.opcodes())), Ok(( "", vec![ @@ -1530,7 +1844,7 @@ mod tests { .cloned() .collect::>(); assert_eq!( - super::expr("[\"abc\", \"def\"].contains($0)").map(|(i, o)| (i, o.opcodes())), + super::expr("{\"abc\", \"def\"}.contains($0)").map(|(i, o)| (i, o.opcodes())), Ok(( "", vec![ @@ -1542,7 +1856,7 @@ mod tests { ); assert_eq!( - super::expr("![\"abc\", \"def\"].contains($0)").map(|(i, o)| (i, o.opcodes())), + super::expr("!{\"abc\", \"def\"}.contains($0)").map(|(i, o)| (i, o.opcodes())), Ok(( "", vec![ @@ -1610,7 +1924,7 @@ mod tests { )) ); } - /* + #[test] fn rule() { assert_eq!( @@ -1623,7 +1937,7 @@ mod tests { &[ builder::pred("resource", &[builder::variable("0")]), builder::pred("operation", &[builder::string("read")]), - ] + ], ) )) ); @@ -1640,9 +1954,7 @@ mod tests { "", builder::constrained_rule( "valid_date", - &[ - builder::string("file1"), - ], + &[builder::string("file1")], &[ builder::pred("time", &[builder::variable("0")]), builder::pred("resource", &[builder::string("file1")]), @@ -1694,9 +2006,9 @@ mod tests { assert_eq!( super::rule("right($0, $test) <- resource($0), operation(\"read\")"), Err( nom::Err::Failure(Error { - input: "right($0, $test)", + input: "right($0, $test) <- resource($0), operation(\"read\")", code: ErrorKind::Satisfy, - message: Some("rule head contains variables that are not used in predicates of the rule's body: $test".to_string()), + message: Some("the rule contains variables that are not bound by predicates in the rule's body: $test".to_string()), })) ); } @@ -1709,6 +2021,7 @@ mod tests { Ok(( "", builder::Check { + kind: builder::CheckKind::One, queries: vec![ builder::rule( "query", @@ -1766,12 +2079,9 @@ mod tests { #[test] fn expression() { use super::Expr; - use crate::datalog::SymbolTable; use builder::{date, int, string, var, Binary, Op, Term}; use std::time::{Duration, SystemTime}; - let mut syms = SymbolTable::new(); - let input = " -1 "; println!("parsing: {}", input); let res = super::expr(input); @@ -1779,8 +2089,6 @@ mod tests { let ops = res.unwrap().1.opcodes(); println!("ops: {:#?}", ops); - let e = builder::Expression { ops }.convert(&mut syms); - println!("print: {}", e.print(&syms).unwrap()); let input = " $0 <= 2019-12-04T09:46:41+00:00"; println!("parsing: {}", input); @@ -1801,9 +2109,6 @@ mod tests { let ops = res.unwrap().1.opcodes(); println!("ops: {:#?}", ops); - let e = builder::Expression { ops }.convert(&mut syms); - println!("print: {}", e.print(&syms).unwrap()); - let input = " 1 < $test + 2 "; println!("parsing: {}", input); let res = super::expr(input); @@ -1825,8 +2130,6 @@ mod tests { let ops = res.unwrap().1.opcodes(); println!("ops: {:#?}", ops); - let e = builder::Expression { ops }.convert(&mut syms); - println!("print: {}", e.print(&syms).unwrap()); let input = " 2 < $test && $var2.starts_with(\"test\") && true "; println!("parsing: {}", input); @@ -1836,40 +2139,37 @@ mod tests { Ok(( " ", Expr::Binary( - Op::Binary(Binary::And), + Op::Binary(Binary::LazyAnd), Box::new(Expr::Binary( - Op::Binary(Binary::And), + Op::Binary(Binary::LazyAnd), Box::new(Expr::Binary( Op::Binary(Binary::LessThan), Box::new(Expr::Value(int(2))), Box::new(Expr::Value(var("test"))), )), - Box::new(Expr::Binary( - Op::Binary(Binary::Prefix), - Box::new(Expr::Value(var("var2"))), - Box::new(Expr::Value(string("test"))), + Box::new(Expr::Closure( + vec![], + Box::new(Expr::Binary( + Op::Binary(Binary::Prefix), + Box::new(Expr::Value(var("var2"))), + Box::new(Expr::Value(string("test"))), + )) )), )), - Box::new(Expr::Value(Term::Bool(true))), + Box::new(Expr::Closure( + vec![], + Box::new(Expr::Value(Term::Bool(true))), + )) ) )) ); - let ops = res.unwrap().1.opcodes(); println!("ops: {:#?}", ops); - let e = builder::Expression { ops }.convert(&mut syms); - println!("print: {}", e.print(&syms).unwrap()); - - //panic!(); } #[test] fn parens() { - use crate::datalog::{SymbolTable, TemporarySymbolTable}; use builder::{int, Binary, Op, Unary}; - use std::collections::HashMap; - - let mut syms = SymbolTable::new(); let input = " 1 + 2 * 3 "; println!("parsing: {}", input); @@ -1877,15 +2177,6 @@ mod tests { let ops = res.opcodes(); println!("ops: {:#?}", ops); - let e = builder::Expression { ops: ops.clone() }.convert(&mut syms); - - let printed = e.print(&syms).unwrap(); - println!("print: {}", e.print(&syms).unwrap()); - let h = HashMap::new(); - let result = e - .evaluate(&h, &mut TemporarySymbolTable::new(&syms)) - .unwrap(); - println!("evaluates to: {:?}", result); assert_eq!( ops, @@ -1897,8 +2188,6 @@ mod tests { Op::Binary(Binary::Add), ] ); - assert_eq!(&printed, "1 + 2 * 3"); - assert_eq!(result, datalog::Term::Integer(7)); let input = " (1 + 2) * 3 "; println!("parsing: {}", input); @@ -1906,15 +2195,6 @@ mod tests { let ops = res.opcodes(); println!("ops: {:#?}", ops); - let e = builder::Expression { ops: ops.clone() }.convert(&mut syms); - - let printed = e.print(&syms).unwrap(); - println!("print: {}", e.print(&syms).unwrap()); - let h = HashMap::new(); - let result = e - .evaluate(&h, &mut TemporarySymbolTable::new(&syms)) - .unwrap(); - println!("evaluates to: {:?}", result); assert_eq!( ops, @@ -1927,8 +2207,6 @@ mod tests { Op::Binary(Binary::Mul), ] ); - assert_eq!(&printed, "(1 + 2) * 3"); - assert_eq!(result, datalog::Term::Integer(9)); } #[test] @@ -1946,7 +2224,7 @@ mod tests { rule_head($var0) <- fact($var0, $var1), 1 < 2; // line comment - check if 1 == 2; + check if 1 === 2; allow if rule_head("string"); @@ -1988,6 +2266,7 @@ mod tests { let expected_checks = vec![ Check { + kind: CheckKind::One, queries: vec![constrained_rule( "query", empty_terms, @@ -2002,6 +2281,7 @@ mod tests { )], }, Check { + kind: CheckKind::One, queries: vec![ rule("query", empty_terms, &[pred("fact", &[int(5678)])]), constrained_rule( @@ -2019,6 +2299,7 @@ mod tests { ], }, Check { + kind: CheckKind::One, queries: vec![constrained_rule( "query", empty_terms, @@ -2095,7 +2376,7 @@ mod tests { fact2(1234); rule_head($var0) <- fact($var0, $var1), 1 < 2; // line comment - check if 1 == 2; /* + check if 1 === 2; /* other comment */ check if @@ -2131,6 +2412,7 @@ mod tests { let expected_checks = vec![ Check { + kind: CheckKind::One, queries: vec![constrained_rule( "query", empty_terms, @@ -2145,6 +2427,7 @@ mod tests { )], }, Check { + kind: CheckKind::One, queries: vec![ rule("query", empty_terms, &[pred("fact", &[int(5678)])]), constrained_rule( @@ -2162,6 +2445,7 @@ mod tests { ], }, Check { + kind: CheckKind::One, queries: vec![constrained_rule( "query", empty_terms, @@ -2194,15 +2478,14 @@ mod tests { result.checks.drain(..).map(|(_, r)| r).collect::>(), expected_checks ); - }*/ - + } #[test] fn chained_calls() { use builder::{int, set, Binary, Op}; assert_eq!( - super::expr("[1].intersection([2]).contains(3)").map(|(i, o)| (i, o.opcodes())), + super::expr("{1}.intersection({2}).contains(3)").map(|(i, o)| (i, o.opcodes())), Ok(( "", vec![ @@ -2216,7 +2499,7 @@ mod tests { ); assert_eq!( - super::expr("[1].intersection([2]).union([3]).length()").map(|(i, o)| (i, o.opcodes())), + super::expr("{1}.intersection({2}).union({3}).length()").map(|(i, o)| (i, o.opcodes())), Ok(( "", vec![ @@ -2231,7 +2514,7 @@ mod tests { ); assert_eq!( - super::expr("[1].intersection([2]).length().union([3])").map(|(i, o)| (i, o.opcodes())), + super::expr("{1}.intersection({2}).length().union({3})").map(|(i, o)| (i, o.opcodes())), Ok(( "", vec![ @@ -2245,4 +2528,59 @@ mod tests { )) ); } + + #[test] + fn arrays() { + let h = vec![int(1), int(2)]; + assert_eq!( + super::expr("[1, 2].contains($0)").map(|(i, o)| (i, o.opcodes())), + Ok(( + "", + vec![ + Op::Value(array(h.clone())), + Op::Value(var("0")), + Op::Binary(Binary::Contains), + ] + )) + ) + } + + #[test] + fn extern_funcs() { + use builder::{int, Binary, Op}; + + assert_eq!( + super::expr("2.extern::toto()").map(|(i, o)| (i, o.opcodes())), + Ok(( + "", + vec![Op::Value(int(2)), Op::Unary(Unary::Ffi("toto".to_string()))], + )) + ); + + assert_eq!( + super::expr("2.extern::toto(3)").map(|(i, o)| (i, o.opcodes())), + Ok(( + "", + vec![ + Op::Value(int(2)), + Op::Value(int(3)), + Op::Binary(Binary::Ffi("toto".to_string())), + ], + )) + ); + } + + #[test] + fn empty_set_map() { + use builder::{map, set}; + + assert_eq!( + super::expr("{,}").map(|(i, o)| (i, o.opcodes())), + Ok(("", vec![Op::Value(set(Default::default()))],)) + ); + assert_eq!( + super::expr("{}").map(|(i, o)| (i, o.opcodes())), + Ok(("", vec![Op::Value(map(Default::default()))],)) + ); + } } diff --git a/biscuit-quote/src/lib.rs b/biscuit-quote/src/lib.rs index 8b2380dc..5263b44d 100644 --- a/biscuit-quote/src/lib.rs +++ b/biscuit-quote/src/lib.rs @@ -129,7 +129,7 @@ pub fn authorizer(input: proc_macro::TokenStream) -> proc_macro::TokenStream { parameters, } = syn::parse_macro_input!(input as ParsedCreateNew); - let ty = syn::parse_quote!(::biscuit_auth::Authorizer); + let ty = syn::parse_quote!(::biscuit_auth::builder::AuthorizerBuilder); let builder = Builder::source(ty, None, datalog, parameters) .unwrap_or_else(|e| abort_call_site!(e.to_string())); @@ -148,7 +148,7 @@ pub fn authorizer_merge(input: proc_macro::TokenStream) -> proc_macro::TokenStre parameters, } = syn::parse_macro_input!(input as ParsedMerge); - let ty = syn::parse_quote!(::biscuit_auth::Authorizer); + let ty = syn::parse_quote!(::biscuit_auth::builder::AuthorizerBuilder); let builder = Builder::source(ty, Some(target), datalog, parameters) .unwrap_or_else(|e| abort_call_site!(e.to_string())); @@ -349,7 +349,7 @@ impl Item { }, middle: TokenStream::new(), end: quote! { - __biscuit_auth_builder.add_fact(__biscuit_auth_item).unwrap(); + __biscuit_auth_builder = __biscuit_auth_builder.fact(__biscuit_auth_item).unwrap(); }, } } @@ -361,7 +361,7 @@ impl Item { }, middle: TokenStream::new(), end: quote! { - __biscuit_auth_builder.add_rule(__biscuit_auth_item).unwrap(); + __biscuit_auth_builder = __biscuit_auth_builder.rule(__biscuit_auth_item).unwrap(); }, } } @@ -374,7 +374,7 @@ impl Item { }, middle: TokenStream::new(), end: quote! { - __biscuit_auth_builder.add_check(__biscuit_auth_item).unwrap(); + __biscuit_auth_builder =__biscuit_auth_builder.check(__biscuit_auth_item).unwrap(); }, } } @@ -387,7 +387,7 @@ impl Item { }, middle: TokenStream::new(), end: quote! { - __biscuit_auth_builder.add_policy(__biscuit_auth_item).unwrap(); + __biscuit_auth_builder = __biscuit_auth_builder.policy(__biscuit_auth_item).unwrap(); }, } } @@ -476,7 +476,7 @@ impl ToTokens for Builder { let builder_type = &self.builder_type; let builder_quote = if let Some(target) = &self.target { quote! { - let __biscuit_auth_builder: &mut #builder_type = #target; + let mut __biscuit_auth_builder: #builder_type = #target; } } else { quote! {