User Policy Problem When I define two User models - laravel

I'm using Policies in Laravel. When I use two user models in policy it would not access me! However, both $user and $player return me correct data, individually. How can I handle it? What is my mistake?
in Controller:
$this->authorize('modifyTournamentRegistration', $player);
in Policy:
public function modifyTournamentRegistration(User $user, User $player)
{
return $user->id === $player->id || $user->inRole('admin');
}

I found out that it is because of Input variable type in Policy. I added User class in AuthServiceProvider:
protected $policies = [
'App\Model' => 'App\Policies\ModelPolicy',
User::class => UserPolicy::class,
];
Then, I defined the function in UserPolicy:
public function modifyPlayerTournamentRegistration(User $user, User $player)
{
return $player->id === $user->id || $user->inRole('admin');
}
And used it in Controller:
$this->authorize('modifyTournamentRegistration', $player);
It works as well :)

Related

Laravel: policy for viewAny always returns false

I looked at other similar questions but they are not helping.
My policy method viewAny for User always returns false even if the function body is literally just return true, so I'm not sure what's happening.
On my AuthServiceProvider, I have:
protected $policies = [
'App\Account' => 'App\Policies\AccountPolicy',
'App\User' => 'App\Policies\UserPolicy'
];
UserPolicy:
public function viewAny(User $user, Account $account) {
// $role = $user->roles()->wherePivot('account_id', $account->id)->first();
return true;
}
UserController
protected $account;
// just sharing this so you know where $account comes from
public function __construct()
{
$this->middleware(function($req, $next){
$this->account = Account::find($req->session()->get('account_id'));
return $next($req);
});
}
public function index()
{
$this->authorize('viewAny', $this->account);
$users = $this->account->users;
$usersWithRoles = [];
foreach ($users as $user) {
$usersWithRoles['user'] = $user;
$usersWithRoles['user']['role'] = $this->account->roles()->wherePivot('user_id', $user->id)->first();
}
return view('users.index', [
'users' => $users
]);
}
One thing to note is that my User routes are inside a grouping. So the actual uri looks like /account/users
Route::prefix('account')->group(function() {
/**
* Users
*/
Route::resource('users', 'UserController')->only([
'index', 'destroy', 'update', 'edit'
]);
});
The index route always returns false and a 403 page
The line
$this->authorize('viewAny', $this->account);
will make laravel look for a viewAny function in the AccountPolicy, not in the UserPolicy.
Basically you ask: can user view any account?
Therefore it's an Account policy.
For an admin you might need: can user view any user?
Than its a User policy.

Laravel Spatie Roles and permissions - error on Update

I need help for my Laravel application.
I use spatie roles and permissions.
When i create the user, assign roles is no problem. When i will update the same user, there is the following error:
[2019-12-06 08:44:36] local.ERROR: The given role or permission should use guard instead of `web`. {"userId":1,"exception":"[object] (Spatie\\Permission\\Exceptions\\GuardDoesNotMatch(code: 0): The given role or permission should use guard instead of web. at /home/vagrant/code/test/vendor/spatie/laravel-permission/src/Exceptions/GuardDoesNotMatch.php:12)
public function store(StoreUserRequest $request)
{
$user = $this->repo->create( $request->all());
$user->assignRole($request->roles);
return $user;
}
public function update(UpdateUserRequest $request, User $user)
{
$user = $this->repo->update($user, $request->all());
$user->assignRole($request->roles);
return $user;
}
I added protected $guard_name = 'web'; to the User Model but still the same problem.
What is wrong? Is there a problme with the userId?
On update method use syncRoles instead of assignRole
When you assign role for a user the model path App\User insert into table model_has_roles if you change the model folders spatie give you an error. I think this code may help you more.
don't missing add HasRoles
class User extends Authenticatable
{
use Notifiable, HasRoles;
...
}
make sure you import use App\User;
UserController
public function update(Request $request, $id)
{
$this->validate($request, [
'name' => 'required',
'email' => 'required|email|unique:users,email,'.$id,
'password' => 'same:confirm-password',
'roles' => 'required'
]);
$input = $request->all();
if(!empty($input['password'])){
$input['password'] = Hash::make($input['password']);
}else{
$input = array_except($input,array('password'));
}
$user = User::find($id);
$user->update($input);
DB::table('model_has_roles')->where('model_id',$id)->delete();
$user->assignRole($request->input('roles'));
return redirect()->route('users.index')
->with('success','User updated successfully');
}

How do I check User Role before Logging in A user with Spatie's Laravel-Permission?

I'm using Spatie's "Laravel Permission" Package for the ACL. Everything is working perfectly. I now want to allow only the User roles 2 and 3 to Login.
Previously, before using "Laravel Permission" package I had only this "role" column on my table and I could easily Log In a user with this line of code on Login Controller's credentials method.
protected function credentials(Request $request)
{
$credentials = $request->only($this->username(), 'password');
//$credentials['role'] = '1';
return $credentials;
}
$credentials = $request->only($this->username(), 'password');
$credentials['role'] = '1';
How do I allow Login only for the 2 and 3 User Roles?
You can override authenticated() in LoginController and check user role. Pretty simple.
protected function authenticated(Request $request, $user)
{
//Check user role, if it is not admin then logout
if(!$user->hasRole(['Admin', 'Super-Admin']))
{
$this->guard()->logout();
$request->session()->invalidate();
return redirect('/login')->withErrors('You are unauthorized to login');
}
}
You could go with the workaround as follow:
If you're using the default LoginController from the App\Http\Controllers\Auth folder, then override its attemptLogin() method that comes from the Trait used.
protected function attemptLogin(Request $request)
{
if( $this->guard()->attempt(
$this->credentials($request), $request->filled('remember')
) ) { // Credential auth was successful
// Get user model
$user = Auth::user();
return $user->hasRole([2, 3]); // Check if user has role ids 2 or 3
}
return false;
}
hasRoles() method comes from the HasRoles trait used for the User model.
Or you could override the Laravel credentials during login. In your default LoginController from the App\Http\Controllers\Auth folder, then override its credentials(Request $request) method that comes from the Trait used.
Override it to look something similar to this
protected function credentials(Request $request)
{
return [ 'email' => $request-> { this-> username() }, 'password' -> $request -> password, 'role_id' => [ '1', '2' ] ];
This is presuming you have a role_id in your user model. Worked for me.return

redirect to different views in laravel logged in users based on role mentioned by using if condition in laravel controller

In laravel i have student,parent ,employee(Teacher,Librarian,warden) roles with different permissions...based on user role it should redirect to different blade files when user logged in..my problem is if user parent or student it is redirecting to different dashboards but whenever user is teacher or other it does not logged in but in users table already user exist.
below is my LoginController code
LoginController.php:
public function login(Request $request){
if(Auth::attempt([
'email'=>$request->email,
'username'=>$request->username,
'password'=>$request->password,
]))
{
$user=User::where('username',$request->username)->first();
$usertype=$user->user_type;
$username=$user->username;
$roles=DB::table('roles')->where('id','=',$usertype)->first();
$rolename=$roles->role_name;
if($rolename=="student"){
$student=DB::table('students')->where('stud_adm_id','=',$username)->first();
$classid=$student->class_id;
$sectionid=$student->section_id;
$class=DB::table('classtables')->where('id',$classid)->first();
$section=DB::table('sections')->where('id',$sectionid)->first();
return view('studentdashboard',compact('student','class','section'));
}elseif($rolename=="Teacher"){
$employeedetails=DB::table('employees')->where('employee_fname','=',$username)->first();
return view('teacherdashboard',compact('employeedetails'));
}
elseif($rolename=="parent"){
$parentdetails=DB::table('parents')->where('mother_phone','=',$username)->first();
$stateid=$parentdetails->state;
$state=DB::table('states')->where('state_id','=',$stateid)->first();
return view('parentdashboard',compact('parentdetails','state'));
}
}else{
return redirect()->back();
}
}
my roles are mentioned in role table and that id stored in users table
Thanks in advance...
You better create a middleware to check user role, and based on the role redirect user to different pages!
Run the command below to create a middleware that checks user's role.
php artisan make:middleware CheckRoleMiddleware
The command will create a file under App\Http\Middleware named CheckRoleMiddleware this class will come a predefined method handle() there you can place the logic that checks user's role and redirects them to different pages example:
<?php namespace App\Http\Middleware;
use Closure;
class CheckRoleMiddleware {
public function handle($request, Closure $next)
{
//User role is admin
if ( Auth::check() && Auth::user()->isAdmin() )
{
return $next($request);
}
//If user role is student
if(Auth::check() && auth()->user()->role === 'student')
{
return view('studentDashboard');
or route('routeName');
}
//If user role is teacher
if(Auth::check() && auth()->user()->role ==='teacher')
{
return view('teacherDashboard');
or route('routeName');
}
//default redirect
return redirect('home');
}
}
And don't forget to add CheckRoleMiddleware to App\Http\Kernel.php
protected $routeMiddleware = [
'auth' => 'App\Http\Middleware\Authenticate',
'auth.basic' => 'Illuminate\Auth\Middleware\AuthenticateWithBasicAuth',
'guest' => 'App\Http\Middleware\RedirectIfAuthenticated',
'user-role' => 'App\Http\Middleware\CheckRoleMiddleware', // this line right here
];
Lets just ignore everything about how this stuff should work and how it does by default and take what you have.
Do you see a problem here:
if ($rolename == "student")
elseif ($rolename == "Teacher")
elseif ($rolename == "parent")
If not, lets try it this way:
student
Teacher
parent
Which one is not like the others? And which one was the one that "was not working" correctly?
I would like to assume you have these roles named in a consistent fashion, so Teacher should be teacher.
You can do all of what you want to do with the default LoginController by overriding a method or two.
You should not be returning views from processing routes. POST REDIRECT GET. POST comes in, return a REDIRECT, that causes a GET request (which most likely is going to return a view).
For validating the login credentials:
protected function validateLogin(Request $request)
{
$this->validate($request, [
'email' => '...',
'username' => '...',
'password' => '...',
]);
}
For what credentials should be used from the request:
protected function credentials(Request $request)
{
return $request->only('email', 'username', 'password');
}
The response to return after successful authentication:
protected function authenticated(Request $request, $user)
{
// do your role checking here
// return a redirect response to where you want that particular user type to go
// return redirect()->route(...);
}
Everything related to building the views and gathering that data is its own route. All you have to do is return a redirect for the login flow. Where you redirect to is responsible for gathering the data needed to display a view.

Laravel Policy - Wrong User transmitted from Controller

I registered a Policy to access User's Profiles (view, edit).
I either allow it if:
The Profile you are trying to view belongs to you
You have the Permission "edit any profile".
so this is my view()-Function in the Policy:
public function view(User $user)
{
debug('User in Controller: '.$user->id);
return (Auth::user()->id === $user->id) || Auth::user()->can('edit any profile');
}
This is method to show the profile view taken from the ProfileController:
public function show(User $user) {
debug('User in Controller: '.$user->id);
$this->authorize('view', $user);
return view('profile.index')->with([
"user" => $user,
]);
}
And finally the route:
Route::get('/profile/{user}', 'ProfileController#show')->name('profile');
Of course, the Policy has been registered in the AuthServiceProvider:
protected $policies = [
'App\Model' => 'App\Policies\ModelPolicy',
User::class => ProfilePolicy::class,
];
Basically the controller transmits the wrong user to the policy. Here are both messages from the respective debug()'s:
So I don't like to answer my own questions but I found out what the problem was. The first Parameter in a policy function is always an instance of the current user.
So you can simply have this in your policy:
public function view(User $currentUser, User $user)
{
return $currentUser->is($user) || $currentUser->can('edit any profile');
}
Laravel automatically sets $currentUser to Auth::user().

Resources