Laravel Policy - Wrong User transmitted from Controller - laravel

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().

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.

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

User Policy Problem When I define two User models

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 :)

Auth::user() returns null on Module __construct()

I created a new Module named Article using laravel-modules. Some backend routes needed authentication and i added auth middleware and an additional permission view_backend. I am using https://github.com/spatie/laravel-permission package for role-permissions.
the issue is when i try to access the route admin/article/posts it prompts me the login as expected. But after login it show null on __construct() method for Auth::user();
I added web middleware as mentioned on #204 but it did not solve the issue. Can you please guide me to resolve this? My project is on Laravel 5.6 and using the latest version of Laravel-Modules
Route::group(['namespace' => 'Modules\Article\Http\Controllers\Backend', 'as' => 'backend.article.', 'middleware' => ['web', 'auth', 'can:view_backend'], 'prefix' => 'admin/article'], function () {
Route::resource("posts", "PostsController");
});
My project is hosted at Github, https://github.com/nasirkhan/laravel-starter/tree/module
First of all, add Spatie Middleware to your kernel:
protected $routeMiddleware = [
// ...
'role' => \Spatie\Permission\Middlewares\RoleMiddleware::class,
'permission' => \Spatie\Permission\Middlewares\PermissionMiddleware::class,
];
Then in your controller check for permission or roles:
public function __construct(Request $request)
{
$this->middleware(['permission: order.index']);
}
Now you can access to your authenticated with $request->user() like:
public function create(Request $request)
{
if ($request->user()->hasRole('admin')) {
// return view("carmodel.create", ["manufacturers"=>$manufacturers]);
} else {
return view("admin.error", ['code'=>'001','msg'=>'err']);
}
}
According to the docs:
In previous versions of Laravel, you could access session variables or the authenticated user in your controller's constructor. This was never intended to be an explicit feature of the framework. In Laravel 5.3, you can't access the session or authenticated user in your controller's constructor because the middleware has not run yet.
As an alternative, you may define a Closure based middleware directly
in your controller's constructor. Before using this feature, make sure
that your application is running Laravel 5.3.4 or above:
public function __construct()
{
$this->middleware(function ($request, $next) {
$this->projects = Auth::user()->projects;
return $next($request);
});
}
Or you could typehint it:
public function index(Request $request)
{
$projects = $request->user()->projects;
$value = $request->session()->get('key');
}
Docs

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.

Resources