guard('jwt')->login($user); } protected function authHeaders(User $user): array { return ['Authorization' => 'Bearer ' . $this->getAuthToken($user)]; } protected function createTestUser(string $role_name, array $overrides = []): User { $role = Role::query()->where('name', $role_name)->firstOrFail(); $suffix = bin2hex(random_bytes(4)); return User::query()->create(array_merge([ 'username' => 'perm_test_' . $suffix, 'email' => 'perm_test_' . $suffix . '@example.com', 'password' => 'Pass_' . $suffix, 'status' => 1, 'role_id' => $role->id, ], $overrides)); } // ========== Step 1: 路由访问检查 ========== public function test_administrator_bypasses_route_check(): void { $user = $this->createTestUser('administrator'); $response = $this->get('/api/v1/users', [], $this->authHeaders($user)); $response->assertStatus(200); $response->assertJsonPath('code', 0); } public function test_non_admin_without_route_group_returns_403(): void { $user = $this->createTestUser('accessor'); // accessor 没有路由组授权,应返回 403 $response = $this->get('/api/v1/users', [], $this->authHeaders($user)); $response->assertStatus(403); } public function test_route_group_authorization_allows_access(): void { $user = $this->createTestUser('developer'); $route = Route::query() ->where('method', 'GET') ->where('path', '/api/v1/users') ->first(); if (!$route) { $this->markTestSkipped('routes 表中无 GET /api/v1/users 路由'); } // 创建路由组并授权 $group = RouteGroup::query()->create([ 'name' => 'test_user_mgmt_' . uniqid(), 'label' => '用户管理测试', ]); $old_group_id = $route->group_id; $route->group_id = $group->id; $route->save(); Db::table('role_route_groups')->insert([ 'role_id' => $user->role_id, 'group_id' => $group->id, ]); $response = $this->get('/api/v1/users', [], $this->authHeaders($user)); $response->assertStatus(200); // 清理 Db::table('role_route_groups') ->where('role_id', $user->role_id) ->where('group_id', $group->id) ->delete(); $route->group_id = $old_group_id; $route->save(); $group->delete(); } public function test_override_allows_access_bypassing_group(): void { $user = $this->createTestUser('accessor'); $route = Route::query() ->where('method', 'GET') ->where('path', '/api/v1/users') ->first(); if (!$route) { $this->markTestSkipped('routes 表中无 GET /api/v1/users 路由'); } // 设置 override 为允许(无需路由组) RoleRouteOverride::query()->create([ 'role_id' => $user->role_id, 'route_id' => $route->id, 'allowed' => true, ]); $response = $this->get('/api/v1/users', [], $this->authHeaders($user)); $response->assertStatus(200); // 清理 RoleRouteOverride::query() ->where('role_id', $user->role_id) ->where('route_id', $route->id) ->delete(); } public function test_override_deny_overrides_group_auth(): void { $user = $this->createTestUser('developer'); $route = Route::query() ->where('method', 'GET') ->where('path', '/api/v1/users') ->first(); if (!$route) { $this->markTestSkipped('routes 表中无 GET /api/v1/users 路由'); } // 先授权路由组 $group = RouteGroup::query()->create([ 'name' => 'test_override_deny_' . uniqid(), 'label' => '覆盖拒绝测试', ]); $old_group_id = $route->group_id; $route->group_id = $group->id; $route->save(); Db::table('role_route_groups')->insert([ 'role_id' => $user->role_id, 'group_id' => $group->id, ]); // 设置 override 为拒绝(优先级高于 group) RoleRouteOverride::query()->create([ 'role_id' => $user->role_id, 'route_id' => $route->id, 'allowed' => false, ]); $response = $this->get('/api/v1/users', [], $this->authHeaders($user)); $response->assertStatus(403); // 清理 RoleRouteOverride::query() ->where('role_id', $user->role_id) ->where('route_id', $route->id) ->delete(); Db::table('role_route_groups') ->where('role_id', $user->role_id) ->where('group_id', $group->id) ->delete(); $route->group_id = $old_group_id; $route->save(); $group->delete(); } // ========== Step 2: 数据范围检查 ========== public function test_user_without_role_returns_403(): void { $suffix = bin2hex(random_bytes(4)); $user = User::query()->create([ 'username' => 'norole_' . $suffix, 'email' => 'norole_' . $suffix . '@example.com', 'password' => 'Pass_' . $suffix, 'status' => 1, 'role_id' => null, ]); $response = $this->get('/api/v1/users', [], $this->authHeaders($user)); $response->assertStatus(403); } public function test_accessor_with_store_scope_and_override(): void { $user = $this->createTestUser('accessor'); $route = Route::query() ->where('method', 'GET') ->where('path', '/api/v1/users') ->first(); if (!$route) { $this->markTestSkipped('routes 表中无 GET /api/v1/users 路由'); } // 授权路由访问 RoleRouteOverride::query()->create([ 'role_id' => $user->role_id, 'route_id' => $route->id, 'allowed' => true, ]); // 添加 store scope $store = Store::query()->first(); if ($store) { UserDataScope::query()->create([ 'user_id' => $user->id, 'scope_type' => 'store', 'scope_id' => $store->id, ]); } $response = $this->get('/api/v1/users', [], $this->authHeaders($user)); $response->assertStatus(200); // 清理 RoleRouteOverride::query() ->where('role_id', $user->role_id) ->where('route_id', $route->id) ->delete(); UserDataScope::query()->where('user_id', $user->id)->delete(); } public function test_administrator_scope_type_is_all(): void { $user = $this->createTestUser('administrator'); // administrator 应有 scope_type='all',可以正常访问 $response = $this->get('/api/v1/users', [], $this->authHeaders($user)); $response->assertStatus(200); $response->assertJsonPath('code', 0); } // ========== 安全修复测试 ========== public function test_unregistered_route_denied_for_non_admin(): void { $user = $this->createTestUser('developer'); // 从 routes 表删除 GET /api/v1/users 记录,模拟未注册场景 $route = Route::query()->where('method', 'GET')->where('path', '/api/v1/users')->first(); if (!$route) { $this->markTestSkipped('routes 表中无 GET /api/v1/users 路由'); } $route_data = $route->toArray(); $route->delete(); try { // 白名单模式:routes 表中无记录 → 403 $response = $this->get('/api/v1/users', [], $this->authHeaders($user)); $response->assertStatus(403); } finally { // 恢复路由记录(使用 Db::table 保留原 id) Db::table('routes')->insert($route_data); } } public function test_admin_allowed_on_unregistered_route(): void { $user = $this->createTestUser('administrator'); // 从 routes 表删除 GET /api/v1/users 记录 $route = Route::query()->where('method', 'GET')->where('path', '/api/v1/users')->first(); if (!$route) { $this->markTestSkipped('routes 表中无 GET /api/v1/users 路由'); } $route_data = $route->toArray(); $route->delete(); try { // administrator 跳过路由检查,即使路由未在 routes 表中也应正常访问 $response = $this->get('/api/v1/users', [], $this->authHeaders($user)); $response->assertStatus(200); } finally { // 恢复路由记录(使用 Db::table 保留原 id) Db::table('routes')->insert($route_data); } } public function test_parametric_route_matches_template_path(): void { $user = $this->createTestUser('developer'); $route = Route::query() ->where('method', 'GET') ->where('path', '/api/v1/users/{id}') ->first(); if (!$route) { $this->markTestSkipped('routes 表中无 GET /api/v1/users/{id} 路由'); } // 创建路由组并授权 $group = RouteGroup::query()->create([ 'name' => 'test_param_route_' . uniqid(), 'label' => '参数化路由测试', ]); $old_group_id = $route->group_id; $route->group_id = $group->id; $route->save(); Db::table('role_route_groups')->insert([ 'role_id' => $user->role_id, 'group_id' => $group->id, ]); // 访问参数化路由 /api/v1/users/1(应匹配模板路径 /api/v1/users/{id}) $response = $this->get('/api/v1/users/1', [], $this->authHeaders($user)); // 路由检查应通过(200 或 404 取决于用户是否存在,但不应是 403) $this->assertNotEquals(403, $response->getStatusCode()); // 清理 Db::table('role_route_groups') ->where('role_id', $user->role_id) ->where('group_id', $group->id) ->delete(); $route->group_id = $old_group_id; $route->save(); $group->delete(); } }