Authorising Laravel Nova actions
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 (despite some claims to the contrary).
Until Nova adds support for a “delete callback”, destructive actions will continue to be controlled to the model policy.
Sign up for my newsletter
A monthly round-up of blog posts, projects, and internet oddments.