Notes and Best Practices β
This document provides important considerations, common pitfalls, and best practice guidelines for using the MineAdmin data permission system. Following these guidelines ensures system security, reliability, and performance.
β Key Considerations β
1. Coroutine Context Isolation β
Critical Warning
Coroutine context isolation is a core security feature of the data permission system and must be strictly followed!
Issue Description β
In the Hyperf coroutine environment, each coroutine has its own independent context space. Improper handling of coroutine contexts may lead to:
- Data leakage: User A's data being visible to User B
- Privilege escalation: Low-privilege users gaining high-privilege access
- Data inconsistency: The same user seeing different data across different requests
Correct Approach β
// Source: Based on /Users/zhuzhu/project/mineadmin/app/Library/DataPermission/Context.php
use App\Library\DataPermission\Context;
use App\Library\DataPermission\ScopeType;
// β
Correct: Set context at the start of each coroutine
co(function () {
// Set data permission context
Context::setDeptColumn('dept_id');
Context::setCreatedByColumn('created_by');
Context::setScopeType(ScopeType::DEPT_CREATED_BY);
Context::setOnlyTables(['user']);
// Execute business logic
$data = User::query()->get();
});
// β
Correct: Create a coroutine context management helper class
class CoroutineDataPermissionHelper
{
public static function withContext(User $user, callable $callback): mixed
{
return co(function () use ($user, $callback) {
// Set user-specific permission context
self::setupContextForUser($user);
// Execute callback
return $callback();
});
}
private static function setupContextForUser(User $user): void
{
Context::setDeptColumn('dept_id');
Context::setCreatedByColumn('created_by');
// Set permission scope based on user policy
$policy = $user->getPolicy();
if ($policy) {
Context::setScopeType(ScopeType::DEPT_CREATED_BY);
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
Incorrect Approach β
// β Incorrect: Sharing context across coroutines
$globalUser = auth()->user();
go(function () use ($globalUser) {
// Dangerous! May use another coroutine's context
$data = User::query()->get();
});
// β Incorrect: Not resetting context in coroutine pool
for ($i = 0; $i < 10; $i++) {
go(function () use ($i) {
// Dangerous! Coroutine pool reuse may cause context pollution
$user = User::find($i);
// Missing Context reset
$data = User::query()->get();
});
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2. Database Field Mapping β
Ensure Correct Field Names β
// Source: Based on /Users/zhuzhu/project/mineadmin/app/Library/DataPermission/Attribute/DataScope.php
// β
Correct: Explicitly specify field names
#[DataScope(
deptColumn: 'department_id', // Ensure this field exists in the table
createdByColumn: 'creator_id', // Ensure this field exists in the table
onlyTables: ['orders', 'customers'] // Only applies to specified tables
)]
public function getData(): Collection
{
return Order::with('customer')->get();
}
// β
Correct: Validate field existence (recommended helper method)
function validatePermissionFields(string $table, array $fields): bool
{
try {
$schema = DB::connection()->getSchemaBuilder()->getColumnListing($table);
foreach ($fields as $field) {
if (!in_array($field, $schema)) {
throw new \InvalidArgumentException(
"Field '{$field}' does not exist in table '{$table}'"
);
}
}
return true;
} catch (\Exception $e) {
Log::error('Field validation failed', [
'table' => $table,
'fields' => $fields,
'error' => $e->getMessage()
]);
return false;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
Incorrect Approach β
// β Incorrect: Using non-existent fields
#[DataScope(
deptColumn: 'dept_id', // If the actual field is 'department_id'
createdByColumn: 'created_by' // If the actual field is 'creator_id'
)]
public function getData(): Collection
{
// Will cause SQL errors or permission failures
return Order::query()->get();
}
2
3
4
5
6
7
8
9
10
β οΈ Security Warnings β
1. Preventing Permission Bypass β
Security Risk
The following behaviors may lead to permission bypass and must be avoided!
// β Dangerous: Manually constructing SQL to bypass permission checks
$sql = "SELECT * FROM users WHERE dept_id = ?";
$users = DB::select($sql, [auth()->user()->dept_id]);
// β Dangerous: Using whereRaw to bypass permission filtering
$users = User::whereRaw('1=1')->get();
// β Dangerous: Not using permission control in admin interfaces
public function adminGetAllUsers(): Collection
{
// Dangerous! Returns all user data directly
return User::all();
}
// β
Secure: Always use the permission system
// Source: Based on /Users/zhuzhu/project/mineadmin/app/Service/Permission/UserService.php:94-98
#[DataScope(
scopeType: ScopeType::CREATED_BY,
onlyTables: ['user'],
createdByColumn: 'id'
)]
public function adminGetAllUsers(): Collection
{
return User::query()->get();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2. Input Validation and Sanitization β
// β
Secure: Validate user input
class SecurePermissionService
{
public function getFilteredData(array $filters): Collection
{
// Validate input parameters
$this->validateFilters($filters);
// Use allowlist validation
$allowedColumns = ['name', 'email', 'status'];
$filters = array_intersect_key($filters, array_flip($allowedColumns));
// Apply data permissions
Context::setDeptColumn('dept_id');
Context::setCreatedByColumn('created_by');
Context::setScopeType(ScopeType::DEPT_CREATED_BY);
return User::query()
->when($filters['name'] ?? null, fn($q, $name) => $q->where('name', 'like', "%{$name}%"))
->when($filters['status'] ?? null, fn($q, $status) => $q->where('status', $status))
->get();
}
private function validateFilters(array $filters): void
{
foreach ($filters as $key => $value) {
if (!in_array($key, ['name', 'email', 'status'])) {
throw new \InvalidArgumentException("Invalid filter: {$key}");
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
3. Audit Logging β
// Source: Based on /Users/zhuzhu/project/mineadmin/app/Library/DataPermission/Factory.php
// β
Important: Enable logging for sensitive operations
#[DataScope(
scopeType: ScopeType::CREATED_BY,
onlyTables: ['financial_records']
)]
public function getFinancialData(): Collection
{
// Log sensitive data access
Log::info('Sensitive data access', [
'user_id' => auth()->id(),
'operation' => 'get_financial_data',
'ip' => request()->ip(),
'user_agent' => request()->userAgent(),
'timestamp' => now()
]);
return FinancialRecord::query()->get();
}
// β
Permission operation monitoring (recommended implementation)
class DataPermissionLogger
{
public static function logPermissionAccess(User $user, string $operation): void
{
Log::info('Data permission access', [
'user_id' => $user->id,
'operation' => $operation,
'policy' => $user->getPolicy()?->toArray(),
'ip' => request()->ip(),
'timestamp' => now()
]);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
π‘οΈ Best Practices β
1. Permission Policy Design β
Follow the Principle of Least Privilege β
// Source: Based on /Users/zhuzhu/project/mineadmin/app/Model/Permission/User.php:160-179
class DataPermissionService
{
public function getDataByUserRole(User $user): Collection
{
// Get user policy
$policy = $user->getPolicy();
if (!$policy) {
// Use most restrictive permissions when no policy exists
Context::setScopeType(ScopeType::CREATED_BY);
Context::setOnlyTables(['user']);
return collect();
}
// Configure scope based on policy type
$this->configureScopeByPolicy($policy);
return User::query()->get();
}
private function configureScopeByPolicy($policy): void
{
Context::setDeptColumn('dept_id');
Context::setCreatedByColumn('created_by');
// Configure based on policy type
match($policy->policy_type->value) {
'ALL' => Context::setScopeType(ScopeType::DEPT),
'DEPT_TREE' => Context::setScopeType(ScopeType::DEPT_CREATED_BY),
'DEPT_SELF' => Context::setScopeType(ScopeType::DEPT_CREATED_BY),
'SELF' => Context::setScopeType(ScopeType::CREATED_BY),
default => Context::setScopeType(ScopeType::CREATED_BY)
};
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
2. Error Handling Strategy β
// β
Best Practice: Graceful error handling
class SafeDataPermissionService
{
public function executeWithFallback(callable $primaryAction, callable $fallbackAction = null): mixed
{
try {
return $primaryAction();
} catch (\Exception $e) {
// Log permission-related errors
Log::warning('Data permission operation failed', [
'user_id' => auth()->id(),
'error' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine()
]);
// Execute fallback strategy
if ($fallbackAction) {
return $fallbackAction();
}
return collect(); // Return empty collection
}
}
}
// Usage example
$service = new SafeDataPermissionService();
$data = $service->executeWithFallback(
fn() => $this->getComplexDataWithPermissions(),
fn() => $this->getBasicUserData() // Fallback
);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
3. Performance Optimization β
// β
Best Practice: Performance monitoring
class DataPermissionMonitor
{
public function monitorExecution(callable $action, string $operationName): mixed
{
$startTime = microtime(true);
try {
$result = $action();
$executionTime = (microtime(true) - $startTime) * 1000;
// Log performance metrics
if ($executionTime > 100) { // Log if exceeds 100ms
Log::warning('Data permission operation took too long', [
'operation' => $operationName,
'execution_time' => $executionTime . 'ms',
'user_id' => auth()->id()
]);
}
return $result;
} catch (\Throwable $e) {
Log::error('Data permission operation exception', [
'operation' => $operationName,
'error' => $e->getMessage()
]);
throw $e;
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
4. Testing Best Practices β
// β
Best Practice: Permission testing
class DataPermissionTest extends TestCase
{
public function test_department_isolation(): void
{
// Create test data
$dept1 = Department::factory()->create();
$dept2 = Department::factory()->create();
$user1 = User::factory()->create(['dept_id' => $dept1->id]);
$user2 = User::factory()->create(['dept_id' => $dept2->id]);
$order1 = Order::factory()->create(['dept_id' => $dept1->id]);
$order2 = Order::factory()->create(['dept_id' => $dept2->id]);
// Test that user1 can only see their department's data
$this->actingAs($user1);
Context::setDeptColumn('dept_id');
Context::setScopeType(ScopeType::DEPT);
Context::setOnlyTables(['orders']);
$results = Order::query()->get();
$this->assertCount(1, $results);
$this->assertEquals($order1->id, $results->first()->id);
}
public function test_user_policy_application(): void
{
// Source: Based on /Users/zhuzhu/project/mineadmin/app/Model/Permission/User.php:160-179
$user = User::factory()->create();
// Create user policy
Policy::factory()->create([
'user_id' => $user->id,
'policy_type' => PolicyType::DeptSelf,
'is_default' => true
]);
// Verify policy retrieval
$policy = $user->getPolicy();
$this->assertNotNull($policy);
$this->assertEquals(PolicyType::DeptSelf, $policy->policy_type);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
π Checklist β
Before using the data permission system, use this checklist to ensure security:
Configuration Check β
- Correct field mapping - Ensure
deptColumn
andcreatedByColumn
match actual table fields - Appropriate policy types - Choose suitable
ScopeType
based on business needs - Clear table scope - Use
onlyTables
to limit scope - Super admin check - Confirm super admin bypass logic is correct
Security Check β
- Coroutine context management - Reset context for each new coroutine
- Input validation - Validate and sanitize all user input
- Error handling - Implement proper error handling and fallback strategies
- Audit logging - Enable detailed logging for sensitive operations
Performance Check β
- Database indexes - Create appropriate indexes for permission fields
- Query optimization - Avoid N+1 queries and unnecessary joins
- Monitoring alerts - Set up query performance monitoring
Testing Check β
- Unit tests - Write unit tests for various permission scenarios
- Integration tests - Test integration with other components
- Boundary tests - Test permission boundaries and edge cases
Summary β
The core of MineAdmin's data permission system lies in correctly configuring Context and DataScope annotations. Key points:
- Strict coroutine context management - Prevent permission leaks
- Correct field mapping - Ensure permission filtering works
- Follow least privilege principle - Default to most restrictive permissions
- Comprehensive error handling - Ensure system security in exceptional cases
- Thorough test coverage - Validate correctness across permission scenarios
Following these considerations and best practices ensures the MineAdmin data permission system operates securely and efficiently in your application.