$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:

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 '

MisticGate: ' . esc_html( $message ) . ' ' . esc_html( $link_text ) . '

'; } /** * 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' ); ?>

MisticGate™ 1.0.0

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 '
'; } ?>

MisticGate™ 1.0.0

  1. ' . esc_html( "define( 'MISTICGATE_DISABLE', true );" ) . '' ); ?>

←

MisticGate™ 1.0.0


$role_name ) : ?>

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; } }