mirror of
https://github.com/tiennm99/coolify.git
synced 2026-08-20 06:23:23 +00:00
feat: self-hosted GitLab Apps OAuth integration
Adds self-hosted GitLab OAuth sources so Coolify can connect to a self-managed GitLab instance, list private repositories, clone over an OAuth token, and deploy (the GitLab counterpart to GitHub Apps). Hardening: authenticated, one-time team-bound OAuth callback state; token redaction in deploy logs; custom host port/path kept in clone and ls-remote URLs; submodule OAuth auth; system-wide source selection. Covered by unit and feature tests. cosigned by OpenAI Codex at M1 Max
This commit is contained in:
committed by
Andras Bacsai
parent
9d341d0bb9
commit
a26091de0a
@@ -3,9 +3,16 @@
|
||||
use App\Models\Application;
|
||||
use App\Models\GitlabApp;
|
||||
use App\Models\PrivateKey;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Encryption\Encrypter;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
beforeEach(function () {
|
||||
Model::encryptUsing(new Encrypter(str_repeat('a', 32), 'AES-256-CBC'));
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
Model::encryptUsing(null);
|
||||
Mockery::close();
|
||||
});
|
||||
|
||||
@@ -54,6 +61,9 @@ it('generates ls-remote commands for GitLab source with private key', function (
|
||||
$gitlabSource->shouldReceive('getAttribute')->with('privateKey')->andReturn($privateKey);
|
||||
$gitlabSource->shouldReceive('getAttribute')->with('private_key_id')->andReturn(1);
|
||||
$gitlabSource->shouldReceive('getAttribute')->with('custom_port')->andReturn(22);
|
||||
$gitlabSource->shouldReceive('getAttribute')->with('access_token')->andReturn(null);
|
||||
$gitlabSource->shouldReceive('getAttribute')->with('refresh_token')->andReturn(null);
|
||||
$gitlabSource->shouldReceive('isConnected')->andReturn(false);
|
||||
|
||||
$application = Mockery::mock(Application::class)->makePartial();
|
||||
$application->git_branch = 'main';
|
||||
@@ -83,6 +93,9 @@ it('generates ls-remote commands for GitLab source without private key', functio
|
||||
$gitlabSource->shouldReceive('getAttribute')->with('html_url')->andReturn('https://gitlab.com');
|
||||
$gitlabSource->shouldReceive('getAttribute')->with('privateKey')->andReturn(null);
|
||||
$gitlabSource->shouldReceive('getAttribute')->with('private_key_id')->andReturn(null);
|
||||
$gitlabSource->shouldReceive('getAttribute')->with('access_token')->andReturn(null);
|
||||
$gitlabSource->shouldReceive('getAttribute')->with('refresh_token')->andReturn(null);
|
||||
$gitlabSource->shouldReceive('isConnected')->andReturn(false);
|
||||
|
||||
$application = Mockery::mock(Application::class)->makePartial();
|
||||
$application->git_branch = 'main';
|
||||
@@ -112,6 +125,9 @@ it('does not return null for GitLab source type', function () {
|
||||
$gitlabSource->shouldReceive('getAttribute')->with('html_url')->andReturn('https://gitlab.com');
|
||||
$gitlabSource->shouldReceive('getAttribute')->with('privateKey')->andReturn(null);
|
||||
$gitlabSource->shouldReceive('getAttribute')->with('private_key_id')->andReturn(null);
|
||||
$gitlabSource->shouldReceive('getAttribute')->with('access_token')->andReturn(null);
|
||||
$gitlabSource->shouldReceive('getAttribute')->with('refresh_token')->andReturn(null);
|
||||
$gitlabSource->shouldReceive('isConnected')->andReturn(false);
|
||||
|
||||
$application = Mockery::mock(Application::class)->makePartial();
|
||||
$application->git_branch = 'main';
|
||||
@@ -127,3 +143,73 @@ it('does not return null for GitLab source type', function () {
|
||||
expect($lsRemoteResult)->not->toBeNull();
|
||||
expect($lsRemoteResult)->toHaveKeys(['commands', 'branch', 'fullRepoUrl']);
|
||||
});
|
||||
|
||||
it('preserves custom GitLab http port for connected OAuth sources', function () {
|
||||
$deploymentUuid = 'test-deployment-uuid';
|
||||
|
||||
$gitlabSource = new GitlabApp([
|
||||
'html_url' => 'http://gitlab.example.test:8081',
|
||||
'access_token' => 'gitlab-access-token',
|
||||
'refresh_token' => 'gitlab-refresh-token',
|
||||
'expires_at' => time() + 3600,
|
||||
]);
|
||||
|
||||
$application = Mockery::mock(Application::class)->makePartial();
|
||||
$application->git_branch = 'main';
|
||||
$application->shouldReceive('deploymentType')->andReturn('source');
|
||||
$application->shouldReceive('customRepository')->andReturn([
|
||||
'repository' => 'root/qa-private-app',
|
||||
'port' => 22,
|
||||
]);
|
||||
$application->shouldReceive('getAttribute')->with('source')->andReturn($gitlabSource);
|
||||
$application->source = $gitlabSource;
|
||||
|
||||
$result = $application->generateGitLsRemoteCommands($deploymentUuid, false);
|
||||
|
||||
expect($result['fullRepoUrl'])
|
||||
->toContain('gitlab.example.test:8081')
|
||||
->toBe('http://oauth2:gitlab-access-token@gitlab.example.test:8081/root/qa-private-app.git');
|
||||
expect($result['commands'])->toContain('gitlab.example.test:8081/root/qa-private-app.git');
|
||||
});
|
||||
|
||||
it('applies OAuth git config to GitLab merge-request fetch and submodule checkout', function () {
|
||||
$deploymentUuid = 'test-deployment-uuid';
|
||||
|
||||
$gitlabSource = new GitlabApp([
|
||||
'html_url' => 'https://gitlab.example.test',
|
||||
'access_token' => 'gitlab-access-token',
|
||||
'refresh_token' => 'gitlab-refresh-token',
|
||||
'expires_at' => time() + 3600,
|
||||
]);
|
||||
|
||||
$settings = (object) [
|
||||
'is_git_shallow_clone_enabled' => false,
|
||||
'is_git_submodules_enabled' => true,
|
||||
];
|
||||
|
||||
$application = Mockery::mock(Application::class)->makePartial();
|
||||
$application->git_branch = 'main';
|
||||
$application->shouldReceive('deploymentType')->andReturn('source');
|
||||
$application->shouldReceive('customRepository')->andReturn([
|
||||
'repository' => 'root/qa-private-app',
|
||||
'port' => 22,
|
||||
]);
|
||||
$application->shouldReceive('getAttribute')->with('source')->andReturn($gitlabSource);
|
||||
$application->shouldReceive('getAttribute')->with('settings')->andReturn($settings);
|
||||
$application->source = $gitlabSource;
|
||||
|
||||
$result = $application->generateGitImportCommands(
|
||||
deployment_uuid: $deploymentUuid,
|
||||
pull_request_id: 2,
|
||||
exec_in_docker: false,
|
||||
only_checkout: true,
|
||||
custom_base_dir: '/artifacts/test',
|
||||
);
|
||||
|
||||
$commands = $result['commands'];
|
||||
// The MR-ref fetch and submodule update must run through the OAuth-rewritten git, or private same-host submodules fail.
|
||||
expect($commands)
|
||||
->toContain('oauth2:gitlab-access-token@gitlab.example.test')
|
||||
->toContain("http.version=HTTP/1.1 fetch origin 'merge-requests/2/head:pr-2-coolify'")
|
||||
->toContain('http.version=HTTP/1.1 submodule update --init --recursive');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user