From 54d8cb9027d848feaa03d8c0273f46cad8400ab2 Mon Sep 17 00:00:00 2001 From: Niklas Lausch Date: Tue, 23 Jan 2024 11:34:25 +0100 Subject: [PATCH] feat: added manual webhook support for bitbucket --- app/Livewire/Project/Shared/Webhooks.php | 3 + ...32_add_manual_webhook_secret_bitbucket.php | 28 ++++++ .../project/shared/webhooks.blade.php | 4 + routes/webhooks.php | 86 +++++++++++++++++++ 4 files changed, 121 insertions(+) create mode 100755 database/migrations/2024_01_23_095832_add_manual_webhook_secret_bitbucket.php diff --git a/app/Livewire/Project/Shared/Webhooks.php b/app/Livewire/Project/Shared/Webhooks.php index c54a1c38f..6bb9428d5 100644 --- a/app/Livewire/Project/Shared/Webhooks.php +++ b/app/Livewire/Project/Shared/Webhooks.php @@ -10,9 +10,11 @@ class Webhooks extends Component public ?string $deploywebhook = null; public ?string $githubManualWebhook = null; public ?string $gitlabManualWebhook = null; + public ?string $bitbucketManualWebhook = null; protected $rules = [ 'resource.manual_webhook_secret_github' => 'nullable|string', 'resource.manual_webhook_secret_gitlab' => 'nullable|string', + 'resource.manual_webhook_secret_bitbucket' => 'nullable|string', ]; public function saveSecret() { @@ -29,6 +31,7 @@ class Webhooks extends Component $this->deploywebhook = generateDeployWebhook($this->resource); $this->githubManualWebhook = generateGitManualWebhook($this->resource, 'github'); $this->gitlabManualWebhook = generateGitManualWebhook($this->resource, 'gitlab'); + $this->bitbucketManualWebhook = generateGitManualWebhook($this->resource, 'bitbucket'); } public function render() { diff --git a/database/migrations/2024_01_23_095832_add_manual_webhook_secret_bitbucket.php b/database/migrations/2024_01_23_095832_add_manual_webhook_secret_bitbucket.php new file mode 100755 index 000000000..74917a049 --- /dev/null +++ b/database/migrations/2024_01_23_095832_add_manual_webhook_secret_bitbucket.php @@ -0,0 +1,28 @@ +string('manual_webhook_secret_bitbucket')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('applications', function (Blueprint $table) { + $table->dropColumn('manual_webhook_secret_bitbucket'); + }); + } +}; diff --git a/resources/views/livewire/project/shared/webhooks.blade.php b/resources/views/livewire/project/shared/webhooks.blade.php index a90b01b58..121cdf058 100644 --- a/resources/views/livewire/project/shared/webhooks.blade.php +++ b/resources/views/livewire/project/shared/webhooks.blade.php @@ -33,6 +33,10 @@ helper="Need to set a secret to be able to use this webhook. It should match with the secret in GitLab." label="GitLab Webhook Secret" id="resource.manual_webhook_secret_gitlab"> +
+ + +
Save @else diff --git a/routes/webhooks.php b/routes/webhooks.php index 1fb0bce4e..f55dddd13 100644 --- a/routes/webhooks.php +++ b/routes/webhooks.php @@ -231,6 +231,92 @@ Route::post('/source/gitlab/events/manual', function () { return handleError($e); } }); +Route::post('/source/bitbucket/events/manual', function () { + try { + $return_payloads = collect([]); + $payload = request()->collect(); + $headers = request()->headers->all(); + $x_bitbucket_token = data_get($headers, 'x-hub-signature', [""])[0]; + $x_bitbucket_event = data_get($headers, 'x-event-key', [""])[0]; + if ($x_bitbucket_event === 'repo:push') { + $branch = data_get($payload, 'push.changes.0.new.name'); + $name = data_get($payload, 'repository.name'); + + if (!$branch) { + $return_payloads->push([ + 'status' => 'failed', + 'message' => 'Nothing to do. No branch found in the request.', + ]); + return response($return_payloads); + } + ray('Manual Webhook bitbucket Push Event with branch: ' . $branch); + } + $applications = Application::where('git_repository', 'like', "%$name%"); + if ($x_bitbucket_event === 'repo:push') { + $applications = $applications->where('git_branch', $branch)->get(); + if ($applications->isEmpty()) { + $return_payloads->push([ + 'status' => 'failed', + 'message' => "Nothing to do. No applications found with deploy key set, branch is '$branch' and Git Repository name has $name.", + ]); + return response($return_payloads); + } + } + foreach ($applications as $application) { + $webhook_secret = data_get($application, 'manual_webhook_secret_bitbucket'); + $payload = request()->getContent(); + + fwrite(STDOUT, $payload); + + list($algo, $hash) = explode('=', $x_bitbucket_token, 2); + + $payloadHash = hash_hmac($algo, $payload, $webhook_secret); + + if (!hash_equals($hash, $payloadHash)) { + $return_payloads->push([ + 'application' => $application->name, + 'status' => 'failed', + 'message' => 'Invalid token.', + ]); + ray('Invalid signature'); + continue; + } + $isFunctional = $application->destination->server->isFunctional(); + if (!$isFunctional) { + $return_payloads->push([ + 'application' => $application->name, + 'status' => 'failed', + 'message' => 'Server is not functional', + ]); + ray('Server is not functional: ' . $application->destination->server->name); + continue; + } + if ($x_bitbucket_event === 'repo:push') { + if ($application->isDeployable()) { + ray('Deploying ' . $application->name . ' with branch ' . $branch); + $deployment_uuid = new Cuid2(7); + queue_application_deployment( + application_id: $application->id, + deployment_uuid: $deployment_uuid, + force_rebuild: false, + is_webhook: true + ); + } else { + $return_payloads->push([ + 'application' => $application->name, + 'status' => 'failed', + 'message' => 'Deployments disabled', + ]); + ray('Deployments disabled for ' . $application->name); + } + } + } + return response($return_payloads); + } catch (Exception $e) { + ray($e->getMessage()); + return handleError($e); + } +}); Route::post('/source/github/events/manual', function () { try { $x_github_event = Str::lower(request()->header('X-GitHub-Event'));