Skip to content

Commit afcb6b8

Browse files
authored
Support gist remote URLs for GitHub (git-ecosystem#1402)
We have not been consistently detecting or normalising "gist" URLs for dotcom or GHES instances. Gists are backed by a Git repository and can be cloned/pushed-to etc like a normal repository. Credentials are the same as the base site. Update our OAuth, rest API, and dotcom-detection methods that deal with the remote or target URL to correctly support gists URLs. Also add some tests around this. Fixes git-ecosystem#1401
2 parents 9902e8f + b501b8b commit afcb6b8

File tree

5 files changed

+37
-9
lines changed

5 files changed

+37
-9
lines changed

src/shared/GitHub.Tests/GitHubHostProviderTests.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,11 @@ public class GitHubHostProviderTests
1515
[InlineData("https://github.com", true)]
1616
[InlineData("https://gitHUB.CoM", true)]
1717
[InlineData("https://GITHUB.COM", true)]
18+
[InlineData("https://gist.github.com", true)]
1819
[InlineData("https://foogithub.com", false)]
1920
[InlineData("https://api.github.com", false)]
21+
[InlineData("https://api.gist.github.com", false)]
22+
[InlineData("https://foogist.github.com", false)]
2023
public void GitHubHostProvider_IsGitHubDotCom(string input, bool expected)
2124
{
2225
Assert.Equal(expected, GitHubHostProvider.IsGitHubDotCom(new Uri(input)));
@@ -98,6 +101,8 @@ public void GitHubHostProvider_GetCredentialServiceUrl(string protocol, string h
98101
[InlineData("https://GitHub.Com", "none", GitHubConstants.DotComAuthenticationModes)]
99102
[InlineData("https://github.com", null, GitHubConstants.DotComAuthenticationModes)]
100103
[InlineData("https://GitHub.Com", null, GitHubConstants.DotComAuthenticationModes)]
104+
[InlineData("https://gist.github.com", null, GitHubConstants.DotComAuthenticationModes)]
105+
[InlineData("https://GIST.GITHUB.COM", null, GitHubConstants.DotComAuthenticationModes)]
101106
public async Task GitHubHostProvider_GetSupportedAuthenticationModes(string uriString, string gitHubAuthModes, AuthenticationModes expectedModes)
102107
{
103108
var targetUri = new Uri(uriString);

src/shared/GitHub.Tests/GitHubRestApiTests.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
using System.Linq;
33
using System.Net;
44
using System.Net.Http;
5-
using System.Net.Http.Headers;
6-
using System.Text;
75
using System.Threading.Tasks;
86
using GitCredentialManager.Tests;
97
using GitCredentialManager.Tests.Objects;
@@ -13,6 +11,20 @@ namespace GitHub.Tests
1311
{
1412
public class GitHubRestApiTests
1513
{
14+
[Theory]
15+
[InlineData("https://github.com", "user", "https://api.github.com/user")]
16+
[InlineData("https://github.com", "users/123", "https://api.github.com/users/123")]
17+
[InlineData("https://gItHuB.cOm", "uSeRs/123", "https://api.github.com/uSeRs/123")]
18+
[InlineData("https://gist.github.com", "user", "https://api.github.com/user")]
19+
[InlineData("https://github.example.com", "user", "https://github.example.com/api/v3/user")]
20+
[InlineData("https://raw.github.example.com", "user", "https://github.example.com/api/v3/user")]
21+
[InlineData("https://gist.github.example.com", "user", "https://github.example.com/api/v3/user")]
22+
public void GitHubRestApi_GetApiRequestUri(string targetUrl, string apiUrl, string expected)
23+
{
24+
Uri actualUri = GitHubRestApi.GetApiRequestUri(new Uri(targetUrl), apiUrl);
25+
Assert.Equal(expected, actualUri.ToString());
26+
}
27+
1628
[Fact]
1729
public async Task GitHubRestApi_AcquireTokenAsync_NullUri_ThrowsException()
1830
{

src/shared/GitHub/GitHubHostProvider.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -487,10 +487,12 @@ public static bool IsGitHubDotCom(Uri targetUri)
487487
{
488488
EnsureArgument.AbsoluteUri(targetUri, nameof(targetUri));
489489

490-
return StringComparer.OrdinalIgnoreCase.Equals(targetUri.Host, GitHubConstants.GitHubBaseUrlHost);
490+
// github.com or gist.github.com are both considered dotcom
491+
return StringComparer.OrdinalIgnoreCase.Equals(targetUri.Host, GitHubConstants.GitHubBaseUrlHost) ||
492+
StringComparer.OrdinalIgnoreCase.Equals(targetUri.Host, GitHubConstants.GistBaseUrlHost);
491493
}
492494

493-
private static Uri NormalizeUri(Uri uri)
495+
internal static Uri NormalizeUri(Uri uri)
494496
{
495497
if (uri is null)
496498
{
@@ -500,8 +502,9 @@ private static Uri NormalizeUri(Uri uri)
500502
// Special case for gist.github.com which are git backed repositories under the hood.
501503
// Credentials for these repositories are the same as the one stored with "github.com".
502504
// Same for gist.github[.subdomain].domain.tld. The general form was already checked via IsSupported.
503-
int firstDot = uri.DnsSafeHost.IndexOf(".");
504-
if (firstDot > -1 && uri.DnsSafeHost.Substring(0, firstDot).Equals("gist", StringComparison.OrdinalIgnoreCase)) {
505+
int firstDot = uri.DnsSafeHost.IndexOf(".", StringComparison.Ordinal);
506+
if (firstDot > -1 && uri.DnsSafeHost.Substring(0, firstDot).Equals("gist", StringComparison.OrdinalIgnoreCase))
507+
{
505508
return new Uri("https://" + uri.DnsSafeHost.Substring(firstDot+1));
506509
}
507510

src/shared/GitHub/GitHubOAuth2Client.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,11 @@ public GitHubOAuth2Client(HttpClient httpClient, ISettings settings, Uri baseUri
1111
: base(httpClient, CreateEndpoints(baseUri),
1212
GetClientId(settings), trace2, GetRedirectUri(settings, baseUri), GetClientSecret(settings)) { }
1313

14-
private static OAuth2ServerEndpoints CreateEndpoints(Uri baseUri)
14+
private static OAuth2ServerEndpoints CreateEndpoints(Uri uri)
1515
{
16+
// Ensure that the base URI is normalized to support Gist subdomains
17+
Uri baseUri = GitHubHostProvider.NormalizeUri(uri);
18+
1619
Uri authEndpoint = new Uri(baseUri, GitHubConstants.OAuthAuthorizationEndpointRelativeUri);
1720
Uri tokenEndpoint = new Uri(baseUri, GitHubConstants.OAuthTokenEndpointRelativeUri);
1821
Uri deviceAuthEndpoint = new Uri(baseUri, GitHubConstants.OAuthDeviceEndpointRelativeUri);

src/shared/GitHub/GitHubRestApi.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ private async Task<AuthenticationResult> ParseSuccessResponseAsync(Uri targetUri
203203
}
204204
}
205205

206-
private Uri GetApiRequestUri(Uri targetUri, string apiUrl)
206+
internal /* for testing */ static Uri GetApiRequestUri(Uri targetUri, string apiUrl)
207207
{
208208
if (GitHubHostProvider.IsGitHubDotCom(targetUri))
209209
{
@@ -214,8 +214,13 @@ private Uri GetApiRequestUri(Uri targetUri, string apiUrl)
214214
// If we're here, it's GitHub Enterprise via a configured authority
215215
var baseUrl = targetUri.GetLeftPart(UriPartial.Authority);
216216

217+
RegexOptions reOptions = RegexOptions.CultureInvariant | RegexOptions.IgnoreCase;
218+
217219
// Check for 'raw.' in the hostname and remove it to get the correct GHE API URL
218-
baseUrl = Regex.Replace(baseUrl, @"^(https?://)raw\.", "$1", RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
220+
baseUrl = Regex.Replace(baseUrl, @"^(https?://)raw\.", "$1", reOptions);
221+
222+
// Likewise check for `gist.` in the hostname and remove it to get the correct GHE API URL
223+
baseUrl = Regex.Replace(baseUrl, @"^(https?://)gist\.", "$1", reOptions);
219224

220225
return new Uri(baseUrl + $"/api/v3/{apiUrl}");
221226
}

0 commit comments

Comments
 (0)