mirror of
https://github.com/coollabsio/coolify.git
synced 2026-06-14 03:19:51 +00:00
Harden token permission handling
This commit is contained in:
@@ -5,6 +5,7 @@ namespace App\Livewire\Security;
|
||||
use App\Models\InstanceSettings;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Laravel\Sanctum\PersonalAccessToken;
|
||||
use Livewire\Attributes\Locked;
|
||||
use Livewire\Component;
|
||||
|
||||
class ApiTokens extends Component
|
||||
@@ -29,8 +30,10 @@ class ApiTokens extends Component
|
||||
|
||||
public $isApiEnabled;
|
||||
|
||||
#[Locked]
|
||||
public bool $canUseRootPermissions = false;
|
||||
|
||||
#[Locked]
|
||||
public bool $canUseWritePermissions = false;
|
||||
|
||||
public function render()
|
||||
@@ -54,7 +57,7 @@ class ApiTokens extends Component
|
||||
public function updatedPermissions($permissionToUpdate)
|
||||
{
|
||||
// Check if user is trying to use restricted permissions
|
||||
if ($permissionToUpdate == 'root' && ! $this->canUseRootPermissions) {
|
||||
if ($permissionToUpdate == 'root' && ! auth()->user()->can('useRootPermissions', PersonalAccessToken::class)) {
|
||||
$this->dispatch('error', 'You do not have permission to use root permissions.');
|
||||
// Remove root from permissions if it was somehow added
|
||||
$this->permissions = array_diff($this->permissions, ['root']);
|
||||
@@ -62,7 +65,7 @@ class ApiTokens extends Component
|
||||
return;
|
||||
}
|
||||
|
||||
if (in_array($permissionToUpdate, ['write', 'write:sensitive']) && ! $this->canUseWritePermissions) {
|
||||
if (in_array($permissionToUpdate, ['write', 'write:sensitive'], true) && ! auth()->user()->can('useWritePermissions', PersonalAccessToken::class)) {
|
||||
$this->dispatch('error', 'You do not have permission to use write permissions.');
|
||||
// Remove write permissions if they were somehow added
|
||||
$this->permissions = array_diff($this->permissions, ['write', 'write:sensitive']);
|
||||
@@ -72,7 +75,7 @@ class ApiTokens extends Component
|
||||
|
||||
if ($permissionToUpdate == 'root') {
|
||||
$this->permissions = ['root'];
|
||||
} elseif ($permissionToUpdate == 'read:sensitive' && ! in_array('read', $this->permissions)) {
|
||||
} elseif ($permissionToUpdate == 'read:sensitive' && ! in_array('read', $this->permissions, true)) {
|
||||
$this->permissions[] = 'read';
|
||||
} elseif ($permissionToUpdate == 'deploy') {
|
||||
$this->permissions = ['deploy'];
|
||||
@@ -90,11 +93,11 @@ class ApiTokens extends Component
|
||||
$this->authorize('create', PersonalAccessToken::class);
|
||||
|
||||
// Validate permissions based on user role
|
||||
if (in_array('root', $this->permissions) && ! $this->canUseRootPermissions) {
|
||||
if (in_array('root', $this->permissions, true) && ! auth()->user()->can('useRootPermissions', PersonalAccessToken::class)) {
|
||||
throw new \Exception('You do not have permission to create tokens with root permissions.');
|
||||
}
|
||||
|
||||
if (array_intersect(['write', 'write:sensitive'], $this->permissions) && ! $this->canUseWritePermissions) {
|
||||
if (array_intersect(['write', 'write:sensitive'], $this->permissions) && ! auth()->user()->can('useWritePermissions', PersonalAccessToken::class)) {
|
||||
throw new \Exception('You do not have permission to create tokens with write permissions.');
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
use App\Livewire\Security\ApiTokens;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Livewire\Attributes\Locked;
|
||||
use Livewire\Features\SupportLockedProperties\CannotUpdateLockedPropertyException;
|
||||
use Livewire\Livewire;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
InstanceSettings::unguarded(fn () => InstanceSettings::query()->create([
|
||||
'id' => 0,
|
||||
'is_api_enabled' => true,
|
||||
]));
|
||||
|
||||
$this->team = Team::factory()->create();
|
||||
});
|
||||
|
||||
test('api token permission flags are locked', function (string $property) {
|
||||
$property = new ReflectionProperty(ApiTokens::class, $property);
|
||||
|
||||
expect($property->getAttributes(Locked::class))->not->toBeEmpty();
|
||||
})->with([
|
||||
'root permission flag' => 'canUseRootPermissions',
|
||||
'write permission flag' => 'canUseWritePermissions',
|
||||
]);
|
||||
|
||||
test('member cannot tamper with root permission flag', function () {
|
||||
$member = User::factory()->create();
|
||||
$this->team->members()->attach($member->id, ['role' => 'member']);
|
||||
|
||||
$this->actingAs($member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(ApiTokens::class)
|
||||
->set('canUseRootPermissions', true);
|
||||
})->throws(CannotUpdateLockedPropertyException::class);
|
||||
|
||||
test('member cannot create root token through tampered permissions payload', function () {
|
||||
$member = User::factory()->create();
|
||||
$this->team->members()->attach($member->id, ['role' => 'member']);
|
||||
|
||||
$this->actingAs($member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(ApiTokens::class)
|
||||
->set('description', 'pwned-root-token')
|
||||
->set('expiresInDays', 30)
|
||||
->set('permissions', ['root'])
|
||||
->call('addNewToken');
|
||||
|
||||
expect($member->tokens()->count())->toBe(0);
|
||||
});
|
||||
|
||||
test('member can still create read token', function () {
|
||||
$member = User::factory()->create();
|
||||
$this->team->members()->attach($member->id, ['role' => 'member']);
|
||||
|
||||
$this->actingAs($member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(ApiTokens::class)
|
||||
->set('description', 'read-token')
|
||||
->set('expiresInDays', 30)
|
||||
->set('permissions', ['read'])
|
||||
->call('addNewToken')
|
||||
->assertHasNoErrors();
|
||||
|
||||
$token = $member->tokens()->latest()->first();
|
||||
|
||||
expect($token)->not->toBeNull()
|
||||
->and($token->abilities)->toBe(['read']);
|
||||
});
|
||||
|
||||
test('owner can create root token', function () {
|
||||
$owner = User::factory()->create();
|
||||
$this->team->members()->attach($owner->id, ['role' => 'owner']);
|
||||
|
||||
$this->actingAs($owner);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(ApiTokens::class)
|
||||
->set('description', 'root-token')
|
||||
->set('expiresInDays', 30)
|
||||
->set('permissions', ['root'])
|
||||
->call('addNewToken')
|
||||
->assertHasNoErrors();
|
||||
|
||||
$token = $owner->tokens()->latest()->first();
|
||||
|
||||
expect($token)->not->toBeNull()
|
||||
->and($token->abilities)->toBe(['root']);
|
||||
});
|
||||
Reference in New Issue
Block a user