Completed
Push — add/testing-info ( be1095...03b7e9 )
by
unknown
09:20
created

Tokens::get_access_token()   F

Complexity

Conditions 30
Paths 340

Size

Total Lines 95

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 30
nc 340
nop 3
dl 0
loc 95
rs 1.5833
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * The Jetpack Connection Tokens class file.
4
 *
5
 * @package automattic/jetpack-connection
6
 */
7
8
namespace Automattic\Jetpack\Connection;
9
10
use Automattic\Jetpack\Constants;
11
use Automattic\Jetpack\Roles;
12
use Jetpack_Options;
13
use WP_Error;
14
15
/**
16
 * The Jetpack Connection Tokens class that manages tokens.
17
 */
18
class Tokens {
19
20
	const MAGIC_NORMAL_TOKEN_KEY = ';normal;';
21
22
	/**
23
	 * Deletes all connection tokens and transients from the local Jetpack site.
24
	 */
25
	public function delete_all() {
26
		Jetpack_Options::delete_option(
27
			array(
28
				'blog_token',
29
				'user_token',
30
				'user_tokens',
31
			)
32
		);
33
	}
34
35
	/**
36
	 * Perform the API request to validate the blog and user tokens.
37
	 *
38
	 * @param int|null $user_id ID of the user we need to validate token for. Current user's ID by default.
39
	 *
40
	 * @return array|false|WP_Error The API response: `array( 'blog_token_is_healthy' => true|false, 'user_token_is_healthy' => true|false )`.
41
	 */
42
	public function validate( $user_id = null ) {
43
		$blog_id = Jetpack_Options::get_option( 'id' );
44
		if ( ! $blog_id ) {
45
			return new WP_Error( 'site_not_registered', 'Site not registered.' );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'site_not_registered'.

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...
46
		}
47
		$url = sprintf(
48
			'%s/%s/v%s/%s',
49
			Constants::get_constant( 'JETPACK__WPCOM_JSON_API_BASE' ),
50
			'wpcom',
51
			'2',
52
			'sites/' . $blog_id . '/jetpack-token-health'
53
		);
54
55
		$user_token = $this->get_access_token( $user_id ? $user_id : get_current_user_id() );
56
		$blog_token = $this->get_access_token();
57
58
		// Cannot validate non-existent tokens.
59
		if ( false === $user_token || false === $blog_token ) {
60
			return false;
61
		};
62
63
		$method   = 'POST';
64
		$body     = array(
65
			'user_token' => $this->get_signed_token( $user_token ),
66
			'blog_token' => $this->get_signed_token( $blog_token ),
67
		);
68
		$response = Client::_wp_remote_request( $url, compact( 'body', 'method' ) );
69
70 View Code Duplication
		if ( is_wp_error( $response ) || ! wp_remote_retrieve_body( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {
71
			return false;
72
		}
73
74
		$body = json_decode( wp_remote_retrieve_body( $response ), true );
75
76
		return $body ? $body : false;
77
	}
78
79
	/**
80
	 * Perform the API request to validate only the blog.
81
	 *
82
	 * @return bool|WP_Error Boolean with the test result. WP_Error if test cannot be performed.
83
	 */
84
	public function validate_blog_token() {
85
		$blog_id = Jetpack_Options::get_option( 'id' );
86
		if ( ! $blog_id ) {
87
			return new WP_Error( 'site_not_registered', 'Site not registered.' );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'site_not_registered'.

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...
88
		}
89
		$url = sprintf(
90
			'%s/%s/v%s/%s',
91
			Constants::get_constant( 'JETPACK__WPCOM_JSON_API_BASE' ),
92
			'wpcom',
93
			'2',
94
			'sites/' . $blog_id . '/jetpack-token-health/blog'
95
		);
96
97
		$method   = 'GET';
98
		$response = Client::remote_request( compact( 'url', 'method' ) );
99
100 View Code Duplication
		if ( is_wp_error( $response ) || ! wp_remote_retrieve_body( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {
101
			return false;
102
		}
103
104
		$body = json_decode( wp_remote_retrieve_body( $response ), true );
105
106
		return is_array( $body ) && isset( $body['is_healthy'] ) && true === $body['is_healthy'];
107
	}
108
109
	/**
110
	 * Obtains the auth token.
111
	 *
112
	 * @param array  $data The request data.
113
	 * @param string $token_api_url The URL of the Jetpack "token" API.
114
	 * @return object|WP_Error Returns the auth token on success.
115
	 *                          Returns a WP_Error on failure.
116
	 */
117
	public function get( $data, $token_api_url ) {
118
		$roles = new Roles();
119
		$role  = $roles->translate_current_user_to_role();
120
121
		if ( ! $role ) {
122
			return new WP_Error( 'role', __( 'An administrator for this blog must set up the Jetpack connection.', 'jetpack' ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'role'.

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...
123
		}
124
125
		$client_secret = $this->get_access_token();
126
		if ( ! $client_secret ) {
127
			return new WP_Error( 'client_secret', __( 'You need to register your Jetpack before connecting it.', 'jetpack' ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'client_secret'.

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...
128
		}
129
130
		/**
131
		 * Filter the URL of the first time the user gets redirected back to your site for connection
132
		 * data processing.
133
		 *
134
		 * @since 8.0.0
135
		 *
136
		 * @param string $redirect_url Defaults to the site admin URL.
137
		 */
138
		$processing_url = apply_filters( 'jetpack_token_processing_url', admin_url( 'admin.php' ) );
139
140
		$redirect = isset( $data['redirect'] ) ? esc_url_raw( (string) $data['redirect'] ) : '';
141
142
		/**
143
		* Filter the URL to redirect the user back to when the authentication process
144
		* is complete.
145
		*
146
		* @since 8.0.0
147
		*
148
		* @param string $redirect_url Defaults to the site URL.
149
		*/
150
		$redirect = apply_filters( 'jetpack_token_redirect_url', $redirect );
151
152
		$redirect_uri = ( 'calypso' === $data['auth_type'] )
153
			? $data['redirect_uri']
154
			: add_query_arg(
155
				array(
156
					'handler'  => 'jetpack-connection-webhooks',
157
					'action'   => 'authorize',
158
					'_wpnonce' => wp_create_nonce( "jetpack-authorize_{$role}_{$redirect}" ),
159
					'redirect' => $redirect ? rawurlencode( $redirect ) : false,
160
				),
161
				esc_url( $processing_url )
162
			);
163
164
		/**
165
		 * Filters the token request data.
166
		 *
167
		 * @since 8.0.0
168
		 *
169
		 * @param array $request_data request data.
170
		 */
171
		$body = apply_filters(
172
			'jetpack_token_request_body',
173
			array(
174
				'client_id'     => Jetpack_Options::get_option( 'id' ),
175
				'client_secret' => $client_secret->secret,
176
				'grant_type'    => 'authorization_code',
177
				'code'          => $data['code'],
178
				'redirect_uri'  => $redirect_uri,
179
			)
180
		);
181
182
		$args = array(
183
			'method'  => 'POST',
184
			'body'    => $body,
185
			'headers' => array(
186
				'Accept' => 'application/json',
187
			),
188
		);
189
		add_filter( 'http_request_timeout', array( $this, 'return_30' ), PHP_INT_MAX - 1 );
190
		$response = Client::_wp_remote_request( $token_api_url, $args );
191
		remove_filter( 'http_request_timeout', array( $this, 'return_30' ), PHP_INT_MAX - 1 );
192
193
		if ( is_wp_error( $response ) ) {
194
			return new WP_Error( 'token_http_request_failed', $response->get_error_message() );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'token_http_request_failed'.

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...
195
		}
196
197
		$code   = wp_remote_retrieve_response_code( $response );
198
		$entity = wp_remote_retrieve_body( $response );
199
200
		if ( $entity ) {
201
			$json = json_decode( $entity );
202
		} else {
203
			$json = false;
204
		}
205
206 View Code Duplication
		if ( 200 !== $code || ! empty( $json->error ) ) {
207
			if ( empty( $json->error ) ) {
208
				return new WP_Error( 'unknown', '', $code );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'unknown'.

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...
209
			}
210
211
			/* translators: Error description string. */
212
			$error_description = isset( $json->error_description ) ? sprintf( __( 'Error Details: %s', 'jetpack' ), (string) $json->error_description ) : '';
213
214
			return new WP_Error( (string) $json->error, $error_description, $code );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with (string) $json->error.

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...
215
		}
216
217
		if ( empty( $json->access_token ) || ! is_scalar( $json->access_token ) ) {
218
			return new WP_Error( 'access_token', '', $code );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'access_token'.

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...
219
		}
220
221
		if ( empty( $json->token_type ) || 'X_JETPACK' !== strtoupper( $json->token_type ) ) {
222
			return new WP_Error( 'token_type', '', $code );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'token_type'.

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...
223
		}
224
225
		if ( empty( $json->scope ) ) {
226
			return new WP_Error( 'scope', 'No Scope', $code );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'scope'.

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...
227
		}
228
229
		// TODO: get rid of the error silencer.
230
		// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
231
		@list( $role, $hmac ) = explode( ':', $json->scope );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
232
		if ( empty( $role ) || empty( $hmac ) ) {
233
			return new WP_Error( 'scope', 'Malformed Scope', $code );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'scope'.

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...
234
		}
235
236
		if ( $this->sign_role( $role ) !== $json->scope ) {
237
			return new WP_Error( 'scope', 'Invalid Scope', $code );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'scope'.

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...
238
		}
239
240
		$cap = $roles->translate_role_to_cap( $role );
241
		if ( ! $cap ) {
242
			return new WP_Error( 'scope', 'No Cap', $code );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'scope'.

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...
243
		}
244
245
		if ( ! current_user_can( $cap ) ) {
246
			return new WP_Error( 'scope', 'current_user_cannot', $code );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'scope'.

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...
247
		}
248
249
		return (string) $json->access_token;
250
	}
251
252
	/**
253
	 * Enters a user token into the user_tokens option
254
	 *
255
	 * @param int    $user_id The user id.
256
	 * @param string $token The user token.
257
	 * @param bool   $is_master_user Whether the user is the master user.
258
	 * @return bool
259
	 */
260
	public function update_user_token( $user_id, $token, $is_master_user ) {
261
		// Not designed for concurrent updates.
262
		$user_tokens = Jetpack_Options::get_option( 'user_tokens' );
263
		if ( ! is_array( $user_tokens ) ) {
264
			$user_tokens = array();
265
		}
266
		$user_tokens[ $user_id ] = $token;
267
		if ( $is_master_user ) {
268
			$master_user = $user_id;
269
			$options     = compact( 'user_tokens', 'master_user' );
270
		} else {
271
			$options = compact( 'user_tokens' );
272
		}
273
		return Jetpack_Options::update_options( $options );
274
	}
275
276
	/**
277
	 * Sign a user role with the master access token.
278
	 * If not specified, will default to the current user.
279
	 *
280
	 * @access public
281
	 *
282
	 * @param string $role    User role.
283
	 * @param int    $user_id ID of the user.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $user_id not be integer|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
284
	 * @return string Signed user role.
285
	 */
286
	public function sign_role( $role, $user_id = null ) {
287
		if ( empty( $user_id ) ) {
288
			$user_id = (int) get_current_user_id();
289
		}
290
291
		if ( ! $user_id ) {
292
			return false;
293
		}
294
295
		$token = $this->get_access_token();
296
		if ( ! $token || is_wp_error( $token ) ) {
297
			return false;
298
		}
299
300
		return $role . ':' . hash_hmac( 'md5', "{$role}|{$user_id}", $token->secret );
301
	}
302
303
	/**
304
	 * Increases the request timeout value to 30 seconds.
305
	 *
306
	 * @return int Returns 30.
307
	 */
308
	public function return_30() {
309
		return 30;
310
	}
311
312
	/**
313
	 * Gets the requested token.
314
	 *
315
	 * Tokens are one of two types:
316
	 * 1. Blog Tokens: These are the "main" tokens. Each site typically has one Blog Token,
317
	 *    though some sites can have multiple "Special" Blog Tokens (see below). These tokens
318
	 *    are not associated with a user account. They represent the site's connection with
319
	 *    the Jetpack servers.
320
	 * 2. User Tokens: These are "sub-"tokens. Each connected user account has one User Token.
321
	 *
322
	 * All tokens look like "{$token_key}.{$private}". $token_key is a public ID for the
323
	 * token, and $private is a secret that should never be displayed anywhere or sent
324
	 * over the network; it's used only for signing things.
325
	 *
326
	 * Blog Tokens can be "Normal" or "Special".
327
	 * * Normal: The result of a normal connection flow. They look like
328
	 *   "{$random_string_1}.{$random_string_2}"
329
	 *   That is, $token_key and $private are both random strings.
330
	 *   Sites only have one Normal Blog Token. Normal Tokens are found in either
331
	 *   Jetpack_Options::get_option( 'blog_token' ) (usual) or the JETPACK_BLOG_TOKEN
332
	 *   constant (rare).
333
	 * * Special: A connection token for sites that have gone through an alternative
334
	 *   connection flow. They look like:
335
	 *   ";{$special_id}{$special_version};{$wpcom_blog_id};.{$random_string}"
336
	 *   That is, $private is a random string and $token_key has a special structure with
337
	 *   lots of semicolons.
338
	 *   Most sites have zero Special Blog Tokens. Special tokens are only found in the
339
	 *   JETPACK_BLOG_TOKEN constant.
340
	 *
341
	 * In particular, note that Normal Blog Tokens never start with ";" and that
342
	 * Special Blog Tokens always do.
343
	 *
344
	 * When searching for a matching Blog Tokens, Blog Tokens are examined in the following
345
	 * order:
346
	 * 1. Defined Special Blog Tokens (via the JETPACK_BLOG_TOKEN constant)
347
	 * 2. Stored Normal Tokens (via Jetpack_Options::get_option( 'blog_token' ))
348
	 * 3. Defined Normal Tokens (via the JETPACK_BLOG_TOKEN constant)
349
	 *
350
	 * @param int|false    $user_id   false: Return the Blog Token. int: Return that user's User Token.
351
	 * @param string|false $token_key If provided, check that the token matches the provided input.
352
	 * @param bool|true    $suppress_errors If true, return a falsy value when the token isn't found; When false, return a descriptive WP_Error when the token isn't found.
353
	 *
354
	 * @return object|false
355
	 */
356
	public function get_access_token( $user_id = false, $token_key = false, $suppress_errors = true ) {
357
		$possible_special_tokens = array();
358
		$possible_normal_tokens  = array();
359
		$user_tokens             = Jetpack_Options::get_option( 'user_tokens' );
360
361
		if ( $user_id ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $user_id of type false|integer is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
362
			if ( ! $user_tokens ) {
363
				return $suppress_errors ? false : new WP_Error( 'no_user_tokens', __( 'No user tokens found', 'jetpack' ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'no_user_tokens'.

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...
364
			}
365
			if ( true === $user_id ) { // connection owner.
366
				$user_id = Jetpack_Options::get_option( 'master_user' );
367
				if ( ! $user_id ) {
368
					return $suppress_errors ? false : new WP_Error( 'empty_master_user_option', __( 'No primary user defined', 'jetpack' ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'empty_master_user_option'.

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...
369
				}
370
			}
371
			if ( ! isset( $user_tokens[ $user_id ] ) || ! $user_tokens[ $user_id ] ) {
372
				// translators: %s is the user ID.
373
				return $suppress_errors ? false : new WP_Error( 'no_token_for_user', sprintf( __( 'No token for user %d', 'jetpack' ), $user_id ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'no_token_for_user'.

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...
374
			}
375
			$user_token_chunks = explode( '.', $user_tokens[ $user_id ] );
376
			if ( empty( $user_token_chunks[1] ) || empty( $user_token_chunks[2] ) ) {
377
				// translators: %s is the user ID.
378
				return $suppress_errors ? false : new WP_Error( 'token_malformed', sprintf( __( 'Token for user %d is malformed', 'jetpack' ), $user_id ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'token_malformed'.

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...
379
			}
380
			if ( $user_token_chunks[2] !== (string) $user_id ) {
381
				// translators: %1$d is the ID of the requested user. %2$d is the user ID found in the token.
382
				return $suppress_errors ? false : new WP_Error( 'user_id_mismatch', sprintf( __( 'Requesting user_id %1$d does not match token user_id %2$d', 'jetpack' ), $user_id, $user_token_chunks[2] ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'user_id_mismatch'.

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...
383
			}
384
			$possible_normal_tokens[] = "{$user_token_chunks[0]}.{$user_token_chunks[1]}";
385
		} else {
386
			$stored_blog_token = Jetpack_Options::get_option( 'blog_token' );
387
			if ( $stored_blog_token ) {
388
				$possible_normal_tokens[] = $stored_blog_token;
389
			}
390
391
			$defined_tokens_string = Constants::get_constant( 'JETPACK_BLOG_TOKEN' );
392
393
			if ( $defined_tokens_string ) {
394
				$defined_tokens = explode( ',', $defined_tokens_string );
395
				foreach ( $defined_tokens as $defined_token ) {
396
					if ( ';' === $defined_token[0] ) {
397
						$possible_special_tokens[] = $defined_token;
398
					} else {
399
						$possible_normal_tokens[] = $defined_token;
400
					}
401
				}
402
			}
403
		}
404
405
		if ( self::MAGIC_NORMAL_TOKEN_KEY === $token_key ) {
406
			$possible_tokens = $possible_normal_tokens;
407
		} else {
408
			$possible_tokens = array_merge( $possible_special_tokens, $possible_normal_tokens );
409
		}
410
411
		if ( ! $possible_tokens ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $possible_tokens of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
412
			// If no user tokens were found, it would have failed earlier, so this is about blog token.
413
			return $suppress_errors ? false : new WP_Error( 'no_possible_tokens', __( 'No blog token found', 'jetpack' ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'no_possible_tokens'.

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...
414
		}
415
416
		$valid_token = false;
417
418
		if ( false === $token_key ) {
419
			// Use first token.
420
			$valid_token = $possible_tokens[0];
421
		} elseif ( self::MAGIC_NORMAL_TOKEN_KEY === $token_key ) {
422
			// Use first normal token.
423
			$valid_token = $possible_tokens[0]; // $possible_tokens only contains normal tokens because of earlier check.
424
		} else {
425
			// Use the token matching $token_key or false if none.
426
			// Ensure we check the full key.
427
			$token_check = rtrim( $token_key, '.' ) . '.';
428
429
			foreach ( $possible_tokens as $possible_token ) {
430
				if ( hash_equals( substr( $possible_token, 0, strlen( $token_check ) ), $token_check ) ) {
431
					$valid_token = $possible_token;
432
					break;
433
				}
434
			}
435
		}
436
437
		if ( ! $valid_token ) {
438
			if ( $user_id ) {
439
				// translators: %d is the user ID.
440
				return $suppress_errors ? false : new WP_Error( 'no_valid_user_token', sprintf( __( 'Invalid token for user %d', 'jetpack' ), $user_id ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'no_valid_user_token'.

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...
441
			} else {
442
				return $suppress_errors ? false : new WP_Error( 'no_valid_blog_token', __( 'Invalid blog token', 'jetpack' ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'no_valid_blog_token'.

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...
443
			}
444
		}
445
446
		return (object) array(
447
			'secret'           => $valid_token,
448
			'external_user_id' => (int) $user_id,
449
		);
450
	}
451
452
	/**
453
	 * Updates the blog token to a new value.
454
	 *
455
	 * @access public
456
	 *
457
	 * @param string $token the new blog token value.
458
	 * @return Boolean Whether updating the blog token was successful.
459
	 */
460
	public function update_blog_token( $token ) {
461
		return Jetpack_Options::update_option( 'blog_token', $token );
462
	}
463
464
	/**
465
	 * Unlinks the current user from the linked WordPress.com user.
466
	 *
467
	 * @access public
468
	 * @static
469
	 *
470
	 * @todo Refactor to properly load the XMLRPC client independently.
471
	 *
472
	 * @param Integer $user_id the user identifier.
473
	 * @param bool    $can_overwrite_primary_user Allow for the primary user to be disconnected.
474
	 * @return Boolean Whether the disconnection of the user was successful.
475
	 */
476
	public function disconnect_user( $user_id, $can_overwrite_primary_user = false ) {
477
		$tokens = Jetpack_Options::get_option( 'user_tokens' );
478
		if ( ! $tokens ) {
479
			return false;
480
		}
481
482
		if ( Jetpack_Options::get_option( 'master_user' ) === $user_id && ! $can_overwrite_primary_user ) {
483
			return false;
484
		}
485
486
		if ( ! isset( $tokens[ $user_id ] ) ) {
487
			return false;
488
		}
489
490
		unset( $tokens[ $user_id ] );
491
492
		Jetpack_Options::update_option( 'user_tokens', $tokens );
493
494
		return true;
495
	}
496
497
	/**
498
	 * Returns an array of user_id's that have user tokens for communicating with wpcom.
499
	 * Able to select by specific capability.
500
	 *
501
	 * @param string $capability The capability of the user.
502
	 * @return array Array of WP_User objects if found.
503
	 */
504
	public function get_connected_users( $capability = 'any' ) {
505
		$connected_users = array();
506
		$user_tokens     = Jetpack_Options::get_option( 'user_tokens' );
507
508
		if ( ! is_array( $user_tokens ) || empty( $user_tokens ) ) {
509
			return $connected_users;
510
		}
511
		$connected_user_ids = array_keys( $user_tokens );
512
513
		if ( ! empty( $connected_user_ids ) ) {
514
			foreach ( $connected_user_ids as $id ) {
515
				// Check for capability.
516
				if ( 'any' !== $capability && ! user_can( $id, $capability ) ) {
517
					continue;
518
				}
519
520
				$user_data = get_userdata( $id );
521
				if ( $user_data instanceof \WP_User ) {
0 ignored issues
show
Bug introduced by
The class WP_User does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
522
					$connected_users[] = $user_data;
523
				}
524
			}
525
		}
526
527
		return $connected_users;
528
	}
529
530
	/**
531
	 * Fetches a signed token.
532
	 *
533
	 * @param object $token the token.
534
	 * @return WP_Error|string a signed token
535
	 */
536
	public function get_signed_token( $token ) {
537
		if ( ! isset( $token->secret ) || empty( $token->secret ) ) {
538
			return new WP_Error( 'invalid_token' );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'invalid_token'.

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...
539
		}
540
541
		list( $token_key, $token_secret ) = explode( '.', $token->secret );
542
543
		$token_key = sprintf(
544
			'%s:%d:%d',
545
			$token_key,
546
			Constants::get_constant( 'JETPACK__API_VERSION' ),
547
			$token->external_user_id
548
		);
549
550
		$timestamp = time();
551
552 View Code Duplication
		if ( function_exists( 'wp_generate_password' ) ) {
553
			$nonce = wp_generate_password( 10, false );
554
		} else {
555
			$nonce = substr( sha1( wp_rand( 0, 1000000 ) ), 0, 10 );
556
		}
557
558
		$normalized_request_string = join(
559
			"\n",
560
			array(
561
				$token_key,
562
				$timestamp,
563
				$nonce,
564
			)
565
		) . "\n";
566
567
		// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
568
		$signature = base64_encode( hash_hmac( 'sha1', $normalized_request_string, $token_secret, true ) );
569
570
		$auth = array(
571
			'token'     => $token_key,
572
			'timestamp' => $timestamp,
573
			'nonce'     => $nonce,
574
			'signature' => $signature,
575
		);
576
577
		$header_pieces = array();
578
		foreach ( $auth as $key => $value ) {
579
			$header_pieces[] = sprintf( '%s="%s"', $key, $value );
580
		}
581
582
		return join( ' ', $header_pieces );
583
	}
584
}
585