Add support for WAM broker (#324)
* Add WAM support to MSAL --------- Co-authored-by: John Schmeichel <johsch@microsoft.com>
This commit is contained in:
Родитель
38b9fe36b6
Коммит
e1b7067a34
|
@ -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>();
|
||||
|
|
|
@ -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!
|
||||
)
|
||||
)
|
Загрузка…
Ссылка в новой задаче