| @@ -15,15 +15,17 @@ | ||
| 15 | 15 | |
| 16 | 16 | /** |
| 17 | 17 | * Connection management. |
| 18 | 18 | * |
| 19 | + * NOTE: For wordpress.org admins: This is not called in the free, hosted version. This is only used if: | |
| 20 | + * - The user explicitly entered a license key. | |
| 21 | + * - This then opens a window to our site allowing the user to authorize the connection & installation of pro. | |
| 22 | + * | |
| 19 | 23 | * @package ContentControl |
| 20 | 24 | */ |
| 21 | 25 | class Connect { |
| 22 | 26 | |
| 23 | - const API_URL = 'https://upgrade.contentcontrolplugin.com/'; | |
| 24 | - const DEBUG_MODE = false; | |
| 25 | - | |
| 27 | + const API_URL = 'https://upgrade.contentcontrolplugin.com/'; | |
| 26 | 28 | const TOKEN_OPTION_NAME = 'content_control_connect_token'; |
| 27 | 29 | const NONCE_OPTION_NAME = 'content_control_connect_nonce'; |
| 28 | 30 | |
| 29 | 31 | const ERROR_REFERRER = 1; |
| @@ -49,16 +51,25 @@ | ||
| 49 | 51 | $this->c = $c; |
| 50 | 52 | } |
| 51 | 53 | |
| 52 | 54 | /** |
| 55 | + * Check if debug mode is enabled. | |
| 56 | + * | |
| 57 | + * @return bool | |
| 58 | + */ | |
| 59 | + public function debug_mode_enabled() { | |
| 60 | + return defined( '\WP_DEBUG' ) && \WP_DEBUG; | |
| 61 | + } | |
| 62 | + | |
| 63 | + /** | |
| 53 | 64 | * Generate a new authorizatin token. |
| 54 | 65 | * |
| 55 | 66 | * @return string |
| 56 | 67 | */ |
| 57 | 68 | public function generate_token() { |
| 58 | - $token = hash( 'sha512', wp_rand() ); | |
| 69 | + $token = hash( 'sha512', (string) wp_rand() ); | |
| 59 | 70 | |
| 60 | - \update_option( self::TOKEN_OPTION_NAME, $token ); | |
| 71 | + \set_site_transient( self::TOKEN_OPTION_NAME, $token, HOUR_IN_SECONDS ); | |
| 61 | 72 | |
| 62 | 73 | return $token; |
| 63 | 74 | } |
| 64 | 75 | |
| @@ -67,9 +78,9 @@ | ||
| 67 | 78 | * |
| 68 | 79 | * @return string|false |
| 69 | 80 | */ |
| 70 | 81 | public function get_access_token() { |
| 71 | - return \get_option( self::TOKEN_OPTION_NAME, false ); | |
| 82 | + return \get_site_transient( self::TOKEN_OPTION_NAME ); | |
| 72 | 83 | } |
| 73 | 84 | |
| 74 | 85 | /** |
| 75 | 86 | * Get the current nonce. |
| @@ -88,11 +99,13 @@ | ||
| 88 | 99 | * Here to prevent constant conditional checks for the debug mode. |
| 89 | 100 | * |
| 90 | 101 | * @param string $message Message. |
| 91 | 102 | * @param string $type Type. |
| 103 | + * | |
| 104 | + * @return void | |
| 92 | 105 | */ |
| 93 | 106 | public function debug_log( $message, $type = 'INFO' ) { |
| 94 | - if ( self::DEBUG_MODE ) { | |
| 107 | + if ( $this->debug_mode_enabled() ) { | |
| 95 | 108 | plugin( 'logging' )->log( "Plugin\Connect.$type: $message" ); |
| 96 | 109 | } |
| 97 | 110 | } |
| 98 | 111 | |
| @@ -98,9 +111,9 @@ | ||
| 98 | 111 | |
| 99 | 112 | /** |
| 100 | 113 | * Get header Authorization |
| 101 | 114 | * |
| 102 | - * @return string | |
| 115 | + * @return null|string | |
| 103 | 116 | */ |
| 104 | 117 | public function get_request_authorization_header() { |
| 105 | 118 | $headers = null; |
| 106 | 119 | |
| @@ -156,9 +169,9 @@ | ||
| 156 | 169 | * Get the OAuth connect URL. |
| 157 | 170 | * |
| 158 | 171 | * @param string $license_key License key. |
| 159 | 172 | * |
| 160 | - * @return string | |
| 173 | + * @return array{url:string,back_url:string} | |
| 161 | 174 | */ |
| 162 | 175 | public function get_connect_info( $license_key ) { |
| 163 | 176 | $token = $this->generate_token(); |
| 164 | 177 | $nonce = wp_create_nonce( $this->get_nonce_name( $token ) ); |
| @@ -205,15 +218,25 @@ | ||
| 205 | 218 | |
| 206 | 219 | /** |
| 207 | 220 | * Kill the connection with no permission. |
| 208 | 221 | * |
| 209 | - * @param int $error_no Error number. | |
| 210 | - * @param string $message Error message. | |
| 222 | + * @param int $error_no Error number. | |
| 223 | + * @param string|false $message Error message. | |
| 224 | + * | |
| 225 | + * @return void | |
| 211 | 226 | */ |
| 212 | 227 | public function kill_connection( $error_no = self::ERROR_REFERRER, $message = false ) { |
| 213 | 228 | $this->debug_log( "Killing connection with error ($error_no) message: " . $message, 'ERROR' ); |
| 214 | 229 | |
| 215 | - wp_die( esc_html( self::DEBUG_MODE && $message ? $message : __( 'Sorry, You Are Not Allowed to Access This Page.', 'content-control' ) ), esc_attr( $error_no ), [ 'response' => 403 ] ); | |
| 230 | + wp_die( | |
| 231 | + esc_html( | |
| 232 | + $this->debug_mode_enabled() && $message ? | |
| 233 | + $message : | |
| 234 | + __( 'Sorry, You Are Not Allowed to Access This Page.', 'content-control' ) | |
| 235 | + ), | |
| 236 | + esc_attr( (string) $error_no ), | |
| 237 | + [ 'response' => 403 ] | |
| 238 | + ); | |
| 216 | 239 | } |
| 217 | 240 | |
| 218 | 241 | /** |
| 219 | 242 | * Verify the user agent. |
| @@ -260,8 +283,10 @@ | ||
| 260 | 283 | /** |
| 261 | 284 | * Verify the nonce. |
| 262 | 285 | * |
| 263 | 286 | * @deprecated 2.0.0 Don't use, it doesn't work as its a separate server making request. |
| 287 | + * | |
| 288 | + * @return void | |
| 264 | 289 | */ |
| 265 | 290 | public function verify_nonce() { |
| 266 | 291 | $token = $this->get_access_token(); |
| 267 | 292 | $nonce = $this->get_request_nonce(); |
| @@ -300,10 +325,14 @@ | ||
| 300 | 325 | |
| 301 | 326 | /** |
| 302 | 327 | * Generate signature hash. |
| 303 | 328 | * |
| 304 | - * @param array|string $data Data to hash. | |
| 305 | - * @param string $token Token to hash with. | |
| 329 | + * This must match the hash generated on the server. | |
| 330 | + * | |
| 331 | + * @see \ontentControlUpgrader\App::generate_hash() | |
| 332 | + * | |
| 333 | + * @param array<string,mixed>|string $data Data to hash. | |
| 334 | + * @param string $token Token to hash with. | |
| 306 | 335 | * @return string |
| 307 | 336 | */ |
| 308 | 337 | public function generate_hash( $data, $token ) { |
| 309 | 338 | // Convert boolean values to their string representation. |
| @@ -324,8 +353,9 @@ | ||
| 324 | 353 | |
| 325 | 354 | // Generate the hash binary. |
| 326 | 355 | $hash = hash_hmac( 'sha256', $data, $token, true ); |
| 327 | 356 | |
| 357 | + // The only deviation from ServerSide is that we optionally log the hash if debug mode is enabled. | |
| 328 | 358 | $this->debug_log( 'Hash: ' . $hash, 'DEBUG' ); |
| 329 | 359 | $this->debug_log( 'Data: ' . $data, 'DEBUG' ); |
| 330 | 360 | |
| 331 | 361 | // Encode the hash in base64 to make it URL safe. |
| @@ -386,9 +416,9 @@ | ||
| 386 | 416 | |
| 387 | 417 | /** |
| 388 | 418 | * Get the webhook args. |
| 389 | 419 | * |
| 390 | - * @return array | |
| 420 | + * @return array{file:string,type:string,slug:string,force:boolean} | |
| 391 | 421 | */ |
| 392 | 422 | public function get_webhook_args() { |
| 393 | 423 | $args = [ |
| 394 | 424 | // phpcs:disable WordPress.Security.NonceVerification.Recommended |
| @@ -406,9 +436,9 @@ | ||
| 406 | 436 | |
| 407 | 437 | /** |
| 408 | 438 | * Verify and return webhook args. |
| 409 | 439 | * |
| 410 | - * @param array $args The webhook args. | |
| 440 | + * @param array{file:string,type:string,slug:string,force:bool} $args The webhook args. | |
| 411 | 441 | * |
| 412 | 442 | * @return void |
| 413 | 443 | */ |
| 414 | 444 | public function verify_webhook_args( $args ) { |
| @@ -439,15 +469,9 @@ | ||
| 439 | 469 | |
| 440 | 470 | // 2. Get the webhook data. |
| 441 | 471 | $args = $this->get_webhook_args(); |
| 442 | 472 | |
| 443 | - // 3. Delete the token to prevent abuse. | |
| 444 | - if ( ! self::DEBUG_MODE ) { | |
| 445 | - $this->debug_log( 'Deleting token', 'DEBUG' ); | |
| 446 | - \delete_option( self::TOKEN_OPTION_NAME ); | |
| 447 | - } | |
| 448 | - | |
| 449 | - // 4. Validate license key. | |
| 473 | + // 3. Validate license key. | |
| 450 | 474 | if ( ! plugin( 'license' )->is_license_active() ) { |
| 451 | 475 | $this->debug_log( 'License not active', 'DEBUG' ); |
| 452 | 476 | wp_send_json_error( $error ); |
| 453 | 477 | } |
| @@ -454,19 +478,26 @@ | ||
| 454 | 478 | |
| 455 | 479 | // Set the current screen to avoid undefined notices. |
| 456 | 480 | set_current_screen( 'settings_page_content-control-settings' ); |
| 457 | 481 | |
| 482 | + // 4. Install the plugin. | |
| 458 | 483 | switch ( $args['type'] ) { |
| 459 | 484 | case 'plugin': |
| 460 | 485 | $this->install_plugin( $args ); |
| 461 | 486 | break; |
| 462 | 487 | } |
| 488 | + | |
| 489 | + // 5. Delete the token to prevent abuse. Doing so last means it should be possible to retry if something goes wrong. | |
| 490 | + if ( ! $this->debug_mode_enabled() ) { | |
| 491 | + $this->debug_log( 'Deleting token', 'DEBUG' ); | |
| 492 | + \delete_site_transient( self::TOKEN_OPTION_NAME ); | |
| 493 | + } | |
| 463 | 494 | } |
| 464 | 495 | |
| 465 | 496 | /** |
| 466 | 497 | * Install a plugin. |
| 467 | 498 | * |
| 468 | - * @param array $args The file args. | |
| 499 | + * @param array{file:string,type:string,slug:string,force:bool} $args The file args. | |
| 469 | 500 | * @return void |
| 470 | 501 | */ |
| 471 | 502 | public function install_plugin( $args ) { |
| 472 | 503 | $this->debug_log( 'Installing plugin...', 'DEBUG' ); |