Skip to content

feat: Integrate Laravel's built-in authorization Gates #73

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Aug 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading