updated plugin Jetpack Protect
version 3.0.2
This commit is contained in:
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
/**
|
||||
* Connection_Assets.
|
||||
*
|
||||
* @package automattic/jetpack-connection
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Connection;
|
||||
|
||||
use Automattic\Jetpack\Assets;
|
||||
|
||||
/**
|
||||
* Connection_Assets class.
|
||||
*/
|
||||
class Connection_Assets {
|
||||
|
||||
/**
|
||||
* Initialize the class.
|
||||
*/
|
||||
public static function configure() {
|
||||
add_action( 'wp_loaded', array( __CLASS__, 'register_assets' ) );
|
||||
|
||||
add_filter( 'jetpack_admin_js_script_data', array( Initial_State::class, 'set_connection_script_data' ), 10, 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register assets.
|
||||
*/
|
||||
public static function register_assets() {
|
||||
|
||||
Assets::register_script(
|
||||
'jetpack-connection',
|
||||
'../dist/jetpack-connection.js',
|
||||
__FILE__,
|
||||
array(
|
||||
'in_footer' => true,
|
||||
'textdomain' => 'jetpack-connection',
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
@ -136,7 +136,8 @@ class Error_Handler {
|
||||
|
||||
add_action( 'rest_api_init', array( $this, 'register_verify_error_endpoint' ) );
|
||||
|
||||
$this->handle_verified_errors();
|
||||
// Handle verified errors on admin pages.
|
||||
add_action( 'admin_init', array( $this, 'handle_verified_errors' ) );
|
||||
|
||||
// If the site gets reconnected, clear errors.
|
||||
add_action( 'jetpack_site_registered', array( $this, 'delete_all_errors' ) );
|
||||
|
@ -39,6 +39,18 @@ class Initial_State {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the connection script data.
|
||||
*
|
||||
* @param array $data The script data.
|
||||
*/
|
||||
public static function set_connection_script_data( $data ) {
|
||||
|
||||
$data['connection'] = self::get_data();
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the initial state into a JavaScript variable.
|
||||
*
|
||||
|
@ -199,7 +199,10 @@ class Manager {
|
||||
if ( ! Constants::get_constant( 'XMLRPC_REQUEST' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Display errors can cause the XML to be not well formed.
|
||||
// This only affects Jetpack XML-RPC endpoints received from WordPress.com servers.
|
||||
// All other XML-RPC requests are unaffected.
|
||||
@ini_set( 'display_errors', false ); // phpcs:ignore
|
||||
|
||||
if ( $xmlrpc_server ) {
|
||||
@ -1879,10 +1882,10 @@ class Manager {
|
||||
*
|
||||
* @since 2.7.6 Added optional $from and $raw parameters.
|
||||
*
|
||||
* @param WP_User $user (optional) defaults to the current logged in user.
|
||||
* @param string $redirect (optional) a redirect URL to use instead of the default.
|
||||
* @param bool|string $from If not false, adds 'from=$from' param to the connect URL.
|
||||
* @param bool $raw If true, URL will not be escaped.
|
||||
* @param WP_User|null $user (optional) defaults to the current logged in user.
|
||||
* @param string|null $redirect (optional) a redirect URL to use instead of the default.
|
||||
* @param bool|string $from If not false, adds 'from=$from' param to the connect URL.
|
||||
* @param bool $raw If true, URL will not be escaped.
|
||||
*
|
||||
* @return string Connect URL.
|
||||
*/
|
||||
@ -2123,7 +2126,7 @@ class Manager {
|
||||
( new Nonce_Handler() )->clean_all();
|
||||
|
||||
/**
|
||||
* Fires when a site is disconnected.
|
||||
* Fires before a site is disconnected.
|
||||
*
|
||||
* @since 1.36.3
|
||||
*/
|
||||
|
@ -12,7 +12,7 @@ namespace Automattic\Jetpack\Connection;
|
||||
*/
|
||||
class Package_Version {
|
||||
|
||||
const PACKAGE_VERSION = '2.8.4';
|
||||
const PACKAGE_VERSION = '4.0.1';
|
||||
|
||||
const PACKAGE_SLUG = 'connection';
|
||||
|
||||
|
@ -7,6 +7,7 @@
|
||||
|
||||
namespace Automattic\Jetpack\Connection;
|
||||
|
||||
use Jetpack_Options;
|
||||
use WP_Error;
|
||||
|
||||
/**
|
||||
@ -204,9 +205,12 @@ class Plugin_Storage {
|
||||
|
||||
// If a plugin was activated or deactivated.
|
||||
// self::$plugins is populated in Config::ensure_options_connection().
|
||||
$number_of_plugins_differ = count( self::$plugins ) !== count( (array) get_option( self::ACTIVE_PLUGINS_OPTION_NAME, array() ) );
|
||||
$configured_plugin_keys = array_keys( self::$plugins );
|
||||
$stored_plugin_keys = array_keys( (array) get_option( self::ACTIVE_PLUGINS_OPTION_NAME, array() ) );
|
||||
sort( $configured_plugin_keys );
|
||||
sort( $stored_plugin_keys );
|
||||
|
||||
if ( $number_of_plugins_differ ) {
|
||||
if ( $configured_plugin_keys !== $stored_plugin_keys ) {
|
||||
self::update_active_plugins_option();
|
||||
}
|
||||
}
|
||||
@ -219,9 +223,14 @@ class Plugin_Storage {
|
||||
public static function update_active_plugins_option() {
|
||||
// Note: Since this option is synced to wpcom, if you change its structure, you have to update the sanitizer at wpcom side.
|
||||
update_option( self::ACTIVE_PLUGINS_OPTION_NAME, self::$plugins );
|
||||
|
||||
if ( ! class_exists( 'Automattic\Jetpack\Sync\Settings' ) || ! \Automattic\Jetpack\Sync\Settings::is_sync_enabled() ) {
|
||||
self::update_active_plugins_wpcom_no_sync_fallback();
|
||||
// Remove the checksum for active plugins, so it gets recalculated when sync gets activated.
|
||||
$jetpack_callables_sync_checksum = Jetpack_Options::get_raw_option( 'jetpack_callables_sync_checksum' );
|
||||
if ( isset( $jetpack_callables_sync_checksum['jetpack_connection_active_plugins'] ) ) {
|
||||
unset( $jetpack_callables_sync_checksum['jetpack_connection_active_plugins'] );
|
||||
Jetpack_Options::update_raw_option( 'jetpack_callables_sync_checksum', $jetpack_callables_sync_checksum );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -444,7 +444,7 @@ class REST_Connector {
|
||||
|
||||
$connection_status = array(
|
||||
'isActive' => $connection->has_connected_owner(), // TODO deprecate this.
|
||||
'isStaging' => $status->is_staging_site(),
|
||||
'isStaging' => $status->in_safe_mode(), // TODO deprecate this.
|
||||
'isRegistered' => $connection->is_connected(),
|
||||
'isUserConnected' => $connection->is_user_connected(),
|
||||
'hasConnectedOwner' => $connection->has_connected_owner(),
|
||||
|
@ -38,7 +38,7 @@ class Tracking {
|
||||
/**
|
||||
* Creates the Tracking object.
|
||||
*
|
||||
* @param String $product_name the slug of the product that we are tracking.
|
||||
* @param string $product_name the slug of the product that we are tracking.
|
||||
* @param \Automattic\Jetpack\Connection\Manager $connection the connection manager object.
|
||||
*/
|
||||
public function __construct( $product_name = 'jetpack', $connection = null ) {
|
||||
@ -252,6 +252,7 @@ class Tracking {
|
||||
|
||||
$blog_details = array(
|
||||
'blog_lang' => isset( $properties['blog_lang'] ) ? $properties['blog_lang'] : get_bloginfo( 'language' ),
|
||||
'blog_id' => \Jetpack_Options::get_option( 'id' ),
|
||||
);
|
||||
|
||||
$timestamp = ( false !== $event_timestamp_millis ) ? $event_timestamp_millis : round( microtime( true ) * 1000 );
|
||||
|
@ -0,0 +1,30 @@
|
||||
#wpadminbar #wp-admin-bar-jetpack-idc {
|
||||
margin-right: 5px;
|
||||
|
||||
.jp-idc-admin-bar {
|
||||
border-radius: 2px;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #EFEFF0;
|
||||
padding: 6px 8px;
|
||||
}
|
||||
|
||||
&.hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dashicons {
|
||||
font-family: 'dashicons';
|
||||
margin-top: -6px;
|
||||
|
||||
&:before {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.ab-item {
|
||||
padding: 0;
|
||||
background: #E68B28;
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
import { IDCScreen } from '@automattic/jetpack-idc';
|
||||
import * as WPElement from '@wordpress/element';
|
||||
import React from 'react';
|
||||
|
||||
import './admin-bar.scss';
|
||||
import './style.scss';
|
||||
|
||||
/**
|
||||
* The initial renderer function.
|
||||
*/
|
||||
function render() {
|
||||
if ( ! Object.hasOwn( window, 'JP_IDENTITY_CRISIS__INITIAL_STATE' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const container = document.getElementById(
|
||||
window.JP_IDENTITY_CRISIS__INITIAL_STATE.containerID || 'jp-identity-crisis-container'
|
||||
);
|
||||
|
||||
if ( null === container ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
WP_API_root,
|
||||
WP_API_nonce,
|
||||
wpcomHomeUrl,
|
||||
currentUrl,
|
||||
redirectUri,
|
||||
tracksUserData,
|
||||
tracksEventData,
|
||||
isSafeModeConfirmed,
|
||||
consumerData,
|
||||
isAdmin,
|
||||
possibleDynamicSiteUrlDetected,
|
||||
isDevelopmentSite,
|
||||
} = window.JP_IDENTITY_CRISIS__INITIAL_STATE;
|
||||
|
||||
if ( ! isSafeModeConfirmed ) {
|
||||
const component = (
|
||||
<IDCScreen
|
||||
wpcomHomeUrl={ wpcomHomeUrl }
|
||||
currentUrl={ currentUrl }
|
||||
apiRoot={ WP_API_root }
|
||||
apiNonce={ WP_API_nonce }
|
||||
redirectUri={ redirectUri }
|
||||
tracksUserData={ tracksUserData || {} }
|
||||
tracksEventData={ tracksEventData }
|
||||
customContent={
|
||||
Object.hasOwn( consumerData, 'customContent' ) ? consumerData.customContent : {}
|
||||
}
|
||||
isAdmin={ isAdmin }
|
||||
logo={ Object.hasOwn( consumerData, 'logo' ) ? consumerData.logo : undefined }
|
||||
possibleDynamicSiteUrlDetected={ possibleDynamicSiteUrlDetected }
|
||||
isDevelopmentSite={ isDevelopmentSite }
|
||||
/>
|
||||
);
|
||||
WPElement.createRoot( container ).render( component );
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener( 'load', () => render() );
|
@ -0,0 +1,9 @@
|
||||
#jp-identity-crisis-container .jp-idc__idc-screen {
|
||||
margin-top: 40px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
#jp-identity-crisis-container.notice {
|
||||
background: none;
|
||||
border: none;
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
/**
|
||||
* Exception class for the Identity Crisis component.
|
||||
*
|
||||
* @package automattic/jetpack-connection
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\IdentityCrisis;
|
||||
|
||||
/**
|
||||
* Exception class for the Identity Crisis component.
|
||||
*/
|
||||
class Exception extends \Exception {}
|
@ -0,0 +1,833 @@
|
||||
<?php
|
||||
/**
|
||||
* Identity_Crisis class of the Connection package.
|
||||
*
|
||||
* @package automattic/jetpack-connection
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack;
|
||||
|
||||
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
|
||||
use Automattic\Jetpack\Connection\Urls;
|
||||
use Automattic\Jetpack\IdentityCrisis\Exception;
|
||||
use Automattic\Jetpack\IdentityCrisis\UI;
|
||||
use Automattic\Jetpack\IdentityCrisis\URL_Secret;
|
||||
use Jetpack_Options;
|
||||
use WP_Error;
|
||||
|
||||
/**
|
||||
* This class will handle everything involved with fixing an Identity Crisis.
|
||||
*
|
||||
* @since automattic/jetpack-identity-crisis:0.2.0
|
||||
* @since-jetpack 4.4.0
|
||||
* @since 2.9.0
|
||||
*/
|
||||
class Identity_Crisis {
|
||||
/**
|
||||
* Persistent WPCOM blog ID that stays in the options after disconnect.
|
||||
*/
|
||||
const PERSISTENT_BLOG_ID_OPTION_NAME = 'jetpack_persistent_blog_id';
|
||||
|
||||
/**
|
||||
* Instance of the object.
|
||||
*
|
||||
* @var Identity_Crisis
|
||||
**/
|
||||
private static $instance = null;
|
||||
|
||||
/**
|
||||
* The wpcom value of the home URL.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $wpcom_home_url;
|
||||
|
||||
/**
|
||||
* Has safe mode been confirmed?
|
||||
* Beware, it never contains `true` for non-admins, so doesn't always reflect the actual value.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public static $is_safe_mode_confirmed;
|
||||
|
||||
/**
|
||||
* The current screen, which is set if the current user is a non-admin and this is an admin page.
|
||||
*
|
||||
* @var \WP_Screen
|
||||
*/
|
||||
public static $current_screen;
|
||||
|
||||
/**
|
||||
* Initializer.
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
public static function init() {
|
||||
if ( self::$instance === null ) {
|
||||
self::$instance = new Identity_Crisis();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function __construct() {
|
||||
add_action( 'jetpack_sync_processed_actions', array( $this, 'maybe_clear_migrate_option' ) );
|
||||
add_action( 'rest_api_init', array( 'Automattic\\Jetpack\\IdentityCrisis\\REST_Endpoints', 'initialize_rest_api' ) );
|
||||
add_action( 'jetpack_idc_disconnect', array( __CLASS__, 'do_jetpack_idc_disconnect' ) );
|
||||
add_action( 'jetpack_received_remote_request_response', array( $this, 'check_http_response_for_idc_detected' ) );
|
||||
|
||||
add_filter( 'jetpack_connection_disconnect_site_wpcom', array( __CLASS__, 'jetpack_connection_disconnect_site_wpcom_filter' ) );
|
||||
|
||||
add_filter( 'jetpack_remote_request_url', array( $this, 'add_idc_query_args_to_url' ) );
|
||||
|
||||
add_filter( 'jetpack_connection_validate_urls_for_idc_mitigation_response', array( static::class, 'add_secret_to_url_validation_response' ) );
|
||||
add_filter( 'jetpack_connection_validate_urls_for_idc_mitigation_response', array( static::class, 'add_ip_requester_to_url_validation_response' ) );
|
||||
|
||||
add_filter( 'jetpack_options', array( static::class, 'reverse_wpcom_urls_for_idc' ) );
|
||||
|
||||
add_filter( 'jetpack_register_request_body', array( static::class, 'register_request_body' ) );
|
||||
add_action( 'jetpack_site_registered', array( static::class, 'site_registered' ) );
|
||||
|
||||
$urls_in_crisis = self::check_identity_crisis();
|
||||
if ( false === $urls_in_crisis ) {
|
||||
return;
|
||||
}
|
||||
|
||||
self::$wpcom_home_url = $urls_in_crisis['wpcom_home'];
|
||||
add_action( 'init', array( $this, 'wordpress_init' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect current connection and clear IDC options.
|
||||
*/
|
||||
public static function do_jetpack_idc_disconnect() {
|
||||
$connection = new Connection_Manager();
|
||||
|
||||
// If the site is in an IDC because sync is not allowed,
|
||||
// let's make sure to not disconnect the production site.
|
||||
if ( ! self::validate_sync_error_idc_option() ) {
|
||||
$connection->disconnect_site( true );
|
||||
} else {
|
||||
$connection->disconnect_site( false );
|
||||
}
|
||||
|
||||
delete_option( static::PERSISTENT_BLOG_ID_OPTION_NAME );
|
||||
|
||||
// Clear IDC options.
|
||||
self::clear_all_idc_options();
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter to prevent site from disconnecting from WPCOM if it's in an IDC.
|
||||
*
|
||||
* @see jetpack_connection_disconnect_site_wpcom filter.
|
||||
*
|
||||
* @return bool False if the site is in IDC, true otherwise.
|
||||
*/
|
||||
public static function jetpack_connection_disconnect_site_wpcom_filter() {
|
||||
return ! self::validate_sync_error_idc_option();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method loops through the array of processed items from sync and checks if one of the items was the
|
||||
* home_url or site_url callable. If so, then we delete the jetpack_migrate_for_idc option.
|
||||
*
|
||||
* @param array $processed_items Array of processed items that were synced to WordPress.com.
|
||||
*/
|
||||
public function maybe_clear_migrate_option( $processed_items ) {
|
||||
foreach ( (array) $processed_items as $item ) {
|
||||
|
||||
// First, is this item a jetpack_sync_callable action? If so, then proceed.
|
||||
$callable_args = ( is_array( $item ) && isset( $item[0] ) && isset( $item[1] ) && 'jetpack_sync_callable' === $item[0] )
|
||||
? $item[1]
|
||||
: null;
|
||||
|
||||
// Second, if $callable_args is set, check if the callable was home_url or site_url. If so,
|
||||
// clear the migrate option.
|
||||
if (
|
||||
isset( $callable_args[0] )
|
||||
&& ( 'home_url' === $callable_args[0] || 'site_url' === $callable_args[1] )
|
||||
) {
|
||||
Jetpack_Options::delete_option( 'migrate_for_idc' );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WordPress init.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function wordpress_init() {
|
||||
if ( current_user_can( 'jetpack_disconnect' ) ) {
|
||||
if (
|
||||
isset( $_GET['jetpack_idc_clear_confirmation'] ) && isset( $_GET['_wpnonce'] ) &&
|
||||
wp_verify_nonce( $_GET['_wpnonce'], 'jetpack_idc_clear_confirmation' ) // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- WordPress core doesn't unslash or verify nonces either.
|
||||
) {
|
||||
Jetpack_Options::delete_option( 'safe_mode_confirmed' );
|
||||
self::$is_safe_mode_confirmed = false;
|
||||
} else {
|
||||
self::$is_safe_mode_confirmed = (bool) Jetpack_Options::get_option( 'safe_mode_confirmed' );
|
||||
}
|
||||
}
|
||||
|
||||
// 121 Priority so that it's the most inner Jetpack item in the admin bar.
|
||||
add_action( 'admin_bar_menu', array( $this, 'display_admin_bar_button' ), 121 );
|
||||
|
||||
UI::init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the idc query arguments to the url.
|
||||
*
|
||||
* @param string $url The remote request url.
|
||||
*/
|
||||
public function add_idc_query_args_to_url( $url ) {
|
||||
$status = new Status();
|
||||
if ( ! is_string( $url )
|
||||
|| $status->is_offline_mode()
|
||||
|| self::validate_sync_error_idc_option() ) {
|
||||
return $url;
|
||||
}
|
||||
$home_url = Urls::home_url();
|
||||
$site_url = Urls::site_url();
|
||||
$hostname = wp_parse_url( $site_url, PHP_URL_HOST );
|
||||
|
||||
// If request is from an IP, make sure ip_requester option is set
|
||||
if ( self::url_is_ip( $hostname ) ) {
|
||||
self::maybe_update_ip_requester( $hostname );
|
||||
}
|
||||
|
||||
$query_args = array(
|
||||
'home' => $home_url,
|
||||
'siteurl' => $site_url,
|
||||
);
|
||||
|
||||
if ( self::should_handle_idc() ) {
|
||||
$query_args['idc'] = true;
|
||||
}
|
||||
|
||||
if ( \Jetpack_Options::get_option( 'migrate_for_idc', false ) ) {
|
||||
$query_args['migrate_for_idc'] = true;
|
||||
}
|
||||
|
||||
if ( is_multisite() ) {
|
||||
$query_args['multisite'] = true;
|
||||
}
|
||||
|
||||
return add_query_arg( $query_args, $url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the admin bar button.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function display_admin_bar_button() {
|
||||
global $wp_admin_bar;
|
||||
|
||||
$href = is_admin()
|
||||
? add_query_arg( 'jetpack_idc_clear_confirmation', '1' )
|
||||
: add_query_arg( 'jetpack_idc_clear_confirmation', '1', admin_url() );
|
||||
|
||||
$href = wp_nonce_url( $href, 'jetpack_idc_clear_confirmation' );
|
||||
|
||||
$consumer_data = UI::get_consumer_data();
|
||||
$label = isset( $consumer_data['customContent']['adminBarSafeModeLabel'] )
|
||||
? esc_html( $consumer_data['customContent']['adminBarSafeModeLabel'] )
|
||||
: esc_html__( 'Jetpack Safe Mode', 'jetpack-connection' );
|
||||
|
||||
$title = sprintf(
|
||||
'<span class="jp-idc-admin-bar">%s %s</span>',
|
||||
'<span class="dashicons dashicons-info-outline"></span>',
|
||||
$label
|
||||
);
|
||||
|
||||
$menu = array(
|
||||
'id' => 'jetpack-idc',
|
||||
'title' => $title,
|
||||
'href' => esc_url( $href ),
|
||||
'parent' => 'top-secondary',
|
||||
);
|
||||
|
||||
if ( ! self::$is_safe_mode_confirmed ) {
|
||||
$menu['meta'] = array(
|
||||
'class' => 'hide',
|
||||
);
|
||||
}
|
||||
|
||||
$wp_admin_bar->add_node( $menu );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the site is currently in an identity crisis.
|
||||
*
|
||||
* @return array|bool Array of options that are in a crisis, or false if everything is OK.
|
||||
*/
|
||||
public static function check_identity_crisis() {
|
||||
$connection = new Connection_Manager( 'jetpack' );
|
||||
|
||||
if ( ! $connection->is_connected() || ( new Status() )->is_offline_mode() || ! self::validate_sync_error_idc_option() ) {
|
||||
return false;
|
||||
}
|
||||
return Jetpack_Options::get_option( 'sync_error_idc' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the HTTP response body for the 'idc_detected' key. If the key exists,
|
||||
* checks the idc_detected value for a valid idc error.
|
||||
*
|
||||
* @param array|WP_Error $http_response The HTTP response.
|
||||
*
|
||||
* @return bool Whether the site is in an identity crisis.
|
||||
*/
|
||||
public function check_http_response_for_idc_detected( $http_response ) {
|
||||
if ( ! is_array( $http_response ) ) {
|
||||
return false;
|
||||
}
|
||||
$response_body = json_decode( wp_remote_retrieve_body( $http_response ), true );
|
||||
|
||||
if ( isset( $response_body['idc_detected'] ) ) {
|
||||
return $this->check_response_for_idc( $response_body['idc_detected'] );
|
||||
}
|
||||
|
||||
if ( isset( $response_body['migrated_for_idc'] ) ) {
|
||||
Jetpack_Options::delete_option( 'migrate_for_idc' );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the WPCOM response to determine if the site is in an identity crisis. Updates the
|
||||
* sync_error_idc option if it is.
|
||||
*
|
||||
* @param array $response The response data.
|
||||
*
|
||||
* @return bool Whether the site is in an identity crisis.
|
||||
*/
|
||||
public function check_response_for_idc( $response ) {
|
||||
if ( is_array( $response ) && isset( $response['error_code'] ) ) {
|
||||
$error_code = $response['error_code'];
|
||||
$allowed_idc_error_codes = array(
|
||||
'jetpack_url_mismatch',
|
||||
'jetpack_home_url_mismatch',
|
||||
'jetpack_site_url_mismatch',
|
||||
);
|
||||
|
||||
if ( in_array( $error_code, $allowed_idc_error_codes, true ) ) {
|
||||
Jetpack_Options::update_option(
|
||||
'sync_error_idc',
|
||||
self::get_sync_error_idc_option( $response )
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all IDC specific options. This method is used on disconnect and reconnect.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function clear_all_idc_options() {
|
||||
// If the site is currently in IDC, let's also clear the VaultPress connection options.
|
||||
// We have to check if the site is in IDC, otherwise we'd be clearing the VaultPress
|
||||
// connection any time the Jetpack connection is cycled.
|
||||
if ( self::validate_sync_error_idc_option() ) {
|
||||
delete_option( 'vaultpress' );
|
||||
delete_option( 'vaultpress_auto_register' );
|
||||
}
|
||||
|
||||
Jetpack_Options::delete_option(
|
||||
array(
|
||||
'sync_error_idc',
|
||||
'safe_mode_confirmed',
|
||||
'migrate_for_idc',
|
||||
)
|
||||
);
|
||||
|
||||
delete_transient( 'jetpack_idc_possible_dynamic_site_url_detected' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the sync_error_idc option is valid or not, and if not, will do cleanup.
|
||||
*
|
||||
* @return bool
|
||||
* @since-jetpack 5.4.0 Do not call get_sync_error_idc_option() unless site is in IDC
|
||||
*
|
||||
* @since 0.2.0
|
||||
* @since-jetpack 4.4.0
|
||||
*/
|
||||
public static function validate_sync_error_idc_option() {
|
||||
$is_valid = false;
|
||||
|
||||
// Is the site opted in and does the stored sync_error_idc option match what we now generate?
|
||||
$sync_error = Jetpack_Options::get_option( 'sync_error_idc' );
|
||||
if ( $sync_error && self::should_handle_idc() ) {
|
||||
$local_options = self::get_sync_error_idc_option();
|
||||
|
||||
// Ensure all values are set.
|
||||
if ( isset( $sync_error['home'] ) && isset( $local_options['home'] ) && isset( $sync_error['siteurl'] ) && isset( $local_options['siteurl'] ) ) {
|
||||
// If the WP.com expected home and siteurl match local home and siteurl it is not valid IDC.
|
||||
if (
|
||||
isset( $sync_error['wpcom_home'] ) &&
|
||||
isset( $sync_error['wpcom_siteurl'] ) &&
|
||||
$sync_error['wpcom_home'] === $local_options['home'] &&
|
||||
$sync_error['wpcom_siteurl'] === $local_options['siteurl']
|
||||
) {
|
||||
// Enable migrate_for_idc so that sync actions are accepted.
|
||||
Jetpack_Options::update_option( 'migrate_for_idc', true );
|
||||
} elseif ( $sync_error['home'] === $local_options['home'] && $sync_error['siteurl'] === $local_options['siteurl'] ) {
|
||||
$is_valid = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters whether the sync_error_idc option is valid.
|
||||
*
|
||||
* @param bool $is_valid If the sync_error_idc is valid or not.
|
||||
*
|
||||
* @since 0.2.0
|
||||
* @since-jetpack 4.4.0
|
||||
*/
|
||||
$is_valid = (bool) apply_filters( 'jetpack_sync_error_idc_validation', $is_valid );
|
||||
|
||||
if ( ! $is_valid && $sync_error ) {
|
||||
// Since the option exists, and did not validate, delete it.
|
||||
Jetpack_Options::delete_option( 'sync_error_idc' );
|
||||
}
|
||||
|
||||
return $is_valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverses WP.com URLs stored in sync_error_idc option.
|
||||
*
|
||||
* @param array $sync_error error option containing reversed URLs.
|
||||
* @return array
|
||||
*/
|
||||
public static function reverse_wpcom_urls_for_idc( $sync_error ) {
|
||||
if ( isset( $sync_error['reversed_url'] ) ) {
|
||||
if ( array_key_exists( 'wpcom_siteurl', $sync_error ) ) {
|
||||
$sync_error['wpcom_siteurl'] = strrev( $sync_error['wpcom_siteurl'] );
|
||||
}
|
||||
if ( array_key_exists( 'wpcom_home', $sync_error ) ) {
|
||||
$sync_error['wpcom_home'] = strrev( $sync_error['wpcom_home'] );
|
||||
}
|
||||
}
|
||||
return $sync_error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a url by doing three things:
|
||||
* - Strips protocol
|
||||
* - Strips www
|
||||
* - Adds a trailing slash
|
||||
*
|
||||
* @param string $url URL to parse.
|
||||
*
|
||||
* @return WP_Error|string
|
||||
* @since 0.2.0
|
||||
* @since-jetpack 4.4.0
|
||||
*/
|
||||
public static function normalize_url_protocol_agnostic( $url ) {
|
||||
$parsed_url = wp_parse_url( trailingslashit( esc_url_raw( $url ) ) );
|
||||
if ( ! $parsed_url || empty( $parsed_url['host'] ) || empty( $parsed_url['path'] ) ) {
|
||||
return new WP_Error(
|
||||
'cannot_parse_url',
|
||||
sprintf(
|
||||
/* translators: %s: URL to parse. */
|
||||
esc_html__( 'Cannot parse URL %s', 'jetpack-connection' ),
|
||||
$url
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Strip www and protocols.
|
||||
$url = preg_replace( '/^www\./i', '', $parsed_url['host'] . $parsed_url['path'] );
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value that is to be saved in the jetpack_sync_error_idc option.
|
||||
*
|
||||
* @param array $response HTTP response.
|
||||
*
|
||||
* @return array Array of the local urls, wpcom urls, and error code.
|
||||
* @since 0.2.0
|
||||
* @since-jetpack 4.4.0
|
||||
* @since-jetpack 5.4.0 Add transient since home/siteurl retrieved directly from DB.
|
||||
*/
|
||||
public static function get_sync_error_idc_option( $response = array() ) {
|
||||
// Since the local options will hit the database directly, store the values
|
||||
// in a transient to allow for autoloading and caching on subsequent views.
|
||||
$local_options = get_transient( 'jetpack_idc_local' );
|
||||
if ( false === $local_options ) {
|
||||
$local_options = array(
|
||||
'home' => Urls::home_url(),
|
||||
'siteurl' => Urls::site_url(),
|
||||
);
|
||||
set_transient( 'jetpack_idc_local', $local_options, MINUTE_IN_SECONDS );
|
||||
}
|
||||
|
||||
$options = array_merge( $local_options, $response );
|
||||
|
||||
$returned_values = array();
|
||||
foreach ( $options as $key => $option ) {
|
||||
if ( 'error_code' === $key ) {
|
||||
$returned_values[ $key ] = $option;
|
||||
continue;
|
||||
}
|
||||
|
||||
$normalized_url = self::normalize_url_protocol_agnostic( $option );
|
||||
if ( is_wp_error( $normalized_url ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$returned_values[ $key ] = $normalized_url;
|
||||
}
|
||||
// We need to protect WPCOM URLs from search & replace by reversing them. See https://wp.me/pf5801-3R
|
||||
// Add 'reversed_url' key for backward compatibility
|
||||
if ( array_key_exists( 'wpcom_home', $returned_values ) && array_key_exists( 'wpcom_siteurl', $returned_values ) ) {
|
||||
$returned_values['reversed_url'] = true;
|
||||
$returned_values = self::reverse_wpcom_urls_for_idc( $returned_values );
|
||||
}
|
||||
|
||||
return $returned_values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the jetpack_should_handle_idc filter or constant.
|
||||
* If set to true, the site will be put into staging mode.
|
||||
*
|
||||
* This method uses both the current jetpack_should_handle_idc filter
|
||||
* and constant to determine whether an IDC should be handled.
|
||||
*
|
||||
* @return bool
|
||||
* @since 0.2.6
|
||||
*/
|
||||
public static function should_handle_idc() {
|
||||
if ( Constants::is_defined( 'JETPACK_SHOULD_HANDLE_IDC' ) ) {
|
||||
$default = Constants::get_constant( 'JETPACK_SHOULD_HANDLE_IDC' );
|
||||
} else {
|
||||
$default = ! Constants::is_defined( 'SUNRISE' ) && ! is_multisite();
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows sites to opt in for IDC mitigation which blocks the site from syncing to WordPress.com when the home
|
||||
* URL or site URL do not match what WordPress.com expects. The default value is either true, or the value of
|
||||
* JETPACK_SHOULD_HANDLE_IDC constant if set.
|
||||
*
|
||||
* @param bool $default Whether the site is opted in to IDC mitigation.
|
||||
*
|
||||
* @since 0.2.6
|
||||
*/
|
||||
return (bool) apply_filters( 'jetpack_should_handle_idc', $default );
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the site is undergoing identity crisis.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function has_identity_crisis() {
|
||||
return false !== static::check_identity_crisis() && ! static::$is_safe_mode_confirmed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether an admin has confirmed safe mode.
|
||||
* Unlike `static::$is_safe_mode_confirmed` this function always returns the actual flag value.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function safe_mode_is_confirmed() {
|
||||
return Jetpack_Options::get_option( 'safe_mode_confirmed' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the mismatched URLs.
|
||||
*
|
||||
* @return array|bool The mismatched urls, or false if the site is not connected, offline, in safe mode, or the IDC error is not valid.
|
||||
*/
|
||||
public static function get_mismatched_urls() {
|
||||
if ( ! static::has_identity_crisis() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$data = static::check_identity_crisis();
|
||||
|
||||
if ( ! $data ||
|
||||
! isset( $data['error_code'] ) ||
|
||||
! isset( $data['wpcom_home'] ) ||
|
||||
! isset( $data['home'] ) ||
|
||||
! isset( $data['wpcom_siteurl'] ) ||
|
||||
! isset( $data['siteurl'] )
|
||||
) {
|
||||
// The jetpack_sync_error_idc option is missing a key.
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( 'jetpack_site_url_mismatch' === $data['error_code'] ) {
|
||||
return array(
|
||||
'wpcom_url' => $data['wpcom_siteurl'],
|
||||
'current_url' => $data['siteurl'],
|
||||
);
|
||||
}
|
||||
|
||||
return array(
|
||||
'wpcom_url' => $data['wpcom_home'],
|
||||
'current_url' => $data['home'],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to detect $_SERVER['HTTP_HOST'] being used within WP_SITEURL or WP_HOME definitions inside of wp-config.
|
||||
*
|
||||
* If `HTTP_HOST` usage is found, it's possbile (though not certain) that site URLs are dynamic.
|
||||
*
|
||||
* When a site URL is dynamic, it can lead to a Jetpack IDC. If potentially dynamic usage is detected,
|
||||
* helpful support info will be shown on the IDC UI about setting a static site/home URL.
|
||||
*
|
||||
* @return bool True if potentially dynamic site urls were detected in wp-config, false otherwise.
|
||||
*/
|
||||
public static function detect_possible_dynamic_site_url() {
|
||||
$transient_key = 'jetpack_idc_possible_dynamic_site_url_detected';
|
||||
$transient_val = get_transient( $transient_key );
|
||||
|
||||
if ( false !== $transient_val ) {
|
||||
return (bool) $transient_val;
|
||||
}
|
||||
|
||||
$path = self::locate_wp_config();
|
||||
$wp_config = $path ? file_get_contents( $path ) : false; // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
|
||||
if ( $wp_config ) {
|
||||
$matched = preg_match(
|
||||
'/define ?\( ?[\'"](?:WP_SITEURL|WP_HOME).+(?:HTTP_HOST).+\);/',
|
||||
$wp_config
|
||||
);
|
||||
|
||||
if ( $matched ) {
|
||||
set_transient( $transient_key, 1, HOUR_IN_SECONDS );
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
set_transient( $transient_key, 0, HOUR_IN_SECONDS );
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets path to WordPress configuration.
|
||||
* Source: https://github.com/wp-cli/wp-cli/blob/master/php/utils.php
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function locate_wp_config() {
|
||||
static $path;
|
||||
|
||||
if ( null === $path ) {
|
||||
$path = false;
|
||||
|
||||
if ( getenv( 'WP_CONFIG_PATH' ) && file_exists( getenv( 'WP_CONFIG_PATH' ) ) ) {
|
||||
$path = getenv( 'WP_CONFIG_PATH' );
|
||||
} elseif ( file_exists( ABSPATH . 'wp-config.php' ) ) {
|
||||
$path = ABSPATH . 'wp-config.php';
|
||||
} elseif ( file_exists( dirname( ABSPATH ) . '/wp-config.php' ) && ! file_exists( dirname( ABSPATH ) . '/wp-settings.php' ) ) {
|
||||
$path = dirname( ABSPATH ) . '/wp-config.php';
|
||||
}
|
||||
|
||||
if ( $path ) {
|
||||
$path = realpath( $path );
|
||||
}
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds `url_secret` to the `jetpack.idcUrlValidation` URL validation endpoint.
|
||||
* Adds `url_secret_error` in case of an error.
|
||||
*
|
||||
* @param array $response The endpoint response that we're modifying.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* phpcs:ignore Squiz.Commenting.FunctionCommentThrowTag -- The exception is being caught, false positive.
|
||||
*/
|
||||
public static function add_secret_to_url_validation_response( array $response ) {
|
||||
try {
|
||||
$secret = new URL_Secret();
|
||||
|
||||
$secret->create();
|
||||
|
||||
if ( $secret->exists() ) {
|
||||
$response['url_secret'] = $secret->get_secret();
|
||||
}
|
||||
} catch ( Exception $e ) {
|
||||
$response['url_secret_error'] = new WP_Error( 'unable_to_create_url_secret', $e->getMessage() );
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if URL is an IP.
|
||||
*
|
||||
* @param string $hostname The hostname to check.
|
||||
* @return bool
|
||||
*/
|
||||
public static function url_is_ip( $hostname = null ) {
|
||||
|
||||
if ( ! $hostname ) {
|
||||
$hostname = wp_parse_url( Urls::site_url(), PHP_URL_HOST );
|
||||
}
|
||||
|
||||
$is_ip = filter_var( $hostname, FILTER_VALIDATE_IP ) !== false ? $hostname : false;
|
||||
return $is_ip;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add IDC-related data to the registration query.
|
||||
*
|
||||
* @param array $params The existing query params.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function register_request_body( array $params ) {
|
||||
$persistent_blog_id = get_option( static::PERSISTENT_BLOG_ID_OPTION_NAME );
|
||||
if ( $persistent_blog_id ) {
|
||||
$params['persistent_blog_id'] = $persistent_blog_id;
|
||||
$params['url_secret'] = URL_Secret::create_secret( 'registration_request_url_secret_failed' );
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the necessary options when site gets registered.
|
||||
*
|
||||
* @param int $blog_id The blog ID.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function site_registered( $blog_id ) {
|
||||
update_option( static::PERSISTENT_BLOG_ID_OPTION_NAME, (int) $blog_id, false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we need to update the ip_requester option.
|
||||
*
|
||||
* @param string $hostname The hostname to check.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function maybe_update_ip_requester( $hostname ) {
|
||||
// Check if transient exists
|
||||
$transient_key = ip2long( $hostname );
|
||||
if ( $transient_key && ! get_transient( 'jetpack_idc_ip_requester_' . $transient_key ) ) {
|
||||
self::set_ip_requester_for_idc( $hostname, $transient_key );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If URL is an IP, add the IP value to the ip_requester option with its expiry value.
|
||||
*
|
||||
* @param string $hostname The hostname to check.
|
||||
* @param int $transient_key The transient key.
|
||||
*/
|
||||
public static function set_ip_requester_for_idc( $hostname, $transient_key ) {
|
||||
// Check if option exists
|
||||
$data = Jetpack_Options::get_option( 'identity_crisis_ip_requester' );
|
||||
|
||||
$ip_requester = array(
|
||||
'ip' => $hostname,
|
||||
'expires_at' => time() + 360,
|
||||
);
|
||||
|
||||
// If not set, initialize it
|
||||
if ( empty( $data ) ) {
|
||||
$data = array( $ip_requester );
|
||||
} else {
|
||||
$updated_data = array();
|
||||
$updated_value = false;
|
||||
|
||||
// Remove expired values and update existing IP
|
||||
foreach ( $data as $item ) {
|
||||
if ( time() > $item['expires_at'] ) {
|
||||
continue; // Skip expired IP
|
||||
}
|
||||
|
||||
if ( $item['ip'] === $hostname ) {
|
||||
$item['expires_at'] = time() + 360;
|
||||
$updated_value = true;
|
||||
}
|
||||
|
||||
$updated_data[] = $item;
|
||||
}
|
||||
|
||||
if ( ! $updated_value || empty( $updated_data ) ) {
|
||||
$updated_data[] = $ip_requester;
|
||||
}
|
||||
|
||||
$data = $updated_data;
|
||||
}
|
||||
|
||||
self::update_ip_requester( $data, $transient_key );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the ip_requester option and set a transient to expire in 5 minutes.
|
||||
*
|
||||
* @param array $data The data to be updated.
|
||||
* @param int $transient_key The transient key.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function update_ip_requester( $data, $transient_key ) {
|
||||
// Update the option
|
||||
$updated = Jetpack_Options::update_option( 'identity_crisis_ip_requester', $data );
|
||||
// Set a transient to expire in 5 minutes
|
||||
if ( $updated ) {
|
||||
$transient_name = 'jetpack_idc_ip_requester_' . $transient_key;
|
||||
set_transient( $transient_name, $data, 300 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds `ip_requester` to the `jetpack.idcUrlValidation` URL validation endpoint.
|
||||
*
|
||||
* @param array $response The enpoint response that we're modifying.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function add_ip_requester_to_url_validation_response( array $response ) {
|
||||
$requesters = Jetpack_Options::get_option( 'identity_crisis_ip_requester' );
|
||||
if ( $requesters ) {
|
||||
// Loop through the requesters and add the IP to the response if it's not expired
|
||||
$i = 0;
|
||||
foreach ( $requesters as $ip ) {
|
||||
if ( $ip['expires_at'] > time() ) {
|
||||
$response['ip_requester'][] = $ip['ip'];
|
||||
}
|
||||
// Limit the response to five IPs
|
||||
$i = ++$i;
|
||||
if ( $i === 5 ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
}
|
@ -0,0 +1,322 @@
|
||||
<?php
|
||||
/**
|
||||
* Identity_Crisis REST endpoints of the Connection package.
|
||||
*
|
||||
* @package automattic/jetpack-connection
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\IdentityCrisis;
|
||||
|
||||
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
|
||||
use Automattic\Jetpack\Connection\Rest_Authentication;
|
||||
use Jetpack_Options;
|
||||
use Jetpack_XMLRPC_Server;
|
||||
use WP_Error;
|
||||
use WP_REST_Server;
|
||||
|
||||
/**
|
||||
* This class will handle Identity Crisis Endpoints
|
||||
*
|
||||
* @since automattic/jetpack-identity-crisis:0.2.0
|
||||
* @since 2.9.0
|
||||
*/
|
||||
class REST_Endpoints {
|
||||
|
||||
/**
|
||||
* Initialize REST routes.
|
||||
*/
|
||||
public static function initialize_rest_api() {
|
||||
|
||||
// Confirm that a site in identity crisis should be in staging mode.
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/identity-crisis/confirm-safe-mode',
|
||||
array(
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
'callback' => __CLASS__ . '::confirm_safe_mode',
|
||||
'permission_callback' => __CLASS__ . '::identity_crisis_mitigation_permission_check',
|
||||
)
|
||||
);
|
||||
|
||||
// Handles the request to migrate stats and subscribers during an identity crisis.
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'identity-crisis/migrate',
|
||||
array(
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
'callback' => __CLASS__ . '::migrate_stats_and_subscribers',
|
||||
'permission_callback' => __CLASS__ . '::identity_crisis_mitigation_permission_check',
|
||||
)
|
||||
);
|
||||
|
||||
// IDC resolve: create an entirely new shadow site for this URL.
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/identity-crisis/start-fresh',
|
||||
array(
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
'callback' => __CLASS__ . '::start_fresh_connection',
|
||||
'permission_callback' => __CLASS__ . '::identity_crisis_mitigation_permission_check',
|
||||
'args' => array(
|
||||
'redirect_uri' => array(
|
||||
'description' => __( 'URI of the admin page where the user should be redirected after connection flow', 'jetpack-connection' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
// Fetch URL and secret for IDC check.
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/identity-crisis/idc-url-validation',
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( static::class, 'validate_urls_and_set_secret' ),
|
||||
'permission_callback' => array( static::class, 'url_secret_permission_check' ),
|
||||
)
|
||||
);
|
||||
|
||||
// Fetch URL verification secret.
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/identity-crisis/url-secret',
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( static::class, 'fetch_url_secret' ),
|
||||
'permission_callback' => array( static::class, 'url_secret_permission_check' ),
|
||||
)
|
||||
);
|
||||
|
||||
// Fetch URL verification secret.
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/identity-crisis/compare-url-secret',
|
||||
array(
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
'callback' => array( static::class, 'compare_url_secret' ),
|
||||
'permission_callback' => array( static::class, 'compare_url_secret_permission_check' ),
|
||||
'args' => array(
|
||||
'secret' => array(
|
||||
'description' => __( 'URL secret to compare to the ones stored in the database.', 'jetpack-connection' ),
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles identity crisis mitigation, confirming safe mode for this site.
|
||||
*
|
||||
* @since 0.2.0
|
||||
* @since-jetpack 4.4.0
|
||||
*
|
||||
* @return bool | WP_Error True if option is properly set.
|
||||
*/
|
||||
public static function confirm_safe_mode() {
|
||||
$updated = Jetpack_Options::update_option( 'safe_mode_confirmed', true );
|
||||
if ( $updated ) {
|
||||
return rest_ensure_response(
|
||||
array(
|
||||
'code' => 'success',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return new WP_Error(
|
||||
'error_setting_jetpack_safe_mode',
|
||||
esc_html__( 'Could not confirm safe mode.', 'jetpack-connection' ),
|
||||
array( 'status' => 500 )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles identity crisis mitigation, migrating stats and subscribers from old url to this, new url.
|
||||
*
|
||||
* @since 0.2.0
|
||||
* @since-jetpack 4.4.0
|
||||
*
|
||||
* @return bool | WP_Error True if option is properly set.
|
||||
*/
|
||||
public static function migrate_stats_and_subscribers() {
|
||||
if ( Jetpack_Options::get_option( 'sync_error_idc' ) && ! Jetpack_Options::delete_option( 'sync_error_idc' ) ) {
|
||||
return new WP_Error(
|
||||
'error_deleting_sync_error_idc',
|
||||
esc_html__( 'Could not delete sync error option.', 'jetpack-connection' ),
|
||||
array( 'status' => 500 )
|
||||
);
|
||||
}
|
||||
|
||||
if ( Jetpack_Options::get_option( 'migrate_for_idc' ) || Jetpack_Options::update_option( 'migrate_for_idc', true ) ) {
|
||||
return rest_ensure_response(
|
||||
array(
|
||||
'code' => 'success',
|
||||
)
|
||||
);
|
||||
}
|
||||
return new WP_Error(
|
||||
'error_setting_jetpack_migrate',
|
||||
esc_html__( 'Could not confirm migration.', 'jetpack-connection' ),
|
||||
array( 'status' => 500 )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* This IDC resolution will disconnect the site and re-connect to a completely new
|
||||
* and separate shadow site than the original.
|
||||
*
|
||||
* It will first will disconnect the site without phoning home as to not disturb the production site.
|
||||
* It then builds a fresh connection URL and sends it back along with the response.
|
||||
*
|
||||
* @since 0.2.0
|
||||
* @since-jetpack 4.4.0
|
||||
*
|
||||
* @param \WP_REST_Request $request The request sent to the WP REST API.
|
||||
*
|
||||
* @return \WP_REST_Response|WP_Error
|
||||
*/
|
||||
public static function start_fresh_connection( $request ) {
|
||||
/**
|
||||
* Fires when Users have requested through Identity Crisis for the connection to be reset.
|
||||
* Should be used to disconnect any connections and reset options.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*/
|
||||
do_action( 'jetpack_idc_disconnect' );
|
||||
|
||||
$connection = new Connection_Manager();
|
||||
$result = $connection->try_registration( true );
|
||||
|
||||
// early return if site registration fails.
|
||||
if ( ! $result || is_wp_error( $result ) ) {
|
||||
return rest_ensure_response( $result );
|
||||
}
|
||||
|
||||
$redirect_uri = $request->get_param( 'redirect_uri' ) ? admin_url( $request->get_param( 'redirect_uri' ) ) : null;
|
||||
|
||||
/**
|
||||
* Filters the connection url that users should be redirected to for re-establishing their connection.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*
|
||||
* @param \WP_REST_Response|WP_Error $connection_url Connection URL user should be redirected to.
|
||||
*/
|
||||
return apply_filters( 'jetpack_idc_authorization_url', rest_ensure_response( $connection->get_authorization_url( null, $redirect_uri ) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that user can mitigate an identity crisis.
|
||||
*
|
||||
* @since 0.2.0
|
||||
* @since-jetpack 4.4.0
|
||||
*
|
||||
* @return true|WP_Error True if the user has capability 'jetpack_disconnect', an error object otherwise.
|
||||
*/
|
||||
public static function identity_crisis_mitigation_permission_check() {
|
||||
if ( current_user_can( 'jetpack_disconnect' ) ) {
|
||||
return true;
|
||||
}
|
||||
$error_msg = esc_html__(
|
||||
'You do not have the correct user permissions to perform this action.
|
||||
Please contact your site admin if you think this is a mistake.',
|
||||
'jetpack-connection'
|
||||
);
|
||||
|
||||
return new WP_Error( 'invalid_user_permission_identity_crisis', $error_msg, array( 'status' => rest_authorization_required_code() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Endpoint for URL validation and creating a secret.
|
||||
*
|
||||
* @since 0.18.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function validate_urls_and_set_secret() {
|
||||
$xmlrpc_server = new Jetpack_XMLRPC_Server();
|
||||
$result = $xmlrpc_server->validate_urls_for_idc_mitigation();
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Endpoint for fetching the existing secret.
|
||||
*
|
||||
* @return WP_Error|\WP_REST_Response
|
||||
*/
|
||||
public static function fetch_url_secret() {
|
||||
$secret = new URL_Secret();
|
||||
|
||||
if ( ! $secret->exists() ) {
|
||||
return new WP_Error( 'missing_url_secret', esc_html__( 'URL secret does not exist.', 'jetpack-connection' ) );
|
||||
}
|
||||
|
||||
return rest_ensure_response(
|
||||
array(
|
||||
'code' => 'success',
|
||||
'data' => array(
|
||||
'secret' => $secret->get_secret(),
|
||||
'expires_at' => $secret->get_expires_at(),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Endpoint for comparing the existing secret.
|
||||
*
|
||||
* @param \WP_REST_Request $request The request sent to the WP REST API.
|
||||
*
|
||||
* @return WP_Error|\WP_REST_Response
|
||||
*/
|
||||
public static function compare_url_secret( $request ) {
|
||||
$match = false;
|
||||
|
||||
$storage = new URL_Secret();
|
||||
|
||||
if ( $storage->exists() ) {
|
||||
$remote_secret = $request->get_param( 'secret' );
|
||||
$match = $remote_secret && hash_equals( $storage->get_secret(), $remote_secret );
|
||||
}
|
||||
|
||||
return rest_ensure_response(
|
||||
array(
|
||||
'code' => 'success',
|
||||
'match' => $match,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify url_secret create/fetch permissions (valid blog token authentication).
|
||||
*
|
||||
* @return true|WP_Error
|
||||
*/
|
||||
public static function url_secret_permission_check() {
|
||||
return Rest_Authentication::is_signed_with_blog_token()
|
||||
? true
|
||||
: new WP_Error(
|
||||
'invalid_user_permission_identity_crisis',
|
||||
esc_html__( 'You do not have the correct user permissions to perform this action.', 'jetpack-connection' ),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The endpoint is only available on non-connected sites.
|
||||
* use `/identity-crisis/url-secret` for connected sites.
|
||||
*
|
||||
* @return true|WP_Error
|
||||
*/
|
||||
public static function compare_url_secret_permission_check() {
|
||||
return ( new Connection_Manager() )->is_connected()
|
||||
? new WP_Error(
|
||||
'invalid_connection_status',
|
||||
esc_html__( 'The endpoint is not available on connected sites.', 'jetpack-connection' ),
|
||||
array( 'status' => 403 )
|
||||
)
|
||||
: true;
|
||||
}
|
||||
}
|
@ -0,0 +1,204 @@
|
||||
<?php
|
||||
/**
|
||||
* Identity_Crisis UI class of the Connection package.
|
||||
*
|
||||
* @package automattic/jetpack-connection
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\IdentityCrisis;
|
||||
|
||||
use Automattic\Jetpack\Assets;
|
||||
use Automattic\Jetpack\Identity_Crisis;
|
||||
use Automattic\Jetpack\Status;
|
||||
use Automattic\Jetpack\Status\Host;
|
||||
use Automattic\Jetpack\Tracking;
|
||||
use Jetpack_Options;
|
||||
use Jetpack_Tracks_Client;
|
||||
|
||||
/**
|
||||
* The Identity Crisis UI handling.
|
||||
*/
|
||||
class UI {
|
||||
|
||||
/**
|
||||
* Temporary storage for consumer data.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $consumers;
|
||||
|
||||
/**
|
||||
* Initialization.
|
||||
*/
|
||||
public static function init() {
|
||||
if ( did_action( 'jetpack_identity_crisis_ui_init' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Action called after initializing Identity Crisis UI.
|
||||
*
|
||||
* @since 0.6.0
|
||||
*/
|
||||
do_action( 'jetpack_identity_crisis_ui_init' );
|
||||
|
||||
$idc_data = Identity_Crisis::check_identity_crisis();
|
||||
|
||||
if ( false === $idc_data ) {
|
||||
return;
|
||||
}
|
||||
|
||||
add_action( 'admin_enqueue_scripts', array( static::class, 'enqueue_scripts' ) );
|
||||
|
||||
Tracking::register_tracks_functions_scripts( true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue scripts!
|
||||
*/
|
||||
public static function enqueue_scripts() {
|
||||
if ( is_admin() ) {
|
||||
Assets::register_script(
|
||||
'jp_identity_crisis_banner',
|
||||
'../../dist/identity-crisis.js',
|
||||
__FILE__,
|
||||
array(
|
||||
'in_footer' => true,
|
||||
'textdomain' => 'jetpack-connection',
|
||||
)
|
||||
);
|
||||
Assets::enqueue_script( 'jp_identity_crisis_banner' );
|
||||
wp_add_inline_script( 'jp_identity_crisis_banner', static::get_initial_state(), 'before' );
|
||||
|
||||
add_action( 'admin_notices', array( static::class, 'render_container' ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the container element for the IDC banner.
|
||||
*/
|
||||
public static function render_container() {
|
||||
?>
|
||||
<div id="jp-identity-crisis-container" class="notice"></div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the rendered initial state JavaScript code.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function get_initial_state() {
|
||||
return 'var JP_IDENTITY_CRISIS__INITIAL_STATE=JSON.parse(decodeURIComponent("' . rawurlencode( wp_json_encode( static::get_initial_state_data() ) ) . '"));';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the initial state data.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private static function get_initial_state_data() {
|
||||
$idc_urls = Identity_Crisis::get_mismatched_urls();
|
||||
$current_screen = get_current_screen();
|
||||
$is_admin = current_user_can( 'jetpack_disconnect' );
|
||||
$possible_dynamic_site_url_detected = (bool) Identity_Crisis::detect_possible_dynamic_site_url();
|
||||
$is_development_site = (bool) Status::is_development_site();
|
||||
|
||||
return array(
|
||||
'WP_API_root' => esc_url_raw( rest_url() ),
|
||||
'WP_API_nonce' => wp_create_nonce( 'wp_rest' ),
|
||||
'wpcomHomeUrl' => ( is_array( $idc_urls ) && array_key_exists( 'wpcom_url', $idc_urls ) ) ? $idc_urls['wpcom_url'] : null,
|
||||
'currentUrl' => ( is_array( $idc_urls ) && array_key_exists( 'current_url', $idc_urls ) ) ? $idc_urls['current_url'] : null,
|
||||
'redirectUri' => isset( $_SERVER['REQUEST_URI'] ) ? str_replace( '/wp-admin/', '/', filter_var( wp_unslash( $_SERVER['REQUEST_URI'] ) ) ) : '',
|
||||
'tracksUserData' => Jetpack_Tracks_Client::get_connected_user_tracks_identity(),
|
||||
'tracksEventData' => array(
|
||||
'isAdmin' => $is_admin,
|
||||
'currentScreen' => $current_screen ? $current_screen->id : false,
|
||||
'blogID' => Jetpack_Options::get_option( 'id' ),
|
||||
'platform' => static::get_platform(),
|
||||
),
|
||||
'isSafeModeConfirmed' => Identity_Crisis::$is_safe_mode_confirmed,
|
||||
'consumerData' => static::get_consumer_data(),
|
||||
'isAdmin' => $is_admin,
|
||||
'possibleDynamicSiteUrlDetected' => $possible_dynamic_site_url_detected,
|
||||
'isDevelopmentSite' => $is_development_site,
|
||||
|
||||
/**
|
||||
* Use the filter to provide custom HTML elecontainer ID.
|
||||
*
|
||||
* @since 0.10.0
|
||||
*
|
||||
* @param string|null $containerID The container ID.
|
||||
*/
|
||||
'containerID' => apply_filters( 'identity_crisis_container_id', null ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the package consumer data.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_consumer_data() {
|
||||
if ( null !== static::$consumers ) {
|
||||
return static::$consumers;
|
||||
}
|
||||
|
||||
$consumers = apply_filters( 'jetpack_idc_consumers', array() );
|
||||
|
||||
if ( ! $consumers ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
usort(
|
||||
$consumers,
|
||||
function ( $c1, $c2 ) {
|
||||
$priority1 = ( array_key_exists( 'priority', $c1 ) && (int) $c1['priority'] ) ? (int) $c1['priority'] : 10;
|
||||
$priority2 = ( array_key_exists( 'priority', $c2 ) && (int) $c2['priority'] ) ? (int) $c2['priority'] : 10;
|
||||
|
||||
return $priority1 <=> $priority2;
|
||||
}
|
||||
);
|
||||
|
||||
$consumer_chosen = null;
|
||||
$consumer_url_length = 0;
|
||||
|
||||
foreach ( $consumers as $consumer ) {
|
||||
if ( empty( $consumer['admin_page'] ) || ! is_string( $consumer['admin_page'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( isset( $_SERVER['REQUEST_URI'] ) && str_starts_with( filter_var( wp_unslash( $_SERVER['REQUEST_URI'] ) ), $consumer['admin_page'] ) && strlen( $consumer['admin_page'] ) > $consumer_url_length ) {
|
||||
$consumer_chosen = $consumer;
|
||||
$consumer_url_length = strlen( $consumer['admin_page'] );
|
||||
}
|
||||
}
|
||||
|
||||
static::$consumers = $consumer_chosen ? $consumer_chosen : array_shift( $consumers );
|
||||
|
||||
return static::$consumers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the site platform.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function get_platform() {
|
||||
$host = new Host();
|
||||
|
||||
if ( $host->is_woa_site() ) {
|
||||
return 'woa';
|
||||
}
|
||||
|
||||
if ( $host->is_vip_site() ) {
|
||||
return 'vip';
|
||||
}
|
||||
|
||||
if ( $host->is_newspack_site() ) {
|
||||
return 'newspack';
|
||||
}
|
||||
|
||||
return 'self-hosted';
|
||||
}
|
||||
}
|
@ -0,0 +1,159 @@
|
||||
<?php
|
||||
/**
|
||||
* IDC URL secret functionality.
|
||||
*
|
||||
* @package automattic/jetpack-connection
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\IdentityCrisis;
|
||||
|
||||
use Automattic\Jetpack\Connection\Urls;
|
||||
use Automattic\Jetpack\Tracking;
|
||||
use Jetpack_Options;
|
||||
|
||||
/**
|
||||
* IDC URL secret functionality.
|
||||
* A short-lived secret used to verify whether an IDC is coming from the same vs a different Jetpack site.
|
||||
*/
|
||||
class URL_Secret {
|
||||
|
||||
/**
|
||||
* The options key used to store the secret.
|
||||
*/
|
||||
const OPTION_KEY = 'identity_crisis_url_secret';
|
||||
|
||||
/**
|
||||
* Secret lifespan (5 minutes)
|
||||
*/
|
||||
const LIFESPAN = 300;
|
||||
|
||||
/**
|
||||
* The URL secret string.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
private $secret = null;
|
||||
|
||||
/**
|
||||
* The URL secret expiration date in unix timestamp.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
private $expires_at = null;
|
||||
|
||||
/**
|
||||
* Initialize the class.
|
||||
*/
|
||||
public function __construct() {
|
||||
$secret_data = $this->fetch();
|
||||
|
||||
if ( $secret_data !== null ) {
|
||||
$this->secret = $secret_data['secret'];
|
||||
$this->expires_at = $secret_data['expires_at'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the URL secret from the database.
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
private function fetch() {
|
||||
$data = Jetpack_Options::get_option( static::OPTION_KEY );
|
||||
|
||||
if ( $data === false || empty( $data['secret'] ) || empty( $data['expires_at'] ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( time() > $data['expires_at'] ) {
|
||||
Jetpack_Options::delete_option( static::OPTION_KEY );
|
||||
return null;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new secret and save it in the options.
|
||||
*
|
||||
* @throws Exception Thrown if unable to save the new secret.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function create() {
|
||||
$secret_data = array(
|
||||
'secret' => $this->generate_secret(),
|
||||
'expires_at' => strval( time() + static::LIFESPAN ),
|
||||
);
|
||||
|
||||
$result = Jetpack_Options::update_option( static::OPTION_KEY, $secret_data );
|
||||
|
||||
if ( ! $result ) {
|
||||
throw new Exception( esc_html__( 'Unable to save new URL secret', 'jetpack-connection' ) );
|
||||
}
|
||||
|
||||
$this->secret = $secret_data['secret'];
|
||||
$this->expires_at = $secret_data['expires_at'];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL secret.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function get_secret() {
|
||||
return $this->secret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL secret expiration date.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function get_expires_at() {
|
||||
return $this->expires_at;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the secret exists.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function exists() {
|
||||
return $this->secret && $this->expires_at;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the secret string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function generate_secret() {
|
||||
return wp_generate_password( 12, false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate secret for response.
|
||||
*
|
||||
* @param string $flow used to tell which flow generated the exception.
|
||||
* @return string|null
|
||||
*/
|
||||
public static function create_secret( $flow = 'generating_secret_failed' ) {
|
||||
$secret_value = null;
|
||||
try {
|
||||
|
||||
$secret = new self();
|
||||
$secret->create();
|
||||
|
||||
if ( $secret->exists() ) {
|
||||
$secret_value = $secret->get_secret();
|
||||
}
|
||||
} catch ( Exception $e ) {
|
||||
// Track the error and proceed.
|
||||
( new Tracking() )->record_user_event( $flow, array( 'current_url' => Urls::site_url() ) );
|
||||
}
|
||||
return $secret_value;
|
||||
}
|
||||
}
|
@ -214,12 +214,14 @@ class Notices {
|
||||
* Error message that is displayed when the current site is in an identity crisis and SSO can not be used.
|
||||
*
|
||||
* @since jetpack-4.4.0
|
||||
* @deprecated since 2.10.0
|
||||
*
|
||||
* @param string $message Error message.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function sso_not_allowed_in_staging( $message ) {
|
||||
_deprecated_function( __FUNCTION__, '2.10.0', 'sso_not_allowed_in_safe_mode' );
|
||||
$error = __(
|
||||
'Logging in with WordPress.com is disabled for sites that are in staging mode.',
|
||||
'jetpack-connection'
|
||||
@ -234,7 +236,36 @@ class Notices {
|
||||
*
|
||||
* @param string $error Error text.
|
||||
*/
|
||||
$error = apply_filters( 'jetpack_sso_disallowed_staging_notice', $error );
|
||||
$error = apply_filters_deprecated( 'jetpack_sso_disallowed_staging_notice', array( $error ), '2.9.1', 'jetpack_sso_disallowed_safe_mode_notice' );
|
||||
$message .= sprintf( '<p class="message">%s</p>', esc_html( $error ) );
|
||||
return $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Error message that is displayed when the current site is in an identity crisis and SSO can not be used.
|
||||
*
|
||||
* @since 2.10.0
|
||||
*
|
||||
* @param string $message Error message.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function sso_not_allowed_in_safe_mode( $message ) {
|
||||
$error = __(
|
||||
'Logging in with WordPress.com is disabled for sites that are in safe mode.',
|
||||
'jetpack-connection'
|
||||
);
|
||||
|
||||
/**
|
||||
* Filters the disallowed notice for sites in safe mode attempting SSO.
|
||||
*
|
||||
* @module sso
|
||||
*
|
||||
* @since 2.10.0
|
||||
*
|
||||
* @param string $error Error text.
|
||||
*/
|
||||
$error = apply_filters( 'jetpack_sso_disallowed_safe_mode_notice', $error );
|
||||
$message .= sprintf( '<p class="message">%s</p>', esc_html( $error ) );
|
||||
return $message;
|
||||
}
|
||||
|
@ -268,7 +268,7 @@ class SSO {
|
||||
// Always add the jetpack-sso class so that we can add SSO specific styling even when the SSO form isn't being displayed.
|
||||
$classes[] = 'jetpack-sso';
|
||||
|
||||
if ( ! ( new Status() )->is_staging_site() ) {
|
||||
if ( ! ( new Status() )->in_safe_mode() ) {
|
||||
/**
|
||||
* Should we show the SSO login form?
|
||||
*
|
||||
@ -446,13 +446,6 @@ class SSO {
|
||||
return $wants_to_login;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to determine if the user has indicated they want to use the wp-admin interface.
|
||||
*/
|
||||
private function use_wp_admin_interface() {
|
||||
return 'wp-admin' === get_option( 'wpcom_admin_interface' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialization for a SSO request.
|
||||
*/
|
||||
@ -488,8 +481,8 @@ class SSO {
|
||||
if ( isset( $_GET['result'] ) && isset( $_GET['user_id'] ) && isset( $_GET['sso_nonce'] ) && 'success' === $_GET['result'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$this->handle_login();
|
||||
$this->display_sso_login_form();
|
||||
} elseif ( ( new Status() )->is_staging_site() ) {
|
||||
add_filter( 'login_message', array( Notices::class, 'sso_not_allowed_in_staging' ) );
|
||||
} elseif ( ( new Status() )->in_safe_mode() ) {
|
||||
add_filter( 'login_message', array( Notices::class, 'sso_not_allowed_in_safe_mode' ) );
|
||||
} else {
|
||||
// Is it wiser to just use wp_redirect than do this runaround to wp_safe_redirect?
|
||||
add_filter( 'allowed_redirect_hosts', array( Helpers::class, 'allowed_redirect_hosts' ) );
|
||||
@ -510,7 +503,7 @@ class SSO {
|
||||
* to the WordPress.com login page AND that the request to wp-login.php
|
||||
* is not something other than login (Like logout!)
|
||||
*/
|
||||
if ( ! $this->use_wp_admin_interface() && Helpers::bypass_login_forward_wpcom() && $this->wants_to_login() ) {
|
||||
if ( Helpers::bypass_login_forward_wpcom() && $this->wants_to_login() ) {
|
||||
add_filter( 'allowed_redirect_hosts', array( Helpers::class, 'allowed_redirect_hosts' ) );
|
||||
$reauth = ! empty( $_GET['force_reauth'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$sso_url = $this->get_sso_url_or_die( $reauth );
|
||||
@ -531,8 +524,8 @@ class SSO {
|
||||
add_filter( 'login_body_class', array( $this, 'login_body_class' ) );
|
||||
add_action( 'login_head', array( $this, 'print_inline_admin_css' ) );
|
||||
|
||||
if ( ( new Status() )->is_staging_site() ) {
|
||||
add_filter( 'login_message', array( Notices::class, 'sso_not_allowed_in_staging' ) );
|
||||
if ( ( new Status() )->in_safe_mode() ) {
|
||||
add_filter( 'login_message', array( Notices::class, 'sso_not_allowed_in_safe_mode' ) );
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -57,7 +57,7 @@ class User_Admin {
|
||||
add_action( 'delete_user_form', array( $this, 'render_invitations_notices_for_deleted_users' ) );
|
||||
add_action( 'delete_user', array( $this, 'revoke_user_invite' ) );
|
||||
add_filter( 'manage_users_columns', array( $this, 'jetpack_user_connected_th' ) );
|
||||
add_action( 'manage_users_custom_column', array( $this, 'jetpack_show_connection_status' ), 10, 3 );
|
||||
add_filter( 'manage_users_custom_column', array( $this, 'jetpack_show_connection_status' ), 10, 3 );
|
||||
add_action( 'user_row_actions', array( $this, 'jetpack_user_table_row_actions' ), 10, 2 );
|
||||
add_action( 'admin_notices', array( $this, 'handle_invitation_results' ) );
|
||||
add_action( 'admin_post_jetpack_invite_user_to_wpcom', array( $this, 'invite_user_to_wpcom' ) );
|
||||
@ -1203,7 +1203,7 @@ class User_Admin {
|
||||
'<span tabindex="0" role="tooltip" aria-label="%4$s: %3$s" class="jetpack-sso-invitation-tooltip-icon sso-disconnected-user">
|
||||
<a href="%1$s" class="jetpack-sso-invitation sso-disconnected-user">%2$s</a>
|
||||
<span class="sso-disconnected-user-icon dashicons dashicons-warning">
|
||||
<span class="jetpack-sso-invitation-tooltip jetpack-sso-td-tooltip" tabindex="0">%3$s</span>
|
||||
<span class="jetpack-sso-invitation-tooltip jetpack-sso-td-tooltip">%3$s</span>
|
||||
</span>
|
||||
</span>',
|
||||
add_query_arg(
|
||||
@ -1221,6 +1221,7 @@ class User_Admin {
|
||||
return $connection_html;
|
||||
}
|
||||
}
|
||||
return $val;
|
||||
}
|
||||
|
||||
/**
|
||||
|
Reference in New Issue
Block a user