Skip to content

Commit 9262dda

Browse files
committed
Web browser and PAT support for GitLab.
1 parent 0fb970d commit 9262dda

15 files changed

+783
-0
lines changed

Git-Credential-Manager.sln

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GitHub.UI.Windows", "src\wi
5959
EndProject
6060
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Atlassian.Bitbucket.UI.Windows", "src\windows\Atlassian.Bitbucket.UI.Windows\Atlassian.Bitbucket.UI.Windows.csproj", "{3F015046-DAF2-4D2A-96EC-F9782F169E45}"
6161
EndProject
62+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GitLab", "src\shared\GitLab\GitLab.csproj", "{570897DC-A85C-4598-B793-9A00CF710119}"
63+
EndProject
64+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GitLab.Tests", "src\shared\GitLab.Tests\GitLab.Tests.csproj", "{1AF9F7C5-FA2E-48F1-B216-4D5E9A27F393}"
65+
EndProject
6266
Global
6367
GlobalSection(SolutionConfigurationPlatforms) = preSolution
6468
Debug|Any CPU = Debug|Any CPU
@@ -397,6 +401,38 @@ Global
397401
{3F015046-DAF2-4D2A-96EC-F9782F169E45}.MacRelease|Any CPU.ActiveCfg = Release|Any CPU
398402
{3F015046-DAF2-4D2A-96EC-F9782F169E45}.WindowsRelease|Any CPU.ActiveCfg = Release|Any CPU
399403
{3F015046-DAF2-4D2A-96EC-F9782F169E45}.WindowsRelease|Any CPU.Build.0 = Release|Any CPU
404+
{570897DC-A85C-4598-B793-9A00CF710119}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
405+
{570897DC-A85C-4598-B793-9A00CF710119}.Debug|Any CPU.Build.0 = Debug|Any CPU
406+
{570897DC-A85C-4598-B793-9A00CF710119}.MacDebug|Any CPU.ActiveCfg = Debug|Any CPU
407+
{570897DC-A85C-4598-B793-9A00CF710119}.MacDebug|Any CPU.Build.0 = Debug|Any CPU
408+
{570897DC-A85C-4598-B793-9A00CF710119}.MacRelease|Any CPU.ActiveCfg = Debug|Any CPU
409+
{570897DC-A85C-4598-B793-9A00CF710119}.MacRelease|Any CPU.Build.0 = Debug|Any CPU
410+
{570897DC-A85C-4598-B793-9A00CF710119}.Release|Any CPU.ActiveCfg = Release|Any CPU
411+
{570897DC-A85C-4598-B793-9A00CF710119}.Release|Any CPU.Build.0 = Release|Any CPU
412+
{570897DC-A85C-4598-B793-9A00CF710119}.WindowsDebug|Any CPU.ActiveCfg = Debug|Any CPU
413+
{570897DC-A85C-4598-B793-9A00CF710119}.WindowsDebug|Any CPU.Build.0 = Debug|Any CPU
414+
{570897DC-A85C-4598-B793-9A00CF710119}.WindowsRelease|Any CPU.ActiveCfg = Debug|Any CPU
415+
{570897DC-A85C-4598-B793-9A00CF710119}.WindowsRelease|Any CPU.Build.0 = Debug|Any CPU
416+
{570897DC-A85C-4598-B793-9A00CF710119}.LinuxDebug|Any CPU.ActiveCfg = Debug|Any CPU
417+
{570897DC-A85C-4598-B793-9A00CF710119}.LinuxDebug|Any CPU.Build.0 = Debug|Any CPU
418+
{570897DC-A85C-4598-B793-9A00CF710119}.LinuxRelease|Any CPU.ActiveCfg = Debug|Any CPU
419+
{570897DC-A85C-4598-B793-9A00CF710119}.LinuxRelease|Any CPU.Build.0 = Debug|Any CPU
420+
{1AF9F7C5-FA2E-48F1-B216-4D5E9A27F393}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
421+
{1AF9F7C5-FA2E-48F1-B216-4D5E9A27F393}.Debug|Any CPU.Build.0 = Debug|Any CPU
422+
{1AF9F7C5-FA2E-48F1-B216-4D5E9A27F393}.MacDebug|Any CPU.ActiveCfg = Debug|Any CPU
423+
{1AF9F7C5-FA2E-48F1-B216-4D5E9A27F393}.MacDebug|Any CPU.Build.0 = Debug|Any CPU
424+
{1AF9F7C5-FA2E-48F1-B216-4D5E9A27F393}.MacRelease|Any CPU.ActiveCfg = Debug|Any CPU
425+
{1AF9F7C5-FA2E-48F1-B216-4D5E9A27F393}.MacRelease|Any CPU.Build.0 = Debug|Any CPU
426+
{1AF9F7C5-FA2E-48F1-B216-4D5E9A27F393}.Release|Any CPU.ActiveCfg = Release|Any CPU
427+
{1AF9F7C5-FA2E-48F1-B216-4D5E9A27F393}.Release|Any CPU.Build.0 = Release|Any CPU
428+
{1AF9F7C5-FA2E-48F1-B216-4D5E9A27F393}.WindowsDebug|Any CPU.ActiveCfg = Debug|Any CPU
429+
{1AF9F7C5-FA2E-48F1-B216-4D5E9A27F393}.WindowsDebug|Any CPU.Build.0 = Debug|Any CPU
430+
{1AF9F7C5-FA2E-48F1-B216-4D5E9A27F393}.WindowsRelease|Any CPU.ActiveCfg = Debug|Any CPU
431+
{1AF9F7C5-FA2E-48F1-B216-4D5E9A27F393}.WindowsRelease|Any CPU.Build.0 = Debug|Any CPU
432+
{1AF9F7C5-FA2E-48F1-B216-4D5E9A27F393}.LinuxDebug|Any CPU.ActiveCfg = Debug|Any CPU
433+
{1AF9F7C5-FA2E-48F1-B216-4D5E9A27F393}.LinuxDebug|Any CPU.Build.0 = Debug|Any CPU
434+
{1AF9F7C5-FA2E-48F1-B216-4D5E9A27F393}.LinuxRelease|Any CPU.ActiveCfg = Debug|Any CPU
435+
{1AF9F7C5-FA2E-48F1-B216-4D5E9A27F393}.LinuxRelease|Any CPU.Build.0 = Debug|Any CPU
400436
EndGlobalSection
401437
GlobalSection(SolutionProperties) = preSolution
402438
HideSolutionNode = FALSE
@@ -429,6 +465,8 @@ Global
429465
{714ACBE7-0C69-4D8A-9224-22792CAA8264} = {D5277A0E-997E-453A-8CB9-4EFCC8B16A29}
430466
{0A86ED89-1FC5-42AA-925C-4578FA30607A} = {66722747-1B61-40E4-A89B-1AC8E6D62EA9}
431467
{3F015046-DAF2-4D2A-96EC-F9782F169E45} = {66722747-1B61-40E4-A89B-1AC8E6D62EA9}
468+
{570897DC-A85C-4598-B793-9A00CF710119} = {D5277A0E-997E-453A-8CB9-4EFCC8B16A29}
469+
{1AF9F7C5-FA2E-48F1-B216-4D5E9A27F393} = {D5277A0E-997E-453A-8CB9-4EFCC8B16A29}
432470
EndGlobalSection
433471
GlobalSection(ExtensibilityGlobals) = postSolution
434472
SolutionGuid = {0EF9FC65-E6BA-45D4-A455-262A9EA4366B}

docs/configuration.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ ID|Provider
6464
`azure-repos`|Azure Repos
6565
`github`|GitHub
6666
`bitbucket`|Bitbucket
67+
`gitlab`|GitLab<br/>_(supports OAuth in browser, personal access token and Basic Authentication)_
6768
`generic`|Generic (any other provider not listed above)
6869

6970
Automatic provider selection is based on the remote URL.
@@ -94,6 +95,7 @@ Authority|Provider(s)
9495
`msa`, `microsoft`, `microsoftaccount`,<br/>`aad`, `azure`, `azuredirectory`,</br>`live`, `liveconnect`, `liveid`|Azure Repos<br/>_(supports Microsoft Authentication)_
9596
`github`|GitHub<br/>_(supports GitHub Authentication)_
9697
`bitbucket`|Bitbucket.org<br/>_(supports Basic Authentication and OAuth)_<br/>Bitbucket Server<br/>_(supports Basic Authentication)_
98+
`gitlab`|GitLab<br/>_(supports OAuth in browser, personal access token and Basic Authentication)_
9799
`basic`, `integrated`, `windows`, `kerberos`, `ntlm`,<br/>`tfs`, `sso`|Generic<br/>_(supports Basic and Windows Integrated Authentication)_
98100

99101
#### Example
@@ -250,6 +252,31 @@ git config --global credential.gitHubAuthModes "oauth,basic"
250252

251253
---
252254

255+
### credential.gitLabAuthModes
256+
257+
Override the available authentication modes presented during GitLab authentication.
258+
If this option is not set, then the available authentication modes will be automatically detected.
259+
260+
**Note:** This setting supports multiple values separated by commas.
261+
262+
Value|Authentication Mode
263+
-|-
264+
_(unset)_|Automatically detect modes
265+
`browser`|OAuth authentication via a web browser _(requires a GUI)_
266+
`basic`|Basic authentication using username and password
267+
`pat`|Personal Access Token (pat)-based authentication
268+
269+
#### Example
270+
271+
```shell
272+
git config --global credential.gitLabAuthModes "browser"
273+
```
274+
275+
**Also see: [GCM_GITLAB_AUTHMODES](environment.md#GCM_GITLAB_AUTHMODES)**
276+
277+
---
278+
279+
253280
### credential.namespace
254281

255282
Use a custom namespace prefix for credentials read and written in the OS credential store.

docs/environment.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,36 @@ export GCM_GITHUB_AUTHMODES="oauth,basic"
409409

410410
---
411411

412+
### GCM_GITLAB_AUTHMODES
413+
414+
Override the available authentication modes presented during GitLab authentication.
415+
If this option is not set, then the available authentication modes will be automatically detected.
416+
417+
**Note:** This setting supports multiple values separated by commas.
418+
419+
Value|Authentication Mode
420+
-|-
421+
_(unset)_|Automatically detect modes
422+
`browser`|OAuth authentication via a web browser _(requires a GUI)_
423+
`basic`|Basic authentication using username and password
424+
`pat`|Personal Access Token (pat)-based authentication
425+
426+
##### Windows
427+
428+
```batch
429+
SET GCM_GITLAB_AUTHMODES="browser"
430+
```
431+
432+
##### macOS/Linux
433+
434+
```bash
435+
export GCM_GITLAB_AUTHMODES="browser"
436+
```
437+
438+
**Also see: [credential.gitLabAuthModes](configuration.md#credentialgitLabAuthModes)**
439+
440+
---
441+
412442
### GCM_NAMESPACE
413443

414444
Use a custom namespace prefix for credentials read and written in the OS credential store.

docs/gitlab.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# GitLab support
2+
3+
git-credential-manager supports the following public GitLab instances out the box:
4+
5+
* https://gitlab.com
6+
* https://gitlab.freedesktop.org
7+
8+
If you'd like support for another puublic instance, please [open an issue](https://github.com/GitCredentialManager/git-credential-manager/issues).
9+
10+
## Using on a custom instance
11+
12+
To use on another instance, eg. `https://gitlab.example.com` requires setup and configuration:
13+
14+
1. [Create an OAuth application](https://docs.gitlab.com/ee/integration/oauth_provider.html). This can be at the user, group or instance level. Specify name `git-credential-manager` and redirect URI `http://127.0.0.1/`. Select options 'Confidential', 'Expire access tokens' and scope 'write_repository'.
15+
2. Copy the application ID and configure `git config --global credential.https://gitlab.example.com.GitLabDevClientId <APPLICATION_ID>`
16+
3. Copy the application secret and configure `git config --global credential.https://gitlab.example.com.GitLabDevClientSecret <APPLICATION_SECRET>`
17+
4. For good measure, configure `git config --global credential.https://gitlab.example.com.provider gitlab`
18+
5. Verify the config is as expected `git config --global --get-urlmatch credential https://gitlab.example.com`
19+
20+
### Clearing config
21+
22+
```
23+
git config --global --unset-all credential.https://gitlab.example.com.GitLabDevClientId
24+
git config --global --unset-all credential.https://gitlab.example.com.GitLabDevClientSecret
25+
git config --global --unset-all credential.https://gitlab.example.com.provider
26+
```
27+
28+
## Preferences
29+
30+
```
31+
Select an authentication method for 'https://gitlab.com/':
32+
1. Web browser (default)
33+
2. Personal access token
34+
3. Username/password
35+
option (enter for default):
36+
```
37+
38+
39+
If you have a preferred authentication mode, you can specify [credential.gitLabAuthModes](configuration.md#credential.gitLabAuthModes):
40+
41+
`git config --global credential.gitlabauthmodes browser`
42+
43+
## Caveats
44+
45+
Improved support requires changes in GitLab. Please vote for these issues if they affect you:
46+
47+
1. No support for OAuth device authorization (necessary for machines without web browser) https://gitlab.com/gitlab-org/gitlab/-/issues/332682
48+
2. Only domains with prefix `gitlab.` are recognised as GitLab remotes https://gitlab.com/gitlab-org/gitlab/-/issues/349464
49+
3. Username/password authentication is suggested even if disabled on server https://gitlab.com/gitlab-org/gitlab/-/issues/349463

src/shared/Git-Credential-Manager/Git-Credential-Manager.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
<ItemGroup>
1818
<ProjectReference Include="..\Atlassian.Bitbucket\Atlassian.Bitbucket.csproj" />
1919
<ProjectReference Include="..\GitHub\GitHub.csproj" />
20+
<ProjectReference Include="..\GitLab\GitLab.csproj" />
2021
<ProjectReference Include="..\Microsoft.AzureRepos\Microsoft.AzureRepos.csproj" />
2122
<ProjectReference Include="..\Core\Core.csproj" />
2223
</ItemGroup>

src/shared/Git-Credential-Manager/Program.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using Atlassian.Bitbucket;
33
using GitHub;
4+
using GitLab;
45
using Microsoft.AzureRepos;
56
using GitCredentialManager.Authentication;
67

@@ -35,6 +36,7 @@ public static void Main(string[] args)
3536
app.RegisterProvider(new AzureReposHostProvider(context), HostProviderPriority.Normal);
3637
app.RegisterProvider(new BitbucketHostProvider(context), HostProviderPriority.Normal);
3738
app.RegisterProvider(new GitHubHostProvider(context), HostProviderPriority.Normal);
39+
app.RegisterProvider(new GitLabHostProvider(context), HostProviderPriority.Normal);
3840
app.RegisterProvider(new GenericHostProvider(context), HostProviderPriority.Low);
3941

4042
int exitCode = app.RunAsync(args)
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net5.0</TargetFramework>
5+
<IsPackable>false</IsPackable>
6+
<IsTestProject>true</IsTestProject>
7+
<LangVersion>latest</LangVersion>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="coverlet.collector" Version="3.1.0">
12+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
13+
<PrivateAssets>all</PrivateAssets>
14+
</PackageReference>
15+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.7.0" />
16+
<PackageReference Include="ReportGenerator" Version="4.8.13" />
17+
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
18+
<DotNetCliToolReference Include="dotnet-xunit" Version="2.3.1" />
19+
</ItemGroup>
20+
21+
<ItemGroup>
22+
<ProjectReference Include="..\GitLab\GitLab.csproj" />
23+
<ProjectReference Include="..\TestInfrastructure\TestInfrastructure.csproj" />
24+
</ItemGroup>
25+
26+
</Project>
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Threading.Tasks;
5+
using GitCredentialManager.Tests.Objects;
6+
using Moq;
7+
using Moq.Protected;
8+
using Xunit;
9+
10+
namespace GitLab.Tests
11+
{
12+
public class GitLabAuthenticationTests
13+
{
14+
[Fact]
15+
public void GitLabAuthentication_GetAuthenticationAsync_AuthenticationModesNone_ThrowsException()
16+
{
17+
var context = new TestCommandContext();
18+
var auth = new GitLabAuthentication(context);
19+
Assert.Throws<ArgumentException>("modes",
20+
() => auth.GetAuthentication(null, null, AuthenticationModes.None)
21+
);
22+
}
23+
24+
[Theory]
25+
[InlineData(AuthenticationModes.Browser)]
26+
public void GitLabAuthentication_GetAuthenticationAsync_SingleChoice_TerminalAndInteractionNotRequired(GitLab.AuthenticationModes modes)
27+
{
28+
var context = new TestCommandContext();
29+
context.Settings.IsTerminalPromptsEnabled = false;
30+
context.Settings.IsInteractionAllowed = false;
31+
context.SessionManager.IsDesktopSession = true; // necessary for browser
32+
var auth = new GitLabAuthentication(context);
33+
var result = auth.GetAuthentication(null, null, modes);
34+
Assert.Equal(modes, result.AuthenticationMode);
35+
}
36+
37+
[Fact]
38+
public void GitLabAuthentication_GetAuthenticationAsync_TerminalPromptsDisabled_Throws()
39+
{
40+
var context = new TestCommandContext();
41+
context.Settings.IsTerminalPromptsEnabled = false;
42+
var auth = new GitLabAuthentication(context);
43+
var exception = Assert.Throws<InvalidOperationException>(
44+
() => auth.GetAuthentication(null, null, AuthenticationModes.All)
45+
);
46+
Assert.Equal("Cannot prompt because terminal prompts have been disabled.", exception.Message);
47+
}
48+
49+
[Fact]
50+
public void GitLabAuthentication_GetAuthenticationAsync_Terminal()
51+
{
52+
var context = new TestCommandContext();
53+
var auth = new GitLabAuthentication(context);
54+
context.SessionManager.IsDesktopSession = true;
55+
context.Terminal.Prompts["option (enter for default)"] = "";
56+
var result = auth.GetAuthentication(null, null, AuthenticationModes.All);
57+
Assert.Equal(AuthenticationModes.Browser, result.AuthenticationMode);
58+
}
59+
60+
[Fact]
61+
public void GitLabAuthentication_GetAuthenticationAsync_AuthenticationModesAll_RequiresInteraction()
62+
{
63+
var context = new TestCommandContext();
64+
context.Settings.IsInteractionAllowed = false;
65+
var auth = new GitLabAuthentication(context);
66+
var exception = Assert.Throws<InvalidOperationException>(
67+
() => auth.GetAuthentication(new Uri("https://GitLab.com"), null, AuthenticationModes.All)
68+
);
69+
Assert.Equal("Cannot prompt because user interactivity has been disabled.", exception.Message);
70+
}
71+
}
72+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Threading.Tasks;
4+
using GitCredentialManager;
5+
using GitCredentialManager.Authentication.OAuth;
6+
using GitCredentialManager.Tests.Objects;
7+
using Moq;
8+
using Xunit;
9+
10+
namespace GitLab.Tests
11+
{
12+
public class GitLabHostProviderTests
13+
{
14+
[Theory]
15+
[InlineData("https", "gitlab.com", true)]
16+
[InlineData("http", "gitlab.com", true)]
17+
[InlineData("https", "gitlab.freedesktop.org", true)]
18+
[InlineData("https", "gitlab.gnome.org", true)]
19+
[InlineData("https", "github.com", false)]
20+
public void GitLabHostProvider_IsSupported(string protocol, string host, bool expected)
21+
{
22+
var input = new InputArguments(new Dictionary<string, string>
23+
{
24+
["protocol"] = protocol,
25+
["host"] = host,
26+
});
27+
28+
var provider = new GitLabHostProvider(new TestCommandContext());
29+
Assert.Equal(expected, provider.IsSupported(input));
30+
}
31+
}
32+
}

src/shared/GitLab/GitLab.csproj

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFrameworks>netstandard2.0</TargetFrameworks>
5+
<TargetFrameworks Condition="'$(OSPlatform)'=='windows'">netstandard2.0;net472</TargetFrameworks>
6+
<AssemblyName>GitLab</AssemblyName>
7+
<RootNamespace>GitLab</RootNamespace>
8+
<IsTestProject>false</IsTestProject>
9+
<LangVersion>latest</LangVersion>
10+
</PropertyGroup>
11+
12+
<ItemGroup>
13+
<ProjectReference Include="..\Core\Core.csproj" />
14+
</ItemGroup>
15+
16+
<ItemGroup Condition="'$(TargetFramework)' == 'net472'">
17+
<Reference Include="System.Net.Http" />
18+
</ItemGroup>
19+
20+
</Project>

0 commit comments

Comments
 (0)