3
votes

I'm trying to allow user to view their own profile in Laravel 5.4.

UserPolicy.php

public function view(User $authUser, $user)
{ 
 return true;
}

registered policy in AuthServiceProvider.php

protected $policies = [
    App\Task::class => App\Policies\TaskPolicy::class,
    App\User::class => App\Policies\UserPolicy::class
];

Routes

Route::group(['middleware' => 'auth'], function() {
  Route::resource('user', 'UserController');
} );

Blade template

@can ( 'view', $user )
// yes
@else
// no
@endcan

UserController.php

public function profile()
{
    return $this->show(Auth::user()->id);
}
public function show($id)
{
    $user = User::find($id);
    return view('user.show', array( 'user'=>$user,'data'=>$this->data ) );
}

The return is always 'false'. Same for calling policy form the controller. Where do I go wrong?

5

5 Answers

6
votes

Answering my own question feels weird, but I hate it when I come across questions without followups.

So after double checking It turned out that if I remove authorizeResource from the constructor:

  public function __construct()
  {
    $this->authorizeResource(User::class);
  }

and check for authorization in the controller function:

  $this->authorize('view',$user);

everything works. I must've missed this part when I added $user as a parameter in the policy function. So the user to be viewed is never passed in the authorizeResource method.

Thanks everyone for taking your time to help me.

2
votes

When you add

public function __construct()
{
   $this->authorizeResource(User::class);
}

to your Controller, you have to edit all your function signatures to match it to the class e.g. your show signature has to change from public function show($id) to public function show(User $user)

After that it should work

1
votes

Just a different approach here to users viewing their own profile.

First, I will create a route for that

Route::group(['middleware' => 'auth'], function() {
    Route::get('profile', 'UserController@profile');
});

Then in the profile function I do

public function profile()
{
    $user = Auth::user();
    return view('profile', compact('user'));
}

This way, user automatically only views their own profile.

Now, if you want to allow some users to view others' profiles, then you can use Policy. Why? Because I think user should ALWAYS be able to view their own profile. But not all users should view other users profiles.

0
votes

Solution:

Change the second parameter from @can( 'view', $user ) to @can( 'view', $subject ) and it will work find.

Why:

Because you're doing it the wrong way.

public function view(User $user, $subject){ 
 return true;
}

Just look carefully the policy view method, first parameter is authenticated user or current user and second parameter is $subject, Since policies organize authorization logic around models.

Policies are classes that organize authorization logic around a particular model or resource. For example, if your application is a blog, you may have a Post model and a corresponding PostPolicy to authorize user actions such as creating or updating posts.

if you want to go further deep inside it.

https://github.com/laravel/framework/blob/5.3/src/Illuminate/Auth/Access/Gate.php#L353

/**
 * Resolve the callback for a policy check.
 *
 * @param  \Illuminate\Contracts\Auth\Authenticatable  $user
 * @param  string  $ability
 * @param  array  $arguments
 * @return callable
 */
protected function resolvePolicyCallback($user, $ability, array $arguments)
{
    return function () use ($user, $ability, $arguments) {
        $instance = $this->getPolicyFor($arguments[0]);

        // If we receive a non-null result from the before method, we will return it
        // as the final result. This will allow developers to override the checks
        // in the policy to return a result for all rules defined in the class.
        if (method_exists($instance, 'before')) {
            if (! is_null($result = $instance->before($user, $ability, ...$arguments))) {
                return $result;
            }
        }

        if (strpos($ability, '-') !== false) {
            $ability = Str::camel($ability);
        }

        // If the first argument is a string, that means they are passing a class name
        // to the policy. We will remove the first argument from this argument list
        // because the policy already knows what type of models it can authorize.
        if (isset($arguments[0]) && is_string($arguments[0])) {
            array_shift($arguments);
        }

        if (! is_callable([$instance, $ability])) {
            return false;
        }

        return $instance->{$ability}($user, ...$arguments);
    };
}

See the last line where it is calling the method with $user and $argument( in our case Model ) is passed.

Laravel Docs for Authorization/Policies

0
votes

It's possible to escape one or more policies methods using options parameter at authorizeResource with except:

public function __construct()
{
   $this->authorizeResource(User::class, 'user', ['except' => ['view']]);
}

This should be on Laravel's documentation, but it isn't. I discovered this just guessing. I think this way it is a better approach thus, by removing authorizeResource method in the construct, it would be necessary to implement the authorization method for each resource action in order to protect the controller.