From ac3e93ef65cd7ff2d04bed6b31b6bf8740104d1e Mon Sep 17 00:00:00 2001 From: Jake Fink Date: Wed, 20 Dec 2023 14:41:34 -0500 Subject: [PATCH] [PM-3437] add validation for master password policy (#452) ## Type of change ``` - [ ] Bug fix - [x] New feature development - [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [ ] Other ``` ## Objective Implements the validation for master password policy ## Code changes - **file.ext:** Description of what was changed and why ## Screenshots ## Before you submit - Please add **unit tests** where it makes sense to do so (encouraged but not required) --- crates/bitwarden-uniffi/src/auth/mod.rs | 2 +- crates/bitwarden/src/auth/password.rs | 154 +++++++++++++++++++++++- 2 files changed, 151 insertions(+), 5 deletions(-) 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]