rescuepayloadstorage
2 weeks ago
MfaBackupCodes.php
2 weeks ago
MfaBackupCodesInterface.php
2 weeks ago
MfaEndpoint.php
2 weeks ago
MfaEndpointInterface.php
2 weeks ago
MfaManager.php
2 weeks ago
MfaSettings.php
2 weeks ago
MfaSettingsInterface.php
2 weeks ago
MfaValidator.php
2 weeks ago
RescueCode.php
2 weeks ago
MfaSettings.php
133 lines
| 1 | <?php |
| 2 | |
| 3 | namespace LLAR\Core\Mfa; |
| 4 | |
| 5 | use LLAR\Core\Config; |
| 6 | use LLAR\Core\MfaConstants; |
| 7 | |
| 8 | if ( ! defined( 'ABSPATH' ) ) { |
| 9 | exit; |
| 10 | } |
| 11 | |
| 12 | /** |
| 13 | * MFA settings: view data, cleanup, temporarily disabled state. |
| 14 | * Uses MfaValidator for block reason; MfaBackupCodes for rescue-popup logic. |
| 15 | */ |
| 16 | class MfaSettings implements MfaSettingsInterface { |
| 17 | |
| 18 | /** |
| 19 | * Whether MFA is temporarily disabled (rescue flow). |
| 20 | * When transient expires, we do not auto-enable MFA so admin's explicit disable is preserved. |
| 21 | * |
| 22 | * @return bool |
| 23 | */ |
| 24 | public function is_mfa_temporarily_disabled() { |
| 25 | return false !== get_transient( MfaConstants::TRANSIENT_MFA_DISABLED ); |
| 26 | } |
| 27 | |
| 28 | /** |
| 29 | * Reason MFA is temporarily disabled: 'api_unreachable' or 1 (rescue link). Null when not disabled. |
| 30 | * |
| 31 | * @return string|int|null |
| 32 | */ |
| 33 | public function get_mfa_disabled_reason() { |
| 34 | if ( ! $this->is_mfa_temporarily_disabled() ) { |
| 35 | return null; |
| 36 | } |
| 37 | $value = get_transient( MfaConstants::TRANSIENT_MFA_DISABLED ); |
| 38 | return $value; |
| 39 | } |
| 40 | |
| 41 | /** |
| 42 | * Cleanup rescue codes and MFA transients when MFA is disabled. |
| 43 | * |
| 44 | * @return void |
| 45 | */ |
| 46 | public function cleanup_rescue_codes() { |
| 47 | Config::delete( 'mfa_rescue_codes' ); |
| 48 | Config::delete( 'mfa_rescue_download_token' ); |
| 49 | $this->delete_mfa_transients(); |
| 50 | } |
| 51 | |
| 52 | /** |
| 53 | * Delete MFA transients via prepared statements. |
| 54 | */ |
| 55 | private function delete_mfa_transients() { |
| 56 | global $wpdb; |
| 57 | |
| 58 | // Cleanup general MFA-related transients. |
| 59 | $like = $wpdb->esc_like( '_transient_llar_mfa' ) . '%'; |
| 60 | $wpdb->query( $wpdb->prepare( 'DELETE FROM ' . $wpdb->options . ' WHERE option_name LIKE %s', $like ) ); |
| 61 | $like_timeout = $wpdb->esc_like( '_transient_timeout_llar_mfa' ) . '%'; |
| 62 | $wpdb->query( $wpdb->prepare( 'DELETE FROM ' . $wpdb->options . ' WHERE option_name LIKE %s', $like_timeout ) ); |
| 63 | |
| 64 | // Cleanup rescue-link transients (one per hash_id). |
| 65 | $like_rescue = $wpdb->esc_like( '_transient_' . MfaConstants::TRANSIENT_RESCUE_PREFIX ) . '%'; |
| 66 | $wpdb->query( $wpdb->prepare( 'DELETE FROM ' . $wpdb->options . ' WHERE option_name LIKE %s', $like_rescue ) ); |
| 67 | $like_rescue_timeout = $wpdb->esc_like( '_transient_timeout_' . MfaConstants::TRANSIENT_RESCUE_PREFIX ) . '%'; |
| 68 | $wpdb->query( $wpdb->prepare( 'DELETE FROM ' . $wpdb->options . ' WHERE option_name LIKE %s', $like_rescue_timeout ) ); |
| 69 | } |
| 70 | |
| 71 | /** |
| 72 | * Prepare roles for MFA tab. Caches get_editable_roles() per request to avoid repeated calls. |
| 73 | * |
| 74 | * @return array prepared_roles, editable_roles |
| 75 | */ |
| 76 | public function prepare_roles_data() { |
| 77 | static $editable_roles = null; |
| 78 | if ( null === $editable_roles ) { |
| 79 | $editable_roles = get_editable_roles(); |
| 80 | } |
| 81 | $prepared_roles = array(); |
| 82 | foreach ( $editable_roles as $role_key => $role_data ) { |
| 83 | $prepared_roles[ $role_key ] = esc_html( translate_user_role( $role_data['name'] ) ); |
| 84 | } |
| 85 | return array( |
| 86 | 'prepared_roles' => $prepared_roles, |
| 87 | 'editable_roles' => $editable_roles, |
| 88 | ); |
| 89 | } |
| 90 | |
| 91 | /** |
| 92 | * MFA settings for view. Single source; uses MfaValidator for block reason. |
| 93 | * |
| 94 | * @param bool $show_rescue_popup From form submit when popup should open. |
| 95 | * @return array mfa_enabled, mfa_temporarily_disabled, mfa_disabled_reason, mfa_roles, prepared_roles, editable_roles, show_rescue_popup, mfa_block_reason, mfa_block_message |
| 96 | */ |
| 97 | public function get_settings_for_view( $show_rescue_popup = false ) { |
| 98 | $mfa_enabled_raw = Config::get( 'mfa_enabled', false ); |
| 99 | $mfa_temporarily_disabled = $this->is_mfa_temporarily_disabled(); |
| 100 | $mfa_disabled_reason = $this->get_mfa_disabled_reason(); |
| 101 | $mfa_checkbox_state = get_transient( MfaConstants::TRANSIENT_CHECKBOX_STATE ); |
| 102 | |
| 103 | // When temporarily disabled, keep checkbox state aligned with persistent config so that |
| 104 | // after transient expires MFA is effectively on again without user action. |
| 105 | $mfa_enabled = ( $mfa_enabled_raw && ! $mfa_temporarily_disabled ) || ( 1 === $mfa_checkbox_state ) |
| 106 | || ( $mfa_temporarily_disabled && $mfa_enabled_raw ); |
| 107 | |
| 108 | $mfa_roles = Config::get( 'mfa_roles', array() ); |
| 109 | if ( ! is_array( $mfa_roles ) ) { |
| 110 | $mfa_roles = array(); |
| 111 | } |
| 112 | |
| 113 | $roles_data = $this->prepare_roles_data(); |
| 114 | $codes = Config::get( 'mfa_rescue_codes', array() ); |
| 115 | // Only show rescue popup when MFA is enabled or user just enabled it (checkbox state). |
| 116 | $mfa_should_show = $mfa_enabled_raw || ( 1 === $mfa_checkbox_state ); |
| 117 | $show_popup = $mfa_should_show && ( $show_rescue_popup || MfaBackupCodes::should_show_rescue_popup( $codes ) ); |
| 118 | $mfa_block_reason = MfaValidator::get_block_reason(); |
| 119 | |
| 120 | return array( |
| 121 | 'mfa_enabled' => $mfa_enabled, |
| 122 | 'mfa_temporarily_disabled' => $mfa_temporarily_disabled, |
| 123 | 'mfa_disabled_reason' => $mfa_disabled_reason, |
| 124 | 'mfa_roles' => $mfa_roles, |
| 125 | 'prepared_roles' => $roles_data['prepared_roles'], |
| 126 | 'editable_roles' => $roles_data['editable_roles'], |
| 127 | 'show_rescue_popup' => $show_popup, |
| 128 | 'mfa_block_reason' => $mfa_block_reason, |
| 129 | 'mfa_block_message' => $mfa_block_reason ? MfaValidator::get_block_message( $mfa_block_reason ) : '', |
| 130 | ); |
| 131 | } |
| 132 | } |
| 133 |