I have a Laravel application. In the routes file, I have
Route::group(['prefix' => 'user'], function () {
Route::group(['middleware' => ['auth', 'roles'], 'roles' => ['buyer']], function() {
Route::get('dashboard/buyer', ['as' => 'buyer_dashboard', 'uses' => 'User\Buyer\DashboardController#index']);
});
Route::group(['middleware' => ['auth', 'roles'], 'roles' => ['seller']], function() {
Route::get('dashboard/seller', ['as' => 'seller_dashboard', 'uses' => 'User\Seller\DashboardController#index']);
});
});
I have a middleware that basically checks if the id as supplied in the route, is the same as the current logged in user. If this is not the case, I return an error page. The reason for having this is that I want to prevent that a user can access the dashboard of another user. I also want to prevent that a user can place a bid for someone else (by changing the id in the http request)
The issue is that in the first route, the id is referring to the user. In the second route, the id is referring to the lot id.
Am I obliged to change the second route to:
Route::get('{id}/lot/{lot}/bid/create', ['as' => 'buyer_lot_bid_create', 'uses' => 'User\Buyer\BidsController#create']);
where the first id refers to the user so that I can check the id of the user?
Or is there another way to prevent users from accessing other users pages without explicitly passing the user/{id} in the route?
Doing this in a middleware sounds like a bad idea. You've already come up against one exception to your middleware rule, and you'll certaily come across more.
There's two ways to do this:
Using laravel's built in authorisation tools: https://laravel.com/docs/5.1/authorization
If the check is that the "id as supplied in the route, is the same as the current logged in user" then there's no need to pass the user's id in via the route at all. The user can just visit eg. /dashboard/buyer with no params in the route. Who's dashboard are they visiting? The one of the logged in user, of course. So there's no way for a user to even try to visit another user's dashboard. Likewise with bidding. You can make your bid endpoint so that the bidder's id is not passed in via a route - it's just set to the id of the logged in user in your controller method. So again, there's no way to even try to bid on behalf of another user.
class AuthServiceProvider extends ServiceProvider
{
public function boot(GateContract $gate)
{
$this->registerPolicies($gate);
$gate->define('see-profile', function ($user, $profile) {
return $user->id === $profile->user_id;
});
}
Controller:
public function profile($id)
{
$post = Profile::findOrFail($id);
if (Gate::denies('see-profile', $profile)) {
abort(403);
}
}
Related
After a user creates a conference he is redirected to a page "http://proj.test/conference/manage/2" to manage that specific conference.
A user that creates a conference with id "2" for example should be allowed to access "http://proj.test/conference/manage/2". But only this user that created this conference with id "2" should be allowed to access this conference.
The other users if they access "http://proj.test/conference/manage/2" should be redirected to the login page if are not authenticated and to the homepage if they are authenticated.
I was trying to do this with the code below, particularly with the code in the manage method, but when the user is redirected to an url for example "http://proj.test/conference/manage/2" it appears always:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'conference.user_id' in 'where clause' (SQL: select * from `conferences` where `conferences`.`user_id` = 1 and `conferences`.`user_id` is not null and `conferences`.`id` = 7 limit 1)
The issue should be because in the conferences table I have a field "conference_creator_id" instead of "user_id". But it's not possible to maintain the "conference creator_id"? But also, I dont know if this approach in the manage method is ok for this context and if with this approach is possible to do that thing of redirect a user, that accesses "http://layout.test/conference/manage/2", and he didn't create this conference with id 2, to the login page if is not authenticated and redirect to the homepage if he is authenticated. Do you know a proper approach to do that?
Store method:
public function store(Request $request)
{
$this->validate($request, [
'conference_name' => 'required|max:255|string',
...
]);
$conference = Conference::create([
'name' => $request->conference_name,
...
]);
return redirect()->route('conference.manage', ['id' => $conference->id]);
}
Manage method:
public function manage($id){
$conference = Auth::user()->conferences()->findOrFail($id);
return view('conferences.manage')>with('myconference',$conferene);
}
Routes:
Route::group(['prefix' => '', 'middleware' => 'auth'], function(){
Route::post('/conference/store', [
'uses' => 'ConferenceController#store',
'as' => 'conference.store'
]);
Route::get('/conference/create', [
'uses' => 'ConferenceController#create',
'as' => 'conference.create'
]);
Route::get('conference/manage/{id}', [ 'uses' => 'ConferenceController#manage', 'as'=>'conference.manage']);
});
Add the custom foreign key to the conferences relationship definition:
public function conferences()
{
return $this->hasMany(Conference::class, 'conference_creator_id');
}
But it's better to follow Laravel naming conventions described in my repo and change the column name to user_id.
And you need to use policies for authorization purposes.
You could add to your controller class:
public function __construct()
{
$this->middleware('auth');
}
This way it'll check whether the user is signed in or as a guest and deal with them accordingly depending on the result.
The alternative would be to wrap a Route group around anything that required 'auth' (as my answer states in your previous question).
A simple way will be to check the id of the current user
public function __construct()
{
//User should be logged in before accessing the `manage` method
$this->middleware('auth')->only(['manage']);
}
public function manage(Conference $conference){
//Redirect the user to the homepage when its ID is different of the ID of the user who created the conference
if ($conference->user_id != Auth::id()){
return redirect(url('/'));
}
return view('conferences.manage')>with('myconference',$conferene);
}
In web.php I have two middleware groups for two user roles - admins and non_admins:
Route::group(['middleware' => ['auth', 'admin']], function () {
// if user is logged in AND has a role of admin...
Route::get('/', 'Admin\IndexController#index');
});
Route::group(['middleware' => ['auth', 'non_admin']], function () {
// if user is logged in AND has a role of non_admin
Route::get('/', 'NonAdmin\IndexController#index');
});
Both admin and non_admin middleware check that the role of Auth::user() is admin or non_admin respectively; if it's not, the middleware fails with abort(403). Why do I not have a single middleware? The point of this to separate the two roles, so that each has its own independent controller logic and its own views.
Problem
If I log in as an admin I get 403, if I log in as a non_admin, it works as expected. My guess: Laravel sees the two duplicate routes, and only resolves the one that is defined last (which happens to be in ['middleware' => ['auth', 'non_admin']]).
Question
How do I resolve duplicate routes but separate controller and presentation logic? Again, admin and non_admin users will visit the same route ('/') but see two different views. I also want to implement this in two different controllers.
Hmmm....
I would personally keep everything encapsulated under a common controller, and perform the necessary checks and modifications using a service class.
But if you really want to keep everything separated under two different controllers based on your roles, you could do it this way. This assumes that you're using Laravel's built-in Authorization:
routes
Route::group(['middleware' => ['auth']], function () {
Route::get('/', function(){
$isAdmin = Auth::user()->can('do-arbitrary-admin-task');
$ns = $isAdmin ? 'Admin' : 'NonAdmin';
$controller = app()->make("{$ns}\\IndexController");
return $controller->callAction('index', $parameters = []);
});
});
I'm a Laravel newbie (VERY newbie) using Cartalyst Sentinel on Laravel 5.2 to leverage roles authorizations.
On the admin section I have three (or more) roles, i.e. "admin", "agent" and "writer".
I also have some sections that should have mixed roles access, i.e. like this:
dashboard (accessible to all roles: admin, agent, writer)
users (accessible to: admin)
orders (accessible to: admin, agent)
pages (accessible to: admin, writer)
no_admin_here (accessible to: agent, writer)
At the moment I managed it to work with only two roles, but now I am stuck.
What I've done so far (I put only the necessary code):
routes.php
// only authenticated users can access these pages
Route::group(['prefix' => 'admin', 'as' => 'admin.', 'middleware' => ['check']], function(){
// these pages are accessible to all roles
Route::get('dashboard', ['as' => 'dashboard', function(){
return view('admin/dashboard');
}]);
// only admin can access this section
Route::group(['middleware' => 'admin'], function(){
Route::get('users', function(){
return view('admin/users');
});
});
});
SentinelCheck Middleware (named 'check' in Kernel.php)
if (!Sentinel::check()) { // user is not authenticated
return redirect()->route('admin.login')->with('error', 'You must be logged to view the page');
}
if (Sentinel::inRole('customer')) { // user is authenticated but he is a customer
return redirect()->route('admin.login')->with('error', 'You are a customer and cannot access to backend section');
}
SentinelAdmin Middleware (named 'admin' in Kernel.php)
if (!Sentinel::inRole('admin')) { // user is authenticated but he is not an admin
return redirect()->route('admin.login')->with('error', 'You are not admin and cannot view requested section');
}
SentinelAgent Middleware (named 'agent' in Kernel.php)
if (!Sentinel::inRole('agent')) { // user is authenticated but he is not an agent
return redirect()->route('admin.login')->with('error', 'You are not agent and cannot view requested section');
}
So far so good, as I said, but things mess up when I try to mix roles; i.e. I can't write a route like this:
// only admin and agent can access this section
Route::group(['middleware' => ['admin', 'agent']], function(){
Route::get('orders', function(){
return view('admin/orders');
});
});
because "agent" will never reach the section since "admin" middleware will block and logout him. And, likewise, I can't do every other roles mix:
['middleware' => ['admin', 'writer']]
['middleware' => ['agent', 'writer']]
['middleware' => ['admin', 'writer', 'whatever_else_role']]
etc..
So, is there a (easy) way in which I can easily mix roles accesses to sections? Thanks in advance for your help
It was easier that I expected using middleware parameters
How add filter, when I logged in, and I enter user/login or user/register on url, I want to redirect to home.
and when I logged out, and I want to protect user/panel
Laravel has filters options the official website states the following for the filters
Route filters provide a convenient way of limiting access to a given route, which is useful for creating areas of your site which require authentication. There are several filters included in the Laravel framework, including an auth filter, an auth.basic filter, a guest filter, and a csrf filter. These are located in the app/filters.php file.
Defining a route filter
Route::filter('old', function()
{
if (Input::get('age') < 200)
{
return Redirect::to('home');
}
});
If the filter returns a response, that response is considered the
response to the request and the route will not execute. Any after
filters on the route are also cancelled.
Attaching a filter to the route
Route::get('user', array('before' => 'old', function()
{
return 'You are over 200 years old!';
}));
You can find complete documentation here
Route.php
Route::group(array('prefix'=>'user','before' => 'isUser'), function () {
Route::get('register', array('as' => 'register', 'uses' => 'UsersController#create'));
Route::get('login', array('as' => 'login', 'uses' => 'UsersController#login'));
// add post routes as required
});
filters.php
Route::filter('isUser', function ()
{
// I have used Sentry for user Authorisation
if (Sentry::check()) {
return Redirect::to('/')->with('message', "You are already logged in.");
}
else{
// benefit of using Redirect::guest() is that you can use Redirect::intended()
return Redirect::guest('login');
}
});
Hey guys could you please help me? This one is driving me crazy...
Let's say that I have a method for checking if the user is an admin or not:
public function isAdmin()
{
return Auth::user()->role === 'admin';
}
Then I attach it to a route filter:
Route::filter('admin', function($route, $request)
{
if ( ! Auth::user()->isAdmin()) {
Notification::error('No permission to view this page!');
return Redirect::back();
}
});
Now, I just pass it to the route group
Route::group(array('before' => 'admin'), function()
{
Route::post('users/{id}/update_password', 'UserController#update_password');
Route::post('users/{id}/delete', 'UserController#force_delete');
Route::delete('users/{id}', array('as' => 'users.destroy', 'uses' => 'UserController#destroy'));
Route::post('users/{id}/restore', 'UserController#restore');
Route::get('users/create', array('as' => 'users.create', 'uses' => 'UserController#create'));
Route::post('users', array('as' => 'users.store', 'uses' => 'UserController#store'));
Route::get('users/{id}/edit', array('as' => 'users.edit', 'uses' => 'UserController#edit'));
Route::put('users/{id}', array('as' => 'users.update', 'uses' => 'UserController#update'));
});
The question here is how do I allow a user to bypass this filter if for example he's trying to update it's own profile page an obviously he's not and admin?
I just want to block all access to the users routes for nonadmins but allow the user to edit/update etc on his own profile but allow the admin to do that too.
Could you please point me to the right direction?
You can get the related request segment to check it in your filter:
Route::filter('admin', function($route, $request)
{
if ( ! Auth::user()->isAdmin() && Auth::user()->username !== Request::segment(2)) {
Notification::error('No permission to view this page!');
return Redirect::back();
}
});
There are a few ways to do this, but having a filter that checks the request segments against the currently authenticated user isn't the best way.
Choice Number 1
You simply check that a user is auth'd (use the auth filter), and then in the controller itself you check whether or not the user is an admin, and/or it's their profile.
Choice Number 2
Define a secondary sets of routes specifically for a user modifying their own profile, that doesn't follow the /user/{id}/* pattern.
Route::group(['before' => 'admin'], function() {
// admin routes here
}
Route::group(['prefix' => '/me'], function() {
Route::post('/update_password', 'UserController#update_password');
Route::post('/delete', 'UserController#force_delete');
// etc
}
This would mean that to edit their own profile, they could simply go to /me/edit rather than /user/{id}/edit. To avoid issues like repeating the same code, or errors because an argument is missing, you could do something like this in your controller.
private function getUserOrMe($id)
{
return $id !== false ? User::find($id) : Auth::user();
}
public function edit($id = false)
{
$user = $this->getUserOrMe($id);
}
I recently used this particular method for an API. Sure it requires defining the routes again, but providing that you've set them up with groups that make use of the prefix option, it's a copy and paste job, plus, there are routes an admin would have that a user wouldn't.
Either way, filters weren't intended to do complex logic, but rather, to provide a level of base logic and protection for routes. Logic that identifies whether the current uri is that of the currently logged in user, is something better handled in a controller.