Laravel Gate : Authorization doesn't work - php

I am trying to make laravel basic authorization. I'm using gate for laravel authorization.
Table structure
User Table, Permission Table, Role, role_permission table
user : id, name , password, email
permission : id, name
role:id , name
role_permission: id, role_id, permission_id
authServiceProvider
public function boot(GateContract $gate)
{
$this->registerPolicies();
foreach($this->getPermissions() as $permission)
{
$gate->define($permission->name,function($user) use($permission){
return $user->role->id == $permission->role_id;
});
}
}
public function getPermissions()
{
$permissions = \DB::table('role_permission')
->join('permissions', 'permissions.id', '=', 'role_permission.permission_id')
->select('role_permission.*','permissions.*')
->get();
return $permissions;
}
It doesn't work properly means I can't access the route though it's there in permission table with the appropriate user.

You should not access database from service provider. Always try to keep your service provider simple. Please follow the bellow steps to serve your purpose.
AuthServiceProvider.class
public function boot(GateContract $gate)
{
$this->registerPolicies($gate);
$gate->before(function($user, $ability) {
return $user->hasPermission($ability);
});
}
Now add the following methods in App\User model.
public function hasPermission($name)
{
$permission = Permission::where('name','=', $name)->first();
if(! $permission) {
return false;
}
return $this->hasRole($permission->roles);
}
public function hasRole($role)
{
if (is_string($role)) {
return $this->roles->contains('name', $role);
}
return (bool) $role->intersect($this->roles)->count();
}
Hope this will work to serve your purpose.

Related

Get total permissions from users & roles belongsToMany

I have 3 models:
User
Role
Permission
Note:
a user can have direct permissions
a role can have permissions
a user can have roles
I'm trying to get the total permissions a user has, either through their direct permissions or through their roles permissions. So I need to combine both into 1 collection and count the total.
I've set up the belongsToMany relationships for User and Role:
public function permissions()
{
return $this->belongsToMany('App\Permission');
}
How do I do this?
You need to use the hasManyThrough relation
Here is the link to the documentation: Eloquent Documentation
so you would do something like this:
public function permissions()
{
$directPermissions = $this->belongsToMany('App\Permission');
$rolePermissions = $this->hasManyThrough('App\Permissions', 'App\Role');
return $directPermissions->merge($rolePermissions);
}
I figured it out via flatMap.
i am not sure but it will help
open your user model
public function roles()
{
return $this->belongsToMany(Role::class);
}
public function permissions()
{
return $this->belongsToMany(Permission::class);
}
public function hasRole(...$roles)
{
foreach($roles as $role)
{
if($this->roles->contains('name',$role))
{
return true;
}
}
return false;
}
public function hasPermission($permission)
{
return $this->hasPermissionThroughRole($permission) || (bool) $this->permissions->where('name',$permission->name)->count();
}
public function hasPermissionThroughRole($permission)
{
foreach($permission->roles as $role)
{
if($this->roles->contains($role))
{
return true;
}
}
return false;
}
Then open your Role Model and add these
public function permissions()
{
return $this->belongsToMany(Permission::class);
}
public function users()
{
return $this->belongsToMany(User::class);
}
and open your permission model
public function roles()
{
return $this->belongsToMany(Role::class);
}
public function users()
{
return $this->belongsToMany(User::class);
}
and finaly to boot all the permission to roles and user
run the command php artisan make:provider PermissionServiceProvider
open you service provider created newely and
add
use App\Permission;
use Illuminate\Support\Facades\Gate;
add the code under the boot method
Permission::get()->map(function ($permission)
{
Gate::define($permission->name, function ($user) use ($permission)
{
return $user->hasPermission($permission);
});
});
hope it helps if you find any difficulties please comment below

Laravel permissions can() function | Authorization

I have users, app_roles, app_permissions, app_permission_app_role, app_role_user.
The tables are self explanatory, I am creating permissions, Then assigning that permissions to new role on role creation, And then i assigns roles to users.
i check permission of the authenticated user like :
#if(auth()->user()->can('some route name'))
Html...
#endif
The above condition checks if the user have access to that content or not based of the assigned role as we know that the role have permissions, And the can('some route name') parameter is a route name. Its working fine.
Where i am stuck !!!
The table app_role_user had user_id, app_role_id, Now i added another column organization_id... (Consider Organizations as groups, Where a user can be a member of that groups, And the owner of the group assigns single role(Can't assign multiple role) to that user). Because now the user can switch between organization and the user can have different roles in different organizations.
I have to clear path for the :
#if(auth()->user()->can('some route name'))
Html...
#endif
Note : : Auth::user()->current_org->id show the id of the organization in which the user is in right now
As well as currently i am saving role_id, user_id, organization_id in app_role_user table.
Here is my AuthServiceProvider class,
I am Dynamically registering permissions with Laravel's Gate :
public function boot(GateContract $gate)
{
$this->registerPolicies();
$this->registerAllPermissions($gate);
}
protected function getPermissions() {
return $this->app->make('App\Repositories\PermissionRepository')->withRoles();
}
private function registerAllPermissions($gate) {
if (Schema::hasTable('app_permissions') and Schema::hasTable('users') and Schema::hasTable('app_roles')) {
cache()->forget('app_permissions_with_roles');
foreach ($this->getPermissions() as $permission) {
$gate->define($permission->name, function ($user) use ($permission) {
return $user->hasPermission($permission);
});
}
}
}
Here is PermissionRepository class :
class PermissionRepository
{
protected $model;
public function __construct(AppPermission $model)
{
$this->model = $model;
}
public function all(){
return $this->model->all();
}
public function withRoles(){
$model = $this->model;
$permissions = cache()->remember('app_permissions_with_roles', 1*60*24, function() use($model) {
return $model->with('roles')->get();
});
return $permissions;
}
}
And here is HasRoles trait having hasPermission(AppPermission $permission) because AuthServiceProvider class needs it in registerAllPermissions.
trait HasRoles {
public function assignRole($role)
{
return $this->roles()->save(
AppRole::whereName($role)->firstOrFail()
);
}
public function hasRole($role)
{
if (is_string($role)) {
return $this->roles->contains('name', $role);
}
return !! $role->intersect($this->roles)->count();
}
public function hasPermission(AppPermission $permission)
{
return $this->hasRole($permission->roles);
}
}
What should i do, I have tried many conditions but nothing worked at all.
Looking forward to hear from you guys.
Thanks for the read, Need serious attention please.
You can try like this
User Model
//add organization_id as pivot field
public function roles(){
return $this->belongsToMany(AppRole::class)->withPivot('organization_id');
}
//define a function
public function orgRoles($orgId){
return $this->roles()->wherePivot('organization_id', $orgId)->get();
}
Now in trait modify hasRole function
public function hasRole($role)
{
$orgId = Auth::user()->current_org->id;
if (is_string($role)) {
return $this->orgRoles($orgId)->contains('name', $role);
}
return !! $role->intersect($this->orgRoles($orgId))->count();
}

How to check user Permissions using Custom Middleware in Laravel

I'm developing a Laravel ACL System. My base Table's are users,roles,permissions and pivot tables are role_user,role_permission,user_permission.
I want to check User Permissions using my custom middleware HasPermission. I have tried this way but it's not working properly. every user can access the all the permissions which have or have not.
Now, How can I solve the issue. Please see my code sample.
My Controller.
function __construct()
{
$this->middleware('auth');
$this->middleware('HasPermission:Role_Read|Role_Update|Role_Delete');
}
My Middleware.
class HasPermission
{
public function handle($request, Closure $next,$permissions)
{
$permissions_array = explode('|', $permissions);
// $user = $this->auth->user();
foreach($permissions_array as $permission){
if(!$request->user()->hasPermission($permission)){
return $next($request);
}
}
return redirect()->back();
}
}
and, my User Model method.
public function user_permissions()
{
return $this->belongsToMany(Permission::class,'user_permission');
}
public function hasPermission(string $permission)
{
if($this->user_permissions()->where('name', $permission)->first())
{
return true;
}
else
{
return false;
}
}
Best way to do is that you need to introduce an new service provider and in that you can check the authorization and permissions.
I made a test project (last year) for db driven permission and I used service provider.
That's the perfect way to implement.
Basically !$request->user()->hasPermission($permission) is saying if the user associated with the request does not have this permission the middleware passes, however this is not what you want. Here's what you should do:
If you need the user to have one of the stated permissions you need to do:
class HasPermission
{
public function handle($request, Closure $next,$permissions)
{
$permissions_array = explode('|', $permissions);
foreach($permissions_array as $permission){
if ($request->user()->hasPermission($permission)){
return $next($request);
}
}
return redirect()->back();
}
}
If you want the user to have all stated permissions you need to do:
class HasPermission
{
public function handle($request, Closure $next,$permissions)
{
$permissions_array = explode('|', $permissions);
foreach($permissions_array as $permission){
if (!$request->user()->hasPermission($permission)){
return redirect()->back();
}
}
return $next($request);
}
}
As an added note if you want to do this in a more elegant way you can do:
class HasPermission
{
public function handle($request, Closure $next, ...$permissions_array)
{
//Function body from above without the explode part
}
}
And
function __construct()
{
$this->middleware('auth');
$this->middleware('HasPermission:Role_Read,Role_Update,Role_Delete');
}
If you use commas then the framework will split the string into arguments for you .
In my case i just added simple function to get permissions from database and then check it Middleware. Check this code:
// Add new function to get permissions from database
public static function user_permissions($user) {
$permissions=DB::table('permissions')->where('user_id', $user)->first();
return $permissions;
}
// In Middleware check your permissions
if(Auth::guest())
{
return redirect('/');
}
elseif(Functions::user_permissions(Auth::user()->id)->user_managment != 1) {
return redirect('/');
} else {
return $next($request);
}
In web.php/api.php:
Route::middleware('hasPermission')->group(function() { // for all routes
Route::get('/article', [ArticleController::class, 'index'])->name('article.index');
});
in middleWare:
class HasPermission
{
public function handle($request, Closure $next)
{
$routeName = Request::route()->getName();
$permission = $user->permissions()->where('route_name', $routeName)->first();
if ( ! empty($permission)){
return redirect()->back();
}
return $next($request);
}
}

Laravel Eloquent relation between models

I need help with querying the result of relation between models. Two models are defined, User and Role model.
User:
public function roles()
{
return $this->belongsToMany('App\Role', 'role_users');
}
public function hasAccess(array $permissions) : bool
{
foreach ($this->roles as $role) {
if ($role->hasAccess($permissions)) {
return true;
}
}
return false;
}
public function inRole(string $slug)
{
return $this->roles()->where('slug', $slug)->count() == 1;
}
Role:
public function users()
{
return $this->hasMany('App\User', 'role_users');
}
public function hasAccess(array $permissions) : bool
{
foreach ($permissions as $permission) {
if ($this->hasPermission($permission)) {
return true;
}
}
return false;
}
private function hasPermission(string $permission) : bool
{
return $this->permissions[$permission] ?? false;
}
Also, a pivot table called role_users is defined. In the roles database several roles are pre-defined by seeding (Admin, Editor, Author).
I want to query the users by its roles, for example,
$editors = App\User::roles(2)->orderBy('name', 'asc')->get()
where in roles database the id of editor is 2. I got the error
PHP Deprecated: Non-static method App/User::roles()
How to solve this? Note: I'm using Laravel 5.6 and new to Laravel and framework. I tried to refer to docs but it confused me.
Thanks in advance.
You have to use whereHas(..):
$editors = \App\User::whereHas('roles', function ($q) {
$q->where('id', 2);
})->orderBy('name', 'asc')->get()
#devk is correct but here you have another way to get the data of user by role.
\App\Role::where('id', 2)->with('users')->get();

Laravel _ How to inject a query into Auth::user()

I'm making a blog with laravel. When I look into user authentication, I have a few issues here. I have 2 tables, one is users: id, name, ... the other is role: user_id, privilege .. I need to check whether a user is admin or not, I will need a function like isAdmin() or a $isAdmin attribute. This is my function placed in the app/providers/AuthServiceProvider.php:
private static $isAdmin;
public static function isAdmin() {
if (isset(self::$isAdmin)) {
return self::$isAdmin;
}
$user_privilege = DB::table('role')
->select('privilege')
->where([
['privilege', '=', 'admin'],
['user_id', '=', Auth::user()->id],
])
->get()
->first();
self::$isAdmin = isset($user_privilege->privilege);
return self::$isAdmin;
}
This code works fine, but this will require two queries to the database to check the user's admin rights. So I wanted to find a way to inject a query into Auth :: user () so that only one query would retrieve all the stuff I wanted. I'm a beginner with laravel. Can you help me?
I assume that user can have only one role. You can create isAdmin() method in the User model:
public function isAdmin()
{
return auth()->user()->role->privilege === 'admin';
}
Define the relationship if you didn't do that yet:
public function role()
{
return $this->hasOne(Role::class);
}
Then use it with auth()->user()->isAdmin().
If a user can have many roles:
public function isAdmin()
{
auth()->user()->loadMissing('roles');
return auth()->user()->roles->contains('admin');
}
And the relationship:
public function roles()
{
return $this->hasMany(Role::class);
}
On your User model define an isAdmin method:
public function isAdmin() {
// simplified your query here
return $this->hasRole('admin');
}
Then it will be accessible on the Auth guard like:
Auth::user()->isAdmin();

Categories