Skip to content

Commit 0fb970d

Browse files
authored
Merge pull request #587 from hickford/usability
Skip helper when no choice necessary
2 parents 8599002 + a3deb6a commit 0fb970d

File tree

5 files changed

+112
-5
lines changed

5 files changed

+112
-5
lines changed

src/shared/Core/Authentication/AuthenticationBase.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ protected Task<IDictionary<string, string>> InvokeHelperAsync(string path, strin
2727
return InvokeHelperAsync(path, args, null, CancellationToken.None);
2828
}
2929

30-
protected async Task<IDictionary<string, string>> InvokeHelperAsync(string path, string args,
30+
internal protected virtual async Task<IDictionary<string, string>> InvokeHelperAsync(string path, string args,
3131
IDictionary<string, string> standardInput, CancellationToken ct)
3232
{
3333
var procStartInfo = new ProcessStartInfo(path)

src/shared/Core/InternalsVisibleTo.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
using System.Runtime.CompilerServices;
22

33
[assembly: InternalsVisibleTo("Core.Tests")]
4-
4+
[assembly: InternalsVisibleTo("GitHub.Tests")]
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
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 GitHub.Tests
11+
{
12+
public class GitHubAuthenticationTests
13+
{
14+
[Fact]
15+
public async Task GitHubAuthentication_GetAuthenticationAsync_AuthenticationModesNone_ThrowsException()
16+
{
17+
var context = new TestCommandContext();
18+
var auth = new GitHubAuthentication(context);
19+
await Assert.ThrowsAsync<ArgumentException>("modes",
20+
() => auth.GetAuthenticationAsync(null, null, AuthenticationModes.None)
21+
);
22+
}
23+
24+
[Theory]
25+
[InlineData(AuthenticationModes.Browser)]
26+
[InlineData(AuthenticationModes.Device)]
27+
public async Task GitHubAuthentication_GetAuthenticationAsync_SingleChoice_TerminalAndInteractionNotRequired(GitHub.AuthenticationModes modes)
28+
{
29+
var context = new TestCommandContext();
30+
context.Settings.IsTerminalPromptsEnabled = false;
31+
context.Settings.IsInteractionAllowed = false;
32+
context.SessionManager.IsDesktopSession = true; // necessary for browser
33+
context.FileSystem.Files["/usr/local/bin/GitHub.UI"] = new byte[0];
34+
context.FileSystem.Files[@"C:\Program Files\Git Credential Manager Core\GitHub.UI.exe"] = new byte[0];
35+
var auth = new GitHubAuthentication(context);
36+
var result = await auth.GetAuthenticationAsync(null, null, modes);
37+
Assert.Equal(modes, result.AuthenticationMode);
38+
}
39+
40+
[Fact]
41+
public async Task GitHubAuthentication_GetAuthenticationAsync_TerminalPromptsDisabled_Throws()
42+
{
43+
var context = new TestCommandContext();
44+
context.Settings.IsTerminalPromptsEnabled = false;
45+
var auth = new GitHubAuthentication(context);
46+
var exception = await Assert.ThrowsAsync<InvalidOperationException>(
47+
() => auth.GetAuthenticationAsync(null, null, AuthenticationModes.All)
48+
);
49+
Assert.Equal("Cannot prompt because terminal prompts have been disabled.", exception.Message);
50+
}
51+
52+
// reproduces https://github.com/GitCredentialManager/git-credential-manager/issues/453
53+
[Fact]
54+
public async Task GitHubAuthentication_GetAuthenticationAsync_Terminal()
55+
{
56+
var context = new TestCommandContext();
57+
context.FileSystem.Files["/usr/local/bin/GitHub.UI"] = new byte[0];
58+
context.FileSystem.Files[@"C:\Program Files\Git Credential Manager Core\GitHub.UI.exe"] = new byte[0];
59+
var auth = new GitHubAuthentication(context);
60+
context.Terminal.Prompts["option (enter for default)"] = "";
61+
var result = await auth.GetAuthenticationAsync(null, null, AuthenticationModes.All);
62+
Assert.Equal(AuthenticationModes.Device, result.AuthenticationMode);
63+
}
64+
65+
[Fact]
66+
public async Task GitHubAuthentication_GetAuthenticationAsync_AuthenticationModesAll_RequiresInteraction()
67+
{
68+
var context = new TestCommandContext();
69+
context.Settings.IsInteractionAllowed = false;
70+
var auth = new GitHubAuthentication(context);
71+
var exception = await Assert.ThrowsAsync<InvalidOperationException>(
72+
() => auth.GetAuthenticationAsync(new Uri("https://github.com"), null, AuthenticationModes.All)
73+
);
74+
Assert.Equal("Cannot prompt because user interactivity has been disabled.", exception.Message);
75+
}
76+
77+
[Fact]
78+
public async Task GitHubAuthentication_GetAuthenticationAsync_Helper_Basic()
79+
{
80+
var context = new TestCommandContext();
81+
context.FileSystem.Files["/usr/local/bin/GitHub.UI"] = new byte[0];
82+
context.FileSystem.Files[@"C:\Program Files\Git Credential Manager Core\GitHub.UI.exe"] = new byte[0];
83+
context.SessionManager.IsDesktopSession = true;
84+
var auth = new Mock<GitHubAuthentication>(MockBehavior.Strict, context);
85+
auth.Setup(x => x.InvokeHelperAsync(It.IsAny<string>(), "prompt --all", It.IsAny<IDictionary<string, string>>(), It.IsAny<System.Threading.CancellationToken>()))
86+
.Returns(Task.FromResult<IDictionary<string, string>>(
87+
new Dictionary<string, string>
88+
{
89+
["mode"] = "basic",
90+
["username"] = "tim",
91+
["password"] = "hunter2"
92+
}));
93+
var result = await auth.Object.GetAuthenticationAsync(new Uri("https://github.com"), null, AuthenticationModes.All);
94+
Assert.Equal(AuthenticationModes.Basic, result.AuthenticationMode);
95+
Assert.Equal("tim", result.Credential.Account);
96+
Assert.Equal("hunter2", result.Credential.Password);
97+
}
98+
}
99+
}

src/shared/GitHub/GitHubAuthentication.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using System;
22
using System.Threading.Tasks;
33
using System.Collections.Generic;
4-
using System.Globalization;
54
using System.Net.Http;
65
using System.Text;
76
using System.Threading;
@@ -65,8 +64,6 @@ public GitHubAuthentication(ICommandContext context)
6564

6665
public async Task<AuthenticationPromptResult> GetAuthenticationAsync(Uri targetUri, string userName, AuthenticationModes modes)
6766
{
68-
ThrowIfUserInteractionDisabled();
69-
7067
// If we don't have a desktop session/GUI then we cannot offer browser
7168
if (!Context.SessionManager.IsDesktopSession)
7269
{
@@ -79,6 +76,15 @@ public async Task<AuthenticationPromptResult> GetAuthenticationAsync(Uri targetU
7976
throw new ArgumentException(@$"Must specify at least one {nameof(AuthenticationModes)}", nameof(modes));
8077
}
8178

79+
// If there is no mode choice to be made and no interaction required,
80+
// just return that result.
81+
if (modes == AuthenticationModes.Browser ||
82+
modes == AuthenticationModes.Device)
83+
{
84+
return new AuthenticationPromptResult(modes);
85+
}
86+
87+
ThrowIfUserInteractionDisabled();
8288
if (Context.SessionManager.IsDesktopSession && TryFindHelperExecutablePath(out string helperPath))
8389
{
8490
var promptArgs = new StringBuilder("prompt");

src/shared/GitHub/GitHubHostProvider.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,8 @@ public static bool IsGitHubDotCom(string targetUrl)
316316

317317
public static bool IsGitHubDotCom(Uri targetUri)
318318
{
319+
EnsureArgument.AbsoluteUri(targetUri, nameof(targetUri));
320+
319321
return StringComparer.OrdinalIgnoreCase.Equals(targetUri.Host, GitHubConstants.GitHubBaseUrlHost);
320322
}
321323

0 commit comments

Comments
 (0)