fido_mds/
query.rs

1//! This implements a query language for the FIDO Metadata Service. This is loosely
2//! based on the SCIM query language.
3//!
4//! `aaguid eq abcd and userverification eq passcodeexternal`
5
6use crate::{AuthenticatorStatus, AuthenticatorTransport, UserVerificationMethod};
7use std::str::FromStr;
8use uuid::Uuid;
9
10#[derive(Debug, PartialEq)]
11pub enum AttrValueAssertion {
12    AaguidEq(Uuid),
13    DescriptionEq(String),
14    DescriptionCnt(String),
15    StatusEq(AuthenticatorStatus),
16    StatusGte(AuthenticatorStatus),
17    StatusLt(AuthenticatorStatus),
18    TransportEq(AuthenticatorTransport),
19    UserVerificationCnt(UserVerificationMethod),
20}
21
22#[derive(Debug, PartialEq)]
23pub enum Query {
24    Op(AttrValueAssertion),
25    And(Box<Query>, Box<Query>),
26    Or(Box<Query>, Box<Query>),
27    Not(Box<Query>),
28}
29
30impl Query {
31    pub fn exclude_compromised_devices() -> Self {
32        Query::Not(Box::new(Query::Op(AttrValueAssertion::StatusLt(
33            AuthenticatorStatus::FidoCertified,
34        ))))
35    }
36}
37
38impl FromStr for Query {
39    type Err = peg::error::ParseError<peg::str::LineCol>;
40
41    fn from_str(q: &str) -> Result<Self, Self::Err> {
42        query::parse(q)
43    }
44}
45
46peg::parser! {
47    grammar query() for str {
48        pub rule parse() -> Query = precedence!{
49                 a:(@) separator()+ "or" separator()+ b:@ {
50                Query::Or(
51                    Box::new(a),
52                    Box::new(b)
53                )
54            }
55            --
56            a:(@) separator()+ "and" separator()+ b:@ {
57                Query::And(
58                    Box::new(a),
59                    Box::new(b)
60                )
61            }
62            --
63            "not" separator()+ "(" e:parse() ")" {
64                Query::Not(Box::new(e))
65            }
66            --
67            "(" e:parse() ")" { e }
68            a:expr() { a }
69        }
70
71        rule separator() =
72            ['\n' | ' ' | '\t' ]
73
74        rule operator() =
75            ['\n' | ' ' | '\t' | '(' | ')' ]
76
77        pub(crate) rule expr() -> Query =
78            uuid_eq_expr() /
79            desc_eq_expr() /
80            desc_cn_expr() /
81            authstat_eq_expr() /
82            authstat_gte_expr() /
83            authstat_lt_expr() /
84            authtrans_eq_expr() /
85            uvm_cnt_expr()
86
87        rule uuid_eq_expr() -> Query =
88            "aaguid" separator()+ "eq" separator()+ v:uuid() { Query::Op(AttrValueAssertion::AaguidEq(v)) }
89
90        rule desc_eq_expr() -> Query =
91            "desc" separator()+ "eq" separator()+ v:octetstr() { Query::Op(AttrValueAssertion::DescriptionEq(v)) }
92
93        rule desc_cn_expr() -> Query =
94            "desc" separator()+ "cnt" separator()+ v:octetstr() { Query::Op(AttrValueAssertion::DescriptionCnt(v)) }
95
96        rule authstat_eq_expr() -> Query =
97            "status" separator()+ "eq" separator()+ v:status() { Query::Op(AttrValueAssertion::StatusEq(v)) }
98
99        rule authstat_gte_expr() -> Query =
100            "status" separator()+ "gte" separator()+ v:status() { Query::Op(AttrValueAssertion::StatusGte(v)) }
101
102        rule authstat_lt_expr() -> Query =
103            "status" separator()+ "lt" separator()+ v:status() { Query::Op(AttrValueAssertion::StatusLt(v)) }
104
105        rule authtrans_eq_expr() -> Query =
106            "transport" separator()+ "eq" separator()+ v:transport() { Query::Op(AttrValueAssertion::TransportEq(v)) }
107
108        rule uvm_cnt_expr() -> Query =
109            "uvm" separator()+ "cnt" separator()+ v:uvm() { Query::Op(AttrValueAssertion::UserVerificationCnt(v)) }
110
111        pub(crate) rule uuid() -> Uuid =
112            s:$((!operator()[_])+) {? Uuid::from_str(s).map_err(|_| "invalid UUID" ) }
113
114        pub(crate) rule status() -> AuthenticatorStatus =
115            s:$((!operator()[_])+) {? AuthenticatorStatus::from_str(s).map_err(|_| "invalid Authenticator Status" ) }
116
117        pub(crate) rule transport() -> AuthenticatorTransport =
118            s:$((!operator()[_])+) {? AuthenticatorTransport::from_str(s).map_err(|_| "invalid Authenticator Transport" ) }
119
120        pub(crate) rule uvm() -> UserVerificationMethod =
121            s:$((!operator()[_])+) {? UserVerificationMethod::from_str(s).map_err(|_| "invalid User Verification Method" ) }
122
123        pub(crate) rule octetstr() -> String =
124            dquotedoctetstr() / squotedoctetstr() / bareoctetstr()
125
126        rule squotedoctetstr() -> String =
127            "\'" s:$((!"\'"[_])*) "\'" { s.to_string() }
128
129        rule dquotedoctetstr() -> String =
130            "\"" s:$((!"\""[_])*) "\"" { s.to_string() }
131
132        rule bareoctetstr() -> String =
133            s:$((!operator()[_])*) { s.to_string() }
134    }
135}
136
137#[cfg(test)]
138mod test {
139    use super::*;
140
141    #[test]
142    fn test_attr_uuid() {
143        assert_eq!(
144            query::uuid("c370f859-622e-4388-9ad2-a7fe7551fdba"),
145            Ok(uuid::uuid!("c370f859-622e-4388-9ad2-a7fe7551fdba"))
146        );
147        assert!(query::uuid("oueuntonaeunaun").is_err());
148    }
149
150    #[test]
151    fn test_query_attr_uuid() {
152        assert_eq!(
153            query::expr("aaguid eq c370f859-622e-4388-9ad2-a7fe7551fdba"),
154            Ok(Query::Op(AttrValueAssertion::AaguidEq(uuid::uuid!(
155                "c370f859-622e-4388-9ad2-a7fe7551fdba"
156            ))))
157        );
158    }
159
160    #[test]
161    fn test_query_not() {
162        assert_eq!(
163            query::parse("not (aaguid eq c370f859-622e-4388-9ad2-a7fe7551fdba)"),
164            Ok(Query::Not(Box::new(Query::Op(
165                AttrValueAssertion::AaguidEq(uuid::uuid!("c370f859-622e-4388-9ad2-a7fe7551fdba"))
166            ))))
167        );
168    }
169
170    #[test]
171    fn test_query_and() {
172        assert_eq!(
173            query::parse("aaguid eq c370f859-622e-4388-9ad2-a7fe7551fdba and aaguid eq 70f11dce-befb-4619-a091-110633d923f6"),
174            Ok(
175                Query::And(
176                    Box::new(
177                        Query::Op(
178                            AttrValueAssertion::AaguidEq(uuid::uuid!("c370f859-622e-4388-9ad2-a7fe7551fdba"))
179                        )
180                    ),
181                    Box::new(
182                        Query::Op(
183                            AttrValueAssertion::AaguidEq(uuid::uuid!("70f11dce-befb-4619-a091-110633d923f6"))
184                        )
185                    ),
186                )
187            )
188        );
189    }
190
191    #[test]
192    fn test_query_or() {
193        assert_eq!(
194            query::parse("aaguid eq c370f859-622e-4388-9ad2-a7fe7551fdba or aaguid eq 70f11dce-befb-4619-a091-110633d923f6"),
195            Ok(
196                Query::Or(
197                    Box::new(
198                        Query::Op(
199                            AttrValueAssertion::AaguidEq(uuid::uuid!("c370f859-622e-4388-9ad2-a7fe7551fdba"))
200                        )
201                    ),
202                    Box::new(
203                        Query::Op(
204                            AttrValueAssertion::AaguidEq(uuid::uuid!("70f11dce-befb-4619-a091-110633d923f6"))
205                        )
206                    ),
207                )
208            )
209        );
210    }
211}