d_code() ) ); } /** * Permission check for the disconnect site endpoint. * * @since 1.30.1 * * @return bool|WP_Error True if user is able to disconnect the site. */ public static function disconnect_site_permission_check() { if ( current_user_can( 'jetpack_disconnect' ) ) { return true; } return new WP_Error( 'invalid_user_permission_jetpack_disconnect', self::get_user_permissions_error_msg(), array( 'status' => rest_authorization_required_code() ) ); } /** * Get miscellaneous user data related to the connection. Similar data available in old "My Jetpack". * Information about the master/primary user. * Information about the current user. * * @param bool $rest_response Should we return a rest response or a simple array. Default to rest response. * * @since 1.30.1 * * @return \WP_REST_Response|array */ public static function get_user_connection_data( $rest_response = true ) { $blog_id = \Jetpack_Options::get_option( 'id' ); $connection = new Manager(); $current_user = wp_get_current_user(); $connection_owner = $connection->get_connection_owner(); $owner_display_name = false === $connection_owner ? null : $connection_owner->display_name; $is_user_connected = $connection->is_user_connected(); $is_master_user = false === $connection_owner ? false : ( $current_user->ID === $connection_owner->ID ); $wpcom_user_data = $connection->get_connected_user_data(); // Add connected user gravatar to the returned wpcom_user_data. // Probably we shouldn't do this when $wpcom_user_data is false, but we have been since 2016 so // clients probably expect that by now. if ( false === $wpcom_user_data ) { $wpcom_user_data = array(); } $wpcom_user_data['avatar'] = ( ! empty( $wpcom_user_data['email'] ) ? get_avatar_url( $wpcom_user_data['email'], array( 'size' => 64, 'default' => 'mysteryman', ) ) : false ); $current_user_connection_data = array( 'isConnected' => $is_user_connected, 'isMaster' => $is_master_user, 'username' => $current_user->user_login, 'id' => $current_user->ID, 'blogId' => $blog_id, 'wpcomUser' => $wpcom_user_data, 'gravatar' => get_avatar_url( $current_user->ID, 64, 'mm', '', array( 'force_display' => true ) ), 'permissions' => array( 'connect' => current_user_can( 'jetpack_connect' ), 'connect_user' => current_user_can( 'jetpack_connect_user' ), 'disconnect' => current_user_can( 'jetpack_disconnect' ), ), ); /** * Filters the current user connection data. * * @since 1.30.1 * * @param array An array containing the current user connection data. */ $current_user_connection_data = apply_filters( 'jetpack_current_user_connection_data', $current_user_connection_data ); $response = array( 'currentUser' => $current_user_connection_data, 'connectionOwner' => $owner_display_name, ); if ( $rest_response ) { return rest_ensure_response( $response ); } return $response; } /** * Permission check for the connection/data endpoint * * @return bool|WP_Error */ public static function user_connection_data_permission_check() { if ( current_user_can( 'jetpack_connect_user' ) ) { return true; } return new WP_Error( 'invalid_user_permission_user_connection_data', self::get_user_permissions_error_msg(), array( 'status' => rest_authorization_required_code() ) ); } /** * Verifies if the request was signed with the Jetpack Debugger key * * @param string|null $pub_key The public key used to verify the signature. Default is the Jetpack Debugger key. This is used for testing purposes. * * @return bool */ public static function is_request_signed_by_jetpack_debugger( $pub_key = null ) { // phpcs:disable WordPress.Security.NonceVerification.Recommended if ( ! isset( $_GET['signature'] ) || ! isset( $_GET['timestamp'] ) || ! isset( $_GET['url'] ) || ! isset( $_GET['rest_route'] ) ) { return false; } // signature timestamp must be within 5min of current time. if ( abs( time() - (int) $_GET['timestamp'] ) > 300 ) { return false; } $signature = base64_decode( filter_var( wp_unslash( $_GET['signature'] ) ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode $signature_data = wp_json_encode( array( 'rest_route' => filter_var( wp_unslash( $_GET['rest_route'] ) ), 'timestamp' => (int) $_GET['timestamp'], 'url' => filter_var( wp_unslash( $_GET['url'] ) ), ) ); if ( ! function_exists( 'openssl_verify' ) || 1 !== openssl_verify( $signature_data, $signature, $pub_key ? $pub_key : static::JETPACK__DEBUGGER_PUBLIC_KEY ) ) { return false; } // phpcs:enable WordPress.Security.NonceVerification.Recommended return true; } /** * Verify that user is allowed to disconnect Jetpack. * * @since 1.15.0 * * @return bool|WP_Error Whether user has the capability 'jetpack_disconnect'. */ public static function jetpack_reconnect_permission_check() { if ( current_user_can( 'jetpack_reconnect' ) ) { return true; } return new WP_Error( 'invalid_user_permission_jetpack_disconnect', self::get_user_permissions_error_msg(), array( 'status' => rest_authorization_required_code() ) ); } /** * Returns generic error message when user is not allowed to perform an action. * * @return string The error message. */ public static function get_user_permissions_error_msg() { return self::$user_permissions_error_msg; } /** * The endpoint tried to partially or fully reconnect the website to WP.com. * * @since 1.15.0 * * @return \WP_REST_Response|WP_Error */ public function connection_reconnect() { $response = array(); $next = null; $result = $this->connection->restore(); if ( is_wp_error( $result ) ) { $response = $result; } elseif ( is_string( $result ) ) { $next = $result; } else { $next = true === $result ? 'completed' : 'failed'; } switch ( $next ) { case 'authorize': $response['status'] = 'in_progress'; $response['authorizeUrl'] = $this->connection->get_authorization_url(); break; case 'completed': $response['status'] = 'completed'; /** * Action fired when reconnection has completed successfully. * * @since 1.18.1 */ do_action( 'jetpack_reconnection_completed' ); break; case 'failed': $response = new WP_Error( 'Reconnect failed' ); break; } return rest_ensure_response( $response ); } /** * Verify that user is allowed to connect Jetpack. * * @since 1.26.0 * * @return bool|WP_Error Whether user has the capability 'jetpack_connect'. */ public static function jetpack_register_permission_check() { if ( current_user_can( 'jetpack_connect' ) ) { return true; } return new WP_Error( 'invalid_user_permission_jetpack_connect', self::get_user_permissions_error_msg(), array( 'status' => rest_authorization_required_code() ) ); } /** * The endpoint tried to partially or fully reconnect the website to WP.com. * * @since 1.7.0 * @since-jetpack 7.7.0 * * @param \WP_REST_Request $request The request sent to the WP REST API. * * @return \WP_REST_Response|WP_Error */ public function connection_register( $request ) { if ( ! wp_verify_nonce( $request->get_param( 'registration_nonce' ), 'jetpack-registration-nonce' ) ) { return new WP_Error( 'invalid_nonce', __( 'Unable to verify your request.', 'jetpack-connection' ), array( 'status' => 403 ) ); } if ( isset( $request['from'] ) ) { $this->connection->add_register_request_param( 'from', (string) $request['from'] ); } if ( ! empty( $request['plugin_slug'] ) ) { // If `plugin_slug` matches a plugin using the connection, let's inform the plugin that is establishing the connection. $connected_plugin = Plugin_Storage::get_one( (string) $request['plugin_slug'] ); if ( ! is_wp_error( $connected_plugin ) && ! empty( $connected_plugin ) ) { $this->connection->set_plugin_instance( new Plugin( (string) $request['plugin_slug'] ) ); } } $result = $this->connection->try_registration(); if ( is_wp_error( $result ) ) { return $result; } $redirect_uri = $request->get_param( 'redirect_uri' ) ? admin_url( $request->get_param( 'redirect_uri' ) ) : null; $authorize_url = ( new Authorize_Redirect( $this->connection ) )->build_authorize_url( $redirect_uri ); /** * Filters the response of jetpack/v4/connection/register endpoint * * @param array $response Array response * @since 1.27.0 */ $response_body = apply_filters( 'jetpack_register_site_rest_response', array() ); // We manipulate the alternate URLs after the filter is applied, so they can not be overwritten. $response_body['authorizeUrl'] = $authorize_url; if ( ! empty( $response_body['alternateAuthorizeUrl'] ) ) { $response_body['alternateAuthorizeUrl'] = Redirect::get_url( $response_body['alternateAuthorizeUrl'] ); } return rest_ensure_response( $response_body ); } /** * Get the authorization URL. * * @since 1.27.0 * * @param \WP_REST_Request $request The request sent to the WP REST API. * * @return \WP_REST_Response|WP_Error */ public function connection_authorize_url( $request ) { $redirect_uri = $request->get_param( 'redirect_uri' ) ? admin_url( $request->get_param( 'redirect_uri' ) ) : null; $authorize_url = $this->connection->get_authorization_url( null, $redirect_uri ); return rest_ensure_response( array( 'authorizeUrl' => $authorize_url, ) ); } /** * The endpoint tried to partially or fully reconnect the website to WP.com. * * @since 1.29.0 * * @param \WP_REST_Request $request The request sent to the WP REST API. * * @return \WP_REST_Response|WP_Error */ public static function update_user_token( $request ) { $token_parts = explode( '.', $request['user_token'] ); if ( count( $token_parts ) !== 3 || ! (int) $token_parts[2] || ! ctype_digit( $token_parts[2] ) ) { return new WP_Error( 'invalid_argument_user_token', esc_html__( 'Invalid user token is provided', 'jetpack-connection' ) ); } $user_id = (int) $token_parts[2]; if ( false === get_userdata( $user_id ) ) { return new WP_Error( 'invalid_argument_user_id', esc_html__( 'Invalid user id is provided', 'jetpack-connection' ) ); } $connection = new Manager(); if ( ! $connection->is_connected() ) { return new WP_Error( 'site_not_connected', esc_html__( 'Site is not connected', 'jetpack-connection' ) ); } $is_connection_owner = isset( $request['is_connection_owner'] ) ? (bool) $request['is_connection_owner'] : ( new Manager() )->get_connection_owner_id() === $user_id; ( new Tokens() )->update_user_token( $user_id, $request['user_token'], $is_connection_owner ); /** * Fires when the user token gets successfully replaced. * * @since 1.29.0 * * @param int $user_id User ID. * @param string $token New user token. */ do_action( 'jetpack_updated_user_token', $user_id, $request['user_token'] ); return rest_ensure_response( array( 'success' => true, ) ); } /** * Disconnects Jetpack from the WordPress.com Servers * * @since 1.30.1 * * @return bool|WP_Error True if Jetpack successfully disconnected. */ public static function disconnect_site() { $connection = new Manager(); if ( $connection->is_connected() ) { $connection->disconnect_site(); return rest_ensure_response( array( 'code' => 'success' ) ); } return new WP_Error( 'disconnect_failed', esc_html__( 'Failed to disconnect the site as it appears already disconnected.', 'jetpack-connection' ), array( 'status' => 400 ) ); } /** * Verify that the API client is allowed to replace user token. * * @since 1.29.0 * * @return bool|WP_Error */ public static function update_user_token_permission_check() { return Rest_Authentication::is_signed_with_blog_token() ? true : new WP_Error( 'invalid_permission_update_user_token', self::get_user_permissions_error_msg(), array( 'status' => rest_authorization_required_code() ) ); } /** * Change the connection owner. * * @since 1.29.0 * * @param WP_REST_Request $request The request sent to the WP REST API. * * @return \WP_REST_Response|WP_Error */ public static function set_connection_owner( $request ) { $new_owner_id = $request['owner']; $owner_set = ( new Manager() )->update_connection_owner( $new_owner_id ); if ( is_wp_error( $owner_set ) ) { return $owner_set; } return rest_ensure_response( array( 'code' => 'success', ) ); } /** * Check that user has permission to change the master user. * * @since 1.7.0 * @since-jetpack 6.2.0 * @since-jetpack 7.7.0 Update so that any user with jetpack_disconnect privs can set owner. * * @return bool|WP_Error True if user is able to change master user. */ public static function set_connection_owner_permission_check() { if ( current_user_can( 'jetpack_disconnect' ) ) { return true; } return new WP_Error( 'invalid_user_permission_set_connection_owner', self::get_user_permissions_error_msg(), array( 'status' => rest_authorization_required_code() ) ); } /** * The endpoint verifies blog connection and blog token validity. * * @since 2.7.0 * * @return mixed|null */ public function connection_check() { /** * Filters the successful response of the REST API test_connection method * * @param string $response The response string. */ $status = apply_filters( 'jetpack_rest_connection_check_response', 'success' ); return rest_ensure_response( array( 'status' => $status, ) ); } /** * Remote connect endpoint permission check. * * @return true|WP_Error */ public function connection_check_permission_check() { if ( current_user_can( 'jetpack_connect' ) ) { return true; } return Rest_Authentication::is_signed_with_blog_token() ? true : new WP_Error( 'invalid_permission_connection_check', self::get_user_permissions_error_msg(), array( 'status' => rest_authorization_required_code() ) ); } }