PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3
Jetpack – WP Security, Backup, Speed, & Growth v16.3
16.3 16.3-beta 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 All 508 releases
← All changes | jetpack_vendor/automattic/jetpack-connection/src/class-secrets.php +283 -0 16.2-beta → 16.3 View file →
@@ -1,0 +1,283 @@
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 +}