Single Laravel Route for multiple controllers - php

I am creating a project where i have multiple user types, eg. superadmin, admin, managers etc. Once the user is authenticated, the system checks the user type and sends him to the respective controller. The middle ware for this is working fine.
So when manager goes to http://example.com/dashboard he will see the managers dashboard while when admin goes to the same link he can see the admin dashboard.
The below route groups work fine individually but when placed together only the last one works.
/***** Routes.php ****/
// SuperAdmin Routes
Route::group(['middleware' => 'App\Http\Middleware\SuperAdminMiddleware'], function () {
Route::get('dashboard', 'SuperAdmin\dashboard#index'); // SuperAdmin Dashboard
Route::get('users', 'SuperAdmin\manageUsers#index'); // SuperAdmin Users
});
// Admin Routes
Route::group(['middleware' => 'App\Http\Middleware\AdminMiddleware'], function () {
Route::get('dashboard', 'Admin\dashboard#index'); // Admin Dashboard
Route::get('users', 'Admin\manageUsers#index'); // Admin Users
});
I know we can rename the routes like superadmin/dashboard and admin/dashboard but i was wondering if there is any other way to achieve the clean route. Does anyone know of any anywork arounds ?
BTW i am using LARAVEL 5.1
Any help is appreciated :)

You can do this with a Before Middleware that overrides the route action's namespace, uses and controller attributes:
use Closure;
use Illuminate\Http\Request;
use Illuminate\Contracts\Container\Container;
use App\Http\Middleware\AdminMiddleware;
use App\Http\Middleware\SuperAdminMiddleware;
class AdminRoutingMiddleware
{
/**
* #var Container
*/
private $container;
public function __construct(Container $container)
{
$this->container = $container;
}
private static $ROLES = [
'admin' => [
'namespace' => 'Admin',
'middleware' => AdminMiddleware::class,
],
'super' => [
'namespace' => 'SuperAdmin',
'middleware' => SuperAdminMiddleware::class,
]
];
public function handle(Request $request, Closure $next)
{
$action = $request->route()->getAction();
$role = static::$ROLES[$request->user()->role];
$namespace = $action['namespace'] . '\\' . $role['namespace'];
$action['uses'] = str_replace($action['namespace'], $namespace, $action['uses']);
$action['controller'] = str_replace($action['namespace'], $namespace, $action['controller']);
$action['namespace'] = $namespace;
$request->route()->setAction($action);
return $this->container->make($role['middleware'])->handle($request, $next);
}
}
This way you have to register each route only once without the final namespace prefix:
Route::group(['middleware' => 'App\Http\Middleware\AdminRoutingMiddleware'], function () {
Route::get('dashboard', 'dashboard#index');
Route::get('users', 'manageUsers#index');
});
The middleware will convert 'dashboard#index' to 'Admin\dashboard#index' or 'SuperAdmin\dashboard#index' depending on current user's role attribute as well as apply the role specific middleware.

The best solution I can think is to create one controller that manages all the pages for the users.
example in routes.php file:
Route::get('dashboard', 'PagesController#dashboard');
Route::get('users', 'PagesController#manageUsers');
your PagesController.php file:
protected $user;
public function __construct()
{
$this->user = Auth::user();
}
public function dashboard(){
//you have to define 'isSuperAdmin' and 'isAdmin' functions inside your user model or somewhere else
if($this->user->isSuperAdmin()){
$controller = app()->make('SuperAdminController');
return $controller->callAction('dashboard');
}
if($this->user->isAdmin()){
$controller = app()->make('AdminController');
return $controller->callAction('dashboard');
}
}
public function manageUsers(){
if($this->user->isSuperAdmin()){
$controller = app()->make('SuperAdminController');
return $controller->callAction('manageUsers');
}
if($this->user->isAdmin()){
$controller = app()->make('AdminController');
return $controller->callAction('manageUsers');
}
}

Related

Laravel custom Authentication: Route [login] not defined

I created a custom authentication for my Laravel app following that tutorial: https://medium.com/#nasrulhazim/laravel-using-different-table-and-guard-for-login-bc426d067901
I adapted it to my needs but I didn't have to change much.
In the end, when I try to go the the /home route, but it says: "Route [login] not defined."
My guess is that a default behavior of the authentication call the login route instead of the /fidelite/login I've created.
Here is my provider:
fidelite' => [
'driver' => 'eloquent',
'model' => App\Fidelite::class,
],
And the guard
'fidelite' => [
'redirectTo' => 'fidelite.home',
'driver' => 'session',
'provider' => 'fidelite',
],
The routes defined in the web.php file
Route::prefix('fidelite')
->as('fidelite.')
->group(function() {
Route::get('/home', 'Home\FideliteHomeController#index')->name('home');
Route::namespace('Auth\Login')
->group(function() {
Route::get('login', 'FideliteController#showLoginForm')->name('login');
Route::post('login', 'FideliteController#login')->name('login');
Route::post('logout', 'FideliteController#logout')->name('logout');
Route::get('register', 'FideliteController#showRegisterForm')->name('register');
});
});
Basically, there is two controllers; the first one, FideliteController adds the middleware and show the needed forms to login / register
class FideliteController extends DefaultLoginController
{
protected $redirectTo = '/fidelite/home';
public function __construct()
{
$this->middleware('guest:fidelite')->except('logout');
}
public function showLoginForm()
{
return view('auth.login.fidelite');
}
public function showRegisterForm()
{
return view('auth.compte');
}
public function username()
{
return 'email';
}
protected function guard()
{
return Auth::guard('fidelite');
}
}
And the other one returns the /fidelite/home page when the user is logged
class FideliteHomeController extends Controller
{
public function __construct()
{
$this->middleware('auth:fidelite');
}
public function index()
{
return view('home.fidelite');
}
}
There is something I missing, but what ?
Many thanks for your help and time...
Found it ! Thanks to the Alpha whom helps me find the problem !
The problem was that the middleware I was using (Authenticate.php) was redirecting to the route('login') instead of the custom route I needed.
You are duplicating the login name route. change the name of login to any specific name that properly define your route behavior.

Laravel, add gate to see if user has access

I have got a table with my permissions, I want to be able to setup a route like below so that I check the logged in users permissions and see if they have got access to the edit_users page for example.
Route::group([ 'prefix' => 'users', 'middleware' => 'can:access,edit_users' ], static function() {
I have added the following Gate, where I would do my query and check, however $permission is null...
public function boot()
{
$this->registerPolicies();
Gate::define('access', static function ($user, $permission) {
dd($user, $permission);
});
}
How would I go about doing this? As I don't want to hard-code all the permissions into gates!
The default middleware passes a route parameter as the second argument. I think what you'll need to do in this case is write your own middleware that takes a string as the argument, then do your check manually.
namespace App\Http\Middleware;
use Closure;
class HasPermission {
public function handle($request, Closure $next, $permission)
{
if($request->user()->can('access', $permission)) {
return $next($request);
}
return redirect()->back();
}
}
Then register your new middleware in the kernel file
'hasPermission' => \App\Http\Middleware\HasPermission::class
Then you can use your new middleware instead of the can middleware in your route.
Route::group([ 'prefix' => 'users', 'middleware' => 'hasPermission:edit_users' ], function() {});

Multiple auth for laravel

I want to split middleware auth to two role one is for admin and second for user
but some route is use for all user and admin and few route is for admin only how can i split with route?
Auth::routes();
Route::group(['middleware' => 'auth'], function () {
//Some route here
});
Route::group(['middleware' => ['guest']], function () {
//some route here
});
Here is my implementation for access control for admin and users(agents in my case) I have a boolean field in my user table (is_admin) which is 0 for normal users and 1 for admins.
In your User model add this:
protected $casts = [
'is_admin' => 'boolean',
];
public function isAdmin()
{
return $this->is_admin;
}
Create a new middlewares for Admin and Agent:
php artisan make:middleware Admin
php artisan make:middleware Agent
The middleware files will be created in App\Http\Middleware\
Add this to class inside Admin.php:
public function handle($request, Closure $next)
{
if ( Auth::check() && Auth::user()->isAdmin() )
{
return $next($request);
}
return redirect('/agent');
}
Add this to Agent.php
public function handle($request, Closure $next)
{
if ( Auth::check() && !Auth::user()->isAdmin() )
{
return $next($request);
}
return redirect('/home');
}
After this register your middleware with laravel to do this add this to protected $routeMiddleware in your Kernel.php which is located at app\Http\Kernel.php
'admin' => 'App\Http\Middleware\Admin',
'agent' => 'App\Http\Middleware\Agent',
Make sure to create proper routes for redirection as we've mentioned in our middleware files. After this you are almost done. Now to verify if a user is admin or normal user add this to the constructor method of your controller.
Actions allowed only for admin users:
public function __construct()
{
$this->middleware('auth');
$this->middleware('admin');
}
Action allowed only for normal (agent) users:
public function __construct() {
$this->middleware('auth');
$this->middleware('agent');
}
Or you can also add middleware to your routes,
Route::group(['middleware' => 'admin'], function () {
//Some route here
});

Laravel 5.2 Controller middleware for more than one role

I have a roles administrator, moderator and member in my laravel application. Application have fronted and backend sections. I want to allow access to backend section only for administrator and moderator. I create SuperUsersMiddleware:
<?php
namespace CMS\Http\Middleware;
use Closure;
class SuperUsersMiddleware
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if (! $request->user()->hasRole('administrator') || ! $request->user()->hasRole('moderator')) {
return redirect('/');
}
return $next($request);
}
}
Register in Kernel.php:
......
protected $routeMiddleware = [
'superusers' => \CMS\Http\Middleware\SuperUsersMiddleware::class,
'administrator' => \CMS\Http\Middleware\AdminMiddleware::class,
'moderator' => \CMS\Http\Middleware\ModeratorMiddleware::class,
'auth' => \CMS\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'can' => \Illuminate\Foundation\Http\Middleware\Authorize::class,
'guest' => \CMS\Http\Middleware\RedirectIfAuthenticated::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
];
.....
and in my backend folder I create Controller.php (all other controllers in backend section extends this controller) and in __construct() function set middleware:
...
public function __construct()
{
$this->middleware('superusers');
}
...
But this doesn't work for me. I also create administrator and moderator middleware and it works separately but I needed both - together. How to do that? I tray:
public function __construct()
{
$this->middleware('administrator');
$this->middleware('moderator');
}
But this also can't work. What is a best practice for this situation?
First off I wouldn't apply any Middleware on your master Controller as then middleware would be applied to everything. You should do this on each individual controller like UserController.
You can apply as many middleware instances to a route/function as you want. I'm not aware of any limitations on that. So I'm not sure why you say this doesn't work:
public function __construct()
{
$this->middleware('administrator');
$this->middleware('moderator');
}
You can apply the different middleware to the routes that require the different levels. You can do this in your routes.php or in your Controllers. If you want to do it in your Controller like you are doing above you would have something like this:
public function __construct()
{
$this->middleware('auth'); //this applies to all actions
$this->middleware('administrator', ['only' => ['adminFunction', 'otherAdminFunction','bothCanAccess']]);
$this->middleware('moderator',['only' => ['moderatorFunction','bothCanAccess']);
}
public function adminfunction()
{
...
}
public function otherAdminfunction()
{
...
}
public function moderatorFunction()
{
...
}
public function bothCanAccess()
{
...
}
So first off the auth middleware will apply to all actions. This means a user has to be logged in to access any function in here. Then you can apply specific middleware to each function. If you need more info on this check out the documentation:
https://laravel.com/docs/5.2/controllers#controller-middleware
To do this in your router you would do something like this:
Route::get('/admin', ['middleware' => ['auth', 'administrator'],'uses'=>'Controller#adminFunction']);
So in this case it will apply the auth middleware first to make sure someone is logged in, then fire the administrator middleware and make sure the user is an admin.
Hopefully that helps.

Should I re-use controllers in laravel between admin & api ? or have my admin consume my API?

New to laravel and trying to work out the best way to structure my app.
It has both an admin interface and an API (JSON, angularjs front-end).
my routes currently look like:
Route::group(array('prefix' => 'admin', 'before' => 'auth.admin'), function()
{
Route::any('/', array('as' => 'admin.index', function() {
return View::make('admin.index');
}));
Route::resource('countries.products', 'ProductsController');
Route::resource('countries', 'CountriesController');
Route::resource('orders', 'OrdersController');
});
// Route group for API versioning
Route::group(array('prefix' => 'api/v1'), function()
{
Route::resource('products', 'APIProductsController', array('only' => array('index', 'show')));
Route::resource('orders', 'APIOrdersController', array('only' => array('store', 'update')));
});
There is a lot of duplicated logic in eg, the OrdersController & APIOrdersController. Should I re-use a single controller somehow, maybe with content-negotation? or is it better to modify OrdersController to query the API routes instead of using eloquent?
or is there another, better way?
As I see it, I would extract all object creation logic to a proper class (sounds like a good case for a repository). This class should only know about the parameters it has to receive, and respond accordingly. For example:
class EloquentOrder implements OrderRepositoryInterface {
// Instance of OrderValidator,
// assuming we have one
protected $validator;
public function create($params)
{
// Pseudo-code
$this->validator = new Ordervalidator($params);
if ($this->validator->passes())
create and return new Order
else
return validator errors
}
}
Then, each of your modules can use this functionality inside its controllers.
In your API, you could have this:
class APIOrderController extends APIController {
protected $repository;
public function __construct(OrderRepositoryInterface $repository)
{
$this->repository = $repository;
}
public function create()
{
// Let's imagine you have an APIAuth class which
// authenticates via auth tokens:
if (APIAuth::check()) {
$params = Input::all();
return $this->repository->new($params);
}
return Response::json(['error' => 'You are not authorized to create orders'], 401);
}
}
While in your administration module, you could have:
class AdminOrderController extends AdminController {
protected $repository;
public function __construct(OrderRepositoryInterface $repository)
{
$this->repository = $repository;
}
public function create()
{
// Now, let's imagine Auth uses a different authentication
// method, and can check for specific permissions
if (Auth::check() && Auth::hasPermission('create.orders')) {
$params = Input::all();
return $this->repository->new($params);
}
return Redirect::home()->with('message', 'You are not authorized to create orders');
}
}
As you can see, this allows you to reuse your object creation logic in different contexts. In the example I've used different authentication methods and responses just to show flexibility, but this will really depend on your project requirements.

Categories