Laravel : Setting dynamic routes based on access control list

为君一笑 提交于 2019-12-03 07:26:21

问题


I am building REST API with JWT authentication and authorization with own logic. It's working perfectly. Now, I want to set the routes dynamically based on roles and permission. Suppose I have database structure like:

Role:

id  |   name
1   |  school
2   | transport

Permissions:

id  |   name                   |  controller         | routes
1   |  view-class-result       |  ApiController      | getClassResult
2   |  view-student-result     |  ApiController      | studentResult
3   |  download-student-result |  ApiController      | donwloadSchoolTemplate

Permission_role

role_id |  permission_id
1            1
1            2
1            3

Now, I want to create routes according to roles and permission in database.

Currently my routes seems like:

//All JWT authentication API goes here
Route::group(['middleware' => 'jwt.auth'], function() {
   Route::get('user', 'ApiController@getAuthUser');
   Route::get('invalidate', 'ApiController@invalidate');

   //All authorized API goes here
   Route::group(['middleware' => 'ability:school,view-class-result,true'], function() {
       Route::post('classResult', 'ApiController@getClassResult');
   });
   Route::group(['middleware' => 'ability:school,view-student-result,true'], function() {
       Route::post('studentResult', 'ApiController@studentResult');
   });
   Route::group(['middleware' => 'ability:school,download-student-result,true'], function() {
       Route::post('getStudentExamResult', 'ApiController@downloadSchoolTemplate');
   });
});

I don't want above routes to be hard coded. How can I get this routes from database. Something like below. But couldnot get idea how to do it.

In routes file,

$a = User:all();
foreach($a->roles as $value){
   foreach($value->permission as $val){

      Route::group(['middleware' => 'ability:{$value->name},{$val->name},true'], function() {
         Route::post('{$val->controller}', '{$val->controller}@{$val->method}');
      });

   }
}

Thank you.


回答1:


The best idea was using middleware parameter create Middleware call CheckPermission then you have to register that middleware into your app/Http/kernel.php file thats only you need check below code

Your kernel.php file

protected $routeMiddleware = [    
        'checkPermission' => \App\Http\Middleware\CheckPermission::class,
    ];

CheckPermission.php

    <?php

    namespace App\Http\Middleware;
    use Closure;
    use DB;

    class CheckPermission
    {
        /**
         * Handle an incoming request.
         *
         * @param  \Illuminate\Http\Request  $request
         * @param  \Closure  $next
         * @return mixed
         */
        public function handle($request, Closure $next,$permission_name)
        {
            //first check that name in your db
            $permission = DB::table('Permission')->where('name',$permission_name)->first()
            if($permission){
              //here you have to get logged in user role
              $role_id = Auth::user()->role;
              ## so now check permission
              $check_permission = DB::table('Permission_role')->where('role_id',$role_id)->where('permission_id',$permission->id)->first();
              if($check_permission){
                 return $next($request);
              }
              //if Permission not assigned for this user role show what you need
            }
            // if Permission name not in table then do what you need 
            ## Ex1 : return 'Permission not in Database';
            ## Ex2 : return redirect()->back();

        }
    }

Your Route file

 Route::group(['middleware' => 'jwt.auth'], function() {
        Route::post('classResult', 'ApiController@getClassResult')->middleware('checkPermission:view-class-result');
        Route::post('studentResult', 'ApiController@studentResult')->middleware('checkPermission:view-student-result');
        Route::post('getStudentExamResult', 'ApiController@downloadSchoolTemplate')->middleware('checkPermission:download-student-result');

   }



回答2:


So what you can do is make your role name accountants a value to key in the .env file and same for each and every role name.

In case you want to change it in near future you can change it manually in the .env file or you can make changes in the .env file via php code writtern on one of your Laravel function.




回答3:


While I doubt that this is the best approach to this, but in your way of thinking you could try out this "pseudo code". I hope this expresses the basic idea. What that implies is:

  • A route pattern, not to include all routes explicitely in your routes file. ie. api/studentResult
  • Your controller to dispatch to the proper method that implements your api call via a single action controller (Link to documentation)
  • Your controller to load the correct middleware to take care on authorization

Routes

Route::group(['middleware' => 'jwt.auth'], function() {
    Route::get('user', 'ApiController@getAuthUser');
    Route::get('invalidate', 'ApiController@invalidate');

    // Choose whatever pattern you like...
    Route::post('api/{name}', ApiController::class);
});

Controller

class ApiController {

    public function __construct() {
        $permisions = $this->loadPersionForUser();

        $this->middleware('ability', [$permisions->value1, 'whatever']);
    }

    public function __invoke($method) {
        if (method_exists($this, $method)) {
            return $this->$method();
        }
    }
}

I'm not totally sure if you can load your middleware dynamically like this. If so, this could be a valid approach for this.




回答4:


In your routes.php

Route::group(['middleware' => 'jwt.auth'], function() {

Route::post('{uri}', 'AccessController@redirectURI');

});

Add this route at the end of all your routes.

Now create a new controller called AccessController and add the below constructor and method to it. Assuming user has a relationship with roles.

public function __construct(Request $request)
{
    $authorised_user = User::where('id', Auth::User()->id)->whereHas('role', function($query) use ($request)
            {
                $query->whereHas('permission', function($query) use ($request)
                {
                    $query->where('routes', $request->route('uri'))
                });
            })->firstOrFail();

            if( $authorised_user )
            {
                $permission = Permission::where('routes', $request->route('uri'))->findOrFail();

                $this->middleware([ 'ability:'.$authorised_user->role()->name.','.$permission->name.',true' ]);
            }
    else
    {
    // user is not authorised. Do what ever you want
    }

}

public function redirectURI($uri)
{
    $permission = Permission::where('routes', $uri)->findOrFail();

    return app('App\\Http\\Controllers\\'. $permission->controller )->$permission->method();
}

Overall it is retrieving the URL and comparing it against available routes in the authenticated user's permissions. If the authenticated user has the permission which the route belongs to, Then adding appropriate middleware.

Finally the redirectURI method is calling the appropriate controller method and returning the response.

Remember to replace the code with appropriate namespace and relations where ever necessary.



来源:https://stackoverflow.com/questions/48758552/laravel-setting-dynamic-routes-based-on-access-control-list

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!