Laravel 5 : Restrict access to controllers by User group - php

I've started learning Laravel 5.1 and so far I'm liking it! But there is one thing I don't get yet..
In my previous project I had 2 specific controllers (eg: "normal", "extended") which , after a successfull login, were called based on the Users user_group from the database.
If "Foo.Bar" enters his valid credentials and has the group normal he is redirected to NormalControler. Since I wasn't using any framework I restricted access to the other group by setting a $_SESSION with the group and checking it. So if another group tried to access that controller he got redirected.
How would this be achievable in Laravel 5? So far I have a controller which is callable without an Authentication and one restricted by this code in routes.php :
// All routes in the group are protected, only authed user are allowed to access them
Route::group(array('before' => 'auth'), function() {
// TO-DO : Seperate Controller access
});
And the login looks like this :
public function performLogin()
{
$logindata = array(
'username' => Input::get('user_name'),
'password' => Input::get('user_pass')
);
if( Auth::attempt( $logindata ) ){
// return \Redirect::to( check group and access this controller based on it);
}
else {
// TO-DO : Redirect back and show error message
dd('Login failed!');
}
}
----- EDIT -----
I've run the artisan command and made this middleware as you suggested :
namespace App\Http\Middleware;
use Closure;
use Request;
class GroupPermissions
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next, $group)
{
// Check User Group Permissions
if( $request->user()->group === $group ){
// Continue the request
return $next($request);
}
// Redirect
return redirect('restricted');
}
}
and edited this line into Kernel.php into $routeMiddleware :
'group.perm' => \App\Http\Middleware\GroupPermissions::class
I think this is done right so far, correct me if I'm wrong! Could I then do something like this to restrict the controllers?
Route::group(array('before' => 'auth'), function() {
Route::group( ['middleware' => 'group.perm', 'group' => 'normal'], function(){
Route::get('/normal/index', 'DummyNormalController#index');
});
Route::group( ['middleware' => 'group.perm', 'group' => 'extended'], function(){
Route::get('/extended/index', 'DummyExtendedController#index');
});
});

Ok, here is what you might do. Once user is logged in, you would check his credentials, get his user_group and decide what controller he should be redirected to.
if( Auth::attempt( $logindata ) ){
$user = Auth::user();
if ($user->inGroup('normal')) {
return redirect()->route('normal_controllers_named_route');
}
return redirect()->route('extended_controllers_named_route');
}
return redirect()->back()->withFlashMessage('don\'t get me wrong');
This will handle right routing after logging in.
The next portion where you need to protect you routes from unwanted user groups may be achieved with middlewares.
do an artisan command php artisan make:middleware ShouldBeInGroup
go to app/http/Kernel.php and add your new middleware to the routeMiddleware array. Key of the item might be anything you like. Let's call in inGroup. So: 'inGroup' => 'App\Http\Middleware\ShouldBeInGroup'
Now, in your controller, in constructor, you are able to call this middleware
$this->middleware('inGroup:extended'); //we also passing the name of the group
at lastly, work on the our middleware. Open newly created ShouldBeInGroup class and edit the handle method.
public function handle($request, Closure $next, $groupName)
{
if (Auth::check() && Auth::user()->inGroup($groupName)) {
return $next($request);
}
return redirect('/');
}
And finally you should work on inGroup method, that should return true of false. I assume that you have user_group field your users table. Then in your User eloquent model add the method
public function inGroup($groupName) {
return $this->user_group == $groupName;
}
Edit
if you want to use this middleware in your routes, you can do the following
Route::group(array('before' => 'auth'), function() {
Route::get('/normal/index', ['middleware' => 'group.perm:normal', 'uses' =>'DummyNormalController#index']);
}
But generally it's better to put all your middlewares into your Controller's constructor
public function __construct(){
$this->middleware('group.perm:normal'); // you can also pass in second argument to limit the methods this middleware is applied to : ['only' => ['store', 'update']];
}
And also on this note, Laravel provides built in auth middleware that you can use
public function __construct(){
$this->middleware('auth');
$this->middleware('group.perm:normal');
}
so then your routes would become much cleaner, just:
Route::get('normal/index', 'DummyNormalController#index');

I think the best way to do that is using middlewares. See the doc here
You can easily create a middleware using the following artisan command:
php artisan make:middleware ExtendedMiddleware
If you can't or don't want to use artisan, you need to create a class in The App/Http/Middleware folder.
In this class you'll need the following method to handle the request. In the method you can check for the user group.
public function handle($request, Closure $next)
{
// check user group
if( user_group_ok )
return $next($request); // Continue the request
return redirect('restricted'); // Redidrect
}
You can then use this middleware in your route.php file:
Route::group(['middleware' => 'auth'], function()
{
// Logged-in user with the extended group
Route::group(['middleware' => 'extended'], function()
{
// Restricted routes here
});
// Normal routes here
});

You can create a Middleware called : PermissionFilter
In PermissionFilter, you check if requesting user is in the group or not.
I can't provide a demo for now, but if you want I can make a demo later.
L5 middleware: http://laravel.com/docs/5.1/middleware

Related

Laravel routes: same routes in two different route groups

I currently have two route groups where one route group has six routes and the other has two routes (that are also in the previous group).
/**
* Foo Routes for admin
*/
Route::group(['middleware' => 'bar:admin'], function () {
Route::put('foo/{uuid}/publish', 'FooController#publish');
Route::put('foo/{uuid}/disable', 'FooController#disable');
Route::put('foo/{uuid}/enable', 'FooController#enable');
Route::delete('foo/{uuid}', 'FooController#destroy');
Route::post('foo', 'FooController#store');
Route::put('foo/{uuid}', 'FooController#update');
});
/**
* Foo Routes for creator
*/
Route::group(['middleware' => 'bar:creator'], function () {
Route::post('foo', 'FooController#store');
Route::put('foo/{uuid}', 'FooController#update');
});
The reason for this split is because the creator needs access to two of the routes from the admin group, but admin needs permission to all the routes. Access is given via the middleware bar.
However, whenever I am an admin and I try to access one of the two routes available in the second route group, my bar class denies its request. It says that I must be a creator to access the route. Does this mean that routes have a cascading behaviour where the last instance of a route group is the one laravel uses? If it does, how can I format my routes to avoid this issue?
bar code:
public function handle($request, \Closure $next, ...$permissionRules)
{
.
.
.
$userPermissions = $decodedToken['user']['permissions'];
// If the user does not have every permission defined via route parameters, deny.
foreach ($permissionRules as $permissions) {
if (!in_array($permissions, $userPermissions)) {
return $this->denyResponse();
}
}
// The user has every permission rule defined via route parameters, so allow.
return $next($request);
}
The proper way to do this would be to customize the middleware you are using (bar) to accept multiple permissions/roles.
An easy way to do this would be to pass a comma-delimited list of acceptable permissions, convert it to an array in then check to see if the Auth user has the passed permissions.
To use the code you gave us originally, here is a way to implement:
First, create a new Route Group for the group of permissions:
/**
* Foo Routes for admin
*/
Route::group(['middleware' => 'bar:admin'], function () {
Route::put('foo/{uuid}/publish', 'FooController#publish');
Route::put('foo/{uuid}/disable', 'FooController#disable');
Route::put('foo/{uuid}/enable', 'FooController#enable');
Route::delete('foo/{uuid}', 'FooController#destroy');
});
/**
* Foo Routes for creator
*/
Route::group(['middleware' => 'bar:creator'], function () {
// Other Routes available only to Creator permission users
});
/**
* Foo Routes for creator & admin
*/
Route::group(['middleware' => 'bar:creator,admin'], function () {
Route::post('foo', 'FooController#store');
Route::put('foo/{uuid}', 'FooController#update');
});
Second, update bar middleware to convert the comma-delimited string to an array
public function handle($request, \Closure $next, ...$permissionRules)
{
.
.
.
$permissionRules = explode(',', $permissionRules);
$userPermissions = $decodedToken['user']['permissions']; //Assuming this is an array of the Auth'ed user permissions.
// If the user does not have every permission defined via route
parameters, deny.
foreach ($permissionRules as $permission) {
if (in_array($permission, $userPermissions)) {
// Change this to see if the permission is in the array, opposed to NOT in the array
return $next($request);
}
}
// Made it so that if the permission is NOT found in the array then Deny
return $this->denyResponse();
}
This should be all you need. Hope this helps!

Test that middleware was used in Laravel

I have a post route inside a middleware that checks whether a user performing a request belongs to a specific role. I have 4 roles in total. And I have found it silly to write tests like :
testRole2CanNotDoAction
testRole3CanNotDoAction
testRole4CanNotDoAction
every time I want to test some action. So it would be really nice if I can just write 1 test
testMiddlewareWasCalled
to assure that route is placed in right place. How can I do it? To give you the context lets assume I have the following middleware:
class MustBeRole1
{
public function handle($request, Closure $next)
{
$user = $request->user();
if ($user && $user->isRole1()) {
return $next($request);
}
abort(403, 'You are not an role1!');
}
}
and following routes:
Route::group(['middleware' => ['isRole1']], function () {
Route::post('/testAction', ['as' => 'testAction', 'uses' => 'testActionController#test']);
}
How should my test look like?

Laravel 5.3 protecting routes from user have different roles

problem
I'm looking for a way to protect users from access routes which do not belong to them, example admin cannot access user area and simple user cannot access admin area
Hi, i've a laravel 5.3 app and it has two types of users
Admin
Simple User
i'm trying to prevent admin from accessing simple user routes and vice-versa, I search a lot and found one solution of creating a middleware
what i've done
<?php
namespace App\Http\Middleware;
use Auth;
use Closure;
class UserRole
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if ( Auth::check()) // user logged
{
$request_url = $request->getRequestUri();
if( Auth::user()->user_type == 1 ) // simple user
{
// checking if simple user is visiting routes // starts with /user in the url
if (strpos($request_url, "/user") === 0)
{
return $next($request);
}
else
{
return redirect('/');
}
}
// checking if admin is visiting routes // starts with /admin in the url
else if( Auth::user()->user_type == 2 ) // admin
{
if (strpos($request_url, "/admin") === 0)
{
return $next($request);
}
else
{
return redirect('/');
}
}
else
{
return redirect('/');
}
}
return redirect('/');
}
}
unfortunately both are able to access each others restricted areas. I'm unable to find a better way to protect user from accessing routes which they don't have access too.
If you want to accomplish that using middleware you need to do following -
Create two middlewares, one for admin and one for simple user.
Then create two route group in your routes file i.e. routes/web.php
Protect one route group with admin middleware, and put all of your admin related routes in that group. Protect another route group
with
simple-user middleware and put all of your admin related routes in that group.
Route::group(['middleware' => ['auth', 'admin']], function() {
// put all your admin routes here
});
Route::group(['middleware' => ['auth', 'simple-user']], function() {
// put all your simple user routes here
});
You can also accomplish that using role and permission. Here is a package that can satisfy your needs.
https://packagist.org/packages/zizaco/entrust

Override laravel's 5.2 authenticate method in AuthController

I'm still very noobish with Larvel, so i can't figure out how to do this:
I'm stuck at the login system. I'm trying to understand how i can prevent inactive users to use the application.
Edit
With inactive users i mean those users which has the record field 'active' on 0. So i need to implement a check which verifies the mail and the password and also the active field
I've made the tables and the auth system, and as written in the documentation, I've placed this method inside my AuthController:
public function authenticate()
{
dd('hi');
}
Well it completely gets ignored. It seems like it never gets triggered. Am I missing something?
My controller looks like the original Laravel's 5.2 AuthController except for the method before. No other changes has been made, since according to the documentation no other changes are mentioned...
My tests:
I've also searched for a method called authenticate. But no methods where found (using php storm). So my question is:
If it's a trait, isn't supposed to have that method declared? So i can override it just by declaring a method with the same name?
Important files:
Routes.php
<?php
/*
|--------------------------------------------------------------------------
| Application Routes
|--------------------------------------------------------------------------
|
| Here is where you can register all of the routes for an application.
| It's a breeze. Simply tell Laravel the URIs it should respond to
| and give it the controller to call when that URI is requested.
|
*/
Route::get('/', 'HomeController#index');
Route::auth();
Route::get('/home', 'HomeController#index');
SOLVED
If you type php artisan route:list you see when login by post method, user post their info account to login action in LoginController. So, I've solved the problem by override login method in Auth\LoginController as below:
public function login(Request $request)
{
$credentials = $request->only('email', 'password');
$credentials['active'] = 1;
if (Auth::attempt($credentials)) {
// Authentication passed...
return redirect()->intended('/');
}
}
Firstly you need to remove the Auth routes but before that run php artisan route:list in your terminal. Copy the auth related routes and stick them in your routes file.
Change the controller in each of the routes to point to your own AuthController in your application.
Next, include the following 2 traits into your controller (top of the page) plus 2 classes;
use Illuminate\Foundation\Auth\ThrottlesLogins;
use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
and then include the traits in the top of your class;
use AuthenticatesAndRegistersUsers, ThrottlesLogins;
as it stands, the auth functionality should still work as normal.
Then, alter the login method to accept an active flag. Add the following to your new controller;
/**
* [login description]
* #param Request $request [description]
* #return [type] [description]
*/
public function login( Request $request, $guard )
{
$this->validateLogin( $request );
$throttles = $this->isUsingThrottlesLoginsTrait();
if ( $throttles && $lockedOut = $this->hasTooManyLoginAttempts( $request ) ) {
$this->fireLockoutEvent( $request );
return $this->sendLockoutResponse( $request );
}
$credentials = $this->getCredentials( $request );
if ( Auth::guard( $guard )->attempt( [ 'active' => 1 ] + $credentials, $request->has( 'remember' ) ) ) {
return $this->handleUserWasAuthenticated( $request, $throttles, $guard );
}
// check to see if they can login without the active flag
if( Auth::guard( $guard )->attempt( $credentials ) ) {
// log them back out and send them back
Auth::guard( $guard )->logout();
return redirect()->back()
->withInput( $request->only( $this->loginUsername(), 'remember' ) )
->withErrors([
'active' => 'Your account is currently not active',
]);
}
if ( $throttles && ! $lockedOut ) {
$this->incrementLoginAttempts( $request );
}
return $this->sendFailedLoginResponse( $request );
}
using the traits in your own controller should give you more flexibility in the future.
regards
To prevent guest users (not authenticated) to access certain routes, you should protect those routes in your routes.php:
// All the routes inside the group are accessible for logged in users only
Route::group(array('before' => 'auth'), function() {
Route::get('myroute', 'Controller#action');
});
// For routes accessible for everybody
Route::get('login', function(){
return View::make('login');
});
Solved
I've managed to do this by myself. Not sure that is the proper way, but anyway it works.
I've decided to create a middleware that after the successful login, checks that the user 'active' field is 1. If not it redirects to a specific page.
public function handle($request, Closure $next)
{
// If the user is not logged in
if(!Auth::user()){
return redirect('login');
}
// If the user is inactive
if(!Auth::user()->active){
return redirect('inactive');
}
return $next($request);
}

laravel how to create page restriction

Please tell me how to restrict the page using laravel,
i have 3 users.
1. admin, 2. client, 3. partner
i want if admin is logged in then open only- admin.index page
and if client logged in then open only- client.index page
i used in route.php following code-
Route::group(array('before' => 'role'), function(){
Route::resource('admin','AdminController#index');
Route::resource('client','clientController#index');
Route::resource('partner','partnerController#index');
});
using above code this if no any user login then it's coming properly,
and suppose if admin logged in, then page redirect to AdminController but,
if i hard coded (url) hit clientController or partnerController like http://localhost/laravel-login/public/client then client page is coming.
so please tell me how to avoid these
sorry for my english..
thanks
You may use different route filters for each route and create individual filters, for example:
Route::group(array('before' => 'auth'), function() {
Route::resource('admin','AdminController#index');
Route::resource('client','clientController#index');
Route::resource('partner','partnerController#index');
});
In each controller create a __construct method and add filter like:
public function __construct()
{
// In your AdminController
$this->beforeFilter(function() {
if(Auth::user()->role->name != 'admin') return redirect::to('/'); // home
});
}
Same way declare other filters in other controllers:
public function __construct()
{
// In your clientController
$this->beforeFilter(function() {
if(Auth::user()->role->name != 'client') return redirect::to('/'); // home
});
}
And so on. Check more on Laravel website about controller filtering.
The best way to restrict controllers to make new middleware , where you can define rules before the request. example :
I have a admin controller only register users with admin role can access it .
to do so when you define the route include the middleware .
// namespace = indicate where my controller is (sub folder )
// middleware = indicate what restriction i want for my controller you can pass one middleware or array of midlewares
Route::group([ 'namespace' => 'Admin','middleware' => ['auth' , 'IsAdmin'] ], function()
{
Route::resource('admin/posts', 'PostsController');
});
to create the middle ware and register it follow the documentation
look this is my middleware after
<?php
namespace App\Http\Middleware;
use Closure;
class IsAdmin
{
public function handle($request, Closure $next)
{
if($request->user()->is_admin == false ){
return redirect('/');
}
return $next($request);
}
}

Categories