Files
datahub/backend/app/Middleware/PermissionMiddleware.php
T
2026-03-09 14:15:11 +08:00

229 lines
7.8 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 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
{
// 获取已认证用户(由 AuthMiddleware 预先认证)
$user = $this->auth->guard('jwt')->user();
if (!$user) {
return $handler->handle($request);
}
// 获取用户 scope(含角色和 bitmap
$user_scope = $this->scopeTableManager->getUserScope($user->id);
if (!$user_scope) {
return $this->forbiddenResponse('用户权限未配置');
}
$role = $user_scope['role'];
$method = $request->getMethod();
$path = $request->getUri()->getPath();
// ===== Step 1: 路由访问检查 =====
if ($role !== 'administrator') {
$access_result = $this->checkRouteAccess($user->role_id, $method, $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 true;
}
// 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);
}
}