diff --git a/app/Livewire/Source/Github/Change.php b/app/Livewire/Source/Github/Change.php index 6c65190e80..9e354a2315 100644 --- a/app/Livewire/Source/Github/Change.php +++ b/app/Livewire/Source/Github/Change.php @@ -10,6 +10,7 @@ use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Http; use Illuminate\Support\Str; +use Illuminate\Validation\Rule; use Illuminate\Validation\ValidationException; use Livewire\Component; @@ -102,7 +103,7 @@ class Change extends Component 'contents' => 'nullable|string', 'metadata' => 'nullable|string', 'pullRequests' => 'nullable|string', - 'privateKeyId' => 'nullable|int', + 'privateKeyId' => ['nullable', 'integer', Rule::exists('private_keys', 'id')->where('team_id', $this->github_app->team_id)], 'webhook_endpoint' => ['required', 'string', 'url'], 'custom_webhook_endpoint' => ['nullable', 'string', 'url'], 'use_custom_webhook_endpoint' => ['required', 'bool'], @@ -449,6 +450,8 @@ class Change extends Component try { $this->authorize('update', $this->github_app); + $this->validateOnly('privateKeyId'); + $this->syncData(true); $this->github_app->save(); $this->isConnected = $this->github_app->isConnected(); diff --git a/app/Livewire/Source/Gitlab/Change.php b/app/Livewire/Source/Gitlab/Change.php index 50c9e9115c..a0e2049e01 100644 --- a/app/Livewire/Source/Gitlab/Change.php +++ b/app/Livewire/Source/Gitlab/Change.php @@ -10,6 +10,7 @@ use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Gate; use Illuminate\Support\Facades\Http; use Illuminate\Support\Str; +use Illuminate\Validation\Rule; use Livewire\Component; class Change extends Component @@ -79,7 +80,7 @@ class Change extends Component 'webhookToken' => 'nullable|string', 'groupName' => 'nullable|string', 'isSystemWide' => 'required|bool', - 'privateKeyId' => 'nullable|int', + 'privateKeyId' => ['nullable', 'integer', Rule::exists('private_keys', 'id')->where('team_id', $this->gitlab_app->team_id)], 'webhook_endpoint' => ['required', 'string', 'url'], 'custom_webhook_endpoint' => ['nullable', 'string', 'url'], 'use_custom_webhook_endpoint' => ['required', 'bool'], diff --git a/tests/Feature/Application/GithubSourceChangeTest.php b/tests/Feature/Application/GithubSourceChangeTest.php index 3e9372a8cc..b179fa121b 100644 --- a/tests/Feature/Application/GithubSourceChangeTest.php +++ b/tests/Feature/Application/GithubSourceChangeTest.php @@ -236,6 +236,48 @@ describe('GitHub Source Change Component', function () { ->and($privateKey->refresh()->name)->toBe('github-app-actual-github-slug'); }); + test('saving settings keeps key selection within the source team', function (string $action) { + $otherTeam = Team::factory()->create(); + $foreignKey = PrivateKey::create([ + 'name' => 'other-team-key', + 'private_key' => validPrivateKey(), + 'team_id' => $otherTeam->id, + ]); + $ownKey = PrivateKey::create([ + 'name' => 'own-team-key', + 'private_key' => validPrivateKey(), + 'team_id' => $this->team->id, + ]); + + $githubApp = GithubApp::create([ + 'name' => 'Test GitHub App', + 'api_url' => 'https://api.github.com', + 'html_url' => 'https://github.com', + 'custom_user' => 'git', + 'custom_port' => 22, + 'app_id' => 12345, + 'installation_id' => 67890, + 'private_key_id' => $ownKey->id, + 'team_id' => $this->team->id, + 'is_system_wide' => false, + ]); + + Livewire::withQueryParams(['github_app_uuid' => $githubApp->uuid]) + ->test(Change::class) + ->set('privateKeyId', $foreignKey->id) + ->call($action); + + expect($githubApp->refresh()->private_key_id)->toBe($ownKey->id); + + Livewire::withQueryParams(['github_app_uuid' => $githubApp->uuid]) + ->test(Change::class) + ->set('privateKeyId', $ownKey->id) + ->call($action) + ->assertHasNoErrors(); + + expect($githubApp->refresh()->private_key_id)->toBe($ownKey->id); + })->with(['submit', 'instantSave']); + test('ghe.com installation path encodes the organization segment', function () { $githubApp = new GithubApp; $githubApp->forceFill([ diff --git a/tests/Feature/GitlabAppAuthorizationTest.php b/tests/Feature/GitlabAppAuthorizationTest.php index 0793b2f547..4e1f2c2df1 100644 --- a/tests/Feature/GitlabAppAuthorizationTest.php +++ b/tests/Feature/GitlabAppAuthorizationTest.php @@ -5,6 +5,7 @@ use App\Livewire\Source\Gitlab\Change; use App\Models\Application; use App\Models\GitlabApp; use App\Models\InstanceSettings; +use App\Models\PrivateKey; use App\Models\Team; use App\Models\User; use Illuminate\Foundation\Testing\RefreshDatabase; @@ -12,6 +13,18 @@ use Livewire\Livewire; uses(RefreshDatabase::class); +function gitlabTestPrivateKey(): string +{ + $key = openssl_pkey_new([ + 'private_key_bits' => 2048, + 'private_key_type' => OPENSSL_KEYTYPE_RSA, + ]); + + openssl_pkey_export($key, $privateKey); + + return $privateKey; +} + beforeEach(function () { $this->team = Team::factory()->create(); $this->owner = User::factory()->create(); @@ -104,6 +117,42 @@ describe('GitLab App authorization', function () { expect($this->gitlabApp->refresh()->is_system_wide)->toBeTrue(); }); + test('saving settings keeps key selection within the source team', function () { + $otherTeam = Team::factory()->create(); + $foreignKey = PrivateKey::create([ + 'name' => 'other-team-key', + 'private_key' => gitlabTestPrivateKey(), + 'team_id' => $otherTeam->id, + ]); + $ownKey = PrivateKey::create([ + 'name' => 'own-team-key', + 'private_key' => gitlabTestPrivateKey(), + 'team_id' => $this->team->id, + ]); + $this->gitlabApp->update([ + 'api_url' => 'https://gitlab.com/api/v4', + 'html_url' => 'https://gitlab.com', + ]); + + $this->actingAs($this->owner); + session(['currentTeam' => $this->team]); + + Livewire::withQueryParams(['gitlab_app_uuid' => $this->gitlabApp->uuid]) + ->test(Change::class) + ->set('privateKeyId', $foreignKey->id) + ->call('submit'); + + expect($this->gitlabApp->refresh()->private_key_id)->toBeNull(); + + Livewire::withQueryParams(['gitlab_app_uuid' => $this->gitlabApp->uuid]) + ->test(Change::class) + ->set('privateKeyId', $ownKey->id) + ->call('submit') + ->assertHasNoErrors(); + + expect($this->gitlabApp->refresh()->private_key_id)->toBe($ownKey->id); + }); + test('instantSave rejects unsafe GitLab URLs', function (string $url) { $this->actingAs($this->owner); session(['currentTeam' => $this->team]);