bitwarden_core/auth/password/
policy.rs

1/// Validate the provided password passes the provided Master Password Requirements Policy.
2pub(crate) fn satisfies_policy(
3    password: String,
4    strength: u8,
5    policy: &MasterPasswordPolicyOptions,
6) -> bool {
7    if policy.min_complexity > 0 && policy.min_complexity > strength {
8        return false;
9    }
10
11    if policy.min_length > 0 && usize::from(policy.min_length) > password.len() {
12        return false;
13    }
14
15    if policy.require_upper && password.to_lowercase() == password {
16        return false;
17    }
18
19    if policy.require_lower && password.to_uppercase() == password {
20        return false;
21    }
22
23    if policy.require_numbers && !password.chars().any(|c| c.is_numeric()) {
24        return false;
25    }
26
27    if policy.require_special && !password.chars().any(|c| "!@#$%^&*".contains(c)) {
28        return false;
29    }
30
31    true
32}
33
34#[derive(Debug)]
35#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
36#[allow(dead_code)]
37pub struct MasterPasswordPolicyOptions {
38    min_complexity: u8,
39    min_length: u8,
40    require_upper: bool,
41    require_lower: bool,
42    require_numbers: bool,
43    require_special: bool,
44
45    /// Flag to indicate if the policy should be enforced on login.
46    /// If true, and the user's password does not meet the policy requirements,
47    /// the user will be forced to update their password.
48    enforce_on_login: bool,
49}
50
51#[cfg(test)]
52mod tests {
53
54    use super::{satisfies_policy, MasterPasswordPolicyOptions};
55
56    #[test]
57    fn satisfies_policy_gives_success() {
58        let password = "lkasfo!icbb$2323ALKJCO22".to_string();
59        let options = MasterPasswordPolicyOptions {
60            min_complexity: 3,
61            min_length: 5,
62            require_upper: true,
63            require_lower: true,
64            require_numbers: true,
65            require_special: true,
66            enforce_on_login: false,
67        };
68
69        let result = satisfies_policy(password, 4, &options);
70        assert!(result);
71    }
72
73    #[test]
74    fn satisfies_policy_evaluates_strength() {
75        let password = "password123".to_string();
76        let options = MasterPasswordPolicyOptions {
77            min_complexity: 3,
78            min_length: 0,
79            require_upper: false,
80            require_lower: false,
81            require_numbers: false,
82            require_special: false,
83            enforce_on_login: false,
84        };
85
86        let result = satisfies_policy(password, 0, &options);
87        assert!(!result);
88    }
89
90    #[test]
91    fn satisfies_policy_evaluates_length() {
92        let password = "password123".to_string();
93        let options = MasterPasswordPolicyOptions {
94            min_complexity: 0,
95            min_length: 20,
96            require_upper: false,
97            require_lower: false,
98            require_numbers: false,
99            require_special: false,
100            enforce_on_login: false,
101        };
102
103        let result = satisfies_policy(password, 0, &options);
104        assert!(!result);
105    }
106
107    #[test]
108    fn satisfies_policy_evaluates_upper() {
109        let password = "password123".to_string();
110        let options = MasterPasswordPolicyOptions {
111            min_complexity: 0,
112            min_length: 0,
113            require_upper: true,
114            require_lower: false,
115            require_numbers: false,
116            require_special: false,
117            enforce_on_login: false,
118        };
119
120        let result = satisfies_policy(password, 0, &options);
121        assert!(!result);
122    }
123
124    #[test]
125    fn satisfies_policy_evaluates_lower() {
126        let password = "ABCDEFG123".to_string();
127        let options = MasterPasswordPolicyOptions {
128            min_complexity: 0,
129            min_length: 0,
130            require_upper: false,
131            require_lower: true,
132            require_numbers: false,
133            require_special: false,
134            enforce_on_login: false,
135        };
136
137        let result = satisfies_policy(password, 0, &options);
138        assert!(!result);
139    }
140
141    #[test]
142    fn satisfies_policy_evaluates_numbers() {
143        let password = "password".to_string();
144        let options = MasterPasswordPolicyOptions {
145            min_complexity: 0,
146            min_length: 0,
147            require_upper: false,
148            require_lower: false,
149            require_numbers: true,
150            require_special: false,
151            enforce_on_login: false,
152        };
153
154        let result = satisfies_policy(password, 0, &options);
155        assert!(!result);
156    }
157
158    #[test]
159    fn satisfies_policy_evaluates_special() {
160        let password = "Password123".to_string();
161        let options = MasterPasswordPolicyOptions {
162            min_complexity: 0,
163            min_length: 0,
164            require_upper: false,
165            require_lower: false,
166            require_numbers: false,
167            require_special: true,
168            enforce_on_login: false,
169        };
170
171        let result = satisfies_policy(password, 0, &options);
172        assert!(!result);
173    }
174}