Sistema avanzado de autorización de recursos en Laravel. Parte 3. Atributos de lectura / escritura, modelos propios

Introducción


Hola queridos Khabrovchans.

Hoy les traigo a su atención la parte final de una serie de artículos sobre autorización avanzada de acciones con recursos en Laravel. Para comprender mejor lo que se discutirá en este artículo, debe leer la primera y la segunda parte.


Esta vez, el enunciado del problema ha cambiado un poco: ya tenemos autorización de acciones con recursos mediante la autorización de métodos de los controladores correspondientes. La tarea es autorizar la modificación / visualización de campos específicos del modelo. También debe darse cuenta de la capacidad de autorizar la edición / visualización por parte del autor de sus modelos.


Este material está destinado a los programadores en ejercicio y será difícil de entender para un desarrollador novato.



Parte 4. Autorización de acciones con atributos


Parte teórica


Laravel HasAttributes, HidesAttributes, GuardsAttributes. . / , / — .


:


  • getHidden(), getVisible() .
  • isFillable() .
  • authUpdate() .
  • authView() .
  • totallyGuarded() .

— , , . . , . - Banana Monkey Jungle, . / — . .


Laravel :


  • $visible
  • $hidden
  • $guarded
  • $fillable

. $visible . — $guarded . — , .



// , . app/Extensions/Traits, app/Extensions/Traits/GuardedModel. , ( ).

authView()( ) authUpdate()( ), , .

getVisible() getHidden().

, . filterVisibility(), , () .

filterVisibility() $hidden makeHidden(), , userCanViewAttribute($key) . makeVisible()


isFillable($key), , userCanUpdateAttribute($key)


userCanUpdateAttribute($key) userCanViewAttribute($key) . $user->can() spatie/laravel-permission. , , .


GuardedModel


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

totallyGuarded(). Illuminate\Database\Eloquent\MassAssignmentException , . — , .

$user->can(«update-attr-$key-». static::class), . , attr. /.


, authView() authUpdate(). user user_id. user_id, user user_id . . , , $guarded, $hidden, $fillable, .


Posts


<?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.



. user(), user_id. . , . $user->can('delete-self-'. $this->getModelClass()). , . isOwner(User $user, Model $model). . , , — .


Política general de políticas de modelo


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

También noto que para evitar errores durante la ejecución, vale la pena introducir una verificación de la disponibilidad del método user () o similar en el modelo de destino.


Eso es todo por ahora. Espero que este artículo haya sido útil. Contamos con un sistema bastante completo y flexible de autorización de acciones. Si tiene preguntas, definitivamente responderé. Y, por supuesto, comentarios constructivos o sugerencias también son bienvenidos. Si lo desea, puede ver la implementación de este proyecto de capacitación con más detalle en el repositorio .


All Articles