accumulo_access/
lib.rs

1// Copyright 2024 Lars Wilhelmsen <sral-backwards@sral.org>. All rights reserved.
2// Use of this source code is governed by the MIT or Apache-2.0 license that can be found in the LICENSE_MIT or LICENSE_APACHE files.
3
4mod lexer;
5mod parser;
6#[cfg(feature = "caching")]
7pub mod caching;
8pub mod authorization_expression;
9mod authorizations;
10
11pub use crate::lexer::Lexer;
12pub use crate::parser::Parser;
13pub use crate::parser::ParserError;
14pub use crate::authorizations::Authorizations;
15pub use crate::authorization_expression::AuthorizationExpression;
16
17pub enum JsonError {
18    ParsingFailed(String),
19}
20
21// implement Display for JsonError 
22impl std::fmt::Display for JsonError {
23    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
24        match self {
25            JsonError::ParsingFailed(e) => write!(f, "{}", e),
26        }
27    }
28}
29
30pub struct AccessEvaluator {}
31
32/// Checks if the given set of access tokens authorizes access to the resource which protection is described by the given expression.
33///
34/// Arguments:
35/// * `expression` - The expression to parse and evaluate.
36/// * `tokens` - The set of access tokens to check.
37///
38/// Returns:
39/// * `Ok(true)` if the expression is valid and the tokens are authorized.
40/// * `Ok(false)` if the expression is valid and the tokens are not authorized.
41/// * `Err(ParserError)` if the expression is invalid.
42///
43/// # Examples
44/// ```
45/// use accumulo_access::check_authorization;
46///
47///    let expression = "label1|label5";
48///    let tokens = &Vec::from([
49///      String::from("label1"),
50///      String::from("label5"),
51///      String::from("label 🕺"),
52///    ]);
53///
54///    let result = match check_authorization(expression, tokens) {
55///     Ok(result) => {
56///         assert_eq!(result, true);
57///     }
58///     Err(_) => panic!("Unexpected error"),
59///    };
60/// ```
61pub fn check_authorization(expression: &str, tokens: &[String]) -> Result<bool, ParserError> {
62    let lexer: Lexer<'_> = Lexer::new(expression);
63    let mut parser = Parser::new(lexer);
64
65    let auth_expr = parser.parse()?;
66    let authorized_labels = tokens.iter().cloned().collect();
67    let result = auth_expr.evaluate(&authorized_labels);
68    Ok(result)
69}
70
71// Prepares a function that can be used to check if the given set of access tokens authorizes access to the resource which protection is described by the given expression.
72pub fn prepare_authorization_csv(tokens: String) -> impl Fn(String) -> Result<bool, ParserError> {
73    let tokens: Vec<String> = tokens.split(',').map(|s| s.to_string()).collect();
74    move |expression| check_authorization(expression.as_str(), &tokens)
75}
76
77/// Checks if the given set of access tokens authorizes access to the resource which protection is described by the given expression.
78pub fn check_authorization_csv(
79    expression: String,
80    tokens: String,
81) -> Result<bool, ParserError> {
82    prepare_authorization_csv(tokens)(expression)
83}
84
85pub fn expression_to_json_string(expression: &str) -> Result<String, ParserError> {
86    let lexer: Lexer<'_> = Lexer::new(expression);
87    let mut parser = Parser::new(lexer);
88
89    let auth_expr = parser.parse();
90    
91    match auth_expr {
92        Ok(auth_expr) => {
93            Ok(auth_expr.to_json_str())
94        }
95        Err(e) => Err(e),
96    }
97}
98
99pub fn expression_to_json(expression: &str) -> Result<serde_json::Value, JsonError> {
100    let lexer: Lexer<'_> = Lexer::new(expression);
101    let mut parser = Parser::new(lexer);
102    let expr = parser.parse();
103    match expr {
104        Ok(expr) => Ok(expr.to_json()),
105        Err(e) => Err(JsonError::ParsingFailed(format!("{:?}", e))),
106    }
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112    use rstest::rstest;
113
114    #[rstest]
115    #[case("", "", true)]
116    #[case("label1", "label1", true)]
117    #[case("label1|label2", "label1", true)]
118    #[case("label1&label2", "label1", false)]
119    #[case("label1&label2", "label1,label2", true)]
120    #[case("label1&(label2|label3)", "label1", false)]
121    #[case("label1&(label2|label3)", "label1,label3", true)]
122    #[case("label1&(label2|label3)", "label1,label2", true)]
123    #[case("(label2|label3)", "label1", false)]
124    #[case("(label2|label3)", "label2", true)]
125    #[case("(label2&label3)", "label2", false)]
126    #[case("((label2|label3))", "label2", true)]
127    #[case("((label2&label3))", "label2", false)]
128    #[case("(((((label2&label3)))))", "label2", false)]
129    #[case("\"a b c\"", "\"a b c\"", true)]
130    #[case("\"abc!12\"&\"abc\\\\xyz\"&GHI", "abc\\xyz,abc!12", false)] // Taken from SPECIFICATION.md
131    fn test_check_authorization(
132        #[case] expr: impl AsRef<str>,
133        #[case] authorized_tokens: impl AsRef<str>,
134        #[case] expected: bool,
135    ) {
136        let authorized_tokens: Vec<String> = AsRef::as_ref(&authorized_tokens).split(',')
137            .map(|s| s.to_string().replace(['"','\''], ""))
138            .collect();
139
140        let result = check_authorization(expr.as_ref(), &authorized_tokens).unwrap();
141        assert_eq!(result, expected);
142    }
143
144    #[test]
145    fn normalization_test() {
146        let expression = "A&B&A&(D|E)&(E|D)"; // -> A&B&(D|E)
147        let lexer: Lexer<'_> = Lexer::new(expression);
148        let mut parser = Parser::new(lexer);
149
150        let mut auth_expr = parser.parse().unwrap();
151        auth_expr.normalize();
152        let expected = AuthorizationExpression::ConjunctionOf(
153            vec![
154                AuthorizationExpression::AccessToken("A".to_string()),
155                AuthorizationExpression::AccessToken("B".to_string()),
156                AuthorizationExpression::DisjunctionOf(vec![
157                    AuthorizationExpression::AccessToken("D".to_string()),
158                    AuthorizationExpression::AccessToken("E".to_string())
159                ])
160            ],
161        );
162        assert_eq!(expected, auth_expr)
163    }
164}