Skip to content

Commit 8d2ace5

Browse files
committed
oauth: provide UI prompts for generic OAuth auth
Add detection and use of GUI prompts for OAuth authentication.
1 parent 241afed commit 8d2ace5

File tree

1 file changed

+122
-26
lines changed

1 file changed

+122
-26
lines changed

src/shared/Core/Authentication/OAuthAuthentication.cs

Lines changed: 122 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -57,35 +57,77 @@ public async Task<OAuthAuthenticationModes> GetAuthenticationModeAsync(
5757
return modes;
5858
}
5959

60-
ThrowIfTerminalPromptsDisabled();
61-
62-
switch (modes)
60+
if (Context.Settings.IsGuiPromptsEnabled && Context.SessionManager.IsDesktopSession &&
61+
TryFindHelperCommand(out string command, out string args))
62+
{
63+
var promptArgs = new StringBuilder(args);
64+
promptArgs.Append("oauth");
65+
66+
if (!string.IsNullOrWhiteSpace(resource))
67+
{
68+
promptArgs.AppendFormat(" --resource {0}", QuoteCmdArg(resource));
69+
}
70+
71+
if ((modes & OAuthAuthenticationModes.Browser) != 0)
72+
{
73+
promptArgs.Append(" --browser");
74+
}
75+
76+
if ((modes & OAuthAuthenticationModes.DeviceCode) != 0)
77+
{
78+
promptArgs.Append(" --device-code");
79+
}
80+
81+
IDictionary<string, string> resultDict = await InvokeHelperAsync(command, promptArgs.ToString());
82+
83+
if (!resultDict.TryGetValue("mode", out string responseMode))
84+
{
85+
throw new Exception("Missing 'mode' in response");
86+
}
87+
88+
switch (responseMode.ToLowerInvariant())
89+
{
90+
case "browser":
91+
return OAuthAuthenticationModes.Browser;
92+
93+
case "devicecode":
94+
return OAuthAuthenticationModes.DeviceCode;
95+
96+
default:
97+
throw new Exception($"Unknown mode value in response '{responseMode}'");
98+
}
99+
}
100+
else
63101
{
64-
case OAuthAuthenticationModes.Browser:
65-
return OAuthAuthenticationModes.Browser;
102+
ThrowIfTerminalPromptsDisabled();
66103

67-
case OAuthAuthenticationModes.DeviceCode:
68-
return OAuthAuthenticationModes.DeviceCode;
104+
switch (modes)
105+
{
106+
case OAuthAuthenticationModes.Browser:
107+
return OAuthAuthenticationModes.Browser;
69108

70-
default:
71-
var menuTitle = $"Select an authentication method for '{resource}'";
72-
var menu = new TerminalMenu(Context.Terminal, menuTitle);
109+
case OAuthAuthenticationModes.DeviceCode:
110+
return OAuthAuthenticationModes.DeviceCode;
73111

74-
TerminalMenuItem browserItem = null;
75-
TerminalMenuItem deviceItem = null;
112+
default:
113+
var menuTitle = $"Select an authentication method for '{resource}'";
114+
var menu = new TerminalMenu(Context.Terminal, menuTitle);
76115

77-
if ((modes & OAuthAuthenticationModes.Browser) != 0) browserItem = menu.Add("Web browser");
78-
if ((modes & OAuthAuthenticationModes.DeviceCode) != 0) deviceItem = menu.Add("Device code");
116+
TerminalMenuItem browserItem = null;
117+
TerminalMenuItem deviceItem = null;
79118

80-
// Default to the 'first' choice in the menu
81-
TerminalMenuItem choice = menu.Show(0);
119+
if ((modes & OAuthAuthenticationModes.Browser) != 0) browserItem = menu.Add("Web browser");
120+
if ((modes & OAuthAuthenticationModes.DeviceCode) != 0) deviceItem = menu.Add("Device code");
82121

83-
if (choice == browserItem) goto case OAuthAuthenticationModes.Browser;
84-
if (choice == deviceItem) goto case OAuthAuthenticationModes.DeviceCode;
122+
// Default to the 'first' choice in the menu
123+
TerminalMenuItem choice = menu.Show(0);
85124

86-
throw new Exception();
125+
if (choice == browserItem) goto case OAuthAuthenticationModes.Browser;
126+
if (choice == deviceItem) goto case OAuthAuthenticationModes.DeviceCode;
127+
128+
throw new Exception();
129+
}
87130
}
88-
89131
}
90132

91133
public async Task<OAuth2TokenResult> GetTokenByBrowserAsync(OAuth2Client client, string[] scopes)
@@ -110,14 +152,68 @@ public async Task<OAuth2TokenResult> GetTokenByDeviceCodeAsync(OAuth2Client clie
110152

111153
OAuth2DeviceCodeResult dcr = await client.GetDeviceCodeAsync(scopes, CancellationToken.None);
112154

113-
ThrowIfTerminalPromptsDisabled();
155+
// If we have a desktop session show the device code in a dialog
156+
if (Context.Settings.IsGuiPromptsEnabled && Context.SessionManager.IsDesktopSession &&
157+
TryFindHelperCommand(out string command, out string args))
158+
{
159+
var promptArgs = new StringBuilder(args);
160+
promptArgs.Append("device");
161+
promptArgs.AppendFormat(" --code {0} ", QuoteCmdArg(dcr.UserCode));
162+
promptArgs.AppendFormat(" --url {0}", QuoteCmdArg(dcr.VerificationUri.ToString()));
163+
164+
var promptCts = new CancellationTokenSource();
165+
var tokenCts = new CancellationTokenSource();
166+
167+
// Show the dialog with the device code but don't await its closure
168+
Task promptTask = InvokeHelperAsync(command, promptArgs.ToString(), null, promptCts.Token);
169+
170+
// Start the request for an OAuth token but don't wait
171+
Task<OAuth2TokenResult> tokenTask = client.GetTokenByDeviceCodeAsync(dcr, tokenCts.Token);
172+
173+
Task t = await Task.WhenAny(promptTask, tokenTask);
174+
175+
// If the dialog was closed the user wishes to cancel the request
176+
if (t == promptTask)
177+
{
178+
tokenCts.Cancel();
179+
}
180+
181+
OAuth2TokenResult tokenResult;
182+
try
183+
{
184+
tokenResult = await tokenTask;
185+
}
186+
catch (OperationCanceledException)
187+
{
188+
throw new Exception("User canceled device code authentication");
189+
}
190+
191+
// Close the dialog
192+
promptCts.Cancel();
193+
194+
return tokenResult;
195+
}
196+
else
197+
{
198+
ThrowIfTerminalPromptsDisabled();
114199

115-
string deviceMessage = $"To complete authentication please visit {dcr.VerificationUri} and enter the following code:" +
116-
Environment.NewLine +
117-
dcr.UserCode;
118-
Context.Terminal.WriteLine(deviceMessage);
200+
string deviceMessage = $"To complete authentication please visit {dcr.VerificationUri} and enter the following code:" +
201+
Environment.NewLine +
202+
dcr.UserCode;
203+
Context.Terminal.WriteLine(deviceMessage);
119204

120-
return await client.GetTokenByDeviceCodeAsync(dcr, CancellationToken.None);
205+
return await client.GetTokenByDeviceCodeAsync(dcr, CancellationToken.None);
206+
}
207+
}
208+
209+
private bool TryFindHelperCommand(out string command, out string args)
210+
{
211+
return TryFindHelperCommand(
212+
Constants.EnvironmentVariables.GcmUiHelper,
213+
Constants.GitConfiguration.Credential.UiHelper,
214+
Constants.DefaultUiHelper,
215+
out command,
216+
out args);
121217
}
122218
}
123219
}

0 commit comments

Comments
 (0)