Laravel Permissions and Roles with Gate/Can - laravel

at the moment i do a tutorial called "roles and permissions in laravel" from codecourse.com.
In part 4 of the tutorial i have the problem i get everytime the result true.
If i ask for the permission "edit posts" it should show me true, if i ask for the permission "delete posts" it should show me false.
I checked the database relationship, but there is no relationship between the user and the permission "delete posts".
Only if i ask for a permission that not exist like "blabla" (i mean not exist in the database) i got false.
I believe he is only checking is there a permission with this name and not checking have the user the permission.
web.php
Route::get('/', function (\illuminate\Http\Request $request) {
$user = $request->user();
dump($user->can("delete posts"));});
HasPermissionTrait.php
trait HasPermissionsTrait {
public function hasRole(...$roles)
{
foreach ($roles as $role) {
if ($this->roles->contains('name', $role)) {
return true;
}
}
return false;
}
public function hasPermissionTo($permission) {
//Check has permission through role
return $this->hasPermission($permission);
}
protected function hasPermission($permission) {
return (bool) $this->permissions->where('name', $permission->name);
}
public function roles() {
return $this->belongsToMany(Role::class, 'users_roles');
}
public function permissions() {
return $this->belongsToMany(Permission::class, 'users_permissions');
}
PermissionsServiceProvider.php
public function boot()
{
Permission::get()->map(function ($permission) {
Gate::define($permission->name, function ($user) use ($permission) {
return $user->hasPermissionTo($permission);
});
});
}
/**
* Register the application services.
*
* #return void
*/
public function register()
{
//
}
you can watch my full code here -> https://github.com/RahmanG/joko
On this image you can see the Auth. There is no permission "delete posts". But you can see the Gate is giving true.
https://imgur.com/a/gf923
Thank you for supporting

i got already the answer. Thank you to tykus from laracasts.com
tykus:
Looks like your trait might have a mistake:
protected function hasPermission($permission) {
return (boolean) $this->permissions->where('name', $permission->name);
}
$this->permissions->where('name', $permission->name) returns a Builder instance, and an object cast to a boolean always is true.
You probably need to return a count or first - something that would be falsey if there was no permission, e.g.
protected function hasPermission($permission) {
return (boolean) $this->permissions->where('name', $permission->name)->count();
}

Related

Same check for all policy functions

Almost for all models, the policy functions look like this example:
public function index(User $user)
{
return $user->hasPermission('view-post');
}
The hasPermission exists in the User model:
public function hasPermission($permission){
$roleId = Auth()->user()->role_id;
$id = $this->select('p.id')->join('role_permissions AS rp', 'rp.role_id', 'users.role_id')->join('permissions AS p', 'p.id', 'rp.permission_id')->where('rp.role_id', roleId)->where('permission', $permission)->get()->toArray();
return $id ? true : false;
}
Then in postController:
public function index(User $user){
if ($user->cannot('index', Post::class)) {
return redirect('/');
}
}
Is there a way to avoid repeating the policy example at the top for every model and every policy function?
I want to be able to use the method in both controller and view to check against permissions

Trying to get property 'designation_name' of non-object in Laravel 8

I want to show the user role from my user role table, but I can't.
User.php
public function role()
{
return $this->belongsTo('App\Models\Designation');
}
UserController
public function index()
{
$user = User::all();
return view('dashboard2', compact('user'));
}
View
<h1>{{ Auth::user()->role->designation_name}}</h1>
You actually should have shared more details about the relationships. But I will give an answer based on an assumption. It seems like there is a one-to-one relationship betwen User and Role as well as Role and Designation. So you want to reach designation from user. Based on that:
//User.php
public function role()
{
return $this->hasOne(Role::class);
}
//Role.php
public function user()
{
return $this->belongsTo(User::class);
}
public function designation()
{
return $this->hasOne(Designation::class);
}
//Designation.php
public function role()
{
return $this->belongsTo(Role::class);
}
// Controller
public function index()
{
// If you need only the auth user's data, you don't
// need adding relationships into the query.
$users = User::with('role.desgination')->all();
return view('dashboard2', compact('users'));
}
// View
// For auth user:
// I haven't used this way before,
// but technically it should work.
auth()->role()->designation()->name;
// For users collection:
// Make sure you added the query to the controller.
#foreach($users as $user)
$user->role->desgination->name
#endforeach

Laravel: prevent changing other users' items

I have three models. I want to avoid that users can change the todo's from todolists belonging to other users.
class User extends Authenticatable
{
public function todolists()
{
return $this->hasMany('App\Todolist');
}
public function todos()
{
return $this->hasManyThrough('App\Todo', 'App\Todolist');
}
}
class Todolist extends Model
{
public function user()
{
return $this->belongsTo('App\User');
}
public function todos()
{
return $this->hasMany('App\Todo');
}
}
class Todo extends Model
{
protected $casts = [
'completed' => 'boolean',
];
public function todolist()
{
return $this->belongsTo('App\Todolist');
}
}
To avoid users can view other users' todolists and todo items, I have implemented the following:
public function getTodosForTodolist(Todolist $todolist)
{
if (Auth::user()->id == $todolist->user_id) {
$todos = Todo::where('todolist_id', $todolist->id )->get();
return view('todo/index', ['todos' => $todos);
}
else {
abort(403, 'Unauthorized action.');
}
}
Next step is to prevent that users can edit other users' todo items. Currently in the TodoController I have simply the following:
public function edit(Todo $todo)
{
if (Auth::user()->todos->id == $todo->todolist->id) {
return view('todo/edit', ['todo' => $todo]);
}
}
This gives the following error:
Property [id] does not exist on this collection instance.
The error is because the current user has multiple todos. So I changed my code as follows.
public function edit(Todo $todo)
{
if (Auth::user()->todos->first()->id == $todo->todolist->id) {
return view('todo/edit', ['todo' => $todo]);
}
abort('403', 'Unauthorized action.');
}
This works but it just feels very wrong to do this as such.
What would be a better way to accomplish that users' can view/edit/delete items belonging to other users?
I suggest that you use policies for your Todo and TodoList models and a scope to restrict todos to one user to prevent duplicated code within your app:
class ToDoListPolicy
{
public function view(User $user, TodoList $post)
{
return $user->id === $todolist->user_id;
}
}
class ToDoPolicy
{
public function edit(User $user, Todo $toDo)
{
$toDo->loadMissing('todolist');
return $user->id === $toDo->todolist->user_id;
}
}
Register them in your AuthServiceProvider.php
class AuthServiceProvider extends ServiceProvider
{
protected $policies = [
TodoList::class => ToDoListPolicy::class,
Todo::class => ToDoPolicy::class
];
}
and then use them in your actions:
public function getTodosForTodolist(Todolist $toDoList)
{
$this->authorize('view', $toDoList);
$toDoList->loadMissing('todos');
return view('todo.index', ['todos' => $toDoList->todos);
}
class ToDoController extends Controller
{
public function edit(Todo $toDo)
{
$this->authorize('edit', $toDo);
return view('todo.edit', compact('toDo'));
}
}
And a scope to restrict the query to a specific user:
class Todo extends Model {
// ...
public function scopeByUser(Builder $query, ?User $user = null)
{
if (! $user) {
$user = Auth::user();
}
$query->whereHas('todolist', function (Builder $toDoListQuery) use ($user) {
$toDoListQuery->where('user_id', $user->id);
});
}
}
Answer to your questions in the comments.
Q1: I had to put Auth::user()->can('view', $todolist); in an if-else clause for it to work. Guess this is the way it works?
Q2: what is the difference between $this->authorize('edit', $todo) and Auth::user()->can('edit', $todo)?
Sorry, that was a mistake on my side. Auth::user()->can() returns a boolean whereas $this->authorize() (which is a method of the AuthorizesRequests trait usually included in the BaseController) throws an exception if the authorization failed.
If you want to let each user work only with his/her own Todos then adding a Global Scope is what you are looking for. This implementation will let your application feel that Todos ( of users other than the logged one ) does not exist.
Global Scopes can be used for many models which means it will reduce boiler plate code.
https://laravel.com/docs/7.x/eloquent#global-scopes

Laravel: How the best way for redirect a default laravel user to admin page if user is admin or to user page if user is not admin?

The User model has an isAdmin() function to check if the user is an administrator. What to do next?
The best way is to use default laravel LoginController located under App\Http\Controllers\Auth\LoginController.
In that controller you can override authenticated method that is injected from AuthenticatesUsers trait, by simply adding that method in LoginController:
* #param Request $request
* #param $user
*/
protected function authenticated(Request $request, $user)
{
if ($user->isAdmin()) {
return redirect(route('admin-dashboard'));
//redirect to desired place since user is admin.
}
}
Best practique is whit roles, and you add role on your Routes::middleware,
Route::group(['middleware' => ['auth', 'roles:admin']], function () {
//Your routes
});
Kernel.php
'roles' => Middleware\CheckRole::class,
Create middleware
namespace App\Http\Middleware;
use Closure;
class CheckRole
{
public function handle($request, Closure $next, ...$role)
{
if ($request->user()->hasAnyRole($role)) {
return $next($request);
}
return redirect(route('hour'));
}
}
create function on User model
public function authorizeRole($role)
{
if ($this->hasAnyRole($role)) {
return true;
}
return abort(401, 'Unauthorized.');
}
public function hasAnyRole($roles)
{
if (is_array($roles)) {
foreach ($roles as $role) {
if ($this->hasRole($role)) {
return true;
}
}
} else {
if ($this->hasRole($roles)) {
return true;
}
}
return false;
}
public function hasRole($role)
{
if ($this->role()->where('name', $role)->first()) {
return true;
}
return false;
}
public function role()
{
return $this->belongsTo('App\Role')->withDefault();
}
And Role model
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Role extends Model
{
public function user()
{
return $this->hasMany('App\User');
}
}
Is more code, but best way for this action

Laravel 5 checkif Admin middleware logic not correct (one to many with role)

I'm building a Laravel 5 REST-webapp. Now I would like to add an Admi. I have done so by setting an one-to-many relation (user can have 1 role, 1 role can have many users) table
Like so:
public function role()
{
return $this->hasOne(Role::class);
}
This works, I have a user with an role_id.
But now I'm stuck writing the middleware and using this role_id correctly.
Now, my admin middleware is
public function handle($request, Closure $next, $guard = null)
{
if (Auth::User()->isRole('Admin')) {
return $next($request);
}
return redirect()->guest('login');
}
And in my User Model I have:
public function isRole($roleName)
{
if ($this->with('role') == $roleName)
{
print_r($this->with('role.name'));
}
return false;
}
But this code just sends me to the home page.
My Middleware Logic is wrong (everything else is setup correctly).
Kind regards
You should probably change:
public function isRole($roleName)
{
if ($this->with('role') == $roleName)
{
print_r($this->with('role.name'));
}
return false;
}
into:
public function isRole($roleName)
{
return $this->role->name == $roleName;
}
to make it work
EDIT
You should also probably change your relationship from:
public function role()
{
return $this->hasOne(Role::class);
}
into
public function role()
{
return $this->belongsTo(Role::class);
}
assuming you have role_id column in users table

Resources