Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
26.00% covered (danger)
26.00%
13 / 50
20.00% covered (danger)
20.00%
2 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
Password_Strength_Meter
26.00% covered (danger)
26.00%
13 / 50
20.00% covered (danger)
20.00%
2 / 10
165.29
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 verify_nonce
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 send_json_error
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 send_json_success
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 validate_password_ajax
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
5
 enqueue_jetpack_password_strength_meter_profile_script
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 enqueue_jetpack_password_strength_meter_reset_script
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
20
 localize_jetpack_data
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
2
 enqueue_script
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 enqueue_styles
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * Class used to define Password Strength Meter.
4 *
5 * @package automattic/jetpack-account-protection
6 */
7
8namespace Automattic\Jetpack\Account_Protection;
9
10use Automattic\Jetpack\Assets\Logo as Jetpack_Logo;
11
12/**
13 * Class Password_Strength_Meter
14 */
15class Password_Strength_Meter {
16    /**
17     * Validaton service instance
18     *
19     * @var Validation_Service
20     */
21    private $validation_service;
22
23    /**
24     * Validation_Service constructor.
25     *
26     * @param ?Validation_Service $validation_service Password manager instance.
27     */
28    public function __construct( ?Validation_Service $validation_service = null ) {
29        $this->validation_service = $validation_service ?? new Validation_Service();
30    }
31
32    /**
33     * Wrapper method for nonce verification.
34     *
35     * @param string $nonce  Nonce value.
36     * @param string $action Nonce action.
37     *
38     * @return bool
39     */
40    protected function verify_nonce( string $nonce, string $action ): bool {
41        return wp_verify_nonce( $nonce, $action );
42    }
43
44    /**
45     * Wrapper method for sending a JSON error response.
46     *
47     * @param string $message The error message.
48     *
49     * @return void
50     */
51    protected function send_json_error( string $message ): void {
52        // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- It takes null, but its phpdoc only says int.
53        wp_send_json_error( array( 'message' => $message ), null, JSON_UNESCAPED_SLASHES );
54    }
55
56    /**
57     * Wrapper method for sending a JSON success response.
58     *
59     * @param array $data The data to send.
60     *
61     * @return void
62     */
63    protected function send_json_success( array $data ): void {
64        // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- It takes null, but its phpdoc only says int.
65        wp_send_json_success( $data, null, JSON_UNESCAPED_SLASHES );
66    }
67
68    /**
69     * AJAX endpoint for password validation.
70     *
71     * @return void
72     */
73    public function validate_password_ajax(): void {
74        // phpcs:disable WordPress.Security.NonceVerification
75        if ( ! isset( $_POST['password'] ) ) {
76            $this->send_json_error( __( 'No password provided.', 'jetpack-account-protection' ) );
77            return;
78        }
79
80        if ( ! isset( $_POST['nonce'] ) || ! $this->verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'validate_password_nonce' ) ) {
81            $this->send_json_error( __( 'Invalid nonce.', 'jetpack-account-protection' ) );
82            return;
83        }
84
85        $user_specific = false;
86        if ( isset( $_POST['user_specific'] ) ) {
87            $user_specific = filter_var( sanitize_text_field( wp_unslash( $_POST['user_specific'] ) ), FILTER_VALIDATE_BOOLEAN );
88        }
89
90        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
91        $password = wp_unslash( $_POST['password'] );
92        // phpcs:enable WordPress.Security.NonceVerification
93        $state = $this->validation_service->get_validation_state( $password, $user_specific );
94
95        $this->send_json_success( array( 'state' => $state ) );
96    }
97
98    /**
99     * Enqueue the password strength meter script on the profile page.
100     *
101     * @return void
102     */
103    public function enqueue_jetpack_password_strength_meter_profile_script(): void {
104        global $pagenow;
105
106        if ( ! isset( $pagenow ) || ! in_array( $pagenow, array( 'profile.php', 'user-new.php', 'user-edit.php' ), true ) ) {
107            return;
108        }
109
110        $this->enqueue_script();
111        $this->enqueue_styles();
112
113        // Only profile page should run user specific checks.
114        $this->localize_jetpack_data( 'profile.php' === $pagenow );
115    }
116
117    /**
118     * Enqueue the password strength meter script on the reset password page.
119     *
120     * @return void
121     */
122    public function enqueue_jetpack_password_strength_meter_reset_script(): void {
123        // No nonce verification necessary as the action includes a robust verification process
124        // phpcs:ignore WordPress.Security.NonceVerification
125        if ( isset( $_GET['action'] ) && ( 'rp' === $_GET['action'] || 'resetpass' === $_GET['action'] ) ) {
126            $this->enqueue_script();
127            $this->enqueue_styles();
128            $this->localize_jetpack_data();
129        }
130    }
131
132    /**
133     * Localize the Jetpack data for the password strength meter.
134     *
135     * @param bool $user_specific Whether or not to run user specific checks.
136     *
137     * @return void
138     */
139    public function localize_jetpack_data( bool $user_specific = false ): void {
140        $jetpack_logo = new Jetpack_Logo();
141
142        wp_localize_script(
143            'jetpack-password-strength-meter',
144            'jetpackData',
145            array(
146                'ajaxurl'                => admin_url( 'admin-ajax.php' ),
147                'nonce'                  => wp_create_nonce( 'validate_password_nonce' ),
148                'userSpecific'           => $user_specific,
149                'logo'                   => htmlspecialchars( $jetpack_logo->get_jp_emblem( true ), ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401 ),
150                'validationInitialState' => $this->validation_service->get_validation_initial_state( $user_specific ),
151            )
152        );
153    }
154
155    /**
156     * Enqueue the password strength meter script.
157     *
158     * @return void
159     */
160    public function enqueue_script(): void {
161        wp_enqueue_script(
162            'jetpack-password-strength-meter',
163            plugin_dir_url( __FILE__ ) . 'js/jetpack-password-strength-meter.js',
164            array( 'jquery' ),
165            Account_Protection::PACKAGE_VERSION,
166            true
167        );
168    }
169
170    /**
171     * Enqueue the password strength meter styles.
172     *
173     * @return void
174     */
175    public function enqueue_styles(): void {
176        wp_enqueue_style(
177            'strength-meter-styles',
178            plugin_dir_url( __FILE__ ) . 'css/strength-meter.css',
179            array(),
180            Account_Protection::PACKAGE_VERSION
181        );
182    }
183}