skip to Main Content

I am working on a blogging application in Laravel 8.

The application gives the users rights by assigning them roles. Every role has a set of permissions. There is a many-to-many relationship between roles and permissions.

In the user-rights view, I output each user’s permissions successfully:

 @foreach ($user->role->permissions as $permission)
    <span class="badge bg-primary">{{ $permission->slug }}</span>
 @endforeach

The goal

I am trying to restrict access to the Site settings section of the application, like this:

// Settings routes
Route::group(['prefix' => 'settings', 'middleware' => ['checkUserPermissions:edit-settings']], function() {
    Route::get('/', [SettingsController::class, 'index'])->name('dashboard.settings');
}); 

For this purpose, I have created the checkUserPermissions middleware:

class CheckUserPermissions
{
    /**
     * Handle an incoming request.
     *
     * @param  IlluminateHttpRequest  $request
     * @param  Closure(IlluminateHttpRequest): (IlluminateHttpResponse|IlluminateHttpRedirectResponse)  $next
     * @return IlluminateHttpResponse|IlluminateHttpRedirectResponse
     */

    // Permissions checker
    public function hasPermissionTo($permission) {
        return in_array($permission, Auth::user()->role->permissions->toArray());
    }

    public function handle(Request $request, Closure $next, ...$permissions)
    {
      // Check user permissions
        foreach ($permissions as $permission) {
          if (!$this->hasPermissionTo($permission)) { 
            $permission_label = join(' ',  explode('-', $permission));
            return redirect()->back()->with('error', 'You do not have permission to ' . $permission_label);
          }
        }

        return $next($request);
    }
}

The problem

Although the super admin does have the permission to edit settings, dd(in_array($permission, Auth::user()->role->permissions->toArray())) returns false.

That means that the restriction(s) apply when they should not.

NOTE

dd(Auth::user()->role->permissions->toArray()) returns:

enter image description here

Questions

  1. What causes this bug?
  2. What is the easiest fix?

2

Answers


  1. In your custom middleware, you need to compare the permission to the slugs

    return in_array($permission, Auth::user()->role->permissions()->pluck('slug')->toArray());
    
    Login or Signup to reply.
  2. Since you’re eager loading permissions, Eqloquent will return a collection of models. Inside of the Collection instance, there is a pluck method that will create a new collection of columns.

    Using the toArray method, you will then end up with the expected array that your $permission will match to:

    Auth::user()->role->permissions->pluck('slug')->toArray()
    

    An alternative way would be to use PHP native methods (array_column) to achieve this. You could then return a list back to the view of all of the missing permissions as apposed to a single missing permission. This is untested but should just work out the box:

    public function handle(Request $request, Closure $next, ...$permissionsToTest)
    {
    
        $permissions = array_column(Auth::user()->role->permissions->toArray(), 'slug');
    
        $results = array_map(fn(string $permission): bool => in_array($permission, $permissions), $permissionsToTest);
        $failedResults = array_keys(array_filter($results, fn(bool $result): bool => !$result));
        $missingPermissions = array_map(fn(int $key): string => 'You do not have permission to ' . implode(' ', explode('-', $permissionsToTest[$key])), $failedResults);
    
        # Utilise the built-in "errors"
        return empty($missingPermissions) ? $next($request) : redirect()->back()->with('errors', $missingPermissions);
    }
    

    For a vanilla PHP mock-up example, See it 100% working over at 3v4l.org

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search