accumulo_access/
authorization_expression.rs

1use std::cmp::Ordering;
2use std::collections::HashSet;
3use std::fmt::{Display, Formatter};
4use std::hash::{Hash, Hasher};
5
6#[derive(Debug, Clone)]
7pub enum AuthorizationExpression {
8    /// A conjunction of multiple access tokens or scopes.
9    ConjunctionOf(Vec<AuthorizationExpression>),
10    /// A disjunction of multiple access tokens or scopes.
11    DisjunctionOf(Vec<AuthorizationExpression>),
12    /// An access token.
13    AccessToken(String),
14    /// A nil expression (empty string).
15    Nil
16}
17
18impl Hash for AuthorizationExpression {
19    fn hash<H: Hasher>(&self, state: &mut H) {
20        match self {
21            AuthorizationExpression::ConjunctionOf(nodes) => {
22                let mut sorted = nodes.clone();
23                sorted.sort();
24                state.write_u8(0);
25                sorted.hash(state);
26            }
27            AuthorizationExpression::DisjunctionOf(nodes) => {
28                let mut sorted = nodes.clone();
29                sorted.sort();
30                state.write_u8(1);
31                sorted.hash(state);
32            }
33            AuthorizationExpression::AccessToken(token) => {
34                state.write_u8(2);
35                token.hash(state);
36            }
37            AuthorizationExpression::Nil => {
38                state.write_u8(3);
39            }
40        }
41    }
42}
43
44impl Ord for AuthorizationExpression {
45    fn cmp(&self, other: &Self) -> Ordering {
46        match (self, other) {
47            (AuthorizationExpression::ConjunctionOf(a), AuthorizationExpression::DisjunctionOf(b)) => a.cmp(b),
48            (AuthorizationExpression::DisjunctionOf(a), AuthorizationExpression::ConjunctionOf(b)) => a.cmp(b),
49            (AuthorizationExpression::ConjunctionOf(a), AuthorizationExpression::ConjunctionOf(b)) => a.cmp(b),
50            (AuthorizationExpression::DisjunctionOf(a), AuthorizationExpression::DisjunctionOf(b)) => a.cmp(b),
51            (AuthorizationExpression::AccessToken(a), AuthorizationExpression::AccessToken(b)) => a.cmp(b),
52            (AuthorizationExpression::AccessToken(_), _) => Ordering::Greater,
53            (AuthorizationExpression::ConjunctionOf(_), AuthorizationExpression::AccessToken(_)) => Ordering::Less,
54            (AuthorizationExpression::DisjunctionOf(_), AuthorizationExpression::AccessToken(_)) => Ordering::Less,
55            (_, AuthorizationExpression::AccessToken(_)) => Ordering::Less,
56            (_, AuthorizationExpression::Nil) => Ordering::Equal,
57            (AuthorizationExpression::Nil, _) => Ordering::Equal
58        }
59    }
60}
61
62impl PartialOrd for AuthorizationExpression {
63    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
64        Some(self.cmp(other))
65    }
66}
67
68impl Eq for AuthorizationExpression {}
69
70impl PartialEq for AuthorizationExpression {
71    fn eq(&self, other: &Self) -> bool {
72        match (self, other) {
73            (AuthorizationExpression::ConjunctionOf(a), AuthorizationExpression::ConjunctionOf(b)) => {
74                let self_set: HashSet<_> = a.iter().collect();
75                let other_set: HashSet<_> = b.iter().collect();
76                self_set == other_set
77            },
78            (AuthorizationExpression::DisjunctionOf(a), AuthorizationExpression::DisjunctionOf(b)) => {
79                let self_set: HashSet<_> = a.iter().collect();
80                let other_set: HashSet<_> = b.iter().collect();
81                self_set == other_set
82            },
83            (AuthorizationExpression::AccessToken(a), AuthorizationExpression::AccessToken(b)) => a == b,
84            (AuthorizationExpression::Nil, AuthorizationExpression::Nil) => true,
85            _ => false,
86        }
87    }
88}
89
90impl Display for AuthorizationExpression {
91    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
92        f.write_str(self.to_expression_str().as_str())
93    }
94}
95
96impl AuthorizationExpression {
97    /// Create a new `AuthorizationExpression` from a JSON value.
98    /// 
99    /// # Arguments
100    /// json - The JSON value to parse.
101    /// 
102    /// # Returns
103    /// A new `AuthorizationExpression` instance.
104    /// 
105    /// # Example
106    /// ```
107    /// use accumulo_access::AuthorizationExpression;
108    /// let json = serde_json::json!({
109    ///   "and": [
110    ///    "A",
111    ///   {
112    ///     "or": [
113    ///       "B",
114    ///      "C"
115    ///    ]
116    ///  }
117    /// ]
118    /// });
119    /// let expr = AuthorizationExpression::from_json(&json).unwrap();
120    /// ```
121    pub fn from_json(json: &serde_json::Value) -> Result<Self, String> {
122        match json {
123            serde_json::Value::Object(obj) => {
124                if obj.contains_key("and") {
125                    let and = obj.get("and").unwrap().as_array().unwrap();
126                    let mut nodes = Vec::new();
127                    for node in and {
128                        nodes.push(AuthorizationExpression::from_json(node)?);
129                    }
130                    Ok(AuthorizationExpression::ConjunctionOf(nodes))
131                } else if obj.contains_key("or") {
132                    let or = obj.get("or").unwrap().as_array().unwrap();
133                    let mut nodes = Vec::new();
134                    for node in or {
135                        nodes.push(AuthorizationExpression::from_json(node)?);
136                    }
137                    Ok(AuthorizationExpression::DisjunctionOf(nodes))
138                } else {
139                    Err("Invalid JSON object".to_string())
140                }
141            }
142            serde_json::Value::String(token) => Ok(AuthorizationExpression::AccessToken(token.to_string())),
143            _ => Err("Invalid JSON value".to_string()),
144        }
145    }
146
147    /// Evaluate the expression with the given set of authorizations.
148    /// Returns `true` if the authorizations are valid, `false` otherwise.
149    /// 
150    /// # Arguments
151    /// authorizations - The set of authorizations to check.
152    /// 
153    /// # Example
154    /// ```
155    /// use std::collections::HashSet;
156    /// use accumulo_access::AuthorizationExpression;
157    /// let expr = AuthorizationExpression::ConjunctionOf(vec![
158    ///    AuthorizationExpression::AccessToken("A".to_string()),
159    ///   AuthorizationExpression::DisjunctionOf(vec![
160    ///      AuthorizationExpression::AccessToken("B".to_string()),
161    ///     AuthorizationExpression::AccessToken("C".to_string()),
162    /// ]),
163    /// ]);
164    /// let authorizations = HashSet::from([
165    ///    "A".to_string(),
166    ///   "B".to_string(),
167    /// ]);
168    /// assert_eq!(expr.evaluate(&authorizations), true);
169    /// ```
170    pub fn evaluate(&self, authorizations: &HashSet<String>) -> bool {
171        match self {
172            AuthorizationExpression::Nil => true,
173
174            AuthorizationExpression::ConjunctionOf(nodes) =>
175                nodes.iter().all(|node| node.evaluate(authorizations)),
176
177            AuthorizationExpression::DisjunctionOf(nodes) =>
178                nodes.iter().any(|node| node.evaluate(authorizations)),
179
180            AuthorizationExpression::AccessToken(token) => authorizations.contains(token),
181        }
182    }
183
184
185    /// Create a JSON representation of the expression tree.
186    /// 
187    /// # Returns
188    /// A JSON value representing the expression tree.
189    /// 
190    /// # Example
191    /// ```
192    /// use accumulo_access::AuthorizationExpression;
193    /// let expr = AuthorizationExpression::ConjunctionOf(vec![
194    ///   AuthorizationExpression::AccessToken("A".to_string()),
195    ///  AuthorizationExpression::AccessToken("B".to_string()),
196    /// ]);
197    /// let json = expr.to_json();
198    /// assert_eq!(json, serde_json::json!({"and": ["A", "B"]}));
199    pub fn to_json(&self) -> serde_json::Value {
200        match self {
201            AuthorizationExpression::Nil => serde_json::Value::Null,
202            AuthorizationExpression::ConjunctionOf(nodes) => {
203                let mut json = serde_json::json!({"and": []});
204                let and = json.as_object_mut().unwrap().get_mut("and").unwrap();
205                for node in nodes {
206                    and.as_array_mut().unwrap().push(node.to_json());
207                }
208                json
209            }
210            AuthorizationExpression::DisjunctionOf(nodes) => {
211                let mut json = serde_json::json!({"or": []});
212                let or = json.as_object_mut().unwrap().get_mut("or").unwrap();
213                for node in nodes {
214                    or.as_array_mut().unwrap().push(node.to_json());
215                }
216                json
217            }
218            AuthorizationExpression::AccessToken(token) => serde_json::json!(token),
219        }
220    }
221
222    /// Create a JSON string representation of the expression tree.
223    /// 
224    /// # Returns
225    /// A JSON string representing the expression tree.
226    /// 
227    /// # Example
228    /// ```
229    /// use accumulo_access::AuthorizationExpression;
230    /// let expr = AuthorizationExpression::ConjunctionOf(vec![
231    ///  AuthorizationExpression::AccessToken("A".to_string()),
232    ///  AuthorizationExpression::AccessToken("B".to_string()),
233    /// ]);
234    /// 
235    /// let json_str = expr.to_json_str();
236    /// assert_eq!(json_str, "{\"and\":[\"A\",\"B\"]}");
237    pub fn to_json_str(&self) -> String {
238        self.to_json().to_string()
239    }
240
241    /// Create a string representation of the expression tree.
242    /// 
243    /// # Returns
244    /// A string representing the expression tree.
245    /// 
246    /// # Example
247    /// ```
248    /// use accumulo_access::AuthorizationExpression;
249    /// let expr1 = AuthorizationExpression::ConjunctionOf(vec![
250    /// AuthorizationExpression::AccessToken("A".to_string()),
251    /// AuthorizationExpression::AccessToken("B".to_string()),
252    /// ]);
253    /// 
254    /// let expr_str = expr1.to_expression_str();
255    /// assert_eq!(expr_str, "A&B");    ///
256    ///
257    /// let expr2 = AuthorizationExpression::DisjunctionOf(vec![
258    /// AuthorizationExpression::AccessToken("A".to_string()),
259    /// AuthorizationExpression::AccessToken("B".to_string()),
260    /// ]);
261    ///
262    /// let expr_str = expr2.to_expression_str();
263    /// assert_eq!(expr_str, "A|B");
264    pub fn to_expression_str(&self) -> String {
265        // serialize the expression tree back as a valid Accumulo Security Expression including parentheses, optional quotes, '&' and '|'.
266        match self {
267            AuthorizationExpression::Nil => String::new(),
268            AuthorizationExpression::ConjunctionOf(nodes) => {
269                let mut expression = String::new();
270                for node in nodes {
271                    expression.push_str(&node.to_expression_str());
272                    expression.push('&');
273                }
274                expression.pop();
275                expression
276            }
277            AuthorizationExpression::DisjunctionOf(nodes) => {
278                let mut expression = String::new();
279                for node in nodes {
280                    expression.push_str(&node.to_expression_str());
281                    expression.push('|');
282                }
283                expression.pop();
284                expression
285            }
286            AuthorizationExpression::AccessToken(token) => token.clone(),
287        }
288    }
289
290    /// Normalize the expression tree by sorting and deduplicating the nodes.
291    /// 
292    /// # Example
293    /// ```
294    /// use accumulo_access::AuthorizationExpression;
295    /// let mut expr = AuthorizationExpression::ConjunctionOf(vec![
296    /// AuthorizationExpression::AccessToken("B".to_string()),
297    /// AuthorizationExpression::AccessToken("A".to_string()),
298    /// AuthorizationExpression::AccessToken("B".to_string()),
299    /// AuthorizationExpression::DisjunctionOf(vec![
300    /// AuthorizationExpression::AccessToken("C".to_string()),
301    /// AuthorizationExpression::AccessToken("D".to_string()),
302    /// AuthorizationExpression::AccessToken("D".to_string())]
303    /// )]);
304    /// expr.normalize();
305    /// let expected = AuthorizationExpression::ConjunctionOf(vec![
306    /// AuthorizationExpression::AccessToken("A".to_string()),
307    /// AuthorizationExpression::AccessToken("B".to_string()),
308    /// AuthorizationExpression::DisjunctionOf(vec![
309    /// AuthorizationExpression::AccessToken("C".to_string()),
310    /// AuthorizationExpression::AccessToken("D".to_string())]
311    /// )]);
312    /// 
313    /// assert_eq!(expr, expected);
314    pub fn normalize(&mut self) {
315        match self {
316            AuthorizationExpression::Nil => {},
317
318            AuthorizationExpression::ConjunctionOf(nodes) => {
319                nodes.sort();
320                nodes.dedup();
321                for node in nodes {
322                    node.normalize();
323                }
324            }
325            AuthorizationExpression::DisjunctionOf(nodes) => {
326                nodes.sort();
327                nodes.dedup();
328                for node in nodes {
329                    node.normalize();
330                }
331            }
332            AuthorizationExpression::AccessToken(_) => {}
333        }
334    }
335}
336
337// test for normalize
338#[cfg(test)]
339mod tests {
340    use super::*;
341
342    #[test]
343    fn some_basic_equality_and_ordering_tests() {
344        assert_eq!(AuthorizationExpression::AccessToken("A".to_string()), AuthorizationExpression::AccessToken("A".to_string()));
345        assert_ne!(AuthorizationExpression::AccessToken("A".to_string()), AuthorizationExpression::AccessToken("B".to_string()));
346
347        assert_eq!(AuthorizationExpression::ConjunctionOf(vec![
348            AuthorizationExpression::AccessToken("A".to_string()),
349            AuthorizationExpression::AccessToken("B".to_string()),
350        ]), AuthorizationExpression::ConjunctionOf(vec![
351            AuthorizationExpression::AccessToken("B".to_string()),
352            AuthorizationExpression::AccessToken("A".to_string()),
353        ]));
354
355        assert_eq!(AuthorizationExpression::DisjunctionOf(vec![
356            AuthorizationExpression::AccessToken("A".to_string()),
357            AuthorizationExpression::AccessToken("B".to_string()),
358        ]), AuthorizationExpression::DisjunctionOf(vec![
359            AuthorizationExpression::AccessToken("B".to_string()),
360            AuthorizationExpression::AccessToken("A".to_string()),
361        ]));
362    }
363
364    #[test]
365    fn new_expr_from_json() {
366        let json = serde_json::json!({
367            "and": [
368                "A",
369                {
370                    "or": [
371                        "B",
372                        "C"
373                    ]
374                }
375            ]
376        });
377        let expr = AuthorizationExpression::from_json(&json).unwrap();
378        assert_eq!(expr, AuthorizationExpression::ConjunctionOf(vec![
379            AuthorizationExpression::AccessToken("A".to_string()),
380            AuthorizationExpression::DisjunctionOf(vec![
381                AuthorizationExpression::AccessToken("B".to_string()),
382                AuthorizationExpression::AccessToken("C".to_string()),
383            ]),
384        ]));
385    }
386
387    #[test]
388    fn test_normalize1() {
389        let mut expr = AuthorizationExpression::ConjunctionOf(vec![
390            AuthorizationExpression::AccessToken("B".to_string()),
391            AuthorizationExpression::AccessToken("A".to_string()),
392            AuthorizationExpression::AccessToken("B".to_string()),
393            AuthorizationExpression::AccessToken("B".to_string()),
394            AuthorizationExpression::DisjunctionOf(vec![
395                AuthorizationExpression::AccessToken("C".to_string()),
396                AuthorizationExpression::AccessToken("D".to_string()),
397                AuthorizationExpression::AccessToken("D".to_string()),
398                AuthorizationExpression::AccessToken("D".to_string()),
399                AuthorizationExpression::AccessToken("D".to_string()),
400                AuthorizationExpression::AccessToken("D".to_string()),
401            ]),
402        ]);
403
404        expr.normalize();
405
406        assert_eq!(expr, AuthorizationExpression::ConjunctionOf(vec![
407            AuthorizationExpression::AccessToken("B".to_string()),
408            AuthorizationExpression::AccessToken("A".to_string()),
409            AuthorizationExpression::DisjunctionOf(vec![
410                AuthorizationExpression::AccessToken("D".to_string()),
411                AuthorizationExpression::AccessToken("C".to_string()),
412            ]),
413        ]));
414    }
415}