File: /var/www/html/wptoho/wp-content/plugins/defender-security/src/component/class-strong-password.php
<?php
/**
* Enforces strong password policies for user accounts.
*
* @package WP_Defender\Component
*/
namespace WP_Defender\Component;
use WP_User;
use WP_Error;
use stdClass;
use WP_Defender\Component;
use WP_Defender\Traits\User;
use WP_Defender\Model\Setting\Strong_Password as Settings;
/**
* Enforces strong password policies for user accounts.
*/
class Strong_Password extends Component {
use User;
/**
* Cookie key to show warning message on reset password page.
*
* @var string
*/
protected const COOKIE_KEY = 'display_strong_password_warning';
/**
* Error code.
*
* @var string
*/
protected const CODE = 'password_strength';
/**
* Password reset settings model.
*
* @var Settings|null
*/
protected ?Settings $model;
/**
* Helper instance for reuse methods from pwned password protection.
*
* @var Password_Protection|null
*/
protected ?Password_Protection $helper;
/**
* Constructs the object, setting the model to a Settings instance.
*/
public function __construct() {
$this->model = wd_di()->get( Settings::class );
$this->helper = wd_di()->get( Password_Protection::class );
}
/**
* Filters whether the given user can be authenticated with the provided password.
*
* @param WP_User|WP_Error $user WP_User or WP_Error object if a previous callback failed authentication.
* @param string $password Password to check against the user.
*
* @return WP_User|WP_Error WP_User or WP_Error object if a previous callback failed authentication.
*/
public function during_authentication( $user, $password ) {
// Check if the user object is valid.
if ( is_wp_error( $user ) || ! $user instanceof WP_User ) {
return $user;
}
// Check if the password is empty.
if ( empty( $password ) ) {
return new WP_Error(
'defender_invalid_password',
esc_html__( 'Invalid user data.', 'defender-security' )
);
}
// Verify the provided password against the user's password.
if ( ! wp_check_password( $password, $user->user_pass, $user->ID ) ) {
return $user;
}
// Check if the user's role requires strong password enforcement.
if ( ! $this->should_enforce_for_user( $user ) ) {
return $user;
}
// Validate the strength of the provided password.
if ( $this->is_weak_password( $password ) ) {
$this->helper->trigger_redirect( $user, self::CODE, self::COOKIE_KEY );
exit;
}
return $user;
}
/**
* Check if a password is weak.
*
* Password requirements:
* - At least 12 characters long.
* - Must contain both uppercase and lowercase letters.
* - At least one symbol.
* - At least one number.
*
* @param string $password The password to validate.
*
* @return bool True if the password is weak, false otherwise.
*/
private function is_weak_password( $password ): bool {
// Check minimum length.
if ( strlen( $password ) < 12 ) {
return true;
}
// Check for at least one uppercase letter.
if ( ! preg_match( '/[A-Z]/', $password ) ) {
return true;
}
// Check for at least one lowercase letter.
if ( ! preg_match( '/[a-z]/', $password ) ) {
return true;
}
// Check for at least one number.
if ( ! preg_match( '/[0-9]/', $password ) ) {
return true;
}
// Check for at least one special character or symbol.
$symbol_pattern = '/[!@#$%^&*()_+\-={};:\'",.<>?~\[\]\/|`]/';
if ( ! preg_match( $symbol_pattern, $password ) ) {
return true;
}
return false;
}
/**
* Enqueue custom password strength script on relevant admin pages.
*
* @param string $hook_suffix The current admin page.
*/
public function scripts( $hook_suffix ) {
// Check if the current admin page is relevant.
if ( '' === $hook_suffix ) {
global $pagenow;
$hook_suffix = $pagenow;
}
if ( 'user-edit.php' === $hook_suffix ) {
// Changes for another existing user? Skip case for a new user on /wp-admin/user-new.php.
global $profile_user;
$user_obj = $profile_user;
} elseif ( 'profile.php' === $hook_suffix ) {
// Changes for the current user.
global $current_user;
$user_obj = $current_user;
} else {
$user_obj = null;
}
// Prevent strong password enforcement for exempt users.
if ( isset( $user_obj ) && ! $this->should_enforce_for_user( $user_obj ) ) {
return;
}
global $user;
if ( isset( $user ) && ! $this->should_enforce_for_user( $user ) ) {
return;
}
// Collect all locations.
$pages = array( 'profile.php', 'user-new.php', 'user-edit.php', 'wp-login.php' );
if ( in_array( $hook_suffix, $pages, true ) ) {
wp_enqueue_style( 'wd-strong-password', plugins_url( 'assets/css/strong-password.css', WP_DEFENDER_FILE ), array( 'dashicons' ), DEFENDER_VERSION );
wp_enqueue_script(
'wd-strong-password',
plugins_url( 'assets/js/strong-password.js', WP_DEFENDER_FILE ),
array(
'jquery',
'password-strength-meter',
),
DEFENDER_VERSION,
true
);
wp_localize_script(
'wd-strong-password',
'wpdef_pws_strings',
array(
'message' => esc_html__( 'Hint: Your password must follow the guidelines below.', 'defender-security' ),
'requirements' => array(
'length' => esc_html__( 'At least 12 characters', 'defender-security' ),
'case' => esc_html__( 'Uppercase and lowercase letters', 'defender-security' ),
'symbol' => esc_html__( 'At least one symbol', 'defender-security' ),
'number' => esc_html__( 'At least one number', 'defender-security' ),
'zxcvbn' => esc_html__( 'Avoid common words or sequences of letters/numbers', 'defender-security' ),
),
)
);
add_filter( 'password_hint', '__return_empty_string' );
}
}
/**
* Validate password during user profile update.
*
* @param WP_Error $errors WP_Error object.
* @param bool $update Whether this is a user update.
* @param stdClass|WP_User $user User object.
*/
public function on_profile_update( $errors, $update, $user ) {
if (
// If there are already errors with the password, exit.
$errors->get_error_message( 'pass' ) ||
// If the user object is invalid, exit.
is_wp_error( $user ) ||
// If the user's password is not set, exit.
! isset( $user->user_pass )
) {
return;
}
// When updating the profile check if user's role preference is enabled.
if ( ! $this->should_enforce_for_user( $user ) ) {
return;
}
$login_password = $this->helper->get_submitted_password();
// Check if the submitted password is weak.
if ( isset( $login_password ) && $this->is_weak_password( $login_password ) ) {
$errors->add( self::CODE, $this->model->get_message() );
}
}
/**
* Validate password during password reset.
*
* @param WP_Error $errors WP_Error object.
* @param WP_User $user WP_User object.
*/
public function on_password_reset( $errors, $user ) {
if ( is_wp_error( $user ) ) {
return;
}
if ( ! $this->should_enforce_for_user( $user ) ) {
return;
}
// Check if display_strong_password_warning cookie enabled then show warning message on reset password page.
$cookie_value = isset( $_COOKIE[ self::COOKIE_KEY ] ) ? sanitize_text_field( wp_unslash( $_COOKIE[ self::COOKIE_KEY ] ) ) : '';
// If the cookie is set, show the warning message.
if ( ! empty( $cookie_value ) ) {
$errors->add( self::CODE, $this->model->get_message() );
// Remove the cookie.
$this->helper->remove_cookie_notice( self::COOKIE_KEY );
}
$submitted_password = $this->helper->get_submitted_password();
// Check if the submitted password is weak.
if ( ! empty( $submitted_password ) && $this->is_weak_password( $submitted_password ) ) {
$errors->add( self::CODE, $this->model->get_message() );
}
}
/**
* Generate a strong password with a mix of character types.
*
* @param string $password The password to be generated.
* @param int $length The length of the password to be generated (12 or 24).
*
* @return string Strong password.
*/
public function generate_password( $password, $length ) {
if ( ! in_array( $length, array( 12, 24 ), true ) ) {
return $password;
}
$upper = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
$lower = 'abcdefghijklmnopqrstuvwxyz';
$numbers = '0123456789';
$symbols = '!@#$%^&*()_+-={};:\'",.<>?~[]|`';
// Ensure the password includes at least one character from each set to meet strength requirements.
$password = '';
$password .= $upper[ random_int( 0, strlen( $upper ) - 1 ) ];
$password .= $lower[ random_int( 0, strlen( $lower ) - 1 ) ];
$password .= $numbers[ random_int( 0, strlen( $numbers ) - 1 ) ];
$password .= $symbols[ random_int( 0, strlen( $symbols ) - 1 ) ];
// Fill the rest of the password length with random characters from all character sets.
$all = $upper . $lower . $numbers . $symbols;
for ( $i = strlen( $password ); $i < $length; $i++ ) {
$password .= $all[ random_int( 0, strlen( $all ) - 1 ) ];
}
// Shuffle the password to prevent predictable character order.
return str_shuffle( $password );
}
}