Skip to content

Commit

Permalink
feat: Integrate Laravel's built-in authorization Gates (#73)
Browse files Browse the repository at this point in the history
- Integrate Laravel's built-in authorization Gates (#70)
- Added guidance for Gates in README.md
  • Loading branch information
Dobmod authored Aug 7, 2024
1 parent 4d49aef commit 11ffe28
Show file tree
Hide file tree
Showing 9 changed files with 211 additions and 2 deletions.
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,19 @@ Route::group(['middleware' => ['http_request']], function () {
});
```

### Using Gates

You can use Laravel Gates to check if a user has a permission, provided that you have set an existing user instance as the currently authenticated user.

```php
$user->can('articles,read');
// For multiple enforcers
$user->can('articles,read', 'second');
// The methods cant, cannot, canAny, etc. also work
```

If you require custom Laravel Gates, you can disable the automatic registration by setting `enabled_register_at_gates` to `false` in the lauthz file. After that, you can use `Gates::before` or `Gates::after` in your ServiceProvider to register custom Gates. See [Gates](https://laravel.com/docs/11.x/authorization#gates) for more details.

### Multiple enforcers

If you need multiple permission controls in your project, you can configure multiple enforcers.
Expand Down
8 changes: 8 additions & 0 deletions config/lauthz.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@
*/
'default' => 'basic',

/*
* Lauthz Localizer
*/
'localizer' => [
// changes whether enforcer will register at gates.
'enabled_register_at_gates' => true
],

'basic' => [
/*
* Casbin model setting.
Expand Down
61 changes: 61 additions & 0 deletions src/EnforcerLocalizer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

namespace Lauthz;

use Illuminate\Contracts\Auth\Access\Authorizable;
use Illuminate\Contracts\Auth\Access\Gate;
use Lauthz\Facades\Enforcer;

class EnforcerLocalizer
{
/**
* The application instance.
*
* @var \Illuminate\Foundation\Application
*/
protected $app;

/**
* Create a new localizer instance.
*
* @param \Illuminate\Foundation\Application $app
*/
public function __construct($app)
{
$this->app = $app;
}

/**
* Register the localizer based on the configuration.
*/
public function register()
{
if ($this->app->config->get('lauthz.localizer.enabled_register_at_gates')) {
$this->registerAtGate();
}
}

/**
* Register the localizer at the gate.
*/
protected function registerAtGate()
{
$this->app->make(Gate::class)->before(function (Authorizable $user, string $ability, array $guards) {
/** @var \Illuminate\Contracts\Auth\Authenticatable $user */
$identifier = $user->getAuthIdentifier();
if (method_exists($user, 'getAuthzIdentifier')) {
/** @var \Lauthz\Tests\Models\User $user */
$identifier = $user->getAuthzIdentifier();
}
$identifier = strval($identifier);
$ability = explode(',', $ability);
if (empty($guards)) {
return Enforcer::enforce($identifier, ...$ability);
}

foreach ($guards as $guard) {
return Enforcer::guard($guard)->enforce($identifier, ...$ability);
}
});
}
}
17 changes: 17 additions & 0 deletions src/LauthzServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Lauthz;

use Illuminate\Support\ServiceProvider;
use Lauthz\EnforcerLocalizer;
use Lauthz\Loaders\ModelLoaderManager;
use Lauthz\Models\Rule;
use Lauthz\Observers\RuleObserver;
Expand Down Expand Up @@ -31,6 +32,8 @@ public function boot()
$this->mergeConfigFrom(__DIR__ . '/../config/lauthz.php', 'lauthz');

$this->bootObserver();

$this->registerLocalizer();
}

/**
Expand All @@ -55,5 +58,19 @@ public function register()
$this->app->singleton(ModelLoaderManager::class, function ($app) {
return new ModelLoaderManager($app);
});

$this->app->singleton(EnforcerLocalizer::class, function ($app) {
return new EnforcerLocalizer($app);
});
}

/**
* Register a gate that allows users to use Laravel's built-in Gate to call Enforcer.
*
* @return void
*/
protected function registerLocalizer()
{
$this->app->make(EnforcerLocalizer::class)->register();
}
}
1 change: 1 addition & 0 deletions src/Middlewares/EnforcerMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public function handle($request, Closure $next, ...$args)
$user = Auth::user();
$identifier = $user->getAuthIdentifier();
if (method_exists($user, 'getAuthzIdentifier')) {
/** @var \Lauthz\Tests\Models\User $user */
$identifier = $user->getAuthzIdentifier();
}
$identifier = strval($identifier);
Expand Down
2 changes: 1 addition & 1 deletion tests/DatabaseAdapterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

namespace Lauthz\Tests;

use Enforcer;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Casbin\Persist\Adapters\Filter;
use Casbin\Exceptions\InvalidFilterTypeException;
use Lauthz\Facades\Enforcer;

class DatabaseAdapterTest extends TestCase
{
Expand Down
40 changes: 40 additions & 0 deletions tests/EnforcerCustomLocalizerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

use Illuminate\Contracts\Auth\Access\Gate;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Lauthz\Tests\TestCase;

class EnforcerCustomLocalizerTest extends TestCase
{
use DatabaseMigrations;

public function testCustomRegisterAtGatesBefore()
{
$user = $this->user("alice");
$this->assertFalse($user->can('data3,read'));

app(Gate::class)->before(function () {
return true;
});

$this->assertTrue($user->can('data3,read'));
}

public function testCustomRegisterAtGatesDefine()
{
$user = $this->user("alice");
$this->assertFalse($user->can('data3,read'));

app(Gate::class)->define('data3,read', function () {
return true;
});

$this->assertTrue($user->can('data3,read'));
}

public function initConfig()
{
parent::initConfig();
$this->app['config']->set('lauthz.localizer.enabled_register_at_gates', false);
}
}
69 changes: 69 additions & 0 deletions tests/EnforcerLocalizerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

use Illuminate\Contracts\Auth\Access\Gate;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Lauthz\Facades\Enforcer;
use Lauthz\Tests\TestCase;

class EnforcerLocalizerTest extends TestCase
{
use DatabaseMigrations;

public function testRegisterAtGates()
{
$user = $this->user('alice');
$this->assertTrue($user->can('data1,read'));
$this->assertFalse($user->can('data1,write'));
$this->assertFalse($user->cannot('data2,read'));

Enforcer::guard('second')->addPolicy('alice', 'data1', 'read');
$this->assertTrue($user->can('data1,read', 'second'));
$this->assertFalse($user->can('data3,read', 'second'));
}

public function testNotLogin()
{
$this->assertFalse(app(Gate::class)->allows('data1,read'));
$this->assertTrue(app(Gate::class)->forUser($this->user('alice'))->allows('data1,read'));
$this->assertFalse(app(Gate::class)->forUser($this->user('bob'))->allows('data1,read'));
}

public function testAfterLogin()
{
$this->login('alice');
$this->assertTrue(app(Gate::class)->allows('data1,read'));
$this->assertTrue(app(Gate::class)->allows('data2,read'));
$this->assertTrue(app(Gate::class)->allows('data2,write'));

$this->login('bob');
$this->assertFalse(app(Gate::class)->allows('data1,read'));
$this->assertTrue(app(Gate::class)->allows('data2,write'));
}

public function initConfig()
{
parent::initConfig();
$this->app['config']->set('lauthz.second.model.config_type', 'text');
$this->app['config']->set(
'lauthz.second.model.config_text',
$this->getModelText()
);
}

protected function getModelText(): string
{
return <<<EOT
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act
EOT;
}
}
2 changes: 1 addition & 1 deletion tests/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ public function createApplication()
});

$this->app->make(Kernel::class)->bootstrap();
$this->initConfig();

$this->app->register(\Lauthz\LauthzServiceProvider::class);
$this->initConfig();

$this->artisan('vendor:publish', ['--provider' => 'Lauthz\LauthzServiceProvider']);
$this->artisan('migrate', ['--force' => true]);
Expand Down

0 comments on commit 11ffe28

Please sign in to comment.