Compare commits

...

3 Commits

Author SHA1 Message Date
Dobando a050a9c222
BREAKING CHANGE: upgrade to PHP 8.0 and PHP-Casbin 4.0 (#75) 2024-10-28 23:22:19 +08:00
Dobando 434ee8003f
docs: fix CI badge and update TOC in README.md (#74)
* feat: Integrate Laravel's built-in authorization Gates

- Integrate Laravel's built-in authorization Gates (#70)
- Added guidance for Gates in README.md

* docs: fix CI badge and update TOC in README.md
2024-08-08 10:23:35 +08:00
Dobando 11ffe28750
feat: Integrate Laravel's built-in authorization Gates (#73)
- Integrate Laravel's built-in authorization Gates (#70)
- Added guidance for Gates in README.md
2024-08-07 23:18:59 +08:00
13 changed files with 226 additions and 85 deletions

View File

@ -27,73 +27,7 @@ jobs:
# laravel: [ ] # laravel: [ ]
# stability: [ prefer-lowest, prefer-stable ] # stability: [ prefer-lowest, prefer-stable ]
include: include:
# Laravel 5.5
- php: 7.1
laravel: 5.5.*
phpunit: ~6.0
- php: 7.2
laravel: 5.5.*
phpunit: ~6.0
- php: 7.3
laravel: 5.5.*
phpunit: ~6.0
# Laravel 5.6
- php: 7.1
laravel: 5.6.*
phpunit: ~7.0
- php: 7.2
laravel: 5.6.*
phpunit: ~7.0
- php: 7.3
laravel: 5.6.*
phpunit: ~7.0
# Laravel 5.7
- php: 7.1
laravel: 5.7.*
phpunit: ~7.5
- php: 7.2
laravel: 5.7.*
phpunit: ~7.5
- php: 7.3
laravel: 5.7.*
phpunit: ~7.5
# Laravel 5.8
- php: 7.1
laravel: 5.8.*
phpunit: ~7.5
- php: 7.2
laravel: 5.8.*
phpunit: ~8.0
- php: 7.3
laravel: 5.8.*
phpunit: ~8.0
# Laravel 6.x
- php: 7.2
laravel: 6.*
phpunit: ~8.0
- php: 7.3
laravel: 6.*
phpunit: ~8.0
# Laravel 7.x
- php: 7.3
laravel: 7.*
phpunit: ~9.0
- php: 7.4
laravel: 7.*
phpunit: ~9.0
# Laravel 8.x # Laravel 8.x
- php: 7.3
laravel: 8.*
phpunit: ~9.0
- php: 7.4
laravel: 8.*
phpunit: ~9.0
- php: 8.0 - php: 8.0
laravel: 8.* laravel: 8.*
phpunit: ~9.0 phpunit: ~9.0

View File

@ -8,7 +8,7 @@
<p align="center"> <p align="center">
<a href="https://github.com/php-casbin/laravel-authz/actions"> <a href="https://github.com/php-casbin/laravel-authz/actions">
<img src="https://github.com/php-casbin/laravel-authz/workflows/build/badge.svg?branch=master" alt="Build Status"> <img src="https://github.com/php-casbin/laravel-authz/actions/workflows/build.yml/badge.svg?branch=master" alt="Build Status">
</a> </a>
<a href="https://coveralls.io/github/php-casbin/laravel-authz"> <a href="https://coveralls.io/github/php-casbin/laravel-authz">
<img src="https://coveralls.io/repos/github/php-casbin/laravel-authz/badge.svg" alt="Coverage Status"> <img src="https://coveralls.io/repos/github/php-casbin/laravel-authz/badge.svg" alt="Coverage Status">
@ -35,6 +35,7 @@ All you need to learn to use `Casbin` first.
* [Using a middleware](#using-a-middleware) * [Using a middleware](#using-a-middleware)
* [basic Enforcer Middleware](#basic-enforcer-middleware) * [basic Enforcer Middleware](#basic-enforcer-middleware)
* [HTTP Request Middleware ( RESTful is also supported )](#http-request-middleware--restful-is-also-supported-) * [HTTP Request Middleware ( RESTful is also supported )](#http-request-middleware--restful-is-also-supported-)
* [Using Gates](#using-gates)
* [Multiple enforcers](#multiple-enforcers) * [Multiple enforcers](#multiple-enforcers)
* [Using artisan commands](#using-artisan-commands) * [Using artisan commands](#using-artisan-commands)
* [Cache](#using-cache) * [Cache](#using-cache)
@ -277,6 +278,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 ### Multiple enforcers
If you need multiple permission controls in your project, you can configure multiple enforcers. If you need multiple permission controls in your project, you can configure multiple enforcers.

View File

@ -20,18 +20,17 @@
], ],
"license": "Apache-2.0", "license": "Apache-2.0",
"require": { "require": {
"php": ">=7.1.0", "php": ">=8.0",
"illuminate/support": "~5.5|~6.0|~7.0|~8.0|~9.0|~10.0|~11.0", "illuminate/support": "~8.0|~9.0|~10.0|~11.0",
"illuminate/database": "~5.5|~6.0|~7.0|~8.0|~9.0|~10.0|~11.0", "illuminate/database": "~8.0|~9.0|~10.0|~11.0",
"illuminate/console": "~5.5|~6.0|~7.0|~8.0|~9.0|~10.0|~11.0", "illuminate/console": "~8.0|~9.0|~10.0|~11.0",
"casbin/casbin": "~3.1", "casbin/casbin": "~4.0"
"casbin/psr3-bridge": "^1.1"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "~7.0|~8.0|~9.0|~10.5", "phpunit/phpunit": "~9.0|~10.5",
"php-coveralls/php-coveralls": "^2.4", "php-coveralls/php-coveralls": "^2.7",
"mockery/mockery": "^1.0", "mockery/mockery": "^1.0",
"laravel/laravel": "~5.5|~6.0|~7.0|~8.0|~9.0|~10.0|~11.0" "laravel/laravel": "~9.0|~10.0|~11.0"
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {

View File

@ -6,6 +6,14 @@ return [
*/ */
'default' => 'basic', 'default' => 'basic',
/*
* Lauthz Localizer
*/
'localizer' => [
// changes whether enforcer will register at gates.
'enabled_register_at_gates' => true
],
'basic' => [ 'basic' => [
/* /*
* Casbin model setting. * Casbin model setting.

61
src/EnforcerLocalizer.php Normal file
View File

@ -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);
}
});
}
}

View File

@ -2,10 +2,10 @@
namespace Lauthz; namespace Lauthz;
use Casbin\Bridge\Logger\LoggerBridge;
use Casbin\Enforcer; use Casbin\Enforcer;
use Casbin\Model\Model; use Casbin\Model\Model;
use Casbin\Log\Log; use Casbin\Log\Log;
use Casbin\Log\Logger\DefaultLogger;
use Lauthz\Contracts\Factory; use Lauthz\Contracts\Factory;
use Lauthz\Models\Rule; use Lauthz\Models\Rule;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
@ -80,10 +80,10 @@ class EnforcerManager implements Factory
if ($logger = Arr::get($config, 'log.logger')) { if ($logger = Arr::get($config, 'log.logger')) {
if (is_string($logger)) { if (is_string($logger)) {
$logger = $this->app->make($logger); $logger = new DefaultLogger($this->app->make($logger));
} }
Log::setLogger(new LoggerBridge($logger)); Log::setLogger($logger);
} }
$model = new Model(); $model = new Model();
@ -98,7 +98,7 @@ class EnforcerManager implements Factory
]); ]);
} }
return new Enforcer($model, $adapter, Arr::get($config, 'log.enabled', false)); return new Enforcer($model, $adapter, $logger, Arr::get($config, 'log.enabled', false));
} }
/** /**

View File

@ -3,6 +3,7 @@
namespace Lauthz; namespace Lauthz;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
use Lauthz\EnforcerLocalizer;
use Lauthz\Loaders\ModelLoaderManager; use Lauthz\Loaders\ModelLoaderManager;
use Lauthz\Models\Rule; use Lauthz\Models\Rule;
use Lauthz\Observers\RuleObserver; use Lauthz\Observers\RuleObserver;
@ -31,6 +32,8 @@ class LauthzServiceProvider extends ServiceProvider
$this->mergeConfigFrom(__DIR__ . '/../config/lauthz.php', 'lauthz'); $this->mergeConfigFrom(__DIR__ . '/../config/lauthz.php', 'lauthz');
$this->bootObserver(); $this->bootObserver();
$this->registerLocalizer();
} }
/** /**
@ -55,5 +58,19 @@ class LauthzServiceProvider extends ServiceProvider
$this->app->singleton(ModelLoaderManager::class, function ($app) { $this->app->singleton(ModelLoaderManager::class, function ($app) {
return new ModelLoaderManager($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();
} }
} }

View File

@ -31,6 +31,7 @@ class EnforcerMiddleware
$user = Auth::user(); $user = Auth::user();
$identifier = $user->getAuthIdentifier(); $identifier = $user->getAuthIdentifier();
if (method_exists($user, 'getAuthzIdentifier')) { if (method_exists($user, 'getAuthzIdentifier')) {
/** @var \Lauthz\Tests\Models\User $user */
$identifier = $user->getAuthzIdentifier(); $identifier = $user->getAuthzIdentifier();
} }
$identifier = strval($identifier); $identifier = strval($identifier);

View File

@ -2,11 +2,9 @@
namespace Lauthz\Tests; namespace Lauthz\Tests;
use Enforcer;
use Lauthz\Models\Rule; use Lauthz\Models\Rule;
use Illuminate\Foundation\Testing\DatabaseMigrations; use Illuminate\Foundation\Testing\DatabaseMigrations;
use Casbin\Persist\Adapters\Filter; use Lauthz\Facades\Enforcer;
use Casbin\Exceptions\InvalidFilterTypeException;
class DatabaseAdapterForCacheTest extends TestCase class DatabaseAdapterForCacheTest extends TestCase
{ {

View File

@ -2,10 +2,10 @@
namespace Lauthz\Tests; namespace Lauthz\Tests;
use Enforcer;
use Illuminate\Foundation\Testing\DatabaseMigrations; use Illuminate\Foundation\Testing\DatabaseMigrations;
use Casbin\Persist\Adapters\Filter; use Casbin\Persist\Adapters\Filter;
use Casbin\Exceptions\InvalidFilterTypeException; use Casbin\Exceptions\InvalidFilterTypeException;
use Lauthz\Facades\Enforcer;
class DatabaseAdapterTest extends TestCase class DatabaseAdapterTest extends TestCase
{ {

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -27,9 +27,9 @@ abstract class TestCase extends BaseTestCase
}); });
$this->app->make(Kernel::class)->bootstrap(); $this->app->make(Kernel::class)->bootstrap();
$this->initConfig();
$this->app->register(\Lauthz\LauthzServiceProvider::class); $this->app->register(\Lauthz\LauthzServiceProvider::class);
$this->initConfig();
$this->artisan('vendor:publish', ['--provider' => 'Lauthz\LauthzServiceProvider']); $this->artisan('vendor:publish', ['--provider' => 'Lauthz\LauthzServiceProvider']);
$this->artisan('migrate', ['--force' => true]); $this->artisan('migrate', ['--force' => true]);