mirror of
https://gitee.com/ulthon/ulthon_admin.git
synced 2026-10-29 08:58:22 +08:00
feat(auth): AuthService 新增 getAuthPayload 权限载荷构建
This commit is contained in:
@@ -181,6 +181,45 @@ class AuthServiceBase
|
|||||||
return $allowedNodes;
|
return $allowedNodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建当前管理员的权限载荷.
|
||||||
|
*
|
||||||
|
* 形状契约(全局按钮显隐等前端消费):
|
||||||
|
* - 超管:['all' => true, 'nodes' => []]
|
||||||
|
* - 普通管理员:['all' => false, 'nodes' => [控制器 => [动作, ...]]]
|
||||||
|
*
|
||||||
|
* 允许集合完全复用 getAdminAllowedNodes(内部走 checkNodeResolved 判定链),
|
||||||
|
* 本方法不复制判定逻辑;底层 nodeList/adminNode 已带缓存,不再叠加独立缓存。
|
||||||
|
* 无 '/' 或动作段为空的畸形节点(如控制器级节点)跳过。
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getAuthPayload(): array
|
||||||
|
{
|
||||||
|
if ($this->isSuperAdmin()) {
|
||||||
|
return ['all' => true, 'nodes' => []];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 候选仅保留动作节点(含 '/'):控制器级节点无 auth 键,进判定链会触发
|
||||||
|
// Undefined array key,且它们本就因无 '/' 不会进入载荷,提前过滤语义等价。
|
||||||
|
$candidates = array_filter(
|
||||||
|
array_keys($this->getNodeList()),
|
||||||
|
fn ($node) => strpos((string) $node, '/') !== false
|
||||||
|
);
|
||||||
|
|
||||||
|
$allowed = $this->getAdminAllowedNodes($candidates);
|
||||||
|
|
||||||
|
$nodes = [];
|
||||||
|
foreach ($allowed as $node) {
|
||||||
|
$parts = explode('/', $node, 2);
|
||||||
|
if (count($parts) !== 2 || $parts[1] === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$nodes[$parts[0]][] = $parts[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return ['all' => false, 'nodes' => $nodes];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取当前节点.
|
* 获取当前节点.
|
||||||
* @return string
|
* @return string
|
||||||
|
|||||||
132
tests/Feature/AuthPayloadTest.php
Normal file
132
tests/Feature/AuthPayloadTest.php
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace tests\Feature;
|
||||||
|
|
||||||
|
use app\common\constants\AdminConstant;
|
||||||
|
use app\common\service\AuthService;
|
||||||
|
use app\common\test\TestCase;
|
||||||
|
use think\facade\Db;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AuthService::getAuthPayload 权限载荷构建测试.
|
||||||
|
*
|
||||||
|
* 覆盖三组契约:
|
||||||
|
* a) 超管(AdminConstant::SUPER_ADMIN_ID)→ all === true
|
||||||
|
* b) 仅授权 system.quick/index 与 system.quick/read 的普通管理员
|
||||||
|
* → all === false,nodes['system.quick'] 恰为两者,不含 add/edit/delete
|
||||||
|
* c) 载荷格式:nodes 每个键不含 '/',每个值为非空字符串数组
|
||||||
|
*
|
||||||
|
* fixture 沿用 McpServiceTest 既定模式(ulthon-testing 技能约定):
|
||||||
|
* 事务内自造全新管理员/角色/节点授权,避开 SystemAuthNode 60 秒查询缓存与
|
||||||
|
* adminInfo autoCache 窗口;tearDown 自动回滚,无残留。
|
||||||
|
*/
|
||||||
|
class AuthPayloadTest extends TestCase
|
||||||
|
{
|
||||||
|
/** 受测控制器(system.quick 的 index/read 动作节点均经 CurdTrait 注解注册,auth 默认 true). */
|
||||||
|
private const CONTROLLER = 'system.quick';
|
||||||
|
|
||||||
|
/** 授权动作(恰为载荷期望值). */
|
||||||
|
private const ALLOWED_ACTIONS = ['index', 'read'];
|
||||||
|
|
||||||
|
/** 不应出现的动作(同控制器其余 auth 受控动作). */
|
||||||
|
private const FORBIDDEN_ACTIONS = ['add', 'edit', 'delete'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fixture:角色(仅授权 index/read)+ 绑定该角色的启用管理员.
|
||||||
|
*
|
||||||
|
* @return int 管理员ID
|
||||||
|
*/
|
||||||
|
private function createRestrictedAdmin(): int
|
||||||
|
{
|
||||||
|
$roleId = Db::name('system_auth')->insertGetId([
|
||||||
|
'title' => 'apt' . uniqid(),
|
||||||
|
'status' => 2, // system_auth 枚举:1=禁用 2=启用
|
||||||
|
'sort' => 100,
|
||||||
|
'create_time' => time(),
|
||||||
|
'update_time' => time(),
|
||||||
|
'delete_time' => 0,
|
||||||
|
]);
|
||||||
|
foreach (self::ALLOWED_ACTIONS as $action) {
|
||||||
|
Db::name('system_auth_node')->insert([
|
||||||
|
'auth_id' => $roleId,
|
||||||
|
'node' => self::CONTROLLER . '/' . $action,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Db::name('system_admin')->insertGetId([
|
||||||
|
'username' => 'apt_' . uniqid(),
|
||||||
|
'password' => 'should-not-leak',
|
||||||
|
'auth_ids' => (string) $roleId,
|
||||||
|
'status' => 1,
|
||||||
|
'create_time' => time(),
|
||||||
|
'update_time' => time(),
|
||||||
|
'delete_time' => 0,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// a) 超管
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
public function test_super_admin_payload_is_all_true(): void
|
||||||
|
{
|
||||||
|
$service = new AuthService(AdminConstant::SUPER_ADMIN_ID);
|
||||||
|
|
||||||
|
$payload = $service->getAuthPayload();
|
||||||
|
|
||||||
|
$this->assertTrue($payload['all']);
|
||||||
|
$this->assertSame([], $payload['nodes']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// b) 普通管理员:按角色授权精确聚合
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
public function test_restricted_admin_payload_contains_only_seeded_nodes(): void
|
||||||
|
{
|
||||||
|
$adminId = $this->createRestrictedAdmin();
|
||||||
|
$service = new AuthService($adminId);
|
||||||
|
|
||||||
|
$payload = $service->getAuthPayload();
|
||||||
|
|
||||||
|
$this->assertFalse($payload['all']);
|
||||||
|
$this->assertArrayHasKey(self::CONTROLLER, $payload['nodes'], 'payload: ' . json_encode($payload, JSON_UNESCAPED_SLASHES));
|
||||||
|
$this->assertCount(
|
||||||
|
count(self::ALLOWED_ACTIONS),
|
||||||
|
$payload['nodes'][self::CONTROLLER],
|
||||||
|
'payload: ' . json_encode($payload, JSON_UNESCAPED_SLASHES)
|
||||||
|
);
|
||||||
|
$this->assertEqualsCanonicalizing(self::ALLOWED_ACTIONS, $payload['nodes'][self::CONTROLLER]);
|
||||||
|
foreach (self::FORBIDDEN_ACTIONS as $action) {
|
||||||
|
$this->assertNotContains($action, $payload['nodes'][self::CONTROLLER]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// c) 载荷格式契约
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
public function test_payload_nodes_format_contract(): void
|
||||||
|
{
|
||||||
|
$adminId = $this->createRestrictedAdmin();
|
||||||
|
$service = new AuthService($adminId);
|
||||||
|
|
||||||
|
$payload = $service->getAuthPayload();
|
||||||
|
|
||||||
|
$this->assertFalse($payload['all']);
|
||||||
|
$this->assertIsArray($payload['nodes']);
|
||||||
|
$this->assertNotEmpty($payload['nodes'], '受限管理员 nodes 不应为空');
|
||||||
|
foreach ($payload['nodes'] as $controller => $actions) {
|
||||||
|
$this->assertIsString($controller);
|
||||||
|
$this->assertStringNotContainsString('/', $controller, '控制器键不得含 /');
|
||||||
|
$this->assertIsArray($actions);
|
||||||
|
$this->assertNotEmpty($actions);
|
||||||
|
foreach ($actions as $action) {
|
||||||
|
$this->assertIsString($action);
|
||||||
|
$this->assertNotSame('', $action, '动作不得为空字符串');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user