From a93b477081a1f9d6070beda11025c061ee7c90b2 Mon Sep 17 00:00:00 2001 From: Geoffroy Couprie Date: Sun, 29 Jan 2023 15:48:41 +0100 Subject: [PATCH] Refactor authorizer serialization (#127) This separates the `AuthorizerPolicies`, used to load data in an authorizer to perform an authorization, from the `AuthorizerSnapshot`, that serializes an entire authorizer excution, along with the token's data and runtime limits --- biscuit-auth/src/datalog/mod.rs | 10 +- biscuit-auth/src/datalog/origin.rs | 3 +- biscuit-auth/src/format/convert.rs | 144 ++++++++- biscuit-auth/src/format/schema.proto | 47 +++ biscuit-auth/src/format/schema.rs | 79 +++++ biscuit-auth/src/token/authorizer.rs | 300 +++++++++--------- biscuit-auth/src/token/authorizer/snapshot.rs | 237 ++++++++++++++ biscuit-auth/src/token/block.rs | 44 +++ biscuit-auth/src/token/builder.rs | 36 +++ 9 files changed, 721 insertions(+), 179 deletions(-) create mode 100644 biscuit-auth/src/token/authorizer/snapshot.rs diff --git a/biscuit-auth/src/datalog/mod.rs b/biscuit-auth/src/datalog/mod.rs index 697a4f6c..cfd3e52d 100644 --- a/biscuit-auth/src/datalog/mod.rs +++ b/biscuit-auth/src/datalog/mod.rs @@ -46,7 +46,7 @@ impl AsRef for Term { } } -#[derive(Debug, Clone, PartialEq, Hash, Eq)] +#[derive(Debug, Clone, PartialEq, Hash, Eq, PartialOrd, Ord)] pub struct Predicate { pub name: SymbolIndex, pub terms: Vec, @@ -67,7 +67,7 @@ impl AsRef for Predicate { } } -#[derive(Debug, Clone, PartialEq, Hash, Eq)] +#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct Fact { pub predicate: Predicate, } @@ -548,7 +548,7 @@ pub fn match_preds(rule_pred: &Predicate, fact_pred: &Predicate) -> bool { pub struct World { pub facts: FactSet, pub rules: RuleSet, - pub iterations: u32, + pub iterations: u64, } impl World { @@ -675,9 +675,9 @@ impl World { #[derive(Debug, Clone)] pub struct RunLimits { /// maximum number of Datalog facts (memory usage) - pub max_facts: u32, + pub max_facts: u64, /// maximum number of iterations of the rules applications (prevents degenerate rules) - pub max_iterations: u32, + pub max_iterations: u64, /// maximum execution time pub max_time: Duration, } diff --git a/biscuit-auth/src/datalog/origin.rs b/biscuit-auth/src/datalog/origin.rs index e237c5a1..f28b9b8c 100644 --- a/biscuit-auth/src/datalog/origin.rs +++ b/biscuit-auth/src/datalog/origin.rs @@ -8,7 +8,7 @@ use crate::token::Scope; #[derive(Clone, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct Origin { - inner: BTreeSet, + pub(crate) inner: BTreeSet, } impl Origin { @@ -89,6 +89,7 @@ impl TrustedOrigins { origins.insert(0); TrustedOrigins(origins) } + pub fn from_scopes( rule_scopes: &[Scope], default_origins: &TrustedOrigins, diff --git a/biscuit-auth/src/format/convert.rs b/biscuit-auth/src/format/convert.rs index 7fad2e40..a049ca93 100644 --- a/biscuit-auth/src/format/convert.rs +++ b/biscuit-auth/src/format/convert.rs @@ -3,6 +3,7 @@ use self::v2::proto_scope_to_token_scope; use super::schema; +use crate::builder::Convert; use crate::crypto::PublicKey; use crate::datalog::*; use crate::error; @@ -112,32 +113,133 @@ pub fn proto_block_to_token_block( }) } -pub fn authorizer_to_proto_authorizer(input: &AuthorizerPolicies) -> schema::AuthorizerPolicies { - let mut symbols = input.symbols.clone(); - let policies = input - .policies - .iter() - .map(|p| v2::policy_to_proto_policy(p, &mut symbols)) - .collect(); - - schema::AuthorizerPolicies { - symbols: symbols.strings(), +pub fn token_block_to_proto_snapshot_block(input: &Block) -> schema::SnapshotBlock { + schema::SnapshotBlock { + context: input.context.clone(), version: Some(input.version), - facts: input + facts_v2: input .facts .iter() .map(v2::token_fact_to_proto_fact) .collect(), - rules: input + rules_v2: input .rules .iter() .map(v2::token_rule_to_proto_rule) .collect(), - checks: input + checks_v2: input .checks .iter() .map(v2::token_check_to_proto_check) .collect(), + scope: input + .scopes + .iter() + .map(v2::token_scope_to_proto_scope) + .collect(), + external_key: input.external_key.map(|key| key.to_proto()), + } +} + +pub fn proto_snapshot_block_to_token_block( + input: &schema::SnapshotBlock, +) -> Result { + let version = input.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, + }); + } + + let mut facts = vec![]; + let mut rules = vec![]; + let mut checks = vec![]; + let mut scopes = vec![]; + for fact in input.facts_v2.iter() { + facts.push(v2::proto_fact_to_token_fact(fact)?); + } + + for rule in input.rules_v2.iter() { + 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(), + )); + } + + for check in input.checks_v2.iter() { + checks.push(v2::proto_check_to_token_check(check, version)?); + } + for scope in input.scope.iter() { + scopes.push(v2::proto_scope_to_token_scope(scope)?); + } + + let context = input.context.clone(); + + let detected_schema_version = get_schema_version(&facts, &rules, &checks, &scopes); + + detected_schema_version.check_compatibility(version)?; + + let scopes: Result, _> = + input.scope.iter().map(proto_scope_to_token_scope).collect(); + + let external_key = match &input.external_key { + None => None, + Some(key) => Some(PublicKey::from_proto(&key)?), + }; + + Ok(Block { + symbols: SymbolTable::new(), + facts, + rules, + checks, + context, + version, + external_key, + public_keys: PublicKeys::default(), + scopes: scopes?, + }) +} +pub fn authorizer_to_proto_authorizer(input: &AuthorizerPolicies) -> schema::AuthorizerPolicies { + let mut symbols = SymbolTable::default(); + + let facts = input + .facts + .iter() + .map(|f| f.convert(&mut symbols)) + .map(|f| v2::token_fact_to_proto_fact(&f)) + .collect(); + + let rules = input + .rules + .iter() + .map(|r| r.convert(&mut symbols)) + .map(|r| v2::token_rule_to_proto_rule(&r)) + .collect(); + + let checks = input + .checks + .iter() + .map(|c| c.convert(&mut symbols)) + .map(|c| v2::token_check_to_proto_check(&c)) + .collect(); + + let policies = input + .policies + .iter() + .map(|p| v2::policy_to_proto_policy(p, &mut symbols)) + .collect(); + + schema::AuthorizerPolicies { + symbols: symbols.strings(), + version: Some(input.version), + facts, + rules, + checks, policies, } } @@ -162,15 +264,24 @@ pub fn proto_authorizer_to_authorizer( let mut policies = vec![]; for fact in input.facts.iter() { - facts.push(v2::proto_fact_to_token_fact(fact)?); + facts.push(crate::builder::Fact::convert_from( + &v2::proto_fact_to_token_fact(fact)?, + &symbols, + )?); } for rule in input.rules.iter() { - rules.push(v2::proto_rule_to_token_rule(rule, version)?.0); + rules.push(crate::builder::Rule::convert_from( + &v2::proto_rule_to_token_rule(rule, version)?.0, + &symbols, + )?); } for check in input.checks.iter() { - checks.push(v2::proto_check_to_token_check(check, version)?); + checks.push(crate::builder::Check::convert_from( + &v2::proto_check_to_token_check(check, version)?, + &symbols, + )?); } for policy in input.policies.iter() { @@ -179,7 +290,6 @@ pub fn proto_authorizer_to_authorizer( Ok(AuthorizerPolicies { version, - symbols, facts, rules, checks, diff --git a/biscuit-auth/src/format/schema.proto b/biscuit-auth/src/format/schema.proto index 4adfe5c5..bc6cb295 100644 --- a/biscuit-auth/src/format/schema.proto +++ b/biscuit-auth/src/format/schema.proto @@ -181,4 +181,51 @@ message ThirdPartyBlockRequest { message ThirdPartyBlockContents { required bytes payload = 1; required ExternalSignature externalSignature = 2; +} + +message AuthorizerSnapshot { + required RunLimits limits = 1; + required uint64 executionTime = 2; + required AuthorizerWorld world = 3; +} + +message RunLimits { + required uint64 maxFacts = 1; + required uint64 maxIterations = 2; + required uint64 maxTime = 3; +} + +message AuthorizerWorld { + optional uint32 version = 1; + repeated string symbols = 2; + repeated PublicKey publicKeys = 3; + repeated SnapshotBlock blocks = 4; + required SnapshotBlock authorizerBlock = 5; + repeated Policy authorizerPolicies = 6; + repeated GeneratedFacts generatedFacts = 7; + required uint64 iterations = 8; +} + +message Origin { + oneof Content { + Empty authorizer = 1; + uint32 origin = 2; + } +} + +message Empty {} + +message GeneratedFacts { + repeated Origin origins = 1; + repeated FactV2 facts = 2; +} + +message SnapshotBlock { + optional string context = 1; + optional uint32 version = 2; + repeated FactV2 facts_v2 = 3; + repeated RuleV2 rules_v2 = 4; + 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 ad77e49a..869858f9 100644 --- a/biscuit-auth/src/format/schema.rs +++ b/biscuit-auth/src/format/schema.rs @@ -281,3 +281,82 @@ pub struct ThirdPartyBlockContents { #[prost(message, required, tag="2")] pub external_signature: ExternalSignature, } +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct AuthorizerSnapshot { + #[prost(message, required, tag="1")] + pub limits: RunLimits, + #[prost(uint64, required, tag="2")] + pub execution_time: u64, + #[prost(message, required, tag="3")] + pub world: AuthorizerWorld, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RunLimits { + #[prost(uint64, required, tag="1")] + pub max_facts: u64, + #[prost(uint64, required, tag="2")] + pub max_iterations: u64, + #[prost(uint64, required, tag="3")] + pub max_time: u64, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct AuthorizerWorld { + #[prost(uint32, optional, tag="1")] + pub version: ::core::option::Option, + #[prost(string, repeated, tag="2")] + pub symbols: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + #[prost(message, repeated, tag="3")] + pub public_keys: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="4")] + pub blocks: ::prost::alloc::vec::Vec, + #[prost(message, required, tag="5")] + pub authorizer_block: SnapshotBlock, + #[prost(message, repeated, tag="6")] + pub authorizer_policies: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="7")] + pub generated_facts: ::prost::alloc::vec::Vec, + #[prost(uint64, required, tag="8")] + pub iterations: u64, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Origin { + #[prost(oneof="origin::Content", tags="1, 2")] + pub content: ::core::option::Option, +} +/// Nested message and enum types in `Origin`. +pub mod origin { + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Content { + #[prost(message, tag="1")] + Authorizer(super::Empty), + #[prost(uint32, tag="2")] + Origin(u32), + } +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Empty { +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GeneratedFacts { + #[prost(message, repeated, tag="1")] + pub origins: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="2")] + pub facts: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SnapshotBlock { + #[prost(string, optional, tag="1")] + pub context: ::core::option::Option<::prost::alloc::string::String>, + #[prost(uint32, optional, tag="2")] + pub version: ::core::option::Option, + #[prost(message, repeated, tag="3")] + pub facts_v2: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="4")] + pub rules_v2: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="5")] + pub checks_v2: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="6")] + pub scope: ::prost::alloc::vec::Vec, + #[prost(message, optional, tag="7")] + pub external_key: ::core::option::Option, +} diff --git a/biscuit-auth/src/token/authorizer.rs b/biscuit-auth/src/token/authorizer.rs index 02cbbf94..cc23b3d8 100644 --- a/biscuit-auth/src/token/authorizer.rs +++ b/biscuit-auth/src/token/authorizer.rs @@ -7,7 +7,7 @@ use super::builder_ext::{AuthorizerExt, BuilderExt}; use super::{Biscuit, Block}; use crate::builder::{CheckKind, Convert}; use crate::crypto::PublicKey; -use crate::datalog::{self, Origin, RunLimits, TrustedOrigins}; +use crate::datalog::{self, Origin, RunLimits, SymbolTable, TrustedOrigins}; use crate::error; use crate::time::Instant; use crate::token; @@ -23,6 +23,9 @@ use std::{ time::SystemTime, }; +mod snapshot; +pub use snapshot::*; + /// used to check authorization policies on a token /// /// can be created from [Biscuit::authorizer] or [Authorizer::new] @@ -75,45 +78,8 @@ impl Authorizer { } /// creates an `Authorizer` from a serialized [crate::format::schema::AuthorizerPolicies] - pub fn from(slice: &[u8]) -> Result { - let data = crate::format::schema::AuthorizerPolicies::decode(slice).map_err(|e| { - error::Format::DeserializationError(format!("deserialization error: {:?}", e)) - })?; - - let AuthorizerPolicies { - version: _, - symbols, - facts, - rules, - checks, - policies, - } = crate::format::convert::proto_authorizer_to_authorizer(&data)?; - - let mut authorizer = Self::new(); - - for fact in facts { - authorizer - .authorizer_block_builder - .add_fact(Fact::convert_from(&fact, &symbols)?)?; - } - - for rule in rules { - authorizer - .authorizer_block_builder - .add_rule(Rule::convert_from(&rule, &symbols)?)?; - } - - for check in checks { - authorizer - .authorizer_block_builder - .add_check(Check::convert_from(&check, &symbols)?)?; - } - - for policy in policies { - authorizer.policies.push(policy); - } - - Ok(authorizer) + pub fn from(data: &[u8]) -> Result { + AuthorizerPolicies::deserialize(data)?.try_into() } /// add a token to an empty authorizer @@ -136,104 +102,77 @@ impl Authorizer { let mut blocks = Vec::new(); - let mut authority = token.block(0)?; + for i in 0..token.block_count() { + let mut block = token.block(i)?; + + self.add_block(&mut block, i, &token.symbols)?; - let mut authority_origin = Origin::default(); - authority_origin.insert(0); + blocks.push(block); + } - let authority_trusted_origins = TrustedOrigins::from_scopes( - &authority.scopes, + self.blocks = Some(blocks); + self.token_origins = TrustedOrigins::from_scopes( + &[token::Scope::Previous], &TrustedOrigins::default(), - 0, + token.block_count(), &self.public_key_to_block_id, ); - // add authority facts and rules right away to make them available to queries - for fact in authority.facts.iter() { - let fact = Fact::convert_from(fact, &token.symbols)?.convert(&mut self.symbols); - self.world.facts.insert(&authority_origin, fact); + + Ok(()) + } + + fn add_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 { + let mut symbols = block.symbols.clone(); + symbols.public_keys = token_symbols.public_keys.clone(); + symbols + }; + + let mut block_origin = Origin::default(); + block_origin.insert(i); + + let block_trusted_origins = TrustedOrigins::from_scopes( + &block.scopes, + &TrustedOrigins::default(), + i, + &self.public_key_to_block_id, + ); + + for fact in block.facts.iter() { + let fact = Fact::convert_from(fact, &block_symbols)?.convert(&mut self.symbols); + self.world.facts.insert(&block_origin, fact); } - for rule in authority.rules.iter() { - if let Err(_message) = rule.validate_variables(&token.symbols) { + for rule in block.rules.iter() { + if let Err(_message) = rule.validate_variables(&block_symbols) { return Err( - error::Logic::InvalidBlockRule(0, token.symbols.print_rule(rule)).into(), + error::Logic::InvalidBlockRule(0, block_symbols.print_rule(rule)).into(), ); } + let rule = rule.translate(&block_symbols, &mut self.symbols)?; - let rule = rule.translate(&token.symbols, &mut self.symbols)?; let rule_trusted_origins = TrustedOrigins::from_scopes( &rule.scopes, - &authority_trusted_origins, - 0, - &self.public_key_to_block_id, - ); - self.world.rules.insert(0, &rule_trusted_origins, rule); - } - - for check in authority.checks.iter_mut() { - let c = Check::convert_from(check, &token.symbols)?; - *check = c.convert(&mut self.symbols); - } - - blocks.push(authority); - for i in 1..token.block_count() { - let mut block = token.block(i)?; - - // if it is a 3rd party block, it should not affect the main symbol table - let block_symbols = if block.external_key.is_none() { - &token.symbols - } else { - &block.symbols - }; - - let mut block_origin = Origin::default(); - block_origin.insert(i); - - let block_trusted_origins = TrustedOrigins::from_scopes( - &block.scopes, - &TrustedOrigins::default(), + &block_trusted_origins, i, &self.public_key_to_block_id, ); - for fact in block.facts.iter() { - let fact = Fact::convert_from(fact, block_symbols)?.convert(&mut self.symbols); - self.world.facts.insert(&block_origin, fact); - } - - for rule in block.rules.iter() { - if let Err(_message) = rule.validate_variables(block_symbols) { - return Err( - error::Logic::InvalidBlockRule(0, block_symbols.print_rule(rule)).into(), - ); - } - let 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); - } - - for check in block.checks.iter_mut() { - let c = Check::convert_from(check, &block_symbols)?; - *check = c.convert(&mut self.symbols); - } - - blocks.push(block); + self.world.rules.insert(i, &rule_trusted_origins, rule); } - self.blocks = Some(blocks); - self.token_origins = TrustedOrigins::from_scopes( - &[token::Scope::Previous], - &TrustedOrigins::default(), - token.block_count(), - &self.public_key_to_block_id, - ); + for check in block.checks.iter_mut() { + let c = Check::convert_from(check, &block_symbols)?; + *check = c.convert(&mut self.symbols); + } Ok(()) } @@ -241,43 +180,36 @@ impl Authorizer { /// serializes a authorizer's content /// /// you can use this to save a set of policies and load them quickly before - /// verification, or to store a verification context to debug it later - pub fn save(&self) -> Result, error::Token> { - let mut symbols = self.symbols.clone(); - let mut checks: Vec = self + /// verification. This will not store data obtained or generated from a token. + pub fn save(&self) -> Result { + let facts = self + .authorizer_block_builder + .facts + .iter() + .cloned() + .collect(); + + let rules = self + .authorizer_block_builder + .rules + .iter() + .cloned() + .collect(); + + let checks = self .authorizer_block_builder .checks .iter() - .map(|c| c.convert(&mut symbols)) + .cloned() .collect(); - if let Some(blocks) = &self.blocks { - for block in blocks { - checks.extend(block.checks.clone().into_iter()); - } - } - todo!(); - /* - let policies = AuthorizerPolicies { + Ok(AuthorizerPolicies { version: crate::token::MAX_SCHEMA_VERSION, - symbols, - //FIXME - facts: self.world.facts.iter().cloned().collect(), - rules: self.world.rules.clone(), + facts, + rules, checks, policies: self.policies.clone(), - }; - - let proto = crate::format::convert::authorizer_to_proto_authorizer(&policies); - - let mut v = Vec::new(); - - proto - .encode(&mut v) - .map(|_| v) - .map_err(|e| error::Format::SerializationError(format!("serialization error: {:?}", e))) - .map_err(error::Token::Format) - */ + }) } /// Add the rules, facts, checks, and policies of another `Authorizer`. @@ -502,7 +434,7 @@ impl Authorizer { error::Token: From<>::Error>, { let mut limits = self.limits.clone(); - limits.max_iterations -= self.world.iterations as u32; + limits.max_iterations -= self.world.iterations; if self.execution_time >= limits.max_time { return Err(error::Token::RunLimit(error::RunLimit::Timeout)); } @@ -593,7 +525,7 @@ impl Authorizer { error::Token: From<>::Error>, { let mut limits = self.limits.clone(); - limits.max_iterations -= self.world.iterations as u32; + limits.max_iterations -= self.world.iterations; if self.execution_time >= limits.max_time { return Err(error::Token::RunLimit(error::RunLimit::Timeout)); } @@ -700,7 +632,7 @@ impl Authorizer { /// on success, it returns the index of the policy that matched pub fn authorize(&mut self) -> Result { let mut limits = self.limits.clone(); - limits.max_iterations -= self.world.iterations as u32; + limits.max_iterations -= self.world.iterations; if self.execution_time >= limits.max_time { return Err(error::Token::RunLimit(error::RunLimit::Timeout)); } @@ -1175,20 +1107,76 @@ impl std::fmt::Display for Authorizer { } } +impl TryFrom for Authorizer { + type Error = error::Token; + + fn try_from(authorizer_policies: AuthorizerPolicies) -> Result { + let AuthorizerPolicies { + version: _, + facts, + rules, + checks, + policies, + } = authorizer_policies; + + let mut authorizer = Self::new(); + + for fact in facts.into_iter() { + authorizer.authorizer_block_builder.add_fact(fact)?; + } + + for rule in rules.into_iter() { + authorizer.authorizer_block_builder.add_rule(rule)?; + } + + for check in checks.into_iter() { + authorizer.authorizer_block_builder.add_check(check)?; + } + + for policy in policies { + authorizer.policies.push(policy); + } + + Ok(authorizer) + } +} + #[derive(Debug, Clone)] pub struct AuthorizerPolicies { pub version: u32, - /// list of symbols introduced by this block - pub symbols: datalog::SymbolTable, /// list of facts provided by this block - pub facts: Vec, + pub facts: Vec, /// list of rules provided by blocks - pub rules: Vec, + pub rules: Vec, /// checks that the token and ambient data must validate - pub checks: Vec, + pub checks: Vec, pub policies: Vec, } +impl AuthorizerPolicies { + pub fn serialize(&self) -> Result, error::Token> { + let proto = crate::format::convert::authorizer_to_proto_authorizer(self); + + let mut v = Vec::new(); + + proto + .encode(&mut v) + .map(|_| v) + .map_err(|e| error::Format::SerializationError(format!("serialization error: {:?}", e))) + .map_err(error::Token::Format) + } + + pub fn deserialize(data: &[u8]) -> Result { + let data = crate::format::schema::AuthorizerPolicies::decode(data).map_err(|e| { + error::Format::DeserializationError(format!("deserialization error: {:?}", e)) + })?; + + Ok(crate::format::convert::proto_authorizer_to_authorizer( + &data, + )?) + } +} + pub type AuthorizerLimits = RunLimits; impl BuilderExt for Authorizer { diff --git a/biscuit-auth/src/token/authorizer/snapshot.rs b/biscuit-auth/src/token/authorizer/snapshot.rs new file mode 100644 index 00000000..d6762fff --- /dev/null +++ b/biscuit-auth/src/token/authorizer/snapshot.rs @@ -0,0 +1,237 @@ +use std::{collections::HashMap, time::Duration}; + +use crate::{ + builder::{BlockBuilder, Convert, Policy}, + datalog::{Origin, RunLimits, TrustedOrigins}, + error, + format::{ + convert::{ + proto_snapshot_block_to_token_block, token_block_to_proto_snapshot_block, + v2::{ + policy_to_proto_policy, proto_fact_to_token_fact, proto_policy_to_policy, + token_fact_to_proto_fact, + }, + }, + schema::{self, GeneratedFacts}, + }, + token::{default_symbol_table, MAX_SCHEMA_VERSION, MIN_SCHEMA_VERSION}, + PublicKey, +}; + +impl super::Authorizer { + 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 execution_time = Duration::from_nanos(execution_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()); + } + + 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 = super::Authorizer::new(); + authorizer.symbols = symbols; + authorizer.authorizer_block_builder = authorizer_block_builder; + authorizer.policies = policies; + authorizer.limits = limits; + authorizer.execution_time = execution_time; + + let mut public_key_to_block_id: HashMap> = HashMap::new(); + let mut blocks = Vec::new(); + for (i, block) in world.blocks.iter().enumerate() { + let token_symbols = if block.external_key.is_none() { + authorizer.symbols.clone() + } else { + let mut token_symbols = authorizer.symbols.clone(); + token_symbols.public_keys = authorizer.symbols.public_keys.clone(); + token_symbols + }; + + let mut block = proto_snapshot_block_to_token_block(block)?; + + if let Some(key) = block.external_key.as_ref() { + public_key_to_block_id + .entry(authorizer.symbols.public_keys.insert(key) as usize) + .or_default() + .push(i); + } + + authorizer.add_block(&mut block, i, &token_symbols)?; + blocks.push(block); + } + + authorizer.public_key_to_block_id = public_key_to_block_id; + + if !blocks.is_empty() { + authorizer.token_origins = TrustedOrigins::from_scopes( + &[crate::token::Scope::Previous], + &TrustedOrigins::default(), + blocks.len(), + &authorizer.public_key_to_block_id, + ); + authorizer.blocks = Some(blocks); + } + + for GeneratedFacts { origins, facts } in world.generated_facts { + let origin = proto_origin_to_authorizer_origin(&origins)?; + + for fact in &facts { + let fact = proto_fact_to_token_fact(fact)?; + //let fact = Fact::convert_from(&fact, &symbols)?.convert(&mut authorizer.symbols); + authorizer.world.facts.insert(&origin, fact); + } + } + + authorizer.world.iterations = world.iterations; + + Ok(authorizer) + } + + 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)?; + + println!("will serialize authorizer block: {:?}", authorizer_block); + let authorizer_block = token_block_to_proto_snapshot_block(&authorizer_block); + + let blocks = match self.blocks.as_ref() { + None => Vec::new(), + Some(blocks) => blocks + .iter() + .map(|block| { + block + .translate(&self.symbols, &mut symbols) + .map(|block| token_block_to_proto_snapshot_block(&block)) + }) + .collect::, error::Format>>()?, + }; + + let generated_facts = self + .world + .facts + .inner + .iter() + .map(|(origin, facts)| { + Ok(GeneratedFacts { + origins: authorizer_origin_to_proto_origin(origin), + facts: facts + .iter() + .map(|fact| { + Ok(token_fact_to_proto_fact( + &crate::builder::Fact::convert_from(fact, &self.symbols)? + .convert(&mut symbols), + )) + }) + .collect::, error::Format>>()?, + }) + }) + .collect::, error::Format>>()?; + + 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: self.world.iterations, + }; + + Ok(schema::AuthorizerSnapshot { + world, + execution_time: self.execution_time.as_nanos() as u64, + 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, + }, + }) + } +} + +fn authorizer_origin_to_proto_origin(origin: &Origin) -> Vec { + origin + .inner + .iter() + .map(|o| { + if *o == usize::MAX { + schema::Origin { + content: Some(schema::origin::Content::Authorizer(schema::Empty {})), + } + } else { + schema::Origin { + content: Some(schema::origin::Content::Origin(*o as u32)), + } + } + }) + .collect() +} + +fn proto_origin_to_authorizer_origin(origins: &[schema::Origin]) -> Result { + let mut new_origin = Origin::default(); + + for origin in origins { + match origin.content { + Some(schema::origin::Content::Authorizer(schema::Empty {})) => { + new_origin.insert(usize::MAX) + } + Some(schema::origin::Content::Origin(o)) => new_origin.insert(o as usize), + _ => { + return Err(error::Format::DeserializationError( + "invalid origin".to_string(), + )) + } + } + } + + Ok(new_origin) +} diff --git a/biscuit-auth/src/token/block.rs b/biscuit-auth/src/token/block.rs index 26b3dfd2..6ba119e7 100644 --- a/biscuit-auth/src/token/block.rs +++ b/biscuit-auth/src/token/block.rs @@ -1,6 +1,8 @@ use crate::{ + builder::{self, Convert}, crypto::PublicKey, datalog::{Check, Fact, Rule, SymbolTable, Term}, + error, }; use super::{public_keys::PublicKeys, Scope}; @@ -66,4 +68,46 @@ impl Block { res } + + pub(crate) fn translate( + &self, + from_symbols: &SymbolTable, + to_symbols: &mut SymbolTable, + ) -> Result { + Ok(Block { + symbols: SymbolTable::new(), + facts: self + .facts + .iter() + .map(|f| { + builder::Fact::convert_from(f, from_symbols).map(|f| f.convert(to_symbols)) + }) + .collect::, error::Format>>()?, + rules: self + .rules + .iter() + .map(|r| { + builder::Rule::convert_from(r, from_symbols).map(|r| r.convert(to_symbols)) + }) + .collect::, error::Format>>()?, + checks: self + .checks + .iter() + .map(|c| { + builder::Check::convert_from(c, from_symbols).map(|c| c.convert(to_symbols)) + }) + .collect::, error::Format>>()?, + context: self.context.clone(), + version: self.version.clone(), + external_key: self.external_key.clone(), + public_keys: self.public_keys.clone(), + scopes: self + .scopes + .iter() + .map(|s| { + builder::Scope::convert_from(s, from_symbols).map(|s| s.convert(to_symbols)) + }) + .collect::, error::Format>>()?, + }) + } } diff --git a/biscuit-auth/src/token/builder.rs b/biscuit-auth/src/token/builder.rs index 7a3074f9..201e139e 100644 --- a/biscuit-auth/src/token/builder.rs +++ b/biscuit-auth/src/token/builder.rs @@ -223,6 +223,35 @@ impl BlockBuilder { } } + 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) { @@ -380,6 +409,13 @@ impl BiscuitBuilder { 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