95 lines
4.5 KiB
PHP
95 lines
4.5 KiB
PHP
<?php
|
||
// +----------------------------------------------------------------------
|
||
// | YwxApp [ WE CAN DO IT JUST THINK ]
|
||
// +----------------------------------------------------------------------
|
||
// | Copyright (c) 2026-2036 http://ywxapp.cn All rights reserved.
|
||
// +----------------------------------------------------------------------
|
||
// | Author: ywxapp<admin@ywxapp.cn>
|
||
// +----------------------------------------------------------------------
|
||
namespace addon\mqttbroker\command;
|
||
|
||
use think\console\Command;
|
||
use think\console\Input;
|
||
use think\console\Output;
|
||
use think\facade\Db;
|
||
use ywxapp\service\AddonService;
|
||
use addon\mqttbroker\service\Auth;
|
||
|
||
/**
|
||
* ACL 实网决策验证:用真实 DB 规则校验 Auth::checkAcl 的放行/拒绝逻辑。
|
||
* php think mqttbroker:acltest
|
||
* 验证项:deny 发布拒绝、默认放行、用户定向拒绝、超管绕过、$SYS 只读。
|
||
* 不依赖运行中的 Broker,直接复用 Broker 实际调用的 Auth 逻辑。
|
||
*/
|
||
class TestAcl extends Command
|
||
{
|
||
|
||
protected function configure()
|
||
{
|
||
$this->setName('mqttbroker:acltest')
|
||
->setDescription('验证 ACL 发布/订阅权限决策(含 deny 拒绝)');
|
||
}
|
||
|
||
|
||
protected function execute(Input $input, Output $output)
|
||
{
|
||
// 配置:开启 ACL,默认放行
|
||
$saved = [];
|
||
try { $saved = AddonService::config('mqttbroker') ?: []; } catch (\Throwable $e) {}
|
||
$config = array_merge($saved, ['acl_enabled' => '1', 'acl_default' => 'allow']);
|
||
|
||
$auth = new Auth($config);
|
||
$auth->ensureTables();
|
||
|
||
// 插入临时规则(测试后清理)
|
||
$rows = [
|
||
['target_type' => 'member', 'target' => 'alice', 'topic' => 'alice/secret', 'access' => 2, 'allow' => 0, 'sort' => 5, 'remark' => 'tmp'],
|
||
['target_type' => 'all', 'target' => '', 'topic' => 'secret/#', 'access' => 2, 'allow' => 0, 'sort' => 10, 'remark' => 'tmp'],
|
||
['target_type' => 'all', 'target' => '', 'topic' => 'public/#', 'access' => 3, 'allow' => 1, 'sort' => 20, 'remark' => 'tmp'],
|
||
];
|
||
$ids = [];
|
||
foreach ($rows as $r) {
|
||
$ids[] = Db::name('mqttbroker_acl')->insertGetId(array_merge($r, ['create_at' => time()]));
|
||
}
|
||
|
||
$results = [];
|
||
$chk = function (string $case, bool $got, bool $expect) use (&$results) {
|
||
$results[$case] = ['got' => $got, 'expect' => $expect, 'ok' => $got === $expect];
|
||
};
|
||
|
||
// 1) deny 发布 secret/# 被拒
|
||
$chk('deny publish secret/#', $auth->checkAcl(null, 'c1', 'secret/data', Auth::ACT_PUB), false);
|
||
// 2) 同一主题仅 deny 发布,订阅走默认放行
|
||
$chk('subscribe secret/# default-allow', $auth->checkAcl(null, 'c1', 'secret/data', Auth::ACT_SUB), true);
|
||
// 3) allow 规则 public/# 发布放行
|
||
$chk('allow publish public/info', $auth->checkAcl(null, 'c1', 'public/info', Auth::ACT_PUB), true);
|
||
// 4) 未命中规则走默认放行
|
||
$chk('default allow other/topic', $auth->checkAcl(null, 'c1', 'other/topic', Auth::ACT_PUB), true);
|
||
// 5) 用户定向 deny:alice 发布 alice/secret 被拒
|
||
$chk('user alice deny publish', $auth->checkAcl('alice', 'cA', 'alice/secret', Auth::ACT_PUB), false);
|
||
// 6) alice 订阅 alice/secret(user 规则仅限发布)走默认放行
|
||
$chk('user alice subscribe (rule is pub-only)', $auth->checkAcl('alice', 'cA', 'alice/secret', Auth::ACT_SUB), true);
|
||
// 7) 超管绕过 deny
|
||
$chk('superuser bypass deny', $auth->checkAcl('root', 'cR', 'secret/data', Auth::ACT_PUB, true), true);
|
||
// 8) $SYS 只读:订阅放行、发布拒绝
|
||
$chk('$SYS subscribe allowed', $auth->checkAcl(null, 'c2', '$SYS/broker/uptime', Auth::ACT_SUB), true);
|
||
$chk('$SYS publish denied', $auth->checkAcl(null, 'c2', '$SYS/broker/uptime', Auth::ACT_PUB), false);
|
||
|
||
// 清理临时规则
|
||
if ($ids) {
|
||
Db::name('mqttbroker_acl')->whereIn('id', $ids)->delete();
|
||
}
|
||
|
||
$pass = 0;
|
||
$fail = 0;
|
||
foreach ($results as $case => $r) {
|
||
$flag = $r['ok'] ? 'PASS' : 'FAIL';
|
||
if ($r['ok']) { $pass++; } else { $fail++; }
|
||
$output->writeln("[{$flag}] {$case} (got=" . var_export($r['got'], true) . " expect=" . var_export($r['expect'], true) . ")");
|
||
}
|
||
$output->writeln("");
|
||
$output->writeln("ACL 验证结果:PASS={$pass} FAIL={$fail}");
|
||
return $fail === 0 ? 0 : 1;
|
||
}
|
||
}
|