Nova uses Laravel policies to manage resource authorisation. This can be problematic when it comes to resource actions.

For example, you may wish to prevent a user from editing a resource directly, but allow her to update it by running an action.

A simple solution

The solution is to attach a callback to the $runCallback action property.

<?php

use Illuminate\Http\Request;
use Laravel\Nova\Actions\Action;

class UpdateResource extends action
{
    public function __construct()
    {
        $this->runCallback = function (Request $request, $model) {
            return true;
        };
    }
}

How does this work?

Nova uses the Action::authorizedToRun method to determine whether an action may be executed.

public function authorizedToRun(Request $request, $model)
{
    return $this->runCallback
        ? call_user_func($this->runCallback, $request, $model)
        : true;
}

If the $runCallback instance property is truthy, Nova assumes it’s a callback. The value returned by the callback controls whether the action is executed.

Why bother with a callback?

You may be wondering why we need to bother with `$runCallback` at all. If we don’t define a callback function, `authorizedToRun` returns `true` anyway.

The answer lies in the Action::filterByResourceAuthorization method, which discards models the user is not permitted to update. Here’s the relevant chunk of code.

protected function filterByResourceAuthorization(ActionRequest $request)
{
    if ($request->action()->runCallback) {
        $models = $this->mapInto($request->resource())->map->resource;
    } else {
        $models = $this->mapInto($request->resource())
            ->filter->authorizedToUpdate($request)->map->resource;
    }

    // ...more code
}

If $runCallback is falsey, Nova runs the Resource::authorizedToUpdate method. This, in turn, checks the Laravel model policy. Precisely what we don’t want to happen.

Destructive actions

Unfortunately, Nova treats “destructive” actions differently. Here’s the remainder of the filterByResourceAuthorization method:

protected function filterByResourceAuthorization(ActionRequest $request)
{
    // Previous code omitted...

    $action = $request->action();

    if ($action instanceof DestructiveAction) {
        $models = $this->mapInto($request->resource())
            ->filter->authorizedToDelete($request)->map->resource;
    }
}

Unlike “non-destructive” actions, Nova does not check for a callback; it goes straight to the model policy. As such, the $runCallback workaround does not work for destructive actions[1].

Until Nova adds support for a “delete callback”, destructive actions will continue to be controlled to the model policy.


  1. Despite some claims to the contrary. ↩︎