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

Commit 16ed245

Browse files
committed
WIP: Check allowed login methods of enterprise instance
Unfortunately my idea of checking whether the `redirect_url` is correctly set by sending a request with the required `redirect_url` with `HttpClient` and listening for an error if it doesn't match that on the server doesn't seem to work.
1 parent 1fb69b1 commit 16ed245

File tree

12 files changed

+176
-44
lines changed

12 files changed

+176
-44
lines changed

src/GitHub.Api/LoginManager.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,10 @@ Uri GetLoginUrl(IOauthClient client, string state)
345345
request.Scopes.Add(scope);
346346
}
347347

348-
return client.GetGitHubLoginUrl(request);
348+
var uri = client.GetGitHubLoginUrl(request);
349+
350+
// OauthClient.GetGitHubLoginUrl seems to give the wrong URL. Fix this.
351+
return new Uri(uri.ToString().Replace("/api/v3", ""));
349352
}
350353
}
351354
}

src/GitHub.App/GitHub.App.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@
133133
<Compile Include="Models\IssueCommentModel.cs" />
134134
<Compile Include="Models\PullRequestReviewCommentModel.cs" />
135135
<Compile Include="Models\PullRequestDetailArgument.cs" />
136+
<Compile Include="Services\EnterpriseCapabilitiesService.cs" />
136137
<Compile Include="Services\GlobalConnection.cs" />
137138
<Compile Include="Services\OAuthCallbackListener.cs" />
138139
<Compile Include="ViewModels\Dialog\GistCreationViewModel.cs" />
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
using System;
2+
using System.ComponentModel.Composition;
3+
using System.Net.Http;
4+
using System.Threading.Tasks;
5+
using GitHub.Api;
6+
using GitHub.Factories;
7+
using GitHub.Primitives;
8+
using Octokit;
9+
10+
namespace GitHub.Services
11+
{
12+
[Export(typeof(IEnterpriseCapabilitiesService))]
13+
[PartCreationPolicy(CreationPolicy.Shared)]
14+
public class EnterpriseCapabilitiesService : IEnterpriseCapabilitiesService
15+
{
16+
readonly ISimpleApiClientFactory apiClientFactory;
17+
readonly IEnterpriseProbe probe;
18+
19+
[ImportingConstructor]
20+
public EnterpriseCapabilitiesService(
21+
ISimpleApiClientFactory apiClientFactory,
22+
IEnterpriseProbe probe)
23+
{
24+
this.apiClientFactory = apiClientFactory;
25+
this.probe = probe;
26+
}
27+
28+
public Task<EnterpriseProbeResult> Probe(Uri enterpriseBaseUrl) => probe.Probe(enterpriseBaseUrl);
29+
30+
public async Task<EnterpriseLoginMethods> ProbeLoginMethods(Uri enterpriseBaseUrl)
31+
{
32+
var client = await apiClientFactory.Create(UriString.ToUriString(enterpriseBaseUrl));
33+
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+
//}
45+
46+
return result;
47+
}
48+
49+
private Uri GetLoginUrl(IConnection connection)
50+
{
51+
var oauthClient = new OauthClient(connection);
52+
var oauthLoginRequest = new OauthLoginRequest(ApiClientConfiguration.ClientId)
53+
{
54+
RedirectUri = new Uri(OAuthCallbackListener.CallbackUrl),
55+
};
56+
var uri = oauthClient.GetGitHubLoginUrl(oauthLoginRequest);
57+
58+
// OauthClient.GetGitHubLoginUrl seems to give the wrong URL. Fix this.
59+
return new Uri(uri.ToString().Replace("/api/v3", ""));
60+
}
61+
}
62+
}

src/GitHub.App/Services/OAuthCallbackListener.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ namespace GitHub.Services
2727
public class OAuthCallbackListener : IOAuthCallbackListener
2828
{
2929
const int CallbackPort = 42549;
30-
readonly static string CallbackUrl = Invariant($"http://localhost:{CallbackPort}/");
3130
readonly IHttpListener httpListener;
3231

3332
[ImportingConstructor]
@@ -39,6 +38,8 @@ public OAuthCallbackListener(IHttpListener httpListener)
3938
httpListener.Prefixes.Add(CallbackUrl);
4039
}
4140

41+
public readonly static string CallbackUrl = Invariant($"http://localhost:{CallbackPort}/");
42+
4243
public async Task<string> Listen(string id, CancellationToken cancel)
4344
{
4445
if (httpListener.IsListening) httpListener.Stop();

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

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -21,23 +21,23 @@ namespace GitHub.ViewModels.Dialog
2121
public class LoginToGitHubForEnterpriseViewModel : LoginTabViewModel, ILoginToGitHubForEnterpriseViewModel
2222
{
2323
readonly ISimpleApiClientFactory apiClientFactory;
24-
readonly IEnterpriseProbe enterpriseProbe;
24+
readonly IEnterpriseCapabilitiesService enterpriseCapabilities;
2525

2626
[ImportingConstructor]
2727
public LoginToGitHubForEnterpriseViewModel(
2828
IConnectionManager connectionManager,
2929
ISimpleApiClientFactory apiClientFactory,
30-
IEnterpriseProbe enterpriseProbe,
30+
IEnterpriseCapabilitiesService enterpriseCapabilities,
3131
IVisualStudioBrowser browser)
3232
: base(connectionManager, browser)
3333
{
3434
Guard.ArgumentNotNull(connectionManager, nameof(connectionManager));
3535
Guard.ArgumentNotNull(apiClientFactory, nameof(apiClientFactory));
36-
Guard.ArgumentNotNull(enterpriseProbe, nameof(enterpriseProbe));
36+
Guard.ArgumentNotNull(enterpriseCapabilities, nameof(enterpriseCapabilities));
3737
Guard.ArgumentNotNull(browser, nameof(browser));
3838

3939
this.apiClientFactory = apiClientFactory;
40-
this.enterpriseProbe = enterpriseProbe;
40+
this.enterpriseCapabilities = enterpriseCapabilities;
4141

4242
EnterpriseUrlValidator = ReactivePropertyValidator.For(this, x => x.EnterpriseUrl)
4343
.IfNullOrEmpty(Resources.EnterpriseUrlValidatorEmpty)
@@ -91,11 +91,11 @@ public EnterpriseProbeStatus ProbeStatus
9191
private set { this.RaiseAndSetIfChanged(ref probeStatus, value); }
9292
}
9393

94-
bool? supportsUserNameAndPassword;
95-
public bool? SupportsUserNameAndPassword
94+
EnterpriseLoginMethods? supportedLoginMethods;
95+
public EnterpriseLoginMethods? SupportedLoginMethods
9696
{
97-
get { return supportsUserNameAndPassword; }
98-
private set { this.RaiseAndSetIfChanged(ref supportsUserNameAndPassword, value); }
97+
get { return supportedLoginMethods; }
98+
private set { this.RaiseAndSetIfChanged(ref supportedLoginMethods, value); }
9999
}
100100

101101
public ReactivePropertyValidator EnterpriseUrlValidator { get; }
@@ -119,35 +119,34 @@ async void EnterpriseUrlChanged(string url, bool valid)
119119
{
120120
// The EnterpriseUrlValidator will display an adorner in this case so don't show anything.
121121
ProbeStatus = EnterpriseProbeStatus.None;
122+
SupportedLoginMethods = null;
122123
return;
123124
}
124125

125126
var enterpriseInstance = false;
126-
var passwordAuth = (bool?)null;
127+
var loginMethods = (EnterpriseLoginMethods?)null;
127128

128129
try
129130
{
131+
var uri = new Uri(url);
130132
ProbeStatus = EnterpriseProbeStatus.Checking;
131-
SupportsUserNameAndPassword = null;
132133

133-
if (await enterpriseProbe.Probe(new Uri(url)) == EnterpriseProbeResult.Ok)
134+
if (await enterpriseCapabilities.Probe(uri) == EnterpriseProbeResult.Ok)
134135
{
135-
var client = await apiClientFactory.Create(new UriString(url));
136-
var meta = await client.GetMetadata();
137-
136+
loginMethods = await enterpriseCapabilities.ProbeLoginMethods(uri);
138137
enterpriseInstance = true;
139-
passwordAuth = meta.VerifiablePasswordAuthentication;
140138
}
141139
}
142140
catch
143141
{
144142
ProbeStatus = EnterpriseProbeStatus.Invalid;
143+
loginMethods = null;
145144
}
146145

147146
if (url == EnterpriseUrl)
148147
{
149148
ProbeStatus = enterpriseInstance ? EnterpriseProbeStatus.Valid : EnterpriseProbeStatus.Invalid;
150-
SupportsUserNameAndPassword = passwordAuth;
149+
SupportedLoginMethods = loginMethods;
151150
}
152151
}
153152
}

src/GitHub.Exports.Reactive/ViewModels/Dialog/ILoginToGitHubForEnterpriseViewModel.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Reactive;
2+
using GitHub.Services;
23
using GitHub.Validation;
34
using ReactiveUI;
45

@@ -47,10 +48,10 @@ public interface ILoginToGitHubForEnterpriseViewModel : ILoginToHostViewModel
4748
EnterpriseProbeStatus ProbeStatus { get; }
4849

4950
/// <summary>
50-
/// Gets a value indcating whether the GitHub Enterprise instance at <see cref="EnterpriseUrl"/>
51-
/// supports logging in with a username and password.
51+
/// Gets the supported login methods for the GitHub Enterprise instance at
52+
/// <see cref="EnterpriseUrl"/>.
5253
/// </summary>
53-
bool? SupportsUserNameAndPassword { get; }
54+
EnterpriseLoginMethods? SupportedLoginMethods { get; }
5455

5556
/// <summary>
5657
/// Gets the validator instance used for validating the

src/GitHub.Exports/GitHub.Exports.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@
146146
<Compile Include="Models\ICommentModel.cs" />
147147
<Compile Include="Models\IInlineCommentModel.cs" />
148148
<Compile Include="Models\IPullRequestReviewCommentModel.cs" />
149+
<Compile Include="Services\IEnterpriseCapabilitiesService.cs" />
149150
<Compile Include="Services\IGlobalConnection.cs" />
150151
<Compile Include="Services\ILocalRepositories.cs" />
151152
<Compile Include="Services\IVisualStudioBrowser.cs" />
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
using System;
2+
using System.Threading.Tasks;
3+
using Octokit;
4+
5+
namespace GitHub.Services
6+
{
7+
/// <summary>
8+
/// Describes the login methods supported by an enterprise instance.
9+
/// </summary>
10+
[Flags]
11+
public enum EnterpriseLoginMethods
12+
{
13+
TokenOnly = 0x00,
14+
UsernameAndPassword = 0x01,
15+
OAuth = 0x02,
16+
All = 0x03,
17+
}
18+
19+
/// <summary>
20+
/// Services for checking the capabilities of enterprise installations.
21+
/// </summary>
22+
public interface IEnterpriseCapabilitiesService
23+
{
24+
/// <summary>
25+
/// Makes a request to the specified URL and returns whether or not the probe could definitively determine that a GitHub
26+
/// Enterprise Instance exists at the specified URL.
27+
/// </summary>
28+
/// <remarks>
29+
/// The probe checks the absolute path /site/sha at the specified <paramref name="enterpriseBaseUrl" />.
30+
/// </remarks>
31+
/// <param name="enterpriseBaseUrl">The URL to test</param>
32+
/// <returns>An <see cref="EnterpriseProbeResult" /> with either <see cref="EnterpriseProbeResult.Ok"/>,
33+
/// <see cref="EnterpriseProbeResult.NotFound"/>, or <see cref="EnterpriseProbeResult.Failed"/> in the case the request failed</returns>
34+
Task<EnterpriseProbeResult> Probe(Uri enterpriseBaseUrl);
35+
36+
/// <summary>
37+
/// Checks the login methods supported by an enterprise instance.
38+
/// </summary>
39+
/// <param name="enterpriseBaseUrl">The URL to test.</param>
40+
/// <returns>The supported login methods.</returns>
41+
Task<EnterpriseLoginMethods> ProbeLoginMethods(Uri enterpriseBaseUrl);
42+
}
43+
}

src/GitHub.VisualStudio.UI/Resources.Designer.cs

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/GitHub.VisualStudio.UI/Resources.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,4 +398,7 @@
398398
<data name="OpenFileInSolution" xml:space="preserve">
399399
<value>Open File in Solution</value>
400400
</data>
401+
<data name="TokenPrompt" xml:space="preserve">
402+
<value>Token</value>
403+
</data>
401404
</root>

0 commit comments

Comments
 (0)