biscuit_parser/
builder.rs

1//! helper functions and structure to create tokens and blocks
2use std::{
3    collections::{BTreeSet, HashMap},
4    time::{SystemTime, UNIX_EPOCH},
5};
6
7#[cfg(feature = "datalog-macro")]
8use quote::{quote, ToTokens};
9
10/// Builder for a Datalog value
11#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
12pub enum Term {
13    Variable(String),
14    Integer(i64),
15    Str(String),
16    Date(u64),
17    Bytes(Vec<u8>),
18    Bool(bool),
19    Set(BTreeSet<Term>),
20    Parameter(String),
21}
22
23impl From<&Term> for Term {
24    fn from(i: &Term) -> Self {
25        match i {
26            Term::Variable(ref v) => Term::Variable(v.clone()),
27            Term::Integer(ref i) => Term::Integer(*i),
28            Term::Str(ref s) => Term::Str(s.clone()),
29            Term::Date(ref d) => Term::Date(*d),
30            Term::Bytes(ref s) => Term::Bytes(s.clone()),
31            Term::Bool(b) => Term::Bool(*b),
32            Term::Set(ref s) => Term::Set(s.clone()),
33            Term::Parameter(ref p) => Term::Parameter(p.clone()),
34        }
35    }
36}
37
38impl AsRef<Term> for Term {
39    fn as_ref(&self) -> &Term {
40        self
41    }
42}
43
44#[cfg(feature = "datalog-macro")]
45impl ToTokens for Term {
46    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
47        tokens.extend(match self {
48            Term::Variable(v) => quote! { ::biscuit_auth::builder::Term::Variable(#v.to_string()) },
49            Term::Integer(v) => quote! { ::biscuit_auth::builder::Term::Integer(#v) },
50            Term::Str(v) => quote! { ::biscuit_auth::builder::Term::Str(#v.to_string()) },
51            Term::Date(v) => quote! { ::biscuit_auth::builder::Term::Date(#v) },
52            Term::Bool(v) => quote! { ::biscuit_auth::builder::Term::Bool(#v) },
53            Term::Parameter(v) => quote! { ::biscuit_auth::builder::Term::Parameter(#v.to_string()) },
54            Term::Bytes(v) => quote! { ::biscuit_auth::builder::Term::Bytes(<[u8]>::into_vec(Box::new([ #(#v),*]))) },
55            Term::Set(v) => {
56                quote! {{
57                    use std::iter::FromIterator;
58                    ::biscuit_auth::builder::Term::Set(::std::collections::BTreeSet::from_iter(<[::biscuit_auth::builder::Term]>::into_vec(Box::new([ #(#v),*])))) 
59                }}
60            }
61        })
62    }
63}
64
65#[derive(Clone, Debug, Hash, PartialEq, Eq)]
66pub enum Scope {
67    Authority,
68    Previous,
69    PublicKey(PublicKey),
70    Parameter(String),
71}
72
73#[cfg(feature = "datalog-macro")]
74impl ToTokens for Scope {
75    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
76        tokens.extend(match self {
77            Scope::Authority => quote! { ::biscuit_auth::builder::Scope::Authority},
78            Scope::Previous => quote! { ::biscuit_auth::builder::Scope::Previous},
79            Scope::PublicKey(pk) => {
80                let bytes = pk.iter();
81                quote! { ::biscuit_auth::builder::Scope::PublicKey(
82                  ::biscuit_auth::PublicKey::from_bytes(&[#(#bytes),*]).unwrap()
83                )}
84            }
85            Scope::Parameter(v) => {
86                quote! { ::biscuit_auth::builder::Scope::Parameter(#v.to_string())}
87            }
88        })
89    }
90}
91
92/// Builder for a Datalog dicate, used in facts and rules
93#[derive(Debug, Clone, PartialEq, Hash, Eq)]
94pub struct Predicate {
95    pub name: String,
96    pub terms: Vec<Term>,
97}
98
99impl Predicate {
100    pub fn new<T: Into<Vec<Term>>>(name: String, terms: T) -> Predicate {
101        Predicate {
102            name,
103            terms: terms.into(),
104        }
105    }
106}
107
108#[cfg(feature = "datalog-macro")]
109impl ToTokens for Predicate {
110    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
111        let name = &self.name;
112        let terms = self.terms.iter();
113        tokens.extend(quote! {
114            ::biscuit_auth::builder::Predicate::new(
115              #name.to_string(),
116              <[::biscuit_auth::builder::Term]>::into_vec(Box::new([#(#terms),*]))
117            )
118        })
119    }
120}
121
122/// Builder for a Datalog fact
123#[derive(Debug, Clone, PartialEq, Eq)]
124pub struct Fact {
125    pub predicate: Predicate,
126    pub parameters: Option<HashMap<String, Option<Term>>>,
127}
128
129impl Fact {
130    pub fn new<T: Into<Vec<Term>>>(name: String, terms: T) -> Fact {
131        let mut parameters = HashMap::new();
132        let terms: Vec<Term> = terms.into();
133
134        for term in &terms {
135            if let Term::Parameter(name) = &term {
136                parameters.insert(name.to_string(), None);
137            }
138        }
139        Fact {
140            predicate: Predicate::new(name, terms),
141            parameters: Some(parameters),
142        }
143    }
144}
145
146#[cfg(feature = "datalog-macro")]
147impl ToTokens for Fact {
148    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
149        let name = &self.predicate.name;
150        let terms = self.predicate.terms.iter();
151        tokens.extend(quote! {
152            ::biscuit_auth::builder::Fact::new(
153              #name.to_string(),
154              <[::biscuit_auth::builder::Term]>::into_vec(Box::new([#(#terms),*]))
155            )
156        })
157    }
158}
159
160/// Builder for a Datalog expression
161#[derive(Debug, Clone, PartialEq, Eq)]
162pub struct Expression {
163    pub ops: Vec<Op>,
164}
165
166#[cfg(feature = "datalog-macro")]
167impl ToTokens for Expression {
168    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
169        let ops = self.ops.iter();
170        tokens.extend(quote! {
171          ::biscuit_auth::builder::Expression {
172            ops: <[::biscuit_auth::builder::Op]>::into_vec(Box::new([#(#ops),*]))
173          }
174        });
175    }
176}
177
178/// Builder for an expression operation
179#[derive(Debug, Clone, PartialEq, Eq)]
180pub enum Op {
181    Value(Term),
182    Unary(Unary),
183    Binary(Binary),
184}
185
186#[derive(Debug, Clone, PartialEq, Eq)]
187pub enum Unary {
188    Negate,
189    Parens,
190    Length,
191}
192
193#[derive(Debug, Clone, PartialEq, Eq)]
194pub enum Binary {
195    LessThan,
196    GreaterThan,
197    LessOrEqual,
198    GreaterOrEqual,
199    Equal,
200    Contains,
201    Prefix,
202    Suffix,
203    Regex,
204    Add,
205    Sub,
206    Mul,
207    Div,
208    And,
209    Or,
210    Intersection,
211    Union,
212    BitwiseAnd,
213    BitwiseOr,
214    BitwiseXor,
215    NotEqual,
216}
217
218#[cfg(feature = "datalog-macro")]
219impl ToTokens for Op {
220    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
221        tokens.extend(match self {
222            Op::Value(t) => quote! { ::biscuit_auth::builder::Op::Value(#t) },
223            Op::Unary(u) => quote! { ::biscuit_auth::builder::Op::Unary(#u) },
224            Op::Binary(b) => quote! { ::biscuit_auth::builder::Op::Binary(#b) },
225        });
226    }
227}
228
229#[cfg(feature = "datalog-macro")]
230impl ToTokens for Unary {
231    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
232        tokens.extend(match self {
233            Unary::Negate => quote! {::biscuit_auth::datalog::Unary::Negate },
234            Unary::Parens => quote! {::biscuit_auth::datalog::Unary::Parens },
235            Unary::Length => quote! {::biscuit_auth::datalog::Unary::Length },
236        });
237    }
238}
239
240#[cfg(feature = "datalog-macro")]
241impl ToTokens for Binary {
242    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
243        tokens.extend(match self {
244            Binary::LessThan => quote! { ::biscuit_auth::datalog::Binary::LessThan  },
245            Binary::GreaterThan => quote! { ::biscuit_auth::datalog::Binary::GreaterThan  },
246            Binary::LessOrEqual => quote! { ::biscuit_auth::datalog::Binary::LessOrEqual  },
247            Binary::GreaterOrEqual => quote! { ::biscuit_auth::datalog::Binary::GreaterOrEqual  },
248            Binary::Equal => quote! { ::biscuit_auth::datalog::Binary::Equal  },
249            Binary::Contains => quote! { ::biscuit_auth::datalog::Binary::Contains  },
250            Binary::Prefix => quote! { ::biscuit_auth::datalog::Binary::Prefix  },
251            Binary::Suffix => quote! { ::biscuit_auth::datalog::Binary::Suffix  },
252            Binary::Regex => quote! { ::biscuit_auth::datalog::Binary::Regex  },
253            Binary::Add => quote! { ::biscuit_auth::datalog::Binary::Add  },
254            Binary::Sub => quote! { ::biscuit_auth::datalog::Binary::Sub  },
255            Binary::Mul => quote! { ::biscuit_auth::datalog::Binary::Mul  },
256            Binary::Div => quote! { ::biscuit_auth::datalog::Binary::Div  },
257            Binary::And => quote! { ::biscuit_auth::datalog::Binary::And  },
258            Binary::Or => quote! { ::biscuit_auth::datalog::Binary::Or  },
259            Binary::Intersection => quote! { ::biscuit_auth::datalog::Binary::Intersection  },
260            Binary::Union => quote! { ::biscuit_auth::datalog::Binary::Union  },
261            Binary::BitwiseAnd => quote! { ::biscuit_auth::datalog::Binary::BitwiseAnd  },
262            Binary::BitwiseOr => quote! { ::biscuit_auth::datalog::Binary::BitwiseOr  },
263            Binary::BitwiseXor => quote! { ::biscuit_auth::datalog::Binary::BitwiseXor  },
264            Binary::NotEqual => quote! { ::biscuit_auth::datalog::Binary::NotEqual },
265        });
266    }
267}
268
269pub type PublicKey = Vec<u8>;
270
271/// Builder for a Datalog rule
272#[derive(Debug, Clone, PartialEq, Eq)]
273pub struct Rule {
274    pub head: Predicate,
275    pub body: Vec<Predicate>,
276    pub expressions: Vec<Expression>,
277    pub parameters: Option<HashMap<String, Option<Term>>>,
278    pub scopes: Vec<Scope>,
279    pub scope_parameters: Option<HashMap<String, Option<PublicKey>>>,
280}
281
282impl Rule {
283    pub fn new(
284        head: Predicate,
285        body: Vec<Predicate>,
286        expressions: Vec<Expression>,
287        scopes: Vec<Scope>,
288    ) -> Rule {
289        let mut parameters = HashMap::new();
290        let mut scope_parameters = HashMap::new();
291
292        for term in &head.terms {
293            if let Term::Parameter(name) = &term {
294                parameters.insert(name.to_string(), None);
295            }
296        }
297
298        for predicate in &body {
299            for term in &predicate.terms {
300                if let Term::Parameter(name) = &term {
301                    parameters.insert(name.to_string(), None);
302                }
303            }
304        }
305
306        for expression in &expressions {
307            for op in &expression.ops {
308                if let Op::Value(Term::Parameter(name)) = &op {
309                    parameters.insert(name.to_string(), None);
310                }
311            }
312        }
313
314        for scope in &scopes {
315            if let Scope::Parameter(name) = &scope {
316                scope_parameters.insert(name.to_string(), None);
317            }
318        }
319
320        Rule {
321            head,
322            body,
323            expressions,
324            parameters: Some(parameters),
325            scopes,
326            scope_parameters: Some(scope_parameters),
327        }
328    }
329
330    pub fn validate_variables(&self) -> Result<(), String> {
331        let mut head_variables: std::collections::HashSet<String> = self
332            .head
333            .terms
334            .iter()
335            .filter_map(|term| match term {
336                Term::Variable(s) => Some(s.to_string()),
337                _ => None,
338            })
339            .collect();
340
341        for predicate in self.body.iter() {
342            for term in predicate.terms.iter() {
343                if let Term::Variable(v) = term {
344                    head_variables.remove(v);
345                    if head_variables.is_empty() {
346                        return Ok(());
347                    }
348                }
349            }
350        }
351
352        if head_variables.is_empty() {
353            Ok(())
354        } else {
355            Err(format!(
356                    "rule head contains variables that are not used in predicates of the rule's body: {}",
357                    head_variables
358                    .iter()
359                    .map(|s| format!("${}", s))
360                    .collect::<Vec<_>>()
361                    .join(", ")
362                    ))
363        }
364    }
365}
366
367#[cfg(feature = "datalog-macro")]
368impl ToTokens for Rule {
369    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
370        let head = &self.head;
371        let body = self.body.iter();
372        let expressions = self.expressions.iter();
373        let scopes = self.scopes.iter();
374        tokens.extend(quote! {
375          ::biscuit_auth::builder::Rule::new(
376            #head,
377            <[::biscuit_auth::builder::Predicate]>::into_vec(Box::new([#(#body),*])),
378            <[::biscuit_auth::builder::Expression]>::into_vec(Box::new([#(#expressions),*])),
379            <[::biscuit_auth::builder::Scope]>::into_vec(Box::new([#(#scopes),*]))
380          )
381        });
382    }
383}
384
385/// Builder for a Biscuit check
386#[derive(Debug, Clone, PartialEq, Eq)]
387pub struct Check {
388    pub queries: Vec<Rule>,
389    pub kind: CheckKind,
390}
391
392#[derive(Debug, Clone, PartialEq, Eq)]
393pub enum CheckKind {
394    One,
395    All,
396}
397
398#[cfg(feature = "datalog-macro")]
399impl ToTokens for Check {
400    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
401        let queries = self.queries.iter();
402        let kind = &self.kind;
403        tokens.extend(quote! {
404          ::biscuit_auth::builder::Check {
405            queries: <[::biscuit_auth::builder::Rule]>::into_vec(Box::new([#(#queries),*])),
406            kind: #kind,
407          }
408        });
409    }
410}
411
412#[cfg(feature = "datalog-macro")]
413impl ToTokens for CheckKind {
414    fn to_tokens(&self, tokens: &mut quote::__private::TokenStream) {
415        tokens.extend(match self {
416            CheckKind::One => quote! {
417              ::biscuit_auth::builder::CheckKind::One
418            },
419            CheckKind::All => quote! {
420              ::biscuit_auth::builder::CheckKind::All
421            },
422        });
423    }
424}
425
426#[derive(Debug, Clone, PartialEq, Eq)]
427pub enum PolicyKind {
428    Allow,
429    Deny,
430}
431
432#[cfg(feature = "datalog-macro")]
433impl ToTokens for PolicyKind {
434    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
435        tokens.extend(match self {
436            PolicyKind::Allow => quote! {
437              ::biscuit_auth::builder::PolicyKind::Allow
438            },
439            PolicyKind::Deny => quote! {
440              ::biscuit_auth::builder::PolicyKind::Deny
441            },
442        });
443    }
444}
445
446/// Builder for a Biscuit policy
447#[derive(Debug, Clone, PartialEq, Eq)]
448pub struct Policy {
449    pub queries: Vec<Rule>,
450    pub kind: PolicyKind,
451}
452
453#[cfg(feature = "datalog-macro")]
454impl ToTokens for Policy {
455    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
456        let queries = self.queries.iter();
457        let kind = &self.kind;
458        tokens.extend(quote! {
459          ::biscuit_auth::builder::Policy{
460            kind: #kind,
461            queries: <[::biscuit_auth::builder::Rule]>::into_vec(Box::new([#(#queries),*])),
462          }
463        });
464    }
465}
466
467/// creates a new fact
468pub fn fact<I: AsRef<Term>>(name: &str, terms: &[I]) -> Fact {
469    let pred = pred(name, terms);
470    Fact::new(pred.name, pred.terms)
471}
472
473/// creates a predicate
474pub fn pred<I: AsRef<Term>>(name: &str, terms: &[I]) -> Predicate {
475    Predicate {
476        name: name.to_string(),
477        terms: terms.iter().map(|term| term.as_ref().clone()).collect(),
478    }
479}
480
481/// creates a rule
482pub fn rule<T: AsRef<Term>, P: AsRef<Predicate>>(
483    head_name: &str,
484    head_terms: &[T],
485    predicates: &[P],
486) -> Rule {
487    Rule::new(
488        pred(head_name, head_terms),
489        predicates.iter().map(|p| p.as_ref().clone()).collect(),
490        Vec::new(),
491        vec![],
492    )
493}
494
495/// creates a rule with constraints
496pub fn constrained_rule<T: AsRef<Term>, P: AsRef<Predicate>, E: AsRef<Expression>>(
497    head_name: &str,
498    head_terms: &[T],
499    predicates: &[P],
500    expressions: &[E],
501) -> Rule {
502    Rule::new(
503        pred(head_name, head_terms),
504        predicates.iter().map(|p| p.as_ref().clone()).collect(),
505        expressions.iter().map(|c| c.as_ref().clone()).collect(),
506        vec![],
507    )
508}
509
510/// creates a check
511pub fn check<P: AsRef<Predicate>>(predicates: &[P], kind: CheckKind) -> Check {
512    let empty_terms: &[Term] = &[];
513    Check {
514        queries: vec![Rule::new(
515            pred("query", empty_terms),
516            predicates.iter().map(|p| p.as_ref().clone()).collect(),
517            vec![],
518            vec![],
519        )],
520        kind,
521    }
522}
523
524/// creates an integer value
525pub fn int(i: i64) -> Term {
526    Term::Integer(i)
527}
528
529/// creates a string
530pub fn string(s: &str) -> Term {
531    Term::Str(s.to_string())
532}
533
534/// creates a date
535///
536/// internally the date will be stored as seconds since UNIX_EPOCH
537pub fn date(t: &SystemTime) -> Term {
538    let dur = t.duration_since(UNIX_EPOCH).unwrap();
539    Term::Date(dur.as_secs())
540}
541
542/// creates a variable for a rule
543pub fn var(s: &str) -> Term {
544    Term::Variable(s.to_string())
545}
546
547/// creates a variable for a rule
548pub fn variable(s: &str) -> Term {
549    Term::Variable(s.to_string())
550}
551
552/// creates a byte array
553pub fn bytes(s: &[u8]) -> Term {
554    Term::Bytes(s.to_vec())
555}
556
557/// creates a boolean
558pub fn boolean(b: bool) -> Term {
559    Term::Bool(b)
560}
561
562/// creates a set
563pub fn set(s: BTreeSet<Term>) -> Term {
564    Term::Set(s)
565}
566
567/// creates a parameter
568pub fn parameter(p: &str) -> Term {
569    Term::Parameter(p.to_string())
570}