Files
datahub/backend/app/Middleware/PermissionMiddleware.php
T

233 lines
8.2 KiB
PHP
Raw Normal View History

2026-03-09 14:15:11 +08:00
<?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;
2026-03-12 14:04:32 +08:00
use Hyperf\HttpServer\Router\Dispatched;
2026-03-09 14:15:11 +08:00
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
{
2026-04-16 13:16:43 +08:00
// 获取已认证用户(优先从 attribute 获取,兼容 JWT guard
$user = $request->getAttribute('auth_user') ?? $this->auth->guard('jwt')->user();
2026-03-09 14:15:11 +08:00
if (!$user) {
2026-03-12 14:04:32 +08:00
return $this->forbiddenResponse('用户认证异常');
2026-03-09 14:15:11 +08:00
}
// 获取用户 scope(含角色和 bitmap
$user_scope = $this->scopeTableManager->getUserScope($user->id);
if (!$user_scope) {
return $this->forbiddenResponse('用户权限未配置');
}
$role = $user_scope['role'];
$method = $request->getMethod();
2026-03-12 14:04:32 +08:00
// 通过 Dispatched 获取路由模板路径(如 /api/v1/users/{id}),解决参数化路由匹配问题
$dispatched = $request->getAttribute(Dispatched::class);
$route_path = $dispatched?->handler?->route ?? $request->getUri()->getPath();
2026-03-09 14:15:11 +08:00
// ===== Step 1: 路由访问检查 =====
if ($role !== 'administrator') {
2026-03-12 14:04:32 +08:00
$access_result = $this->checkRouteAccess($user->role_id, $method, $route_path);
2026-03-09 14:15:11 +08:00
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) {
2026-03-12 14:04:32 +08:00
// 白名单模式:未注册到 routes 表的路由拒绝访问
return false;
2026-03-09 14:15:11 +08:00
}
// 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);
}
}