mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-12 15:53:46 -04:00
fix(access): clearer credential message and backup S3 defaults
- Show a clear message when team-wide write access is needed to use team credentials. - Reset the S3 selection on the backup page when a change is not allowed. - Do not save database backups to S3 when the API request does not send save_s3, as the API documentation says.
This commit is contained in:
@@ -1244,6 +1244,7 @@ class DatabasesController extends Controller
|
||||
if (! isset($backupData['enabled'])) {
|
||||
$backupData['enabled'] = true;
|
||||
}
|
||||
$backupData['save_s3'] ??= false;
|
||||
|
||||
if ($s3Storages?->isNotEmpty()) {
|
||||
$this->authorize('useTeamCredentials', $database);
|
||||
|
||||
@@ -192,6 +192,10 @@ class BackupEdit extends Component
|
||||
// The team of the schedule, because the instance database has no environment to read it from.
|
||||
if ($this->backup->newS3StorageIds($storageIds)->isNotEmpty()
|
||||
&& ! app(AccessResolver::class)->allowsTeamWide(Auth::user(), AccessLevel::Write, (int) $this->backup->team_id)) {
|
||||
$this->backup->refresh();
|
||||
$this->saveS3 = (bool) $this->backup->save_s3;
|
||||
$this->s3StorageIds = $this->backup->selectedS3Storages()->pluck('id')->all();
|
||||
|
||||
throw new AuthorizationException('You need write access on the whole team to add an S3 storage to this backup.');
|
||||
}
|
||||
$changedFields = auditChangedFields($this->backup);
|
||||
|
||||
@@ -6,6 +6,7 @@ use App\Enums\AccessLevel;
|
||||
use App\Models\Environment;
|
||||
use App\Models\User;
|
||||
use App\Services\Access\AccessResolver;
|
||||
use Illuminate\Auth\Access\Response;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ResourceCreatePolicy
|
||||
@@ -30,10 +31,12 @@ class ResourceCreatePolicy
|
||||
* yet: private keys, git sources, integration tokens, and backup files of other resources.
|
||||
* Needs write access on the whole team of the resource, or of the current team without a resource.
|
||||
*/
|
||||
public function useTeamCredentials(User $user, ?Model $resource = null): bool
|
||||
public function useTeamCredentials(User $user, ?Model $resource = null): Response
|
||||
{
|
||||
$teamId = $resource !== null ? $resource->team()?->id : $user->currentTeam()?->id;
|
||||
|
||||
return $teamId !== null && app(AccessResolver::class)->allowsTeamWide($user, AccessLevel::Write, (int) $teamId);
|
||||
return $teamId !== null && app(AccessResolver::class)->allowsTeamWide($user, AccessLevel::Write, (int) $teamId)
|
||||
? Response::allow()
|
||||
: Response::deny('You need write access on the whole team to use team credentials, such as private keys, git sources, and S3 storages.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,7 +236,8 @@ describe('team credentials in the API', function () {
|
||||
];
|
||||
|
||||
placementTestApi($this->developerToken, 'POST', '/api/v1/applications/private-deploy-key', [...$request, 'private_key_uuid' => $privateKey->uuid])
|
||||
->assertForbidden();
|
||||
->assertForbidden()
|
||||
->assertJsonPath('message', 'You need write access on the whole team to use team credentials, such as private keys, git sources, and S3 storages.');
|
||||
placementTestApi($this->developerToken, 'POST', '/api/v1/applications/private-github-app', [...$request, 'github_app_uuid' => 'any-github-app'])
|
||||
->assertForbidden();
|
||||
|
||||
@@ -314,6 +315,26 @@ describe('team S3 storages for backups', function () {
|
||||
->and((bool) $backup->fresh()->save_s3)->toBeFalse();
|
||||
});
|
||||
|
||||
test('the backup page shows one error when a project-scoped member selects a team S3 storage', function () {
|
||||
$backup = ScheduledDatabaseBackup::create([
|
||||
'frequency' => '0 0 * * *',
|
||||
'save_s3' => false,
|
||||
'database_type' => $this->database->getMorphClass(),
|
||||
'database_id' => $this->database->id,
|
||||
'team_id' => $this->team->id,
|
||||
]);
|
||||
$this->actingAs($this->developer);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
$component = Livewire::test(BackupEdit::class, ['backup' => $backup->fresh(), 'availableS3Storages' => collect()])
|
||||
->set('s3StorageIds', [$this->firstStorage->id]);
|
||||
|
||||
$errors = collect($component->effects['dispatches'] ?? [])->where('name', 'error');
|
||||
expect($errors)->toHaveCount(1)
|
||||
->and($backup->fresh()->s3Storages()->count())->toBe(0);
|
||||
$component->assertSet('s3StorageIds', [])->assertSet('saveS3', false);
|
||||
});
|
||||
|
||||
test('a member with team-wide write can send a backup schedule to a team S3 storage', function () {
|
||||
$operator = placementTestMember($this->team, [['scope' => 'team', 'uuid' => null, 'levels' => ['write', 'deploy', 'delete']]]);
|
||||
|
||||
|
||||
@@ -80,6 +80,16 @@ function backupHeaders(): array
|
||||
}
|
||||
|
||||
describe('POST /api/v1/databases/{uuid}/backups', function () {
|
||||
test('does not save to S3 when save_s3 is not sent', function () {
|
||||
$this->withHeaders(backupHeaders())
|
||||
->postJson("/api/v1/databases/{$this->database->uuid}/backups", ['frequency' => 'daily'])
|
||||
->assertCreated();
|
||||
|
||||
$backup = ScheduledDatabaseBackup::firstOrFail();
|
||||
expect((bool) $backup->save_s3)->toBeFalse()
|
||||
->and($backup->s3_storage_id)->toBeNull();
|
||||
});
|
||||
|
||||
test('rejects backup configurations for unsupported database types', function () {
|
||||
$database = StandaloneRedis::create([
|
||||
'uuid' => (string) Str::uuid(),
|
||||
|
||||
Reference in New Issue
Block a user