Fix source selection flow
This commit is contained in:
parent
783344c875
commit
e9b8320d5f
3 changed files with 101 additions and 29 deletions
|
|
@ -9,6 +9,7 @@
|
|||
use App\Support\ValidationPatterns;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Livewire\Attributes\Locked;
|
||||
use Livewire\Component;
|
||||
|
||||
class GithubPrivateRepository extends Component
|
||||
|
|
@ -29,6 +30,7 @@ class GithubPrivateRepository extends Component
|
|||
|
||||
public int $selected_repository_id;
|
||||
|
||||
#[Locked]
|
||||
public int $selected_github_app_id;
|
||||
|
||||
public string $selected_repository_owner;
|
||||
|
|
@ -37,8 +39,6 @@ class GithubPrivateRepository extends Component
|
|||
|
||||
public string $selected_branch_name = 'main';
|
||||
|
||||
public string $token;
|
||||
|
||||
public $repositories;
|
||||
|
||||
public int $total_repositories_count = 0;
|
||||
|
|
@ -71,7 +71,10 @@ public function mount()
|
|||
$this->parameters = get_route_parameters();
|
||||
$this->query = request()->query();
|
||||
$this->repositories = $this->branches = collect();
|
||||
$this->github_apps = GithubApp::private();
|
||||
$this->github_apps = GithubApp::where('team_id', currentTeam()->id)
|
||||
->where('is_public', false)
|
||||
->whereNotNull('app_id')
|
||||
->get();
|
||||
}
|
||||
|
||||
public function updatedSelectedRepositoryId(): void
|
||||
|
|
@ -96,22 +99,25 @@ public function updatedBuildPack()
|
|||
}
|
||||
}
|
||||
|
||||
public function loadRepositories($github_app_id)
|
||||
public function loadRepositories(int $github_app_id): void
|
||||
{
|
||||
$this->repositories = collect();
|
||||
$this->branches = collect();
|
||||
$this->total_branches_count = 0;
|
||||
$this->page = 1;
|
||||
$this->selected_github_app_id = $github_app_id;
|
||||
$this->github_app = GithubApp::where('id', $github_app_id)->first();
|
||||
$this->token = generateGithubInstallationToken($this->github_app);
|
||||
$repositories = loadRepositoryByPage($this->github_app, $this->token, $this->page);
|
||||
$this->github_app = GithubApp::where('team_id', currentTeam()->id)
|
||||
->where('is_public', false)
|
||||
->whereNotNull('app_id')
|
||||
->findOrFail($github_app_id);
|
||||
$token = generateGithubInstallationToken($this->github_app);
|
||||
$repositories = loadRepositoryByPage($this->github_app, $token, $this->page);
|
||||
$this->total_repositories_count = $repositories['total_count'];
|
||||
$this->repositories = $this->repositories->concat(collect($repositories['repositories']));
|
||||
if ($this->repositories->count() < $this->total_repositories_count) {
|
||||
while ($this->repositories->count() < $this->total_repositories_count) {
|
||||
$this->page++;
|
||||
$repositories = loadRepositoryByPage($this->github_app, $this->token, $this->page);
|
||||
$repositories = loadRepositoryByPage($this->github_app, $token, $this->page);
|
||||
$this->total_repositories_count = $repositories['total_count'];
|
||||
$this->repositories = $this->repositories->concat(collect($repositories['repositories']));
|
||||
}
|
||||
|
|
@ -142,7 +148,9 @@ public function loadBranches()
|
|||
|
||||
protected function loadBranchByPage()
|
||||
{
|
||||
$response = Http::GitHub($this->github_app->api_url, $this->token)
|
||||
$token = generateGithubInstallationToken($this->github_app);
|
||||
|
||||
$response = Http::GitHub($this->github_app->api_url, $token)
|
||||
->timeout(20)
|
||||
->retry(3, 200, throw: false)
|
||||
->get("/repos/{$this->selected_repository_owner}/{$this->selected_repository_repo}/branches", [
|
||||
|
|
|
|||
|
|
@ -73,26 +73,6 @@ public static function ownedByCurrentTeam()
|
|||
});
|
||||
}
|
||||
|
||||
public static function public()
|
||||
{
|
||||
return GithubApp::where(function ($query) {
|
||||
$query->where(function ($q) {
|
||||
$q->where('team_id', currentTeam()->id)
|
||||
->orWhere('is_system_wide', true);
|
||||
})->where('is_public', true);
|
||||
})->whereNotNull('app_id')->get();
|
||||
}
|
||||
|
||||
public static function private()
|
||||
{
|
||||
return GithubApp::where(function ($query) {
|
||||
$query->where(function ($q) {
|
||||
$q->where('team_id', currentTeam()->id)
|
||||
->orWhere('is_system_wide', true);
|
||||
})->where('is_public', false);
|
||||
})->whereNotNull('app_id')->get();
|
||||
}
|
||||
|
||||
public function team()
|
||||
{
|
||||
return $this->belongsTo(Team::class);
|
||||
|
|
|
|||
|
|
@ -5,8 +5,10 @@
|
|||
use App\Models\PrivateKey;
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Livewire\Features\SupportLockedProperties\CannotUpdateLockedPropertyException;
|
||||
use Livewire\Livewire;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
|
@ -64,6 +66,21 @@ function fakeGithubHttp(array $repositories): void
|
|||
]);
|
||||
}
|
||||
|
||||
function githubPrivateRepositoryTestPrivateKeyForTeam(Team $team): PrivateKey
|
||||
{
|
||||
$rsaKey = openssl_pkey_new([
|
||||
'private_key_bits' => 2048,
|
||||
'private_key_type' => OPENSSL_KEYTYPE_RSA,
|
||||
]);
|
||||
openssl_pkey_export($rsaKey, $pemKey);
|
||||
|
||||
return PrivateKey::create([
|
||||
'name' => 'Test Key '.$team->id,
|
||||
'private_key' => $pemKey,
|
||||
'team_id' => $team->id,
|
||||
]);
|
||||
}
|
||||
|
||||
describe('GitHub Private Repository Component', function () {
|
||||
test('loadRepositories fetches and displays repositories', function () {
|
||||
$repos = [
|
||||
|
|
@ -81,6 +98,73 @@ function fakeGithubHttp(array $repositories): void
|
|||
->assertSet('selected_repository_id', 1);
|
||||
});
|
||||
|
||||
test('loadRepositories rejects a github app owned by another team', function () {
|
||||
$victimTeam = Team::factory()->create();
|
||||
$victimPrivateKey = githubPrivateRepositoryTestPrivateKeyForTeam($victimTeam);
|
||||
$victimGithubApp = GithubApp::create([
|
||||
'name' => 'Victim GitHub App',
|
||||
'api_url' => 'https://api.github.com',
|
||||
'html_url' => 'https://github.com',
|
||||
'custom_user' => 'git',
|
||||
'custom_port' => 22,
|
||||
'app_id' => 54321,
|
||||
'installation_id' => 98765,
|
||||
'client_id' => 'victim-client-id',
|
||||
'client_secret' => 'victim-client-secret',
|
||||
'webhook_secret' => 'victim-webhook-secret',
|
||||
'private_key_id' => $victimPrivateKey->id,
|
||||
'team_id' => $victimTeam->id,
|
||||
'is_public' => false,
|
||||
'is_system_wide' => false,
|
||||
]);
|
||||
|
||||
Http::fake();
|
||||
|
||||
expect(fn () => Livewire::test(GithubPrivateRepository::class, ['type' => 'private-gh-app'])
|
||||
->call('loadRepositories', $victimGithubApp->id)
|
||||
)->toThrow(ModelNotFoundException::class);
|
||||
|
||||
Http::assertNothingSent();
|
||||
});
|
||||
|
||||
test('loadRepositories does not mint tokens for another teams system wide github app', function () {
|
||||
$victimTeam = Team::factory()->create();
|
||||
$victimPrivateKey = githubPrivateRepositoryTestPrivateKeyForTeam($victimTeam);
|
||||
$systemWideGithubApp = GithubApp::create([
|
||||
'name' => 'System Wide GitHub App',
|
||||
'api_url' => 'https://api.github.com',
|
||||
'html_url' => 'https://github.com',
|
||||
'custom_user' => 'git',
|
||||
'custom_port' => 22,
|
||||
'app_id' => 54321,
|
||||
'installation_id' => 98765,
|
||||
'client_id' => 'system-client-id',
|
||||
'client_secret' => 'system-client-secret',
|
||||
'webhook_secret' => 'system-webhook-secret',
|
||||
'private_key_id' => $victimPrivateKey->id,
|
||||
'team_id' => $victimTeam->id,
|
||||
'is_public' => false,
|
||||
'is_system_wide' => true,
|
||||
]);
|
||||
|
||||
Http::fake();
|
||||
|
||||
expect(fn () => Livewire::test(GithubPrivateRepository::class, ['type' => 'private-gh-app'])
|
||||
->call('loadRepositories', $systemWideGithubApp->id)
|
||||
)->toThrow(ModelNotFoundException::class);
|
||||
|
||||
Http::assertNothingSent();
|
||||
});
|
||||
|
||||
test('github installation token is not stored as public component state', function () {
|
||||
expect((new ReflectionClass(GithubPrivateRepository::class))->hasProperty('token'))->toBeFalse();
|
||||
});
|
||||
|
||||
test('selected github app id cannot be tampered with from the client', function () {
|
||||
Livewire::test(GithubPrivateRepository::class, ['type' => 'private-gh-app'])
|
||||
->set('selected_github_app_id', $this->githubApp->id);
|
||||
})->throws(CannotUpdateLockedPropertyException::class);
|
||||
|
||||
test('loadRepositories can be called again to refresh the repository list', function () {
|
||||
$initialRepos = [
|
||||
['id' => 1, 'name' => 'alpha-repo', 'owner' => ['login' => 'testuser']],
|
||||
|
|
|
|||
Loading…
Reference in a new issue