Completed
Push — renovate/node-12.x ( 02b777...439792 )
by
unknown
45:19 queued 36:16
created

Tokens::validate()   B

Complexity

Conditions 7
Paths 4

Size

Total Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
nc 4
nop 1
dl 0
loc 30
rs 8.5066
c 0
b 0
f 0
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
		$method     = 'POST';
58
		$body       = array(
59
			'user_token' => $this->get_signed_token( $user_token ),
0 ignored issues
show
Security Bug introduced by
It seems like $user_token defined by $this->get_access_token(... get_current_user_id()) on line 55 can also be of type false; however, Automattic\Jetpack\Conne...ens::get_signed_token() does only seem to accept object, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
60
			'blog_token' => $this->get_signed_token( $blog_token ),
0 ignored issues
show
Security Bug introduced by
It seems like $blog_token defined by $this->get_access_token() on line 56 can also be of type false; however, Automattic\Jetpack\Conne...ens::get_signed_token() does only seem to accept object, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
61
		);
62
		$response   = Client::_wp_remote_request( $url, compact( 'body', 'method' ) );
63
64
		if ( is_wp_error( $response ) || ! wp_remote_retrieve_body( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {
65
			return false;
66
		}
67
68
		$body = json_decode( wp_remote_retrieve_body( $response ), true );
69
70
		return $body ? $body : false;
71
	}
72
73
	/**
74
	 * Obtains the auth token.
75
	 *
76
	 * @param array  $data The request data.
77
	 * @param string $token_api_url The URL of the Jetpack "token" API.
78
	 * @return object|WP_Error Returns the auth token on success.
79
	 *                          Returns a WP_Error on failure.
80
	 */
81
	public function get( $data, $token_api_url ) {
82
		$roles = new Roles();
83
		$role  = $roles->translate_current_user_to_role();
84
85
		if ( ! $role ) {
86
			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...
87
		}
88
89
		$client_secret = $this->get_access_token();
90
		if ( ! $client_secret ) {
91
			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...
92
		}
93
94
		/**
95
		 * Filter the URL of the first time the user gets redirected back to your site for connection
96
		 * data processing.
97
		 *
98
		 * @since 8.0.0
99
		 *
100
		 * @param string $redirect_url Defaults to the site admin URL.
101
		 */
102
		$processing_url = apply_filters( 'jetpack_token_processing_url', admin_url( 'admin.php' ) );
103
104
		$redirect = isset( $data['redirect'] ) ? esc_url_raw( (string) $data['redirect'] ) : '';
105
106
		/**
107
		* Filter the URL to redirect the user back to when the authentication process
108
		* is complete.
109
		*
110
		* @since 8.0.0
111
		*
112
		* @param string $redirect_url Defaults to the site URL.
113
		*/
114
		$redirect = apply_filters( 'jetpack_token_redirect_url', $redirect );
115
116
		$redirect_uri = ( 'calypso' === $data['auth_type'] )
117
			? $data['redirect_uri']
118
			: add_query_arg(
119
				array(
120
					'handler'  => 'jetpack-connection-webhooks',
121
					'action'   => 'authorize',
122
					'_wpnonce' => wp_create_nonce( "jetpack-authorize_{$role}_{$redirect}" ),
123
					'redirect' => $redirect ? rawurlencode( $redirect ) : false,
124
				),
125
				esc_url( $processing_url )
126
			);
127
128
		/**
129
		 * Filters the token request data.
130
		 *
131
		 * @since 8.0.0
132
		 *
133
		 * @param array $request_data request data.
134
		 */
135
		$body = apply_filters(
136
			'jetpack_token_request_body',
137
			array(
138
				'client_id'     => Jetpack_Options::get_option( 'id' ),
139
				'client_secret' => $client_secret->secret,
140
				'grant_type'    => 'authorization_code',
141
				'code'          => $data['code'],
142
				'redirect_uri'  => $redirect_uri,
143
			)
144
		);
145
146
		$args = array(
147
			'method'  => 'POST',
148
			'body'    => $body,
149
			'headers' => array(
150
				'Accept' => 'application/json',
151
			),
152
		);
153
		add_filter( 'http_request_timeout', array( $this, 'return_30' ), PHP_INT_MAX - 1 );
154
		$response = Client::_wp_remote_request( $token_api_url, $args );
155
		remove_filter( 'http_request_timeout', array( $this, 'return_30' ), PHP_INT_MAX - 1 );
156
157
		if ( is_wp_error( $response ) ) {
158
			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...
159
		}
160
161
		$code   = wp_remote_retrieve_response_code( $response );
162
		$entity = wp_remote_retrieve_body( $response );
163
164
		if ( $entity ) {
165
			$json = json_decode( $entity );
166
		} else {
167
			$json = false;
168
		}
169
170 View Code Duplication
		if ( 200 !== $code || ! empty( $json->error ) ) {
171
			if ( empty( $json->error ) ) {
172
				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...
173
			}
174
175
			/* translators: Error description string. */
176
			$error_description = isset( $json->error_description ) ? sprintf( __( 'Error Details: %s', 'jetpack' ), (string) $json->error_description ) : '';
177
178
			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...
179
		}
180
181
		if ( empty( $json->access_token ) || ! is_scalar( $json->access_token ) ) {
182
			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...
183
		}
184
185
		if ( empty( $json->token_type ) || 'X_JETPACK' !== strtoupper( $json->token_type ) ) {
186
			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...
187
		}
188
189
		if ( empty( $json->scope ) ) {
190
			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...
191
		}
192
193
		// TODO: get rid of the error silencer.
194
		// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
195
		@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...
196
		if ( empty( $role ) || empty( $hmac ) ) {
197
			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...
198
		}
199
200
		if ( $this->sign_role( $role ) !== $json->scope ) {
201
			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...
202
		}
203
204
		$cap = $roles->translate_role_to_cap( $role );
205
		if ( ! $cap ) {
206
			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...
207
		}
208
209
		if ( ! current_user_can( $cap ) ) {
210
			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...
211
		}
212
213
		return (string) $json->access_token;
214
	}
215
216
	/**
217
	 * Enters a user token into the user_tokens option
218
	 *
219
	 * @param int    $user_id The user id.
220
	 * @param string $token The user token.
221
	 * @param bool   $is_master_user Whether the user is the master user.
222
	 * @return bool
223
	 */
224
	public function update_user_token( $user_id, $token, $is_master_user ) {
225
		// Not designed for concurrent updates.
226
		$user_tokens = Jetpack_Options::get_option( 'user_tokens' );
227
		if ( ! is_array( $user_tokens ) ) {
228
			$user_tokens = array();
229
		}
230
		$user_tokens[ $user_id ] = $token;
231
		if ( $is_master_user ) {
232
			$master_user = $user_id;
233
			$options     = compact( 'user_tokens', 'master_user' );
234
		} else {
235
			$options = compact( 'user_tokens' );
236
		}
237
		return Jetpack_Options::update_options( $options );
238
	}
239
240
	/**
241
	 * Sign a user role with the master access token.
242
	 * If not specified, will default to the current user.
243
	 *
244
	 * @access public
245
	 *
246
	 * @param string $role    User role.
247
	 * @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...
248
	 * @return string Signed user role.
249
	 */
250
	public function sign_role( $role, $user_id = null ) {
251
		if ( empty( $user_id ) ) {
252
			$user_id = (int) get_current_user_id();
253
		}
254
255
		if ( ! $user_id ) {
256
			return false;
257
		}
258
259
		$token = $this->get_access_token();
260
		if ( ! $token || is_wp_error( $token ) ) {
261
			return false;
262
		}
263
264
		return $role . ':' . hash_hmac( 'md5', "{$role}|{$user_id}", $token->secret );
265
	}
266
267
	/**
268
	 * Increases the request timeout value to 30 seconds.
269
	 *
270
	 * @return int Returns 30.
271
	 */
272
	public function return_30() {
273
		return 30;
274
	}
275
276
	/**
277
	 * Gets the requested token.
278
	 *
279
	 * Tokens are one of two types:
280
	 * 1. Blog Tokens: These are the "main" tokens. Each site typically has one Blog Token,
281
	 *    though some sites can have multiple "Special" Blog Tokens (see below). These tokens
282
	 *    are not associated with a user account. They represent the site's connection with
283
	 *    the Jetpack servers.
284
	 * 2. User Tokens: These are "sub-"tokens. Each connected user account has one User Token.
285
	 *
286
	 * All tokens look like "{$token_key}.{$private}". $token_key is a public ID for the
287
	 * token, and $private is a secret that should never be displayed anywhere or sent
288
	 * over the network; it's used only for signing things.
289
	 *
290
	 * Blog Tokens can be "Normal" or "Special".
291
	 * * Normal: The result of a normal connection flow. They look like
292
	 *   "{$random_string_1}.{$random_string_2}"
293
	 *   That is, $token_key and $private are both random strings.
294
	 *   Sites only have one Normal Blog Token. Normal Tokens are found in either
295
	 *   Jetpack_Options::get_option( 'blog_token' ) (usual) or the JETPACK_BLOG_TOKEN
296
	 *   constant (rare).
297
	 * * Special: A connection token for sites that have gone through an alternative
298
	 *   connection flow. They look like:
299
	 *   ";{$special_id}{$special_version};{$wpcom_blog_id};.{$random_string}"
300
	 *   That is, $private is a random string and $token_key has a special structure with
301
	 *   lots of semicolons.
302
	 *   Most sites have zero Special Blog Tokens. Special tokens are only found in the
303
	 *   JETPACK_BLOG_TOKEN constant.
304
	 *
305
	 * In particular, note that Normal Blog Tokens never start with ";" and that
306
	 * Special Blog Tokens always do.
307
	 *
308
	 * When searching for a matching Blog Tokens, Blog Tokens are examined in the following
309
	 * order:
310
	 * 1. Defined Special Blog Tokens (via the JETPACK_BLOG_TOKEN constant)
311
	 * 2. Stored Normal Tokens (via Jetpack_Options::get_option( 'blog_token' ))
312
	 * 3. Defined Normal Tokens (via the JETPACK_BLOG_TOKEN constant)
313
	 *
314
	 * @param int|false    $user_id   false: Return the Blog Token. int: Return that user's User Token.
315
	 * @param string|false $token_key If provided, check that the token matches the provided input.
316
	 * @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.
317
	 *
318
	 * @return object|false
319
	 */
320
	public function get_access_token( $user_id = false, $token_key = false, $suppress_errors = true ) {
321
		$possible_special_tokens = array();
322
		$possible_normal_tokens  = array();
323
		$user_tokens             = Jetpack_Options::get_option( 'user_tokens' );
324
325
		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...
326
			if ( ! $user_tokens ) {
327
				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...
328
			}
329
			if ( true === $user_id ) { // connection owner.
330
				$user_id = Jetpack_Options::get_option( 'master_user' );
331
				if ( ! $user_id ) {
332
					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...
333
				}
334
			}
335
			if ( ! isset( $user_tokens[ $user_id ] ) || ! $user_tokens[ $user_id ] ) {
336
				// translators: %s is the user ID.
337
				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...
338
			}
339
			$user_token_chunks = explode( '.', $user_tokens[ $user_id ] );
340 View Code Duplication
			if ( empty( $user_token_chunks[1] ) || empty( $user_token_chunks[2] ) ) {
341
				// translators: %s is the user ID.
342
				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...
343
			}
344
			if ( $user_token_chunks[2] !== (string) $user_id ) {
345
				// translators: %1$d is the ID of the requested user. %2$d is the user ID found in the token.
346
				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...
347
			}
348
			$possible_normal_tokens[] = "{$user_token_chunks[0]}.{$user_token_chunks[1]}";
349
		} else {
350
			$stored_blog_token = Jetpack_Options::get_option( 'blog_token' );
351
			if ( $stored_blog_token ) {
352
				$possible_normal_tokens[] = $stored_blog_token;
353
			}
354
355
			$defined_tokens_string = Constants::get_constant( 'JETPACK_BLOG_TOKEN' );
356
357
			if ( $defined_tokens_string ) {
358
				$defined_tokens = explode( ',', $defined_tokens_string );
359
				foreach ( $defined_tokens as $defined_token ) {
360
					if ( ';' === $defined_token[0] ) {
361
						$possible_special_tokens[] = $defined_token;
362
					} else {
363
						$possible_normal_tokens[] = $defined_token;
364
					}
365
				}
366
			}
367
		}
368
369
		if ( self::MAGIC_NORMAL_TOKEN_KEY === $token_key ) {
370
			$possible_tokens = $possible_normal_tokens;
371
		} else {
372
			$possible_tokens = array_merge( $possible_special_tokens, $possible_normal_tokens );
373
		}
374
375
		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...
376
			// If no user tokens were found, it would have failed earlier, so this is about blog token.
377
			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...
378
		}
379
380
		$valid_token = false;
381
382
		if ( false === $token_key ) {
383
			// Use first token.
384
			$valid_token = $possible_tokens[0];
385
		} elseif ( self::MAGIC_NORMAL_TOKEN_KEY === $token_key ) {
386
			// Use first normal token.
387
			$valid_token = $possible_tokens[0]; // $possible_tokens only contains normal tokens because of earlier check.
388
		} else {
389
			// Use the token matching $token_key or false if none.
390
			// Ensure we check the full key.
391
			$token_check = rtrim( $token_key, '.' ) . '.';
392
393
			foreach ( $possible_tokens as $possible_token ) {
394
				if ( hash_equals( substr( $possible_token, 0, strlen( $token_check ) ), $token_check ) ) {
395
					$valid_token = $possible_token;
396
					break;
397
				}
398
			}
399
		}
400
401
		if ( ! $valid_token ) {
402
			if ( $user_id ) {
403
				// translators: %d is the user ID.
404
				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...
405
			} else {
406
				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...
407
			}
408
		}
409
410
		return (object) array(
411
			'secret'           => $valid_token,
412
			'external_user_id' => (int) $user_id,
413
		);
414
	}
415
416
	/**
417
	 * Updates the blog token to a new value.
418
	 *
419
	 * @access public
420
	 *
421
	 * @param string $token the new blog token value.
422
	 * @return Boolean Whether updating the blog token was successful.
423
	 */
424
	public function update_blog_token( $token ) {
425
		return Jetpack_Options::update_option( 'blog_token', $token );
426
	}
427
428
	/**
429
	 * Unlinks the current user from the linked WordPress.com user.
430
	 *
431
	 * @access public
432
	 * @static
433
	 *
434
	 * @todo Refactor to properly load the XMLRPC client independently.
435
	 *
436
	 * @param Integer $user_id the user identifier.
437
	 * @param bool    $can_overwrite_primary_user Allow for the primary user to be disconnected.
438
	 * @return Boolean Whether the disconnection of the user was successful.
439
	 */
440
	public function disconnect_user( $user_id, $can_overwrite_primary_user = false ) {
441
		$tokens = Jetpack_Options::get_option( 'user_tokens' );
442
		if ( ! $tokens ) {
443
			return false;
444
		}
445
446
		if ( Jetpack_Options::get_option( 'master_user' ) === $user_id && ! $can_overwrite_primary_user ) {
447
			return false;
448
		}
449
450
		if ( ! isset( $tokens[ $user_id ] ) ) {
451
			return false;
452
		}
453
454
		unset( $tokens[ $user_id ] );
455
456
		Jetpack_Options::update_option( 'user_tokens', $tokens );
457
458
		return true;
459
	}
460
461
	/**
462
	 * Returns an array of user_id's that have user tokens for communicating with wpcom.
463
	 * Able to select by specific capability.
464
	 *
465
	 * @param string $capability The capability of the user.
466
	 * @return array Array of WP_User objects if found.
467
	 */
468
	public function get_connected_users( $capability = 'any' ) {
469
		$connected_users = array();
470
		$user_tokens     = Jetpack_Options::get_option( 'user_tokens' );
471
472
		if ( ! is_array( $user_tokens ) || empty( $user_tokens ) ) {
473
			return $connected_users;
474
		}
475
		$connected_user_ids = array_keys( $user_tokens );
476
477
		if ( ! empty( $connected_user_ids ) ) {
478
			foreach ( $connected_user_ids as $id ) {
479
				// Check for capability.
480
				if ( 'any' !== $capability && ! user_can( $id, $capability ) ) {
481
					continue;
482
				}
483
484
				$user_data = get_userdata( $id );
485
				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...
486
					$connected_users[] = $user_data;
487
				}
488
			}
489
		}
490
491
		return $connected_users;
492
	}
493
494
	/**
495
	 * Fetches a signed token.
496
	 *
497
	 * @param object $token the token.
498
	 * @return WP_Error|string a signed token
499
	 */
500
	public function get_signed_token( $token ) {
501
		if ( ! isset( $token->secret ) || empty( $token->secret ) ) {
502
			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...
503
		}
504
505
		list( $token_key, $token_secret ) = explode( '.', $token->secret );
506
507
		$token_key = sprintf(
508
			'%s:%d:%d',
509
			$token_key,
510
			Constants::get_constant( 'JETPACK__API_VERSION' ),
511
			$token->external_user_id
512
		);
513
514
		$timestamp = time();
515
516 View Code Duplication
		if ( function_exists( 'wp_generate_password' ) ) {
517
			$nonce = wp_generate_password( 10, false );
518
		} else {
519
			$nonce = substr( sha1( wp_rand( 0, 1000000 ) ), 0, 10 );
520
		}
521
522
		$normalized_request_string = join(
523
			"\n",
524
			array(
525
				$token_key,
526
				$timestamp,
527
				$nonce,
528
			)
529
		) . "\n";
530
531
		// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
532
		$signature = base64_encode( hash_hmac( 'sha1', $normalized_request_string, $token_secret, true ) );
533
534
		$auth = array(
535
			'token'     => $token_key,
536
			'timestamp' => $timestamp,
537
			'nonce'     => $nonce,
538
			'signature' => $signature,
539
		);
540
541
		$header_pieces = array();
542
		foreach ( $auth as $key => $value ) {
543
			$header_pieces[] = sprintf( '%s="%s"', $key, $value );
544
		}
545
546
		return join( ' ', $header_pieces );
547
	}
548
}
549