Я реализую API в Laravel, используя спецификацию JSON:API.
В нем у меня есть ресурс, назовем его Пруды, с отношениями «многие ко многим» с другим ресурсом, назовем его Утки.
Согласно спецификациям JSON: API, чтобы удалить такую связь, я должен использовать конечную точку DELETE /пруды/{id}/отношения/утки с запросом следующего тела:
{
"data": [
{ "type": "ducks", "id": "123" },
{ "type": "ducks", "id": "987" }
]
}
Это обрабатывается PondRemoveDucksRequest, который выглядит следующим образом:
<?php
...
class PondRemoveDucksRequest extends FormRequest
{
public function authorize()
{
return $this->allDucksAreRemovableByUser();
}
public function rules()
{
return [
"data.*.type" => "required|in:ducks",
"data.*.id" => "required|string|min:1"
];
}
protected function allDucksAreRemovableByUser(): bool
{
// Here goes the somewhat complex logic determining if the user is authorized
// to remove each and every relationship passed in the data array.
}
}
Проблема в том, что если я отправлю тело, например:
{
"data": [
{ "type": "ducks", "id": "123" },
{ "type": "ducks" }
]
}
, я получаю 500, потому что проверка авторизации запускается первой и зависит от идентификаторов, присутствующих в каждом элементе массива. В идеале я хотел бы получить ошибку 422 со стандартным сообщением о проверке правил.
Быстрое исправление, которое я вижу, заключается в добавлении проверки наличия идентификатора в метод allDucksAreRemovableByUser(), но это кажется несколько хакерским.
Есть ли лучший способ сначала проверить правила проверки, а затем перейти к части авторизации?
Заранее спасибо!
@party-ring Я не уверен, что понял твой вопрос. Насколько я понимаю, правила проверки проверяются автоматически при использовании запроса в параметрах метода контроллера.
В вашем контроллере, когда вы получаете запрос, не могли бы вы dd $request->validated(); просто посмотреть, проходит ли запрос проверку? Потому что если он проходит, то есть проблема с проверкой, а если нет, то вам нужно отклонить этот запрос :)
@party-ring Сама проверка правил, похоже, работает, поскольку она возвращает 422, если в одном из удаленных элементов указан неверный «тип». Я проверю, добавил ли $request->validated(); хотя помогает с порядком правил и проверок авторизации, спасибо!
удачи в решении?
@party-ring Добавление вызова $request->validated() в контроллер ничего не изменило. Я предполагаю, что логика авторизации FormRequest выполняется перед любым кодом в методах контроллера. Что помогло, так это добавление $this->getValidatorInstance()->validated(); в начале метода authorize(). getValidatorInstance() требуется, потому что при выполнении логики авторизации экземпляр валидатора еще не создан. Таким образом, я получил стандартные сообщения об ошибках и проверку на основе rules(). Тем не менее, все еще выглядит немного грязно, я думаю о переносе его на промежуточное программное обеспечение.
Проверка IMO перед авторизацией звучит неправильно. Возврат ошибок проверки пользователю, которому вообще не разрешено просматривать этот ресурс, может привести к раскрытию информации. Я бы порекомендовал защитить вашу логику авторизации от искаженных запросов.






Здесь немного другой подход, чем тот, который вы пытаетесь использовать, но он может привести к желаемому результату для вас.
Если вы пытаетесь проверить, принадлежит ли данный идентификатор утки пользователю, это можно сделать в самом правиле следующим образом:
"data.*.id" => "exists:ducks,id,user_id,".Auth::user()->id
Это правило спрашивает, существует ли запись в таблице уток, которая соответствует идентификатору и где user_id — это текущий вошедший в систему user_id.
Если вы привяжете его к своим существующим правилам (required|string|min:1), используя 'bail', то он не будет запускать запрос, если сначала не будет передано три других правила:
"data.*.id" => "bail|required|string|min:1|exists:ducks,id,user_id,".Auth::user()->id
Это похоже на правильное решение в случае, если логика авторизации опирается только на отношения между пользователем и прудом. К сожалению, в моем случае это еще не все - необходимо определить вещи, когда речь идет о привилегиях пользователя, отношении к родительскому объекту пруда (на данный момент не могу найти хорошую аналогию;)) и т. д. В любом случае спасибо, я думаю, что это может быть полезным во многих других случаях.
добавить $this->getValidatorInstance()->validate(); в начало метода authorize()
1 - Создайте абстрактный класс с именем «FormRequest» внутри каталога App\Requests и переопределите метод проверки разрешенных():
<?php
namespace App\Http\Requests;
use Illuminate\Validation\ValidationException;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Foundation\Http\FormRequest as BaseFormRequest;
abstract class FormRequest extends BaseFormRequest
{
/**
* Validate the class instance.
*
* @return void
* @throws AuthorizationException
* @throws ValidationException
*/
public function validateResolved()
{
$validator = $this->getValidatorInstance();
if ($validator->fails())
{
$this->failedValidation($validator);
}
if (!$this->passesAuthorization())
{
$this->failedAuthorization();
}
}
}
2 - Расширьте свои FormRequests с помощью пользовательского FormRequest
<?php
namespace App\Http\Requests\Orders;
use App\Http\Requests\FormRequest;
class StoreOrderRequest extends FormRequest
{
}
Самым простым решением, которое я нашел для решения этой проблемы, было создание небольшой черты для FormRequest и использование ее в любое время, когда вы хотите запустить проверку перед авторизацией. Посмотрите пример ниже:
<?php
namespace App\Http\Requests\Traits;
/**
* This trait to run the authorize after a valid validation
*/
trait AuthorizesAfterValidation
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Set the logic after the validation
*
* @param $validator
* @return void
*/
public function withValidator($validator)
{
$validator->after(function ($validator) {
if (! $validator->failed() && ! $this->authorizeValidated()) {
$this->failedAuthorization();
}
});
}
/**
* Define the abstract method to run the logic.
*
* @return void
*/
abstract public function authorizeValidated();
}
Затем в вашем классе запроса:
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use App\Http\Requests\Traits\AuthorizesAfterValidation;
class SomeKindOfRequest extends FormRequest
{
use AuthorizesAfterValidation;
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorizeValidated()
{
return true; // <---- Set your authorization logic here
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
//
];
}
}
Источник https://github.com/laravel/framework/issues/27808#issuecomment-470394076
Не могли бы вы опубликовать, где вы называете правила проверки, пожалуйста?