After following a tutorial on how the built-in acl of laravel works I tried it and it works well by defining every route by itself.
Now I'm trying to use a resource but it's not working as intended. I added the following code to my routes file:
Route::group(['middleware' => 'acl:create_client'], function()
{
Route::resource('clients', 'ClientController');
});
Now I understand what the problem is:
all the methods in the Clientcontroller will be checked against my db if this user has the acl:create_client, resulting in all methods available to the logged in user that has this acl.
How do I split every method to use it's own acl without having to write it like this:
Route::get('/client/create', [
'middleware' => 'acl:create_client',
'as' => 'clients.create',
'uses' => 'ClientController#create'
]);
Resulting in something like this:
create needs create_client
index needs index_client
update need update_client
etc etc
The bottom line is: you need to setup the 'list' in the Access Control List (ACL) somehow. IMO, the most flexible way would be to pull this list from the database based on the session user; you're off to a good start. You can skip the explicit route assignment by using the already assigned 'as' that you define in a route. An example route:
Route::get('/', ['as'=>'clients.create', 'uses'=>'ClientsController#create']);
Here, you would use the 'clients.create' in your ACL check. Just remember: the ACL will still need the 'as' value set for all your routes (which is good to do anyway).
Step-by-step
Now that you have the required background information, here's how to get it working. These steps assume you were able to correctly setup the tutorial code and database. This will stick to the original tutorial setup and will focus on making the ACL independent from the route configuration.
1) In App\Http\Middleware\Acl\CheckPermission, you will need to replace the argument $permission = null with the 'as' string that you set in routes.php. The new code:
<?php namespace App\Http\Middleware;
use Closure;
class CheckPermission
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next/*, $permission = null REMOVE THIS*/)
{
// Add the next two lines:
$action = $request->route()->getAction();
$permission = isset($action['as']) ? $action['as'] : '';
if (!app('Illuminate\Contracts\Auth\Guard')->guest()) {
if ($request->user()->can($permission)) {
return $next($request);
}
}
return $request->ajax ? response('Unauthorized.', 401) : redirect('/login');
}
}
2) Now, you need to assign this middleware in a different manner. You do not want to use a specific permission, but instead use the 'as' string we just setup in the middleware. You can assign the middleware in two different ways: a) assign it to a group of routes, or b) assign it to every page. I suggest using 2a instead of 2b, because you may not want to use the ACL on all routes.
2a) Here's the method to assign it to only a group of routes. The two important things to notice here are the 'as'=>'clients.*' strings and the assignment of the middleware to the route group 'middleware' => 'acl'. Also note this route group does not pass the extra string parameter like the tutorial does (e.g. 'middleware' => 'acl:manage_user'). This is because we removed that argument from the handle() function above. You will need to change these example routes to match your target URIs and controller functions.
Route::group(['middleware' => 'acl'], function()
{
Route::get('/clients', ['as'=>'clients.view', 'uses'=>'ClientsController#index']);
Route::get('/clients/new', ['as'=>'clients.create', 'uses'=>'ClientsController#create']);
// Add more routes ...
}
2b) Here's how to assign it to every page. The tutorial uses the file /app/Http/Kernel.php to setup the middleware as a $routeMiddleware. This is the correct way to do it for step 2a above, but not if you want it on every page. To make the middleware a global middleware: add '\App\Http\Middleware\CheckPermission' to the $middleware variable found in the same file. You will not need the $routeMiddleware addition from the tutorial if you use the global variable.
3) In the tutorial database, you need to use the 'as' string in the permissions table within the permission_slug column. Here are example SQL inserts that allows user with id 123 to access route clients.create. These two create the permission and role we need to create access to the 'client.create' route.
INSERT INTO permissions ('permission_title', 'permission_slug', 'permission_description')
VALUES ('Create a Client', 'clients.create', 'Allow the user to create a client');
INSERT INTO roles ('role_title', 'role_slug')
VALUES ('Client Admin', 'clients.admin');
For the next query, you need to know the id of the two rows above. This assumes your database was newly created with no rows yet added, so each of the inserts will be id=1. This says: permission with id=1 is assigned to role with id=1.
INSERT INTO permission_role ('permission_id', 'role_id') VALUES (1, 1);
The next query also assumes the new role will be id=1 and the user id is 123. This assigns the new role with id=1 to the existing user with id=123.
INSERT INTO role_user ('role_id', 'user_id') VALUES (1, 123);
At this point, you should have a user with id=123 that has the Client Admin role. The Client Admin role should have the 'clients.create' permission. When you are logged in as user id=123, you will be verified to have the 'clients.create' permission, and you should be able to access the page (example.com/clients/new in my example). Any other user will not have access, and they will be redirected to the login page (this doesn't make sense to me if you're already logged in; this is just what the tutorial setup).
I recommend you do not build acl by yourself,there have some good packages out there like entrust
and if you truly want know the principle or laravel acl just follow this video tutorial from laracast laracast laravel acl tutorial
Related
I have a route resource group that can only be accessible by one of 2 middleware rules. I have registered them both and they both work independently if I test them both out alone, but when I have them together they don't work
I have tried running them both as either an "or" statement (which means the middleware works as intended) but this means that anyone not logged in can also access the routes for some reason. If I use a comma to separate the middleware, it's blocked for everyone. I know both middleware works ok as they do work if I try them independently. I am using the below code
Route::group(['middleware' => ['IsAdmin' or 'IsPatreon']], function(){
Route::resource('patreon', 'patreonGalleryController', ['names'=>[
'index'=>'patreonGallery.index',
'create'=>'patreonGallery.create',
'store'=>'patreonGallery.store',
'edit'=>'patreonGallery.edit',
'show'=>'patreonGallery.show',
'destroy'=>'patreonGallery.destroy',
]]);
});
How can I set it so that only either admin or patreon uses can see the paths?
Two middlewares are working separately.
IsAdmin is checking that user is admin
IsPatreon is checking that user is patreon...
You cannot merge these 2 middlewares by OR Operator
Probably you need to create new middelware, something like
IsAdminOrPatreon and do you checks inside of that middleware and assing that middleware to your Group..
Or you can try with middleware parameters, for example
Route::group(['middleware' => ['checkRoles:admin,patreon']], function(){
Route::resource('patreon', 'patreonGalleryController', ['names'=>[
'index'=>'patreonGallery.index',
'create'=>'patreonGallery.create',
'store'=>'patreonGallery.store',
'edit'=>'patreonGallery.edit',
'show'=>'patreonGallery.show',
'destroy'=>'patreonGallery.destroy',
]]);
});
And in you checkRoles middleware get the admin and patreaon roles like this:
public function handle($request, Closure $next) {
// will contain ['role1', 'role2']
$allowedRoles = array_slice(func_get_args(), 2);
// here you can loop and check your roles
}
Note! If you pass 'checkRoles:admin,patreon' you will get
array(admin,patreon)
If you pass 'checkRoles:admin' you will get
array(admin)
you can't use or condition inside middleware array. middleware array always return and condition. you can specify the user role inside your middleware.
gist sample role middleware
https://gist.github.com/ivanhoe011/931417be3e36b3f06e994bfe5cd004f9
You do something like this in your controller.
public function __construct()
{
return ($this->middleware('IsAdmin')) || $this->middleware('IsPatreon');
}
Each route on this controller will be authenticated by any one of middleware.
I am working on an application, Where i have three type of users(real scenario), The application have three areas Freelancers and Lms (Learning Management systems) and admin panel for both :
Admins => Users of the admin panel, Where all the statistics/data is there.
Freelancers section : Freelancers section signin/signup
Lms section => Learning management system section's users signin/signup
Currently i am not using any kind of multi auth functionalities, And whenever i login in a user in the freelancer section and i goes to the lms section the authenticated user is available there.
As i am using only one table for the users where i have a column userType(Which is not in use but there for future proofing).
I know a couple of packages Like this one. which i can implement but i haven't and thought that there might be a better way and stackoverflow community can provide one.
My question is how to handle this type of situation, Whats the most efficient and robust way.
This is how I would do it.
I am going to skip long details about setting up auth controllers and views. They are quite straight forward to scaffold using the artisan console.
The first thing we need is new field on your Users table. An admin field if you only have two levels (admin and non-admin). An ENUM value in your case.
Depending on what the value in that field is you want to grant (or not) access to certain sections/pages/resources etc.
The artisan console generates all the necessary pages, middle-ware, routes for a basic login. But after you have done that you will need a second middle-ware to check for different levels of access. Lets call it CheckAdmin.
Use the following command
php artisan make:middleware CheckAdmin. This creates a new middleware with specified name in app\Http\Middleware
Now register the middleware in Kernel.php (Last line of code). This gives the middleware class that we just created a name (admin in this case).
protected $routeMiddleware = [
'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'admin' => \App\Http\Middleware\CheckAdmin::class,
];
All the routes in your that check for certain admin rights should use the admin middleware we just registered right after the auth factory middleware provided with laravel. There are two ways of doing this depending how you are building your app/website.
A. NOT using a resources controllers for the route.
Go to your routes web.php file. And register the auth and admin middleware for a request. An example follows.
Route::get('/example-admin-only-route', function () { //SOME LOGIC
})->middleware('auth', 'admin');
B. USING resources controllers. (Which you probably should whenever you can)
In the constructor of the resource controller. ExampleController.php for our Example resource.
class ExampleController extends Controller
{
public function __construct()
{
$this->middleware('auth');
$this->middleware('admin');
}
...
}
Write the logic for your CheckAdmin middleware.
namespace App\Http\Middleware;
use Closure;
class CheckAdmin
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if($request->user()->admin == 'Admin') //or FreeLancers or LMS (or maybe Unicorns)
{
return $next($request);
}
else
{
return redirect("/not-worthy");
}
}
}
The code checks the admin privileges and either lets the request pass or does something else with it. In our-case redirects to a different end-point. But you may wish to do more creative things with it.
Cheers.
It's probably not multiauth you're looking for but permissions on certain pages. Take a look at: https://github.com/spatie/laravel-permission
It's very simple and straightforward. It even lets you single out users, that for example both have admin level, but just one can view it.
The most efficient and robust way is to use simple user_type column to store user type and create some helpers. For example, to check if user is an admin you can create something like this in the User model:
public function isAdmin()
{
return auth()->check() && auth()->user()->user_type === 3;
}
This is the simplest method with a lot of advantages. It's good for apps that use a lot of relationships, it's good for speed etc.
I have this path:
http://localhost:8000/home
I want when a regular user opens path above, then I call this controller:
mainPage#index
But when a admin opens that path, then I call this controller:
panelPage#index
So as you see, I'm looking for "dynamic routes" kinda .. Is implementing that possible? In other word, can I call two different controllers for admin and regular member ?
This is a good case to use Middlewares to filter HTTP requests.
You could also do something conditional in your routes file, like:
if (Auth::user()->isAdmin()){
Route::get('/', 'panelPage#index');
}
else {
Route::get('/', 'mainPage#index');
}
Depending on what your application looks like, you can define isAdmin() in your User model. This is a very simple example where you have a column called role_id and id nr 1 equals admin. If authenticated user is admin, it displays true, otherwise false:
public function isAdmin()
{
return Auth::user()->role_id == 1;
}
A more dynamic and advanced approach would be to create a role table, and associate the role with the user with a role_user pivot table.
If you want it to take a step further, you can create a permissions table and associate the role with the permissions with a permission_role pivot table. Then you can in your application define that a permission is needed to be able to do an action and add all the permissions that a given user role has in that pivot table. Then you just check if the user (with a specific role) has the given permissions.
For best practice, you could use Middleware to sort-out and categorize your routes and controllers.
In this - relatively simple - case, you could also use something like this (in your routes file):
if(!is_null(Auth::user())) {
// first check if user is logged in, else Auth::user() will return null
$uses = 'mainPage#index';
if(Auth::user()->admin) {
$uses = 'panelPage#index';
}
Route::get('/', $uses);
}
Update
Or you could wrap everything inside this if statement in an auth middleware group, like this:
Route::group(['middleware' => ['auth']], function(){
$uses = 'mainPage#index';
if(Auth::user()->admin) {
$uses = 'panelPage#index';
}
Route::get('/', $uses);
});
Also make sure that your users table has a column named 'admin'.
I am using Laravel 5.1 for my project. I am trying to secure Routes and make sure only logged in user can access certain routes. I am aware about middlewares but I am wondering if anyone post an example or a link explaining about middleware and how to protect a page using middleware.
Thanks
To build on the answer given by Joe Rose, you can also specify the middleware in your controller rather than in your routes.php file.
E.g you could have your routes set out like
Route::get('/example', 'ExampleController#index');
Route::post('/example/post', 'ExampleController#post');
Route::resource('blog', 'BlogController');
And then inside your controller reference it like so:
class ExampleController extends Controller
{
public function __construct()
{
$this->middleware('auth');
}
//....
If you're looking for more info, check out the link to the docs Joe gave, and also this blog post which explains what middleware is really well and how to create your own if you need to.
You are correct about using middleware. The included Auth middleware is what you should use, as long as you are also using the included Auth controller to authenticate users. You would write your route like this:
Route::get('/page', array(
'uses' => 'Controller#method',
'middleware'=>'auth'
));
(The above example is using a GET request, but it could other request types, like POST for example).
This will use the default behavior of the middleware which checks to see if the user is logged in (authenticated). You can also extend or overwrite the built-in functions to allow you to direct the application on where to send the user if they are or are not logged in, etc. Laravel's official documentation is a good starting point: link
I have a fairly simple ACL system set up. The filter checks if the user is part of a user group which has access to the route or if the user has access to the route. It works for individual routes and works for resources in general. However I want some users to have access to a specific method of a resource route, but not all of it. for example, user1 is part of the admin group and always has access to the admin resource route but user2 is not a part of the admin user group and I want to give him access to teh resource admin/create. How can I go about this in Laravel 4 with my setup
Database
Routes:
id
route
created_by
last_editted_by
created
updated
deleted_at
acl (table it looks at to see if user has access)
id
routes_id
user_id
group_id
created
updated
deleted_at
Filter
if (Auth::check()){
$route = Request::segment(1);
$user_id = Auth::user()->id;
$acl_count = Acls::join('routes','routes.id','=','acl.routes_id')
->where('routes.route','=',$route)
->Where(function($in_parenthesis) use($user_id){
$in_parenthesis->whereIn('acl.group_id',function($where_in) use($user_id){
$where_in->select('group_id')
->from('user_group_junction')
->where('user_id','=',$user_id);
})
->orWhere('acl.user_id','=',$user_id);
})
->count();
if($acl_count < 1){
return Redirect::to('/');
}
}else{
return Redirect::to('/');
}
Routes
Route::get('/','HomeController#index');
Route::get('login','AuthorizationController#loginForm');
Route::post('authenticate','HomeController#authenticate');
Route::get('logout','HomeController#logout');
Route::group(array('before'=>'auth'),function(){
Route::group(array('before'=>'user_permission'),function(){
Route::get('protected','HomeController#protectedPage');
Route::resource('sources', 'SourcesController');
Route::resource('admins', 'AdminsController');
});
});
You can use beforeFilter inside the __construct method of the AdminBaseController like this (create a different one for admin controllers only)
class AdminController extends AdminBaseController {
function __construct() {
// Use filter on all methods but not on create
$this->beforeFilter('admin', array('except' => array('create')));
}
}
Also, you may directly use this beforeFilter inside your resource controller and use except or you can use only (only works reverse of except, allows access to all but filters only mentioned methods in the array). You can also check conditions inside the constructor method s well.
I figured out that part of my problem is with the filter. I am only looking at the first segment of the url which doesn't work correctly if my route (whether it is a resource or just a route with a "/" in it) won't work. Thus I asked another question located here