Skip to content

Instantly share code, notes, and snippets.

@mortenscheel
Forked from uzegonemad/entrust-user-permission.md
Last active February 16, 2019 16:55
Show Gist options
  • Save mortenscheel/76b6c8f2751dd3030fb2c6e628e93409 to your computer and use it in GitHub Desktop.
Save mortenscheel/76b6c8f2751dd3030fb2c6e628e93409 to your computer and use it in GitHub Desktop.
Entrust Laravel 5.3+ User Permissions

Edits

All credit goes to uzegonemad. I've only added the following.

  1. Now supports Laravel 5.3+ (using pluck() method in stead of lists())
  2. The methods attachPermission() and detachPermission() now accept strings (name of the permission)
  3. Permissions are reloaded before can() checks to ensure they are up to date.

What is this?

Entrust is a fantastic role-based permission library for Laravel. However, by design, it only supports attaching permissions to roles, not to users.

This gist adds support for user-specific permissions on top of existing roles.

There's a chance that this hasn't been thought out fully, so use it at your own risk... I'm offering zero support for this. It either works, or it doesn't.

This has only been tested on Entrust's Laravel 5 branch.

How to use it

1. Migration

The migration adds support for a many-to-many relationship between users and permissions.

1a. Create new migration

php artisan make:migration create_permission_user_table

1b. Migration contents

<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreatePermissionUserTable extends Migration {

    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('permission_user', function (Blueprint $table) {
            $table->integer('user_id')->unsigned();
            $table->integer('permission_id')->unsigned();

            $table->foreign('user_id')->references('id')->on('users')
                  ->onUpdate('cascade')->onDelete('cascade');
            $table->foreign('permission_id')->references('id')->on('permissions')
                  ->onUpdate('cascade')->onDelete('cascade');

            $table->primary(['user_id', 'permission_id']);
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::drop('permission_user');
    }

}

1c. Run Migration

php artisan migrate

2. Trait

The trait "extends" Entrust's existing EntrustUserTrait trait.

Overriding can() method The can() method is overridden. It looks in our new permission_user table and if it doesn't find the permission, calls the original Entrust can() method. This override is forward compatible.

There are also methods for attaching permissions to a specific user, in the form of:

  • attachPermission()
  • detachPermission()
  • attachPermissions()
  • detachPermissions()

The singular methods take an ID and the plural methods take an array of IDs, just like the existing Entrust syntax.

2a. Create new trait

I created a new directory app/Traits. Put the trait wherever you want, just be sure to update the namespace accordingly.

2b. Trait contents

<?php 

namespace App\Traits;

use App\Models\Permission;
use Illuminate\Support\Facades\Config;
use Zizaco\Entrust\Traits\EntrustUserTrait;

trait EntrustUserWithPermissionsTrait
{
    use EntrustUserTrait {
        can as canEntrust;
    }

    /**
     * Many-to-Many relations with Permission.
     *
     * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
     */
    public function permissions()
    {
        return $this->belongsToMany(Config::get('entrust.permission'), Config::get('entrust.permission_user_table'), 'user_id', 'permission_id');
    }

    /**
     * Check if user has a permission by its name.
     *
     * @param string|array $permission Permission string or array of permissions.
     * @param bool         $requireAll All permissions in the array are required.
     *
     * @return bool
     */
    public function can($permission, $requireAll = false)
    {
        // Check specific permissions first because permissions override roles
        $permFound = false;
        // (re)load permissions in case they were changed since last load
        $this->load("permissions");
        $permissionArray = is_array($permission) ? $permission : is_object($permission) ? [$permission->name] : [$permission];
        $getUserPermissions = $this->permissions->pluck('name')->all();
        foreach($getUserPermissions as $userPerm)
        {
            // if permission IS found
            if(in_array($userPerm, $permissionArray))
            {
                $permFound = true;

                // if we DON'T require all, bail
                if(!$requireAll)
                {
                    break;
                }
            }
            // if permission is NOT found
            else
            {
                $permFound = false;

                // if we DO require all, bail
                if($requireAll)
                {
                    break;
                }
            }
        }

        // User permission override found
        if($permFound)
        {
            return $permFound;
        }

        // User permission not granted, check roles via entrust
        return $this->canEntrust($permission, $requireAll);
    }

    /**
     * Alias to eloquent many-to-many relation's attach() method.
     *
     * @param mixed $permission
     */
    public function attachPermission($permission)
    {
        if(is_object($permission)) {
            $permission = $permission->getKey();
        }
        else if(is_array($permission)) {
            $permission = $permission['id'];
        }
        else if (is_string($permission)){
            $permission = Permission::whereName($permission)->get()->first()->id;
        }
        $this->permissions()->attach($permission);
    }

    /**
     * Alias to eloquent many-to-many relation's detach() method.
     *
     * @param mixed $permission
     */
    public function detachPermission($permission)
    {
        if (is_object($permission)) {
            $permission = $permission->getKey();
        }
        else if (is_array($permission)) {
            $permission = $permission['id'];
        }
        else if (is_string($permission)){
            $permission = Permission::whereName($permission)->get()->first()->id;
        }
        $this->permissions()->detach($permission);
    }

    /**
     * Attach multiple permissions to a user
     *
     * @param mixed $permissions
     */
    public function attachPermissions($permissions)
    {
        foreach ($permissions as $permission) {
            $this->attachPermission($permission);
        }
    }

    /**
     * Detach multiple permissions from a user
     *
     * @param mixed $permissions
     */
    public function detachPermissions($permissions)
    {
        foreach ($permissions as $permission) {
            $this->detachPermission($permission);
        }
    }
}

2c. How do I use it?

In your User model, replace Zizaco\Entrust\Traits\EntrustUserTrait with App\Traits\EntrustUserWithPermissionsTrait

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment