Add UserAgent and IMsalHttpClientFactory support to authentication library (#404)

Bringing PlatformUtils and UserAgent defaults over to the authentication
library. Since netstandard2.0 doesn't have all the types required it's
still expected that callers will need to construct their own http client
factory. For .NET Framework however, using a static HttpClient is still
the recommendation so providing some extension methods to support that.
This commit is contained in:
John Schmeichel 2023-04-28 16:54:33 -07:00 коммит произвёл GitHub
Родитель f5a44bd497
Коммит 9abad78d08
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
11 изменённых файлов: 151 добавлений и 11 удалений

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

@ -35,7 +35,7 @@ public class AccountPriorityTests
{
Username = "bill.gates@live.com",
Environment = string.Empty,
HomeAccountId = new AccountId(string.Empty, string.Empty, Constants.MsaAccountTenant.ToString()),
HomeAccountId = new AccountId(string.Empty, string.Empty, MsalConstants.MsaAccountTenant.ToString()),
};
private static readonly List<List<IAccount>> Permutations = new List<List<IAccount>>()
@ -53,7 +53,7 @@ public class AccountPriorityTests
{
foreach (var accounts in Permutations)
{
var applicable = MsalExtensions.GetApplicableAccounts(accounts, Constants.FirstPartyTenant, loginHint: null);
var applicable = MsalExtensions.GetApplicableAccounts(accounts, MsalConstants.FirstPartyTenant, loginHint: null);
Assert.AreEqual(applicable[0].Item1.Username, MsaUser.Username);
}
}

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

@ -52,6 +52,12 @@ public static class AzureArtifacts
return builder;
}
public static PublicClientApplicationBuilder WithHttpClient(this PublicClientApplicationBuilder builder, HttpClient? httpClient = null)
{
// Default HttpClient is only meant for .NET Framework clients that can't use the SocketsHttpHandler
return builder.WithHttpClientFactory(new MsalHttpClientFactory(httpClient ?? new HttpClient()));
}
#region https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/issues/3590
enum GetAncestorFlags
{

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

@ -7,7 +7,7 @@
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>0.1.0$(VersionSuffix)</Version>
<Version>0.1.1$(VersionSuffix)</Version>
<Authors>Microsoft</Authors>
<Owners>Microsoft</Owners>
<Description>Azure Artifacts authentication library for credential providers.</Description>

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

@ -4,7 +4,7 @@
namespace Microsoft.Artifacts.Authentication;
public static class Constants
public static class MsalConstants
{
private const string AzureDevOpsResource = "499b84ac-1321-427f-aa17-267ca6975798/.default";
public static readonly IEnumerable<string> AzureDevOpsScopes = Array.AsReadOnly(new[] { AzureDevOpsResource });

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

@ -34,7 +34,7 @@ public class MsalDeviceCodeTokenProvider : ITokenProvider
try
{
var result = await app.AcquireTokenWithDeviceCode(Constants.AzureDevOpsScopes, tokenRequest.DeviceCodeResultCallback ?? ((DeviceCodeResult deviceCodeResult) =>
var result = await app.AcquireTokenWithDeviceCode(MsalConstants.AzureDevOpsScopes, tokenRequest.DeviceCodeResultCallback ?? ((DeviceCodeResult deviceCodeResult) =>
{
logger.LogInformation(deviceCodeResult.Message);

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

@ -29,7 +29,7 @@ public static partial class MsalExtensions
{
applicableAccounts.Add((account, canonicalName));
}
else if (accountTenantId == Constants.MsaAccountTenant && (authorityTenantId == Constants.FirstPartyTenant || authorityTenantId == Guid.Empty))
else if (accountTenantId == MsalConstants.MsaAccountTenant && (authorityTenantId == MsalConstants.FirstPartyTenant || authorityTenantId == Guid.Empty))
{
applicableAccounts.Add((account, canonicalName));
}
@ -45,9 +45,9 @@ public static partial class MsalExtensions
// Even if using the organizations tenant the presence of an MSA will attempt to use the consumers tenant
// which is not supported by the Azure DevOps application. Detect this case and use the first party tenant.
if (Guid.TryParse(account.HomeAccountId?.TenantId, out Guid accountTenantId) && accountTenantId == Constants.MsaAccountTenant)
if (Guid.TryParse(account.HomeAccountId?.TenantId, out Guid accountTenantId) && accountTenantId == MsalConstants.MsaAccountTenant)
{
builder = builder.WithTenantId(Constants.FirstPartyTenant.ToString());
builder = builder.WithTenantId(MsalConstants.FirstPartyTenant.ToString());
}
return builder;

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

@ -0,0 +1,52 @@
// Copyright (c) Microsoft. All rights reserved.
//
// Licensed under the MIT license.
using System.Net.Http.Headers;
using Microsoft.Identity.Client;
namespace Microsoft.Artifacts.Authentication;
public class MsalHttpClientFactory : IMsalHttpClientFactory
{
private readonly HttpClient httpClient;
public MsalHttpClientFactory(HttpClient httpClient)
{
this.httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
var userAgent = this.httpClient.DefaultRequestHeaders.UserAgent;
userAgent.Add(ProgramProduct);
userAgent.Add(ProgramComment);
userAgent.Add(ClrProduct);
userAgent.Add(ClrComment);
}
public static ProductInfoHeaderValue ProgramProduct =>
new ProductInfoHeaderValue(PlatformInformation.GetProgramName(), PlatformInformation.GetProgramVersion());
public static ProductInfoHeaderValue ProgramComment =>
new ProductInfoHeaderValue($"({PlatformInformation.GetOSType()}; {PlatformInformation.GetCpuArchitecture()}; {PlatformInformation.GetOsDescription()})");
public static ProductInfoHeaderValue ClrProduct =>
new ProductInfoHeaderValue("CLR", PlatformInformation.GetClrVersion());
public static ProductInfoHeaderValue ClrComment =>
new ProductInfoHeaderValue($"({PlatformInformation.GetClrFramework()}; {PlatformInformation.GetClrRuntime()}; {PlatformInformation.GetClrDescription()})");
// Produces a value similar to the following:
// CredentialProvider.Microsoft/1.0.4+aae4981de95d543b7935811c05474e393dd9e144 (Windows; X64; Microsoft Windows 10.0.19045) CLR/6.0.16 (.NETCoreApp,Version=v6.0; win10-x64; .NET 6.0.16)
public static IEnumerable<ProductInfoHeaderValue> UserAgent =>
Array.AsReadOnly(new[]
{
ProgramProduct,
ProgramComment,
ClrProduct,
ClrComment
});
public HttpClient GetHttpClient()
{
return httpClient;
}
}

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

@ -39,7 +39,7 @@ public class MsalIntegratedWindowsAuthTokenProvider : ITokenProvider
return null;
}
var result = await app.AcquireTokenByIntegratedWindowsAuth(Constants.AzureDevOpsScopes)
var result = await app.AcquireTokenByIntegratedWindowsAuth(MsalConstants.AzureDevOpsScopes)
.WithUsername(upn)
.ExecuteAsync(cancellationToken);

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

@ -35,7 +35,7 @@ public class MsalInteractiveTokenProvider : ITokenProvider
try
{
var result = await app.AcquireTokenInteractive(Constants.AzureDevOpsScopes)
var result = await app.AcquireTokenInteractive(MsalConstants.AzureDevOpsScopes)
.WithPrompt(Prompt.SelectAccount)
.WithUseEmbeddedWebView(false)
.ExecuteAsync(cts.Token);

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

@ -61,7 +61,7 @@ public class MsalSilentTokenProvider : ITokenProvider
{
this.logger.LogTrace(Resources.MsalAccountAttempt, canonicalName);
var result = await app.AcquireTokenSilent(Constants.AzureDevOpsScopes, account)
var result = await app.AcquireTokenSilent(MsalConstants.AzureDevOpsScopes, account)
.WithAccountTenantId(account)
.ExecuteAsync(cancellationToken);

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

@ -0,0 +1,82 @@
// Copyright (c) Microsoft. All rights reserved.
//
// Licensed under the MIT license.
using System.Reflection;
using System.Runtime.InteropServices;
namespace Microsoft.Artifacts.Authentication;
public static class PlatformInformation
{
private static string? programName = null;
private static string? programVersion = null;
private static string? runtimeIdentifier = null;
private static AssemblyName CurrentAssemblyName => typeof(PlatformInformation).Assembly.GetName();
public static string GetProgramName()
{
return programName ??= Assembly
.GetEntryAssembly()?
.GetCustomAttribute<AssemblyTitleAttribute>()?.Title ?? CurrentAssemblyName.Name;
}
public static string GetProgramVersion()
{
return programVersion ??= Assembly
.GetEntryAssembly()?
.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion ?? CurrentAssemblyName.Version.ToString();
}
public static string GetOSType()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return nameof(OSPlatform.Windows);
}
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
return nameof(OSPlatform.Linux);
}
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
return nameof(OSPlatform.OSX);
}
return "Unknown";
}
public static string GetCpuArchitecture()
{
return RuntimeInformation.OSArchitecture.ToString();
}
public static string GetOsDescription()
{
return RuntimeInformation.OSDescription;
}
public static string GetClrVersion()
{
return Environment.Version.ToString();
}
public static string GetClrFramework()
{
return AppContext.TargetFrameworkName;
}
public static string GetClrRuntime()
{
// RuntimeInformation.RuntimeIdentifier not available on .NET Standard
return runtimeIdentifier ??= AppContext.GetData("RUNTIME_IDENTIFIER") as string ?? "win-x64";
}
public static string GetClrDescription()
{
return RuntimeInformation.FrameworkDescription;
}
}