Skip to content
This repository was archived by the owner on Jun 21, 2023. It is now read-only.

Commit 9f98221

Browse files
committed
Allow signing into enterprise with token.
Also added some unit tests for enterprise login view model.
1 parent 6f4f34a commit 9f98221

File tree

13 files changed

+358
-29
lines changed

13 files changed

+358
-29
lines changed

src/GitHub.Api/ILoginManager.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,17 @@ Task<User> LoginViaOAuth(
4646
Action<Uri> openBrowser,
4747
CancellationToken cancel);
4848

49+
/// <summary>
50+
/// Attempts to log into a GitHub server with a token.
51+
/// </summary>
52+
/// <param name="hostAddress">The address of the server.</param>
53+
/// <param name="client">An octokit client configured to access the server.</param>
54+
/// <param name="token">The token.</param>
55+
Task<User> LoginWithToken(
56+
HostAddress hostAddress,
57+
IGitHubClient client,
58+
string token);
59+
4960
/// <summary>
5061
/// Attempts to log into a GitHub server using existing credentials.
5162
/// </summary>

src/GitHub.Api/LoginManager.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,31 @@ public async Task<User> LoginViaOAuth(
151151
return user;
152152
}
153153

154+
/// <inheritdoc/>
155+
public async Task<User> LoginWithToken(
156+
HostAddress hostAddress,
157+
IGitHubClient client,
158+
string token)
159+
{
160+
Guard.ArgumentNotNull(hostAddress, nameof(hostAddress));
161+
Guard.ArgumentNotNull(client, nameof(client));
162+
Guard.ArgumentNotEmptyString(token, nameof(token));
163+
164+
await keychain.Save("[token]", token, hostAddress).ConfigureAwait(false);
165+
166+
try
167+
{
168+
var user = await ReadUserWithRetry(client);
169+
await keychain.Save(user.Login, token, hostAddress).ConfigureAwait(false);
170+
return user;
171+
}
172+
catch
173+
{
174+
await keychain.Delete(hostAddress);
175+
throw;
176+
}
177+
}
178+
154179
/// <inheritdoc/>
155180
public Task<User> LoginFromCache(HostAddress hostAddress, IGitHubClient client)
156181
{

src/GitHub.App/Services/EnterpriseCapabilitiesService.cs

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
using System.Net.Http;
44
using System.Threading.Tasks;
55
using GitHub.Api;
6-
using GitHub.Factories;
76
using GitHub.Primitives;
87
using Octokit;
98

@@ -29,19 +28,11 @@ public EnterpriseCapabilitiesService(
2928

3029
public async Task<EnterpriseLoginMethods> ProbeLoginMethods(Uri enterpriseBaseUrl)
3130
{
31+
var result = EnterpriseLoginMethods.Token;
3232
var client = await apiClientFactory.Create(UriString.ToUriString(enterpriseBaseUrl));
3333
var meta = await client.GetMetadata();
34-
var result = meta.VerifiablePasswordAuthentication ?
35-
EnterpriseLoginMethods.UsernameAndPassword :
36-
EnterpriseLoginMethods.TokenOnly;
37-
var httpClient = new HttpClient();
38-
var loginUrl = GetLoginUrl(client.Client.Connection);
39-
var response = await httpClient.GetAsync(loginUrl);
40-
41-
//if (response.IsSuccessStatusCode)
42-
//{
43-
// result |= EnterpriseLoginMethods.OAuth;
44-
//}
34+
35+
if (meta.VerifiablePasswordAuthentication) result |= EnterpriseLoginMethods.UsernameAndPassword;
4536

4637
return result;
4738
}

src/GitHub.App/ViewModels/Dialog/LoginTabViewModel.cs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,13 @@ protected LoginTabViewModel(
4949

5050
Login = ReactiveCommand.CreateAsyncTask(this.WhenAny(x => x.CanLogin, x => x.Value), LogIn);
5151
Login.ThrownExceptions.Subscribe(HandleError);
52+
isLoggingIn = Login.IsExecuting.ToProperty(this, x => x.IsLoggingIn);
5253

5354
LoginViaOAuth = ReactiveCommand.CreateAsyncTask(
5455
this.WhenAnyValue(x => x.IsLoggingIn, x => !x),
5556
LogInViaOAuth);
5657
LoginViaOAuth.ThrownExceptions.Subscribe(HandleError);
5758

58-
isLoggingIn = Login.IsExecuting.ToProperty(this, x => x.IsLoggingIn);
59-
6059
Reset = ReactiveCommand.CreateAsyncTask(_ => Clear());
6160

6261
NavigateForgotPassword = new RecoveryCommand(Resources.ForgotPasswordLink, _ =>
@@ -83,8 +82,7 @@ protected LoginTabViewModel(
8382
string usernameOrEmail;
8483
public string UsernameOrEmail
8584
{
86-
get
87-
{ return usernameOrEmail; }
85+
get { return usernameOrEmail; }
8886
set { this.RaiseAndSetIfChanged(ref usernameOrEmail, value); }
8987
}
9088

@@ -98,8 +96,7 @@ public ReactivePropertyValidator UsernameOrEmailValidator
9896
string password;
9997
public string Password
10098
{
101-
get
102-
{ return password; }
99+
get { return password; }
103100
set { this.RaiseAndSetIfChanged(ref password, value); }
104101
}
105102

src/GitHub.App/ViewModels/Dialog/LoginToGitHubForEnterpriseViewModel.cs

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.ComponentModel.Composition;
33
using System.Reactive;
4+
using System.Reactive.Concurrency;
45
using System.Reactive.Linq;
56
using System.Threading.Tasks;
67
using GitHub.Api;
@@ -29,6 +30,16 @@ public LoginToGitHubForEnterpriseViewModel(
2930
ISimpleApiClientFactory apiClientFactory,
3031
IEnterpriseCapabilitiesService enterpriseCapabilities,
3132
IVisualStudioBrowser browser)
33+
: this(connectionManager, apiClientFactory, enterpriseCapabilities, browser, Scheduler.Default)
34+
{
35+
}
36+
37+
public LoginToGitHubForEnterpriseViewModel(
38+
IConnectionManager connectionManager,
39+
ISimpleApiClientFactory apiClientFactory,
40+
IEnterpriseCapabilitiesService enterpriseCapabilities,
41+
IVisualStudioBrowser browser,
42+
IScheduler scheduler)
3243
: base(connectionManager, browser)
3344
{
3445
Guard.ArgumentNotNull(connectionManager, nameof(connectionManager));
@@ -44,19 +55,19 @@ public LoginToGitHubForEnterpriseViewModel(
4455
.IfNotUri(Resources.EnterpriseUrlValidatorInvalid)
4556
.IfGitHubDotComHost(Resources.EnterpriseUrlValidatorNotAGitHubHost);
4657

47-
canLogin = this.WhenAny(
58+
canLogin = this.WhenAnyValue(
4859
x => x.UsernameOrEmailValidator.ValidationResult.IsValid,
4960
x => x.PasswordValidator.ValidationResult.IsValid,
50-
x => x.EnterpriseUrlValidator.ValidationResult.IsValid,
51-
(x, y, z) => x.Value && y.Value && z.Value)
61+
x => x.SupportedLoginMethods,
62+
(x, y, z) => (x || (z & EnterpriseLoginMethods.Token) != 0) && y)
5263
.ToProperty(this, x => x.CanLogin);
5364

5465
canSsoLogin = this.WhenAnyValue(
5566
x => x.EnterpriseUrlValidator.ValidationResult.IsValid)
5667
.ToProperty(this, x => x.CanLogin);
5768

5869
this.WhenAnyValue(x => x.EnterpriseUrl, x => x.EnterpriseUrlValidator.ValidationResult)
59-
.Throttle(TimeSpan.FromMilliseconds(500))
70+
.Throttle(TimeSpan.FromMilliseconds(500), scheduler)
6071
.ObserveOn(RxApp.MainThreadScheduler)
6172
.Subscribe(x => EnterpriseUrlChanged(x.Item1, x.Item2?.IsValid ?? false));
6273

@@ -69,7 +80,14 @@ public LoginToGitHubForEnterpriseViewModel(
6980

7081
protected override Task<IConnection> LogIn(object args)
7182
{
72-
return LogInToHost(HostAddress.Create(EnterpriseUrl));
83+
if (string.IsNullOrWhiteSpace(UsernameOrEmail))
84+
{
85+
return LogInWithToken(HostAddress.Create(EnterpriseUrl), Password);
86+
}
87+
else
88+
{
89+
return LogInToHost(HostAddress.Create(EnterpriseUrl));
90+
}
7391
}
7492

7593
protected override Task<IConnection> LogInViaOAuth(object args)
@@ -145,9 +163,27 @@ async void EnterpriseUrlChanged(string url, bool valid)
145163

146164
if (url == EnterpriseUrl)
147165
{
166+
if ((loginMethods & EnterpriseLoginMethods.Token) != 0 &&
167+
(loginMethods & EnterpriseLoginMethods.UsernameAndPassword) != 0)
168+
{
169+
loginMethods &= ~EnterpriseLoginMethods.Token;
170+
}
171+
148172
ProbeStatus = enterpriseInstance ? EnterpriseProbeStatus.Valid : EnterpriseProbeStatus.Invalid;
149173
SupportedLoginMethods = loginMethods;
150174
}
151175
}
176+
177+
async Task<IConnection> LogInWithToken(HostAddress hostAddress, string token)
178+
{
179+
Guard.ArgumentNotNull(hostAddress, nameof(hostAddress));
180+
181+
if (await ConnectionManager.GetConnection(hostAddress) != null)
182+
{
183+
await ConnectionManager.LogOut(hostAddress);
184+
}
185+
186+
return await ConnectionManager.LogInWithToken(hostAddress, token);
187+
}
152188
}
153189
}

src/GitHub.App/ViewModels/Dialog/LoginViewModel.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public LoginViewModel(
2323
twoFactorHandler.SetViewModel(twoFactor);
2424

2525
Content = credentials;
26-
Done = credentials.Done;
26+
Done = credentials.Done;
2727

2828
twoFactor.WhenAnyValue(x => x.TwoFactorType)
2929
.Subscribe(x =>

src/GitHub.Exports/Services/IConnectionManager.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,21 @@ public interface IConnectionManager
7171
/// </exception>
7272
Task<IConnection> LogInViaOAuth(HostAddress address, CancellationToken cancel);
7373

74+
/// <summary>
75+
/// Attempts to login to a GitHub instance with a token.
76+
/// </summary>
77+
/// <param name="address">The instance address.</param>
78+
/// <param name="token">The token.</param>
79+
/// <returns>
80+
/// A connection if the login succeded. If the login fails, throws an exception. An
81+
/// exception is also thrown if an existing connection with the same host address already
82+
/// exists.
83+
/// </returns>
84+
/// <exception cref="InvalidOperationException">
85+
/// A connection to the host already exists.
86+
/// </exception>
87+
Task<IConnection> LogInWithToken(HostAddress address, string token);
88+
7489
/// <summary>
7590
/// Logs out of a GitHub instance.
7691
/// </summary>

src/GitHub.Exports/Services/IEnterpriseCapabilitiesService.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,9 @@ namespace GitHub.Services
1010
[Flags]
1111
public enum EnterpriseLoginMethods
1212
{
13-
TokenOnly = 0x00,
14-
UsernameAndPassword = 0x01,
15-
OAuth = 0x02,
16-
All = 0x03,
13+
Token = 0x01,
14+
UsernameAndPassword = 0x02,
15+
OAuth = 0x04,
1716
}
1817

1918
/// <summary>

src/GitHub.VisualStudio/Services/ConnectionManager.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,26 @@ public async Task<IConnection> LogInViaOAuth(HostAddress address, CancellationTo
109109
return connection;
110110
}
111111

112+
/// <inheritdoc/>
113+
public async Task<IConnection> LogInWithToken(HostAddress address, string token)
114+
{
115+
var conns = await GetLoadedConnectionsInternal();
116+
117+
if (conns.Any(x => x.HostAddress == address))
118+
{
119+
throw new InvalidOperationException($"A connection to {address.Title} already exists.");
120+
}
121+
122+
var client = CreateClient(address);
123+
var user = await loginManager.LoginWithToken(address, client, token);
124+
var connection = new Connection(address, user.Login, user, null);
125+
126+
conns.Add(connection);
127+
await SaveConnections();
128+
await usageTracker.IncrementCounter(x => x.NumberOfLogins);
129+
return connection;
130+
}
131+
112132
/// <inheritdoc/>
113133
public async Task LogOut(HostAddress address)
114134
{

src/GitHub.VisualStudio/Services/LoginManagerDispatcher.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,5 +47,10 @@ public Task Logout(HostAddress hostAddress, IGitHubClient client)
4747
{
4848
return inner.Logout(hostAddress, client);
4949
}
50+
51+
public Task<User> LoginWithToken(HostAddress hostAddress, IGitHubClient client, string token)
52+
{
53+
return inner.LoginWithToken(hostAddress, client, token);
54+
}
5055
}
5156
}

0 commit comments

Comments
 (0)