Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
78.38% |
29 / 37 |
|
50.00% |
3 / 6 |
CRAP | |
0.00% |
0 / 1 |
| Password_Manager | |
78.38% |
29 / 37 |
|
50.00% |
3 / 6 |
29.82 | |
0.00% |
0 / 1 |
| __construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| validate_profile_update | |
66.67% |
6 / 9 |
|
0.00% |
0 / 1 |
5.93 | |||
| validate_password_reset | |
75.00% |
9 / 12 |
|
0.00% |
0 / 1 |
6.56 | |||
| on_profile_update | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
5 | |||
| on_password_reset | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
4 | |||
| save_recent_password_hash | |
75.00% |
6 / 8 |
|
0.00% |
0 / 1 |
3.14 | |||
| 1 | <?php |
| 2 | /** |
| 3 | * Class used to define Password Manager. |
| 4 | * |
| 5 | * @package automattic/jetpack-account-protection |
| 6 | */ |
| 7 | |
| 8 | namespace Automattic\Jetpack\Account_Protection; |
| 9 | |
| 10 | /** |
| 11 | * Class Password_Manager |
| 12 | */ |
| 13 | class Password_Manager { |
| 14 | /** |
| 15 | * Validaton service instance |
| 16 | * |
| 17 | * @var Validation_Service |
| 18 | */ |
| 19 | private $validation_service; |
| 20 | |
| 21 | /** |
| 22 | * Validation_Service constructor. |
| 23 | * |
| 24 | * @param ?Validation_Service $validation_service Password manager instance. |
| 25 | */ |
| 26 | public function __construct( ?Validation_Service $validation_service = null ) { |
| 27 | $this->validation_service = $validation_service ?? new Validation_Service(); |
| 28 | } |
| 29 | |
| 30 | /** |
| 31 | * Validate the profile update. |
| 32 | * |
| 33 | * @param \WP_Error $errors The error object. |
| 34 | * @param bool $update Whether the user is being updated. |
| 35 | * @param \stdClass $user A copy of the new user object. |
| 36 | * |
| 37 | * @return void |
| 38 | */ |
| 39 | public function validate_profile_update( \WP_Error $errors, bool $update, \stdClass $user ): void { |
| 40 | if ( empty( $user->user_pass ) ) { |
| 41 | return; |
| 42 | } |
| 43 | |
| 44 | // If bypass is enabled, do not validate the password |
| 45 | // phpcs:ignore WordPress.Security.NonceVerification |
| 46 | if ( isset( $_POST['pw_weak'] ) && 'on' === $_POST['pw_weak'] ) { |
| 47 | return; |
| 48 | } |
| 49 | |
| 50 | $core_validation_errors = $errors->get_error_messages( 'pass' ); |
| 51 | $jetpack_validation_errors = $this->validation_service->get_validation_errors( $user->user_pass, true, $user ); |
| 52 | $validation_errors = array_diff( $jetpack_validation_errors, $core_validation_errors ); |
| 53 | |
| 54 | foreach ( $validation_errors as $validation_error ) { |
| 55 | $errors->add( 'pass', $validation_error, array( 'form-field' => 'pass1' ) ); |
| 56 | } |
| 57 | } |
| 58 | |
| 59 | /** |
| 60 | * Validate the password reset. |
| 61 | * |
| 62 | * @param \WP_Error $errors The error object. |
| 63 | * @param \WP_User|\WP_Error $user The user object. |
| 64 | * |
| 65 | * @return void |
| 66 | */ |
| 67 | public function validate_password_reset( \WP_Error $errors, $user ): void { |
| 68 | if ( is_wp_error( $user ) ) { |
| 69 | return; |
| 70 | } |
| 71 | |
| 72 | // phpcs:ignore WordPress.Security.NonceVerification |
| 73 | if ( empty( $_POST['pass1'] ) ) { |
| 74 | return; |
| 75 | } |
| 76 | |
| 77 | // If bypass is enabled, do not validate the password |
| 78 | // phpcs:ignore WordPress.Security.NonceVerification |
| 79 | if ( isset( $_POST['pw_weak'] ) && 'on' === $_POST['pw_weak'] ) { |
| 80 | return; |
| 81 | } |
| 82 | |
| 83 | // phpcs:ignore WordPress.Security.NonceVerification, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized |
| 84 | $password = wp_unslash( $_POST['pass1'] ); |
| 85 | |
| 86 | $core_validation_errors = $errors->get_error_messages( 'pass' ); |
| 87 | $jetpack_validation_errors = $this->validation_service->get_validation_errors( $password ); |
| 88 | $validation_errors = array_diff( $jetpack_validation_errors, $core_validation_errors ); |
| 89 | |
| 90 | foreach ( $validation_errors as $validation_error ) { |
| 91 | $errors->add( 'pass', $validation_error, array( 'form-field' => 'pass1' ) ); |
| 92 | } |
| 93 | } |
| 94 | |
| 95 | /** |
| 96 | * Handle the profile update. |
| 97 | * |
| 98 | * @param int $user_id The user ID. |
| 99 | * @param \WP_User|\stdClass|null $old_user_data Object containing user data prior to update. |
| 100 | * |
| 101 | * @return void |
| 102 | */ |
| 103 | public function on_profile_update( int $user_id, $old_user_data ): void { |
| 104 | if ( ! is_object( $old_user_data ) || empty( $old_user_data->user_pass ) ) { |
| 105 | return; |
| 106 | } |
| 107 | |
| 108 | // phpcs:ignore WordPress.Security.NonceVerification |
| 109 | if ( isset( $_POST['action'] ) && $_POST['action'] === 'update' ) { |
| 110 | $this->save_recent_password_hash( $user_id, $old_user_data->user_pass ); |
| 111 | } |
| 112 | } |
| 113 | |
| 114 | /** |
| 115 | * Handle the password reset. |
| 116 | * |
| 117 | * @param \WP_User|\stdClass|null $user The user object. |
| 118 | * |
| 119 | * @return void |
| 120 | */ |
| 121 | public function on_password_reset( $user ): void { |
| 122 | if ( ! is_object( $user ) || ! isset( $user->ID ) || empty( $user->user_pass ) ) { |
| 123 | return; |
| 124 | } |
| 125 | |
| 126 | $this->save_recent_password_hash( $user->ID, $user->user_pass ); |
| 127 | } |
| 128 | |
| 129 | /** |
| 130 | * Save the new password hash to the user's recent passwords list. |
| 131 | * |
| 132 | * @param int $user_id The user ID. |
| 133 | * @param string $password_hash The password hash to store. |
| 134 | * |
| 135 | * @return void |
| 136 | */ |
| 137 | public function save_recent_password_hash( int $user_id, string $password_hash ): void { |
| 138 | $recent_passwords = get_user_meta( $user_id, Config::RECENT_PASSWORD_HASHES_USER_META_KEY, true ); |
| 139 | |
| 140 | if ( ! is_array( $recent_passwords ) ) { |
| 141 | $recent_passwords = array(); |
| 142 | } |
| 143 | |
| 144 | if ( in_array( $password_hash, $recent_passwords, true ) ) { |
| 145 | return; |
| 146 | } |
| 147 | |
| 148 | // Add the new hashed password and keep only the last 10 |
| 149 | array_unshift( $recent_passwords, $password_hash ); |
| 150 | $recent_passwords = array_slice( $recent_passwords, 0, Config::PASSWORD_MANAGER_RECENT_PASSWORDS_LIMIT ); |
| 151 | |
| 152 | update_user_meta( $user_id, Config::RECENT_PASSWORD_HASHES_USER_META_KEY, $recent_passwords ); |
| 153 | } |
| 154 | } |