From 2c3aaac7521b21ba17d954d55b28d9b7fd028c61 Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Wed, 10 Mar 2021 14:40:15 +0000 Subject: [PATCH 01/34] msauth: tell MSAL about any parent window handles MSAL supports parenting windows it creates to another window. GCM also supports being told about a parent window by the caller. Pass through this parent window to MSAL if we have it! --- .../Authentication/MicrosoftAuthentication.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/shared/Microsoft.Git.CredentialManager/Authentication/MicrosoftAuthentication.cs b/src/shared/Microsoft.Git.CredentialManager/Authentication/MicrosoftAuthentication.cs index 4435a506e..9d7582d77 100644 --- a/src/shared/Microsoft.Git.CredentialManager/Authentication/MicrosoftAuthentication.cs +++ b/src/shared/Microsoft.Git.CredentialManager/Authentication/MicrosoftAuthentication.cs @@ -197,6 +197,14 @@ private async Task CreatePublicClientApplicationAsync( appBuilder.WithLogging(OnMsalLogMessage, LogLevel.Verbose, enablePiiLogging, false); } + // If we have a parent window ID we should tell MSAL about it so it can parent any authentication dialogs + // correctly. We only support this on Windows right now as MSAL only supports embedded/dialogs on Windows. + if (PlatformUtils.IsWindows() && !string.IsNullOrWhiteSpace(Context.Settings.ParentWindowId) && + int.TryParse(Context.Settings.ParentWindowId, out int hWndInt) && hWndInt > 0) + { + appBuilder.WithParentActivityOrWindow(() => new IntPtr(hWndInt)); + } + IPublicClientApplication app = appBuilder.Build(); // Register the application token cache From d42b74595d7303c58495e37c4f35f9136c8f27dd Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Thu, 11 Mar 2021 11:10:52 +0000 Subject: [PATCH 02/34] app: ensure we locate helpers correctly with single file Ensure that we are using the correct entry executable directory when published as a single file application. When looking for UI helpers we were still using the Assembly::Location property, which is null for single file applications. Move to pass the (correctly computed) application path to the ICommandContext, which is available everywhere. --- src/shared/Git-Credential-Manager/Program.cs | 4 +- .../ApplicationTests.cs | 52 +++++++++---------- .../Application.cs | 18 +++---- .../Authentication/AuthenticationBase.cs | 2 +- .../CommandContext.cs | 12 ++++- .../Objects/TestCommandContext.cs | 7 +++ 6 files changed, 54 insertions(+), 41 deletions(-) diff --git a/src/shared/Git-Credential-Manager/Program.cs b/src/shared/Git-Credential-Manager/Program.cs index 1dc953202..a9befbbce 100644 --- a/src/shared/Git-Credential-Manager/Program.cs +++ b/src/shared/Git-Credential-Manager/Program.cs @@ -15,8 +15,8 @@ public static class Program public static void Main(string[] args) { string appPath = GetApplicationPath(); - using (var context = new CommandContext()) - using (var app = new Application(context, appPath)) + using (var context = new CommandContext(appPath)) + using (var app = new Application(context)) { // Register all supported host providers at the normal priority. // The generic provider should never win against a more specific one, so register it with low priority. diff --git a/src/shared/Microsoft.Git.CredentialManager.Tests/ApplicationTests.cs b/src/shared/Microsoft.Git.CredentialManager.Tests/ApplicationTests.cs index 3d8b7bbd3..797c98193 100644 --- a/src/shared/Microsoft.Git.CredentialManager.Tests/ApplicationTests.cs +++ b/src/shared/Microsoft.Git.CredentialManager.Tests/ApplicationTests.cs @@ -16,8 +16,8 @@ public async Task Application_ConfigureAsync_NoHelpers_AddsEmptyAndGcm() const string executablePath = "/usr/local/share/gcm-core/git-credential-manager-core"; string key = $"{Constants.GitConfiguration.Credential.SectionName}.{Constants.GitConfiguration.Credential.Helper}"; - var context = new TestCommandContext(); - IConfigurableComponent application = new Application(context, executablePath); + var context = new TestCommandContext {AppPath = executablePath}; + IConfigurableComponent application = new Application(context); await application.ConfigureAsync(ConfigurationTarget.User); Assert.Single(context.Git.Configuration.Global); @@ -34,8 +34,8 @@ public async Task Application_ConfigureAsync_Gcm_AddsEmptyBeforeGcm() const string executablePath = "/usr/local/share/gcm-core/git-credential-manager-core"; string key = $"{Constants.GitConfiguration.Credential.SectionName}.{Constants.GitConfiguration.Credential.Helper}"; - var context = new TestCommandContext(); - IConfigurableComponent application = new Application(context, executablePath); + var context = new TestCommandContext {AppPath = executablePath}; + IConfigurableComponent application = new Application(context); context.Git.Configuration.Global[key] = new List {executablePath}; @@ -55,8 +55,8 @@ public async Task Application_ConfigureAsync_EmptyAndGcm_DoesNothing() const string executablePath = "/usr/local/share/gcm-core/git-credential-manager-core"; string key = $"{Constants.GitConfiguration.Credential.SectionName}.{Constants.GitConfiguration.Credential.Helper}"; - var context = new TestCommandContext(); - IConfigurableComponent application = new Application(context, executablePath); + var context = new TestCommandContext {AppPath = executablePath}; + IConfigurableComponent application = new Application(context); context.Git.Configuration.Global[key] = new List { @@ -80,8 +80,8 @@ public async Task Application_ConfigureAsync_EmptyAndGcmWithOthersBefore_DoesNot const string executablePath = "/usr/local/share/gcm-core/git-credential-manager-core"; string key = $"{Constants.GitConfiguration.Credential.SectionName}.{Constants.GitConfiguration.Credential.Helper}"; - var context = new TestCommandContext(); - IConfigurableComponent application = new Application(context, executablePath); + var context = new TestCommandContext {AppPath = executablePath}; + IConfigurableComponent application = new Application(context); context.Git.Configuration.Global[key] = new List { @@ -106,8 +106,8 @@ public async Task Application_ConfigureAsync_EmptyAndGcmWithOthersAfter_DoesNoth const string executablePath = "/usr/local/share/gcm-core/git-credential-manager-core"; string key = $"{Constants.GitConfiguration.Credential.SectionName}.{Constants.GitConfiguration.Credential.Helper}"; - var context = new TestCommandContext(); - IConfigurableComponent application = new Application(context, executablePath); + var context = new TestCommandContext {AppPath = executablePath}; + IConfigurableComponent application = new Application(context); context.Git.Configuration.Global[key] = new List { @@ -133,8 +133,8 @@ public async Task Application_ConfigureAsync_EmptyAndGcmWithOthersBeforeAndAfter const string executablePath = "/usr/local/share/gcm-core/git-credential-manager-core"; string key = $"{Constants.GitConfiguration.Credential.SectionName}.{Constants.GitConfiguration.Credential.Helper}"; - var context = new TestCommandContext(); - IConfigurableComponent application = new Application(context, executablePath); + var context = new TestCommandContext {AppPath = executablePath}; + IConfigurableComponent application = new Application(context); context.Git.Configuration.Global[key] = new List { @@ -160,8 +160,8 @@ public async Task Application_ConfigureAsync_EmptyAndGcmWithEmptyAfter_RemovesEx const string executablePath = "/usr/local/share/gcm-core/git-credential-manager-core"; string key = $"{Constants.GitConfiguration.Credential.SectionName}.{Constants.GitConfiguration.Credential.Helper}"; - var context = new TestCommandContext(); - IConfigurableComponent application = new Application(context, executablePath); + var context = new TestCommandContext {AppPath = executablePath}; + IConfigurableComponent application = new Application(context); context.Git.Configuration.Global[key] = new List { @@ -186,8 +186,8 @@ public async Task Application_UnconfigureAsync_NoHelpers_DoesNothing() const string executablePath = "/usr/local/share/gcm-core/git-credential-manager-core"; string key = $"{Constants.GitConfiguration.Credential.SectionName}.{Constants.GitConfiguration.Credential.Helper}"; - var context = new TestCommandContext(); - IConfigurableComponent application = new Application(context, executablePath); + var context = new TestCommandContext {AppPath = executablePath}; + IConfigurableComponent application = new Application(context); await application.UnconfigureAsync(ConfigurationTarget.User); Assert.Empty(context.Git.Configuration.Global); @@ -199,8 +199,8 @@ public async Task Application_UnconfigureAsync_Gcm_RemovesGcm() const string executablePath = "/usr/local/share/gcm-core/git-credential-manager-core"; string key = $"{Constants.GitConfiguration.Credential.SectionName}.{Constants.GitConfiguration.Credential.Helper}"; - var context = new TestCommandContext(); - IConfigurableComponent application = new Application(context, executablePath); + var context = new TestCommandContext {AppPath = executablePath}; + IConfigurableComponent application = new Application(context); context.Git.Configuration.Global[key] = new List {executablePath}; @@ -216,8 +216,8 @@ public async Task Application_UnconfigureAsync_EmptyAndGcm_RemovesEmptyAndGcm() const string executablePath = "/usr/local/share/gcm-core/git-credential-manager-core"; string key = $"{Constants.GitConfiguration.Credential.SectionName}.{Constants.GitConfiguration.Credential.Helper}"; - var context = new TestCommandContext(); - IConfigurableComponent application = new Application(context, executablePath); + var context = new TestCommandContext {AppPath = executablePath}; + IConfigurableComponent application = new Application(context); context.Git.Configuration.Global[key] = new List {emptyHelper, executablePath}; @@ -234,8 +234,8 @@ public async Task Application_UnconfigureAsync_EmptyAndGcmWithOthersBefore_Remov const string executablePath = "/usr/local/share/gcm-core/git-credential-manager-core"; string key = $"{Constants.GitConfiguration.Credential.SectionName}.{Constants.GitConfiguration.Credential.Helper}"; - var context = new TestCommandContext(); - IConfigurableComponent application = new Application(context, executablePath); + var context = new TestCommandContext {AppPath = executablePath}; + IConfigurableComponent application = new Application(context); context.Git.Configuration.Global[key] = new List { @@ -258,8 +258,8 @@ public async Task Application_UnconfigureAsync_EmptyAndGcmWithOthersAfterBefore_ const string executablePath = "/usr/local/share/gcm-core/git-credential-manager-core"; string key = $"{Constants.GitConfiguration.Credential.SectionName}.{Constants.GitConfiguration.Credential.Helper}"; - var context = new TestCommandContext(); - IConfigurableComponent application = new Application(context, executablePath); + var context = new TestCommandContext {AppPath = executablePath}; + IConfigurableComponent application = new Application(context); context.Git.Configuration.Global[key] = new List { @@ -284,8 +284,8 @@ public async Task Application_UnconfigureAsync_EmptyAndGcmWithOthersBeforeAndAft const string executablePath = "/usr/local/share/gcm-core/git-credential-manager-core"; string key = $"{Constants.GitConfiguration.Credential.SectionName}.{Constants.GitConfiguration.Credential.Helper}"; - var context = new TestCommandContext(); - IConfigurableComponent application = new Application(context, executablePath); + var context = new TestCommandContext {AppPath = executablePath}; + IConfigurableComponent application = new Application(context); context.Git.Configuration.Global[key] = new List { diff --git a/src/shared/Microsoft.Git.CredentialManager/Application.cs b/src/shared/Microsoft.Git.CredentialManager/Application.cs index fbbefe61a..76863010f 100644 --- a/src/shared/Microsoft.Git.CredentialManager/Application.cs +++ b/src/shared/Microsoft.Git.CredentialManager/Application.cs @@ -17,27 +17,23 @@ namespace Microsoft.Git.CredentialManager { public class Application : ApplicationBase, IConfigurableComponent { - private readonly string _appPath; private readonly IHostProviderRegistry _providerRegistry; private readonly IConfigurationService _configurationService; private readonly IList _providerCommands = new List(); - public Application(ICommandContext context, string appPath) - : this(context, new HostProviderRegistry(context), new ConfigurationService(context), appPath) + public Application(ICommandContext context) + : this(context, new HostProviderRegistry(context), new ConfigurationService(context)) { } internal Application(ICommandContext context, IHostProviderRegistry providerRegistry, - IConfigurationService configurationService, - string appPath) + IConfigurationService configurationService) : base(context) { EnsureArgument.NotNull(providerRegistry, nameof(providerRegistry)); EnsureArgument.NotNull(configurationService, nameof(configurationService)); - EnsureArgument.NotNullOrWhiteSpace(appPath, nameof(appPath)); - _appPath = appPath; _providerRegistry = providerRegistry; _configurationService = configurationService; @@ -84,7 +80,7 @@ protected override async Task RunInternalAsync(string[] args) Context.Trace.WriteLine($"Version: {Constants.GcmVersion}"); Context.Trace.WriteLine($"Runtime: {info.ClrVersion}"); Context.Trace.WriteLine($"Platform: {info.OperatingSystemType} ({info.CpuArchitecture})"); - Context.Trace.WriteLine($"AppPath: {_appPath}"); + Context.Trace.WriteLine($"AppPath: {Context.ApplicationPath}"); Context.Trace.WriteLine($"Arguments: {string.Join(" ", args)}"); var parser = new CommandLineBuilder(rootCommand) @@ -243,18 +239,18 @@ private string GetGitConfigAppName() { const string gitCredentialPrefix = "git-credential-"; - string appName = Path.GetFileNameWithoutExtension(_appPath); + string appName = Path.GetFileNameWithoutExtension(Context.ApplicationPath); if (appName != null && appName.StartsWith(gitCredentialPrefix, StringComparison.OrdinalIgnoreCase)) { return appName.Substring(gitCredentialPrefix.Length); } - return _appPath; + return Context.ApplicationPath; } private string GetGitConfigAppPath() { - string path = _appPath; + string path = Context.ApplicationPath; // On Windows we must use UNIX style path separators if (PlatformUtils.IsWindows()) diff --git a/src/shared/Microsoft.Git.CredentialManager/Authentication/AuthenticationBase.cs b/src/shared/Microsoft.Git.CredentialManager/Authentication/AuthenticationBase.cs index 0106d0bfb..6fcf4178a 100644 --- a/src/shared/Microsoft.Git.CredentialManager/Authentication/AuthenticationBase.cs +++ b/src/shared/Microsoft.Git.CredentialManager/Authentication/AuthenticationBase.cs @@ -121,7 +121,7 @@ protected bool TryFindHelperExecutablePath(string envar, string configName, stri } else { - string executableDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + string executableDirectory = Path.GetDirectoryName(Context.ApplicationPath); path = Path.Combine(executableDirectory!, helperName); } diff --git a/src/shared/Microsoft.Git.CredentialManager/CommandContext.cs b/src/shared/Microsoft.Git.CredentialManager/CommandContext.cs index 981ba5ddb..7ec5900f6 100644 --- a/src/shared/Microsoft.Git.CredentialManager/CommandContext.cs +++ b/src/shared/Microsoft.Git.CredentialManager/CommandContext.cs @@ -14,6 +14,11 @@ namespace Microsoft.Git.CredentialManager /// public interface ICommandContext : IDisposable { + /// + /// Absolute path the application entry executable. + /// + string ApplicationPath { get; } + /// /// Settings and configuration for Git Credential Manager. /// @@ -75,8 +80,11 @@ public interface ICommandContext : IDisposable /// public class CommandContext : DisposableObject, ICommandContext { - public CommandContext() + public CommandContext(string appPath) { + EnsureArgument.NotNullOrWhiteSpace(appPath, nameof (appPath)); + + ApplicationPath = appPath; Streams = new StandardStreams(); Trace = new Trace(); @@ -165,6 +173,8 @@ private static string GetGitPath(IEnvironment environment, IFileSystem fileSyste #region ICommandContext + public string ApplicationPath { get; } + public ISettings Settings { get; } public IStandardStreams Streams { get; } diff --git a/src/shared/TestInfrastructure/Objects/TestCommandContext.cs b/src/shared/TestInfrastructure/Objects/TestCommandContext.cs index 6947e70ea..b42aba9d7 100644 --- a/src/shared/TestInfrastructure/Objects/TestCommandContext.cs +++ b/src/shared/TestInfrastructure/Objects/TestCommandContext.cs @@ -12,6 +12,10 @@ public class TestCommandContext : ICommandContext { public TestCommandContext() { + AppPath = PlatformUtils.IsWindows() + ? @"C:\Program Files\Git Credential Manager Core\git-credential-manager-core.exe" + : "/usr/local/bin/git-credential-manager-core"; + Streams = new TestStandardStreams(); Terminal = new TestTerminal(); SessionManager = new TestSessionManager(); @@ -26,6 +30,7 @@ public TestCommandContext() Settings = new TestSettings {Environment = Environment, GitConfiguration = Git.Configuration}; } + public string AppPath { get; set; } public TestSettings Settings { get; set; } public TestStandardStreams Streams { get; set; } public TestTerminal Terminal { get; set; } @@ -40,6 +45,8 @@ public TestCommandContext() #region ICommandContext + string ICommandContext.ApplicationPath => AppPath; + IStandardStreams ICommandContext.Streams => Streams; ISettings ICommandContext.Settings => Settings; From 81360176f8835c64ecd789a843497b1bfc60af12 Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Tue, 16 Mar 2021 17:32:50 +0000 Subject: [PATCH 03/34] docs: add link to git config/envars to switch cred types Add a link to the Azure Repos Users and Tokens document to the relevant config docs. --- docs/azrepos-users-and-tokens.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/azrepos-users-and-tokens.md b/docs/azrepos-users-and-tokens.md index 7caf63156..379e67929 100644 --- a/docs/azrepos-users-and-tokens.md +++ b/docs/azrepos-users-and-tokens.md @@ -7,6 +7,11 @@ The Azure Repos host provider supports creating multiple types of credential: - Azure DevOps personal access tokens - Microsoft identity OAuth tokens (experimental) +To select which type of credential the Azure Repos host provider will create +and use, you can set the [`credential.azreposCredentialType`](https://github.com/microsoft/Git-Credential-Manager-Core/blob/master/docs/configuration.md#credentialazreposcredentialtype-experimental) +configuration entry (or [`GCM_AZREPOS_CREDENTIALTYPE`](https://github.com/microsoft/Git-Credential-Manager-Core/blob/master/docs/environment.md#GCM_AZREPOS_CREDENTIALTYPE-experimental) +environment variable). + ### Azure DevOps personal access tokens Historically, the only option supported by the Azure Repos host provider was From 2e207e975b645c6abdd133a92c25192c9a4df6bb Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Wed, 10 Mar 2021 15:36:53 +0000 Subject: [PATCH 04/34] osx: run postinstall configure cmd as current user In the macOS installer postinstall script we run the `configure` command to get GCM to configure the current user's credential helper as GCM. However, the postinstall script is often run as root (because that's how `installer` works out of the box), meaning although GCM will be writing to the ~/.gitconfig file, it will be doing so from a process running as root. To avoid having root take ownership of ~/.gitconfig we run `sudo -u` to run the `configure` command as the real user (not root). --- src/osx/Installer.Mac/scripts/postinstall | 5 +++-- src/osx/Installer.Mac/uninstall.sh | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/osx/Installer.Mac/scripts/postinstall b/src/osx/Installer.Mac/scripts/postinstall index 86922a7a5..cb73b07db 100755 --- a/src/osx/Installer.Mac/scripts/postinstall +++ b/src/osx/Installer.Mac/scripts/postinstall @@ -30,7 +30,8 @@ fi mkdir -p /usr/local/bin /bin/ln -Fs "$INSTALL_DESTINATION/git-credential-manager-core" /usr/local/bin/git-credential-manager-core -# Configure GCM for the current user -"$INSTALL_DESTINATION/git-credential-manager-core" configure +# Configure GCM for the current user (running as the current user to avoid root +# from taking ownership of ~/.gitconfig) +sudo -u ${USER} "$INSTALL_DESTINATION/git-credential-manager-core" configure exit 0 diff --git a/src/osx/Installer.Mac/uninstall.sh b/src/osx/Installer.Mac/uninstall.sh index 2657046d0..989ed9956 100755 --- a/src/osx/Installer.Mac/uninstall.sh +++ b/src/osx/Installer.Mac/uninstall.sh @@ -10,9 +10,9 @@ then exit $? fi -# Unconfigure +# Unconfigure (as the current user) echo "Unconfiguring credential helper..." -"$GCMBIN" unconfigure +sudo -u `/usr/bin/logname` "$GCMBIN" unconfigure # Remove symlink if [ -L /usr/local/bin/git-credential-manager-core ] From 5dda6a61fbaa9cdcd988ba8746c2fc11ebb25aea Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Wed, 24 Mar 2021 09:15:59 +0000 Subject: [PATCH 05/34] git: drop config --show-scope option usage The Git config `--show-scope` option was only introduced in Git from version 2.26 onwards. The latest version of Git available in some distributions of Linux (or macOS) is often older. We only needed to know the scope of configuration values in one particular call site: reading all Azure Repos user bindinds. Replace the single `IGitConfiguration::Enumerate` call with two calls to `Enumerate`, one for the global scope, and one for the local one. Drop the --show-scope option parsing. --- .../AzureReposBindingManager.cs | 44 ++++++++++--------- .../GitConfiguration.cs | 38 +--------------- .../GitConfigurationEntry.cs | 4 +- .../Objects/TestGitConfiguration.cs | 2 +- 4 files changed, 27 insertions(+), 61 deletions(-) diff --git a/src/shared/Microsoft.AzureRepos/AzureReposBindingManager.cs b/src/shared/Microsoft.AzureRepos/AzureReposBindingManager.cs index 4219eb11d..bd43065ec 100644 --- a/src/shared/Microsoft.AzureRepos/AzureReposBindingManager.cs +++ b/src/shared/Microsoft.AzureRepos/AzureReposBindingManager.cs @@ -174,31 +174,33 @@ public IEnumerable GetBindings(string orgName = null) string orgPrefix = $"{AzureDevOpsConstants.UrnOrgPrefix}/"; - config.Enumerate( - Constants.GitConfiguration.Credential.SectionName, - Constants.GitConfiguration.Credential.UserName, - entry => + bool ExtractUserBinding(GitConfigurationEntry entry, IDictionary dict) + { + if (GitConfigurationKeyComparer.TrySplit(entry.Key, out _, out string scope, out _) && + Uri.TryCreate(scope, UriKind.Absolute, out Uri uri) && + uri.Scheme == AzureDevOpsConstants.UrnScheme && uri.AbsolutePath.StartsWith(orgPrefix)) { - if (GitConfigurationKeyComparer.TrySplit(entry.Key, out _, out string scope, out _) && - Uri.TryCreate(scope, UriKind.Absolute, out Uri uri) && - uri.Scheme == AzureDevOpsConstants.UrnScheme && uri.AbsolutePath.StartsWith(orgPrefix)) + string entryOrgName = uri.AbsolutePath.Substring(orgPrefix.Length); + if (orgName is null || StringComparer.OrdinalIgnoreCase.Equals(entryOrgName, orgName)) { - string entryOrgName = uri.AbsolutePath.Substring(orgPrefix.Length); - if (orgName is null || StringComparer.OrdinalIgnoreCase.Equals(entryOrgName, orgName)) - { - if (entry.Level == GitConfigurationLevel.Local) - { - localUsers[entryOrgName] = entry.Value; - } - else - { - globalUsers[entryOrgName] = entry.Value; - } - } + dict[entryOrgName] = entry.Value; } + } + + return true; + } - return true; - }); + config.Enumerate( + GitConfigurationLevel.Local, + Constants.GitConfiguration.Credential.SectionName, + Constants.GitConfiguration.Credential.UserName, + entry => ExtractUserBinding(entry, localUsers)); + + config.Enumerate( + GitConfigurationLevel.Global, + Constants.GitConfiguration.Credential.SectionName, + Constants.GitConfiguration.Credential.UserName, + entry => ExtractUserBinding(entry, globalUsers)); foreach (string org in globalUsers.Keys.Union(localUsers.Keys)) { diff --git a/src/shared/Microsoft.Git.CredentialManager/GitConfiguration.cs b/src/shared/Microsoft.Git.CredentialManager/GitConfiguration.cs index 3fef4f9fe..a73c8bfa1 100644 --- a/src/shared/Microsoft.Git.CredentialManager/GitConfiguration.cs +++ b/src/shared/Microsoft.Git.CredentialManager/GitConfiguration.cs @@ -117,7 +117,7 @@ internal GitProcessConfiguration(ITrace trace, GitProcess git) public void Enumerate(GitConfigurationLevel level, GitConfigurationEnumerationCallback cb) { string levelArg = GetLevelFilterArg(level); - using (Process git = _git.CreateProcess($"config --null {levelArg} --list --show-scope")) + using (Process git = _git.CreateProcess($"config --null {levelArg} --list")) { git.Start(); // To avoid deadlocks, always read the output stream first and then wait @@ -134,31 +134,14 @@ public void Enumerate(GitConfigurationLevel level, GitConfigurationEnumerationCa throw GitProcess.CreateGitException(git, "Failed to enumerate all Git configuration entries"); } - var scope = new StringBuilder(); var name = new StringBuilder(); var value = new StringBuilder(); int i = 0; while (i < data.Length) { - scope.Clear(); name.Clear(); value.Clear(); - // Read config scope (null terminated) - while (i < data.Length && data[i] != '\0') - { - scope.Append(data[i++]); - } - - if (i >= data.Length) - { - _trace.WriteLine("Invalid Git configuration output. Expected null terminator (\\0) after scope."); - break; - } - - // Skip the null terminator - i++; - // Read key name (LF terminated) while (i < data.Length && data[i] != '\n') { @@ -189,24 +172,7 @@ public void Enumerate(GitConfigurationLevel level, GitConfigurationEnumerationCa // Skip the null terminator i++; - GitConfigurationLevel entryLevel; - switch (scope.ToString()) - { - case "system": - entryLevel = GitConfigurationLevel.System; - break; - case "global": - entryLevel = GitConfigurationLevel.Global; - break; - case "local": - entryLevel = GitConfigurationLevel.Local; - break; - default: - entryLevel = GitConfigurationLevel.Unknown; - break; - } - - var entry = new GitConfigurationEntry(entryLevel, name.ToString(), value.ToString()); + var entry = new GitConfigurationEntry(name.ToString(), value.ToString()); if (!cb(entry)) { diff --git a/src/shared/Microsoft.Git.CredentialManager/GitConfigurationEntry.cs b/src/shared/Microsoft.Git.CredentialManager/GitConfigurationEntry.cs index 0dddf70df..e57c855e4 100644 --- a/src/shared/Microsoft.Git.CredentialManager/GitConfigurationEntry.cs +++ b/src/shared/Microsoft.Git.CredentialManager/GitConfigurationEntry.cs @@ -4,14 +4,12 @@ namespace Microsoft.Git.CredentialManager { public class GitConfigurationEntry { - public GitConfigurationEntry(GitConfigurationLevel level, string key, string value) + public GitConfigurationEntry(string key, string value) { - Level = level; Key = key; Value = value; } - public GitConfigurationLevel Level { get; } public string Key { get; } public string Value { get; } } diff --git a/src/shared/TestInfrastructure/Objects/TestGitConfiguration.cs b/src/shared/TestInfrastructure/Objects/TestGitConfiguration.cs index af9dee1d5..c9a15a6ce 100644 --- a/src/shared/TestInfrastructure/Objects/TestGitConfiguration.cs +++ b/src/shared/TestInfrastructure/Objects/TestGitConfiguration.cs @@ -26,7 +26,7 @@ public void Enumerate(GitConfigurationLevel level, GitConfigurationEnumerationCa { foreach (var value in kvp.Value) { - var entry = new GitConfigurationEntry(dictLevel, kvp.Key, value); + var entry = new GitConfigurationEntry(kvp.Key, value); if (!cb(entry)) { break; From 97b95c1fd556617546560d02925a5e226a1490a2 Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Tue, 30 Mar 2021 16:44:51 +0100 Subject: [PATCH 06/34] Create experimental issue template --- .github/ISSUE_TEMPLATE/experimental.md | 65 ++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/experimental.md diff --git a/.github/ISSUE_TEMPLATE/experimental.md b/.github/ISSUE_TEMPLATE/experimental.md new file mode 100644 index 000000000..c07154154 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/experimental.md @@ -0,0 +1,65 @@ +--- +name: Experimental feature issues +about: A problem or issue occurred when using an experimental feature. +title: '' +labels: 'experimental' +assignees: '' +--- + +**Which version of GCM Core are you using?** + +From a terminal, run `git-credential-manager-core version` and paste the output. + + + +**Which Git host provider are you trying to connect to?** + +* [ ] Azure DevOps +* [ ] Azure DevOps Server (TFS/on-prem) +* [ ] GitHub +* [ ] GitHub Enterprise +* [ ] Bitbucket +* [ ] Other - please describe + +**Can you access the remote repository directly in the browser using the remote URL?** + +From a terminal, run `git remote -v` to see your remote URL. + + + +* [ ] Yes +* [ ] No, I get a permission error +* [ ] No, for a different reason - please describe + +--- + +**_[Azure DevOps only]_ What format is your remote URL?** + +* [ ] Not applicable +* [ ] https://dev.azure.com/`{org}`/... +* [ ] https://`{org}`@dev.azure.com/`{org}`/... +* [ ] https://`{org}`.visualstudio.com/... + +**_[Azure DevOps only]_ If the account picker shows more than one identity as you authenticate, check that you selected the same one that has access on the web.** + +* [ ] Not applicable +* [ ] I only see one identity +* [ ] I checked each identity and none worked + +--- + +**Expected behavior** + +I am authenticated and my Git operation completes successfully. + +**Actual behavior** + +A clear and concise description of what happens. For example: exception is thrown, UI freezes, etc. + +**Logs** + +Set the environment variables `GCM_TRACE=1` and `GIT_TRACE=1` and re-run your Git command. Review and redact any private information and attach the log. From c151bbbf45ecbcd770a706c93b30d2146b8cd041 Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Tue, 30 Mar 2021 16:47:27 +0100 Subject: [PATCH 07/34] Use correct label on auth-problem issue template --- .github/ISSUE_TEMPLATE/auth-problem.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/auth-problem.md b/.github/ISSUE_TEMPLATE/auth-problem.md index 99346df26..8973dc52b 100644 --- a/.github/ISSUE_TEMPLATE/auth-problem.md +++ b/.github/ISSUE_TEMPLATE/auth-problem.md @@ -2,7 +2,7 @@ name: Authentication failure about: An authentication problem occurred when running a Git command. title: '' -labels: 'auth-failure' +labels: 'auth-issue' assignees: '' --- From 40c3b29d931912d824a394923e27f6015233f45c Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Tue, 30 Mar 2021 17:20:03 +0100 Subject: [PATCH 08/34] Add .NET SDK download link to dev docs --- docs/development.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/development.md b/docs/development.md index a30ed148c..330237bf4 100644 --- a/docs/development.md +++ b/docs/development.md @@ -6,6 +6,8 @@ Start by cloning this repository: git clone https://github.com/microsoft/Git-Credential-Manager-Core ``` +You also need the latest version of the .NET SDK which can be downloaded and installed from [here](https://dotnet.microsoft.com/). + ## Building The `Git-Credential-Manager.sln` solution can be opened and built in Visual Studio, Visual Studio for Mac, Visual Studio Code, or JetBrains Rider. From d0fb2ba3d9a8a011918b02b5a17753a9c12d488b Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Tue, 30 Mar 2021 17:26:45 +0100 Subject: [PATCH 09/34] Update Windows build instructions --- docs/development.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/development.md b/docs/development.md index 330237bf4..20c19551b 100644 --- a/docs/development.md +++ b/docs/development.md @@ -33,8 +33,7 @@ To build from inside an IDE, make sure to select the `WindowsDebug` or `WindowsR To build from the command line, run: ```powershell -msbuild /t:restore /p:Configuration=WindowsDebug -msbuild /p:Configuration=WindowsDebug +dotnet build -c WindowsDebug ``` You can find a copy of the installer .exe file in `out\windows\Installer.Windows\bin\Debug\net472`. From c81bd22191c2c31915cf7af4dbd25397d46c25f8 Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Mon, 1 Feb 2021 13:54:56 +0000 Subject: [PATCH 10/34] msauth: add support for Windows broker (WAM) Add support for broker-assisted authentication on Windows using "WAM" (Web Authentication Manager) as provided by the MSAL.Desktop library. The GCM_MSAUTH_USEBROKER environment variable or the credential.msauthUseBroker configuration option will control if WAM is enabled or not. By default WAM _is_ enabled. --- docs/configuration.md | 25 +++ docs/environment.md | 31 ++++ .../Authentication/MicrosoftAuthentication.cs | 150 +++++++++++++----- .../Constants.cs | 2 + .../Microsoft.Git.CredentialManager.csproj | 5 +- .../PlatformUtils.cs | 48 ++++++ src/windows/Installer.Windows/Setup.iss | 5 + 7 files changed, 220 insertions(+), 46 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 4362b747b..450eab05c 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -258,6 +258,10 @@ Specify which authentication flow should be used when performing Microsoft authe Defaults to the value `auto`. +**Note:** If [`credential.msauthUseBroker`](#credentialmsauthusebroker) is set +to `true` and the operating system authentication broker is available, all flows +will be delegated to the broker; this setting has no effect. + Value|Authentication Flow -|- `auto` _(default)_|Select the best option depending on the current environment and platform. @@ -275,6 +279,27 @@ git config --global credential.msauthFlow devicecode --- +### credential.msauthUseBroker + +Use the operating system account manager where available. + +Defaults to the value `true`. + +Value|Description +-|- +`true` _(default)_|Use the operating system account manager as an authentication broker. +`false`|Do not use the broker. + +#### Example + +```shell +git config --global credential.msauthUseBroker false +``` + +**Also see: [GCM_MSAUTH_USEBROKER](environment.md#GCM_MSAUTH_USEBROKER)** + +--- + ### credential.useHttpPath Tells Git to pass the entire repository URL, rather than just the hostname, when calling out to a credential provider. (This setting [comes from Git itself](https://git-scm.com/docs/gitcredentials/#Documentation/gitcredentials.txt-useHttpPath), not GCM Core.) diff --git a/docs/environment.md b/docs/environment.md index 5b953a860..fe313041e 100644 --- a/docs/environment.md +++ b/docs/environment.md @@ -404,6 +404,10 @@ Specify which authentication flow should be used when performing Microsoft authe Defaults to the value `auto`. +**Note:** If [`GCM_MSAUTH_USEBROKER`](#gcm_msauth_usebroker) is set to `true` +and the operating system authentication broker is available, all flows will be +delegated to the broker; this setting has no effect. + Value|Authentication Flow -|- `auto` _(default)_|Select the best option depending on the current environment and platform. @@ -427,6 +431,33 @@ export GCM_MSAUTH_FLOW="devicecode" --- +### GCM_MSAUTH_USEBROKER + +Use the operating system account manager where available. + +Defaults to the value `true`. + +Value|Description +-|- +`true` _(default)_|Use the operating system account manager as an authentication broker. +`false`|Do not use the broker. + +##### Windows + +```batch +SET GCM_MSAUTH_USEBROKER="false" +``` + +##### macOS/Linux + +```bash +export GCM_MSAUTH_USEBROKER="false" +``` + +**Also see: [credential.msauthUseBroker](configuration.md#credentialmsauthusebroker)** + +--- + ### GCM_AZREPOS_CREDENTIALTYPE _(experimental)_ Specify the type of credential the Azure Repos host provider should return. diff --git a/src/shared/Microsoft.Git.CredentialManager/Authentication/MicrosoftAuthentication.cs b/src/shared/Microsoft.Git.CredentialManager/Authentication/MicrosoftAuthentication.cs index aa47872cb..2527736d4 100644 --- a/src/shared/Microsoft.Git.CredentialManager/Authentication/MicrosoftAuthentication.cs +++ b/src/shared/Microsoft.Git.CredentialManager/Authentication/MicrosoftAuthentication.cs @@ -9,6 +9,10 @@ using Microsoft.Identity.Client; using Microsoft.Identity.Client.Extensions.Msal; +#if NETFRAMEWORK +using Microsoft.Identity.Client.Desktop; +#endif + namespace Microsoft.Git.CredentialManager.Authentication { public interface IMicrosoftAuthentication @@ -28,7 +32,7 @@ public enum MicrosoftAuthenticationFlowType Auto = 0, EmbeddedWebView = 1, SystemWebView = 2, - DeviceCode = 3 + DeviceCode = 3, } public class MicrosoftAuthentication : AuthenticationBase, IMicrosoftAuthentication @@ -48,7 +52,15 @@ public MicrosoftAuthentication(ICommandContext context) public async Task GetTokenAsync( string authority, string clientId, Uri redirectUri, string[] scopes, string userName) { - IPublicClientApplication app = await CreatePublicClientApplicationAsync(authority, clientId, redirectUri); + // Check if we can and should use OS broker authentication + bool useBroker = CanUseBroker(); + if (useBroker) + { + Context.Trace.WriteLine("OS broker is available and enabled."); + } + + // Create the public client application for authentication + IPublicClientApplication app = await CreatePublicClientApplicationAsync(authority, clientId, redirectUri, useBroker); AuthenticationResult result = null; @@ -65,6 +77,9 @@ public MicrosoftAuthentication(ICommandContext context) // If the user has expressed a preference in how the want to perform the interactive authentication flows then we respect that. // Otherwise, depending on the current platform and session type we try to show the most appropriate authentication interface: // + // On Windows 10 & .NET Framework, MSAL supports the Web Account Manager (WAM) broker - we try to use that if possible + // in the first instance. + // // On .NET Framework MSAL supports the WinForms based 'embedded' webview UI. For Windows + .NET Framework this is the // best and natural experience. // @@ -82,48 +97,61 @@ public MicrosoftAuthentication(ICommandContext context) // If the user has disabled interaction all we can do is fail at this point ThrowIfUserInteractionDisabled(); - // Check for a user flow preference - MicrosoftAuthenticationFlowType flowType = GetFlowType(); - switch (flowType) + // If we're using the OS broker then delegate everything to that + if (useBroker) { - case MicrosoftAuthenticationFlowType.Auto: - if (CanUseEmbeddedWebView()) - goto case MicrosoftAuthenticationFlowType.EmbeddedWebView; - - if (CanUseSystemWebView(app, redirectUri)) - goto case MicrosoftAuthenticationFlowType.SystemWebView; - - // Fall back to device code flow - goto case MicrosoftAuthenticationFlowType.DeviceCode; - - case MicrosoftAuthenticationFlowType.EmbeddedWebView: - Context.Trace.WriteLine("Performing interactive auth with embedded web view..."); - EnsureCanUseEmbeddedWebView(); - result = await app.AcquireTokenInteractive(scopes) - .WithPrompt(Prompt.SelectAccount) - .WithUseEmbeddedWebView(true) - .ExecuteAsync(); - break; - - case MicrosoftAuthenticationFlowType.SystemWebView: - Context.Trace.WriteLine("Performing interactive auth with system web view..."); - EnsureCanUseSystemWebView(app, redirectUri); - result = await app.AcquireTokenInteractive(scopes) - .WithPrompt(Prompt.SelectAccount) - .WithSystemWebViewOptions(GetSystemWebViewOptions()) - .ExecuteAsync(); - break; - - case MicrosoftAuthenticationFlowType.DeviceCode: - Context.Trace.WriteLine("Performing interactive auth with device code..."); - // We don't have a way to display a device code without a terminal at the moment - // TODO: introduce a small GUI window to show a code if no TTY exists - ThrowIfTerminalPromptsDisabled(); - result = await app.AcquireTokenWithDeviceCode(scopes, ShowDeviceCodeInTty).ExecuteAsync(); - break; - - default: - goto case MicrosoftAuthenticationFlowType.Auto; + Context.Trace.WriteLine("Performing interactive auth with broker..."); + result = await app.AcquireTokenInteractive(scopes) + .WithPrompt(Prompt.SelectAccount) + // We must configure the system webview as a fallback + .WithSystemWebViewOptions(GetSystemWebViewOptions()) + .ExecuteAsync(); + } + else + { + // Check for a user flow preference if they've specified one + MicrosoftAuthenticationFlowType flowType = GetFlowType(); + switch (flowType) + { + case MicrosoftAuthenticationFlowType.Auto: + if (CanUseEmbeddedWebView()) + goto case MicrosoftAuthenticationFlowType.EmbeddedWebView; + + if (CanUseSystemWebView(app, redirectUri)) + goto case MicrosoftAuthenticationFlowType.SystemWebView; + + // Fall back to device code flow + goto case MicrosoftAuthenticationFlowType.DeviceCode; + + case MicrosoftAuthenticationFlowType.EmbeddedWebView: + Context.Trace.WriteLine("Performing interactive auth with embedded web view..."); + EnsureCanUseEmbeddedWebView(); + result = await app.AcquireTokenInteractive(scopes) + .WithPrompt(Prompt.SelectAccount) + .WithUseEmbeddedWebView(true) + .ExecuteAsync(); + break; + + case MicrosoftAuthenticationFlowType.SystemWebView: + Context.Trace.WriteLine("Performing interactive auth with system web view..."); + EnsureCanUseSystemWebView(app, redirectUri); + result = await app.AcquireTokenInteractive(scopes) + .WithPrompt(Prompt.SelectAccount) + .WithSystemWebViewOptions(GetSystemWebViewOptions()) + .ExecuteAsync(); + break; + + case MicrosoftAuthenticationFlowType.DeviceCode: + Context.Trace.WriteLine("Performing interactive auth with device code..."); + // We don't have a way to display a device code without a terminal at the moment + // TODO: introduce a small GUI window to show a code if no TTY exists + ThrowIfTerminalPromptsDisabled(); + result = await app.AcquireTokenWithDeviceCode(scopes, ShowDeviceCodeInTty).ExecuteAsync(); + break; + + default: + goto case MicrosoftAuthenticationFlowType.Auto; + } } } @@ -179,7 +207,8 @@ private async Task GetAccessTokenSilentlyAsync(IPublicClie } } - private async Task CreatePublicClientApplicationAsync(string authority, string clientId, Uri redirectUri) + private async Task CreatePublicClientApplicationAsync( + string authority, string clientId, Uri redirectUri, bool enableBroker) { var httpFactoryAdaptor = new MsalHttpClientFactoryAdaptor(Context.HttpClientFactory); @@ -205,6 +234,15 @@ private async Task CreatePublicClientApplicationAsync( appBuilder.WithParentActivityOrWindow(() => new IntPtr(hWndInt)); } + // On Windows 10 & .NET Framework try and use the WAM broker + if (enableBroker && PlatformUtils.IsWindows10()) + { +#if NETFRAMEWORK + appBuilder.WithExperimentalFeatures(); + appBuilder.WithWindowsBroker(); +#endif + } + IPublicClientApplication app = appBuilder.Build(); // Register the application token cache @@ -360,6 +398,30 @@ public HttpClient GetHttpClient() #region Auth flow capability detection + private bool CanUseBroker() + { + // We only support the broker on Windows 10 and .NET Framework +#if NETFRAMEWORK + if (Context.SessionManager.IsDesktopSession && PlatformUtils.IsWindows10()) + { + // Default to using the OS broker + const bool defaultValue = true; + + if (Context.Settings.TryGetSetting(Constants.EnvironmentVariables.MsAuthUseBroker, + Constants.GitConfiguration.Credential.SectionName, + Constants.GitConfiguration.Credential.MsAuthUseBroker, + out string valueStr)) + { + return valueStr.ToBooleanyOrDefault(defaultValue); + } + + return defaultValue; + } +#endif + + return false; + } + private bool CanUseEmbeddedWebView() { // If we're in an interactive session and on .NET Framework then MSAL can show the WinForms-based embedded UI diff --git a/src/shared/Microsoft.Git.CredentialManager/Constants.cs b/src/shared/Microsoft.Git.CredentialManager/Constants.cs index e6ae24610..4d694aa79 100644 --- a/src/shared/Microsoft.Git.CredentialManager/Constants.cs +++ b/src/shared/Microsoft.Git.CredentialManager/Constants.cs @@ -52,6 +52,7 @@ public static class EnvironmentVariables public const string GcmInteractive = "GCM_INTERACTIVE"; public const string GcmParentWindow = "GCM_MODAL_PARENTHWND"; public const string MsAuthFlow = "GCM_MSAUTH_FLOW"; + public const string MsAuthUseBroker = "GCM_MSAUTH_USEBROKER"; public const string GcmCredNamespace = "GCM_NAMESPACE"; public const string GcmCredentialStore = "GCM_CREDENTIAL_STORE"; public const string GcmCredCacheOptions = "GCM_CREDENTIAL_CACHE_OPTIONS"; @@ -83,6 +84,7 @@ public static class Credential public const string UseHttpPath = "useHttpPath"; public const string Interactive = "interactive"; public const string MsAuthFlow = "msauthFlow"; + public const string MsAuthUseBroker = "msauthUseBroker"; public const string CredNamespace = "namespace"; public const string CredentialStore = "credentialStore"; public const string CredCacheOptions = "cacheOptions"; diff --git a/src/shared/Microsoft.Git.CredentialManager/Microsoft.Git.CredentialManager.csproj b/src/shared/Microsoft.Git.CredentialManager/Microsoft.Git.CredentialManager.csproj index c2e97ab38..663c318d5 100644 --- a/src/shared/Microsoft.Git.CredentialManager/Microsoft.Git.CredentialManager.csproj +++ b/src/shared/Microsoft.Git.CredentialManager/Microsoft.Git.CredentialManager.csproj @@ -13,12 +13,13 @@ + - - + + diff --git a/src/shared/Microsoft.Git.CredentialManager/PlatformUtils.cs b/src/shared/Microsoft.Git.CredentialManager/PlatformUtils.cs index 8c6e32760..d5ffea918 100644 --- a/src/shared/Microsoft.Git.CredentialManager/PlatformUtils.cs +++ b/src/shared/Microsoft.Git.CredentialManager/PlatformUtils.cs @@ -20,6 +20,26 @@ public static PlatformInformation GetPlatformInformation() return new PlatformInformation(osType, cpuArch, clrVersion); } + public static bool IsWindows10() + { + if (!IsWindows()) + { + return false; + } + + // Implementation of version checking was taken from: + // https://github.com/dotnet/runtime/blob/6578f257e3be2e2144a65769706e981961f0130c/src/libraries/System.Private.CoreLib/src/System/Environment.Windows.cs#L110-L122 + // + // Note that we cannot use Environment.OSVersion in .NET Framework (or Core versions less than 5.0) as + // The implementation in those versions "lies" about Windows versions > 8.1 if there is no application manifest. + if (RtlGetVersionEx(out RTL_OSVERSIONINFOEX osvi) != 0) + { + return false; + } + + return (int) osvi.dwMajorVersion == 10; + } + /// /// Check if the current Operating System is macOS. /// @@ -169,6 +189,34 @@ private static string GetClrVersion() } #endregion + + #region Windows Native Version APIs + + // Interop code sourced from the .NET Runtime as of version 5.0: + // https://github.com/dotnet/runtime/blob/6578f257e3be2e2144a65769706e981961f0130c/src/libraries/Common/src/Interop/Windows/NtDll/Interop.RtlGetVersion.cs + + [DllImport("ntdll.dll", ExactSpelling = true)] + private static extern int RtlGetVersion(ref RTL_OSVERSIONINFOEX lpVersionInformation); + + private static unsafe int RtlGetVersionEx(out RTL_OSVERSIONINFOEX osvi) + { + osvi = default; + osvi.dwOSVersionInfoSize = (uint)sizeof(RTL_OSVERSIONINFOEX); + return RtlGetVersion(ref osvi); + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + private unsafe struct RTL_OSVERSIONINFOEX + { + internal uint dwOSVersionInfoSize; + internal uint dwMajorVersion; + internal uint dwMinorVersion; + internal uint dwBuildNumber; + internal uint dwPlatformId; + internal fixed char szCSDVersion[128]; + } + + #endregion } public struct PlatformInformation diff --git a/src/windows/Installer.Windows/Setup.iss b/src/windows/Installer.Windows/Setup.iss index 36484e0df..a7c96ea96 100644 --- a/src/windows/Installer.Windows/Setup.iss +++ b/src/windows/Installer.Windows/Setup.iss @@ -112,14 +112,19 @@ Source: "{#PayloadDir}\GitHub.UI.exe.config"; DestDir: Source: "{#PayloadDir}\Microsoft.AzureRepos.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "{#PayloadDir}\Microsoft.Git.CredentialManager.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "{#PayloadDir}\Microsoft.Git.CredentialManager.UI.dll"; DestDir: "{app}"; Flags: ignoreversion +Source: "{#PayloadDir}\Microsoft.Identity.Client.Desktop.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "{#PayloadDir}\Microsoft.Identity.Client.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "{#PayloadDir}\Microsoft.Identity.Client.Extensions.Msal.dll"; DestDir: "{app}"; Flags: ignoreversion +Source: "{#PayloadDir}\Microsoft.Web.WebView2.Core.dll"; DestDir: "{app}"; Flags: ignoreversion +Source: "{#PayloadDir}\Microsoft.Web.WebView2.WinForms.dll"; DestDir: "{app}"; Flags: ignoreversion +Source: "{#PayloadDir}\Microsoft.Web.WebView2.Wpf.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "{#PayloadDir}\Newtonsoft.Json.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "{#PayloadDir}\System.Buffers.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "{#PayloadDir}\System.CommandLine.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "{#PayloadDir}\System.Memory.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "{#PayloadDir}\System.Numerics.Vectors.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "{#PayloadDir}\System.Runtime.CompilerServices.Unsafe.dll"; DestDir: "{app}"; Flags: ignoreversion +Source: "{#PayloadDir}\WebView2Loader.dll"; DestDir: "{app}"; Flags: ignoreversion [Code] // Don't allow installing conflicting architectures From ef2305e92cfbbfbf2623f3f623955165ad791ea0 Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Fri, 9 Apr 2021 15:46:10 +0100 Subject: [PATCH 11/34] msauth: reorder CanUseBroker logic Reorder the CanUseBroker logic to be easier to grok. --- .../Authentication/MicrosoftAuthentication.cs | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/shared/Microsoft.Git.CredentialManager/Authentication/MicrosoftAuthentication.cs b/src/shared/Microsoft.Git.CredentialManager/Authentication/MicrosoftAuthentication.cs index 2527736d4..0ab7d23db 100644 --- a/src/shared/Microsoft.Git.CredentialManager/Authentication/MicrosoftAuthentication.cs +++ b/src/shared/Microsoft.Git.CredentialManager/Authentication/MicrosoftAuthentication.cs @@ -400,26 +400,29 @@ public HttpClient GetHttpClient() private bool CanUseBroker() { - // We only support the broker on Windows 10 and .NET Framework #if NETFRAMEWORK - if (Context.SessionManager.IsDesktopSession && PlatformUtils.IsWindows10()) + // We only support the broker on Windows 10 and require an interactive session + if (!Context.SessionManager.IsDesktopSession || !PlatformUtils.IsWindows10()) { - // Default to using the OS broker - const bool defaultValue = true; + return false; + } + + // Default to using the OS broker + const bool defaultValue = true; - if (Context.Settings.TryGetSetting(Constants.EnvironmentVariables.MsAuthUseBroker, + if (Context.Settings.TryGetSetting(Constants.EnvironmentVariables.MsAuthUseBroker, Constants.GitConfiguration.Credential.SectionName, Constants.GitConfiguration.Credential.MsAuthUseBroker, out string valueStr)) - { - return valueStr.ToBooleanyOrDefault(defaultValue); - } - - return defaultValue; + { + return valueStr.ToBooleanyOrDefault(defaultValue); } -#endif + return defaultValue; +#else + // OS broker requires .NET Framework right now until we migrate to .NET 5.0 (net5.0-windows10.x.y.z) return false; +#endif } private bool CanUseEmbeddedWebView() From fa282a15c8f34162efb2c48c8fc687015d852c91 Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Mon, 12 Apr 2021 09:29:24 +0100 Subject: [PATCH 12/34] docs: improve GCM_MSAUTH_FLOW wording Improve the wording around which settings and values cause the auth-flow setting to be ignored. Grammer iz hard. --- docs/configuration.md | 7 ++++--- docs/environment.md | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 450eab05c..11d646604 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -256,11 +256,12 @@ git config --global credential.plaintextStorePath /mnt/external-drive/credential Specify which authentication flow should be used when performing Microsoft authentication and an interactive flow is required. -Defaults to the value `auto`. +Defaults to `auto`. **Note:** If [`credential.msauthUseBroker`](#credentialmsauthusebroker) is set to `true` and the operating system authentication broker is available, all flows -will be delegated to the broker; this setting has no effect. +will be delegated to the broker. If both of those things are true, then the +value of `credential.msauthFlow` has no effect. Value|Authentication Flow -|- @@ -283,7 +284,7 @@ git config --global credential.msauthFlow devicecode Use the operating system account manager where available. -Defaults to the value `true`. +Defaults to `true`. Value|Description -|- diff --git a/docs/environment.md b/docs/environment.md index fe313041e..bd7883f42 100644 --- a/docs/environment.md +++ b/docs/environment.md @@ -402,11 +402,12 @@ export GCM_PLAINTEXT_STORE_PATH=/mnt/external-drive/credentials Specify which authentication flow should be used when performing Microsoft authentication and an interactive flow is required. -Defaults to the value `auto`. +Defaults to `auto`. **Note:** If [`GCM_MSAUTH_USEBROKER`](#gcm_msauth_usebroker) is set to `true` and the operating system authentication broker is available, all flows will be -delegated to the broker; this setting has no effect. +delegated to the broker. If both of those things are true, then the value of +`GCM_MSAUTH_FLOW` has no effect. Value|Authentication Flow -|- @@ -435,7 +436,7 @@ export GCM_MSAUTH_FLOW="devicecode" Use the operating system account manager where available. -Defaults to the value `true`. +Defaults to `true`. Value|Description -|- From a8f9e0a00bff9baf545dc0051b688097668d76c5 Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Mon, 12 Apr 2021 10:01:42 +0100 Subject: [PATCH 13/34] msauth: fix typo --- .../Authentication/MicrosoftAuthentication.cs | 2 +- src/shared/Microsoft.Git.CredentialManager/PlatformUtils.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shared/Microsoft.Git.CredentialManager/Authentication/MicrosoftAuthentication.cs b/src/shared/Microsoft.Git.CredentialManager/Authentication/MicrosoftAuthentication.cs index 0ab7d23db..c9d8af83e 100644 --- a/src/shared/Microsoft.Git.CredentialManager/Authentication/MicrosoftAuthentication.cs +++ b/src/shared/Microsoft.Git.CredentialManager/Authentication/MicrosoftAuthentication.cs @@ -32,7 +32,7 @@ public enum MicrosoftAuthenticationFlowType Auto = 0, EmbeddedWebView = 1, SystemWebView = 2, - DeviceCode = 3, + DeviceCode = 3 } public class MicrosoftAuthentication : AuthenticationBase, IMicrosoftAuthentication diff --git a/src/shared/Microsoft.Git.CredentialManager/PlatformUtils.cs b/src/shared/Microsoft.Git.CredentialManager/PlatformUtils.cs index d5ffea918..a05ddae22 100644 --- a/src/shared/Microsoft.Git.CredentialManager/PlatformUtils.cs +++ b/src/shared/Microsoft.Git.CredentialManager/PlatformUtils.cs @@ -31,7 +31,7 @@ public static bool IsWindows10() // https://github.com/dotnet/runtime/blob/6578f257e3be2e2144a65769706e981961f0130c/src/libraries/System.Private.CoreLib/src/System/Environment.Windows.cs#L110-L122 // // Note that we cannot use Environment.OSVersion in .NET Framework (or Core versions less than 5.0) as - // The implementation in those versions "lies" about Windows versions > 8.1 if there is no application manifest. + // the implementation in those versions "lies" about Windows versions > 8.1 if there is no application manifest. if (RtlGetVersionEx(out RTL_OSVERSIONINFOEX osvi) != 0) { return false; From c882674e3692c999544ad679ca603afe49ceffd7 Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Mon, 12 Apr 2021 10:18:34 +0100 Subject: [PATCH 14/34] msauth: drop deprecated TokenCache storage props ctor Remove the usage of a now deprecated constructor for the shared token cache storage properties. This constructor took the client ID which was only used to eventing; GCM doesn't use this. --- .../Authentication/MicrosoftAuthentication.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/shared/Microsoft.Git.CredentialManager/Authentication/MicrosoftAuthentication.cs b/src/shared/Microsoft.Git.CredentialManager/Authentication/MicrosoftAuthentication.cs index c9d8af83e..2e9bb1814 100644 --- a/src/shared/Microsoft.Git.CredentialManager/Authentication/MicrosoftAuthentication.cs +++ b/src/shared/Microsoft.Git.CredentialManager/Authentication/MicrosoftAuthentication.cs @@ -267,14 +267,12 @@ private async Task RegisterTokenCacheAsync(IPublicClientApplication app) return; } - string clientId = app.AppConfig.ClientId; - // We use the MSAL extension library to provide us consistent cache file access semantics (synchronisation, etc) // as other Microsoft developer tools such as the Azure PowerShell CLI. MsalCacheHelper helper = null; try { - var storageProps = CreateTokenCacheProps(clientId, useLinuxFallback: false); + var storageProps = CreateTokenCacheProps(useLinuxFallback: false); helper = await MsalCacheHelper.CreateAsync(storageProps); // Test that cache access is working correctly @@ -300,7 +298,7 @@ private async Task RegisterTokenCacheAsync(IPublicClientApplication app) // On Linux the SecretService/keyring might not be available so we must fall-back to a plaintext file. Context.Streams.Error.WriteLine("warning: using plain-text fallback token cache"); Context.Trace.WriteLine("Using fall-back plaintext token cache on Linux."); - var storageProps = CreateTokenCacheProps(clientId, useLinuxFallback: true); + var storageProps = CreateTokenCacheProps(useLinuxFallback: true); helper = await MsalCacheHelper.CreateAsync(storageProps); } } @@ -317,7 +315,7 @@ private async Task RegisterTokenCacheAsync(IPublicClientApplication app) } } - private StorageCreationProperties CreateTokenCacheProps(string clientId, bool useLinuxFallback) + private StorageCreationProperties CreateTokenCacheProps(bool useLinuxFallback) { const string cacheFileName = "msal.cache"; string cacheDirectory; @@ -336,7 +334,7 @@ private StorageCreationProperties CreateTokenCacheProps(string clientId, bool us } // The keychain is used on macOS with the following service & account names - var builder = new StorageCreationPropertiesBuilder(cacheFileName, cacheDirectory, clientId) + var builder = new StorageCreationPropertiesBuilder(cacheFileName, cacheDirectory) .WithMacKeyChain("Microsoft.Developer.IdentityService", "MSALCache"); if (useLinuxFallback) From 550ebe5ff4f483ead04ac69b4a9dcc92141e953f Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Mon, 12 Apr 2021 15:04:17 +0100 Subject: [PATCH 15/34] msauth: default to NOT use WAM/broker --- docs/configuration.md | 8 ++++---- docs/environment.md | 8 ++++---- .../Authentication/MicrosoftAuthentication.cs | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 11d646604..f001dd589 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -284,17 +284,17 @@ git config --global credential.msauthFlow devicecode Use the operating system account manager where available. -Defaults to `true`. +Defaults to `false`. This default is subject to change in the future. Value|Description -|- -`true` _(default)_|Use the operating system account manager as an authentication broker. -`false`|Do not use the broker. +`true`|Use the operating system account manager as an authentication broker. +`false` _(default)_|Do not use the broker. #### Example ```shell -git config --global credential.msauthUseBroker false +git config --global credential.msauthUseBroker true ``` **Also see: [GCM_MSAUTH_USEBROKER](environment.md#GCM_MSAUTH_USEBROKER)** diff --git a/docs/environment.md b/docs/environment.md index bd7883f42..873b8624e 100644 --- a/docs/environment.md +++ b/docs/environment.md @@ -436,17 +436,17 @@ export GCM_MSAUTH_FLOW="devicecode" Use the operating system account manager where available. -Defaults to `true`. +Defaults to `false`. This default is subject to change in the future. Value|Description -|- -`true` _(default)_|Use the operating system account manager as an authentication broker. -`false`|Do not use the broker. +`true`|Use the operating system account manager as an authentication broker. +`false` _(default)_|Do not use the broker. ##### Windows ```batch -SET GCM_MSAUTH_USEBROKER="false" +SET GCM_MSAUTH_USEBROKER="true" ``` ##### macOS/Linux diff --git a/src/shared/Microsoft.Git.CredentialManager/Authentication/MicrosoftAuthentication.cs b/src/shared/Microsoft.Git.CredentialManager/Authentication/MicrosoftAuthentication.cs index 2e9bb1814..525b8a0c6 100644 --- a/src/shared/Microsoft.Git.CredentialManager/Authentication/MicrosoftAuthentication.cs +++ b/src/shared/Microsoft.Git.CredentialManager/Authentication/MicrosoftAuthentication.cs @@ -405,8 +405,8 @@ private bool CanUseBroker() return false; } - // Default to using the OS broker - const bool defaultValue = true; + // Default to not using the OS broker + const bool defaultValue = false; if (Context.Settings.TryGetSetting(Constants.EnvironmentVariables.MsAuthUseBroker, Constants.GitConfiguration.Credential.SectionName, From 3e6719d8acccbbe583ffe4a53044f83e5e9b5dd5 Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Tue, 20 Apr 2021 11:14:26 +0100 Subject: [PATCH 16/34] docs: update aztokens doc to add PAT REST API link --- docs/azrepos-users-and-tokens.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/azrepos-users-and-tokens.md b/docs/azrepos-users-and-tokens.md index 379e67929..a735602cc 100644 --- a/docs/azrepos-users-and-tokens.md +++ b/docs/azrepos-users-and-tokens.md @@ -18,7 +18,7 @@ Historically, the only option supported by the Azure Repos host provider was Azure DevOps Personal Access Tokens (PATs). These PATs are only used by Azure DevOps, and must be [managed through the Azure -DevOps user settings page](https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=preview-page). +DevOps user settings page](https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=preview-page) or [REST API](https://docs.microsoft.com/en-gb/rest/api/azure/devops/tokens/pats). PATs have a limited lifetime and new tokens must be created once they expire. In Git Credential Manager, when a PAT expired (or was manually revoked) this From f521f4c5e74b62add3d7fb3aaedf28a37e2720bb Mon Sep 17 00:00:00 2001 From: Lessley Dennington Date: Mon, 26 Apr 2021 11:10:07 -0700 Subject: [PATCH 17/34] Updating gcm winget-release to use latest task version and manifest fields --- .github/workflows/release-winget.yaml | 31 +++++++++++++++------------ 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/.github/workflows/release-winget.yaml b/.github/workflows/release-winget.yaml index 552f62970..8b69e85e6 100644 --- a/.github/workflows/release-winget.yaml +++ b/.github/workflows/release-winget.yaml @@ -7,26 +7,29 @@ jobs: release: runs-on: ubuntu-latest steps: - - name: Update winget repository - uses: mjcheetham/update-winget@v1.0 + - id: update winget + name: Update winget repository + uses: mjcheetham/update-winget@v1.1 with: - token: ${{ secrets.WINGET_TOKEN }} - repo: microsoft/winget-pkgs id: Microsoft.GitCredentialManagerCore + token: ${{ secrets.WINGET_TOKEN }} releaseAsset: gcmcore-win-x86-(.*)\.exe manifestText: | - Id: {{id}} - Version: {{version}} - Name: Git Credential Manager Core + PackageIdentifier: {{id}} + PackageVersion: {{version}} + PackageName: Git Credential Manager Core Publisher: Microsoft Corporation - AppMoniker: git-credential-manager-core - Homepage: https://aka.ms/gcmcore + Moniker: git-credential-manager-core + PackageUrl: https://aka.ms/gcmcore Tags: "gcm, gcmcore, git, credential" License: Copyright (C) Microsoft Corporation - Description: Secure, cross-platform Git credential storage with authentication to GitHub, Azure Repos, and other popular Git hosting services. + ShortDescription: Secure, cross-platform Git credential storage with authentication to GitHub, Azure Repos, and other popular Git hosting services. Installers: - - Arch: x86 - Url: {{url}} - InstallerType: Inno - Sha256: {{sha256}} + - Arch: x86 + Url: {{url}} + InstallerType: Inno + Sha256: {{sha256}} + PackageLocale: en-US + ManifestType: singleton + ManifestVersion: 1.0.0 alwaysUsePullRequest: true From d0b6cb23df173abcada2c3d128fc75add7aa5860 Mon Sep 17 00:00:00 2001 From: Lessley Dennington Date: Mon, 26 Apr 2021 14:07:58 -0700 Subject: [PATCH 18/34] Updates for testing --- .github/workflows/release-winget.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release-winget.yaml b/.github/workflows/release-winget.yaml index 8b69e85e6..48e3191cb 100644 --- a/.github/workflows/release-winget.yaml +++ b/.github/workflows/release-winget.yaml @@ -9,11 +9,12 @@ jobs: steps: - id: update winget name: Update winget repository - uses: mjcheetham/update-winget@v1.1 + uses: mjcheetham/update-winget@v1.2 with: id: Microsoft.GitCredentialManagerCore token: ${{ secrets.WINGET_TOKEN }} releaseAsset: gcmcore-win-x86-(.*)\.exe + repo: ldennington/winget-playground manifestText: | PackageIdentifier: {{id}} PackageVersion: {{version}} @@ -33,3 +34,5 @@ jobs: ManifestType: singleton ManifestVersion: 1.0.0 alwaysUsePullRequest: true + releaseRepo: 'microsoft/Git-Credential-Manager-Core' + releaseTag: 'v2.0.194-beta' From d87c0aac0c503ebc1a0fe69befa5648842396443 Mon Sep 17 00:00:00 2001 From: Lessley Dennington Date: Mon, 26 Apr 2021 14:16:20 -0700 Subject: [PATCH 19/34] Updating workflow for testing --- .github/workflows/release-winget.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/release-winget.yaml b/.github/workflows/release-winget.yaml index 48e3191cb..ce89d06e5 100644 --- a/.github/workflows/release-winget.yaml +++ b/.github/workflows/release-winget.yaml @@ -2,6 +2,9 @@ name: "release-winget" on: release: types: [released] + push: + branches: + - ldennington/update-winget-deployments jobs: release: From c19fec9128ae2293bb1be78b79a71a82e1e07d12 Mon Sep 17 00:00:00 2001 From: Lessley Dennington Date: Mon, 26 Apr 2021 14:16:41 -0700 Subject: [PATCH 20/34] Test task version correction --- .github/workflows/release-winget.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release-winget.yaml b/.github/workflows/release-winget.yaml index ce89d06e5..2b17b7ac7 100644 --- a/.github/workflows/release-winget.yaml +++ b/.github/workflows/release-winget.yaml @@ -12,7 +12,7 @@ jobs: steps: - id: update winget name: Update winget repository - uses: mjcheetham/update-winget@v1.2 + uses: mjcheetham/update-winget@v1.1 with: id: Microsoft.GitCredentialManagerCore token: ${{ secrets.WINGET_TOKEN }} From 2b7eb89638c71d1ad5a9f3fb399513d008195f58 Mon Sep 17 00:00:00 2001 From: Lessley Dennington Date: Mon, 26 Apr 2021 14:18:24 -0700 Subject: [PATCH 21/34] Fixing id --- .github/workflows/release-winget.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release-winget.yaml b/.github/workflows/release-winget.yaml index 2b17b7ac7..445af9bda 100644 --- a/.github/workflows/release-winget.yaml +++ b/.github/workflows/release-winget.yaml @@ -10,7 +10,7 @@ jobs: release: runs-on: ubuntu-latest steps: - - id: update winget + - id: update-winget name: Update winget repository uses: mjcheetham/update-winget@v1.1 with: From 561093c5f818b2fb22c96e53aa32e8f7dd79d67d Mon Sep 17 00:00:00 2001 From: Lessley Dennington Date: Mon, 26 Apr 2021 14:30:14 -0700 Subject: [PATCH 22/34] Specifying master branch --- .github/workflows/release-winget.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release-winget.yaml b/.github/workflows/release-winget.yaml index 445af9bda..ee49f0345 100644 --- a/.github/workflows/release-winget.yaml +++ b/.github/workflows/release-winget.yaml @@ -12,6 +12,7 @@ jobs: steps: - id: update-winget name: Update winget repository + branch: master uses: mjcheetham/update-winget@v1.1 with: id: Microsoft.GitCredentialManagerCore From 61ce3d87a7ab38a75ac428f96636ded80c142462 Mon Sep 17 00:00:00 2001 From: Lessley Dennington Date: Mon, 26 Apr 2021 14:32:04 -0700 Subject: [PATCH 23/34] Putting branch in the right spot --- .github/workflows/release-winget.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release-winget.yaml b/.github/workflows/release-winget.yaml index ee49f0345..60ea235d5 100644 --- a/.github/workflows/release-winget.yaml +++ b/.github/workflows/release-winget.yaml @@ -12,13 +12,13 @@ jobs: steps: - id: update-winget name: Update winget repository - branch: master uses: mjcheetham/update-winget@v1.1 with: id: Microsoft.GitCredentialManagerCore token: ${{ secrets.WINGET_TOKEN }} releaseAsset: gcmcore-win-x86-(.*)\.exe repo: ldennington/winget-playground + branch: master manifestText: | PackageIdentifier: {{id}} PackageVersion: {{version}} From 0e80bef131c51b12f77e319fbd57278a3c6f16cd Mon Sep 17 00:00:00 2001 From: Lessley Dennington Date: Mon, 26 Apr 2021 14:34:29 -0700 Subject: [PATCH 24/34] Main --- .github/workflows/release-winget.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release-winget.yaml b/.github/workflows/release-winget.yaml index 60ea235d5..c0bc641ec 100644 --- a/.github/workflows/release-winget.yaml +++ b/.github/workflows/release-winget.yaml @@ -18,7 +18,7 @@ jobs: token: ${{ secrets.WINGET_TOKEN }} releaseAsset: gcmcore-win-x86-(.*)\.exe repo: ldennington/winget-playground - branch: master + branch: main manifestText: | PackageIdentifier: {{id}} PackageVersion: {{version}} From d3e1012f5055dc37967edb98893cf5fc575e0d25 Mon Sep 17 00:00:00 2001 From: Lessley Dennington Date: Mon, 26 Apr 2021 14:39:27 -0700 Subject: [PATCH 25/34] Attempting with hard-coded values --- .github/workflows/release-winget.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release-winget.yaml b/.github/workflows/release-winget.yaml index c0bc641ec..4ad0b10fa 100644 --- a/.github/workflows/release-winget.yaml +++ b/.github/workflows/release-winget.yaml @@ -15,6 +15,7 @@ jobs: uses: mjcheetham/update-winget@v1.1 with: id: Microsoft.GitCredentialManagerCore + version: 2.0.194.40577 token: ${{ secrets.WINGET_TOKEN }} releaseAsset: gcmcore-win-x86-(.*)\.exe repo: ldennington/winget-playground From 0b0c8edd7e4df9b5baaf683948aa28af40c494d1 Mon Sep 17 00:00:00 2001 From: Lessley Dennington Date: Mon, 26 Apr 2021 14:48:25 -0700 Subject: [PATCH 26/34] Removing branch --- .github/workflows/release-winget.yaml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release-winget.yaml b/.github/workflows/release-winget.yaml index 4ad0b10fa..7ca80f64c 100644 --- a/.github/workflows/release-winget.yaml +++ b/.github/workflows/release-winget.yaml @@ -19,7 +19,6 @@ jobs: token: ${{ secrets.WINGET_TOKEN }} releaseAsset: gcmcore-win-x86-(.*)\.exe repo: ldennington/winget-playground - branch: main manifestText: | PackageIdentifier: {{id}} PackageVersion: {{version}} @@ -32,9 +31,9 @@ jobs: ShortDescription: Secure, cross-platform Git credential storage with authentication to GitHub, Azure Repos, and other popular Git hosting services. Installers: - Arch: x86 - Url: {{url}} + Url: https://github.com/microsoft/Git-Credential-Manager-Core/releases/download/v2.0.194-beta/gcmcore-win-x86-2.0.194.40577.exe InstallerType: Inno - Sha256: {{sha256}} + Sha256: 26D3662F66E6AEC76C3BF155028D22D30ED8D1F1AFFA7C676F8DB0426939E8AD PackageLocale: en-US ManifestType: singleton ManifestVersion: 1.0.0 From 281eae06d51ef0d5093562acdd87d3bb9902ed8a Mon Sep 17 00:00:00 2001 From: Lessley Dennington Date: Mon, 26 Apr 2021 14:53:06 -0700 Subject: [PATCH 27/34] Removing hard-coded values --- .github/workflows/release-winget.yaml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release-winget.yaml b/.github/workflows/release-winget.yaml index 7ca80f64c..445af9bda 100644 --- a/.github/workflows/release-winget.yaml +++ b/.github/workflows/release-winget.yaml @@ -15,7 +15,6 @@ jobs: uses: mjcheetham/update-winget@v1.1 with: id: Microsoft.GitCredentialManagerCore - version: 2.0.194.40577 token: ${{ secrets.WINGET_TOKEN }} releaseAsset: gcmcore-win-x86-(.*)\.exe repo: ldennington/winget-playground @@ -31,9 +30,9 @@ jobs: ShortDescription: Secure, cross-platform Git credential storage with authentication to GitHub, Azure Repos, and other popular Git hosting services. Installers: - Arch: x86 - Url: https://github.com/microsoft/Git-Credential-Manager-Core/releases/download/v2.0.194-beta/gcmcore-win-x86-2.0.194.40577.exe + Url: {{url}} InstallerType: Inno - Sha256: 26D3662F66E6AEC76C3BF155028D22D30ED8D1F1AFFA7C676F8DB0426939E8AD + Sha256: {{sha256}} PackageLocale: en-US ManifestType: singleton ManifestVersion: 1.0.0 From ac05a49a1d6a6a939e570885aa0909505a953f61 Mon Sep 17 00:00:00 2001 From: Lessley Dennington Date: Tue, 27 Apr 2021 12:24:50 -0700 Subject: [PATCH 28/34] Fixing manifest --- .github/workflows/release-winget.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release-winget.yaml b/.github/workflows/release-winget.yaml index 445af9bda..08565f401 100644 --- a/.github/workflows/release-winget.yaml +++ b/.github/workflows/release-winget.yaml @@ -25,14 +25,14 @@ jobs: Publisher: Microsoft Corporation Moniker: git-credential-manager-core PackageUrl: https://aka.ms/gcmcore - Tags: "gcm, gcmcore, git, credential" + Tags: [ gcm, gcmcore, git, credential ] License: Copyright (C) Microsoft Corporation ShortDescription: Secure, cross-platform Git credential storage with authentication to GitHub, Azure Repos, and other popular Git hosting services. Installers: - - Arch: x86 - Url: {{url}} - InstallerType: Inno - Sha256: {{sha256}} + - Architecture: x86 + InstallerUrl: {{url}} + InstallerType: inno + InstallerSha256: {{sha256}} PackageLocale: en-US ManifestType: singleton ManifestVersion: 1.0.0 From a8088e2078b91b42a4d22998304d002e56249ee3 Mon Sep 17 00:00:00 2001 From: Lessley Dennington Date: Wed, 28 Apr 2021 13:01:12 -0700 Subject: [PATCH 29/34] Cleaning up --- .github/workflows/release-winget.yaml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.github/workflows/release-winget.yaml b/.github/workflows/release-winget.yaml index 08565f401..664f3ab9b 100644 --- a/.github/workflows/release-winget.yaml +++ b/.github/workflows/release-winget.yaml @@ -2,9 +2,6 @@ name: "release-winget" on: release: types: [released] - push: - branches: - - ldennington/update-winget-deployments jobs: release: @@ -12,7 +9,7 @@ jobs: steps: - id: update-winget name: Update winget repository - uses: mjcheetham/update-winget@v1.1 + uses: mjcheetham/update-winget@v1.2 with: id: Microsoft.GitCredentialManagerCore token: ${{ secrets.WINGET_TOKEN }} @@ -37,5 +34,3 @@ jobs: ManifestType: singleton ManifestVersion: 1.0.0 alwaysUsePullRequest: true - releaseRepo: 'microsoft/Git-Credential-Manager-Core' - releaseTag: 'v2.0.194-beta' From ce63156fd9f27c8a6594480c9d76684d315d1b33 Mon Sep 17 00:00:00 2001 From: Lessley Dennington Date: Thu, 29 Apr 2021 10:40:13 -0700 Subject: [PATCH 30/34] Removing repo --- .github/workflows/release-winget.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/release-winget.yaml b/.github/workflows/release-winget.yaml index 664f3ab9b..12f9ed3a4 100644 --- a/.github/workflows/release-winget.yaml +++ b/.github/workflows/release-winget.yaml @@ -14,7 +14,6 @@ jobs: id: Microsoft.GitCredentialManagerCore token: ${{ secrets.WINGET_TOKEN }} releaseAsset: gcmcore-win-x86-(.*)\.exe - repo: ldennington/winget-playground manifestText: | PackageIdentifier: {{id}} PackageVersion: {{version}} From 4dc9a0dd4d5ca085b3f357f9704a71b1d509ab4f Mon Sep 17 00:00:00 2001 From: Grahn Cooledge Date: Thu, 29 Apr 2021 17:26:54 -0400 Subject: [PATCH 31/34] Add an "s" to the word "work". This commit corrects a small grammatical error. --- docs/environment.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/environment.md b/docs/environment.md index 873b8624e..77c083489 100644 --- a/docs/environment.md +++ b/docs/environment.md @@ -1,6 +1,6 @@ # Environment variables -[Git Credential Manager Core](usage.md) work out of the box for most users. Configuration options are available to customize or tweak behavior. +[Git Credential Manager Core](usage.md) works out of the box for most users. Configuration options are available to customize or tweak behavior. Git Credential Manager Core (GCM Core) can be configured using environment variables. **Environment variables take precedence over [configuration](configuration.md) options.** From e245b54e4b2fb3f9636fac41c06b47392760d413 Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Wed, 5 May 2021 14:38:00 +0100 Subject: [PATCH 32/34] windows: fix post install configure bug Fix a bug where an "invalid" argument was being passed to the GCM executable post-install of the user-only installer to configure the Git credential helper. --- src/windows/Installer.Windows/Setup.iss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/windows/Installer.Windows/Setup.iss b/src/windows/Installer.Windows/Setup.iss index a7c96ea96..672cab282 100644 --- a/src/windows/Installer.Windows/Setup.iss +++ b/src/windows/Installer.Windows/Setup.iss @@ -19,7 +19,7 @@ #define GcmAppId "{{aa76d31d-432c-42ee-844c-bc0bc801cef3}}" #define GcmLongName "Git Credential Manager Core (User)" #define GcmSetupExe "gcmcoreuser" - #define GcmConfigureCmdArgs "--user" + #define GcmConfigureCmdArgs "" #elif InstallTarget == "system" #define GcmAppId "{{fdfae50a-1bc1-4ead-9228-1e1c275e8d12}}" #define GcmLongName "Git Credential Manager Core" From c4a6da8e00b90b32e3df1d763a8dfd282fe34c8a Mon Sep 17 00:00:00 2001 From: Derrick Stolee Date: Wed, 5 May 2021 10:14:18 -0400 Subject: [PATCH 33/34] AzureReposHostProviderTests: fix warnings around orgName We keep getting warnings from the actions bot saying: The variable 'orgName' is assigned but its value is never used However, these variables _are_ used, just as parameters to lambdas within LINQ methods. By extracting this org string into a static readonly, we should be able to avoid these warnings. --- .../AzureReposHostProviderTests.cs | 30 ++++++++----------- 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/src/shared/Microsoft.AzureRepos.Tests/AzureReposHostProviderTests.cs b/src/shared/Microsoft.AzureRepos.Tests/AzureReposHostProviderTests.cs index 468aad804..fcee71301 100644 --- a/src/shared/Microsoft.AzureRepos.Tests/AzureReposHostProviderTests.cs +++ b/src/shared/Microsoft.AzureRepos.Tests/AzureReposHostProviderTests.cs @@ -18,6 +18,7 @@ public class AzureReposHostProviderTests $"{Constants.GitConfiguration.Credential.SectionName}.{Constants.GitConfiguration.Credential.Helper}"; private static readonly string AzDevUseHttpPathKey = $"{Constants.GitConfiguration.Credential.SectionName}.https://dev.azure.com.{Constants.GitConfiguration.Credential.UseHttpPath}"; + private static readonly string OrgName = "org"; [Fact] public void AzureReposProvider_IsSupported_AzureHost_UnencryptedHttp_ReturnsTrue() @@ -143,7 +144,6 @@ public async Task AzureReposProvider_GetCredentialAsync_UnencryptedHttp_ThrowsEx [Fact] public async Task AzureReposProvider_GetCredentialAsync_JwtMode_CachedAuthority_VsComUrlUser_ReturnsCredential() { - var orgName = "org"; var urlAccount = "jane.doe"; var input = new InputArguments(new Dictionary @@ -176,7 +176,7 @@ public async Task AzureReposProvider_GetCredentialAsync_JwtMode_CachedAuthority_ .ReturnsAsync(authResult); var authorityCacheMock = new Mock(MockBehavior.Strict); - authorityCacheMock.Setup(x => x.GetAuthority(orgName)).Returns(authorityUrl); + authorityCacheMock.Setup(x => x.GetAuthority(OrgName)).Returns(authorityUrl); var userMgrMock = new Mock(MockBehavior.Strict); @@ -192,7 +192,6 @@ public async Task AzureReposProvider_GetCredentialAsync_JwtMode_CachedAuthority_ [Fact] public async Task AzureReposProvider_GetCredentialAsync_JwtMode_CachedAuthority_DevAzureUrlUser_ReturnsCredential() { - var orgName = "org"; var urlAccount = "jane.doe"; var input = new InputArguments(new Dictionary @@ -226,7 +225,7 @@ public async Task AzureReposProvider_GetCredentialAsync_JwtMode_CachedAuthority_ .ReturnsAsync(authResult); var authorityCacheMock = new Mock(MockBehavior.Strict); - authorityCacheMock.Setup(x => x.GetAuthority(orgName)).Returns(authorityUrl); + authorityCacheMock.Setup(x => x.GetAuthority(OrgName)).Returns(authorityUrl); var userMgrMock = new Mock(MockBehavior.Strict); @@ -242,7 +241,6 @@ public async Task AzureReposProvider_GetCredentialAsync_JwtMode_CachedAuthority_ [Fact] public async Task AzureReposProvider_GetCredentialAsync_JwtMode_CachedAuthority_DevAzureUrlOrgName_ReturnsCredential() { - var orgName = "org"; var input = new InputArguments(new Dictionary { @@ -276,10 +274,10 @@ public async Task AzureReposProvider_GetCredentialAsync_JwtMode_CachedAuthority_ .ReturnsAsync(authResult); var authorityCacheMock = new Mock(MockBehavior.Strict); - authorityCacheMock.Setup(x => x.GetAuthority(orgName)).Returns(authorityUrl); + authorityCacheMock.Setup(x => x.GetAuthority(OrgName)).Returns(authorityUrl); var userMgrMock = new Mock(MockBehavior.Strict); - userMgrMock.Setup(x => x.GetBinding(orgName)).Returns((AzureReposBinding)null); + userMgrMock.Setup(x => x.GetBinding(OrgName)).Returns((AzureReposBinding)null); var provider = new AzureReposHostProvider(context, azDevOpsMock.Object, msAuthMock.Object, authorityCacheMock.Object, userMgrMock.Object); @@ -302,7 +300,6 @@ public async Task AzureReposProvider_GetCredentialAsync_JwtMode_CachedAuthority_ var expectedOrgUri = new Uri("https://dev.azure.com/org"); var remoteUri = new Uri("https://dev.azure.com/org/proj/_git/repo"); - var orgName = "org"; var authorityUrl = "https://login.microsoftonline.com/common"; var expectedClientId = AzureDevOpsConstants.AadClientId; var expectedRedirectUri = AzureDevOpsConstants.AadRedirectUri; @@ -324,10 +321,10 @@ public async Task AzureReposProvider_GetCredentialAsync_JwtMode_CachedAuthority_ .ReturnsAsync(authResult); var authorityCacheMock = new Mock(MockBehavior.Strict); - authorityCacheMock.Setup(x => x.GetAuthority(orgName)).Returns(authorityUrl); + authorityCacheMock.Setup(x => x.GetAuthority(OrgName)).Returns(authorityUrl); var userMgrMock = new Mock(MockBehavior.Strict); - userMgrMock.Setup(x => x.GetBinding(orgName)).Returns((AzureReposBinding)null); + userMgrMock.Setup(x => x.GetBinding(OrgName)).Returns((AzureReposBinding)null); var provider = new AzureReposHostProvider(context, azDevOpsMock.Object, msAuthMock.Object, authorityCacheMock.Object, userMgrMock.Object); @@ -341,7 +338,6 @@ public async Task AzureReposProvider_GetCredentialAsync_JwtMode_CachedAuthority_ [Fact] public async Task AzureReposProvider_GetCredentialAsync_JwtMode_CachedAuthority_BoundUser_ReturnsCredential() { - var orgName = "org"; var input = new InputArguments(new Dictionary { @@ -373,11 +369,11 @@ public async Task AzureReposProvider_GetCredentialAsync_JwtMode_CachedAuthority_ .ReturnsAsync(authResult); var authorityCacheMock = new Mock(MockBehavior.Strict); - authorityCacheMock.Setup(x => x.GetAuthority(orgName)).Returns(authorityUrl); + authorityCacheMock.Setup(x => x.GetAuthority(OrgName)).Returns(authorityUrl); var userMgrMock = new Mock(MockBehavior.Strict); - userMgrMock.Setup(x => x.GetBinding(orgName)) - .Returns(new AzureReposBinding(orgName, account, null)); + userMgrMock.Setup(x => x.GetBinding(OrgName)) + .Returns(new AzureReposBinding(OrgName, account, null)); var provider = new AzureReposHostProvider(context, azDevOpsMock.Object, msAuthMock.Object, authorityCacheMock.Object, userMgrMock.Object); @@ -391,7 +387,6 @@ public async Task AzureReposProvider_GetCredentialAsync_JwtMode_CachedAuthority_ [Fact] public async Task AzureReposProvider_GetCredentialAsync_JwtMode_NoCachedAuthority_NoUser_ReturnsCredential() { - var orgName = "org"; var input = new InputArguments(new Dictionary { @@ -425,10 +420,10 @@ public async Task AzureReposProvider_GetCredentialAsync_JwtMode_NoCachedAuthorit var authorityCacheMock = new Mock(MockBehavior.Strict); authorityCacheMock.Setup(x => x.GetAuthority(It.IsAny())).Returns((string)null); - authorityCacheMock.Setup(x => x.UpdateAuthority(orgName, authorityUrl)); + authorityCacheMock.Setup(x => x.UpdateAuthority(OrgName, authorityUrl)); var userMgrMock = new Mock(MockBehavior.Strict); - userMgrMock.Setup(x => x.GetBinding(orgName)).Returns((AzureReposBinding)null); + userMgrMock.Setup(x => x.GetBinding(OrgName)).Returns((AzureReposBinding)null); var provider = new AzureReposHostProvider(context, azDevOpsMock.Object, msAuthMock.Object, authorityCacheMock.Object, userMgrMock.Object); @@ -442,7 +437,6 @@ public async Task AzureReposProvider_GetCredentialAsync_JwtMode_NoCachedAuthorit [Fact] public async Task AzureReposProvider_GetCredentialAsync_PatMode_NoExistingPat_GeneratesCredential() { - var orgName = "org"; var input = new InputArguments(new Dictionary { From 17f3f8cb7fbf58f96af5acc27bff609147472239 Mon Sep 17 00:00:00 2001 From: Lessley Dennington Date: Wed, 5 May 2021 17:06:39 -0700 Subject: [PATCH 34/34] Bumping mjcheetham/update-winget to v1.2.1 --- .github/workflows/release-winget.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release-winget.yaml b/.github/workflows/release-winget.yaml index 12f9ed3a4..2a2050c67 100644 --- a/.github/workflows/release-winget.yaml +++ b/.github/workflows/release-winget.yaml @@ -9,7 +9,7 @@ jobs: steps: - id: update-winget name: Update winget repository - uses: mjcheetham/update-winget@v1.2 + uses: mjcheetham/update-winget@v1.2.1 with: id: Microsoft.GitCredentialManagerCore token: ${{ secrets.WINGET_TOKEN }}