Advanced resource authorization system in Laravel. Part 3. Reading / writing attributes, own models

Introduction


Hello, dear Khabrovchans.

Today I bring to your attention the final part of a series of articles on advanced authorization of actions with resources in Laravel. To better understand what will be discussed in this article, you need to read the first and second parts.


This time, the statement of the problem has changed a bit: We already have authorization of actions with resources by means of authorization of methods of the corresponding controllers. The task is to authorize the modification / viewing of specific fields of the model. You also need to realize the ability to authorize editing / viewing by the author of your models.


This material is intended for practicing programmers, and will be difficult for a novice developer to understand.



Part 4. Authorization of actions with attributes


Theoretical part


To implement this functionality, I plan to use the built-in traits of Laravel HasAttributes, HidesAttributes, GuardsAttributes . With these traits, the framework controls the mass filling and display of attributes. I plan to leave the ability to forcefully fill / read the attributes, because if I completely authorize all the read / write actions, it is very likely that we will interrupt the program’s performance.


To accomplish the task, we need:


  • Override the getHidden (), getVisible () methods to hide unauthorized fields.
  • Override the isFillable () method to protect against unauthorized recording of fields.
  • 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). . , , — .


General ModelPolicy Policy


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

I also note that in order to avoid errors during execution, it is worthwhile to introduce a check on the availability of the user () method or similar in the target model.


That's all for now. Hope this article has been helpful. We have a fairly complete and flexible system of authorization of actions. If you have questions, I will definitely answer. And of course, constructive comments or suggestions are also welcome. If you wish, you can see the implementation of this training project in more detail in the repository .


All Articles