Skip to content

Commit 255abf2

Browse files
authored
Merge pull request #191 from biscuit-auth/better-display-for-logic-error
Better display for `errors::Logic`
2 parents 6ad6f12 + eb2a7c4 commit 255abf2

File tree

3 files changed

+112
-10
lines changed

3 files changed

+112
-10
lines changed

biscuit-auth/src/error.rs

Lines changed: 57 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
//! error types
22
//!
33
4-
use std::convert::{From, Infallible};
4+
use std::{
5+
convert::{From, Infallible},
6+
fmt::Display,
7+
};
58
use thiserror::Error;
69

710
/// the global error type for Biscuit
@@ -16,7 +19,7 @@ pub enum Token {
1619
AppendOnSealed,
1720
#[error("tried to seal an already sealed token")]
1821
AlreadySealed,
19-
#[error("authorization failed")]
22+
#[error("authorization failed: {0}")]
2023
FailedLogic(Logic),
2124
#[error("error generating Datalog: {0}")]
2225
Language(biscuit_parser::error::LanguageError),
@@ -168,9 +171,9 @@ pub enum Signature {
168171
#[derive(Error, Clone, Debug, PartialEq, Eq)]
169172
#[cfg_attr(feature = "serde-error", derive(serde::Serialize, serde::Deserialize))]
170173
pub enum Logic {
171-
#[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")]
174+
#[error("a rule provided by a block is producing a fact with unbound variables")]
172175
InvalidBlockRule(u32, String),
173-
#[error("authorization failed")]
176+
#[error("{policy}, and the following checks failed: {}", display_failed_checks(.checks))]
174177
Unauthorized {
175178
/// the policy that matched
176179
policy: MatchedPolicy,
@@ -179,7 +182,7 @@ pub enum Logic {
179182
},
180183
#[error("the authorizer already contains a token")]
181184
AuthorizerNotEmpty,
182-
#[error("no matching policy was found")]
185+
#[error("no matching policy was found, and the following checks failed: {}", display_failed_checks(.checks))]
183186
NoMatchingPolicy {
184187
/// list of checks that failed validation
185188
checks: Vec<FailedCheck>,
@@ -189,22 +192,29 @@ pub enum Logic {
189192
#[derive(Error, Clone, Debug, PartialEq, Eq)]
190193
#[cfg_attr(feature = "serde-error", derive(serde::Serialize, serde::Deserialize))]
191194
pub enum MatchedPolicy {
192-
#[error("an allow policy matched")]
195+
#[error("an allow policy matched (policy index: {0})")]
193196
Allow(usize),
194-
#[error("a deny policy matched")]
197+
#[error("a deny policy matched (policy index: {0})")]
195198
Deny(usize),
196199
}
197200

198201
/// check errors
199202
#[derive(Error, Clone, Debug, PartialEq, Eq)]
200203
#[cfg_attr(feature = "serde-error", derive(serde::Serialize, serde::Deserialize))]
201204
pub enum FailedCheck {
202-
#[error("a check failed in a block")]
205+
#[error("{0}")]
203206
Block(FailedBlockCheck),
204-
#[error("a check provided by the authorizer failed")]
207+
#[error("{0}")]
205208
Authorizer(FailedAuthorizerCheck),
206209
}
207210

211+
fn display_failed_checks(c: &[FailedCheck]) -> String {
212+
c.iter()
213+
.map(|c| c.to_string())
214+
.collect::<Vec<_>>()
215+
.join(", ")
216+
}
217+
208218
#[derive(Clone, Debug, PartialEq, Eq)]
209219
#[cfg_attr(feature = "serde-error", derive(serde::Serialize, serde::Deserialize))]
210220
pub struct FailedBlockCheck {
@@ -214,6 +224,16 @@ pub struct FailedBlockCheck {
214224
pub rule: String,
215225
}
216226

227+
impl Display for FailedBlockCheck {
228+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
229+
write!(
230+
f,
231+
"Check n°{} in block n°{}: {}",
232+
self.check_id, self.block_id, self.rule
233+
)
234+
}
235+
}
236+
217237
#[derive(Clone, Debug, PartialEq, Eq)]
218238
#[cfg_attr(feature = "serde-error", derive(serde::Serialize, serde::Deserialize))]
219239
pub struct FailedAuthorizerCheck {
@@ -222,6 +242,12 @@ pub struct FailedAuthorizerCheck {
222242
pub rule: String,
223243
}
224244

245+
impl Display for FailedAuthorizerCheck {
246+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
247+
write!(f, "Check n°{} in authorizer: {}", self.check_id, self.rule)
248+
}
249+
}
250+
225251
/// Datalog execution errors
226252
#[derive(Error, Clone, Debug, PartialEq, Eq)]
227253
#[cfg_attr(feature = "serde-error", derive(serde::Serialize, serde::Deserialize))]
@@ -283,5 +309,27 @@ mod tests {
283309
format!("{}", Token::Base64(Base64Error::InvalidLength)),
284310
"Cannot decode base64 token: Encoded text cannot have a 6-bit remainder."
285311
);
312+
313+
assert_eq!(
314+
format!(
315+
"{}",
316+
Token::FailedLogic(Logic::Unauthorized {
317+
policy: MatchedPolicy::Allow(0),
318+
checks: vec![
319+
FailedCheck::Authorizer(FailedAuthorizerCheck {
320+
check_id: 0,
321+
rule: "check if false".to_string()
322+
}),
323+
FailedCheck::Block(FailedBlockCheck {
324+
block_id: 0,
325+
check_id: 0,
326+
rule: "check if false".to_string()
327+
})
328+
]
329+
})
330+
)
331+
.to_string(),
332+
"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"
333+
);
286334
}
287335
}

biscuit-auth/src/token/authorizer.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1378,6 +1378,8 @@ impl AuthorizerExt for Authorizer {
13781378
mod tests {
13791379
use std::time::Duration;
13801380

1381+
use token::{public_keys::PublicKeys, DATALOG_3_1};
1382+
13811383
use crate::{
13821384
builder::{Algorithm, BiscuitBuilder, BlockBuilder},
13831385
KeyPair,
@@ -1815,4 +1817,56 @@ allow if true;
18151817
let authorizer = Authorizer::new();
18161818
assert_eq!("", authorizer.to_string())
18171819
}
1820+
1821+
#[test]
1822+
fn rule_validate_variables() {
1823+
let mut authorizer = Authorizer::new();
1824+
let mut syms = SymbolTable::new();
1825+
let rule_name = syms.insert("test");
1826+
let pred_name = syms.insert("pred");
1827+
let rule = datalog::rule(
1828+
rule_name,
1829+
&[datalog::var(&mut syms, "unbound")],
1830+
&[datalog::pred(pred_name, &[datalog::var(&mut syms, "any")])],
1831+
);
1832+
let mut block = Block {
1833+
symbols: syms.clone(),
1834+
facts: vec![],
1835+
rules: vec![rule],
1836+
checks: vec![],
1837+
context: None,
1838+
version: DATALOG_3_1,
1839+
external_key: None,
1840+
public_keys: PublicKeys::new(),
1841+
scopes: vec![],
1842+
};
1843+
1844+
assert_eq!(
1845+
authorizer
1846+
.load_and_translate_block(&mut block, 0, &syms)
1847+
.unwrap_err(),
1848+
error::Token::FailedLogic(error::Logic::InvalidBlockRule(
1849+
0,
1850+
"test($unbound) <- pred($any)".to_string()
1851+
))
1852+
);
1853+
1854+
// broken rules directly added to the authorizer currently don’t trigger any error, but silently fail to generate facts when they match
1855+
authorizer
1856+
.add_rule(builder::rule(
1857+
"test",
1858+
&[var("unbound")],
1859+
&[builder::pred("pred", &[builder::var("any")])],
1860+
))
1861+
.unwrap();
1862+
let res: Vec<(String,)> = authorizer
1863+
.query(builder::rule(
1864+
"output",
1865+
&[builder::string("x")],
1866+
&[builder::pred("test", &[builder::var("any")])],
1867+
))
1868+
.unwrap();
1869+
1870+
assert_eq!(res, vec![]);
1871+
}
18181872
}

biscuit-capi/tests/capi.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ biscuit append error? (null)
114114
authorizer creation error? (null)
115115
authorizer add check error? (null)
116116
authorizer add policy error? (null)
117-
authorizer error(code = 21): authorization failed
117+
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")
118118
failed checks (2):
119119
Authorizer check 0: check if right("efgh")
120120
Block 1, check 0: check if operation("read")

0 commit comments

Comments
 (0)