4

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

davejal
  • 6,009
  • 10
  • 39
  • 82
  • What's the problem with explicit declaration like this? Also, imagine you had a full control over this – how would you see it, is there something to get rid of in the existing syntax, really? – Denis Mysenko Jan 15 '16 at 00:48
  • By doing it explicitly I though it wasn't as `clean` as it could be. What do you mean or refer to in your second question? – davejal Jan 15 '16 at 00:52
  • I mean, imagine you could write it any way you wanted to. Would you actually make it shorter/simpler? Besides repeating 'uses' part (and even that - not completely) - I don't see much of repetition anyway! Moreover, as people say, routes.php is a documentation and when it's explicit like this - it's a good documentation. – Denis Mysenko Jan 15 '16 at 01:20
  • So you're actually suggesting to create all routes explicitly? So if I had 10 models I would have to create 30 routes if each model has create,update and delete? And different authorization levels (different kind of users with different roles). – davejal Jan 15 '16 at 01:27

2 Answers2

2

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).

Siphon
  • 1,041
  • 8
  • 17
  • This is pretty detailed, but I will need some time to grasp the idea. While I'm doing that could you explain the db structure needed, or at least the parts that are not similar? – davejal Jan 28 '16 at 12:06
  • The differences between my database and the tutorial database are insignificant; the important aspects of the structures are identical. The really important part of my code is where it uses the `'as'` of a route instead of explicitly defining the individual permissions in the route. What you need to do is match the permission slug to the route alias to confirm permission to a resource. – Siphon Jan 28 '16 at 13:46
  • And to explain further. My `User::hasPermission(Auth::user()->id, $action_alias)` could be replaced with the tutorial `$request->user()->can($permission)` where the `$permission` should be my `$action_alias`. – Siphon Jan 28 '16 at 14:16
  • I'm a little lost trying to implement this, is their some way you could explain the routes a little better (referring to either step by step or chatroom) or should I provide more details to what I'm doing, although it is pretty straightforward. – davejal Jan 29 '16 at 19:35
  • I'll add a step-by-step shortly. – Siphon Jan 29 '16 at 19:46
  • i.e: how would the adminController look like? And how should I translate what the `$page_map` does? – davejal Jan 29 '16 at 19:50
  • You might be over-thinking the solution. All of the ACL work is done in the middleware and the database models. The controller and routes are only examples to show how to add the middleware. The `route.php` code that is included above shows how to add the middleware to only a group of routes (admin routes in my example). The actual routes themselves are irrelevant to getting the ACL functional, as long as the middleware runs when that route is accessed via a web browser. – Siphon Jan 29 '16 at 20:55
  • yes I'm lost in this, pretty new to Laravel and already trying out the intermediate stuff ... – davejal Jan 29 '16 at 21:04
  • Sorry for the delay. I completely changed the format of my question. It now has a step-by-step format instead. – Siphon Feb 01 '16 at 15:18
  • no problem, it already looks promising. I will test it tonight. – davejal Feb 01 '16 at 15:22
  • finally really finished it and tested it. I had to do some minor changes but it works now. I will update the change tommorow, it's late now. – davejal Feb 10 '16 at 03:25
0

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

Raymond Cheng
  • 2,425
  • 21
  • 34