use std::collections::HashMap;
use std::convert::TryInto;
use std::fmt::Display;
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 crate::crypto::{self};
use crate::format::convert::proto_block_to_token_block;
use crate::format::schema::{self, ThirdPartyBlockContents};
use authorizer::Authorizer;
pub mod authorizer;
pub(crate) mod block;
pub mod builder;
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::*;
pub const MIN_SCHEMA_VERSION: u32 = 3;
pub const MAX_SCHEMA_VERSION: u32 = 4;
pub fn default_symbol_table() -> SymbolTable {
SymbolTable::new()
}
#[derive(Clone, Debug)]
pub struct Biscuit {
pub(crate) root_key_id: Option<u32>,
pub(crate) authority: schema::Block,
pub(crate) blocks: Vec<schema::Block>,
pub(crate) symbols: SymbolTable,
pub(crate) container: SerializedBiscuit,
pub(crate) public_key_to_block_id: HashMap<usize, Vec<usize>>,
}
impl Biscuit {
pub fn builder() -> BiscuitBuilder {
BiscuitBuilder::new()
}
pub fn from<T, KP>(slice: T, key_provider: KP) -> Result<Self, error::Token>
where
T: AsRef<[u8]>,
KP: RootKeyProvider,
{
Biscuit::from_with_symbols(slice.as_ref(), key_provider, default_symbol_table())
}
pub fn from_base64<T, KP>(slice: T, key_provider: KP) -> Result<Self, error::Token>
where
T: AsRef<[u8]>,
KP: RootKeyProvider,
{
Biscuit::from_base64_with_symbols(slice, key_provider, default_symbol_table())
}
pub fn to_vec(&self) -> Result<Vec<u8>, error::Token> {
self.container.to_vec().map_err(error::Token::Format)
}
pub fn to_base64(&self) -> Result<String, error::Token> {
self.container
.to_vec()
.map_err(error::Token::Format)
.map(|v| base64::encode_config(v, base64::URL_SAFE))
}
pub fn serialized_size(&self) -> Result<usize, error::Token> {
Ok(self.container.serialized_size())
}
pub fn seal(&self) -> Result<Biscuit, error::Token> {
let container = self.container.seal()?;
let mut token = self.clone();
token.container = container;
Ok(token)
}
pub fn authorizer(&self) -> Result<Authorizer, error::Token> {
Authorizer::from_token(self)
}
pub fn authorize(&self, authorizer: &Authorizer) -> Result<usize, error::Token> {
let mut a = authorizer.clone();
a.add_token(self)?;
a.authorize()
}
pub fn append(&self, block_builder: BlockBuilder) -> Result<Self, error::Token> {
let keypair = KeyPair::new_with_rng(&mut rand::rngs::OsRng);
self.append_with_keypair(&keypair, block_builder)
}
pub fn context(&self) -> Vec<Option<String>> {
let mut res = vec![self.authority.context.clone()];
for b in self.blocks.iter() {
res.push(b.context.clone());
}
res
}
pub fn root_key_id(&self) -> Option<u32> {
self.root_key_id
}
pub fn revocation_identifiers(&self) -> Vec<Vec<u8>> {
let mut res = vec![self.container.authority.signature.to_bytes().to_vec()];
for block in self.container.blocks.iter() {
res.push(block.signature.to_bytes().to_vec());
}
res
}
pub fn external_public_keys(&self) -> Vec<Option<PublicKey>> {
let mut res = vec![None];
for block in self.container.blocks.iter() {
res.push(block.external_signature.as_ref().map(|sig| sig.public_key));
}
res
}
pub fn print(&self) -> String {
format!("{}", &self)
}
pub fn print_block_source(&self, index: usize) -> Result<String, error::Token> {
self.block(index).map(|block| {
let symbols = if block.external_key.is_some() {
&block.symbols
} else {
&self.symbols
};
block.print_source(symbols)
})
}
pub(crate) fn new_with_rng<T: RngCore + CryptoRng>(
rng: &mut T,
root_key_id: Option<u32>,
root: &KeyPair,
mut symbols: SymbolTable,
authority: Block,
) -> Result<Biscuit, error::Token> {
if !symbols.is_disjoint(&authority.symbols) {
return Err(error::Token::Format(error::Format::SymbolTableOverlap));
}
symbols.extend(&authority.symbols)?;
let blocks = vec![];
let next_keypair = KeyPair::new_with_rng(rng);
let container = SerializedBiscuit::new(root_key_id, root, &next_keypair, &authority)?;
symbols.public_keys.extend(&authority.public_keys)?;
let authority = schema::Block::decode(&container.authority.data[..]).map_err(|e| {
error::Token::Format(error::Format::BlockDeserializationError(format!(
"error deserializing block: {:?}",
e
)))
})?;
Ok(Biscuit {
root_key_id,
authority,
blocks,
symbols,
container,
public_key_to_block_id: HashMap::new(),
})
}
fn from_with_symbols<KP>(
slice: &[u8],
key_provider: KP,
symbols: SymbolTable,
) -> Result<Self, error::Token>
where
KP: RootKeyProvider,
{
let container =
SerializedBiscuit::from_slice(slice, key_provider).map_err(error::Token::Format)?;
Biscuit::from_serialized_container(container, symbols)
}
fn from_serialized_container(
container: SerializedBiscuit,
mut symbols: SymbolTable,
) -> Result<Self, error::Token> {
let (authority, blocks, public_key_to_block_id) = container.extract_blocks(&mut symbols)?;
let root_key_id = container.root_key_id;
Ok(Biscuit {
root_key_id,
authority,
blocks,
symbols,
container,
public_key_to_block_id,
})
}
fn from_base64_with_symbols<T, KP>(
slice: T,
key_provider: KP,
symbols: SymbolTable,
) -> Result<Self, error::Token>
where
T: AsRef<[u8]>,
KP: RootKeyProvider,
{
let decoded = base64::decode_config(slice, base64::URL_SAFE)?;
Biscuit::from_with_symbols(&decoded, key_provider, symbols)
}
pub fn container(&self) -> &SerializedBiscuit {
&self.container
}
pub fn append_with_keypair(
&self,
keypair: &KeyPair,
block_builder: BlockBuilder,
) -> Result<Self, error::Token> {
let block = block_builder.build(self.symbols.clone());
if !self.symbols.is_disjoint(&block.symbols) {
return Err(error::Token::Format(error::Format::SymbolTableOverlap));
}
let authority = self.authority.clone();
let mut blocks = self.blocks.clone();
let mut symbols = self.symbols.clone();
let mut public_key_to_block_id = self.public_key_to_block_id.clone();
let container = self.container.append(keypair, &block, None)?;
symbols.extend(&block.symbols)?;
symbols.public_keys.extend(&block.public_keys)?;
if let Some(index) = block
.external_key
.as_ref()
.and_then(|pk| symbols.public_keys.get(pk))
{
public_key_to_block_id
.entry(index as usize)
.or_default()
.push(self.block_count() + 1);
}
let deser = schema::Block::decode(
&container
.blocks
.last()
.expect("a new block was just added so the list is not empty")
.data[..],
)
.map_err(|e| {
error::Token::Format(error::Format::BlockDeserializationError(format!(
"error deserializing block: {:?}",
e
)))
})?;
blocks.push(deser);
Ok(Biscuit {
root_key_id: self.root_key_id,
authority,
blocks,
symbols,
container,
public_key_to_block_id,
})
}
pub fn third_party_request(&self) -> Result<ThirdPartyRequest, error::Token> {
ThirdPartyRequest::from_container(&self.container)
}
pub fn append_third_party(
&self,
external_key: PublicKey,
response: ThirdPartyBlock,
) -> Result<Self, error::Token> {
let next_keypair = KeyPair::new_with_rng(&mut rand::rngs::OsRng);
self.append_third_party_with_keypair(external_key, response, next_keypair)
}
pub fn append_third_party_with_keypair(
&self,
external_key: PublicKey,
response: ThirdPartyBlock,
next_keypair: KeyPair,
) -> Result<Self, error::Token> {
let ThirdPartyBlockContents {
payload,
external_signature,
} = response.0;
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 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).map_err(|e| {
error::Format::BlockSignatureDeserializationError(format!(
"block external signature deserialization error: {:?}",
e
))
})?;
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 block = schema::Block::decode(&payload[..]).map_err(|e| {
error::Token::Format(error::Format::DeserializationError(format!(
"deserialization error: {:?}",
e
)))
})?;
let external_signature = crypto::ExternalSignature {
public_key: external_key,
signature,
};
let mut symbols = self.symbols.clone();
let mut public_key_to_block_id = self.public_key_to_block_id.clone();
let mut blocks = self.blocks.clone();
let container =
self.container
.append_serialized(&next_keypair, payload, Some(external_signature))?;
let token_block = proto_block_to_token_block(&block, Some(external_key)).unwrap();
for key in &token_block.public_keys.keys {
symbols.public_keys.insert_fallible(key)?;
}
if let Some(index) = token_block
.external_key
.as_ref()
.and_then(|pk| symbols.public_keys.get(pk))
{
public_key_to_block_id
.entry(index as usize)
.or_default()
.push(self.block_count());
}
blocks.push(block);
Ok(Biscuit {
root_key_id: self.root_key_id,
authority: self.authority.clone(),
blocks,
symbols,
container,
public_key_to_block_id,
})
}
pub fn block_symbols(&self, index: usize) -> Result<Vec<String>, error::Token> {
let block = if index == 0 {
&self.authority
} else {
match self.blocks.get(index - 1) {
None => return Err(error::Token::Format(error::Format::InvalidBlockId(index))),
Some(block) => block,
}
};
Ok(block.symbols.clone())
}
pub fn block_public_keys(&self, index: usize) -> Result<PublicKeys, error::Token> {
let block = if index == 0 {
&self.authority
} else {
match self.blocks.get(index - 1) {
None => return Err(error::Token::Format(error::Format::InvalidBlockId(index))),
Some(block) => block,
}
};
let mut public_keys = PublicKeys::new();
for pk in &block.public_keys {
public_keys.insert(&PublicKey::from_proto(pk)?);
}
Ok(public_keys)
}
pub fn block_external_key(&self, index: usize) -> Result<Option<PublicKey>, error::Token> {
let block = if index == 0 {
&self.container.authority
} else {
match self.container.blocks.get(index - 1) {
None => return Err(error::Token::Format(error::Format::InvalidBlockId(index))),
Some(block) => block,
}
};
Ok(block
.external_signature
.as_ref()
.map(|signature| signature.public_key))
}
pub fn block_count(&self) -> usize {
1 + self.blocks.len()
}
pub(crate) fn block(&self, index: usize) -> Result<Block, error::Token> {
let mut block = if index == 0 {
proto_block_to_token_block(
&self.authority,
self.container
.authority
.external_signature
.as_ref()
.map(|ex| ex.public_key),
)
.map_err(error::Token::Format)?
} else {
if index > self.blocks.len() + 1 {
return Err(error::Token::Format(
error::Format::BlockDeserializationError("invalid block index".to_string()),
));
}
proto_block_to_token_block(
&self.blocks[index - 1],
self.container.blocks[index - 1]
.external_signature
.as_ref()
.map(|ex| ex.public_key),
)
.map_err(error::Token::Format)?
};
block.symbols.public_keys = self.symbols.public_keys.clone();
Ok(block)
}
}
impl Display for Biscuit {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let authority = self
.block(0)
.as_ref()
.map(|block| print_block(&self.symbols, block))
.unwrap_or_else(|_| String::new());
let blocks: Vec<_> = (1..self.block_count())
.map(|i| {
self.block(i)
.as_ref()
.map(|block| print_block(&self.symbols, block))
.unwrap_or_else(|_| String::new())
})
.collect();
write!(f, "Biscuit {{\n symbols: {:?}\n public keys: {:?}\n authority: {}\n blocks: [\n {}\n ]\n}}",
self.symbols.strings(),
self.symbols.public_keys.keys.iter().map(|pk| hex::encode(pk.to_bytes())).collect::<Vec<_>>(),
authority,
blocks.join(",\n\t")
)
}
}
fn print_block(symbols: &SymbolTable, block: &Block) -> String {
let facts: Vec<_> = block.facts.iter().map(|f| symbols.print_fact(f)).collect();
let rules: Vec<_> = block.rules.iter().map(|r| symbols.print_rule(r)).collect();
let checks: Vec<_> = block
.checks
.iter()
.map(|r| symbols.print_check(r))
.collect();
let facts = if facts.is_empty() {
String::new()
} else {
format!(
"\n {}\n ",
facts.join(",\n ")
)
};
let rules = if rules.is_empty() {
String::new()
} else {
format!(
"\n {}\n ",
rules.join(",\n ")
)
};
let checks = if checks.is_empty() {
String::new()
} else {
format!(
"\n {}\n ",
checks.join(",\n ")
)
};
format!(
"Block {{\n symbols: {:?}\n version: {}\n context: \"{}\"\n external key: {}\n public keys: {:?}\n scopes: {:?}\n facts: [{}]\n rules: [{}]\n checks: [{}]\n }}",
block.symbols.strings(),
block.version,
block.context.as_deref().unwrap_or(""),
block.external_key.as_ref().map(|k| hex::encode(k.to_bytes())).unwrap_or_else(String::new),
block.public_keys.keys.iter().map(|k | hex::encode(k.to_bytes())).collect::<Vec<_>>(),
block.scopes,
facts,
rules,
checks,
)
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub enum Scope {
Authority,
Previous,
PublicKey(u64),
}
pub trait RootKeyProvider {
fn choose(&self, key_id: Option<u32>) -> Result<PublicKey, error::Format>;
}
impl RootKeyProvider for Box<dyn RootKeyProvider> {
fn choose(&self, key_id: Option<u32>) -> Result<PublicKey, error::Format> {
self.as_ref().choose(key_id)
}
}
impl RootKeyProvider for std::rc::Rc<dyn RootKeyProvider> {
fn choose(&self, key_id: Option<u32>) -> Result<PublicKey, error::Format> {
self.as_ref().choose(key_id)
}
}
impl RootKeyProvider for std::sync::Arc<dyn RootKeyProvider> {
fn choose(&self, key_id: Option<u32>) -> Result<PublicKey, error::Format> {
self.as_ref().choose(key_id)
}
}
impl RootKeyProvider for PublicKey {
fn choose(&self, _: Option<u32>) -> Result<PublicKey, error::Format> {
Ok(*self)
}
}
impl RootKeyProvider for &PublicKey {
fn choose(&self, _: Option<u32>) -> Result<PublicKey, error::Format> {
Ok(**self)
}
}
impl<F: Fn(Option<u32>) -> Result<PublicKey, error::Format>> RootKeyProvider for F {
fn choose(&self, root_key_id: Option<u32>) -> Result<PublicKey, error::Format> {
self(root_key_id)
}
}
#[cfg(test)]
mod tests {
use super::builder::{check, fact, pred, rule, string, var};
use super::builder_ext::BuilderExt;
use super::*;
use crate::builder::CheckKind;
use crate::crypto::KeyPair;
use crate::error::*;
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 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
.build_with_rng(&root, default_symbol_table(), &mut rng)
.unwrap();
println!("biscuit1 (authority): {}", biscuit1);
biscuit1.to_vec().unwrap()
};
println!("generated biscuit token: {} bytes", serialized1.len());
let serialized2 = {
let biscuit1_deser = Biscuit::from(&serialized1, &root.public()).unwrap();
let mut block2 = BlockBuilder::new();
block2
.add_check(rule(
"check1",
&[var("resource")],
&[
pred("resource", &[var("resource")]),
pred("operation", &[string("read")]),
pred("right", &[var("resource"), string("read")]),
],
))
.unwrap();
let keypair2 = KeyPair::new_with_rng(&mut rng);
let biscuit2 = biscuit1_deser
.append_with_keypair(&keypair2, block2)
.unwrap();
println!("biscuit2 (1 check): {}", biscuit2);
biscuit2.to_vec().unwrap()
};
println!("generated biscuit token 2: {} bytes", serialized2.len());
let serialized3 = {
let biscuit2_deser = Biscuit::from(&serialized2, root.public()).unwrap();
let mut block3 = BlockBuilder::new();
block3
.add_check(rule(
"check2",
&[string("file1")],
&[pred("resource", &[string("file1")])],
))
.unwrap();
let keypair3 = KeyPair::new_with_rng(&mut rng);
let biscuit3 = biscuit2_deser
.append_with_keypair(&keypair3, block3)
.unwrap();
biscuit3.to_vec().unwrap()
};
println!("generated biscuit token 3: {} bytes", serialized3.len());
let final_token = Biscuit::from(&serialized3, &root.public()).unwrap();
println!("final token:\n{}", final_token);
{
let mut authorizer = final_token.authorizer().unwrap();
let mut facts = vec![
fact("resource", &[string("file1")]),
fact("operation", &[string("read")]),
];
for fact in facts.drain(..) {
authorizer.add_fact(fact).unwrap();
}
authorizer.allow().unwrap();
let res = authorizer.authorize();
println!("res1: {:?}", res);
res.unwrap();
}
{
let mut authorizer = final_token.authorizer().unwrap();
let mut facts = vec![
fact("resource", &[string("file2")]),
fact("operation", &[string("write")]),
];
for fact in facts.drain(..) {
authorizer.add_fact(fact).unwrap();
}
authorizer.allow().unwrap();
let res = authorizer.authorize();
println!("res2: {:#?}", res);
assert_eq!(res,
Err(Token::FailedLogic(Logic::Unauthorized {
policy: MatchedPolicy::Allow(0),
checks: vec![
FailedCheck::Block(FailedBlockCheck { block_id: 1, check_id: 0, rule: String::from("check if resource($resource), operation(\"read\"), right($resource, \"read\")") }),
FailedCheck::Block(FailedBlockCheck { block_id: 2, check_id: 0, rule: String::from("check if resource(\"file1\")") })
]
})));
}
}
#[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
.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 keypair2 = KeyPair::new_with_rng(&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 res = authorizer.authorize();
println!("res1: {:?}", res);
println!("authorizer:\n{}", authorizer.print_world());
res.unwrap();
}
{
let mut authorizer = biscuit2.authorizer().unwrap();
authorizer.add_fact("resource(\"/folder2/file3\")").unwrap();
authorizer.add_fact("operation(\"read\")").unwrap();
authorizer.allow().unwrap();
let res = authorizer.authorize();
println!("res2: {:?}", res);
assert_eq!(
res,
Err(Token::FailedLogic(Logic::Unauthorized {
policy: MatchedPolicy::Allow(0),
checks: vec![FailedCheck::Block(FailedBlockCheck {
block_id: 1,
check_id: 0,
rule: String::from(
"check if resource($resource), $resource.starts_with(\"/folder1/\")"
)
}),]
}))
);
}
{
let mut authorizer = biscuit2.authorizer().unwrap();
authorizer.add_fact("resource(\"/folder2/file1\")").unwrap();
authorizer.add_fact("operation(\"write\")").unwrap();
let res = authorizer.authorize();
println!("res3: {:?}", res);
assert_eq!(res,
Err(Token::FailedLogic(Logic::NoMatchingPolicy {
checks: vec![
FailedCheck::Block(FailedBlockCheck { block_id: 1, check_id: 0, rule: String::from("check if resource($resource), $resource.starts_with(\"/folder1/\")") }),
FailedCheck::Block(FailedBlockCheck { block_id: 1, check_id: 1, rule: String::from("check if resource($resource_name), operation(\"read\"), right($resource_name, \"read\")") }),
]})));
}
}
#[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();
builder.add_right("file1", "read");
builder.add_right("file2", "read");
let biscuit1 = builder
.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 keypair2 = KeyPair::new_with_rng(&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 res = authorizer.authorize();
println!("res1: {:?}", res);
res.unwrap();
}
{
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 res = authorizer.authorize();
println!("res3: {:?}", res);
assert!(res.is_ok());
}
}
#[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
.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 keypair2 = KeyPair::new_with_rng(&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 res = authorizer.authorize();
println!("res1: {:?}", res);
res.unwrap();
}
let _serialized = biscuit2.to_vec().unwrap();
let sealed = biscuit2.seal().unwrap().to_vec().unwrap();
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 res = authorizer.authorize();
println!("res1: {:?}", res);
res.unwrap();
}
}
#[test]
fn verif_no_blocks() {
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
.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 res = v.authorize();
println!("res: {:?}", res);
assert_eq!(
res,
Err(Token::FailedLogic(Logic::NoMatchingPolicy {
checks: vec![FailedCheck::Authorizer(FailedAuthorizerCheck {
check_id: 0,
rule: String::from("check if right(\"file2\", \"write\")")
}),]
}))
);
}
#[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 biscuit1 = builder
.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 keypair2 = KeyPair::new_with_rng(&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 keypair3 = KeyPair::new_with_rng(&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 other_authorizer = authorizer.clone();
let authorization_res = authorizer.authorize();
println!("authorization result: {:?}", authorization_res);
println!("world:\n{}", authorizer.print_world());
let res2: Result<Vec<builder::Fact>, crate::error::Token> =
authorizer.query_all("key_verif($id) <- key($id)");
println!("res2: {:?}", res2);
let mut res2 = res2
.unwrap()
.iter()
.map(|f| f.to_string())
.collect::<Vec<_>>();
res2.sort();
assert_eq!(
res2,
vec![
"key_verif(0)".to_string(),
"key_verif(1234)".to_string(),
"key_verif(5678)".to_string(),
]
);
let res1: Result<Vec<builder::Fact>, crate::error::Token> =
other_authorizer.query("key_verif($id) <- key($id)");
println!("res1: {:?}", res1);
assert_eq!(
res1.unwrap()
.into_iter()
.map(|f| f.to_string())
.collect::<Vec<_>>(),
vec!["key_verif(0)".to_string()]
);
}
}
#[test]
fn check_head_name() {
let mut rng: StdRng = SeedableRng::seed_from_u64(0);
let root = KeyPair::new_with_rng(&mut rng);
let mut builder = Biscuit::builder();
builder
.add_check(check(
&[pred("resource", &[string("hello")])],
CheckKind::One,
))
.unwrap();
let biscuit1 = builder
.build_with_rng(&root, default_symbol_table(), &mut rng)
.unwrap();
println!("biscuit1 (authority): {}", biscuit1);
let mut block2 = BlockBuilder::new();
block2.add_fact(fact("check1", &[string("test")])).unwrap();
let keypair2 = KeyPair::new_with_rng(&mut rng);
let biscuit2 = biscuit1.append_with_keypair(&keypair2, block2).unwrap();
println!("biscuit2: {}", biscuit2);
{
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();
println!("world:\n{}", authorizer.print_world());
println!("symbols: {:?}", authorizer.symbols);
let res = authorizer.authorize();
println!("res1: {:?}", res);
assert_eq!(
res,
Err(Token::FailedLogic(Logic::NoMatchingPolicy {
checks: vec![FailedCheck::Block(FailedBlockCheck {
block_id: 0,
check_id: 0,
rule: String::from("check if resource(\"hello\")"),
}),]
}))
);
}
}
#[test]
fn bytes_constraints() {
let mut rng: StdRng = SeedableRng::seed_from_u64(0);
let root = KeyPair::new_with_rng(&mut rng);
let mut builder = Biscuit::builder();
builder.add_fact("bytes(hex:0102AB)").unwrap();
let biscuit1 = builder
.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)")
.unwrap();
let keypair2 = KeyPair::new_with_rng(&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)")
.unwrap();
authorizer.allow().unwrap();
let res = authorizer.authorize();
println!("res1: {:?}", res);
res.unwrap();
let res: Vec<(Vec<u8>,)> = authorizer.query("data($0) <- bytes($0)").unwrap();
println!("query result: {:x?}", res);
println!("query result: {:?}", res[0]);
}
#[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 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
.build_with_rng(&root, default_symbol_table(), &mut rng)
.unwrap();
println!("biscuit1 (authority): {}", biscuit1);
biscuit1.to_vec().unwrap()
};
println!("generated biscuit token: {} bytes", serialized1.len());
let serialized2 = {
let biscuit1_deser = Biscuit::from(&serialized1, |_| Ok(root.public())).unwrap();
let mut block2 = BlockBuilder::new();
block2
.add_rule("operation(\"read\") <- operation($any)")
.unwrap();
block2
.add_rule("resource(\"/folder1/\") <- resource($any)")
.unwrap();
block2.add_rule("right($file, $right) <- right($any1, $any2), resource($file), operation($right)")
.unwrap();
let keypair2 = KeyPair::new_with_rng(&mut rng);
let biscuit2 = biscuit1_deser
.append_with_keypair(&keypair2, block2)
.unwrap();
println!("biscuit2 (1 check): {}", biscuit2);
biscuit2.to_vec().unwrap()
};
println!("generated biscuit token 2: {} bytes", serialized2.len());
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)")
.unwrap();
authorizer.deny().unwrap();
let res = authorizer.authorize_with_limits(crate::token::authorizer::AuthorizerLimits {
max_time: Duration::from_secs(1),
..Default::default()
});
println!("res1: {:?}", res);
println!("authorizer:\n{}", authorizer.print_world());
assert!(res.is_err());
}
#[test]
fn check_all() {
let mut rng: StdRng = SeedableRng::seed_from_u64(0);
let root = KeyPair::new_with_rng(&mut rng);
let mut builder = Biscuit::builder();
builder.add_check("check if fact($v), $v < 1").unwrap();
let biscuit1 = builder
.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
.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();
authorizer.allow().unwrap();
let res = authorizer.authorize();
println!("res1: {:?}", res);
res.unwrap();
}
{
let mut authorizer = biscuit2.authorizer().unwrap();
authorizer.add_fact("fact(0)").unwrap();
authorizer.add_fact("fact(1)").unwrap();
authorizer.allow().unwrap();
let res = authorizer.authorize();
println!("res2: {:?}", res);
assert_eq!(
res,
Err(Token::FailedLogic(Logic::Unauthorized {
policy: MatchedPolicy::Allow(0),
checks: vec![FailedCheck::Block(FailedBlockCheck {
block_id: 0,
check_id: 0,
rule: String::from("check all fact($v), $v < 1"),
}),]
}))
);
}
}
}