Recently I was in a project that uses laravel bouncer with backpack 4. Here is how it was done after you have installed laravel backpack and bouncer in your project
Step 1: Create a Role
model that extends \Silber\Bouncer\Database\Role
This need to be done as Laravel Backpack requires the model to use Backpack\CRUD\app\Models\Traits\CrudTrait
.
You may create an Ability
model that extends \Silber\Bouncer\Database\Ability
as well for the same reasons. Although I am not doing it for reasons that will be clearer later.
Step 2: Now, let's create some roles and ability with laravel tinker
Bouncer::allow('super-admin')->everything();
Bouncer::allow('super-reader')->to('view')->everything();
Bouncer::allow('super-reader')->to('viewAny')->everything();
$admin = User::first();
Bouncer::assign('super-admin')->to($admin);
$reader = User::find(2);
Bouncer::assign('super-reader')->to($reader);
Step 3: Let's look at the database now. The roles
and abilities
table are self explanatory. But the permissions
table is worth a closer look. It is a pivot (many to many) table between roles
and abilities
. Notice how the entity_type
is roles
and not Silber\Bouncer\Database\Role
.
The reason for that is because the Bouncer package has declared relation morph map for both its Role and Ability model.
This will cause us some issues later, as we wanted it to refer to our Role model in App\Models\Role
. I will reveal an easy fix for it in a later step.
Step 5: Let's create our RoleCrudController
. It should reference the Role
model we created in App\Models
.
Step 6. Add the appropriate fields to create and update a Role. For me I have chosen to only allow the updating of the abilities a Role is associated with. As I do not want an admin user to edit the role name or ability name and potentially break the permissions check in my code.
Below is how I implemented my setupUpdateOperation
method of my RoleCrudController
. If you have your own Ability model class that extends Bouncer Ability class, please reference your model class instead.
protected function setupUpdateOperation()
{
$currentRole = $this->crud->getCurrentEntry();
if (backpack_user()->cannot('update', $currentRole)) {
$this->crud->denyAccess(['update']);
}
$this->crud->setValidation(UpdateRequest::class);
$this->crud->addFields([
[
'name' => 'name',
'type' => 'custom_html',
'value' => '<strong>Role: </strong>'.$this->crud->getCurrentEntry()->name,
],
[
'name' => 'abilities',
'type' => 'checklist',
'entity' => 'abilities',
'attribute' => 'title',
'model' => Silber\Bouncer\Database\Ability::class,
'pivot' => true,
],
]);
}
Step 7: Click edit on a Role
record and look at how the checklist field looks like. You will notice nothing is checked. That is because the checklist field is checking the ability against App\Model\Role
but using Bouncer code, we assigned the abilities to Silber\Bouncer\Database\Role
. (Remember step 3?)
To solve this is simple. In your AppServiceProvider
boot method. Tell Bouncer to use your Role model instead of theirs. While we are at it let's define the relation morphMap for our other models such as the User model too. This will save us from other troubles later. You may learn more about relationships morph map here.
Relation::morphMap([
'users' => App\Models\User::class,
]);
// App\Models\Role::class will now map to 'roles' in entity_type column
// instead of Bouncer Role model
BouncerFacade::useRoleModel(App\Models\Role::class);
Step 8. Now, what's left is to add a checklist_dependency field to our UserCrudController. Below is an example for my UserCrudController setupUpdateOperation()
method:
if (backpack_user()->isA('super-admin')) {
$this->crud->addFields([
[
'label' => 'Roles and Abilities',
'field_unique_name' => 'user_role_ability',
'type' => 'checklist_dependency',
'name' => ['roles', 'abilities'],
'subfields' => [
'primary' => [
'label' => 'Roles',
'name' => 'roles',
'entity' => 'roles',
'entity_secondary' => 'abilities',
'attribute' => 'title',
'model' => App\Models\Role::class,
'pivot' => true,
'number_columns' => 3,
],
'secondary' => [
'label' => 'Abilities',
'name' => 'abilities',
'entity' => 'abilities',
'entity_primary' => 'roles',
'attribute' => 'title',
'model' => Silber\Bouncer\Database\Ability::class,
'pivot' => true,
'number_columns' => 3,
],
],
],
]);
}
I'm not sure if checking for the super-admin
role here specifically is good since it is usually best practice to check for a specific ability but for now I'm going with a specific check for the super-admin
role in case a super-admin accidentally assign a givePermission
ability to a lower tier Role in edit Role.
That's the end of this tutorial. I hope it help anyone who wanted to use Bouncer with Backpack.
Update: It appears I didn't handle the case of forbidden permissions. I will keep this in mind and update this article again next time when I thought of a way.