mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-04 10:42:23 +00:00
Require deploy permission for API changes that trigger server side effects, including resource deletion, storage access, provider operations, and database imports. Also escape Docker cleanup filters and sanitize log drain server names.
186 lines
6.1 KiB
PHP
186 lines
6.1 KiB
PHP
<?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('api token list only contains tokens for the current team', function () {
|
|
$user = User::factory()->create();
|
|
$otherTeam = Team::factory()->create();
|
|
$this->team->members()->attach($user->id, ['role' => 'admin']);
|
|
$otherTeam->members()->attach($user->id, ['role' => 'admin']);
|
|
|
|
session(['currentTeam' => $this->team]);
|
|
$currentTeamToken = $user->createToken('current-team-token', ['read'])->accessToken;
|
|
|
|
session(['currentTeam' => $otherTeam]);
|
|
$user->createToken('other-team-token', ['read']);
|
|
|
|
$this->actingAs($user);
|
|
session(['currentTeam' => $this->team]);
|
|
|
|
Livewire::test(ApiTokens::class)
|
|
->assertSet('tokens', fn ($tokens) => $tokens->pluck('id')->all() === [$currentTeamToken->id]);
|
|
});
|
|
|
|
test('user cannot revoke a token from another team through the current team', function () {
|
|
$user = User::factory()->create();
|
|
$otherTeam = Team::factory()->create();
|
|
$this->team->members()->attach($user->id, ['role' => 'admin']);
|
|
$otherTeam->members()->attach($user->id, ['role' => 'admin']);
|
|
|
|
session(['currentTeam' => $otherTeam]);
|
|
$otherTeamToken = $user->createToken('other-team-token', ['read'])->accessToken;
|
|
|
|
$this->actingAs($user);
|
|
session(['currentTeam' => $this->team]);
|
|
|
|
Livewire::test(ApiTokens::class)->call('revoke', $otherTeamToken->id);
|
|
|
|
expect($user->tokens()->whereKey($otherTeamToken->id)->exists())->toBeTrue();
|
|
});
|
|
|
|
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']);
|
|
});
|
|
|
|
test('owner can deselect root and falls back to read permission', 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('permissions', ['root'])
|
|
->set('permissions', [])
|
|
->assertSet('permissions', ['read']);
|
|
});
|
|
|
|
test('deploy permission can be combined with write permission', function (array $steps) {
|
|
$owner = User::factory()->create();
|
|
$this->team->members()->attach($owner->id, ['role' => 'owner']);
|
|
|
|
$this->actingAs($owner);
|
|
session(['currentTeam' => $this->team]);
|
|
|
|
$component = Livewire::test(ApiTokens::class)->set('permissions', ['read']);
|
|
|
|
foreach ($steps as $permission) {
|
|
$component->set('permissions.'.count($component->get('permissions')), $permission);
|
|
}
|
|
|
|
$component->assertSet('permissions', ['deploy', 'read', 'write']);
|
|
})->with([
|
|
'write then deploy' => [['write', 'deploy']],
|
|
'deploy then write' => [['deploy', 'write']],
|
|
]);
|
|
|
|
test('owner can create token with read, write, and deploy permissions', 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', 'deploy-write-token')
|
|
->set('expiresInDays', 30)
|
|
->set('permissions', ['read', 'write', 'deploy'])
|
|
->call('addNewToken')
|
|
->assertHasNoErrors();
|
|
|
|
$token = $owner->tokens()->latest()->first();
|
|
|
|
expect($token)->not->toBeNull()
|
|
->and($token->abilities)->toEqualCanonicalizing(['read', 'write', 'deploy']);
|
|
});
|