biscuit_auth/token/
builder.rs

1//! helper functions and structure to create tokens and blocks
2
3use std::{
4    collections::BTreeSet,
5    time::{SystemTime, UNIX_EPOCH},
6};
7
8// reexport those because the builder uses the same definitions
9use super::Block;
10use crate::crypto::PublicKey;
11use crate::datalog::SymbolTable;
12pub use crate::datalog::{
13    Binary as DatalogBinary, Expression as DatalogExpression, Op as DatalogOp,
14    Unary as DatalogUnary,
15};
16use crate::error;
17
18mod algorithm;
19mod authorizer;
20mod biscuit;
21mod block;
22mod check;
23mod expression;
24mod fact;
25mod policy;
26mod predicate;
27mod rule;
28mod scope;
29mod term;
30
31pub use algorithm::*;
32pub use authorizer::*;
33pub use biscuit::*;
34pub use block::*;
35pub use check::*;
36pub use expression::*;
37pub use fact::*;
38pub use policy::*;
39pub use predicate::*;
40pub use rule::*;
41pub use scope::*;
42pub use term::*;
43
44pub trait Convert<T>: Sized {
45    fn convert(&self, symbols: &mut SymbolTable) -> T;
46    fn convert_from(f: &T, symbols: &SymbolTable) -> Result<Self, error::Format>;
47    fn translate(
48        f: &T,
49        from_symbols: &SymbolTable,
50        to_symbols: &mut SymbolTable,
51    ) -> Result<T, error::Format> {
52        Ok(Self::convert_from(f, from_symbols)?.convert(to_symbols))
53    }
54}
55
56/// creates a new fact
57pub fn fact<I: AsRef<Term>>(name: &str, terms: &[I]) -> Fact {
58    let pred = pred(name, terms);
59    Fact::new(pred.name, pred.terms)
60}
61
62/// creates a predicate
63pub fn pred<I: AsRef<Term>>(name: &str, terms: &[I]) -> Predicate {
64    Predicate {
65        name: name.to_string(),
66        terms: terms.iter().map(|term| term.as_ref().clone()).collect(),
67    }
68}
69
70/// creates a rule
71pub fn rule<T: AsRef<Term>, P: AsRef<Predicate>>(
72    head_name: &str,
73    head_terms: &[T],
74    predicates: &[P],
75) -> Rule {
76    Rule::new(
77        pred(head_name, head_terms),
78        predicates.iter().map(|p| p.as_ref().clone()).collect(),
79        Vec::new(),
80        vec![],
81    )
82}
83
84/// creates a rule with constraints
85pub fn constrained_rule<T: AsRef<Term>, P: AsRef<Predicate>, E: AsRef<Expression>>(
86    head_name: &str,
87    head_terms: &[T],
88    predicates: &[P],
89    expressions: &[E],
90) -> Rule {
91    Rule::new(
92        pred(head_name, head_terms),
93        predicates.iter().map(|p| p.as_ref().clone()).collect(),
94        expressions.iter().map(|c| c.as_ref().clone()).collect(),
95        vec![],
96    )
97}
98
99/// creates a check
100pub fn check<P: AsRef<Predicate>>(predicates: &[P], kind: CheckKind) -> Check {
101    let empty_terms: &[Term] = &[];
102    Check {
103        queries: vec![Rule::new(
104            pred("query", empty_terms),
105            predicates.iter().map(|p| p.as_ref().clone()).collect(),
106            vec![],
107            vec![],
108        )],
109        kind,
110    }
111}
112
113/// creates an integer value
114pub fn int(i: i64) -> Term {
115    Term::Integer(i)
116}
117
118/// creates a string
119pub fn string(s: &str) -> Term {
120    Term::Str(s.to_string())
121}
122
123/// creates a date
124///
125/// internally the date will be stored as seconds since UNIX_EPOCH
126pub fn date(t: &SystemTime) -> Term {
127    let dur = t.duration_since(UNIX_EPOCH).unwrap();
128    Term::Date(dur.as_secs())
129}
130
131/// creates a variable for a rule
132pub fn var(s: &str) -> Term {
133    Term::Variable(s.to_string())
134}
135
136/// creates a variable for a rule
137pub fn variable(s: &str) -> Term {
138    Term::Variable(s.to_string())
139}
140
141/// creates a byte array
142pub fn bytes(s: &[u8]) -> Term {
143    Term::Bytes(s.to_vec())
144}
145
146/// creates a boolean
147pub fn boolean(b: bool) -> Term {
148    Term::Bool(b)
149}
150
151/// creates a set
152pub fn set(s: BTreeSet<Term>) -> Term {
153    Term::Set(s)
154}
155
156/// creates a parameter
157pub fn parameter(p: &str) -> Term {
158    Term::Parameter(p.to_string())
159}
160
161#[cfg(feature = "datalog-macro")]
162pub enum AnyParam {
163    Term(Term),
164    PublicKey(PublicKey),
165}
166
167#[cfg(feature = "datalog-macro")]
168pub trait ToAnyParam {
169    fn to_any_param(&self) -> AnyParam;
170}
171
172#[cfg(feature = "datalog-macro")]
173impl ToAnyParam for PublicKey {
174    fn to_any_param(&self) -> AnyParam {
175        AnyParam::PublicKey(*self)
176    }
177}
178
179#[cfg(test)]
180mod tests {
181    use std::{collections::HashMap, convert::TryFrom};
182
183    use super::*;
184
185    #[test]
186    fn set_rule_parameters() {
187        let mut rule = Rule::try_from(
188            "fact($var1, {p2}, {p5}) <- f1($var1, $var3), f2({p2}, $var3, {p4}), $var3.starts_with({p2})",
189        )
190        .unwrap();
191        rule.set("p2", "hello").unwrap();
192        rule.set("p4", 0i64).unwrap();
193        rule.set("p4", 1i64).unwrap();
194
195        let mut term_set = BTreeSet::new();
196        term_set.insert(int(0i64));
197        rule.set("p5", term_set).unwrap();
198
199        let s = rule.to_string();
200        assert_eq!(s, "fact($var1, \"hello\", {0}) <- f1($var1, $var3), f2(\"hello\", $var3, 1), $var3.starts_with(\"hello\")");
201    }
202
203    #[test]
204    fn set_closure_parameters() {
205        let mut rule = Rule::try_from("fact(true) <- false || {p1}").unwrap();
206        rule.set_lenient("p1", true).unwrap();
207        println!("{rule:?}");
208        let s = rule.to_string();
209        assert_eq!(s, "fact(true) <- false || true");
210
211        let mut rule = Rule::try_from("fact(true) <- false || {p1}").unwrap();
212        rule.set("p1", true).unwrap();
213        let s = rule.to_string();
214        assert_eq!(s, "fact(true) <- false || true");
215    }
216
217    #[test]
218    fn set_rule_scope_parameters() {
219        let pubkey = PublicKey::from_bytes(
220            &hex::decode("6e9e6d5a75cf0c0e87ec1256b4dfed0ca3ba452912d213fcc70f8516583db9db")
221                .unwrap(),
222            Algorithm::Ed25519,
223        )
224        .unwrap();
225        let mut rule = Rule::try_from(
226            "fact($var1, {p2}) <- f1($var1, $var3), f2({p2}, $var3, {p4}), $var3.starts_with({p2}) trusting {pk}",
227        )
228        .unwrap();
229        rule.set("p2", "hello").unwrap();
230        rule.set("p4", 0i64).unwrap();
231        rule.set("p4", 1i64).unwrap();
232        rule.set_scope("pk", pubkey).unwrap();
233
234        let s = rule.to_string();
235        assert_eq!(s, "fact($var1, \"hello\") <- f1($var1, $var3), f2(\"hello\", $var3, 1), $var3.starts_with(\"hello\") trusting ed25519/6e9e6d5a75cf0c0e87ec1256b4dfed0ca3ba452912d213fcc70f8516583db9db");
236    }
237
238    #[test]
239    fn set_code_parameters() {
240        let mut builder = BlockBuilder::new();
241        let mut params = HashMap::new();
242        params.insert("p1".to_string(), "hello".into());
243        params.insert("p2".to_string(), 1i64.into());
244        params.insert("p3".to_string(), true.into());
245        params.insert("p4".to_string(), "this will be ignored".into());
246        let pubkey = PublicKey::from_bytes(
247            &hex::decode("6e9e6d5a75cf0c0e87ec1256b4dfed0ca3ba452912d213fcc70f8516583db9db")
248                .unwrap(),
249            Algorithm::Ed25519,
250        )
251        .unwrap();
252        let mut scope_params = HashMap::new();
253        scope_params.insert("pk".to_string(), pubkey);
254        builder = builder
255            .code_with_params(
256                r#"fact({p1}, "value");
257             rule($head_var) <- f1($head_var), {p2} > 0 trusting {pk};
258             check if {p3} trusting {pk};
259            "#,
260                params,
261                scope_params,
262            )
263            .unwrap();
264        assert_eq!(
265            format!("{}", &builder),
266            r#"fact("hello", "value");
267rule($head_var) <- f1($head_var), 1 > 0 trusting ed25519/6e9e6d5a75cf0c0e87ec1256b4dfed0ca3ba452912d213fcc70f8516583db9db;
268check if true trusting ed25519/6e9e6d5a75cf0c0e87ec1256b4dfed0ca3ba452912d213fcc70f8516583db9db;
269"#
270        );
271    }
272
273    #[test]
274    fn forbid_unbound_parameters() {
275        let builder = BlockBuilder::new();
276
277        let mut fact = Fact::try_from("fact({p1}, {p4})").unwrap();
278        fact.set("p1", "hello").unwrap();
279        let res = builder.clone().fact(fact);
280        assert_eq!(
281            res.unwrap_err(),
282            error::Token::Language(biscuit_parser::error::LanguageError::Parameters {
283                missing_parameters: vec!["p4".to_string()],
284                unused_parameters: vec![],
285            })
286        );
287        let mut rule = Rule::try_from(
288            "fact($var1, {p2}) <- f1($var1, $var3), f2({p2}, $var3, {p4}), $var3.starts_with({p2})",
289        )
290        .unwrap();
291        rule.set("p2", "hello").unwrap();
292        let res = builder.clone().rule(rule);
293        assert_eq!(
294            res.unwrap_err(),
295            error::Token::Language(biscuit_parser::error::LanguageError::Parameters {
296                missing_parameters: vec!["p4".to_string()],
297                unused_parameters: vec![],
298            })
299        );
300        let mut check = Check::try_from("check if {p4}, {p3}").unwrap();
301        check.set("p3", true).unwrap();
302        let res = builder.clone().check(check);
303        assert_eq!(
304            res.unwrap_err(),
305            error::Token::Language(biscuit_parser::error::LanguageError::Parameters {
306                missing_parameters: vec!["p4".to_string()],
307                unused_parameters: vec![],
308            })
309        );
310    }
311
312    #[test]
313    fn forbid_unbound_parameters_in_set_code() {
314        let builder = BlockBuilder::new();
315        let mut params = HashMap::new();
316        params.insert("p1".to_string(), "hello".into());
317        params.insert("p2".to_string(), 1i64.into());
318        params.insert("p4".to_string(), "this will be ignored".into());
319        let res = builder.code_with_params(
320            r#"fact({p1}, "value");
321             rule($head_var) <- f1($head_var), {p2} > 0;
322             check if {p3};
323            "#,
324            params,
325            HashMap::new(),
326        );
327
328        assert_eq!(
329            res.unwrap_err(),
330            error::Token::Language(biscuit_parser::error::LanguageError::Parameters {
331                missing_parameters: vec!["p3".to_string()],
332                unused_parameters: vec![],
333            })
334        );
335    }
336    #[test]
337    fn empty_set_display() {
338        assert_eq!(Term::Set(BTreeSet::new()).to_string(), "{,}");
339    }
340}