PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-a.7
Jetpack – WP Security, Backup, Speed, & Growth v16.3-a.7
16.3-a.5 16.3-a.7 16.3-a.3 16.3-a.1 16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 All 506 releases
jetpack / jetpack_vendor / automattic / jetpack-connection / src / class-secrets.php

class-secrets.php in Jetpack – WP Security, Backup, Speed, & Growth 16.3-a.7, at jetpack_vendor/automattic/jetpack-connection/src/class-secrets.php

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