Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PHP/Laravel - Extending authorizeResource to work on custom method

Tags:

php

laravel

I have a resource controller called StreamController.php, that utilizes a policy called StreamPolicy.php.

In my controller, I have this:

    //StreamController.php
    /**
     * Construct method.
     */
    public function __construct()
    {
        $this->middleware('auth');
        $this->authorizeResource(Stream::class, 'stream');
    }

With above, all the RESTful endpoints is successfully "protected" using the policy.

However, I have added a new method to my controller, called documents(), like so:

//web.php
Route::get('streams/{stream}/documents', 'StreamController@documents');
    //StreamController.php
    /**
     * Display the imported documents of the resource
     *
     * @return \Illuminate\Http\Response
     */
    public function documents(Stream $stream)
    {
        return view('streams.documents', compact('stream'));
    }

Now the problem is if I visit the URL:

example.com/streams/1 and I am not the owner of the stream, I get a 403 page - but if I go to: example.com/streams/1/documents and I am not the owner of the stream, I can still access the page.

What am I doing wrong? How can I make so my policy also covers the documents() methods in my controller?

Edit:

This is my StreamPolicy.php file:

//StreamPolicy.php
namespace App\Policies;

use App\User;
use App\Stream;
use Illuminate\Auth\Access\HandlesAuthorization;

class StreamPolicy
{
    use HandlesAuthorization;

    /**
     * Determine whether the user can view the stream.
     *
     * @param  \App\User  $user
     * @param  \App\Stream  $stream
     * @return mixed
     */
    public function view(User $user, Stream $stream)
    {
        return $user->id == $stream->user_id;
    }

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

    /**
     * Determine whether the user can update the stream.
     *
     * @param  \App\User  $user
     * @param  \App\Stream  $stream
     * @return mixed
     */
    public function update(User $user, Stream $stream)
    {
        //

        return $user->id == $stream->user_id;
    }

    /**
     * Determine whether the user can delete the stream.
     *
     * @param  \App\User  $user
     * @param  \App\Stream  $stream
     * @return mixed
     */
    public function delete(User $user, Stream $stream)
    {
        //

        return $user->id == $stream->user_id;
    }

    /**
     * Determine whether the user can restore the stream.
     *
     * @param  \App\User  $user
     * @param  \App\Stream  $stream
     * @return mixed
     */
    public function restore(User $user, Stream $stream)
    {
        //
    }

    /**
     * Determine whether the user can permanently delete the stream.
     *
     * @param  \App\User  $user
     * @param  \App\Stream  $stream
     * @return mixed
     */
    public function forceDelete(User $user, Stream $stream)
    {
        //
    }
}
like image 207
oliverbj Avatar asked Feb 18 '19 09:02

oliverbj


2 Answers

Controller.php uses "AuthorizesRequest" trait which defines below 2 methods:

trait AuthorizesRequests
{
  /**
 * Get the map of resource methods to ability names.
 *
 * @return array
 */
protected function resourceAbilityMap()
{
    return [
        'show' => 'view',
        'create' => 'create',
        'store' => 'create',
        'edit' => 'update',
        'update' => 'update',
        'destroy' => 'delete',
    ];
}

/**
 * Get the list of resource methods which do not have model parameters.
 *
 * @return array
 */
protected function resourceMethodsWithoutModels()
{
    return ['index', 'create', 'store'];
}

You can override these 2 protected methods per controller basis because every controller extends Controller.php

class UserController extends Controller
{
    public function __construct ()
    {
        $this->authorizeResource ( User::class, 'user' );
    }

   /**
     * Get the map of resource methods to ability names.
     *
     * @return array
     */
    protected function resourceAbilityMap()
    {
        return [
            'show' => 'view',
            'create' => 'create',
            'store' => 'create',
            'edit' => 'update',
            'update' => 'update',
            'destroy' => 'delete',
            'customMethod'=>'customMethod',
            'customMethodWithoutModel'=>'customMethodWithoutModel'
        ];
    }

    /**
     * Get the list of resource methods which do not have model parameters.
     *
     * @return array
     */
    protected function resourceMethodsWithoutModels()
    {
        return ['index', 'create', 'store','customMethodWithoutModel'];
    }

Its Policy Class

class UserPolicy
{

   /**
     * Determine whether the user can custom method.
     *
     * @param  \App\User $user
     * @param  \App\User $model
     * @return mixed
     */
   public function customMethod(User $user, User $model){
        return true; 
   }

   /**
     * Determine whether the user can custom method without model.
     *
     * @param  \App\User $user
     * @return mixed
     */
   public function customMethodWithoutModel(User $user){
        return true;
   }
like image 161
Junaid Khan Avatar answered Nov 14 '22 09:11

Junaid Khan


I don't know exactly why is not working but I'm afraid that the authorizeResource method only handles the routes for the well-known resources end-points: view, create, update, delete and restore.

Later edit: Take a look in the docs to see which are the actions handled by the Resource Controllers https://laravel.com/docs/5.7/controllers#resource-controllers

What you should do is to explicitly set the authorization to the new route:

Route::get('streams/{stream}/documents', 'StreamController@documents')->middleware('can:documents,stream');

Of course, the documents method should exist on the StreamPolicy class.

OR

To authorize inside the StreamController.documents method:

public function documents(Stream $stream)
{
    $this->authorize('documents', $stream);

    return view('streams.documents', compact('stream'));
}
like image 27
Mihai Matei Avatar answered Nov 14 '22 10:11

Mihai Matei