11 | * @link http://www.workerman.net/
12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License
13 | */
14 |
15 | use Dotenv\Dotenv;
16 | use support\Log;
17 | use Webman\Bootstrap;
18 | use Webman\Config;
19 | use Webman\Middleware;
20 | use Webman\Route;
21 | use Webman\Util;
22 |
23 | $worker = $worker ?? null;
24 |
25 | set_error_handler(function ($level, $message, $file = '', $line = 0) {
26 | if (error_reporting() & $level) {
27 | throw new ErrorException($message, 0, $level, $file, $line);
28 | }
29 | });
30 |
31 | if ($worker) {
32 | register_shutdown_function(function ($startTime) {
33 | if (time() - $startTime <= 0.1) {
34 | sleep(1);
35 | }
36 | }, time());
37 | }
38 |
39 | if (class_exists('Dotenv\Dotenv') && file_exists(base_path(false) . '/.env')) {
40 | if (method_exists('Dotenv\Dotenv', 'createUnsafeMutable')) {
41 | Dotenv::createUnsafeMutable(base_path(false))->load();
42 | } else {
43 | Dotenv::createMutable(base_path(false))->load();
44 | }
45 | }
46 |
47 | Config::clear();
48 | support\App::loadAllConfig(['route']);
49 | if ($timezone = config('app.default_timezone')) {
50 | date_default_timezone_set($timezone);
51 | }
52 |
53 | foreach (config('autoload.files', []) as $file) {
54 | include_once $file;
55 | }
56 | foreach (config('plugin', []) as $firm => $projects) {
57 | foreach ($projects as $name => $project) {
58 | if (!is_array($project)) {
59 | continue;
60 | }
61 | foreach ($project['autoload']['files'] ?? [] as $file) {
62 | include_once $file;
63 | }
64 | }
65 | foreach ($projects['autoload']['files'] ?? [] as $file) {
66 | include_once $file;
67 | }
68 | }
69 |
70 | Middleware::load(config('middleware', []));
71 | foreach (config('plugin', []) as $firm => $projects) {
72 | foreach ($projects as $name => $project) {
73 | if (!is_array($project) || $name === 'static') {
74 | continue;
75 | }
76 | Middleware::load($project['middleware'] ?? []);
77 | }
78 | Middleware::load($projects['middleware'] ?? [], $firm);
79 | if ($staticMiddlewares = config("plugin.$firm.static.middleware")) {
80 | Middleware::load(['__static__' => $staticMiddlewares], $firm);
81 | }
82 | }
83 | Middleware::load(['__static__' => config('static.middleware', [])]);
84 |
85 | foreach (config('bootstrap', []) as $className) {
86 | if (!class_exists($className)) {
87 | $log = "Warning: Class $className setting in config/bootstrap.php not found\r\n";
88 | echo $log;
89 | Log::error($log);
90 | continue;
91 | }
92 | /** @var Bootstrap $className */
93 | $className::start($worker);
94 | }
95 |
96 | foreach (config('plugin', []) as $firm => $projects) {
97 | foreach ($projects as $name => $project) {
98 | if (!is_array($project)) {
99 | continue;
100 | }
101 | foreach ($project['bootstrap'] ?? [] as $className) {
102 | if (!class_exists($className)) {
103 | $log = "Warning: Class $className setting in config/plugin/$firm/$name/bootstrap.php not found\r\n";
104 | echo $log;
105 | Log::error($log);
106 | continue;
107 | }
108 | /** @var Bootstrap $className */
109 | $className::start($worker);
110 | }
111 | }
112 | foreach ($projects['bootstrap'] ?? [] as $className) {
113 | /** @var string $className */
114 | if (!class_exists($className)) {
115 | $log = "Warning: Class $className setting in plugin/$firm/config/bootstrap.php not found\r\n";
116 | echo $log;
117 | Log::error($log);
118 | continue;
119 | }
120 | /** @var Bootstrap $className */
121 | $className::start($worker);
122 | }
123 | }
124 |
125 | $directory = base_path() . '/plugin';
126 | $paths = [config_path()];
127 | foreach (Util::scanDir($directory) as $path) {
128 | if (is_dir($path = "$path/config")) {
129 | $paths[] = $path;
130 | }
131 | }
132 | Route::load($paths);
133 |
134 |
--------------------------------------------------------------------------------
/tests/SimplePermissionTest.php:
--------------------------------------------------------------------------------
1 | [
19 | 'casbin' => [
20 | 'webman-permission' => [
21 | 'permission' => [
22 | 'default' => 'default',
23 | 'drivers' => [
24 | 'default' => [
25 | 'model' => [
26 | 'config_type' => 'text',
27 | 'config_text' => '
28 | [request_definition]
29 | r = sub, obj, act
30 |
31 | [policy_definition]
32 | p = sub, obj, act
33 |
34 | [role_definition]
35 | g = _, _
36 |
37 | [policy_effect]
38 | e = some(where (p.eft == allow))
39 |
40 | [matchers]
41 | m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
42 | ',
43 | ],
44 | 'adapter' => \Casbin\WebmanPermission\Adapter\DatabaseAdapter::class,
45 | ],
46 | 'other' => [
47 | 'model' => [
48 | 'config_type' => 'text',
49 | 'config_text' => '
50 | [request_definition]
51 | r = sub, obj, act
52 |
53 | [policy_definition]
54 | p = sub, obj, act
55 |
56 | [role_definition]
57 | g = _, _
58 |
59 | [policy_effect]
60 | e = some(where (p.eft == allow))
61 |
62 | [matchers]
63 | m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
64 | ',
65 | ],
66 | 'adapter' => \Casbin\WebmanPermission\Adapter\DatabaseAdapter::class,
67 | 'adapter_config' => [
68 | 'table' => 'other_casbin_rule'
69 | ],
70 | ],
71 | 'log' => [
72 | 'enabled' => false,
73 | 'logger' => 'casbin',
74 | 'path' => '/tmp/casbin.log',
75 | ],
76 | ]
77 | ]
78 | ]
79 | ]
80 | ];
81 |
82 | try {
83 | Permission::clear();
84 | } catch (\Exception $e) {
85 | // 忽略清理时的错误
86 | }
87 | }
88 |
89 | public function testBasicPermission()
90 | {
91 | $result = Permission::addPolicy('writer', 'articles', 'edit');
92 | $this->assertTrue($result);
93 |
94 | $result = Permission::enforce('writer', 'articles', 'edit');
95 | $this->assertTrue($result);
96 |
97 | $result = Permission::enforce('writer', 'articles', 'delete');
98 | $this->assertFalse($result);
99 | }
100 |
101 | public function testRoleManagement()
102 | {
103 | $result = Permission::addRoleForUser('alice', 'admin');
104 | $this->assertTrue($result);
105 |
106 | $result = Permission::hasRoleForUser('alice', 'admin');
107 | $this->assertTrue($result);
108 |
109 | $roles = Permission::getRolesForUser('alice');
110 | $this->assertContains('admin', $roles);
111 | }
112 |
113 | public function testPermissionForUser()
114 | {
115 | $result = Permission::addPermissionForUser('alice', 'data1', 'read');
116 | $this->assertTrue($result);
117 |
118 | $result = Permission::enforce('alice', 'data1', 'read');
119 | $this->assertTrue($result);
120 |
121 | $permissions = Permission::getPermissionsForUser('alice');
122 | $this->assertContains(['alice', 'data1', 'read'], $permissions);
123 | }
124 |
125 | public function testBatchOperations()
126 | {
127 | $policies = [
128 | ['alice', 'data1', 'read'],
129 | ['bob', 'data2', 'write']
130 | ];
131 |
132 | $result = Permission::addPolicies($policies);
133 | $this->assertTrue($result);
134 |
135 | $this->assertTrue(Permission::enforce('alice', 'data1', 'read'));
136 | $this->assertTrue(Permission::enforce('bob', 'data2', 'write'));
137 | }
138 |
139 | public function testPolicyUpdate()
140 | {
141 | Permission::addPolicy('writer', 'articles', 'edit');
142 | $this->assertTrue(Permission::hasPolicy('writer', 'articles', 'edit'));
143 |
144 | $result = Permission::updatePolicies(
145 | [['writer', 'articles', 'edit']],
146 | [['writer', 'articles', 'update']]
147 | );
148 | $this->assertTrue($result);
149 |
150 | $this->assertFalse(Permission::hasPolicy('writer', 'articles', 'edit'));
151 | $this->assertTrue(Permission::hasPolicy('writer', 'articles', 'update'));
152 | }
153 |
154 | public function testRemoveOperations()
155 | {
156 | Permission::addPolicy('writer', 'articles', 'edit');
157 | $this->assertTrue(Permission::hasPolicy('writer', 'articles', 'edit'));
158 |
159 | $result = Permission::removePolicy('writer', 'articles', 'edit');
160 | $this->assertTrue($result);
161 | $this->assertFalse(Permission::hasPolicy('writer', 'articles', 'edit'));
162 | }
163 |
164 | public function testDriverManagement()
165 | {
166 | $driver = Permission::getDefaultDriver();
167 | $this->assertNotEmpty($driver);
168 |
169 | $drivers = Permission::getAllDriver();
170 | $this->assertIsArray($drivers);
171 | }
172 |
173 | public function testClear()
174 | {
175 | Permission::addPolicy('writer', 'articles', 'edit');
176 | $this->assertTrue(Permission::hasPolicy('writer', 'articles', 'edit'));
177 |
178 | Permission::clear();
179 | $this->assertFalse(Permission::hasPolicy('writer', 'articles', 'edit'));
180 | }
181 |
182 | protected function tearDown(): void
183 | {
184 | Permission::clear();
185 | }
186 | }
--------------------------------------------------------------------------------
/src/Permission.php:
--------------------------------------------------------------------------------
1 | loadModel($config['model']['config_file_path']);
92 | } elseif ('text' == $config['model']['config_type']) {
93 | $model->loadModel($config['model']['config_text']);
94 | }
95 | $logConfig = self::getConfig('log');
96 | $logger = null;
97 | if (true === $logConfig['enabled']) {
98 | /** @var LoggerInterface $casbinLogger 创建一个 Monolog 日志记录器 */
99 | $casbinLogger = new Logger($logConfig['logger']);
100 | $casbinLogger->pushHandler(new StreamHandler($logConfig['path'], Logger::DEBUG));
101 | $logger = new DefaultLogger($casbinLogger);
102 | }
103 | static::$_manager[$driver] = new Enforcer($model, Container::make($config['adapter'], [$driver]), $logger, $logConfig['enabled']);
104 |
105 | $watcher = new RedisWatcher(config('redis.default'), $driver);
106 | static::$_manager[$driver]->setWatcher($watcher);
107 | $watcher->setUpdateCallback(function () use ($driver) {
108 | static::$_manager[$driver]->loadPolicy();
109 | });
110 | return static::$_manager[$driver];
111 | }
112 |
113 | /**
114 | * @desc: 获取所有驱动
115 | * @return Enforcer[]
116 | * @author Tinywan(ShaoBo Wan)
117 | */
118 | public static function getAllDriver(): array
119 | {
120 | return static::$_manager;
121 | }
122 |
123 | /**
124 | * @desc: 默认驱动
125 | * @return mixed
126 | * @author Tinywan(ShaoBo Wan)
127 | */
128 | public static function getDefaultDriver(): mixed
129 | {
130 | return self::getConfig('default');
131 | }
132 |
133 | /**
134 | * @desc: 获取驱动配置
135 | * @param string|null $name 名称
136 | * @param null $default 默认值
137 | * @return mixed
138 | * @author Tinywan(ShaoBo Wan)
139 | */
140 | public static function getConfig(?string $name = null, $default = null): mixed
141 | {
142 | if (!is_null($name)) {
143 | return config('plugin.casbin.webman-permission.permission.' . $name, $default);
144 | }
145 | return config('plugin.casbin.webman-permission.permission.default');
146 | }
147 |
148 | /**
149 | * @desc: 静态调用
150 | * @param string $method
151 | * @param $arguments
152 | * @return mixed
153 | * @throws CasbinException
154 | * @author Tinywan(ShaoBo Wan)
155 | */
156 | public static function __callStatic(string $method, $arguments)
157 | {
158 | return self::driver()->{$method}(...$arguments);
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | **🐇 An Authorization For Webman Plugin. 🐇
**
4 |
5 | # 🐇 Webman Authorization Plugin Base Casbin. 🐇
6 |
7 | [](https://github.com/php-casbin/webman-permission/actions/workflows/default.yml)
8 | [](https://packagist.org/packages/casbin/webman-permission)
9 | [](https://packagist.org/packages/casbin/webman-permission)
10 | [](https://packagist.org/packages/casbin/webman-permission)
11 |
12 | An authorization library that supports access control models like ACL, RBAC, ABAC for webman plugin
13 |
14 | # Install
15 |
16 | Composer Install
17 | ```sh
18 | composer require -W casbin/webman-permission
19 | ```
20 |
21 | # Use
22 |
23 | ## Dependency Injection configuration
24 |
25 | Modify the `config/container.php` configuration to perform the following final contents:
26 |
27 | ```php
28 | $builder = new \DI\ContainerBuilder();
29 | $builder->addDefinitions(config('dependence', []));
30 | $builder->useAutowiring(true);
31 | return $builder->build();
32 | ```
33 |
34 | ## Database configuration
35 |
36 | 默认策略存储是使用的ThinkORM。
37 |
38 | ### 1、模型配置
39 |
40 | 默认使用ThinkORM。修改数据库 `thinkorm.php` 配置
41 |
42 | > 如使用laravel数据库,配置参考如下
43 | - 修改数据库 `database.php` 配置
44 | - 修改数据库 `permission.php` 的`adapter`适配器为laravel适配器
45 |
46 | ### 2、创建 `casbin_rule` 数据表
47 | ```sql
48 | CREATE TABLE `casbin_rule` (
49 | `id` BIGINT ( 20 ) UNSIGNED NOT NULL AUTO_INCREMENT,
50 | `ptype` VARCHAR ( 128 ) NOT NULL DEFAULT '',
51 | `v0` VARCHAR ( 128 ) NOT NULL DEFAULT '',
52 | `v1` VARCHAR ( 128 ) NOT NULL DEFAULT '',
53 | `v2` VARCHAR ( 128 ) NOT NULL DEFAULT '',
54 | `v3` VARCHAR ( 128 ) NOT NULL DEFAULT '',
55 | `v4` VARCHAR ( 128 ) NOT NULL DEFAULT '',
56 | `v5` VARCHAR ( 128 ) NOT NULL DEFAULT '',
57 | PRIMARY KEY ( `id` ) USING BTREE,
58 | KEY `idx_ptype` ( `ptype` ) USING BTREE,
59 | KEY `idx_v0` ( `v0` ) USING BTREE,
60 | KEY `idx_v1` ( `v1` ) USING BTREE,
61 | KEY `idx_v2` ( `v2` ) USING BTREE,
62 | KEY `idx_v3` ( `v3` ) USING BTREE,
63 | KEY `idx_v4` ( `v4` ) USING BTREE,
64 | KEY `idx_v5` ( `v5` ) USING BTREE
65 | ) ENGINE = INNODB CHARSET = utf8mb4 COMMENT = '策略规则表';
66 | ```
67 | ### 3、配置 `config/redis` 配置
68 |
69 | ### 4、重启webman
70 |
71 | ```
72 | php start.php restart
73 | ```
74 | 或者
75 | ```
76 | php start.php restart -d
77 | ```
78 |
79 | # 使用
80 |
81 | 安装成功后,可以这样使用:
82 |
83 | ```php
84 | use Casbin\WebmanPermission\Permission;
85 |
86 | // adds permissions to a user
87 | Permission::addPermissionForUser('eve', 'articles', 'read');
88 | // adds a role for a user.
89 | Permission::addRoleForUser('eve', 'writer');
90 | // adds permissions to a rule
91 | Permission::addPolicy('writer', 'articles','edit');
92 | ```
93 |
94 | 你可以检查一个用户是否拥有某个权限:
95 |
96 | ```php
97 | if (\Casbin\WebmanPermission\Permission::enforce('eve', 'articles', 'edit')) {
98 | echo '恭喜你!通过权限认证';
99 | } else {
100 | echo '对不起,您没有该资源访问权限';
101 | }
102 | ```
103 |
104 | # 多套驱动配置
105 |
106 | ```php
107 | $permission = \Casbin\WebmanPermission\Permission::driver('restful_conf');
108 | // adds permissions to a user
109 | $permission->addPermissionForUser('eve', 'articles', 'read');
110 | // adds a role for a user.
111 | $permission->addRoleForUser('eve', 'writer');
112 | // adds permissions to a rule
113 | $permission->addPolicy('writer', 'articles','edit');
114 |
115 | if ($permission->enforce('eve', 'articles', 'edit')) {
116 | echo '恭喜你!通过权限认证';
117 | } else {
118 | echo '对不起,您没有该资源访问权限';
119 | }
120 | ```
121 |
122 | 更多 `API` 参考 [Casbin API](https://casbin.org/docs/en/management-api) 。
123 |
124 | # 教程
125 | * [Casbin权限实战:入门分享(中文)](https://www.bilibili.com/video/BV1A541187M4/?vd_source=a9321be9ed112f8d6fdc8ee87640be1b)
126 | * [Casbin权限实战:基于角色的RBAC授权](https://www.bilibili.com/video/BV1A541187M4/?vd_source=a9321be9ed112f8d6fdc8ee87640be1b)
127 | * [Casbin权限实战:RESTful及中间件使用](https://www.bilibili.com/video/BV1uk4y117up/?vd_source=a9321be9ed112f8d6fdc8ee87640be1b)
128 | * [Casbin权限实战:如何使用自定义匹配函数](https://www.bilibili.com/video/BV1dq4y1Z78g/?vd_source=a9321be9ed112f8d6fdc8ee87640be1b)
129 | * [Webman实战教程:如何使用casbin权限控制](https://www.bilibili.com/video/BV1X34y1Q7ZH/?vd_source=a9321be9ed112f8d6fdc8ee87640be1b)
130 |
131 | # 测试
132 |
133 | ## 测试套件
134 |
135 | 本项目包含完整的单元测试套件,覆盖了以下方面:
136 |
137 | ### 测试文件结构
138 |
139 | ```
140 | tests/
141 | ├── Adapter.php # 适配器基础测试
142 | ├── PermissionTest.php # Permission类测试
143 | ├── AdapterTest.php # 适配器详细测试
144 | ├── EdgeCaseTest.php # 边界情况测试
145 | ├── IntegrationTest.php # 集成测试
146 | ├── LaravelDatabase/
147 | │ ├── LaravelDatabaseAdapterTest.php
148 | │ └── TestCase.php
149 | ├── ThinkphpDatabase/
150 | │ ├── DatabaseAdapterTest.php
151 | │ └── TestCase.php
152 | └── config/
153 | └── plugin/
154 | └── casbin/
155 | └── webman-permission/
156 | └── permission.php
157 | ```
158 |
159 | ### 测试覆盖范围
160 |
161 | 1. **基础功能测试**
162 | - 权限添加、删除、检查
163 | - 角色分配、移除
164 | - 策略管理
165 |
166 | 2. **适配器测试**
167 | - 数据库操作
168 | - 过滤器功能
169 | - 批量操作
170 | - 事务处理
171 |
172 | 3. **边界情况测试**
173 | - 空值处理
174 | - 特殊字符
175 | - 大数据量
176 | - 性能测试
177 |
178 | 4. **集成测试**
179 | - RBAC完整流程
180 | - 域权限控制
181 | - 多驱动支持
182 | - 复杂业务场景
183 |
184 | 5. **错误处理测试**
185 | - 异常情况
186 | - 无效输入
187 | - 并发访问
188 |
189 | ### 运行测试
190 |
191 | ```bash
192 | # 运行所有测试
193 | php vendor/bin/phpunit tests/
194 |
195 | # 运行特定测试文件
196 | php vendor/bin/phpunit tests/PermissionTest.php
197 |
198 | # 运行特定测试方法
199 | php vendor/bin/phpunit --filter testAddPermissionForUser tests/PermissionTest.php
200 |
201 | # 生成测试覆盖率报告
202 | php vendor/bin/phpunit --coverage-html coverage tests/
203 | ```
204 |
205 | ### 测试要求
206 |
207 | - PHP >= 8.1
208 | - PHPUnit >= 9.0
209 | - 数据库连接
210 | - Redis连接
211 |
212 | ### 测试环境配置
213 |
214 | 测试环境会自动创建以下数据表:
215 | - `casbin_rule` - 默认策略表
216 | - `other_casbin_rule` - 其他驱动策略表
217 |
218 | ### 测试最佳实践
219 |
220 | 1. **编写新测试**
221 | - 继承适当的测试基类
222 | - 遵循命名约定
223 | - 添加必要的断言
224 |
225 | 2. **测试数据管理**
226 | - 使用 `setUp()` 和 `tearDown()` 方法
227 | - 确保测试数据隔离
228 | - 清理测试数据
229 |
230 | 3. **测试覆盖**
231 | - 覆盖正常流程
232 | - 测试异常情况
233 | - 验证边界条件
234 |
235 | ## 贡献指南
236 |
237 | ### 添加新功能测试
238 |
239 | 1. 为新功能编写对应的测试用例
240 | 2. 确保测试覆盖率达到要求
241 | 3. 运行完整测试套件
242 | 4. 提交代码前检查测试状态
243 |
244 | ### 修复Bug测试
245 |
246 | 1. 为Bug编写重现测试
247 | 2. 修复Bug后验证测试通过
248 | 3. 确保不影响现有功能
249 |
250 | # 感谢
251 |
252 | [Casbin](https://github.com/php-casbin/php-casbin),你可以查看全部文档在其 [官网](https://casbin.org/) 上。
253 |
254 |
255 |
256 | 解除 https://github.com/PHP-DI/PHP-DI依赖的解决方案(不推荐)
257 |
258 | 1、卸载DI依赖包:`composer remove php-di/php-di`
259 |
260 | 2、修改:`Casbin\WebmanPermission\Permission` 文件
261 |
262 | ```php
263 | if (is_null(static::$_manager)) {
264 | static::$_manager = new Enforcer($model, Container::get($config['adapter']),false);
265 | }
266 | ```
267 | 替换为
268 | ```php
269 | if (is_null(static::$_manager)) {
270 | if ($config['adapter'] == DatabaseAdapter::class) {
271 | $_model = new RuleModel();
272 | } elseif ($config['adapter'] == LaravelDatabaseAdapter::class) {
273 | $_model = new LaravelRuleModel();
274 | }
275 | static::$_manager = new Enforcer($model, new $config['adapter']($_model), false);
276 | }
277 | ```
278 | 耦合太高,不建议这么搞,更多了解:https://www.workerman.net/doc/webman/di.html
279 |
280 |
281 | ## 问题
282 |
283 | * Laravel的驱动报错:`Call to a member function connection() on null|webman2.1/vendor/illuminate/database/Eloquent/Model.
284 | php|1918`。解决方案,请检查本地数据库代理是否正常,如使用了Docker容器主机地址`dnmp-mysql`可能会导致该问题出现。
285 |
--------------------------------------------------------------------------------
/tests/AdapterTest.php:
--------------------------------------------------------------------------------
1 | databaseAdapter = new DatabaseAdapter();
21 | $this->laravelAdapter = new LaravelDatabaseAdapter();
22 | }
23 |
24 | public function testFilterRule()
25 | {
26 | $rule = ['ptype', 'v0', 'v1', '', null, 'v4'];
27 | $filtered = $this->databaseAdapter->filterRule($rule);
28 |
29 | $this->assertEquals(['ptype', 'v0', 'v1', 'v4'], $filtered);
30 | }
31 |
32 | public function testFilterRuleWithAllEmpty()
33 | {
34 | $rule = ['ptype', '', null, ''];
35 | $filtered = $this->databaseAdapter->filterRule($rule);
36 |
37 | $this->assertEquals(['ptype'], $filtered);
38 | }
39 |
40 | public function testIsFiltered()
41 | {
42 | $this->assertFalse($this->databaseAdapter->isFiltered());
43 |
44 | $this->databaseAdapter->setFiltered(true);
45 | $this->assertTrue($this->databaseAdapter->isFiltered());
46 | }
47 |
48 | public function testLoadFilteredPolicyWithStringFilter()
49 | {
50 | $model = new \Casbin\Model\Model();
51 | $model->addDef('p', 'p', ['sub', 'obj', 'act']);
52 |
53 | $filter = "ptype = 'p' AND v0 = 'alice'";
54 |
55 | $this->databaseAdapter->loadFilteredPolicy($model, $filter);
56 | $this->assertTrue($this->databaseAdapter->isFiltered());
57 | }
58 |
59 | public function testLoadFilteredPolicyWithFilterObject()
60 | {
61 | $model = new \Casbin\Model\Model();
62 | $model->addDef('p', 'p', ['sub', 'obj', 'act']);
63 |
64 | $filter = new Filter();
65 | $filter->p[] = 'v0';
66 | $filter->g[] = 'alice';
67 |
68 | $this->databaseAdapter->loadFilteredPolicy($model, $filter);
69 | $this->assertTrue($this->databaseAdapter->isFiltered());
70 | }
71 |
72 | public function testLoadFilteredPolicyWithClosure()
73 | {
74 | $model = new \Casbin\Model\Model();
75 | $model->addDef('p', 'p', ['sub', 'obj', 'act']);
76 |
77 | $filter = function($query) {
78 | return $query->where('v0', 'alice');
79 | };
80 |
81 | $this->databaseAdapter->loadFilteredPolicy($model, $filter);
82 | $this->assertTrue($this->databaseAdapter->isFiltered());
83 | }
84 |
85 | public function testLoadFilteredPolicyWithInvalidFilter()
86 | {
87 | $model = new \Casbin\Model\Model();
88 | $model->addDef('p', 'p', ['sub', 'obj', 'act']);
89 |
90 | $this->expectException(InvalidFilterTypeException::class);
91 | $this->databaseAdapter->loadFilteredPolicy($model, 123);
92 | }
93 |
94 | public function testSavePolicyLine()
95 | {
96 | $this->databaseAdapter->savePolicyLine('p', ['alice', 'data1', 'read']);
97 |
98 | $this->assertTrue(true);
99 | }
100 |
101 | public function testAddPolicy()
102 | {
103 | $this->databaseAdapter->addPolicy('p', 'p', ['alice', 'data1', 'read']);
104 |
105 | $this->assertTrue(true);
106 | }
107 |
108 | public function testAddPolicies()
109 | {
110 | $policies = [
111 | ['alice', 'data1', 'read'],
112 | ['bob', 'data2', 'write']
113 | ];
114 |
115 | $this->databaseAdapter->addPolicies('p', 'p', $policies);
116 |
117 | $this->assertTrue(true);
118 | }
119 |
120 | public function testRemovePolicy()
121 | {
122 | $this->databaseAdapter->addPolicy('p', 'p', ['alice', 'data1', 'read']);
123 | $this->databaseAdapter->removePolicy('p', 'p', ['alice', 'data1', 'read']);
124 |
125 | $this->assertTrue(true);
126 | }
127 |
128 | public function testRemovePolicies()
129 | {
130 | $policies = [
131 | ['alice', 'data1', 'read'],
132 | ['bob', 'data2', 'write']
133 | ];
134 |
135 | $this->databaseAdapter->addPolicies('p', 'p', $policies);
136 | $this->databaseAdapter->removePolicies('p', 'p', $policies);
137 |
138 | $this->assertTrue(true);
139 | }
140 |
141 | public function testUpdatePolicy()
142 | {
143 | $this->databaseAdapter->addPolicy('p', 'p', ['alice', 'data1', 'read']);
144 | $this->databaseAdapter->updatePolicy('p', 'p', ['alice', 'data1', 'read'], ['alice', 'data1', 'write']);
145 |
146 | $this->assertTrue(true);
147 | }
148 |
149 | public function testUpdatePolicies()
150 | {
151 | $oldPolicies = [
152 | ['alice', 'data1', 'read'],
153 | ['bob', 'data2', 'write']
154 | ];
155 |
156 | $newPolicies = [
157 | ['alice', 'data1', 'write'],
158 | ['bob', 'data2', 'read']
159 | ];
160 |
161 | $this->databaseAdapter->addPolicies('p', 'p', $oldPolicies);
162 | $this->databaseAdapter->updatePolicies('p', 'p', $oldPolicies, $newPolicies);
163 |
164 | $this->assertTrue(true);
165 | }
166 |
167 | public function testRemoveFilteredPolicy()
168 | {
169 | $this->databaseAdapter->addPolicies('p', 'p', [
170 | ['alice', 'data1', 'read'],
171 | ['alice', 'data2', 'write'],
172 | ['bob', 'data1', 'read']
173 | ]);
174 |
175 | $this->databaseAdapter->removeFilteredPolicy('p', 'p', 0, 'alice');
176 |
177 | $this->assertTrue(true);
178 | }
179 |
180 | public function testUpdateFilteredPolicies()
181 | {
182 | $this->databaseAdapter->addPolicies('p', 'p', [
183 | ['alice', 'data1', 'read'],
184 | ['alice', 'data2', 'write']
185 | ]);
186 |
187 | $newPolicies = [
188 | ['alice', 'data1', 'write'],
189 | ['alice', 'data2', 'read']
190 | ];
191 |
192 | $this->databaseAdapter->updateFilteredPolicies('p', 'p', $newPolicies, 0, 'alice');
193 |
194 | $this->assertTrue(true);
195 | }
196 |
197 | public function testAdapterWithEmptyRule()
198 | {
199 | $this->databaseAdapter->addPolicy('p', 'p', ['', '', '']);
200 | $this->databaseAdapter->removePolicy('p', 'p', ['', '', '']);
201 |
202 | $this->assertTrue(true);
203 | }
204 |
205 | public function testAdapterWithNullValues()
206 | {
207 | $this->databaseAdapter->addPolicy('p', 'p', ['alice', null, 'read']);
208 | $this->databaseAdapter->removePolicy('p', 'p', ['alice', null, 'read']);
209 |
210 | $this->assertTrue(true);
211 | }
212 |
213 | public function testAdapterWithSpecialCharacters()
214 | {
215 | $this->databaseAdapter->addPolicy('p', 'p', ['user@domain.com', 'data#1', 'action:read']);
216 | $this->databaseAdapter->removePolicy('p', 'p', ['user@domain.com', 'data#1', 'action:read']);
217 |
218 | $this->assertTrue(true);
219 | }
220 |
221 | public function testAdapterWithLargePolicySet()
222 | {
223 | $policies = [];
224 | for ($i = 0; $i < 1000; $i++) {
225 | $policies[] = ['user' . $i, 'resource' . $i, 'action' . $i];
226 | }
227 |
228 | $this->databaseAdapter->addPolicies('p', 'p', $policies);
229 | $this->databaseAdapter->removePolicies('p', 'p', $policies);
230 |
231 | $this->assertTrue(true);
232 | }
233 |
234 | public function testAdapterConcurrentOperations()
235 | {
236 | $policies = [];
237 | for ($i = 0; $i < 100; $i++) {
238 | $policies[] = ['user' . $i, 'resource' . $i, 'action' . $i];
239 | }
240 |
241 | $this->databaseAdapter->addPolicies('p', 'p', $policies);
242 |
243 | foreach ($policies as $policy) {
244 | $this->databaseAdapter->removePolicy('p', 'p', $policy);
245 | }
246 |
247 | $this->assertTrue(true);
248 | }
249 |
250 | public function testLaravelAdapterMethods()
251 | {
252 | $this->laravelAdapter->addPolicy('p', 'p', ['alice', 'data1', 'read']);
253 | $this->laravelAdapter->removePolicy('p', 'p', ['alice', 'data1', 'read']);
254 |
255 | $this->assertTrue(true);
256 | }
257 |
258 | public function testLaravelAdapterUpdateOrCreate()
259 | {
260 | $this->laravelAdapter->addPolicy('p', 'p', ['alice', 'data1', 'read']);
261 | $this->laravelAdapter->addPolicy('p', 'p', ['alice', 'data1', 'read']);
262 |
263 | $this->assertTrue(true);
264 | }
265 |
266 | public function testAdapterWithDifferentPtypes()
267 | {
268 | $this->databaseAdapter->addPolicy('p', 'p', ['alice', 'data1', 'read']);
269 | $this->databaseAdapter->addPolicy('g', 'g', ['alice', 'admin']);
270 | $this->databaseAdapter->addPolicy('p2', 'p2', ['admin', 'data1', 'write']);
271 |
272 | $this->databaseAdapter->removePolicy('p', 'p', ['alice', 'data1', 'read']);
273 | $this->databaseAdapter->removePolicy('g', 'g', ['alice', 'admin']);
274 | $this->databaseAdapter->removePolicy('p2', 'p2', ['admin', 'data1', 'write']);
275 |
276 | $this->assertTrue(true);
277 | }
278 |
279 | public function testAdapterWithPartialFieldMatching()
280 | {
281 | $this->databaseAdapter->addPolicies('p', 'p', [
282 | ['alice', 'data1', 'read'],
283 | ['alice', 'data2', 'read'],
284 | ['bob', 'data1', 'write']
285 | ]);
286 |
287 | $this->databaseAdapter->removeFilteredPolicy('p', 'p', 1, 'data1');
288 |
289 | $this->assertTrue(true);
290 | }
291 |
292 | public function testAdapterWithEmptyFieldValues()
293 | {
294 | $this->databaseAdapter->addPolicy('p', 'p', ['alice', '', 'read']);
295 | $this->databaseAdapter->removeFilteredPolicy('p', 'p', 1, '');
296 |
297 | $this->assertTrue(true);
298 | }
299 |
300 | public function testAdapterTransactionRollback()
301 | {
302 | $this->expectException(\Exception::class);
303 |
304 | $policies = [
305 | ['alice', 'data1', 'read'],
306 | ['bob', 'data2', 'write']
307 | ];
308 |
309 | $this->databaseAdapter->addPolicies('p', 'p', $policies);
310 |
311 | throw new \Exception('Test rollback');
312 | }
313 |
314 | protected function tearDown(): void
315 | {
316 | $this->databaseAdapter = null;
317 | $this->laravelAdapter = null;
318 | }
319 | }
--------------------------------------------------------------------------------
/tests/EdgeCaseTest.php:
--------------------------------------------------------------------------------
1 | assertTrue(Permission::hasPolicy('', '', ''));
22 |
23 | $result = Permission::removePolicy('', '', '');
24 | $this->assertTrue($result);
25 | }
26 |
27 | public function testNullValuesInPolicy()
28 | {
29 | Permission::addPolicy('alice', null, 'read');
30 | $this->assertTrue(Permission::hasPolicy('alice', null, 'read'));
31 |
32 | $result = Permission::removePolicy('alice', null, 'read');
33 | $this->assertTrue($result);
34 | }
35 |
36 | public function testVeryLongStrings()
37 | {
38 | $longString = str_repeat('a', 1000);
39 | Permission::addPolicy($longString, $longString, $longString);
40 | $this->assertTrue(Permission::hasPolicy($longString, $longString, $longString));
41 |
42 | $result = Permission::removePolicy($longString, $longString, $longString);
43 | $this->assertTrue($result);
44 | }
45 |
46 | public function testSpecialCharacters()
47 | {
48 | $specialChars = [
49 | 'user@domain.com',
50 | 'data#1',
51 | 'action:read',
52 | 'user+test@example.com',
53 | 'data?query=param',
54 | 'action&operation=test',
55 | 'user|domain',
56 | 'data\\path',
57 | 'action"quoted"',
58 | "user'string",
59 | 'data[0]',
60 | 'action(test)'
61 | ];
62 |
63 | foreach ($specialChars as $char) {
64 | Permission::addPolicy($char, $char, $char);
65 | $this->assertTrue(Permission::hasPolicy($char, $char, $char));
66 |
67 | $result = Permission::removePolicy($char, $char, $char);
68 | $this->assertTrue($result);
69 | }
70 | }
71 |
72 | public function testUnicodeCharacters()
73 | {
74 | $unicodeStrings = [
75 | '用户名',
76 | '数据1',
77 | '操作:读取',
78 | 'αβγδε',
79 | 'αβγδεζηθικλμνξοπρστυφχψω',
80 | '漢字',
81 | '😀emoji😊',
82 | 'café',
83 | 'naïve',
84 | 'résumé'
85 | ];
86 |
87 | foreach ($unicodeStrings as $str) {
88 | Permission::addPolicy($str, $str, $str);
89 | $this->assertTrue(Permission::hasPolicy($str, $str, $str));
90 |
91 | $result = Permission::removePolicy($str, $str, $str);
92 | $this->assertTrue($result);
93 | }
94 | }
95 |
96 | public function testMixedCaseSensitivity()
97 | {
98 | Permission::addPolicy('Alice', 'Data1', 'Read');
99 | Permission::addPolicy('alice', 'data1', 'read');
100 |
101 | $this->assertTrue(Permission::hasPolicy('Alice', 'Data1', 'Read'));
102 | $this->assertTrue(Permission::hasPolicy('alice', 'data1', 'read'));
103 |
104 | $this->assertFalse(Permission::hasPolicy('alice', 'Data1', 'Read'));
105 | $this->assertFalse(Permission::hasPolicy('Alice', 'data1', 'read'));
106 | }
107 |
108 | public function testWhitespaceHandling()
109 | {
110 | Permission::addPolicy(' alice ', ' data1 ', ' read ');
111 | $this->assertTrue(Permission::hasPolicy(' alice ', ' data1 ', ' read '));
112 |
113 | $this->assertFalse(Permission::hasPolicy('alice', 'data1', 'read'));
114 | $this->assertFalse(Permission::hasPolicy('alice ', 'data1 ', 'read '));
115 | }
116 |
117 | public function testEmptyPoliciesArray()
118 | {
119 | $result = Permission::addPolicies([]);
120 | $this->assertFalse($result);
121 |
122 | $result = Permission::removePolicies([]);
123 | $this->assertTrue($result);
124 | }
125 |
126 | public function testSingleCharacterPolicies()
127 | {
128 | Permission::addPolicy('a', 'b', 'c');
129 | $this->assertTrue(Permission::hasPolicy('a', 'b', 'c'));
130 |
131 | $result = Permission::removePolicy('a', 'b', 'c');
132 | $this->assertTrue($result);
133 | }
134 |
135 | public function testNumericPolicies()
136 | {
137 | Permission::addPolicy('123', '456', '789');
138 | $this->assertTrue(Permission::hasPolicy('123', '456', '789'));
139 |
140 | $result = Permission::removePolicy('123', '456', '789');
141 | $this->assertTrue($result);
142 | }
143 |
144 | public function testBooleanLikeStrings()
145 | {
146 | Permission::addPolicy('true', 'false', 'null');
147 | $this->assertTrue(Permission::hasPolicy('true', 'false', 'null'));
148 |
149 | $result = Permission::removePolicy('true', 'false', 'null');
150 | $this->assertTrue($result);
151 | }
152 |
153 | public function testSQLInjectionAttempts()
154 | {
155 | $maliciousInputs = [
156 | "alice'; DROP TABLE casbin_rule; --",
157 | "alice' OR '1'='1",
158 | "alice'; SELECT * FROM users; --",
159 | "alice' UNION SELECT * FROM users; --",
160 | "alice' AND SLEEP(10); --"
161 | ];
162 |
163 | foreach ($maliciousInputs as $input) {
164 | Permission::addPolicy($input, 'data1', 'read');
165 | $this->assertTrue(Permission::hasPolicy($input, 'data1', 'read'));
166 |
167 | $result = Permission::removePolicy($input, 'data1', 'read');
168 | $this->assertTrue($result);
169 | }
170 | }
171 |
172 | public function testXSSAttempts()
173 | {
174 | $xssInputs = [
175 | '',
176 | 'javascript:alert("xss")',
177 | '">',
178 | '
',
179 | '