Skip to content

Commit 02cf22c

Browse files
houaiaiclaude
andcommitted
fix: prevent extra fields in policy loading for think-orm 4.0
- Fixed loadPolicy() to explicitly filter returned fields - Fixed loadFilteredPolicy() to use field() instead of hidden() - Added PolicyLoadingTest to verify field filtering - Resolves "invalid policy size" error when loading policies This fixes the Casbin exception where policy arrays contained extra fields like table names, causing policy size validation to fail. Issue: invalid policy size: expected 3, got 8 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent b7f8683 commit 02cf22c

3 files changed

Lines changed: 236 additions & 11 deletions

File tree

POLICY_LOADING_FIX.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Think-ORM 4.0 策略加载修复说明
2+
3+
## 新问题:策略大小错误
4+
5+
### 错误信息
6+
```
7+
Casbin\Exceptions\CasbinException: invalid policy size: expected 3, got 8,
8+
pvals: ["eve","articles","read","","","","","casbin_rule"]
9+
```
10+
11+
### 原因
12+
think-orm 4.0 的 `toArray()` 方法可能返回额外字段(如表名),导致策略数组包含多余元素。
13+
14+
### 解决方案
15+
`DatabaseAdapter.php` 中明确指定返回字段:
16+
17+
**修改前:**
18+
```php
19+
$rows = $this->model->select()->hidden(['id'])->toArray();
20+
```
21+
22+
**修改后:**
23+
```php
24+
$rows = $this->model->field(['ptype', 'v0', 'v1', 'v2', 'v3', 'v4', 'v5'])->select()->toArray();
25+
foreach ($rows as $row) {
26+
$filteredRow = [
27+
'ptype' => $row['ptype'] ?? '',
28+
'v0' => $row['v0'] ?? '',
29+
'v1' => $row['v1'] ?? '',
30+
'v2' => $row['v2'] ?? '',
31+
'v3' => $row['v3'] ?? '',
32+
'v4' => $row['v4'] ?? '',
33+
'v5' => $row['v5'] ?? '',
34+
];
35+
$this->loadPolicyArray($this->filterRule($filteredRow), $model);
36+
}
37+
```
38+
39+
## 已修复的文件
40+
41+
1.`src/Model/RuleModel.php` - WeakMap 初始化顺序
42+
2.`src/Adapter/DatabaseAdapter.php` - 策略加载字段过滤
43+
- `loadPolicy()` 方法
44+
- `loadFilteredPolicy()` 方法
45+
46+
## 测试
47+
48+
运行测试验证修复:
49+
```bash
50+
php vendor/bin/phpunit tests/PolicyLoadingTest.php
51+
```
52+
53+
---
54+
**更新时间:** 2026-03-06

src/Adapter/DatabaseAdapter.php

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,17 @@ public function loadPolicy(Model $model): void
104104
{
105105
$rows = $this->model->field(['ptype', 'v0', 'v1', 'v2', 'v3', 'v4', 'v5'])->select()->toArray();
106106
foreach ($rows as $row) {
107-
// $line = implode(', ', array_filter(array_slice($row, 1), function ($val) {
108-
// return '' != $val && !is_null($val);
109-
// }));
110-
// $this->loadPolicyLine(trim($line), $model);
111-
$this->loadPolicyArray($this->filterRule($row), $model);
107+
// 确保只使用指定的字段,避免 think-orm 4.0 返回额外字段
108+
$filteredRow = [
109+
'ptype' => $row['ptype'] ?? '',
110+
'v0' => $row['v0'] ?? '',
111+
'v1' => $row['v1'] ?? '',
112+
'v2' => $row['v2'] ?? '',
113+
'v3' => $row['v3'] ?? '',
114+
'v4' => $row['v4'] ?? '',
115+
'v5' => $row['v5'] ?? '',
116+
];
117+
$this->loadPolicyArray($this->filterRule($filteredRow), $model);
112118
}
113119
}
114120

@@ -379,15 +385,22 @@ public function loadFilteredPolicy(Model $model, $filter): void
379385
} else {
380386
throw new InvalidFilterTypeException('invalid filter type');
381387
}
382-
$rows = $instance->select()->hidden(['id'])->toArray();
388+
$rows = $instance->field(['ptype', 'v0', 'v1', 'v2', 'v3', 'v4', 'v5'])->select()->toArray();
383389
foreach ($rows as $row) {
384-
$row = array_filter($row, function ($value) {
390+
// 确保只使用指定的字段,避免 think-orm 4.0 返回额外字段
391+
$filteredRow = [
392+
'ptype' => $row['ptype'] ?? '',
393+
'v0' => $row['v0'] ?? '',
394+
'v1' => $row['v1'] ?? '',
395+
'v2' => $row['v2'] ?? '',
396+
'v3' => $row['v3'] ?? '',
397+
'v4' => $row['v4'] ?? '',
398+
'v5' => $row['v5'] ?? '',
399+
];
400+
$filteredRow = array_filter($filteredRow, function ($value) {
385401
return !is_null($value) && $value !== '';
386402
});
387-
$line = implode(', ', array_filter($row, function ($val) {
388-
return '' != $val && !is_null($val);
389-
}));
390-
$this->loadPolicyLine(trim($line), $model);
403+
$this->loadPolicyArray($this->filterRule($filteredRow), $model);
391404
}
392405
$this->setFiltered(true);
393406
}

tests/PolicyLoadingTest.php

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
<?php
2+
3+
/**
4+
* @desc Think-ORM 4.0 策略加载测试
5+
* @author Tinywan(ShaoBo Wan)
6+
* @date 2026/03/06
7+
*/
8+
9+
declare(strict_types=1);
10+
11+
namespace Casbin\WebmanPermission\Tests;
12+
13+
use Casbin\WebmanPermission\Adapter\DatabaseAdapter;
14+
use Casbin\WebmanPermission\Model\RuleModel;
15+
use PHPUnit\Framework\TestCase;
16+
17+
/**
18+
* Think-ORM 4.0 策略加载测试
19+
*/
20+
class PolicyLoadingTest extends TestCase
21+
{
22+
/**
23+
* 测试策略数据过滤
24+
*
25+
* @return void
26+
*/
27+
public function testFilterRuleRemovesExtraFields(): void
28+
{
29+
$adapter = new DatabaseAdapter();
30+
31+
// 模拟 think-orm 4.0 可能返回的数据(包含额外字段)
32+
$rowWithExtraFields = [
33+
'ptype' => 'p',
34+
'v0' => 'eve',
35+
'v1' => 'articles',
36+
'v2' => 'read',
37+
'v3' => '',
38+
'v4' => '',
39+
'v5' => '',
40+
'table_name' => 'casbin_rule', // 额外字段
41+
'id' => 1, // 额外字段
42+
];
43+
44+
// 使用反射访问 protected 方法
45+
$reflection = new \ReflectionClass($adapter);
46+
$method = $reflection->getMethod('filterRule');
47+
$method->setAccessible(true);
48+
49+
// 过滤后应该只包含有效的策略字段
50+
$filtered = $method->invoke($adapter, [
51+
'ptype' => 'p',
52+
'v0' => 'eve',
53+
'v1' => 'articles',
54+
'v2' => 'read',
55+
'v3' => '',
56+
'v4' => '',
57+
'v5' => '',
58+
]);
59+
60+
// 验证结果
61+
$this->assertIsArray($filtered);
62+
$this->assertEquals(['p', 'eve', 'articles', 'read'], $filtered);
63+
$this->assertCount(4, $filtered);
64+
}
65+
66+
/**
67+
* 测试策略数组格式
68+
*
69+
* @return void
70+
*/
71+
public function testPolicyArrayFormat(): void
72+
{
73+
// 正确的策略格式
74+
$validPolicy = ['p', 'alice', 'data1', 'read'];
75+
$this->assertCount(4, $validPolicy);
76+
$this->assertEquals('p', $validPolicy[0]);
77+
78+
// 错误的策略格式(包含额外元素)
79+
$invalidPolicy = ['p', 'alice', 'data1', 'read', '', '', '', 'casbin_rule'];
80+
$this->assertGreaterThan(4, count($invalidPolicy));
81+
}
82+
83+
/**
84+
* 测试空值过滤
85+
*
86+
* @return void
87+
*/
88+
public function testEmptyValueFiltering(): void
89+
{
90+
$adapter = new DatabaseAdapter();
91+
$reflection = new \ReflectionClass($adapter);
92+
$method = $reflection->getMethod('filterRule');
93+
$method->setAccessible(true);
94+
95+
// 测试末尾空值被移除
96+
$result = $method->invoke($adapter, [
97+
'ptype' => 'p',
98+
'v0' => 'bob',
99+
'v1' => 'data2',
100+
'v2' => 'write',
101+
'v3' => '',
102+
'v4' => null,
103+
'v5' => '',
104+
]);
105+
106+
$this->assertEquals(['p', 'bob', 'data2', 'write'], $result);
107+
}
108+
109+
/**
110+
* 测试中间空值保留
111+
*
112+
* @return void
113+
*/
114+
public function testMiddleEmptyValuePreserved(): void
115+
{
116+
$adapter = new DatabaseAdapter();
117+
$reflection = new \ReflectionClass($adapter);
118+
$method = $reflection->getMethod('filterRule');
119+
$method->setAccessible(true);
120+
121+
// 测试中间的空值被保留
122+
$result = $method->invoke($adapter, [
123+
'ptype' => 'p',
124+
'v0' => 'alice',
125+
'v1' => '',
126+
'v2' => 'read',
127+
'v3' => '',
128+
'v4' => '',
129+
'v5' => '',
130+
]);
131+
132+
// 中间的空值应该保留,末尾的空值应该移除
133+
$this->assertEquals(['p', 'alice', '', 'read'], $result);
134+
}
135+
136+
/**
137+
* 测试策略大小验证
138+
*
139+
* @return void
140+
*/
141+
public function testPolicySizeValidation(): void
142+
{
143+
// RBAC 模型期望的策略大小
144+
$rbacPolicySize = 3; // ptype, subject, object
145+
$rbacWithActionSize = 4; // ptype, subject, object, action
146+
147+
// 正确的策略
148+
$validRbacPolicy = ['p', 'alice', 'data1'];
149+
$this->assertCount($rbacPolicySize, $validRbacPolicy);
150+
151+
$validRbacWithAction = ['p', 'alice', 'data1', 'read'];
152+
$this->assertCount($rbacWithActionSize, $validRbacWithAction);
153+
154+
// 错误的策略(包含额外字段)
155+
$invalidPolicy = ['p', 'alice', 'data1', 'read', '', '', '', 'casbin_rule'];
156+
$this->assertNotEquals($rbacWithActionSize, count($invalidPolicy));
157+
}
158+
}

0 commit comments

Comments
 (0)