Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
using System;
using System.IO;
using System.Threading.Tasks;
using GitCredentialManager.Authentication;
using GitCredentialManager.Tests.Objects;
using Microsoft.Identity.Client.AppConfig;
using Microsoft.Identity.Client.Extensions.Msal;
using Xunit;

namespace GitCredentialManager.Tests.Authentication
Expand All @@ -29,6 +31,51 @@ await Assert.ThrowsAsync<Trace2InvalidOperationException>(
() => msAuth.GetTokenForUserAsync(authority, clientId, redirectUri, scopes, userName, false));
}

[MacOSFact]
public void MicrosoftAuthentication_CreateUserTokenCacheProps_OnMacOS_UsesGcmKeychain()
{
var context = new TestCommandContext();
var msAuth = new MicrosoftAuthentication(context);

StorageCreationProperties actual = msAuth.CreateUserTokenCacheProps(useLinuxFallback: false);

Assert.Equal("user.cache", actual.CacheFileName);
Assert.Equal(Path.Combine(context.FileSystem.UserDataDirectoryPath, "msal"), actual.CacheDirectory);
Assert.Equal("GitCredentialManager.MSAL", actual.MacKeyChainServiceName);
Assert.Equal("UserCache", actual.MacKeyChainAccountName);
}

[WindowsFact]
public void MicrosoftAuthentication_CreateUserTokenCacheProps_OnWindows_UsesSharedCache()
{
var context = new TestCommandContext();
var msAuth = new MicrosoftAuthentication(context);

StorageCreationProperties actual = msAuth.CreateUserTokenCacheProps(useLinuxFallback: false);

Assert.Equal("msal.cache", actual.CacheFileName);
Assert.Equal(
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), ".IdentityService"),
actual.CacheDirectory);
}

[LinuxFact]
public void MicrosoftAuthentication_CreateUserTokenCacheProps_OnLinux_UsesSharedCache()
{
var context = new TestCommandContext();
var msAuth = new MicrosoftAuthentication(context);

StorageCreationProperties actual = msAuth.CreateUserTokenCacheProps(useLinuxFallback: false);

Assert.Equal("msal.cache", actual.CacheFileName);
Assert.Equal(
Path.Combine(context.FileSystem.UserHomePath, ".local", ".IdentityService"),
actual.CacheDirectory);
Assert.Equal("msal.cache", actual.KeyringSchemaName);
Assert.Equal("default", actual.KeyringCollection);
Assert.Equal("MSALCache", actual.KeyringSecretLabel);
}

[Theory]
[InlineData(null)]
[InlineData("")]
Expand Down
53 changes: 35 additions & 18 deletions src/shared/Core/Authentication/MicrosoftAuthentication.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,10 @@ public enum MicrosoftAuthenticationFlowType

public class MicrosoftAuthentication : AuthenticationBase, IMicrosoftAuthentication
{
private const string GcmMacKeychainServiceName = "GitCredentialManager.MSAL";
private const string GcmMacKeychainUserAccountName = "UserCache";
private const string GcmMacKeychainAppAccountName = "AppCache";

public static readonly string[] AuthorityIds =
{
"msa", "microsoft", "microsoftaccount",
Expand Down Expand Up @@ -719,8 +723,8 @@ private async Task RegisterTokenCacheAsync(ITokenCache cache, StoragePropertiesB
return;
}

// We use the MSAL extension library to provide us consistent cache file access semantics (synchronisation, etc)
// as other GCM processes, and other Microsoft developer tools such as the Azure PowerShell CLI.
// We use the MSAL extension library to provide consistent cache file access semantics (synchronisation, etc)
// between GCM processes. On Windows and Linux this cache is also shared with other Microsoft developer tools.
MsalCacheHelper helper = null;
try
{
Expand Down Expand Up @@ -771,32 +775,45 @@ private async Task RegisterTokenCacheAsync(ITokenCache cache, StoragePropertiesB

/// <summary>
/// Create the properties for the user token cache. This is used by public client applications only.
/// This cache is shared between GCM processes, and also other Microsoft developer tools such as the Azure
/// PowerShell CLI.
/// This cache is shared between GCM processes. On Windows and Linux it is also shared with other Microsoft
/// developer tools such as the Azure PowerShell CLI.
/// </summary>
/// <param name="useLinuxFallback"></param>
/// <returns></returns>
internal StorageCreationProperties CreateUserTokenCacheProps(bool useLinuxFallback)
{
const string cacheFileName = "msal.cache";
string cacheFileName;
string cacheDirectory;
if (PlatformUtils.IsWindows())
StorageCreationPropertiesBuilder builder;

if (PlatformUtils.IsMacOS())
{
// The shared MSAL cache is located at "%LocalAppData%\.IdentityService\msal.cache" on Windows.
cacheDirectory = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
".IdentityService"
);
// Keep the macOS cache isolated from other Microsoft developer tools. Those tools can recreate the
// shared Keychain item and discard GCM's access control entry, causing repeated password prompts.
cacheFileName = "user.cache";
cacheDirectory = Path.Combine(Context.FileSystem.UserDataDirectoryPath, "msal");
builder = new StorageCreationPropertiesBuilder(cacheFileName, cacheDirectory)
.WithMacKeyChain(GcmMacKeychainServiceName, GcmMacKeychainUserAccountName);
}
else
{
// The shared MSAL cache metadata is located at "~/.local/.IdentityService/msal.cache" on UNIX.
cacheDirectory = Path.Combine(Context.FileSystem.UserHomePath, ".local", ".IdentityService");
}
cacheFileName = "msal.cache";
if (PlatformUtils.IsWindows())
{
// The shared MSAL cache is located at "%LocalAppData%\.IdentityService\msal.cache" on Windows.
cacheDirectory = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
".IdentityService"
);
}
else
{
// The shared MSAL cache metadata is located at "~/.local/.IdentityService/msal.cache" on Linux.
cacheDirectory = Path.Combine(Context.FileSystem.UserHomePath, ".local", ".IdentityService");
}

// The keychain is used on macOS with the following service & account names
var builder = new StorageCreationPropertiesBuilder(cacheFileName, cacheDirectory)
.WithMacKeyChain("Microsoft.Developer.IdentityService", "MSALCache");
builder = new StorageCreationPropertiesBuilder(cacheFileName, cacheDirectory);
}

if (useLinuxFallback)
{
Expand Down Expand Up @@ -872,7 +889,7 @@ internal StorageCreationProperties CreateAppTokenCacheProps(bool useLinuxFallbac

// The keychain is used on macOS with the following service & account names
var builder = new StorageCreationPropertiesBuilder(cacheFileName, cacheDirectory)
.WithMacKeyChain("GitCredentialManager.MSAL", "AppCache");
.WithMacKeyChain(GcmMacKeychainServiceName, GcmMacKeychainAppAccountName);

if (useLinuxFallback)
{
Expand Down