Skip to content

Commit

Permalink
GCM Release 2.2.2 (#1333)
Browse files Browse the repository at this point in the history
**Changes since 2.2.1:**

- Fix an issue where duplicate "Personal Access Token" GitHub account
options are shown when Visual Studio has a GitHub account signed-in
(#1325 #1328)
- Fix an issue with Azure DevOps Server (TFS) and Windows Integrated
Authentication (#1331 #1332)
- Fix an issue with OAuth redirects GitHub Enterprise Server (#1329
#1330)
- Correctly handle non-ASCII username/passwords with the WPF UI helpers
(#1287 #1326)
  • Loading branch information
mjcheetham authored Jul 12, 2023
2 parents 2f5264d + d6035ef commit 5d7e823
Show file tree
Hide file tree
Showing 12 changed files with 118 additions and 21 deletions.
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2.2.1.0
2.2.2.0
8 changes: 4 additions & 4 deletions docs/rename.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ project in its own [organization][gcm-org].
![Git Credential Manager Core renamed](img/gcmcore-rename.png)

At the time, the actual exectuable name was not updated and continued to be
`git-credential-manager-core`. As of [VERSION][rename-ver], the executable has
`git-credential-manager-core`. As of [2.0.877][rename-ver], the executable has
been renamed to `git-credential-manager`, matching the new project name.

## Rename transition
Expand All @@ -21,11 +21,11 @@ warning: git-credential-manager-core was renamed to git-credential-manager
warning: see https://aka.ms/gcm/rename for more information
```

Since the executable was renamed in VERSION, GCM has also included symlinks
Since the executable was renamed in 2.0.877, GCM has also included symlinks
using the old name in order to ensure no one's setups would immediately break.

These links will remain until _two_ major Git versions are released after GCM
VERSION, _**at which point the symlinks will no longer be included**_.
2.0.877, _**at which point the symlinks will no longer be included**_.

It is recommended to update your Git configuration to use the new executable
name as soon as possible to prevent any issues in the future.
Expand Down Expand Up @@ -159,7 +159,7 @@ or `manager` respectively.
[rename-pr]: https://github.com/git-ecosystem/git-credential-manager/pull/541
[rename-blog]: https://github.blog/2022-04-07-git-credential-manager-authentication-for-everyone/#universal-git-authentication
[gcm-org]: https://github.com/git-ecosystem
[rename-ver]: https://github.com/git-ecosystem/git-credential-manager/releases
[rename-ver]: https://github.com/git-ecosystem/git-credential-manager/releases/tag/v2.0.877
[git-windows]: https://git-scm.com/download/win
[gcm-latest]: https://aka.ms/gcm/latest
[warnings]: #rename-transition
Expand Down
2 changes: 1 addition & 1 deletion docs/wsl.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ After updating the `WSLENV` environment variable, restart your WSL installation.
If you have installed GCM using the user-only installer (i.e, the `gcmuser-*.exe`
installer and not the system-wide/admin required installer), you need to modify
the above instructions to point to
`/mnt/c/Users/<USERNAME>/AppData/Local/Programs/Git\ Credential\ Manager\ Core/git-credential-manager.exe`
`/mnt/c/Users/<USERNAME>/AppData/Local/Programs/Git\ Credential\ Manager/git-credential-manager.exe`
instead.

## How it works
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,70 @@ public void WindowsCredentialManager_IsMatch(
Assert.Equal(expected, actual);
}

[PlatformFact(Platforms.Windows)]
public void WindowsCredentialManager_IsMatch_NoNamespace_NotMatched()
{
var win32Cred = new Win32Credential
{
UserName = "test",
TargetName = $"{WindowsCredentialManager.TargetNameLegacyGenericPrefix}https://example.com"
};

var credManager = new WindowsCredentialManager(TestNamespace);

bool result = credManager.IsMatch("https://example.com", null, win32Cred);

Assert.False(result);
}

[PlatformFact(Platforms.Windows)]
public void WindowsCredentialManager_IsMatch_DifferentNamespace_NotMatched()
{
var win32Cred = new Win32Credential
{
UserName = "test",
TargetName = $"{WindowsCredentialManager.TargetNameLegacyGenericPrefix}:random-namespace:https://example.com"
};

var credManager = new WindowsCredentialManager(TestNamespace);

bool result = credManager.IsMatch("https://example.com", null, win32Cred);

Assert.False(result);
}

[PlatformFact(Platforms.Windows)]
public void WindowsCredentialManager_IsMatch_CaseSensitiveNamespace_NotMatched()
{
var win32Cred = new Win32Credential
{
UserName = "test",
TargetName = $"{WindowsCredentialManager.TargetNameLegacyGenericPrefix}:nAmEsPaCe:https://example.com"
};

var credManager = new WindowsCredentialManager("namespace");

bool result = credManager.IsMatch("https://example.com", null, win32Cred);

Assert.False(result);
}

[PlatformFact(Platforms.Windows)]
public void WindowsCredentialManager_IsMatch_NoNamespaceInQuery_IsMatched()
{
var win32Cred = new Win32Credential
{
UserName = "test",
TargetName = $"{WindowsCredentialManager.TargetNameLegacyGenericPrefix}https://example.com"
};

var credManager = new WindowsCredentialManager();

bool result = credManager.IsMatch("https://example.com", null, win32Cred);

Assert.True(result);
}

[PlatformTheory(Platforms.Windows)]
[InlineData("https://example.com", null, "https://example.com")]
[InlineData("https://example.com", "bob", "https://[email protected]")]
Expand Down
13 changes: 13 additions & 0 deletions src/shared/Core.Tests/StreamExtensionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,19 @@ public void StreamExtensions_ReadMultiDictionary_CaseInsensitive_ReturnsDictiona
AssertMultiDictionary(new[] { "2" }, "a", output);
}

[Fact]
public void StreamExtensions_ReadMultiDictionary_EmptyString_ReturnsKeyWithEmptyStringValue()
{
string input = "a=\n\n";

var output = ReadStringStream(input, StreamExtensions.ReadMultiDictionary);

Assert.NotNull(output);
Assert.Equal(1, output.Count);

AssertMultiDictionary(new[] { String.Empty, }, "a", output);
}

[Fact]
public void StreamExtensions_ReadMultiDictionary_Spaces_ReturnsCorrectKeysAndValues()
{
Expand Down
3 changes: 2 additions & 1 deletion src/shared/Core/Authentication/AuthenticationBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ protected internal virtual async Task<IDictionary<string, string>> InvokeHelperA
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = false, // Do not redirect stderr as tracing might be enabled
UseShellExecute = false
UseShellExecute = false,
StandardOutputEncoding = EncodingEx.UTF8NoBom,
};

Context.Trace.WriteLine($"Starting helper process: {path} {args}");
Expand Down
8 changes: 8 additions & 0 deletions src/shared/Core/EncodingEx.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using System.Text;

namespace GitCredentialManager;

public static class EncodingEx
{
public static readonly Encoding UTF8NoBom = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false);
}
12 changes: 10 additions & 2 deletions src/shared/Core/Interop/Windows/WindowsCredentialManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -274,11 +274,19 @@ private WindowsCredential CreateCredentialFromStructure(Win32Credential credenti
return false;
}

// Trim the "LegacyGeneric" prefix Windows adds and any namespace we have been filtered with
// Trim the "LegacyGeneric" prefix Windows adds
string targetName = credential.TargetName.TrimUntilIndexOf(TargetNameLegacyGenericPrefix);

// Only match credentials with the namespace we have been configured with (if any)
if (!string.IsNullOrWhiteSpace(_namespace))
{
targetName = targetName.TrimUntilIndexOf($"{_namespace}:");
string nsPrefix = $"{_namespace}:";
if (!targetName.StartsWith(nsPrefix, StringComparison.Ordinal))
{
return false;
}

targetName = targetName.Substring(nsPrefix.Length);
}

// If the target name matches the service name exactly then return 'match'
Expand Down
8 changes: 3 additions & 5 deletions src/shared/Core/StandardStreams.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@ public class StandardStreams : IStandardStreams
{
private const string LineFeed = "\n";

private static readonly Encoding Utf8NoBomEncoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false);

private TextReader _stdIn;
private TextWriter _stdOut;
private TextWriter _stdErr;
Expand All @@ -41,7 +39,7 @@ public TextReader In
{
if (_stdIn == null)
{
_stdIn = new StreamReader(Console.OpenStandardInput(), Utf8NoBomEncoding);
_stdIn = new StreamReader(Console.OpenStandardInput(), EncodingEx.UTF8NoBom);
}

return _stdIn;
Expand All @@ -54,7 +52,7 @@ public TextWriter Out
{
if (_stdOut == null)
{
_stdOut = new StreamWriter(Console.OpenStandardOutput(), Utf8NoBomEncoding)
_stdOut = new StreamWriter(Console.OpenStandardOutput(), EncodingEx.UTF8NoBom)
{
AutoFlush = true,
NewLine = LineFeed,
Expand All @@ -71,7 +69,7 @@ public TextWriter Error
{
if (_stdErr == null)
{
_stdErr = new StreamWriter(Console.OpenStandardError(), Utf8NoBomEncoding)
_stdErr = new StreamWriter(Console.OpenStandardError(), EncodingEx.UTF8NoBom)
{
AutoFlush = true,
NewLine = LineFeed,
Expand Down
9 changes: 5 additions & 4 deletions src/shared/Core/StreamExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -248,14 +248,15 @@ private static void ParseMultiLine(IDictionary<string, IList<string>> dict, stri
// Only allow one value for non-multi/array entries ("key=value")
// and reset the array of a multi-entry if the value is empty ("key[]=<empty>")
bool emptyValue = string.IsNullOrEmpty(value);

if (!multi || emptyValue)
{
list.Clear();
}

if (emptyValue)
{
return;
}
if (multi && emptyValue)
{
return;
}

list.Add(value);
Expand Down
1 change: 1 addition & 0 deletions src/shared/GitHub/GitHubConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public static class GitHubConstants
// [SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification="OAuth2 public client application 'secrets' are required and permitted to be public")]
public const string OAuthClientSecret = "18867509d956965542b521a529a79bb883344c90";
public static readonly Uri OAuthRedirectUri = new Uri("http://127.0.0.1/"); // Note that the trailing slash is important!
public static readonly Uri OAuthLegacyRedirectUri = new Uri("http://localhost/"); // Note that the trailing slash is important!
public static readonly Uri OAuthAuthorizationEndpointRelativeUri = new Uri("/login/oauth/authorize", UriKind.Relative);
public static readonly Uri OAuthTokenEndpointRelativeUri = new Uri("/login/oauth/access_token", UriKind.Relative);
public static readonly Uri OAuthDeviceEndpointRelativeUri = new Uri("/login/device/code", UriKind.Relative);
Expand Down
9 changes: 6 additions & 3 deletions src/shared/GitHub/GitHubOAuth2Client.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public class GitHubOAuth2Client : OAuth2Client
{
public GitHubOAuth2Client(HttpClient httpClient, ISettings settings, Uri baseUri, ITrace2 trace2)
: base(httpClient, CreateEndpoints(baseUri),
GetClientId(settings), trace2, GetRedirectUri(settings), GetClientSecret(settings)) { }
GetClientId(settings), trace2, GetRedirectUri(settings, baseUri), GetClientSecret(settings)) { }

private static OAuth2ServerEndpoints CreateEndpoints(Uri baseUri)
{
Expand Down Expand Up @@ -37,7 +37,7 @@ private static string GetClientId(ISettings settings)
return GitHubConstants.OAuthClientId;
}

private static Uri GetRedirectUri(ISettings settings)
private static Uri GetRedirectUri(ISettings settings, Uri targetUri)
{
// Check for developer override value
if (settings.TryGetSetting(
Expand All @@ -48,7 +48,10 @@ private static Uri GetRedirectUri(ISettings settings)
return redirectUri;
}

return GitHubConstants.OAuthRedirectUri;
// Only GitHub.com supports the new OAuth redirect URI today
return GitHubHostProvider.IsGitHubDotCom(targetUri)
? GitHubConstants.OAuthRedirectUri
: GitHubConstants.OAuthLegacyRedirectUri;
}

private static string GetClientSecret(ISettings settings)
Expand Down

0 comments on commit 5d7e823

Please sign in to comment.