biscuit_auth/token/
builder.rs

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