Skip to content

Commit 11ffe28

Browse files
authored
feat: Integrate Laravel's built-in authorization Gates (#73)
- Integrate Laravel's built-in authorization Gates (#70) - Added guidance for Gates in README.md
1 parent 4d49aef commit 11ffe28

9 files changed

+211
-2
lines changed

Diff for: README.md

+13
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,19 @@ Route::group(['middleware' => ['http_request']], function () {
277277
});
278278
```
279279

280+
### Using Gates
281+
282+
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.
283+
284+
```php
285+
$user->can('articles,read');
286+
// For multiple enforcers
287+
$user->can('articles,read', 'second');
288+
// The methods cant, cannot, canAny, etc. also work
289+
```
290+
291+
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.
292+
280293
### Multiple enforcers
281294

282295
If you need multiple permission controls in your project, you can configure multiple enforcers.

Diff for: config/lauthz.php

+8
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,14 @@
66
*/
77
'default' => 'basic',
88

9+
/*
10+
* Lauthz Localizer
11+
*/
12+
'localizer' => [
13+
// changes whether enforcer will register at gates.
14+
'enabled_register_at_gates' => true
15+
],
16+
917
'basic' => [
1018
/*
1119
* Casbin model setting.

Diff for: src/EnforcerLocalizer.php

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php
2+
3+
namespace Lauthz;
4+
5+
use Illuminate\Contracts\Auth\Access\Authorizable;
6+
use Illuminate\Contracts\Auth\Access\Gate;
7+
use Lauthz\Facades\Enforcer;
8+
9+
class EnforcerLocalizer
10+
{
11+
/**
12+
* The application instance.
13+
*
14+
* @var \Illuminate\Foundation\Application
15+
*/
16+
protected $app;
17+
18+
/**
19+
* Create a new localizer instance.
20+
*
21+
* @param \Illuminate\Foundation\Application $app
22+
*/
23+
public function __construct($app)
24+
{
25+
$this->app = $app;
26+
}
27+
28+
/**
29+
* Register the localizer based on the configuration.
30+
*/
31+
public function register()
32+
{
33+
if ($this->app->config->get('lauthz.localizer.enabled_register_at_gates')) {
34+
$this->registerAtGate();
35+
}
36+
}
37+
38+
/**
39+
* Register the localizer at the gate.
40+
*/
41+
protected function registerAtGate()
42+
{
43+
$this->app->make(Gate::class)->before(function (Authorizable $user, string $ability, array $guards) {
44+
/** @var \Illuminate\Contracts\Auth\Authenticatable $user */
45+
$identifier = $user->getAuthIdentifier();
46+
if (method_exists($user, 'getAuthzIdentifier')) {
47+
/** @var \Lauthz\Tests\Models\User $user */
48+
$identifier = $user->getAuthzIdentifier();
49+
}
50+
$identifier = strval($identifier);
51+
$ability = explode(',', $ability);
52+
if (empty($guards)) {
53+
return Enforcer::enforce($identifier, ...$ability);
54+
}
55+
56+
foreach ($guards as $guard) {
57+
return Enforcer::guard($guard)->enforce($identifier, ...$ability);
58+
}
59+
});
60+
}
61+
}

Diff for: src/LauthzServiceProvider.php

+17
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Lauthz;
44

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

3334
$this->bootObserver();
35+
36+
$this->registerLocalizer();
3437
}
3538

3639
/**
@@ -55,5 +58,19 @@ public function register()
5558
$this->app->singleton(ModelLoaderManager::class, function ($app) {
5659
return new ModelLoaderManager($app);
5760
});
61+
62+
$this->app->singleton(EnforcerLocalizer::class, function ($app) {
63+
return new EnforcerLocalizer($app);
64+
});
65+
}
66+
67+
/**
68+
* Register a gate that allows users to use Laravel's built-in Gate to call Enforcer.
69+
*
70+
* @return void
71+
*/
72+
protected function registerLocalizer()
73+
{
74+
$this->app->make(EnforcerLocalizer::class)->register();
5875
}
5976
}

Diff for: src/Middlewares/EnforcerMiddleware.php

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ public function handle($request, Closure $next, ...$args)
3131
$user = Auth::user();
3232
$identifier = $user->getAuthIdentifier();
3333
if (method_exists($user, 'getAuthzIdentifier')) {
34+
/** @var \Lauthz\Tests\Models\User $user */
3435
$identifier = $user->getAuthzIdentifier();
3536
}
3637
$identifier = strval($identifier);

Diff for: tests/DatabaseAdapterTest.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22

33
namespace Lauthz\Tests;
44

5-
use Enforcer;
65
use Illuminate\Foundation\Testing\DatabaseMigrations;
76
use Casbin\Persist\Adapters\Filter;
87
use Casbin\Exceptions\InvalidFilterTypeException;
8+
use Lauthz\Facades\Enforcer;
99

1010
class DatabaseAdapterTest extends TestCase
1111
{

Diff for: tests/EnforcerCustomLocalizerTest.php

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
use Illuminate\Contracts\Auth\Access\Gate;
4+
use Illuminate\Foundation\Testing\DatabaseMigrations;
5+
use Lauthz\Tests\TestCase;
6+
7+
class EnforcerCustomLocalizerTest extends TestCase
8+
{
9+
use DatabaseMigrations;
10+
11+
public function testCustomRegisterAtGatesBefore()
12+
{
13+
$user = $this->user("alice");
14+
$this->assertFalse($user->can('data3,read'));
15+
16+
app(Gate::class)->before(function () {
17+
return true;
18+
});
19+
20+
$this->assertTrue($user->can('data3,read'));
21+
}
22+
23+
public function testCustomRegisterAtGatesDefine()
24+
{
25+
$user = $this->user("alice");
26+
$this->assertFalse($user->can('data3,read'));
27+
28+
app(Gate::class)->define('data3,read', function () {
29+
return true;
30+
});
31+
32+
$this->assertTrue($user->can('data3,read'));
33+
}
34+
35+
public function initConfig()
36+
{
37+
parent::initConfig();
38+
$this->app['config']->set('lauthz.localizer.enabled_register_at_gates', false);
39+
}
40+
}

Diff for: tests/EnforcerLocalizerTest.php

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?php
2+
3+
use Illuminate\Contracts\Auth\Access\Gate;
4+
use Illuminate\Foundation\Testing\DatabaseMigrations;
5+
use Lauthz\Facades\Enforcer;
6+
use Lauthz\Tests\TestCase;
7+
8+
class EnforcerLocalizerTest extends TestCase
9+
{
10+
use DatabaseMigrations;
11+
12+
public function testRegisterAtGates()
13+
{
14+
$user = $this->user('alice');
15+
$this->assertTrue($user->can('data1,read'));
16+
$this->assertFalse($user->can('data1,write'));
17+
$this->assertFalse($user->cannot('data2,read'));
18+
19+
Enforcer::guard('second')->addPolicy('alice', 'data1', 'read');
20+
$this->assertTrue($user->can('data1,read', 'second'));
21+
$this->assertFalse($user->can('data3,read', 'second'));
22+
}
23+
24+
public function testNotLogin()
25+
{
26+
$this->assertFalse(app(Gate::class)->allows('data1,read'));
27+
$this->assertTrue(app(Gate::class)->forUser($this->user('alice'))->allows('data1,read'));
28+
$this->assertFalse(app(Gate::class)->forUser($this->user('bob'))->allows('data1,read'));
29+
}
30+
31+
public function testAfterLogin()
32+
{
33+
$this->login('alice');
34+
$this->assertTrue(app(Gate::class)->allows('data1,read'));
35+
$this->assertTrue(app(Gate::class)->allows('data2,read'));
36+
$this->assertTrue(app(Gate::class)->allows('data2,write'));
37+
38+
$this->login('bob');
39+
$this->assertFalse(app(Gate::class)->allows('data1,read'));
40+
$this->assertTrue(app(Gate::class)->allows('data2,write'));
41+
}
42+
43+
public function initConfig()
44+
{
45+
parent::initConfig();
46+
$this->app['config']->set('lauthz.second.model.config_type', 'text');
47+
$this->app['config']->set(
48+
'lauthz.second.model.config_text',
49+
$this->getModelText()
50+
);
51+
}
52+
53+
protected function getModelText(): string
54+
{
55+
return <<<EOT
56+
[request_definition]
57+
r = sub, obj, act
58+
59+
[policy_definition]
60+
p = sub, obj, act
61+
62+
[policy_effect]
63+
e = some(where (p.eft == allow))
64+
65+
[matchers]
66+
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act
67+
EOT;
68+
}
69+
}

Diff for: tests/TestCase.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ public function createApplication()
2727
});
2828

2929
$this->app->make(Kernel::class)->bootstrap();
30+
$this->initConfig();
3031

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

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

0 commit comments

Comments
 (0)