25 Mayıs 2016 Çarşamba

How to Implement ACL Mechanism into Laravel?

Due to my daily security research and QA job in Netsparker, I have not dealt with programming, especially Laravel for a while.

However I've thought to write an article about implementing ACL to Laravel. Because in days I was seeking a solution, I ran into many in market but eventually I decided to write my own. Because I had to scale it according to my needs.

Yes, let's start to implementation.

Please note that, I tested this solution with Laravel 5.1.


1) First of all, we depend on Zend ACL library, so we have to add it into Laravel, to do this, we have to add lines below  into composer.json:

"require": {
"php": ">=5.5.9",
"laravel/framework": "5.1.*",
"zendframework/zend-permissions-acl": "~2.3"
},
2) Create a directory in app/Providers and name it as ZendAcl.  After creating the directory, create a PHP file in the directory, then name it as ZendAclServiceProvider.php. Put the below lines into the PHP file you've just created:

<?php namespace App\Providers\ZendAcl;
use Illuminate\Support\ServiceProvider;
use Zend\Permissions\Acl\Acl;
class ZendAclServiceProvider extends ServiceProvider {
private $AclInstance;
/**
* Bootstrap the application services.
*
* @return void
*/
public function boot() {
$this->AclInstance->addResource('page');
$this->AclInstance->addRole('test');
$this->AclInstance->allow('test', 'page');
}
/**
* Register the application services.
*
* @return void
*/
public function register()
{
$this->AclInstance = new Acl();
$this->app->singleton("Acl",function() {
return $this->AclInstance;
});
}
}
3) Create another directory in ZendAcl directory, and name the new directory as Facades. After creating Facades directory, create a PHP file named ACL.php and write the lines below into ACL.php:

<?php namespace App\Providers\ZendAcl\Facades;
use Illuminate\Support\Facades\Facade;
class Acl extends Facade {
/**
* Get the registered name of the component.
*
* @return string
*/
protected static function getFacadeAccessor() {
return "Acl";
}
}
view raw ACL.php hosted with ❤ by GitHub
4) We should set providers and put ACL in providers array in config/app.php. Please pay special attention to line 36 and 174.
<?php
return [
/*
|--------------------------------------------------------------------------
| Application Debug Mode
|--------------------------------------------------------------------------
|
| When your application is in debug mode, detailed error messages with
| stack traces will be shown on every error that occurs within your
| application. If disabled, a simple generic error page is shown.
|
*/
'debug' => env('APP_DEBUG', false),
/*
|--------------------------------------------------------------------------
| Application URL
|--------------------------------------------------------------------------
|
| This URL is used by the console to properly generate URLs when using
| the Artisan command line tool. You should set this to the root of
| your application so that it is used when running Artisan tasks.
|
*/
'url' => 'http://ziyahan.local',
/*
|--------------------------------------------------------------------------
| Application Timezone
|--------------------------------------------------------------------------
|
| Here you may specify the default timezone for your application, which
| will be used by the PHP date and date-time functions. We have gone
| ahead and set this to a sensible default for you out of the box.
|
*/
'timezone' => 'UTC',
/*
|--------------------------------------------------------------------------
| Application Locale Configuration
|--------------------------------------------------------------------------
|
| The application locale determines the default locale that will be used
| by the translation service provider. You are free to set this value
| to any of the locales which will be supported by the application.
|
*/
'locale' => 'en',
/*
|--------------------------------------------------------------------------
| Application Fallback Locale
|--------------------------------------------------------------------------
|
| The fallback locale determines the locale to use when the current one
| is not available. You may change the value to correspond to any of
| the language folders that are provided through your application.
|
*/
'fallback_locale' => 'en',
/*
|--------------------------------------------------------------------------
| Encryption Key
|--------------------------------------------------------------------------
|
| This key is used by the Illuminate encrypter service and should be set
| to a random, 32 character string, otherwise these encrypted strings
| will not be safe. Please do this before deploying an application!
|
*/
'key' => env('APP_KEY', 'YOUR_APP_KEY'),
'cipher' => 'AES-256-CBC',
/*
|--------------------------------------------------------------------------
| Logging Configuration
|--------------------------------------------------------------------------
|
| Here you may configure the log settings for your application. Out of
| the box, Laravel uses the Monolog PHP logging library. This gives
| you a variety of powerful log handlers / formatters to utilize.
|
| Available Settings: "single", "daily", "syslog", "errorlog"
|
*/
'log' => 'single',
/*
|--------------------------------------------------------------------------
| Autoloaded Service Providers
|--------------------------------------------------------------------------
|
| The service providers listed here will be automatically loaded on the
| request to your application. Feel free to add your own services to
| this array to grant expanded functionality to your applications.
|
*/
'providers' => [
/*
* Laravel Framework Service Providers...
*/
Illuminate\Foundation\Providers\ArtisanServiceProvider::class,
Illuminate\Auth\AuthServiceProvider::class,
Illuminate\Broadcasting\BroadcastServiceProvider::class,
Illuminate\Bus\BusServiceProvider::class,
Illuminate\Cache\CacheServiceProvider::class,
Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class,
Illuminate\Routing\ControllerServiceProvider::class,
Illuminate\Cookie\CookieServiceProvider::class,
Illuminate\Database\DatabaseServiceProvider::class,
Illuminate\Encryption\EncryptionServiceProvider::class,
Illuminate\Filesystem\FilesystemServiceProvider::class,
Illuminate\Foundation\Providers\FoundationServiceProvider::class,
Illuminate\Hashing\HashServiceProvider::class,
Illuminate\Mail\MailServiceProvider::class,
Illuminate\Pagination\PaginationServiceProvider::class,
Illuminate\Pipeline\PipelineServiceProvider::class,
Illuminate\Queue\QueueServiceProvider::class,
Illuminate\Redis\RedisServiceProvider::class,
Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
Illuminate\Session\SessionServiceProvider::class,
Illuminate\Translation\TranslationServiceProvider::class,
Illuminate\Validation\ValidationServiceProvider::class,
Illuminate\View\ViewServiceProvider::class,
/*
* Application Service Providers...
*/
App\Providers\AppServiceProvider::class,
App\Providers\AuthServiceProvider::class,
App\Providers\EventServiceProvider::class,
App\Providers\RouteServiceProvider::class,
App\Modules\ServiceProvider::class,
App\Providers\ZendAcl\ZendAclServiceProvider::class,
],
/*
|--------------------------------------------------------------------------
| Class Aliases
|--------------------------------------------------------------------------
|
| This array of class aliases will be registered when this application
| is started. However, feel free to register as many as you wish as
| the aliases are "lazy" loaded so they don't hinder performance.
|
*/
'aliases' => [
'App' => Illuminate\Support\Facades\App::class,
'Artisan' => Illuminate\Support\Facades\Artisan::class,
'Auth' => Illuminate\Support\Facades\Auth::class,
'Blade' => Illuminate\Support\Facades\Blade::class,
'Bus' => Illuminate\Support\Facades\Bus::class,
'Cache' => Illuminate\Support\Facades\Cache::class,
'Config' => Illuminate\Support\Facades\Config::class,
'Cookie' => Illuminate\Support\Facades\Cookie::class,
'Crypt' => Illuminate\Support\Facades\Crypt::class,
'DB' => Illuminate\Support\Facades\DB::class,
'Eloquent' => Illuminate\Database\Eloquent\Model::class,
'Event' => Illuminate\Support\Facades\Event::class,
'File' => Illuminate\Support\Facades\File::class,
'Gate' => Illuminate\Support\Facades\Gate::class,
'Hash' => Illuminate\Support\Facades\Hash::class,
'Input' => Illuminate\Support\Facades\Input::class,
'Inspiring' => Illuminate\Foundation\Inspiring::class,
'Lang' => Illuminate\Support\Facades\Lang::class,
'Log' => Illuminate\Support\Facades\Log::class,
'Mail' => Illuminate\Support\Facades\Mail::class,
'Password' => Illuminate\Support\Facades\Password::class,
'Queue' => Illuminate\Support\Facades\Queue::class,
'Redirect' => Illuminate\Support\Facades\Redirect::class,
'Redis' => Illuminate\Support\Facades\Redis::class,
'Request' => Illuminate\Support\Facades\Request::class,
'Response' => Illuminate\Support\Facades\Response::class,
'Route' => Illuminate\Support\Facades\Route::class,
'Schema' => Illuminate\Support\Facades\Schema::class,
'Session' => Illuminate\Support\Facades\Session::class,
'Storage' => Illuminate\Support\Facades\Storage::class,
'URL' => Illuminate\Support\Facades\URL::class,
'Validator' => Illuminate\Support\Facades\Validator::class,
'View' => Illuminate\Support\Facades\View::class,
'ZendAcl' => App\Providers\ZendAcl\Facades\Acl::class,
],
];
view raw app.php hosted with ❤ by GitHub

5) At the heart of ACL, there is middleware. You know that middlewares are mechanism that meets you before entering application. In this implementation we have a middleware named CheckPermission. We're going to create a PHP file named CheckPermission.php and locate it under app/Http/Middleware:

<?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)
{
$aclInstance = app()->make("Acl");
$Response = app()->make("Response");
$resource =$this->getResource($request);
if($request->user()) {
$userRole = $request->user()->role;
do {
if($aclInstance->isAllowed($userRole->role_slug, $resource)) {
return $next($request);
}
}
while($userRole = $userRole->parents);
return $Response::view('errors.403', array(), 403);
} else {
if($aclInstance->isAllowed("guest", $resource)) {
return $next($request);
} else {
return redirect()->route("Home");
}
}
}
function getResource(&$request) {
return $request->route()->getAction()["as"];
}
}
6) When define a route, you can set a resource name with "as" param to this route. ACL mechanism will check user role, the resource name of route and decide whether request is allowed.

// You can define a spesific route
Route::get('user/{id}/profile', ['middleware'=>'acl','as' => 'UserProfile', function ($id) {
//
}]);
view raw routes.php hosted with ❤ by GitHub
To stay in scope of the blog post, I've had to keep it simple.  A skeleton application contains my module implementation and this ACL implementation is ready. If you want to take a look on it, please contact me.  Soon, I will share it as public.





Hiç yorum yok: