Compare commits

...

5 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
Dobando 4d49aef1ab
feat: use Laravel's built-in Manager class (#72)
- Use Laravel's built-in abstract Manager class instead of ModelLoaderFactory (#71)
2024-07-05 19:16:47 +08:00
Pike 259a389595
feat: loading model from remote url (#69)
* feat: loading model from remote url

* Apply suggestions from code review

---------

Co-authored-by: Jon <techlee@qq.com>
2024-06-16 01:18:49 +08:00
20 changed files with 652 additions and 95 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,17 +6,27 @@ 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.
*/ */
'model' => [ 'model' => [
// Available Settings: "file", "text" // Available Settings: "file", "text", "url"
'config_type' => 'file', 'config_type' => 'file',
'config_file_path' => __DIR__ . DIRECTORY_SEPARATOR . 'lauthz-rbac-model.conf', 'config_file_path' => __DIR__ . DIRECTORY_SEPARATOR . 'lauthz-rbac-model.conf',
'config_text' => '', 'config_text' => '',
'config_url' => ''
], ],
/* /*

View File

@ -0,0 +1,17 @@
<?php
namespace Lauthz\Contracts;
use Casbin\Model\Model;
interface ModelLoader
{
/**
* Loads model definitions into the provided model object.
*
* @param Model $model
* @return void
*/
function loadModel(Model $model): void;
}

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,14 +2,15 @@
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;
use InvalidArgumentException; use InvalidArgumentException;
use Lauthz\Loaders\ModelLoaderManager;
/** /**
* @mixin \Casbin\Enforcer * @mixin \Casbin\Enforcer
@ -79,19 +80,17 @@ 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();
$configType = Arr::get($config, 'model.config_type'); $loader = $this->app->make(ModelLoaderManager::class);
if ('file' == $configType) { $loader->initFromConfig($config);
$model->loadModel(Arr::get($config, 'model.config_file_path', '')); $loader->loadModel($model);
} elseif ('text' == $configType) {
$model->loadModelFromText(Arr::get($config, 'model.config_text', ''));
}
$adapter = Arr::get($config, 'adapter'); $adapter = Arr::get($config, 'adapter');
if (!is_null($adapter)) { if (!is_null($adapter)) {
$adapter = $this->app->make($adapter, [ $adapter = $this->app->make($adapter, [
@ -99,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,8 @@
namespace Lauthz; namespace Lauthz;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
use Lauthz\EnforcerLocalizer;
use Lauthz\Loaders\ModelLoaderManager;
use Lauthz\Models\Rule; use Lauthz\Models\Rule;
use Lauthz\Observers\RuleObserver; use Lauthz\Observers\RuleObserver;
@ -30,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();
} }
/** /**
@ -50,5 +54,23 @@ class LauthzServiceProvider extends ServiceProvider
$this->app->singleton('enforcer', function ($app) { $this->app->singleton('enforcer', function ($app) {
return new EnforcerManager($app); return new EnforcerManager($app);
}); });
$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();
} }
} }

View File

@ -0,0 +1,39 @@
<?php
namespace Lauthz\Loaders;
use Casbin\Model\Model;
use Illuminate\Support\Arr;
use Lauthz\Contracts\ModelLoader;
class FileLoader implements ModelLoader
{
/**
* The path to the model file.
*
* @var string
*/
private $filePath;
/**
* Constructor to initialize the file path.
*
* @param array $config
*/
public function __construct(array $config)
{
$this->filePath = Arr::get($config, 'model.config_file_path', '');
}
/**
* Loads model from file.
*
* @param Model $model
* @return void
* @throws \Casbin\Exceptions\CasbinException
*/
public function loadModel(Model $model): void
{
$model->loadModel($this->filePath);
}
}

View File

@ -0,0 +1,108 @@
<?php
namespace Lauthz\Loaders;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Illuminate\Support\Manager;
use InvalidArgumentException;
/**
* The model loader manager.
*
* A model loader is responsible for a loading model from an arbitrary source.
* Developers can customize loading behavior by implementing
* and register the custom loader in AppServiceProvider through `app(LoaderManager::class)->extend()`.
*
* Built-in loader implementations include:
* - FileLoader: For loading model from file.
* - TextLoader: Suitable for model defined as a multi-line string.
* - UrlLoader: Handles model loading from URL.
*
* To utilize a built-in or custom loader, set 'model.config_type' in the configuration to match one of the above types.
*/
class ModelLoaderManager extends Manager
{
/**
* The array of the lauthz driver configuration.
*
* @var array
*/
protected $config;
/**
* Initialize configuration for the loader manager instance.
*
* @param array $config the lauthz driver configuration.
*/
public function initFromConfig(array $config)
{
$this->config = $config;
}
/**
* Get the default driver from the configuration.
*
* @return string The default driver name.
*/
public function getDefaultDriver()
{
return Arr::get($this->config, 'model.config_type', '');
}
/**
* Create a new TextLoader instance.
*
* @return TextLoader
*/
public function createTextDriver()
{
return new TextLoader($this->config);
}
/**
* Create a new UrlLoader instance.
*
* @return UrlLoader
*/
public function createUrlDriver()
{
return new UrlLoader($this->config);
}
/**
* Create a new FileLoader instance.
*
* @return FileLoader
*/
public function createFileDriver()
{
return new FileLoader($this->config);
}
/**
* Create a new driver instance.
*
* @param string $driver
* @return mixed
*
* @throws \InvalidArgumentException
*/
protected function createDriver($driver)
{
if(empty($driver)) {
throw new InvalidArgumentException('Unsupported empty model loader type.');
}
if (isset($this->customCreators[$driver])) {
return $this->callCustomCreator($driver);
}
$method = 'create' . Str::studly($driver) . 'Driver';
if (method_exists($this, $method)) {
return $this->$method();
}
throw new InvalidArgumentException("Unsupported model loader type: {$driver}.");
}
}

View File

@ -0,0 +1,39 @@
<?php
namespace Lauthz\Loaders;
use Casbin\Model\Model;
use Illuminate\Support\Arr;
use Lauthz\Contracts\ModelLoader;
class TextLoader implements ModelLoader
{
/**
* Model text.
*
* @var string
*/
private $text;
/**
* Constructor to initialize the model text.
*
* @param array $config
*/
public function __construct(array $config)
{
$this->text = Arr::get($config, 'model.config_text', '');
}
/**
* Loads model from text.
*
* @param Model $model
* @return void
* @throws \Casbin\Exceptions\CasbinException
*/
public function loadModel(Model $model): void
{
$model->loadModelFromText($this->text);
}
}

58
src/Loaders/UrlLoader.php Normal file
View File

@ -0,0 +1,58 @@
<?php
namespace Lauthz\Loaders;
use Casbin\Model\Model;
use Illuminate\Support\Arr;
use Lauthz\Contracts\ModelLoader;
use RuntimeException;
class UrlLoader implements ModelLoader
{
/**
* The url to fetch the remote model string.
*
* @var string
*/
private $url;
/**
* Constructor to initialize the url path.
*
* @param array $config
*/
public function __construct(array $config)
{
$this->url = Arr::get($config, 'model.config_url', '');
}
/**
* Loads model from remote url.
*
* @param Model $model
* @return void
* @throws \Casbin\Exceptions\CasbinException
* @throws RuntimeException
*/
public function loadModel(Model $model): void
{
$contextOptions = [
'http' => [
'method' => 'GET',
'header' => "Accept: text/plain\r\n",
'timeout' => 3
]
];
$context = stream_context_create($contextOptions);
$response = @file_get_contents($this->url, false, $context);
if ($response === false) {
$error = error_get_last();
throw new RuntimeException(
"Failed to fetch remote model " . $this->url . ": " . $error['message']
);
}
$model->loadModelFromText($response);
}
}

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
{ {
@ -309,7 +309,7 @@ class DatabaseAdapterTest extends TestCase
$this->assertEquals([ $this->assertEquals([
['bob', 'data2', 'write'] ['bob', 'data2', 'write']
], Enforcer::getPolicy()); ], Enforcer::getPolicy());
// Filter // Filter
$filter = new Filter(['v2'], ['read']); $filter = new Filter(['v2'], ['read']);
Enforcer::loadFilteredPolicy($filter); Enforcer::loadFilteredPolicy($filter);

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

138
tests/ModelLoaderTest.php Normal file
View File

@ -0,0 +1,138 @@
<?php
namespace Lauthz\Tests;
use Lauthz\Facades\Enforcer;
use Lauthz\Loaders\ModelLoaderManager;
use InvalidArgumentException;
use RuntimeException;
class ModelLoaderTest extends TestCase
{
public function testUrlLoader(): void
{
$this->initUrlConfig();
$this->assertFalse(Enforcer::enforce('alice', 'data', 'read'));
Enforcer::addPolicy('data_admin', 'data', 'read');
Enforcer::addRoleForUser('alice', 'data_admin');
$this->assertTrue(Enforcer::enforce('alice', 'data', 'read'));
}
public function testTextLoader(): void
{
$this->initTextConfig();
Enforcer::addPolicy('data_admin', 'data', 'read');
$this->assertFalse(Enforcer::enforce('alice', 'data', 'read'));
$this->assertTrue(Enforcer::enforce('data_admin', 'data', 'read'));
}
public function testFileLoader(): void
{
$this->assertFalse(Enforcer::enforce('alice', 'data', 'read'));
Enforcer::addPolicy('data_admin', 'data', 'read');
Enforcer::addRoleForUser('alice', 'data_admin');
$this->assertTrue(Enforcer::enforce('alice', 'data', 'read'));
}
public function testCustomLoader(): void
{
$this->initCustomConfig();
Enforcer::guard('second')->addPolicy('data_admin', 'data', 'read');
$this->assertFalse(Enforcer::guard('second')->enforce('alice', 'data', 'read'));
$this->assertTrue(Enforcer::guard('second')->enforce('data_admin', 'data', 'read'));
}
public function testMultipleLoader(): void
{
$this->testFileLoader();
$this->testCustomLoader();
}
public function testEmptyModel(): void
{
Enforcer::shouldUse('third');
$this->expectException(InvalidArgumentException::class);
$this->assertFalse(Enforcer::enforce('alice', 'data', 'read'));
}
public function testEmptyLoaderType(): void
{
$this->app['config']->set('lauthz.basic.model.config_type', '');
$this->expectException(InvalidArgumentException::class);
$this->assertFalse(Enforcer::enforce('alice', 'data', 'read'));
}
public function testNotExistLoaderType(): void
{
$this->app['config']->set('lauthz.basic.model.config_type', 'not_exist');
$this->expectException(InvalidArgumentException::class);
$this->assertFalse(Enforcer::enforce('alice', 'data', 'read'));
}
public function testBadUrlConnection(): void
{
$this->initUrlConfig();
$this->app['config']->set('lauthz.basic.model.config_url', 'http://filenoexists');
$this->expectException(RuntimeException::class);
$this->assertFalse(Enforcer::enforce('alice', 'data', 'read'));
}
protected function initUrlConfig(): void
{
$this->app['config']->set('lauthz.basic.model.config_type', 'url');
$this->app['config']->set(
'lauthz.basic.model.config_url',
'https://raw.githubusercontent.com/casbin/casbin/master/examples/rbac_model.conf'
);
}
protected function initTextConfig(): void
{
$this->app['config']->set('lauthz.basic.model.config_type', 'text');
$this->app['config']->set(
'lauthz.basic.model.config_text',
$this->getModelText()
);
}
protected function initCustomConfig(): void
{
$this->app['config']->set('lauthz.second.model.config_type', 'custom');
$this->app['config']->set(
'lauthz.second.model.config_text',
$this->getModelText()
);
$config = $this->app['config']->get('lauthz.second');
$loader = $this->app->make(ModelLoaderManager::class);
$loader->extend('custom', function () use ($config) {
return new \Lauthz\Loaders\TextLoader($config);
});
}
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

@ -5,6 +5,7 @@ namespace Lauthz\Tests;
use Lauthz\Middlewares\RequestMiddleware; use Lauthz\Middlewares\RequestMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations; use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Lauthz\Facades\Enforcer;
use Lauthz\Models\Rule; use Lauthz\Models\Rule;
class RequestMiddlewareTest extends TestCase class RequestMiddlewareTest extends TestCase
@ -34,11 +35,19 @@ class RequestMiddlewareTest extends TestCase
$this->assertEquals($this->middleware(Request::create('/foo1/123', 'PUT')), 'Unauthorized Exception'); $this->assertEquals($this->middleware(Request::create('/foo1/123', 'PUT')), 'Unauthorized Exception');
$this->assertEquals($this->middleware(Request::create('/proxy', 'GET')), 'Unauthorized Exception'); $this->assertEquals($this->middleware(Request::create('/proxy', 'GET')), 'Unauthorized Exception');
Enforcer::guard('second')->addPolicy('alice', '/foo1/*', '(GET|POST)');
$this->assertEquals($this->middleware(Request::create('/foo1/123', 'GET'), 'second'), 200);
$this->assertEquals($this->middleware(Request::create('/foo1/123', 'POST'), 'second'), 200);
$this->assertEquals($this->middleware(Request::create('/foo1/123', 'PUT'), 'second'), 'Unauthorized Exception');
$this->assertEquals($this->middleware(Request::create('/proxy', 'GET'), 'second'), 'Unauthorized Exception');
} }
protected function middleware($request) protected function middleware($request, ...$guards)
{ {
return parent::runMiddleware(RequestMiddleware::class, $request); return parent::runMiddleware(RequestMiddleware::class, $request, ...$guards);
} }
protected function initConfig() protected function initConfig()
@ -62,6 +71,8 @@ e = some(where (p.eft == allow))
m = g(r.sub, p.sub) && r.sub == p.sub && keyMatch2(r.obj, p.obj) && regexMatch(r.act, p.act) m = g(r.sub, p.sub) && r.sub == p.sub && keyMatch2(r.obj, p.obj) && regexMatch(r.act, p.act)
EOT; EOT;
$this->app['config']->set('lauthz.basic.model.config_text', $text); $this->app['config']->set('lauthz.basic.model.config_text', $text);
$this->app['config']->set('lauthz.second.model.config_type', 'text');
$this->app['config']->set('lauthz.second.model.config_text', $text);
} }
protected function initTable() protected function initTable()

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]);