Laravel, having a custom controller method with custom policy method?
Asked Answered
B

2

5

I have a resource controller and want to add an extra custom policy method for destroyMany In which I would check if the user is admin before deleting many.

The default methods work fine

Controller Method Policy Method
index viewAny
show view
create create
store create
edit update
update update
destroy delete
destroyMany destroyMany

Controller destroyMany method is called, the policy isn't Or should I stick to Gates for this extra method? The docs say I can have any name for the methods and policies, How can both be linked?

destroyMany->destroyMany or destroyMany->deleteMany would be a good setup.

And would be a great addition to my resource controller (where it should reside)

class ResourceController extends Controller
{
    public function __construct()
    {
      $this->middleware('auth:api');
      $this->authorizeResource(Resource::class, 'resource');
    }

    public function index()
    {

        return ResourceCollection::collection(Resource::all());
    }

    public function destroyMany(Request $request)
    {
        // gets called but needs a policy which isn't called
    }
}

policy

class ResourcePolicy
{
    use HandlesAuthorization;

    /**
     * Create a new policy instance.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    public function viewAny(User $user)
    {
        // works
        return $user->hasAnyRoles(['admin', 'superAdmin']);
    }

    public function delete(User $user, Resource $resource)
    {
       // works
        return $user->hasAnyRoles(['admin', 'superAdmin']);
    }

    public function deleteMany(User $user, Resource $resource)
    {
        // not called because the controller method needs to be hooked up, like the other methods
       
    }
}
Bennion answered 9/3, 2021 at 7:13 Comment(2)
What version of Laravel are you using? Please can you also show your controller code.Alb
I've edited my question, it's a normal resource controller with policies. Laravel 8Bennion
A
9

To get the addition policy method to work you will need to update the resourceAbilityMap for the controller. Adding the following to your controller should do the trick:

protected function resourceAbilityMap()
{
    return array_merge(parent::resourceAbilityMap(), [
        'destroyMany' => 'deleteMany'
    ]);
} 

Also, if you don't return anything from your deleteMany policy method it will result in a 403.

If your route/controller method isn't receiving an instance of the model then you will also need to update the array returned from the resourceMethodsWithoutModels method:

protected function resourceMethodsWithoutModels()
{
    return array_merge(parent::resourceMethodsWithoutModels(), ['destroyMany']);
}
Alb answered 9/3, 2021 at 9:30 Comment(6)
Great answer! Although the 'destroyMany' => 'deleteMany' is added and returned via resourceAbilityMap I still get a 403, I've edited the code sample and added the delete method which works. the deleteMany has the same construction.Bennion
@Bennion If you don't return anything from the policy method you will get a 403 since returning nothing will result in a falsy value. Sorry, I should have mentioned.Alb
Still a 403 when returning true in the policy for deleteMany i'm afraid,... your help is much appreciated!Bennion
@Bennion Sorry, it hadn't occurred to me that you wouldn't be passing an instance of Resource to the route/controller. I've updated the answer to show you'll need to add the resourceMethodsWithoutModels method to your controller as well.Alb
Brilliant! resourceMethodsWithoutModels did the trick! I'm very grateful for your help, you show deep knowledge! Hope you get plenty bonus points. ;-)Bennion
@Bennion Glad I could help!Alb
L
-1

A different approach is extending the policy ability and call it explicitely: a bit more tedious (you have to manually invoke authorize within your Controller's method) but the given ability is accessible also in other places (e.g. Blade templates, services, wherever you can use a can helper...).

In AuthServiceProvider

use Illuminate\Support\Facades\Gate;

public function boot(): void
{
  Gate::define('destroyMany', [ResourcePolicy::class, 'deleteMany']);
}

In ResourceController

public function destroyMany(Request $request)
{
  $this->authorize('destroyMany');
  // ...
}

Anywhere

$user->can('destroyMany');

Source: Laravel documentation.

Lafountain answered 27/6, 2023 at 8:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.