Checking permissions in Laravel Policies using magic methods

Let's refactor policy classes and get rid of unnecessary code using the Laravel Policy Permission Check package

If you worked with user rights in Laravel, you probably used policy classes.

In some situations, the policy methods work on the same principle - the conditions work very similar:

I made a small package that provides access to the abstract class MagicPolicy. Inherit your policy classes from it and your code will be reduced, as well as it will become more convenient to support.

In the example shown in the image above, the final code (when using my package) will be as follows:

Laravel Policy Permission Check

Installing and connecting the package

You must install the package using composer:

composer require sarvarov/laravel-policy-permission-check

After that, all you need to do is to extend Sarvarov\LaravelPolicyPermissionCheck\MagicPolicy in your policy:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
<?php   namespace App\Models\Policies;   use App\Models\Auth\User; use App\Models\Post; use Illuminate\Auth\Access\HandlesAuthorization; use Sarvarov\LaravelPolicyPermissionCheck\MagicPolicy;   class PostPolicy extends MagicPolicy { use HandlesAuthorization;   /* ... */ }

Magic methods

If you just check whether the user has access to an action, the package will find the appropriate permission itself, taking the method name as the basis.

For example, if you inherit from MagicPolicy, you can delete the following policy method (it is unnecessary):

public function update(User $user, Post $post)
{
    return $user->can('update posts');
}

By default, it tries to find the resolution using the following key: event + space + plural model:

view any posts 
view posts 
create posts 
update posts 
delete posts
 
# etc...

If you use a different rules for permission names, change them by editing the following settings:

  • permission.naming_rules.subject_plural - whether to make the model name plural.
    Example (if set false): will be view post, not view posts (as default).
  • permission.naming_rules.delimiters.between_words - word separator in the permission name.
    Example (if set -): will be view-any blog-posts, not view any blog posts (as default).
  • permission.naming_rules.delimiters.between_subject_and_action - delimiter between the action and the model.
    Example (if set .): will be posts.view, not posts view (as default).
  • permission.naming_rules.key_pattern - the order of elements in the permission key.
    Example (if set {subject}{delimiter}{action}): will be posts view any, not view any posts (as default).

For example, if your application uses the dot notation in the keys of permissions, then you need to change the following settings:

  1. permission.naming_rules.delimiters.between_words - set to ..
  2. permission.naming_rules.delimiters.between_subject_and_action - set to -.
  3. permission.naming_rules.key_pattern - set to {subject}{delimiter}{action}.

Result will be:

# Before
view any posts
 
# After
posts.view-any

This way, you can adapt the package to the naming rules of permissions of your app.

Additional checks

If, in addition to the permission check, you need to perform some additional checks for passing conditions - you need to create the appropriate method in the policy class.

In this method you can run the checkPermission() helper:

  1. In the first argument, pass $user.
  2. In the second argument, pass the event (if you don't pass it, the permission will be checked based on the name of the method being called).
1 2 3 4 5 6 7 8
protected function delete(User $user, Post $post) { if ($user->id === $post->author_id) { return $this->checkPermission($user, 'delete own posts'); }   return $this->checkPermission($user); }

Proxy methods

If you repeat the code of several methods, you can use proxy methods.

To do this, you need to create an array - the protected $proxies property in the policy class.

The key of elements in this array is the name of the method where need to "redirect" from the called method. The value of these elements will be an array of names of proxy methods (redirect from methods):

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
// Было:   class PostPolicy extends MagicPolicy { use HandlesAuthorization;   protected function update(User $user, Post $post) { if ($user->id === $post->author_id) { return $this->checkPermission($user, 'update own'); }   return $this->checkPermission($user); }   protected function delete(User $user, Post $post) { if ($user->id === $post->author_id) { return $this->checkPermission($user, 'delete own'); }   return $this->checkPermission($user); } }   // Стало:   class PostPolicy extends MagicPolicy { use HandlesAuthorization;   protected $proxies = [ 'manage' => ['update', 'delete'], ];   protected function manage(User $user, Post $post) { if ($user->id === $post->author_id) { return $this->checkProxiedPermission($user, 'own'); // checks for `update own posts` and `delete own posts` }   return $this->checkPermission($user); // checks for `update posts` and `delete posts` } }

By default, permissions are compared by proxy method names, but you can override this by adding :false to the key in the $proxies property element(s):

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
class PostPolicy extends MagicPolicy { use HandlesAuthorization;   protected $proxies = [ 'manage:false' => ['update', 'delete'], ];   protected function manage(User $user, Post $post) { if ($user->id === $post->author_id) { return $this->checkProxiedPermission($user, 'own'); // checks for `manage own posts` always }   return $this->checkPermission($user); // checks for `manage posts` always } }

Managing settings

To change the package configuration, you must create a file /config/permission.php. Inside this file, you need to return an array of settings.

All parameters can be found in the package files: MagicPermission.php and PermissionCheckHelper.php.

Comments

Spelling error report

The following text will be sent to our editors: