F
Filament5mo ago
Dom

Table Builder doesn't respect my policies on a DeleteAction::make()

- I'm building a project in a modular way, and registering my policies from a module but that doesn't seem to be the problem here. - If I specify a ->action() for my DeleteAction::make(), then in the I can successfully call my policy. But Filament doesn't pick it up by default. It deletes every item I call delete on. - https://filamentphp.com/docs/3.x/panels/resources/deleting-records#authorization according to the docs, as I understand, it should be able to do that. - I'm using a public facing custom livewire component with a table builder in it, not admin panel. - Right now the only way im binding my model to the table builder is:
return $table
->query(Listing::query())
return $table
->query(Listing::query())
So in short, this works:
->actions([
DeleteAction::make()
->action(function ($record) {
if (! Auth::user()->can('delete', $record)) {
abort(403, 'Unauthorized action.');
}
$record->delete();
}),
])
->actions([
DeleteAction::make()
->action(function ($record) {
if (! Auth::user()->can('delete', $record)) {
abort(403, 'Unauthorized action.');
}
$record->delete();
}),
])
and this does'nt:
->actions([
DeleteAction::make(),
])
->actions([
DeleteAction::make(),
])
41 Replies
toeknee
toeknee5mo ago
So you don't have a policy applied by the looks of it? if you dd on the delete function in policy is it hit?
Dennis Koch
Dennis Koch5mo ago
if you run artisan model:show 'App\Models\YourModel' does it show the policy? Should be one of the first lines
Dom
DomOP5mo ago
Modules\Listing\Models\Listing ...................................................................................................................
Database ................................................................................................................................... mysql
Table ................................................................................................................................... listings
Policy .................................................................................................... Modules\Listing\Policies\ListingPolicy
it is able to find the policy
toeknee
toeknee5mo ago
Ok so if you DD on the ListingPolicy delete method? is it hit when tyring to delete?
Dom
DomOP5mo ago
yes it does, when i define my custom action like i've shown above:
->actions([
DeleteAction::make()
->action(function ($record) {
if (! Auth::user()->can('delete', $record)) {
abort(403, 'Unauthorized action.');
}
$record->delete();
}),
])
->actions([
DeleteAction::make()
->action(function ($record) {
if (! Auth::user()->can('delete', $record)) {
abort(403, 'Unauthorized action.');
}
$record->delete();
}),
])
if i put a dd inside the delete policy function it gets hit
toeknee
toeknee5mo ago
Without your custom action? just DeleteAction is it hit?
Dom
DomOP5mo ago
no its not, thats my main problem. it deletes ever item i press it on without any kind of authorization
toeknee
toeknee5mo ago
So does it delete every time.... or just the item being deleted but without the policy being hit?
Dom
DomOP5mo ago
both, the item gets deleted every time, and the policy is NOT being hit. the policy works perfectly if its being hit, its just a comparison between logged in user's id and the user_id in my listings table
toeknee
toeknee5mo ago
Hmmm This is a Resource table correct?
Dom
DomOP5mo ago
let me share my whole livewire component where im using the table builder in. it might help shining a light on this:
<?php

namespace App\Livewire;

// use...

class ProfileListingTable extends Component implements HasForms, HasTable
{
use InteractsWithForms;
use InteractsWithTable;

protected static string $model = Listing::class;

public function table(Table $table): Table
{
return $table
->query(Listing::where('user_id', '!=', Auth::id()))
->columns([
TextColumn::make('user_id'),
SpatieMediaLibraryImageColumn::make('media.first')
->limit(1)
->collection('listing-images')
->conversion('thumb')
->label('Image')
->defaultImageUrl(asset('images/listing-placeholder.png')),
TextColumn::make('title')
->url(fn (Listing $record): string => route('listing.edit', $record)),
TextColumn::make('created_at'),
])
->filters([

])
->actions([
DeleteAction::make(),
/* ->action(function ($record) { // TODO when I know how, make it work without custom action */
/* if (! Auth::user()->can('delete', $record)) { */
/* abort(403, 'Unauthorized action.'); */
/* } */
/* $record->delete(); */
/* }), */
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([

]),
]);
}

public function render(): View
{
return view('livewire.profile-listing-table');
}
}
<?php

namespace App\Livewire;

// use...

class ProfileListingTable extends Component implements HasForms, HasTable
{
use InteractsWithForms;
use InteractsWithTable;

protected static string $model = Listing::class;

public function table(Table $table): Table
{
return $table
->query(Listing::where('user_id', '!=', Auth::id()))
->columns([
TextColumn::make('user_id'),
SpatieMediaLibraryImageColumn::make('media.first')
->limit(1)
->collection('listing-images')
->conversion('thumb')
->label('Image')
->defaultImageUrl(asset('images/listing-placeholder.png')),
TextColumn::make('title')
->url(fn (Listing $record): string => route('listing.edit', $record)),
TextColumn::make('created_at'),
])
->filters([

])
->actions([
DeleteAction::make(),
/* ->action(function ($record) { // TODO when I know how, make it work without custom action */
/* if (! Auth::user()->can('delete', $record)) { */
/* abort(403, 'Unauthorized action.'); */
/* } */
/* $record->delete(); */
/* }), */
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([

]),
]);
}

public function render(): View
{
return view('livewire.profile-listing-table');
}
}
no, as i mentioned in my original description, this is a public facing custom livewire component (the query function under the table return is intentionally asking for listings that aren't the users, so i can test this problem)
toeknee
toeknee5mo ago
and the user isn't a super user?
Dom
DomOP5mo ago
i dont see a model possibility in my autofill.
No description
Dom
DomOP5mo ago
i did not define roles like that yet
toeknee
toeknee5mo ago
Yeah I got confused with form for a second
Dom
DomOP5mo ago
wait, maybe it defaults to it for me. im already using spatie permissions in my project and it does have a default role
toeknee
toeknee5mo ago
You can bypass it with: Tables\Actions\DeleteAction::make()->visible(fn (Model $record): bool => auth()->user()->can('delete', $record)), To ensure it is only shown to authorised users. But it is still strange. So by default if you have a default role that can do it..
Dom
DomOP5mo ago
does laravel take that into account?
toeknee
toeknee5mo ago
Well by default the auth is true and doesn't hit the policy if you have a super user for example whereby the role is set to true.
Dom
DomOP5mo ago
sadly it still does the same witha freshly made user
toeknee
toeknee5mo ago
Depending how Spatie Permissions has been setup obvs
Dom
DomOP5mo ago
that doesnt have roles
toeknee
toeknee5mo ago
So install debugbar and view the gates checked etc. if it's checking and returning true you need to revisit how you setup the system.
Dom
DomOP5mo ago
i already have it installed, what should i pay attention to?
toeknee
toeknee5mo ago
How you setup the middleware and the boot methods to define default roles/teams. And look at the gates in the debugbar as to what was passed.
Dom
DomOP5mo ago
im using jetstream and only its default middlewares. and there isnt a gate sectionin my debugbar
toeknee
toeknee5mo ago
There should be...
No description
toeknee
toeknee5mo ago
If gates are being skipped theres you issue...
Dom
DomOP5mo ago
at the moment i do not use gates at all, im not sure, so filament looks for the policies through them or something? in other routes i just used the "can" function directly i mean, if the policies are shown by model:show i would assume filament would be able to reach for them
toeknee
toeknee5mo ago
I am fairly sure we need gates, have you disabled them? No because that would assume we are doing custom work with the policies, were are just adhering to them.
Dom
DomOP5mo ago
I didnt disable them i just dont use them Right now middlewares tske care of everything i need, except model based authorization. So to be honest im not sure how gates come in the picture in the context of this problem
toeknee
toeknee5mo ago
Because Filament users gates for policy checking... so if your not getting any gate checks you are not checking the polices Are you using a 3rd party packge to modulise laravel?
Dom
DomOP5mo ago
Yes im using nwidart's
toeknee
toeknee5mo ago
Sounds like a possible culprit... But you'll need to debug this as to why they are not registering the policies within filament..
Dom
DomOP5mo ago
Ill try defining my policies in normal folder structure
Dennis Koch
Dennis Koch5mo ago
I couldn’t read all of your conversation but I saw that it’s a custom component that doesn’t have a reference to the resource and therefore no reference to the model and policy.
toeknee
toeknee5mo ago
Correct, but the record is loaded and so the policy should be checked
Dennis Koch
Dennis Koch5mo ago
I don’t think it works that way. Maybe try renaming it to $record. Because that’s what filament always uses internally.
Dom
DomOP5mo ago
What should I rename exactly? Right now the only thing that refers to the model in my component is the ->query(Listing::query()) on my table form
Dennis Koch
Dennis Koch5mo ago
$model to $record Nevermind. It is the model class not the record. The authorization on the DeleteAction comes from the ListPage which you don't have. So just apply your own. The default is:
->authorize(fn (Model $record): bool => static::getResource()::canDelete($record));
->authorize(fn (Model $record): bool => static::getResource()::canDelete($record));
Dom
DomOP5mo ago
Thank you! So going the custom component way might require some additional stuff in the future. Good to know!

Did you find this page helpful?