precis_profiles/
passwords.rs

1use crate::common;
2use lazy_static::lazy_static;
3use precis_core::profile::{PrecisFastInvocation, Profile, Rules};
4use precis_core::Error;
5use precis_core::{FreeformClass, StringClass};
6use std::borrow::Cow;
7
8/// [`OpaqueString`](<https://datatracker.ietf.org/doc/html/rfc8265#section-4.2>)
9/// Profile designed to deal with passwords and other opaque strings in security
10/// and application protocols.
11/// Replaces:  The `SASLprep` profile of `Stringprep`. Look at the
12/// [`IANA` Considerations](https://datatracker.ietf.org/doc/html/rfc8265#section-7.3)
13/// section for more details.
14/// # Example
15/// ```rust
16/// # use precis_core::Error;
17/// # use precis_core::profile::Profile;
18/// # use precis_profiles::OpaqueString;
19/// # use std::borrow::Cow;
20/// // create OpaqueString profile
21/// let profile = OpaqueString::new();
22///
23/// // prepare string
24/// assert_eq!(profile.prepare("I'm Guybrush Threepwood, Mighty Pirate ☠"),
25///     Ok(Cow::from("I'm Guybrush Threepwood, Mighty Pirate ☠")));
26///
27/// // enforce string
28/// assert_eq!(profile.enforce("Look behind you, a three-headed monkey!🐒"),
29///     Ok(Cow::from("Look behind you, a three-headed monkey!🐒")));
30///
31/// // compare strings
32/// assert_eq!(profile.compare("That’s the second biggest 🐵 I’ve ever seen!",
33///     "That’s the second biggest 🐵 I’ve ever seen!"), Ok(true));
34/// ```
35#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
36pub struct OpaqueString(FreeformClass);
37
38impl OpaqueString {
39    /// Creates a [`OpaqueString`] profile.
40    pub fn new() -> Self {
41        Self(FreeformClass::default())
42    }
43}
44
45impl Profile for OpaqueString {
46    fn prepare<'a, S>(&self, s: S) -> Result<Cow<'a, str>, Error>
47    where
48        S: Into<Cow<'a, str>>,
49    {
50        let s = s.into();
51        let s = (!s.is_empty()).then_some(s).ok_or(Error::Invalid)?;
52        self.0.allows(&s)?;
53        Ok(s)
54    }
55
56    fn enforce<'a, S>(&self, s: S) -> Result<Cow<'a, str>, Error>
57    where
58        S: Into<Cow<'a, str>>,
59    {
60        let s = self.prepare(s)?;
61        let s = self.additional_mapping_rule(s)?;
62        let s = self.normalization_rule(s)?;
63        (!s.is_empty()).then_some(s).ok_or(Error::Invalid)
64    }
65
66    fn compare<A, B>(&self, s1: A, s2: B) -> Result<bool, Error>
67    where
68        A: AsRef<str>,
69        B: AsRef<str>,
70    {
71        Ok(self.enforce(s1.as_ref())? == self.enforce(s2.as_ref())?)
72    }
73}
74
75impl Rules for OpaqueString {
76    fn additional_mapping_rule<'a, T>(&self, s: T) -> Result<Cow<'a, str>, Error>
77    where
78        T: Into<Cow<'a, str>>,
79    {
80        let s = s.into();
81        match s.find(common::is_non_ascii_space) {
82            None => Ok(s),
83            Some(pos) => {
84                let mut res = String::from(&s[..pos]);
85                res.reserve(s.len() - res.len());
86                for c in s[pos..].chars() {
87                    if common::is_non_ascii_space(c) {
88                        res.push(common::SPACE);
89                    } else {
90                        res.push(c);
91                    }
92                }
93                Ok(res.into())
94            }
95        }
96    }
97
98    fn normalization_rule<'a, T>(&self, s: T) -> Result<Cow<'a, str>, Error>
99    where
100        T: Into<Cow<'a, str>>,
101    {
102        common::normalization_form_nfc(s)
103    }
104}
105
106fn get_opaque_string_profile() -> &'static OpaqueString {
107    lazy_static! {
108        static ref OPAQUE_STRING: OpaqueString = OpaqueString::default();
109    }
110    &OPAQUE_STRING
111}
112
113impl PrecisFastInvocation for OpaqueString {
114    fn prepare<'a, S>(s: S) -> Result<Cow<'a, str>, Error>
115    where
116        S: Into<Cow<'a, str>>,
117    {
118        get_opaque_string_profile().prepare(s)
119    }
120
121    fn enforce<'a, S>(s: S) -> Result<Cow<'a, str>, Error>
122    where
123        S: Into<Cow<'a, str>>,
124    {
125        get_opaque_string_profile().enforce(s)
126    }
127
128    fn compare<A, B>(s1: A, s2: B) -> Result<bool, Error>
129    where
130        A: AsRef<str>,
131        B: AsRef<str>,
132    {
133        get_opaque_string_profile().compare(s1, s2)
134    }
135}
136
137#[cfg(test)]
138mod test_passwords {
139    use crate::passwords::*;
140
141    #[test]
142    fn opaque_string_profile() {
143        let profile = OpaqueString::new();
144
145        let res = profile.prepare("πßå");
146        assert_eq!(res, Ok(Cow::from("πßå")));
147
148        let res = profile.enforce("πßå");
149        assert_eq!(res, Ok(Cow::from("πßå")));
150
151        let res = profile.compare("Secret", "Secret");
152        assert_eq!(res, Ok(true));
153    }
154}