Skip to content

Commit e8d7b4a

Browse files
committed
FOUR-29436: track legacy permission cache keys
1 parent bffe1a1 commit e8d7b4a

8 files changed

Lines changed: 124 additions & 19 deletions

File tree

ProcessMaker/Http/Controllers/Auth/LoginController.php

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use ProcessMaker\Models\Setting;
1717
use ProcessMaker\Models\User;
1818
use ProcessMaker\Package\Auth\Database\Seeds\AuthDefaultSeeder;
19+
use ProcessMaker\Services\PermissionCacheService;
1920
use ProcessMaker\Traits\HasControllerAddons;
2021

2122
class LoginController extends Controller
@@ -262,7 +263,7 @@ public function beforeLogout(Request $request)
262263

263264
//Clear the user permissions
264265
$userId = Auth::user()->id;
265-
Cache::forget("user_{$userId}_permissions");
266+
app(PermissionCacheService::class)->forgetLegacyUserPermissions($userId);
266267
Cache::forget("user_{$userId}_project_assets");
267268

268269
// Clear the user session
@@ -364,9 +365,13 @@ public function login(Request $request, User $user)
364365
return redirect()->route('password.change');
365366
}
366367
// Cache user permissions for a day to improve performance
367-
Cache::remember("user_{$user->id}_permissions", 86400, function () use ($user) {
368-
return $user->permissions()->pluck('name')->toArray();
369-
});
368+
app(PermissionCacheService::class)->rememberLegacyUserPermissions(
369+
$user->id,
370+
86400,
371+
function () use ($user) {
372+
return $user->permissions()->pluck('name')->toArray();
373+
}
374+
);
370375

371376
$this->setupLanguage($request, $user);
372377

ProcessMaker/Http/Middleware/CustomAuthorize.php

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
use Illuminate\Auth\Middleware\Authorize as Middleware;
88
use Illuminate\Http\Request;
99
use Illuminate\Support\Arr;
10-
use Illuminate\Support\Facades\Cache;
1110
use Illuminate\Support\Facades\DB;
1211
use Illuminate\Support\Facades\Log;
1312
use Illuminate\Support\Str;
@@ -16,6 +15,7 @@
1615
use ProcessMaker\Models\Screen;
1716
use ProcessMaker\Models\Script;
1817
use ProcessMaker\Models\User;
18+
use ProcessMaker\Services\PermissionCacheService;
1919
use ProcessMaker\Traits\ProjectAssetTrait;
2020
use Symfony\Component\HttpFoundation\Response;
2121

@@ -94,9 +94,13 @@ private function userHasAccessToProject($request, $userId, $models)
9494

9595
private function getUserPermissions($user)
9696
{
97-
return Cache::remember("user_{$user->id}_permissions", 86400, function () use ($user) {
98-
return $user->permissions()->pluck('name')->toArray();
99-
});
97+
return app(PermissionCacheService::class)->rememberLegacyUserPermissions(
98+
$user->id,
99+
86400,
100+
function () use ($user) {
101+
return $user->permissions()->pluck('name')->toArray();
102+
}
103+
);
100104
}
101105

102106
private function hasPermission($userPermissions, $permission)

ProcessMaker/Http/Middleware/IsManager.php

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use Illuminate\Http\Request;
77
use Illuminate\Support\Facades\Cache;
88
use Illuminate\Support\Facades\Log;
9+
use ProcessMaker\Services\PermissionCacheService;
910
use Symfony\Component\HttpFoundation\Response;
1011

1112
class IsManager
@@ -76,12 +77,11 @@ private function simulateRequiredPermissionsForRequest($user, array $requiredPer
7677
}
7778

7879
// simulate the permissions by adding them temporarily to the cache of permissions
79-
$cacheKey = "user_{$user->id}_permissions";
8080
$simulatedPermissions = array_merge($currentPermissions, $permissionsToAdd);
8181

8282
// save in cache temporarily (only for this request)
8383
// use a very short time to expire quickly if not cleaned manually
84-
Cache::put($cacheKey, $simulatedPermissions, 5); // 5 segundos como fallback
84+
app(PermissionCacheService::class)->putLegacyUserPermissions($user->id, $simulatedPermissions, 5);
8585
} catch (\Exception $e) {
8686
Log::error('IsManager middleware - Error simulating permissions: ' . $e->getMessage());
8787
}
@@ -93,10 +93,8 @@ private function simulateRequiredPermissionsForRequest($user, array $requiredPer
9393
private function cleanupSimulatedPermission($user)
9494
{
9595
try {
96-
$cacheKey = "user_{$user->id}_permissions";
97-
9896
// delete the cache to force the reload of real permissions
99-
Cache::forget($cacheKey);
97+
app(PermissionCacheService::class)->forgetLegacyUserPermissions($user->id);
10098
} catch (\Exception $e) {
10199
Log::error('IsManager middleware - Error cleaning up simulated permissions: ' . $e->getMessage());
102100
}

ProcessMaker/Models/User.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -591,7 +591,7 @@ public function refresh()
591591

592592
// Clear permissions and user_permissions
593593
Cache::forget('permissions');
594-
Cache::forget("user_{$this->id}_permissions");
594+
app(\ProcessMaker\Services\PermissionCacheService::class)->forgetLegacyUserPermissions($this->id);
595595

596596
// return the refreshed user instance
597597
return $this;

ProcessMaker/Services/PermissionCacheService.php

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,57 @@ public function invalidateUserPermissions(int $userId): void
102102
}
103103
}
104104

105+
/**
106+
* Remember legacy user permissions and track the key for scoped clearAll().
107+
*/
108+
public function rememberLegacyUserPermissions(int $userId, int $ttl, callable $callback): array
109+
{
110+
$key = $this->getLegacyUserPermissionsKey($userId);
111+
112+
try {
113+
$permissions = Cache::remember($key, $ttl, $callback);
114+
$this->trackPermissionKey($key);
115+
116+
return is_array($permissions) ? $permissions : [];
117+
} catch (\Exception $e) {
118+
Log::warning("Failed to remember legacy user permissions for user {$userId}: " . $e->getMessage());
119+
120+
$permissions = $callback();
121+
122+
return is_array($permissions) ? $permissions : [];
123+
}
124+
}
125+
126+
/**
127+
* Cache legacy user permissions and track the key for scoped clearAll().
128+
*/
129+
public function putLegacyUserPermissions(int $userId, array $permissions, int $ttl): void
130+
{
131+
$key = $this->getLegacyUserPermissionsKey($userId);
132+
133+
try {
134+
Cache::put($key, $permissions, $ttl);
135+
$this->trackPermissionKey($key);
136+
} catch (\Exception $e) {
137+
Log::warning("Failed to cache legacy user permissions for user {$userId}: " . $e->getMessage());
138+
}
139+
}
140+
141+
/**
142+
* Forget the legacy user permission cache and remove it from the tracked index.
143+
*/
144+
public function forgetLegacyUserPermissions(int $userId): void
145+
{
146+
$key = $this->getLegacyUserPermissionsKey($userId);
147+
148+
try {
149+
Cache::forget($key);
150+
$this->untrackPermissionKey($key);
151+
} catch (\Exception $e) {
152+
Log::warning("Failed to forget legacy user permissions for user {$userId}: " . $e->getMessage());
153+
}
154+
}
155+
105156
/**
106157
* Invalidate group permissions cache
107158
*/

ProcessMaker/Traits/HasAuthorization.php

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22

33
namespace ProcessMaker\Traits;
44

5-
use Illuminate\Support\Facades\Cache;
65
use Illuminate\Support\Facades\Log;
76
use ProcessMaker\Models\Group;
87
use ProcessMaker\Models\Permission;
8+
use ProcessMaker\Services\PermissionCacheService;
99
use ProcessMaker\Services\PermissionServiceManager;
1010

1111
trait HasAuthorization
@@ -33,9 +33,13 @@ public function loadPermissions()
3333
public function loadUserPermissions()
3434
{
3535
$user = $this;
36-
$permissions = Cache::remember("user_{$user->id}_permissions", 86400, function () use ($user) {
37-
return $user->permissions()->pluck('name')->toArray();
38-
});
36+
$permissions = app(PermissionCacheService::class)->rememberLegacyUserPermissions(
37+
$user->id,
38+
86400,
39+
function () use ($user) {
40+
return $user->permissions()->pluck('name')->toArray();
41+
}
42+
);
3943

4044
return $this->addCategoryViewPermissions($permissions);
4145
}

tests/unit/ProcessMaker/Services/PermissionCacheServiceTest.php

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ public function test_invalidate_user_permissions_clears_cache_correctly()
126126
{
127127
// Cache user permissions
128128
$this->cacheService->cacheUserPermissions($this->userId, $this->userPermissions);
129-
Cache::put("user_{$this->userId}_permissions", $this->userPermissions, 3600);
129+
$this->cacheService->putLegacyUserPermissions($this->userId, $this->userPermissions, 3600);
130130

131131
// Verify cache exists
132132
$this->assertNotNull(Cache::get("user_permissions:{$this->userId}"));
@@ -165,22 +165,44 @@ public function test_clear_all_clears_all_permission_caches()
165165
{
166166
// Cache both user and group permissions
167167
$this->cacheService->cacheUserPermissions($this->userId, $this->userPermissions);
168+
$this->cacheService->putLegacyUserPermissions($this->userId, $this->userPermissions, 3600);
168169
$this->cacheService->cacheGroupPermissions($this->groupId, $this->groupPermissions);
169170
Cache::put('unrelated-cache-key', 'keep-me', 3600);
170171

171172
// Verify both caches exist
172173
$this->assertNotNull(Cache::get("user_permissions:{$this->userId}"));
174+
$this->assertNotNull(Cache::get("user_{$this->userId}_permissions"));
173175
$this->assertNotNull(Cache::get("group_permissions:{$this->groupId}"));
174176

175177
// Clear all caches
176178
$this->cacheService->clearAll();
177179

178180
// Verify both caches were cleared
179181
$this->assertNull(Cache::get("user_permissions:{$this->userId}"));
182+
$this->assertNull(Cache::get("user_{$this->userId}_permissions"));
180183
$this->assertNull(Cache::get("group_permissions:{$this->groupId}"));
181184
$this->assertSame('keep-me', Cache::get('unrelated-cache-key'));
182185
}
183186

187+
/**
188+
* Test that clearAll clears tracked legacy permission caches without touching unrelated cache.
189+
*/
190+
public function test_clear_all_clears_legacy_user_permission_cache_only_when_tracked()
191+
{
192+
$permissions = $this->cacheService->rememberLegacyUserPermissions($this->userId, 3600, function () {
193+
return $this->userPermissions;
194+
});
195+
Cache::put('unrelated-cache-key', 'keep-me', 3600);
196+
197+
$this->assertEquals($this->userPermissions, $permissions);
198+
$this->assertNotNull(Cache::get("user_{$this->userId}_permissions"));
199+
200+
$this->cacheService->clearAll();
201+
202+
$this->assertNull(Cache::get("user_{$this->userId}_permissions"));
203+
$this->assertSame('keep-me', Cache::get('unrelated-cache-key'));
204+
}
205+
184206
/**
185207
* Test that cache keys are generated correctly
186208
*/

tests/unit/ProcessMaker/Traits/HasAuthorizationTest.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
namespace Tests\Unit\ProcessMaker\Traits;
44

55
use Illuminate\Foundation\Testing\RefreshDatabase;
6+
use Illuminate\Support\Facades\Cache;
67
use ProcessMaker\Models\Group;
78
use ProcessMaker\Models\Permission;
89
use ProcessMaker\Models\User;
10+
use ProcessMaker\Services\PermissionCacheService;
911
use Tests\TestCase;
1012

1113
class HasAuthorizationTest extends TestCase
@@ -66,6 +68,25 @@ public function test_load_permissions_works_correctly()
6668
$this->assertTrue($this->user->hasPermission('test-permission'));
6769
}
6870

71+
/**
72+
* Test that legacy permission caches written by the trait are tracked for clearAll().
73+
*/
74+
public function test_load_user_permissions_registers_legacy_cache_for_clear_all()
75+
{
76+
$this->user->permissions()->attach($this->permission->id);
77+
Cache::put('unrelated-cache-key', 'keep-me', 3600);
78+
79+
$permissions = $this->user->loadUserPermissions();
80+
81+
$this->assertContains('test-permission', $permissions);
82+
$this->assertNotNull(Cache::get("user_{$this->user->id}_permissions"));
83+
84+
app(PermissionCacheService::class)->clearAll();
85+
86+
$this->assertNull(Cache::get("user_{$this->user->id}_permissions"));
87+
$this->assertSame('keep-me', Cache::get('unrelated-cache-key'));
88+
}
89+
6990
/**
7091
* Test that invalidatePermissionCache works correctly
7192
*/

0 commit comments

Comments
 (0)