Completed
Push — update/clear-xmlrpc-errors-on-... ( 0b51e2...beb363 )
by
unknown
32:37 queued 24:53
created

Broken_Token_XmlRpc::get_sample_error()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 19

Duplication

Lines 19
Ratio 100 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 2
dl 19
loc 19
rs 9.6333
c 0
b 0
f 0
1
<?php // phpcs:disable WordPress.PHP.DevelopmentFunctions.error_log_print_r
2
/**
3
 * XMLRPC Brokeness.
4
 *
5
 * @package Jetpack.
6
 */
7
8
use Automattic\Jetpack\Connection\Error_Handler;
9
10
/**
11
 * Class Broken_Token_XmlRpc
12
 */
13
class Broken_Token_XmlRpc {
14
15
	/**
16
	 * Broken_Token_XmlRpc constructor.
17
	 */
18
	public function __construct() {
19
20
		add_action( 'admin_menu', array( $this, 'register_submenu_page' ), 1000 );
21
22
		add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
23
24
		add_action( 'admin_post_clear_all_xmlrpc_errors', array( $this, 'admin_post_clear_all_xmlrpc_errors' ) );
25
		add_action( 'admin_post_clear_all_verified_xmlrpc_errors', array( $this, 'admin_post_clear_all_verified_xmlrpc_errors' ) );
26
		add_action( 'admin_post_refresh_verified_errors_list', array( $this, 'admin_post_refresh_verified_errors_list' ) );
27
		add_action( 'admin_post_create_error', array( $this, 'admin_post_create_error' ) );
28
		add_action( 'admin_post_clear_all_errors', array( $this, 'admin_post_clear_all_errors' ) );
29
30
		$this->error_manager   = Error_Handler::get_instance();
0 ignored issues
show
Bug introduced by
The property error_manager does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
31
		$this->stored_errors   = $this->error_manager->get_stored_errors();
0 ignored issues
show
Bug introduced by
The property stored_errors does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
32
		$this->verified_errors = $this->error_manager->get_verified_errors();
0 ignored issues
show
Bug introduced by
The property verified_errors does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
33
		$this->dev_debug_on    = defined( 'JETPACK_DEV_DEBUG' ) && JETPACK_DEV_DEBUG;
0 ignored issues
show
Bug introduced by
The property dev_debug_on does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
34
	}
35
36
	/**
37
	 * Enqueue scripts.
38
	 *
39
	 * @param string $hook Called hook.
40
	 */
41
	public function enqueue_scripts( $hook ) {
42
		if ( 'jetpack-debug_page_broken-token-xmlrpc-errors' === $hook ) {
43
			wp_enqueue_script( 'broken_token_xmlrpc_errors', plugin_dir_url( __FILE__ ) . 'js/xmlrpc-errors.js', array( 'jquery' ), JETPACK_DEBUG_HELPER_VERSION, true );
44
			wp_localize_script(
45
				'broken_token_xmlrpc_errors',
46
				'jetpack_broken_token_xmlrpc_errors',
47
				array(
48
					'verify_error_url'              => get_rest_url() . 'jetpack/v4/verify_xmlrpc_error',
49
					'admin_post_url'                => admin_url( 'admin-post.php' ),
50
					'refresh_verified_errors_nonce' => wp_create_nonce( 'refresh-verified-errors' ),
51
				)
52
			);
53
		}
54
	}
55
56
	/**
57
	 * Register submenu page.
58
	 */
59
	public function register_submenu_page() {
60
		add_submenu_page(
61
			'jetpack-debug-tools',
62
			'XML-RPC Errors',
63
			'XML-RPC Errors',
64
			'manage_options',
65
			'broken-token-xmlrpc-errors',
66
			array( $this, 'render_ui' ),
67
			99
68
		);
69
	}
70
71
	/**
72
	 * Render UI.
73
	 */
74
	public function render_ui() {
75
		?>
76
			<h1>XML-RPC errors</h1>
77
			<p>
78
				This page helps you to trigger XML-RPC requests with invalid signatures.
79
			</p>
80
			<?php if ( $this->dev_debug_on ) : ?>
81
				<div class="notice notice-success">
82
					<p>JETPACK_DEV_DEBUG constant is ON. This means every xml-rpc error will be reported. You're good to test.</p>
83
				</div>
84
			<?php else : ?>
85
				<div class="notice notice-warning">
86
					<p>JETPACK_DEV_DEBUG constant is OFF. This means xml-rpc error will only be reported once evey hour. Set it to true so you can test it.</p>
87
				</div>
88
			<?php endif; ?>
89
90
			<p>
91
				Now head to <a href="https://jetpack.com/debug/?url=<?php echo esc_url_raw( get_home_url() ); ?>">Jetpack Debugger</a> and trigger some requests!
92
			</p>
93
			<form action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" method="post">
94
				<input type="hidden" name="action" value="create_error">
95
				<?php wp_nonce_field( 'create-error' ); ?>
96
				<h3>
97
					Create fake errors
98
				</h3>
99
				<p>
100
					<input type="radio" name="token_type" value="blog" checked /> With the blog token
101
					<input type="radio" name="token_type" value="user"  /> With a user token
102
					|
103
					<input type="radio" name="verified" value="yes" checked /> Verified
104
					<input type="radio" name="verified" value="no"  /> Not verified
105
				</p>
106
				<p>
107
					<input type="submit" value="Create error" class="button button-primary">
108
				</p>
109
			</form>
110
111
			<p>
112
				<form action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" method="post">
113
					<input type="hidden" name="action" value="clear_all_errors">
114
					<?php wp_nonce_field( 'clear-all-errors' ); ?>
115
					<input type="submit" value="Clear all errors" class="button button-primary">
116
				</form>
117
			</p>
118
119
			<div id="current_xmlrpc_errors">
120
121
122
				<form action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" method="post">
123
					<input type="hidden" name="action" value="clear_all_xmlrpc_errors">
124
					<?php wp_nonce_field( 'clear-xmlrpc-errors' ); ?>
125
					<h2>
126
						Current Unverified Errors
127
						<input type="submit" value="Clear all unverified errors" class="button button-primary">
128
					</h2>
129
				</form>
130
				<p>
131
					Unverified errors are errors that were detected but that we don't know if they are legit and came from WP.com
132
				</p>
133
				<p>
134
					After an error is detected, we send a request to WP.COM and ask it to reach back to us with a nonce to confirm the error is legit. They do this by sending a request to the verify error API endpoint. You can simulate this request clicking on the "Verify error" buttons below.
135
				</p>
136
				<div id="stored-xmlrpc-error">
137
					<?php $this->print_current_errors(); ?>
138
				</div>
139
				<div id="verified-xmlrpc-error">
140
					<form action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" method="post">
141
						<input type="hidden" name="action" value="clear_all_verified_xmlrpc_errors">
142
						<?php wp_nonce_field( 'clear-verified-xmlrpc-errors' ); ?>
143
						<h2>
144
							Current Verified Errors
145
							<input type="submit" value="Clear all verified errors" class="button button-primary">
146
						</h2>
147
					</form>
148
					<p>
149
						Verified errors are errors we know are legit and now we will display them to the user or do some self healing, depending on the error.
150
					</p>
151
					<div id="verified_errors_list">
152
						<?php $this->print_verified_errors(); ?>
153
					</div>
154
				</div>
155
			</div>
156
157
		<?php
158
	}
159
160
	/**
161
	 * Print current errors.
162
	 */
163
	public function print_current_errors() {
164
		foreach ( $this->stored_errors as $error_code => $error_group ) {
165
166
			echo '<h4>' . esc_html( $error_code ) . '</h4>';
167
168
			foreach ( $error_group as $user_id => $error ) {
169
				?>
170
				<b>User: <?php echo esc_html( $user_id ); ?></b>
171
				<pre><?php print_r( $error ); ?></pre>
172
173
				<input type="button" value="Verify error (via API)" data-nonce="<?php echo esc_attr( $error['nonce'] ); ?>" class="button button-primary verify-error">
174
175
				<hr />
176
				<?php
177
			}
178
		}
179
	}
180
181
	/**
182
	 * Print verified errors.
183
	 */
184
	public function print_verified_errors() {
185
		foreach ( $this->verified_errors as $error_code => $error_group ) {
186
187
			echo '<h4>' . esc_html( $error_code ) . '</h4>';
188
189
			foreach ( $error_group as $user_id => $error ) {
190
				?>
191
				<b>User: <?php echo esc_html( $user_id ); ?></b>
192
				<pre><?php print_r( $error ); ?></pre>
193
				<hr />
194
				<?php
195
			}
196
		}
197
	}
198
199
	/**
200
	 * Clear all XMLRPC Errors.
201
	 */
202
	public function admin_post_clear_all_errors() {
203
		check_admin_referer( 'clear-all-errors' );
204
		$this->error_manager->delete_all_errors();
205
		$this->admin_post_redirect_referrer();
206
	}
207
208
	/**
209
	 * Clear all unverified XMLRPC Errors.
210
	 */
211
	public function admin_post_clear_all_xmlrpc_errors() {
212
		check_admin_referer( 'clear-xmlrpc-errors' );
213
		$this->error_manager->delete_stored_errors();
214
		$this->admin_post_redirect_referrer();
215
	}
216
217
	/**
218
	 * Clear all verified XMLRPC Errors.
219
	 */
220
	public function admin_post_clear_all_verified_xmlrpc_errors() {
221
		check_admin_referer( 'clear-verified-xmlrpc-errors' );
222
		$this->error_manager->delete_verified_errors();
223
		$this->admin_post_redirect_referrer();
224
	}
225
226
	/**
227
	 * Return the list of verified errors to dynamically refresh the interface
228
	 */
229
	public function admin_post_refresh_verified_errors_list() {
230
		check_admin_referer( 'refresh-verified-errors' );
231
		$this->print_verified_errors();
232
		exit;
233
	}
234
235
	/**
236
	 * Just redirects back to the referrer. Keeping it DRY.
237
	 */
238
	public function admin_post_redirect_referrer() {
239
		if ( wp_get_referer() ) {
240
			wp_safe_redirect( wp_get_referer() );
241
		} else {
242
			wp_safe_redirect( get_home_url() );
243
		}
244
	}
245
246
	/**
247
	 * Generates a sample WP_Error object in the same format Manager class does for broken signatures
248
	 *
249
	 * @param string $error_code The error code you want the error to have.
250
	 * @param string $user_id The user id you want the token to have.
251
	 * @return \WP_Error
252
	 */
253 View Code Duplication
	public function get_sample_error( $error_code, $user_id ) {
254
255
		$signature_details = array(
256
			'token'     => 'dhj938djh938d:1:' . $user_id,
257
			'timestamp' => time(),
258
			'nonce'     => 'asd3d32d',
259
			'body_hash' => 'dsf34frf',
260
			'method'    => 'POST',
261
			'url'       => 'https://example.org',
262
			'signature' => 'sdf234fe',
263
		);
264
265
		return new \WP_Error(
266
			$error_code,
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with $error_code.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
267
			'An error was triggered',
268
			compact( 'signature_details' )
269
		);
270
271
	}
272
273
	/**
274
	 * Creates a fake error
275
	 */
276
	public function admin_post_create_error() {
277
		check_admin_referer( 'create-error' );
278
279
		$user_id = isset( $_POST['token_type'] ) && 'user' === $_POST['token_type'] ? 1 : 0;
280
281
		$error = $this->get_sample_error( 'invalid_token', $user_id );
282
283
		$this->error_manager->store_error( $error );
284
285
		if ( isset( $_POST['verified'] ) && 'yes' === $_POST['verified'] ) {
286
			$errors = $this->error_manager->get_stored_errors();
287
			if ( isset( $errors['invalid_token'] ) && isset( $errors['invalid_token'][ $user_id ] ) ) {
288
				$this->error_manager->verify_error( $errors['invalid_token'][ $user_id ] );
289
			}
290
		}
291
292
		$this->admin_post_redirect_referrer();
293
	}
294
295
}
296
297
// phpcs:enable
298