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

Secrets   A

Complexity

Total Complexity 27

Size/Duplication

Total Lines 253
Duplicated Lines 0 %

Coupling/Cohesion

Components 0
Dependencies 2

Importance

Changes 0
Metric Value
dl 0
loc 253
rs 10
c 0
b 0
f 0
wmc 27
lcom 0
cbo 2

6 Methods

Rating   Name   Duplication   Size   Complexity  
A delete_all() 0 3 1
A secret_callable_method() 0 3 1
A generate() 0 32 5
A get() 0 18 3
A delete() 0 11 2
D verify() 0 136 15
1
<?php
2
/**
3
 * The Jetpack Connection Secrets class file.
4
 *
5
 * @package automattic/jetpack-connection
6
 */
7
8
namespace Automattic\Jetpack\Connection;
9
10
use Jetpack_Options;
11
use WP_Error;
12
13
/**
14
 * The Jetpack Connection Secrets class that is used to manage secrets.
15
 */
16
class Secrets {
17
18
	const SECRETS_MISSING            = 'secrets_missing';
19
	const SECRETS_EXPIRED            = 'secrets_expired';
20
	const LEGACY_SECRETS_OPTION_NAME = 'jetpack_secrets';
21
22
	/**
23
	 * Deletes all connection secrets from the local Jetpack site.
24
	 */
25
	public function delete_all() {
26
		Jetpack_Options::delete_raw_option( 'jetpack_secrets' );
27
	}
28
29
	/**
30
	 * Runs the wp_generate_password function with the required parameters. This is the
31
	 * default implementation of the secret callable, can be overridden using the
32
	 * jetpack_connection_secret_generator filter.
33
	 *
34
	 * @return String $secret value.
35
	 */
36
	private function secret_callable_method() {
37
		return wp_generate_password( 32, false );
38
	}
39
40
	/**
41
	 * Generates two secret tokens and the end of life timestamp for them.
42
	 *
43
	 * @param String       $action       The action name.
44
	 * @param Integer|bool $user_id The user identifier. Defaults to `false`.
45
	 * @param Integer      $exp          Expiration time in seconds.
46
	 */
47
	public function generate( $action, $user_id = false, $exp = 600 ) {
48
		if ( false === $user_id ) {
49
			$user_id = get_current_user_id();
50
		}
51
52
		$callable = apply_filters( 'jetpack_connection_secret_generator', array( get_called_class(), 'secret_callable_method' ) );
53
54
		$secrets = Jetpack_Options::get_raw_option(
55
			self::LEGACY_SECRETS_OPTION_NAME,
56
			array()
57
		);
58
59
		$secret_name = 'jetpack_' . $action . '_' . $user_id;
60
61
		if (
62
			isset( $secrets[ $secret_name ] ) &&
63
			$secrets[ $secret_name ]['exp'] > time()
64
		) {
65
			return $secrets[ $secret_name ];
66
		}
67
68
		$secret_value = array(
69
			'secret_1' => call_user_func( $callable ),
70
			'secret_2' => call_user_func( $callable ),
71
			'exp'      => time() + $exp,
72
		);
73
74
		$secrets[ $secret_name ] = $secret_value;
75
76
		$res = Jetpack_Options::update_raw_option( self::LEGACY_SECRETS_OPTION_NAME, $secrets );
77
		return $res ? $secrets[ $secret_name ] : false;
78
	}
79
80
	/**
81
	 * Returns two secret tokens and the end of life timestamp for them.
82
	 *
83
	 * @param String  $action  The action name.
84
	 * @param Integer $user_id The user identifier.
85
	 * @return string|array an array of secrets or an error string.
86
	 */
87
	public function get( $action, $user_id ) {
88
		$secret_name = 'jetpack_' . $action . '_' . $user_id;
89
		$secrets     = Jetpack_Options::get_raw_option(
90
			self::LEGACY_SECRETS_OPTION_NAME,
91
			array()
92
		);
93
94
		if ( ! isset( $secrets[ $secret_name ] ) ) {
95
			return self::SECRETS_MISSING;
96
		}
97
98
		if ( $secrets[ $secret_name ]['exp'] < time() ) {
99
			$this->delete( $action, $user_id );
100
			return self::SECRETS_EXPIRED;
101
		}
102
103
		return $secrets[ $secret_name ];
104
	}
105
106
	/**
107
	 * Deletes secret tokens in case they, for example, have expired.
108
	 *
109
	 * @param String  $action  The action name.
110
	 * @param Integer $user_id The user identifier.
111
	 */
112
	public function delete( $action, $user_id ) {
113
		$secret_name = 'jetpack_' . $action . '_' . $user_id;
114
		$secrets     = Jetpack_Options::get_raw_option(
115
			self::LEGACY_SECRETS_OPTION_NAME,
116
			array()
117
		);
118
		if ( isset( $secrets[ $secret_name ] ) ) {
119
			unset( $secrets[ $secret_name ] );
120
			Jetpack_Options::update_raw_option( self::LEGACY_SECRETS_OPTION_NAME, $secrets );
121
		}
122
	}
123
124
	/**
125
	 * Verify a Previously Generated Secret.
126
	 *
127
	 * @param string $action   The type of secret to verify.
128
	 * @param string $secret_1 The secret string to compare to what is stored.
129
	 * @param int    $user_id  The user ID of the owner of the secret.
130
	 * @return WP_Error|string WP_Error on failure, secret_2 on success.
131
	 */
132
	public function verify( $action, $secret_1, $user_id ) {
133
		$allowed_actions = array( 'register', 'authorize', 'publicize' );
134
		if ( ! in_array( $action, $allowed_actions, true ) ) {
135
			return new WP_Error( 'unknown_verification_action', 'Unknown Verification Action', 400 );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'unknown_verification_action'.

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...
136
		}
137
138
		$user = get_user_by( 'id', $user_id );
139
140
		/**
141
		 * We've begun verifying the previously generated secret.
142
		 *
143
		 * @since 7.5.0
144
		 *
145
		 * @param string   $action The type of secret to verify.
146
		 * @param \WP_User $user The user object.
147
		 */
148
		do_action( 'jetpack_verify_secrets_begin', $action, $user );
149
150
		$return_error = function ( WP_Error $error ) use ( $action, $user ) {
151
			/**
152
			 * Verifying of the previously generated secret has failed.
153
			 *
154
			 * @since 7.5.0
155
			 *
156
			 * @param string    $action  The type of secret to verify.
157
			 * @param \WP_User  $user The user object.
158
			 * @param WP_Error $error The error object.
159
			 */
160
			do_action( 'jetpack_verify_secrets_fail', $action, $user, $error );
161
162
			return $error;
163
		};
164
165
		$stored_secrets = $this->get( $action, $user_id );
166
		$this->delete( $action, $user_id );
167
168
		$error = null;
169
		if ( empty( $secret_1 ) ) {
170
			$error = $return_error(
171
				new WP_Error(
172
					'verify_secret_1_missing',
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'verify_secret_1_missing'.

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
					/* translators: "%s" is the name of a paramter. It can be either "secret_1" or "state". */
174
					sprintf( __( 'The required "%s" parameter is missing.', 'jetpack' ), 'secret_1' ),
175
					400
176
				)
177
			);
178
		} elseif ( ! is_string( $secret_1 ) ) {
179
			$error = $return_error(
180
				new WP_Error(
181
					'verify_secret_1_malformed',
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'verify_secret_1_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...
182
					/* translators: "%s" is the name of a paramter. It can be either "secret_1" or "state". */
183
					sprintf( __( 'The required "%s" parameter is malformed.', 'jetpack' ), 'secret_1' ),
184
					400
185
				)
186
			);
187
		} elseif ( empty( $user_id ) ) {
188
			// $user_id is passed around during registration as "state".
189
			$error = $return_error(
190
				new WP_Error(
191
					'state_missing',
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'state_missing'.

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...
192
					/* translators: "%s" is the name of a paramter. It can be either "secret_1" or "state". */
193
					sprintf( __( 'The required "%s" parameter is missing.', 'jetpack' ), 'state' ),
194
					400
195
				)
196
			);
197
		} elseif ( ! ctype_digit( (string) $user_id ) ) {
198
			$error = $return_error(
199
				new WP_Error(
200
					'state_malformed',
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'state_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...
201
					/* translators: "%s" is the name of a paramter. It can be either "secret_1" or "state". */
202
					sprintf( __( 'The required "%s" parameter is malformed.', 'jetpack' ), 'state' ),
203
					400
204
				)
205
			);
206
		} elseif ( self::SECRETS_MISSING === $stored_secrets ) {
207
			$error = $return_error(
208
				new WP_Error(
209
					'verify_secrets_missing',
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'verify_secrets_missing'.

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...
210
					__( 'Verification secrets not found', 'jetpack' ),
211
					400
212
				)
213
			);
214
		} elseif ( self::SECRETS_EXPIRED === $stored_secrets ) {
215
			$error = $return_error(
216
				new WP_Error(
217
					'verify_secrets_expired',
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'verify_secrets_expired'.

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...
218
					__( 'Verification took too long', 'jetpack' ),
219
					400
220
				)
221
			);
222
		} elseif ( ! $stored_secrets ) {
223
			$error = $return_error(
224
				new WP_Error(
225
					'verify_secrets_empty',
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'verify_secrets_empty'.

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...
226
					__( 'Verification secrets are empty', 'jetpack' ),
227
					400
228
				)
229
			);
230
		} elseif ( is_wp_error( $stored_secrets ) ) {
231
			$stored_secrets->add_data( 400 );
0 ignored issues
show
Bug introduced by
The method add_data cannot be called on $stored_secrets (of type string|array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
232
			$error = $return_error( $stored_secrets );
233
		} elseif ( empty( $stored_secrets['secret_1'] ) || empty( $stored_secrets['secret_2'] ) || empty( $stored_secrets['exp'] ) ) {
234
			$error = $return_error(
235
				new WP_Error(
236
					'verify_secrets_incomplete',
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'verify_secrets_incomplete'.

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...
237
					__( 'Verification secrets are incomplete', 'jetpack' ),
238
					400
239
				)
240
			);
241
		} elseif ( ! hash_equals( $secret_1, $stored_secrets['secret_1'] ) ) {
242
			$error = $return_error(
243
				new WP_Error(
244
					'verify_secrets_mismatch',
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'verify_secrets_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...
245
					__( 'Secret mismatch', 'jetpack' ),
246
					400
247
				)
248
			);
249
		}
250
251
		// Something went wrong during the checks, returning the error.
252
		if ( ! empty( $error ) ) {
253
			return $error;
254
		}
255
256
		/**
257
		 * We've succeeded at verifying the previously generated secret.
258
		 *
259
		 * @since 7.5.0
260
		 *
261
		 * @param string   $action The type of secret to verify.
262
		 * @param \WP_User $user The user object.
263
		 */
264
		do_action( 'jetpack_verify_secrets_success', $action, $user );
265
266
		return $stored_secrets['secret_2'];
267
	}
268
}
269