Продвинутая Система Авторизации Действий С Ресурсами В Laravel. Часть 3. Чтение/Запись Атрибутов, Пользовательские Модели



Введение Здравствуйте, уважаемые хабровчане.

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

На этот раз постановка задачи немного изменилась: У нас уже есть авторизация действий с ресурсами путем авторизации методов соответствующих контроллеров.

Задача состоит в том, чтобы разрешить изменение/просмотр определенных полей модели.

Также необходимо реализовать возможность авторизации редактирования/просмотра своих моделей.

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



Часть 4. Авторизация действий с атрибутами



Теоретическая часть

Для реализации этого функционала я планирую использовать встроенные трейты Laravel. HasAttributes, HidesAttributes, GuardsAttributes .

Используя эти черты, фреймворк управляет массовым заполнением и отображением атрибутов.

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

Для реализации задачи нам понадобится:

  • Переопределить методы получитьСкрытый(), получитьВидимый() чтобы скрыть неавторизованные поля.

  • Переопределить метод isFillable() для защиты от несанкционированного проникновения в поля.

  • Определите свой собственный метод аутентификацияUpdate() который вернет массив полей, защищенных от записи.

  • Определите свой собственный метод авторизация() который вернет массив полей, защищенных от чтения.

  • Переопределить метод полностью защищен () так как в результате добавления логики защиты этот метод может сработать ложно.

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

У каждого метода есть свои плюсы и минусы.

Но для меня главное, чтобы не было избыточности логики.

То есть постарайтесь избежать проблемы Банановые обезьяньи джунгли и облегчить получателям поддержку проекта.

А поскольку защита полей от записи/чтения достаточно точная, я реализовал черту.

Но это дело вкуса.

Laravel предоставляет возможность воспользоваться следующими свойствами:

  • $видимый
  • $скрыто
  • $охраняется
  • $заполняемый
В этом случае необходимо понимать приоритетность использования этих полей.

Когда вы переопределяете поле $видимый система считает, что все остальные поля скрыты.

По аналогии — когда вы переопределяете поле $охраняется система считает, что все остальные поля разрешены к заполнению.

Для сохранения обратной совместимости уже написанного кода проекта эту логику следует не менять, а использовать.



Давайте начнем практиковаться

Было бы неплохо определиться с системой хранения дополнительных трейтов/хелперов/сервисов во фреймворке, а затем соответствовать собственным требованиям.

Для черт я использую путь приложение/Расширения/Трейты , поэтому я создаю новую черту приложение/Расширения/Трейты/GuardedModel .

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

В признаке я определяю методы авторизация() (разрешить просмотр) и аутентификацияUpdate() (разрешить редактирование), который вернет пустые массивы и может быть переопределен в целевой модели.

Далее вам нужно добавить в свой сервис встроенные методы.

getVisible() И получитьСкрытый() .

Но перед этим нужно решить, как именно будут скрываться атрибуты.

Предлагаю создать свой метод фильтрВидимость() , который будет запущен до выполнения родительского (встроенного) метода фреймворка.

Метод фильтрВидимость() сначала добавит все защищенные поля в массив $скрыто с помощью встроенного механизма сделатьСкрытый() , проходим по массиву защищенных полей, проверяя доступность каждого нашим методом userCanViewAttribute ($ключ) и формируем массив разрешенных.

Впоследствии разрешайте авторизованные поля с помощью встроенного механизма.

makeVisible() Для определения доступности редактирования воспользуемся стандартным методом isFillable ($ключ) , который перед вызовом родительского метода проверит необходимость авторизации ввода этого поля, и при необходимости проверит доступность нашего метода userCanUpdateAttribute ($ключ) Методы userCanUpdateAttribute ($ключ) И userCanViewAttribute ($ключ) выполнит проверку авторизации.

В моем случае используя метод $user-> может() библиотеки Spatie/Laravel-разрешение .

Вы, как я писал ранее, вольны использовать свои методы.



Черта Защищенная модель

  
  
   

<Эphp namespace App\Extensions\Traits; use App\Models\User; use Illuminate\Database\Eloquent\Model; trait GuardedModel { protected function authView(): array { return []; } protected function authUpdate(): array { return []; } public function getHidden(): array { $this->filterVisibility(); return parent::getHidden(); } public function getVisible(): array { $this->filterVisibility(); return parent::getVisible(); } public function isFillable($key) { if (in_array($key, $this->authView())) { return $this->userCanUpdateAttribute($key); } return parent::isFillable($key); } private function filterVisibility(): void { $this->makeHidden($this->authView()); $authVisible = array_filter( $this->authView(), fn ($attr) => $this->userCanViewAttribute($attr) ); $this->makeVisible($authVisible); } private function userCanViewAttribute(string $key): bool { /** @var User $user */ $user = auth()->user(); $ability = !empty($user) && $user->can("view-attr-$key-" .

static::class); return $ability; } private function userCanUpdateAttribute(string $key): bool { /** @var User $user */ $user = auth()->user(); $ability = !empty($user) && $user->can("update-attr-$key-" .

static::class); return $ability; } public function totallyGuarded() { $guarded = ( count($this->getFillable()) === 0 && count($this->authView()) === 0 && $this->getGuarded() == ['*'] ); return $guarded; } }

Стоит дополнительно отметить необходимость переопределить метод полностью защищен () .

Этот метод отвечает за выдачу исключения Illuminate\Database\Eloquent\MassAssignmentException в случаях, когда осуществляется попытка заполнения поля, когда все поля защищены и не открыты для заполнения.

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

Также обратите внимание на вызов метода $user-> can("обновление- атрибут -$key-".

static::class) , а именно на формирование названия разрешения.

Оно аналогично названию разрешения для всей модели, только с добавлением приставки атрибут .

Именно по этому принципу необходимо будет получить разрешение во время первоначального посева/использования.

Давайте расширим модель нашей чертой и определим методы.

авторизация() И аутентификацияUpdate() .

Например, он вернет поля пользователь И ID пользователя .

Я сознательно взял атрибут ID пользователя чтобы продемонстрировать, что пользователь И ID пользователя представлены разными полями.

И нужно отдельно ограничить их просмотр.

Также, например, я добавил поля $охраняемый, $скрытый, $заполняемый , который необходим для настройки массового заполнения модели.



Модель Сообщения



<Эphp namespace App\Models; use App\Extensions\Traits\GuardedModel; use Illuminate\Database\Eloquent\Model; class Post extends Model { use GuardedModel; protected $hidden = ['id']; protected $guarded = ['created_at', 'updated_at']; protected $fillable = ['title', 'description', 'user_id']; protected function authView(): array { return ['user_id', 'user']; } protected function authUpdate(): array { return ['user_id']; } public function user() { return $this->belongsTo(User::class); } }



Часть 5. Авторизация действий с вашими ресурсами



Сразу к практике

Данный функционал лишь немного расширяет уже имеющийся.

Чтобы реализовать это, нам нужно определить соединение в целевой модели.

пользователь() и в процессе миграции добавьте поле ID пользователя .

Далее вам нужно немного изменить политики.

Мы расширим методы, которые принимают экземпляр модели.

Например вот так $user-> can('delete-self-'.

$this-> getModelClass()) .

Но прежде чем проверять, можно ли удалять свои модели, необходимо узнать владельца модели.

Для этого создадим в политике метод isOwner(Пользователь $user, Модель $model) .

Комбинируя эти методы, мы можем определить доступность действия.

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



Общая политика МодельПолитика



<Эphp namespace App\Policies; use App\Models\User; use Illuminate\Auth\Access\HandlesAuthorization; use Illuminate\Database\Eloquent\Model; abstract class ModelPolicy { use HandlesAuthorization; abstract protected function getModelClass(): string; public function delete(User $user, Model $model) { if ($user->can('delete-' .

$this->getModelClass())) { return true; } if ($user->can('delete-self-' .

$this->getModelClass())) { return $this->isOwner($user, $model); } return false; } private function isOwner(User $user, Model $model): bool { if (!empty($user) && method_exists($model, 'user')) { return $user->getKey() === $model->getRelation('user')->getKey(); } return false; } }

Также отмечу, что во избежание ошибок при выполнении стоит ввести проверку доступности метода.

пользователь() или аналогичный в целевой модели.

Это все на данный момент. Я надеюсь, что эта статья была полезна.

У нас достаточно полная и гибкая система авторизации действий.

Если будут вопросы, обязательно отвечу.

И, конечно же, конструктивные комментарии или предложения также приветствуются.

При желании, более подробно с реализацией данного образовательного проекта вы можете ознакомиться в репозитории .

Теги: #Разработка веб-сайта #php #Laravel #разрешения #атрибуты #владелец модели

Вместе с данным постом часто просматривают: