Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

jrdavidson's avatar

ACL Role Permissions

I decided to make another post about this since this is somewhat of a separate issue. With my Gate calls it works for the user specified but it actually doesn't block out the users that don't have access and I"m not quite sure what I am doing wrong.

<?php

namespace App\Http\Controllers;

use Gate;
use App\Http\Controllers\Controller;
use App\Http\Requests;
use App\User;
use Illuminate\Http\Request;

class UsersController extends Controller
{
    /**
     * Display a listing of the users.
     *
     * @return Response
     */
    public function index()
    {
//        $user = auth()->loginUsingId(1)->toArray();
//        auth()->logout();
        auth()->loginUsingId(8);
        if (Gate::denies('view-users')) {
            abort(403, 'Nope');
        }

        $users = User::all();

        return view('users.index', compact('users'));
    }
}

0 likes
37 replies
BartJan's avatar

Could you show you Policy method? You're not giving any attributes to you denies function call.

jrdavidson's avatar

I was performing all of this inside the AuthServiceProvider.

public function boot(GateContract $gate)
    {
        parent::registerPolicies($gate);

        foreach($this->getPermissions() as $permission) {
            $gate->define($permission->name, function($user) use ($permission) {
                return $user->hasRole($permission->roles);
            });
        }
    }

    protected function getPermissions()
    {
        return Permission::with('roles')->get();
    }
jekinney's avatar

Remember it has to return a boolean value. So as @thomaskim asked what is your hasRole method?

jrdavidson's avatar

@jekinney @thomaskim The only difference between my app and @JeffreyWay 's example app is the fact that his the user can have multiple roles and mine a user can only have one role.

<?php

namespace App;

trait HasRoles {
    public function role()
    {
        return $this->belongsTo(Role::class);
    }

    public function assignRole($role)
    {
        return $this->roles()->save(
            Role::whereName($role)->firstOrFail()
        );
    }

    public function hasRole($role)
    {
        if (is_string($role)) {
            return $this->roles->contains('name', $role);
        }

        return !! $role->intersect($this->roles)->count();
    }
}
jekinney's avatar

Are you still using a pivot table or did you add a role_id column in the users table?

jekinney's avatar

In the above trait your relationship is role but on the assignRloes you are trying to access roles(). That is issue number one already.

jekinney's avatar

You need to re code everything after your relationship to reflect that. The code is assuming you are still using a pivot table.

For flexibility I still use a many to many if a user can have one role. Just incase later I need to allow multiple roles.

I just add a protected method that checks the user only has one role. And fire that check after assigning a role. Utilizing the sync method will detach all current roles and add any roles passed. Just make sure it's one role.

jrdavidson's avatar

I don't think my project will need to allow for multiple roles, however still something isn't right.

<?php

namespace App\Http\Controllers;

use Gate;
use App\Http\Controllers\Controller;
use App\Http\Requests;
use App\User;
use Illuminate\Http\Request;

class UsersController extends Controller
{
    /**
     * Display a listing of the users.
     *
     * @return Response
     */
    public function index()
    {
//        auth()->loginUsingId(1); // Has a role of 5 (super admin) 
//        auth()->logout();
        auth()->loginUsingId(2); // Has a role of 1 (basic user)
        if (Gate::denies('view-users')) {
            abort(403, 'Nope');
        }

        $users = User::all();

        return view('users.index', compact('users'));
    }
<?php

namespace App;

use Illuminate\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Auth\Passwords\CanResetPassword;
use Illuminate\Foundation\Auth\Access\Authorizable;
use App\Presenters\Contracts\PresentableInterface;
use App\Presenters\PresentableTrait;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
use Illuminate\Database\Eloquent\SoftDeletes;

class User extends Model implements AuthenticatableContract,
                                    AuthorizableContract,
                                    CanResetPasswordContract,
                                    PresentableInterface
{
    use Authenticatable, Authorizable, CanResetPassword, HasRoles, PresentableTrait, SoftDeletes;

    /**
     * The database table used by the model.
     *
     * @var string
     */
    protected $table = 'users';

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = ['name', 'email', 'password'];

    /**
     * The attributes excluded from the model's JSON form.
     *
     * @var array
     */
    protected $hidden = ['password', 'remember_token'];

    /**
     * Get the presenter that is to be used by the model.
     *
     * @var array
     */
    protected $presenter = 'App\Presenters\User';

    public function role()
    {
        return $this->hasOne(Role::class);
    }

    public function owns($related)
    {
        return $this->id == $related->user_id;
    }
}

thomaskim's avatar

@xtremer360 You have a conflicting relationship setup. You have this in your User model.

    public function role()
    {
        return $this->hasOne(Role::class);
    }

Then, in your HasRoles trait, you have this:

    public function role()
    {
        return $this->belongsTo(Role::class);
    }

Does the user have one role or does he belong to a role? Also, like jekinney said, you basically need to change the entire HasRoles class to reflect the proper relationship.

jrdavidson's avatar

Can you explain how I would need to change the whole trait then?

jekinney's avatar

That's the fun of programming. Not to sound mean, but if someone codes if for you what will you learn? I know for me this was the first frustration in coding for me as it is a requirement for even the most basic site, but it is an advanced topic.

I think you need to read up and do some more work first. That's just my 2 cents.

The only problem I see is your relationships. Read up and/or watch a few more videos. Try it out a few more times.

jrdavidson's avatar

Its okay. I changed my relationship in the trait and it still didn't work so I don't know what the problem is. I may scrap this whole idea since its not working for me.

jrdavidson's avatar

Is there anyone else that knows why its accepting all users and not blocking out users that aren't supposed to have access?

jrdavidson's avatar

Any further thoughts on this anyone?

<?php

namespace App\Providers;

use App\Permission;
use App\User;
use App\Post;
use App\Policies\PostPolicy;
use App\Policies\UserPolicy;
use Illuminate\Contracts\Auth\Access\Gate as GateContract;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;

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

    /**
     * Register any application authentication / authorization services.
     *
     * @param  \Illuminate\Contracts\Auth\Access\Gate  $gate
     * @return void
     */
    public function boot(GateContract $gate)
    {
        parent::registerPolicies($gate);

        foreach($this->getPermissions() as $permission) {
            $gate->define($permission->name, function($user) use ($permission) {
//                dd($user->hasRole($permission->roles));
                return $user->hasRole($permission->roles);
            });
        }
    }

    protected function getPermissions()
    {
        return Permission::with('roles')->get();
//        dd(Permission::with('roles')->get());
    }
}

<?php

namespace App;

trait HasRoles {

    public function role()
    {
        return $this->hasOne(Role::class);
    }

    public function assignRole($role)
    {
        return $this->role()->save(
            Role::whereName($role)->firstOrFail()
        );
    }

    public function hasRole($role)
    {
        if (is_string($role)) {
            return $this->role->contains('name', $role);
        }
        return !! $role->intersect($this->roles)->count();
    }
}
thomaskim's avatar

@xtremer360 You're on the right path. If you set up your relationship in the trait though, remember to remove the one in the model.

Also, for your hasRole function, it has to reflect your new relationship. Jeffrey used the contains and intersect methods because he got a collection of roles. If you setup a hasOne relationship, you won't retrieve a collection. You'll retrieve a single Role model. Actually, you should be getting an undefined method error message since a model shouldn't have those methods.

Something like this might be better:

public function hasRole($role)
{
    // If you pass a role name, just compare 
    // it with the user's role's name.
    if (is_string($role)) {
        return $this->role->name == $role;
    }

    // Otherwise, it seems like you're passing a 
    // collection of roles so here, you can use
    // that collection's contain method.
    return $role->contains('name', $this->role->name);
}

This assumes that you are only passing a string name or a collection of roles. I would also setup another check to see if you pass in a model, but that's up to you and how you're building things.

jrdavidson's avatar

Odd thing is every time I dd role in the top of the hasRole function it always returns a collection.

thomaskim's avatar

Yes, you are passing a collection of roles here:

        foreach($this->getPermissions() as $permission) {
            $gate->define($permission->name, function($user) use ($permission) {
                return $user->hasRole($permission->roles);
            });
        }

But the user only has one role.

In other words, in the hasRole method, $role is a collection. $this->role should be a single Role model.

jrdavidson's avatar

I mostly understand that but the fact that it's still passing a collection instead of a single hasOne relationship relationship.

jrdavidson's avatar

@thomaskim can you help me see why it's passing a collection of I have the relationship set up correctly in the trait and removed the relationship from the user model.

jrdavidson's avatar

@thomaskim

<?php

namespace App;

trait HasRoles {

    public function role()
    {
        return $this->hasOne(Role::class);
    }

    public function assignRole($role)
    {
        return $this->role()->save(
            Role::whereName($role)->firstOrFail()
        );
    }

    public function hasRole($role)
    {
        if (is_string($role)) {
            return $this->role->name == $role;
        }
        
        return $role->contains('name', $this->role->name);
    }
}
<?php

namespace App;

use Illuminate\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Auth\Passwords\CanResetPassword;
use Illuminate\Foundation\Auth\Access\Authorizable;
use App\Presenters\Contracts\PresentableInterface;
use App\Presenters\PresentableTrait;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
use Illuminate\Database\Eloquent\SoftDeletes;

class User extends Model implements AuthenticatableContract,
                                    AuthorizableContract,
                                    CanResetPasswordContract,
                                    PresentableInterface
{
    use Authenticatable, Authorizable, CanResetPassword, HasRoles, PresentableTrait, SoftDeletes;

    /**
     * The database table used by the model.
     *
     * @var string
     */
    protected $table = 'users';

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = ['name', 'email', 'password'];

    /**
     * The attributes excluded from the model's JSON form.
     *
     * @var array
     */
    protected $hidden = ['password', 'remember_token'];

    /**
     * Get the presenter that is to be used by the model.
     *
     * @var array
     */
    protected $presenter = 'App\Presenters\User';

    public function owns($related)
    {
        return $this->id == $related->user_id;
    }
}

jrdavidson's avatar

Is there anyone else in addition to @thomaskim that might have an understanding of what needs to be corrected?

jrdavidson's avatar

@thomaskim

public function hasRole($role)
    {
        dd($this);
        if (is_string($role)) {
            return $this->role->name == $role;
        }

        return $role->contains('name', $this->role->name);
    }

gives me ...

User {#328 ▼
  #table: "users"
  #fillable: array:3 [▶]
  #hidden: array:2 [▶]
  #presenter: "App\Presenters\User"
  #connection: null
  #primaryKey: "id"
  #perPage: 15
  +incrementing: true
  +timestamps: true
  #attributes: array:11 [▶]
  #original: array:11 [▼
    "id" => 3
    "first_name" => "Nannie"
    "last_name" => "Little"
    "username" => "anita89"
    "email_address" => "beffertz@balistreri.com"
    "password" => "$2y$10$3bQR5Mgk/6Kzq.KuQf7KveWDj5up7KVL/gdmvLJ3GTgSBFFbAp6jS"
    "role_id" => 1
    "remember_token" => "g7fRJ8qwYR"
    "created_at" => "2015-09-14 13:51:45"
    "updated_at" => "2015-09-14 13:51:45"
    "deleted_at" => null
  ]
  #relations: []
  #visible: []
  #appends: []
  #guarded: array:1 [▶]
  #dates: []
  #dateFormat: null
  #casts: []
  #touches: []
  #observables: []
  #with: []
  #morphClass: null
  +exists: true
  +wasRecentlyCreated: false
  #forceDeleting: false

thomaskim's avatar

@xtremer360 Honestly, I don't know what to say. I copied/pasted your code. It works just fine for me. The user should return only one role.

Can you dd($role) and dd($this->role) and post what you get?

Next

Please or to participate in this conversation.