User not getting access despite being granted role that has permission
abodnar opened this issue · 2 comments
Per our conversation on Mastodon, I'm running into the situation where despite a user having the role that has the ability to view clients, I'm receiving a 403.
Setup:
$adminRole = Bouncer::role()->firstOrCreate([
'name' => 'admin',
'title' => 'Administrator',
]);
$viewerRole = Bouncer::role()->firstOrCreate([
'name' => 'viewer',
'title' => 'Viewer',
]);
// Set up the abilities/permissions
$userEditAbility = Bouncer::ability()->firstOrCreate([
'name' => 'user_edit',
'title' => 'Edit Users',
]);
$userViewAbility = Bouncer::ability()->firstOrCreate([
'name' => 'user_view',
'title' => 'View Users',
]);
$clientEditAbility = Bouncer::ability()->firstOrCreate([
'name' => 'client_edit',
'title' => 'Edit Clients',
]);
$clientViewAbility = Bouncer::ability()->firstOrCreate([
'name' => 'client_view',
'title' => 'View Clients',
]);
// Set up Admin role's abilities
Bouncer::allow($adminRole)->to($userEditAbility, User::class);
Bouncer::allow($adminRole)->to($userViewAbility, User::class);
Bouncer::allow($adminRole)->to($clientEditAbility, Client::class);
Bouncer::allow($adminRole)->to($clientViewAbility, Client::class);
// Set up Viewer role's abilities
Bouncer::allow($viewerRole)->to($userViewAbility, User::class);
Bouncer::allow($viewerRole)->to($clientViewAbility, Client::class);
$user = User::create([
'name' => 'Adam',
'email' => 'my@email.com'
]);
$user->assign('admin');
$client = Client::create([
'name' => 'Client X'
]);
User class:
class User extends Authenticatable
{
use SoftDeletes, HasApiTokens, HasFactory, Notifiable, HasRolesAndAbilities;
public function clients(): BelongsToMany
{
return $this->belongsToMany(Client::class)->withTimestamps();
}
}
Client class:
class Client extends Model
{
use SoftDeletes, HasFactory;
public function users(): BelongsToMany
{
return $this->belongsToMany(User::class)->withTimestamps();
}
}
Route:
Route::prefix('v1')->group(static function () {
Route::middleware('auth:api')->group(function () {
Route::prefix('clients')->as('clients.v1.')
->group(static function () {
Route::get('/', \App\Http\Controllers\Clients\ListClientController::class)->name('list');
});
Controller:
class ListClientController extends Controller
{
public function __invoke(ListRequest $request, ClientsGetMany $clientsGetManyService): object
{
$this->authorize('client_view', Client::class);
// Get clients
if (! $clientsCollection) {
return $this->response->success([], 'no data found');
}
return $this->response->success(ClientResource::collection($clientsCollection)->appends($request->query()));
}
}
I tried two other situations.
I changed the $this->authorize('client_view');
and that did work for my user, but if I try $this->authorize('client_view', $client);
it fails for the Admin user.
The problem lies in this piece of code:
$clientViewAbility = Bouncer::ability()->firstOrCreate([
'name' => 'client_view',
'title' => 'View Clients',
]);
This ability is not connected to any model. To create a model ability, you must set the entity_type
:
$clientViewAbility = Bouncer::ability()->firstOrCreate([
'name' => 'view', // You can use 'client_view', though I wouldn't recommend it
'title' => 'View Clients',
'entity_type' => Client::class,
]);
Then assign it using:
Bouncer::allow($viewerRole)->to($clientViewAbility);
What you were doing was mixing two separate things. The to
method takes either an actual ability model or a name and entity. Not both.
So you can do either:
Bouncer::allow($viewerRole)->to($abilityModel);
or
Bouncer::allow($viewerRole)->to('view', Client::class);
...in which case Bouncer will automatically find/create the ability for you.
If you look at the to
method:
bouncer/src/Conductors/Concerns/AssociatesAbilities.php
Lines 21 to 27 in 502221b
...and follow it down to the getAbilityIds
method:
bouncer/src/Conductors/Concerns/FindsAndCreatesAbilities.php
Lines 23 to 27 in 502221b
...you'll see that if the first argument is an actual ability, the rest of the arguments are ignored.
🤦 I can't believe how simple that was. That totally solved it for me and make sense.
Thanks for working through this with me.