phplaravel-5.8laravel-middlewarepolicies

Laravel policy never called always 403


Laravel is being used as an API endpoint, I'm trying to control users ability to see certain portions of the site depending on which User type they are. Using either $this->authorizeResource() or $this->authorize() it always returns 403 without hitting the policies. Commenting the lines out the application allows access.

AuthServiceProvider

<?php

namespace Project\Providers;

use Project\Entities\Plumber;
use Illuminate\Support\Facades\Gate;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Auth;
use Project\Policies\PlumberPolicy;

class AuthServiceProvider extends ServiceProvider
{
    /**
     * The policy mappings for the application.
     *
     * @var array
     */
    protected $policies = [
        Plumber::class => PlumberPolicy::class
    ];

    /**
     * Register any authentication / authorization services.
     *
     * @return void
     */
    public function boot()
    {
        $this->registerPolicies();
    }
}

PlumberPolicy, where everything returns true for the sake of testing.

<?php

namespace Project\Policies;

use Project\Entities\User;
use Project\Entities\Plumber;
use Illuminate\Auth\Access\HandlesAuthorization;

class PlumberPolicy
{
    use HandlesAuthorization;

    /**
     * Determine whether the user can view the Plumber.
     *
     * @param  \Project\Entities\User  $user
     * @param  \Project\Entities\Plumber  $plumber
     * @return mixed
     */
    public function view(User $user, Plumber $plumber)
    {
        //
        return true;
    }

    /**
     * Determine whether the user can create Plumbers.
     *
     * @param  \Project\Entities\User  $user
     * @return mixed
     */
    public function create(User $user)
    {
        //
        return true;
    }

    /**
     * Determine whether the user can update the Plumber.
     *
     * @param  \Project\Entities\User  $user
     * @param  \Project\Entities\Plumber  $plumber
     * @return mixed
     */
    public function update(User $user, Plumber $plumber)
    {
        //
        return true;
    }

    /**
     * Determine whether the user can delete the Plumber.
     *
     * @param  \Project\Entities\User  $user
     * @param  \Project\Entities\Plumber  $plumber
     * @return mixed
     */
    public function delete(User $user, Plumber $plumber)
    {
        //
        return true;
    }
}

Controllers setup.

namespace Project\Http\Controllers;
class Controller extends BaseController
{
    use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
}

namespace Project\Http\Controllers;
class ApiController extends Controller
{

    public function index(Request $request) { .... }
    public function show(Request $request, $id) { .... }
    public function store(Request $request) { .... }
    public function create(Request $request) { .... }
    public function edit(Request $request) { .... }
    public function update(Request $request, $id) { .... }
    public function destroy(Request $request, $id) { .... }
}

namespace Project\Http\Controllers;
class PlumberController extends ApiController {
    public function __construct() {
        $this->authorizeResource(\Project\Entities\Plumber::class);
    }

    public function show(Request $request, $id) {
        return parent::show($request, $id);
    }
}

I've tried every suggestion for loading the policy and calling authorizeResource and authorize() with string declarations of the class names, to escaping the namespaces and without.

Stepping through with Xdebug it seems that it's failing on vendor/laravel/framework/src/Illuminate/Auth/Access/Gate.php

public function authorize($ability, $arguments = [])
{
    $result = $this->raw($ability, $arguments);

    if ($result instanceof Response) {
        return $result;
    }

    return $result ? $this->allow() : $this->deny();
}

Where $result = $this->raw($ability, $arguments); is returning as false. $ability = view and $arguments = empty array.

I've read https://github.com/laravel/framework/issues/22847#issuecomment-521308861 where the user mentions needing to pass the model in the method declaration, which I'm unable to do with a huge system rewrite.

Routes list is

| GET|HEAD                               | api/plumber                                         | plumber.index                 | Project\Http\Controllers\PlumberController@index                     | api,auth:api,role:valuer,plumber                                   |
| POST                                   | api/plumber                                         | plumber.store                 | Project\Http\Controllers\PlumberController@store                     | api,auth:api,role:valuer,plumber,can:create,Project\Entities\plumber |
| GET|HEAD                               | api/plumber/{plumber}                               | plumber.show                  | Project\Http\Controllers\PlumberController@show                      | api,auth:api,role:valuer,plumber,can:view,plumber                  |
| PUT|PATCH                              | api/plumber/{plumber}                               | plumber.update                | Project\Http\Controllers\PlumberController@update                    | api,auth:api,role:valuer,plumber,can:update,plumber                |
| DELETE                                 | api/plumber/{plumber}                               | plumber.destroy               | Project\Http\Controllers\PlumberController@destroy   

Solution

  • From what I found it isn't possible using authorizeResource(). You need to call $this->authorize() and pass in an object, as it will error with an array.

    class AuthServiceProvider extends ServiceProvider
    {
        protected $policies = [
            Plumber::class => PlumberPolicy::class
        ];
    }
    

    authorize() expects an object, it will throw an error if passed an array.

    class PlumberController extends ApiController
    {
        public function index(Request $request) { 
            $this->authorize('viewAny', \Project\Entities\Plumber::class);
        }
        public function update(Request $request, $id) { 
            $plumber = $this->repository->getByID($id);
    
            $this->authorize('update', $plumber);
        }
        public function store(Request $request) { 
            $this->authorize('create', \Project\Entities\Plumber::class);
        }
        public function show(Request $request, $id) { 
            $plumber = $this->repository->getByID($id);
    
            $this->authorize('update', $plumber);
        }
        public function destroy(Request $request, $id) { 
            $plumber = $this->repository->getByID($id);
            
            $this->authorize('update', $plumber);
        }
    }