Compare commits

..

No commits in common. "master" and "v3.1.1" have entirely different histories.

26 changed files with 142 additions and 818 deletions

View File

@ -27,7 +27,73 @@ 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
@ -35,17 +101,6 @@ jobs:
- php: 8.0 - php: 8.0
laravel: 9.* laravel: 9.*
phpunit: ~9.0 phpunit: ~9.0
# Laravel 10.x
- php: 8.1
laravel: 10.*
phpunit: ~9.0
# Laravel 11.x
- php: 8.2
laravel: 11.*
phpunit: ~10.5
- php: 8.3
laravel: 11.*
phpunit: ~10.5
name: Laravel${{ matrix.laravel }}-PHP${{ matrix.php }} name: Laravel${{ matrix.laravel }}-PHP${{ matrix.php }}
@ -71,13 +126,8 @@ jobs:
composer install --prefer-dist --no-progress --no-suggest composer install --prefer-dist --no-progress --no-suggest
- name: Run test suite - name: Run test suite
if: matrix.laravel != '11.*'
run: ./vendor/bin/phpunit -v run: ./vendor/bin/phpunit -v
- name: Run test suite laravel 11
if: matrix.laravel == '11.*'
run: ./vendor/bin/phpunit -c phpunit.10.xml
- name: Run Coveralls - name: Run Coveralls
env: env:
COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@ -102,9 +152,9 @@ jobs:
needs: [ test, upload-coverage ] needs: [ test, upload-coverage ]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions/setup-node@v2 - uses: actions/setup-node@v1
with: with:
node-version: 'lts/*' node-version: '14.17'
- name: Run semantic-release - name: Run semantic-release
env: env:

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/actions/workflows/build.yml/badge.svg?branch=master" alt="Build Status"> <img src="https://github.com/php-casbin/laravel-authz/workflows/build/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,7 +35,6 @@ 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)
@ -106,7 +105,7 @@ use Enforcer;
Enforcer::addPermissionForUser('eve', 'articles', 'read'); Enforcer::addPermissionForUser('eve', 'articles', 'read');
// adds a role for a user. // adds a role for a user.
Enforcer::addRoleForUser('eve', 'writer'); Enforcer::addRoleForUser('eve', 'writer');
// adds permissions to a role // adds permissions to a rule
Enforcer::addPolicy('writer', 'articles','edit'); Enforcer::addPolicy('writer', 'articles','edit');
``` ```
@ -223,7 +222,7 @@ Determines whether a user has a permission.
Enforcer::hasPermissionForUser('eve', 'articles', 'read'); // true or false Enforcer::hasPermissionForUser('eve', 'articles', 'read'); // true or false
``` ```
See [Casbin API](https://casbin.org/docs/management-api#reference) for more APIs. See [Casbin API](https://casbin.org/docs/en/management-api) for more APIs.
### Using a middleware ### Using a middleware
@ -278,19 +277,6 @@ 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.
@ -349,8 +335,6 @@ Adds a role for a user:
```bash ```bash
php artisan role:assign eve writer php artisan role:assign eve writer
# Specify the ptype of the role assignment by using the --ptype option.
php artisan role:assign eve writer --ptype=g2
``` ```
### Using cache ### Using cache

View File

@ -20,17 +20,18 @@
], ],
"license": "Apache-2.0", "license": "Apache-2.0",
"require": { "require": {
"php": ">=8.0", "php": ">=7.1.0",
"illuminate/support": "~8.0|~9.0|~10.0|~11.0", "illuminate/support": "~5.5|~6.0|~7.0|~8.0|~9.0",
"illuminate/database": "~8.0|~9.0|~10.0|~11.0", "illuminate/database": "~5.5|~6.0|~7.0|~8.0|~9.0",
"illuminate/console": "~8.0|~9.0|~10.0|~11.0", "illuminate/console": "~5.5|~6.0|~7.0|~8.0|~9.0",
"casbin/casbin": "~4.0" "casbin/casbin": "~3.1",
"casbin/psr3-bridge": "^1.1"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "~9.0|~10.5", "phpunit/phpunit": "~7.0|~8.0|~9.0",
"php-coveralls/php-coveralls": "^2.7", "php-coveralls/php-coveralls": "^2.4",
"mockery/mockery": "^1.0", "mockery/mockery": "^1.0",
"laravel/laravel": "~9.0|~10.0|~11.0" "laravel/laravel": "~5.5|~6.0|~7.0|~8.0|~9.0"
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {
@ -51,10 +52,5 @@
"Enforcer": "Lauthz\\Facades\\Enforcer" "Enforcer": "Lauthz\\Facades\\Enforcer"
} }
} }
},
"config": {
"allow-plugins": {
"kylekatarnls/update-helper": true
}
} }
} }

View File

@ -6,27 +6,17 @@ 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", "url" // Available Settings: "file", "text"
'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

@ -1,42 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
backupGlobals="false"
bootstrap="vendor/autoload.php"
colors="true"
processIsolation="false"
stopOnFailure="false"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.0/phpunit.xsd">
<testsuites>
<testsuite name="Application Test Suite">
<directory>./tests/</directory>
</testsuite>
</testsuites>
<coverage includeUncoveredFiles="true"
pathCoverage="false"
ignoreDeprecatedCodeUnits="true"
disableCodeCoverageIgnore="true">
<report>
<clover outputFile="build/logs/clover.xml"/>
<html outputDirectory="build/html" lowUpperBound="50" highLowerBound="90"/>
</report>
</coverage>
<source>
<include>
<directory>./src</directory>
</include>
</source>
<php>
<env name="APP_ENV" value="testing"/>
<env name="BCRYPT_ROUNDS" value="4"/>
<env name="CACHE_DRIVER" value="array"/>
<env name="MAIL_DRIVER" value="array"/>
<env name="QUEUE_CONNECTION" value="sync"/>
<env name="SESSION_DRIVER" value="array"/>
<env name="DB_DATABASE" value="lauthz"/>
<env name="DB_USERNAME" value="root"/>
</php>
</phpunit>

View File

@ -1,11 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <phpunit backupGlobals="false"
backupGlobals="false" backupStaticAttributes="false"
bootstrap="vendor/autoload.php" bootstrap="vendor/autoload.php"
colors="true" colors="true"
processIsolation="false" convertErrorsToExceptions="true"
stopOnFailure="false" convertNoticesToExceptions="true"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.0/phpunit.xsd"> convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false">
<testsuites> <testsuites>
<testsuite name="Application Test Suite"> <testsuite name="Application Test Suite">
<directory>./tests/</directory> <directory>./tests/</directory>
@ -16,12 +18,10 @@
<directory suffix=".php">./src</directory> <directory suffix=".php">./src</directory>
</whitelist> </whitelist>
</filter> </filter>
<logging> <logging>
<log type="coverage-clover" target="build/logs/clover.xml"/> <log type="coverage-clover" target="build/logs/clover.xml"/>
<log type="coverage-html" target="build/html"/> <log type="coverage-html" target="build/html"/>
</logging> </logging>
<php> <php>
<env name="APP_ENV" value="testing"/> <env name="APP_ENV" value="testing"/>
<env name="BCRYPT_ROUNDS" value="4"/> <env name="BCRYPT_ROUNDS" value="4"/>

View File

@ -186,7 +186,7 @@ class DatabaseAdapter implements DatabaseAdapterContract, BatchDatabaseAdapterCo
*/ */
public function removePolicies(string $sec, string $ptype, array $rules): void public function removePolicies(string $sec, string $ptype, array $rules): void
{ {
$this->eloquent->getConnection()->transaction(function () use ($sec, $rules, $ptype) { DB::transaction(function () use ($sec, $rules, $ptype) {
foreach ($rules as $rule) { foreach ($rules as $rule) {
$this->removePolicy($sec, $ptype, $rule); $this->removePolicy($sec, $ptype, $rule);
} }
@ -256,11 +256,7 @@ class DatabaseAdapter implements DatabaseAdapterContract, BatchDatabaseAdapterCo
foreach($oldRule as $k => $v) { foreach($oldRule as $k => $v) {
$instance->where('v' . $k, $v); $instance->where('v' . $k, $v);
} }
$instance = $instance->first(); $instance->first();
if (!$instance) {
return;
}
$update = []; $update = [];
foreach($newPolicy as $k => $v) { foreach($newPolicy as $k => $v) {
$update['v' . $k] = $v; $update['v' . $k] = $v;
@ -280,7 +276,7 @@ class DatabaseAdapter implements DatabaseAdapterContract, BatchDatabaseAdapterCo
*/ */
public function updatePolicies(string $sec, string $ptype, array $oldRules, array $newRules): void public function updatePolicies(string $sec, string $ptype, array $oldRules, array $newRules): void
{ {
$this->eloquent->getConnection()->transaction(function () use ($sec, $ptype, $oldRules, $newRules) { DB::transaction(function () use ($sec, $ptype, $oldRules, $newRules) {
foreach ($oldRules as $i => $oldRule) { foreach ($oldRules as $i => $oldRule) {
$this->updatePolicy($sec, $ptype, $oldRule, $newRules[$i]); $this->updatePolicy($sec, $ptype, $oldRule, $newRules[$i]);
} }
@ -300,7 +296,7 @@ class DatabaseAdapter implements DatabaseAdapterContract, BatchDatabaseAdapterCo
public function updateFilteredPolicies(string $sec, string $ptype, array $newPolicies, int $fieldIndex, string ...$fieldValues): array public function updateFilteredPolicies(string $sec, string $ptype, array $newPolicies, int $fieldIndex, string ...$fieldValues): array
{ {
$oldRules = []; $oldRules = [];
$this->eloquent->getConnection()->transaction(function () use ($sec, $ptype, $fieldIndex, $fieldValues, $newPolicies, &$oldRules) { DB::transaction(function () use ($sec, $ptype, $fieldIndex, $fieldValues, $newPolicies, &$oldRules) {
$oldRules = $this->_removeFilteredPolicy($sec, $ptype, $fieldIndex, ...$fieldValues); $oldRules = $this->_removeFilteredPolicy($sec, $ptype, $fieldIndex, ...$fieldValues);
$this->addPolicies($sec, $ptype, $newPolicies); $this->addPolicies($sec, $ptype, $newPolicies);
}); });

View File

@ -17,8 +17,7 @@ class RoleAssign extends Command
*/ */
protected $signature = 'role:assign protected $signature = 'role:assign
{user : the identifier of user} {user : the identifier of user}
{role : the name of role} {role : the name of role}';
{--ptype= : the ptype of role}';
/** /**
* The console command description. * The console command description.
@ -36,9 +35,8 @@ class RoleAssign extends Command
{ {
$user = $this->argument('user'); $user = $this->argument('user');
$role = $this->argument('role'); $role = $this->argument('role');
$ptype = $this->option('ptype') ?: 'g';
$ret = Enforcer::addNamedGroupingPolicy($ptype, $user, $role); $ret = Enforcer::addRoleForUser($user, $role);
if ($ret) { if ($ret) {
$this->info('Added `'.$role.'` role to `'.$user.'` successfully'); $this->info('Added `'.$role.'` role to `'.$user.'` successfully');
} else { } else {

View File

@ -1,17 +0,0 @@
<?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;
}

View File

@ -1,61 +0,0 @@
<?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,15 +2,14 @@
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
@ -80,17 +79,19 @@ 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 = new DefaultLogger($this->app->make($logger)); $logger = $this->app->make($logger);
} }
Log::setLogger($logger); Log::setLogger(new LoggerBridge($logger));
} }
$model = new Model(); $model = new Model();
$loader = $this->app->make(ModelLoaderManager::class); $configType = Arr::get($config, 'model.config_type');
$loader->initFromConfig($config); if ('file' == $configType) {
$loader->loadModel($model); $model->loadModel(Arr::get($config, 'model.config_file_path', ''));
} 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, [
@ -98,7 +99,7 @@ class EnforcerManager implements Factory
]); ]);
} }
return new Enforcer($model, $adapter, $logger, Arr::get($config, 'log.enabled', false)); return new Enforcer($model, $adapter, Arr::get($config, 'log.enabled', false));
} }
/** /**

View File

@ -6,36 +6,6 @@ use Illuminate\Support\Facades\Facade;
/** /**
* @see \Casbin\Enforcer * @see \Casbin\Enforcer
* @method static string[] getRolesForUser(string $name, string ...$domain)
* @method static string[] getUsersForRole(string $name, string ...$domain)
* @method static bool hasRoleForUser(string $name, string $role, string ...$domain)
* @method static bool addRoleForUser(string $user, string $role, string ...$domain)
* @method static bool addRolesForUser(string $user, array $roles, string ...$domain)
* @method static bool deleteRoleForUser(string $user, string $role, string ...$domain)
* @method static bool deleteRolesForUser(string $user, string ...$domain)
* @method static bool deleteUser(string $user)
* @method static bool deleteRole(string $role)
* @method static bool deletePermission(string ...$permission)
* @method static bool addPermissionForUser(string $user, string ...$permission)
* @method static bool addPermissionsForUser(string $user, array ...$permissions)
* @method static bool deletePermissionForUser(string $user, string ...$permission)
* @method static bool deletePermissionsForUser(string $user)
* @method static array getPermissionsForUser(string $user, string ...$domain)
* @method static bool hasPermissionForUser(string $user, string ...$permission)
* @method static array getImplicitRolesForUser(string $name, string ...$domain)
* @method static array getImplicitUsersForRole(string $name, string ...$domain)
* @method static array getImplicitResourcesForUser(string $user, string ...$domain)
* @method static array getImplicitPermissionsForUser(string $user, string ...$domain)
* @method static array getImplicitUsersForPermission(string ...$permission)
* @method static string[] getAllUsersByDomain(string $domain)
* @method static array getUsersForRoleInDomain(string $name, string $domain)
* @method static array getRolesForUserInDomain(string $name, string $domain)
* @method static array getPermissionsForUserInDomain(string $name, string $domain)
* @method static bool addRoleForUserInDomain(string $user, string $role, string $domain)
* @method static bool deleteRoleForUserInDomain(string $user, string $role, string $domain)
* @method static bool deleteRolesForUserInDomain(string $user, string $domain)
* @method static bool deleteAllUsersByDomain(string $domain)
* @method static bool deleteDomains(string ...$domains)
*/ */
class Enforcer extends Facade class Enforcer extends Facade
{ {

View File

@ -3,8 +3,6 @@
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;
@ -32,8 +30,6 @@ class LauthzServiceProvider extends ServiceProvider
$this->mergeConfigFrom(__DIR__ . '/../config/lauthz.php', 'lauthz'); $this->mergeConfigFrom(__DIR__ . '/../config/lauthz.php', 'lauthz');
$this->bootObserver(); $this->bootObserver();
$this->registerLocalizer();
} }
/** /**
@ -54,23 +50,5 @@ 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

@ -1,39 +0,0 @@
<?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

@ -1,108 +0,0 @@
<?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

@ -1,39 +0,0 @@
<?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);
}
}

View File

@ -1,58 +0,0 @@
<?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,7 +31,6 @@ 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,7 +2,6 @@
namespace Lauthz\Tests\Commands; namespace Lauthz\Tests\Commands;
use Casbin\Model\Model;
use Lauthz\Facades\Enforcer; use Lauthz\Facades\Enforcer;
use Lauthz\Tests\TestCase; use Lauthz\Tests\TestCase;
use Illuminate\Foundation\Testing\DatabaseMigrations; use Illuminate\Foundation\Testing\DatabaseMigrations;
@ -20,21 +19,5 @@ class RoleAssignTest extends TestCase
$exitCode = Artisan::call('role:assign', ['user' => 'eve', 'role' => 'writer']); $exitCode = Artisan::call('role:assign', ['user' => 'eve', 'role' => 'writer']);
$this->assertFalse(0 === $exitCode); $this->assertFalse(0 === $exitCode);
$this->assertTrue(Enforcer::hasRoleForUser('eve', 'writer')); $this->assertTrue(Enforcer::hasRoleForUser('eve', 'writer'));
$model = Model::newModel();
$model->addDef('r', 'r', 'sub, obj, act');
$model->addDef('p', 'p', 'sub, obj, act');
$model->addDef('g', 'g', '_, _');
$model->addDef('g', 'g2', '_, _');
$model->addDef('e', 'e', 'some(where (p.eft == allow))');
$model->addDef('m', 'm', 'g(r.sub, p.sub) && g2(r.obj, p.obj) && r.act == p.act');
Enforcer::setModel($model);
Enforcer::loadPolicy();
$this->assertFalse(Enforcer::hasNamedGroupingPolicy('g2', 'eve', 'writer'));
$exitCode = Artisan::call('role:assign', ['user' => 'eve', 'role' => 'writer', '--ptype' => 'g2']);
$this->assertTrue(0 === $exitCode);
$exitCode = Artisan::call('role:assign', ['user' => 'eve', 'role' => 'writer', '--ptype' => 'g2']);
$this->assertFalse(0 === $exitCode);
$this->assertTrue(Enforcer::hasNamedGroupingPolicy('g2', 'eve', 'writer'));
} }
} }

View File

@ -2,9 +2,11 @@
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 Lauthz\Facades\Enforcer; use Casbin\Persist\Adapters\Filter;
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

@ -1,40 +0,0 @@
<?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

@ -1,69 +0,0 @@
<?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

@ -1,138 +0,0 @@
<?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,7 +5,6 @@ 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
@ -35,19 +34,11 @@ 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, ...$guards) protected function middleware($request)
{ {
return parent::runMiddleware(RequestMiddleware::class, $request, ...$guards); return parent::runMiddleware(RequestMiddleware::class, $request);
} }
protected function initConfig() protected function initConfig()
@ -71,8 +62,6 @@ 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]);
@ -46,7 +46,6 @@ abstract class TestCase extends BaseTestCase
$this->app['config']->set('database.default', 'mysql'); $this->app['config']->set('database.default', 'mysql');
$this->app['config']->set('database.connections.mysql.charset', 'utf8'); $this->app['config']->set('database.connections.mysql.charset', 'utf8');
$this->app['config']->set('database.connections.mysql.collation', 'utf8_unicode_ci'); $this->app['config']->set('database.connections.mysql.collation', 'utf8_unicode_ci');
$this->app['config']->set('cache.default', 'array');
// $app['config']->set('lauthz.log.enabled', true); // $app['config']->set('lauthz.log.enabled', true);
} }