Skip to content

Commit

Permalink
authorizer builder (#250)
Browse files Browse the repository at this point in the history
This adds an `AuthorizerBuilder` struct that is used to create an `Authorizer`. All of the mutable behaviour, like adding facts or executing Datalog rules is moved into the builder, while the authorizer is limited to read-only queries (still requiring self mutability to track execution time). This will solve some awkward behaviour where the authorizer had to run Datalog rules again when facts or rules were added, but it was not done consistently. The `AuthorizerBuilder` is compatible with snapshots, to store and reuse checks and policies. It has a `build` method taking a token as argument, and a `build_unauthenticated` for authorization without token.

The builder APIs are alo changing. Before, we had the following:

```rust
 let mut builder = Biscuit::builder();	
builder.add_fact(r"right("file1", "read")"#)?;
builder.add_fact(r"right("file2", "read")"#)?;
let token = builder.build()?;
```

Builders are now constructed like this:

```rust
let token = Biscuit::builder()
    .fact(r"right("file1", "read")"#)?
    .fact(r"right("file2", "read")"#)?
    .build()?;
````
  • Loading branch information
Geal authored Nov 29, 2024
1 parent 6d55705 commit fc0d069
Show file tree
Hide file tree
Showing 22 changed files with 1,810 additions and 1,365 deletions.
511 changes: 291 additions & 220 deletions biscuit-auth/benches/token.rs

Large diffs are not rendered by default.

41 changes: 19 additions & 22 deletions biscuit-auth/examples/testcases.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ 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;
Expand Down Expand Up @@ -344,12 +343,13 @@ fn validate_token_with_limits_and_external_functions(
revocation_ids.push(hex::encode(&bytes));
}

let mut authorizer = Authorizer::new();
authorizer.set_extern_funcs(extern_funcs);
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 {
Expand Down Expand Up @@ -878,9 +878,9 @@ 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(Algorithm::Ed25519, &mut rng);
let biscuit3 = biscuit2.append_with_keypair(&keypair3, block3).unwrap();
Expand Down Expand Up @@ -973,14 +973,13 @@ 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(Algorithm::Ed25519, &mut rng);
let biscuit2 = biscuit1.append_with_keypair(&keypair2, block2).unwrap();
Expand Down Expand Up @@ -1410,11 +1409,9 @@ 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")])],
Expand Down
35 changes: 20 additions & 15 deletions biscuit-auth/examples/third_party.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,22 @@
use biscuit_auth::{
builder::Algorithm, builder::BlockBuilder, datalog::SymbolTable, Biscuit, KeyPair,
builder::{Algorithm, AuthorizerBuilder, BlockBuilder},
builder_ext::AuthorizerExt,
datalog::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(Algorithm::Ed25519, &mut rng);
let external = KeyPair::new_with_rng(Algorithm::Ed25519, &mut rng);

let mut builder = Biscuit::builder();

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();

Expand All @@ -27,21 +25,28 @@ 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()
.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()
.build(&biscuit2)
.unwrap();

println!("authorize biscuit2:\n{:?}", authorizer.authorize());
println!("world:\n{}", authorizer.print_world());
}
5 changes: 2 additions & 3 deletions biscuit-auth/examples/verifying_printer.rs
Original file line number Diff line number Diff line change
@@ -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();
Expand All @@ -25,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());
Expand Down
2 changes: 1 addition & 1 deletion biscuit-auth/src/datalog/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -783,7 +783,7 @@ impl FactSet {
pub fn iterator<'a>(
&'a self,
block_ids: &'a TrustedOrigins,
) -> impl Iterator<Item = (&Origin, &Fact)> + Clone {
) -> impl Iterator<Item = (&'a Origin, &'a Fact)> + Clone {
self.inner
.iter()
.filter_map(move |(ids, facts)| {
Expand Down
19 changes: 11 additions & 8 deletions biscuit-auth/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
//!
Expand All @@ -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(())
//! }
Expand Down
20 changes: 12 additions & 8 deletions biscuit-auth/src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
//! expiration = SystemTime::now() + Duration::from_secs(86_400),
//! )).expect("Failed to append block");
//!
//! new_biscuit.authorize(&authorizer!(
//! authorizer!(
//! r#"
//! time({now});
//! operation({operation});
Expand All @@ -42,7 +42,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.
Expand Down Expand Up @@ -78,8 +82,8 @@ pub use biscuit_quote::authorizer;
/// now = SystemTime::now()
/// );
///
/// authorizer_merge!(
/// &mut b,
/// b = authorizer_merge!(
/// b,
/// r#"
/// allow if true;
/// "#
Expand Down Expand Up @@ -128,8 +132,8 @@ pub use biscuit_quote::biscuit;
/// user_id = "1234"
/// );
///
/// biscuit_merge!(
/// &mut b,
/// b = biscuit_merge!(
/// b,
/// r#"
/// check if time($time), $time < {expiration}
/// "#,
Expand Down Expand Up @@ -173,8 +177,8 @@ pub use biscuit_quote::block;
/// user_id = "1234"
/// );
///
/// block_merge!(
/// &mut b,
/// b = block_merge!(
/// b,
/// r#"
/// check if user($id);
/// "#
Expand Down
Loading

0 comments on commit fc0d069

Please sign in to comment.