Skip to content

Commit a7dddcc

Browse files
committed
Merge remote-tracking branch 'origin/next' into ghe-support-helpers
2 parents b90380f + 8ce054c commit a7dddcc

3 files changed

Lines changed: 241 additions & 66 deletions

File tree

app/Livewire/Source/Github/Change.php

Lines changed: 21 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,8 @@
88
use App\Rules\SafeExternalUrl;
99
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
1010
use Illuminate\Support\Facades\Cache;
11-
use Illuminate\Support\Facades\Http;
1211
use Illuminate\Support\Str;
13-
use Lcobucci\JWT\Configuration;
14-
use Lcobucci\JWT\Signer\Key\InMemory;
15-
use Lcobucci\JWT\Signer\Rsa\Sha256;
12+
use Illuminate\Validation\ValidationException;
1613
use Livewire\Component;
1714

1815
class Change extends Component
@@ -216,6 +213,8 @@ public function checkPermissions()
216213
return;
217214
}
218215

216+
syncGithubAppName($this->github_app);
217+
219218
GithubAppPermissionJob::dispatchSync($this->github_app);
220219
$this->github_app->refresh()->makeVisible('client_secret')->makeVisible('webhook_secret');
221220
$this->syncData(false);
@@ -314,64 +313,34 @@ public function getGithubAppNameUpdatePath()
314313
return rtrim($this->github_app->html_url, '/')."/settings/apps/{$name}";
315314
}
316315

317-
private function generateGithubJwt($private_key, $app_id): string
318-
{
319-
$configuration = Configuration::forAsymmetricSigner(
320-
new Sha256,
321-
InMemory::plainText($private_key),
322-
InMemory::plainText($private_key)
323-
);
324-
325-
$now = time();
326-
327-
return $configuration->builder()
328-
->issuedBy((string) $app_id)
329-
->permittedFor($this->github_app->api_url)
330-
->identifiedBy((string) $now)
331-
->issuedAt(new \DateTimeImmutable("@{$now}"))
332-
->expiresAt(new \DateTimeImmutable('@'.($now + 600)))
333-
->getToken($configuration->signer(), $configuration->signingKey())
334-
->toString();
335-
}
336-
337316
public function updateGithubAppName()
338317
{
339318
try {
340319
$this->authorize('update', $this->github_app);
341320

342-
$privateKey = PrivateKey::ownedByCurrentTeam()->find($this->github_app->private_key_id);
321+
$this->github_app->app_id = $this->appId;
322+
$this->github_app->private_key_id = $this->privateKeyId;
323+
$this->github_app->unsetRelation('privateKey');
324+
325+
if (! $this->appId) {
326+
$this->dispatch('error', 'App ID is required before synchronizing the GitHub App name.');
343327

344-
if (! $privateKey) {
328+
return;
329+
}
330+
331+
if (! PrivateKey::ownedByCurrentTeam()->find($this->privateKeyId)) {
345332
$this->dispatch('error', 'No private key found for this GitHub App.');
346333

347334
return;
348335
}
349336

350-
$jwt = $this->generateGithubJwt($privateKey->private_key, $this->github_app->app_id);
351-
352-
$response = Http::withHeaders([
353-
'Accept' => 'application/vnd.github+json',
354-
'X-GitHub-Api-Version' => '2022-11-28',
355-
'Authorization' => "Bearer {$jwt}",
356-
])->get("{$this->github_app->api_url}/app");
357-
358-
if ($response->successful()) {
359-
$app_data = $response->json();
360-
$app_slug = $app_data['slug'] ?? null;
361-
362-
if ($app_slug) {
363-
$this->github_app->name = $app_slug;
364-
$this->name = str($app_slug)->kebab();
365-
$privateKey->name = "github-app-{$app_slug}";
366-
$privateKey->save();
367-
$this->github_app->save();
368-
$this->dispatch('success', 'GitHub App name and SSH key name synchronized successfully.');
369-
} else {
370-
$this->dispatch('info', 'Could not find App Name (slug) in GitHub response.');
371-
}
337+
$appSlug = syncGithubAppName($this->github_app, true);
338+
339+
if ($appSlug) {
340+
$this->name = str($appSlug)->kebab();
341+
$this->dispatch('success', 'GitHub App name and private key name synchronized successfully.');
372342
} else {
373-
$error_message = $response->json()['message'] ?? 'Unknown error';
374-
$this->dispatch('error', "Failed to fetch GitHub App information: {$error_message}");
343+
$this->dispatch('info', 'Could not find App Name (slug) in GitHub response.');
375344
}
376345
} catch (\Throwable $e) {
377346
return handleError($e, $this);
@@ -391,6 +360,8 @@ public function submit()
391360
$this->syncData(true);
392361
$this->github_app->save();
393362
$this->dispatch('success', 'Github App updated.');
363+
} catch (ValidationException $e) {
364+
throw $e;
394365
} catch (\Throwable $e) {
395366
return handleError($e, $this);
396367
}

bootstrap/helpers/github.php

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
use App\Models\GithubApp;
44
use App\Models\GitlabApp;
5+
use App\Models\PrivateKey;
56
use Carbon\Carbon;
67
use Carbon\CarbonImmutable;
78
use Illuminate\Support\Facades\Cache;
@@ -147,9 +148,9 @@ function encodeGithubPathSegment(string $segment): string
147148
return rawurlencode($segment);
148149
}
149150

150-
function generateGithubToken(GithubApp $source, string $type)
151+
function assertGithubClockInSync(string $apiUrl): void
151152
{
152-
$response = Http::get("{$source->api_url}/zen");
153+
$response = Http::get("{$apiUrl}/zen");
153154
$serverTime = CarbonImmutable::now()->setTimezone('UTC');
154155
$githubTime = Carbon::parse($response->header('date'));
155156
$timeDiff = abs($serverTime->diffInSeconds($githubTime));
@@ -163,6 +164,11 @@ function generateGithubToken(GithubApp $source, string $type)
163164
'Please synchronize your system clock.'
164165
);
165166
}
167+
}
168+
169+
function generateGithubToken(GithubApp $source, string $type)
170+
{
171+
assertGithubClockInSync($source->api_url);
166172

167173
$signingKey = InMemory::plainText($source->privateKey->private_key);
168174
$algorithm = new Sha256;
@@ -251,6 +257,73 @@ function githubApi(GithubApp|GitlabApp|null $source, string $endpoint, string $m
251257
];
252258
}
253259

260+
function generateGithubAppJwt(string $privateKey, string|int $appId): string
261+
{
262+
$algorithm = new Sha256;
263+
$tokenBuilder = (new Builder(new JoseEncoder, ChainedFormatter::default()));
264+
$now = CarbonImmutable::now()->setTimezone('UTC');
265+
$now = $now->setTime($now->format('H'), $now->format('i'), $now->format('s'));
266+
267+
return $tokenBuilder
268+
->issuedBy((string) $appId)
269+
->issuedAt($now->modify('-1 minute'))
270+
->expiresAt($now->modify('+8 minutes'))
271+
->getToken($algorithm, InMemory::plainText($privateKey))
272+
->toString();
273+
}
274+
275+
function syncGithubAppName(GithubApp $source, bool $throw = false): ?string
276+
{
277+
try {
278+
if (blank($source->app_id) || blank($source->private_key_id)) {
279+
return null;
280+
}
281+
282+
$privateKey = $source->privateKey ?: PrivateKey::find($source->private_key_id);
283+
284+
if (! $privateKey) {
285+
return null;
286+
}
287+
288+
assertGithubClockInSync($source->api_url);
289+
290+
$jwt = generateGithubAppJwt($privateKey->private_key, $source->app_id);
291+
292+
$response = Http::withHeaders([
293+
'Accept' => 'application/vnd.github+json',
294+
'X-GitHub-Api-Version' => '2022-11-28',
295+
'Authorization' => "Bearer {$jwt}",
296+
])->get("{$source->api_url}/app");
297+
298+
if (! $response->successful()) {
299+
throw new RuntimeException(data_get($response->json(), 'message', 'Failed to fetch GitHub App information.'));
300+
}
301+
302+
$appSlug = data_get($response->json(), 'slug');
303+
304+
if (blank($appSlug)) {
305+
return null;
306+
}
307+
308+
$source->name = $appSlug;
309+
310+
if ($source->exists) {
311+
$source->save();
312+
}
313+
314+
$privateKey->name = "github-app-{$appSlug}";
315+
$privateKey->save();
316+
317+
return $appSlug;
318+
} catch (Throwable $e) {
319+
if ($throw) {
320+
throw $e;
321+
}
322+
323+
return null;
324+
}
325+
}
326+
254327
function getInstallationPath(GithubApp $source): string
255328
{
256329
$name = encodeGithubPathSegment(Str::kebab($source->name));

0 commit comments

Comments
 (0)