Laravel Roles and Permissions based on Role specific Ability - laravel

I have a project in which I want a Specific page to be viewed by a specific user which have a role of viewing for example I have User 1 that has an Admin Role and the Admin Role has the Ability to View this page in my design I made 3 models Users, Roles, and Abilities
User Model:
<?php
namespace App;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable
{
use Notifiable;
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'name', 'email', 'password','district','area','committee','position',
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
'password', 'remember_token',
];
/**
* The attributes that should be cast to native types.
*
* #var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
];
public function answer()
{
return $this->hasMany('App\Answer');
}
public function roles()
{
return $this->belongsToMany('App\Role');
}
public function hasRole($role)
{
if ($this->roles()->where('name', $role)->first()) {
return true;
}
return false;
}
public function assignRole($role)
{
$this->roles()->save($role);
}
}
Role Model:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Role extends Model
{
protected $fillable = ['name'];
public function abilities()
{
return $this->belongsToMany('App\Ability');
}
public function hasAbility($ability)
{
if ($this->abilities()->where('name', $ability)->first()) {
return true;
}
return false;
}
public function assignAbility($ability)
{
$this->abilities()->save($ability);
}
public function users()
{
return $this->belongsToMany('App\User');
}
}
Ability Model:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Ability extends Model
{
protected $fillable = ['name'];
public function roles()
{
return $this->belongsToMany('App\Role');
}
}
This is my UserPolicy:
<?php
namespace App\Policies;
use App\User;
use App\Role;
use Illuminate\Auth\Access\HandlesAuthorization;
class UserPolicy
{
use HandlesAuthorization;
public function view (Role $role)
{
return $role->hasAbility('view');
}
public function manage (User $user)
{
return true;
}
public function edit (User $user)
{
return true;
}
public function update (User $user)
{
return true;
}
public function add (User $user)
{
return true;
}
}
And the Controller of The Policy
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use App\User;
use App\Role;
class MemberController extends Controller
{
public function index(Role $role)
{
$this->authorize('view', $role);
return view ('members.create')->with('users', User::all());
}
public function manage(User $user)
{
$this->authorize('manage', $user);
return view ('members.manage')->with('users', User::all());
}
public function edit(User $user)
{
$this->authorize('edit', $user);
return view ('members.edit')->with('user', User::all())->with('roles', Role::all());
}
public function update(Request $request, User $user)
{
$this->authorize('update', $user);
$user->roles()->sync($request->roles);
return redirect('/members/edit');
}
public function store(User $user)
{
$this->authorize('add', $user);
$this->validate(request(), [
'name' => ['required', 'string', 'max:255'],
'district' => ['required', 'string', 'max:255'],
'area' => ['required', 'string', 'max:255'],
'committee' => ['required', 'string', 'max:255'],
'position' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
'password' => ['required', 'string', 'min:8', 'confirmed'],
]);
$data = request()->all();
$member = new User();
$member->name = $data['name'];
$member->district = $data['district'];
$member->area = $data['area'];
$member->committee = $data['committee'];
$member->position = $data['position'];
$member->email = $data['email'];
$member->password = Hash::make($data['password']);
$member->save();
return redirect('/members/create');
}
}
The index function should be the one related to the function view in the UserPolicy
and this is the can located in my blade.php file
#can('view', \App\Role::class)
<li class="">
<a class="" href="/members/create">
<span><i class="fa fa-user-plus" aria-hidden="true"></i></span>
<span>Add Member</span>
</a>
</li>
#endcan
in the policy when I link it to the name of the role of the logged in user everything works just fine but if I want to link it to an ability of the role it doesn't work so any idea on how the View Function in the UserPolicy should be implemented ?

The first parameter that is passed to the policy is the authenticated User, not its Role. I don't think it works. Maybe if you reimplement using an EXISTS query.
public function view (User $user)
{
return $user->roles()->whereHas('abilities', function ($ability) {
$ability->where('name', 'view');
})
->exists();
}
->exists() turns the query into an EXISTS query, which will return a boolean value if the query finds anything without having to return any rows.
https://laravel.com/docs/7.x/queries#aggregates
You could put that logic into an User method.
# User model
public function hasAbility($ability): bool
{
return $this->roles()->whereHas('abilities', function ($ability) {
$ability->where('name', 'view');
})
->exists();
}
public function view (User $user)
{
return $user->hasAbility('view');
}

Related

How to modify a relationship?

My app has 2 main modules which are Foo and Bar. It also has 3 types of role: admin, manager & staff.
Each user is assigned to a supervisor, so that every supervisor will have some subordinates assigned to him/her.
For example, staff1 is supervised by manager1 whom is also supervised by admin1.
The current practice for this relationship is implemented in both modules. Therefore each supervisor is in charge for the subordinates in the matter of their Foo and Bar.
User.php
<?php
namespace App;
use Spatie\Permission\Traits\HasRoles;
use Illuminate\Support\Str;
class User {
protected $fillable = ['name','email','password','supervisor_id'];
protected $appends = ['role'];
public function getRoleAttribute(){
return $this->roles[0];
}
public function getNameAttribute($value){
return Str::title($value);
}
public function parent(){
return $this->hasOne('App\UserStructure', 'user_id');
}
public function scopeSupervisor($query){
return $query->where('id', $this->supervisor_id)->first();
}
public function foo(){
return $this->hasMany(Foo::class, 'user_id', 'id');
}
public function bar(){
return $this->hasMany(Bar::class, 'user_id', 'id');
}
}
UserStructure.php
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class UserStructure extends Model{
protected $fillable = ['user_id', 'parent_id'];
public function user(){
return $this->belongsTo('App\User', 'user_id');
}
}
Role.php
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Role extends Model{
protected $fillable = ['name'];
public function roles(){
return $this->belongsTo('App\User', 'role');
}
}
RoleAndPermission.php
<?php
use Illuminate\Database\Seeder;
use Spatie\Permission\Models\Role;
use Spatie\Permission\Models\Permission;
class RolesAndPermissionsSeeder extends Seeder{
public function run(){
$roles = ['admin','manager','staff'];
app()[\Spatie\Permission\PermissionRegistrar::class]->forgetCachedPermissions();
foreach ($roles as $role) {
Role::updateOrCreate(['name' => $role]);
}
}
}
UserSeeder.php
<?php
use Illuminate\Database\Seeder;
use App\User;
use App\UserStructure;
class UserSeeder extends Seeder{
public function run(){
$items = [
['role'=> 'admin',
'name'=> 'admin',
'email'=> 'admin#myapp.com',
'password'=> 'password',
'supervisor_id'=> 1],
['role'=> 'manager',
'name'=> 'manager',
'email'=> 'manager#myapp.com',
'password'=> 'password',
'supervisor_id'=> 1],
['role'=> 'staff',
'name'=> 'staff',
'email'=> 'staff#myapp.com',
'password'=> 'password',
'supervisor_id'=> 2],
];
foreach($items as $data) {
$user = User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => Hash::make($data['password']),
'supervisor_id' => $data['supervisor_id'],
]);
$user->assignRole($data['role']);
}
$userStructure = [
['parent_id'=> 0, 'user_id'=> 1],
['parent_id'=> 1, 'user_id'=> 2],
['parent_id'=> 2, 'user_id'=> 3]
];
UserStructure::insert($userStructure);
}
}
My question is, how do I modify this relationship accordingly so that any supervisor [admin/ manager] will be assigned to the subordinate [manager/ staff] of one module only?
(E.g:
In Foo module, staff1 is supervised by manager1.
While in Bar module, he will be supervised by manager2.)
As i understand, you can add one column to the Role model (like type), and then assign roles to users with a condition

Argument 1 passed to ::showAll() must be an instance of Collection, instance ofCollection given, called BuyerProductController.php on line 23

I don't understand this mistake, can someone help me?
I am taking a course on ApiRestfull and the code works for the teacher but I can't get it to work for me
I am using laravel 5.8*
The error he shows me is this: Error:
Argument 1 passed to App\Http\Controllers\ApiController::showAll() must be an instance of Illuminate\Database\Eloquent\Collection, instance of Illuminate\Support\Collection given, called in C:\laragon\www\udemy-apirestfull\app\Http\Controllers\Buyer\BuyerProductController.php on line 23
BuyerProductController.php:
<?php
namespace App\Http\Controllers\Buyer;
use App\Buyer;
use Illuminate\Http\Request;
use App\Http\Controllers\ApiController;
class BuyerProductController extends ApiController
{
/**
* Display a listing of the resource.
*
* #return \Illuminate\Http\Response
*/
public function index(Buyer $buyer)
{
$products = $buyer->transactions()->with('product')
->get()
->pluck('product');
return $this->showAll($products);
}
}
ApiController:
<?php
namespace App\Http\Controllers;
use App\Traits\ApiResponser;
use Illuminate\Http\Request;
class ApiController extends Controller
{
use ApiResponser;
}
ApiResponser:
<?php
namespace App\Traits;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Collection;
trait ApiResponser
{
private function successResponse($data, $code)
{
return response()->json($data, $code);
}
protected function errorResponse($message, $code)
{
return response()->json(['error' => $message, 'code' => $code], $code);
}
protected function showAll(Collection $collection, $code = 200)
{
return $this->successResponse(['data' => $collection], $code);
}
protected function showOne(Model $instance, $code = 200)
{
return $this->successResponse(['data' => $instance], $code);
}
}
Buyer model:
<?php
namespace App;
use App\Transaction;
use App\Scopes\BuyerScope;
class Buyer extends User
{
protected static function boot()
{
parent::boot();
static::addGlobalScope(new BuyerScope);
}
public function transactions()
{
return $this->hasMany(Transaction::class);
}
}
Product Model:
<?php
namespace App;
use App\Seller;
use App\Category;
use App\Transaction;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Product extends Model
{
use SoftDeletes;
const PRODUCTO_DISPONIBLE = 'disponible';
const PRODUCTO_NO_DISPONIBLE = 'no disponible';
protected $dates = ['deleted_at'];
protected $fillable = [
'name',
'description',
'quantity',
'status',
'image',
'seller_id',
];
public function estaDisponible()
{
return $this->status == Product::PRODUCTO_DISPONIBLE;
}
public function seller()
{
return $this->belongsTo(Seller::class);
}
public function transactions()
{
return $this->hasMany(Transaction::class);
}
public function categories()
{
return $this->belongsToMany(Category::class);
}
}
Transaction Model:
<?php
namespace App;
use App\Buyer;
use App\Product;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Transaction extends Model
{
use SoftDeletes;
protected $dates = ['deleted_at'];
protected $fillable = [
'quantity',
'buyer_id',
'product_id',
];
public function buyer()
{
return $this->belongsTo(Buyer::class);
}
public function product()
{
return $this->belongsTo(Product::class);
}
}
Illuminate\Database\Eloquent\Collection extends Illuminate\Support\Collection
So if not mandatory, you can change the signature of showAll method to accept Illuminate\Support\Collection as a parameter
There will be no error if the parameter supplied will be an instance of Illuminate\Database\Eloquent\Collection
<?php
namespace App\Traits;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection; //Changed here
trait ApiResponser
{
private function successResponse($data, $code)
{
return response()->json($data, $code);
}
protected function errorResponse($message, $code)
{
return response()->json(['error' => $message, 'code' => $code], $code);
}
protected function showAll(Collection $collection, $code = 200)
{
return $this->successResponse(['data' => $collection], $code);
}
protected function showOne(Model $instance, $code = 200)
{
return $this->successResponse(['data' => $instance], $code);
}
}

Not found for some routes

I have a problem with some of my routes in Laravel. this my code in web.php file:
Route::group(['namespace' => 'Admin', 'middleware' => ['auth:web']], function () {
Route::get('/admin/audio/create/{audio?}', 'AdminAudioController#create')->name('admin.audioCreate');
Route::get('/admin/article/create/{article?}', 'AdminArticleController#create')->name('admin.articleCreate');
}
and this my link in blade
<i class="fa fa-edit"></i>
<i class="fa fa-edit"></i>
and this are my Controllers:
AdminAudioController
<?php
namespace App\Http\Controllers\Admin;
use App\Article;
use App\Http\Requests\ArticleRequest;
class AdminArticleController extends AdminController
{
public function index()
{
$articleList = Article::where('removed', false)->latest()->paginate(10);
return view('admin.article.archive', compact('articleList'));
}
public function create(Article $article = null)
{
return view('admin.article.create', compact('article'));
}
}
AdminArticleController
<?php
namespace App\Http\Controllers\Admin;
use App\Article;
use App\Http\Requests\ArticleRequest;
class AdminArticleController extends AdminController
{
public function index()
{
$articleList = Article::where('removed', false)->latest()->paginate(10);
return view('admin.article.archive', compact('articleList'));
}
public function create(Article $article = null)
{
return view('admin.article.create', compact('article'));
}
}
but my second link with name "admin.articleCreate" doesn't work and get "404 not found" what should I do?
and this is my article model
class Article extends Model
{
protected $primaryKey = 'articleId';
use Sluggable;
protected $fillable = [
'title',
'subTitle1', 'subTitle2',
'image',
'description',
'body',
'enable',
];
protected $casts = [
'image' => 'array'
];
/**
* Return the sluggable configuration array for this model.
*
* #return array
*/
public function sluggable(): array
{
return [
'slug' => [
'source' => 'title'
]
];
}
public function getRouteKeyName()
{
return 'slug';
}
}
When you call the method create(Article $article = null) on your controller, Laravel uses Model Binding to resolve your model and the model binding uses the method you have added to your model
public function getRouteKeyName()
{
return 'slug'; // by default it will be $primaryKey which is 'id'
}
In short, Laravel will try to use slug to find your model while your giving him articleId
So to fix it you have few options
Using the slug in the URL (the one I would recommend)
// blade.php
<i class="fa fa-edit"></i>
Using the primary articleId in the URL
// blade.php
<i class="fa fa-edit"></i>
// Article.php.php
public function getRouteKeyName()
{
return 'articleId';
}
Using a query
// blade.php
<i class="fa fa-edit"></i>
//Controller.php
public function create($article = null)
{
$article = Article::where('YOUR_FIELD', $article)->firstOrFail();
return view('admin.article.create', compact('article'));
}
you have code
return view('admin.article.create', compact('$article'));
but need
return view('admin.article.create', compact('article'));
I can see you have mentioned $article in side compact.
Can you please check once, I think the create method should look like this:
public function create(Article $article = null)
{
return view('admin.article.create', compact('article'));
}

InvalidArgumentException route notdefined

I have error like (1/1) InvalidArgumentException
Route [home] not defined. whenever i used the store function but i'm pretty sure that i use the redirect method right what could be the possible error, all i wanted was to redirect to home once the store method is done.
web.php
<?php
Route::get('/', function () {
return view('main');
});
Route::get('/create', 'BuildingController#createBuilding');
Route::post('/store', 'BuildingController#store');
Route::post('home', 'BuildingController#getAllBuilding');
Building.php
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Building extends Model
{
public $timestamps = false;
protected $fillable = [
'id',
'building_name',
'building_information',
'building_image'
];
}
BuildingController.php
<?php
namespace App\Http\Controllers;
use App\Building;
use Image;
use Illuminate\Http\Request;
use App\Repositories\Building\BuildingRepository;
class BuildingController extends Controller
{
/**
* Display a listing of the resource.
*
* #return \Illuminate\Http\Response
*/
private $building;
public function __construct(BuildingRepository $building)
{
$this->building = $building;
}
public function createBuilding()
{
return view('building.create');
}
public function store(Request $request)
{
$this->validate($request, array(
'building_name'=>'required',
'building_information'=>'required',
'building_image' => 'required'
));
$image = $request->file('building_image');
$filename = time() . '.' . $image->getClientOriginalExtension();
$location = public_path('images/' .$filename);
Image::make($image)->resize(800,400)->save($location);
$buildings = array('building_name' => $request->building_name,
'building_information' => $request->building_information,
'building_image' => $filename);
$this->building->create($buildings);
return redirect()->route('home');
}
public function getAllBuilding()
{
$buildings = $this->building->getAll();
return view('building.home')->with('buildings', $buildings);
}
public function getSpecificRecord()
{
$buildings = $this->building->getById(1);
return view('building.show')->with('buildings', $buildings);
}
}
EloquentBuilding.php
<?php
namespace App\Repositories\Building;
use \App\Building;
class EloquentBuilding implements BuildingRepository
{
private $model;
public function __construct(Building $model)
{
$this->model = $model;
}
public function getById($id)
{
return $this->model->findOrFail($id);
}
public function getAll()
{
return $this->model->all();
}
public function create(array $attributes)
{
return $this->model->create($attributes);
}
public function update($id, array $attributes)
{
}
public function delete($id)
{
}
}
BuildingRepository.php
<?php
namespace App\Repositories\Building;
interface BuildingRepository
{
public function getById($id);
public function getAll();
public function create(array $attributes);
public function update($id, array $attributes);
public function delete($id);
}
Since you're using route(), you need to name the route. Also, make it get:
Route::get('home', 'BuildingController#getAllBuilding')->name('home');
Or:
Route::get('home', ['as' => 'home', 'uses' => 'BuildingController#getAllBuilding']);
You are trying to use route with post, replace it with get and also add/specify name attribute to call route using name.
Route::get('home', 'BuildingController#getAllBuilding')->name('home');
OR
Route::get('home', ['as' => 'home', 'uses' => 'BuildingController#getAllBuilding']);
Above both are comes with same output...

Argument 1 passed to Illuminate\Auth\Guard::login() must implement interface Illuminate\Auth\UserInterface, null given open:

I have facebook login which uses socialite library. The error in the question occurs when the callback occurs.
Here is my "USER" model
<?php
namespace App;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Model;
class User extends Model implements Authenticatable
{
//use Illuminate\Contracts\Auth\Authenticatable;
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'name', 'email', 'password',
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
'password', 'remember_token',
];
use \Illuminate\Auth\Authenticatable;
public function posts()
{
return $this->hasMany('App\Post');
}
public function likes()
{
return $this->hasMany('App\Like');
}
}
The Socialite logins are handled by SocialAuthController and what i understood from the error is , auth()->login($user); , null is passed to the login("NULL"). Here is the code of SocialAuthController. What's the mistake i have made here and how to fix this. thanks in advance
<?php
namespace App\Http\Controllers;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Http\Request;
use App\Http\Requests;
use App\Http\Controllers\Controller;
use Socialite;
use App\SocialAccountService;
class SocialAuthController extends Controller
{
public function redirect($provider)
{
return Socialite::driver($provider)->redirect();
}
use \Illuminate\Auth\Authenticatable;
public function callback(SocialAccountService $service , $provider)
{
$user = $service->createOrGetUser(Socialite::driver($provider));
auth()->login($user);
return redirect()->to('/home');
}
}
The below is the handling service that will try to register user or log in if account already exists.
Here is the code of SocialAccountService.php
<?php
namespace App;
use Laravel\Socialite\Contracts\Provider;
class SocialAccountService
{
public function createOrGetUser(Provider $provider)
{
$providerUser = $provider->user();
$providerName = class_basename($provider);
$account = SocialAccount::whereProvider($providerName)
->whereProviderUserId($providerUser->getId())
->first();
if ($account) {
return $account->user;
} else {
$account = new SocialAccount([
'provider_user_id' => $providerUser->getId(),
'provider' => $providerName
]);
$user = User::whereEmail($providerUser->getEmail())->first();
if (!$user) {
$user = User::create([
'email' => $providerUser->getEmail(),
'name' => $providerUser->getName(),
]);
}
$account->user()->associate($user);
$account->save();
return $user;
}
}
}
This will try to find provider's account in the system and if it is not present it will create new user. This method will also try to associate social account with the email address in case that user already has an account.
My wild guess is that createOrGetUser() returns NULL because the SocialAccount does not have a user. So what could do is change the if condition in that method to check if the $account has a user:
public function createOrGetUser(Provider $provider)
{
...
if ( $account && property_exists($account, 'user') && $account->user ) {
return $account->user;
} else {
...

Resources