PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 6.3.0
Defender Security – Malware Scanner, Login Security & Firewall v6.3.0
6.3.0 6.2.3 6.2.4 6.2.0 6.2.1 6.2.2 6.1.0 5.3.1 5.4.0 5.4.1 5.5.0 5.5.1 5.6.0 5.6.1 5.6.2 5.7.0 5.7.1 5.7.2 5.8.0 5.8.1 5.9.0 6.0.0 6.0.1 3.0.1 3.1.0 All 141 releases
← All changes | src/component/class-strong-password.php +197 -22 5.4.0 → 6.3.0 View file →
@@ -11,8 +11,9 @@
11 11 use WP_Error;
12 12 use stdClass;
13 13 use WP_Defender\Component;
14 14 use WP_Defender\Traits\User;
15 +use WP_Defender\Integrations\Woocommerce;
15 16 use WP_Defender\Model\Setting\Strong_Password as Settings;
16 17
17 18 /**
18 19 * Enforces strong password policies for user accounts.
@@ -48,13 +49,28 @@
48 49 */
49 50 protected ?Password_Protection $helper;
50 51
51 52 /**
53 + * WooCommerce integration instance.
54 + *
55 + * @var Woocommerce|null
56 + */
57 + protected ?Woocommerce $woo;
58 +
59 + /**
60 + * Flag to track if cookie has been handled.
61 + *
62 + * @var bool
63 + */
64 + private $cookie_handled = false;
65 +
66 + /**
52 67 * Constructs the object, setting the model to a Settings instance.
53 68 */
54 69 public function __construct() {
55 70 $this->model = wd_di()->get( Settings::class );
56 71 $this->helper = wd_di()->get( Password_Protection::class );
72 + $this->woo = wd_di()->get( Woocommerce::class );
57 73 }
58 74
59 75 /**
60 76 * Filters whether the given user can be authenticated with the provided password.
@@ -63,16 +79,16 @@
63 79 * @param string $password Password to check against the user.
64 80 *
65 81 * @return WP_User|WP_Error WP_User or WP_Error object if a previous callback failed authentication.
66 82 */
67 - public function during_authentication( $user, $password ) {
83 + public function during_core_authentication( $user, $password ) {
68 84 // Check if the user object is valid.
69 - if ( is_wp_error( $user ) || ! $user instanceof WP_User ) {
85 + if ( is_wp_error( $user ) ) {
70 86 return $user;
71 87 }
72 88
73 89 // Check if the password is empty.
74 - if ( empty( $password ) ) {
90 + if ( '' === $password ) {
75 91 return new WP_Error(
76 92 'defender_invalid_password',
77 93 esc_html__( 'Invalid user data.', 'defender-security' )
78 94 );
@@ -83,12 +99,17 @@
83 99 return $user;
84 100 }
85 101
86 102 // Check if the user's role requires strong password enforcement.
87 - if ( ! $this->should_enforce_for_user( $user ) ) {
103 + if ( ! $this->should_enforce_for_user( $user, $this->model ) ) {
88 104 return $user;
89 105 }
90 106
107 + // Skip enforcement on WooCommerce requests — dedicated Woo filters handle them based on settings.
108 + if ( $this->is_woo_request() ) {
109 + return $user;
110 + }
111 +
91 112 // Validate the strength of the provided password.
92 113 if ( $this->is_weak_password( $password ) ) {
93 114 $this->helper->trigger_redirect( $user, self::CODE, self::COOKIE_KEY );
94 115 exit;
@@ -163,18 +184,23 @@
163 184 } else {
164 185 $user_obj = null;
165 186 }
166 187 // Prevent strong password enforcement for exempt users.
167 - if ( isset( $user_obj ) && ! $this->should_enforce_for_user( $user_obj ) ) {
188 + if ( isset( $user_obj ) && ! $this->should_enforce_for_user( $user_obj, $this->model ) ) {
168 189 return;
169 190 }
170 191 global $user;
171 - if ( isset( $user ) && ! $this->should_enforce_for_user( $user ) ) {
192 + if ( isset( $user ) && ! $this->should_enforce_for_user( $user, $this->model ) ) {
172 193 return;
173 194 }
174 195 // Collect all locations.
175 196 $pages = array( 'profile.php', 'user-new.php', 'user-edit.php', 'wp-login.php' );
197 + if ( $this->woo->is_activated() ) {
198 + $pages[] = 'index.php';
199 + }
200 +
176 201 if ( in_array( $hook_suffix, $pages, true ) ) {
202 + wp_dequeue_script( 'wc-password-strength-meter' );
177 203 wp_enqueue_style( 'wd-strong-password', plugins_url( 'assets/css/strong-password.css', WP_DEFENDER_FILE ), array( 'dashicons' ), DEFENDER_VERSION );
178 204 wp_enqueue_script(
179 205 'wd-strong-password',
180 206 plugins_url( 'assets/js/strong-password.js', WP_DEFENDER_FILE ),
@@ -188,10 +214,10 @@
188 214 wp_localize_script(
189 215 'wd-strong-password',
190 216 'wpdef_pws_strings',
191 217 array(
192 - 'message' => esc_html__( 'Hint: Your password must follow the guidelines below.', 'defender-security' ),
193 - 'requirements' => array(
218 + 'message' => esc_html__( 'Hint: Your password must follow the guidelines below.', 'defender-security' ),
219 + 'requirements' => array(
194 220 'length' => esc_html__( 'At least 12 characters', 'defender-security' ),
195 221 'case' => esc_html__( 'Uppercase and lowercase letters', 'defender-security' ),
196 222 'symbol' => esc_html__( 'At least one symbol', 'defender-security' ),
197 223 'number' => esc_html__( 'At least one number', 'defender-security' ),
@@ -196,8 +222,11 @@
196 222 'symbol' => esc_html__( 'At least one symbol', 'defender-security' ),
197 223 'number' => esc_html__( 'At least one number', 'defender-security' ),
198 224 'zxcvbn' => esc_html__( 'Avoid common words or sequences of letters/numbers', 'defender-security' ),
199 225 ),
226 + 'woo_locations' => isset( $this->model->forms['woocommerce'] ) && is_array( $this->model->forms['woocommerce'] )
227 + ? $this->model->forms['woocommerce']
228 + : array(),
200 229 )
201 230 );
202 231 add_filter( 'password_hint', '__return_empty_string' );
203 232 }
@@ -212,11 +241,9 @@
212 241 */
213 242 public function on_profile_update( $errors, $update, $user ) {
214 243 if (
215 244 // If there are already errors with the password, exit.
216 - $errors->get_error_message( 'pass' ) ||
217 - // If the user object is invalid, exit.
218 - is_wp_error( $user ) ||
245 + '' !== $errors->get_error_message( 'pass' ) ||
219 246 // If the user's password is not set, exit.
220 247 ! isset( $user->user_pass )
221 248 ) {
222 249 return;
@@ -222,9 +249,9 @@
222 249 return;
223 250 }
224 251
225 252 // When updating the profile check if user's role preference is enabled.
226 - if ( ! $this->should_enforce_for_user( $user ) ) {
253 + if ( ! $this->should_enforce_for_user( $user, $this->model ) ) {
227 254 return;
228 255 }
229 256
230 257 $login_password = $this->helper->get_submitted_password();
@@ -229,9 +256,9 @@
229 256
230 257 $login_password = $this->helper->get_submitted_password();
231 258
232 259 // Check if the submitted password is weak.
233 - if ( isset( $login_password ) && $this->is_weak_password( $login_password ) ) {
260 + if ( '' !== $login_password && $this->is_weak_password( $login_password ) ) {
234 261 $errors->add( self::CODE, $this->model->get_message() );
235 262 }
236 263 }
237 264
@@ -237,35 +264,45 @@
237 264
238 265 /**
239 266 * Validate password during password reset.
240 267 *
241 - * @param WP_Error $errors WP_Error object.
242 - * @param WP_User $user WP_User object.
268 + * @param WP_Error $errors WP_Error object.
269 + * @param WP_User|null $user WP_User object.
243 270 */
244 - public function on_password_reset( $errors, $user ) {
245 - if ( is_wp_error( $user ) ) {
271 + public function on_password_reset( $errors, $user = null ) {
272 + if ( null === $user ) {
246 273 return;
247 274 }
248 275
249 - if ( ! $this->should_enforce_for_user( $user ) ) {
276 + if ( ! $this->should_enforce_for_user( $user, $this->model ) ) {
250 277 return;
251 278 }
252 279
280 + // WooCommerce also fires this core hook — gate on the lost-password form setting.
281 + if ( $this->is_woo_request() && ! $this->is_woo_form_enabled( Woocommerce::WOO_LOST_PASSWORD_FORM ) ) {
282 + return;
283 + }
284 +
253 285 // Check if display_strong_password_warning cookie enabled then show warning message on reset password page.
254 286 $cookie_value = isset( $_COOKIE[ self::COOKIE_KEY ] ) ? sanitize_text_field( wp_unslash( $_COOKIE[ self::COOKIE_KEY ] ) ) : '';
255 287
256 288 // If the cookie is set, show the warning message.
257 - if ( ! empty( $cookie_value ) ) {
289 + if ( '' !== $cookie_value ) {
258 290 $errors->add( self::CODE, $this->model->get_message() );
259 291
260 292 // Remove the cookie.
261 293 $this->helper->remove_cookie_notice( self::COOKIE_KEY );
294 + $this->cookie_handled = true;
262 295 }
263 296
264 297 $submitted_password = $this->helper->get_submitted_password();
265 298
266 299 // Check if the submitted password is weak.
267 - if ( ! empty( $submitted_password ) && $this->is_weak_password( $submitted_password ) ) {
300 + if (
301 + '' !== $submitted_password &&
302 + $this->is_weak_password( $submitted_password ) &&
303 + ! in_array( self::CODE, $errors->get_error_codes(), true )
304 + ) {
268 305 $errors->add( self::CODE, $this->model->get_message() );
269 306 }
270 307 }
271 308
@@ -272,14 +309,14 @@
272 309 /**
273 310 * Generate a strong password with a mix of character types.
274 311 *
275 312 * @param string $password The password to be generated.
276 - * @param int $length The length of the password to be generated (12 or 24).
313 + * @param int $length The length of the password to be generated (12).
277 314 *
278 315 * @return string Strong password.
279 316 */
280 317 public function generate_password( $password, $length ) {
281 - if ( ! in_array( $length, array( 12, 24 ), true ) ) {
318 + if ( 12 !== $length ) {
282 319 return $password;
283 320 }
284 321
285 322 $upper = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
@@ -301,6 +338,144 @@
301 338 }
302 339
303 340 // Shuffle the password to prevent predictable character order.
304 341 return str_shuffle( $password );
342 + }
343 +
344 + /**
345 + * Add WooCommerce error message for password reset warnings.
346 + *
347 + * @param string $wc_message WooCommerce default error message.
348 + * @return string WooCommerce error message.
349 + */
350 + public function add_woocommerce_error_message( $wc_message ) {
351 + if ( ! isset( $this->model->forms['woocommerce'] ) ) {
352 + return $wc_message;
353 + }
354 +
355 + if ( isset( $_COOKIE[ self::COOKIE_KEY ] ) && function_exists( 'wc_print_notice' ) && ! $this->cookie_handled ) {
356 + $message = $this->model->get_message();
357 + wc_print_notice( $message, 'error' );
358 + $this->helper->remove_cookie_notice( self::COOKIE_KEY );
359 + }
360 + return $wc_message;
361 + }
362 +
363 + /**
364 + * Validate password strength during WooCommerce checkout registration.
365 + *
366 + * @param WP_Error $errors Error object.
367 + *
368 + * @return WP_Error Modified error object.
369 + */
370 + public function during_woo_registration( $errors ) {
371 + if ( ! isset( $this->model->forms['woocommerce'] ) || ! is_array( $this->model->forms['woocommerce'] ) || ! in_array( Woocommerce::WOO_REGISTER_FORM, $this->model->forms['woocommerce'], true ) ) {
372 + return $errors;
373 + }
374 +
375 + $password = $this->helper->get_submitted_password();
376 +
377 + if (
378 + '' !== $password &&
379 + $this->is_weak_password( $password ) &&
380 + ! in_array( self::CODE, $errors->get_error_codes(), true )
381 + ) {
382 + $errors->add( self::CODE, $this->model->get_message() );
383 + }
384 +
385 + return $errors;
386 + }
387 +
388 + /**
389 + * Validate password strength during WooCommerce account details save.
390 + *
391 + * @param WP_Error $errors WP_Error object.
392 + * @param WP_User|stdClass $user User object.
393 + */
394 + public function on_woo_account_update( $errors, $user ) {
395 + if ( ! $user instanceof stdClass || ! isset( $user->user_pass ) ) {
396 + return;
397 + }
398 +
399 + if ( ! $this->should_enforce_for_user( $user, $this->model ) ) {
400 + return;
401 + }
402 +
403 + if ( ! isset( $this->model->forms['woocommerce'] ) || ! is_array( $this->model->forms['woocommerce'] ) ) {
404 + return;
405 + }
406 +
407 + $password = $this->helper->get_submitted_password();
408 +
409 + if (
410 + '' !== $password &&
411 + $this->is_weak_password( $password ) &&
412 + ! in_array( self::CODE, $errors->get_error_codes(), true )
413 + ) {
414 + $errors->add( self::CODE, $this->model->get_message() );
415 + }
416 + }
417 +
418 + /**
419 + * Validate password strength during WooCommerce login.
420 + *
421 + * @param WP_Error $validation_error WP_Error object to collect validation errors.
422 + * @param string $username User login name.
423 + * @param string $password User password.
424 + *
425 + * @return WP_User|WP_Error WP_User or WP_Error object with any validation errors added.
426 + */
427 + public function during_woo_authentication( $validation_error, $username, $password ) {
428 + if ( '' === $password ) {
429 + return $validation_error;
430 + }
431 +
432 + if ( $this->is_woo_request() && ! $this->is_woo_form_enabled( Woocommerce::WOO_LOGIN_FORM ) ) {
433 + return $validation_error;
434 + }
435 + $user = get_user_by( 'login', $username );
436 + if ( ! $user ) {
437 + $user = get_user_by( 'email', $username );
438 + }
439 +
440 + if ( ! $user ) {
441 + return $validation_error;
442 + }
443 +
444 + if ( ! $this->should_enforce_for_user( $user, $this->model ) ) {
445 + return $validation_error;
446 + }
447 +
448 + if ( ! wp_check_password( $password, $user->user_pass, $user->ID ) ) {
449 + return $validation_error;
450 + }
451 +
452 + if ( $this->is_weak_password( $password ) ) {
453 + $this->helper->trigger_redirect( $user, self::CODE, self::COOKIE_KEY );
454 + exit;
455 + }
456 +
457 + return $validation_error;
458 + }
459 +
460 + /**
461 + * Determine if current request is coming from WooCommerce flows.
462 + */
463 + private function is_woo_request(): bool {
464 + return $this->woo->is_activated() && $this->woo->is_wc_login_context();
465 + }
466 +
467 + /**
468 + * Check whether a specific WooCommerce form is enabled in settings.
469 + *
470 + * @param string $form Form slug.
471 + */
472 + private function is_woo_form_enabled( string $form ): bool {
473 + if ( ! isset( $this->model->plugins['woocommerce'] ) || ! $this->model->plugins['woocommerce'] ) {
474 + return false;
475 + }
476 +
477 + $forms = $this->model->forms['woocommerce'] ?? array();
478 +
479 + return in_array( $form, $forms, true );
305 480 }
306 481 }