laravel 中间件之前的表单请求
laravel formrequest before middleware
我知道,这是一个复杂的案例,但也许你们中的某个人可能知道如何做到这一点。
概念
我的 API 中有以下过程:
- 处理查询字符串参数(
FormRequest
)
- 用首选键替换键别名
- 如果需要数组,则将字符串参数映射到数组
- 设置默认值(包括基于
id
的参数的 Auth::user()
)
- 等等
- 检查是否允许用户执行请求 (
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();
}
}
我知道,这是一个复杂的案例,但也许你们中的某个人可能知道如何做到这一点。
概念
我的 API 中有以下过程:
- 处理查询字符串参数(
FormRequest
)- 用首选键替换键别名
- 如果需要数组,则将字符串参数映射到数组
- 设置默认值(包括基于
id
的参数的Auth::user()
) - 等等
- 检查是否允许用户执行请求 (
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();
}
}