diff --git a/app/Http/Middleware/ApiAbility.php b/app/Http/Middleware/ApiAbility.php index 8951ad50de..7a18695836 100644 --- a/app/Http/Middleware/ApiAbility.php +++ b/app/Http/Middleware/ApiAbility.php @@ -25,8 +25,7 @@ class ApiAbility extends CheckForAnyAbility $teamId = data_get($token, 'team_id'); if ($teamId !== null && ! $request->user()->isAdminOfTeam((int) $teamId)) { - $tokenAbilities = $token->abilities ?? []; - $disallowed = array_intersect($tokenAbilities, self::MEMBER_DISALLOWED_ABILITIES); + $disallowed = array_filter(self::MEMBER_DISALLOWED_ABILITIES, fn (string $ability) => $token->can($ability)); if (! empty($disallowed)) { return response()->json([ diff --git a/tests/Feature/Security/ApiTokenCreationSecurityTest.php b/tests/Feature/Security/ApiTokenCreationSecurityTest.php index 60b23b0e56..fa7e4434b2 100644 --- a/tests/Feature/Security/ApiTokenCreationSecurityTest.php +++ b/tests/Feature/Security/ApiTokenCreationSecurityTest.php @@ -1,10 +1,12 @@ assertStatus(403); }); + test('ApiAbility evaluates member token abilities through the token', function () { + session(['currentTeam' => $this->team]); + $token = $this->member->createToken('token', ['*']); + $this->member->withAccessToken($token->accessToken); + + $request = Request::create('/api/v1/projects'); + $request->setUserResolver(fn () => $this->member); + + $response = (new ApiAbility)->handle($request, fn () => response('ok'), 'read'); + + expect($response->getStatusCode())->toBe(403); + }); + test('member read token passes on non-zero team', function () { session(['currentTeam' => $this->team]); $token = $this->member->createToken('read-token', ['read']);