Files
2meet-data-optimizer/tests/unit/OptionsManagerProtectedTest.php
T
wpdev 9fa84845be fix: NinjaFirewall 相容性 — 保護 WAF 設定選項不被重導向
評估 NinjaFirewall (WP Edition) 4.9 的相容性,結論是兩者可共存且
不需要開發 AddOn(該外掛全 codebase 零個 apply_filters('nfw_*') /
do_action('nfw_*'),官方相容手段全在部署層)。但核心有一個隱患必須補。

TMDO_Options_Manager::register_settings_group() 以 pre_update_option_{key}
回傳 $old_value,讓選項不再落地 wp_options、改存專屬設定表。而
NinjaFirewall 的 Full WAF 走 auto_prepend_file,在 WordPress 載入前就以
原生 mysqli 直查 wp_options 取 nfw_options / nfw_rules。一旦這些鍵被
重導向,WAF 會讀不到設定而靜默停止防護 —— 不報錯、不寫 log。

nfw_rules 約 77KB 且 autoload=auto,正是 autoload 瘦身最誘人的目標,
因此這條路徑並非理論風險。

Added
- PROTECTED_OPTIONS 常數與註冊守衛(nfw_options / nfw_rules / nfw_checked),
  命中時發出 _doing_it_wrong()。守衛置於方法開頭,全部鍵都被擋時提前返回,
  不再建立空的設定表。
- tests/unit/OptionsManagerProtectedTest.php(4 tests / 10 assertions),
  鎖住「受保護鍵絕不會被掛上 pre_option_* / pre_update_option_* 攔截」。
- docs/WAF-COMPATIBILITY.md:模式差異、symlink 多租戶部署、WP SaaS 開站
  流程與驗證清單、三條開發約束、實測風險矩陣。

Changed
- Migration Wizard 輪詢 500ms → 2s,與四個 stress-test 面板一致。
  原本 2 req/s 打同一 REST endpoint,易觸發 WAF rate-limit 與 bot 偵測。

autoload 最佳化不受影響:optimize_autoload() 只改 autoload 欄位、不刪列,
而 Full WAF 的 SELECT * 不看 autoload。

驗證:591 tests / 1166 assertions 通過,PHPCS 零違規,版本一致性 1.0.2。
dev30 於 Full WAF 與 WP WAF 兩種模式下實測,firewall log 中 TMDO 相關
攔截 0 筆。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdjXAU473eekjPBB8vPVRS
2026-08-15 20:38:09 +08:00

101 lines
3.4 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
use PHPUnit\Framework\TestCase;
/**
* Unit tests for TMDO_Options_Manager 的受保護選項守衛。
*
* 背景:register_settings_group() 會以 pre_update_option_{key} 回傳 $old_value
* 讓選項不再落地 wp_options、改存專屬設定表。但 NinjaFirewall Full WAF 走
* auto_prepend_file,在 WordPress 載入前就以原生 mysqli 直查 wp_options 取
* nfw_options / nfw_rules。若這些鍵被重導向,WAF 會讀不到設定而靜默停止防護
* (不報錯、不寫 log),因此必須在註冊階段就擋下。
*
* 註:混合「受保護 + 一般」鍵的情境需要走到 dbDelta(),而單元測試 bootstrap
* 沒有該 stub(ABSPATH 為假路徑),故此處只覆蓋純受保護鍵與常數契約 —— 這已
* 足以鎖住「受保護鍵絕不會被掛上攔截 filter」這個核心保證。
*/
class OptionsManagerProtectedTest extends TestCase {
/** 每個測試前清空靜態註冊表,避免跨測試污染。 */
protected function setUp(): void {
parent::setUp();
$prop = new ReflectionProperty( 'TMDO_Options_Manager', 'redirected_options' );
$prop->setAccessible( true );
$prop->setValue( null, array() );
}
// ── 常數契約 ────────────────────────────────────────────────────────────
public function test_protected_options_covers_ninjafirewall_keys(): void {
$ref = new ReflectionClass( 'TMDO_Options_Manager' );
$protected = $ref->getConstant( 'PROTECTED_OPTIONS' );
$this->assertIsArray( $protected );
foreach ( array( 'nfw_options', 'nfw_rules', 'nfw_checked' ) as $key ) {
$this->assertContains(
$key,
$protected,
"{$key} 必須列入 PROTECTED_OPTIONS,否則重導向後 NinjaFirewall Full WAF 會靜默失效"
);
}
}
// ── 註冊守衛 ────────────────────────────────────────────────────────────
public function test_protected_options_are_not_registered(): void {
TMDO_Options_Manager::register_settings_group(
'waf_guard_test',
array( 'nfw_options', 'nfw_rules', 'nfw_checked' )
);
$this->assertSame(
array(),
TMDO_Options_Manager::get_redirected_options(),
'受保護的選項不得進入重導向清單'
);
}
public function test_protected_options_get_no_interception_filters(): void {
TMDO_Options_Manager::register_settings_group(
'waf_guard_test',
array( 'nfw_options', 'nfw_rules' )
);
$hooks = $GLOBALS['_wp_filter_callbacks'] ?? array();
foreach ( array( 'nfw_options', 'nfw_rules' ) as $key ) {
$this->assertArrayNotHasKey(
"pre_option_{$key}",
$hooks,
"pre_option_{$key} 不得被掛上 —— 會讓 WAF 讀到轉址後的值"
);
$this->assertArrayNotHasKey(
"pre_update_option_{$key}",
$hooks,
"pre_update_option_{$key} 不得被掛上 —— 會讓設定不再寫回 wp_options"
);
}
}
/** 全部鍵都被擋下時應提前返回,不建立設定表。 */
public function test_all_protected_keys_skips_table_creation(): void {
global $wpdb;
$before = $wpdb->last_query ?? null;
TMDO_Options_Manager::register_settings_group(
'waf_guard_test',
array( 'nfw_options' )
);
$this->assertSame(
$before,
$wpdb->last_query ?? null,
'不應對資料庫發出任何查詢'
);
}
}