* Add WAM support to MSAL

---------

Co-authored-by: John Schmeichel <johsch@microsoft.com>
This commit is contained in:
John Erickson 2023-03-02 20:28:12 -08:00 коммит произвёл GitHub
Родитель 38b9fe36b6
Коммит e1b7067a34
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
25 изменённых файлов: 545 добавлений и 135 удалений

Просмотреть файл

@ -0,0 +1,114 @@
// Copyright (c) Microsoft. All rights reserved.
//
// Licensed under the MIT license.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Identity.Client;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using NuGetCredentialProvider.CredentialProviders.Vsts;
namespace CredentialProvider.Microsoft.Tests.CredentialProviders.Vsts
{
[TestClass]
public class AccountPriorityTests
{
private class TestAccount : IAccount
{
public string Username {get; set;}
public string Environment {get; set;}
public AccountId HomeAccountId {get; set;}
}
private static readonly Guid ContosoTenant = Guid.NewGuid();
private static readonly Guid FabrikamTenant = Guid.NewGuid();
private static readonly IAccount FabrikamUser = new TestAccount
{
Username = "billg@fabrikam.com",
Environment = string.Empty,
HomeAccountId = new AccountId(string.Empty, string.Empty, FabrikamTenant.ToString()),
};
private static readonly IAccount ContosoUser = new TestAccount
{
Username = "billg@contoso.com",
Environment = string.Empty,
HomeAccountId = new AccountId(string.Empty, string.Empty, ContosoTenant.ToString()),
};
private static readonly IAccount MsaUser = new TestAccount
{
Username = "bill.gates@live.com",
Environment = string.Empty,
HomeAccountId = new AccountId(string.Empty, string.Empty, AuthUtil.MsaAccountTenant.ToString()),
};
private static readonly List<List<IAccount>> Permutations = new List<List<IAccount>>()
{
new List<IAccount> { ContosoUser, MsaUser, FabrikamUser },
new List<IAccount> { ContosoUser, FabrikamUser, MsaUser },
new List<IAccount> { MsaUser, ContosoUser, FabrikamUser },
new List<IAccount> { MsaUser, FabrikamUser, ContosoUser },
new List<IAccount> { FabrikamUser, MsaUser, ContosoUser },
new List<IAccount> { FabrikamUser, ContosoUser, MsaUser },
};
[TestMethod]
public void MsaMatchesMsa()
{
foreach (var accounts in Permutations)
{
var applicable = MsalTokenProvider.GetApplicableAccounts(accounts, AuthUtil.FirstPartyTenant, loginHint: null);
Assert.AreEqual(applicable[0].Item1.Username, MsaUser.Username);
}
}
[TestMethod]
public void ContosoMatchesContoso()
{
foreach (var accounts in Permutations)
{
var applicable = MsalTokenProvider.GetApplicableAccounts(accounts, ContosoTenant, loginHint: null);
Assert.AreEqual(applicable[0].Item1.Username, ContosoUser.Username);
}
}
[TestMethod]
public void FabrikamMatchesFabrikam()
{
foreach (var accounts in Permutations)
{
var applicable = MsalTokenProvider.GetApplicableAccounts(accounts, FabrikamTenant, loginHint: null);
Assert.AreEqual(applicable[0].Item1.Username, FabrikamUser.Username);
}
}
[TestMethod]
public void LoginHintOverride()
{
foreach (var accounts in Permutations)
{
foreach (var tenantId in Permutations[0].Select(a => Guid.Parse(a.HomeAccountId.TenantId)))
{
foreach (var loginHint in Permutations[0].Select(a => a.Username))
{
var applicable = MsalTokenProvider.GetApplicableAccounts(accounts, tenantId, loginHint);
Assert.AreEqual(applicable[0].Item1.Username, loginHint);
}
}
}
}
[TestMethod]
public void UnknownAuthorityTenantPrefersMsa()
{
foreach (var accounts in Permutations)
{
var applicable = MsalTokenProvider.GetApplicableAccounts(accounts, Guid.Empty, loginHint: null);
Assert.AreEqual(applicable[0].Item1.Username, MsaUser.Username);
}
}
}
}

Просмотреть файл

@ -215,7 +215,7 @@ namespace CredentialProvider.Microsoft.Tests.CredentialProviders.Vsts
foreach (var ppeUri in ppeUris)
{
var authorityUri = await authUtil.GetAadAuthorityUriAsync(ppeUri, cancellationToken);
authorityUri.Should().Be(new Uri("https://login.windows-ppe.net/organizations"));
authorityUri.Should().Be(new Uri("https://login.windows-ppe.net/f8cdef31-a31e-4b4a-93e4-5f571e91255a"));
}
}
@ -227,7 +227,7 @@ namespace CredentialProvider.Microsoft.Tests.CredentialProviders.Vsts
var authorityUri = await authUtil.GetAadAuthorityUriAsync(requestUri, cancellationToken);
authorityUri.Should().Be(organizationsAuthority);
authorityUri.Should().Be(new Uri("https://login.microsoftonline.com/f8cdef31-a31e-4b4a-93e4-5f571e91255a"));
}
@ -250,7 +250,7 @@ namespace CredentialProvider.Microsoft.Tests.CredentialProviders.Vsts
var authorityUri = await authUtil.GetAadAuthorityUriAsync(requestUri, cancellationToken);
authorityUri.Should().Be(new Uri("https://login.windows-ppe.net/organizations"));
authorityUri.Should().Be(new Uri("https://login.windows-ppe.net/f8cdef31-a31e-4b4a-93e4-5f571e91255a"));
}
private void MockResponseHeaders(string key, string value)

Просмотреть файл

@ -31,7 +31,7 @@ namespace CredentialProvider.Microsoft.Tests.CredentialProviders.Vsts
private VstsCredentialProvider vstsCredentialProvider;
private IDisposable environmentLock;
[TestInitialize]
public void TestInitialize()
{
@ -44,7 +44,7 @@ namespace CredentialProvider.Microsoft.Tests.CredentialProviders.Vsts
mockBearerTokenProvider2.Setup(x => x.ShouldRun(It.IsAny<bool>(), It.IsAny<bool>(), It.IsAny<bool>())).Returns(true);
mockBearerTokenProvider2.Setup(x => x.GetTokenAsync(It.IsAny<Uri>(), It.IsAny<CancellationToken>())).ReturnsAsync((string)null);
mockBearerTokenProvidersFactory = new Mock<IBearerTokenProvidersFactory>();
mockBearerTokenProvidersFactory.Setup(x => x.Get(It.IsAny<string>())).Returns(new[] { mockBearerTokenProvider1.Object, mockBearerTokenProvider2.Object });
mockBearerTokenProvidersFactory.Setup(x => x.Get(It.IsAny<Uri>())).Returns(new[] { mockBearerTokenProvider1.Object, mockBearerTokenProvider2.Object });
mockVstsSessionTokenFromBearerTokenProvider = new Mock<IAzureDevOpsSessionTokenFromBearerTokenProvider>();
mockVstsSessionTokenFromBearerTokenProvider.Setup(x => x.GetAzureDevOpsSessionTokenFromBearerToken(It.IsAny<GetAuthenticationCredentialsRequest>(), It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()));

Просмотреть файл

@ -7,8 +7,10 @@ folder InstallDir:\Common7\IDE\CommonExtensions\Microsoft\NuGet\Plugins\Credenti
file source=$(PluginBinPath)\CredentialProvider.Microsoft.exe
file source=$(PluginBinPath)\CredentialProvider.Microsoft.exe.config
file source=$(PluginBinPath)\CredentialProvider.Microsoft.pdb
file source=$(PluginBinPath)\Microsoft.Identity.Client.Broker.dll
file source=$(PluginBinPath)\Microsoft.Identity.Client.dll
file source=$(PluginBinPath)\Microsoft.Identity.Client.Extensions.Msal.dll
file source=$(PluginBinPath)\Microsoft.Identity.Client.NativeInterop.dll
file source=$(PluginBinPath)\Microsoft.IdentityModel.Abstractions.dll
file source=$(PluginBinPath)\Microsoft.IdentityModel.Clients.ActiveDirectory.dll
file source=$(PluginBinPath)\Microsoft.Win32.Primitives.dll
@ -92,8 +94,8 @@ folder InstallDir:\Common7\IDE\CommonExtensions\Microsoft\NuGet\Plugins\Credenti
file source=$(PluginBinPath)\System.Runtime.Serialization.Xml.dll
file source=$(PluginBinPath)\System.Security.Claims.dll
file source=$(PluginBinPath)\System.Security.Cryptography.Algorithms.dll
file source=$(PluginBinPath)\System.Security.Cryptography.Csp.dll
file source=$(PluginBinPath)\System.Security.Cryptography.Cng.dll
file source=$(PluginBinPath)\System.Security.Cryptography.Csp.dll
file source=$(PluginBinPath)\System.Security.Cryptography.Encoding.dll
file source=$(PluginBinPath)\System.Security.Cryptography.Pkcs.dll
file source=$(PluginBinPath)\System.Security.Cryptography.Primitives.dll
@ -117,3 +119,12 @@ folder InstallDir:\Common7\IDE\CommonExtensions\Microsoft\NuGet\Plugins\Credenti
file source=$(PluginBinPath)\System.Xml.XmlSerializer.dll
file source=$(PluginBinPath)\System.Xml.XPath.dll
file source=$(PluginBinPath)\System.Xml.XPath.XDocument.dll
folder InstallDir:\Common7\IDE\CommonExtensions\Microsoft\NuGet\Plugins\CredentialProvider.Microsoft\runtimes\win-arm64\native
file source=$(PluginBinPath)\runtimes\win-arm64\native\msalruntime_arm64.dll
folder InstallDir:\Common7\IDE\CommonExtensions\Microsoft\NuGet\Plugins\CredentialProvider.Microsoft\runtimes\win-x64\native
file source=$(PluginBinPath)\runtimes\win-x64\native\msalruntime.dll
folder InstallDir:\Common7\IDE\CommonExtensions\Microsoft\NuGet\Plugins\CredentialProvider.Microsoft\runtimes\win-x86\native
file source=$(PluginBinPath)\runtimes\win-x86\native\msalruntime_x86.dll

Просмотреть файл

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), Build.props))\Build.props" />
<PropertyGroup>
<OutputType>Exe</OutputType>
@ -17,19 +17,21 @@
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<DebugType>portable</DebugType>
<Optimize>false</Optimize>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DefineConstants>$(DefineConstants);DEBUG;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<DefineConstants>TRACE</DefineConstants>
<DefineConstants>$(DefineConstants);TRACE</DefineConstants>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Identity.Client" Version="4.46.2" />
<PackageReference Include="Microsoft.Identity.Client.Extensions.Msal" Version="2.18.5" />
<PackageReference Include="Microsoft.Identity.Client" Version="4.50.0" />
<PackageReference Include="Microsoft.Identity.Client.Broker" Version="4.50.0-preview"/>
<PackageReference Include="Microsoft.Identity.Client.NativeInterop" Version="0.13.5"/>
<PackageReference Include="Microsoft.Identity.Client.Extensions.Msal" Version="2.26.0" />
<PackageReference Include="Microsoft.IdentityModel.Clients.ActiveDirectory" Version="5.2.9" />
<PackageReference Include="NuGet.Protocol" Version="5.11.3" />
<PackageReference Include="PowerArgs" Version="3.6.0" />

Просмотреть файл

@ -19,9 +19,9 @@ namespace NuGetCredentialProvider.CredentialProviders.Vsts
private readonly string clientId;
private readonly TokenCache tokenCache;
internal AdalTokenProvider(string authority, string resource, string clientId, TokenCache tokenCache)
internal AdalTokenProvider(Uri authority, string resource, string clientId, TokenCache tokenCache)
{
this.authority = authority;
this.authority = authority.ToString();
this.resource = resource;
this.clientId = clientId;
this.tokenCache = tokenCache;
@ -74,11 +74,11 @@ namespace NuGetCredentialProvider.CredentialProviders.Vsts
{
return null;
}
}
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
public async Task<IAdalToken> AcquireTokenWithUI(CancellationToken cancellationToken)
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
}
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
public async Task<IAdalToken> AcquireTokenWithUI(CancellationToken cancellationToken)
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
{
#if NETFRAMEWORK
var authenticationContext = new AuthenticationContext(authority, tokenCache);

Просмотреть файл

@ -2,6 +2,7 @@
//
// Licensed under the MIT license.
using System;
using System.Collections.Generic;
using NuGetCredentialProvider.Logging;
@ -18,7 +19,7 @@ namespace NuGetCredentialProvider.CredentialProviders.Vsts
this.logger = logger;
}
public IEnumerable<IBearerTokenProvider> Get(string authority)
public IEnumerable<IBearerTokenProvider> Get(Uri authority)
{
IAdalTokenProvider adalTokenProvider = adalTokenProviderFactory.Get(authority);
return new IBearerTokenProvider[]
@ -31,4 +32,4 @@ namespace NuGetCredentialProvider.CredentialProviders.Vsts
};
}
}
}
}

Просмотреть файл

@ -2,10 +2,12 @@
//
// Licensed under the MIT license.
using System;
namespace NuGetCredentialProvider.CredentialProviders.Vsts
{
public interface IAdalTokenProviderFactory
{
IAdalTokenProvider Get(string authority);
IAdalTokenProvider Get(Uri authority);
}
}
}

Просмотреть файл

@ -33,10 +33,11 @@ namespace NuGetCredentialProvider.CredentialProviders.Vsts
{
public const string VssResourceTenant = "X-VSS-ResourceTenant";
public const string VssAuthorizationEndpoint = "X-VSS-AuthorizationEndpoint";
private const string OrganizationsTenant = "organizations";
private const string CommonTenant = "common";
public static readonly Guid FirstPartyTenant = Guid.Parse("f8cdef31-a31e-4b4a-93e4-5f571e91255a");
public static readonly Guid MsaAccountTenant = Guid.Parse("9188040d-6c67-4c5b-b112-36a304b66dad");
public const string VssE2EID = "X-VSS-E2EID";
private readonly ILogger logger;
public AuthUtil(ILogger logger)
@ -54,7 +55,7 @@ namespace NuGetCredentialProvider.CredentialProviders.Vsts
var headers = await GetResponseHeadersAsync(uri, cancellationToken);
var bearerHeaders = headers.WwwAuthenticate.Where(x => x.Scheme.Equals("Bearer", StringComparison.Ordinal));
foreach (var param in bearerHeaders)
{
if (param.Parameter == null)
@ -81,7 +82,10 @@ namespace NuGetCredentialProvider.CredentialProviders.Vsts
var aadBase = UsePpeAadUrl(uri) ? "https://login.windows-ppe.net" : "https://login.microsoftonline.com";
logger.Verbose(string.Format(Resources.AADAuthorityNotFound, aadBase));
var tenant = EnvUtil.MsalEnabled() ? OrganizationsTenant: CommonTenant;
// WAM gets confused about the Common tenant, so we'll just assume that if there isn't
// a tenant GUID provided, that it's a consumer tenant.
var tenant = EnvUtil.MsalEnabled() ? FirstPartyTenant.ToString() : CommonTenant;
return new Uri($"{aadBase}/{tenant}");
}

Просмотреть файл

@ -2,12 +2,13 @@
//
// Licensed under the MIT license.
using System;
using System.Collections.Generic;
namespace NuGetCredentialProvider.CredentialProviders.Vsts
{
public interface IBearerTokenProvidersFactory
{
IEnumerable<IBearerTokenProvider> Get(string authority);
IEnumerable<IBearerTokenProvider> Get(Uri authority);
}
}
}

Просмотреть файл

@ -21,6 +21,8 @@ namespace NuGetCredentialProvider.CredentialProviders.Vsts
Task<IMsalToken> AcquireTokenWithWindowsIntegratedAuth(CancellationToken cancellationToken);
ILogger Logger {get;}
string NameSuffix {get;}
}
internal interface IMsalToken

Просмотреть файл

@ -2,12 +2,13 @@
//
// Licensed under the MIT license.
using System;
using NuGetCredentialProvider.Logging;
namespace NuGetCredentialProvider.CredentialProviders.Vsts
{
internal interface IMsalTokenProviderFactory
{
IMsalTokenProvider Get(string authority, ILogger logger);
IMsalTokenProvider Get(Uri authority, bool brokerEnabled, ILogger logger);
}
}

Просмотреть файл

@ -15,17 +15,17 @@ namespace NuGetCredentialProvider.CredentialProviders.Vsts
/// <summary>
/// Acquire an AAD token via the Msal cache
/// </summary>
internal class MsalCacheBearerTokenProvider : IBearerTokenProvider
internal class MsalSilentBearerTokenProvider : IBearerTokenProvider
{
private readonly IMsalTokenProvider msalTokenProvider;
public MsalCacheBearerTokenProvider(IMsalTokenProvider msalTokenProvider)
public MsalSilentBearerTokenProvider(IMsalTokenProvider msalTokenProvider)
{
this.msalTokenProvider = msalTokenProvider;
}
public bool Interactive { get; } = false;
public string Name { get; } = "Msal Cache";
public string Name => $"MSAL Silent " + msalTokenProvider.NameSuffix;
public async Task<string> GetTokenAsync(Uri uri, CancellationToken cancellationToken)
{
@ -51,7 +51,7 @@ namespace NuGetCredentialProvider.CredentialProviders.Vsts
}
public bool Interactive { get; } = false;
public string Name { get; } = "Msal Windows Integrated Authentication";
public string Name { get; } = "MSAL Windows Integrated Authentication";
public async Task<string> GetTokenAsync(Uri uri, CancellationToken cancellationToken)
{

Просмотреть файл

@ -2,8 +2,10 @@
//
// Licensed under the MIT license.
using System;
using System.Collections.Generic;
using NuGetCredentialProvider.Logging;
using NuGetCredentialProvider.Util;
namespace NuGetCredentialProvider.CredentialProviders.Vsts
{
@ -12,22 +14,26 @@ namespace NuGetCredentialProvider.CredentialProviders.Vsts
private readonly ILogger logger;
private readonly IMsalTokenProviderFactory msalTokenProviderFactory;
public MsalBearerTokenProvidersFactory(ILogger logger,IMsalTokenProviderFactory msalTokenProviderFactory)
public MsalBearerTokenProvidersFactory(ILogger logger, IMsalTokenProviderFactory msalTokenProviderFactory)
{
this.msalTokenProviderFactory = msalTokenProviderFactory;
this.logger = logger;
}
public IEnumerable<IBearerTokenProvider> Get(string authority)
public IEnumerable<IBearerTokenProvider> Get(Uri authority)
{
IMsalTokenProvider msalTokenProvider = msalTokenProviderFactory.Get(authority, logger);
return new IBearerTokenProvider[]
var options = EnvUtil.MsalAllowBrokerEnabled()
? new [] {true, false}
: new [] {false};
foreach(bool brokerEnabled in options)
{
new MsalCacheBearerTokenProvider(msalTokenProvider),
new MsalWindowsIntegratedAuthBearerTokenProvider(msalTokenProvider),
new MsalUserInterfaceBearerTokenProvider(msalTokenProvider),
new MsalDeviceCodeFlowBearerTokenProvider(msalTokenProvider)
};
IMsalTokenProvider msalTokenProvider = msalTokenProviderFactory.Get(authority, brokerEnabled, logger);
yield return new MsalSilentBearerTokenProvider(msalTokenProvider);
yield return new MsalWindowsIntegratedAuthBearerTokenProvider(msalTokenProvider);
yield return new MsalUserInterfaceBearerTokenProvider(msalTokenProvider);
yield return new MsalDeviceCodeFlowBearerTokenProvider(msalTokenProvider);
}
}
}
}

Просмотреть файл

@ -3,11 +3,12 @@
// Licensed under the MIT license.
using System;
using System.IO;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Identity.Client;
using Microsoft.Identity.Client.Broker;
using Microsoft.Identity.Client.Extensions.Msal;
using NuGetCredentialProvider.Logging;
using NuGetCredentialProvider.Util;
@ -16,39 +17,91 @@ namespace NuGetCredentialProvider.CredentialProviders.Vsts
{
internal class MsalTokenProvider : IMsalTokenProvider
{
private const string NativeClientRedirect = "https://login.microsoftonline.com/common/oauth2/nativeclient";
private readonly string authority;
private readonly Uri authority;
private readonly string resource;
private readonly string clientId;
private readonly bool brokerEnabled;
private static MsalCacheHelper helper;
private bool cacheEnabled = false;
private string cacheLocation;
internal MsalTokenProvider(string authority, string resource, string clientId, ILogger logger)
internal MsalTokenProvider(Uri authority, string resource, string clientId, bool brokerEnabled, ILogger logger)
{
this.authority = authority;
this.resource = resource;
this.clientId = clientId;
this.brokerEnabled = brokerEnabled;
this.Logger = logger;
this.cacheEnabled = EnvUtil.MsalFileCacheEnabled();
this.cacheLocation = this.cacheEnabled ? EnvUtil.GetMsalCacheLocation() : null;
}
public string NameSuffix => $"with{(this.brokerEnabled ? "" : "out")} WAM broker.";
public ILogger Logger { get; private set; }
private async Task<MsalCacheHelper> GetMsalCacheHelperAsync()
{
// There are options to set up the cache correctly using StorageCreationProperties on other OS's but that will need to be tested
// for now only support windows
if (helper == null && this.cacheEnabled && RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
if (helper == null && this.cacheEnabled)
{
var fileName = Path.GetFileName(cacheLocation);
var directory = Path.GetDirectoryName(cacheLocation);
this.Logger.Verbose($"Using MSAL cache at `{cacheLocation}`");
var builder = new StorageCreationPropertiesBuilder(fileName, directory).WithCacheChangedEvent(this.clientId);
StorageCreationProperties creationProps = builder.Build();
helper = await MsalCacheHelper.CreateAsync(creationProps);
const string cacheFileName = "msal.cache";
// Copied from GCM https://github.com/GitCredentialManager/git-credential-manager/blob/bdc20d91d325d66647f2837ffb4e2b2fe98d7e70/src/shared/Core/Authentication/MicrosoftAuthentication.cs#L371-L407
try
{
var storageProps = CreateTokenCacheProperties(useLinuxFallback: false);
helper = await MsalCacheHelper.CreateAsync(storageProps);
helper.VerifyPersistence();
}
catch (MsalCachePersistenceException ex)
{
this.Logger.Warning("warning: cannot persist Microsoft authentication token cache securely!");
this.Logger.Verbose(ex.ToString());
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
// On macOS sometimes the Keychain returns the "errSecAuthFailed" error - we don't know why
// but it appears to be something to do with not being able to access the keychain.
// Locking and unlocking (or restarting) often fixes this.
this.Logger.Error(
"warning: there is a problem accessing the login Keychain - either manually lock and unlock the " +
"login Keychain, or restart the computer to remedy this");
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
// On Linux the SecretService/keyring might not be available so we must fall-back to a plaintext file.
this.Logger.Warning("warning: using plain-text fallback token cache");
var storageProps = CreateTokenCacheProperties(useLinuxFallback: true);
helper = await MsalCacheHelper.CreateAsync(storageProps);
}
}
StorageCreationProperties CreateTokenCacheProperties(bool useLinuxFallback)
{
var builder = new StorageCreationPropertiesBuilder(cacheFileName, cacheLocation)
.WithMacKeyChain("Microsoft.Developer.IdentityService", "MSALCache");
if (useLinuxFallback)
{
builder.WithLinuxUnprotectedFile();
}
else
{
// The SecretService/keyring is used on Linux with the following collection name and attributes
builder.WithLinuxKeyring(cacheFileName,
"default", "MSALCache",
new KeyValuePair<string, string>("MsalClientID", "Microsoft.Developer.IdentityService"),
new KeyValuePair<string, string>("Microsoft.Developer.IdentityService", "1.0.0.0"));
}
return builder.Build();
}
}
return helper;
@ -64,44 +117,90 @@ namespace NuGetCredentialProvider.CredentialProviders.Vsts
var publicClient = await GetPCAAsync().ConfigureAwait(false);
try
var msalBuilder = publicClient.AcquireTokenWithDeviceCode(new string[] { resource }, deviceCodeHandler);
var result = await msalBuilder.ExecuteAsync(linkedCancellationToken);
return new MsalToken(result);
}
internal static List<(IAccount, string)> GetApplicableAccounts(IEnumerable<IAccount> accounts, Guid authorityTenantId, string loginHint)
{
var applicableAccounts = new List<(IAccount, string)>();
foreach (var account in accounts)
{
var msalBuilder = publicClient.AcquireTokenWithDeviceCode(new string[] { resource }, deviceCodeHandler);
var result = await msalBuilder.ExecuteAsync(linkedCancellationToken);
return new MsalToken(result);
}
finally
{
var helper = await GetMsalCacheHelperAsync();
helper?.UnregisterCache(publicClient.UserTokenCache);
string canonicalName = $"{account.HomeAccountId?.TenantId}\\{account.Username}";
// If a login hint is provided and matches, try that first
if (!string.IsNullOrEmpty(loginHint) && account.Username == loginHint)
{
applicableAccounts.Insert(0, (account, canonicalName));
continue;
}
if (Guid.TryParse(account.HomeAccountId?.TenantId, out Guid accountTenantId))
{
if (accountTenantId == authorityTenantId)
{
applicableAccounts.Add((account, canonicalName));
}
else if (accountTenantId == AuthUtil.MsaAccountTenant && (authorityTenantId == AuthUtil.FirstPartyTenant || authorityTenantId == Guid.Empty))
{
applicableAccounts.Add((account, canonicalName));
}
}
}
return applicableAccounts;
}
public async Task<IMsalToken> AcquireTokenSilentlyAsync(CancellationToken cancellationToken)
{
var publicClient = await GetPCAAsync().ConfigureAwait(false);
IPublicClientApplication publicClient = await GetPCAAsync().ConfigureAwait(false);
var accounts = await publicClient.GetAccountsAsync();
try
foreach (var account in accounts)
{
foreach (var account in accounts)
{
try
{
var silentBuilder = publicClient.AcquireTokenSilent(new string[] { resource }, account);
var result = await silentBuilder.ExecuteAsync(cancellationToken);
return new MsalToken(result);
}
catch (MsalUiRequiredException)
{ }
catch (MsalServiceException)
{ }
}
this.Logger.Verbose($"Found in cache: {account.HomeAccountId?.TenantId}\\{account.Username}");
}
finally
if (Guid.TryParse(this.authority.AbsolutePath.Trim('/'), out Guid authorityTenantId))
{
var helper = await GetMsalCacheHelperAsync();
helper?.UnregisterCache(publicClient.UserTokenCache);
this.Logger.Verbose($"Found tenant `{authorityTenantId}` authority URL: `{this.authority}`");
}
else
{
this.Logger.Verbose($"Could not determine tenant from authority URL `{this.authority}`");
}
string loginHint = EnvUtil.GetMsalLoginHint();
var applicableAccounts = GetApplicableAccounts(accounts, authorityTenantId, loginHint);
if (this.brokerEnabled && RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
applicableAccounts.Add((PublicClientApplication.OperatingSystemAccount, PublicClientApplication.OperatingSystemAccount.HomeAccountId.Identifier));
}
foreach ((IAccount account, string canonicalName) in applicableAccounts)
{
try
{
this.Logger.Verbose($"Attempting to use identity `{canonicalName}`");
var result = await publicClient.AcquireTokenSilent(new string[] { resource }, account)
.ExecuteAsync(cancellationToken);
return new MsalToken(result);
}
catch (MsalUiRequiredException e)
{
this.Logger.Verbose(e.Message);
}
catch (MsalServiceException e)
{
this.Logger.Warning(e.Message);
}
}
return null;
@ -111,16 +210,18 @@ namespace NuGetCredentialProvider.CredentialProviders.Vsts
{
var deviceFlowTimeout = EnvUtil.GetDeviceFlowTimeoutFromEnvironmentInSeconds(logging);
CancellationTokenSource cts = new CancellationTokenSource(TimeSpan.FromSeconds(deviceFlowTimeout));
var linkedCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, cts.Token).Token;
using CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
cts.CancelAfter(TimeSpan.FromSeconds(deviceFlowTimeout));
var publicClient = await GetPCAAsync(useLocalHost: true).ConfigureAwait(false);
try
{
var msalBuilder = publicClient.AcquireTokenInteractive(new string[] { resource });
msalBuilder.WithPrompt(Prompt.SelectAccount);
msalBuilder.WithUseEmbeddedWebView(false);
var result = await msalBuilder.ExecuteAsync(linkedCancellationToken);
var result = await publicClient.AcquireTokenInteractive(new string[] { resource })
.WithPrompt(Prompt.SelectAccount)
.WithUseEmbeddedWebView(false)
.ExecuteAsync(cts.Token);
return new MsalToken(result);
}
catch (MsalServiceException e)
@ -132,11 +233,6 @@ namespace NuGetCredentialProvider.CredentialProviders.Vsts
throw;
}
finally
{
var helper = await GetMsalCacheHelperAsync();
helper?.UnregisterCache(publicClient.UserTokenCache);
}
}
public async Task<IMsalToken> AcquireTokenWithWindowsIntegratedAuth(CancellationToken cancellationToken)
@ -151,9 +247,9 @@ namespace NuGetCredentialProvider.CredentialProviders.Vsts
return null;
}
var builder = publicClient.AcquireTokenByIntegratedWindowsAuth(new string[] { resource});
builder.WithUsername(upn);
var result = await builder.ExecuteAsync(cancellationToken);
var result = await publicClient.AcquireTokenByIntegratedWindowsAuth(new string[] { resource })
.WithUsername(upn)
.ExecuteAsync(cancellationToken);
return new MsalToken(result);
}
@ -166,32 +262,92 @@ namespace NuGetCredentialProvider.CredentialProviders.Vsts
throw;
}
finally
{
var helper = await GetMsalCacheHelperAsync();
helper?.UnregisterCache(publicClient.UserTokenCache);
}
}
}
private async Task<IPublicClientApplication> GetPCAAsync(bool useLocalHost = false)
{
var helper = await GetMsalCacheHelperAsync().ConfigureAwait(false);
var publicClientBuilder = PublicClientApplicationBuilder.Create(this.clientId)
.WithAuthority(this.authority);
.WithAuthority(this.authority)
.WithDefaultRedirectUri()
.WithLogging(
(LogLevel level, string message, bool _containsPii) =>
{
// We ignore containsPii param because we are passing in enablePiiLogging below.
this.Logger.Verbose($"MSAL Log ({level}): {message}");
},
enablePiiLogging: EnvUtil.GetLogPIIEnabled()
);
if (this.brokerEnabled)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
this.Logger.Verbose($"MSAL using WithBrokerPreview");
// The application being used doesn't support MSA passthrough, so disable if using an MSA.
// Still want to enable for non-MSA as this setting affects the broker UI for non-MSA accounts.
bool msaPassthrough = !this.authority.AbsolutePath.Contains(AuthUtil.FirstPartyTenant.ToString());
publicClientBuilder
.WithBrokerPreview()
.WithParentActivityOrWindow(() => GetConsoleOrTerminalWindow())
.WithWindowsBrokerOptions(new WindowsBrokerOptions()
{
HeaderText = "Azure DevOps Artifacts",
ListWindowsWorkAndSchoolAccounts = true,
MsaPassthrough = msaPassthrough
});
}
else
{
this.Logger.Verbose($"MSAL using WithBroker");
publicClientBuilder.WithBroker();
}
}
if (useLocalHost)
{
publicClientBuilder.WithRedirectUri("http://localhost");
}
else
{
publicClientBuilder.WithRedirectUri(NativeClientRedirect);
}
var publicClient = publicClientBuilder.Build();
helper?.RegisterCache(publicClient.UserTokenCache);
return publicClient;
}
#region https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/WAM
enum GetAncestorFlags
{
GetParent = 1,
GetRoot = 2,
/// <summary>
/// Retrieves the owned root window by walking the chain of parent and owner windows returned by GetParent.
/// </summary>
GetRootOwner = 3
}
/// <summary>
/// Retrieves the handle to the ancestor of the specified window.
/// </summary>
/// <param name="hwnd">A handle to the window whose ancestor is to be retrieved.
/// If this parameter is the desktop window, the function returns NULL. </param>
/// <param name="flags">The ancestor to be retrieved.</param>
/// <returns>The return value is the handle to the ancestor window.</returns>
[DllImport("user32.dll", ExactSpelling = true)]
static extern IntPtr GetAncestor(IntPtr hwnd, GetAncestorFlags flags);
[DllImport("kernel32.dll")]
static extern IntPtr GetConsoleWindow();
public IntPtr GetConsoleOrTerminalWindow()
{
IntPtr consoleHandle = GetConsoleWindow();
IntPtr handle = GetAncestor(consoleHandle, GetAncestorFlags.GetRootOwner);
return handle;
}
#endregion
}
}

Просмотреть файл

@ -2,6 +2,7 @@
//
// Licensed under the MIT license.
using System;
using NuGetCredentialProvider.Logging;
namespace NuGetCredentialProvider.CredentialProviders.Vsts
@ -11,9 +12,9 @@ namespace NuGetCredentialProvider.CredentialProviders.Vsts
private const string Resource = "499b84ac-1321-427f-aa17-267ca6975798/.default";
private const string ClientId = "872cd9fa-d31f-45e0-9eab-6e460a02d1f1";
public IMsalTokenProvider Get(string authority, ILogger logger)
public IMsalTokenProvider Get(Uri authority, bool brokerEnabled, ILogger logger)
{
return new MsalTokenProvider(authority, Resource, ClientId, logger);
return new MsalTokenProvider(authority, Resource, ClientId, brokerEnabled, logger);
}
}
}

Просмотреть файл

@ -3,12 +3,7 @@
// Licensed under the MIT license.
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using NuGet.Protocol.Plugins;
using NuGetCredentialProvider.Logging;
using NuGetCredentialProvider.Util;
namespace NuGetCredentialProvider.CredentialProviders.Vsts
{
@ -16,7 +11,7 @@ namespace NuGetCredentialProvider.CredentialProviders.Vsts
{
private const string Resource = "499b84ac-1321-427f-aa17-267ca6975798";
private const string ClientId = "872cd9fa-d31f-45e0-9eab-6e460a02d1f1";
private readonly TokenCache tokenCache;
public VstsAdalTokenProviderFactory(TokenCache tokenCache)
@ -24,7 +19,7 @@ namespace NuGetCredentialProvider.CredentialProviders.Vsts
this.tokenCache = tokenCache;
}
public IAdalTokenProvider Get(string authority)
public IAdalTokenProvider Get(Uri authority)
{
return new AdalTokenProvider(authority, Resource, ClientId, tokenCache);
}

Просмотреть файл

@ -37,7 +37,7 @@ namespace NuGetCredentialProvider.CredentialProviders.Vsts
public override async Task<bool> CanProvideCredentialsAsync(Uri uri)
{
// If for any reason we reach this point and any of the three build task env vars are set,
// If for any reason we reach this point and any of the three build task env vars are set,
// we should not try get credentials with this cred provider.
string feedEndPointsJsonEnvVar = Environment.GetEnvironmentVariable(EnvUtil.BuildTaskExternalEndpoints);
string uriPrefixesStringEnvVar = Environment.GetEnvironmentVariable(EnvUtil.BuildTaskUriPrefixes);
@ -95,7 +95,7 @@ namespace NuGetCredentialProvider.CredentialProviders.Vsts
Uri authority = await authUtil.GetAadAuthorityUriAsync(request.Uri, cancellationToken);
Verbose(string.Format(Resources.AdalUsingAuthority, authority));
IEnumerable<IBearerTokenProvider> bearerTokenProviders = bearerTokenProvidersFactory.Get(authority.ToString());
IEnumerable<IBearerTokenProvider> bearerTokenProviders = bearerTokenProvidersFactory.Get(authority);
cancellationToken.ThrowIfCancellationRequested();
// Try each bearer token provider (e.g. ADAL cache, ADAL WIA, ADAL UI, ADAL DeviceCode) in order.
@ -155,4 +155,4 @@ namespace NuGetCredentialProvider.CredentialProviders.Vsts
return null;
}
}
}
}

Просмотреть файл

@ -76,6 +76,8 @@ namespace NuGetCredentialProvider.CredentialProviders.Vsts
using (var request = CreateRequest(uriBuilder.Uri, validTo))
using (var response = await httpClient.SendAsync(request, cancellationToken))
{
logger.LogResponse(NuGet.Common.LogLevel.Verbose, true, response);
string serializedResponse;
if (response.StatusCode == System.Net.HttpStatusCode.BadRequest)
{
@ -88,6 +90,8 @@ namespace NuGetCredentialProvider.CredentialProviders.Vsts
using(var response2 = await httpClient.SendAsync(request2, cancellationToken))
{
response2.EnsureSuccessStatusCode();
logger.LogResponse(NuGet.Common.LogLevel.Verbose, true, response2);
serializedResponse = await response2.Content.ReadAsStringAsync();
}
}

Просмотреть файл

@ -2,6 +2,8 @@
//
// Licensed under the MIT license.
using System.Linq;
using System.Net.Http;
using NuGet.Common;
namespace NuGetCredentialProvider.Logging
@ -12,4 +14,20 @@ namespace NuGetCredentialProvider.Logging
void SetLogLevel(LogLevel newLogLevel);
}
public static class LoggerExtensions
{
public static void LogResponse(this ILogger logger, LogLevel level, bool allowOnConsole, HttpResponseMessage response)
{
logger.Log(NuGet.Common.LogLevel.Verbose, true, $"Response: {response.StatusCode}");
if (response.Headers.TryGetValues("ActivityId", out var activityIds))
{
string activityId = activityIds.FirstOrDefault();
if (activityId != null)
{
logger.Log(NuGet.Common.LogLevel.Verbose, true, $" ActivityId: {activityId}");
}
}
}
}
}

Просмотреть файл

@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.IO;
using System.Net.Http.Headers;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;
@ -22,6 +23,7 @@ using NuGetCredentialProvider.Util;
using PowerArgs;
using ILogger = NuGetCredentialProvider.Logging.ILogger;
[assembly: InternalsVisibleToAttribute("CredentialProvider.Microsoft.Tests")]
namespace NuGetCredentialProvider
{
public static class Program

Просмотреть файл

@ -8,7 +8,6 @@ using System.Threading;
using System.Threading.Tasks;
using NuGet.Protocol.Plugins;
using NuGetCredentialProvider.CredentialProviders;
using NuGetCredentialProvider.CredentialProviders.Vsts;
using NuGetCredentialProvider.Logging;
using NuGetCredentialProvider.Util;
@ -65,7 +64,7 @@ namespace NuGetCredentialProvider.RequestHandlers
{
Logger.Verbose(string.Format(Resources.SkippingCredentialProvider, credentialProvider, request.Uri.AbsoluteUri));
continue;
}
}
Logger.Verbose(string.Format(Resources.UsingCredentialProvider, credentialProvider, request.Uri.AbsoluteUri));
if (credentialProvider.IsCachable && TryCache(request, out string cachedToken))
@ -107,8 +106,8 @@ namespace NuGetCredentialProvider.RequestHandlers
authenticationTypes: null,
responseCode: MessageResponseCode.Error);
}
}
}
Logger.Verbose(Resources.CredentialsNotFound);
return new GetAuthenticationCredentialsResponse(
username: null,

Просмотреть файл

@ -355,25 +355,26 @@ Device Flow Authentication Timeout
NuGet workarounds
{15}
Set to "true" or "false" to override any other sources of the
CanShowDialog parameter
CanShowDialog parameter.
Enable MSAL (Experimental: may be changed or removed in a future version)
Enable MSAL
{16}
Use the MSAL library to get a bearer token to then get a session token
Use the MSAL library to get a bearer token to then get a session token.
Defaults to `true`.
MSAL Authority (Experimental: may be changed or removed in a future version)
MSAL Authority
{17}
Set to override the authority used when fetching an MSAL token.
e.g. https://login.microsoftonline.com/organizations
MSAL Token File Cache Enabled (Experimental: may be changed or removed in a future version)
MSAL Token File Cache Enabled
{18}
Boolean to enable/disable the MSAL token cache. Disabled by default.
Default MSAL Cache Location (Experimental: may be changed or removed in a future version)
Default MSAL Cache Location
{19}
Provide MSAL Cache Location (Experimental: may be changed or removed in a future version)
Provide MSAL Cache Location
{20}
Provide the location where the MSAL cache should be read and written to</value>
</data>

Просмотреть файл

@ -6,6 +6,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using NuGetCredentialProvider.CredentialProviders.Vsts;
using NuGetCredentialProvider.Logging;
@ -14,6 +15,7 @@ namespace NuGetCredentialProvider.Util
public static class EnvUtil
{
public const string LogPathEnvVar = "NUGET_CREDENTIALPROVIDER_LOG_PATH";
public const string LogPIIEnvVar = "NUGET_CREDENTIALPROVIDER_LOG_PII";
public const string SessionTokenCacheEnvVar = "NUGET_CREDENTIALPROVIDER_SESSIONTOKENCACHE_ENABLED";
public const string WindowsIntegratedAuthenticationEnvVar = "NUGET_CREDENTIALPROVIDER_WINDOWSINTEGRATEDAUTHENTICATION_ENABLED";
public const string ForceCanShowDialogEnvVar = "NUGET_CREDENTIALPROVIDER_FORCE_CANSHOWDIALOG_TO";
@ -32,24 +34,46 @@ namespace NuGetCredentialProvider.Util
public const string BuildTaskExternalEndpoints = "VSS_NUGET_EXTERNAL_FEED_ENDPOINTS";
public const string MsalEnabledEnvVar = "NUGET_CREDENTIALPROVIDER_MSAL_ENABLED";
public const string MsalLoginHintEnvVar = "NUGET_CREDENTIALPROVIDER_MSAL_LOGIN_HINT";
public const string MsalAuthorityEnvVar = "NUGET_CREDENTIALPROVIDER_MSAL_AUTHORITY";
public const string MsalFileCacheEnvVar = "NUGET_CREDENTIALPROVIDER_MSAL_FILECACHE_ENABLED";
public const string MsalFileCacheLocationEnvVar = "NUGET_CREDENTIALPROVIDER_MSAL_FILECACHE_LOCATION";
public const string MsalAllowBrokerEnvVar = "NUGET_CREDENTIALPROVIDER_MSAL_ALLOW_BROKER";
public static bool GetLogPIIEnabled()
{
return GetEnabledFromEnvironment(LogPIIEnvVar, defaultValue: false);
}
private static readonly string LocalAppDataLocation = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData, Environment.SpecialFolderOption.Create);
private const string CredenetialProviderFolderName = "MicrosoftCredentialProvider";
public static string AdalTokenCacheLocation { get; } = Path.Combine(LocalAppDataLocation, CredenetialProviderFolderName, "ADALTokenCache.dat");
private const string CredentialProviderFolderName = "MicrosoftCredentialProvider";
public static string AdalTokenCacheLocation { get; } = Path.Combine(LocalAppDataLocation, CredentialProviderFolderName, "ADALTokenCache.dat");
// from https://github.com/GitCredentialManager/git-credential-manager/blob/df90676d1249759eef8cec57155c27e869503225/src/shared/Microsoft.Git.CredentialManager/Authentication/MicrosoftAuthentication.cs#L277
// The Visual Studio MSAL cache is located at "%LocalAppData%\.IdentityService\msal.cache" on Windows.
// We use the MSAL extension library to provide us consistent cache file access semantics (synchronisation, etc)
// We use the MSAL extension library to provide us consistent cache file access semantics (synchronization, etc)
// as Visual Studio itself follows, as well as other Microsoft developer tools such as the Azure PowerShell CLI.
public static string DefaultMsalCacheLocation { get; } = Path.Combine(LocalAppDataLocation, ".IdentityService", "msal.cache");
public static string DefaultMsalCacheLocation
{
get
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
// The shared MSAL cache is located at "%LocalAppData%\.IdentityService\msal.cache" on Windows.
return Path.Combine(LocalAppDataLocation, ".IdentityService");
}
else
{
// The shared MSAL cache metadata is located at "~/.local/.IdentityService/msal.cache" on UNIX.
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", ".IdentityService");
}
}
}
public static string FileLogLocation { get; } = Environment.GetEnvironmentVariable(LogPathEnvVar);
public static string SessionTokenCacheLocation { get; } = Path.Combine(LocalAppDataLocation, CredenetialProviderFolderName, "SessionTokenCache.dat");
public static string SessionTokenCacheLocation { get; } = Path.Combine(LocalAppDataLocation, CredentialProviderFolderName, "SessionTokenCache.dat");
public static Uri GetAuthorityFromEnvironment(ILogger logger)
{
@ -73,6 +97,11 @@ namespace NuGetCredentialProvider.Util
return null;
}
public static string GetMsalLoginHint()
{
return Environment.GetEnvironmentVariable(MsalLoginHintEnvVar);
}
public static string GetMsalCacheLocation()
{
string msalCacheFromEnvironment = Environment.GetEnvironmentVariable(MsalFileCacheLocationEnvVar);
@ -89,6 +118,11 @@ namespace NuGetCredentialProvider.Util
return GetEnabledFromEnvironment(MsalFileCacheEnvVar, defaultValue: true);
}
public static bool MsalAllowBrokerEnabled()
{
return GetEnabledFromEnvironment(MsalAllowBrokerEnvVar, defaultValue: RuntimeInformation.IsOSPlatform(OSPlatform.Windows));
}
public static IList<string> GetHostsFromEnvironment(ILogger logger, string envVar, IEnumerable<string> defaultHosts, [CallerMemberName] string collectionName = null)
{
var hosts = new List<string>();

56
test.bat Normal file
Просмотреть файл

@ -0,0 +1,56 @@
@echo OFF
SETLOCAL EnableDelayedExpansion
@REM A Windows domain user should be able to run this against a feed in an AAD-back AzDO org
@REM and all scenarios should succeed non-interactively.
set TEST_FEED=%1
set NUGET_CREDENTIALPROVIDER_MSAL_FILECACHE_ENABLED=true
set NUGET_CREDENTIALPROVIDER_MSAL_FILECACHE_LOCATION=%TEMP%\msal.cache
IF EXIST %NUGET_CREDENTIALPROVIDER_MSAL_FILECACHE_LOCATION% (del /q %NUGET_CREDENTIALPROVIDER_MSAL_FILECACHE_LOCATION%)
echo "Testing MSAL with broker"
set NUGET_CREDENTIALPROVIDER_MSAL_ENABLED=true
set NUGET_CREDENTIALPROVIDER_MSAL_ALLOW_BROKER=true
CALL :TEST_FRAMEWORKS
IF %ERRORLEVEL% NEQ 0 (
echo "Failed: %ERRORLEVEL%"
exit /b %ERRORLEVEL%
)
echo "Testing MSAL without broker"
set NUGET_CREDENTIALPROVIDER_MSAL_ENABLED=true
set NUGET_CREDENTIALPROVIDER_MSAL_ALLOW_BROKER=false
CALL :TEST_FRAMEWORKS
IF %ERRORLEVEL% NEQ 0 (
echo "Failed: %ERRORLEVEL%"
exit /b %ERRORLEVEL%
)
echo "Testing ADAL"
set NUGET_CREDENTIALPROVIDER_MSAL_ENABLED=false
set NUGET_CREDENTIALPROVIDER_MSAL_ALLOW_BROKER=
CALL :TEST_FRAMEWORKS
IF %ERRORLEVEL% NEQ 0 (
echo "Failed: %ERRORLEVEL%"
exit /b %ERRORLEVEL%
)
echo "All tests passed!"
exit /b 0
:TEST_FRAMEWORKS
for %%I in ("netcoreapp3.1","net461","net6.0") DO (
del /q "!UserProfile!\AppData\Local\MicrosoftCredentialProvider\*.dat" 2>NUL
del /q "%NUGET_CREDENTIALPROVIDER_MSAL_FILECACHE_LOCATION%" 2>NUL
echo Testing %%I with NUGET_CREDENTIALPROVIDER_MSAL_ENABLED=!NUGET_CREDENTIALPROVIDER_MSAL_ENABLED! NUGET_CREDENTIALPROVIDER_MSAL_ALLOW_BROKER=!NUGET_CREDENTIALPROVIDER_MSAL_ALLOW_BROKER!
echo dotnet run --no-restore --no-build -f %%I --project CredentialProvider.Microsoft\CredentialProvider.Microsoft.csproj -- -C -U !TEST_FEED! -V Debug
dotnet run --no-restore --no-build -f %%I --project CredentialProvider.Microsoft\CredentialProvider.Microsoft.csproj -- -C -U !TEST_FEED! -V Debug ^
> test.%%I.%NUGET_CREDENTIALPROVIDER_MSAL_ENABLED%.%NUGET_CREDENTIALPROVIDER_MSAL_ALLOW_BROKER%.log
IF !ERRORLEVEL! NEQ 0 (
echo "Previous command execution failed: !ERRORLEVEL!"
dotnet run --no-restore --no-build -f %%I --project CredentialProvider.Microsoft\CredentialProvider.Microsoft.csproj -- -C -U !TEST_FEED! -V Debug
exit /b !ERRORLEVEL!
)
)