Подтвердить что ты не робот

Возможности пользователя Laravel

Внутри Laravel вы можете легко определить способности, а затем подключиться к ним позже с запросом пользователя о выполнении разных действий:

$gate->define('update-post', function ($user, $post) {
    return $user->id === $post->user_id;
});

Но почти все мои определенные способности имеют эту часть $user->id === $model->user_id. Мне это не нравится, так как это повторяющееся условие, над которым я думаю, может быть более абстрактным.

Большинство моих определенных способностей соответствуют обновлению/удалению записей, поэтому было бы лучше, если бы я мог сделать глобальное условие применимым ко всем из них или если может быть определение групповой способности, которое похоже на то, что мы делаем в маршрутизации.

Есть ли какое-нибудь обходное решение? Мне очень нравится DRY.

4b9b3361

Ответ 1

Все в 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 better in case the pk of a user is different that id
        return $user->getAuthIdentifier() == $model->{$userIdKey};
    }
}

Затем вам нужно будет сказать Laravel, чтобы использовать ваши ворота вместо стандартного. Вы можете сделать это в своем AuthServiceProvider (предполагая, что он расширяется Illuminate\Auth\AuthServiceProvider, просто добавьте следующий метод.

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; который используется для случая, когда модель имеет другое поле для идентификатора пользователя.

Примечание. Я написал код "на лету" и не пробовал его, он может иметь ошибки, но вы поняли.

Ответ 2

Я проверил ваш вопрос совсем немного, но я не нашел "простого" способа сделать это.

Вместо этого я, вероятно, сделаю следующее:

<?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;
         } 
    }
 }

Ответ 3

Добавьте эту функцию в свой 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);

Ответ 4

Вы можете определить другую функцию и вызвать ее в анонимной функции. Это позволит вам иметь обычно используемый код в одном центральном расположении, сохраняя при этом любую логику, специфичную для ресурса.

Добавьте эту функцию в класс 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*/;
});

Ответ 5

Я думаю, вы можете использовать middlewares.

Просто создайте промежуточное программное обеспечение администратора и используйте его в своих маршрутах и ​​группах маршрутов.

И в вашем проекте нет ошибки безопасности (удаление, создание и... действия), поскольку у Laravel есть токен csrf!

Вы также можете использовать before().

И затем важно отметить:

если вы не определяете функцию соответствия в классе Policy и вызываете ее $this->authorize($post) на контроллере, ошибка unauthorized Action будет выбрана, если метод before() return s true.

например вызов $this->authorize на Dashboard\PostsController:

public function edit($id)
{
    $post = Post::find($id)->first();
    $this->authorize($post);
    return view('dashboard.post')->with(compact('post'));
}

и если мы определили класс PostPolicy:

class PostPolicy
{
    use HandlesAuthorization;

    public function before($user, $ability)
    {
        return $user->is_admin;
    }
}

Если пользователь может быть администратором, он может редактировать пост, потому что мы return ed true в before(), несмотря на то, что у него нет метода с тем же именем (как edit метод в PostsController).

Фактически Laravel будет проверять до метода mthod на Policy Class. если before return 's null будет проверять метод соответствия с тем же именем на методе контроллера, и если этот метод не найден, пользователь не может выполнить действие.

Спасибо, ларавел для СУХИХ нас! ♥