I have a Laravel controller with destroying method and middleware with name CheckRole.
public function destroy(Request $request, $id)
{
// code to delete item
}
I am unable to use the $id in middleware CheckRole.
class CheckRole
{
public function handle($request, Closure $next)
{
}
}
And
$request->id and
$request->route('id')
are also not working with the middleware.
How to fix this?
Getting URL Parameters in Middleware
class CheckRole
{
public function handle($request, Closure $next)
{
$id = $request->route()->parameter('id');
}
}
The $id in the destroy function is the id of the resource you want to delete, so that will never be the authenticated user id a better option for checking the role might be an authorization policy instead of a middleware.
Or get the authenticated user id by using: auth()->id() or $request->user()->id
You can also add a method in your User model, and check like that for example:
class User ...
public function isAdmin()
{
return $this->role === 'admin';
}
In the middleware
$request->user()->isAdmin();
--- EDIT
So if you use a resource when you defined your route for example:
Route::resource('user', 'UsersController');
Then in your middleware you can access the id of the user like this:
$request->user
If your route is like this:
Route::delete('/users/{user}', 'UsersController#destroy');
Then again you can get it using the named parameter $request->user. Make sure that whatever you use on your route in the {} that's the name of the parameter you use to get the id from.
There's no real safe way to do this as the user can just change the ID within the form submitted, unless you just want users who can sign in to be an admin. If this is true I would use #nakov's suggestion.
If you want to have a full permission system with roles, etc I would suggestion: https://github.com/spatie/laravel-permission
A good tutorial can be found here but please do not change anything it states with the password: https://scotch.io/tutorials/user-authorization-in-laravel-54-with-spatie-laravel-permission
Related
I'm using Laravel 5.5 and I'm trying to use Gate facade to allow admins to access resources like users. First, I define a gate in AuthServiceProvider.php like following:
Gate::define('view-users', 'App\Policies\UserPolicy#view');
Then, I write view method in Policy class like this:
public function view(Admin $admin, User $user)
{
return true;
}
And, I apply the authorization like following:
//UsersController.php
$user = User::first();
if (Gate::allows('view-users', $user)) {
$users = User::all();
return view('admin.users.list', compact('users'));
}
return abort(403);
I note that, the $user argument is useless variable and I don't need it to perform authorization.
By the way, when I use allows() method of Gate facade, it always returns false. While, when I use denies() instead, these steps work fine.
what's wrong with allows() method?!
However, corresponding to the Laravel Docs, I tested other ways to apply authorization via middleware(), Model or authorize(). But, I got the same result.
Edit:
I should note that I'm using custom guard named web_admin
Thanks for any help.
Change your policy method to this:
public function view(User $user)
{
return $user->isAdmin;
}
The first argument of the policy method is always the current authenticated user. Note that you are not required to pass the currently authenticated user to these methods. Laravel will automatically take care of passing the user into the gate Closure:
if (Gate::allows('view-users')) {
// The current user can view all users...
}
If you want to check if the current user can update a specific user your policy method would be:
public function update(User $authenticatedUser, User $beeingEditedUser)
{
return $authenticatedUser->isAdmin;
}
Then authorize like this:
if (Gate::allows('update-user', $beeingEditedUser)) {
// The current user can update the user...
}
If you're using custom guard (according to your comment), you may have 2 options:
Use forUser method:
use Illuminate\Support\Facades\Auth;
if (Gate::forUser(Auth::guard('web_admin')->user())->allows('view-users')) {
// The current user can view all users...
}
Protecting the routes, specifying the guard:
Route::middleware('auth:web_admin')->group(function () {
Route::get('/users', 'UserController#index');
});
This causes Larvel to set your default auth driver and resolve the auth user based on your custom guard.
it should forbid that the user grabs this url:
?main_title=banner
?main_title=law
?main_title=faq
with this
if(\Auth::user()->hasRole(['super_admin']))
I am going to assume that you are using spatie/laravel-permission based on your example code.
Laravel Permission comes with built-in role middlewares
One of the ways you could use them is by grouping the routes you want to be accessible only by super admins
Route::group(['middleware' => ['role:super_admin']], function () {
// YOUR ROUTES HERE
});
It's always good to using the middlewares ,
So in your case first create a Trait for roles
public function isSuperadmin(){
return Auth::user()->role->role=='superadmin';
}
After that create a middlewar like superadmin for the superuser and in that first include your trait
use App\Traits\Roles;
after that
use Roles;
public function handle($request, Closure $next)
{
if(!$this->isSuperadmin())
{
return back();
}
return $next($request);
}
and just register the middleware in the app/http/kernal.php in protected $routeMiddleware function
'superadmin' => \App\Http\Middleware\superadmin::class,
so it's make your life very easy now you don't need to check the url or role every time , for any url you want to block for other users just use
Route::get('/?main_title=law', 'HomeController#function')->middleware('superadmin')->name('admin-dashboard-home');
so if the user role is superadmin then he is allow to assess the url you can redirect the other users or show the error message :)
I have seen other topics regarding this issue, didn't work out.
So in Laravel 5.4 Route Model Binding, we can bind a route to a model like:
define the route in web.php:
web.php:
Route::get('/users/{user}', UsersController#show);
UsersController#show:
public function show(User $user){
// now we already have access to $user because of route model binding
// so we don't need to use User::find($user), we just return it:
return view(users.show, compact('user'));
}
The above code will work just fine, so in our controller we can return the $user without finding the user, we already have it.
but imagine this:
web.php:
Route::patch('/users/archive', UsersController#archive);
EDITED: now the above line makes a patch route and we don't have {user} in the route url, the user id is being posted via the form.
UsersController#archive:
public function archive(Request $request, User $user){
// how can I access the $user here without using User::find($user);
// I get to this action via a form which is posting `user` as a value like `5`
dd($request->user); // this now echo `5`
// I can do:
// $user = User::find($request->user);
// and it works, but is there a way to not repeat it every time in every action
}
What I have tried:
in RouteServiceProvider::boot() I have:
Route::model('user', 'App\User');
The above is what i have found in Google, but not working.
I would appreciate any kind of help.
EDIT:
It seems it's not called Route Model Binding anymore since we don't have the {user} in the route and that's because my code is not working, the user variable is being posted to the controller and it's only accessible via $request->user.
this is route model binding:
Route::patch('users/{user}/archive', UsersController#archive);
this is not:
Route::patch('users/archive', UsersController#archive);
since we don't have {user} and it's being posted via the form and could be accessed only via $request->user.
(please correct me if I am wrong about the definition of route model binding)
SO:
what I want to achieve in a nutshell: in every request being sent to my UsersController, if I am sending user variable as a post variable, it must be bounded to User::findOrFail($request->user) and then $user must be available in my controller actions.
I want to achieve this because in every action I am repeating myself doing User::findOrFail($request->user) and I don't want to do that, so I want to check in every request if I have a variable name like a model name, they should be bounded.
There's no need to bind explicitly to the User class, so Route::model('user', 'App\User'); should be removed; type-hinting should be enough instead.
public function archive(Request $request, User $user) { ... }
should be working, just make sure you are importing the right User class at the top of the file (use App\User;).
Then the model is in your $user variable (method argument), try dd($user).
It's clear now that since the {user} variable is not in the URI, this is not a route model binding issue. You just want the User instance injected as a parameter based on the contents of the request.
$this->app->bind(User::class, function () {
$user_id = request('user') ?: request()->route('user');
return User::findOrFail($user_id);
});
You could add that to the register method in the AppServiceProvider (or any other registered provider) to have the model injected. I leave it to you to generalize this to other model classes.
You don't even need (Request $request) in your controller.
If you correctly imported User class, as alepeino said, you can access all user values from Model with this syntax $user-><value> for example:
public function archive(User $user) {
$userId = $user->id;
}
According to update.
If you use POST request, you can access it's data with such code request()->get('<variable you send as parameter>')
For example:
public function archive() {
$userId = request()->get('user');
$userInfo = User::find($userId);
//Or as you said
$user = User::findOrFail(request()->get('user'));
}
Can you try this;
public function archive(Request $request, $u = User::find($user){
//now variable $u should point to the user with id from url
}
I have an application which uses Laravel 5's out of the box authentication. I need to require authentication on the show method of a controller ONLY when the field called "approved" is equal to 1.
How can I require authentication using middlewares on a conditional basis such that unauthenticated users can access entries whose approved column is equal to 1, but only authenticated users can see entries where approved is equal to 0.
My constructor currently looks like this:
public function __construct(){
$this->middleware('auth', ['only' => ['edit', 'destroy', 'approve', 'markRecovered']]);
}
You may create your own middleware instead of using Laravel's default auth middleware and in that case, you may create a middleware such as 'checkApproval' using something like this:
php artisan make:middleware checkApproval
Then in your app/Http/Middleware directory you'll find the new middleware created and it'll contain the basic structure including handle method, so now you may erite code inside this middleware's handle method to check the user's state and the approved field and then either redirect to login page if condition doesn't match or allow access. here is a basic idea:
use Illuminate\Contracts\Auth\Guard;
class CheckPermission implements Middleware {
protected $auth;
public function __construct(Guard $auth)
{
$this->auth = $auth;
}
public function handle($request, Closure $next)
{
if($this->auth->guest() && !$this->checkApproval($request))
{
return redirect('login');
}
return $next($request);
}
protected function checkApproval($request)
{
// Get the auth/logged in user
// $user = $request->user();
// Get a parameter from route
// $id = $request->route()->parameter('id')
// Check the approved field here and return true or false
}
}
Now assign the middleware a short-hand key in your app/Http/Kernel.php file. By default, the $routeMiddleware property of this class contains entries for the middleware included with Laravel. To add your own, simply append it to this list and assign it a key to use in your route/controller, for example, checkApproval so in the place of auth you may use checkApproval for the the method view in your controller.
This is an abstract idea, but you can implement one of your own now so check the documentation for more information.
Imagine I have this route:
POST sub.domain.tld/user/{user_id}/order
This creates a new order for user with id user_id.
I then conveniently bind the User model to the route using:
Route::model('user_id', 'User');
So in my OrderController I can do:
public function storeByUser(User $user)
but there is one more requirement: the user needs to be authenticated as himself, therefore I would like to define an auth filter like so:
//create an order for given user by id
Route::post('/user/{user_id}/order', array('before' => 'auth'), 'OrderController#storeByUser')
->where('user_id', '[0-9]+');
But the issue is in the implementation of the auth filter:
Route::filter('auth', function(User $user)
{
//check if $user->id is equal to Auth::user()->id
});
Unfortunately the above is not possible, because a filter only has the parameters $route and $request.
How can I pass the model binding (User) to the auth filter to implement such an authentication scheme?
They fixed this in Laravel 5 with middleware, you simply make this your Controller subclass constructor:
public function __construct()
{
$this->middleware('auth');
}
The result is only authorised users can access any of the methods in this controller.
That being said, to solve your current example it isn't clear if you want users to be able to create orders for other users. If this is not the case then you don't even need the user_id param and just save the order using the user that is Auth::user().