From 680f4e5d29d7041497c65eb5b114c2cd4fb6f207 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Tue, 29 Sep 2026 22:36:56 +0200 Subject: [PATCH] fix: use GitLab API v4 for OAuth and gate env copy by update policy Socialite's GitLab provider fetches the user from /api/v3/user, which GitLab removed in 11.0 (410 Gone). Add App\Auth\GitlabProvider that reads /api/v4/user with a bearer token and register it in the socialite helper, for both gitlab.com and self-hosted base URLs. Environment variable copyValue now checks the 'update' policy on the variable instead of the member role of the current team, so members can no longer copy values after switching to a team they own. Remove the redundant member check from ShowHardcoded. Add tests for the GitLab v4 user lookup and the team-switch copy case. --- app/Auth/GitlabProvider.php | 26 ++++++++++ .../Shared/EnvironmentVariable/Show.php | 2 +- .../EnvironmentVariable/ShowHardcoded.php | 4 -- bootstrap/helpers/socialite.php | 2 +- .../EnvironmentVariableCopyValueTest.php | 16 ++++++ tests/Feature/GitlabOauthProviderTest.php | 49 +++++++++++++++++++ 6 files changed, 93 insertions(+), 6 deletions(-) create mode 100644 app/Auth/GitlabProvider.php create mode 100644 tests/Feature/GitlabOauthProviderTest.php diff --git a/app/Auth/GitlabProvider.php b/app/Auth/GitlabProvider.php new file mode 100644 index 0000000000..eab85c98ef --- /dev/null +++ b/app/Auth/GitlabProvider.php @@ -0,0 +1,26 @@ + + */ + protected function getUserByToken($token) + { + $response = $this->getHttpClient()->get($this->host.'/api/v4/user', [ + RequestOptions::HEADERS => ['Authorization' => 'Bearer '.$token], + ]); + + return json_decode((string) $response->getBody(), true); + } +} diff --git a/app/Livewire/Project/Shared/EnvironmentVariable/Show.php b/app/Livewire/Project/Shared/EnvironmentVariable/Show.php index 694f18c9c9..70cdef7f6b 100644 --- a/app/Livewire/Project/Shared/EnvironmentVariable/Show.php +++ b/app/Livewire/Project/Shared/EnvironmentVariable/Show.php @@ -174,7 +174,7 @@ class Show extends Component public function copyValue(): ?string { - if ($this->env->is_shown_once || (auth()->user()?->isMember() ?? true)) { + if ($this->env->is_shown_once || (auth()->user()?->cannot('update', $this->env) ?? true)) { return null; } diff --git a/app/Livewire/Project/Shared/EnvironmentVariable/ShowHardcoded.php b/app/Livewire/Project/Shared/EnvironmentVariable/ShowHardcoded.php index 3a7eaf098f..8e85c40b17 100644 --- a/app/Livewire/Project/Shared/EnvironmentVariable/ShowHardcoded.php +++ b/app/Livewire/Project/Shared/EnvironmentVariable/ShowHardcoded.php @@ -40,10 +40,6 @@ class ShowHardcoded extends Component public function copyValue(): ?string { - if (auth()->user()?->isMember() ?? true) { - return null; - } - $environmentVariable = EnvironmentVariable::make([ 'value' => $this->value, 'is_preview' => $this->isPreview, diff --git a/bootstrap/helpers/socialite.php b/bootstrap/helpers/socialite.php index f177e6c16f..5f7e8d868a 100644 --- a/bootstrap/helpers/socialite.php +++ b/bootstrap/helpers/socialite.php @@ -1,11 +1,11 @@ assertReturned(null); }); +test('members get no value after switching to a team they own', function (string $variableType) { + $member = User::factory()->create(); + $this->team->members()->attach($member, ['role' => 'member']); + $ownedTeam = Team::factory()->create(); + $ownedTeam->members()->attach($member, ['role' => 'owner']); + $this->actingAs($member); + $env = $variableType === 'shared' + ? SharedEnvironmentVariable::create(['key' => 'TEAM_WIDE', 'value' => 'team-wide-secret', 'type' => 'team', 'team_id' => $this->team->id]) + : createEnvironmentVariable(); + + $component = Livewire::test(Show::class, ['env' => $env, 'type' => 'application']); + session(['currentTeam' => $ownedTeam]); + + $component->call('copyValue')->assertReturned(null); +})->with(['application variable' => 'application', 'shared variable' => 'shared']); + test('locked variables get no copy button and no value', function () { Livewire::test(Show::class, ['env' => createEnvironmentVariable(['is_shown_once' => true]), 'type' => 'application']) ->assertDontSeeHtml('Copy value') diff --git a/tests/Feature/GitlabOauthProviderTest.php b/tests/Feature/GitlabOauthProviderTest.php new file mode 100644 index 0000000000..bd268ad5ec --- /dev/null +++ b/tests/Feature/GitlabOauthProviderTest.php @@ -0,0 +1,49 @@ + 'gitlab', + 'client_id' => 'client-id', + 'client_secret' => 'client-secret', + 'redirect_uri' => 'https://coolify.example.com/auth/gitlab/callback', + 'base_url' => $baseUrl, + 'enabled' => true, + ]); + + $requests = []; + $handler = HandlerStack::create(new MockHandler([ + new Response(200, ['Content-Type' => 'application/json'], json_encode([ + 'id' => 42, + 'username' => 'gitlab-user', + 'name' => 'GitLab User', + 'email' => 'user@example.com', + 'avatar_url' => null, + 'confirmed_at' => '2026-01-10T09:05:22Z', + ])), + ])); + $handler->push(Middleware::history($requests)); + + $gitlabUser = get_socialite_provider('gitlab') + ->setHttpClient(new Client(['handler' => $handler])) + ->userFromToken('gitlab-access-token'); + + $request = $requests[0]['request']; + expect((string) $request->getUri())->toBe($expectedUrl) + ->and($request->getHeaderLine('Authorization'))->toBe('Bearer gitlab-access-token') + ->and($gitlabUser->getId())->toBe(42) + ->and($gitlabUser->getEmail())->toBe('user@example.com') + ->and($gitlabUser->user['confirmed_at'])->toBe('2026-01-10T09:05:22Z'); +})->with([ + 'gitlab.com' => [null, 'https://gitlab.com/api/v4/user'], + 'self-hosted GitLab' => ['https://gitlab.example.com/', 'https://gitlab.example.com/api/v4/user'], +]);