Laravel Backpack with Bouncer for Access Control

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

```php
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](https://laravel.com/docs/6.x/eloquent-relationships#custom-polymorphic-types) 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.

```php
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](https://josephsilber.com/posts/2018/07/02/eloquent-polymorphic-relations-morph-map).


```php

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:

```php

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.