Возможности пользователя Laravel
В Laravel вы можете легко определить способности , а затем подключить их позже по запросу пользователя относительно выполнения различных действий:
$gate->define('update-post', function ($user, $post) {
return $user->id === $post->user_id;
});
Но почти во всех моих определенных способностях есть эта часть $user->id === $model->user_id
. Мне это не нравится, так как это своего рода повторение условия снова и снова, которое, я думаю, могло бы быть более абстрактным.
Большинство моих определенных способностей соответствуют обновлению/удалению записей, поэтому было бы лучше, если бы я мог применить глобальное условие ко всем из них или может ли быть групповая способность, определяющая, что похоже на то, что мы делаем в маршрутизации.
Есть ли какой-либо обходной путь для этого? Мне очень нравится, когда он СУХОЙ.
5 answers
Все в Laravel расширяемо, в этом сила его поставщиков услуг.
Вы можете расширить объект Gate
до объекта MyCustomGate
и делать в этом объекте все, что хотите. Вот пример:
MyCustomGate.php
class MyCustomGate extends \Illuminate\Auth\Access\Gate
{
protected $hasOwnershipVerification = [];
/**
* Define a new ability.
*
* @param string $ability
* @param callable|string $callback
* @return $this
*
* @throws \InvalidArgumentException
*/
public function defineWithOwnership($ability, $callback, $foreignUserIdKey = "user_id")
{
// We will add this
$this->hasOwnershipVerification[$ability] = $foreignUserIdKey;
return $this->define($ability, $callback);
}
/**
* Resolve and call the appropriate authorization callback.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param string $ability
* @param array $arguments
* @return bool
*/
protected function callAuthCallback($user, $ability, array $arguments)
{
$callback = $this->resolveAuthCallback(
$user, $ability, $arguments
);
// We will assume that the model is ALWAYS the first key
$model = is_array($arguments) ? $arguments[0] : $arguments;
return $this->checkDirectOwnership($ability, $user, $model) && call_user_func_array(
$callback, array_merge([$user], $arguments)
);
}
/**
* Check if the user owns a model.
*
* @param string $ability
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param \Illuminate\Database\Eloquent\Model $model
* @return bool
*/
protected function checkDirectOwnership($ability, $user, $model)
{
if(!isset($this->hasOwnershipVerification[$ability])) {
return true
}
$userIdKey = $this->hasOwnershipVerification[$ability];
// getAuthIdentifier() is just ->id, but it's better in case the pk of a user is different that id
return $user->getAuthIdentifier() == $model->{$userIdKey};
}
}
Затем вам придется сказать Laravel, чтобы он использовал ваши ворота вместо ворот по умолчанию. Вы можете сделать это в своем AuthServiceProvider
(предполагая, что он расширяет Illuminate\Auth\AuthServiceProvider
, просто добавьте следующее метод.
Поставщик услуг аутентификации
/**
* Register the access gate service.
*
* @return void
*/
protected function registerAccessGate()
{
$this->app->singleton(\Illuminate\Contracts\Auth\Access\Gate::class, function ($app) {
return new MyCustomGate($app, function () use ($app) {
return $app['auth']->user();
});
});
}
И таким образом, вы можете определить способности, используя метод defineWithOwnership()
вместо метода define()
. Вы все еще можете использовать define()
для способностей, которые не требуют подтверждения владения. Существует третий параметр defineWithOwnership()
, который принимает значение $foreignUserIdKey
; он используется в случае, когда модель имеет другое поле для идентификатора пользователя.
Примечание: Я написал код на лету и не пробовал его, в нем могут быть ошибки, но вы поняли идею.
Я немного проверил ваш вопрос, но не нашел "простого" способа сделать это.
Вместо этого я, вероятно, сделал бы следующее:
<?php
namespace App\Policies;
use App\User;
use App\Post;
trait CheckOwnership {
protected function checkOwnership($user, $model) {
$owned = $user->id === $model->user_id;
if ($owned === false)
throw new NotOwnedException;
}
}
class PostPolicy
{
use CheckOwnership;
public function update(User $user, Post $post)
{
try {
$this->checkOwnership($user, $post);
//continue other checks
} catch (NotOwnedException $ex) {
return false;
}
}
}
Добавьте эту функцию в свой AuthServiceProvider
public function defineAbilities(array $abilities, $gate)
{
foreach($abilities as $name => $model){
$gate->define($name, function ($user, $model){
return $user->id === ${$model}->user_id;
});
}
}
, А затем внутри метода загрузки
$this->defineAbilities(['ability1' => 'model1', 'ability2' => 'model2'], $gate);
Вы можете определить другую функцию и вызвать ее в анонимной функции. Это позволит вам иметь часто используемый код в одном центральном расположении, сохраняя при этом любую логику, зависящую от ресурсов.
Добавьте эту функцию в свой класс AuthServiceProvider
:
public function userCheck(User $user, $target)
{
// do the user id check
$result = isset($target->user_id) && isset($user) && $user->id === $target->user_id;
return $result;
}
Ваш код изменен:
$gate->define('update-post', function ($user, $post) {
// call the function
$result = $this->userCheck($user, $post);
// do some kind of 'update-post' specific check
return $result/* && some_bool_statement*/;
});
Я думаю, вы можете использовать промежуточные программы.
Просто создайте промежуточное программное обеспечение администратора и используйте его в своих маршрутах и группе маршрутов.
И в вашем проекте нет ошибки безопасности (удалить, создать &... действия), потому что у Laravel есть токен csrf!
Вы можете использовать before()
функция, также.
И затем важное примечание:
Если вы не определите соответствующую функцию в классе политики и не вызовете ее $this->authorize($post)
на контроллере, будет выдана ошибка unauthorized Action
, если before()
метод return
strue
.
Например, вызовите $this->authorize
на Dashboard\PostsController
:
public function edit($id)
{
$post = Post::find($id)->first();
$this->authorize($post);
return view('dashboard.post')->with(compact('post'));
}
И если бы мы определили класс постполитики:
class PostPolicy
{
use HandlesAuthorization;
public function before($user, $ability)
{
return $user->is_admin;
}
}
Если пользователь является администратором, он/она может редактировать сообщение, потому что мы return
редактируем true
в методе before()
, несмотря на то, что у нас нет метода с тем же именем (как у метода edit
в PostsController
).
На самом деле Laravel проверит метод before на Policy Class
. если before return
null
будет проверять соответствующий метод с тем же именем на методе контроллера и если этот метод не нашел пользователя не удается выполнить действие.
Спасибо, ларавель, что ВЫСУШИЛ нас!♥