更新
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\common\cache {
|
||||
// 菜单权限数据在内存中,测试不连接缓存和业务数据库。
|
||||
class AdminAuthCache
|
||||
{
|
||||
public static array $allUri = ['tcm.diagnosis/lists', 'tcm.prescriptionorder/lists'];
|
||||
public static array $adminUri = ['tcm.diagnosis/lists'];
|
||||
public function __construct(int $adminId)
|
||||
{
|
||||
if ($adminId !== 9) { throw new \RuntimeException('wrong permission identity'); }
|
||||
}
|
||||
public function getAllUri(): array { return self::$allUri; }
|
||||
public function getAdminUri(): array { return self::$adminUri; }
|
||||
}
|
||||
}
|
||||
namespace {
|
||||
require dirname(__DIR__) . '/vendor/autoload.php';
|
||||
require dirname(__DIR__) . '/vendor/topthink/framework/src/helper.php';
|
||||
use app\adminapi\http\middleware\AuthMiddleware;
|
||||
|
||||
$app = new think\App();
|
||||
function rpaIpCheck(bool $ok, string $message): void
|
||||
{
|
||||
if (!$ok) { throw new RuntimeException($message); }
|
||||
}
|
||||
// 桌面在员工电脑登录(198.51.100.10),客服后台服务器(203.0.113.20)拿同一令牌代查患者。
|
||||
$desktop = ['admin_id' => 9, 'terminal' => 7, 'root' => 0, 'login_ip' => '198.51.100.10'];
|
||||
$invoke = function (array $identity, string $ip, string $controller = 'tcm.diagnosis', string $action = 'lists') use ($app) {
|
||||
$request = (new think\Request())->withServer(['REMOTE_ADDR' => $ip]);
|
||||
$request->setController($controller);
|
||||
$request->setAction($action);
|
||||
$request->adminInfo = $identity;
|
||||
$request->controllerObject = new class {
|
||||
public function isNotNeedLogin(): bool { return false; }
|
||||
};
|
||||
$app->instance('request', $request);
|
||||
$result = (new AuthMiddleware())->handle($request, static fn() => ['code' => 1, 'msg' => 'passed']);
|
||||
return is_array($result) ? $result : $result->getData();
|
||||
};
|
||||
$ipChanged = static fn(array $r): bool => $r['code'] === -1 && str_contains($r['msg'], 'ip地址发生变化');
|
||||
$setTrusted = static fn(string $value) => $app->env->set('wecom_rpa.server_ips', $value);
|
||||
|
||||
rpaIpCheck(env('wecom_rpa.server_ips') === null, 'test process must not inherit WECOM_RPA_SERVER_IPS');
|
||||
rpaIpCheck($ipChanged($invoke($desktop, '203.0.113.20')), 'missing config keeps the login IP check');
|
||||
$setTrusted('');
|
||||
rpaIpCheck($ipChanged($invoke($desktop, '203.0.113.20')), 'empty config keeps the login IP check');
|
||||
|
||||
// [WECOM_RPA] SERVER_IPS 在 .env 里是逗号/空白分隔的列表
|
||||
$setTrusted(' 192.0.2.1,203.0.113.20 2001:db8::20 ');
|
||||
rpaIpCheck(env('wecom_rpa.server_ips') === ' 192.0.2.1,203.0.113.20 2001:db8::20 ', 'section key maps to env');
|
||||
rpaIpCheck($invoke($desktop, '203.0.113.20')['code'] === 1, 'desktop token from the kefu server passes');
|
||||
rpaIpCheck($invoke(array_replace($desktop, ['terminal' => 9]), '203.0.113.20')['code'] === 1,
|
||||
'mobile console token from the kefu server passes');
|
||||
rpaIpCheck($invoke(array_replace($desktop, ['terminal' => '7']), '2001:db8::20')['code'] === 1,
|
||||
'IPv6 server address and string terminal from cache pass');
|
||||
rpaIpCheck($invoke($desktop, '198.51.100.10')['code'] === 1, 'desktop token from the login IP still passes');
|
||||
|
||||
rpaIpCheck($ipChanged($invoke($desktop, '198.51.100.99')), 'desktop token from an untrusted IP is rejected');
|
||||
rpaIpCheck($ipChanged($invoke($desktop, '203.0.113.2')), 'trusted IP must match exactly, not by prefix');
|
||||
rpaIpCheck($ipChanged($invoke($desktop, '2001:db8::2')), 'trusted IPv6 must match exactly');
|
||||
rpaIpCheck($ipChanged($invoke(array_replace($desktop, ['terminal' => 1]), '203.0.113.20')),
|
||||
'web token from the kefu server keeps IP protection');
|
||||
rpaIpCheck($ipChanged($invoke(array_replace($desktop, ['terminal' => 2]), '203.0.113.20')),
|
||||
'admin mobile web token keeps IP protection');
|
||||
rpaIpCheck($ipChanged($invoke(array_replace($desktop, ['terminal' => 8]), '203.0.113.20')),
|
||||
'AI browser console token keeps IP protection');
|
||||
rpaIpCheck($ipChanged($invoke(array_diff_key($desktop, ['terminal' => 0]), '203.0.113.20')),
|
||||
'token without terminal keeps IP protection');
|
||||
|
||||
// 跳过的只有 IP 校验:客服后台仍只能访问该账号角色允许的接口
|
||||
$denied = $invoke($desktop, '203.0.113.20', 'tcm.prescriptionOrder', 'lists');
|
||||
rpaIpCheck($denied['code'] === 0 && str_contains($denied['msg'], '权限不足'), 'menu permissions still apply');
|
||||
rpaIpCheck($invoke(array_replace($desktop, ['root' => 1]), '203.0.113.20', 'tcm.prescriptionOrder', 'lists')['code'] === 1,
|
||||
'root bypass is unchanged');
|
||||
|
||||
$setTrusted('true');
|
||||
rpaIpCheck(env('wecom_rpa.server_ips') === true, 'think Env converts true to bool');
|
||||
rpaIpCheck($ipChanged($invoke($desktop, '203.0.113.20')), 'non-list config keeps the login IP check');
|
||||
|
||||
rpaIpCheck(!AuthMiddleware::isTrustedRpaServerRequest($desktop, '', ','), 'empty request IP never trusted');
|
||||
rpaIpCheck(!AuthMiddleware::isTrustedRpaServerRequest($desktop, '203.0.113.20', " ,\n\t"), 'separators only');
|
||||
rpaIpCheck(AuthMiddleware::isTrustedRpaServerRequest($desktop, '203.0.113.20', "192.0.2.1\n203.0.113.20"),
|
||||
'newline separated list');
|
||||
|
||||
// 示例配置能按 .env 规则解析且默认留空;生产 IP 只写在运维配置里,不硬编码进代码
|
||||
$exampleEnv = new think\Env();
|
||||
$exampleEnv->load(dirname(__DIR__) . '/.env.wecom-rpa.example');
|
||||
rpaIpCheck($exampleEnv->get('wecom_rpa.server_ips') === '', 'env example parses and ships an empty list');
|
||||
$middlewareSource = file_get_contents(dirname(__DIR__) . '/app/adminapi/http/middleware/AuthMiddleware.php');
|
||||
rpaIpCheck(is_string($middlewareSource) && !str_contains($middlewareSource, '123.14.'),
|
||||
'kefu server IP is not hard-coded in the middleware');
|
||||
echo "AdminWecomRpaServerIpTest passed\n";
|
||||
}
|
||||
Reference in New Issue
Block a user