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


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

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

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

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

Есть ли какой-либо обходной путь для этого? Мне очень нравится, когда он СУХОЙ.

Author: revo, 2015-12-19

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

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

 6
Author: Blue Genie, 2016-02-18 01:57:29

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

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

<?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;
         } 
    }
 }
 5
Author: Tzook Bar Noy, 2016-02-18 21:08:40

Добавьте эту функцию в свой 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
Author: Nehal Hasnayeen, 2015-12-19 22:08:38

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

Добавьте эту функцию в свой класс 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*/;
});
 4
Author: Siphon, 2016-02-22 14:17:02

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

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

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

Вы можете использовать before() функция, также.

И затем важное примечание:

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

Например, вызовите $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 returnnull будет проверять соответствующий метод с тем же именем на методе контроллера и если этот метод не нашел пользователя не удается выполнить действие.

Спасибо, ларавель, что ВЫСУШИЛ нас!♥

 3
Author: ivahidmontazer, 2015-12-24 13:52:44