Could you show you Policy method? You're not giving any attributes to you denies function call.
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'));
}
}
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();
}
you should define the policy like so:
' $gate->define('view-users', function ($user, $post) { return $user->id === $post->user_id; }); '
http://laravel.com/docs/master/authorization#defining-abilities
I'm just going off of the this: https://laracasts.com/series/whats-new-in-laravel-5-1/episodes/16
@xtremer360 Everything looks correct to me. Can you show us the hasRole method?
Remember it has to return a boolean value. So as @thomaskim asked what is your hasRole method?
@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();
}
}
Are you still using a pivot table or did you add a role_id column in the users table?
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 I added a role_id column in my user's table.
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.
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;
}
}
@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.
The user has one role.
Can you explain how I would need to change the whole trait then?
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.
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.
Is there anyone else that knows why its accepting all users and not blocking out users that aren't supposed to have access?
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();
}
}
@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.
Odd thing is every time I dd role in the top of the hasRole function it always returns a collection.
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.
I mostly understand that but the fact that it's still passing a collection instead of a single hasOne relationship relationship.
@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.
@xtremer360 So if you dd($this->role), you get a collection? Can you post your model/trait again?
<?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;
}
}
Is there anyone else in addition to @thomaskim that might have an understanding of what needs to be corrected?
@xtremer360 If you dd($this) inside the hasRole method, what do you get?
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
@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?
Please or to participate in this conversation.