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>
This commit is contained in:
parent
783c4017b0
commit
259a389595
|
@ -11,12 +11,14 @@ return [
|
|||
* Casbin model setting.
|
||||
*/
|
||||
'model' => [
|
||||
// Available Settings: "file", "text"
|
||||
// Available Settings: "file", "text", "url"
|
||||
'config_type' => 'file',
|
||||
|
||||
'config_file_path' => __DIR__ . DIRECTORY_SEPARATOR . 'lauthz-rbac-model.conf',
|
||||
|
||||
'config_text' => '',
|
||||
|
||||
'config_url' => ''
|
||||
],
|
||||
|
||||
/*
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -7,6 +7,7 @@ use Casbin\Enforcer;
|
|||
use Casbin\Model\Model;
|
||||
use Casbin\Log\Log;
|
||||
use Lauthz\Contracts\Factory;
|
||||
use Lauthz\Contracts\ModelLoader;
|
||||
use Lauthz\Models\Rule;
|
||||
use Illuminate\Support\Arr;
|
||||
use InvalidArgumentException;
|
||||
|
@ -86,12 +87,9 @@ class EnforcerManager implements Factory
|
|||
}
|
||||
|
||||
$model = new Model();
|
||||
$configType = Arr::get($config, 'model.config_type');
|
||||
if ('file' == $configType) {
|
||||
$model->loadModel(Arr::get($config, 'model.config_file_path', ''));
|
||||
} elseif ('text' == $configType) {
|
||||
$model->loadModelFromText(Arr::get($config, 'model.config_text', ''));
|
||||
}
|
||||
$loader = $this->app->make(ModelLoader::class, $config);
|
||||
$loader->loadModel($model);
|
||||
|
||||
$adapter = Arr::get($config, 'adapter');
|
||||
if (!is_null($adapter)) {
|
||||
$adapter = $this->app->make($adapter, [
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
namespace Lauthz;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Lauthz\Contracts\ModelLoader;
|
||||
use Lauthz\Loaders\ModelLoaderFactory;
|
||||
use Lauthz\Models\Rule;
|
||||
use Lauthz\Observers\RuleObserver;
|
||||
|
||||
|
@ -50,5 +52,9 @@ class LauthzServiceProvider extends ServiceProvider
|
|||
$this->app->singleton('enforcer', function ($app) {
|
||||
return new EnforcerManager($app);
|
||||
});
|
||||
|
||||
$this->app->bind(ModelLoader::class, function($app, $config) {
|
||||
return ModelLoaderFactory::createFromConfig($config);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
namespace Lauthz\Loaders;
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
use Lauthz\Contracts\Factory;
|
||||
use InvalidArgumentException;
|
||||
|
||||
class ModelLoaderFactory implements Factory
|
||||
{
|
||||
/**
|
||||
* Create a model loader from configuration.
|
||||
*
|
||||
* A model loader is responsible for a loading model from an arbitrary source.
|
||||
* Developers can customize loading behavior by implementing
|
||||
* the ModelLoader interface and specifying their custom class
|
||||
* via 'model.config_loader_class' in the configuration.
|
||||
*
|
||||
* 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 loader, set 'model.config_type' to match one of the above types.
|
||||
*
|
||||
* @param array $config
|
||||
* @return \Lauthz\Contracts\ModelLoader
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public static function createFromConfig(array $config) {
|
||||
$customLoader = Arr::get($config, 'model.config_loader_class', '');
|
||||
if (class_exists($customLoader)) {
|
||||
return new $customLoader($config);
|
||||
}
|
||||
|
||||
$loaderType = Arr::get($config, 'model.config_type', '');
|
||||
switch ($loaderType) {
|
||||
case 'file':
|
||||
return new FileLoader($config);
|
||||
case 'text':
|
||||
return new TextLoader($config);
|
||||
case 'url':
|
||||
return new UrlLoader($config);
|
||||
default:
|
||||
throw new InvalidArgumentException("Unsupported model loader type: {$loaderType}");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
<?php
|
||||
|
||||
namespace Lauthz\Tests;
|
||||
|
||||
use Lauthz\Facades\Enforcer;
|
||||
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 testBadUlrConnection(): 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_loader_class', '\Lauthz\Loaders\TextLoader');
|
||||
$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;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue