All credit goes to uzegonemad. I've only added the following.
- Now supports Laravel 5.3+ (using
pluck()
method in stead oflists()
) - The methods
attachPermission()
anddetachPermission()
now accept strings (name of the permission) - Permissions are reloaded before
can()
checks to ensure they are up to date.
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.
The migration adds support for a many-to-many relationship between users and permissions.
php artisan make:migration create_permission_user_table
<?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');
}
}
php artisan migrate
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.
I created a new directory app/Traits
. Put the trait wherever you want, just be sure to update the namespace accordingly.
<?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);
}
}
}
In your User model, replace Zizaco\Entrust\Traits\EntrustUserTrait
with App\Traits\EntrustUserWithPermissionsTrait