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.
Related
My ultimate objective is to limit accesses to the group of routes by validating permissions provided to the user.
These target 'group of routes' have ONE COMMON PARENT GROUP and may have zero or more sub-groups, such that, if access to these target 'group of routes' is permitted/accessible to the user then, all its sub-route groups are also accessible to the user.
To achieve this, I believe I need to differentiate these target group of routes by any uniqueString/parameter in middleware, which is indeed answered here.
But, I want to generalize this further, by applying middleware to common SINGLE PARENT GROUP of all these target group of routes and identify these target group of routes by any means in the middleware.
So, my question is how do I identify/differentiate these target group of routes in the middleware? Is there any way to do so?
Sample Code of what I am trying to describe:
Route::group(['prefix' => 'singleParent','middleware' => 'permissionMiddleware'], function (){
Route::group(['prefix' => 'target-group-1', 'groupUniqueString' => 'tsg1'], function (){
Route::group(['prefix' => 'sub-group-1.1'], function (){
});
Route::group(['prefix' => 'sub-group-1.2'], function (){
});
});
Route::group(['prefix' => 'target-group-2', 'groupUniqueString' => 'tsg2'], function (){
Route::get('route-1','Controller#method-of-Route1');
});
});
So, to specify a route group in your middleware to handle some actions, you can do it in this way :
Route::group(['prefix' => 'singleParent','middleware' => 'permissionMiddleware'], function (){
Route::group(['prefix' => 'target-group-1', 'as' => 'tsg1.'], function (){
//...
});
});
This will generate route names with the prefix : tsg1
Now in your middleware you can do like this to get the route group :
function getCurrentRouteGroup() {
$routeName = Illuminate\Support\Facades\Route::current()->getName();
return explode('.',$routeName)[0];
}
Updated
and to check :
if ($request->route()->named('name')) {
//
}
return $next($request);
Or in another approach you can achieve :
To get the prefix of a route group you can do something like this :
$uri = $request->path();
// this will give you the url path like -> if this is the url :
// http://localhost:8000/foo/bar you will get foo/bar
And then :
$prefix = explode('/',$uri)[0];
// and you will get 'foo'
Let me know if this works for you.
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 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);
}
}
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');
}
});
Im curious to know if it is possible to prevent users who don't have a role of owner or administrator from accessing certain controllers in a laravel application?
Yes you can. You can do this with a route filter.
routes.php
Route::group(['prefix' => 'admin', 'before' => 'auth.admin'), function()
{
// Your routes
}
]);
and in filters.php
Route::filter('auth.admin', function()
{
// logic to set $isAdmin to true or false
if(!$isAdmin)
{
return Redirect::to('login')->with('flash_message', 'Please Login with your admin credentials');
}
});
Route filters have already been proposed but since your filter should be Controller specific you might want to try controller filters.
First off, lets add this your controller(s)
public function __construct()
{
$this->beforeFilter(function()
{
// check permissions
});
}
This function gets called before a controller action is executed.
In there it depends on you what you want to do. I'm just guessing now, because I don't know your exact architecture but I suppose you want to do something like this:
$user = Auth::user();
$role = $user->role->identifier;
if($role !== 'admin' && $role !== 'other-role-that-has-access'){
App::abort(401); // Throw an unauthorized error
}
Instead of throwing an error you could also make a redirect, render a view or do basically whatever you want. Just do something that stops further execution so your controller action doesn't get called.
Edit
Instead of using Closure function, you can use predefined filters (from the routes.php or filters.php)
$this->beforeFilter('filter-name', array('only' => array('fooAction', 'barAction')));
For more information, check out the documentation