From 5740c93032d3dac33fa5bc497bfcd45dd4735070 Mon Sep 17 00:00:00 2001 From: xDev789 <54103426+xDev789@users.noreply.github.com> Date: Sun, 28 Dec 2025 08:00:59 +0700 Subject: [PATCH] Per request cache for permission checks (#2029) Co-authored-by: MartinOscar <40749467+rmartinoscar@users.noreply.github.com> Co-authored-by: Lance Pioch --- app/Models/User.php | 18 +++++++++++++----- .../Server/Subuser/UpdateSubuserTest.php | 4 ++++ 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/app/Models/User.php b/app/Models/User.php index e389c8371..00782da36 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -39,6 +39,7 @@ use Illuminate\Notifications\Notifiable; use Illuminate\Support\Carbon; use Illuminate\Support\Collection; use Illuminate\Support\Facades\App; +use Illuminate\Support\Facades\Context; use Illuminate\Support\Str; use Illuminate\Validation\Rules\In; use ResourceBundle; @@ -333,12 +334,8 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac return !$key ? $customization : $customization[$key->value]; } - protected function checkPermission(Server $server, string|SubuserPermission $permission = ''): bool + protected function hasPermission(Server $server, string $permission = ''): bool { - if ($permission instanceof SubuserPermission) { - $permission = $permission->value; - } - if ($this->canned('update', $server) || $server->owner_id === $this->id) { return true; } @@ -356,6 +353,17 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac return in_array($permission, $subuser->permissions); } + protected function checkPermission(Server $server, string|SubuserPermission $permission = ''): bool + { + if ($permission instanceof SubuserPermission) { + $permission = $permission->value; + } + + $contextKey = "users.$this->id.servers.$server->id.$permission"; + + return Context::remember($contextKey, fn () => $this->hasPermission($server, $permission)); + } + /** * Laravel's policies strictly check for the existence of a real method, * this checks if the ability is one of our permissions and then checks if the user can do it or not diff --git a/tests/Integration/Api/Client/Server/Subuser/UpdateSubuserTest.php b/tests/Integration/Api/Client/Server/Subuser/UpdateSubuserTest.php index eb15df5ad..d8aafa31c 100644 --- a/tests/Integration/Api/Client/Server/Subuser/UpdateSubuserTest.php +++ b/tests/Integration/Api/Client/Server/Subuser/UpdateSubuserTest.php @@ -6,6 +6,7 @@ use App\Enums\SubuserPermission; use App\Models\Subuser; use App\Models\User; use App\Tests\Integration\Api\Client\ClientApiIntegrationTestCase; +use Illuminate\Support\Facades\Context; use Illuminate\Support\Facades\Http; class UpdateSubuserTest extends ClientApiIntegrationTestCase @@ -41,6 +42,9 @@ class UpdateSubuserTest extends ClientApiIntegrationTestCase $this->actingAs($subuser->user)->postJson($endpoint, $data)->assertForbidden(); $this->actingAs($user)->postJson($endpoint, $data)->assertForbidden(); + // When running the tests, the context is function-scoped instead of request-scoped, so we have to flush it + Context::flush(); + $server->subusers()->where('user_id', $user->id)->update([ 'permissions' => [ SubuserPermission::UserUpdate,