From b07ae470f947833c890b704fb115949579b4e5ef Mon Sep 17 00:00:00 2001 From: Saeed Vaziry <61919774+saeedvaziry@users.noreply.github.com> Date: Sun, 17 Mar 2024 11:36:04 +0100 Subject: [PATCH] fix bitbucket (#119) Fix Bitbucket API errors --- .../SourceControl/ConnectSourceControl.php | 22 ++++--- app/Models/SourceControl.php | 3 + .../AbstractSourceControlProvider.php | 29 +++++++++ app/SourceControlProviders/Bitbucket.php | 65 +++++++++++++++---- app/SourceControlProviders/Github.php | 12 ++-- app/SourceControlProviders/Gitlab.php | 12 ++-- .../SourceControlProvider.php | 6 ++ ...28_085814_create_source_controls_table.php | 3 +- .../partials/connect.blade.php | 45 ++++++++++++- tests/Feature/SourceControlsTest.php | 15 ++--- 10 files changed, 167 insertions(+), 45 deletions(-) diff --git a/app/Actions/SourceControl/ConnectSourceControl.php b/app/Actions/SourceControl/ConnectSourceControl.php index 4c2fae4..690a136 100644 --- a/app/Actions/SourceControl/ConnectSourceControl.php +++ b/app/Actions/SourceControl/ConnectSourceControl.php @@ -13,13 +13,17 @@ class ConnectSourceControl public function connect(array $input): void { $this->validate($input); + $sourceControl = new SourceControl([ 'provider' => $input['provider'], 'profile' => $input['name'], - 'access_token' => $input['token'], 'url' => Arr::has($input, 'url') ? $input['url'] : null, ]); + $this->validateProvider($sourceControl, $input); + + $sourceControl->provider_data = $sourceControl->provider()->createData($input); + if (! $sourceControl->provider()->connect()) { throw ValidationException::withMessages([ 'token' => __('Cannot connect to :provider or invalid token!', ['provider' => $sourceControl->provider] @@ -43,15 +47,15 @@ private function validate(array $input): void 'name' => [ 'required', ], - 'token' => [ - 'required', - ], - 'url' => [ - 'nullable', - 'url:http,https', - 'ends_with:/', - ], ]; Validator::make($input, $rules)->validate(); } + + /** + * @throws ValidationException + */ + private function validateProvider(SourceControl $sourceControl, array $input): void + { + Validator::make($input, $sourceControl->provider()->createRules($input))->validate(); + } } diff --git a/app/Models/SourceControl.php b/app/Models/SourceControl.php index 0abf2d1..93c04f6 100755 --- a/app/Models/SourceControl.php +++ b/app/Models/SourceControl.php @@ -8,6 +8,7 @@ /** * @property string $provider + * @property array $provider_data * @property ?string $profile * @property ?string $url * @property string $access_token @@ -18,6 +19,7 @@ class SourceControl extends AbstractModel protected $fillable = [ 'provider', + 'provider_data', 'profile', 'url', 'access_token', @@ -25,6 +27,7 @@ class SourceControl extends AbstractModel protected $casts = [ 'access_token' => 'encrypted', + 'provider_data' => 'encrypted:array', ]; public function provider(): SourceControlProvider diff --git a/app/SourceControlProviders/AbstractSourceControlProvider.php b/app/SourceControlProviders/AbstractSourceControlProvider.php index 80b23a1..9dcbea1 100755 --- a/app/SourceControlProviders/AbstractSourceControlProvider.php +++ b/app/SourceControlProviders/AbstractSourceControlProvider.php @@ -17,6 +17,35 @@ public function __construct(SourceControl $sourceControl) $this->sourceControl = $sourceControl; } + public function createRules(array $input): array + { + return [ + 'token' => 'required', + 'url' => [ + 'nullable', + 'url:http,https', + 'ends_with:/', + ], + ]; + } + + public function createData(array $input): array + { + return [ + 'token' => $input['token'] ?? '', + ]; + } + + public function data(): array + { + // support for older data + $token = $this->sourceControl->access_token ?? ''; + + return [ + 'token' => $this->sourceControl->provider_data['token'] ?? $token, + ]; + } + /** * @throws SourceControlIsNotConnected * @throws RepositoryNotFound diff --git a/app/SourceControlProviders/Bitbucket.php b/app/SourceControlProviders/Bitbucket.php index eddf272..8dcd339 100755 --- a/app/SourceControlProviders/Bitbucket.php +++ b/app/SourceControlProviders/Bitbucket.php @@ -13,9 +13,33 @@ class Bitbucket extends AbstractSourceControlProvider { protected string $apiUrl = 'https://api.bitbucket.org/2.0'; + public function createRules(array $input): array + { + return [ + 'username' => 'required', + 'password' => 'required', + ]; + } + + public function createData(array $input): array + { + return [ + 'username' => $input['username'] ?? '', + 'password' => $input['password'] ?? '', + ]; + } + + public function data(): array + { + return [ + 'username' => $this->sourceControl->provider_data['username'] ?? '', + 'password' => $this->sourceControl->provider_data['password'] ?? '', + ]; + } + public function connect(): bool { - $res = Http::withToken($this->sourceControl->access_token) + $res = Http::withHeaders($this->getAuthenticationHeaders()) ->get($this->apiUrl.'/repositories'); return $res->successful(); @@ -26,7 +50,7 @@ public function connect(): bool */ public function getRepo(?string $repo = null): mixed { - $res = Http::withToken($this->sourceControl->access_token) + $res = Http::withHeaders($this->getAuthenticationHeaders()) ->get($this->apiUrl."/repositories/$repo"); $this->handleResponseErrors($res, $repo); @@ -44,14 +68,15 @@ public function fullRepoUrl(string $repo, string $key): string */ public function deployHook(string $repo, array $events, string $secret): array { - $response = Http::withToken($this->sourceControl->access_token)->post($this->apiUrl."/repositories/$repo/hooks", [ - 'description' => 'deploy', - 'url' => url('/api/git-hooks?secret='.$secret), - 'events' => [ - 'repo:'.implode(',', $events), - ], - 'active' => true, - ]); + $response = Http::withHeaders($this->getAuthenticationHeaders()) + ->post($this->apiUrl."/repositories/$repo/hooks", [ + 'description' => 'deploy', + 'url' => url('/api/git-hooks?secret='.$secret), + 'events' => [ + 'repo:'.implode(',', $events), + ], + 'active' => true, + ]); if ($response->status() != 201) { throw new FailedToDeployGitHook($response->json()['error']['message']); @@ -69,7 +94,8 @@ public function deployHook(string $repo, array $events, string $secret): array public function destroyHook(string $repo, string $hookId): void { $hookId = urlencode($hookId); - $response = Http::withToken($this->sourceControl->access_token)->delete($this->apiUrl."/repositories/$repo/hooks/$hookId"); + $response = Http::withHeaders($this->getAuthenticationHeaders()) + ->delete($this->apiUrl."/repositories/$repo/hooks/$hookId"); if ($response->status() != 204) { throw new FailedToDestroyGitHook('Error'); @@ -81,7 +107,7 @@ public function destroyHook(string $repo, string $hookId): void */ public function getLastCommit(string $repo, string $branch): ?array { - $res = Http::withToken($this->sourceControl->access_token) + $res = Http::withHeaders($this->getAuthenticationHeaders()) ->get($this->apiUrl."/repositories/$repo/commits?include=".$branch); $this->handleResponseErrors($res, $repo); @@ -108,7 +134,7 @@ public function getLastCommit(string $repo, string $branch): ?array */ public function deployKey(string $title, string $repo, string $key): void { - $res = Http::withToken($this->sourceControl->access_token)->post( + $res = Http::withHeaders($this->getAuthenticationHeaders())->post( $this->apiUrl."/repositories/$repo/deploy-keys", [ 'label' => $title, @@ -116,7 +142,7 @@ public function deployKey(string $title, string $repo, string $key): void ] ); - if ($res->status() != 201) { + if ($res->status() != 200) { throw new FailedToDeployGitKey($res->json()['error']['message']); } } @@ -130,4 +156,15 @@ protected function getCommitter(string $raw): array 'email' => Str::replace('>', '', $committer[1]), ]; } + + private function getAuthenticationHeaders(): array + { + $username = $this->data()['username']; + $password = $this->data()['password']; + $basicAuth = base64_encode("$username:$password"); + + return [ + 'Authorization' => 'Basic '.$basicAuth, + ]; + } } diff --git a/app/SourceControlProviders/Github.php b/app/SourceControlProviders/Github.php index f5ea642..9a5b5b9 100755 --- a/app/SourceControlProviders/Github.php +++ b/app/SourceControlProviders/Github.php @@ -16,7 +16,7 @@ public function connect(): bool { $res = Http::withHeaders([ 'Accept' => 'application/vnd.github.v3+json', - 'Authorization' => 'Bearer '.$this->sourceControl->access_token, + 'Authorization' => 'Bearer '.$this->data()['token'], ])->get($this->apiUrl.'/user/repos'); return $res->successful(); @@ -34,7 +34,7 @@ public function getRepo(?string $repo = null): mixed } $res = Http::withHeaders([ 'Accept' => 'application/vnd.github.v3+json', - 'Authorization' => 'Bearer '.$this->sourceControl->access_token, + 'Authorization' => 'Bearer '.$this->data()['token'], ])->get($url); $this->handleResponseErrors($res, $repo); @@ -54,7 +54,7 @@ public function deployHook(string $repo, array $events, string $secret): array { $response = Http::withHeaders([ 'Accept' => 'application/vnd.github.v3+json', - 'Authorization' => 'Bearer '.$this->sourceControl->access_token, + 'Authorization' => 'Bearer '.$this->data()['token'], ])->post($this->apiUrl."/repos/$repo/hooks", [ 'name' => 'web', 'events' => $events, @@ -82,7 +82,7 @@ public function destroyHook(string $repo, string $hookId): void { $response = Http::withHeaders([ 'Accept' => 'application/vnd.github.v3+json', - 'Authorization' => 'Bearer '.$this->sourceControl->access_token, + 'Authorization' => 'Bearer '.$this->data()['token'], ])->delete($this->apiUrl."/repos/$repo/hooks/$hookId"); if ($response->status() != 204) { @@ -98,7 +98,7 @@ public function getLastCommit(string $repo, string $branch): ?array $url = $this->apiUrl.'/repos/'.$repo.'/commits/'.$branch; $res = Http::withHeaders([ 'Accept' => 'application/vnd.github.v3+json', - 'Authorization' => 'Bearer '.$this->sourceControl->access_token, + 'Authorization' => 'Bearer '.$this->data()['token'], ])->get($url); $this->handleResponseErrors($res, $repo); @@ -124,7 +124,7 @@ public function getLastCommit(string $repo, string $branch): ?array */ public function deployKey(string $title, string $repo, string $key): void { - $response = Http::withToken($this->sourceControl->access_token)->post( + $response = Http::withToken($this->data()['token'])->post( $this->apiUrl.'/repos/'.$repo.'/keys', [ 'title' => $title, diff --git a/app/SourceControlProviders/Gitlab.php b/app/SourceControlProviders/Gitlab.php index 73d4b45..4000a25 100755 --- a/app/SourceControlProviders/Gitlab.php +++ b/app/SourceControlProviders/Gitlab.php @@ -16,7 +16,7 @@ class Gitlab extends AbstractSourceControlProvider public function connect(): bool { - $res = Http::withToken($this->sourceControl->access_token) + $res = Http::withToken($this->data()['token']) ->get($this->getApiUrl().'/projects'); return $res->successful(); @@ -28,7 +28,7 @@ public function connect(): bool public function getRepo(?string $repo = null): mixed { $repository = $repo ? urlencode($repo) : null; - $res = Http::withToken($this->sourceControl->access_token) + $res = Http::withToken($this->data()['token']) ->get($this->getApiUrl().'/projects/'.$repository.'/repository/commits'); $this->handleResponseErrors($res, $repo); @@ -49,7 +49,7 @@ public function fullRepoUrl(string $repo, string $key): string public function deployHook(string $repo, array $events, string $secret): array { $repository = urlencode($repo); - $response = Http::withToken($this->sourceControl->access_token)->post( + $response = Http::withToken($this->data()['token'])->post( $this->getApiUrl().'/projects/'.$repository.'/hooks', [ 'description' => 'deploy', @@ -84,7 +84,7 @@ public function deployHook(string $repo, array $events, string $secret): array public function destroyHook(string $repo, string $hookId): void { $repository = urlencode($repo); - $response = Http::withToken($this->sourceControl->access_token)->delete( + $response = Http::withToken($this->data()['token'])->delete( $this->getApiUrl().'/projects/'.$repository.'/hooks/'.$hookId ); @@ -99,7 +99,7 @@ public function destroyHook(string $repo, string $hookId): void public function getLastCommit(string $repo, string $branch): ?array { $repository = urlencode($repo); - $res = Http::withToken($this->sourceControl->access_token) + $res = Http::withToken($this->data()['token']) ->get($this->getApiUrl().'/projects/'.$repository.'/repository/commits?ref_name='.$branch); $this->handleResponseErrors($res, $repo); @@ -126,7 +126,7 @@ public function getLastCommit(string $repo, string $branch): ?array public function deployKey(string $title, string $repo, string $key): void { $repository = urlencode($repo); - $response = Http::withToken($this->sourceControl->access_token)->post( + $response = Http::withToken($this->data()['token'])->post( $this->getApiUrl().'/projects/'.$repository.'/deploy_keys', [ 'title' => $title, diff --git a/app/SourceControlProviders/SourceControlProvider.php b/app/SourceControlProviders/SourceControlProvider.php index a8fa6c9..a695b75 100755 --- a/app/SourceControlProviders/SourceControlProvider.php +++ b/app/SourceControlProviders/SourceControlProvider.php @@ -4,6 +4,12 @@ interface SourceControlProvider { + public function createRules(array $input): array; + + public function createData(array $input): array; + + public function data(): array; + public function connect(): bool; public function getRepo(?string $repo = null): mixed; diff --git a/database/migrations/2021_06_28_085814_create_source_controls_table.php b/database/migrations/2021_06_28_085814_create_source_controls_table.php index 23a9b69..b0404ed 100755 --- a/database/migrations/2021_06_28_085814_create_source_controls_table.php +++ b/database/migrations/2021_06_28_085814_create_source_controls_table.php @@ -11,7 +11,8 @@ public function up(): void Schema::create('source_controls', function (Blueprint $table) { $table->id(); $table->string('provider'); - $table->longText('access_token'); + $table->json('provider_data')->nullable()->after('provider'); + $table->longText('access_token')->nullable(); // @TODO: remove this column $table->timestamps(); }); } diff --git a/resources/views/settings/source-controls/partials/connect.blade.php b/resources/views/settings/source-controls/partials/connect.blade.php index 2b8c789..b492ab3 100644 --- a/resources/views/settings/source-controls/partials/connect.blade.php +++ b/resources/views/settings/source-controls/partials/connect.blade.php @@ -69,7 +69,7 @@ class="mt-1 w-full" @enderror -