447 lines
13 KiB
PHP
447 lines
13 KiB
PHP
<?php
|
|
/**
|
|
* Network settings class for Multisite
|
|
*
|
|
* @package TwoFactorExtended
|
|
* @since 0.1.0
|
|
*/
|
|
|
|
// Prevent direct access.
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Class Two_Factor_Extended_Network_Settings
|
|
*
|
|
* Handles network-wide settings for Multisite installations.
|
|
*
|
|
* @since 0.1.0
|
|
*/
|
|
class Two_Factor_Extended_Network_Settings {
|
|
|
|
/**
|
|
* Network settings page slug.
|
|
*
|
|
* @since 0.1.0
|
|
* @var string
|
|
*/
|
|
const PAGE_SLUG = 'two-factor-extended-network';
|
|
|
|
/**
|
|
* Constructor.
|
|
*
|
|
* @since 0.1.0
|
|
*/
|
|
public function __construct() {
|
|
// Only initialize for Multisite.
|
|
if ( ! is_multisite() ) {
|
|
return;
|
|
}
|
|
|
|
$this->init_hooks();
|
|
}
|
|
|
|
/**
|
|
* Initialize WordPress hooks.
|
|
*
|
|
* @since 0.1.0
|
|
*/
|
|
private function init_hooks(): void {
|
|
add_action( 'network_admin_menu', array( $this, 'register_network_menu' ) );
|
|
add_action( 'network_admin_edit_two_factor_extended_network', array( $this, 'save_network_settings' ) );
|
|
}
|
|
|
|
/**
|
|
* Register network admin menu.
|
|
*
|
|
* @since 0.1.0
|
|
*/
|
|
public function register_network_menu(): void {
|
|
if ( ! current_user_can( 'manage_network_options' ) ) {
|
|
return;
|
|
}
|
|
|
|
add_submenu_page(
|
|
'settings.php',
|
|
__( 'Two Factor Extended', 'two-factor-extended' ),
|
|
__( 'Two Factor Extended', 'two-factor-extended' ),
|
|
'manage_network_options',
|
|
self::PAGE_SLUG,
|
|
array( $this, 'render_network_page' )
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Render network settings page.
|
|
*
|
|
* @since 0.1.0
|
|
*/
|
|
public function render_network_page(): void {
|
|
// Check capability.
|
|
if ( ! current_user_can( 'manage_network_options' ) ) {
|
|
wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'two-factor-extended' ) );
|
|
}
|
|
|
|
$settings = $this->get_network_settings();
|
|
|
|
?>
|
|
<div class="wrap">
|
|
<h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
|
|
|
|
<?php
|
|
// Show success message.
|
|
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only check
|
|
if ( isset( $_GET['updated'] ) && 'true' === $_GET['updated'] ) :
|
|
?>
|
|
<div class="notice notice-success is-dismissible">
|
|
<p><?php esc_html_e( 'Network settings saved successfully.', 'two-factor-extended' ); ?></p>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<form method="post" action="edit.php?action=two_factor_extended_network">
|
|
<?php wp_nonce_field( 'two_factor_extended_network_settings', 'two_factor_extended_network_nonce' ); ?>
|
|
|
|
<h2><?php esc_html_e( 'Network-Wide Settings', 'two-factor-extended' ); ?></h2>
|
|
|
|
<table class="form-table">
|
|
<tr>
|
|
<th scope="row">
|
|
<label for="network_enforce">
|
|
<?php esc_html_e( 'Network-Wide Enforcement', 'two-factor-extended' ); ?>
|
|
</label>
|
|
</th>
|
|
<td>
|
|
<fieldset>
|
|
<label>
|
|
<input
|
|
type="checkbox"
|
|
id="network_enforce"
|
|
name="network_settings[enforce_network_wide]"
|
|
value="1"
|
|
<?php checked( $settings['enforce_network_wide'] ?? false ); ?>
|
|
/>
|
|
<?php esc_html_e( 'Apply these settings to all sites in the network', 'two-factor-extended' ); ?>
|
|
</label>
|
|
<p class="description">
|
|
<?php esc_html_e( 'When enabled, network settings override individual site settings.', 'two-factor-extended' ); ?>
|
|
</p>
|
|
</fieldset>
|
|
</td>
|
|
</tr>
|
|
|
|
<tr>
|
|
<th scope="row">
|
|
<label for="allow_site_override">
|
|
<?php esc_html_e( 'Allow Site Override', 'two-factor-extended' ); ?>
|
|
</label>
|
|
</th>
|
|
<td>
|
|
<fieldset>
|
|
<label>
|
|
<input
|
|
type="checkbox"
|
|
id="allow_site_override"
|
|
name="network_settings[allow_site_override]"
|
|
value="1"
|
|
<?php checked( $settings['allow_site_override'] ?? false ); ?>
|
|
/>
|
|
<?php esc_html_e( 'Allow individual sites to override network settings', 'two-factor-extended' ); ?>
|
|
</label>
|
|
<p class="description">
|
|
<?php esc_html_e( 'Site administrators can configure their own 2FA requirements.', 'two-factor-extended' ); ?>
|
|
</p>
|
|
</fieldset>
|
|
</td>
|
|
</tr>
|
|
|
|
<tr>
|
|
<th scope="row">
|
|
<?php esc_html_e( 'Super Admin Requirements', 'two-factor-extended' ); ?>
|
|
</th>
|
|
<td>
|
|
<?php $this->render_super_admin_requirements( $settings ); ?>
|
|
</td>
|
|
</tr>
|
|
|
|
<tr>
|
|
<th scope="row">
|
|
<?php esc_html_e( 'Network Grace Period', 'two-factor-extended' ); ?>
|
|
</th>
|
|
<td>
|
|
<input
|
|
type="number"
|
|
id="network_grace_period"
|
|
name="network_settings[grace_period_days]"
|
|
value="<?php echo esc_attr( (string) ( isset( $settings['grace_period_days'] ) && is_numeric( $settings['grace_period_days'] ) ? (int) $settings['grace_period_days'] : 7 ) ); ?>"
|
|
min="0"
|
|
max="365"
|
|
class="small-text"
|
|
/>
|
|
<?php esc_html_e( 'days', 'two-factor-extended' ); ?>
|
|
<p class="description">
|
|
<?php esc_html_e( 'Grace period for all sites in the network. Set to 0 for immediate enforcement.', 'two-factor-extended' ); ?>
|
|
</p>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
|
|
<h2><?php esc_html_e( 'Network Role Requirements', 'two-factor-extended' ); ?></h2>
|
|
<p class="description">
|
|
<?php esc_html_e( 'Configure 2FA requirements that apply across all sites in the network.', 'two-factor-extended' ); ?>
|
|
</p>
|
|
|
|
<?php $this->render_network_role_requirements( $settings ); ?>
|
|
|
|
<?php submit_button( __( 'Save Network Settings', 'two-factor-extended' ) ); ?>
|
|
</form>
|
|
</div>
|
|
<?php
|
|
}
|
|
|
|
/**
|
|
* Render super admin requirements field.
|
|
*
|
|
* @since 0.1.0
|
|
*
|
|
* @param array<string, mixed> $settings Current network settings.
|
|
*/
|
|
private function render_super_admin_requirements( array $settings ): void {
|
|
$providers = Two_Factor_Extended_Provider_Detector::get_provider_names();
|
|
$super_admin_reqs = isset( $settings['super_admin_requirements'] ) && is_array( $settings['super_admin_requirements'] ) ? $settings['super_admin_requirements'] : array();
|
|
|
|
if ( empty( $providers ) ) {
|
|
?>
|
|
<p class="description">
|
|
<?php esc_html_e( 'No 2FA providers detected.', 'two-factor-extended' ); ?>
|
|
</p>
|
|
<?php
|
|
return;
|
|
}
|
|
|
|
?>
|
|
<fieldset>
|
|
<?php foreach ( $providers as $class => $name ) : ?>
|
|
<label style="display: block; margin-bottom: 5px;">
|
|
<input
|
|
type="checkbox"
|
|
name="network_settings[super_admin_requirements][]"
|
|
value="<?php echo esc_attr( $class ); ?>"
|
|
<?php checked( in_array( $class, $super_admin_reqs, true ) ); ?>
|
|
/>
|
|
<?php echo esc_html( $name ); ?>
|
|
</label>
|
|
<?php endforeach; ?>
|
|
<p class="description">
|
|
<?php esc_html_e( 'Required 2FA methods for super administrators.', 'two-factor-extended' ); ?>
|
|
</p>
|
|
</fieldset>
|
|
<?php
|
|
}
|
|
|
|
/**
|
|
* Render network role requirements field.
|
|
*
|
|
* @since 0.1.0
|
|
*
|
|
* @param array<string, mixed> $settings Current network settings.
|
|
*/
|
|
private function render_network_role_requirements( array $settings ): void {
|
|
$roles = Two_Factor_Extended_Role_Manager::get_all_roles();
|
|
$providers = Two_Factor_Extended_Provider_Detector::get_provider_names();
|
|
$network_reqs = isset( $settings['role_requirements'] ) && is_array( $settings['role_requirements'] ) ? $settings['role_requirements'] : array();
|
|
|
|
if ( empty( $providers ) ) {
|
|
?>
|
|
<p class="description">
|
|
<?php esc_html_e( 'No 2FA providers detected.', 'two-factor-extended' ); ?>
|
|
</p>
|
|
<?php
|
|
return;
|
|
}
|
|
|
|
?>
|
|
<table class="widefat fixed striped">
|
|
<thead>
|
|
<tr>
|
|
<th class="column-title"><?php esc_html_e( 'Role', 'two-factor-extended' ); ?></th>
|
|
<th><?php esc_html_e( 'Required 2FA Methods', 'two-factor-extended' ); ?></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ( $roles as $role_slug => $role_name ) : ?>
|
|
<tr>
|
|
<td><strong><?php echo esc_html( $role_name ); ?></strong></td>
|
|
<td>
|
|
<?php
|
|
$role_reqs = isset( $network_reqs[ $role_slug ] ) && is_array( $network_reqs[ $role_slug ] ) ? $network_reqs[ $role_slug ] : array();
|
|
|
|
foreach ( $providers as $class => $name ) :
|
|
?>
|
|
<label style="display: block; margin-bottom: 5px;">
|
|
<input
|
|
type="checkbox"
|
|
name="network_settings[role_requirements][<?php echo esc_attr( $role_slug ); ?>][]"
|
|
value="<?php echo esc_attr( $class ); ?>"
|
|
<?php checked( in_array( $class, $role_reqs, true ) ); ?>
|
|
/>
|
|
<?php echo esc_html( $name ); ?>
|
|
</label>
|
|
<?php endforeach; ?>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
<?php
|
|
}
|
|
|
|
/**
|
|
* Save network settings.
|
|
*
|
|
* @since 0.1.0
|
|
*/
|
|
public function save_network_settings(): void {
|
|
// Check capability.
|
|
if ( ! current_user_can( 'manage_network_options' ) ) {
|
|
wp_die( esc_html__( 'You do not have sufficient permissions to perform this action.', 'two-factor-extended' ) );
|
|
}
|
|
|
|
// Verify nonce.
|
|
$nonce = isset( $_POST['two_factor_extended_network_nonce'] ) && is_string( $_POST['two_factor_extended_network_nonce'] )
|
|
? sanitize_text_field( wp_unslash( $_POST['two_factor_extended_network_nonce'] ) )
|
|
: '';
|
|
|
|
if ( ! wp_verify_nonce( $nonce, 'two_factor_extended_network_settings' ) ) {
|
|
wp_die( esc_html__( 'Security check failed.', 'two-factor-extended' ) );
|
|
}
|
|
|
|
// Get and sanitize input.
|
|
// phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Data sanitized by sanitize_network_settings() method before use
|
|
$input = isset( $_POST['network_settings'] ) && is_array( $_POST['network_settings'] )
|
|
? wp_unslash( $_POST['network_settings'] )
|
|
: array();
|
|
// phpcs:enable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
|
|
|
$sanitized = $this->sanitize_network_settings( $input );
|
|
|
|
// Save settings.
|
|
update_site_option( TWO_FACTOR_EXTENDED_NETWORK_OPTION_SETTINGS, $sanitized );
|
|
|
|
// Redirect with success message.
|
|
wp_safe_redirect(
|
|
add_query_arg(
|
|
'updated',
|
|
'true',
|
|
network_admin_url( 'settings.php?page=' . self::PAGE_SLUG )
|
|
)
|
|
);
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Get network settings.
|
|
*
|
|
* @since 0.1.0
|
|
*
|
|
* @return array<string, mixed> Network settings.
|
|
*/
|
|
public function get_network_settings(): array {
|
|
$defaults = array(
|
|
'enforce_network_wide' => false,
|
|
'allow_site_override' => true,
|
|
'grace_period_days' => 7,
|
|
'super_admin_requirements' => array(),
|
|
'role_requirements' => array(),
|
|
);
|
|
|
|
$settings = get_site_option( TWO_FACTOR_EXTENDED_NETWORK_OPTION_SETTINGS, array() );
|
|
|
|
if ( ! is_array( $settings ) ) {
|
|
$settings = array();
|
|
}
|
|
|
|
return wp_parse_args( $settings, $defaults );
|
|
}
|
|
|
|
/**
|
|
* Sanitize network settings.
|
|
*
|
|
* @since 0.1.0
|
|
*
|
|
* @param array<string, mixed> $input Raw input.
|
|
*
|
|
* @return array<string, mixed> Sanitized settings.
|
|
*/
|
|
private function sanitize_network_settings( array $input ): array {
|
|
$sanitized = array();
|
|
|
|
// Enforce network wide.
|
|
$sanitized['enforce_network_wide'] = ! empty( $input['enforce_network_wide'] );
|
|
|
|
// Allow site override.
|
|
$sanitized['allow_site_override'] = ! empty( $input['allow_site_override'] );
|
|
|
|
// Grace period days.
|
|
if ( isset( $input['grace_period_days'] ) && is_numeric( $input['grace_period_days'] ) ) {
|
|
$days = (int) $input['grace_period_days'];
|
|
$sanitized['grace_period_days'] = max( 0, min( 365, $days ) );
|
|
}
|
|
|
|
// Super admin requirements.
|
|
if ( isset( $input['super_admin_requirements'] ) && is_array( $input['super_admin_requirements'] ) ) {
|
|
$sanitized['super_admin_requirements'] = array_map(
|
|
function ( mixed $item ): string {
|
|
return sanitize_text_field( is_string( $item ) ? $item : '' );
|
|
},
|
|
$input['super_admin_requirements']
|
|
);
|
|
}
|
|
|
|
// Role requirements.
|
|
if ( isset( $input['role_requirements'] ) && is_array( $input['role_requirements'] ) ) {
|
|
$sanitized['role_requirements'] = array();
|
|
|
|
foreach ( $input['role_requirements'] as $role => $providers ) {
|
|
$role_slug = sanitize_key( is_string( $role ) ? $role : '' );
|
|
|
|
if ( Two_Factor_Extended_Role_Manager::role_exists( $role_slug ) && is_array( $providers ) ) {
|
|
$sanitized['role_requirements'][ $role_slug ] = array_map(
|
|
function ( mixed $item ): string {
|
|
return sanitize_text_field( is_string( $item ) ? $item : '' );
|
|
},
|
|
$providers
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
return $sanitized;
|
|
}
|
|
|
|
/**
|
|
* Check if network enforcement is enabled.
|
|
*
|
|
* @since 0.1.0
|
|
*
|
|
* @return bool True if network enforcement is enabled.
|
|
*/
|
|
public function is_network_enforcement_enabled(): bool {
|
|
$settings = $this->get_network_settings();
|
|
|
|
return ! empty( $settings['enforce_network_wide'] );
|
|
}
|
|
|
|
/**
|
|
* Check if site override is allowed.
|
|
*
|
|
* @since 0.1.0
|
|
*
|
|
* @return bool True if site override is allowed.
|
|
*/
|
|
public function is_site_override_allowed(): bool {
|
|
$settings = $this->get_network_settings();
|
|
|
|
return ! empty( $settings['allow_site_override'] );
|
|
}
|
|
}
|