diff --git a/crates/bitwarden-uniffi/src/auth/mod.rs b/crates/bitwarden-uniffi/src/auth/mod.rs index ded3f776a..1dc102b3e 100644 --- a/crates/bitwarden-uniffi/src/auth/mod.rs +++ b/crates/bitwarden-uniffi/src/auth/mod.rs @@ -29,7 +29,7 @@ impl ClientAuth { .await } - /// **API Draft:** Evaluate if the provided password satisfies the provided policy + /// Evaluate if the provided password satisfies the provided policy pub async fn satisfies_policy( &self, password: String, diff --git a/crates/bitwarden/src/auth/password.rs b/crates/bitwarden/src/auth/password.rs index eec68861a..94fe0faa0 100644 --- a/crates/bitwarden/src/auth/password.rs +++ b/crates/bitwarden/src/auth/password.rs @@ -16,11 +16,36 @@ pub(super) fn password_strength( 2 } +/// Validate the provided password passes the provided Master Password Requirements Policy. pub(super) fn satisfies_policy( - _password: String, - _strength: u8, - _policy: &MasterPasswordPolicyOptions, + password: String, + strength: u8, + policy: &MasterPasswordPolicyOptions, ) -> bool { + if policy.min_complexity > 0 && policy.min_complexity > strength { + return false; + } + + if policy.min_length > 0 && usize::from(policy.min_length) > password.len() { + return false; + } + + if policy.require_upper && password.to_lowercase() == password { + return false; + } + + if policy.require_lower && password.to_uppercase() == password { + return false; + } + + if policy.require_numbers && !password.chars().any(|c| c.is_numeric()) { + return false; + } + + if policy.require_special && !password.chars().any(|c| "!@#$%^&*".contains(c)) { + return false; + } + true } @@ -69,8 +94,129 @@ pub struct MasterPasswordPolicyOptions { } #[cfg(test)] - mod tests { + mod satisfies_policy { + use crate::auth::password::{satisfies_policy, MasterPasswordPolicyOptions}; + + #[test] + fn satisfies_policy_gives_success() { + let password = "lkasfo!icbb$2323ALKJCO22".to_string(); + let options = MasterPasswordPolicyOptions { + min_complexity: 3, + min_length: 5, + require_upper: true, + require_lower: true, + require_numbers: true, + require_special: true, + enforce_on_login: false, + }; + + let result = satisfies_policy(password, 4, &options); + assert!(result); + } + + #[test] + fn satisfies_policy_evaluates_strength() { + let password = "password123".to_string(); + let options = MasterPasswordPolicyOptions { + min_complexity: 3, + min_length: 0, + require_upper: false, + require_lower: false, + require_numbers: false, + require_special: false, + enforce_on_login: false, + }; + + let result = satisfies_policy(password, 0, &options); + assert!(!result); + } + + #[test] + fn satisfies_policy_evaluates_length() { + let password = "password123".to_string(); + let options = MasterPasswordPolicyOptions { + min_complexity: 0, + min_length: 20, + require_upper: false, + require_lower: false, + require_numbers: false, + require_special: false, + enforce_on_login: false, + }; + + let result = satisfies_policy(password, 0, &options); + assert!(!result); + } + + #[test] + fn satisfies_policy_evaluates_upper() { + let password = "password123".to_string(); + let options = MasterPasswordPolicyOptions { + min_complexity: 0, + min_length: 0, + require_upper: true, + require_lower: false, + require_numbers: false, + require_special: false, + enforce_on_login: false, + }; + + let result = satisfies_policy(password, 0, &options); + assert!(!result); + } + + #[test] + fn satisfies_policy_evaluates_lower() { + let password = "ABCDEFG123".to_string(); + let options = MasterPasswordPolicyOptions { + min_complexity: 0, + min_length: 0, + require_upper: false, + require_lower: true, + require_numbers: false, + require_special: false, + enforce_on_login: false, + }; + + let result = satisfies_policy(password, 0, &options); + assert!(!result); + } + + #[test] + fn satisfies_policy_evaluates_numbers() { + let password = "password".to_string(); + let options = MasterPasswordPolicyOptions { + min_complexity: 0, + min_length: 0, + require_upper: false, + require_lower: false, + require_numbers: true, + require_special: false, + enforce_on_login: false, + }; + + let result = satisfies_policy(password, 0, &options); + assert!(!result); + } + + #[test] + fn satisfies_policy_evaluates_special() { + let password = "Password123".to_string(); + let options = MasterPasswordPolicyOptions { + min_complexity: 0, + min_length: 0, + require_upper: false, + require_lower: false, + require_numbers: false, + require_special: true, + enforce_on_login: false, + }; + + let result = satisfies_policy(password, 0, &options); + assert!(!result); + } + } #[cfg(feature = "mobile")] #[tokio::test]