229 lines
7.8 KiB
PHP
229 lines
7.8 KiB
PHP
<?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);
|
||
}
|
||
}
|