Sistema avançado de autorização de recursos no Laravel. Parte 3. Atributos de leitura / escrita, modelos próprios

Introdução


Olá queridos Khabrovchans.

Hoje, chamo a atenção para a parte final de uma série de artigos sobre autorização avançada de ações com recursos no Laravel. Para entender melhor o que será discutido neste artigo, você precisa ler a primeira e a segunda partes.


Desta vez, a declaração do problema mudou um pouco: já temos autorização de ações com recursos por meio da autorização de métodos dos controladores correspondentes. A tarefa é autorizar a modificação / visualização de campos específicos do modelo. Você também precisa perceber a capacidade de autorizar a edição / visualização pelo autor de seus modelos.


Este material destina-se à prática de programadores e será difícil para um desenvolvedor iniciante entender.



Parte 4. Autorização de ações com atributos


Parte teórica


Para implementar essa funcionalidade, planejo usar as características internas do Laravel HasAttributes, HidesAttributes, GuardsAttributes . Com essas características, a estrutura controla o preenchimento em massa e a exibição de atributos. Pretendo deixar a capacidade de preencher / ler com força os atributos, porque se eu autorizar totalmente todas as ações de leitura / gravação, é muito provável que interrompamos o desempenho do programa.


Para realizar a tarefa, precisamos:


  • Substitua os métodos getHidden (), getVisible () para ocultar campos não autorizados.
  • Substitua o método isFillable () para proteger contra a gravação não autorizada de campos.
  • 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). . , , — .


Geral ModelPolicy Política


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

Também observo que, para evitar erros no tempo de execução, vale a pena introduzir uma verificação da disponibilidade do método user () ou similar no modelo de destino.


É tudo por agora. Espero que este artigo tenha sido útil. Temos um sistema bastante completo e flexível de autorização de ações. Se você tiver dúvidas, eu definitivamente responderei. E, claro, comentários ou sugestões construtivas também são bem-vindos. Se desejar, você pode ver a implementação deste projeto de treinamento em mais detalhes no repositório .


All Articles