laravel 中间件之前的表单请求

laravel formrequest before middleware

我知道,这是一个复杂的案例,但也许你们中的某个人可能知道如何做到这一点。

概念

我的 API 中有以下过程:

  1. 处理查询字符串参数(FormRequest)
    • 用首选键替换键别名
    • 如果需要数组,则将字符串参数映射到数组
    • 设置默认值(包括基于 id 的参数的 Auth::user()
    • 等等
  2. 检查是否允许用户执行请求 (Middleware)
    • 使用经过处理(经过验证和清理)的查询参数 → 否则我不得不为每个可能的别名和映射做例外,并检查参数是否被选中,这对我来说似乎不合理。

问题

然而,如果您只是通过 ->middleware('middlewareName') 将中间件分配给路由,并通过依赖注入将 FormRequest 分配给控制器方法,则首先调用中间件,然后调用 FormRequest。如上所述,这不是我需要的。

解决方法

我首先尝试在中间件上进行依赖注入,但没有成功。

我的解决方案是在控制器构造函数中分配中间件。依赖注入在这里起作用,但突然 Auth::user() returns null.

然后,我遇到了 \Illuminate\Foundation\Providers\FormRequestServiceProvider.php:34 中的 FormRequest::createFrom($request) 方法以及将 $request 对象传递给中间件的 handle() 方法的可能性。结果如下所示:

public function __construct(Request $request)
{
    $middleware = new MyMiddleware();

    $request = MyRequest::createFrom($request);

    $middleware->handle($request, function() {})
}

但是现在请求还没有被验证。只是调用 $request->validated() returns 什么都没有。所以我深入挖掘了一下,发现 $resolved->validateResolved(); 是在 \Illuminate\Foundation\Providers\FormRequestServiceProvider.php:30 中完成的,但这似乎并没有触发验证,因为它抛出了一个异常,说这个方法不能在 null 上调用但是 $request 不是 null:

Call to a member function validated() on null

现在,我完全被难住了。有谁知道如何解决这个问题,还是我做错了?

提前致谢!

我想,我找到了更好的方法。

我的误解

虽然中间件正在执行 authentication,但我正在那里执行 authorization 因此我必须使用 Gate

结果代码

控制器

...
public function getData(MyRequest $request)
{
    $filters = $request->query();
    // execute queries
}
...

表单请求

class MyRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return Gate::allows('get-data', $this);
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            // ...
        ];
    }

    /**
     * Prepare the data for validation.
     *
     * @return void
     */
    protected function prepareForValidation()
    {
        $this->replace($this->cleanQueryParameters($this->query()));
    }

    private function cleanQueryParameters($queryParams): array
    {
        $queryParams = array_filter($queryParams, function($param) {
            return is_array($param) ? count($param) : strlen($param);
        });
        $defaultStartDate = (new \DateTime())->modify('monday next week');
        $defaultEndDate = (new \DateTime())->modify('friday next week');
        $defaults = [
            'article.created_by_id' => self::getDefaultEmployeeIds(),
            'date_from' => $defaultStartDate->format('Y-m-d'),
            'date_to' => $defaultEndDate->format('Y-m-d')
        ];
        $aliases = [
            // ...
        ];
        $mapper = [
            // ...
        ];
        foreach($aliases as $alias => $key) {
            if (array_key_exists($alias, $queryParams)) {
                $queryParams[$key] = $queryParams[$alias];
                unset($queryParams[$alias]);
            }
        }
        foreach($mapper as $key => $fn) {
            if (array_key_exists($key, $queryParams)) {
                $fn($queryParams, $key);
            }
        }
        $allowedFilters = array_merge(
            Ticket::$allowedApiParameters,
            array_map(function(string $param) {
                return 'article.'.$param;
            }, TicketArticle::$allowedApiParameters)
        );
        $arrayProps = [
            // ..
        ];
        foreach($queryParams as $param => $value) {
            if (!in_array($param, $allowedFilters) && !in_array($param, ['date_from', 'date_to'])) {
                abort(400, 'Filter "'.$param.'" not found');
            }
            if (in_array($param, $arrayProps)) {                
                $queryParams[$param] = guarantee('array', $value);
            }
        }
        return array_merge($defaults, $queryParams);
    }
}

大门

class MyGate
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Auth\Access\Response|Void
     * @throws \Symfony\Component\HttpKernel\Exception\HttpException
     */
    public function authorizeGetDataCall(User $user, MyRequest $request): Response
    {
        Log::info('[MyGate] Checking permissions …');

        if (in_array(LDAPGroups::Admin, session('PermissionGroups', []))) {
            // no further checks needed
            Log::info('[MyGate] User is administrator. No further checks needed');
            return Response::allow();
        }

        if (
            ($request->has('group') && !in_array(Group::toLDAPGroup($request->get('group')), session('PermissionGroups', []))) ||
            $request->has('owner.department') && !in_array(Department::toLDAPGroup($request->query('owner.department')), session('PermissionGroups', [])) ||
            $request->has('creator.department') && !in_array(Department::toLDAPGroup($request->query('creator.department')), session('PermissionGroups', []))
        ) {
            Log::warning('[MyGate] Access denied due to insufficient group/deparment membership', [ 'group/department' =>
                $request->has('group') ?
                    Group::toLDAPGroup($request->get('group')) :
                ($request->has('owner.department') ?
                    Department::toLDAPGroup($request->query('owner.department')) :
                ($request->has('creator.department') ?
                    Department::toLDAPGroup($request->query('creator.department')) :
                null))
            ]);
            return Response::deny('Access denied');
        }
        if ($request->has('customer_id') || $request->has('article.created_by_id')) {
            $ids = [];
            if ($request->has('customer_id')) {
                $ids = array_merge($ids, $request->query('customer_id'));
            }
            if ($request->has('article.created_by_id')) {
                $ids = array_merge($ids, $request->query('article.created_by_id'));
            }
            $users = User::find($ids);
            $hasOtherLDAPGroup = !$users->every(function($user) {
                return in_array(Department::toLDAPGroup($user->department), session('PermissionGroups', []));
            });
            if ($hasOtherLDAPGroup) {
                Log::warning('[MyGate] Access denied due to insufficient permissions to see specific other user\'s data', [ 'ids' => $ids ]);
                return Response::deny('Access denied');;
            }
        }
        if ($request->has('owner.login') || $request->has('creator.login')) {
            $logins = [];
            if ($request->has('owner.login')) {
                $logins = array_merge(
                    $logins,
                    guarantee('array', $request->query('owner.login'))
                );
            }
            if ($request->has('creator.login')) {
                $logins = array_merge(
                    $logins,
                    guarantee('array', $request->query('creator.login'))
                );
            }
            $users = User::where([ 'samaccountname' => $logins ])->get();
            $hasOtherLDAPGroup = !$users->every(function($user) {
                return in_array(Department::toLDAPGroup($user->department), session('PermissionGroups', []));
            });
            if ($hasOtherLDAPGroup) {
                Log::warning('[MyGate] Access denied due to insufficient permissions to see specific other user\'s data', [ 'logins' => $logins ]);
                return Response::deny('Access denied');
            }
        }
        Log::info('[MyGate] Permission checks passed');
        return Response::allow();
    }
}