Files
datahub/backend/app/Middleware/PermissionMiddleware.php
T
2026-04-17 11:06:34 +08:00

233 lines
8.2 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
namespace App\Middleware;
use App\Model\Platform;
use App\Model\RoleRouteOverride;
use App\Model\Route;
use App\Model\Store;
use App\Service\ScopeBitmapService;
use App\Service\ScopeTableManager;
use Hyperf\DbConnection\Db;
use Hyperf\HttpServer\Contract\ResponseInterface as HttpResponse;
use Hyperf\HttpServer\Router\Dispatched;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Qbhy\HyperfAuth\AuthManager;
class PermissionMiddleware implements MiddlewareInterface
{
public function __construct(
protected readonly AuthManager $auth,
protected readonly HttpResponse $response,
protected readonly ScopeTableManager $scopeTableManager,
protected readonly ScopeBitmapService $bitmapService,
) {
}
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
// 获取已认证用户(优先从 attribute 获取,兼容 JWT guard
$user = $request->getAttribute('auth_user') ?? $this->auth->guard('jwt')->user();
if (!$user) {
return $this->forbiddenResponse('用户认证异常');
}
// 获取用户 scope(含角色和 bitmap
$user_scope = $this->scopeTableManager->getUserScope($user->id);
if (!$user_scope) {
return $this->forbiddenResponse('用户权限未配置');
}
$role = $user_scope['role'];
$method = $request->getMethod();
// 通过 Dispatched 获取路由模板路径(如 /api/v1/users/{id}),解决参数化路由匹配问题
$dispatched = $request->getAttribute(Dispatched::class);
$route_path = $dispatched?->handler?->route ?? $request->getUri()->getPath();
// ===== Step 1: 路由访问检查 =====
if ($role !== 'administrator') {
$access_result = $this->checkRouteAccess($user->role_id, $method, $route_path);
if ($access_result === false) {
return $this->forbiddenResponse('无权访问该接口');
}
}
// ===== Step 2: 数据范围检查 =====
$scope_result = $this->applyDataScope($request, $role, $user_scope['scope'], $user);
if ($scope_result instanceof ResponseInterface) {
return $scope_result;
}
return $handler->handle($scope_result);
}
/**
* 路由访问检查
*
* @param int $role_id
* @param string $method
* @param string $path
* @return bool
*/
protected function checkRouteAccess(int $role_id, string $method, string $path): bool
{
// 查找路由记录
$route = Route::query()->where('method', $method)->where('path', $path)->first();
if (!$route) {
// 白名单模式:未注册到 routes 表的路由拒绝访问
return false;
}
// 1. 先查 override(优先级最高)
$override = RoleRouteOverride::query()
->where('role_id', $role_id)
->where('route_id', $route->id)
->first();
if ($override) {
return $override->allowed;
}
// 2. 查 route group 授权
if (!$route->group_id) {
// 路由未分组且无 override → 拒绝
return false;
}
return Db::table('role_route_groups')
->where('role_id', $role_id)
->where('group_id', $route->group_id)
->exists();
}
/**
* 数据范围检查与注入
*
* @return ServerRequestInterface|ResponseInterface
*/
protected function applyDataScope(
ServerRequestInterface $request,
string $role,
string $bitmap,
mixed $user,
): ServerRequestInterface|ResponseInterface {
$params = $request->getQueryParams();
$company_id = isset($params['company_id']) ? (int) $params['company_id'] : null;
$platform_id = isset($params['platform_id']) ? (int) $params['platform_id'] : null;
$store_id = isset($params['store_id']) ? (int) $params['store_id'] : null;
// --- administrator: 全部放行 ---
if ($role === 'administrator') {
$request = $request->withAttribute('scope_type', 'all');
$request = $request->withAttribute('scope_ids', []);
return $request;
}
// --- developer: 基于维护的 platform ---
if ($role === 'developer') {
return $this->applyDeveloperScope($request, $user, $bitmap, $company_id, $platform_id, $store_id);
}
// --- accessor: 基于 bitmap store_ids ---
return $this->applyAccessorScope($request, $bitmap, $company_id, $platform_id, $store_id);
}
/**
* developer 角色的数据范围处理
*
* @return ServerRequestInterface|ResponseInterface
*/
protected function applyDeveloperScope(
ServerRequestInterface $request,
mixed $user,
string $bitmap,
?int $company_id,
?int $platform_id,
?int $store_id,
): ServerRequestInterface|ResponseInterface {
$platform_ids = Platform::query()->where('developer_id', $user->id)->pluck('id')->toArray();
// 有 scope 参数时校验
if ($platform_id !== null && !in_array($platform_id, $platform_ids, true)) {
return $this->forbiddenResponse('平台不在开发者权限范围内');
}
if ($store_id !== null && !$this->bitmapService->has($bitmap, $store_id)) {
return $this->forbiddenResponse('店铺不在开发者权限范围内');
}
if ($company_id !== null) {
$has_store = Store::query()
->where('company_id', $company_id)
->whereIn('platform_id', $platform_ids)
->exists();
if (!$has_store) {
return $this->forbiddenResponse('公司不在开发者权限范围内');
}
}
// 注入 scope
if ($platform_id === null && $store_id === null && $company_id === null) {
$request = $request->withAttribute('scope_type', 'platform');
$request = $request->withAttribute('scope_ids', $platform_ids);
} else {
$request = $request->withAttribute('scope_type', 'store');
$request = $request->withAttribute('scope_ids', $this->bitmapService->decode($bitmap));
}
return $request;
}
/**
* accessor 角色的数据范围处理
*
* @return ServerRequestInterface|ResponseInterface
*/
protected function applyAccessorScope(
ServerRequestInterface $request,
string $bitmap,
?int $company_id,
?int $platform_id,
?int $store_id,
): ServerRequestInterface|ResponseInterface {
$allowed_store_ids = $this->bitmapService->decode($bitmap);
// 有 scope 参数时校验
if ($store_id !== null && !$this->bitmapService->has($bitmap, $store_id)) {
return $this->forbiddenResponse('店铺不在访问权限范围内');
}
if ($company_id !== null) {
$company_stores = Store::query()->where('company_id', $company_id)->pluck('id')->toArray();
if (empty(array_intersect($company_stores, $allowed_store_ids))) {
return $this->forbiddenResponse('公司不在访问权限范围内');
}
}
if ($platform_id !== null) {
$platform_stores = Store::query()->where('platform_id', $platform_id)->pluck('id')->toArray();
if (empty(array_intersect($platform_stores, $allowed_store_ids))) {
return $this->forbiddenResponse('平台不在访问权限范围内');
}
}
// 注入已验证的 store_ids
$request = $request->withAttribute('scope_type', 'store');
$request = $request->withAttribute('scope_ids', $allowed_store_ids);
return $request;
}
/**
* 返回 403 响应
*/
protected function forbiddenResponse(string $message): ResponseInterface
{
return $this->response->json([
'code' => 403,
'message' => $message,
])->withStatus(403);
}
}