$network_wide ? network_admin_url( 'plugins.php' ) : admin_url( 'plugins.php' ),
'settings_url' => $network_wide ? self::network_page_url() : self::site_page_url(),
);
add_filter( 'wp_redirect', array( __CLASS__, 'filter_activation_redirect' ), 10, 2 );
}
/**
* Opens the settings screen after a single activation.
*
* @param string $location Redirect destination.
* @param int $status HTTP status code.
*
* @return string Redirect destination.
*/
public static function filter_activation_redirect( $location, $status ) {
if ( empty( self::$activation_redirect ) || ! is_string( $location ) || 302 !== $status ) {
return $location;
}
$base = explode( '?', $location, 2 );
if ( self::$activation_redirect['plugins_url'] !== $base[0] ) {
return $location;
}
$query = array();
wp_parse_str( (string) wp_parse_url( $location, PHP_URL_QUERY ), $query );
if ( ! isset( $query['activate'] ) || 'true' !== $query['activate'] ) {
return $location;
}
if ( isset( $query['error'] ) || isset( $query['activate-multi'] ) ) {
return $location;
}
$target = self::$activation_redirect['settings_url'];
self::$activation_redirect = array();
remove_filter( 'wp_redirect', array( __CLASS__, 'filter_activation_redirect' ), 10 );
return $target;
}
// The notice reuses a request-local result; never keep an all-clear across requests.
/**
* Returns a site that needs protection in the current scope.
*
* @return int Site ID, or 0 when no site needs attention.
*/
private static function protection_gap_site_id() {
$network_scope = is_multisite() && is_super_admin( get_current_user_id() ) && current_user_can( self::NETWORK_CAPABILITY );
if ( ! $network_scope && ( is_network_admin() || ! current_user_can( self::SITE_CAPABILITY ) ) ) {
return 0;
}
$key = get_current_user_id() . ':' . ( $network_scope ? 'network:' . get_current_network_id() : 'site:' . get_current_blog_id() );
if ( ! array_key_exists( $key, self::$protection_gaps ) ) {
self::$protection_gaps[ $key ] = $network_scope
? MisticGate_Network::first_unprotected_site_id()
: ( MisticGate::protects_site( get_current_blog_id() ) || MisticGate_Network::protects_current_site() ? 0 : get_current_blog_id() );
}
return self::$protection_gaps[ $key ];
}
/**
* Registers the site settings page.
*
* @return void
*/
public static function register_site_page() {
if ( ! current_user_can( self::SITE_CAPABILITY ) ) {
return;
}
self::$page_hooks[] = add_menu_page(
'MisticGate',
'MisticGate',
self::SITE_CAPABILITY,
self::PAGE,
array( __CLASS__, 'render_site_page' ),
'dashicons-shield-alt',
99
);
}
/**
* Registers the network settings page.
*
* @return void
*/
public static function register_network_page() {
if ( ! is_multisite() || ! is_super_admin( get_current_user_id() ) || ! current_user_can( self::NETWORK_CAPABILITY ) ) {
return;
}
self::$page_hooks[] = add_menu_page(
'MisticGate',
'MisticGate',
self::NETWORK_CAPABILITY,
self::PAGE,
array( __CLASS__, 'render_network_page' ),
'dashicons-shield-alt',
99
);
}
/**
* Loads the settings page assets.
*
* @param string $hook_suffix Current admin page hook.
*
* @return void
*/
public static function enqueue_assets( $hook_suffix ) {
if ( ! in_array( $hook_suffix, self::$page_hooks, true ) ) {
return;
}
wp_enqueue_style( 'misticgate-admin', MISTICGATE_BASE_URL . 'inc/css/misticgate-admin.css', array(), MISTICGATE_VERSION );
wp_enqueue_script(
'misticgate-admin',
MISTICGATE_BASE_URL . 'inc/js/misticgate-admin.js',
array(),
MISTICGATE_VERSION,
array(
'in_footer' => true,
'strategy' => 'defer',
)
);
wp_localize_script(
'misticgate-admin',
'misticgateAdminL10n',
array(
'copyUrl' => __( 'Copy URL', 'misticgate' ),
'copied' => __( 'Copied', 'misticgate' ),
'copySuccess' => __( 'URL copied. Save it as a bookmark.', 'misticgate' ),
'copyFailed' => __( 'Could not copy automatically. Copy the selected URL manually.', 'misticgate' ),
'pendingLeave' => __( 'The new gate URL is not active yet. It will not work until you switch to it. Leave this page?', 'misticgate' ),
)
);
}
// Keep WordPress Heartbeat and its dialog behavior; replace only the protected screen's login iframe with guidance.
/**
* Loads the expired-session guidance on protected screens.
*
* @return void
*/
public static function prepare_session_notice() {
if ( ! is_user_logged_in() || ! ( MisticGate::protects_site( get_current_blog_id() ) || MisticGate_Network::protects_current_site() ) ) {
return;
}
$priority = has_action( 'admin_print_footer_scripts', 'wp_auth_check_html' );
if ( false === $priority ) {
return;
}
remove_action( 'admin_print_footer_scripts', 'wp_auth_check_html', $priority );
remove_action( 'wp_print_footer_scripts', 'wp_auth_check_html', $priority );
add_action( 'admin_print_footer_scripts', array( __CLASS__, 'render_session_notice' ), $priority );
wp_enqueue_style( 'misticgate-session', MISTICGATE_BASE_URL . 'inc/css/misticgate-session.css', array( 'wp-auth-check' ), MISTICGATE_VERSION );
wp_enqueue_script( 'misticgate-session', MISTICGATE_BASE_URL . 'inc/js/misticgate-session.js', array( 'wp-auth-check' ), MISTICGATE_VERSION, true );
}
/**
* Outputs the expired-session guidance template.
*
* @return void
*/
public static function render_session_notice() {
?>
' . esc_html__( 'Settings', 'misticgate' ) . '' );
return $links;
}
/**
* Adds the Settings link on the Network Plugins screen.
*
* @param array $actions Plugin action links.
* @param string $plugin_file Plugin file.
* @param array $plugin_data Plugin data.
* @param string $context Plugin list context.
*
* @return array Plugin action links.
*/
public static function add_network_action_link( $actions, $plugin_file, $plugin_data, $context ) {
unset( $plugin_data, $context );
if ( is_multisite() && is_super_admin( get_current_user_id() ) && current_user_can( self::NETWORK_CAPABILITY ) && plugin_basename( MISTICGATE_MAIN_FILE ) === $plugin_file ) {
array_unshift( $actions, '' . esc_html__( 'Settings', 'misticgate' ) . '' );
}
return $actions;
}
/**
* Shows the notice that opens the settings while the gate is off.
*
* @return void
*/
public static function show_setup_notice() {
if ( self::$protection_notice_shown || MisticGate::is_emergency_disabled() ) {
return;
}
$screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
if ( ! $screen ) {
return;
}
if ( is_network_admin() ) {
if ( ! is_multisite() || 'plugins-network' !== $screen->id || ! is_super_admin( get_current_user_id() ) || ! current_user_can( self::NETWORK_CAPABILITY ) ) {
return;
}
$settings = MisticGate_Network::get_settings();
$page_url = self::network_page_url();
} else {
if ( 'plugins' !== $screen->id || ! current_user_can( self::SITE_CAPABILITY ) || ( is_multisite() && MisticGate_Network::protects_current_site() ) ) {
return;
}
$settings = MisticGate::get_settings();
$page_url = self::site_page_url();
}
if ( $settings['enabled'] ) {
return;
}
?>
MisticGate: ' . esc_html__( 'Protection is paused by MISTICGATE_DISABLE. Saved settings have not changed.', 'misticgate' ) . '
';
return;
}
if ( is_multisite() && is_super_admin( get_current_user_id() ) && current_user_can( self::NETWORK_CAPABILITY ) ) {
$site_id = self::protection_gap_site_id();
if ( 0 === $site_id ) {
return;
}
$message = __( 'Some sites in this network are not protected by MisticGate.', 'misticgate' );
$main_active = MisticGate::is_active_on_site( get_main_site_id( get_current_network_id() ) );
$url = $main_active ? self::network_page_url( $site_id ) : network_admin_url( 'plugins.php' );
$link_text = $main_active ? __( 'Review protection', 'misticgate' ) : __( 'Open network plugins', 'misticgate' );
if ( ! $main_active ) {
$message .= ' ' . __( 'Activate MisticGate on the main site or for the network to open network settings.', 'misticgate' );
}
} else {
if ( 0 === self::protection_gap_site_id() ) {
return;
}
$message = __( 'This site\'s login page is not protected by MisticGate.', 'misticgate' );
$url = self::site_page_url();
$link_text = __( 'Open settings', 'misticgate' );
}
self::$protection_notice_shown = true;
echo '';
}
/**
* Outputs the site settings page.
*
* @return void
*/
public static function render_site_page() {
if ( ! current_user_can( self::SITE_CAPABILITY ) ) {
wp_die( esc_html__( 'You are not allowed to manage MisticGate.', 'misticgate' ), '', array( 'response' => 403 ) );
}
if ( 'readme' === self::query_key( 'tab' ) ) {
self::render_readme_page( false );
return;
}
self::render_settings_page(
'site',
get_current_blog_id(),
admin_url( 'admin-post.php' ),
'',
''
);
}
/**
* Outputs the network settings page.
*
* @return void
*/
public static function render_network_page() {
if (
! is_multisite() ||
! current_user_can( self::NETWORK_CAPABILITY ) ||
! is_super_admin( get_current_user_id() )
) {
wp_die( esc_html__( 'You are not allowed to manage MisticGate network settings.', 'misticgate' ), '', array( 'response' => 403 ) );
}
MisticGate_Network::ensure_settings();
$site_id = self::query_integer( 'site_id' );
$site = $site_id > 0 ? get_site( $site_id ) : null;
if ( 0 === $site_id ) {
if ( 'readme' === self::query_key( 'tab' ) ) {
self::render_readme_page( true );
return;
}
self::render_network_site_list();
return;
}
if ( ! $site || get_current_network_id() !== (int) $site->network_id ) {
wp_die(
esc_html__( 'This site is no longer available in this network.', 'misticgate' ),
'',
array(
'response' => 404,
'back_link' => true,
)
);
}
if ( ! wp_is_site_initialized( $site_id ) ) {
wp_die(
esc_html__( 'This site is not ready. Finish creating or restoring it in WordPress before configuring MisticGate.', 'misticgate' ),
'',
array(
'response' => 409,
'back_link' => true,
)
);
}
$form_action = admin_url( 'admin-post.php' );
$site_name = (string) get_blog_option( $site_id, 'blogname', '' );
$site_url = get_home_url( $site_id, '/' );
switch_to_blog( $site_id );
try {
MisticGate::ensure_settings();
self::render_settings_page(
'network',
$site_id,
$form_action,
$site_name,
$site_url
);
} finally {
restore_current_blog();
}
}
// Settings screens.
/**
* Outputs the site list on the network settings page.
*
* @return void
*/
private static function render_network_site_list() {
$paged = max( 1, self::query_integer( 'paged' ) );
$network_id = get_current_network_id();
$offset = ( $paged - 1 ) * self::SITES_PER_PAGE;
$sites = get_sites(
array(
'network_id' => $network_id,
'number' => self::SITES_PER_PAGE,
'offset' => $offset,
'orderby' => 'id',
'order' => 'ASC',
)
);
$total = (int) get_sites(
array(
'network_id' => $network_id,
'count' => true,
)
);
$network_settings = MisticGate_Network::get_settings();
$form_action = admin_url( 'admin-post.php' );
$network_message = self::query_key( 'misticgate_network_message' );
?>
| | |
|
blog_id;
$site_ready = wp_is_site_initialized( $network_site_id );
$site_name = $site_ready ? (string) get_blog_option( $network_site_id, 'blogname', '' ) : '';
$home_url = $site_ready ? get_home_url( $network_site_id, '/' ) : $network_site->domain . $network_site->path;
$network_protected = $site_ready && MisticGate_Network::protects_site( $network_site_id, $network_settings );
$site_enabled = $site_ready && MisticGate::protects_site( $network_site_id );
if ( ! $site_ready ) {
$status = __( 'Site not ready', 'misticgate' );
} elseif ( MisticGate::is_emergency_disabled() ) {
$status = __( 'Protection paused', 'misticgate' );
} elseif ( $network_protected ) {
$status = $site_enabled ? __( 'Network + site gate', 'misticgate' ) : __( 'Network gate', 'misticgate' );
} else {
$status = $site_enabled ? __( 'Site gate', 'misticgate' ) : __( 'Not protected', 'misticgate' );
}
?>
|
|
|
1 ) {
echo '
';
echo wp_kses_post(
paginate_links(
array(
'base' => add_query_arg( 'paged', '%#%', self::network_page_url() ),
'format' => '',
'current' => $paged,
'total' => $total_pages,
'prev_text' => __( 'Previous', 'misticgate' ),
'next_text' => __( 'Next', 'misticgate' ),
)
)
);
echo '
';
}
?>
-
' . esc_html( "define( 'MISTICGATE_DISABLE', true );" ) . ''
);
?>
roles as $role => $details ) {
if ( empty( $details['capabilities'][ MisticGate::EXEMPT_CAPABILITY ] ) ) {
$roles[ $role ] = translate_user_role( $details['name'] );
}
}
return $roles;
}
/**
* Outputs a gate URL row with its copy button.
*
* @param string $id Field ID.
* @param string $url Gate URL.
* @param string $label Field label.
*
* @return void
*/
private static function render_url_row( $id, $url, $label ) {
?>
';
}
// Notices.
/**
* Outputs a site settings message.
*
* @param string $message Message key.
*
* @return void
*/
private static function render_message( $message ) {
$messages = array(
'enabled' => array( 'success', __( 'Site gate enabled.', 'misticgate' ) ),
'disabled' => array( 'info', __( 'Site gate disabled. Network protection was not changed.', 'misticgate' ) ),
'pending' => array( 'info', __( 'New URL created. The current URL still works until you switch.', 'misticgate' ) ),
'rotated' => array( 'success', __( 'New URL enabled. The previous URL no longer works.', 'misticgate' ) ),
'cancelled' => array( 'info', __( 'URL change cancelled.', 'misticgate' ) ),
'exemptions_saved' => array( 'success', __( 'Setting for logged-in users saved.', 'misticgate' ) ),
'bookmark_required' => array( 'error', __( 'Confirm that you saved the URL as a bookmark.', 'misticgate' ) ),
'invalid_request' => array( 'error', __( 'Could not complete the action. Reload the page and try again.', 'misticgate' ) ),
'generation_failed' => array( 'error', __( 'Could not create a secure URL. No settings were changed.', 'misticgate' ) ),
'save_failed' => array( 'error', __( 'Could not save the settings. Another change may have been made. Reload the page and try again.', 'misticgate' ) ),
);
if ( ! isset( $messages[ $message ] ) ) {
return;
}
list( $type, $text ) = $messages[ $message ];
echo '' . esc_html( $text ) . '
';
}
/**
* Outputs a network settings message.
*
* @param string $message Message key.
*
* @return void
*/
private static function render_network_message( $message ) {
$messages = array(
'enabled' => array( 'success', __( 'Network gate enabled.', 'misticgate' ) ),
'disabled' => array( 'info', __( 'Network gate disabled. Site gates were not changed.', 'misticgate' ) ),
'pending' => array( 'info', __( 'New network gate URL created. The current URL still works until you switch.', 'misticgate' ) ),
'rotated' => array( 'success', __( 'New network gate URL enabled. The previous URL no longer works.', 'misticgate' ) ),
'cancelled' => array( 'info', __( 'URL change cancelled.', 'misticgate' ) ),
'exemptions_saved' => array( 'success', __( 'Setting for logged-in users saved.', 'misticgate' ) ),
'bookmark_required' => array( 'error', __( 'Confirm that you saved the URL as a bookmark.', 'misticgate' ) ),
'invalid_request' => array( 'error', __( 'Could not complete the action. Reload the page and try again.', 'misticgate' ) ),
'generation_failed' => array( 'error', __( 'Could not create a secure URL. No settings were changed.', 'misticgate' ) ),
'save_failed' => array( 'error', __( 'Could not save the network gate settings. Another change may have been made. Reload the page and try again.', 'misticgate' ) ),
);
if ( ! isset( $messages[ $message ] ) ) {
return;
}
list( $type, $text ) = $messages[ $message ];
echo '' . esc_html( $text ) . '
';
}
// Site actions.
/**
* Enables a site gate.
*
* @return void
*/
public static function handle_enable() {
$request = self::prepare_action( 'misticgate_enable' );
$message = 'invalid_request';
$switched = self::begin_target_site( $request );
try {
if ( ! $request['bookmark_confirmed'] ) {
$message = 'bookmark_required';
} else {
$settings = MisticGate::get_settings();
$expected = $settings;
self::require_current_settings( $expected, $request );
if ( ! MisticGate::is_valid_secret( $settings['secret'] ) ) {
$message = 'generation_failed';
} else {
$settings['enabled'] = true;
$settings['generation'] = MisticGate::next_generation( $settings['generation'] );
$settings['updated_at'] = time();
if ( self::save_settings( $settings, $expected ) ) {
MisticGate::invalidate_tickets();
self::clear_browser_access_if_local( $request );
$message = 'enabled';
} else {
$message = 'save_failed';
}
}
}
} finally {
self::end_target_site( $switched );
}
self::redirect_with_message( $message, $request );
}
/**
* Disables a site gate.
*
* @return void
*/
public static function handle_disable() {
$request = self::prepare_action( 'misticgate_disable' );
$message = 'save_failed';
$switched = self::begin_target_site( $request );
try {
$settings = MisticGate::get_settings();
$expected = $settings;
self::require_current_settings( $expected, $request );
$settings['enabled'] = false;
$settings['pending_secret'] = '';
$settings['generation'] = MisticGate::next_generation( $settings['generation'] );
$settings['updated_at'] = time();
if ( self::save_settings( $settings, $expected ) ) {
MisticGate::invalidate_tickets();
self::clear_browser_access_if_local( $request );
$message = 'disabled';
}
} finally {
self::end_target_site( $switched );
}
self::redirect_with_message( $message, $request );
}
/**
* Prepares a replacement site gate URL.
*
* @return void
*/
public static function handle_prepare_rotation() {
$request = self::prepare_action( 'misticgate_prepare_rotation' );
$message = 'invalid_request';
$switched = self::begin_target_site( $request );
try {
$settings = MisticGate::get_settings();
$expected = $settings;
self::require_current_settings( $expected, $request );
if ( $settings['enabled'] ) {
if ( ! MisticGate::is_valid_secret( $settings['pending_secret'] ) ) {
try {
$settings['pending_secret'] = MisticGate_Token::generate_secret();
} catch ( Exception $exception ) {
$settings['pending_secret'] = '';
$message = 'generation_failed';
}
if ( MisticGate::is_valid_secret( $settings['pending_secret'] ) ) {
$settings['updated_at'] = time();
$message = self::save_settings( $settings, $expected ) ? 'pending' : 'save_failed';
}
} else {
$message = 'pending';
}
}
} finally {
self::end_target_site( $switched );
}
self::redirect_with_message( $message, $request );
}
/**
* Switches a site gate to the prepared URL.
*
* @return void
*/
public static function handle_activate_rotation() {
$request = self::prepare_action( 'misticgate_activate_rotation' );
$message = 'invalid_request';
$switched = self::begin_target_site( $request );
try {
$settings = MisticGate::get_settings();
$expected = $settings;
self::require_current_settings( $expected, $request );
if ( ! $request['bookmark_confirmed'] ) {
$message = 'bookmark_required';
} elseif (
$settings['enabled'] &&
MisticGate::is_valid_secret( $settings['pending_secret'] ) &&
hash_equals( MisticGate::secret_fingerprint( $settings['pending_secret'] ), $request['pending_id'] )
) {
$settings['secret'] = $settings['pending_secret'];
$settings['pending_secret'] = '';
$settings['generation'] = MisticGate::next_generation( $settings['generation'] );
$settings['updated_at'] = time();
if ( self::save_settings( $settings, $expected ) ) {
MisticGate::invalidate_tickets();
self::clear_browser_access_if_local( $request );
$message = 'rotated';
} else {
$message = 'save_failed';
}
}
} finally {
self::end_target_site( $switched );
}
self::redirect_with_message( $message, $request );
}
/**
* Discards a prepared site gate URL.
*
* @return void
*/
public static function handle_cancel_rotation() {
$request = self::prepare_action( 'misticgate_cancel_rotation' );
$message = 'invalid_request';
$switched = self::begin_target_site( $request );
try {
$settings = MisticGate::get_settings();
$expected = $settings;
self::require_current_settings( $expected, $request );
if (
MisticGate::is_valid_secret( $settings['pending_secret'] ) &&
hash_equals( MisticGate::secret_fingerprint( $settings['pending_secret'] ), $request['pending_id'] )
) {
$settings['pending_secret'] = '';
$settings['updated_at'] = time();
$message = self::save_settings( $settings, $expected ) ? 'cancelled' : 'save_failed';
}
} finally {
self::end_target_site( $switched );
}
self::redirect_with_message( $message, $request );
}
/**
* Saves the roles that can open a site login page without the gate.
*
* @return void
*/
public static function handle_exempt_roles() {
$request = self::prepare_action( 'misticgate_exempt_roles' );
$message = 'invalid_request';
$switched = self::begin_target_site( $request );
try {
$settings = MisticGate::get_settings();
$expected = $settings;
self::require_current_settings( $expected, $request );
if ( $settings['enabled'] ) {
$settings['exempt_roles'] = self::allowed_exempt_roles( $request['exempt_roles'] );
if ( $settings['exempt_roles'] === $expected['exempt_roles'] ) {
$message = 'exemptions_saved';
} else {
$settings['updated_at'] = time();
$message = self::save_settings( $settings, $expected ) ? 'exemptions_saved' : 'save_failed';
}
}
} finally {
self::end_target_site( $switched );
}
self::redirect_with_message( $message, $request );
}
// Network actions.
/**
* Enables the network gate.
*
* @return void
*/
public static function handle_network_enable() {
$request = self::prepare_network_action( 'misticgate_network_enable' );
$message = 'invalid_request';
MisticGate_Network::ensure_settings();
if ( ! $request['bookmark_confirmed'] ) {
$message = 'bookmark_required';
} else {
$settings = MisticGate_Network::get_settings();
$expected = $settings;
self::require_current_settings( $expected, $request );
if ( ! MisticGate::is_valid_secret( $settings['secret'] ) ) {
$message = 'generation_failed';
} else {
$settings['enabled'] = true;
$settings['protect_all_sites'] = $request['protect_all_sites'];
$settings['generation'] = MisticGate::next_generation( $settings['generation'] );
$settings['updated_at'] = time();
if ( self::save_network_settings( $settings, $expected ) ) {
MisticGate_Network::invalidate_tickets();
MisticGate_Network::clear_access_cookies();
$message = 'enabled';
} else {
$message = 'save_failed';
}
}
}
self::redirect_network_message( $message );
}
/**
* Disables the network gate.
*
* @return void
*/
public static function handle_network_disable() {
$request = self::prepare_network_action( 'misticgate_network_disable' );
MisticGate_Network::ensure_settings();
$settings = MisticGate_Network::get_settings();
$expected = $settings;
self::require_current_settings( $expected, $request );
$settings['enabled'] = false;
$settings['pending_secret'] = '';
$settings['generation'] = MisticGate::next_generation( $settings['generation'] );
$settings['updated_at'] = time();
$message = 'save_failed';
if ( self::save_network_settings( $settings, $expected ) ) {
MisticGate_Network::invalidate_tickets();
MisticGate_Network::clear_access_cookies();
$message = 'disabled';
}
self::redirect_network_message( $message );
}
/**
* Prepares a replacement network gate URL.
*
* @return void
*/
public static function handle_network_prepare_rotation() {
$request = self::prepare_network_action( 'misticgate_network_prepare_rotation' );
MisticGate_Network::ensure_settings();
$settings = MisticGate_Network::get_settings();
$expected = $settings;
self::require_current_settings( $expected, $request );
$message = 'invalid_request';
if ( $settings['enabled'] ) {
if ( ! MisticGate::is_valid_secret( $settings['pending_secret'] ) ) {
try {
$settings['pending_secret'] = MisticGate_Token::generate_secret();
} catch ( Exception $exception ) {
$settings['pending_secret'] = '';
$message = 'generation_failed';
}
if ( MisticGate::is_valid_secret( $settings['pending_secret'] ) ) {
$settings['updated_at'] = time();
$message = self::save_network_settings( $settings, $expected ) ? 'pending' : 'save_failed';
}
} else {
$message = 'pending';
}
}
self::redirect_network_message( $message );
}
/**
* Switches the network gate to the prepared URL.
*
* @return void
*/
public static function handle_network_activate_rotation() {
$request = self::prepare_network_action( 'misticgate_network_activate_rotation' );
MisticGate_Network::ensure_settings();
$settings = MisticGate_Network::get_settings();
$expected = $settings;
self::require_current_settings( $expected, $request );
$message = 'invalid_request';
if ( ! $request['bookmark_confirmed'] ) {
$message = 'bookmark_required';
} elseif (
$settings['enabled'] &&
MisticGate::is_valid_secret( $settings['pending_secret'] ) &&
hash_equals( MisticGate::secret_fingerprint( $settings['pending_secret'] ), $request['pending_id'] )
) {
$settings['secret'] = $settings['pending_secret'];
$settings['pending_secret'] = '';
$settings['generation'] = MisticGate::next_generation( $settings['generation'] );
$settings['updated_at'] = time();
if ( self::save_network_settings( $settings, $expected ) ) {
MisticGate_Network::invalidate_tickets();
MisticGate_Network::clear_access_cookies();
$message = 'rotated';
} else {
$message = 'save_failed';
}
}
self::redirect_network_message( $message );
}
/**
* Discards a prepared network gate URL.
*
* @return void
*/
public static function handle_network_cancel_rotation() {
$request = self::prepare_network_action( 'misticgate_network_cancel_rotation' );
MisticGate_Network::ensure_settings();
$settings = MisticGate_Network::get_settings();
$expected = $settings;
self::require_current_settings( $expected, $request );
$message = 'invalid_request';
if (
MisticGate::is_valid_secret( $settings['pending_secret'] ) &&
hash_equals( MisticGate::secret_fingerprint( $settings['pending_secret'] ), $request['pending_id'] )
) {
$settings['pending_secret'] = '';
$settings['updated_at'] = time();
$message = self::save_network_settings( $settings, $expected ) ? 'cancelled' : 'save_failed';
}
self::redirect_network_message( $message );
}
/**
* Saves the roles that can open login pages protected by the network gate without the gate.
*
* @return void
*/
public static function handle_network_exempt_roles() {
$request = self::prepare_network_action( 'misticgate_network_exempt_roles' );
MisticGate_Network::ensure_settings();
$settings = MisticGate_Network::get_settings();
$expected = $settings;
self::require_current_settings( $expected, $request );
$message = 'invalid_request';
if ( $settings['enabled'] ) {
$settings['exempt_roles'] = self::allowed_exempt_roles( $request['exempt_roles'] );
if ( $settings['exempt_roles'] === $expected['exempt_roles'] ) {
$message = 'exemptions_saved';
} else {
$settings['updated_at'] = time();
$message = self::save_network_settings( $settings, $expected ) ? 'exemptions_saved' : 'save_failed';
}
}
self::redirect_network_message( $message );
}
// Request validation and persistence.
/**
* Validates a network settings request.
*
* @param string $nonce_action Nonce action.
*
* @return array Request data.
*/
private static function prepare_network_action( $nonce_action ) {
if ( 'POST' !== self::request_method() ) {
wp_die( esc_html__( 'A POST request is required.', 'misticgate' ), '', array( 'response' => 405 ) );
}
if (
! is_multisite() ||
! current_user_can( self::NETWORK_CAPABILITY ) ||
! is_super_admin( get_current_user_id() )
) {
wp_die( esc_html__( 'You are not allowed to manage MisticGate network settings.', 'misticgate' ), '', array( 'response' => 403 ) );
}
check_admin_referer( $nonce_action );
$protect_all_sites = isset( $_POST['protect_all_sites'] ) && '1' === sanitize_text_field( wp_unslash( $_POST['protect_all_sites'] ) );
if ( $protect_all_sites && 'misticgate_network_enable' === $nonce_action && ! MisticGate_Network::is_network_active() ) {
wp_die(
esc_html__( 'Activate MisticGate for the network to protect login pages on all sites.', 'misticgate' ),
'',
array(
'response' => 409,
'back_link' => true,
)
);
}
return array(
'bookmark_confirmed' => isset( $_POST['bookmark_confirmed'] ) && '1' === sanitize_text_field( wp_unslash( $_POST['bookmark_confirmed'] ) ),
'protect_all_sites' => $protect_all_sites,
'settings_id' => isset( $_POST['settings_id'] ) ? sanitize_text_field( wp_unslash( $_POST['settings_id'] ) ) : '',
'pending_id' => isset( $_POST['pending_id'] ) ? sanitize_text_field( wp_unslash( $_POST['pending_id'] ) ) : '',
'exempt_roles' => self::posted_roles(),
);
}
/**
* Saves network settings through the network controller.
*
* @param array $settings New settings.
* @param array $expected Normalized settings the caller started from.
*
* @return bool Whether the settings were saved.
*/
private static function save_network_settings( $settings, $expected ) {
return MisticGate_Network::save_settings( $settings, $expected );
}
/**
* Returns to the network settings page with a message, then exits.
*
* @param string $message Message key.
*
* @return void
*/
private static function redirect_network_message( $message ) {
$url = add_query_arg( 'misticgate_network_message', sanitize_key( $message ), self::network_page_url() );
wp_safe_redirect( $url, 302, 'WordPress' );
exit;
}
/**
* Validates a site settings request.
*
* @param string $nonce_action Nonce action.
*
* @return array Request data.
*/
private static function prepare_action( $nonce_action ) {
if ( 'POST' !== self::request_method() ) {
wp_die( esc_html__( 'A POST request is required.', 'misticgate' ), '', array( 'response' => 405 ) );
}
check_admin_referer( $nonce_action );
$context = isset( $_POST['misticgate_context'] )
? sanitize_key( sanitize_text_field( wp_unslash( $_POST['misticgate_context'] ) ) )
: 'site';
$site_id = isset( $_POST['site_id'] )
? absint( sanitize_text_field( wp_unslash( $_POST['site_id'] ) ) )
: 0;
$bookmark_confirmed = isset( $_POST['bookmark_confirmed'] ) && '1' === sanitize_text_field( wp_unslash( $_POST['bookmark_confirmed'] ) );
$pending_id = isset( $_POST['pending_id'] )
? sanitize_text_field( wp_unslash( $_POST['pending_id'] ) )
: '';
if ( 'network' === $context ) {
$site = is_multisite() && $site_id > 0 ? get_site( $site_id ) : null;
if ( ! is_super_admin( get_current_user_id() ) || ! current_user_can( self::NETWORK_CAPABILITY ) || ! $site || get_current_network_id() !== (int) $site->network_id ) {
wp_die( esc_html__( 'You are not allowed to manage MisticGate network settings.', 'misticgate' ), '', array( 'response' => 403 ) );
}
if ( ! wp_is_site_initialized( $site_id ) ) {
wp_die(
esc_html__( 'This site is not ready. Finish creating or restoring it in WordPress before configuring MisticGate.', 'misticgate' ),
'',
array(
'response' => 409,
'back_link' => true,
)
);
}
} else {
$context = 'site';
$site_id = get_current_blog_id();
if ( ! current_user_can( self::SITE_CAPABILITY ) ) {
wp_die( esc_html__( 'You are not allowed to manage MisticGate.', 'misticgate' ), '', array( 'response' => 403 ) );
}
}
return array(
'context' => $context,
'site_id' => $site_id,
'bookmark_confirmed' => $bookmark_confirmed,
'pending_id' => $pending_id,
'settings_id' => isset( $_POST['settings_id'] ) ? sanitize_text_field( wp_unslash( $_POST['settings_id'] ) ) : '',
'exempt_roles' => self::posted_roles(),
);
}
/**
* Returns the posted role names.
*
* @return string[] Role names.
*/
private static function posted_roles() {
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Called only after check_admin_referer() during request preparation.
if ( ! isset( $_POST['exempt_roles'] ) || ! is_array( $_POST['exempt_roles'] ) ) {
return array();
}
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Called only after check_admin_referer() during request preparation.
return MisticGate::normalize_roles( map_deep( wp_unslash( $_POST['exempt_roles'] ), 'sanitize_text_field' ) );
}
/**
* Keeps only roles that an administrator can allow on the current site.
*
* @param string[] $roles Requested role names.
*
* @return string[] Allowed role names.
*/
private static function allowed_exempt_roles( $roles ) {
return MisticGate::normalize_roles( array_intersect( $roles, array_keys( self::exemptible_roles() ) ) );
}
/**
* Switches to the site that a request targets.
*
* @param array $request Request data.
*
* @return bool Whether the site was switched.
*/
private static function begin_target_site( $request ) {
$switched = 'network' === $request['context'] && get_current_blog_id() !== (int) $request['site_id'];
if ( $switched ) {
switch_to_blog( (int) $request['site_id'] );
}
MisticGate::ensure_settings();
return $switched;
}
/**
* Restores the original site.
*
* @param bool $switched Whether the site was switched.
*
* @return void
*/
private static function end_target_site( $switched ) {
if ( $switched ) {
restore_current_blog();
}
}
/**
* Deletes gate cookies when the request targets the current site.
*
* @param array $request Request data.
*
* @return void
*/
private static function clear_browser_access_if_local( $request ) {
if ( 'site' === $request['context'] ) {
MisticGate::clear_access_cookies();
}
}
/**
* Stops when the settings changed after the screen was loaded.
*
* @param array $settings Normalized settings.
* @param array $request Request data.
*
* @return void
*/
private static function require_current_settings( $settings, $request ) {
if ( ! hash_equals( MisticGate::settings_fingerprint( $settings ), $request['settings_id'] ) ) {
wp_die(
esc_html__( 'The settings changed. Return to MisticGate settings and reload the page.', 'misticgate' ),
'',
array(
'response' => 409,
'back_link' => true,
)
);
}
}
/**
* Saves site settings through the runtime controller.
*
* @param array $settings New settings.
* @param array $expected Normalized settings the caller started from.
*
* @return bool Whether the settings were saved.
*/
private static function save_settings( $settings, $expected ) {
return MisticGate::save_settings( $settings, $expected );
}
/**
* Returns the request method.
*
* @return string Uppercase request method, or an empty string.
*/
private static function request_method() {
if ( ! isset( $_SERVER['REQUEST_METHOD'] ) || ! is_string( $_SERVER['REQUEST_METHOD'] ) ) {
return '';
}
return strtoupper( sanitize_key( wp_unslash( $_SERVER['REQUEST_METHOD'] ) ) );
}
/**
* Returns a query string value as an integer.
*
* @param string $key Query key.
*
* @return int Value, or 0 when absent.
*/
private static function query_integer( $key ) {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only network page selection.
if ( ! isset( $_GET[ $key ] ) ) {
return 0;
}
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only network page selection.
return absint( sanitize_text_field( wp_unslash( $_GET[ $key ] ) ) );
}
/**
* Returns a query string value as a key.
*
* @param string $key Query key.
*
* @return string Value, or an empty string.
*/
private static function query_key( $key ) {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only fixed notice key.
if ( ! isset( $_GET[ $key ] ) ) {
return '';
}
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only fixed notice key.
return sanitize_key( sanitize_text_field( wp_unslash( $_GET[ $key ] ) ) );
}
// URLs.
/**
* Returns the site settings page URL.
*
* @return string Page URL.
*/
private static function site_page_url() {
return admin_url( 'admin.php?page=' . self::PAGE );
}
/**
* Returns the network settings page URL.
*
* @param int $site_id Site ID to select, or 0.
*
* @return string Page URL.
*/
private static function network_page_url( $site_id = 0 ) {
$url = network_admin_url( 'admin.php?page=' . self::PAGE );
return $site_id > 0 ? add_query_arg( 'site_id', (int) $site_id, $url ) : $url;
}
/**
* Returns to the settings page with a message, then exits.
*
* @param string $message Message key.
* @param array $request Request data.
*
* @return void
*/
private static function redirect_with_message( $message, $request ) {
$page_url = 'network' === $request['context']
? self::network_page_url( (int) $request['site_id'] )
: self::site_page_url();
wp_safe_redirect( add_query_arg( 'misticgate_message', sanitize_key( $message ), $page_url ), 302, 'WordPress' );
exit;
}
}