File "class-itsec-password-requirements.php"
Full Path: /home/siazco/grocery.siazco.se/wp-content/plugins/better-wp-security/core/modules/password-requirements/class-itsec-password-requirements.php
File size: 14.6 KB
MIME-type: text/x-php
Charset: utf-8
<?php
use \iThemesSecurity\User_Groups;
/**
* Class ITSEC_Password_Requirements
*/
class ITSEC_Password_Requirements {
const META_KEY = '_itsec_password_requirements';
public function run() {
add_action( 'user_profile_update_errors', array( $this, 'forward_profile_pass_update' ), 0, 3 );
add_action( 'validate_password_reset', array( $this, 'forward_reset_pass' ), 10, 2 );
add_action( 'profile_update', array( $this, 'handle_update_user' ), 10, 2 );
add_action( 'password_reset', array( $this, 'handle_password_reset' ), 10, 2 );
add_filter( 'wp_authenticate_user', array( $this, 'check_password_on_login' ), 999, 2 );
add_action( 'add_user_role', array( $this, 'handle_role_change' ) );
add_action( 'set_user_role', array( $this, 'handle_role_change' ) );
add_action( 'remove_user_role', array( $this, 'handle_role_change' ) );
add_action( 'itsec_validate_password', array( $this, 'validate_password' ), 10, 4 );
// This needs to run before the interstitial runs.
add_action( 'wp_login', array( $this, 'flag_check' ), - 2000, 2 );
add_action( 'itsec_login_interstitial_init', array( $this, 'register_interstitial' ) );
add_action( 'itsec_register_user_group_settings', [ $this, 'register_group_settings' ] );
}
/**
* When a user's password is updated, or a new user created, verify that the new password is valid.
*
* @param WP_Error $errors
* @param bool $update
* @param WP_User|stdClass $user
*/
public function forward_profile_pass_update( $errors, $update, $user ) {
if ( $errors->get_error_message( 'pass' ) ) {
return;
}
if ( isset( $user->user_pass ) ) {
$this->handle_profile_update_password( $errors, $update, $user );
} elseif ( $update && isset( $user->role ) ) {
$this->handle_profile_update_role( $errors, $user );
}
}
/**
* Handle the password being updated for a user.
*
* @param WP_Error $errors
* @param bool $update
* @param WP_User|stdClass $user
*/
private function handle_profile_update_password( $errors, $update, $user ) {
if ( ! $update ) {
$context = 'admin-user-create';
} elseif ( isset( $user->ID ) && $user->ID === get_current_user_id() ) {
$context = 'profile-update';
} else {
$context = 'admin-profile-update';
}
$args = array(
'error' => $errors,
'context' => $context
);
if ( isset( $user->role ) ) {
$args['role'] = $user->role;
}
ITSEC_Lib_Password_Requirements::validate_password( $user, $user->user_pass, $args );
}
/**
* Handle the user's role being updated.
*
* @param WP_Error $errors
* @param WP_User|stdClass $user
*/
private function handle_profile_update_role( $errors, $user ) {
$settings = ITSEC_Modules::get_setting( 'password-requirements', 'requirement_settings' );
foreach ( ITSEC_Lib_Password_Requirements::get_registered() as $code => $requirement ) {
if ( ! ITSEC_Lib_Password_Requirements::is_requirement_enabled( $code ) ) {
continue;
}
$evaluation = get_user_meta( $user->ID, $requirement->get_meta_key(), true );
if ( '' === $evaluation ) {
continue;
}
require_once( ITSEC_Core::get_core_dir() . '/lib/class-itsec-lib-canonical-roles.php' );
$args = array(
'role' => $user->role,
'canonical' => ITSEC_Lib_Canonical_Roles::get_canonical_role_from_role_and_user( $user->role, $user ),
'target' => User_Groups\Match_Target::for_user( get_userdata( $user->ID ), $user->role )
);
$validated = $requirement->validate( $evaluation, $user, $settings[ $code ], $args );
if ( true === $validated ) {
continue;
}
$message = $validated ?: esc_html__( "The provided password does not meet this site's requirements.", 'better-wp-security' );
$errors->add( 'pass', $message );
}
}
/**
* When a user attempts to reset their password, verify that the new password is valid.
*
* @param WP_Error $errors
* @param WP_User $user
*/
public function forward_reset_pass( $errors, $user ) {
if ( ! isset( $_POST['pass1'] ) || is_wp_error( $user ) ) {
// The validate_password_reset action fires when first rendering the reset page and when handling the form
// submissions. Since the pass1 data is missing, this must be the initial page render. So, we don't need to
// do anything yet.
return;
}
ITSEC_Lib_Password_Requirements::validate_password( $user, $_POST['pass1'], array(
'error' => $errors,
'context' => 'reset-password',
) );
}
/**
* Whenever a user object is updated, set when their password was last updated.
*
* @param int $user_id
* @param object $old_user_data
*/
public function handle_update_user( $user_id, $old_user_data ) {
$user = get_userdata( $user_id );
if ( $user->user_pass === $old_user_data->user_pass ) {
return;
}
$this->handle_password_updated( $user );
}
/**
* When a user resets their password, update the last change time.
*
* For some unknown reason, the password reset routine uses {@see wp_set_password()} instead of {@see wp_update_user()}.
*
* @param WP_User $user
* @param string $new_password
*/
public function handle_password_reset( $user, $new_password ) {
$this->handle_password_updated( $user );
$this->handle_plain_text_password_available( $user, $new_password );
}
/**
* When a user logs in, if their password hasn't been validated yet,
* validate it.
*
* @param WP_User $user
* @param string $password
*
* @return WP_User
*/
public function check_password_on_login( $user, $password ) {
if ( ! $user instanceof WP_User ) {
return $user;
}
if ( ! wp_check_password( $password, $user->user_pass, $user->ID ) ) {
return $user;
}
$this->handle_plain_text_password_available( $user, $password );
return $user;
}
/**
* When a password is updated, set the last updated time and delete any pending required change.
*
* @param WP_User $user
*/
protected function handle_password_updated( $user ) {
delete_user_meta( $user->ID, 'itsec_password_change_required' );
update_user_meta( $user->ID, 'itsec_last_password_change', ITSEC_Core::get_current_time_gmt() );
foreach ( ITSEC_Lib_Password_Requirements::get_registered() as $requirement ) {
delete_user_meta( $user->ID, $requirement->get_meta_key() );
}
}
/**
* When a plain text password is available, we perform any evaluations that have not yet been performed for this password.
*
* @param WP_User $user
* @param string $password
*/
protected function handle_plain_text_password_available( $user, $password ) {
$config = wp_parse_args( get_user_meta( $user->ID, self::META_KEY, true ), array(
'evaluation_times' => array(),
) );
$last_updated = ITSEC_Lib_Password_Requirements::password_last_changed( $user );
$settings = ITSEC_Modules::get_setting( 'password-requirements', 'requirement_settings' );
foreach ( ITSEC_Lib_Password_Requirements::get_registered() as $code => $requirement ) {
if ( ! $requirement->should_evaluate_if_not_enabled() && ! ITSEC_Lib_Password_Requirements::is_requirement_enabled( $code ) ) {
continue;
}
if ( isset( $config['evaluation_times'][ $code ] ) && $config['evaluation_times'][ $code ] > $last_updated ) {
continue;
}
$evaluation = $requirement->evaluate( $password, $user );
if ( is_wp_error( $evaluation ) ) {
continue;
}
$config['evaluation_times'][ $code ] = ITSEC_Core::get_current_time_gmt();
update_user_meta( $user->ID, $requirement->get_meta_key(), $evaluation );
if ( ! ITSEC_Lib_Password_Requirements::is_requirement_enabled( $code ) ) {
continue;
}
$validated = $requirement->validate( $evaluation, $user, $settings[ $code ], array(
'target' => User_Groups\Match_Target::for_user( $user ),
) );
if ( true === $validated ) {
continue;
}
ITSEC_Lib_Password_Requirements::flag_password_change_required( $user, $code );
}
update_user_meta( $user->ID, self::META_KEY, $config );
}
/**
* Validate password.
*
* @param \WP_Error $error
* @param \WP_User|stdClass $user
* @param string $new_password
* @param array $args
*/
public function validate_password( $error, $user, $new_password, $args ) {
$settings = ITSEC_Modules::get_setting( 'password-requirements', 'requirement_settings' );
foreach ( ITSEC_Lib_Password_Requirements::get_registered() as $code => $requirement ) {
if ( ! ITSEC_Lib_Password_Requirements::is_requirement_enabled( $code ) ) {
continue;
}
$evaluation = $requirement->evaluate( $new_password, $user );
if ( is_wp_error( $evaluation ) ) {
continue;
}
$validated = $requirement->validate( $evaluation, $user, $settings[ $code ], $args );
if ( true === $validated ) {
continue;
}
// The default error message is a safeguard that should never occur.
$message = $validated ?: esc_html__( "The provided password does not meet this site's requirements.", 'better-wp-security' );
switch ( $args['context'] ) {
case 'admin-user-create':
$message .= ' ' . __( 'The user has not been created.', 'better-wp-security' );
break;
case 'admin-profile-update':
$message .= ' ' . __( 'The user changes have not been saved.', 'better-wp-security' );
break;
case 'profile-update':
$message .= ' ' . __( 'Your profile has not been updated.', 'better-wp-security' );
break;
case 'reset-password':
$message .= ' ' . __( 'The password has not been updated.', 'better-wp-security' );
break;
}
$error->add( 'pass', $message );
}
}
/**
* When a user logs in, run any flag checks to see if a password change should be forced.
*
* @param string $username
* @param WP_User|null $user
*/
public function flag_check( $username, $user = null ) {
if ( ! $user && is_user_logged_in() ) {
$user = wp_get_current_user();
}
if ( ! $user instanceof WP_User || ! $user->exists() ) {
return;
}
foreach ( ITSEC_Lib_Password_Requirements::get_registered() as $code => $requirement ) {
if ( ! ITSEC_Lib_Password_Requirements::is_requirement_enabled( $code ) ) {
continue;
}
$settings = ITSEC_Lib_Password_Requirements::get_requirement_settings( $code );
if ( $requirement->is_password_change_required( $user, $settings ) ) {
ITSEC_Lib_Password_Requirements::flag_password_change_required( $user, $code );
continue;
}
$evaluation = get_user_meta( $user->ID, $requirement->get_meta_key(), true );
if ( '' !== $evaluation ) {
$validated = $requirement->validate( $evaluation, $user, $settings, array(
'target' => User_Groups\Match_Target::for_user( $user ),
) );
if ( true !== $validated ) {
ITSEC_Lib_Password_Requirements::flag_password_change_required( $user, $code );
}
}
}
}
/**
* When a user's role changes, clear all the evaluation times as evaluat
*
* @param int $user_id
*/
public function handle_role_change( $user_id ) {
$config = get_user_meta( $user_id, self::META_KEY, true );
if ( ! $config || ! is_array( $config ) ) {
return;
}
$config['evaluation_times'] = array();
update_user_meta( $user_id, self::META_KEY, $config );
}
/**
* Register the password change interstitial.
*
* @param ITSEC_Lib_Login_Interstitial $lib
*/
public function register_interstitial( $lib ) {
$lib->register( 'update-password', array( $this, 'render_interstitial' ), array(
'show_to_user' => array( 'ITSEC_Lib_Password_Requirements', 'password_change_required' ),
'info_message' => array( 'ITSEC_Lib_Password_Requirements', 'get_message_for_password_change_reason' ),
'submit' => array( $this, 'submit' ),
) );
}
/**
* Registers the user group settings for each enabled Password Requirement.
*
* @param User_Groups\Settings_Registry $registry
*/
public function register_group_settings( User_Groups\Settings_Registry $registry ) {
foreach ( ITSEC_Lib_Password_Requirements::get_registered() as $code => $requirement ) {
if ( ! $requirement->has_user_group() ) {
continue;
}
$registry->register( new User_Groups\Settings_Registration(
'password-requirements',
"requirement_settings.{$code}.group",
User_Groups\Settings_Registration::T_MULTIPLE,
static function () use ( $requirement ) {
return [
'title' => $requirement->get_title(),
'description' => $requirement->get_description(),
];
}
) );
}
}
/**
* Render the interstitial.
*
* @param WP_User $user
*/
public function render_interstitial( $user ) {
wp_enqueue_script( 'user-profile' );
do_action( 'itsec_password_requirements_change_form', $user );
?>
<div class="user-pass1-wrap">
<p><label for="pass1"><?php _e( 'New Password', 'better-wp-security' ); ?></label></p>
</div>
<div class="wp-pwd">
<span class="password-input-wrapper">
<input type="password" data-reveal="1"
data-pw="<?php echo esc_attr( wp_generate_password( 32 ) ); ?>" name="pass1" id="pass1"
class="input" size="20" value="" autocomplete="off" aria-describedby="pass-strength-result"/>
</span>
<div id="pass-strength-result" class="hide-if-no-js" aria-live="polite"><?php _e( 'Strength indicator', 'better-wp-security' ); ?></div>
<div class="pw-weak">
<label>
<input type="checkbox" name="pw_weak" class="pw-checkbox"/>
<?php _e( 'Confirm use of weak password', 'better-wp-security' ); ?>
</label>
</div>
</div>
<p class="user-pass2-wrap">
<label for="pass2"><?php _e( 'Confirm new password', 'better-wp-security' ) ?></label><br/>
<input type="password" name="pass2" id="pass2" class="input" size="20" value="" autocomplete="off"/>
</p>
<p class="description indicator-hint"><?php echo wp_get_password_hint(); ?></p>
<br class="clear"/>
<p class="submit">
<input type="submit" name="wp-submit" id="wp-submit" class="button button-primary button-large" value="<?php esc_attr_e( 'Update Password', 'better-wp-security' ); ?>"/>
</p>
<?php
}
/**
* Handle the request to update the user's password.
*
* @param WP_User $user
* @param array $data POSTed data.
*
* @return WP_Error|null
*/
public function submit( $user, $data ) {
if ( empty( $data['pass1'] ) ) {
return new WP_Error(
'itsec-password-requirements-empty-password',
__( 'Please enter your new password.', 'better-wp-security' )
);
}
$error = ITSEC_Lib_Password_Requirements::validate_password( $user, $data['pass1'], array(
'context' => 'interstitial',
) );
if ( $error->get_error_message() ) {
return $error;
}
$error = wp_update_user( array(
'ID' => $user->ID,
'user_pass' => $data['pass1']
) );
if ( is_wp_error( $error ) ) {
return $error;
}
$this->handle_plain_text_password_available( $user, $data['pass1'] );
return null;
}
}