WoodyNaDobhar
WoodyNaDobhar

Reputation: 303

Laravel Policy works for view but not viewAny

My policy for an API controller seems to be working fine for view, but returns 'This action is unauthorized.' for viewAll, both while sending an admin api token. Using Laravel 7 with Spatie Roles/Permissions. AppBaseController extends Illuminate\Routing\Controller. I've tried it without the middleware, just to be sure. Tried commenting out the 'before' function, to make sure it's not conflicting. Double-checked I'm sending Answer::class with the viewAny call. Confirmed the model 'can' method also returns false on viewAny. Tried it with and without optional User parameter in viewAny. Read and re-read the documentation, and every similar issue on here I could find. Can't seem to work out the issue. Not even sure how to trace what Laravel is doing to get that response.

routes/api.php

<?php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/

//logged in
Route::group(['middleware' => ['auth:api', 'verified']], function () {
    Route::get('answers', 'AnswerAPIController@index')->name('answers.index');
    Route::post('answers/{id}', 'AnswerAPIController@store')->name('answers.store');
    Route::get('answers/{id}', 'AnswerAPIController@show')->name('answers.show');
    Route::put('answers/{id}', 'AnswerAPIController@update')->name('answers.update');
    Route::delete('answers/{id}', 'AnswerAPIController@destroy')->name('answers.destroy');
});

AnswerPolicy.php

<?php

namespace App\Policies;

use App\Models\Answer;
use App\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization;

class AnswerPolicy
{
    use HandlesAuthorization;
    
    /**
     * Perform pre-authorization checks.
     *
     * @param  \App\Models\User  $user
     * @param  string  $ability
     * @return void|bool
     */
    public function before(User $user, $ability)
    {
        if ($user->hasRole('admin')) {
            return true;
        }
    }

    /**
     * Determine whether the user can view any answers.
     *
     * @param  \App\Models\User  $user
     * @return mixed
     */
    public function viewAny(User $user)
    {
        return true;
        //
        // if ($user !== null) {
        //     return true;
        // }
    }

    /**
     * Determine whether the user can view the answer.
     *
     * @param  \App\Models\User|null $user
     * @param  \App\Models\Answer  $answer
     * @return mixed
     */
    public function view(?User $user, Answer $answer)
    {
        
        return true;
        // if ($answer->published) {
        //     return true;
        // }

        // visitors cannot view unpublished items
        // if ($user === null) {
        //     return false;
        // }

        // // admin overrides published status
        // if ($user->can('view answers')) {
        //     return true;
        // }
    }

    /**
     * Determine whether the user can create answers.
     *
     * @param  \App\Models\User  $user
     * @return mixed
     */
    public function create(User $user)
    {
        return true;
        // if ($user->can('create answers')) {
        //     return true;
        // }
    }

    /**
     * Determine whether the user can update the answer.
     *
     * @param  \App\Models\User  $user
     * @param  \App\Models\Answer  $answer
     * @return mixed
     */
    public function update(User $user, Answer $answer)
    {
        return true;
        // if ($user->can('edit answers')) {
        //     return true;
        // }
    }

    /**
     * Determine whether the user can delete the answer.
     *
     * @param  \App\Models\User  $user
     * @param  \App\Models\Answer  $answer
     * @return mixed
     */
    public function delete(User $user, Answer $answer)
    {
        return true;
        // if ($user->can('delete answers')) {
        //     return $user->id == $answer->user_id;
        // }
    }

    /**
     * Determine whether the user can restore the answer.
     *
     * @param  \App\Models\User  $user
     * @param  \App\Models\Answer  $answer
     * @return mixed
     */
    public function restore(User $user, Answer $answer)
    {
        return true;
        //
    }

    /**
     * Determine whether the user can permanently delete the answer.
     *
     * @param  \App\Models\User  $user
     * @param  \App\Models\Answer  $answer
     * @return mixed
     */
    public function forceDelete(User $user, Answer $answer)
    {
        return true;
        //
    }
}

AnswerAPIController.php

<?php

namespace App\Http\Controllers\API;

use Auth;
use Log;
use Throwable;
use App\Http\Controllers\AppBaseController;
use App\Http\Requests\API\CreateAnswerAPIRequest;
use App\Http\Requests\API\UpdateAnswerAPIRequest;
use App\Repositories\AnswerRepository;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Response;
use Spatie\Permission\Models\Role;
use Spatie\Permission\Models\Permission;

/**
 * Class AnswerController
 * @package App\Http\Controllers\API
 */

class AnswerAPIController extends AppBaseController
{
    
    use AuthorizesRequests;
    
    /** @var  AnswerRepository */
    private $answerRepository;

    public function __construct(AnswerRepository $answerRepo)
    {
        $this->answerRepository = $answerRepo;
    }

    /**
     * @param Request $request
     * @return Response
     */
    public function index(Request $request)
    {
        
        try {

            $this->authorize('viewAny', Answer::class);

            $answers = $this->answerRepository->all(
                $request->has('search') ? $request->get('search') : [],
                $request->has('skip') && $request->has('limit') ? $request->get('skip') : null,
                $request->has('limit') ? $request->get('limit') : null,
                $request->has('columns') ? $request->get('columns') : ['*'],
                $request->has('with') ? $request->get('with') : null,
                $request->has('sort') ? $request->get('sort') : 'id',
                $request->has('direction') ? $request->get('direction') : 'asc'
            );

            return $this->sendResponse($answers->toArray(), 'Answers retrieved successfully.');
        } catch (Throwable $e) {
            $trace = $e->getTrace()[array_search(__FILE__, array_column($e->getTrace(), 'file'))];
            Log::error($e->getMessage() . " (" . $trace['file'] . ":" . $trace['line'] . ")\r\n" . '[stacktrace]' . "\r\n" . $e->getTraceAsString());
            return $this->sendError($e->getMessage(), $request->all());
        }
    }

    /**
     * @param int $id
     * @return Response
     */
    public function show($id, Request $request)
    {
        try {
            /** @var Answer $answer */
            $answer = $this->answerRepository->find(
                $id,
                $request->has('columns') ? $request->get('columns') : ['*'],
                $request->has('with') ? $request->get('with') : null
            );
        
            $this->authorize('view', $answer);

            if (empty($answer)) {
                return $this->sendError('Answer (' . $id . ') not found.');
            }

            return $this->sendResponse($answer->toArray(), 'Answer retrieved successfully.');
        } catch (Throwable $e) {
            $trace = $e->getTrace()[array_search(__FILE__, array_column($e->getTrace(), 'file'))];
            Log::error($e->getMessage() . " (" . $trace['file'] . ":" . $trace['line'] . ")\r\n" . '[stacktrace]' . "\r\n" . $e->getTraceAsString());
            return $this->sendError($e->getMessage());
        }
    }
}

request URL (index)

https://evenpulse.test/api/answers?api_token=****

response

{
  "success": false,
  "message": "This action is unauthorized.",
  "data": {
    "api_token": "****"
  }
}

request URL (view)

https://evenpulse.test/api/answers/1?api_token=****

response

{
  "success": true,
  "data": {
    "id": 1,
    "question_id": 1,
    "order": 1,
    "text": "asdf",
    "is_correct": false
  },
  "message": "Answer retrieved successfully."
}

Upvotes: 1

Views: 3618

Answers (1)

WoodyNaDobhar
WoodyNaDobhar

Reputation: 303

In a classic case of 'asking often illuminates the problem', I figured it out 30 seconds later, after two days of struggles.

In the 'viewAny' authorize method I had put in the Answer::class bit, but nowhere in the controller did I define what 'Answer' is. I fixed it by adding

use App\Models\Answer;

to the top of the controller.

Upvotes: 2

Related Questions