fix(sources): validate the selected private key on source settings

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Andras Bacsai
2026-09-28 11:12:48 +02:00
co-authored by Claude Opus 5.5
parent 1f7a011767
commit af19bd0a50
4 changed files with 97 additions and 2 deletions
+4 -1
View File
@@ -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();
+2 -1
View File
@@ -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'],
@@ -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([
@@ -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]);