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