Completed
Push — try/refactor-secrets-and-token... ( 313ee5 )
by
unknown
381:48 queued 371:37
created

Secrets   A

Complexity

Total Complexity 26

Size/Duplication

Total Lines 246
Duplicated Lines 0 %

Coupling/Cohesion

Components 0
Dependencies 2

Importance

Changes 0
Metric Value
dl 0
loc 246
rs 10
c 0
b 0
f 0
wmc 26
lcom 0
cbo 2

5 Methods

Rating   Name   Duplication   Size   Complexity  
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
12
/**
13
 * The Jetpack Connection Secrets class that is used to manage secrets.
14
 */
15
class Secrets {
16
17
	const SECRETS_MISSING            = 'secrets_missing';
18
	const SECRETS_EXPIRED            = 'secrets_expired';
19
	const LEGACY_SECRETS_OPTION_NAME = 'jetpack_secrets';
20
21
	/**
22
	 * Runs the wp_generate_password function with the required parameters. This is the
23
	 * default implementation of the secret callable, can be overridden using the
24
	 * jetpack_connection_secret_generator filter.
25
	 *
26
	 * @return String $secret value.
27
	 */
28
	private static function secret_callable_method() {
29
		return wp_generate_password( 32, false );
30
	}
31
32
	/**
33
	 * Generates two secret tokens and the end of life timestamp for them.
34
	 *
35
	 * @param String  $action  The action name.
36
	 * @param Integer $user_id The user identifier.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $user_id not be false|integer?

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...
37
	 * @param Integer $exp     Expiration time in seconds.
38
	 */
39
	public static function generate( $action, $user_id = false, $exp = 600 ) {
40
		if ( false === $user_id ) {
41
			$user_id = get_current_user_id();
42
		}
43
44
		$callable = apply_filters( 'jetpack_connection_secret_generator', array( get_called_class(), 'secret_callable_method' ) );
45
46
		$secrets = \Jetpack_Options::get_raw_option(
47
			self::LEGACY_SECRETS_OPTION_NAME,
48
			array()
49
		);
50
51
		$secret_name = 'jetpack_' . $action . '_' . $user_id;
52
53
		if (
54
			isset( $secrets[ $secret_name ] ) &&
55
			$secrets[ $secret_name ]['exp'] > time()
56
		) {
57
			return $secrets[ $secret_name ];
58
		}
59
60
		$secret_value = array(
61
			'secret_1' => call_user_func( $callable ),
62
			'secret_2' => call_user_func( $callable ),
63
			'exp'      => time() + $exp,
64
		);
65
66
		$secrets[ $secret_name ] = $secret_value;
67
68
		$res = Jetpack_Options::update_raw_option( self::LEGACY_SECRETS_OPTION_NAME, $secrets );
69
		return $res ? $secrets[ $secret_name ] : false;
70
	}
71
72
	/**
73
	 * Returns two secret tokens and the end of life timestamp for them.
74
	 *
75
	 * @param String  $action  The action name.
76
	 * @param Integer $user_id The user identifier.
77
	 * @return string|array an array of secrets or an error string.
78
	 */
79
	public static function get( $action, $user_id ) {
80
		$secret_name = 'jetpack_' . $action . '_' . $user_id;
81
		$secrets     = \Jetpack_Options::get_raw_option(
82
			self::LEGACY_SECRETS_OPTION_NAME,
83
			array()
84
		);
85
86
		if ( ! isset( $secrets[ $secret_name ] ) ) {
87
			return self::SECRETS_MISSING;
88
		}
89
90
		if ( $secrets[ $secret_name ]['exp'] < time() ) {
91
			self::delete( $action, $user_id );
92
			return self::SECRETS_EXPIRED;
93
		}
94
95
		return $secrets[ $secret_name ];
96
	}
97
98
	/**
99
	 * Deletes secret tokens in case they, for example, have expired.
100
	 *
101
	 * @param String  $action  The action name.
102
	 * @param Integer $user_id The user identifier.
103
	 */
104
	public static function delete( $action, $user_id ) {
105
		$secret_name = 'jetpack_' . $action . '_' . $user_id;
106
		$secrets     = \Jetpack_Options::get_raw_option(
107
			self::LEGACY_SECRETS_OPTION_NAME,
108
			array()
109
		);
110
		if ( isset( $secrets[ $secret_name ] ) ) {
111
			unset( $secrets[ $secret_name ] );
112
			\Jetpack_Options::update_raw_option( self::LEGACY_SECRETS_OPTION_NAME, $secrets );
113
		}
114
	}
115
116
	/**
117
	 * Verify a Previously Generated Secret.
118
	 *
119
	 * @param string $action   The type of secret to verify.
120
	 * @param string $secret_1 The secret string to compare to what is stored.
121
	 * @param int    $user_id  The user ID of the owner of the secret.
122
	 * @return \WP_Error|string WP_Error on failure, secret_2 on success.
123
	 */
124
	public static function verify( $action, $secret_1, $user_id ) {
125
		$allowed_actions = array( 'register', 'authorize', 'publicize' );
126
		if ( ! in_array( $action, $allowed_actions, true ) ) {
127
			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...
128
		}
129
130
		$user = get_user_by( 'id', $user_id );
131
132
		/**
133
		 * We've begun verifying the previously generated secret.
134
		 *
135
		 * @since 7.5.0
136
		 *
137
		 * @param string   $action The type of secret to verify.
138
		 * @param \WP_User $user The user object.
139
		 */
140
		do_action( 'jetpack_verify_secrets_begin', $action, $user );
141
142
		$return_error = function ( \WP_Error $error ) use ( $action, $user ) {
143
			/**
144
			 * Verifying of the previously generated secret has failed.
145
			 *
146
			 * @since 7.5.0
147
			 *
148
			 * @param string    $action  The type of secret to verify.
149
			 * @param \WP_User  $user The user object.
150
			 * @param \WP_Error $error The error object.
151
			 */
152
			do_action( 'jetpack_verify_secrets_fail', $action, $user, $error );
153
154
			return $error;
155
		};
156
157
		$stored_secrets = self::get( $action, $user_id );
158
		self::delete( $action, $user_id );
159
160
		$error = null;
161
		if ( empty( $secret_1 ) ) {
162
			$error = $return_error(
163
				new \WP_Error(
164
					'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...
165
					/* translators: "%s" is the name of a paramter. It can be either "secret_1" or "state". */
166
					sprintf( __( 'The required "%s" parameter is missing.', 'jetpack' ), 'secret_1' ),
167
					400
168
				)
169
			);
170
		} elseif ( ! is_string( $secret_1 ) ) {
171
			$error = $return_error(
172
				new \WP_Error(
173
					'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...
174
					/* translators: "%s" is the name of a paramter. It can be either "secret_1" or "state". */
175
					sprintf( __( 'The required "%s" parameter is malformed.', 'jetpack' ), 'secret_1' ),
176
					400
177
				)
178
			);
179
		} elseif ( empty( $user_id ) ) {
180
			// $user_id is passed around during registration as "state".
181
			$error = $return_error(
182
				new \WP_Error(
183
					'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...
184
					/* translators: "%s" is the name of a paramter. It can be either "secret_1" or "state". */
185
					sprintf( __( 'The required "%s" parameter is missing.', 'jetpack' ), 'state' ),
186
					400
187
				)
188
			);
189
		} elseif ( ! ctype_digit( (string) $user_id ) ) {
190
			$error = $return_error(
191
				new \WP_Error(
192
					'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...
193
					/* translators: "%s" is the name of a paramter. It can be either "secret_1" or "state". */
194
					sprintf( __( 'The required "%s" parameter is malformed.', 'jetpack' ), 'state' ),
195
					400
196
				)
197
			);
198
		} elseif ( self::SECRETS_MISSING === $stored_secrets ) {
199
			$error = $return_error(
200
				new \WP_Error(
201
					'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...
202
					__( 'Verification secrets not found', 'jetpack' ),
203
					400
204
				)
205
			);
206
		} elseif ( self::SECRETS_EXPIRED === $stored_secrets ) {
207
			$error = $return_error(
208
				new \WP_Error(
209
					'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...
210
					__( 'Verification took too long', 'jetpack' ),
211
					400
212
				)
213
			);
214
		} elseif ( ! $stored_secrets ) {
215
			$error = $return_error(
216
				new \WP_Error(
217
					'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...
218
					__( 'Verification secrets are empty', 'jetpack' ),
219
					400
220
				)
221
			);
222
		} elseif ( is_wp_error( $stored_secrets ) ) {
223
			$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...
224
			$error = $return_error( $stored_secrets );
225
		} elseif ( empty( $stored_secrets['secret_1'] ) || empty( $stored_secrets['secret_2'] ) || empty( $stored_secrets['exp'] ) ) {
226
			$error = $return_error(
227
				new \WP_Error(
228
					'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...
229
					__( 'Verification secrets are incomplete', 'jetpack' ),
230
					400
231
				)
232
			);
233
		} elseif ( ! hash_equals( $secret_1, $stored_secrets['secret_1'] ) ) {
234
			$error = $return_error(
235
				new \WP_Error(
236
					'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...
237
					__( 'Secret mismatch', 'jetpack' ),
238
					400
239
				)
240
			);
241
		}
242
243
		// Something went wrong during the checks, returning the error.
244
		if ( ! empty( $error ) ) {
245
			return $error;
246
		}
247
248
		/**
249
		 * We've succeeded at verifying the previously generated secret.
250
		 *
251
		 * @since 7.5.0
252
		 *
253
		 * @param string   $action The type of secret to verify.
254
		 * @param \WP_User $user The user object.
255
		 */
256
		do_action( 'jetpack_verify_secrets_success', $action, $user );
257
258
		return $stored_secrets['secret_2'];
259
	}
260
}
261