Skip to content
Merged
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
13 changes: 10 additions & 3 deletions docs/azrepos-users-and-tokens.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,14 @@ Unlike PATs, Microsoft identity OAuth tokens get automatically refreshed and
renewed as long as you are actively using them to perform Git operations.

These tokens are also securely shared with other Microsoft developer tools
including the Visual Studio IDE and Azure CLI. This means that as long as you're
using Git or one of these tools with the same account, you'll never need to
re-authenticate due to expired tokens!
including the Visual Studio IDE. This means that as long as you're using Git or
one of these tools with the same account, you'll never need to re-authenticate
due to expired tokens!

GCM uses the shared Microsoft developer tooling token cache by default. To keep
Azure Repos OAuth tokens in a GCM-specific cache instead, set
[`credential.azreposUseMicrosoftSharedCache`][credential-azrepos-shared-cache]
or [`GCM_AZREPOS_USE_MSFT_CACHE`][gcm-azrepos-shared-cache] to `false`.

#### User accounts

Expand Down Expand Up @@ -220,6 +225,8 @@ fabrikam:

[azure-devops-pats]: https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=preview-page
[credential-azreposCredentialType]: configuration.md#credentialazreposcredentialtype
[credential-azrepos-shared-cache]: configuration.md#credentialazreposusemicrosoftsharedcache
[gcm-azrepos-credential-type]: environment.md#GCM_AZREPOS_CREDENTIALTYPE
[gcm-azrepos-shared-cache]: environment.md#GCM_AZREPOS_USE_MSFT_CACHE
[azure-devops-api]: https://docs.microsoft.com/en-gb/rest/api/azure/devops/tokens/pats
[rfc3986-s321]: https://www.rfc-editor.org/rfc/rfc3986#section-3.2.1
22 changes: 22 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -827,6 +827,27 @@ Credential: "git:https://bob@github.com/example/myrepo" (user = bob)

---

### credential.azreposUseMicrosoftSharedCache

Use the token cache shared by Microsoft developer tools when authenticating to
Azure Repos with Microsoft identity OAuth tokens. This allows GCM to reuse
authentication performed by tools such as Visual Studio and Azure PowerShell
CLI, and allows those tools to reuse authentication performed by GCM.

Set this value to `false` to use a GCM-specific token cache instead.

Defaults to `true`.

#### Example

```shell
git config --global credential.azreposUseMicrosoftSharedCache false
```

**Also see: [GCM_AZREPOS_USE_MSFT_CACHE][gcm-azrepos-shared-cache]**

---

### credential.azreposCredentialType

Specify the type of credential the Azure Repos host provider should return.
Expand Down Expand Up @@ -1179,6 +1200,7 @@ Defaults to disabled.
[gcm-authority]: environment.md#GCM_AUTHORITY-deprecated
[gcm-autodetect-timeout]: environment.md#GCM_AUTODETECT_TIMEOUT
[gcm-azrepos-credentialtype]: environment.md#GCM_AZREPOS_CREDENTIALTYPE
[gcm-azrepos-shared-cache]: environment.md#GCM_AZREPOS_USE_MSFT_CACHE
[gcm-azrepos-credentialmanagedidentity]: environment.md#GCM_AZREPOS_MANAGEDIDENTITY
[gcm-azrepos-wif]: environment.md#GCM_AZREPOS_WIF
[gcm-azrepos-wif-clientid]: environment.md#GCM_AZREPOS_WIF_CLIENTID
Expand Down
28 changes: 28 additions & 0 deletions docs/environment.md
Original file line number Diff line number Diff line change
Expand Up @@ -920,6 +920,33 @@ export GCM_MSAUTH_USEDEFAULTACCOUNT="false"

---

### GCM_AZREPOS_USE_MSFT_CACHE
Comment thread
mjcheetham marked this conversation as resolved.
Resolved

Use the token cache shared by Microsoft developer tools when authenticating to
Azure Repos with Microsoft identity OAuth tokens. This allows GCM to reuse
authentication performed by tools such as Visual Studio and Azure PowerShell
CLI, and allows those tools to reuse authentication performed by GCM.

Set this value to `false` to use a GCM-specific token cache instead.

Defaults to `true`.

#### Windows

```batch
SET GCM_AZREPOS_USE_MSFT_CACHE=false
```

#### macOS/Linux

```bash
export GCM_AZREPOS_USE_MSFT_CACHE="false"
```

**Also see: [credential.azreposUseMicrosoftSharedCache][shared-cache]**

---

### GCM_AZREPOS_CREDENTIALTYPE

Specify the type of credential the Azure Repos host provider should return.
Expand Down Expand Up @@ -1351,6 +1378,7 @@ Defaults to disabled.
[credential-authority]: configuration.md#credentialauthority-deprecated
[credential-autodetecttimeout]: configuration.md#credentialautodetecttimeout
[credential-azrepos-credential-type]: configuration.md#credentialazreposcredentialtype
[shared-cache]: configuration.md#credentialazreposusemicrosoftsharedcache
[credential-azrepos-managedidentity]: configuration.md#credentialazreposmanagedidentity
[credential-azrepos-wif]: configuration.md#credentialazreposworkloadfederation
[credential-azrepos-wif-clientid]: configuration.md#credentialazreposworkloadfederationclientid
Expand Down
4 changes: 2 additions & 2 deletions docs/windows-broker.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ Git Credential Manager (GCM) knows how to integrate with the
uses WAM to store credentials for Azure DevOps. Authentication requests are said
to be "brokered" to the operating system. Currently, GCM will share
authentication state with a few other Microsoft developer tools like Visual
Studio and the Azure CLI, meaning fewer authentication prompts. Enabling WAM
integration may also be required with certain
Studio, meaning fewer authentication prompts.
Enabling WAM integration may also be required with certain
[Conditional Access policies][azure-conditional-access], which enterprises use
to help protect their assets, including source code.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ private async Task RegisterCacheAsync(ITokenCache cache, StoragePropertiesBuilde
}

// 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.
// as other GCM processes, and other Microsoft developer tools such as Visual Studio.
MsalCacheHelper helper = null;
try
{
Expand Down Expand Up @@ -86,8 +86,8 @@ private async Task RegisterCacheAsync(ITokenCache cache, StoragePropertiesBuilde

/// <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 if the shared cache is enabled.
/// This cache is shared between GCM processes, and also other Microsoft developer tools such as
/// Visual Studio if the shared cache is enabled.
/// </summary>
internal StorageCreationProperties CreateUserTokenCacheProps(bool useLinuxFallback)
{
Expand All @@ -102,7 +102,7 @@ internal StorageCreationProperties CreateUserTokenCacheProps(bool useLinuxFallba

// If we are using the shared Microsoft Developer cache there are a different set of
// file paths, names, and keychain/keyring attributes to use.
// The shared cache is used by other Microsoft developer tools such as the Azure PowerShell CLI.
// The shared cache is used by other Microsoft developer tools such as Visual Studio.
if (_publicClientConfig.UseSharedCache)
{
Context.Trace.WriteLine("Using shared Microsoft Developer MSAL cache");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public record PublicClientConfig
/// </summary>
/// <remarks>
/// If <see langword="true"/> then use the token cache shared by Microsoft
/// developer tools such as the Azure PowerShell CLI. Otherwise, use the
/// developer tools such as Visual Studio. Otherwise, use the
/// Git Credential Manager cache, used only by GCM.
/// </remarks>
public bool UseSharedCache { get; init; }
Expand Down
15 changes: 12 additions & 3 deletions src/Core/Diagnostics/EntraAuthenticationDiagnostic.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,22 @@ public EntraAuthenticationDiagnostic(ICommandContext context)
{ }

protected override async Task RunInternalAsync(IDiagnosticReporter reporter)
{
await RunDiagnosticAsync(reporter, useSharedCache: false);
await RunDiagnosticAsync(reporter, useSharedCache: true);
}

private async Task RunDiagnosticAsync(IDiagnosticReporter reporter, bool useSharedCache)
{
var entraAuth = new EntraAuthentication(Context, new PublicClientConfig
{
UseSharedCache = true,
UseSharedCache = useSharedCache,
});

reporter.ReportProgress("Gathering MSAL token cache data");
reporter.ReportProgress(useSharedCache
? "Checking shared Microsoft developer tool MSAL token cache"
: "Checking Git Credential Manager MSAL token cache");

StorageCreationProperties cacheProps = entraAuth.CreateUserTokenCacheProps(true);
reporter.ReportInfo($"CacheDirectory: {cacheProps.CacheDirectory}");
reporter.ReportInfo($"CacheFileName: {cacheProps.CacheFileName}");
Expand Down Expand Up @@ -47,7 +56,7 @@ protected override async Task RunInternalAsync(IDiagnosticReporter reporter)
}
catch (Exception ex)
{
reporter.ReportError("Failed cache persistence test", ex);
reporter.ReportWarning($"Failed cache persistence test: {ex.Message}");
}
}
}
Expand Down
31 changes: 31 additions & 0 deletions src/Microsoft.AzureRepos.Tests/AzureReposHostProviderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,39 @@ 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 AzDevUseSharedCacheKey =
$"{Constants.GitConfiguration.Credential.SectionName}.{AzureDevOpsConstants.GitConfiguration.Credential.UseSharedCache}";
private static readonly string OrgName = "org";

[Fact]
public void AzureReposProvider_GetUseSharedCache_NoConfiguration_ReturnsTrue()
{
var provider = new AzureReposHostProvider(new TestCommandContext());

Assert.True(provider.GetUseSharedCache());
}

[Fact]
public void AzureReposProvider_GetUseSharedCache_GitConfigFalse_ReturnsFalse()
{
var context = new TestCommandContext();
context.Git.Configuration.Global[AzDevUseSharedCacheKey] = new List<string> {"false"};
var provider = new AzureReposHostProvider(context);

Assert.False(provider.GetUseSharedCache());
}

[Fact]
public void AzureReposProvider_GetUseSharedCache_EnvironmentOverridesGitConfig()
{
var context = new TestCommandContext();
context.Git.Configuration.Global[AzDevUseSharedCacheKey] = new List<string> {"false"};
context.Environment.Variables[AzureDevOpsConstants.EnvironmentVariables.UseSharedCache] = "true";
var provider = new AzureReposHostProvider(context);

Assert.True(provider.GetUseSharedCache());
}

[Fact]
public void AzureReposProvider_IsSupported_AzureHost_UnencryptedHttp_ReturnsTrue()
{
Expand Down
2 changes: 2 additions & 0 deletions src/Microsoft.AzureRepos/AzureDevOpsConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public static class EnvironmentVariables
{
public const string DevAadClientId = "GCM_DEV_AZREPOS_CLIENTID";
public const string DevAadAuthorityBaseUri = "GCM_DEV_AZREPOS_AUTHORITYBASEURI";
public const string UseSharedCache = "GCM_AZREPOS_USE_MSFT_CACHE";
public const string CredentialType = "GCM_AZREPOS_CREDENTIALTYPE";
public const string ServicePrincipalId = "GCM_AZREPOS_SERVICE_PRINCIPAL";
public const string ServicePrincipalSecret = "GCM_AZREPOS_SP_SECRET";
Expand All @@ -56,6 +57,7 @@ public static class Credential
{
public const string DevAadClientId = "azreposDevClientId";
public const string DevAadAuthorityBaseUri = "azreposDevAuthorityBaseUri";
public const string UseSharedCache = "azreposUseMicrosoftSharedCache";
public const string CredentialType = "azreposCredentialType";
public const string AzureAuthority = "azureAuthority";
public const string ServicePrincipal = "azreposServicePrincipal";
Expand Down
18 changes: 17 additions & 1 deletion src/Microsoft.AzureRepos/AzureReposHostProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -406,14 +406,30 @@ private PublicClientConfig GetEntraConfig()
{
ClientId = GetClientId(),
IsMsaPassthroughEnabled = true,
UseSharedCache = true,
UseSharedCache = GetUseSharedCache(),
Comment thread
mjcheetham marked this conversation as resolved.
Resolved
SupportsWindowsBroker = true,
// TODO: enable once our app registration has the appropriate redirect URLs
//SupportsMacBroker = true,
//SupportsLinuxBroker = true,
};
}

internal bool GetUseSharedCache()
{
const bool defaultValue = true; // prefer using the shared Microsoft dev cache

if (_context.Settings.TryGetSetting(
AzureDevOpsConstants.EnvironmentVariables.UseSharedCache,
Constants.GitConfiguration.Credential.SectionName,
AzureDevOpsConstants.GitConfiguration.Credential.UseSharedCache,
out string str))
{
return str.ToBooleanyOrDefault(defaultValue);
}

return defaultValue;
}

private string GetClientId()
{
// Check for developer override value
Expand Down
Loading