125 lines
6.9 KiB
JavaScript
125 lines
6.9 KiB
JavaScript
'use strict';
|
|
|
|
const assert = require('node:assert/strict');
|
|
const { execFileSync } = require('node:child_process');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
const test = require('node:test');
|
|
|
|
const root = path.resolve(__dirname, '..', '..');
|
|
const read = (file) => fs.readFileSync(path.join(root, file), 'utf8');
|
|
|
|
test('site security schema registers the 网站安全 page without 人机验证 fields', () => {
|
|
const schema = read('src/Admin/Schema/SchemaExtensionsSecurity.php');
|
|
const registry = read('src/Admin/Schema/SchemaExtensions.php');
|
|
const permissions = read('src/Admin/Schema/SchemaPermissions.php');
|
|
const clientMirror = read('assets/admin/src/schema/sections/extensions/security.js');
|
|
const clientIndex = read('assets/admin/src/schema/sections/extensions/index.js');
|
|
const nav = read('assets/admin/src/constants/sections/extensions.js');
|
|
|
|
assert.match(registry, /'security'\s+=> SchemaExtensionsSecurity::build\(\)/);
|
|
assert.match(schema, /'key'\s+=> 'security'/);
|
|
assert.match(schema, /'title'\s+=> '网站安全'/);
|
|
for (const key of [
|
|
'security_brush_post_10min', 'security_brush_post_day',
|
|
'security_brush_comment_10min', 'security_brush_comment_day',
|
|
'security_brush_upload_10min', 'security_brush_upload_day',
|
|
'security_brush_register_10min', 'security_brush_register_day',
|
|
'security_brush_order_10min', 'security_brush_order_day',
|
|
'security_captcha_send_cooldown', 'security_captcha_to_hour', 'security_captcha_to_day',
|
|
'security_captcha_ip_10min', 'security_captcha_ip_day',
|
|
'security_error_frequency_limit', 'security_disabled_pingback', 'security_disabled_xmlrpc',
|
|
'security_user_disable_admin', 'close_attachment_page',
|
|
]) {
|
|
assert.match(schema, new RegExp(`'${key}'`), `missing schema field ${key}`);
|
|
assert.match(clientMirror, new RegExp(`${key}`), `missing client mirror field ${key}`);
|
|
}
|
|
assert.doesNotMatch(schema + clientMirror, /verification_|geetest|tcaptcha|user_verification_type/);
|
|
assert.doesNotMatch(permissions, /close_attachment_page/);
|
|
assert.match(clientIndex, /import \{ securitySchema \} from '\.\/security\.js'/);
|
|
assert.match(clientIndex, /security: securitySchema/);
|
|
assert.match(nav, /key: 'extensions-security'/);
|
|
assert.match(nav, /route: '\/extensions\/security'/);
|
|
assert.match(nav, /section: 'security'/);
|
|
});
|
|
|
|
test('security settings are consumed at runtime and initialized on boot', () => {
|
|
const runtime = read('src/Settings/ExtensionRuntime.php');
|
|
const bootstrap = read('functions.php');
|
|
|
|
assert.match(runtime, /public static function get_security_settings\(\): array/);
|
|
assert.match(bootstrap, /\\AKSRT\\Extensions\\SecurityHardening::get_instance\(\)->init\(\)/);
|
|
});
|
|
|
|
test('hardening switches wire the documented WordPress hooks', () => {
|
|
const hardening = read('src/Extensions/SecurityHardening.php');
|
|
|
|
assert.match(hardening, /add_filter\('xmlrpc_enabled', '__return_false'\)/);
|
|
assert.match(hardening, /add_filter\('xmlrpc_methods', '__return_empty_array'\)/);
|
|
assert.match(hardening, /add_action\('pre_ping', \[\$this, 'disable_self_pingback'\]\)/);
|
|
assert.match(hardening, /add_action\('admin_init', \[\$this, 'restrict_admin_access'\]\)/);
|
|
assert.match(hardening, /wp_doing_ajax\(\) \|\| wp_doing_cron\(\)/);
|
|
assert.match(hardening, /current_user_can\('manage_options'\)/);
|
|
assert.match(hardening, /brush_allow\(string \$scope, string \$subject, int \$cap_10, int \$cap_day\)/);
|
|
assert.match(hardening, /error_frequency_wait\(\): int/);
|
|
assert.match(hardening, /error_frequency_record\(\): void/);
|
|
assert.match(hardening, /error_frequency_clear\(\): void/);
|
|
});
|
|
|
|
test('verification-code sends are funneled through the configurable send limiter', () => {
|
|
const sms = read('src/Sms/SMS.php');
|
|
const sendLimit = read('src/Extensions/SendLimit.php');
|
|
const ajax = read('inc/modules/user-parts/account-ajax.php');
|
|
const controller = read('src/Admin/UserControllerAuthPhoneOperations.php');
|
|
|
|
assert.match(sms, /SendLimit::check\(\$phone\)/);
|
|
assert.match(sms, /SendLimit::record\(\$phone\)/);
|
|
assert.doesNotMatch(sms, /RATE_LIMIT|DAILY_LIMIT|aksrt_sms_rate_|aksrt_sms_daily_/);
|
|
assert.match(sendLimit, /security_captcha_send_cooldown/);
|
|
assert.match(sendLimit, /security_captcha_to_hour/);
|
|
assert.match(sendLimit, /security_captcha_to_day/);
|
|
assert.match(sendLimit, /security_captcha_ip_10min/);
|
|
assert.match(sendLimit, /security_captcha_ip_day/);
|
|
assert.match(ajax, /SendLimit::check\(\$target\)/);
|
|
assert.match(ajax, /SendLimit::record\(\$target\)/);
|
|
assert.match(controller, /SendLimit::check\(\$target\)/);
|
|
assert.match(controller, /SendLimit::record\(\$target\)/);
|
|
});
|
|
|
|
test('content brush limits are wired into post, comment, upload, register, and order flows', () => {
|
|
const frontSubmit = read('src/Core/FrontSubmitSave.php');
|
|
const moderation = read('src/Auth/Moderation.php');
|
|
const upload = read('src/Attachment/UploadLimits.php');
|
|
const registerEmail = read('src/Auth/ServicePart1a.php');
|
|
const registerPhone = read('src/Auth/ServicePart1b.php');
|
|
const order = read('src/Payment/OrderRepositoryCore.php');
|
|
|
|
assert.match(frontSubmit, /brush_allow\('post', \$brush_subject, \(int\) \$security\['security_brush_post_10min'\], \(int\) \$security\['security_brush_post_day'\]\)/);
|
|
assert.match(moderation, /brush_allow\(\s*'comment'/);
|
|
assert.match(upload, /add_filter\('wp_handle_upload_prefilter', \[\$this, 'brush_limit_upload'\], 90\)/);
|
|
assert.match(upload, /brush_allow\('upload', \$subject, \(int\) \$security\['security_brush_upload_10min'\], \(int\) \$security\['security_brush_upload_day'\]\)/);
|
|
assert.match(registerEmail, /brush_allow\('register'/);
|
|
assert.match(registerPhone, /brush_allow\('register'/);
|
|
assert.match(order, /brush_allow\('order', \$brush_subject, \(int\) \$security\['security_brush_order_10min'\], \(int\) \$security\['security_brush_order_day'\]\)/);
|
|
});
|
|
|
|
test('phone masking is shared by the account UI and adapts to international numbers', () => {
|
|
const helper = read('inc/helpers/aksrt-phone.php');
|
|
const bootstrap = read('inc/bootstrap.php');
|
|
const template = read('inc/helpers/user-center-renderers/settings-account.php');
|
|
|
|
assert.match(bootstrap, /inc\/helpers\/aksrt-phone\.php/);
|
|
assert.match(helper, /function aksrt_phone_mask_parts\(string \$phone\): \?array/);
|
|
assert.match(helper, /function aksrt_phone_mask_display\(string \$phone\): string/);
|
|
assert.match(helper, /min\(11, max\(4, \$length - 7\)\)/);
|
|
assert.doesNotMatch(template, /substr_replace\(\$account_phone, '\*\*\*\*', 3, 4\)/);
|
|
assert.match(template, /aksrt_phone_mask_parts\(\$account_phone\)/);
|
|
assert.match(template, /aksrt_phone_mask_display\(\$account_phone\)/);
|
|
});
|
|
|
|
test('site security runtime contract harness passes', () => {
|
|
const harness = path.join(root, 'tests', 'widget-system', 'site-security-harness.php');
|
|
const output = execFileSync('php', [harness], { encoding: 'utf8' });
|
|
assert.match(output, /Site security harness passed/);
|
|
});
|