From 5e37024f102c83a6e61d7c74d82ac04fde8dab94 Mon Sep 17 00:00:00 2001 From: vuguul <88252044+vuguul@users.noreply.github.com> Date: Sat, 6 Jun 2026 17:23:22 -0600 Subject: [PATCH] fix(git): use cloud install path for ghe apps --- bootstrap/helpers/github.php | 16 ++++++++++++++-- tests/Feature/GithubSourceChangeTest.php | 24 ++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/bootstrap/helpers/github.php b/bootstrap/helpers/github.php index 0ec76f6fa..b39a18ea9 100644 --- a/bootstrap/helpers/github.php +++ b/bootstrap/helpers/github.php @@ -120,7 +120,11 @@ function githubApi(GithubApp|GitlabApp|null $source, string $endpoint, string $m function getInstallationPath(GithubApp $source): string { $name = str(Str::kebab($source->name)); - $installation_path = $source->html_url === 'https://github.com' ? 'apps' : 'github-apps'; + $baseUrl = rtrim($source->html_url, '/'); + $host = parse_url($source->html_url, PHP_URL_HOST); + $host = blank($host) ? null : Str::lower($host); + $usesDataResidencyPath = filled($host) && Str::endsWith($host, '.ghe.com'); + $installation_path = $host === 'github.com' || $usesDataResidencyPath ? 'apps' : 'github-apps'; $state = Str::random(64); Cache::put('github-app-setup-state:'.hash('sha256', $state), [ @@ -129,7 +133,15 @@ function getInstallationPath(GithubApp $source): string 'team_id' => $source->team_id, ], now()->addMinutes(60)); - return "$source->html_url/$installation_path/$name/installations/new?".http_build_query(['state' => $state]); + if ($usesDataResidencyPath) { + $organization = str($source->organization)->trim('/'); + + if ($organization->isNotEmpty()) { + return "$baseUrl/$installation_path/$organization/$name/installations/new?".http_build_query(['state' => $state]); + } + } + + return "$baseUrl/$installation_path/$name/installations/new?".http_build_query(['state' => $state]); } function getPermissionsPath(GithubApp $source) diff --git a/tests/Feature/GithubSourceChangeTest.php b/tests/Feature/GithubSourceChangeTest.php index 07bc2a2c3..7148a7d7a 100644 --- a/tests/Feature/GithubSourceChangeTest.php +++ b/tests/Feature/GithubSourceChangeTest.php @@ -147,6 +147,30 @@ function validPrivateKey(): string ]); }); + test('ghe.com installation path uses github cloud owner scoped route', function () { + $githubApp = new GithubApp; + $githubApp->forceFill([ + 'id' => 123, + 'name' => 'provided-github-app', + 'organization' => 'acme-enterprise', + 'html_url' => 'https://octocorp.ghe.com', + 'team_id' => 456, + ]); + + $installationUrl = getInstallationPath($githubApp); + parse_str(parse_url($installationUrl, PHP_URL_QUERY), $query); + $installState = $query['state'] ?? null; + + expect($installationUrl)->toStartWith('https://octocorp.ghe.com/apps/acme-enterprise/provided-github-app/installations/new?') + ->and($installState)->not->toBeEmpty() + ->and(Cache::get('github-app-setup-state:'.hash('sha256', $installState))) + ->toMatchArray([ + 'action' => 'install', + 'github_app_id' => 123, + 'team_id' => 456, + ]); + }); + test('defaults webhook endpoint to app url when it is the first available endpoint', function () { config(['app.url' => 'http://localhost:8000']);