With laravel we have a few auths setup for us which we can control in the routes file or setup in the controller constructor however I need to find a better way of doing this, and not sure if a route can handle it?
With my users, I allow themselves and the admin to edit them. So my controller looks like this.
function edit($id)
{
$user = User::findOrFail($id);
if(!Auth::user()->isAdmin() && $user->id != Auth::user()->id)
{
return Redirect::route('users.index')->withError('Unable to access that user');
}
return View::make('users.edit', compact('user'));
}
which is ok, but then on my update code I also have to do the same auth/user check to make sure a user or admin can only make changes to themselves.
So we get a double up, in some controllers this is repeated 3 times. Another example of context would be a forum post, the person who posted it or the admin can edit it.
Is there a way with route filters to handle doing this?
This may or may not be of use to you, I have a similar problem in my application where a user can only access their own user profile, but management can access anyone's.
My routes look like:
Route::get('user_profile/{user_id?}', array('as' => 'user_profile', 'uses' => 'UserController#get_user_profile'))->where(array('user_id' => '[0-9]+', 'tab' => '[A-Za-z]+'))->before('auth');
Route::when('user_profile/*', 'management');
This applies a management filter if the user attempts to go to a specific user, it defaults to their own profile if no ID is given.
Route::filter('management', function()
{
if(Helper::managementStatus() === NOT_MANAGEMENT)
return Redirect::route('home')
->with('flash_error', 'You must be logged in as a manager to view this page!');
});
Alternatively you could create a filter something like:
Route::filter('management', function()
{
$user = User::findOrFail(Input::get('user_id'));
if(!Auth::user()->isAdmin() && $user->id != Auth::user()->id)
{
return Redirect::route('users.index')->withError('Unable to access that user');
}
}
And then just apply that filter to the relevant routes.
I hope that's helpful.
Related
I wonder if I can do this in Laravel Route. Let's say I have Admin, Premium and User (which can be login too by using Auth) Middleware. Also, I have controller with methods like this: index, create, edit, delete and I want Admin to be able do all those things, but Premium can only be able to access index method, and User can't access anything in this controller (he can access another controller). I know I can use except or only middleware method like this:
public function __construct()
{
$this->middleware('premium')->only('index');
$this->middleware('admin');
// or maybe $this->middleware('admin')->except('index');
}
but when I try to put these two middlewares in __construct method they will start to conflict each other, it makes sense because index method can be access by Premium but then can't be access by the Admin itself. By the way, my middleware is simply checking:
if (Auth::check()) {
if (Auth::user()->role == 'Admin') {
return $next($request);
}
}
return redirect('/home');
So, back to my question, can I have OR Middleware so I can avoid conflict from multiple middleware (which is must be AND condition when they written at the same controller constructor)?
If you change up the way your logic is thinking a little bit, the answer becomes pretty easy. You can create new middleware that checks if it can access the specific method.
So create the following middleware 'CanAccessIndex':
if (Auth::check()) {
if (Auth::user()->role == 'Admin' || Auth::user()->role == 'Premium') {
return $next($request);
}
}
return redirect('/home');
Then, you can put that middleware on the index function (instead of the premium middleware) and put your admin middleware on everything EXCEPT index. Like so:
public function __construct()
{
$this->middleware('canAccessIndex')->only('index');
$this->middleware('admin')->except('index');
}
That's one way to do it.
You need middleware group for this, and to manage these hierarchy of access layer, you can simply use Route grouping.
I will demo an example of what I mean:
Say you have auth middleware general for authenticated users (i.e everybody), then another called premium for premium member, and admin for the Admin.
Then you'll group based on the access level:
Route::middleware('auth')->group(function(){
Route::middleware('premium')->group(function(){
Route::post('/create', 'HomeController#create')->middleware('admin');
Route::put('/update/{id}', 'HomeController#update')->middleware('admin');
Route::get('/index', 'HomeController#index');
Route::put('/delete/{id}', 'HomeController#delete')->middleware('admin');
});
});
So you can have general check in your middleware with a check. It would have been much easier if you have role level say 3 for admin and 2 for premium member. So we can have for the premium middleware:
public function handle($request, Closure $next)
{
return auth()->user->role >= 2
? $next($request)
: redirect('/home');
}
This is just an example. You can do further check based on your need but more importantly, ensure your admin middleware to checks the exact role level that is allowed.
I'm trying to make a "Profile settings" section in an application.
The thing is, I learned how to do this the "Admin" way, the route would be /users/{user}/edit, the would call the edit method on the controller and it would return the edit view. There I would have a form which the user would patch to the route users/{user} and it would call the update method on the controller.
But I don't want anyone editing other users, so I'd like to know if there's a way to limit this route to the current user only.
Thanks in advance.
Since version 5.1 Laravel has Policies which are exactly what you need.
You can create a new policy by typing in command:
php artisan make:policy UserPolicy
In your UserPolicy class you can include the following method:
public function updateProfile(User $user, User $updatedUser) {
return $user->id === $updatedUser->id;
}
Please note: The first parameter $user is resolved automatically behind the scenes and is the currently logged in user. When checking the policy through the Gate facade in your application you need to pass only the second parameter $updatedUser.
Then you need to register your policy in the AuthServiceProvider:
use Acme\User;
use Acme\Policies\UserPolicy;
...
class AuthServiceProvider extends ServiceProvider {
protected $policies = [
User::class => UserPolicy::class
]
Now when you have your policy registered you can check it across your app using the Gate facade like so:
if(Gate::allows('updateProfile', $user)) {
// Your logic goes here
}
Or the other approach with I like more using the denies method and include it at the beginning of my controller methods and return http error:
public function edit($id) {
if(Gate::denies('updateProfile', $user)) {
abort(403, 'You do not have permissions to access this page!');
}
// The check is passed and your can include your logic
}
You can also check for permissions in your blade files using can and cannot like so:
#can('updateProfile', $user)
// Show something only to the user that can edit the $user's profile
#endcan
For more info check the docs.
you should not need to pass in the user id as there user is already logged in and there for should be able to edit themselves, thus only targetting the logged in user.
So you can use the routes /user/editand /user/updateetc
Just get the current user details like
Auth::user()->id
or something else like
$user = Auth::user();
Thus only the logged in user (themselves) can be edited.
In the view there should be a button or link, on click pass the ID to the desired route that's it.
Example:
For Grabbing the current logged in User id you should do like
$user = Auth::user()->id;
And directly in the route you can get it like
Edit
Now when someone clicks on the Edit Button/Link, the route will look like route/currentuserid.
I'm using Laravel 5 on a Windows dev machine. I want to customize and use the Auth middleware throughout my application, to maintain authentication.
My use case is a standard one. There are two (or three) classes of users - Admin and Regular (regular would be all users that are not admin).
The Admin has the obvious role of backend management, and hence has a separate routing group /admin/, which should redirect an unlogged user to /admin/login. I have set it up like so..
Route::group(['middleware'=>'auth', 'prefix' => 'admin'], function() {
Route::get('login','App\AuthController#getLogin');
Route::post('login','App\AuthController#postLogin');
});
When the login form is posted, how do I ask Auth to add a filter
either that only validate from among those users where 'is_admin' is true?
or ask it to first join a User and a UserRoles table to identify only users with an Admin role?
I recommend you to define another middleware that detects if user is admin instead of modifying the auth. Now add this another middleware to your routes that only admins can access.
Add several middleware to route like this
Route::group(['middleware' => ['auth','admin']], function() {
Middleware will look something like
public function handle($request, Closure $next) {
if (Auth::user()->role == "admin") {
return $next($request);
} else {
return redirect("/")->withMyerror("You are not authorized for this action");
}
}
Why not instead of dealing with the Auth filter and trying to "validate" only on a certain condition, in your login code, just check what's the type of the user?
This is my high level code of doing it:
// get roles which are allowed to login to the admin panel
$roles = $this->userService->adminRoles();
$user = User::whereUsername(Input::get('username'))->whereIn('role_id', $roles)->first();
if (is_null($user)) {
// ...
}
// create our user data for the authentication
$userdata = array(
'username' => Input::get('username'),
'password' => Input::get('password'),
);
// attempt to do the login
// Auth::attempt($userdata) ....
This way you only do it once when you attempt the login and that's it?
What I am looking to achieve here is to have the username set as myapp.com/username. I can achieve this by doing:
/*
* Other route logic, containing static-url pages, such as
* myapp.com/login so that it overrides any usernames
*/
Route::get('/{username}', function($username) {
return $username . "'s profile.";
});
However, I also want to include company pages following the same rule. For example, myapp.com/janes-bakery. But I cannot seem to achieve this because Laravel automatically stops searching for routes if it comes across one that returns nothing. Instead it will just throw a 404 page.
I did think that I could use something similar along these lines:
Route::get('/{slug}', ['as' => 'profile', function($slug) {
$company = \App\Models\Company::where('slug', '=', $slug)->get();
if($company->count() > 0) {
return "Company found.";
}
$user = \App\User::where('username', '=', $slug)->get();
if($user->count() > 0) {
return "User found.";
}
abort(404);
}]);
Which works fine, however I feel as though it's bad practice. Besides that, I cannot access suffix routes based on the company/user. For example myapp.com/jane/friends or myapp.com/janes-bakery/services.
Does anybody have any recommendations on how to go about this? I am finding it very hard to think of any solutions. Thank you in advance!
P.S. It is important the URLs have no correlation, e.g. no myapp.com/company/user (or vice versa), myapp.com/company_name_here and myapp.com/users_name_here are two totally separate things.
You can stick with your route and make another route with company/user pattern. You can do it this way.
Create another route with company and user parameter.
Route::get('{company}/{user}', function($company, $user){
dd($company, $user);
});
And you need to put this route above your profile route because, in laravel, the first route present will be processed before the others routes below.
I'm using a ressource controller and it is possible to edit a user like this: users/edit/8 (8 is the id).
How can I check that only user 8 can edit his account?
Because another user could set any ID in the URL and edit any account.
I could use:
if (!Auth::user()->id === $id) {
//throw exception ...
}
But that is not the point of laravel "dont repeat yourself".
Should I use ressource controllers for user related stuff (like account settings etc) ?
Or just for admin related stuff?
Shouldn't be $id?:
if (!Auth::user()->id === $id) {
// still throw exception? Which one?
}
If you don't want a user to be able to have access to a particular route 'users.edit', then you need to create a filtered route:
Route::get('users/edit/{id}', array(
'as' => 'users.edit',
'before' => 'auth|checkUser',
'uses' => 'UserController#edit'
)
);
Route::filter('checkUser', function($route, $request, $value)
{
if (! Auth::use()->id === $route->parameter('id'))
{
return Redirect::to('operation-not-permitted-route');
}
});
As you are using RESTful controllers, this route should be added before your Route::controller() command.
This is one way. Another one would be to create a new controller just for your profile stuff and create specific routes and filters for this controller.
About using RESTful and Resourceful routes, I always like to point this post from Phil Sturgeon: http://philsturgeon.co.uk/blog/2013/07/beware-the-route-to-evil.