-
Notifications
You must be signed in to change notification settings - Fork 810
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Account Protection: Add password validation (#41401)
* Add Account Protection toggle to Jetpack security settings * Import package and run activation/deactivation on module toggle * changelog * Add Protect Settings page and hook up Account Protection toggle * changelog * Update changelog * Register modules on plugin activation * Ensure package is initialized on plugin activation * Make account protection class init static * Add auth hooks, redirect and a custom login action template * Reorg, add Password_Detection class * Remove user cxn req and banner * Do not enabled module by default * Add strict mode option and settings toggle * changelog * Add strict mode toggle * Add strict mode toggle and endpoints * Reorg and add kill switch and is supported check * Add testing infrastructure * Add email handlings, resend AJAX action, and attempt limitations * Add nonces, checks and template error handling * Use method over template to avoid lint errors * Improve render_password_detection_template, update SVG file ext * Remove template file and include * Prep for validation endpoints * Update classes to be dynamic * Add constructors * Reorg user meta methods * Add type declarations and hinting * Simplify method naming * Use dynamic classes * Update class dependencies * Fix copy * Revert unrelated changes * Revert unrelated changes * Fix method calls * Do not activate by default * Fix phan errors * Changelog * Update composer deps * Update lock files, add constructor method * Fix php warning * Update lock file * Changelog * Fix Password_Detection constructor * Changelog * More changelogs * Remove comments * Fix static analysis errors * Remove top level phpunit.xml.dist * Remove never return type * Revert tests dir changes in favour of a dedicated task * Add tests dir * Reapply default test infrastructure * Reorg and rename * Update @Package * Use never phpdoc return type as per static analysis error * Enable module by default * Enable module by default * Remove all reference to and functionality of strict mode * Remove unneeded strict mode code, update Protect settings UI * Updates/fixes * Fix import * Update placeholder content * Revert unrelated changes * Remove missed code * Update reset email to two factor auth email * Updates and improvements * Reorg * Optimizations and reorganizations * Hook up email service * Update error handling todos, fix weak password check * Test * Localize text content * Fix lint warnings/errors * Update todos * Add error handling, enforce input restrictions * Move main constants back entry file * Fix package version check * Optimize setting error transient * Add nonce check for resend email action * Fix spacing * Fix resend nonce handling * Email service fixes * Fixes, improvements to doc consistency * Add remaining password validation * Update weak password check returns * Fix phan errors * Revert prior change * Fix meta key * Add process for add/updating recent pass list * Send auth code via wpcom only * Update method name * Optimize validation * Fix key, remove testing code * Fix docs * Fix tests * Improve matches user data logic * Remove password reset nonce verification code * Updates and fixes * Include tests for new validation methods * Include tests for new validation methods * Add password manager class tests * Remove custom nonce, add core create-user nonce check * Remove todos - always run server side validation * Update constant naming * Translate error message * Ensure styles are enqueued when viewing the password detection page * Use global page now and action check to enqueue styles * Skip recent password checks during create user action * Additional skips, and comment clarification * Revert skips of user specific reset form validation, hook provides access to this * Revert unintended additions * Return early if update is irrelevant * Only verify nonce if pass is set * Skip validation if bypass enabled * Fix test * Update methods, removes nonce checks, fix tests * Fix test * Remove comment
- Loading branch information
Showing
11 changed files
with
637 additions
and
40 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
155 changes: 155 additions & 0 deletions
155
projects/packages/account-protection/src/class-password-manager.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
<?php | ||
/** | ||
* Class used to define Password Manager. | ||
* | ||
* @package automattic/jetpack-account-protection | ||
*/ | ||
|
||
namespace Automattic\Jetpack\Account_Protection; | ||
|
||
/** | ||
* Class Password_Manager | ||
*/ | ||
class Password_Manager { | ||
/** | ||
* Validaton service instance | ||
* | ||
* @var Validation_Service | ||
*/ | ||
private $validation_service; | ||
|
||
/** | ||
* Validation_Service constructor. | ||
* | ||
* @param ?Validation_Service $validation_service Password manager instance. | ||
*/ | ||
public function __construct( ?Validation_Service $validation_service = null ) { | ||
$this->validation_service = $validation_service ?? new Validation_Service(); | ||
} | ||
|
||
/** | ||
* Validate the profile update. | ||
* | ||
* @param \WP_Error $errors The error object. | ||
* @param bool $update Whether the user is being updated. | ||
* @param \stdClass $user A copy of the new user object. | ||
* | ||
* @return void | ||
*/ | ||
public function validate_profile_update( \WP_Error $errors, bool $update, \stdClass $user ): void { | ||
if ( empty( $user->user_pass ) ) { | ||
return; | ||
} | ||
|
||
// If bypass is enabled, do not validate the password | ||
// phpcs:ignore WordPress.Security.NonceVerification | ||
if ( isset( $_POST['pw_weak'] ) && 'on' === $_POST['pw_weak'] ) { | ||
return; | ||
} | ||
|
||
if ( $update ) { | ||
if ( $this->validation_service->is_current_password( $user->ID, $user->user_pass ) ) { | ||
$errors->add( 'password_error', __( '<strong>Error:</strong> The password was used recently.', 'jetpack-account-protection' ) ); | ||
return; | ||
} | ||
} | ||
|
||
$context = $update ? 'update' : 'create-user'; | ||
$error = $this->validation_service->return_first_validation_error( $user, $user->user_pass, $context ); | ||
|
||
if ( ! empty( $error ) ) { | ||
$errors->add( 'password_error', $error ); | ||
return; | ||
} | ||
} | ||
|
||
/** | ||
* Validate the password reset. | ||
* | ||
* @param \WP_Error $errors The error object. | ||
* @param \WP_User|\WP_Error $user The user object. | ||
* | ||
* @return void | ||
*/ | ||
public function validate_password_reset( \WP_Error $errors, $user ): void { | ||
if ( is_wp_error( $user ) ) { | ||
return; | ||
} | ||
|
||
// phpcs:ignore WordPress.Security.NonceVerification | ||
if ( empty( $_POST['pass1'] ) ) { | ||
return; | ||
} | ||
|
||
// If bypass is enabled, do not validate the password | ||
// phpcs:ignore WordPress.Security.NonceVerification | ||
if ( isset( $_POST['pw_weak'] ) && 'on' === $_POST['pw_weak'] ) { | ||
return; | ||
} | ||
|
||
// phpcs:ignore WordPress.Security.NonceVerification | ||
$password = sanitize_text_field( wp_unslash( $_POST['pass1'] ) ); | ||
if ( $this->validation_service->is_current_password( $user->ID, $password ) ) { | ||
$errors->add( 'password_error', __( '<strong>Error:</strong> The password was used recently.', 'jetpack-account-protection' ) ); | ||
return; | ||
} | ||
|
||
$error = $this->validation_service->return_first_validation_error( $user, $password, 'reset' ); | ||
if ( ! empty( $error ) ) { | ||
$errors->add( 'password_error', $error ); | ||
return; | ||
} | ||
} | ||
|
||
/** | ||
* Handle the profile update. | ||
* | ||
* @param int $user_id The user ID. | ||
* @param \WP_User $old_user_data Object containing user data prior to update. | ||
* | ||
* @return void | ||
*/ | ||
public function on_profile_update( int $user_id, \WP_User $old_user_data ): void { | ||
// phpcs:ignore WordPress.Security.NonceVerification | ||
if ( isset( $_POST['action'] ) && $_POST['action'] === 'update' ) { | ||
$this->save_recent_password( $user_id, $old_user_data->user_pass ); | ||
} | ||
} | ||
|
||
/** | ||
* Handle the password reset. | ||
* | ||
* @param \WP_User $user The user. | ||
* | ||
* @return void | ||
*/ | ||
public function on_password_reset( $user ): void { | ||
$this->save_recent_password( $user->ID, $user->user_pass ); | ||
} | ||
|
||
/** | ||
* Save the new password hash to the user's recent passwords list. | ||
* | ||
* @param int $user_id The user ID. | ||
* @param string $password_hash The password hash to store. | ||
* | ||
* @return void | ||
*/ | ||
public function save_recent_password( int $user_id, string $password_hash ): void { | ||
$recent_passwords = get_user_meta( $user_id, Config::VALIDATION_SERVICE_RECENT_PASSWORD_HASHES_USER_META_KEY, true ); | ||
|
||
if ( ! is_array( $recent_passwords ) ) { | ||
$recent_passwords = array(); | ||
} | ||
|
||
if ( in_array( $password_hash, $recent_passwords, true ) ) { | ||
return; | ||
} | ||
|
||
// Add the new hashed password and keep only the last 10 | ||
array_unshift( $recent_passwords, $password_hash ); | ||
$recent_passwords = array_slice( $recent_passwords, 0, 10 ); | ||
|
||
update_user_meta( $user_id, Config::VALIDATION_SERVICE_RECENT_PASSWORD_HASHES_USER_META_KEY, $recent_passwords ); | ||
} | ||
} |
Oops, something went wrong.